#include "game/options.hpp"

#include <SDL3/SDL.h>

#include <algorithm>      // Para find_if
#include <cctype>         // Para isspace
#include <fstream>        // Para basic_ostream, operator<<, basic_ofstream
#include <functional>     // Para function
#include <iostream>       // Para cout, cerr
#include <ranges>
#include <sstream>        // Para basic_istringstream
#include <string>         // Para char_traits, string, operator<<, hash
#include <unordered_map>  // Para unordered_map, operator==, _Node_const_i...
#include <utility>        // Para pair

#include "utils/utils.hpp"  // Para stringToBool, boolToString, safeStoi

namespace Options {
// Declaración de funciones internas
auto setOptions(const std::string& var, const std::string& value) -> bool;
auto trimLine(const std::string& line) -> std::string;
auto isCommentOrEmpty(const std::string& line) -> bool;
auto processConfigLine(const std::string& line) -> bool;
auto readConfigFile(const std::string& file_path) -> bool;

// Crea e inicializa las opciones del programa
void init() {
#ifdef _DEBUG
    console = true;
#else
    console = false;
#endif
}

// Elimina espacios en blanco al inicio y final de una línea
auto trimLine(const std::string& line) -> std::string {
    auto start = std::ranges::find_if(line, [](int ch) { return !std::isspace(ch); });
    auto end = std::ranges::find_if(std::ranges::reverse_view(line), [](int ch) { return !std::isspace(ch); }).base();
    return {start, end};
}

// Verifica si una línea es comentario o está vacía
auto isCommentOrEmpty(const std::string& line) -> bool {
    return line.empty() || line[0] == '#';
}

// Procesa una línea de configuración individual
auto processConfigLine(const std::string& line) -> bool {
    std::istringstream iss(line);
    std::string key;
    std::string value;

    if (iss >> key >> value) {
        if (!setOptions(key, value)) {
            if (console) {
                std::cout << "Warning: file config.txt\n";
                std::cout << "unknown parameter " << key << '\n';
            }
            return false;
        }
    }
    return true;
}

// Lee y procesa el fichero de configuración
auto readConfigFile(const std::string& file_path) -> bool {
    std::ifstream file(file_path);
    if (!file.good()) {
        return false;
    }

    bool success = true;
    if (console) {
        std::cout << "Reading file config.txt\n";
    }

    std::string line;
    while (std::getline(file, line)) {
        line = trimLine(line);

        if (isCommentOrEmpty(line)) {
            continue;
        }

        if (!processConfigLine(line)) {
            success = false;
        }
    }

    if (console) {
        std::cout << "Closing file config.txt\n\n";
    }
    file.close();

    return success;
}

// Carga las opciones desde un fichero
auto loadFromFile(const std::string& file_path) -> bool {
    // Versión actual del fichero
    const std::string CONFIG_VERSION = version;
    version = "";

    // Intenta leer el fichero
    bool success = readConfigFile(file_path);

    // Si no se pudo leer, crea el fichero con valores por defecto
    if (!success) {
        saveToFile(file_path);
        success = true;
    }

    // Si la versión de fichero no coincide, crea un fichero nuevo con los valores por defecto
    if (CONFIG_VERSION != version) {
        init();
        saveToFile(file_path);
        if (console) {
            std::cout << "Wrong config file: initializing \n\n";
        }
    }

    return success;
}

// Guarda las opciones en un fichero
auto saveToFile(const std::string& file_path) -> bool {
    // Crea y abre el fichero de texto
    std::ofstream file(file_path);
    bool success = file.is_open();  // Verifica si el archivo se abrió correctamente

    if (!success)  // Si no se pudo abrir el archivo, muestra un mensaje de error y devuelve false
    {
        if (console) {
            std::cerr << "Error: Unable to open file " << file_path << " for writing." << '\n';
        }
        return false;
    }

    if (console) {
        std::cout << file_path << " open for writing" << '\n';
    }

    // Escribe en el fichero
    file << "# Versión de la configuración\n";
    file << "version " << version << "\n";

    file << "\n## CONTROL\n";
    file << "# Esquema de control: 0 = Cursores, 1 = OPQ, 2 = WAD\n";
    file << "keys " << static_cast<int>(keys) << "\n";

    file << "\n## WINDOW\n";
    file << "# Zoom de la ventana: 1 = Normal, 2 = Doble, 3 = Triple, ...\n";
    file << "window.zoom " << window.zoom << "\n";

    file << "\n## VIDEO\n";
    file << "# Modo de video: 0 = Ventana, 1 = Pantalla completa, 2 = Pantalla completa (escritorio)\n";
    file << "video.mode " << video.fullscreen << "\n\n";
    file << "# Filtro de pantalla: 0 = Nearest, 1 = Linear\n";
    file << "video.filter " << static_cast<int>(video.filter) << "\n\n";
    file << "# Shaders: 1 = Activado, 0 = Desactivado\n";
    file << "video.shaders " << boolToString(video.shaders) << "\n\n";
    file << "# Sincronización vertical: 1 = Activado, 0 = Desactivado\n";
    file << "video.vertical_sync " << boolToString(video.vertical_sync) << "\n\n";
    file << "# Escalado entero: 1 = Activado, 0 = Desactivado\n";
    file << "video.integer_scale " << boolToString(video.integer_scale) << "\n\n";
    file << "# Mantener aspecto: 1 = Activado, 0 = Desactivado\n";
    file << "video.keep_aspect " << boolToString(video.keep_aspect) << "\n\n";
    file << "# Borde: 1 = Activado, 0 = Desactivado\n";
    file << "video.border.enabled " << boolToString(video.border.enabled) << "\n\n";
    file << "# Ancho del borde\n";
    file << "video.border.width " << video.border.width << "\n\n";
    file << "# Alto del borde\n";
    file << "video.border.height " << video.border.height << "\n\n";
    file << "# Paleta\n";
    file << "video.palette " << video.palette << "\n";

    // Cierra el fichero
    file.close();

    return success;
}

auto setOptions(const std::string& var, const std::string& value) -> bool {
    static const std::unordered_map<std::string, std::function<void(const std::string&)>> OPTION_HANDLERS = {
        {"version", [](const std::string& v) { version = v; }},
        {"keys", [](const std::string& v) {
             int val = safeStoi(v, static_cast<int>(GameDefaults::CONTROL_SCHEME));
             if (val == static_cast<int>(ControlScheme::CURSOR) || val == static_cast<int>(ControlScheme::OPQA) || val == static_cast<int>(ControlScheme::WASD)) {
                 keys = static_cast<ControlScheme>(val);
             } else {
                 keys = GameDefaults::CONTROL_SCHEME;
             }
         }},
        {"window.zoom", [](const std::string& v) {
             int val = safeStoi(v, GameDefaults::WINDOW_ZOOM);
             if (val > 0) {
                 window.zoom = val;
             } else {
                 window.zoom = GameDefaults::WINDOW_ZOOM;
             }
         }},
        {"video.mode", [](const std::string& v) { video.fullscreen = stringToBool(v); }},
        {"video.filter", [](const std::string& v) {
             int val = safeStoi(v, static_cast<int>(GameDefaults::VIDEO_FILTER));
             if (val == static_cast<int>(ScreenFilter::NEAREST) || val == static_cast<int>(ScreenFilter::LINEAR)) {
                 video.filter = static_cast<ScreenFilter>(val);
             } else {
                 video.filter = GameDefaults::VIDEO_FILTER;
             }
         }},
        {"video.shaders", [](const std::string& v) { video.shaders = stringToBool(v); }},
        {"video.vertical_sync", [](const std::string& v) { video.vertical_sync = stringToBool(v); }},
        {"video.integer_scale", [](const std::string& v) { video.integer_scale = stringToBool(v); }},
        {"video.keep_aspect", [](const std::string& v) { video.keep_aspect = stringToBool(v); }},
        {"video.border.enabled", [](const std::string& v) { video.border.enabled = stringToBool(v); }},
        {"video.border.width", [](const std::string& v) {
             int val = safeStoi(v, GameDefaults::BORDER_WIDTH);
             if (val > 0) {
                 video.border.width = val;
             } else {
                 video.border.width = GameDefaults::BORDER_WIDTH;
             }
         }},
        {"video.border.height", [](const std::string& v) {
             int val = safeStoi(v, GameDefaults::BORDER_HEIGHT);
             if (val > 0) {
                 video.border.height = val;
             } else {
                 video.border.height = GameDefaults::BORDER_HEIGHT;
             }
         }},
        {"video.palette", [](const std::string& v) {
             video.palette = v;
         }}};

    auto it = OPTION_HANDLERS.find(var);
    if (it != OPTION_HANDLERS.end()) {
        it->second(value);
        return true;
    }
    return false;
}
}