#include "instructions.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_RGBA8888
#include <SDL3/SDL_timer.h>		// Para SDL_GetTicks
#include <algorithm>			// Para max
#include <utility>				// Para move
#include <vector>				// Para vector
#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 "param.h"				// Para Param, param, ParamGame, ParamFade
#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_CENTER, TEXT_COLOR, TEXT_...
#include "tiled_bg.h"			// Para TiledBG, TiledBGMode
#include "utils.h"				// Para Color, shdw_txt_color, Zone, no_color

// Constructor
Instructions::Instructions()
	: renderer_(Screen::get()->getRenderer()),
	  texture_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
	  backbuffer_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.width, param.game.height)),
	  text_(Resource::get()->getText("smb2")),
	  tiled_bg_(std::make_unique<TiledBG>(param.game.game_area.rect, TiledBGMode::STATIC)),
	  fade_(std::make_unique<Fade>())
{
	// Configura las texturas
	SDL_SetTextureBlendMode(backbuffer_, SDL_BLENDMODE_BLEND);
	SDL_SetTextureBlendMode(texture_, SDL_BLENDMODE_BLEND);

	// Inicializa variables
	section::name = section::Name::INSTRUCTIONS;
	view_ = param.game.game_area.rect;

	// Inicializa objetos
	fade_->setColor(FADE_COLOR.r, FADE_COLOR.g, FADE_COLOR.b);
	fade_->setType(FadeType::FULLSCREEN);
	fade_->setPostDuration(param.fade.post_duration);
	fade_->setMode(FadeMode::IN);
	fade_->activate();

	// Inicializa las líneas con un retraso progresivo de 50 ms
	lines_ = initializeLines(256);

	// Rellena la textura de texto
	fillTexture();

	// Inicializa los sprites de los items
	iniSprites();
}

// Destructor
Instructions::~Instructions()
{
	item_textures_.clear();
	sprites_.clear();

	SDL_DestroyTexture(backbuffer_);
	SDL_DestroyTexture(texture_);
}

// Inicializa los sprites de los items
void Instructions::iniSprites()
{
	// Inicializa las texturas
	item_textures_.emplace_back(Resource::get()->getTexture("item_points1_disk.png"));
	item_textures_.emplace_back(Resource::get()->getTexture("item_points2_gavina.png"));
	item_textures_.emplace_back(Resource::get()->getTexture("item_points3_pacmar.png"));
	item_textures_.emplace_back(Resource::get()->getTexture("item_clock.png"));
	item_textures_.emplace_back(Resource::get()->getTexture("item_coffee.png"));

	// Inicializa los sprites
	for (int i = 0; i < (int)item_textures_.size(); ++i)
	{
		auto sprite = std::make_unique<Sprite>(item_textures_[i], 0, 0, param.game.item_size, param.game.item_size);
		sprite->setPosition((SDL_FPoint){sprite_pos_.x, sprite_pos_.y + ((param.game.item_size + item_space_) * i)});
		sprites_.push_back(std::move(sprite));
	}
}

// Actualiza los sprites
void Instructions::updateSprites()
{
	SDL_FRect src_rect = {0, 0, param.game.item_size, param.game.item_size};

	// Disquito
	src_rect.y = param.game.item_size * (((counter_ + 12) / 36) % 2);
	sprites_[0]->setSpriteClip(src_rect);

	// Gavina
	src_rect.y = param.game.item_size * (((counter_ + 9) / 36) % 2);
	sprites_[1]->setSpriteClip(src_rect);

	// Pacmar
	src_rect.y = param.game.item_size * (((counter_ + 6) / 36) % 2);
	sprites_[2]->setSpriteClip(src_rect);

	// Time Stopper
	src_rect.y = param.game.item_size * (((counter_ + 3) / 36) % 2);
	sprites_[3]->setSpriteClip(src_rect);

	// Coffee
	src_rect.y = param.game.item_size * (((counter_ + 0) / 36) % 2);
	sprites_[4]->setSpriteClip(src_rect);
}

// Rellena la textura de texto
void Instructions::fillTexture()
{
	const int desp_x = param.game.item_size + 8;

	// Modifica el renderizador para pintar en la textura
	auto temp = SDL_GetRenderTarget(renderer_);
	SDL_SetRenderTarget(renderer_, texture_);

	// Limpia la textura
	SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
	SDL_RenderClear(renderer_);

	// Constantes
	constexpr int num_lines = 4;
	constexpr int num_item_lines = 4;
	constexpr int num_post_headers = 2;
	constexpr int num_pre_headers = 1;

	constexpr int space_post_header = 20;
	constexpr int space_pre_header = 28;
	const int space_between_lines = text_->getCharacterSize() * 1.5f;
	const int space_between_item_lines = param.game.item_size + item_space_;
	const int space_new_paragraph = space_between_lines * 0.5f;

	const int size = (num_lines * space_between_lines) + (num_item_lines * space_between_item_lines) + (num_post_headers * space_post_header) + (num_pre_headers * space_pre_header) + (space_new_paragraph);
	const int first_line = (param.game.height - size) / 2;

	// Calcula cual es el texto más largo de las descripciones de los items
	int lenght = 0;
	for (int i = 17; i <= 21; ++i)
	{
		const int l = text_->lenght(lang::getText(i));
		lenght = l > lenght ? l : lenght;
	}
	const int anchor_item = (param.game.width - (lenght + desp_x)) / 2;

	// Escribe el texto de las instrucciones
	text_->writeDX(TEXT_CENTER | TEXT_COLOR | TEXT_SHADOW, param.game.game_area.center_x, first_line, lang::getText(11), 1, ORANGE_COLOR, 1, SHADOW_TEXT_COLOR);

	const int anchor1 = first_line + space_post_header;
	text_->writeDX(TEXT_CENTER | TEXT_COLOR | TEXT_SHADOW, param.game.game_area.center_x, anchor1 + space_between_lines * 0, lang::getText(12), 1, NO_COLOR, 1, SHADOW_TEXT_COLOR);
	text_->writeDX(TEXT_CENTER | TEXT_COLOR | TEXT_SHADOW, param.game.game_area.center_x, anchor1 + space_between_lines * 1, lang::getText(13), 1, NO_COLOR, 1, SHADOW_TEXT_COLOR);
	text_->writeDX(TEXT_CENTER | TEXT_COLOR | TEXT_SHADOW, param.game.game_area.center_x, anchor1 + space_new_paragraph + space_between_lines * 2, lang::getText(14), 1, NO_COLOR, 1, SHADOW_TEXT_COLOR);
	text_->writeDX(TEXT_CENTER | TEXT_COLOR | TEXT_SHADOW, param.game.game_area.center_x, anchor1 + space_new_paragraph + space_between_lines * 3, lang::getText(15), 1, NO_COLOR, 1, SHADOW_TEXT_COLOR);

	// Escribe el texto de los objetos y sus puntos
	const int anchor2 = anchor1 + space_pre_header + space_new_paragraph + space_between_lines * 3;
	text_->writeDX(TEXT_CENTER | TEXT_COLOR | TEXT_SHADOW, param.game.game_area.center_x, anchor2, lang::getText(16), 1, ORANGE_COLOR, 1, SHADOW_TEXT_COLOR);

	const int anchor3 = anchor2 + space_post_header;
	text_->writeShadowed(anchor_item + desp_x, anchor3 + space_between_item_lines * 0, lang::getText(17), SHADOW_TEXT_COLOR);
	text_->writeShadowed(anchor_item + desp_x, anchor3 + space_between_item_lines * 1, lang::getText(18), SHADOW_TEXT_COLOR);
	text_->writeShadowed(anchor_item + desp_x, anchor3 + space_between_item_lines * 2, lang::getText(19), SHADOW_TEXT_COLOR);
	text_->writeShadowed(anchor_item + desp_x, anchor3 + space_between_item_lines * 3, lang::getText(20), SHADOW_TEXT_COLOR);
	text_->writeShadowed(anchor_item + desp_x, anchor3 + space_between_item_lines * 4, lang::getText(21), SHADOW_TEXT_COLOR);

	// Deja el renderizador como estaba
	SDL_SetRenderTarget(renderer_, temp);

	// Da valor a la variable
	sprite_pos_.x = anchor_item;
	sprite_pos_.y = anchor3 - ((param.game.item_size - text_->getCharacterSize()) / 2);
}

// Rellena el backbuffer
void Instructions::fillBackbuffer()
{
	// Modifica el renderizador para pintar en la textura
	auto temp = SDL_GetRenderTarget(renderer_);
	SDL_SetRenderTarget(renderer_, backbuffer_);

	// Limpia la textura
	SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
	SDL_RenderClear(renderer_);

	// Coloca el texto de fondo
	SDL_RenderTexture(renderer_, texture_, nullptr, nullptr);

	// Dibuja los sprites
	for (auto &sprite : sprites_)
	{
		sprite->render();
	}

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

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

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

		// Incrementa el contador
		counter_++;

		// Actualiza los sprites
		updateSprites();

		// Gestiona la textura con los graficos
		updateBackbuffer();

		// Actualiza el mosaico de fondo
		tiled_bg_->update();

		// Actualiza el objeto "fade"
		fade_->update();

		// Rellena el backbuffer
		fillBackbuffer();
	}
}

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

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

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

	// Copia la textura y el backbuffer al renderizador
	if (view_.y == 0)
		renderLines(renderer_, backbuffer_, lines_);
	else
		SDL_RenderTexture(renderer_, backbuffer_, nullptr, &view_);

	fade_->render();

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

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

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

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

// Método para inicializar las líneas
std::vector<Line> Instructions::initializeLines(int height)
{
	std::vector<Line> lines;
	for (int y = 0; y < height; y++)
	{
		int direction = (y % 2 == 0) ? -1 : 1; // Pares a la izquierda, impares a la derecha
		lines.emplace_back(y, 0.0f, direction);
	}
	return lines;
}

// Método para mover las líneas con suavizado
bool Instructions::moveLines(std::vector<Line> &lines, int width, float duration, Uint32 startDelay)
{
	Uint32 current_time = SDL_GetTicks();
	bool all_lines_off_screen = true;

	for (auto &line : lines)
	{
		// Establecer startTime en el primer cuadro de animación
		if (line.startTime == 0)
		{
			line.startTime = current_time + line.y * startDelay;
		}

		float elapsed_time = (current_time - line.startTime) / 1000.0f; // Convertir a segundos
		if (elapsed_time < 0)
		{
			all_lines_off_screen = false; // Si aún no se debe mover esta línea, no están todas fuera de pantalla
			continue;
		}
		if (elapsed_time >= duration)
		{
			continue; // Si la línea ha salido de los límites, no la muevas más
		}

		float t = elapsed_time / duration;
		float smooth_factor = easeInOutQuint(t);
		line.x = line.direction * smooth_factor * width;
		all_lines_off_screen = false; // Si alguna línea aún se está moviendo, no están todas fuera de pantalla
	}

	return all_lines_off_screen;
}

// Método para renderizar las líneas
void Instructions::renderLines(SDL_Renderer *renderer, SDL_Texture *texture, const std::vector<Line> &lines)
{
	for (const auto &LINE : lines)
	{
		SDL_FRect srcRect = {0, static_cast<float>(LINE.y), 320, 1};
		SDL_FRect dstRect = {static_cast<float>(LINE.x), static_cast<float>(LINE.y), 320, 1};
		SDL_RenderTexture(renderer, texture, &srcRect, &dstRect);
	}
}

// Gestiona la textura con los graficos
void Instructions::updateBackbuffer()
{
	// Establece la ventana del backbuffer
	view_.y = std::max(0.0f, param.game.height - counter_ + 100);

	// Verifica si view_.y == 0 y gestiona el temporizador
	if (view_.y == 0)
	{
		if (!start_delay_triggered_)
		{
			// Activa el temporizador si no ha sido activado
			start_delay_triggered_ = true;
			start_delay_time_ = SDL_GetTicks();
		}
		else if (SDL_GetTicks() - start_delay_time_ >= 4000)
		{
			// Han pasado tres segundos, mover líneas
			all_lines_off_screen_ = moveLines(lines_, 320, 1.0f, 5);
		}
	}

	// Comprueba si el contador ha llegado al final
	if (all_lines_off_screen_)
	{
		section::name = section::Name::TITLE;
		section::options = section::Options::TITLE_1;
	}
}