#pragma once

#include <SDL2/SDL_render.h>	  // for SDL_Renderer, SDL_Texture
#include <SDL2/SDL_stdinc.h>	  // for Uint32, Uint8
#include <memory>				  // for shared_ptr, unique_ptr
#include <string>				  // for string
#include <vector>				  // for vector
#include "balloon.h"			  // for Balloon
#include "manage_hiscore_table.h" // for HiScoreEntry
#include "options.h"			  // for Options, OptionsGame, options
#include "player.h"				  // for Player
#include "utils.h"				  // for Demo
class Asset;					  // lines 14-14
class Background;				  // lines 15-15
class BalloonFormations;		  // lines 16-16
class Bullet;					  // lines 17-17
class Explosions;				  // lines 18-18
class Fade;						  // lines 19-19
class Input;					  // lines 20-20
class Item;						  // lines 21-21
class PathSprite;				  // lines 22-22
class Scoreboard;				  // lines 23-23
class Screen;					  // lines 24-24
class SmartSprite;				  // lines 25-25
class Texture;					  // lines 27-27
enum class BulletType : Uint8;	  // lines 28-28
enum class ItemType;			  // lines 29-29
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;

/*
	Esta clase gestiona un estado del programa. Se encarga de toda la parte en la
	que se está jugando.

	Tiene:
		- Cacheadas todas las texturas y animaciones que usaran los diferentes objetos.
		  Mediante el método loadMedia() almacena en vectores todos los recursos
		- Tiene vectores con objetos: jugadores, enemigos, balas, explosiones, objetos y otros (sprites con los puntos al coger objetos)
		- Se encarga de comprobar las colisiones entre los diferentes objetos, los marca como deshabilitados si es el caso y
		  luego revisa los vectores para eliminar los objetos deshabilitados

	Utiliza:
		- Un objeto para dibujar el fondo animado
		- Un objeto para dibujar el marcador

	La clase comprueba el nivel de amenaza que hay en pantalla contando el número de enemigos y su peligrosidad y actua en consecuencia:
		- Generando items
		- Generando nuevas oleadas enemigas

	Mientras haya un jugador activo siempre puede unirse un segundo jugador. Cada vez que un jugador muere
	aparece una cuenta atras para permitirle continuar. Si la cuenta atras de ambos jugadores llega a cero,
	el juego termina. Cuando ambos jugadores han finalizado, se permite introducir nombre para la tabla de records
	adjuntando la máxima puntuación obtenida en la partida, solo en caso de que hayan conseguido superar la
	puntuación mínima.
*/

// Clase Game
class Game
{
private:
	// Enum
	enum class GameState
	{
		PLAYING,
		COMPLETED,
		GAME_OVER,
	};

	// Contadores
	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;

	// Porcentaje de aparición de los objetos
	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

		// Constructor con valores predeterminados
		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
	Asset *asset_;			 // Objeto que gestiona todos los ficheros de recursos
	Input *input_;			 // Manejador de entrada
	Scoreboard *scoreboard_; // Objeto para dibujar el marcador

	std::unique_ptr<Background> background_;				// Objeto para dibujar el fondo del juego
	std::unique_ptr<Explosions> explosions_;				// Objeto para dibujar explosiones
	std::unique_ptr<BalloonFormations> balloon_formations_; // Objeto para gestionar las oleadas enemigas

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

	std::vector<std::shared_ptr<Player>> players_;			  // Vector con los jugadores
	std::vector<std::shared_ptr<Balloon>> balloons_;		  // Vector con los globos
	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 smartsprites

	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::shared_ptr<Texture>> balloon_textures_;			 // Vector con las texturas de los globos
	std::vector<std::shared_ptr<Texture>> explosions_textures_;			 // Vector con las texturas de las explosiones
	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::vector<std::vector<std::string>> balloon_animations_;	  // Vector con las animaciones de los globos
	std::vector<std::vector<std::string>> explosions_animations_; // Vector con las animaciones de las explosiones

	std::unique_ptr<Fade> fade_; // Objeto para renderizar fades
	std::vector<Path> paths_;	 // Vector con los recorridos precalculados almacenados

	// Variables
	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

	int current_stage_;									  // Indica la fase actual
	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
	Uint32 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)
	bool power_ball_enabled_ = false;					  // Indica si hay una powerball ya activa
	float balloon_speed_;								  // Velocidad a la que se mueven los enemigos
	float default_balloon_speed_;						  // Velocidad base de los enemigos, sin incrementar
	float difficulty_score_multiplier_;					  // Multiplicador de puntos en función de la dificultad
	int balloon_deploy_counter_ = 0;					  // Cuando se lanza una formación, se le da un valor y no sale otra hasta que llegue a cero
	int balloons_popped_ = 0;							  // Lleva la cuenta de los globos explotados
	int counter_ = 0;									  // Contador para el juego
	int current_power_ = 0;								  // Poder actual almacenado para completar la fase
	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 last_balloon_deploy_ = 0;						  // Guarda cual ha sido la última formación desplegada para no repetir;
	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
	int power_ball_counter_ = 0;						  // Contador de formaciones enemigas entre la aparicion de una PowerBall y otra
	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
	GameState state_ = GameState::PLAYING;				  // Estado
#ifdef DEBUG
	bool auto_pop_balloons_ = false; // Si es true, incrementa automaticamente los globos explotados
#endif

	// Actualiza el juego
	void update();

	// Dibuja el juego
	void render();

	// Comprueba los eventos que hay en cola
	void checkEvents();

	// Asigna texturas y animaciones
	void setResources();

	// Crea una formación de enemigos
	void deployBalloonFormation();

	// Aumenta el poder de la fase
	void increaseStageCurrentPower(int power);

	// Actualiza el valor de HiScore en caso necesario
	void updateHiScore();

	// Actualiza las variables del jugador
	void updatePlayers();

	// Dibuja a los jugadores
	void renderPlayers();

	// Comprueba si hay cambio de fase y actualiza las variables
	void updateStage();

	// Actualiza el estado de fin de la partida
	void updateGameOverState();

	// Actualiza los globos
	void updateBalloons();

	// Pinta en pantalla todos los globos activos
	void renderBalloons();

	// Crea un globo nuevo en el vector de globos
	std::shared_ptr<Balloon> createBalloon(float x, int y, BalloonType type, BalloonSize size, float velx, float speed, int stopped_counter);

	// Crea un globo a partir de otro globo
	void createChildBalloon(const std::shared_ptr<Balloon> &balloon, const std::string &direction);

	// Crea una PowerBall
	void createPowerBall();

	// Establece la velocidad de los globos
	void setBalloonSpeed(float speed);

	// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
	void checkAndUpdateBalloonSpeed();

	// Explosiona un globo. Lo destruye y crea otros dos si es el caso
	void popBalloon(std::shared_ptr<Balloon> balloon);

	// Explosiona un globo. Lo destruye
	void destroyBalloon(std::shared_ptr<Balloon> &balloon);

	// Destruye todos los globos
	void destroyAllBalloons();

	// Destruye todos los items
	void destroyAllItems();

	// Detiene todos los globos
	void stopAllBalloons();

	// Pone en marcha todos los globos
	void startAllBalloons();

	// Cambia el color de todos los globos
	void reverseColorsToAllBalloons();

	// Cambia el color de todos los globos
	void normalColorsToAllBalloons();

	// Vacia el vector de globos
	void freeBalloons();

	// Comprueba la colisión entre el jugador y los globos activos
	bool checkPlayerBalloonCollision(std::shared_ptr<Player> &player);

	// Comprueba la colisión entre el jugador y los items
	void checkPlayerItemCollision(std::shared_ptr<Player> &player);

	// Comprueba la colisión entre las balas y los globos
	void checkBulletBalloonCollision();

	// Mueve las balas activas
	void moveBullets();

	// Pinta las balas activas
	void renderBullets();

	// Crea un objeto bala
	void createBullet(int x, int y, BulletType kind, bool powered_up, int owner);

	// Vacia el vector de balas
	void freeBullets();

	// Actualiza los items
	void updateItems();

	// Pinta los items activos
	void renderItems();

	// Devuelve un item en función del azar
	ItemType dropItem();

	// Crea un objeto item
	void createItem(ItemType type, float x, float y);

	// Vacia el vector de items
	void freeItems();

	// Crea un objeto PathSprite
	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);

	// Vacia el vector de smartsprites
	void freeSmartSprites();

	// Vacia el vector de pathsprites
	void freePathSprites();

	// Crea un SpriteSmart para arrojar el item café al recibir un impacto
	void throwCoffee(int x, int y);

	// Actualiza los SpriteSmarts
	void updateSmartSprites();

	// Pinta los SpriteSmarts activos
	void renderSmartSprites();

	// Actualiza los PathSprites
	void updatePathSprites();

	// Pinta los PathSprites activos
	void renderPathSprites();

	// Acciones a realizar cuando el jugador muere
	void killPlayer(std::shared_ptr<Player> &player);

	// Calcula y establece el valor de amenaza en funcion de los globos activos
	void evaluateAndSetMenace();

	// Actualiza la variable EnemyDeployCounter
	void updateBalloonDeployCounter();

	// Actualiza y comprueba el valor de la variable
	void updateTimeStopped();

	// Gestiona el nivel de amenaza
	void updateMenace();

	// Actualiza el fondo
	void updateBackground();

	// Inicializa las variables que contienen puntos de ruta para mover objetos
	void initPaths();

	// Habilita el efecto del item de detener el tiempo
	void enableTimeStopItem();

	// Deshabilita el efecto del item de detener el tiempo
	void disableTimeStopItem();

	// Indica si se puede crear una powerball
	bool canPowerBallBeCreated();

	// Calcula el poder actual de los globos en pantalla
	int calculateScreenPower();

	// Actualiza las variables de ayuda
	void updateHelper();

	// Comprueba si todos los jugadores han terminado de jugar
	bool allPlayersAreWaitingOrGameOver();

	// Comprueba si todos los jugadores han terminado de jugar
	bool allPlayersAreGameOver();

	// Comprueba si todos los jugadores han terminado de jugar
	bool allPlayersAreNotPlaying();

	// Recarga las texturas
	void reloadTextures();

	// Actualiza el marcador
	void updateScoreboard();

	// Dibuja los elementos de la zona de juego en su textura
	void fillCanvas();

	// Pausa el juego
	void pause(bool value);

	// Comprueba si la música ha de estar sonando
	void checkMusicStatus();

	// Añade una puntuación a la tabla de records
	void addScoreToScoreBoard(const std::string &name, int score);

	// Saca del estado de GAME OVER al jugador si el otro está activo
	void checkAndUpdatePlayerStatus(int active_player_index, int inactive_player_index);

	// Comprueba el estado de juego de los jugadores
	void checkPlayersStatusPlaying();

	// Obtiene un jugador a partir de su "id"
	std::shared_ptr<Player> getPlayer(int id);

	// Obtiene un controlador a partir del "id" del jugador
	int getController(int playerId);

	// Gestiona la entrada durante el juego
	void checkInput();

	// Verifica si alguno de los controladores ha solicitado una pausa y actualiza el estado de pausa del juego.
	void checkPauseInput();

	// Gestiona las entradas de los jugadores en el modo demo, incluyendo movimientos y disparos automáticos.
	void handleDemoMode();

	// Procesa las entradas para un jugador específico durante el modo demo.
	void handleDemoPlayerInput(const std::shared_ptr<Player> &player, int index);

	// Maneja el disparo de un jugador, incluyendo la creación de balas y la gestión del tiempo de espera entre disparos.
	void handleFireInput(const std::shared_ptr<Player> &player, BulletType bulletType);

	// Gestiona las entradas de todos los jugadores en el modo normal (fuera del modo demo).
	void handlePlayersInput();

	// Maneja las entradas de movimiento y disparo para un jugador en modo normal.
	void handleNormalPlayerInput(const std::shared_ptr<Player> &player);

	// Procesa las entradas de disparo del jugador, permitiendo disparos automáticos si está habilitado.
	void handleFireInputs(const std::shared_ptr<Player> &player, bool autofire, int controllerIndex);

	// Maneja la continuación del jugador cuando no está jugando, permitiendo que continúe si se pulsa el botón de inicio.
	void handlePlayerContinue(const std::shared_ptr<Player> &player);

	// Procesa las entradas para la introducción del nombre del jugador.
	void handleNameInput(const std::shared_ptr<Player> &player);

	// Inicializa las variables para el modo DEMO
	void initDemo(int player_id);

	// Calcula el poder total necesario para completar el juego
	void setTotalPower();

	// Inicializa el marcador
	void initScoreboard();

	// Inicializa las opciones relacionadas con la dificultad
	void initDifficultyVars();

	// Inicializa los jugadores
	void initPlayers(int player_id);

	// Crea dos globos gordos
	void createTwoBigBalloons();

	// Pausa la música
	void pauseMusic();

	// Reanuda la música
	void resumeMusic();

	// Detiene la música
	void stopMusic();

	// Actualiza las variables durante el modo demo
	void updateDemo();
#ifdef RECORDING
	// Actualiza las variables durante el modo de grabación
	void updateRecording();
#endif
	// Actualiza las variables durante el transcurso normal del juego
	void updateGame();

	// Actualiza las variables durante el transcurso del final del juego
	void updateCompletedGame();

	// Gestiona eventos para el estado del final del juego
	void updateCompletedState();

	// Comprueba el estado del juego
	void checkState();

	// Vacía los vectores de elementos deshabilitados
	void cleanVectors();

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

	// Destructor
	~Game();

	// Bucle para el juego
	void run();
};