#include "hiscore_table.h"

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

#include <algorithm>   // Para max
#include <cstdlib>     // Para rand, size_t
#include <functional>  // Para function
#include <vector>      // Para vector

#include "audio.h"                 // Para Audio
#include "background.h"            // Para Background
#include "color.h"                 // Para Color, easeOutQuint, NO_TEXT_COLOR
#include "fade.h"                  // Para Fade, FadeMode, FadeType
#include "global_events.h"         // Para check
#include "global_inputs.h"         // Para check
#include "input.h"                 // Para Input
#include "lang.h"                  // Para getText
#include "manage_hiscore_table.h"  // Para HiScoreEntry
#include "options.h"               // Para SettingsOptions, settings
#include "param.h"                 // Para Param, param, ParamGame, ParamFade
#include "path_sprite.h"           // Para PathSprite, Path, PathType
#include "resource.h"              // Para Resource
#include "screen.h"                // Para Screen
#include "section.hpp"             // Para Name, name, Options, options
#include "sprite.h"                // Para Sprite
#include "text.h"                  // Para Text, TEXT_SHADOW, TEXT_COLOR
#include "texture.h"               // Para Texture
#include "utils.h"

// Constructor
HiScoreTable::HiScoreTable()
    : renderer_(Screen::get()->getRenderer()),
      backbuffer_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
      fade_(std::make_unique<Fade>()),
      background_(std::make_unique<Background>()),

      ticks_(0),
      view_area_(SDL_FRect{0, 0, param.game.width, param.game.height}),
      fade_mode_(FadeMode::IN),
      background_fade_color_(Color(0, 0, 0)) {
    // Inicializa el resto
    Section::name = Section::Name::HI_SCORE_TABLE;
    SDL_SetTextureBlendMode(backbuffer_, SDL_BLENDMODE_BLEND);
    initFade();
    initBackground();
    iniEntryColors();
    createSprites();
}

// Destructor
HiScoreTable::~HiScoreTable() {
    SDL_DestroyTexture(backbuffer_);
    Options::settings.clearLastHiScoreEntries();
}

// Actualiza las variables
void HiScoreTable::update() {
    if (SDL_GetTicks() - ticks_ > param.game.speed) {
        ticks_ = SDL_GetTicks();  // Actualiza el contador de ticks
        Screen::get()->update();  // Actualiza el objeto screen

        updateSprites();        // Actualiza las posiciones de los sprites de texto
        background_->update();  // Actualiza el fondo
        updateFade();           // Gestiona el fade
        updateCounter();        // Gestiona el contador y sus eventos
        fillTexture();          // Dibuja los sprites en la textura
    }

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

// Pinta en pantalla
void HiScoreTable::render() {
    static const auto screen = Screen::get();

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

    background_->render();                                              // Pinta el fondo
    view_area_.y = std::max(0.0F, param.game.height - counter_ + 100);  // Establece la ventana del backbuffer
    SDL_RenderTexture(renderer_, backbuffer_, nullptr, &view_area_);    // Copia el backbuffer al renderizador
    fade_->render();                                                    // Renderiza el fade

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

// Dibuja los sprites en la textura
void HiScoreTable::fillTexture() {
    // Pinta en el backbuffer el texto y los sprites
    auto *temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, backbuffer_);
    SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
    SDL_RenderClear(renderer_);

    // Escribe el texto: Mejores puntuaciones
    header_->render();

    // Escribe los nombres de la tabla de puntuaciones
    for (auto const &entry : entry_names_) {
        entry->render();
    }

    // Cambia el destino de renderizado
    SDL_SetRenderTarget(renderer_, temp);
}

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

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

// Bucle para la pantalla de instrucciones
void HiScoreTable::run() {
    Audio::get()->playMusic("title.ogg");
    while (Section::name == Section::Name::HI_SCORE_TABLE) {
        checkInput();
        update();
        checkEvents();  // Tiene que ir antes del render
        render();
    }
}

// Gestiona el fade
void HiScoreTable::updateFade() {
    fade_->update();

    if (fade_->hasEnded() && fade_mode_ == FadeMode::IN) {
        fade_->reset();
        fade_mode_ = FadeMode::OUT;
        fade_->setMode(fade_mode_);
    }

    if (fade_->hasEnded() && fade_mode_ == FadeMode::OUT) {
        Section::name = (Section::options == Section::Options::HI_SCORE_AFTER_PLAYING)
            ? Section::Name::TITLE
            : Section::Name::INSTRUCTIONS;
        Section::options = Section::Options::NONE;
    }
}

// Convierte un entero a un string con separadores de miles
auto HiScoreTable::format(int number) -> std::string {
    const std::string SEPARATOR = ".";
    const std::string SCORE = std::to_string(number);

    auto index = (int)SCORE.size() - 1;
    std::string result;
    auto i = 0;
    while (index >= 0) {
        result = SCORE.at(index) + result;
        index--;
        i++;
        if (i == 3) {
            i = 0;
            result = SEPARATOR + result;
        }
    }

    return result;
}

// Crea los sprites con los textos
void HiScoreTable::createSprites() {
    auto header_text = Resource::get()->getText("04b_25_grey");
    auto entry_text = Resource::get()->getText("smb2");

    // Obtiene el tamaño de la textura
    float backbuffer_width;
    float backbuffer_height;
    SDL_GetTextureSize(backbuffer_, &backbuffer_width, &backbuffer_height);

    constexpr int ENTRY_LENGHT = 22;
    constexpr int MAX_NAMES = 10;
    const int SPACE_BETWEEN_HEADER = entry_text->getCharacterSize() * 4;
    const int SPACE_BETWEEN_LINES = entry_text->getCharacterSize() * 2;
    const int SIZE = SPACE_BETWEEN_HEADER + SPACE_BETWEEN_LINES * (MAX_NAMES - 1) + entry_text->getCharacterSize();
    const int FIRST_LINE = (param.game.height - SIZE) / 2;

    // Crea el sprite para el texto de cabecera
    header_ = std::make_unique<Sprite>(header_text->writeDXToTexture(TEXT_COLOR, Lang::getText("[HIGHSCORE_TABLE] CAPTION"), -2, background_fade_color_.INVERSE().LIGHTEN(25)));
    header_->setPosition(param.game.game_area.center_x - (header_->getWidth() / 2), FIRST_LINE);

    // Crea los sprites para las entradas en la tabla de puntuaciones
    const int ANIMATION = rand() % 4;
    const std::string SAMPLE_LINE(ENTRY_LENGHT + 3, ' ');
    auto sample_entry = std::make_unique<Sprite>(entry_text->writeDXToTexture(TEXT_SHADOW, SAMPLE_LINE, 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR));
    const auto ENTRY_WIDTH = sample_entry->getWidth();
    for (int i = 0; i < MAX_NAMES; ++i) {
        const auto TABLE_POSITION = format(i + 1) + ". ";
        const auto SCORE = format(Options::settings.hi_score_table.at(i).score);
        const auto NUM_DOTS = ENTRY_LENGHT - Options::settings.hi_score_table.at(i).name.size() - SCORE.size();
        const auto *const ONE_CC = Options::settings.hi_score_table.at(i).one_credit_complete ? " }" : "";
        std::string dots;
        for (int j = 0; j < (int)NUM_DOTS; ++j) {
            dots = dots + ".";
        }
        const auto LINE = TABLE_POSITION + Options::settings.hi_score_table.at(i).name + dots + SCORE + ONE_CC;

        entry_names_.emplace_back(std::make_shared<PathSprite>(entry_text->writeDXToTexture(TEXT_SHADOW, LINE, 1, NO_TEXT_COLOR, 1, SHADOW_TEXT_COLOR)));
        const int DEFAULT_POS_X = (backbuffer_width - ENTRY_WIDTH) / 2;
        const int POS_X = (i < 9) ? DEFAULT_POS_X : DEFAULT_POS_X - entry_text->getCharacterSize();
        const int POS_Y = (i * SPACE_BETWEEN_LINES) + FIRST_LINE + SPACE_BETWEEN_HEADER;
        constexpr int STEPS = 80;
        switch (ANIMATION) {
            case 0:  // Ambos lados alternativamente
            {
                if (i % 2 == 0) {
                    entry_names_.back()->addPath(-entry_names_.back()->getWidth(), POS_X, PathType::HORIZONTAL, POS_Y, STEPS, easeOutQuint);
                    entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0);
                } else {
                    entry_names_.back()->addPath(backbuffer_width, POS_X, PathType::HORIZONTAL, POS_Y, STEPS, easeOutQuint);
                    entry_names_.back()->setPosition(backbuffer_width, 0);
                }
                break;
            }

            case 1:  // Entran por la izquierda
            {
                entry_names_.back()->addPath(-entry_names_.back()->getWidth(), POS_X, PathType::HORIZONTAL, POS_Y, STEPS, easeOutQuint);
                entry_names_.back()->setPosition(-entry_names_.back()->getWidth(), 0);
                break;
            }

            case 2:  // Entran por la derecha
            {
                entry_names_.back()->addPath(backbuffer_width, POS_X, PathType::HORIZONTAL, POS_Y, STEPS, easeOutQuint);
                entry_names_.back()->setPosition(backbuffer_width, 0);
                break;
            }

            case 3:  // Entran desde la parte inferior
            {
                entry_names_.back()->addPath(backbuffer_height, POS_Y, PathType::VERTICAL, POS_X, STEPS, easeOutQuint);
                entry_names_.back()->setPosition(0, backbuffer_height);
            }

            default:
                break;
        }
    }
}

// Actualiza las posiciones de los sprites de texto
void HiScoreTable::updateSprites() {
    constexpr int INIT_COUNTER = 190;
    const int COUNTER_BETWEEN_ENTRIES = 16;
    if (counter_ >= INIT_COUNTER) {
        const int COUNTER2 = counter_ - INIT_COUNTER;
        if (COUNTER2 % COUNTER_BETWEEN_ENTRIES == 0) {
            int index = COUNTER2 / COUNTER_BETWEEN_ENTRIES;
            if (index < static_cast<int>(entry_names_.size())) {
                entry_names_.at(index)->enable();
            }
        }
    }
    for (auto const &entry : entry_names_) {
        entry->update();
    }

    glowEntryNames();
}

// Inicializa el fade
void HiScoreTable::initFade() {
    fade_->setColor(param.fade.color);
    fade_->setType(FadeType::RANDOM_SQUARE);
    fade_->setPostDuration(param.fade.post_duration);
    fade_->setMode(fade_mode_);
    fade_->activate();
}

// Inicializa el fondo
void HiScoreTable::initBackground() {
    background_->setPos(param.game.game_area.rect);
    background_->setCloudsSpeed(-0.1F);

    const int LUCKY = rand() % 3;
    switch (LUCKY) {
        case 0:  // Fondo verde
        {
            background_->setGradientNumber(2);
            background_->setTransition(0.0F);
            background_->setSunProgression(1.0F);
            background_->setMoonProgression(0.0F);
            background_fade_color_ = GREEN_SKY_COLOR;
            break;
        }

        case 1:  // Fondo naranja
        {
            background_->setGradientNumber(1);
            background_->setTransition(0.0F);
            background_->setSunProgression(0.65F);
            background_->setMoonProgression(0.0F);
            background_fade_color_ = PINK_SKY_COLOR;
            break;
        }

        case 2:  // Fondo azul
        {
            background_->setGradientNumber(0);
            background_->setTransition(0.0F);
            background_->setSunProgression(0.0F);
            background_->setMoonProgression(0.0F);
            background_fade_color_ = BLUE_SKY_COLOR;
            break;
        }

        default:
            break;
    }
}

// Obtiene un color del vector de colores de entradas
auto HiScoreTable::getEntryColor(int counter) -> Color {
    int cycle_length = entry_colors_.size() * 2 - 2;
    size_t n = counter % cycle_length;

    size_t index;
    if (n < entry_colors_.size()) {
        index = n;  // Avanza: 0,1,2,3
    } else {
        index = 2 * (entry_colors_.size() - 1) - n;  // Retrocede: 2,1
    }

    return entry_colors_[index];
}

// Inicializa los colores de las entradas
void HiScoreTable::iniEntryColors() {
    entry_colors_.clear();
    entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(75));
    entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(50));
    entry_colors_.emplace_back(background_fade_color_.INVERSE().LIGHTEN(25));
    entry_colors_.emplace_back(background_fade_color_.INVERSE());
}

// Hace brillar los nombres de la tabla de records
void HiScoreTable::glowEntryNames() {
    const Color ENTRY_COLOR = getEntryColor(counter_ / 5);
    for (const auto &entry_index : Options::settings.last_hi_score_entry) {
        if (entry_index != -1) {
            entry_names_.at(entry_index)->getTexture()->setColor(ENTRY_COLOR);
        }
    }
}

// Gestiona el contador
void HiScoreTable::updateCounter() {
    ++counter_;

    if (counter_ == 150) {
        background_->setColor(background_fade_color_.DARKEN());
        background_->setAlpha(96);
    }

    if (counter_ == COUNTER_END) {
        fade_->activate();
    }
}