#include "scoreboard.h"

#include <SDL3/SDL.h>  // Para SDL_DestroyTexture, SDL_SetRenderDrawColor, SDL_SetRenderTarget, SDL_CreateTexture, SDL_GetRenderTarget, SDL_GetTicks, SDL_RenderClear, SDL_RenderLine, SDL_RenderTexture, SDL_SetTextureBlendMode, SDL_FRect, SDL_BLENDMODE_BLEND, SDL_PixelFormat, SDL_Texture, SDL_TextureAccess

#include <algorithm>  // Para max
#include <cmath>      // Para roundf
#include <iomanip>    // Para operator<<, setfill, setw
#include <sstream>    // Para basic_ostream, basic_ostringstream, basic_ostream::operator<<, ostringstream

#include "color.h"
#include "enter_name.h"  // Para NAME_SIZE
#include "lang.h"        // Para getText
#include "param.h"       // Para Param, ParamScoreboard, param
#include "resource.h"    // Para Resource
#include "screen.h"      // Para Screen
#include "sprite.h"      // Para Sprite
#include "text.h"        // Para Text, TEXT_CENTER, TEXT_COLOR
#include "texture.h"     // Para Texture

// .at(SINGLETON) Hay que definir las variables estáticas, desde el .h sólo la hemos declarado
Scoreboard *Scoreboard::instance = nullptr;

// .at(SINGLETON) Crearemos el objeto score_board con esta función estática
void Scoreboard::init() {
    Scoreboard::instance = new Scoreboard();
}

// .at(SINGLETON) Destruiremos el objeto score_board con esta función estática
void Scoreboard::destroy() {
    delete Scoreboard::instance;
}

// .at(SINGLETON) Con este método obtenemos el objeto score_board y podemos trabajar con él
auto Scoreboard::get() -> Scoreboard * {
    return Scoreboard::instance;
}

// Constructor
Scoreboard::Scoreboard()
    : renderer_(Screen::get()->getRenderer()),
      game_power_meter_texture_(Resource::get()->getTexture("game_power_meter.png")),
      power_meter_sprite_(std::make_unique<Sprite>(game_power_meter_texture_)),
      text_scoreboard_(Resource::get()->getText("8bithud")) {
    // Inicializa variables
    for (size_t i = 0; i < static_cast<size_t>(Id::SIZE); ++i) {
        name_.at(i).clear();
        record_name_.at(i).clear();
        selector_pos_.at(i) = 0;
        score_.at(i) = 0;
        mult_.at(i) = 0;
        continue_counter_.at(i) = 0;
    }

    panel_.at(static_cast<size_t>(Id::LEFT)).mode = Mode::SCORE;
    panel_.at(static_cast<size_t>(Id::RIGHT)).mode = Mode::SCORE;
    panel_.at(static_cast<size_t>(Id::CENTER)).mode = Mode::STAGE_INFO;

    // Recalcula las anclas de los elementos
    recalculateAnchors();
    power_meter_sprite_->setPosition(SDL_FRect{
        static_cast<float>(slot4_2_.x - 20),
        slot4_2_.y,
        40,
        7});

    // Crea la textura de fondo
    background_ = nullptr;
    createBackgroundTexture();

    // Crea las texturas de los paneles
    createPanelTextures();

    // Rellena la textura de fondo
    fillBackgroundTexture();

    // Inicializa el vector de colores para el nombre
    iniNameColors();
}

Scoreboard::~Scoreboard() {
    if (background_ != nullptr) {
        SDL_DestroyTexture(background_);
    }

    for (auto *texture : panel_texture_) {
        if (texture != nullptr) {
            SDL_DestroyTexture(texture);
        }
    }
}

// Transforma un valor numérico en una cadena de 7 cifras
auto Scoreboard::updateScoreText(int num) -> std::string {
    std::ostringstream oss;
    oss << std::setw(7) << std::setfill('0') << num;
    return oss.str();
}

// Actualiza el contador
void Scoreboard::updateTimeCounter() {
    constexpr int TICKS_SPEED = 100;

    if (SDL_GetTicks() - ticks_ > TICKS_SPEED) {
        ticks_ = SDL_GetTicks();
        ++time_counter_;
    }
}

// Actualiza la lógica del marcador
void Scoreboard::update() {
    fillBackgroundTexture();
    updateTimeCounter();
    ++loop_counter_;
}

// Pinta el marcador
void Scoreboard::render() {
    SDL_RenderTexture(renderer_, background_, nullptr, &rect_);
}

// Establece el valor de la variable
void Scoreboard::setColor(Color color) {
    // Actualiza las variables de colores
    color_ = color;
    text_color1_ = param.scoreboard.text_autocolor ? color_.LIGHTEN(100) : param.scoreboard.text_color1;
    text_color2_ = param.scoreboard.text_autocolor ? color_.LIGHTEN(150) : param.scoreboard.text_color2;

    // Aplica los colores
    power_meter_sprite_->getTexture()->setColor(text_color2_);
    fillBackgroundTexture();
    iniNameColors();
}

// Establece el valor de la variable
void Scoreboard::setPos(SDL_FRect rect) {
    rect_ = rect;

    recalculateAnchors();       // Recalcula las anclas de los elementos
    createBackgroundTexture();  // Crea la textura de fondo
    createPanelTextures();      // Crea las texturas de los paneles
    fillBackgroundTexture();    // Rellena la textura de fondo
}

// Rellena los diferentes paneles del marcador
void Scoreboard::fillPanelTextures() {
    // Guarda a donde apunta actualmente el renderizador
    auto *temp = SDL_GetRenderTarget(renderer_);

    // Genera el contenido de cada panel_
    for (size_t i = 0; i < static_cast<int>(Id::SIZE); ++i) {
        // Cambia el destino del renderizador
        SDL_SetRenderTarget(renderer_, panel_texture_.at(i));

        // Dibuja el fondo de la textura
        SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
        SDL_RenderClear(renderer_);

        renderPanelContent(i);
    }

    // Deja el renderizador apuntando donde estaba
    SDL_SetRenderTarget(renderer_, temp);
}

void Scoreboard::renderPanelContent(size_t panel_index) {
    switch (panel_.at(panel_index).mode) {
        case Mode::SCORE:
            renderScoreMode(panel_index);
            break;
        case Mode::DEMO:
            renderDemoMode();
            break;
        case Mode::WAITING:
            renderWaitingMode();
            break;
        case Mode::GAME_OVER:
            renderGameOverMode();
            break;
        case Mode::STAGE_INFO:
            renderStageInfoMode();
            break;
        case Mode::CONTINUE:
            renderContinueMode(panel_index);
            break;
        case Mode::ENTER_NAME:
            renderEnterNameMode(panel_index);
            break;
        case Mode::SHOW_NAME:
            renderShowNameMode(panel_index);
            break;
        case Mode::GAME_COMPLETED:
            renderGameCompletedMode(panel_index);
            break;
        default:
            break;
    }
}

void Scoreboard::renderScoreMode(size_t panel_index) {
    // SCORE
    text_scoreboard_->writeDX(TEXT_COLOR | TEXT_CENTER, slot4_1_.x, slot4_1_.y, name_.at(panel_index), 1, text_color1_);
    text_scoreboard_->writeDX(TEXT_COLOR | TEXT_CENTER, slot4_2_.x, slot4_2_.y, updateScoreText(score_.at(panel_index)), 1, text_color2_);

    // MULT
    text_scoreboard_->writeDX(TEXT_COLOR | TEXT_CENTER, slot4_3_.x, slot4_3_.y, Lang::getText("[SCOREBOARD] 3"), 1, text_color1_);
    text_scoreboard_->writeDX(TEXT_COLOR | TEXT_CENTER, slot4_4_.x, slot4_4_.y, "x" + std::to_string(mult_.at(panel_index)).substr(0, 3), 1, text_color2_);
}

void Scoreboard::renderDemoMode() {
    // DEMO MODE
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_1_.x, slot4_1_.y + 4, Lang::getText("[SCOREBOARD] 6"), 1, text_color1_);

    // PRESS START TO PLAY
    if (time_counter_ % 10 < 8) {
        text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_3_.x, slot4_3_.y - 2, Lang::getText("[SCOREBOARD] 8"), 1, text_color1_);
        text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_4_.x, slot4_4_.y - 2, Lang::getText("[SCOREBOARD] 9"), 1, text_color1_);
    }
}

void Scoreboard::renderWaitingMode() {
    // GAME OVER
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_1_.x, slot4_1_.y + 4, Lang::getText("[SCOREBOARD] 7"), 1, text_color1_);

    // PRESS START TO PLAY
    if (time_counter_ % 10 < 8) {
        text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_3_.x, slot4_3_.y - 2, Lang::getText("[SCOREBOARD] 8"), 1, text_color1_);
        text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_4_.x, slot4_4_.y - 2, Lang::getText("[SCOREBOARD] 9"), 1, text_color1_);
    }
}

void Scoreboard::renderGameOverMode() {
    // GAME OVER
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_1_.x, slot4_1_.y + 4, Lang::getText("[SCOREBOARD] 7"), 1, text_color1_);

    // PLEASE WAIT
    if (time_counter_ % 10 < 8) {
        text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_3_.x, slot4_3_.y - 2, Lang::getText("[SCOREBOARD] 12"), 1, text_color1_);
        text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_4_.x, slot4_4_.y - 2, Lang::getText("[SCOREBOARD] 13"), 1, text_color1_);
    }
}

void Scoreboard::renderStageInfoMode() {
    // STAGE
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_1_.x, slot4_1_.y, Lang::getText("[SCOREBOARD] 5") + std::to_string(stage_), 1, text_color1_);

    // POWERMETER
    power_meter_sprite_->setSpriteClip(0, 0, 40, 7);
    power_meter_sprite_->render();
    power_meter_sprite_->setSpriteClip(40, 0, int(power_ * 40.0F), 7);
    power_meter_sprite_->render();

    // HI-SCORE
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_3_.x, slot4_3_.y, Lang::getText("[SCOREBOARD] 4"), 1, text_color1_);
    const std::string NAME = hi_score_name_.empty() ? "" : hi_score_name_ + " - ";
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_4_.x, slot4_4_.y, NAME + updateScoreText(hi_score_), 1, text_color2_);
}

void Scoreboard::renderContinueMode(size_t panel_index) {
    // SCORE
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_1_.x, slot4_1_.y, name_.at(panel_index), 1, text_color1_);
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_2_.x, slot4_2_.y, updateScoreText(score_.at(panel_index)), 1, text_color2_);

    // CONTINUE
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_3_.x, slot4_3_.y, Lang::getText("[SCOREBOARD] 10"), 1, text_color1_);
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_4_.x, slot4_4_.y, std::to_string(continue_counter_.at(panel_index)), 1, text_color2_);
}

void Scoreboard::renderEnterNameMode(size_t panel_index) {
    // SCORE
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_1_.x, slot4_1_.y, name_.at(panel_index), 1, text_color1_);
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_2_.x, slot4_2_.y, updateScoreText(score_.at(panel_index)), 1, text_color2_);

    // ENTER NAME
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_3_.x, slot4_3_.y, Lang::getText("[SCOREBOARD] 11"), 1, text_color1_);

    renderNameInputField(panel_index);
}

void Scoreboard::renderNameInputField(size_t panel_index) {
    SDL_FRect rect = {enter_name_pos_.x, enter_name_pos_.y, 5.0F, 7.0F};

    // Recorre todos los slots de letras del nombre
    for (size_t j = 0; j < NAME_SIZE; ++j) {
        // Selecciona el color
        const Color COLOR = j < selector_pos_.at(panel_index) ? text_color2_ : text_color1_;

        if (j != selector_pos_.at(panel_index) || time_counter_ % 3 == 0) {
            // Dibuja la linea
            if (j >= selector_pos_.at(panel_index)) {
                SDL_SetRenderDrawColor(renderer_, COLOR.r, COLOR.g, COLOR.b, 255);
                SDL_RenderLine(renderer_, rect.x, rect.y + rect.h, rect.x + rect.w, rect.y + rect.h);
            }

            // Dibuja la letra
            if (j < record_name_.at(panel_index).size()) {
                text_scoreboard_->writeColored(rect.x, rect.y, record_name_.at(panel_index).substr(j, 1), COLOR);
            }
        }
        rect.x += 7;
    }
}

void Scoreboard::renderShowNameMode(size_t panel_index) {
    // SCORE
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_1_.x, slot4_1_.y, name_.at(panel_index), 1, text_color1_);
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_2_.x, slot4_2_.y, updateScoreText(score_.at(panel_index)), 1, text_color2_);

    // NAME
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_3_.x, slot4_3_.y, Lang::getText("[SCOREBOARD] 11"), 1, text_color1_);

    /* TEXTO CENTRADO */
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_4_.x, slot4_4_.y, record_name_.at(panel_index), 1, getColorLikeKnightRider(name_colors_, loop_counter_ / 5));

    /* TEXTO A LA IZQUIERDA */
    // text_scoreboard_->writeColored(enter_name_pos_.x, enter_name_pos_.y, record_name_.at(panelIndex), getColorLikeKnightRider(name_colors_, loop_counter_ / 5));
}

void Scoreboard::renderGameCompletedMode(size_t panel_index) {
    // GAME OVER
    text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_1_.x, slot4_1_.y + 4, Lang::getText("[SCOREBOARD] 7"), 1, text_color1_);

    // SCORE
    if (time_counter_ % 10 < 8) {
        text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_3_.x, slot4_3_.y - 2, Lang::getText("[SCOREBOARD] 14"), 1, text_color1_);
        text_scoreboard_->writeDX(TEXT_CENTER | TEXT_COLOR, slot4_4_.x, slot4_4_.y - 2, updateScoreText(score_.at(panel_index)), 1, text_color2_);
    }
}

// Rellena la textura de fondo
void Scoreboard::fillBackgroundTexture() {
    // Rellena los diferentes paneles del marcador
    fillPanelTextures();

    // Cambia el destino del renderizador
    SDL_Texture *temp = SDL_GetRenderTarget(renderer_);
    SDL_SetRenderTarget(renderer_, background_);

    // Dibuja el fondo del marcador
    SDL_SetRenderDrawColor(renderer_, color_.r, color_.g, color_.b, 255);
    SDL_RenderClear(renderer_);

    // Copia las texturas de los paneles
    for (int i = 0; i < static_cast<int>(Id::SIZE); ++i) {
        SDL_RenderTexture(renderer_, panel_texture_.at(i), nullptr, &panel_.at(i).pos);
    }

    // Dibuja la linea que separa la zona de juego del marcador
    renderSeparator();

    // Deja el renderizador apuntando donde estaba
    SDL_SetRenderTarget(renderer_, temp);
}

// Recalcula las anclas de los elementos
void Scoreboard::recalculateAnchors() {
    // Recalcula la posición y el tamaño de los paneles
    const float PANEL_WIDTH = rect_.w / (float)static_cast<int>(Id::SIZE);
    for (int i = 0; i < static_cast<int>(Id::SIZE); ++i) {
        panel_.at(i).pos.x = roundf(PANEL_WIDTH * i);
        panel_.at(i).pos.y = 0;
        panel_.at(i).pos.w = roundf(PANEL_WIDTH * (i + 1)) - panel_.at(i).pos.x;
        panel_.at(i).pos.h = rect_.h;
    }

    // Constantes para definir las zonas del panel_: 4 filas y 1 columna
    const int ROW_SIZE = rect_.h / 4;
    const int TEXT_HEIGHT = 7;

    // Filas
    const float ROW1 = 1 + (ROW_SIZE * 0) + (TEXT_HEIGHT / 2);
    const float ROW2 = 1 + (ROW_SIZE * 1) + (TEXT_HEIGHT / 2) - 1;
    const float ROW3 = 1 + (ROW_SIZE * 2) + (TEXT_HEIGHT / 2) - 2;
    const float ROW4 = 1 + (ROW_SIZE * 3) + (TEXT_HEIGHT / 2) - 3;

    // Columna
    const float COL = PANEL_WIDTH / 2;

    // Slots de 4
    slot4_1_ = {COL, ROW1};
    slot4_2_ = {COL, ROW2};
    slot4_3_ = {COL, ROW3};
    slot4_4_ = {COL, ROW4};

    // Primer cuadrado para poner el nombre de record
    const int ENTER_NAME_LENGHT = text_scoreboard_->lenght(std::string(NAME_SIZE, 'A'));
    enter_name_pos_.x = COL - (ENTER_NAME_LENGHT / 2);
    enter_name_pos_.y = ROW4;

    // Recoloca los sprites
    if (power_meter_sprite_) {
        power_meter_sprite_->setX(slot4_2_.x - 20);
        power_meter_sprite_->setY(slot4_2_.y);
    }
}

// Crea la textura de fondo
void Scoreboard::createBackgroundTexture() {
    // Elimina la textura en caso de existir
    if (background_ != nullptr) {
        SDL_DestroyTexture(background_);
    }

    // Recrea la textura de fondo
    background_ = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, rect_.w, rect_.h);
    SDL_SetTextureBlendMode(background_, SDL_BLENDMODE_BLEND);
}

// Crea las texturas de los paneles
void Scoreboard::createPanelTextures() {
    // Elimina las texturas en caso de existir
    for (auto *texture : panel_texture_) {
        if (texture != nullptr) {
            SDL_DestroyTexture(texture);
        }
    }
    panel_texture_.clear();

    // Crea las texturas para cada panel_
    for (auto &i : panel_) {
        SDL_Texture *tex = SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, i.pos.w, i.pos.h);
        SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND);
        panel_texture_.push_back(tex);
    }
}

// Dibuja la linea que separa la zona de juego del marcador
void Scoreboard::renderSeparator() {
    // Dibuja la linea que separa el marcador de la zona de juego
    auto color = param.scoreboard.separator_autocolor ? color_.DARKEN() : param.scoreboard.separator_color;
    SDL_SetRenderDrawColor(renderer_, color.r, color.g, color.b, 255);
    SDL_RenderLine(renderer_, 0, 0, rect_.w, 0);
}

// Inicializa el vector de colores para el nombre
void Scoreboard::iniNameColors() {
    Color color = color_.INVERSE();

    name_colors_.clear();
    name_colors_.emplace_back(color.LIGHTEN(50));
    name_colors_.emplace_back(color.LIGHTEN(25));
    name_colors_.emplace_back(color);
    name_colors_.emplace_back(color.DARKEN(25));
}