#include "ending2.h"
#include <SDL2/SDL_blendmode.h> // for SDL_BLENDMODE_BLEND
#include <SDL2/SDL_events.h>    // for SDL_PollEvent, SDL_Event
#include <SDL2/SDL_rect.h>      // for SDL_Rect
#include <SDL2/SDL_timer.h>     // for SDL_GetTicks
#include <algorithm>            // for max, min, replace
#include "animated_sprite.h"    // for AnimatedSprite
#include "asset.h"              // for Asset
#include "defines.h"            // for GAMECANVAS_HEIGHT, GAMECANVAS_CENTER_X
#include "global_events.h"      // for check
#include "global_inputs.h"      // for check
#include "input.h"              // for Input
#include "jail_audio.h"         // for JA_SetVolume, JA_PlayMusic, JA_StopM...
#include "moving_sprite.h"      // for MovingSprite
#include "options.h"            // for Options, options, OptionsVideo, Sect...
#include "resource.h"           // for Resource
#include "screen.h"             // for Screen
#include "text.h"               // for Text
#include "texture.h"            // for Texture
#include "utils.h"              // for Color, stringToColor

// Constructor
Ending2::Ending2()
    : counter_enabled_(false),
      pre_counter_(0),
      post_counter_(0),
      post_counter_enabled_(false),
      ticks_(0)
{
    options.section.section = Section::ENDING2;
    options.section.subsection = Subsection::NONE;

    // Inicializa el vector de colores
    const std::vector<std::string> color_list = {"white", "yellow", "cyan", "green", "magenta", "red", "blue", "black"};
    for (const auto &color : color_list)
    {
        colors_.push_back(stringToColor(options.video.palette, color));
    }

    // Cambia el color del borde
    Screen::get()->setBorderColor(stringToColor(options.video.palette, "black"));

    // Inicializa la lista de sprites
    iniSpriteList();

    // Carga todos los sprites desde una lista
    loadSprites();

    // Coloca los sprites en su sito
    placeSprites();

    // Crea los sprites con las texturas con los textos
    createSpriteTexts();

    // Crea los sprites con las texturas con los textos del final
    createTexts();
}

// Actualiza el objeto
void Ending2::update()
{
    // Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
    if (SDL_GetTicks() - ticks_ > GAME_SPEED)
    {
        // Actualiza el contador de ticks
        ticks_ = SDL_GetTicks();

        // Comprueba las entradas
        checkInput();

        // Actualiza los contadores
        updateCounters();

        if (counter_enabled_)
        {
            // Actualiza los sprites
            updateSprites();

            // Actualiza los sprites de texto
            updateTextSprites();

            // Actualiza los sprites de texto del final
            updateTexts();
        }

        // Actualiza el fade final
        updateFinalFade();

        // Actualiza el volumen de la musica
        updateMusicVolume();

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

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

    // Limpia la pantalla
    Screen::get()->clean(stringToColor(options.video.palette, "black"));

    // Dibuja los sprites
    renderSprites();

    // Dibuja los sprites con el texto
    renderSpriteTexts();

    // Dibuja los sprites con el texto del final
    renderTexts();

    // Dibuja una trama arriba y abajo
    SDL_SetRenderDrawColor(Screen::get()->getRenderer(), 0, 0, 0, 0xFF);
    for (int i = 0; i < 256; i += 2)
    {
        SDL_RenderDrawPoint(Screen::get()->getRenderer(), i + 0, 0);
        SDL_RenderDrawPoint(Screen::get()->getRenderer(), i + 1, 1);
        SDL_RenderDrawPoint(Screen::get()->getRenderer(), i + 0, 2);
        SDL_RenderDrawPoint(Screen::get()->getRenderer(), i + 1, 3);

        SDL_RenderDrawPoint(Screen::get()->getRenderer(), i, 4);
        SDL_RenderDrawPoint(Screen::get()->getRenderer(), i, 6);

        SDL_RenderDrawPoint(Screen::get()->getRenderer(), i + 0, 191);
        SDL_RenderDrawPoint(Screen::get()->getRenderer(), i + 1, 190);
        SDL_RenderDrawPoint(Screen::get()->getRenderer(), i + 0, 189);
        SDL_RenderDrawPoint(Screen::get()->getRenderer(), i + 1, 188);

        SDL_RenderDrawPoint(Screen::get()->getRenderer(), i, 187);
        SDL_RenderDrawPoint(Screen::get()->getRenderer(), i, 185);
    }

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

// Comprueba el manejador de eventos
void Ending2::checkEvents()
{
    SDL_Event event;
    while (SDL_PollEvent(&event))
    {
        globalEvents::check(event);
    }
}

// Comprueba las entradas
void Ending2::checkInput()
{
    globalInputs::check();
}

// Bucle principal
void Ending2::run()
{
    JA_PlayMusic(Resource::get()->getMusic("ending2.ogg"));

    while (options.section.section == Section::ENDING2)
    {
        update();
        checkEvents();
        render();
    }

    JA_StopMusic();
    JA_SetVolume(128);
}

// Actualiza los contadores
void Ending2::updateCounters()
{
    // Incrementa el contador
    if (pre_counter_ < 200)
    {
        pre_counter_++;
    }
    else
    {
        counter_enabled_ = true;
    }

    if (post_counter_enabled_)
    {
        post_counter_++;
    }

    if (post_counter_ > 600)
    {
        options.section.section = Section::LOGO;
        options.section.subsection = Subsection::LOGO_TO_INTRO;
    }
}

// Inicializa la lista de sprites
void Ending2::iniSpriteList()
{
    // Reinicia el vector
    sprite_list_.clear();

    // Añade los valores
    sprite_list_.push_back("bin");
    sprite_list_.push_back("floppy");
    sprite_list_.push_back("bird");
    sprite_list_.push_back("chip");
    sprite_list_.push_back("jeannine");
    sprite_list_.push_back("spark");
    sprite_list_.push_back("code");
    sprite_list_.push_back("paco");
    sprite_list_.push_back("elsa");
    sprite_list_.push_back("z80");

    sprite_list_.push_back("bell");
    sprite_list_.push_back("dong");

    sprite_list_.push_back("amstrad_cs");
    sprite_list_.push_back("breakout");

    sprite_list_.push_back("flying_arounder");
    sprite_list_.push_back("stopped_arounder");
    sprite_list_.push_back("walking_arounder");
    sprite_list_.push_back("arounders_door");
    sprite_list_.push_back("arounders_machine");

    sprite_list_.push_back("abad");
    sprite_list_.push_back("abad_bell");
    sprite_list_.push_back("lord_abad");

    sprite_list_.push_back("bat");
    sprite_list_.push_back("batman_bell");
    sprite_list_.push_back("batman_fire");
    sprite_list_.push_back("batman");

    sprite_list_.push_back("demon");
    sprite_list_.push_back("heavy");
    sprite_list_.push_back("dimallas");
    sprite_list_.push_back("guitar");

    sprite_list_.push_back("jailbattle_alien");
    sprite_list_.push_back("jailbattle_human");

    sprite_list_.push_back("jailer_#1");
    sprite_list_.push_back("jailer_#2");
    sprite_list_.push_back("jailer_#3");
    sprite_list_.push_back("bry");
    sprite_list_.push_back("upv_student");

    sprite_list_.push_back("lamp");
    sprite_list_.push_back("robot");
    sprite_list_.push_back("congo");
    sprite_list_.push_back("crosshair");
    sprite_list_.push_back("tree_thing");

    sprite_list_.push_back("matatunos");
    sprite_list_.push_back("tuno");

    sprite_list_.push_back("mummy");
    sprite_list_.push_back("sam");

    sprite_list_.push_back("qvoid");
    sprite_list_.push_back("sigmasua");

    sprite_list_.push_back("tv_panel");
    sprite_list_.push_back("tv");

    sprite_list_.push_back("spider");
    sprite_list_.push_back("shock");
    sprite_list_.push_back("wave");

    sprite_list_.push_back("player");
}

// Carga todos los sprites desde una lista
void Ending2::loadSprites()
{
    // Inicializa variables
    sprite_max_width_ = 0;
    sprite_max_height_ = 0;

    // Carga los sprites
    for (const auto &sl : sprite_list_)
    {
        sprites_.emplace_back(std::make_shared<AnimatedSprite>(Resource::get()->getTexture(sl + ".png"), Resource::get()->getAnimations(sl + ".ani")));
        sprite_max_width_ = std::max(sprites_.back()->getWidth(), sprite_max_width_);
        sprite_max_height_ = std::max(sprites_.back()->getHeight(), sprite_max_height_);
    }
}

// Actualiza los sprites
void Ending2::updateSprites()
{
    for (auto sprite : sprites_)
    {
        sprite->update();
    }
}

// Actualiza los sprites de texto
void Ending2::updateTextSprites()
{
    for (auto sprite : sprite_texts_)
    {
        sprite->update();
    }
}

// Actualiza los sprites de texto del final
void Ending2::updateTexts()
{
    if (texts_.back()->getPosY() > GAMECANVAS_CENTER_Y)
    {
        for (auto sprite : texts_)
        {
            sprite->update();
        }
    }
    else
    {
        post_counter_enabled_ = true;
    }
}

// Dibuja los sprites
void Ending2::renderSprites()
{
    const Color color = stringToColor(options.video.palette, "red");
    for (auto sprite : sprites_)
    {
        const bool a = sprite->getRect().y + sprite->getRect().h > 0;
        const bool b = sprite->getRect().y < GAMECANVAS_HEIGHT;
        if (a && b)
        {
            sprite->getTexture()->setColor(color.r, color.g, color.b);
            sprite->render();
        }
    }

    // Pinta el ultimo elemento de otro color
    const Color c = stringToColor(options.video.palette, "white");
    sprites_.back()->getTexture()->setColor(c.r, c.g, c.b);
    sprites_.back()->render();
}

// Dibuja los sprites con el texto
void Ending2::renderSpriteTexts()
{
    const Color color = stringToColor(options.video.palette, "white");
    for (auto sprite : sprite_texts_)
    {
        const bool a = sprite->getRect().y + sprite->getRect().h > 0;
        const bool b = sprite->getRect().y < GAMECANVAS_HEIGHT;
        if (a && b)
        {
            sprite->getTexture()->setColor(color.r, color.g, color.b);
            sprite->render();
        }
    }
}

// Dibuja los sprites con el texto del final
void Ending2::renderTexts()
{
    for (auto sprite : texts_)
    {
        const bool a = sprite->getRect().y + sprite->getRect().h > 0;
        const bool b = sprite->getRect().y < GAMECANVAS_HEIGHT;
        if (a && b)
        {
            sprite->render();
        }
    }
}

// Coloca los sprites en su sito
void Ending2::placeSprites()
{
    for (int i = 0; i < (int)sprites_.size(); ++i)
    {
        const int X = i % 2 == 0 ? FIRST_COL_ : SECOND_COL_;
        const int Y = (i / 1) * (sprite_max_height_ + DIST_SPRITE_TEXT_ + Resource::get()->getText("smb2")->getCharacterSize() + DIST_SPRITE_SPRITE_) + GAMECANVAS_HEIGHT + 40;
        const int W = sprites_.at(i)->getWidth();
        const int H = sprites_.at(i)->getHeight();
        const int DX = -(W / 2);
        const int DY = sprite_max_height_ - H;

        sprites_.at(i)->setPosition({X + DX, Y + DY, W, H});
        sprites_.at(i)->setVelY(SPRITE_DESP_SPEED_);
    }

    // Recoloca el último sprite, que es el del jugador
    const int W = sprites_.back()->getWidth();
    const int X = GAMECANVAS_CENTER_X - (W / 2);
    const int Y = sprites_.back()->getPosY() + sprite_max_height_ * 2;
    sprites_.back()->setPosX(X);
    sprites_.back()->setPosY(Y);
    sprites_.back()->setCurrentAnimation("walk");
}

// Crea los sprites con las texturas con los textos
void Ending2::createSpriteTexts()
{
    // Crea los sprites de texto a partir de la lista
    for (int i = 0; i < static_cast<int>(sprite_list_.size()); ++i)
    {
        auto text = Resource::get()->getText("smb2");

        // Procesa y ajusta el texto del sprite actual
        std::string txt = sprite_list_[i];
        std::replace(txt.begin(), txt.end(), '_', ' '); // Reemplaza '_' por ' '
        if (txt == "player")
        {
            txt = "JAILDOCTOR"; // Reemplaza "player" por "JAILDOCTOR"
        }

        // Calcula las dimensiones del texto
        const int W = text->lenght(txt, 1);
        const int H = text->getCharacterSize();

        // Determina la columna y la posición X del texto
        const int X = (i == static_cast<int>(sprite_list_.size()) - 1)
                          ? (GAMECANVAS_CENTER_X - (W / 2))
                          : ((i % 2 == 0 ? FIRST_COL_ : SECOND_COL_) - (W / 2));

        // Calcula la posición Y del texto en base a la posición y altura del sprite
        const int Y = sprites_.at(i)->getPosY() + sprites_.at(i)->getHeight() + DIST_SPRITE_TEXT_;

        // Crea la textura
        auto texture = std::make_shared<Texture>(Screen::get()->getRenderer());
        texture->createBlank(W, H);
        texture->setAsRenderTarget(Screen::get()->getRenderer());
        texture->setBlendMode(SDL_BLENDMODE_BLEND);
        text->write(0, 0, txt);

        // Crea el sprite
        SDL_Rect pos = {X, Y, W, H};
        sprite_texts_.emplace_back(std::make_shared<MovingSprite>(texture, pos));
        sprite_texts_.back()->setVelY(SPRITE_DESP_SPEED_);
    }
}

// Crea los sprites con las texturas con los textos del final
void Ending2::createTexts()
{
    // Crea los primeros textos
    std::vector<std::string> list;
    list.push_back("STARRING");

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

    // Crea los sprites de texto a partir de la lista
    for (int i = 0; i < (int)list.size(); ++i)
    {
        // Calcula constantes
        const int w = text->lenght(list[i], 1);
        const int h = text->getCharacterSize();
        const int x = GAMECANVAS_CENTER_X;
        const int dx = -(w / 2);
        const int y = GAMECANVAS_HEIGHT + (text->getCharacterSize() * (i * 2));

        // Crea la textura
        auto texture = std::make_shared<Texture>(Screen::get()->getRenderer());
        texture->createBlank(w, h);
        texture->setAsRenderTarget(Screen::get()->getRenderer());
        texture->setBlendMode(SDL_BLENDMODE_BLEND);
        text->write(0, 0, list[i]);

        // Crea el sprite
        SDL_Rect pos = {x + dx, y, w, h};
        texts_.emplace_back(std::make_shared<MovingSprite>(texture, pos));
        texts_.back()->setVelY(SPRITE_DESP_SPEED_);
    }

    // Crea los últimos textos
    // El primer texto va a continuación del ultimo spriteText
    const int start = sprite_texts_.back()->getPosY() + text->getCharacterSize() * 15;
    list.clear();
    list.push_back("THANK YOU");
    list.push_back("FOR PLAYING!");

    // Crea los sprites de texto a partir de la lista
    for (int i = 0; i < (int)list.size(); ++i)
    {
        // Calcula constantes
        const int w = text->lenght(list[i], 1);
        const int h = text->getCharacterSize();
        const int x = GAMECANVAS_CENTER_X;
        const int dx = -(w / 2);
        const int y = start + (text->getCharacterSize() * (i * 2));

        // Crea la textura
        auto texture = std::make_shared<Texture>(Screen::get()->getRenderer());
        texture->createBlank(w, h);
        texture->setAsRenderTarget(Screen::get()->getRenderer());
        texture->setBlendMode(SDL_BLENDMODE_BLEND);
        text->write(0, 0, list[i]);

        // Crea el sprite
        SDL_Rect pos = {x + dx, y, w, h};
        texts_.emplace_back(std::make_shared<MovingSprite>(texture, pos));
        texts_.back()->setVelY(SPRITE_DESP_SPEED_);
    }
}

// Actualiza el fade final
void Ending2::updateFinalFade()
{
    // La variable step va de 0 a 40 en el tramo de postCounter que va de 500 a 540. Al dividirlo por 40, va de 0.0f a 1.0f
    const float STEP = std::min(std::max(post_counter_, 500) - 500, 40) / 40.0f;
    const int INDEX = (colors_.size() - 1) * STEP;

    for (const auto &text : texts_)
    {
        text->getTexture()->setColor(colors_.at(INDEX).r, colors_.at(INDEX).g, colors_.at(INDEX).b);
    }
}

// Actualiza el volumen de la musica
void Ending2::updateMusicVolume()
{
    if (post_counter_ > 0)
    {
        const float step = (600.0f - post_counter_) / 600.0f;
        const int volume = 128 * step;
        JA_SetVolume(volume);
    }
}