#include "asset.h"

#include <SDL3/SDL.h>  // Para SDL_LogInfo, SDL_LogCategory, SDL_LogError, SDL_LogWarn

#include <algorithm>   // Para max
#include <fstream>     // Para basic_ifstream, ifstream
#include <functional>  // Para identity
#include <ranges>      // Para __find_if_fn, find_if
#include <string>      // Para allocator, string, operator==, operator+, char_traits, basic_string

#include "utils.h"  // Para getFileName

// Singleton
Asset *Asset::instance = nullptr;

// Inicializa la instancia única del singleton
void Asset::init(const std::string &executable_path) { Asset::instance = new Asset(executable_path); }

// Libera la instancia
void Asset::destroy() { delete Asset::instance; }

// Obtiene la instancia
auto Asset::get() -> Asset * { return Asset::instance; }

// Añade un elemento a la lista
void Asset::add(const std::string &file, AssetType type, bool required, bool absolute) {
    file_list_.emplace_back(absolute ? file : executable_path_ + file, type, required);
    longest_name_ = std::max(longest_name_, static_cast<int>(file_list_.back().file.size()));
}

// Devuelve la ruta completa a un fichero a partir de una cadena
auto Asset::get(const std::string &text) const -> std::string {
    auto iterator = std::ranges::find_if(file_list_, [&text](const auto &file) {
        return getFileName(file.file) == text;
    });

    if (iterator != file_list_.end()) {
        return iterator->file;
    }
    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Warning: file %s not found", text.c_str());
    return "";
}

// Comprueba que existen todos los elementos
auto Asset::check() const -> bool {
    bool success = true;

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n** CHECKING FILES");

    // Comprueba la lista de ficheros clasificándolos por tipo
    for (int type = 0; type < static_cast<int>(AssetType::COUNT); ++type) {
        // Comprueba si hay ficheros de ese tipo
        bool any = false;

        for (const auto &file : file_list_) {
            if (file.required && file.type == static_cast<AssetType>(type)) {
                any = true;
            }
        }

        // Si hay ficheros de ese tipo, comprueba si existen
        if (any) {
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> %s FILES", getTypeName(static_cast<AssetType>(type)).c_str());

            for (const auto &file : file_list_) {
                if (file.required && file.type == static_cast<AssetType>(type)) {
                    success &= checkFile(file.file);
                }
            }
            if (success) {
                SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "   All files are OK.");
            }
        }
    }

    // Resultado
    if (success) {
        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n** CHECKING FILES COMPLETED.\n");
    } else {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "\n** CHECKING FILES FAILED.\n");
    }

    return success;
}

// Comprueba que existe un fichero
auto Asset::checkFile(const std::string &path) -> bool {
    std::ifstream file(path);
    bool success = file.good();
    file.close();

    if (!success) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Checking file: %s [ ERROR ]", getFileName(path).c_str());
    }

    return success;
}

// Devuelve el nombre del tipo de recurso
auto Asset::getTypeName(AssetType type) -> std::string {
    switch (type) {
        case AssetType::BITMAP:
            return "BITMAP";
        case AssetType::MUSIC:
            return "MUSIC";
        case AssetType::SOUND:
            return "SOUND";
        case AssetType::FONT:
            return "FONT";
        case AssetType::LANG:
            return "LANG";
        case AssetType::DATA:
            return "DATA";
        case AssetType::DEMODATA:
            return "DEMODATA";
        case AssetType::ANIMATION:
            return "ANIMATION";
        case AssetType::PALETTE:
            return "PALETTE";
        default:
            return "ERROR";
    }
}

// Devuelve la lista de recursos de un tipo
auto Asset::getListByType(AssetType type) const -> std::vector<std::string> {
    std::vector<std::string> list;

    for (auto file : file_list_) {
        if (file.type == type) {
            list.push_back(file.file);
        }
    }

    return list;
}