#pragma once

#include <SDL2/SDL.h>
#include "balloon.h"
#include "bullet.h"
#include "common/asset.h"
#include "common/input.h"
#include "common/jail_audio.h"
#include "common/menu.h"
#include "common/movingsprite.h"
#include "common/screen.h"
#include "common/smartsprite.h"
#include "common/sprite.h"
#include "common/text.h"
#include "common/utils.h"
#include "common/writer.h"
#include "const.h"
#include "fade.h"
#include "item.h"
#include "player.h"
#include <iostream>

#ifndef GAME_H
#define GAME_H

// 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 SHAKE_COUNTER 10
#define HELP_COUNTER 1000
#define GAME_COMPLETED_START_FADE 500
#define GAME_COMPLETED_END 700

// Formaciones enemigas
#define NUMBER_OF_ENEMY_FORMATIONS 100
#define MAX_NUMBER_OF_ENEMIES_IN_A_FORMATION 50

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

// Clase Game
class Game
{
private:
	struct enemyInits_t
	{
		int x;					// Posición en el eje X donde crear al enemigo
		int y;					// Posición en el eje Y donde crear al enemigo
		float velX;				// Velocidad inicial en el eje X
		Uint8 kind;				// Tipo de enemigo
		Uint16 creationCounter; // Temporizador para la creación del enemigo
	};

	struct enemyFormation_t // Contiene la información de una formación enemiga
	{
		Uint8 numberOfEnemies;									 // Cantidad de enemigos que forman la formación
		enemyInits_t init[MAX_NUMBER_OF_ENEMIES_IN_A_FORMATION]; // Vector con todas las inicializaciones de los enemigos de la formación
	};

	struct enemyPool_t
	{
		enemyFormation_t *set[10]; // Conjunto de formaciones enemigas
	};

	struct stage_t // Contiene todas las variables relacionadas con una fase
	{
		enemyPool_t *enemyPool; // El conjunto de formaciones enemigas de la fase
		Uint16 currentPower;	// Cantidad actual de poder
		Uint16 powerToComplete; // Cantidad de poder que se necesita para completar la fase
		Uint8 maxMenace;		// Umbral máximo de amenaza de la fase
		Uint8 minMenace;		// Umbral mínimo de amenaza de la fase
		Uint8 number;			// Numero de fase
	};

	struct effect_t
	{
		bool flash;			// Indica si se ha de pintar la pantalla de blanco
		bool shake;			// Indica si se ha de agitar la pantalla
		Uint8 shakeCounter; // Contador para medir el tiempo que dura el efecto
	};

	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
		Uint16 counter;						  // Contador para el modo demo
		demoKeys_t keys;					  // Variable con las pulsaciones de teclas del modo demo
		demoKeys_t dataFile[TOTAL_DEMO_DATA]; // Datos del fichero 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
	Lang *lang;				// Objeto para gestionar los textos en diferentes idiomas
	Input *input;			// Manejador de entrada
	section_t *section;		// Seccion actual dentro del juego

	std::vector<Player *> players;			 // Vector con los jugadores
	std::vector<Balloon *> balloons;		 // Vector con los globos
	std::vector<Bullet *> bullets;			 // Vector con las balas
	std::vector<Item *> items;				 // Vector con los items
	std::vector<SmartSprite *> smartSprites; // Vector con los smartsprites

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

	Texture *gameBuildingsTexture;	// Textura con los edificios de fondo
	Texture *gameCloudsTexture;		// Textura con las nubes de fondo
	Texture *gameGrassTexture;		// Textura con la hierba del suelo
	Texture *gamePowerMeterTexture; // Textura con el marcador de poder de la fase
	Texture *gameSkyColorsTexture;	// Textura con los diferentes colores de fondo del juego
	Texture *gameTextTexture;		// Textura para los sprites con textos
	Texture *gameOverTexture;		// Textura para la pantalla de game over
	Texture *gameOverEndTexture;	// Textura para la pantalla de game over de acabar el juego

	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

	Text *text;			  // Fuente para los textos del juego
	Text *textBig;		  // Fuente de texto grande
	Text *textScoreBoard; // Fuente para el marcador del juego
	Text *textNokia2;	  // Otra fuente de texto para mensajes
	Text *textNokiaBig2;  // Y la versión en grande

	Menu *gameOverMenu; // Menú de la pantalla de game over
	Menu *pauseMenu;	// Menú de la pantalla de pausa

	Fade *fade;				 // Objeto para renderizar fades
	SDL_Event *eventHandler; // Manejador de eventos

	MovingSprite *clouds1A;	  // Sprite para las nubes superiores
	MovingSprite *clouds1B;	  // Sprite para las nubes superiores
	MovingSprite *clouds2A;	  // Sprite para las nubes inferiores
	MovingSprite *clouds2B;	  // Sprite para las nubes inferiores
	SmartSprite *n1000Sprite; // Sprite con el texto 1.000
	SmartSprite *n2500Sprite; // Sprite con el texto 2.500
	SmartSprite *n5000Sprite; // Sprite con el texto 5.000

	Sprite *buildingsSprite;   // Sprite con los edificios de fondo
	Sprite *skyColorsSprite;   // Sprite con los graficos del degradado de color de fondo
	Sprite *grassSprite;	   // Sprite para la hierba
	Sprite *powerMeterSprite;  // Sprite para el medidor de poder de la fase
	Sprite *gameOverSprite;	   // Sprite para dibujar los graficos del game over
	Sprite *gameOverEndSprite; // Sprite para dibujar los graficos del game over de acabar el juego

	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 *gameMusic; // Musica de fondo

	// Variables
	int numPlayers;												 // Numero de jugadores
	Uint32 ticks;												 // Contador de ticks para ajustar la velocidad del programa
	Uint8 ticksSpeed;											 // Velocidad a la que se repiten los bucles del programa
	Uint32 hiScore;												 // Puntuación máxima
	bool hiScoreAchieved;										 // Indica si se ha superado la puntuación máxima
	stage_t stage[10];											 // Variable con los datos de cada pantalla
	Uint8 currentStage;											 // Indica la fase actual
	Uint8 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
	Uint16 deathCounter;										 // Contador para la animación de muerte del jugador
	Uint8 menaceCurrent;										 // Nivel de amenaza actual
	Uint8 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 numero de globos
	bool timeStopped;											 // Indica si el tiempo está detenido
	Uint16 timeStoppedCounter;									 // Temporizador para llevar la cuenta del tiempo detenido
	Uint32 counter;												 // Contador para el juego
	Uint32 scoreDataFile[TOTAL_SCORE_DATA];						 // Datos del fichero de puntos
	SDL_Rect skyColorsRect[4];									 // Vector con las coordenadas de los 4 colores de cielo
	Uint16 balloonsPopped;										 // Lleva la cuenta de los globos explotados
	Uint8 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
	effect_t effect;											 // Variable para gestionar los efectos visuales
	helper_t helper;											 // Variable para gestionar las ayudas
	bool powerBallEnabled;										 // Indica si hay una powerball ya activa
	Uint8 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
	Uint8 difficulty;											 // Dificultad del juego
	float difficultyScoreMultiplier;							 // Multiplicador de puntos en función de la dificultad
	color_t difficultyColor;									 // Color asociado a la dificultad
	struct options_t *options;									 // Variable con todas las variables de las opciones del programa
	Uint8 onePlayerControl;										 // Variable para almacenar el valor de las opciones
	enemyFormation_t enemyFormation[NUMBER_OF_ENEMY_FORMATIONS]; // Vector con todas las formaciones enemigas
	enemyPool_t enemyPool[10];									 // Variable con los diferentes conjuntos de formaciones enemigas
	Uint8 lastStageReached;										 // Contiene el numero 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
	int cloudsSpeed;											 // Velocidad a la que se desplazan las nubes
	int pauseCounter;											 // Contador para salir del menu de pausa y volver al juego
	bool leavingPauseMenu;										 // Indica si esta saliendo del menu de pausa para volver al juego

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

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

	// Carga el fichero de puntos
	bool loadScoreFile();

	// Carga el fichero de datos para la demo
	bool loadDemoFile();

	// Guarda el fichero de puntos
	bool saveScoreFile();

	// Sube la puntuación online
	bool sendOnlineScore();

	// Guarda el fichero de datos para la demo
	bool saveDemoFile();

	// Inicializa las formaciones enemigas
	void initEnemyFormations();

	// Inicializa los conjuntos de formaciones
	void initEnemyPools();

	// Inicializa las fases del juego
	void initGameStages();

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

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

	// Establece el valor de la variable
	void setHiScore(Uint32 score);

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

	// Transforma un valor numérico en una cadena de 6 cifras
	std::string updateScoreText(Uint32 num);

	// Pinta el marcador en pantalla usando un objeto texto
	void renderScoreBoard();

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

	// Dibuja a los jugadores
	void renderPlayers();

	// Actualiza las variables de la fase
	void updateStage();

	// Actualiza el estado de muerte
	void updateDeath();

	// Renderiza el fade final cuando se acaba la partida
	void renderDeathFade(int counter);

	// Actualiza los globos
	void updateBalloons();

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

	// Crea un globo nuevo en el vector de globos
	Uint8 createBalloon(float x, int y, Uint8 kind, float velx, float speed, Uint16 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(Balloon *balloon);

	// Explosiona un globo. Lo destruye
	void destroyBalloon(Balloon *balloon);

	// Explosiona todos los globos
	void popAllBalloons();

	// Destruye todos los globos
	void destroyAllBalloons();

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

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

	// Obtiene el numero de globos activos
	Uint8 countBalloons();

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

	// Comprueba la colisión entre el jugador y los globos activos
	bool checkPlayerBalloonCollision(Player *player);

	// Comprueba la colisión entre el jugador y los items
	void checkPlayerItemCollision(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, Uint8 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
	Uint8 dropItem();

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

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

	// Crea un objeto SmartSprite
	void createItemScoreSprite(int x, int y, SmartSprite *sprite);

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

	// Dibuja el efecto de flash
	void renderFlashEffect();

	// Actualiza el efecto de agitar la pantalla
	void updateShakeEffect();

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

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

	// Obtiene el valor de la variable
	Uint8 getMenace();

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

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

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

	// Incrementa el valor de la variable
	void incTimeStoppedCounter(Uint16 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();

	// Dibuja el fondo
	void renderBackground();

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

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

	// Agita la pantalla
	void shakeScreen();

	// Actualiza las variables del menu de pausa del juego
	void updatePausedGame();

	// Dibuja el menu de pausa del juego
	void renderPausedGame();

	// Bucle para el menu de pausa del juego
	void runPausedGame();

	// Actualiza los elementos de la pantalla de game over
	void updateGameOverScreen();

	// Dibuja los elementos de la pantalla de game over
	void renderGameOverScreen();

	// Bucle para la pantalla de game over
	void runGameOverScreen();

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

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

public:
	// Constructor
	Game(int numPlayers, int currentStage, SDL_Renderer *renderer, Screen *screen, Asset *asset, Lang *lang, Input *input, bool demo, options_t *options, section_t *section);

	// Destructor
	~Game();

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

#endif
