#include "core/resources/asset.h"

#include <SDL3/SDL.h>

#include <cstddef>   // for size_t
#include <iostream>  // for basic_ostream, operator<<, cout, endl

#include "core/resources/resource_helper.h"

// Instancia única
Asset *Asset::instance = nullptr;

// Singleton API
void Asset::init(const std::string &executablePath) {
    Asset::instance = new Asset(executablePath);
}

void Asset::destroy() {
    delete Asset::instance;
    Asset::instance = nullptr;
}

auto Asset::get() -> Asset * {
    return Asset::instance;
}

// Constructor
Asset::Asset(const std::string &executablePath)
    : executable_path_(executablePath.substr(0, executablePath.find_last_of("\\/"))) {
}

// Añade un elemento a la lista
void Asset::add(const std::string &file, Type type, bool required, bool absolute) {
    Item temp;
    temp.file = absolute ? file : executable_path_ + file;
    temp.type = type;
    temp.required = required;
    file_list_.push_back(temp);

    const std::string filename = file.substr(file.find_last_of("\\/") + 1);
    longest_name_ = SDL_max(longest_name_, filename.size());
}

// Devuelve el fichero de un elemento de la lista a partir de una cadena
auto Asset::get(const std::string &text) -> std::string {
    for (const auto &f : file_list_) {
        const size_t lastIndex = f.file.find_last_of('/') + 1;
        const std::string file = f.file.substr(lastIndex);

        if (file == text) {
            return f.file;
        }
    }

    if (verbose_) {
        std::cout << "Warning: file " << text.c_str() << " not found" << '\n';
    }
    return "";
}

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

    if (verbose_) {
        std::cout << "\n** Checking files" << '\n';

        std::cout << "Executable path is:  " << executable_path_ << '\n';
        std::cout << "Sample filepath:  " << file_list_.back().file << '\n';
    }

    // Comprueba la lista de ficheros clasificandolos por tipo
    for (int i = 0; i < static_cast<int>(Type::COUNT); ++i) {
        const Type TYPE = static_cast<Type>(i);

        // Comprueba si hay ficheros de ese tipo
        bool any = false;

        for (const auto &f : file_list_) {
            if (f.required && f.type == TYPE) {
                any = true;
            }
        }

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

            for (const auto &f : file_list_) {
                if (f.required && f.type == TYPE) {
                    success &= checkFile(f.file);
                }
            }
        }
    }

    // Resultado
    if (verbose_) {
        if (success) {
            std::cout << "\n** All files OK.\n"
                      << '\n';
        } else {
            std::cout << "\n** A file is missing. Exiting.\n"
                      << '\n';
        }
    }

    return success;
}

// Comprueba que existe un fichero
auto Asset::checkFile(const std::string &path) const -> bool {
    bool success = false;
    std::string result = "ERROR";

    // Comprueba si existe el fichero (pack o filesystem)
    const std::string filename = path.substr(path.find_last_of("\\/") + 1);
    if (ResourceHelper::shouldUseResourcePack(path)) {
        auto bytes = ResourceHelper::loadFile(path);
        if (!bytes.empty()) {
            result = "OK";
            success = true;
        }
    } else {
        SDL_IOStream *file = SDL_IOFromFile(path.c_str(), "rb");
        if (file != nullptr) {
            result = "OK";
            success = true;
            SDL_CloseIO(file);
        }
    }

    if (verbose_) {
        std::cout.setf(std::ios::left, std::ios::adjustfield);
        std::cout << "Checking file: ";
        std::cout.width(longest_name_ + 2);
        std::cout.fill('.');
        std::cout << filename + " ";
        std::cout << " [" + result + "]" << '\n';
    }

    return success;
}

// Devuelve el nombre del tipo de recurso
auto Asset::getTypeName(Type type) -> std::string {
    switch (type) {
        case Type::BITMAP:
            return "BITMAP";
        case Type::MUSIC:
            return "MUSIC";
        case Type::SOUND:
            return "SOUND";
        case Type::FONT:
            return "FONT";
        case Type::LANG:
            return "LANG";
        case Type::DATA:
            return "DATA";
        case Type::ROOM:
            return "ROOM";
        case Type::ENEMY:
            return "ENEMY";
        case Type::ITEM:
            return "ITEM";
        case Type::COUNT:
        default:
            return "ERROR";
    }
}

// Establece si ha de mostrar texto por pantalla
void Asset::setVerbose(bool value) {
    verbose_ = value;
}