#include "intro.hpp"

#include <SDL3/SDL.h>  // Para SDL_GetTicks, SDL_SetRenderDrawColor, SDL_FRect, SDL_RenderFillRect, SDL_GetRenderTarget, SDL_RenderClear, SDL_RenderRect, SDL_SetRenderTarget, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_PollEvent, SDL_RenderTexture, SDL_TextureAccess, SDL_Event, Uint64

#include <array>       // Para array
#include <functional>  // Para function
#include <string>      // Para basic_string, string
#include <utility>     // Para move

#include "audio.hpp"          // Para Audio
#include "color.hpp"          // Para Color
#include "global_events.hpp"  // Para handle
#include "global_inputs.hpp"  // Para check
#include "input.hpp"          // Para Input
#include "lang.hpp"           // Para getText
#include "param.hpp"          // Para Param, param, ParamGame, ParamIntro, ParamTitle
#include "path_sprite.hpp"    // Para PathSprite, PathType
#include "resource.hpp"       // Para Resource
#include "screen.hpp"         // Para Screen
#include "section.hpp"        // Para Name, name, Options, options
#include "text.hpp"           // Para Text
#include "texture.hpp"        // Para Texture
#include "tiled_bg.hpp"       // Para TiledBG, TiledBGMode
#include "utils.hpp"          // Para Zone, easeInOutExpo, easeInElastic, easeOutBounce, easeOutElastic, easeOutQuad, easeOutQuint
#include "writer.hpp"         // Para Writer

// Constructor
Intro::Intro()
    : tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::DIAGONAL)) {
    // Inicializa variables
    Section::name = Section::Name::INTRO;
    Section::options = Section::Options::NONE;

    // Inicializa las imagens
    initSprites();

    // Inicializa los textos
    initTexts();

    // Configura el fondo
    tiled_bg_->setSpeed(TILED_BG_SPEED);
    tiled_bg_->setColor(bg_color_);
}

// Comprueba los eventos
void Intro::checkEvents() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        GlobalEvents::handle(event);
    }
}

// Comprueba las entradas
void Intro::checkInput() {
    Input::get()->update();
    GlobalInputs::check();
}

// Actualiza las escenas de la intro
void Intro::updateScenes() {
    switch (scene_) {
        case 0:
            updateScene0();
            break;
        case 1:
            updateScene1();
            break;
        case 2:
            updateScene2();
            break;
        case 3:
            updateScene3();
            break;
        case 4:
            updateScene4();
            break;
        case 5:
            updateScene5();
            break;
        default:
            break;
    }
}

void Intro::updateScene0() {
    // Primera imagen - UPV
    enableCardAndShadow(0);

    // Primer texto de la primera imagen
    if (card_sprites_.at(0)->hasFinished() && !texts_.at(0)->hasFinished()) {
        texts_.at(0)->setEnabled(true);
    }

    // Segundo texto de la primera imagen
    if (texts_.at(0)->hasFinished() && !texts_.at(1)->hasFinished()) {
        switchText(0, 1);
    }

    // Tercer texto de la primera imagen
    if (texts_.at(1)->hasFinished() && !texts_.at(2)->hasFinished()) {
        switchText(1, 2);
    }

    // Fin de la primera escena
    if (texts_.at(2)->hasFinished()) {
        texts_.at(2)->setEnabled(false);
        scene_++;
    }
}

void Intro::updateScene1() {
    // Segunda imagen - Máquina
    enableCardAndShadow(1);

    // Primer texto de la segunda imagen
    if (card_sprites_.at(1)->hasFinished() && !texts_.at(3)->hasFinished()) {
        texts_.at(3)->setEnabled(true);
    }

    // Fin de la segunda escena
    if (texts_.at(3)->hasFinished()) {
        texts_.at(3)->setEnabled(false);
        scene_++;
    }
}

void Intro::updateScene2() {
    // Tercera imagen junto con primer texto - GRITO
    if (!texts_.at(4)->hasFinished()) {
        enableCardAndShadow(2);
        texts_.at(4)->setEnabled(true);
    }

    // Fin de la tercera escena
    if (card_sprites_.at(2)->hasFinished() && texts_.at(4)->hasFinished()) {
        texts_.at(4)->setEnabled(false);
        scene_++;
    }
}

void Intro::updateScene3() {
    // Cuarta imagen junto con primer texto - Reflexión
    enableCardAndShadow(3);

    if (!texts_.at(5)->hasFinished()) {
        texts_.at(5)->setEnabled(true);
    }

    // Segundo texto de la cuarta imagen
    if (texts_.at(5)->hasFinished() && !texts_.at(6)->hasFinished()) {
        switchText(5, 6);
    }

    // Fin de la cuarta escena
    if (card_sprites_.at(3)->hasFinished() && texts_.at(6)->hasFinished()) {
        texts_.at(6)->setEnabled(false);
        scene_++;
    }
}

void Intro::updateScene4() {
    // Quinta imagen - Patada
    enableCardAndShadow(4);

    // Primer texto de la quinta imagen
    if (!texts_.at(7)->hasFinished()) {
        texts_.at(7)->setEnabled(true);
    }

    // Fin de la quinta escena
    if (card_sprites_.at(4)->hasFinished() && texts_.at(7)->hasFinished()) {
        texts_.at(7)->setEnabled(false);
        scene_++;
    }
}

void Intro::updateScene5() {
    // Sexta imagen junto con texto - Globos de café
    enableCardAndShadow(5);

    if (!texts_.at(8)->hasFinished()) {
        texts_.at(8)->setEnabled(true);
    }

    // Acaba el último texto
    if (texts_.at(8)->hasFinished()) {
        texts_.at(8)->setEnabled(false);
    }

    // Acaba la ultima imagen
    if (card_sprites_.at(5)->hasFinished() && texts_.at(8)->hasFinished()) {
        state_ = State::POST;
        state_start_time_ = SDL_GetTicks() / 1000.0F;
    }
}

// Helper methods to reduce code duplication
void Intro::enableCardAndShadow(int index) {
    card_sprites_.at(index)->enable();
    shadow_sprites_.at(index)->enable();
}

void Intro::switchText(int from_index, int to_index) {
    texts_.at(from_index)->setEnabled(false);
    texts_.at(to_index)->setEnabled(true);
}

// Actualiza las variables del objeto
void Intro::update(float delta_time) {
    static auto* const SCREEN = Screen::get();
    SCREEN->update(delta_time);  // Actualiza el objeto screen
    Audio::update();             // Actualiza el objeto Audio

    tiled_bg_->update(delta_time);  // Actualiza el fondo

    switch (state_) {
        case State::SCENES:
            updateSprites(delta_time);
            updateTexts(delta_time);
            updateScenes();
            break;

        case State::POST:
            updatePostState();
            break;
    }
}

// Dibuja el objeto en pantalla
void Intro::render() {
    static auto* const SCREEN = Screen::get();

    SCREEN->start();  // Prepara para empezar a dibujar en la textura de juego
    SCREEN->clean();  // Limpia la pantalla

    tiled_bg_->render();  // Dibuja el fondo

    switch (state_) {
        case State::SCENES: {
            renderTextRect();
            renderSprites();
            renderTexts();
            break;
        }
        case State::POST:
            break;
    }

    SCREEN->render();  // Vuelca el contenido del renderizador en pantalla
}

// Calcula el tiempo transcurrido desde el último frame
auto Intro::calculateDeltaTime() -> float {
    const Uint64 CURRENT_TIME = SDL_GetTicks();
    const float DELTA_TIME = static_cast<float>(CURRENT_TIME - last_time_) / 1000.0F;  // Convertir ms a segundos
    last_time_ = CURRENT_TIME;
    return DELTA_TIME;
}

// Bucle principal
void Intro::run() {
    last_time_ = SDL_GetTicks();
    Audio::get()->playMusic("intro.ogg", 0);

    while (Section::name == Section::Name::INTRO) {
        const float DELTA_TIME = calculateDeltaTime();

        checkInput();
        update(DELTA_TIME);
        checkEvents();  // Tiene que ir antes del render
        render();
    }
}

// Inicializa las imagens
void Intro::initSprites() {
    // Listado de imagenes a usar
    const std::array<std::string, 6> TEXTURE_LIST = {
        "intro1.png",
        "intro2.png",
        "intro3.png",
        "intro4.png",
        "intro5.png",
        "intro6.png"};

    // Constantes
    constexpr int TOTAL_SPRITES = TEXTURE_LIST.size();
    const float BORDER = CARD_BORDER_SIZE;

    auto texture = Resource::get()->getTexture(TEXTURE_LIST.front());
    const float CARD_WIDTH = texture->getWidth() + (BORDER * 2);
    const float CARD_HEIGHT = texture->getHeight() + (BORDER * 2);

    // Crea las texturas para las tarjetas
    std::vector<std::shared_ptr<Texture>> card_textures;

    for (int i = 0; i < TOTAL_SPRITES; ++i) {
        // Crea la textura
        auto card_texture = std::make_unique<Texture>(Screen::get()->getRenderer());
        card_texture->createBlank(CARD_WIDTH, CARD_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
        card_texture->setBlendMode(SDL_BLENDMODE_BLEND);

        // Apuntamos el renderizador a la textura
        auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer());
        card_texture->setAsRenderTarget(Screen::get()->getRenderer());

        // Limpia la textura
        SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0);
        SDL_RenderClear(Screen::get()->getRenderer());

        // Pone color en el marco de la textura
        auto color = param.intro.card_color;
        SDL_SetRenderDrawColor(Screen::get()->getRenderer(), color.r, color.g, color.b, color.a);
        SDL_FRect rect1 = {1, 0, CARD_WIDTH - 2, CARD_HEIGHT};
        SDL_FRect rect2 = {0, 1, CARD_WIDTH, CARD_HEIGHT - 2};
        SDL_RenderRect(Screen::get()->getRenderer(), &rect1);
        SDL_RenderRect(Screen::get()->getRenderer(), &rect2);

        // Copia la textura con la imagen dentro del marco
        SDL_FRect dest = {BORDER, BORDER, CARD_WIDTH - (BORDER * 2), CARD_HEIGHT - (BORDER * 2)};
        SDL_RenderTexture(Screen::get()->getRenderer(), Resource::get()->getTexture(TEXTURE_LIST.at(i))->getSDLTexture(), nullptr, &dest);

        // Deja el renderizador como estaba y añade la textura a la lista
        SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);
        card_textures.push_back(std::move(card_texture));
    }

    // Inicializa los sprites para las tarjetas
    for (int i = 0; i < TOTAL_SPRITES; ++i) {
        auto sprite = std::make_unique<PathSprite>(card_textures.at(i));
        sprite->setWidth(CARD_WIDTH);
        sprite->setHeight(CARD_HEIGHT);
        sprite->setSpriteClip(0, 0, CARD_WIDTH, CARD_HEIGHT);
        card_sprites_.push_back(std::move(sprite));
    }

    const float X_DEST = param.game.game_area.center_x - (CARD_WIDTH / 2);
    const float Y_DEST = param.game.game_area.first_quarter_y - (CARD_HEIGHT / 4);

    card_sprites_.at(0)->addPath(-CARD_WIDTH - CARD_OFFSET_MARGIN, X_DEST, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_NORMAL, easeInOutExpo, 0.0F);
    card_sprites_.at(1)->addPath(param.game.width, X_DEST, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_NORMAL, easeOutBounce, 0.0F);
    card_sprites_.at(2)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_FAST, easeOutQuint, 0.0F);
    card_sprites_.at(3)->addPath(param.game.height, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_VERY_SLOW, easeInOutExpo, 0.0F);
    card_sprites_.at(4)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_MEDIUM, easeOutElastic, 0.0F);
    card_sprites_.at(5)->addPath(-CARD_HEIGHT, Y_DEST, PathType::VERTICAL, X_DEST, CARD_ANIM_DURATION_SLOW, easeOutQuad, CARD_ANIM_DELAY_LONG_S);
    card_sprites_.at(5)->addPath(X_DEST, -CARD_WIDTH, PathType::HORIZONTAL, Y_DEST, CARD_ANIM_DURATION_SHORT, easeInElastic, 0.0F);

    // Constantes
    const float DESP = SHADOW_OFFSET;
    const float SHADOW_SPRITE_WIDTH = CARD_WIDTH;
    const float SHADOW_SPRITE_HEIGHT = CARD_HEIGHT;

    // Crea la textura para las sombras de las tarjetas
    auto shadow_texture = std::make_shared<Texture>(Screen::get()->getRenderer());
    shadow_texture->createBlank(SHADOW_SPRITE_WIDTH, SHADOW_SPRITE_HEIGHT, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET);
    shadow_texture->setBlendMode(SDL_BLENDMODE_BLEND);

    // Apuntamos el renderizador a la textura
    auto* temp = SDL_GetRenderTarget(Screen::get()->getRenderer());
    shadow_texture->setAsRenderTarget(Screen::get()->getRenderer());

    // Limpia la textura
    SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0);
    SDL_RenderClear(Screen::get()->getRenderer());

    // Dibuja la sombra sobre la textura
    auto shadow_color = param.intro.shadow_color;
    SDL_SetRenderDrawColor(Screen::get()->getRenderer(), shadow_color.r, shadow_color.g, shadow_color.b, Color::MAX_ALPHA_VALUE);
    SDL_FRect rect1 = {1, 0, SHADOW_SPRITE_WIDTH - 2, SHADOW_SPRITE_HEIGHT};
    SDL_FRect rect2 = {0, 1, SHADOW_SPRITE_WIDTH, SHADOW_SPRITE_HEIGHT - 2};
    SDL_RenderFillRect(Screen::get()->getRenderer(), &rect1);
    SDL_RenderFillRect(Screen::get()->getRenderer(), &rect2);

    // Deja el renderizador como estaba y añade la textura a la lista
    SDL_SetRenderTarget(Screen::get()->getRenderer(), temp);

    // Inicializa los sprites para la sombras usando la texturas con la sombra
    for (int i = 0; i < TOTAL_SPRITES; ++i) {
        auto shadow_color = param.intro.shadow_color;
        auto sprite = std::make_unique<PathSprite>(shadow_texture);
        sprite->setWidth(SHADOW_SPRITE_WIDTH);
        sprite->setHeight(SHADOW_SPRITE_HEIGHT);
        sprite->setSpriteClip(0, 0, SHADOW_SPRITE_WIDTH, SHADOW_SPRITE_HEIGHT);
        sprite->getTexture()->setAlpha(shadow_color.a);
        shadow_sprites_.push_back(std::move(sprite));
    }

    const float S_X_DEST = X_DEST + DESP;
    const float S_Y_DEST = Y_DEST + DESP;

    shadow_sprites_.at(0)->addPath(param.game.height + CARD_OFFSET_MARGIN, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_NORMAL, easeInOutExpo, 0.0F);
    shadow_sprites_.at(1)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_NORMAL, easeOutBounce, 0.0F);
    shadow_sprites_.at(2)->addPath(-SHADOW_SPRITE_WIDTH, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_FAST, easeOutQuint, 0.0F);
    shadow_sprites_.at(3)->addPath(-SHADOW_SPRITE_HEIGHT, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_VERY_SLOW, easeInOutExpo, 0.0F);
    shadow_sprites_.at(4)->addPath(param.game.height, S_Y_DEST, PathType::VERTICAL, S_X_DEST, CARD_ANIM_DURATION_MEDIUM, easeOutElastic, 0.0F);
    shadow_sprites_.at(5)->addPath(param.game.width, S_X_DEST, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_SLOW, easeOutQuad, CARD_ANIM_DELAY_LONG_S);
    shadow_sprites_.at(5)->addPath(S_X_DEST, param.game.width, PathType::HORIZONTAL, S_Y_DEST, CARD_ANIM_DURATION_SHORT, easeInElastic, 0.0F);
}

// Inicializa los textos
void Intro::initTexts() {
    constexpr int TOTAL_TEXTS = 9;
    for (int i = 0; i < TOTAL_TEXTS; ++i) {
        auto writer = std::make_unique<Writer>(Resource::get()->getText("04b_25_metal"));
        writer->setPosX(0);
        writer->setPosY(param.game.height - param.intro.text_distance_from_bottom);
        writer->setKerning(TEXT_KERNING);
        writer->setEnabled(false);
        writer->setFinishedTimerS(TEXT_DISPLAY_DURATION_S);
        texts_.push_back(std::move(writer));
    }

    // Un dia qualsevol de l'any 2000
    texts_.at(0)->setCaption(Lang::getText("[INTRO] 1"));
    texts_.at(0)->setSpeedS(TEXT_SPEED_NORMAL);

    // Tot esta tranquil a la UPV
    texts_.at(1)->setCaption(Lang::getText("[INTRO] 2"));
    texts_.at(1)->setSpeedS(TEXT_SPEED_NORMAL);

    // Fins que un desaprensiu...
    texts_.at(2)->setCaption(Lang::getText("[INTRO] 3"));
    texts_.at(2)->setSpeedS(TEXT_SPEED_FAST);

    // HEY! ME ANE A FERME UN CORTAET...
    texts_.at(3)->setCaption(Lang::getText("[INTRO] 4"));
    texts_.at(3)->setSpeedS(TEXT_SPEED_NORMAL);

    // UAAAAAAAAAAAAA!!!
    texts_.at(4)->setCaption(Lang::getText("[INTRO] 5"));
    texts_.at(4)->setSpeedS(TEXT_SPEED_VERY_SLOW);

    // Espera un moment...
    texts_.at(5)->setCaption(Lang::getText("[INTRO] 6"));
    texts_.at(5)->setSpeedS(TEXT_SPEED_VERY_FAST);

    // Si resulta que no tinc solt!
    texts_.at(6)->setCaption(Lang::getText("[INTRO] 7"));
    texts_.at(6)->setSpeedS(TEXT_SPEED_SLOW);

    // MERDA DE MAQUINA!
    texts_.at(7)->setCaption(Lang::getText("[INTRO] 8"));
    texts_.at(7)->setSpeedS(TEXT_SPEED_MEDIUM_SLOW);

    // Blop... blop... blop...
    texts_.at(8)->setCaption(Lang::getText("[INTRO] 9"));
    texts_.at(8)->setSpeedS(TEXT_SPEED_ULTRA_FAST);

    for (auto& text : texts_) {
        text->center(param.game.game_area.center_x);
    }
}

// Actualiza los sprites
void Intro::updateSprites(float delta_time) {
    for (auto& sprite : card_sprites_) {
        sprite->update(delta_time);
    }

    for (auto& sprite : shadow_sprites_) {
        sprite->update(delta_time);
    }
}

// Actualiza los textos
void Intro::updateTexts(float delta_time) {
    for (auto& text : texts_) {
        text->updateS(delta_time);  // Usar updateS para delta_time en segundos
    }
}

// Dibuja los sprites
void Intro::renderSprites() {
    shadow_sprites_.at(scene_)->render();
    card_sprites_.at(scene_)->render();
}

// Dibuja los textos
void Intro::renderTexts() {
    for (const auto& text : texts_) {
        text->render();
    }
}

// Actualiza el estado POST
void Intro::updatePostState() {
    const float ELAPSED_TIME = (SDL_GetTicks() / 1000.0F) - state_start_time_;

    switch (post_state_) {
        case PostState::STOP_BG:
            // EVENTO: Detiene el fondo después del tiempo especificado
            if (ELAPSED_TIME >= POST_BG_STOP_DELAY_S) {
                tiled_bg_->stopGracefully();

                if (!bg_color_.IS_EQUAL_TO(param.title.bg_color)) {
                    bg_color_ = bg_color_.APPROACH_TO(param.title.bg_color, 1);
                }

                tiled_bg_->setColor(bg_color_);
            }

            // Cambia de estado si el fondo se ha detenido y recuperado el color
            if (tiled_bg_->isStopped() && bg_color_.IS_EQUAL_TO(param.title.bg_color)) {
                post_state_ = PostState::END;
                state_start_time_ = SDL_GetTicks() / 1000.0F;
            }
            break;

        case PostState::END:
            // Finaliza la intro después del tiempo especificado
            if (ELAPSED_TIME >= POST_END_DELAY_S) {
                Audio::get()->stopMusic();
                Section::name = Section::Name::TITLE;
                Section::options = Section::Options::TITLE_1;
            }
            break;

        default:
            break;
    }
}

void Intro::renderTextRect() {
    static const float HEIGHT = Resource::get()->getText("04b_25_metal")->getCharacterSize();
    static SDL_FRect rect_ = {0.0F, param.game.height - param.intro.text_distance_from_bottom - HEIGHT, param.game.width, HEIGHT * 3};
    SDL_SetRenderDrawColor(Screen::get()->getRenderer(), param.intro.shadow_color.r, param.intro.shadow_color.g, param.intro.shadow_color.b, param.intro.shadow_color.a);
    SDL_RenderFillRect(Screen::get()->getRenderer(), &rect_);
}