#pragma once

#include <memory>  // Para shared_ptr
#include <string>  // Para string
#include <utility>
#include <vector>  // Para vector

#include "core/rendering/surface.hpp"                  // Para Surface
#include "core/rendering/surface_animated_sprite.hpp"  // Para AnimationsFileBuffer
#include "core/rendering/text.hpp"                     // Para Text, TextFile
#include "game/gameplay/room.hpp"                      // Para room_t
struct JA_Music_t;                                     // lines 11-11
struct JA_Sound_t;                                     // lines 12-12

// Estructura para almacenar ficheros de sonido y su nombre
struct ResourceSound {
        std::string name;   // Nombre del sonido
        JA_Sound_t* sound;  // Objeto con el sonido

        // Constructor
        ResourceSound(std::string name, JA_Sound_t* sound)
            : name(std::move(name)),
              sound(sound) {}
};

// Estructura para almacenar ficheros musicales y su nombre
struct ResourceMusic {
        std::string name;   // Nombre de la musica
        JA_Music_t* music;  // Objeto con la música

        // Constructor
        ResourceMusic(std::string name, JA_Music_t* music)
            : name(std::move(name)),
              music(music) {}
};

// Estructura para almacenar objetos Surface y su nombre
struct ResourceSurface {
        std::string name;                  // Nombre de la surface
        std::shared_ptr<Surface> surface;  // Objeto con la surface

        // Constructor
        ResourceSurface(std::string name, std::shared_ptr<Surface> surface)
            : name(std::move(name)),
              surface(std::move(std::move(surface))) {}
};

// Estructura para almacenar objetos Palette y su nombre
struct ResourcePalette {
        std::string name;  // Nombre de la surface
        Palette palette;   // Paleta

        // Constructor
        ResourcePalette(std::string name, const Palette& palette)
            : name(std::move(name)),
              palette(palette) {}
};

// Estructura para almacenar ficheros TextFile y su nombre
struct ResourceTextFile {
        std::string name;                      // Nombre del fichero
        std::shared_ptr<Text::File> text_file;  // Objeto con los descriptores de la fuente de texto

        // Constructor
        ResourceTextFile(std::string name, std::shared_ptr<Text::File> text_file)
            : name(std::move(name)),
              text_file(std::move(std::move(text_file))) {}
};

// Estructura para almacenar objetos Text y su nombre
struct ResourceText {
        std::string name;            // Nombre del objeto
        std::shared_ptr<Text> text;  // Objeto

        // Constructor
        ResourceText(std::string name, std::shared_ptr<Text> text)
            : name(std::move(name)),
              text(std::move(std::move(text))) {}
};

// Estructura para almacenar ficheros animaciones y su nombre
struct ResourceAnimation {
        std::string name;                 // Nombre del fichero
        std::vector<uint8_t> yaml_data;   // Bytes del archivo YAML sin parsear

        // Constructor
        ResourceAnimation(std::string name, std::vector<uint8_t> yaml_data)
            : name(std::move(name)),
              yaml_data(std::move(yaml_data)) {}
};

// Estructura para almacenar habitaciones y su nombre
struct ResourceRoom {
        std::string name;                  // Nombre de la habitación
        std::shared_ptr<Room::Data> room;  // Habitación

        // Constructor
        ResourceRoom(std::string name, std::shared_ptr<Room::Data> room)
            : name(std::move(name)),
              room(std::move(std::move(room))) {}
};

// Estructura para llevar la cuenta de los recursos cargados
struct ResourceCount {
        int total;   // Número total de recursos
        int loaded;  // Número de recursos cargados

        // Constructor
        ResourceCount()
            : total(0),
              loaded(0) {}

        // Constructor
        ResourceCount(int total, int loaded)
            : total(total),
              loaded(loaded) {}

        // Añade una cantidad a los recursos cargados
        void add(int amount) {
            loaded += amount;
        }

        // Obtiene el porcentaje de recursos cargados
        [[nodiscard]] auto getPercentage() const -> float {
            return static_cast<float>(loaded) / static_cast<float>(total);
        }
};

namespace Resource {

class Cache {
    private:
        // [SINGLETON] Objeto cache privado para Don Melitón
        static Cache* cache;

        std::vector<ResourceSound> sounds_;          // Vector con los sonidos
        std::vector<ResourceMusic> musics_;          // Vector con las musicas
        std::vector<ResourceSurface> surfaces_;      // Vector con las surfaces
        std::vector<ResourcePalette> palettes_;      // Vector con las paletas
        std::vector<ResourceTextFile> text_files_;   // Vector con los ficheros de texto
        std::vector<ResourceText> texts_;            // Vector con los objetos de texto
        std::vector<ResourceAnimation> animations_;  // Vector con las animaciones
        std::vector<ResourceRoom> rooms_;            // Vector con las habitaciones

        ResourceCount count_;                 // Contador de recursos
        std::shared_ptr<Text> loading_text_;  // Texto para la pantalla de carga

        // Carga los sonidos
        void loadSounds();

        // Carga las musicas
        void loadMusics();

        // Carga las surfaces
        void loadSurfaces();

        // Carga las paletas
        void loadPalettes();

        // Carga los ficheros de texto
        void loadTextFiles();

        // Carga las animaciones
        void loadAnimations();

        // Carga las habitaciones
        void loadRooms();

        // Crea los objetos de texto
        void createText();

        // Vacia todos los vectores de recursos
        void clear();

        // Carga todos los recursos
        void load();

        // Vacía el vector de sonidos
        void clearSounds();

        // Vacía el vector de musicas
        void clearMusics();

        // Calcula el numero de recursos para cargar
        void calculateTotal();

        // Muestra el progreso de carga
        void renderProgress();

        // Comprueba los eventosstatic
        static void checkEvents();

        // Actualiza el progreso de carga
        void updateLoadingProgress(int steps = 5);

        // [SINGLETON] Ahora el constructor y el destructor son privados, para no poder crear objetos cache desde fuera

        // Constructor
        Cache();

        // Destructor
        ~Cache() = default;

    public:
        // [SINGLETON] Crearemos el objeto cache con esta función estática
        static void init();

        // [SINGLETON] Destruiremos el objeto cache con esta función estática
        static void destroy();

        // [SINGLETON] Con este método obtenemos el objeto cache y podemos trabajar con él
        static auto get() -> Cache*;

        // Obtiene el sonido a partir de un nombre
        auto getSound(const std::string& name) -> JA_Sound_t*;

        // Obtiene la música a partir de un nombre
        auto getMusic(const std::string& name) -> JA_Music_t*;

        // Obtiene la surface a partir de un nombre
        auto getSurface(const std::string& name) -> std::shared_ptr<Surface>;

        // Obtiene la paleta a partir de un nombre
        auto getPalette(const std::string& name) -> Palette;

        // Obtiene el fichero de texto a partir de un nombre
        auto getTextFile(const std::string& name) -> std::shared_ptr<Text::File>;

        // Obtiene el objeto de texto a partir de un nombre
        auto getText(const std::string& name) -> std::shared_ptr<Text>;

        // Obtiene los bytes YAML de animación a partir de un nombre (parsing lazy)
        auto getAnimationData(const std::string& name) -> const ResourceAnimation&;

        // Obtiene la habitación a partir de un nombre
        auto getRoom(const std::string& name) -> std::shared_ptr<Room::Data>;

        // Obtiene todas las habitaciones
        auto getRooms() -> std::vector<ResourceRoom>&;

        // Recarga todos los recursos
        void reload();
};

}  // namespace Resource
