#include "resource.h"
#include <SDL3/SDL_log.h> // Para SDL_LogInfo, SDL_LogCategory, SDL_LogError
#include <algorithm>      // Para find_if
#include <stdexcept>      // Para runtime_error
#include "asset.h"        // Para Asset, AssetType
#include "jail_audio.h"   // Para JA_DeleteMusic, JA_DeleteSound, JA_LoadMusic
#include "lang.h"         // Para getText
#include "screen.h"       // Para Screen
#include "text.h"         // Para Text, loadTextFile
struct JA_Music_t;        // lines 11-11
struct JA_Sound_t;        // lines 12-12

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

// [SINGLETON] Crearemos el objeto screen con esta función estática
void Resource::init()
{
    Resource::resource_ = new Resource();
}

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

// [SINGLETON] Con este método obtenemos el objeto screen y podemos trabajar con él
Resource *Resource::get()
{
    return Resource::resource_;
}

// Constructor
Resource::Resource()
{
    load();
}

// Vacia todos los vectores de recursos
void Resource::clear()
{
    clearSounds();
    clearMusics();
    textures_.clear();
    text_files_.clear();
    texts_.clear();
    animations_.clear();
    demos_.clear();
}

// Carga todos los recursos
void Resource::load()
{
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n** LOADING RESOURCES");
    loadSounds();
    loadMusics();
    loadTextures();
    loadTextFiles();
    loadAnimations();
    loadDemoData();
    addPalettes();
    createText();
    createTextures();
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n** RESOURCES LOADED");
}

// Recarga todos los recursos
void Resource::reload()
{
    clear();
    load();
}

// Recarga las texturas
void Resource::reloadTextures()
{
    loadTextures();
    addPalettes();
    createTextures();
}

// Obtiene el sonido a partir de un nombre
JA_Sound_t *Resource::getSound(const std::string &name)
{
    auto it = std::find_if(sounds_.begin(), sounds_.end(), [&name](const auto &s)
                           { return s.name == name; });

    if (it != sounds_.end())
    {
        return it->sound;
    }

    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Sonido no encontrado %s", name.c_str());
    throw std::runtime_error("Sonido no encontrado: " + name);
}

// Obtiene la música a partir de un nombre
JA_Music_t *Resource::getMusic(const std::string &name)
{
    auto it = std::find_if(musics_.begin(), musics_.end(), [&name](const auto &m)
                           { return m.name == name; });

    if (it != musics_.end())
    {
        return it->music;
    }

    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Música no encontrada %s", name.c_str());
    throw std::runtime_error("Música no encontrada: " + name);
}

// Obtiene la textura a partir de un nombre
std::shared_ptr<Texture> Resource::getTexture(const std::string &name)
{
    auto it = std::find_if(textures_.begin(), textures_.end(), [&name](const auto &t)
                           { return t.name == name; });

    if (it != textures_.end())
    {
        return it->texture;
    }

    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Imagen no encontrada %s", name.c_str());
    throw std::runtime_error("Imagen no encontrada: " + name);
}

// Obtiene el fichero de texto a partir de un nombre
std::shared_ptr<TextFile> Resource::getTextFile(const std::string &name)
{
    auto it = std::find_if(text_files_.begin(), text_files_.end(), [&name](const auto &t)
                           { return t.name == name; });

    if (it != text_files_.end())
    {
        return it->text_file;
    }

    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: TextFile no encontrado %s", name.c_str());
    throw std::runtime_error("TextFile no encontrado: " + name);
}

// Obtiene el objeto de texto a partir de un nombre
std::shared_ptr<Text> Resource::getText(const std::string &name)
{
    auto it = std::find_if(texts_.begin(), texts_.end(), [&name](const auto &t)
                           { return t.name == name; });

    if (it != texts_.end())
    {
        return it->text;
    }

    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Text no encontrado %s", name.c_str());
    throw std::runtime_error("Text no encontrado: " + name);
}

// Obtiene la animación a partir de un nombre
AnimationsFileBuffer &Resource::getAnimation(const std::string &name)
{
    auto it = std::find_if(animations_.begin(), animations_.end(), [&name](const auto &a)
                           { return a.name == name; });

    if (it != animations_.end())
    {
        return it->animation;
    }

    SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: Animación no encontrada %s", name.c_str());
    throw std::runtime_error("Animación no encontrada: " + name);
}

// Obtiene el fichero con los datos para el modo demostración a partir de un çindice
DemoData &Resource::getDemoData(int index)
{
    return demos_.at(index);
}

// Carga los sonidos
void Resource::loadSounds()
{
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> SOUND FILES");
    auto list = Asset::get().getListByType(AssetType::SOUND);
    sounds_.clear();

    for (const auto &l : list)
    {
        auto name = getFileName(l);
        sounds_.emplace_back(ResourceSound(name, JA_LoadSound(l.c_str())));
        printWithDots("Sound : ", name, "[ LOADED ]");
    }
}

// Carga las músicas
void Resource::loadMusics()
{
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> MUSIC FILES");
    auto list = Asset::get().getListByType(AssetType::MUSIC);
    musics_.clear();

    for (const auto &l : list)
    {
        auto name = getFileName(l);
        musics_.emplace_back(ResourceMusic(name, JA_LoadMusic(l.c_str())));
        printWithDots("Music : ", name, "[ LOADED ]");
    }
}

// Carga las texturas
void Resource::loadTextures()
{
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> TEXTURES");
    auto list = Asset::get().getListByType(AssetType::BITMAP);
    textures_.clear();

    for (const auto &l : list)
    {
        auto name = getFileName(l);
        textures_.emplace_back(ResourceTexture(name, std::make_shared<Texture>(Screen::get()->getRenderer(), l)));
    }
}

// Carga los ficheros de texto
void Resource::loadTextFiles()
{
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> TEXT FILES");
    auto list = Asset::get().getListByType(AssetType::FONT);
    text_files_.clear();

    for (const auto &l : list)
    {
        auto name = getFileName(l);
        text_files_.emplace_back(ResourceTextFile(name, loadTextFile(l)));
    }
}

// Carga las animaciones
void Resource::loadAnimations()
{
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> ANIMATIONS");
    auto list = Asset::get().getListByType(AssetType::ANIMATION);
    animations_.clear();

    for (const auto &l : list)
    {
        auto name = getFileName(l);
        animations_.emplace_back(ResourceAnimation(name, loadAnimationsFromFile(l)));
    }
}

// Carga los datos para el modo demostración
void Resource::loadDemoData()
{
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> DEMO FILES");
    demos_.emplace_back(loadDemoDataFromFile(Asset::get().get("demo1.bin")));
    demos_.emplace_back(loadDemoDataFromFile(Asset::get().get("demo2.bin")));
}

// Añade paletas a las texturas
void Resource::addPalettes()
{
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> PALETTES");

    // Jugador 1
    getTexture("player1.gif")->addPaletteFromFile(Asset::get().get("player1_1_coffee_palette.gif"));
    getTexture("player1.gif")->addPaletteFromFile(Asset::get().get("player1_2_coffee_palette.gif"));
    getTexture("player1.gif")->addPaletteFromFile(Asset::get().get("player1_invencible_palette.gif"));

    // Jugador 2
    getTexture("player2.gif")->addPaletteFromFile(Asset::get().get("player2_1_coffee_palette.gif"));
    getTexture("player2.gif")->addPaletteFromFile(Asset::get().get("player2_2_coffee_palette.gif"));
    getTexture("player2.gif")->addPaletteFromFile(Asset::get().get("player2_invencible_palette.gif"));

    // Fuentes
    getTexture("smb2.gif")->addPaletteFromFile(Asset::get().get("smb2_palette1.pal"));
}

// Crea texturas
void Resource::createTextures()
{
    struct NameAndText
    {
        std::string name;
        std::string text;

        NameAndText(const std::string &name_init, const std::string &text_init)
            : name(name_init), text(text_init) {}
    };

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> CREATING TEXTURES");

    // Tamaño normal
    std::vector<NameAndText> strings = {
        {"game_text_1000_points", "1.000"},
        {"game_text_2500_points", "2.500"},
        {"game_text_5000_points", "5.000"},
        {"game_text_powerup", lang::getText(117)},
        {"game_text_one_hit", lang::getText(118)},
        {"game_text_stop", lang::getText(119)},
        {"game_text_1000000_points", lang::getText(76)}};

    auto text = getText("04b_25");
    for (const auto &s : strings)
    {
        textures_.emplace_back(ResourceTexture(s.name, text->writeToTexture(s.text, 1, -2)));
        printWithDots("Texture : ", s.name, "[  DONE  ]");
    }

    // Tamaño doble
    std::vector<NameAndText> strings2X = {
        {"game_text_100000_points", "100.000"},
        {"game_text_get_ready", lang::getText(75)},
        {"game_text_last_stage", lang::getText(79)},
        {"game_text_congratulations", lang::getText(50)},
        {"game_text_game_over", "Game Over"}};

    auto text2 = getText("04b_25_2x");
    for (const auto &s : strings2X)
    {
        textures_.emplace_back(ResourceTexture(s.name, text2->writeToTexture(s.text, 1, -4)));
        printWithDots("Texture : ", s.name, "[  DONE  ]");
    }
}

void Resource::createText()
{
    struct ResourceInfo
    {
        std::string key;
        std::string textureFile;
        std::string textFile;

        ResourceInfo(const std::string &k, const std::string &tFile, const std::string &txtFile)
            : key(k), textureFile(tFile), textFile(txtFile) {}
    };

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\n>> CREATING TEXT OBJECTS");

    std::vector<ResourceInfo> resources = {
        {"04b_25", "04b_25.png", "04b_25.txt"},
        {"04b_25_2x", "04b_25_2x.png", "04b_25_2x.txt"},
        {"04b_25_metal", "04b_25_metal.png", "04b_25.txt"},
        {"04b_25_grey", "04b_25_grey.png", "04b_25.txt"},
        {"8bithud", "8bithud.png", "8bithud.txt"},
        {"smb2", "smb2.gif", "smb2.txt"}};

    for (const auto &resource : resources)
    {
        texts_.emplace_back(ResourceText(resource.key, std::make_shared<Text>(
                                                           getTexture(resource.textureFile),
                                                           getTextFile(resource.textFile))));
        printWithDots("Text : ", resource.key, "[  DONE  ]");
    }
}

// Vacía el vector de sonidos
void Resource::clearSounds()
{
    for (auto &sound : sounds_)
    {
        if (sound.sound)
        {
            JA_DeleteSound(sound.sound);
            sound.sound = nullptr;
        }
    }
    sounds_.clear();
}

// Vacía el vector de músicas
void Resource::clearMusics()
{
    for (auto &music : musics_)
    {
        if (music.music)
        {
            JA_DeleteMusic(music.music);
            music.music = nullptr;
        }
    }
    musics_.clear();
}