#pragma once

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

#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

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

// Cantidad de elementos del vector de SmartSprites
#define MAX_SMART_SPRITES 10

// Cantidad máxima posible de balas
#define MAX_BULLETS 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

// Cantidad de objetos simultaneos
#define MAX_ITEMS 10

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

	struct debug_t
	{
		bool enabled;			   // Indica si se va a mostrar la información de debug
		Uint8 enemySet;			   // Escoge el set enemigo a generar
		Uint8 gradR, gradG, gradB; // Colores RGB para modificar el color del gradiente de fondo
		float hudW, hudH;		   // Multiplica el tamaño del hud de debug;
	};

	SDL_Renderer *mRenderer; // El renderizador de la ventana
	Screen *mScreen;		 // Objeto encargado de dibujar en pantalla
	Asset *mAsset;			 // Objeto que gestiona todos los ficheros de recursos
	Lang *mLang;			 // Objeto para gestionar los textos en diferentes idiomas
	Input *mInput;			 // Manejador de entrada

	int mNumPlayers;						 // Numero de jugadores
	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

	LTexture *mTextureBalloon;		// Textura para los enemigos
	LTexture *mTextureBullet;		// Textura para las balas
	LTexture *mTextureGameBG;		// Textura para el fondo del juego
	LTexture *mTextureGameText;		// Textura para los sprites con textos
	LTexture *mTextureItems;		// Textura para los items

	Text *mText;		   // Fuente para los textos del juego
	Text *mTextBig;		   // Fuente de texto grande
	Text *mTextScoreBoard; // Fuente para el marcador del juego
	Text *mTextNokia2;	   // Otra fuente de texto para mesajes
	Text *mTextNokiaBig2;  // Y la versión en grande

	Menu *mMenuGameOver; // Menú de la pantalla de game over
	Menu *mMenuPause;	 // Menú de la pantalla de pausa

	Fade *mFade;			  // Objeto para renderizar fades
	SDL_Event *mEventHandler; // Manejador de eventos

	MovingSprite *mClouds1a;   // Sprite para las nubes superiores
	MovingSprite *mClouds1b;   // Sprite para las nubes superiores
	MovingSprite *mClouds2a;   // Sprite para las nubes inferiores
	MovingSprite *mClouds2b;   // Sprite para las nubes inferiores
	SmartSprite *m1000Bitmap;  // Sprite con el texto 1.000
	SmartSprite *m2500Bitmap;  // Sprite con el texto 2.500
	SmartSprite *m5000Bitmap;  // Sprite con el texto 5.000
	Sprite *mSpriteBackground; // Sprite con los graficos frontales del fondo
	Sprite *mSpriteGetReady;   // Sprite para el texto de GetReady del principio de la partida
	Sprite *mSpriteGradient;   // Sprite con los graficos del degradado de color de fondo
	Sprite *mSpriteGrass;	   // Sprite para la hierba
	Sprite *mSpritePowerMeter; // Sprite para el medidor de poder de la fase
	Sprite *mSpriteScoreBoard; // Sprite para el fondo del marcador

	JA_Sound mSoundBalloon;			// Sonido para la explosión del globo
	JA_Sound mSoundBullet;			// Sonido para los disparos
	JA_Sound mSoundPlayerCollision; // Sonido para la colisión del jugador con un enemigo
	JA_Sound mSoundHiScore;			// Sonido para cuando se alcanza la máxima puntuación
	JA_Sound mSoundItemDrop;		// Sonido para cuando se genera un item
	JA_Sound mSoundItemPickup;		// Sonido para cuando se recoge un item
	JA_Sound mSoundCoffeeOut;		// Sonido para cuando el jugador pierde el café al recibir un impacto
	JA_Sound mSoundStageChange;		// Sonido para cuando se cambia de fase
	JA_Sound mSoundBubble1;			// Sonido para cuando el jugador muere
	JA_Sound mSoundBubble2;			// Sonido para cuando el jugador muere
	JA_Sound mSoundBubble3;			// Sonido para cuando el jugador muere
	JA_Sound mSoundBubble4;			// Sonido para cuando el jugador muere
	JA_Sound mSoundClock;			// Sonido para cuando se detiene el tiempo con el item reloj
	JA_Sound mSoundPowerBall;		// Sonido para cuando se explota una Power Ball
	JA_Sound mSoundCollision;		// Sonido para cuando la máquina de café toca el suelo

	JA_Music mMusicPlaying; // Musica de fondo

	Uint32 mTicks;	   // Contador de ticks para ajustar la velocidad del programa
	Uint8 mTicksSpeed; // Velocidad a la que se repiten los bucles del programa

	Uint32 mHiScore;											  // Puntuación máxima
	bool mHiScoreAchieved;										  // Indica si se ha superado la puntuación máxima
	section_t mSection;											  // Seccion actual dentro del juego
	stage_t mStage[10];											  // Variable con los datos de cada pantalla
	Uint8 mCurrentStage;										  // Indica la fase actual
	Uint8 mStageBitmapCounter;									  // Contador para el tiempo visible del texto de Stage
	float mStageBitmapPath[STAGE_COUNTER];						  // Vector con los puntos Y por donde se desplaza el texto
	float mGetReadyBitmapPath[STAGE_COUNTER];					  // Vector con los puntos X por donde se desplaza el texto
	Uint16 mDeathCounter;										  // Contador para la animación de muerte del jugador
	Uint8 mDeathIndex;											  // Indice del vector de smartsprites que contiene el sprite del jugador
	Uint8 mMenaceCurrent;										  // Nivel de amenaza actual
	Uint8 mMenaceThreshold;										  // 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 mTimeStopped;											  // Indica si el tiempo está detenido
	Uint16 mTimeStoppedCounter;									  // Temporizador para llevar la cuenta del tiempo detenido
	Uint32 mCounter;											  // Contador para el juego
	Uint32 mScoreDataFile[TOTAL_SCORE_DATA];					  // Datos del fichero de puntos
	SDL_Rect mGradientRect[4];									  // Vector con las coordenadas de los 4 gradientes
	Uint16 mBalloonsPopped;										  // Lleva la cuenta de los globos explotados
	Uint8 mLastEnemyDeploy;										  // Guarda cual ha sido la última formación desplegada para no repetir;
	int mEnemyDeployCounter;									  // Cuando se lanza una formación, se le da un valor y no sale otra hasta que llegue a cero
	float mEnemySpeed;											  // Velocidad a la que se mueven los enemigos
	float mDefaultEnemySpeed;									  // Velocidad base de los enemigos, sin incrementar
	effect_t mEffect;											  // Variable para gestionar los efectos visuales
	helper_t mHelper;											  // Variable para gestionar las ayudas
	bool mPowerBallEnabled;										  // Indica si hay una powerball ya activa
	Uint8 mPowerBallCounter;									  // Contador de formaciones enemigas entre la aparicion de una PowerBall y otra
	bool mCoffeeMachineEnabled;									  // Indica si hay una máquina de café en el terreno de juego
	Uint8 mPostFade;											  // Qué hacer al acabar el fade
	float mSin[360];											  // Vector con los valores del seno para 360 grados
	bool mGameCompleted;										  // Indica si se ha completado la partida, llegando al final de la ultima pantalla
	int mGameCompletedCounter;									  // Contador para el tramo final, cuando se ha completado la partida y ya no aparecen más enemigos
	Uint8 mDifficulty;											  // Dificultad del juego
	float mDifficultyScoreMultiplier;							  // Multiplicador de puntos en función de la dificultad
	struct options_t *mOptions;									  // Variable con todas las variables de las opciones del programa
	Uint8 mOnePlayerControl;									  // Variable para almacenar el valor de las opciones
	enemyFormation_t mEnemyFormation[NUMBER_OF_ENEMY_FORMATIONS]; // Vector con todas las formaciones enemigas
	enemyPool_t mEnemyPool[10];									  // Variable con los diferentes conjuntos de formaciones enemigas
	Uint8 mLastStageReached;									  // Contiene el numero de la última pantalla que se ha alcanzado

	demo_t mDemo;	// Variable con todas las variables relacionadas con el modo demo
	debug_t mDebug; // Variable con las opciones de debug

	// Inicializa el vector con los valores del seno
	void initSin();

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

	// Carga los recursos necesarios para la sección 'Game'
	bool 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();

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

	// 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 createNewBalloon(float x, int y, Uint8 kind, float velx, float speed, Uint16 stoppedcounter, LTexture *texture);

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

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

	// 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(LTexture *texture, SDL_Renderer *renderer, Uint8 type, float x, float y);

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

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

	// Crea un SmartSprite para arrojar al jugador al morir
	void throwPlayer(int x, int y, Player *player);

	// Actualiza los SmartSprites
	void updateSmartSprites();

	// Pinta los SmartSprites activos
	void renderSmartSprites();

	// Acciones a realizar cuando el jugador muere
	void killPlayer(Player *player);

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

	// 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 campo de juego
	void updatePlayField();

	// Actualiza el fondo
	void updateBackground();

	// Dibuja el fondo
	void renderBackground();

	// Dibuja el campo de juego
	void renderPlayField();

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

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

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

	// Dibuja la informacion de debug en pantalla
	void renderDebugInfo();

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

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

	// Destructor
	~Game();

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

#endif
