#include "core/input/global_inputs.hpp"

#include <algorithm>   // Para __any_of_fn, any_of
#include <functional>  // Para function
#include <iterator>    // Para pair
#include <string>      // Para basic_string, operator+, allocator, char_traits, string, to_string
#include <utility>     // Para pair
#include <vector>      // Para vector

#include "core/audio/audio.hpp"        // Para Audio
#include "core/input/input.hpp"        // Para Input
#include "core/input/input_types.hpp"  // Para InputAction
#include "core/locale/lang.hpp"        // Para getText, getLangFile, getLangName, getNextLangCode, loadFromFile
#include "core/rendering/screen.hpp"   // Para Screen
#include "core/system/section.hpp"     // Para Name, name, Options, options, AttractMode, attract_mode
#include "game/options.hpp"            // Para Video, video, Settings, settings, Audio, audio, Window, window
#include "game/ui/notifier.hpp"        // Para Notifier
#include "game/ui/service_menu.hpp"    // Para ServiceMenu
#include "utils/utils.hpp"             // Para boolToOnOff

namespace GlobalInputs {
    // Termina
    void quit() {
#ifdef __EMSCRIPTEN__
        // En la versión web no se permite salir: el navegador gestiona el cierre.
        return;
#else
        const std::string CODE = "QUIT";
        if (Notifier::get()->checkCode(CODE)) {
            // Si la notificación de salir está activa, cambia de sección
            Section::name = Section::Name::QUIT;
            Section::options = Section::Options::NONE;
        } else {
            // Si la notificación de salir no está activa, muestra la notificación
            Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 01"), std::string()}, -1, CODE);
        }
#endif
    }

    // Reinicia
    void reset() {
        const std::string CODE = "RESET";
        if (Notifier::get()->checkCode(CODE)) {
            Section::name = Section::Name::RESET;
            Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 15")});
        } else {
            Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 03"), std::string()}, -1, CODE);
        }
    }

    // Activa o desactiva el audio
    void toggleAudio() {
        Options::audio.enabled = !Options::audio.enabled;
        Audio::get()->enable(Options::audio.enabled);
        Notifier::get()->show({"Audio " + boolToOnOff(Options::audio.enabled)});
    }

    // Cambia el modo de escalado entero
    void toggleIntegerScale() {
        Screen::get()->toggleIntegerScale();
        Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 12") + " " + boolToOnOff(Options::video.integer_scale)});
    }

    // Activa / desactiva el vsync
    void toggleVSync() {
        Screen::get()->toggleVSync();
        Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 14") + " " + boolToOnOff(Options::video.vsync)});
    }

    // Activa o desactiva los shaders
    void toggleShaders() {
        Screen::toggleShaders();
        Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 13") + " " + boolToOnOff(Options::video.shader.enabled)});
    }

    // Cambia entre PostFX y CrtPi
    void nextShader() {
        Screen::nextShader();
        const std::string SHADER_NAME = (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) ? "CrtPi" : "PostFX";
        Notifier::get()->show({"Shader: " + SHADER_NAME});
    }

    // Avanza al siguiente preset PostFX o CrtPi según shader activo
    void nextPreset() {
        if (Options::video.shader.current_shader == Rendering::ShaderType::CRTPI) {
            Screen::nextCrtPiPreset();
            const std::string name = Options::crtpi_presets.empty() ? "" : Options::crtpi_presets.at(static_cast<size_t>(Options::video.shader.current_crtpi_preset)).name;
            Notifier::get()->show({"CrtPi: " + name});
        } else {
            Screen::nextPostFXPreset();
            const std::string name = Options::postfx_presets.empty() ? "" : Options::postfx_presets.at(static_cast<size_t>(Options::video.shader.current_postfx_preset)).name;
            Notifier::get()->show({"PostFX: " + name});
        }
    }

    // Activa o desactiva el supersampling
    void toggleSupersampling() {
        Screen::toggleSupersampling();
        Notifier::get()->show({"SS: " + std::string(Options::video.supersampling.enabled ? "ON" : "OFF")});
    }

    // Cambia al siguiente idioma
    void setNextLang() {
        const std::string CODE = "LANG";
        const auto NEXT_LANG_CODE = Lang::getNextLangCode(Options::settings.language);
        const auto NEXT_LANG_NAME = Lang::getLangName(NEXT_LANG_CODE);
        if (Notifier::get()->checkCode(CODE)) {
            // Si la notificación de cambiar idioma está activa, cambia de de idioma
            Options::settings.language = NEXT_LANG_CODE;
            Lang::loadFromFile(Lang::getLangFile(NEXT_LANG_CODE));
            Section::name = Section::Name::RESET;
            Section::options = Section::Options::RELOAD;
            Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 05") + NEXT_LANG_NAME});
        } else {
            // Si la notificación de cambiar idioma no está activa, muestra la notificación
            Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 04") + NEXT_LANG_NAME, std::string()}, -1, CODE);
        }
    }

    // Cambia el modo de disparo
    void toggleFireMode() {
        Options::settings.autofire = !Options::settings.autofire;
        Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 08") + " " + boolToOnOff(Options::settings.autofire)});
    }

    // Salta una sección del juego
    void skipSection() {
        switch (Section::name) {
            case Section::Name::INTRO:
                Audio::get()->stopMusic();
                /* Continua en el case de abajo */
            case Section::Name::LOGO:
            case Section::Name::HI_SCORE_TABLE:
            case Section::Name::INSTRUCTIONS: {
                Section::name = Section::Name::TITLE;
                Section::options = Section::Options::TITLE_1;
                Section::attract_mode = Section::AttractMode::TITLE_TO_DEMO;
                break;
            }
            default:
                break;
        }
    }

    // Activa el menu de servicio
    void toggleServiceMenu() {
        ServiceMenu::get()->toggle();
    }

    // Cambia el modo de pantalla completa
    void toggleFullscreen() {
        Screen::get()->toggleFullscreen();
        const std::string MODE = Options::video.fullscreen ? Lang::getText("[NOTIFICATIONS] 11") : Lang::getText("[NOTIFICATIONS] 10");
        Notifier::get()->show({MODE});
    }

    // Reduce el tamaño de la ventana
    void decWindowSize() {
        if (Screen::get()->decWindowSize()) {
            Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 09") + " x" + std::to_string(Options::window.zoom)});
        }
    }

    // Aumenta el tamaño de la ventana
    void incWindowSize() {
        if (Screen::get()->incWindowSize()) {
            Notifier::get()->show({Lang::getText("[NOTIFICATIONS] 09") + " x" + std::to_string(Options::window.zoom)});
        }
    }

    // Comprueba el boton de servicio
    auto checkServiceButton() -> bool {
        // Teclado
        if (Input::get()->checkAction(Input::Action::SERVICE, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
            toggleServiceMenu();
            return true;
        }

        // Mandos
        if (std::ranges::any_of(Input::get()->getGamepads(), [](const auto& gamepad) -> auto {
                return Input::get()->checkAction(Input::Action::SERVICE, Input::DO_NOT_ALLOW_REPEAT, Input::DO_NOT_CHECK_KEYBOARD, gamepad);
            })) {
            toggleServiceMenu();
            return true;
        }

        return false;
    }

    // Comprueba las entradas para elementos del sistema
    auto checkSystemInputs() -> bool {
        using Action = Input::Action;

        static const std::vector<std::pair<Action, std::function<void()>>> ACTIONS = {
            {Action::WINDOW_FULLSCREEN, toggleFullscreen},
            {Action::WINDOW_DEC_SIZE, decWindowSize},
            {Action::WINDOW_INC_SIZE, incWindowSize},
            {Action::EXIT, quit},
            {Action::RESET, reset},
            {Action::TOGGLE_AUDIO, toggleAudio},
            {Action::TOGGLE_AUTO_FIRE, toggleFireMode},
            {Action::CHANGE_LANG, setNextLang},
            {Action::TOGGLE_VIDEO_INTEGER_SCALE, toggleIntegerScale},
            {Action::TOGGLE_VIDEO_VSYNC, toggleVSync},
#ifdef _DEBUG
            {Action::SHOW_INFO, []() -> void { Screen::get()->toggleDebugInfo(); }},
#endif
        };

        if (std::ranges::any_of(ACTIONS, [](const auto& pair) -> auto {
                if (Input::get()->checkAction(pair.first, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
                    pair.second();
                    return true;
                }
                return false;
            })) {
            return true;
        }

        if (Input::get()->checkAction(Input::Action::TOGGLE_VIDEO_POSTFX, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
            toggleShaders();
            return true;
        }
        if (Input::get()->checkAction(Input::Action::NEXT_SHADER, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
            nextShader();
            return true;
        }
        if (Input::get()->checkAction(Input::Action::NEXT_POSTFX_PRESET, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
            nextPreset();
            return true;
        }
        if (Input::get()->checkAction(Input::Action::TOGGLE_SUPERSAMPLING, Input::DO_NOT_ALLOW_REPEAT, Input::CHECK_KEYBOARD)) {
            toggleSupersampling();
            return true;
        }

        return false;
    }

    // Comprueba el resto de entradas
    auto checkOtherInputs() -> bool {
        // Saltar sección
        if ((Input::get()->checkAnyButton()) && !ServiceMenu::get()->isEnabled()) {
            skipSection();
            return true;
        }
        return false;
    }

    // Comprueba las entradas del Menu de Servicio
    inline auto checkServiceMenuInputs() -> bool {
        return ServiceMenu::get()->checkInput();
    }

    // Comprueba los inputs que se pueden introducir en cualquier sección del juego
    auto check() -> bool {
        if (checkServiceButton()) {
            return true;
        }
        if (checkServiceMenuInputs()) {
            return true;
        }
        if (checkSystemInputs()) {
            return true;
        }
        if (checkOtherInputs()) {
            return true;
        }
        return false;
    }
}  // namespace GlobalInputs
