#include "core/resources/asset.hpp"

#include <algorithm>  // Para find_if, max
#include <fstream>    // Para basic_ostream, operator<<, basic_ifstream, endl
#include <iostream>   // Para cout
#include <string>     // Para allocator, char_traits, string, operator+, oper...

#include "utils/utils.hpp"  // Para getFileName, printWithDots

// [SINGLETON] Hay que definir las variables estáticas, desde el .h sólo la hemos declarado
Asset* Asset::asset = nullptr;

// [SINGLETON] Crearemos el objeto asset con esta función estática
void Asset::init(const std::string& executable_path) {
    Asset::asset = new Asset(executable_path);
}

// [SINGLETON] Destruiremos el objeto asset con esta función estática
void Asset::destroy() {
    delete Asset::asset;
}

// [SINGLETON] Con este método obtenemos el objeto asset y podemos trabajar con él
auto Asset::get() -> Asset* {
    return Asset::asset;
}

// 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 it = std::ranges::find_if(file_list_, [&text](const auto& f) {
        return getFileName(f.file) == text;
    });

    if (it != file_list_.end()) {
        return it->file;
    }
    std::cout << "Warning: file " << text << " not found" << '\n';
    return "";
}

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

    std::cout << "\n** CHECKING FILES" << '\n';

    // std::cout << "Executable path is:  " << executable_path_ << std::endl;
    // std::cout << "Sample filepath:  " << file_list_.back().file << std::endl;

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

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

        // Si hay ficheros de ese tipo, comprueba si existen
        if (any) {
            std::cout << "\n>> " << getTypeName(static_cast<AssetType>(type)).c_str() << " FILES" << '\n';

            for (const auto& f : file_list_) {
                if (f.required && f.type == static_cast<AssetType>(type)) {
                    success &= checkFile(f.file);
                }
            }
            if (success) {
                std::cout << "   All files are OK." << '\n';
            }
        }
    }

    // Resultado
    std::cout << (success ? "\n** CHECKING FILES COMPLETED.\n" : "\n** CHECKING FILES FAILED.\n") << '\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) {
        printWithDots("Checking file : ", getFileName(path), "[ ERROR ]");
    }

    return success;
}

// Devuelve el nombre del tipo de recurso
auto Asset::getTypeName(AssetType type) -> std::string {
    switch (type) {
        case AssetType::DATA:
            return "DATA";
            break;

        case AssetType::BITMAP:
            return "BITMAP";
            break;

        case AssetType::ANIMATION:
            return "ANIMATION";
            break;

        case AssetType::MUSIC:
            return "MUSIC";
            break;

        case AssetType::SOUND:
            return "SOUND";
            break;

        case AssetType::FONT:
            return "FONT";
            break;

        case AssetType::ROOM:
            return "ROOM";
            break;

        case AssetType::TILEMAP:
            return "TILEMAP";
            break;

        case AssetType::PALETTE:
            return "PALETTE";
            break;

        default:
            return "ERROR";
            break;
    }
}

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

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

    return list;
}