#include "hiscore_table.h"
#include <SDL3/SDL_blendmode.h>	  // Para SDL_BLENDMODE_BLEND
#include <SDL3/SDL_events.h>	  // Para SDL_PollEvent, SDL_Event
#include <SDL3/SDL_pixels.h>	  // Para SDL_PixelFormat
#include <SDL3/SDL_render.h>	  // Para SDL_SetRenderTarget, SDL_CreateTex...
#include <SDL3/SDL_timer.h>		  // Para SDL_GetTicks
#include <stdlib.h>				  // Para rand, size_t
#include <algorithm>			  // Para max
#include <functional>			  // Para function
#include <vector>				  // Para vector
#include "background.h"			  // Para Background
#include "fade.h"				  // Para Fade, FadeMode, FadeType
#include "global_events.h"		  // Para check
#include "global_inputs.h"		  // Para check, update
#include "audio.h"				  // Para JA_GetMusicState, JA_Music_state
#include "lang.h"				  // Para getText
#include "manage_hiscore_table.h" // Para HiScoreEntry
#include "options.h"			  // Para GameOptions, Options, options
#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.h"			  // 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"				  // Para Color, easeOutQuint, fade_color, Zone

// 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>()),
	  counter_(0),
	  ticks_(0),
	  view_area_(SDL_FRect{0, 0, static_cast<float>(param.game.width), static_cast<float>(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.game.clear_last_hi_score_entries();
}

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

		// Actualiza las posiciones de los sprites de texto
		updateSprites();

		// Actualiza el fondo
		background_->update();

		// Gestiona el fade
		updateFade();

		// Gestiona el contador y sus eventos
		updateCounter();

		// Dibuja los sprites en la textura
		fillTexture();

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

		// Actualiza las variables de globalInputs
	}
}

// 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);
}

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

	// Limpia la pantalla
	Screen::get()->clean(BG_COLOR);

	// Pinta el fondo
	background_->render();

	// Establece la ventana del backbuffer
	view_area_.y = std::max(0.0f, param.game.height - counter_ + 100);

	// Copia el backbuffer al renderizador
	SDL_RenderTexture(renderer_, backbuffer_, nullptr, &view_area_);

	// Renderiza el fade
	fade_->render();

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

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

// Comprueba las entradas
void HiScoreTable::checkInput()
{
	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
std::string HiScoreTable::format(int number)
{
	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(42), -2, background_fade_color_.getInverse().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, ORANGE_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.game.hi_score_table.at(i).score);
		const auto num_dots = ENTRY_LENGHT - options.game.hi_score_table.at(i).name.size() - score.size();
		const auto one_cc = options.game.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.game.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, ORANGE_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(FADE_COLOR.r, FADE_COLOR.g, FADE_COLOR.b);
	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
Color HiScoreTable::getEntryColor(int counter_)
{
	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_.getInverse().lighten(75));
	entry_colors_.emplace_back(background_fade_color_.getInverse().lighten(50));
	entry_colors_.emplace_back(background_fade_color_.getInverse().lighten(25));
	entry_colors_.emplace_back(background_fade_color_.getInverse());
}

// 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.game.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();
	}
}