#pragma once

#include <SDL3/SDL_events.h>	  // Para SDL_Event
#include <SDL3/SDL_render.h>	  // Para SDL_Renderer, SDL_Texture
#include <SDL3/SDL_stdinc.h>	  // Para Uint64, Uint8
#include <memory>				  // Para shared_ptr, unique_ptr
#include <string>				  // Para string
#include <vector>				  // Para vector
#include "manage_hiscore_table.h" // Para HiScoreEntry
#include "options.h"			  // Para GameOptions, Options, options
#include "player.h"				  // Para Player
#include "utils.h"				  // Para Demo

class Audio;
class Asset;
class Background;
class BalloonManager;
class Tabe;
class Bullet;
class Fade;
class Input;
class Item;
class PathSprite;
class Scoreboard;
class Screen;
class SmartSprite;
class Texture;
enum class BulletType : Uint8;
enum class ItemType;
struct Path;

// Modo demo
constexpr bool GAME_MODE_DEMO_OFF = false;
constexpr bool GAME_MODE_DEMO_ON = true;

// Cantidad de elementos a escribir en los ficheros de datos
constexpr int TOTAL_SCORE_DATA = 3;

// Clase Game
class Game
{
public:
	// Constructor
	Game(int playerID, int current_stage, bool demo);

	// Destructor
	~Game();

	// Bucle principal del juego
	void run();

private:
	// --- Tipos internos ---
	enum class GameState
	{
		FADE_IN,
		ENTERING_PLAYER,
		SHOWING_GET_READY_MESSAGE,
		PLAYING,
		COMPLETED,
		GAME_OVER,
	};

	// --- Constantes internas ---
	static constexpr int HELP_COUNTER_ = 1000;
	static constexpr int GAME_COMPLETED_START_FADE_ = 500;
	static constexpr int GAME_COMPLETED_END_ = 700;
	static constexpr int GAME_OVER_COUNTER_ = 350;
	static constexpr int TIME_STOPPED_COUNTER_ = 360;
	static constexpr int ITEM_POINTS_1_DISK_ODDS_ = 10;
	static constexpr int ITEM_POINTS_2_GAVINA_ODDS_ = 6;
	static constexpr int ITEM_POINTS_3_PACMAR_ODDS_ = 3;
	static constexpr int ITEM_CLOCK_ODDS_ = 5;
	static constexpr int ITEM_COFFEE_ODDS_ = 5;
	static constexpr int ITEM_POWER_BALL_ODDS_ = 0;
	static constexpr int ITEM_COFFEE_MACHINE_ODDS_ = 4;

	// --- Estructuras ---
	struct Helper
	{
		bool need_coffee;			  // Indica si se necesitan cafes
		bool need_coffee_machine;	  // Indica si se necesita PowerUp
		bool need_power_ball;		  // Indica si se necesita una PowerBall
		int counter;				  // Contador para no dar ayudas consecutivas
		int item_disk_odds;			  // Probabilidad de aparición del objeto
		int item_gavina_odds;		  // Probabilidad de aparición del objeto
		int item_pacmar_odds;		  // Probabilidad de aparición del objeto
		int item_clock_odds;		  // Probabilidad de aparición del objeto
		int item_coffee_odds;		  // Probabilidad de aparición del objeto
		int item_coffee_machine_odds; // Probabilidad de aparición del objeto

		Helper()
			: need_coffee(false),
			  need_coffee_machine(false),
			  need_power_ball(false),
			  counter(HELP_COUNTER_),
			  item_disk_odds(ITEM_POINTS_1_DISK_ODDS_),
			  item_gavina_odds(ITEM_POINTS_2_GAVINA_ODDS_),
			  item_pacmar_odds(ITEM_POINTS_3_PACMAR_ODDS_),
			  item_clock_odds(ITEM_CLOCK_ODDS_),
			  item_coffee_odds(ITEM_COFFEE_ODDS_),
			  item_coffee_machine_odds(ITEM_COFFEE_MACHINE_ODDS_) {}
	};

	// --- Objetos y punteros ---
	SDL_Renderer *renderer_; // El renderizador de la ventana
	Screen *screen_;		 // Objeto encargado de dibujar en pantalla
	Input *input_;			 // Manejador de entrada
	Scoreboard *scoreboard_; // Objeto para dibujar el marcador

	std::unique_ptr<Background> background_; // Objeto para dibujar el fondo del juego

	SDL_Texture *canvas_; // Textura para dibujar la zona de juego

	std::vector<std::shared_ptr<Player>> players_;			  // Vector con los jugadores
	std::vector<std::unique_ptr<Bullet>> bullets_;			  // Vector con las balas
	std::vector<std::unique_ptr<Item>> items_;				  // Vector con los items
	std::vector<std::unique_ptr<SmartSprite>> smart_sprites_; // Vector con los smartsprites
	std::vector<std::unique_ptr<PathSprite>> path_sprites_;	  // Vector con los pathsprites

	std::shared_ptr<Texture> bullet_texture_;							 // Textura para las balas
	std::vector<std::shared_ptr<Texture>> item_textures_;				 // Vector con las texturas de los items
	std::vector<std::vector<std::shared_ptr<Texture>>> player_textures_; // Vector con todas las texturas de los jugadores

	std::vector<std::shared_ptr<Texture>> game_text_textures_; // Vector con las texturas para los sprites con textos

	std::vector<std::vector<std::string>> item_animations_;	  // Vector con las animaciones de los items
	std::vector<std::vector<std::string>> player_animations_; // Vector con las animaciones del jugador

	std::unique_ptr<Fade> fade_in_;					  // Objeto para renderizar fades
	std::unique_ptr<Fade> fade_out_;				  // Objeto para renderizar fades
	std::unique_ptr<BalloonManager> balloon_manager_; // Objeto para gestionar los globos
	std::unique_ptr<Tabe> tabe_;					  // Objeto para gestionar el Tabe Volaor
	std::vector<Path> paths_;						  // Vector con los recorridos precalculados almacenados

	// --- Variables de estado ---
	HiScoreEntry hi_score_ = HiScoreEntry(
		options.game.hi_score_table[0].name,
		options.game.hi_score_table[0].score); // Máxima puntuación y nombre de quien la ostenta

	Demo demo_;											  // Variable con todas las variables relacionadas con el modo demo
	GameDifficulty difficulty_ = options.game.difficulty; // Dificultad del juego
	Helper helper_;										  // Variable para gestionar las ayudas
	Uint64 ticks_ = 0;									  // Contador de ticks para ajustar la velocidad del programa
	bool coffee_machine_enabled_ = false;				  // Indica si hay una máquina de café en el terreno de juego
	bool hi_score_achieved_ = false;					  // Indica si se ha superado la puntuación máxima
	bool paused_ = false;								  // Indica si el juego está pausado (no se deberia de poder utilizar en el modo arcade)
	float difficulty_score_multiplier_;					  // Multiplicador de puntos en función de la dificultad
	int counter_ = 0;									  // Contador para el juego
	int game_completed_counter_ = 0;					  // Contador para el tramo final, cuando se ha completado la partida y ya no aparecen más enemigos
	int game_over_counter_ = GAME_OVER_COUNTER_;		  // Contador para el estado de fin de partida
	int time_stopped_counter_ = 0;						  // Temporizador para llevar la cuenta del tiempo detenido
	int total_power_to_complete_game_;					  // La suma del poder necesario para completar todas las fases
	int menace_current_ = 0;							  // Nivel de amenaza actual
	int menace_threshold_ = 0;							  // Umbral del nivel de amenaza. Si el nivel de amenaza cae por debajo del umbral, se generan más globos. Si el umbral aumenta, aumenta el número de globos
	GameState state_ = GameState::FADE_IN;				  // Estado

#ifdef DEBUG
	bool auto_pop_balloons_ = false; // Si es true, incrementa automaticamente los globos explotados

	// Comprueba los eventos en el modo DEBUG
	void checkDebugEvents(const SDL_Event &event);
#endif

	// --- Métodos internos ---

	void update();																					  // Actualiza el juego
	void render();																					  // Dibuja el juego
	void checkEvents();																				  // Comprueba los eventos que hay en cola
	void setResources();																			  // Asigna texturas y animaciones
	void updateHiScore();																			  // Actualiza el valor de HiScore en caso necesario
	void updatePlayers();																			  // Actualiza las variables del jugador
	void renderPlayers();																			  // Dibuja a los jugadores
	void updateStage();																				  // Comprueba si hay cambio de fase y actualiza las variables
	void updateGameStateGameOver();																	  // Actualiza el estado de fin de la partida
	void destroyAllItems();																			  // Destruye todos los items
	bool checkPlayerBalloonCollision(std::shared_ptr<Player> &player);								  // Comprueba la colisión entre el jugador y los globos activos
	void checkPlayerItemCollision(std::shared_ptr<Player> &player);									  // Comprueba la colisión entre el jugador y los items
	void checkBulletCollision();																	  // Comprueba y procesa la colisión de las balas
	void updateBullets();																			  // Mueve las balas activas
	void renderBullets();																			  // Pinta las balas activas
	void createBullet(int x, int y, BulletType kind, bool powered_up, int owner);					  // Crea un objeto bala
	void freeBullets();																				  // Vacia el vector de balas
	void updateItems();																				  // Actualiza los items
	void renderItems();																				  // Pinta los items activos
	ItemType dropItem();																			  // Devuelve un item en función del azar
	void createItem(ItemType type, float x, float y);												  // Crea un objeto item
	void freeItems();																				  // Vacia el vector de items
	void createItemText(int x, std::shared_ptr<Texture> texture);									  // Crea un objeto PathSprite
	void createMessage(const std::vector<Path> &paths, std::shared_ptr<Texture> texture);			  // Crea un objeto PathSprite
	void freeSmartSprites();																		  // Vacia el vector de smartsprites
	void freePathSprites();																			  // Vacia el vector de pathsprites
	void throwCoffee(int x, int y);																	  // Crea un SpriteSmart para arrojar el item café al recibir un impacto
	void updateSmartSprites();																		  // Actualiza los SpriteSmarts
	void renderSmartSprites();																		  // Pinta los SpriteSmarts activos
	void updatePathSprites();																		  // Actualiza los PathSprites
	void renderPathSprites();																		  // Pinta los PathSprites activos
	void killPlayer(std::shared_ptr<Player> &player);												  // Acciones a realizar cuando el jugador muere
	void updateTimeStopped();																		  // Actualiza y comprueba el valor de la variable
	void updateBackground();																		  // Actualiza el fondo
	void initPaths();																				  // Inicializa las variables que contienen puntos de ruta para mover objetos
	void enableTimeStopItem();																		  // Habilita el efecto del item de detener el tiempo
	void disableTimeStopItem();																		  // Deshabilita el efecto del item de detener el tiempo
	void updateHelper();																			  // Actualiza las variables de ayuda
	bool allPlayersAreWaitingOrGameOver();															  // Comprueba si todos los jugadores han terminado de jugar
	bool allPlayersAreGameOver();																	  // Comprueba si todos los jugadores han terminado de jugar
	bool allPlayersAreNotPlaying();																	  // Comprueba si todos los jugadores han terminado de jugar
	void updateScoreboard();																		  // Actualiza el marcador
	void fillCanvas();																				  // Dibuja los elementos de la zona de juego en su textura
	void pause(bool value);																			  // Pausa el juego
	void addScoreToScoreBoard(const std::shared_ptr<Player> &player);								  // Añade una puntuación a la tabla de records
	void checkAndUpdatePlayerStatus(int active_player_index, int inactive_player_index);			  // Saca del estado de GAME OVER al jugador si el otro está activo
	void checkPlayersStatusPlaying();																  // Comprueba el estado de juego de los jugadores
	std::shared_ptr<Player> getPlayer(int id);														  // Obtiene un jugador a partir de su "id"
	int getController(int playerId);																  // Obtiene un controlador a partir del "id" del jugador
	void checkInput();																				  // Gestiona la entrada durante el juego
	void checkPauseInput();																			  // Verifica si alguno de los controladores ha solicitado una pausa y actualiza el estado de pausa del juego.
	void handleDemoMode();																			  // Gestiona las entradas de los jugadores en el modo demo, incluyendo movimientos y disparos automáticos.
	void handleDemoPlayerInput(const std::shared_ptr<Player> &player, int index);					  // Procesa las entradas para un jugador específico durante el modo demo.
	void handleFireInput(const std::shared_ptr<Player> &player, BulletType bulletType);				  // Maneja el disparo de un jugador, incluyendo la creación de balas y la gestión del tiempo de espera entre disparos.
	void handlePlayersInput();																		  // Gestiona las entradas de todos los jugadores en el modo normal (fuera del modo demo).
	void handleNormalPlayerInput(const std::shared_ptr<Player> &player);							  // Maneja las entradas de movimiento y disparo para un jugador en modo normal.
	void handleFireInputs(const std::shared_ptr<Player> &player, bool autofire, int controllerIndex); // Procesa las entradas de disparo del jugador, permitiendo disparos automáticos si está habilitado.
	void handlePlayerContinue(const std::shared_ptr<Player> &player);								  // Maneja la continuación del jugador cuando no está jugando, permitiendo que continúe si se pulsa el botón de inicio.
	void handleNameInput(const std::shared_ptr<Player> &player);									  // Procesa las entradas para la introducción del nombre del jugador.
	void initDemo(int player_id);																	  // Inicializa las variables para el modo DEMO
	void setTotalPower();																			  // Calcula el poder total necesario para completar el juego
	void initScoreboard();																			  // Inicializa el marcador
	void initDifficultyVars();																		  // Inicializa las opciones relacionadas con la dificultad
	void initPlayers(int player_id);																  // Inicializa los jugadores
	void playMusic();																				  // Hace sonar la música
	void stopMusic();																				  // Detiene la música
	void updateDemo();																				  // Actualiza las variables durante el modo demo
#ifdef RECORDING
	void updateRecording(); // Actualiza las variables durante el modo de grabación
#endif
	void updateGameStateFadeIn();				  // Actualiza las variables durante dicho estado
	void updateGameStateEnteringPlayer();		  // Actualiza las variables durante dicho estado
	void updateGameStateShowingGetReadyMessage(); // Actualiza las variables durante dicho estado
	void updateGameStatePlaying();				  // Actualiza las variables durante el transcurso normal del juego
	void updateGameStateCompleted();			  // Gestiona eventos para el estado del final del juego
	void checkState();							  // Comprueba el estado del juego
	void cleanVectors();						  // Vacía los vectores de elementos deshabilitados
	void updateMenace();						  // Gestiona el nivel de amenaza
	void evaluateAndSetMenace();				  // Calcula y establece el valor de amenaza en funcion de los globos activos
	void checkAndUpdateBalloonSpeed();			  // Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
	void setState(GameState state);				  // Cambia el estado del juego
};