#pragma once

#include <SDL2/SDL_events.h> // for SDL_Event
#include <SDL2/SDL_render.h> // for SDL_Renderer, SDL_Texture
#include <SDL2/SDL_stdinc.h> // for Uint32
#include <string>			 // for string
#include <vector>			 // for vector
#include "section.h"		 // for options_e
#include "utils.h"			 // for DemoKeys, Color, HiScoreEntry
#include <memory>
#include "asset.h"			  // lines 11-11
#include "background.h"		  // lines 12-12
#include "balloon.h"		  // lines 13-13
#include "bullet.h"			  // lines 14-14
#include "enemy_formations.h" // lines 15-15
#include "explosions.h"		  // lines 16-16
#include "fade.h"			  // lines 17-17
#include "input.h"			  // lines 18-18
#include "item.h"			  // lines 19-19
#include "player.h"			  // lines 20-20
#include "scoreboard.h"		  // lines 21-21
#include "screen.h"			  // lines 22-22
#include "smart_sprite.h"	  // lines 23-23
#include "text.h"			  // lines 24-24
#include "texture.h"		  // lines 24-24
enum class BulletType;
struct JA_Music_t; // lines 26-26
struct JA_Sound_t; // lines 27-27

#define GAME_MODE_DEMO_OFF false
#define GAME_MODE_DEMO_ON true

// Cantidad de elementos a escribir en los ficheros de datos
#define TOTAL_SCORE_DATA 3
#define TOTAL_DEMO_DATA 2000

// Contadores
#define STAGE_COUNTER 200
#define HELP_COUNTER 1000
#define GAME_COMPLETED_START_FADE 500
#define GAME_COMPLETED_END 700

// Porcentaje de aparición de los objetos
#define ITEM_POINTS_1_DISK_ODDS 10
#define ITEM_POINTS_2_GAVINA_ODDS 6
#define ITEM_POINTS_3_PACMAR_ODDS 3
#define ITEM_CLOCK_ODDS 5
#define ITEM_COFFEE_ODDS 5
#define ITEM_POWER_BALL_ODDS 0
#define ITEM_COFFEE_MACHINE_ODDS 4

// Valores para las variables asociadas a los objetos
#define TIME_STOPPED_COUNTER 300

/*
	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:
	struct helper_t
	{
		bool needCoffee;		   // Indica si se necesitan cafes
		bool needCoffeeMachine;	   // Indica si se necesita PowerUp
		bool needPowerBall;		   // Indica si se necesita una PowerBall
		int counter;			   // Contador para no dar ayudas consecutivas
		int itemPoints1Odds;	   // Probabilidad de aparición del objeto
		int itemPoints2Odds;	   // Probabilidad de aparición del objeto
		int itemPoints3Odds;	   // Probabilidad de aparición del objeto
		int itemClockOdds;		   // Probabilidad de aparición del objeto
		int itemCoffeeOdds;		   // Probabilidad de aparición del objeto
		int itemCoffeeMachineOdds; // Probabilidad de aparición del objeto
	};

	struct demo_t
	{
		bool enabled;						   // Indica si está activo el modo demo
		bool recording;						   // Indica si está activado el modo para grabar la demo
		int counter;						   // Contador para el modo demo
		DemoKeys keys;						   // Variable con las pulsaciones de teclas del modo demo
		DemoKeys dataFile[2][TOTAL_DEMO_DATA]; // Vector con diferentes sets de datos con los movimientos para la demo
	};

	// 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<EnemyFormations> enemyFormations; // 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<SmartSprite *> smartSprites;		// Vector con los smartsprites

	std::shared_ptr<Texture> bulletTexture;							   // Textura para las balas
	std::vector<std::shared_ptr<Texture>> itemTextures;				   // Vector con las texturas de los items
	std::vector<std::shared_ptr<Texture>> balloonTextures;			   // Vector con las texturas de los globos
	std::vector<std::shared_ptr<Texture>> explosionsTextures;		   // Vector con las texturas de las explosiones
	std::vector<std::shared_ptr<Texture>> player1Textures;			   // Vector con las texturas del jugador
	std::vector<std::shared_ptr<Texture>> player2Textures;			   // Vector con las texturas del jugador
	std::vector<std::vector<std::shared_ptr<Texture>>> playerTextures; // Vector con todas las texturas de los jugadores;

	std::shared_ptr<Texture> gameTextTexture; // Textura para los sprites con textos

	std::vector<std::vector<std::string> *> itemAnimations;		  // Vector con las animaciones de los items
	std::vector<std::vector<std::string> *> playerAnimations;	  // Vector con las animaciones del jugador
	std::vector<std::vector<std::string> *> balloonAnimations;	  // Vector con las animaciones de los globos
	std::vector<std::vector<std::string> *> explosionsAnimations; // Vector con las animaciones de las explosiones

	std::unique_ptr<Text> text;			 // Fuente para los textos del juego
	std::unique_ptr<Text> textBig;		 // Fuente de texto grande
	std::unique_ptr<Text> textNokia2;	 // Otra fuente de texto para mensajes
	std::unique_ptr<Text> textNokiaBig2; // Y la versión en grande

	std::unique_ptr<Fade> fade;				 // Objeto para renderizar fades
	std::unique_ptr<SDL_Event> eventHandler; // Manejador de eventos

	std::shared_ptr<SmartSprite> n1000Sprite; // Sprite con el texto 1.000
	std::shared_ptr<SmartSprite> n2500Sprite; // Sprite con el texto 2.500
	std::shared_ptr<SmartSprite> n5000Sprite; // Sprite con el texto 5.000

	JA_Sound_t *balloonSound;		  // Sonido para la explosión del globo
	JA_Sound_t *bulletSound;		  // Sonido para los disparos
	JA_Sound_t *playerCollisionSound; // Sonido para la colisión del jugador con un enemigo
	JA_Sound_t *hiScoreSound;		  // Sonido para cuando se alcanza la máxima puntuación
	JA_Sound_t *itemDropSound;		  // Sonido para cuando se genera un item
	JA_Sound_t *itemPickUpSound;	  // Sonido para cuando se recoge un item
	JA_Sound_t *coffeeOutSound;		  // Sonido para cuando el jugador pierde el café al recibir un impacto
	JA_Sound_t *stageChangeSound;	  // Sonido para cuando se cambia de fase
	JA_Sound_t *bubble1Sound;		  // Sonido para cuando el jugador muere
	JA_Sound_t *bubble2Sound;		  // Sonido para cuando el jugador muere
	JA_Sound_t *bubble3Sound;		  // Sonido para cuando el jugador muere
	JA_Sound_t *bubble4Sound;		  // Sonido para cuando el jugador muere
	JA_Sound_t *clockSound;			  // Sonido para cuando se detiene el tiempo con el item reloj
	JA_Sound_t *powerBallSound;		  // Sonido para cuando se explota una Power Ball
	JA_Sound_t *coffeeMachineSound;	  // Sonido para cuando la máquina de café toca el suelo

	JA_Music_t *music; // Musica de fondo

	// Variables
	Uint32 ticks;							 // Contador de ticks para ajustar la velocidad del programa
	Uint32 ticksSpeed;						 // Velocidad a la que se repiten los bucles del programa
	bool hiScoreAchieved;					 // Indica si se ha superado la puntuación máxima
	HiScoreEntry hiScore;					 // Máxima puntuación y nombre de quien la ostenta
	int currentStage;						 // Indica la fase actual
	int stageBitmapCounter;					 // Contador para el tiempo visible del texto de Stage
	float stageBitmapPath[STAGE_COUNTER];	 // Vector con los puntos Y por donde se desplaza el texto
	float getReadyBitmapPath[STAGE_COUNTER]; // Vector con los puntos X por donde se desplaza el texto
	int gameOverCounter;					 // Contador para el estado de fin de partida
	int menaceCurrent;						 // Nivel de amenaza actual
	int menaceThreshold;					 // 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
	bool timeStopped;						 // Indica si el tiempo está detenido
	int timeStoppedCounter;					 // Temporizador para llevar la cuenta del tiempo detenido
	int counter;							 // Contador para el juego
	int balloonsPopped;						 // Lleva la cuenta de los globos explotados
	int lastEnemyDeploy;					 // Guarda cual ha sido la última formación desplegada para no repetir;
	int enemyDeployCounter;					 // Cuando se lanza una formación, se le da un valor y no sale otra hasta que llegue a cero
	float enemySpeed;						 // Velocidad a la que se mueven los enemigos
	float defaultEnemySpeed;				 // Velocidad base de los enemigos, sin incrementar
	helper_t helper;						 // Variable para gestionar las ayudas
	bool powerBallEnabled;					 // Indica si hay una powerball ya activa
	int powerBallCounter;					 // Contador de formaciones enemigas entre la aparicion de una PowerBall y otra
	bool coffeeMachineEnabled;				 // Indica si hay una máquina de café en el terreno de juego
	bool gameCompleted;						 // Indica si se ha completado la partida, llegando al final de la ultima pantalla
	int gameCompletedCounter;				 // Contador para el tramo final, cuando se ha completado la partida y ya no aparecen más enemigos
	GameDifficulty difficulty;				 // Dificultad del juego
	float difficultyScoreMultiplier;		 // Multiplicador de puntos en función de la dificultad
	Color difficultyColor;					 // Color asociado a la dificultad
	int lastStageReached;					 // Contiene el número de la última pantalla que se ha alcanzado
	demo_t demo;							 // Variable con todas las variables relacionadas con el modo demo
	int totalPowerToCompleteGame;			 // La suma del poder necesario para completar todas las fases
	bool paused;							 // Indica si el juego está pausado (no se deberia de poder utilizar en el modo arcade)
	int currentPower;						 // Poder actual almacenado para completar la fase
#ifdef DEBUG
	bool autoPopBalloons; // 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();

	// Inicializa las variables necesarias para la sección 'Game'
	void init(int playerID);

	// Carga los recursos necesarios para la sección 'Game'
	void loadMedia();

	// Libera los recursos previamente cargados
	void unloadMedia();

	// Carga el fichero de datos para la demo
	bool loadDemoFile(std::string filePath, DemoKeys (*dataFile)[TOTAL_DEMO_DATA]);
#ifdef RECORDING
	// Guarda el fichero de datos para la demo
	bool saveDemoFile(std::string filePath);
#endif
	// Crea una formación de enemigos
	void deployEnemyFormation();

	// 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 updateGameOver();

	// 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, int kind, float velx, float speed, int stoppedcounter);

	// Crea una PowerBall
	void createPowerBall();

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

	// Incrementa la velocidad de los globos
	void incBalloonSpeed();

	// Decrementa la velocidad de los globos
	void decBalloonSpeed();

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

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

	// Explosiona todos los globos
	void popAllBalloons();

	// Destruye todos los globos
	void destroyAllBalloons();

	// Detiene todos los globos
	void stopAllBalloons(int time);

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

	// Obtiene el número de globos activos
	int countBalloons();

	// 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 poweredUp, 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
	int dropItem();

	// Crea un objeto item
	void createItem(int kind, float x, float y);

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

	// Crea un objeto SmartSprite
	void createItemScoreSprite(int x, int y, std::shared_ptr<SmartSprite> sprite);

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

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

	// Actualiza los SmartSprites
	void updateSmartSprites();

	// Pinta los SmartSprites activos
	void renderSmartSprites();

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

	// Obtiene el valor de la variable
	int getMenace() const;

	// Establece el valor de la variable
	void setTimeStopped(bool value);

	// Obtiene el valor de la variable
	bool isTimeStopped() const;

	// Establece el valor de la variable
	void setTimeStoppedCounter(int value);

	// Incrementa el valor de la variable
	void incTimeStoppedCounter(int value);

	// Actualiza la variable EnemyDeployCounter
	void updateEnemyDeployCounter();

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

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

	// Actualiza el fondo
	void updateBackground();

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

	// Pinta diferentes mensajes en la pantalla
	void renderMessages();

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

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

	// Actualiza el tramo final de juego, una vez completado
	void updateGameCompleted();

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

	// Carga las animaciones
	void loadAnimations(std::string filePath, std::vector<std::string> *buffer);

	// Elimina todos los objetos contenidos en vectores
	void deleteAllVectorObjects();

	// 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(std::string name, int score);

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

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

public:
	// Constructor
	Game(int playerID, int currentStage, bool demo, JA_Music_t *music);

	// Destructor
	~Game();

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