#include "options.h"

#include <SDL3/SDL.h>  // Para SDL_ScaleMode, SDL_GamepadButton, SDL_LogCategory, SDL_LogInfo, SDL_LogError, SDL_LogWarn
#include <stddef.h>    // Para size_t

#include <algorithm>   // Para clamp, max
#include <fstream>     // Para basic_ostream, operator<<, basic_ostream::operator<<, basic_ofstream, basic_istream, basic_ifstream, ifstream, ofstream
#include <functional>  // Para function
#include <map>         // Para map, operator==, _Rb_tree_const_iterator
#include <stdexcept>   // Para invalid_argument, out_of_range
#include <string>      // Para char_traits, stoi, operator==, operator<<, allocator, string, basic_string, operator<=>, getline
#include <utility>     // Para swap, pair
#include <vector>      // Para vector

#include "difficulty.h"  // Para Code, init
#include "input.h"       // Para InputDevice
#include "lang.h"        // Para Code
#include "utils.h"       // Para boolToString, stringToBool, getFileName

namespace Options {
// --- Variables globales ---
WindowOptions window;                     // Opciones de la ventana
SettingsOptions settings;                 // Opciones del juego
VideoOptions video;                       // Opciones de vídeo
AudioOptions audio;                       // Opciones de audio
std::vector<GamepadOptions> controllers;  // Opciones de mando para cada jugador
PendingChanges pending_changes;           // Opciones que se aplican al cerrar

// Declaraciones
auto set(const std::string& var, const std::string& value) -> bool;

// Establece el fichero de configuración
void setFile(const std::string& file_path) { settings.config_file = file_path; };

// Inicializa las opciones del programa
void init() {
    // Dificultades
    Difficulty::init();

    // Opciones de control
    controllers.clear();
    controllers.resize(2);
    controllers.at(0).player_id = 1;
    controllers.at(1).player_id = 2;
    setKeyboardToPlayer(1);

    // Opciones pendientes
    pending_changes.new_language = settings.language;
    pending_changes.new_difficulty = settings.difficulty;
    pending_changes.has_pending_changes = false;
}

// Carga el fichero de configuración
auto loadFromFile() -> bool {
    // Inicializa las opciones del programa
    init();

    // Indicador de éxito en la carga
    bool success = true;

    // Variables para manejar el fichero
    std::ifstream file(settings.config_file);

    // Si el fichero se puede abrir
    if (file.good()) {
        // Procesa el fichero línea a línea
        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\nReading file: %s", getFileName(settings.config_file).c_str());
        std::string line;
        while (std::getline(file, line)) {
            // Comprueba que la línea no sea un comentario
            if (line.substr(0, 1) != "#") {
                // Encuentra la posición del carácter '='
                int pos = line.find("=");
                // Procesa las dos subcadenas
                if (!set(line.substr(0, pos), line.substr(pos + 1, line.length()))) {
                    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Unknown parameter: %s", line.substr(0, pos).c_str());
                    success = false;
                }
            }
        }
        file.close();
    }
    // El fichero no existe
    else {
        saveToFile();  // Crea el fichero con los valores por defecto
    }

    // Normaliza los valores
    if (settings.language != Lang::Code::ENGLISH &&
        settings.language != Lang::Code::VALENCIAN &&
        settings.language != Lang::Code::SPANISH) {
        settings.language = Lang::Code::ENGLISH;
    }

    return success;
}

// Guarda el fichero de configuración
auto saveToFile() -> bool {
    std::ofstream file(settings.config_file);

    if (!file.good()) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Error: %s can't be opened", getFileName(settings.config_file).c_str());
        return false;
    }

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Writing file: %s", getFileName(settings.config_file).c_str());

    applyPendingChanges();

    // Opciones de ventana
    file << "## WINDOW\n";
    file << "\n";

    file << "window.zoom=" << window.zoom << "\n";

    // Opciones de video
    file << "## VIDEO\n";
    file << "## video.scale_mode [" << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_NEAREST) << ": nearest, " << static_cast<int>(SDL_ScaleMode::SDL_SCALEMODE_LINEAR) << ": lineal]\n";
    file << "\n";

    file << "video.fullscreen=" << boolToString(video.fullscreen) << "\n";
    file << "video.scale_mode=" << static_cast<int>(video.scale_mode) << "\n";
    file << "video.vsync=" << boolToString(video.vsync) << "\n";
    file << "video.integer_scale=" << boolToString(video.integer_scale) << "\n";
    file << "video.shaders=" << boolToString(video.shaders) << "\n";

    // Opciones de audio
    file << "\n\n## AUDIO\n";
    file << "## volume [0 .. 100]\n";
    file << "\n";

    file << "audio.enabled=" << boolToString(audio.enabled) << "\n";
    file << "audio.volume=" << audio.volume << "\n";
    file << "audio.music.enabled=" << boolToString(audio.music.enabled) << "\n";
    file << "audio.music.volume=" << audio.music.volume << "\n";
    file << "audio.sound.enabled=" << boolToString(audio.sound.enabled) << "\n";
    file << "audio.sound.volume=" << audio.sound.volume << "\n";

    // Opciones del juego
    file << "\n\n## GAME\n";
    file << "## game.language [0: spanish, 1: valencian, 2: english]\n";
    file << "## game.difficulty [" << static_cast<int>(Difficulty::Code::EASY) << ": easy, " << static_cast<int>(Difficulty::Code::NORMAL) << ": normal, " << static_cast<int>(Difficulty::Code::HARD) << ": hard]\n";
    file << "\n";

    file << "game.language=" << static_cast<int>(settings.language) << "\n";
    file << "game.difficulty=" << static_cast<int>(settings.difficulty) << "\n";
    file << "game.autofire=" << boolToString(settings.autofire) << "\n";
    file << "game.shutdown_enabled=" << boolToString(settings.shutdown_enabled) << "\n";

    // Opciones de mandos
    file << "\n\n## CONTROLLERS\n";

    int controller_index = 0;
    for (const auto& controller : controllers) {
        file << "\n";
        file << "controller." << controller_index << ".name=" << controller.name << "\n";
        file << "controller." << controller_index << ".player=" << controller.player_id << "\n";
        file << "controller." << controller_index << ".type=" << static_cast<int>(controller.type) << "\n";
        file << "controller." << controller_index << ".button.fire_left=" << controller.buttons.at(0) << "\n";
        file << "controller." << controller_index << ".button.fire_center=" << controller.buttons.at(1) << "\n";
        file << "controller." << controller_index << ".button.fire_right=" << controller.buttons.at(2) << "\n";
        file << "controller." << controller_index << ".button.start=" << controller.buttons.at(3) << "\n";
        file << "controller." << controller_index << ".button.service=" << controller.buttons.at(4) << "\n";

        // Incrementa el índice
        ++controller_index;
    }

    // Cierra el fichero
    file.close();

    return true;
}

// Función auxiliar para analizar la configuración del mando y reducir duplicación
void parseAndSetController(const std::string& var, const std::string& value) {
    // Lógica básica de análisis (puede hacerse más robusta)
    size_t first_dot = var.find('.');
    size_t second_dot = var.find('.', first_dot + 1);

    int controller_index = std::stoi(var.substr(first_dot + 1, second_dot - first_dot - 1));
    std::string setting_key = var.substr(second_dot + 1);

    auto& controller = controllers.at(controller_index);

    if (setting_key == "name") {
        controller.name = value;
    } else if (setting_key == "player") {
        controller.player_id = std::clamp(std::stoi(value), 1, 2);
    } else if (setting_key == "type") {
        controller.type = static_cast<InputDevice>(std::stoi(value));
    } else if (setting_key == "button.fire_left") {
        controller.buttons.at(0) = static_cast<SDL_GamepadButton>(std::stoi(value));
    } else if (setting_key == "button.fire_center") {
        controller.buttons.at(1) = static_cast<SDL_GamepadButton>(std::stoi(value));
    } else if (setting_key == "button.fire_right") {
        controller.buttons.at(2) = static_cast<SDL_GamepadButton>(std::stoi(value));
    } else if (setting_key == "button.start") {
        controller.buttons.at(3) = static_cast<SDL_GamepadButton>(std::stoi(value));
    } else if (setting_key == "button.service") {
        controller.buttons.at(4) = static_cast<SDL_GamepadButton>(std::stoi(value));
    }
}

auto set(const std::string& var, const std::string& value) -> bool {
    // Clausula de protección: ignora líneas vacías o comentarios
    if (var.empty() || var.starts_with("#")) {
        return true;
    }

    // Un mapa estático asegura que se inicializa solo una vez
    static const std::map<std::string, std::function<void(const std::string&)>> SETTINGS_MAP = {
        // Ventana
        {"window.zoom", [](const auto& val) { window.zoom = std::stoi(val); }},
        // Vídeo
        {"video.fullscreen", [](const auto& val) { video.fullscreen = stringToBool(val); }},
        {"video.scale_mode", [](const auto& val) { video.scale_mode = static_cast<SDL_ScaleMode>(std::stoi(val)); }},
        {"video.shaders", [](const auto& val) { video.shaders = stringToBool(val); }},
        {"video.integer_scale", [](const auto& val) { video.integer_scale = stringToBool(val); }},
        {"video.vsync", [](const auto& val) { video.vsync = stringToBool(val); }},
        // Audio
        {"audio.enabled", [](const auto& val) { audio.enabled = stringToBool(val); }},
        {"audio.volume", [](const auto& val) { audio.volume = std::clamp(std::stoi(val), 0, 100); }},
        {"audio.music.enabled", [](const auto& val) { audio.music.enabled = stringToBool(val); }},
        {"audio.music.volume", [](const auto& val) { audio.music.volume = std::clamp(std::stoi(val), 0, 100); }},
        {"audio.sound.enabled", [](const auto& val) { audio.sound.enabled = stringToBool(val); }},
        {"audio.sound.volume", [](const auto& val) { audio.sound.volume = std::clamp(std::stoi(val), 0, 100); }},
        // Juego
        {"game.language", [](const auto& val) {
             settings.language = static_cast<Lang::Code>(std::stoi(val));
             pending_changes.new_language = settings.language;
         }},
        {"game.difficulty", [](const auto& val) {
             settings.difficulty = static_cast<Difficulty::Code>(std::stoi(val));
             pending_changes.new_difficulty = settings.difficulty;
         }},
        {"game.autofire", [](const auto& val) { settings.autofire = stringToBool(val); }},
        {"game.shutdown_enabled", [](const auto& val) { settings.shutdown_enabled = stringToBool(val); }}};

    // Maneja por separado la configuración general de los mandos
    if (var.starts_with("controller.")) {
        try {
            parseAndSetController(var, value);
            return true;
        } catch (const std::out_of_range& e) {
            // Error: por ejemplo, índice de mando fuera de rango
            return false;
        } catch (const std::invalid_argument& e) {
            // Error: por ejemplo, fallo en std::stoi
            return false;
        }
    }

    // Busca el nombre de la variable en el mapa
    if (auto it = SETTINGS_MAP.find(var); it != SETTINGS_MAP.end()) {
        try {
            // Ejecuta la función lambda asociada
            it->second(value);
            return true;
        } catch (const std::invalid_argument& e) {
            // Maneja casos donde std::stoi falla por entrada inválida
            return false;
        }
    }

    // Si la clave no se encontró en el mapa ni en la lógica de mandos
    return false;
}

// Asigna el teclado al jugador
void setKeyboardToPlayer(int player_id) {
    for (auto& controller : controllers) {
        if (controller.player_id == player_id) {
            controller.type = InputDevice::ANY;
        } else {
            controller.type = InputDevice::CONTROLLER;
        }
    }
}

// Intercambia el teclado de jugador
void swapKeyboard() {
    std::swap(controllers.at(0).type, controllers.at(1).type);
}

// Intercambia los jugadores asignados a los dos primeros mandos
void swapControllers() {
    std::swap(controllers.at(0).player_id, controllers.at(1).player_id);
    std::swap(controllers.at(0).type, controllers.at(1).type);
}

// Averigua quien está usando el teclado
auto getPlayerWhoUsesKeyboard() -> int {
    for (const auto& controller : controllers) {
        if (controller.type == InputDevice::ANY) {
            return controller.player_id;
        }
    }
    return 0;
}

// Aplica los cambios pendientes copiando los valores a sus variables
void applyPendingChanges() {
    if (pending_changes.has_pending_changes) {
        settings.language = pending_changes.new_language;
        settings.difficulty = pending_changes.new_difficulty;
        pending_changes.has_pending_changes = false;
    }
}

void checkPendingChanges() {
    pending_changes.has_pending_changes = settings.language != pending_changes.new_language ||
        settings.difficulty != pending_changes.new_difficulty;
}
}  // namespace Options