// IWYU pragma: no_include <bits/chrono.h>
#include "director.hpp"

#include <SDL3/SDL.h>  // Para SDL_SetLogPriority, SDL_LogCategory, SDL_LogPriority, SDL_Quit

#include <cstdlib>     // Para srand, exit, rand, EXIT_FAILURE
#include <ctime>       // Para time
#include <filesystem>  // Para path, absolute
#include <iostream>    // Para basic_ostream, operator<<, cerr
#include <memory>      // Para make_unique, unique_ptr
#include <span>        // Para span
#include <stdexcept>   // Para runtime_error
#include <string>      // Para allocator, basic_string, char_traits, operator+, string, operator==

#include "asset.hpp"                   // Para Asset
#include "audio.hpp"                   // Para Audio
#include "input.hpp"                   // Para Input
#include "lang.hpp"                    // Para setLanguage
#include "manage_hiscore_table.hpp"    // Para ManageHiScoreTable
#include "options.hpp"                 // Para Settings, loadFromFile, saveToFile, settings, setConfigFile, setControllersFile
#include "param.hpp"                   // Para loadParamsFromFile
#include "player.hpp"                  // Para Player
#include "resource.hpp"                // Para Resource
#include "resource_helper.hpp"         // Para initializeResourceSystem
#include "screen.hpp"                  // Para Screen
#include "section.hpp"                 // Para Name, Options, name, options, AttractMode, attract_mode
#include "sections/credits.hpp"        // Para Credits
#include "sections/game.hpp"           // Para Game
#include "sections/hiscore_table.hpp"  // Para HiScoreTable
#include "sections/instructions.hpp"   // Para Instructions
#include "sections/intro.hpp"          // Para Intro
#include "sections/logo.hpp"           // Para Logo
#include "sections/title.hpp"          // Para Title
#include "shutdown.hpp"                // Para resultToString, shutdownSystem, ShutdownResult
#include "system_utils.hpp"            // Para createApplicationFolder, resultToString, Result
#include "ui/logger.hpp"               // Para section, put
#include "ui/notifier.hpp"             // Para Notifier
#include "ui/service_menu.hpp"         // Para ServiceMenu
#include "utils.hpp"                   // Para Overrides, overrides

// Constructor
Director::Director(int argc, std::span<char*> argv) {
#ifdef RECORDING
    Section::name = Section::Name::GAME;
    Section::options = Section::Options::GAME_PLAY_1P;
#elif _DEBUG
    Section::name = Section::Name::HI_SCORE_TABLE;
    Section::options = Section::Options::GAME_PLAY_1P;
#else  // NORMAL GAME
    Section::name = Section::Name::LOGO;
    Section::options = Section::Options::NONE;
#endif
    Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO;

    // Establece el nivel de prioridad de la categoría de registro
    SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
    SDL_SetLogPriority(SDL_LOG_CATEGORY_TEST, SDL_LOG_PRIORITY_ERROR);

    Logger::put("Game start\n");

    // Inicia la semilla aleatoria usando el tiempo actual en segundos
    std::srand(static_cast<unsigned int>(std::time(nullptr)));

    // Comprueba los parametros del programa
    checkProgramArguments(argc, argv);

    // Crea la carpeta del sistema donde guardar los datos persistentes
    createSystemFolder("jailgames");
    createSystemFolder("jailgames/coffee_crisis_arcade_edition");

    init();
}

Director::~Director() {
    close();
    Logger::put("\nBye!");
}

// Inicializa todo
void Director::init() {
    // Configuración inicial de parametros
    Asset::init(executable_path_);  // Inicializa el sistema de gestión de archivos

#ifdef MACOS_BUNDLE
    ResourceHelper::initializeResourceSystem(executable_path_ + "../Resources/resources.pack");
#else
    ResourceHelper::initializeResourceSystem(executable_path_ + "resources.pack");
#endif
    loadAssets();  // Crea el índice de archivos

    Logger::section("INIT INPUT");
    Input::init(Asset::get()->get("gamecontrollerdb.txt"), Asset::get()->get("controllers.json"));  // Carga configuración de controles

    Logger::section("INIT CONFIG");
    Options::setConfigFile(Asset::get()->get("config_v2.txt"));          // Establece el fichero de configuración
    Options::setControllersFile(Asset::get()->get("controllers.json"));  // Establece el fichero de configuración de mandos
    Options::loadFromFile();                                             // Carga el archivo de configuración
    loadParams();                                                        // Carga los parámetros del programa
    loadScoreFile();                                                     // Carga el archivo de puntuaciones

    // Inicialización de subsistemas principales
    Lang::setLanguage(Options::settings.language);  // Carga el archivo de idioma

    Logger::section("INIT VIDEO");
    Screen::init();  // Inicializa la pantalla y el sistema de renderizado

    Logger::section("INIT AUDIO");
    Audio::init();  // Activa el sistema de audio

    Logger::section("INIT RESOURCES");
#ifdef _DEBUG
    Resource::init(Resource::LoadingMode::PRELOAD);  // Inicializa el sistema de gestión de recursos
#else
    Resource::init(Resource::LoadingMode::PRELOAD);  // Inicializa el sistema de gestión de recursos
#endif
    ServiceMenu::init();                                                 // Inicializa el menú de servicio
    Notifier::init(std::string(), Resource::get()->getText("8bithud"));  // Inicialización del sistema de notificaciones
    Screen::get()->getSingletons();                                      // Obtiene los punteros al resto de singletones

    Logger::section("GAME LOG");
}

// Cierra todo y libera recursos del sistema y de los singletons
void Director::close() {
    // Guarda las opciones actuales en el archivo de configuración
    Options::saveToFile();

    // Libera los singletons y recursos en orden inverso al de inicialización
    Notifier::destroy();     // Libera el sistema de notificaciones
    ServiceMenu::destroy();  // Libera el sistema de menú de servicio
    Input::destroy();        // Libera el sistema de entrada
    Resource::destroy();     // Libera el sistema de recursos gráficos y de texto
    Audio::destroy();        // Libera el sistema de audio
    Screen::destroy();       // Libera el sistema de pantalla y renderizado
    Asset::destroy();        // Libera el gestor de archivos

    // Libera todos los recursos de SDL
    SDL_Quit();

    // Apaga el sistema
    shutdownSystem(Section::options == Section::Options::SHUTDOWN);
}

// Carga los parametros
void Director::loadParams() {
    // Carga los parametros para configurar el juego
#ifdef ANBERNIC
    const std::string PARAM_FILE_PATH = Asset::get()->get("param_320x240.txt");
#else
    const std::string PARAM_FILE_PATH = Asset::get()->get(Options::settings.params_file);
#endif
    loadParamsFromFile(PARAM_FILE_PATH);
}

// Carga el fichero de puntuaciones
void Director::loadScoreFile() {
    auto manager = std::make_unique<ManageHiScoreTable>(Options::settings.hi_score_table);
#ifdef _DEBUG
    manager->clear();
#else
    if (overrides.clear_hi_score_table) {
        manager->clear();
    } else {
        manager->loadFromFile(Asset::get()->get("score.bin"));
    }
#endif
}

// Carga el indice de ficheros desde un fichero
void Director::loadAssets() {
#ifdef MACOS_BUNDLE
    const std::string PREFIX = "/../Resources";
#else
    const std::string PREFIX;
#endif

    // Cargar la configuración de assets (también aplicar el prefijo al archivo de configuración)
    std::string config_path = executable_path_ + PREFIX + "/config/assets.txt";
    Asset::get()->loadFromFile(config_path, PREFIX, system_folder_);

    Logger::put("Assets configuration loaded successfully");

    // Si falta algun fichero, sale del programa
    if (!Asset::get()->check()) {
        throw std::runtime_error("Falta algun fichero");
    }
}

// Comprueba los parametros del programa
void Director::checkProgramArguments(int argc, std::span<char*> argv) {
    // Obtener la ruta absoluta del ejecutable
    std::filesystem::path exe_path = std::filesystem::absolute(argv[0]);
    executable_path_ = exe_path.parent_path().string();

    // Asegurar que termine con separador de directorio
    if (!executable_path_.empty() && executable_path_.back() != '/' && executable_path_.back() != '\\') {
        executable_path_ += "/";
    }

    // Comprueba el resto de parámetros
    for (int i = 1; i < argc; ++i) {
        std::string arg = argv[i];

        if (arg == "--320x240") {
            overrides.param_file = arg;
        } else if (arg == "--clear_score") {
            overrides.clear_hi_score_table = true;
        }
    }
}

// Crea la carpeta del sistema donde guardar datos
void Director::createSystemFolder(const std::string& folder) {
    auto result = SystemUtils::createApplicationFolder(folder, system_folder_);

    if (result != SystemUtils::Result::SUCCESS) {
        std::cerr << "Error creando carpeta del sistema: "
                  << SystemUtils::resultToString(result) << '\n';
        exit(EXIT_FAILURE);
    }
}

// Ejecuta la sección con el logo
void Director::runLogo() {
    auto logo = std::make_unique<Logo>();
    logo->run();
}

// Ejecuta la sección con la secuencia de introducción
void Director::runIntro() {
    auto intro = std::make_unique<Intro>();
    intro->run();
}

// Ejecuta la sección con el título del juego
void Director::runTitle() {
    auto title = std::make_unique<Title>();
    title->run();
}

// Ejecuta la sección donde se juega al juego
void Director::runGame() {
    Player::Id player_id = Player::Id::PLAYER1;

    switch (Section::options) {
        case Section::Options::GAME_PLAY_1P:
            player_id = Player::Id::PLAYER1;
            break;
        case Section::Options::GAME_PLAY_2P:
            player_id = Player::Id::PLAYER2;
            break;
        case Section::Options::GAME_PLAY_BOTH:
            player_id = Player::Id::BOTH_PLAYERS;
            break;
        default:
            break;
    }

#ifdef _DEBUG
    constexpr int CURRENT_STAGE = 0;
#else
    constexpr int CURRENT_STAGE = 0;
#endif
    auto game = std::make_unique<Game>(player_id, CURRENT_STAGE, Game::DEMO_OFF);
    game->run();
}

// Ejecuta la sección donde se muestran las instrucciones
void Director::runInstructions() {
    auto instructions = std::make_unique<Instructions>();
    instructions->run();
}

// Ejecuta la sección donde se muestran los creditos del programa
void Director::runCredits() {
    auto credits = std::make_unique<Credits>();
    credits->run();
}

// Ejecuta la sección donde se muestra la tabla de puntuaciones
void Director::runHiScoreTable() {
    auto hi_score_table = std::make_unique<HiScoreTable>();
    hi_score_table->run();
}

// Ejecuta el juego en modo demo
void Director::runDemoGame() {
    const auto PLAYER_ID = static_cast<Player::Id>((rand() % 2) + 1);
    constexpr auto CURRENT_STAGE = 0;
    auto game = std::make_unique<Game>(PLAYER_ID, CURRENT_STAGE, Game::DEMO_ON);
    game->run();
}

// Reinicia objetos y vuelve a la sección inicial
void Director::reset() {
    Options::saveToFile();
    Options::loadFromFile();
    Lang::setLanguage(Options::settings.language);
    Audio::get()->stopMusic();
    Audio::get()->stopAllSounds();
    Resource::get()->reload();
    ServiceMenu::get()->reset();
    Section::name = Section::Name::LOGO;
}

auto Director::run() -> int {
    // Bucle principal
    while (Section::name != Section::Name::QUIT) {
        switch (Section::name) {
            case Section::Name::RESET:
                reset();
                break;
            case Section::Name::LOGO:
                runLogo();
                break;
            case Section::Name::INTRO:
                runIntro();
                break;
            case Section::Name::TITLE:
                runTitle();
                break;
            case Section::Name::GAME:
                runGame();
                break;
            case Section::Name::HI_SCORE_TABLE:
                runHiScoreTable();
                break;
            case Section::Name::GAME_DEMO:
                runDemoGame();
                break;
            case Section::Name::INSTRUCTIONS:
                runInstructions();
                break;
            case Section::Name::CREDITS:
                runCredits();
                break;
            default:
                break;
        }
    }

    return 0;
}

// Apaga el sistema de forma segura
void Director::shutdownSystem(bool should_shutdown) {
    if (should_shutdown) {
        auto result = SystemShutdown::shutdownSystem(5, true);  // 5 segundos, forzar apps

        if (result != SystemShutdown::ShutdownResult::SUCCESS) {
            std::cerr << SystemShutdown::resultToString(result) << '\n';
        }
    }
}