#include "title.h"

#include <SDL3/SDL.h>  // Para SDL_GetTicks, Uint32, SDL_EventType

#include <algorithm>  // Para find_if
#include <cstddef>    // Para size_t
#include <iostream>   // Para basic_ostream, basic_ostream::operator<<
#include <string>     // Para basic_string, char_traits, operator+
#include <vector>     // Para vector

#include "audio.h"            // Para Audio
#include "color.h"            // Para Color, Zone, NO_TEXT_COLOR, TITLE_SHADO...
#include "define_buttons.h"   // Para DefineButtons
#include "fade.h"             // Para Fade, FadeType
#include "game_logo.h"        // Para GameLogo
#include "global_events.h"    // Para check
#include "global_inputs.h"    // Para check
#include "input.h"            // Para Input, Input::DO_NOT_ALLOW_REPEAT, Input...
#include "lang.h"             // Para getText
#include "options.h"          // Para GamepadOptions, controllers, getPlayerW...
#include "param.h"            // Para Param, param, ParamGame, ParamTitle
#include "player.h"           // Para Player, PlayerState
#include "resource.h"         // Para Resource
#include "screen.h"           // Para Screen
#include "section.hpp"        // Para Name, name, Options, options, AttractMode
#include "sprite.h"           // Para Sprite
#include "text.h"             // Para TEXT_CENTER, TEXT_SHADOW, Text
#include "tiled_bg.h"         // Para TiledBG, TiledBGMode
#include "ui/notifier.h"      // Para Notifier
#include "ui/service_menu.h"  // Para ServiceMenu
#include "utils.h"

class Texture;

#ifdef _DEBUG
#include <iomanip>  // Para operator<<, setfill, setw
#endif

// Constructor
Title::Title()
    : text_(Resource::get()->getText("smb2_grad")),
      fade_(std::make_unique<Fade>()),
      tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::RANDOM)),
      game_logo_(std::make_unique<GameLogo>(param.game.game_area.center_x, param.title.title_c_c_position)),
      mini_logo_sprite_(std::make_unique<Sprite>(Resource::get()->getTexture("logo_jailgames_mini.png"))),
      define_buttons_(std::make_unique<DefineButtons>()),
      num_controllers_(Input::get()->getNumControllers()),
      state_(TitleState::LOGO_ANIMATING) {
    // Configura objetos
    tiled_bg_->setColor(param.title.bg_color);
    game_logo_->enable();
    mini_logo_sprite_->setX(param.game.game_area.center_x - mini_logo_sprite_->getWidth() / 2);
    fade_->setColor(param.fade.color);
    fade_->setType(FadeType::RANDOM_SQUARE);
    fade_->setPostDuration(param.fade.post_duration);
    initPlayers();

    // Asigna valores a otras variables
    Section::options = Section::Options::TITLE_1;
    const bool IS_TITLE_TO_DEMO = (Section::attract_mode == Section::AttractMode::TITLE_TO_DEMO);
    next_section_ = IS_TITLE_TO_DEMO ? Section::Name::GAME_DEMO : Section::Name::LOGO;
    Section::attract_mode = IS_TITLE_TO_DEMO ? Section::AttractMode::TITLE_TO_LOGO : Section::AttractMode::TITLE_TO_DEMO;

    // Define los anclajes de los elementos
    anchor_.mini_logo = (param.game.height / 5 * 4) + BLOCK;
    mini_logo_sprite_->setY(anchor_.mini_logo);
    anchor_.copyright_text = anchor_.mini_logo + mini_logo_sprite_->getHeight() + 3;
}

// Destructor
Title::~Title() {
    Audio::get()->stopAllSounds();
    if (Section::name == Section::Name::LOGO) {
        Audio::get()->fadeOutMusic(300);
    }
}

// Actualiza las variables del objeto
void Title::update() {
    if (SDL_GetTicks() - ticks_ > param.game.speed) {
        ticks_ = SDL_GetTicks();
        Screen::get()->update();

        updateFade();
        updateState();
        updateStartPrompt();
        updatePlayers();
    }

    static const auto audio = Audio::get();
    audio->update();
}

// Dibuja el objeto en pantalla
void Title::render() {
    static const auto screen = Screen::get();

    screen->start();
    screen->clean();

    tiled_bg_->render();
    game_logo_->render();
    renderPlayers();
    renderStartPrompt();
    renderCopyright();
    define_buttons_->render();
    fade_->render();

    screen->render();
}

// Comprueba los eventos
void Title::checkEvents() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        if (event.type == SDL_EVENT_KEY_DOWN) {
            handleKeyDownEvent(event);
        }

        GlobalEvents::check(event);
        define_buttons_->checkEvents(event);
    }
}

void Title::handleKeyDownEvent(const SDL_Event& event) {
    bool is_first_press = static_cast<int>(event.key.repeat) == 0;
    if (is_first_press) {
        handleControlKeys(event.key.key);
    }
#ifdef _DEBUG
    bool is_repeat = static_cast<int>(event.key.repeat) == 1;
    if (is_repeat) {
        handleDebugColorKeys(event.key.key);
    }
#endif
}

#ifdef _DEBUG
void Title::handleDebugColorKeys(SDL_Keycode key) {
    static Color color_ = param.title.bg_color;

    adjustColorComponent(key, color_);

    counter_ = 0;
    tiled_bg_->setColor(color_);
    printColorValue(color_);
}

void Title::adjustColorComponent(SDL_Keycode key, Color& color) {
    switch (key) {
        case SDLK_A:
            incrementColorComponent(color.r);
            break;
        case SDLK_Z:
            decrementColorComponent(color.r);
            break;
        case SDLK_S:
            incrementColorComponent(color.g);
            break;
        case SDLK_X:
            decrementColorComponent(color.g);
            break;
        case SDLK_D:
            incrementColorComponent(color.b);
            break;
        case SDLK_C:
            decrementColorComponent(color.b);
            break;
        case SDLK_F:
            incrementAllComponents(color);
            break;
        case SDLK_V:
            decrementAllComponents(color);
            break;
        default:
            break;
    }
}

void Title::incrementColorComponent(uint8_t& component) {
    if (component < 255) {
        ++component;
    }
}

void Title::decrementColorComponent(uint8_t& component) {
    if (component > 0) {
        --component;
    }
}

void Title::incrementAllComponents(Color& color) {
    incrementColorComponent(color.r);
    incrementColorComponent(color.g);
    incrementColorComponent(color.b);
}

void Title::decrementAllComponents(Color& color) {
    decrementColorComponent(color.r);
    decrementColorComponent(color.g);
    decrementColorComponent(color.b);
}

void Title::printColorValue(const Color& color) {
    std::cout << "#"
              << std::hex << std::setw(2) << std::setfill('0') << (int)color.r
              << std::setw(2) << std::setfill('0') << (int)color.g
              << std::setw(2) << std::setfill('0') << (int)color.b
              << std::endl;
}
#endif

void Title::handleControlKeys(SDL_Keycode key) {
    switch (key) {
        case SDLK_1:
            define_buttons_->enable(0);
            resetCounter();
            break;

        case SDLK_2:
            define_buttons_->enable(1);
            resetCounter();
            break;

        case SDLK_3:
            swapControllers();
            resetCounter();
            break;

        case SDLK_4:
            swapKeyboard();
            resetCounter();
            break;

        case SDLK_5:
            showControllers();
            resetCounter();
            break;

        default:
            break;
    }
}

// Comprueba las entradas
void Title::checkInput() {
    if (shouldSkipInputCheck()) {
        return;
    }

    Input::get()->update();

    if (!ServiceMenu::get()->isEnabled()) {
        processControllerInputs();
    }

    GlobalInputs::check();
}

auto Title::shouldSkipInputCheck() const -> bool {
    return define_buttons_->isEnabled();
}

void Title::processControllerInputs() {
    for (const auto& controller : Options::controllers) {
        if (isStartButtonPressed(controller)) {
            handleStartButtonPress(controller);
        }
    }
}

auto Title::isStartButtonPressed(const Options::GamepadOptions& controller) -> bool {
    return Input::get()->checkAction(
        Input::Action::START,
        Input::DO_NOT_ALLOW_REPEAT,
        Input::CHECK_KEYBOARD,
        controller.gamepad);
}

void Title::handleStartButtonPress(const Options::GamepadOptions& controller) {
    if (!canProcessStartButton()) {
        return;
    }

    if (controller.player_id == 1) {
        processPlayer1Start();
    } else if (controller.player_id == 2) {
        processPlayer2Start();
    }
}

auto Title::canProcessStartButton() const -> bool {
    return (state_ != TitleState::LOGO_ANIMATING || ALLOW_TITLE_ANIMATION_SKIP);
}

void Title::processPlayer1Start() {
    if (!player1_start_pressed_) {
        player1_start_pressed_ = true;
        activatePlayerAndSetState(1);
    }
}

void Title::processPlayer2Start() {
    if (!player2_start_pressed_) {
        player2_start_pressed_ = true;
        activatePlayerAndSetState(2);
    }
}

void Title::activatePlayerAndSetState(int player_id) {
    getPlayer(player_id)->setPlayingState(Player::State::TITLE_ANIMATION);
    setState(TitleState::START_HAS_BEEN_PRESSED);
    counter_ = 0;
}

// Bucle para el titulo del juego
void Title::run() {
    while (Section::name == Section::Name::TITLE) {
        checkInput();
        update();
        checkEvents();  // Tiene que ir antes del render
        render();
    }
}

// Reinicia el contador interno
void Title::resetCounter() { counter_ = 0; }

// Intercambia la asignación de mandos a los jugadores
void Title::swapControllers() {
    if (Input::get()->getNumControllers() == 0) {
        return;
    }

    Options::swapControllers();
    showControllers();
}

// Intercambia el teclado de jugador
void Title::swapKeyboard() {
    Options::swapKeyboard();
    std::string text = Lang::getText("[DEFINE_BUTTONS] PLAYER") + std::to_string(Options::getPlayerWhoUsesKeyboard()) + ": " + Lang::getText("[DEFINE_BUTTONS] KEYBOARD");
    Notifier::get()->show({text});
}

// Muestra información sobre los controles y los jugadores
void Title::showControllers() {
    // Crea vectores de texto vacíos para un número máximo de mandos
    constexpr size_t NUM_CONTROLLERS = 2;
    std::vector<std::string> text(NUM_CONTROLLERS);
    std::vector<int> player_controller_index(NUM_CONTROLLERS, -1);

    // Obtiene de cada jugador el índice del mando que tiene asignado
    for (size_t i = 0; i < NUM_CONTROLLERS; ++i) {
        // Ejemplo: el jugador 1 tiene el mando 2
        player_controller_index.at(Options::controllers.at(i).player_id - 1) = i;
    }

    // Genera el texto correspondiente
    //for (size_t i = 0; i < NUM_CONTROLLERS; ++i) {
    //    const size_t INDEX = player_controller_index.at(i);
    //    if (Options::controllers.at(INDEX).plugged) {
    //        text.at(i) = Lang::getText("[DEFINE_BUTTONS] PLAYER") + std::to_string(i + 1) + ": " + Options::controllers.at(INDEX).name;
    //    }
    //}

    // Muestra la notificación
    Notifier::get()->show({text.at(0), text.at(1)});
}

// Actualiza el fade
void Title::updateFade() {
    fade_->update();
    if (fade_->hasEnded()) {
        const int COMBO = (player1_start_pressed_ ? 1 : 0) | (player2_start_pressed_ ? 2 : 0);

        switch (COMBO) {
            case 0:  // Ningún jugador ha pulsado Start
                Section::name = next_section_;
                break;

            case 1:  // Solo el jugador 1 ha pulsado Start
                Section::name = Section::Name::GAME;
                Section::options = Section::Options::GAME_PLAY_1P;
                Audio::get()->stopMusic();
                break;

            case 2:  // Solo el jugador 2 ha pulsado Start
                Section::name = Section::Name::GAME;
                Section::options = Section::Options::GAME_PLAY_2P;
                Audio::get()->stopMusic();
                break;

            case 3:  // Ambos jugadores han pulsado Start
                Section::name = Section::Name::GAME;
                Section::options = Section::Options::GAME_PLAY_BOTH;
                Audio::get()->stopMusic();
                break;
        }
    }
}

// Actualiza el estado
void Title::updateState() {
    // Establece la lógica según el estado
    switch (state_) {
        case TitleState::LOGO_ANIMATING: {
            game_logo_->update();
            if (game_logo_->hasFinished()) {
                setState(TitleState::LOGO_FINISHED);
            }
            break;
        }
        case TitleState::LOGO_FINISHED: {
            // El contador solo sube si no estamos definiendo botones
            counter_ = define_buttons_->isEnabled() ? 0 : counter_ + 1;

            // Actualiza el logo con el título del juego
            game_logo_->update();

            // Actualiza el mosaico de fondo
            tiled_bg_->update();

            if (counter_ == param.title.title_duration) {
                // El menu ha hecho time out
                fade_->setPostDuration(0);
                fade_->activate();
                selection_ = Section::Options::TITLE_TIME_OUT;
            }

            break;
        }
        case TitleState::START_HAS_BEEN_PRESSED: {
            // Actualiza el logo con el título del juego
            game_logo_->update();

            // Actualiza el mosaico de fondo
            tiled_bg_->update();

            if (counter_ == 100) {
                fade_->activate();
            }
            ++counter_;
            break;
        }

        default:
            break;
    }
}

void Title::updateStartPrompt() {
    constexpr Uint32 LOGO_BLINK_PERIOD = 833;   // milisegundos
    constexpr Uint32 LOGO_BLINK_ON_TIME = 583;  // 833 - 250

    constexpr Uint32 START_BLINK_PERIOD = 167;
    constexpr Uint32 START_BLINK_ON_TIME = 83;  // 167 - 83

    Uint32 time_ms = SDL_GetTicks();
    bool condition_met = false;

    if (!define_buttons_->isEnabled()) {
        switch (state_) {
            case TitleState::LOGO_FINISHED:
                condition_met = (time_ms % LOGO_BLINK_PERIOD) >= (LOGO_BLINK_PERIOD - LOGO_BLINK_ON_TIME);
                break;

            case TitleState::START_HAS_BEEN_PRESSED:
                condition_met = (time_ms % START_BLINK_PERIOD) >= (START_BLINK_PERIOD - START_BLINK_ON_TIME);
                break;

            default:
                break;
        }
    }

    should_render_start_prompt_ = condition_met;
}

void Title::renderStartPrompt() {
    if (should_render_start_prompt_) {
        text_->writeDX(TEXT_CENTER | TEXT_SHADOW,
            param.game.game_area.center_x,
            param.title.press_start_position,
            Lang::getText("[TITLE] PRESS_BUTTON_TO_PLAY"),
            1,
            NO_TEXT_COLOR,
            1,
            TITLE_SHADOW_TEXT_COLOR);
    }
}

void Title::renderCopyright() {
    if (state_ != TitleState::LOGO_ANIMATING) {
        // Mini logo
        mini_logo_sprite_->render();

        // Texto con el copyright
        text_->writeDX(TEXT_CENTER | TEXT_SHADOW,
            param.game.game_area.center_x,
            anchor_.copyright_text,
            std::string(TEXT_COPYRIGHT),
            1,
            NO_TEXT_COLOR,
            1,
            TITLE_SHADOW_TEXT_COLOR);
    }
}

// Cambia el estado
void Title::setState(TitleState state) {
    if (state_ == state) {
        return;
    }

    state_ = state;
    switch (state_) {
        case TitleState::LOGO_ANIMATING:
            break;
        case TitleState::LOGO_FINISHED:
            Audio::get()->playMusic("title.ogg");
            break;
        case TitleState::START_HAS_BEEN_PRESSED:
            Audio::get()->fadeOutMusic(1500);
            break;
    }
}

// Inicializa los jugadores
void Title::initPlayers() {
    std::vector<std::vector<std::shared_ptr<Texture>>> player_textures;  // Vector con todas las texturas de los jugadores;
    std::vector<std::vector<std::string>> player_animations;             // Vector con las animaciones del jugador

    // Texturas - Player1
    {
        std::vector<std::shared_ptr<Texture>> player_texture;
        player_texture.emplace_back(Resource::get()->getTexture("player1.gif"));
        player_texture.emplace_back(Resource::get()->getTexture("player1_power.png"));
        player_textures.push_back(player_texture);
    }

    // Texturas - Player2
    {
        std::vector<std::shared_ptr<Texture>> player_texture;
        player_texture.emplace_back(Resource::get()->getTexture("player2.gif"));
        player_texture.emplace_back(Resource::get()->getTexture("player2_power.png"));
        player_textures.push_back(player_texture);
    }

    // Animaciones -- Jugador
    {
        player_animations.emplace_back(Resource::get()->getAnimation("player.ani"));
        player_animations.emplace_back(Resource::get()->getAnimation("player_power.ani"));
    }

    // Crea los dos jugadores
    constexpr int PLAYER_WIDTH = 32;
    constexpr int PLAYER_HEIGHT = 32;
    const int Y = param.title.press_start_position - (PLAYER_HEIGHT / 2);
    constexpr bool DEMO = false;
    players_.emplace_back(std::make_unique<Player>(1, param.game.game_area.center_x - (PLAYER_WIDTH / 2), Y, DEMO, param.game.play_area.rect, player_textures.at(0), player_animations));
    players_.back()->setPlayingState(Player::State::TITLE_HIDDEN);

    players_.emplace_back(std::make_unique<Player>(2, param.game.game_area.center_x - (PLAYER_WIDTH / 2), Y, DEMO, param.game.play_area.rect, player_textures.at(1), player_animations));
    players_.back()->setPlayingState(Player::State::TITLE_HIDDEN);
}

// Actualza los jugadores
void Title::updatePlayers() {
    for (auto& player : players_) {
        player->update();
    }
}

// Renderiza los jugadores
void Title::renderPlayers() {
    for (auto const& player : players_) {
        player->render();
    }
}

// Obtiene un jugador a partir de su "id"
auto Title::getPlayer(int id) -> std::shared_ptr<Player> {
    auto it = std::find_if(players_.begin(), players_.end(), [id](const auto& player) { return player->getId() == id; });

    if (it != players_.end()) {
        return *it;
    }
    return nullptr;
}