#pragma once

#include <SDL3/SDL_rect.h>	 // Para SDL_FRect
#include <SDL3/SDL_render.h> // Para SDL_Renderer, SDL_SetRenderLogicalPresentation
#include <SDL3/SDL_stdinc.h> // Para Uint32
#include <SDL3/SDL_video.h>	 // Para SDL_Window, SDL_HideWindow, SDL_ShowWindow
#include <SDL3/SDL_log.h>	 // Para SDL_LogCategory, SDL_LogError, SDL_Log
#include <string>			 // Para string
#include <memory>			 // Para shared_ptr
#include "options.h"		 // Para Options, VideoOptions, options
#include "param.h"			 // Para Param, ParamGame, param
#include "utils.h"			 // Para Color

#ifdef DEBUG
#include "text.h"
#include "resource.h"
#endif

// Clase Screen: gestiona la ventana, el renderizador y los efectos visuales globales
class Screen
{
public:
	// --- Métodos de singleton ---
	static void init();	   // Inicializa el objeto Screen
	static void destroy(); // Libera el objeto Screen
	static Screen *get();  // Obtiene el puntero al objeto Screen

	// --- Métodos principales ---
	void update();									   // Actualiza la lógica de la clase
	void clean(Color color = Color(0x00, 0x00, 0x00)); // Limpia la pantalla
	void start();									   // Prepara para empezar a dibujar en la textura de juego
	void render();									   // Vuelca el contenido del renderizador en pantalla

	// --- Configuración de ventana y render ---
	void setFullscreenMode(bool mode = options.video.fullscreen); // Establece el modo de video
	void toggleFullscreen();									  // Cambia entre pantalla completa y ventana
	void setWindowZoom(int size);								  // Cambia el tamaño de la ventana
	bool decWindowZoom();										  // Reduce el tamaño de la ventana
	bool incWindowZoom();										  // Aumenta el tamaño de la ventana

	// --- Efectos visuales ---
	void shake() { shake_effect_.enable(src_rect_, dst_rect_); }														 // Agita la pantalla
	void flash(Color color, int lenght = 10, int delay = 0) { flash_effect_ = FlashEffect(true, lenght, delay, color); } // Pone la pantalla de color
	void toggleShaders() { options.video.shaders = !options.video.shaders; }											 // Activa/desactiva los shaders
	void toggleIntegerScale();																							 // Activa/desactiva el escalado entero
	void toggleVSync();																									 // Activa/desactiva el vsync
	void attenuate(bool value) { attenuate_effect_ = value; }															 // Atenúa la pantalla

	// --- Getters ---
	SDL_Renderer *getRenderer() { return renderer_; } // Obtiene el renderizador
	void show() { SDL_ShowWindow(window_); }		  // Muestra la ventana
	void hide() { SDL_HideWindow(window_); }		  // Oculta la ventana

#ifdef DEBUG
	// --- Debug ---
	void toggleDebugInfo() { debug_info_.show = !debug_info_.show; }
	void setDebugInfoEnabled(bool value) { debug_info_.show = value; }
	void initDebugInfo() { debug_info_.init(); }
#endif

private:
	// --- Constantes ---
	static constexpr int WINDOWS_DECORATIONS_ = 35;

	// --- Estructuras internas ---
	struct FPS
	{
		Uint32 ticks;	// Tiempo en milisegundos desde que se comenzó a contar.
		int frameCount; // Número acumulado de frames en el intervalo.
		int lastValue;	// Número de frames calculado en el último segundo.

		FPS() : ticks(0), frameCount(0), lastValue(0) {}
		void increment() { frameCount++; }
		int calculate(Uint32 currentTicks)
		{
			if (currentTicks - ticks >= 1000)
			{
				lastValue = frameCount;
				frameCount = 0;
				ticks = currentTicks;
			}
			return lastValue;
		}
	};

	struct FlashEffect
	{
		bool enabled;
		int lenght;
		int delay;
		int counter;
		Color color;

		explicit FlashEffect(bool enabled = false, int lenght = 0, int delay = 0, Color color = Color(0xFF, 0xFF, 0xFF))
			: enabled(enabled), lenght(lenght), delay(delay), counter(lenght), color(color) {}

		void update() { (enabled && counter > 0) ? counter-- : enabled = false; }
		bool isRendarable() { return enabled && counter < lenght - delay; }
	};

	struct ShakeEffect
	{
		int desp;
		int delay;
		int counter;
		int lenght;
		int remaining;
		int original_pos;
		int original_width;
		bool enabled;

		explicit ShakeEffect(bool en = false, int dp = 2, int dl = 3, int cnt = 0, int len = 8, int rem = 0, int origPos = 0, int origWidth = 800)
			: desp(dp), delay(dl), counter(cnt), lenght(len), remaining(rem), original_pos(origPos), original_width(origWidth), enabled(en) {}

		void enable(SDL_FRect &src_rect, SDL_FRect &dst_rect)
		{
			if (!enabled)
			{
				enabled = true;
				original_pos = src_rect.x;
				original_width = src_rect.w;
				src_rect.w -= desp;
				dst_rect.w = src_rect.w;
			}
			remaining = lenght;
			counter = delay;
		}

		void update(SDL_FRect &src_rect, SDL_FRect &dst_rect)
		{
			if (enabled)
			{
				if (counter > 0)
				{
					counter--;
				}
				else
				{
					counter = delay;
					const auto SRC_DESP = (remaining % 2 == 0) ? 0 : desp;
					const auto DST_DESP = (remaining % 2 == 1) ? 0 : desp;
					src_rect.x = original_pos + SRC_DESP;
					dst_rect.x = original_pos + DST_DESP;
					remaining--;
					if (remaining == -1)
					{
						enabled = false;
						src_rect.x = original_pos;
						src_rect.w = original_width;
						dst_rect.x = original_pos;
						dst_rect.w = original_width;
					}
				}
			}
		}

		bool isEnabled() const { return enabled; }
	};

#ifdef DEBUG
	struct Debug
	{
		std::shared_ptr<Text> text = nullptr;
		bool show = false;
		void init()
		{
			if (Resource::get())
			{
				text = Resource::get()->getText("smb2");
				if (!text)
				{
					SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to retrieve debug_.text object!");
					return;
				}
			}
		}
	};
#endif

	// --- Singleton ---
	static Screen *screen_;

	// --- Objetos y punteros ---
	SDL_Window *window_;	   // Ventana de la aplicación
	SDL_Renderer *renderer_;   // El renderizador de la ventana
	SDL_Texture *game_canvas_; // Textura donde se dibuja todo antes de volcarse al renderizador

	// --- Variables de estado ---
	SDL_FRect src_rect_;			// Coordenadas de origen para dibujar la textura del juego
	SDL_FRect dst_rect_;			// Coordenadas destino para dibujar la textura del juego
	FPS fps_;						// Gestión de frames por segundo
	std::string shader_source_;		// Almacena el contenido del archivo GLSL
	FlashEffect flash_effect_;		// Efecto de flash en pantalla
	ShakeEffect shake_effect_;		// Efecto de agitar la pantalla
	bool attenuate_effect_ = false; // Indica si la pantalla ha de estar atenuada
#ifdef DEBUG
	Debug debug_info_; // Información de debug
#endif

	// --- Métodos internos ---
	bool initSDL();			 // Arranca SDL VIDEO y crea la ventana
	void renderFlash();		 // Dibuja el efecto de flash en la pantalla
	void renderShake();		 // Aplica el efecto de agitar la pantalla
	void renderInfo();		 // Muestra información por pantalla
	void renderScreen();	 // Selecciona y ejecuta el método de renderizado adecuado
	void loadShaders();		 // Carga el contenido del archivo GLSL
	void initShaders();		 // Inicializa los shaders
	void adjustWindowSize(); // Calcula el tamaño de la ventana
	void adjustRenderLogicalSize() { SDL_SetRenderLogicalPresentation(renderer_, param.game.width, param.game.height, SDL_LOGICAL_PRESENTATION_INTEGER_SCALE); }
	void getDisplayInfo();	// Obtiene información sobre la pantalla
	void renderOverlays();	// Renderiza todos los overlays y efectos
	void renderAttenuate(); // Atenúa la pantalla

	// --- Constructores y destructor ---
	Screen();
	~Screen();
};