#include "game.h"
#include "asset.h"				  // for Asset
#include "background.h"			  // for Background
#include "balloon.h"			  // for Balloon, BALLOON_SCORE, BALLOON_VE...
#include "balloon_formations.h"	  // for BalloonFormations, BalloonFormatio...
#include "bullet.h"				  // for Bullet, BulletType, BulletMoveStatus
#include "explosions.h"			  // for Explosions
#include "fade.h"				  // for Fade, FadeType
#include "global_inputs.h"		  // for check
#include "input.h"				  // for InputType, Input, INPUT_DO_NOT_ALL...
#include "item.h"				  // for Item, ItemType
#include "jail_audio.h"			  // for JA_PlaySound, JA_GetMusicState
#include "lang.h"				  // for getText
#include "manage_hiscore_table.h" // for ManageHiScoreTable, HiScoreEntry
#include "notifier.h"			  // for Notifier
#include "param.h"				  // for Param, param, ParamGame, ParamFade
#include "path_sprite.h"		  // for Path, PathSprite, createPath, Path...
#include "player.h"				  // for Player, PlayerStatus
#include "resource.h"			  // for Resource
#include "scoreboard.h"			  // for Scoreboard, ScoreboardMode, SCOREB...
#include "screen.h"				  // for Screen
#include "section.h"			  // for Name, name, Options, options
#include "smart_sprite.h"		  // for SmartSprite
#include "text.h"				  // for Text
#include "texture.h"			  // for Texture
#include <SDL2/SDL_blendmode.h>	  // for SDL_BLENDMODE_BLEND
#include <SDL2/SDL_events.h>	  // for SDL_PollEvent, SDL_Event, SDL_KEYDOWN
#include <SDL2/SDL_keycode.h>	  // for SDLK_1, SDLK_2, SDLK_3, SDLK_4
#include <SDL2/SDL_pixels.h>	  // for SDL_PIXELFORMAT_RGBA8888
#include <SDL2/SDL_timer.h>		  // for SDL_GetTicks
#include <SDL2/SDL_video.h>		  // for SDL_WINDOWEVENT_FOCUS_GAINED, SDL_...
#include <algorithm>			  // for find_if, clamp, min, remove_if
#include <functional>			  // for function
#include <iterator>				  // for distance, size
#include <numeric>				  // for accumulate
#include <stdlib.h>				  // for rand, size_t

struct JA_Sound_t; // lines 36-36

// Constructor
Game::Game(int player_id, int current_stage, bool demo)
	: renderer_(Screen::get()->getRenderer()),
	  screen_(Screen::get()),
	  asset_(Asset::get()),
	  input_(Input::get()),
	  background_(std::make_unique<Background>()),
	  explosions_(std::make_unique<Explosions>()),
	  balloon_formations_(std::make_unique<BalloonFormations>()),
	  canvas_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.play_area.rect.w, param.game.play_area.rect.h)),
	  fade_(std::make_unique<Fade>()),
	  current_stage_(current_stage)
{
	// Pasa variables
	demo_.enabled = demo;

	// Otras variables
	section::name = section::Name::GAME;
	section::options = section::Options::GAME_PLAY_1P;

	// Asigna texturas y animaciones
	setResources();

	// Crea y configura los objetos
	Scoreboard::init();
	scoreboard_ = Scoreboard::get();

	fade_->setColor(fade_color.r, fade_color.g, fade_color.b);
	fade_->setPost(param.fade.post_duration);
	fade_->setType(FadeType::VENETIAN);

	background_->setPos(param.game.play_area.rect);

	explosions_->addTexture(1, explosions_textures_[0], explosions_animations_[0]);
	explosions_->addTexture(2, explosions_textures_[1], explosions_animations_[1]);
	explosions_->addTexture(3, explosions_textures_[2], explosions_animations_[2]);
	explosions_->addTexture(4, explosions_textures_[3], explosions_animations_[3]);

	SDL_SetTextureBlendMode(canvas_, SDL_BLENDMODE_BLEND);

	// Inicializa el resto de variables
	initPlayers(player_id);
	initScoreboard();
	initDifficultyVars();
	initDemo(player_id);
	initPaths();
	setTotalPower();

#ifdef DEBUG
	// Si se empieza en una fase que no es la primera
	if (!demo_.enabled)
		for (int i = 0; i < current_stage_; ++i)
		{
			balloons_popped_ += balloon_formations_->getStage(i).power_to_complete;
		}
#endif

	// Crea los primeros globos y el mensaje de inicio
	if (!demo_.enabled)
	{
		createTwoBigBalloons();
		evaluateAndSetMenace();
		createMessage({paths_.at(0), paths_.at(1)}, Resource::get()->getTexture("get_ready"));
	}
}

Game::~Game()
{
	// Guarda las puntuaciones en un fichero
	if (!demo_.enabled)
	{
		auto manager = std::make_unique<ManageHiScoreTable>(options.game.hi_score_table);
		manager->saveToFile(asset_->get("score.bin"));
	}
#ifdef RECORDING
	saveDemoFile(Asset::get()->get("demo1.bin"), demo_.data.at(0));
#endif

	Scoreboard::destroy();
	SDL_DestroyTexture(canvas_);
}

// Asigna texturas y animaciones
void Game::setResources()
{
	// Texturas
	{
		bullet_texture_ = Resource::get()->getTexture("bullet.png");
	}

	// Texturas - Game_text
	{
		game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_1000_points"));
		game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_2500_points"));
		game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_5000_points"));
		game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_powerup"));
		game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_one_hit"));
		game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_stop"));
	}

	// Texturas - Globos
	{
		balloon_textures_.emplace_back(Resource::get()->getTexture("balloon1.png"));
		balloon_textures_.emplace_back(Resource::get()->getTexture("balloon2.png"));
		balloon_textures_.emplace_back(Resource::get()->getTexture("balloon3.png"));
		balloon_textures_.emplace_back(Resource::get()->getTexture("balloon4.png"));
		balloon_textures_.emplace_back(Resource::get()->getTexture("powerball.png"));
	}

	// Texturas - Explosiones
	{
		explosions_textures_.emplace_back(Resource::get()->getTexture("explosion1.png"));
		explosions_textures_.emplace_back(Resource::get()->getTexture("explosion2.png"));
		explosions_textures_.emplace_back(Resource::get()->getTexture("explosion3.png"));
		explosions_textures_.emplace_back(Resource::get()->getTexture("explosion4.png"));
	}

	// Texturas - Items
	{
		item_textures_.emplace_back(Resource::get()->getTexture("item_points1_disk.png"));
		item_textures_.emplace_back(Resource::get()->getTexture("item_points2_gavina.png"));
		item_textures_.emplace_back(Resource::get()->getTexture("item_points3_pacmar.png"));
		item_textures_.emplace_back(Resource::get()->getTexture("item_clock.png"));
		item_textures_.emplace_back(Resource::get()->getTexture("item_coffee.png"));
		item_textures_.emplace_back(Resource::get()->getTexture("item_coffee_machine.png"));
	}

	// Texturas - Player1
	{
		std::vector<std::shared_ptr<Texture>> player_texture;
		player_texture.emplace_back(Resource::get()->getTexture("player1.gif"));
		player_texture.emplace_back(Resource::get()->getTexture("player1_power.png"));
		player_textures_.push_back(player_texture);
	}

	// Texturas - Player2
	{
		std::vector<std::shared_ptr<Texture>> player_texture;
		player_texture.emplace_back(Resource::get()->getTexture("player2.gif"));
		player_texture.emplace_back(Resource::get()->getTexture("player2_power.png"));
		player_textures_.push_back(player_texture);
	}

	// Animaciones -- Jugador
	{
		player_animations_.emplace_back(Resource::get()->getAnimation("player.ani"));
		player_animations_.emplace_back(Resource::get()->getAnimation("player_power.ani"));
	}

	// Animaciones -- Globos
	{
		balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon1.ani"));
		balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon2.ani"));
		balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon3.ani"));
		balloon_animations_.emplace_back(Resource::get()->getAnimation("balloon4.ani"));
		balloon_animations_.emplace_back(Resource::get()->getAnimation("powerball.ani"));
	}

	// Animaciones -- Explosiones
	{
		explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion1.ani"));
		explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion2.ani"));
		explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion3.ani"));
		explosions_animations_.emplace_back(Resource::get()->getAnimation("explosion4.ani"));
	}

	// Animaciones -- Items
	{
		item_animations_.emplace_back(Resource::get()->getAnimation("item_points1_disk.ani"));
		item_animations_.emplace_back(Resource::get()->getAnimation("item_points2_gavina.ani"));
		item_animations_.emplace_back(Resource::get()->getAnimation("item_points3_pacmar.ani"));
		item_animations_.emplace_back(Resource::get()->getAnimation("item_clock.ani"));
		item_animations_.emplace_back(Resource::get()->getAnimation("item_coffee.ani"));
		item_animations_.emplace_back(Resource::get()->getAnimation("item_coffee_machine.ani"));
	}
}

// Crea una formación de enemigos
void Game::deployBalloonFormation()
{
	// Solo despliega una formación enemiga si ha pasado cierto tiempo desde la última
	if (balloon_deploy_counter_ == 0)
	{
		// En este punto se decide entre crear una powerball o una formación enemiga
		if ((rand() % 100 < 15) && (canPowerBallBeCreated()))
		{
			// Crea una powerball
			createPowerBall();

			// Da un poco de margen para que se creen mas enemigos
			balloon_deploy_counter_ = 300;
		}
		else
		{
			// Decrementa el contador de despliegues enemigos de la PowerBall
			power_ball_counter_ = (power_ball_counter_ > 0) ? (power_ball_counter_ - 1) : 0;

			// Elige una formación enemiga la azar
			auto formation = rand() % 10;

			// Evita repetir la ultima formación enemiga desplegada
			if (formation == last_balloon_deploy_)
			{
				++formation %= 10;
			}

			last_balloon_deploy_ = formation;

			const auto set = balloon_formations_->getStage(current_stage_).balloon_pool.set[formation];
			const auto numEnemies = set.number_of_balloons;
			for (int i = 0; i < numEnemies; ++i)
			{
				auto p = set.init[i];
				createBalloon(p.x, p.y, p.type, p.size, p.vel_x, balloon_speed_, p.creation_counter);
			}

			balloon_deploy_counter_ = 300;
		}
	}
}

// Aumenta el poder de la fase
void Game::increaseStageCurrentPower(int power) { current_power_ += power; }

// Actualiza el valor de hiScore en caso necesario
void Game::updateHiScore()
{
	// Si la puntuación actual es mayor que la máxima puntuación
	for (const auto &player : players_)
	{
		if (player->getScore() > hi_score_.score)
		{
			// Actualiza la máxima puntuación
			hi_score_.score = player->getScore();

			// Si se supera la máxima puntuación emite sonido
			if (hi_score_achieved_ == false)
			{
				hi_score_achieved_ = true;
				JA_PlaySound(Resource::get()->getSound("hiscore.wav"));
			}
		}
	}
}

// Actualiza las variables del jugador
void Game::updatePlayers()
{
	for (auto &player : players_)
	{
		player->update();

		if (player->isPlaying())
		{
			// Comprueba la colisión entre el jugador y los globos
			if (checkPlayerBalloonCollision(player))
			{
				killPlayer(player);

				if (demo_.enabled && allPlayersAreNotPlaying())
				{
					fade_->setType(FadeType::RANDOM_SQUARE);
					fade_->activate();
				}
			}

			// Comprueba las colisiones entre el jugador y los items
			checkPlayerItemCollision(player);
		}
	}
}

// Dibuja a los jugadores
void Game::renderPlayers()
{
	for (auto &player : players_)
	{
		if (!player->isWaiting())
		{
			player->render();
#ifdef DEBUG
			// SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
			// const Circle c = player->getCollider();
			// DrawCircle(renderer, c.x, c.y, c.r);
#endif
		}
	}
}

// Comprueba si hay cambio de fase y actualiza las variables
void Game::updateStage()
{
	if (state_ == GameState::PLAYING)
	{
		if (current_power_ >= balloon_formations_->getStage(current_stage_).power_to_complete)
		{
			// Cambio de fase
			++current_stage_;
			current_power_ = 0;
			JA_PlaySound(Resource::get()->getSound("stage_change.wav"));
			balloon_speed_ = default_balloon_speed_;
			setBalloonSpeed(balloon_speed_);
			screen_->flash(flash_color, 100);
			screen_->shake();

			// Escribe el texto por pantalla
			if (current_stage_ < 10)
			{
				const auto stage_number = balloon_formations_->getStage(current_stage_).number;
				std::vector<Path> paths = {paths_.at(2), paths_.at(3)};
				if (stage_number == 10)
					createMessage(paths, Resource::get()->getTexture("last_stage"));
				else
				{
					auto text = std::make_unique<Text>(Resource::get()->getTexture("04b_25.png"), Resource::get()->getTextFile("04b_25.txt"));
					const std::string caption = std::to_string(10 - current_stage_) + lang::getText(38);
					createMessage(paths, text->writeToTexture(caption, 2, -2));
				}
			}
		}
	}
}

// Actualiza el estado de fin de la partida
void Game::updateGameOverState()
{
	if (state_ == GameState::GAME_OVER)
	{
		if (game_over_counter_ > 0)
		{
			if (game_over_counter_ == GAME_OVER_COUNTER_)
				createMessage({paths_.at(2), paths_.at(3)}, Resource::get()->getTexture("game_over"));

			game_over_counter_--;

			if ((game_over_counter_ == 250) || (game_over_counter_ == 200) || (game_over_counter_ == 180) || (game_over_counter_ == 120) || (game_over_counter_ == 60))
			{
				// Hace sonar aleatoriamente uno de los 4 sonidos de burbujas
				const auto index = rand() % 4;
				JA_Sound_t *sound[4] = {Resource::get()->getSound("bubble1.wav"), Resource::get()->getSound("bubble2.wav"), Resource::get()->getSound("bubble3.wav"), Resource::get()->getSound("bubble4.wav")};
				JA_PlaySound(sound[index], 0);
			}

			if (game_over_counter_ == 150)
			{
				fade_->activate();
			}
		}

		if (fade_->hasEnded())
		{
			section::name = section::Name::HI_SCORE_TABLE;
		}
	}
}

// Gestiona eventos para el estado del final del juego
void Game::updateCompletedState()
{
	if (state_ == GameState::COMPLETED)
	{
		// Para la música y elimina todos los globos e items
		if (game_completed_counter_ == 0)
		{
			stopMusic();
			current_stage_ = 9;	  // Deja el valor dentro de los limites
			destroyAllBalloons(); // Destruye a todos los globos
			destroyAllItems();	  // Destruye todos los items
			current_power_ = 0;	  // Vuelve a dejar el poder a cero, por lo que hubiera podido subir al destruir todos los globos
		}

		// Comienza las celebraciones
		// Muestra el mensaje de felicitación y da los puntos a los jugadores
		if (game_completed_counter_ == 200)
		{
			createMessage({paths_.at(4), paths_.at(5)}, Resource::get()->getTexture("congratulations"));
			createMessage({paths_.at(6), paths_.at(7)}, Resource::get()->getTexture("1000000_points"));

			for (auto &player : players_)
				if (player->isPlaying())
				{
					player->addScore(1000000);
					player->setPlayingState(PlayerState::CELEBRATING);
				}
				else
				{
					player->setPlayingState(PlayerState::GAME_OVER);
				}

			updateHiScore();
		}

		// Termina las celebraciones
		if (game_completed_counter_ == 500)
		{
			for (auto &player : players_)
				if (player->isCelebrating())
				{
					player->setPlayingState(player->IsEligibleForHighScore() ? PlayerState::ENTERING_NAME_GAME_COMPLETED : PlayerState::GAME_COMPLETED);
				}
		}

		// Incrementa el contador al final
		++game_completed_counter_;
	}
}

// Comprueba el estado del juego
void Game::checkState()
{
	if (state_ != GameState::COMPLETED && current_stage_ == 10)
	{
		state_ = GameState::COMPLETED;
	}

	if (state_ != GameState::GAME_OVER && allPlayersAreGameOver())
	{
		state_ = GameState::GAME_OVER;
	}
}

// Actualiza los globos
void Game::updateBalloons()
{
	for (auto balloon : balloons_)
		balloon->update();
}

// Pinta en pantalla todos los globos activos
void Game::renderBalloons()
{
	for (auto &balloon : balloons_)
		balloon->render();
}

// Crea un globo nuevo en el vector de globos
std::shared_ptr<Balloon> Game::createBalloon(float x, int y, BalloonType type, BalloonSize size, float velx, float speed, int creation_timer)
{
	const int index = static_cast<int>(size);
	balloons_.emplace_back(std::make_shared<Balloon>(x, y, type, size, velx, speed, creation_timer, balloon_textures_.at(index), balloon_animations_.at(index)));
	return balloons_.back();
}

// Crea un globo a partir de otro globo
void Game::createChildBalloon(const std::shared_ptr<Balloon> &balloon, const std::string &direction)
{
	const float vx = direction == "LEFT" ? BALLOON_VELX_NEGATIVE : BALLOON_VELX_POSITIVE;
	const auto lower_size = static_cast<BalloonSize>(static_cast<int>(balloon->getSize()) - 1);
	auto b = createBalloon(0, balloon->getPosY(), balloon->getType(), lower_size, vx, balloon_speed_, 0);
	b->alignTo(balloon->getPosX() + (balloon->getWidth() / 2));
	b->setVelY(b->getType() == BalloonType::BALLOON ? -2.50f : vx * 2.0f);
	if (balloon->isStopped())
		b->stop();
	if (balloon->isUsingReversedColor())
		b->useReverseColor();
}

// Crea una PowerBall
void Game::createPowerBall()
{
	constexpr auto values = 6;
	constexpr auto pos_y = -BLOCK;
	constexpr int creation_time = 300;

	const auto left = param.game.play_area.rect.x;
	const auto center = param.game.play_area.center_x - (BALLOON_SIZE[3] / 2);
	const auto right = param.game.play_area.rect.w - BALLOON_SIZE[3];

	const auto luck = rand() % values;
	const int x[values] = {left, left, center, center, right, right};
	const float vx[values] = {BALLOON_VELX_POSITIVE, BALLOON_VELX_POSITIVE, BALLOON_VELX_POSITIVE, BALLOON_VELX_NEGATIVE, BALLOON_VELX_NEGATIVE, BALLOON_VELX_NEGATIVE};

	balloons_.emplace_back(std::make_unique<Balloon>(x[luck], pos_y, BalloonType::POWERBALL, BalloonSize::SIZE4, vx[luck], balloon_speed_, creation_time, balloon_textures_[4], balloon_animations_[4]));

	power_ball_enabled_ = true;
	power_ball_counter_ = POWERBALL_COUNTER;
}

// Establece la velocidad de los globos
void Game::setBalloonSpeed(float speed)
{
	for (auto &balloon : balloons_)
		balloon->setSpeed(speed);
}

// Actualiza la velocidad de los globos en funcion del poder acumulado de la fase
void Game::checkAndUpdateBalloonSpeed()
{
	if (difficulty_ != GameDifficulty::NORMAL)
		return;

	const float percent = static_cast<float>(current_power_) / balloon_formations_->getStage(current_stage_).power_to_complete;
	const float thresholds[] = {0.2f, 0.4f, 0.6f, 0.8f};

	for (size_t i = 0; i < std::size(thresholds); ++i)
		if (balloon_speed_ == BALLOON_SPEED[i] && percent > thresholds[i])
		{
			balloon_speed_ = BALLOON_SPEED[i + 1];
			setBalloonSpeed(balloon_speed_);
			break; // Salir del bucle una vez actualizada la velocidad y aplicada
		}
}

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

	if (balloon->getType() == BalloonType::POWERBALL)
	{
		destroyAllBalloons();
		power_ball_enabled_ = false;
		balloon_deploy_counter_ = 20;
	}
	else
	{
		if (balloon->getSize() != BalloonSize::SIZE1)
		{
			createChildBalloon(balloon, "LEFT");
			createChildBalloon(balloon, "RIGHT");
		}

		// Agrega la explosión y elimina el globo
		explosions_->add(balloon->getPosX(), balloon->getPosY(), static_cast<int>(balloon->getSize()));
		balloon->pop();
	}

	// Recalcula el nivel de amenaza
	evaluateAndSetMenace();
}

// Explosiona un globo. Lo destruye = no crea otros globos
void Game::destroyBalloon(std::shared_ptr<Balloon> &balloon)
{
	int score = 0;

	// Calcula la puntuación y el poder que generaria el globo en caso de romperlo a él y a sus hijos
	switch (balloon->getSize())
	{
	case BalloonSize::SIZE4:
		score = BALLOON_SCORE[3] + (2 * BALLOON_SCORE[2]) + (4 * BALLOON_SCORE[1]) + (8 * BALLOON_SCORE[0]);
		break;
	case BalloonSize::SIZE3:
		score = BALLOON_SCORE[2] + (2 * BALLOON_SCORE[1]) + (4 * BALLOON_SCORE[0]);
		break;
	case BalloonSize::SIZE2:
		score = BALLOON_SCORE[1] + (2 * BALLOON_SCORE[0]);
		break;
	case BalloonSize::SIZE1:
		score = BALLOON_SCORE[0];
		break;
	default:
		score = 0;
		break;
	}

	// Otorga los puntos correspondientes al globo
	for (auto &player : players_)
		player->addScore(score * player->getScoreMultiplier() * difficulty_score_multiplier_);
	updateHiScore();

	// Aumenta el poder de la fase
	const auto power = balloon->getPower();
	increaseStageCurrentPower(power);
	balloons_popped_ += power;

	// Destruye el globo
	explosions_->add(balloon->getPosX(), balloon->getPosY(), static_cast<int>(balloon->getSize()));
	balloon->pop();
}

// Destruye todos los globos
void Game::destroyAllBalloons()
{
	for (auto &balloon : balloons_)
		destroyBalloon(balloon);

	balloon_deploy_counter_ = 300;
	JA_PlaySound(Resource::get()->getSound("powerball.wav"));
	screen_->flash(flash_color, 100);
	screen_->shake();
}

// Destruye todos los items
void Game::destroyAllItems()
{
	for (auto &item : items_)
		item->disable();
}

// Detiene todos los globos
void Game::stopAllBalloons()
{
	for (auto &balloon : balloons_)
		balloon->stop();
}

// Pone en marcha todos los globos
void Game::startAllBalloons()
{
	for (auto &balloon : balloons_)
		if (!balloon->isBeingCreated())
			balloon->start();
}

// Cambia el color de todos los globos
void Game::reverseColorsToAllBalloons()
{
	for (auto &balloon : balloons_)
		if (balloon->isStopped())
			balloon->useReverseColor();
}

// Cambia el color de todos los globos
void Game::normalColorsToAllBalloons()
{
	for (auto &balloon : balloons_)
		balloon->useNormalColor();
}

// Vacia del vector de globos los globos que ya no sirven
void Game::freeBalloons()
{
	auto it = std::remove_if(balloons_.begin(), balloons_.end(), [](const auto &balloon)
							 { return !balloon->isEnabled(); });
	balloons_.erase(it, balloons_.end());
}

// Comprueba la colisión entre el jugador y los globos activos
bool Game::checkPlayerBalloonCollision(std::shared_ptr<Player> &player)
{
	for (auto &balloon : balloons_)
		if (!balloon->isStopped() && !balloon->isInvulnerable())
			if (checkCollision(player->getCollider(), balloon->getCollider()))
				return true;

	return false;
}

// Comprueba la colisión entre el jugador y los items
void Game::checkPlayerItemCollision(std::shared_ptr<Player> &player)
{
	if (!player->isPlaying())
		return;

	for (auto &item : items_)
	{
		if (item->isEnabled())
		{
			if (checkCollision(player->getCollider(), item->getCollider()))
			{
				switch (item->getType())
				{
				case ItemType::DISK:
				{
					player->addScore(1000);
					const auto x =
						item->getPosX() +
						(item->getWidth() - game_text_textures_[0]->getWidth()) / 2;
					createItemText(x, game_text_textures_[0]);
					break;
				}
				case ItemType::GAVINA:
				{
					player->addScore(2500);
					const auto x =
						item->getPosX() +
						(item->getWidth() - game_text_textures_[1]->getWidth()) / 2;
					createItemText(x, game_text_textures_[1]);
					break;
				}
				case ItemType::PACMAR:
				{
					player->addScore(5000);
					const auto x =
						item->getPosX() +
						(item->getWidth() - game_text_textures_[2]->getWidth()) / 2;
					createItemText(x, game_text_textures_[2]);
					break;
				}
				case ItemType::CLOCK:
				{
					enableTimeStopItem();
					const auto x =
						item->getPosX() +
						(item->getWidth() - game_text_textures_[5]->getWidth()) / 2;
					createItemText(x, game_text_textures_[5]);
					break;
				}
				case ItemType::COFFEE:
				{
					if (player->getCoffees() == 2)
					{
						player->addScore(5000);
						const auto x =
							item->getPosX() +
							(item->getWidth() - game_text_textures_[2]->getWidth()) / 2;
						createItemText(x, game_text_textures_[2]);
					}
					else
					{
						player->giveExtraHit();
						const auto x =
							item->getPosX() +
							(item->getWidth() - game_text_textures_[4]->getWidth()) / 2;
						createItemText(x, game_text_textures_[4]);
					}
					break;
				}
				case ItemType::COFFEE_MACHINE:
				{
					player->setPowerUp();
					coffee_machine_enabled_ = false;
					const auto x =
						item->getPosX() +
						(item->getWidth() - game_text_textures_[3]->getWidth()) / 2;
					createItemText(x, game_text_textures_[3]);
					break;
				}
				default:
					break;
				}

				updateHiScore();
				JA_PlaySound(Resource::get()->getSound("itempickup.wav"));
				item->disable();
			}
		}
	}
}

// Comprueba y procesa la colisión entre las balas y los globos
void Game::checkBulletBalloonCollision()
{
	for (auto &bullet : bullets_)
	{
		for (auto &balloon : balloons_)
		{
			if (balloon->isEnabled() && (!balloon->isInvulnerable()) && bullet->isEnabled())
			{
				if (checkCollision(balloon->getCollider(), bullet->getCollider()))
				{
					// Otorga los puntos al jugador que disparó la bala
					auto player = getPlayer(bullet->getOwner());
					if (!player)
					{
						return;
					}
					player->addScore(balloon->getScore() * player->getScoreMultiplier() * difficulty_score_multiplier_);
					player->incScoreMultiplier();
					updateHiScore();

					// Suelta el item si se da el caso
					const auto droppeditem = dropItem();
					if (droppeditem != ItemType::NONE && !demo_.recording)
					{
						if (droppeditem != ItemType::COFFEE_MACHINE)
						{
							createItem(droppeditem, balloon->getPosX(), balloon->getPosY());
							JA_PlaySound(Resource::get()->getSound("itemdrop.wav"));
						}
						else
						{
							createItem(droppeditem, player->getPosX(), 0);
							coffee_machine_enabled_ = true;
						}
					}

					// Explota el globo
					popBalloon(balloon);

					// Sonido de explosión
					JA_PlaySound(Resource::get()->getSound("balloon.wav"));

					// Deshabilita la bala
					bullet->disable();

					break;
				}
			}
		}
	}
}

// Mueve las balas activas
void Game::moveBullets()
{
	for (auto &bullet : bullets_)
		if (bullet->move() == BulletMoveStatus::OUT)
			getPlayer(bullet->getOwner())->decScoreMultiplier();
}

// Pinta las balas activas
void Game::renderBullets()
{
	for (auto &bullet : bullets_)
		bullet->render();
}

// Crea un objeto bala
void Game::createBullet(int x, int y, BulletType kind, bool powered_up, int owner)
{
	bullets_.emplace_back(
		std::make_unique<Bullet>(x, y, kind, powered_up, owner, bullet_texture_));
}

// Vacia el vector de balas
void Game::freeBullets()
{
	if (!bullets_.empty())
		for (int i = bullets_.size() - 1; i >= 0; --i)
			if (!bullets_[i]->isEnabled())
				bullets_.erase(bullets_.begin() + i);
}

// Actualiza los items
void Game::updateItems()
{
	for (auto &item : items_)
		if (item->isEnabled())
		{
			item->update();
			if (item->isOnFloor())
			{
				JA_PlaySound(Resource::get()->getSound("title.wav"));
				screen_->shake();
			}
		}
}

// Pinta los items activos
void Game::renderItems()
{
	for (auto &item : items_)
		item->render();
}

// Devuelve un item al azar y luego segun sus probabilidades
ItemType Game::dropItem()
{
	const auto lucky_number = rand() % 100;
	const auto item = rand() % 6;

	switch (item)
	{
	case 0:
		if (lucky_number < helper_.item_disk_odds)
		{
			return ItemType::DISK;
		}
		break;
	case 1:
		if (lucky_number < helper_.item_gavina_odds)
		{
			return ItemType::GAVINA;
		}
		break;
	case 2:
		if (lucky_number < helper_.item_pacmar_odds)
		{
			return ItemType::GAVINA;
		}
		break;
	case 3:
		if (lucky_number < helper_.item_clock_odds)
		{
			return ItemType::CLOCK;
		}
		break;
	case 4:
		if (lucky_number < helper_.item_coffee_odds)
		{
			helper_.item_coffee_odds = ITEM_COFFEE_ODDS_;
			return ItemType::COFFEE;
		}
		else
		{
			if (helper_.need_coffee)
				helper_.item_coffee_odds++;
		}
		break;
	case 5:
		if (lucky_number < helper_.item_coffee_machine_odds)
		{
			helper_.item_coffee_machine_odds = ITEM_COFFEE_MACHINE_ODDS_;
			if (!coffee_machine_enabled_ && helper_.need_coffee_machine)
				return ItemType::COFFEE_MACHINE;
		}
		else
		{
			if (helper_.need_coffee_machine)
				helper_.item_coffee_machine_odds++;
		}
		break;
	default:
		break;
	}

	return ItemType::NONE;
}

// Crea un objeto item
void Game::createItem(ItemType type, float x, float y)
{
	items_.emplace_back(std::make_unique<Item>(type, x, y, param.game.play_area.rect, item_textures_[static_cast<int>(type) - 1], item_animations_[static_cast<int>(type) - 1]));
}

// Vacia el vector de items
void Game::freeItems()
{
	if (!items_.empty())
		for (int i = items_.size() - 1; i >= 0; --i)
			if (!items_[i]->isEnabled())
				items_.erase(items_.begin() + i);
}

// Crea un objeto PathSprite
void Game::createItemText(int x, std::shared_ptr<Texture> texture)
{
	path_sprites_.emplace_back(std::make_unique<PathSprite>(texture));

	const auto w = texture->getWidth();
	const auto h = texture->getHeight();

	const int y0 = param.game.play_area.rect.h - h;
	const int y1 = 155;
	const int y2 = -h;

	// Ajusta para que no se dibuje fuera de pantalla
	x = std::clamp(x, 2, param.game.play_area.rect.w - w - 2);

	// Inicializa
	path_sprites_.back()->setWidth(w);
	path_sprites_.back()->setHeight(h);
	path_sprites_.back()->setSpriteClip({0, 0, w, h});
	path_sprites_.back()->addPath(y0, y1, PathType::VERTICAL, x, 100, easeOutQuint, 0);
	path_sprites_.back()->addPath(y1, y2, PathType::VERTICAL, x, 80, easeInQuint, 0);
	path_sprites_.back()->enable();
}

// Crea un objeto PathSprite
void Game::createMessage(const std::vector<Path> &paths, std::shared_ptr<Texture> texture)
{
	path_sprites_.emplace_back(std::make_unique<PathSprite>(texture));

	// Inicializa
	for (const auto &path : paths)
		path_sprites_.back()->addPath(path, true);
	path_sprites_.back()->enable();
}

// Vacia el vector de smartsprites
void Game::freeSmartSprites()
{
	if (!smart_sprites_.empty())
		for (int i = smart_sprites_.size() - 1; i >= 0; --i)
			if (smart_sprites_[i]->hasFinished())
				smart_sprites_.erase(smart_sprites_.begin() + i);
}

// Vacia el vector de pathsprites
void Game::freePathSprites()
{
	if (!path_sprites_.empty())
		for (int i = path_sprites_.size() - 1; i >= 0; --i)
			if (path_sprites_[i]->hasFinished())
				path_sprites_.erase(path_sprites_.begin() + i);
}

// Crea un SpriteSmart para arrojar el item café al recibir un impacto
void Game::throwCoffee(int x, int y)
{
	smart_sprites_.emplace_back(std::make_unique<SmartSprite>(item_textures_[4]));

	smart_sprites_.back()->setPosX(x - 8);
	smart_sprites_.back()->setPosY(y - 8);
	smart_sprites_.back()->setWidth(param.game.item_size);
	smart_sprites_.back()->setHeight(param.game.item_size);
	smart_sprites_.back()->setVelX(-1.0f + ((rand() % 5) * 0.5f));
	smart_sprites_.back()->setVelY(-4.0f);
	smart_sprites_.back()->setAccelX(0.0f);
	smart_sprites_.back()->setAccelY(0.2f);
	smart_sprites_.back()->setDestX(x + (smart_sprites_.back()->getVelX() * 50));
	smart_sprites_.back()->setDestY(param.game.height + 1);
	smart_sprites_.back()->setEnabled(true);
	smart_sprites_.back()->setFinishedCounter(1);
	smart_sprites_.back()->setSpriteClip(0, param.game.item_size, param.game.item_size, param.game.item_size);
	smart_sprites_.back()->enableRotate();
	smart_sprites_.back()->setRotateSpeed(10);
	smart_sprites_.back()->setRotateAmount(90.0);
}

// Actualiza los SmartSprites
void Game::updateSmartSprites()
{
	for (auto &sprite : smart_sprites_)
		sprite->update();
}

// Pinta los SmartSprites activos
void Game::renderSmartSprites()
{
	for (auto &sprite : smart_sprites_)
		sprite->render();
}

// Actualiza los PathSprites
void Game::updatePathSprites()
{
	for (auto &sprite : path_sprites_)
		sprite->update();
}

// Pinta los PathSprites activos
void Game::renderPathSprites()
{
	for (auto &sprite : path_sprites_)
		sprite->render();
}

// Acciones a realizar cuando el jugador muere
void Game::killPlayer(std::shared_ptr<Player> &player)
{
	if (!player->isPlaying() || player->isInvulnerable())
	{ // Si no está jugando o tiene inmunidad, no hace nada
		return;
	}

	// Si tiene cafes
	if (player->hasExtraHit())
	{
		// Lo pierde
		player->removeExtraHit();
		throwCoffee(player->getPosX() + (player->getWidth() / 2), player->getPosY() + (player->getHeight() / 2));
		JA_PlaySound(Resource::get()->getSound("coffeeout.wav"));
		screen_->shake();
	}
	else
	{
		// Si no tiene cafes, muere
		pauseMusic();
		stopAllBalloons();
		JA_PlaySound(Resource::get()->getSound("player_collision.wav"));
		screen_->shake();
		JA_PlaySound(Resource::get()->getSound("coffeeout.wav"));
		player->setPlayingState(PlayerState::DYING);
		allPlayersAreNotPlaying() ? stopMusic() : resumeMusic();
	}
}

// Calcula y establece el valor de amenaza en funcion de los globos activos
void Game::evaluateAndSetMenace()
{
	menace_current_ = std::accumulate(
		balloons_.begin(), balloons_.end(), 0, [](int sum, const auto &balloon)
		{ return sum + (balloon->isEnabled() ? balloon->getMenace() : 0); });
}

// Actualiza y comprueba el valor de la variable
void Game::updateTimeStopped()
{
	if (time_stopped_counter_ > 0)
	{
		time_stopped_counter_--;
		if (time_stopped_counter_ > 120)
		{
			if (time_stopped_counter_ % 30 == 0)
				JA_PlaySound(Resource::get()->getSound("clock.wav"));
		}
		else
		{
			if (time_stopped_counter_ % 15 == 0)
				JA_PlaySound(Resource::get()->getSound("clock.wav"));
			if (time_stopped_counter_ % 30 == 0)
				normalColorsToAllBalloons();
			if (time_stopped_counter_ % 30 == 15)
				reverseColorsToAllBalloons();
		}
	}
	else
		disableTimeStopItem();
}

// Actualiza la variable enemyDeployCounter
void Game::updateBalloonDeployCounter()
{
	if (balloon_deploy_counter_ > 0)
		--balloon_deploy_counter_;
}

// Actualiza el juego
void Game::update()
{
	constexpr int TICKS_SPEED = 15;

	if (SDL_GetTicks() - ticks_ > TICKS_SPEED)
	{
		ticks_ = SDL_GetTicks();
		counter_++;

		updateDemo();

#ifdef RECORDING
		updateRecording();
#endif
		updateGame();

		checkMusicStatus();
		screen_->update();
		fillCanvas();
	}
}

// Actualiza el fondo
void Game::updateBackground()
{
	// Si el juego está completado, se reduce la velocidad de las nubes
	if (state_ == GameState::COMPLETED)
		balloons_popped_ = (balloons_popped_ > 400) ? (balloons_popped_ - 25) : 200;

	// Calcula la velocidad en función de los globos explotados y el total de globos a explotar para acabar el juego
	constexpr float clouds_initial_speed = 0.05f;
	constexpr float clouds_final_speed = 2.00f - clouds_initial_speed;
	const float cloudsSpeed = (-clouds_initial_speed) + (-clouds_final_speed * (static_cast<float>(balloons_popped_) / total_power_to_complete_game_));
	background_->setCloudsSpeed(cloudsSpeed);

	// Calcula la transición de los diferentes fondos
	const float gradient_number = std::min(balloons_popped_ / 1250.0f, 3.0f);
	const float percent = gradient_number - static_cast<int>(gradient_number);
	background_->setGradientNumber(static_cast<int>(gradient_number));
	background_->setTransition(percent);

	// Actualiza el objeto
	background_->update();
}

// Dibuja los elementos de la zona de juego en su textura
void Game::fillCanvas()
{
	// Dibujamos el contenido de la zona de juego en su textura
	auto temp = SDL_GetRenderTarget(renderer_);
	SDL_SetRenderTarget(renderer_, canvas_);

	// Dibuja los objetos
	background_->render();
	renderItems();
	renderSmartSprites();
	explosions_->render();
	renderBalloons();
	renderBullets();
	renderPathSprites();
	renderPlayers();

	// Deja el renderizador apuntando donde estaba
	SDL_SetRenderTarget(renderer_, temp);
}

// Dibuja el juego
void Game::render()
{
	// Prepara para empezar a dibujar en la textura de juego
	screen_->start();

	// Copia la textura con la zona de juego a la pantalla
	SDL_RenderCopy(renderer_, canvas_, nullptr, &param.game.play_area.rect);

	// Dibuja el marcador
	scoreboard_->render();

	// Dibuja el fade
	fade_->render();

	// Vuelca el contenido del renderizador en pantalla
	screen_->blit();
}

// Gestiona el nivel de amenaza
void Game::updateMenace()
{
	const auto stage = balloon_formations_->getStage(current_stage_);
	const float percent = current_power_ / stage.power_to_complete;
	const int difference = stage.max_menace - stage.min_menace;

	// Aumenta el nivel de amenaza en función de la puntuación
	menace_threshold_ = stage.min_menace + (difference * percent);

	// Si el nivel de amenza es inferior al umbral
	if (menace_current_ < menace_threshold_)
	{
		// Crea una formación de enemigos
		deployBalloonFormation();

		// Recalcula el nivel de amenaza con el nuevo globo
		evaluateAndSetMenace();
	}
}

// Habilita el efecto del item de detener el tiempo
void Game::enableTimeStopItem()
{
	stopAllBalloons();
	reverseColorsToAllBalloons();
	time_stopped_counter_ = TIME_STOPPED_COUNTER_;
}

// Deshabilita el efecto del item de detener el tiempo
void Game::disableTimeStopItem()
{
	time_stopped_counter_ = 0;
	startAllBalloons();
	normalColorsToAllBalloons();
}

// Comprueba si la música ha de estar sonando
void Game::checkMusicStatus()
{
	// Si la música no está sonando
	if (JA_GetMusicState() == JA_MUSIC_INVALID ||
		JA_GetMusicState() == JA_MUSIC_STOPPED)
		// Si se ha completado el juego o los jugadores han terminado, detiene la música
		state_ == GameState::COMPLETED || allPlayersAreGameOver() ? JA_StopMusic() : JA_PlayMusic(Resource::get()->getMusic("playing.ogg"));
}

// Bucle para el juego
void Game::run()
{
	while (section::name == section::Name::GAME)
	{
#ifndef RECORDING
		checkInput();
#endif
		update();
		checkEvents(); // Tiene que ir antes del render
		render();
	}

	// Vuelve a dejar el sonido como estaba
	(demo_.enabled) ? JA_EnableSound(options.audio.sound.enabled) : JA_StopMusic();
}

// Indica si se puede crear una powerball
bool Game::canPowerBallBeCreated() { return (!power_ball_enabled_) && (calculateScreenPower() > POWERBALL_SCREENPOWER_MINIMUM) && (power_ball_counter_ == 0); }

// Calcula el poder actual de los globos en pantalla
int Game::calculateScreenPower()
{
	return std::accumulate(balloons_.begin(), balloons_.end(), 0, [](int sum, const auto &balloon)
						   { return sum + (balloon->isEnabled() ? balloon->getPower() : 0); });
}

// Inicializa las variables que contienen puntos de ruta para mover objetos
void Game::initPaths()
{
	// Recorrido para el texto de "Get Ready!" (0,1)
	{
		const auto &texture = Resource::get()->getTexture("get_ready");
		const auto w = texture->getWidth();
		const int x0 = -w;
		const int x1 = param.game.play_area.center_x - w / 2;
		const int x2 = param.game.play_area.rect.w;
		const int y = param.game.play_area.center_y;
		paths_.emplace_back(Path(createPath(x0, x1, PathType::HORIZONTAL, y, 80, easeOutQuint), 20));
		paths_.emplace_back(Path(createPath(x1, x2, PathType::HORIZONTAL, y, 80, easeInQuint), 0));
	}

	// Recorrido para el texto de "Last Stage!" o de "X stages left" o "Game Over"
	// (2,3)
	{
		const auto &texture = Resource::get()->getTexture("last_stage");
		const auto h = texture->getHeight();
		const int y0 = param.game.play_area.rect.h - h;
		const int y1 = param.game.play_area.center_y - h / 2;
		const int y2 = -h;
		const int x = param.game.play_area.center_x;
		paths_.emplace_back(Path(createPath(y0, y1, PathType::VERTICAL, x, 80, easeOutQuint), 20));
		paths_.emplace_back(Path(createPath(y1, y2, PathType::VERTICAL, x, 80, easeInQuint), 0));
	}

	// Recorrido para el texto de "Congratulations!!" (3,4)
	{
		const auto &texture = Resource::get()->getTexture("congratulations");
		const auto w = texture->getWidth();
		const auto h = texture->getHeight();
		const int x0 = -w;
		const int x1 = param.game.play_area.center_x - w / 2;
		const int x2 = param.game.play_area.rect.w;
		const int y = param.game.play_area.center_y - h / 2 - 20;
		paths_.emplace_back(Path(createPath(x0, x1, PathType::HORIZONTAL, y, 80, easeOutQuint), 400));
		paths_.emplace_back(Path(createPath(x1, x2, PathType::HORIZONTAL, y, 80, easeInQuint), 0));
	}

	// Recorrido para el texto de "1.000.000 points!" (5,6)
	{
		const auto &texture = Resource::get()->getTexture("1000000_points");
		const auto w = texture->getWidth();
		const auto h = texture->getHeight();
		const int x0 = param.game.play_area.rect.w;
		const int x1 = param.game.play_area.center_x - w / 2;
		const int x2 = -w;
		const int y = param.game.play_area.center_y + h / 2 - 20;
		paths_.emplace_back(Path(createPath(x0, x1, PathType::HORIZONTAL, y, 80, easeOutQuint), 400));
		paths_.emplace_back(Path(createPath(x1, x2, PathType::HORIZONTAL, y, 80, easeInQuint), 0));
	}
}

// Actualiza las variables de ayuda
void Game::updateHelper()
{
	// Solo ofrece ayuda cuando la amenaza es elevada
	if (menace_current_ > 15)
	{
		helper_.need_coffee = true;
		helper_.need_coffee_machine = true;
		for (const auto &player : players_)
		{
			if (player->isPlaying())
			{
				helper_.need_coffee &= (player->getCoffees() == 0);
				helper_.need_coffee_machine &= (!player->isPowerUp());
			}
		}
	}
	else
		helper_.need_coffee = helper_.need_coffee_machine = false;
}

// Comprueba si todos los jugadores han terminado de jugar
bool Game::allPlayersAreWaitingOrGameOver()
{
	auto success = true;
	for (const auto &player : players_)
		success &= player->isWaiting() || player->isGameOver();

	return success;
}

// Comprueba si todos los jugadores han terminado de jugar
bool Game::allPlayersAreGameOver()
{
	auto success = true;
	for (const auto &player : players_)
		success &= player->isGameOver();

	return success;
}

// Comprueba si todos los jugadores han terminado de jugar
bool Game::allPlayersAreNotPlaying()
{
	auto success = true;
	for (const auto &player : players_)
		success &= !player->isPlaying();

	return success;
}

// Comprueba los eventos que hay en cola
void Game::checkEvents()
{
	SDL_Event event;
	while (SDL_PollEvent(&event))
	{
		// Evento de salida de la aplicación
		if (event.type == SDL_QUIT)
		{
			section::name = section::Name::QUIT;
			section::options = section::Options::QUIT_FROM_EVENT;
			break;
		}

		else if (event.type == SDL_WINDOWEVENT)
		{
			switch (event.window.event)
			{
			case SDL_WINDOWEVENT_FOCUS_LOST:
			{
				pause(!demo_.enabled);
				break;
			}

			case SDL_WINDOWEVENT_FOCUS_GAINED:
			{
				pause(false);
				break;
			}
			case SDL_WINDOWEVENT_SIZE_CHANGED:
			{
				reloadTextures();
				break;
			}
			default:
				break;
			}
		}

#ifdef DEBUG
		else if (event.type == SDL_KEYDOWN && event.key.repeat == 0)
		{
			switch (event.key.keysym.sym)
			{
			case SDLK_1: // Crea una powerball
			{
				createPowerBall();
				break;
			}
			case SDLK_2: // Crea dos globos gordos
			{
				createTwoBigBalloons();
			}
			break;
			case SDLK_3: // Activa el modo para pasar el juego automaticamente
			{
				auto_pop_balloons_ = !auto_pop_balloons_;
				Notifier::get()->showText({"auto_pop_balloons_ " + boolToString(auto_pop_balloons_)});
				break;
			}
			case SDLK_4: // Suelta un item
			{
				createItem(ItemType::CLOCK, players_.at(0)->getPosX(), players_.at(0)->getPosY() - 40);
				break;
			}
			case SDLK_5: // Crea un PathSprite
			{
				const int x = players_.at(0)->getPosX() + (players_.at(0)->getWidth() - game_text_textures_[3]->getWidth()) / 2;
				createItemText(x, game_text_textures_.at(3));
				break;
			}
			case SDLK_6: // Crea un mensaje
			{
				createMessage({paths_.at(4), paths_.at(5)}, Resource::get()->getTexture("congratulations"));
				createMessage({paths_.at(6), paths_.at(7)}, Resource::get()->getTexture("1000000_points"));
				break;
			}
			case SDLK_7: // Flash
			{
				screen_->flash(flash_color, 100);
				break;
			}
			case SDLK_8:
			{
				players_.at(0)->setPlayingState(PlayerState::GAME_COMPLETED);
			}
			default:
				break;
			}
		}
#endif
	}
}

// Recarga las texturas
void Game::reloadTextures()
{
	for (auto &texture : item_textures_)
		texture->reLoad();

	for (auto &texture : balloon_textures_)
		texture->reLoad();

	for (auto &textures : player_textures_)
		for (auto &texture : textures)
			texture->reLoad();

	for (auto &texture : game_text_textures_)
		texture->reLoad();

	bullet_texture_->reLoad();
	background_->reloadTextures();
}

// Actualiza el marcador
void Game::updateScoreboard()
{
	for (const auto &player : players_)
	{
		scoreboard_->setScore(player->getScoreBoardPanel(), player->getScore());
		scoreboard_->setMult(player->getScoreBoardPanel(), player->getScoreMultiplier());
	}

	// Resto de marcador
	scoreboard_->setStage(balloon_formations_->getStage(current_stage_).number);
	scoreboard_->setPower((float)current_power_ / (float)balloon_formations_->getStage(current_stage_).power_to_complete);
	scoreboard_->setHiScore(hi_score_.score);
	scoreboard_->setHiScoreName(hi_score_.name);

	// Lógica del marcador
	scoreboard_->update();
}

// Pausa el juego
void Game::pause(bool value)
{
	paused_ = value;
	screen_->attenuate(paused_);
}

// Añade una puntuación a la tabla de records
void Game::addScoreToScoreBoard(const std::string &name, int score)
{
	const auto entry = HiScoreEntry(trim(name), score);
	auto manager = std::make_unique<ManageHiScoreTable>(options.game.hi_score_table);
	manager->add(entry);
	manager->saveToFile(asset_->get("score.bin"));
}

// Saca del estado de GAME OVER al jugador si el otro está activo
void Game::checkAndUpdatePlayerStatus(int activePlayerIndex, int inactivePlayerIndex)
{
	if (players_[activePlayerIndex]->isGameOver() && !players_[inactivePlayerIndex]->isGameOver() && !players_[inactivePlayerIndex]->isWaiting())
	{
		players_[activePlayerIndex]->setPlayingState(PlayerState::WAITING);
	}
}

// Comprueba el estado de los jugadores
void Game::checkPlayersStatusPlaying()
{
	if (demo_.enabled)
	{
		return;
	}

	// Comprueba si todos los jugadores estan esperando
	if (allPlayersAreWaitingOrGameOver())
	{
		// Entonces los pone en estado de Game Over
		for (auto &player : players_)
		{
			player->setPlayingState(PlayerState::GAME_OVER);
		}
	}

	// Comprobar estado de ambos jugadores
	checkAndUpdatePlayerStatus(0, 1);
	checkAndUpdatePlayerStatus(1, 0);
}

// Obtiene un jugador a partir de su "id"
std::shared_ptr<Player> Game::getPlayer(int id)
{
	auto it = std::find_if(players_.begin(), players_.end(), [id](const auto &player)
						   { return player->getId() == id; });

	if (it != players_.end())
	{
		return *it;
	}
	return nullptr;
}

// Obtiene un controlador a partir del "id" del jugador
int Game::getController(int player_id)
{
	auto it = std::find_if(options.controllers.begin(), options.controllers.end(), [player_id](const auto &controller)
						   { return controller.player_id == player_id; });

	if (it != options.controllers.end())
	{
		return std::distance(options.controllers.begin(), it);
	}

	return -1;
}

// Gestiona la entrada durante el juego
void Game::checkInput()
{
	// Verifica si se debe pausar el juego.
	checkPauseInput();

	demo_.enabled ? handleDemoMode() : handlePlayersInput();

	// Verifica los inputs globales.
	globalInputs::check();
}

// Verifica si alguno de los controladores ha solicitado una pausa y actualiza el estado de pausa del juego.
void Game::checkPauseInput()
{
	// Comprueba los mandos
	for (int i = 0; i < input_->getNumControllers(); ++i)
		if (input_->checkInput(InputType::SERVICE, INPUT_ALLOW_REPEAT, InputDeviceToUse::CONTROLLER, i) &&
			input_->checkInput(InputType::PAUSE, INPUT_DO_NOT_ALLOW_REPEAT, InputDeviceToUse::CONTROLLER, i))
		{
			pause(!paused_);
			return;
		}

	// Comprueba el teclado
	if (input_->checkInput(InputType::PAUSE, INPUT_DO_NOT_ALLOW_REPEAT, InputDeviceToUse::KEYBOARD))
	{
		pause(!paused_);
		return;
	}
}

// Gestiona las entradas de los jugadores en el modo demo, incluyendo movimientos y disparos automáticos.
void Game::handleDemoMode()
{
	int index = 0;
	for (const auto &player : players_)
	{
		if (player->isPlaying())
		{ // Maneja el input específico del jugador en modo demo.
			handleDemoPlayerInput(player, index);
		}

		if (input_->checkAnyButtonPressed())
		{
			section::name = section::Name::TITLE; // Salir del modo demo y regresar al menú principal.
			return;
		}
		++index;
	}
}

// Procesa las entradas para un jugador específico durante el modo demo.
void Game::handleDemoPlayerInput(const std::shared_ptr<Player> &player, int index)
{
	const auto &demoData = demo_.data[index][demo_.counter];

	if (demoData.left == 1)
	{
		player->setInput(InputType::LEFT);
	}
	else if (demoData.right == 1)
	{
		player->setInput(InputType::RIGHT);
	}
	else if (demoData.no_input == 1)
	{
		player->setInput(InputType::NONE);
	}

	if (demoData.fire == 1)
	{
		handleFireInput(player, BulletType::UP);
	}
	else if (demoData.fire_left == 1)
	{
		handleFireInput(player, BulletType::LEFT);
	}
	else if (demoData.fire_right == 1)
	{
		handleFireInput(player, BulletType::RIGHT);
	}
}

// Maneja el disparo de un jugador, incluyendo la creación de balas y la gestión del tiempo de espera entre disparos.
void Game::handleFireInput(const std::shared_ptr<Player> &player, BulletType bulletType)
{
	if (player->canFire())
	{
		player->setInput(bulletType == BulletType::UP ? InputType::FIRE_CENTER : bulletType == BulletType::LEFT ? InputType::FIRE_LEFT
																												: InputType::FIRE_RIGHT);
		createBullet(player->getPosX() + (player->getWidth() / 2) - 6, player->getPosY() + (player->getHeight() / 2), bulletType, player->isPowerUp(), player->getId());
		JA_PlaySound(Resource::get()->getSound("bullet.wav"));
		// Establece un tiempo de espera para el próximo disparo.
		player->setFireCooldown(10);
	}
}

// Gestiona las entradas de todos los jugadores en el modo normal (fuera del modo demo).
void Game::handlePlayersInput()
{
	for (const auto &player : players_)
	{
		if (player->isPlaying())
		{ // Maneja el input de los jugadores en modo normal.
			handleNormalPlayerInput(player);
		}
		else if (player->isContinue() || player->isWaiting())
		{ // Gestiona la continuación del jugador.
			handlePlayerContinue(player);
		}
		else if (player->isEnteringName() || player->isEnteringNameGameCompleted())
		{ // Gestiona la introducción del nombre del jugador.
			handleNameInput(player);
		}
	}
}

// Maneja las entradas de movimiento y disparo para un jugador en modo normal.
void Game::handleNormalPlayerInput(const std::shared_ptr<Player> &player)
{
	const auto &controller = options.controllers.at(player->getController());
	const bool autofire = player->isPowerUp() || options.game.autofire;

	if (input_->checkInput(InputType::LEFT, INPUT_ALLOW_REPEAT, controller.type, controller.index))
	{
		player->setInput(InputType::LEFT);
#ifdef RECORDING
		demo_.keys.left = 1;
#endif
	}
	else if (input_->checkInput(InputType::RIGHT, INPUT_ALLOW_REPEAT, controller.type, controller.index))
	{
		player->setInput(InputType::RIGHT);
#ifdef RECORDING
		demo_.keys.right = 1;
#endif
	}
	else
	{
		player->setInput(InputType::NONE);
#ifdef RECORDING
		demo_.keys.no_input = 1;
#endif
	}

	handleFireInputs(player, autofire, player->getController()); // Verifica y maneja todas las posibles entradas de disparo.
}

// Procesa las entradas de disparo del jugador, permitiendo disparos automáticos si está habilitado.
void Game::handleFireInputs(const std::shared_ptr<Player> &player, bool autofire, int controllerIndex)
{
	if (input_->checkInput(InputType::FIRE_CENTER, autofire, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		handleFireInput(player, BulletType::UP);
#ifdef RECORDING
		demo_.keys.fire = 1;
#endif
	}
	else if (input_->checkInput(InputType::FIRE_LEFT, autofire, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		handleFireInput(player, BulletType::LEFT);
#ifdef RECORDING
		demo_.keys.fire_left = 1;
#endif
	}
	else if (input_->checkInput(InputType::FIRE_RIGHT, autofire, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		handleFireInput(player, BulletType::RIGHT);
#ifdef RECORDING
		demo_.keys.fire_right = 1;
#endif
	}
}

// Maneja la continuación del jugador cuando no está jugando, permitiendo que continúe si se pulsa el botón de inicio.
void Game::handlePlayerContinue(const std::shared_ptr<Player> &player)
{
	const auto controllerIndex = player->getController();
	if (input_->checkInput(InputType::START, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		player->setPlayingState(PlayerState::PLAYING);
	}

	// Disminuye el contador de continuación si se presiona cualquier botón de disparo.
	if (input_->checkInput(InputType::FIRE_LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index) ||
		input_->checkInput(InputType::FIRE_CENTER, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index) ||
		input_->checkInput(InputType::FIRE_RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		player->decContinueCounter();
	}
}

// Procesa las entradas para la introducción del nombre del jugador.
void Game::handleNameInput(const std::shared_ptr<Player> &player)
{
	const auto controllerIndex = player->getController();
	if (input_->checkInput(InputType::FIRE_LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index) ||
		input_->checkInput(InputType::FIRE_CENTER, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index) ||
		input_->checkInput(InputType::FIRE_RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		if (player->getRecordNamePos() == 7)
		{
			player->setInput(InputType::START);
			addScoreToScoreBoard(player->getRecordName(), player->getScore());
			const auto status = player->getPlayingState();
			player->setPlayingState(status == PlayerState::ENTERING_NAME ? PlayerState::CONTINUE : PlayerState::GAME_COMPLETED);
		}
		else
		{
			player->setInput(InputType::RIGHT);
		}
	}
	else if (input_->checkInput(InputType::UP, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		player->setInput(InputType::UP);
	}
	else if (input_->checkInput(InputType::DOWN, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		player->setInput(InputType::DOWN);
	}
	else if (input_->checkInput(InputType::LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		player->setInput(InputType::LEFT);
	}
	/*
	else if (input_->checkInput(InputType::RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controller[controllerIndex].device_type, options.controller[controllerIndex].index))
	{
		player->setInput(InputType::RIGHT);
	}
	*/
	else if (input_->checkInput(InputType::START, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		player->setInput(InputType::START);
		addScoreToScoreBoard(player->getRecordName(), player->getScore());
		const auto status = player->getPlayingState();
		player->setPlayingState(status == PlayerState::ENTERING_NAME ? PlayerState::CONTINUE : PlayerState::GAME_COMPLETED);
	}
}

// Inicializa las variables para el modo DEMO
void Game::initDemo(int player_id)
{
	if (demo_.enabled)
	{
		// Aleatoriza la asignación del fichero con los datos del modo demostracion
		const auto demo1 = rand() % 2;
		const auto demo2 = (demo1 == 0) ? 1 : 0;
		demo_.data.emplace_back(Resource::get()->getDemoData(demo1));
		demo_.data.emplace_back(Resource::get()->getDemoData(demo2));

		// Selecciona una pantalla al azar
		{
			constexpr auto demos = 3;
			const auto demo = rand() % demos;
			const int stages[demos] = {0, 3, 5};
			current_stage_ = stages[demo];
		}

		// Actualiza el numero de globos explotados según la fase del modo demostración
		for (int i = 0; i < current_stage_; ++i)
		{
			balloons_popped_ += balloon_formations_->getStage(i).power_to_complete;
		}

		// Activa o no al otro jugador
		if (rand() % 2 == 0)
		{
			const auto other_player_id = player_id == 1 ? 2 : 1;
			auto other_player = getPlayer(other_player_id);
			other_player->setPlayingState(PlayerState::PLAYING);
		}

		// Asigna cafes a los jugadores
		for (auto &player : players_)
		{
			for (int i = 0; i < rand() % 3; ++i)
				player->giveExtraHit();

			player->setInvulnerable(false);
		}

		// Deshabilita los sonidos
		JA_EnableSound(false);

		// Configura los marcadores
		scoreboard_->setMode(SCOREBOARD_LEFT_PANEL, ScoreboardMode::DEMO);
		scoreboard_->setMode(SCOREBOARD_RIGHT_PANEL, ScoreboardMode::DEMO);
	}

	// Modo grabar demo
#ifdef RECORDING
	demo_.recording = true;
#else
	demo_.recording = false;
#endif
	demo_.counter = 0;
}

// Calcula el poder total necesario para completar el juego
void Game::setTotalPower()
{
	total_power_to_complete_game_ = 0;
	for (int i = 0; i < 10; ++i)
	{
		total_power_to_complete_game_ +=
			balloon_formations_->getStage(i).power_to_complete;
	}
}

// Inicializa el marcador
void Game::initScoreboard()
{
	scoreboard_->setPos({param.scoreboard.x, param.scoreboard.y, param.scoreboard.w, param.scoreboard.h});
	scoreboard_->setMode(SCOREBOARD_CENTER_PANEL, ScoreboardMode::STAGE_INFO);
	for (const auto &player : players_)
	{
		scoreboard_->setName(player->getScoreBoardPanel(), player->getName());
		if (player->isWaiting())
			scoreboard_->setMode(player->getScoreBoardPanel(), ScoreboardMode::WAITING);
	}
}

// Inicializa las opciones relacionadas con la dificultad
void Game::initDifficultyVars()
{
	// Variables relacionadas con la dificultad
	switch (difficulty_)
	{
	case GameDifficulty::EASY:
	{
		default_balloon_speed_ = BALLOON_SPEED[0];
		difficulty_score_multiplier_ = 0.5f;
		scoreboard_->setColor(scoreboard_easy_color);
		break;
	}

	case GameDifficulty::NORMAL:
	{
		default_balloon_speed_ = BALLOON_SPEED[0];
		difficulty_score_multiplier_ = 1.0f;
		scoreboard_->setColor(scoreboard_normal_color);
		break;
	}

	case GameDifficulty::HARD:
	{
		default_balloon_speed_ = BALLOON_SPEED[4];
		difficulty_score_multiplier_ = 1.5f;
		scoreboard_->setColor(scoreboard_hard_color);
		break;
	}

	default:
		break;
	}

	balloon_speed_ = default_balloon_speed_;
}

// Inicializa los jugadores
void Game::initPlayers(int player_id)
{
	// Crea los dos jugadores
	const int y = param.game.play_area.rect.h - 30;
	players_.emplace_back(std::make_unique<Player>(1, param.game.play_area.first_quarter_x - 15, y, demo_.enabled, param.game.play_area.rect, player_textures_[0], player_animations_));
	players_.back()->setScoreBoardPanel(SCOREBOARD_LEFT_PANEL);
	players_.back()->setName(lang::getText(53));
	players_.back()->setController(getController(players_.back()->getId()));

	players_.emplace_back(std::make_unique<Player>(2, param.game.play_area.third_quarter_x - 15, y, demo_.enabled, param.game.play_area.rect, player_textures_[1], player_animations_));
	players_.back()->setScoreBoardPanel(SCOREBOARD_RIGHT_PANEL);
	players_.back()->setName(lang::getText(54));
	players_.back()->setController(getController(players_.back()->getId()));

	// Activa el jugador que coincide con el "player_id"
	auto player = getPlayer(player_id);
	player->setPlayingState(PlayerState::PLAYING);
	player->setInvulnerable(false);
}

// Crea dos globos gordos
void Game::createTwoBigBalloons()
{
	const auto set = balloon_formations_->getStage(0).balloon_pool.set[1];
	const auto numEnemies = set.number_of_balloons;
	for (int i = 0; i < numEnemies; ++i)
	{
		auto p = set.init[i];
		createBalloon(p.x, p.y, p.type, p.size, p.vel_x, balloon_speed_,
					  p.creation_counter);
	}
}

// Pausa la música
void Game::pauseMusic()
{
	if (JA_GetMusicState() == JA_MUSIC_PLAYING && !demo_.enabled)
		JA_PauseMusic();
}

// Reanuda la música
void Game::resumeMusic()
{
	if (JA_GetMusicState() == JA_MUSIC_PAUSED && !demo_.enabled)
		JA_ResumeMusic();
}

// Detiene la música
void Game::stopMusic()
{
	if (!demo_.enabled)
		JA_StopMusic();
}

// Actualiza las variables durante el modo demo
void Game::updateDemo()
{
	if (demo_.enabled)
	{
		// Incrementa el contador de la demo
		if (demo_.counter < TOTAL_DEMO_DATA)
			demo_.counter++;

		// Activa el fundido antes de acabar con los datos de la demo
		if (demo_.counter == TOTAL_DEMO_DATA - 200)
		{
			fade_->setType(FadeType::RANDOM_SQUARE);
			fade_->activate();
		}

		// Si ha terminado el fundido, cambia de sección
		if (fade_->hasEnded())
		{
			section::name = section::Name::HI_SCORE_TABLE;
			return;
		}
	}
}

#ifdef RECORDING
// Actualiza las variables durante el modo de grabación
void Game::updateRecording()
{
	// Solo mira y guarda el input en cada update
	checkInput();

	// Incrementa el contador de la demo
	if (demo_.counter < TOTAL_DEMO_DATA)
		demo_.counter++;

	// Si se ha llenado el vector con datos, sale del programa
	else
	{
		section::name = section::Name::QUIT;
		return;
	}
}
#endif

// Actualiza las variables durante el transcurso normal del juego
void Game::updateGame()
{
	if (!paused_)
	{
#ifdef DEBUG
		if (auto_pop_balloons_ && state_ == GameState::PLAYING)
		{
			balloons_popped_ += 5;
			increaseStageCurrentPower(5);
		}
#endif
		fade_->update();
		updatePlayers();
		checkPlayersStatusPlaying();
		updateScoreboard();
		updateBackground();
		updateBalloons();
		explosions_->update();
		moveBullets();
		updateItems();
		updateStage();
		updateGameOverState();
		updateCompletedState();
		updateSmartSprites();
		updatePathSprites();
		updateTimeStopped();
		updateBalloonDeployCounter();
		updateHelper();
		checkBulletBalloonCollision();
		updateMenace();
		checkAndUpdateBalloonSpeed();
		checkState();
		cleanVectors();
	}
}

// Vacía los vectores de elementos deshabilitados
void Game::cleanVectors()
{
	freeBullets();
	freeBalloons();
	freeItems();
	freeSmartSprites();
	freePathSprites();
}