#include "game/scenes/credits.hpp"

#include <SDL3/SDL.h>

#include <algorithm>  // Para min

#include "core/audio/audio.hpp"                        // Para Audio
#include "core/input/global_inputs.hpp"                // Para check
#include "core/input/input.hpp"                        // Para Input
#include "core/rendering/screen.hpp"                   // Para Screen
#include "core/rendering/surface.hpp"                  // Para Surface
#include "core/rendering/surface_animated_sprite.hpp"  // Para SAnimatedSprite
#include "core/rendering/text.hpp"                     // Para Text, Text::CENTER_FLAG, Text::COLOR_FLAG
#include "core/resources/resource_cache.hpp"           // Para Resource
#include "core/system/global_events.hpp"               // Para check
#include "game/options.hpp"                            // Para Options, options, OptionsGame, Sectio...
#include "game/scene_manager.hpp"                      // Para SceneManager
#include "utils/defines.hpp"                           // Para GAME_SPEED, PlayArea::CENTER_X, PLAY_...
#include "utils/delta_timer.hpp"                       // Para DeltaTimer
#include "utils/utils.hpp"                             // Para PaletteColor

// Constructor
Credits::Credits()
    : text_surface_(std::make_shared<Surface>(Options::game.width, Options::game.height)),
      cover_surface_(std::make_shared<Surface>(Options::game.width, Options::game.height)),
      shining_sprite_(std::make_shared<SurfaceAnimatedSprite>(Resource::Cache::get()->getAnimationData("shine.yaml"))),
      delta_timer_(std::make_unique<DeltaTimer>()) {
    // Configura la escena
    SceneManager::current = SceneManager::Scene::CREDITS;
    SceneManager::options = SceneManager::Options::NONE;
    shining_sprite_->setPos({194, 174, 8, 8});

    // Cambia el color del borde
    Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));

    // Escribe el texto en la textura
    fillTexture();
}

// Comprueba el manejador de eventos
void Credits::handleEvents() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        GlobalEvents::handle(event);
    }
}

// Comprueba las entradas
void Credits::handleInput() {
    Input::get()->update();
    GlobalInputs::handle();
}

// Inicializa los textos
void Credits::iniTexts() {
    std::string keys = "CURSORS";

    texts_.clear();
    texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"INSTRUCTIONS:", static_cast<Uint8>(PaletteColor::YELLOW)});
    texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"HELP JAILDOC TO GET BACK ALL", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"HIS PROJECTS AND GO TO THE", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"JAIL TO FINISH THEM", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});

    texts_.push_back({"KEYS:", static_cast<Uint8>(PaletteColor::YELLOW)});
    texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({keys + " TO MOVE AND JUMP", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"F8 TOGGLE THE MUSIC", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"F11 PAUSE THE GAME", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"F1-F2 WINDOWS SIZE", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"F3 TOGGLE FULLSCREEN", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"F9 TOOGLE BORDER SCREEN", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});

    texts_.push_back({"A GAME BY JAILDESIGNER", static_cast<Uint8>(PaletteColor::YELLOW)});
    texts_.push_back({"MADE ON SUMMER/FALL 2022", static_cast<Uint8>(PaletteColor::YELLOW)});
    texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});

    texts_.push_back({"I LOVE JAILGAMES! ", static_cast<Uint8>(PaletteColor::WHITE)});
    texts_.push_back({"", static_cast<Uint8>(PaletteColor::WHITE)});
}

// Escribe el texto en la textura
void Credits::fillTexture() {
    // Inicializa los textos
    iniTexts();

    // Rellena la textura de texto
    auto previuos_renderer = Screen::get()->getRendererSurface();
    Screen::get()->setRendererSurface(text_surface_);
    text_surface_->clear(static_cast<Uint8>(PaletteColor::BLACK));

    auto text = Resource::Cache::get()->getText("smb2");

    // Escribe el texto en la textura
    const int SIZE = text->getCharacterSize();
    int pos_y = 0;

    for (const auto& t : texts_) {
        text->writeDX(Text::CENTER_FLAG | Text::COLOR_FLAG, PlayArea::CENTER_X, pos_y * SIZE, t.label, 1, t.color);
        pos_y++;
    }

    // Escribe el corazón
    const int TEXT_LENGHT = text->length(texts_[22].label, 1) - text->length(" ", 1);  // Se resta el ultimo caracter que es un espacio
    const int POS_X = ((PlayArea::WIDTH - TEXT_LENGHT) / 2) + TEXT_LENGHT;
    text->writeColored(POS_X, 176, "}", static_cast<Uint8>(PaletteColor::BRIGHT_RED));
    Screen::get()->setRendererSurface(previuos_renderer);

    // Recoloca el sprite del brillo
    shining_sprite_->setPosX(POS_X + 2);

    // Rellena la textura que cubre el texto con color transparente
    cover_surface_->clear(static_cast<Uint8>(PaletteColor::TRANSPARENT));

    // Los primeros 8 pixels crea una malla
    auto color = static_cast<Uint8>(PaletteColor::BLACK);
    for (int i = 0; i < 256; i += 2) {
        cover_surface_->putPixel(i, 0, color);
        cover_surface_->putPixel(i, 2, color);
        cover_surface_->putPixel(i, 4, color);
        cover_surface_->putPixel(i, 6, color);

        cover_surface_->putPixel(i + 1, 5, color);
        cover_surface_->putPixel(i + 1, 7, color);
    }

    // El resto se rellena de color sólido
    SDL_FRect rect = {0, 8, 256, 192};
    cover_surface_->fillRect(&rect, color);
}

// Actualiza las variables
void Credits::update() {
    const float DELTA_TIME = delta_timer_->tick();
    total_time_ += DELTA_TIME;  // Actualiza el tiempo total

    handleEvents();  // Comprueba los eventos
    handleInput();   // Comprueba las entradas

    updateState(DELTA_TIME);  // Actualiza la máquina de estados

    // Actualiza el sprite con el brillo si está después del tiempo de inicio
    if (reveal_time_ > SHINE_START_TIME) {
        shining_sprite_->update(DELTA_TIME);
    }

    Audio::update();                    // Actualiza el objeto Audio
    Screen::get()->update(DELTA_TIME);  // Actualiza el objeto Screen
}

// Transición entre estados
void Credits::transitionToState(State new_state) {
    state_ = new_state;
    state_time_ = 0.0F;
}

// Actualiza la máquina de estados
void Credits::updateState(float delta_time) {
    state_time_ += delta_time;

    switch (state_) {
        case State::REVEALING_TEXT:
            reveal_time_ += delta_time;  // Incrementa reveal_time durante revelación
            if (state_time_ >= REVEAL_PHASE_1_DURATION) {
                transitionToState(State::PAUSE_1);
            }
            break;

        case State::PAUSE_1:
            // reveal_time_ NO incrementa durante pausa (se congela)
            if (state_time_ >= PAUSE_DURATION) {
                transitionToState(State::REVEALING_TEXT_2);
            }
            break;

        case State::REVEALING_TEXT_2:
            reveal_time_ += delta_time;  // Incrementa reveal_time durante revelación
            if (state_time_ >= REVEAL_PHASE_2_DURATION) {
                transitionToState(State::PAUSE_2);
            }
            break;

        case State::PAUSE_2:
            // reveal_time_ NO incrementa durante pausa (se congela)
            if (state_time_ >= PAUSE_DURATION) {
                transitionToState(State::REVEALING_TEXT_3);
            }
            break;

        case State::REVEALING_TEXT_3:
            reveal_time_ += delta_time;  // Incrementa reveal_time durante revelación
            if (state_time_ >= REVEAL_PHASE_3_DURATION) {
                transitionToState(State::PAUSE_3);
            }
            break;

        case State::PAUSE_3:
            // reveal_time_ NO incrementa durante pausa (se congela)
            if (state_time_ >= PAUSE_DURATION) {
                transitionToState(State::DISPLAYING_WITH_SHINE);
            }
            break;

        case State::DISPLAYING_WITH_SHINE:
            reveal_time_ += delta_time;  // Incrementa reveal_time durante revelación
            if (state_time_ >= DISPLAY_WITH_SHINE_DURATION) {
                transitionToState(State::FADING_OUT);
            }
            break;

        case State::FADING_OUT:
            reveal_time_ += delta_time;  // Incrementa reveal_time durante fade
            if (state_time_ >= FADE_OUT_DURATION) {
                transitionToState(State::EXITING);
            }
            break;

        case State::EXITING:
            SceneManager::current = SceneManager::Scene::DEMO;
            break;
    }
}

// Dibuja en pantalla
void Credits::render() {
    // Prepara para empezar a dibujar en la textura de juego
    Screen::get()->start();

    // Limpia la pantalla
    Screen::get()->clearSurface(static_cast<Uint8>(PaletteColor::BLACK));

    if (state_ != State::EXITING) {
        // Dibuja la textura con el texto en pantalla
        text_surface_->render(0, 0);

        // Dibuja la textura que cubre el texto
        // OFFSET basado en reveal_time_ (que se congela durante pausas, como counter_ original)
        const float OFFSET = std::min(reveal_time_ * REVEAL_SPEED / 8.0F, 192.0F / 2.0F);
        SDL_FRect src_rect = {0.0F, 0.0F, 256.0F, 192.0F - (OFFSET * 2.0F)};
        cover_surface_->render(0, static_cast<int>(OFFSET * 2.0F), &src_rect);

        // Dibuja el sprite con el brillo
        if (reveal_time_ > SHINE_START_TIME) {
            shining_sprite_->render(1, static_cast<Uint8>(PaletteColor::BRIGHT_WHITE));
        }
    }

    // Vuelca el contenido del renderizador en pantalla
    Screen::get()->render();
}

// Bucle para el logo del juego
void Credits::run() {
    while (SceneManager::current == SceneManager::Scene::CREDITS) {
        update();
        render();
    }
}