#include "resource.h"
#include <algorithm>    // Para find_if
#include <iostream>     // Para basic_ostream, operator<<, endl, cout, cerr
#include <stdexcept>    // Para runtime_error
#include "asset.h"      // Para Asset, AssetType
#include "lang.h"       // Para lang
#include "jail_audio.h" // Para JA_LoadMusic, JA_LoadSound
#include "screen.h"     // Para Screen
#include "text.h"       // Para Text
struct JA_Music_t;
struct JA_Sound_t;

// [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()
{
    std::cout << "** LOADING RESOURCES" << std::endl;
    loadSounds();
    loadMusics();
    loadTextures();
    loadTextFiles();
    loadAnimations();
    loadDemoData();
    addPalettes();
    createTextures();
    std::cout << "\n** RESOURCES LOADED" << std::endl;
}

// 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;
    }

    std::cerr << "Error: Sonido no encontrado " << name << std::endl;
    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;
    }

    std::cerr << "Error: Música no encontrada " << name << std::endl;
    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;
    }

    std::cerr << "Error: Imagen no encontrada " << name << std::endl;
    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;
    }

    std::cerr << "Error: TextFile no encontrado " << name << std::endl;
    throw std::runtime_error("TextFile 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;
    }

    std::cerr << "Error: Animación no encontrada " << name << std::endl;
    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()
{
    std::cout << "\n>> SOUND FILES" << std::endl;
    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 musicas
void Resource::loadMusics()
{
    std::cout << "\n>> MUSIC FILES" << std::endl;
    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()
{
    std::cout << "\n>> TEXTURES" << std::endl;
    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()
{
    std::cout << "\n>> TEXT FILES" << std::endl;
    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()
{
    std::cout << "\n>> ANIMATIONS" << std::endl;
    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()
{
    std::cout << "\n>> DEMO_FILES" << std::endl;
    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()
{
    // Jugador 1
    std::cout << "\n>> PALETTES" << std::endl;
    getTexture("player1.gif")->addPaletteFromFile(Asset::get()->get("player1_one_coffee_palette.pal"));
    getTexture("player1.gif")->addPaletteFromFile(Asset::get()->get("player1_two_coffee_palette.pal"));
    getTexture("player1.gif")->addPaletteFromFile(Asset::get()->get("player1_all_white_palette.pal"));

    // Jugador 2
    getTexture("player2.gif")->addPaletteFromFile(Asset::get()->get("player2_one_coffee_palette.pal"));
    getTexture("player2.gif")->addPaletteFromFile(Asset::get()->get("player2_two_coffee_palette.pal"));
    getTexture("player2.gif")->addPaletteFromFile(Asset::get()->get("player2_all_white_palette.pal"));

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

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

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

    std::cout << "\n>> CREATING TEXTURES" << std::endl;
    auto text = std::make_unique<Text>(getTexture("04b_25.png"), getTextFile("04b_25.txt"));

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

    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 = {
        NameAndText("get_ready", lang::getText(75)),
        NameAndText("last_stage", lang::getText(79)),
        NameAndText("congratulations", lang::getText(50)),
        NameAndText("game_over", "Game Over")};

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