#include "game/gameplay/cheevos.hpp"

#include <SDL3/SDL.h>

#include <algorithm>  // Para std::count_if
#include <cstddef>    // Para NULL
#include <fstream>    // Para basic_ostream, operator<<, basic_ofstream
#include <iostream>   // Para cout, cerr
#include <utility>

#include "core/locale/locale.hpp"  // Para Locale
#include "game/options.hpp"        // Para Options, options
#include "game/ui/notifier.hpp"    // Para Notifier

// [SINGLETON]
Cheevos* Cheevos::cheevos = nullptr;

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

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

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

// Constructor
Cheevos::Cheevos(std::string file)
    : file_(std::move(file)) {
    init();
    loadFromFile();
}

// Destructor
Cheevos::~Cheevos() {
    saveToFile();
}

// Inicializa los logros
void Cheevos::init() {
    cheevos_list_.clear();
}

// Busca un logro por id y devuelve el indice
auto Cheevos::find(int id) -> int {
    for (int i = 0; i < (int)cheevos_list_.size(); ++i) {
        if (cheevos_list_[i].id == id) {
            return i;
        }
    }

    return -1;
}

// Desbloquea un logro
void Cheevos::unlock(int id) {
    const int INDEX = find(id);

    // Si el índice es inválido, el logro no es válido, ya está completado o el sistema de logros no está habilitado, no hacemos nada
    if (INDEX == -1 || !cheevos_list_.at(INDEX).obtainable || cheevos_list_.at(INDEX).completed || !enabled_) {
        return;
    }

    // Marcar el logro como completado
    cheevos_list_.at(INDEX).completed = true;

    // Mostrar notificación en la pantalla
    Notifier::get()->show({Locale::get()->get("achievements.header"), cheevos_list_.at(INDEX).caption}, Notifier::Style::CHEEVO, -1, false);

    // Guardar el estado de los logros
    saveToFile();
}

// Invalida un logro
void Cheevos::setUnobtainable(int id) {
    const int INDEX = find(id);

    // Si el índice es válido, se invalida el logro
    if (INDEX != -1) {
        cheevos_list_.at(INDEX).obtainable = false;
    }
}

// Carga el estado de los logros desde un fichero
void Cheevos::loadFromFile() {
    std::ifstream file(file_, std::ios::binary);

    // El fichero no existe
    if (!file) {
        std::cout << "Warning: Unable to open " << file_ << "! Creating new file..." << '\n';

        // Crea el fichero en modo escritura (binario)
        std::ofstream new_file(file_, std::ios::binary);

        if (new_file) {
            std::cout << "New " << file_ << " created!" << '\n';

            // Guarda la información
            for (const auto& cheevo : cheevos_list_) {
                new_file.write(reinterpret_cast<const char*>(&cheevo.completed), sizeof(bool));
            }
        } else {
            std::cerr << "Error: Unable to create " << file_ << "!" << '\n';
        }
    }
    // El fichero existe
    else {
        std::cout << "Reading " << file_ << '\n';

        // Carga los datos
        for (auto& cheevo : cheevos_list_) {
            file.read(reinterpret_cast<char*>(&cheevo.completed), sizeof(bool));
        }
    }
}

// Guarda el estado de los logros en un fichero
void Cheevos::saveToFile() {
    // Abre el fichero en modo escritura (binario)
    SDL_IOStream* file = SDL_IOFromFile(this->file_.c_str(), "w+b");
    if (file != nullptr) {
        // Guarda la información
        for (auto& i : cheevos_list_) {
            SDL_WriteIO(file, &i.completed, sizeof(bool));
        }

        // Cierra el fichero
        SDL_CloseIO(file);
    } else {
        std::cout << "Error: Unable to save file! " << SDL_GetError() << '\n';
    }
}

// Devuelve el número total de logros desbloqueados
auto Cheevos::getTotalUnlockedAchievements() -> int {
    return std::count_if(cheevos_list_.begin(), cheevos_list_.end(), [](const auto& cheevo) -> bool { return cheevo.completed; });
}

// Elimina el estado "no obtenible"
void Cheevos::clearUnobtainableState() {
    for (auto& cheevo : cheevos_list_) {
        cheevo.obtainable = true;
    }
}