#include "game/scenes/ending.hpp"

#include <SDL3/SDL.h>

#include "core/audio/audio.hpp"               // Para Audio
#include "core/input/global_inputs.hpp"       // Para check
#include "core/input/input.hpp"               // Para Input
#include "core/locale/locale.hpp"             // Para Locale
#include "core/rendering/pixel_reveal.hpp"    // Para PixelReveal
#include "core/rendering/screen.hpp"          // Para Screen
#include "core/rendering/sprite/sprite.hpp"   // Para SSprite
#include "core/rendering/surface.hpp"         // Para Surface
#include "core/rendering/text.hpp"            // Para Text, TEXT_STROKE
#include "core/resources/resource_cache.hpp"  // Para Resource
#include "core/system/global_events.hpp"      // Para check
#include "game/options.hpp"                   // Para Options, options, OptionsGame, SectionS...
#include "game/scene_manager.hpp"             // Para SceneManager
#include "utils/delta_timer.hpp"              // Para DeltaTimer
#include "utils/utils.hpp"                    // Para PaletteColor

// Destructor
Ending::~Ending() {
    Audio::get()->stopMusic();
}

// Constructor
Ending::Ending()
    : delta_timer_(std::make_unique<DeltaTimer>()) {
    SceneManager::current = SceneManager::Scene::ENDING;
    SceneManager::options = SceneManager::Options::NONE;

    iniTexts();   // Inicializa los textos
    iniPics();    // Inicializa las imagenes
    iniScenes();  // Inicializa las escenas

    Screen::get()->setBorderColor(static_cast<Uint8>(PaletteColor::BLACK));  // Cambia el color del borde
    Audio::get()->playMusic("ending1.ogg");
}

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

    handleInput();  // Comprueba las entradas

    updateState(DELTA_TIME);  // Actualiza la máquina de estados
    updateSpriteCovers();     // Actualiza las cortinillas de los elementos

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

// Dibuja el final en pantalla
void Ending::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));

    // Skip rendering durante WARMING_UP
    if (state_ != State::WARMING_UP) {
        // Dibuja las imagenes de la escena
        const auto& pic = sprite_pics_.at(current_scene_);
        pic.image_sprite->render();
        pic.pixel_reveal->render(pic.pos_x, pic.pos_y);

        // Dibuja los textos de la escena
        for (const auto& ti : scenes_.at(current_scene_).text_index) {
            // Convertir trigger de frames a segundos @ 60fps
            const float TRIGGER_TIME = static_cast<float>(ti.trigger) / 60.0F;

            if (state_time_ > TRIGGER_TIME) {
                const auto& txt = sprite_texts_.at(ti.index);
                txt.image_sprite->render();
                txt.pixel_reveal->render(txt.pos_x, txt.pos_y);
            }
        }

        // Dibuja la cortinilla de cambio de escena
        if (scene_cover_) {
            scene_cover_->render(0, 0);
        }
    }

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

// Despatx d'un event (SDL3 Callback API)
void Ending::handleEvent(const SDL_Event& event) {
    GlobalEvents::handle(event);
}

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

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

    // Al cambiar a una escena, resetear la cortinilla de salida y el contador de fade
    if (new_state != State::WARMING_UP && new_state != State::ENDING) {
        fadeout_time_ = 0.0F;
        scene_cover_.reset();
    }
}

// Lógica de fade común a los estados SCENE_N
void Ending::handleSceneFadeout(float scene_duration, float delta_time) {
    if (state_time_ >= scene_duration - FADEOUT_START_OFFSET) {
        fadeout_time_ += delta_time;
        if (!scene_cover_) {
            scene_cover_ = std::make_unique<PixelReveal>(Options::game.width, Options::game.height, COVER_PIXELS_PER_SECOND, STEP_DURATION, COVER_STEPS, true);
        }
        scene_cover_->update(fadeout_time_);
    }
}

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

    switch (state_) {
        case State::WARMING_UP:
            if (state_time_ >= WARMUP_DURATION) {
                transitionToState(State::SCENE_0);
                current_scene_ = 0;
            }
            break;

        case State::SCENE_0:
            checkChangeScene();
            handleSceneFadeout(SCENE_0_DURATION, delta_time);
            break;

        case State::SCENE_1:
            checkChangeScene();
            handleSceneFadeout(SCENE_1_DURATION, delta_time);
            break;

        case State::SCENE_2:
            checkChangeScene();
            handleSceneFadeout(SCENE_2_DURATION, delta_time);
            break;

        case State::SCENE_3:
            checkChangeScene();
            handleSceneFadeout(SCENE_3_DURATION, delta_time);
            break;

        case State::SCENE_4:
            checkChangeScene();
            handleSceneFadeout(SCENE_4_DURATION, delta_time);
            break;

        case State::ENDING:
            // Esperar ENDING_DURATION y luego transicionar a ENDING2
            if (state_time_ >= ENDING_DURATION) {
                SceneManager::current = SceneManager::Scene::ENDING2;
            }
            break;
    }
}

// Inicializa los textos
void Ending::iniTexts() {
    // Vector con los textos (traducidos según el idioma activo)
    std::vector<TextAndPosition> texts;
    const auto* loc = Locale::get();

    // Escena #0
    texts.push_back({.caption = loc->get("ending.t0"), .pos = 32});
    texts.push_back({.caption = loc->get("ending.t1"), .pos = 42});
    texts.push_back({.caption = loc->get("ending.t2"), .pos = 142});
    texts.push_back({.caption = loc->get("ending.t3"), .pos = 152});

    // Escena #1
    texts.push_back({.caption = loc->get("ending.t4"), .pos = 1});
    texts.push_back({.caption = loc->get("ending.t5"), .pos = 11});
    texts.push_back({.caption = loc->get("ending.t6"), .pos = 21});

    texts.push_back({.caption = loc->get("ending.t7"), .pos = 161});
    texts.push_back({.caption = loc->get("ending.t8"), .pos = 171});

    texts.push_back({.caption = loc->get("ending.t9"), .pos = 181});

    // Escena #2
    texts.push_back({.caption = loc->get("ending.t10"), .pos = 19});
    texts.push_back({.caption = loc->get("ending.t11"), .pos = 29});

    // Escena #3
    texts.push_back({.caption = loc->get("ending.t12"), .pos = 36});
    texts.push_back({.caption = loc->get("ending.t13"), .pos = 46});

    // Escena #4
    texts.push_back({.caption = loc->get("ending.t14"), .pos = 36});
    texts.push_back({.caption = loc->get("ending.t15"), .pos = 46});
    texts.push_back({.caption = loc->get("ending.t16"), .pos = 158});

    // Crea los sprites
    sprite_texts_.clear();

    for (const auto& txt : texts) {
        auto text = Resource::Cache::get()->getText("smb2");

        const float WIDTH = text->length(txt.caption, 1) + 2 + 2;
        const float HEIGHT = text->getCharacterSize() + 2 + 2;
        auto text_color = static_cast<Uint8>(PaletteColor::WHITE);
        auto shadow_color = static_cast<Uint8>(PaletteColor::BLACK);

        EndingSurface st;

        // Crea la textura
        st.image_surface = std::make_shared<Surface>(WIDTH, HEIGHT);
        auto previuos_renderer = Screen::get()->getRendererSurface();
        Screen::get()->setRendererSurface(st.image_surface);
        text->writeDX(Text::STROKE_FLAG, 2, 2, txt.caption, 1, text_color, 2, shadow_color);

        // Crea el sprite
        st.image_sprite = std::make_shared<Sprite>(st.image_surface, 0, 0, st.image_surface->getWidth(), st.image_surface->getHeight());
        st.pos_x = static_cast<int>((Options::game.width - st.image_surface->getWidth()) / 2);
        st.pos_y = txt.pos;
        st.image_sprite->setPosition(st.pos_x, st.pos_y);

        // Crea el efecto de revelado pixel a pixel
        st.pixel_reveal = std::make_unique<PixelReveal>(static_cast<int>(WIDTH), static_cast<int>(HEIGHT), TEXT_PIXELS_PER_SECOND, STEP_DURATION, REVEAL_STEPS);

        sprite_texts_.push_back(std::move(st));
        Screen::get()->setRendererSurface(previuos_renderer);
    }
}

// Inicializa las imagenes
void Ending::iniPics() {
    // Vector con las rutas y la posición
    std::vector<TextAndPosition> pics;

    pics.push_back({.caption = "ending1.gif", .pos = 48});
    pics.push_back({.caption = "ending2.gif", .pos = 26});
    pics.push_back({.caption = "ending3.gif", .pos = 29});
    pics.push_back({.caption = "ending4.gif", .pos = 63});
    pics.push_back({.caption = "ending5.gif", .pos = 53});

    // Crea los sprites
    sprite_pics_.clear();

    for (const auto& pic : pics) {
        EndingSurface sp;

        // Crea la texture
        sp.image_surface = Resource::Cache::get()->getSurface(pic.caption);
        sp.image_surface->setTransparentColor();
        const float WIDTH = sp.image_surface->getWidth();
        const float HEIGHT = sp.image_surface->getHeight();

        // Crea el sprite
        sp.pos_x = static_cast<int>((Options::game.width - WIDTH) / 2);
        sp.pos_y = pic.pos;
        sp.image_sprite = std::make_shared<Sprite>(sp.image_surface, 0, 0, WIDTH, HEIGHT);
        sp.image_sprite->setPosition(sp.pos_x, sp.pos_y);

        // Crea el efecto de revelado pixel a pixel
        sp.pixel_reveal = std::make_unique<PixelReveal>(static_cast<int>(WIDTH), static_cast<int>(HEIGHT), IMAGE_PIXELS_PER_SECOND, STEP_DURATION, REVEAL_STEPS);

        sprite_pics_.push_back(std::move(sp));
    }
}

// Inicializa las escenas
void Ending::iniScenes() {
    // Variable para los tiempos
    int trigger;
    constexpr int LAPSE = 80;

    // Crea el contenedor
    SceneData sc;

    // Inicializa el vector
    scenes_.clear();

    // Crea la escena #0
    sc.counter_end = 1000;
    sc.picture_index = 0;
    sc.text_index.clear();
    trigger = 85 * 2;
    trigger += LAPSE;
    sc.text_index.push_back({.index = 0, .trigger = trigger});
    trigger += LAPSE;
    sc.text_index.push_back({.index = 1, .trigger = trigger});
    trigger += LAPSE * 3;
    sc.text_index.push_back({.index = 2, .trigger = trigger});
    trigger += LAPSE;
    sc.text_index.push_back({.index = 3, .trigger = trigger});
    scenes_.push_back(sc);

    // Crea la escena #1
    sc.counter_end = 1400;
    sc.picture_index = 1;
    sc.text_index.clear();
    trigger = 140 * 2;
    trigger += LAPSE;
    sc.text_index.push_back({.index = 4, .trigger = trigger});
    trigger += LAPSE;
    sc.text_index.push_back({.index = 5, .trigger = trigger});
    trigger += LAPSE;
    sc.text_index.push_back({.index = 6, .trigger = trigger});
    trigger += LAPSE * 3;
    sc.text_index.push_back({.index = 7, .trigger = trigger});
    trigger += LAPSE;
    sc.text_index.push_back({.index = 8, .trigger = trigger});
    trigger += LAPSE * 3;
    sc.text_index.push_back({.index = 9, .trigger = trigger});
    scenes_.push_back(sc);

    // Crea la escena #2
    sc.counter_end = 1000;
    sc.picture_index = 2;
    sc.text_index.clear();
    trigger = 148 / 2;
    trigger += LAPSE;
    sc.text_index.push_back({.index = 10, .trigger = trigger});
    trigger += LAPSE;
    sc.text_index.push_back({.index = 11, .trigger = trigger});
    scenes_.push_back(sc);

    // Crea la escena #3
    sc.counter_end = 800;
    sc.picture_index = 3;
    sc.text_index.clear();
    trigger = 87 / 2;
    trigger += LAPSE;
    sc.text_index.push_back({.index = 12, .trigger = trigger});
    trigger += LAPSE / 2;
    sc.text_index.push_back({.index = 13, .trigger = trigger});
    scenes_.push_back(sc);

    // Crea la escena #4
    sc.counter_end = 1000;
    sc.picture_index = 4;
    sc.text_index.clear();
    trigger = 91 * 2;
    trigger += LAPSE;
    sc.text_index.push_back({.index = 14, .trigger = trigger});
    trigger += LAPSE * 2;
    sc.text_index.push_back({.index = 15, .trigger = trigger});
    trigger += LAPSE * 3;
    sc.text_index.push_back({.index = 16, .trigger = trigger});
    scenes_.push_back(sc);
}

// Un frame de l'escena (SDL3 Callback API)
void Ending::iterate() {
    update();
    render();
}

// Actualiza las cortinillas de los elementos
void Ending::updateSpriteCovers() {
    // Skip durante WARMING_UP
    if (state_ == State::WARMING_UP) {
        return;
    }

    // Actualiza el revelado de los textos
    for (const auto& ti : scenes_.at(current_scene_).text_index) {
        const float TRIGGER_TIME = static_cast<float>(ti.trigger) / 60.0F;

        if (state_time_ > TRIGGER_TIME) {
            const float TIME_SINCE_TRIGGER = state_time_ - TRIGGER_TIME;
            sprite_texts_.at(ti.index).pixel_reveal->update(TIME_SINCE_TRIGGER);
        }
    }

    // Actualiza el revelado de la imagen (desde el inicio de la escena)
    sprite_pics_.at(current_scene_).pixel_reveal->update(state_time_);
}

// Comprueba si se ha de cambiar de escena
void Ending::checkChangeScene() {
    // Obtener duración de la escena actual
    float current_duration = 0.0F;
    State next_state = State::ENDING;

    switch (state_) {
        case State::SCENE_0:
            current_duration = SCENE_0_DURATION;
            next_state = State::SCENE_1;
            break;
        case State::SCENE_1:
            current_duration = SCENE_1_DURATION;
            next_state = State::SCENE_2;
            break;
        case State::SCENE_2:
            current_duration = SCENE_2_DURATION;
            next_state = State::SCENE_3;
            break;
        case State::SCENE_3:
            current_duration = SCENE_3_DURATION;
            next_state = State::SCENE_4;
            break;
        case State::SCENE_4:
            current_duration = SCENE_4_DURATION;
            next_state = State::ENDING;
            break;
        default:
            return;
    }

    // Comprobar si ha pasado la duración de la escena
    if (state_time_ >= current_duration) {
        if (next_state == State::ENDING) {
            // Transición al estado ENDING con fade de audio
            transitionToState(State::ENDING);
            Audio::get()->fadeOutMusic(MUSIC_FADE_DURATION);
        } else {
            // Transición a la siguiente escena
            current_scene_++;
            transitionToState(next_state);
        }
    }
}
