#include "game.h"
#include <SDL3/SDL_blendmode.h>	  // Para SDL_BLENDMODE_BLEND
#include <SDL3/SDL_events.h>	  // Para SDL_EventType, SDL_Event, SDL_Poll...
#include <SDL3/SDL_keycode.h>	  // Para SDLK_1, SDLK_2, SDLK_3, SDLK_4
#include <SDL3/SDL_pixels.h>	  // Para SDL_PixelFormat
#include <SDL3/SDL_timer.h>		  // Para SDL_GetTicks
#include <stdlib.h>				  // Para rand, size_t
#include <algorithm>			  // Para find_if, clamp, min
#include <functional>			  // Para function
#include <iterator>				  // Para distance, size
#include "asset.h"				  // Para Asset
#include "background.h"			  // Para Background
#include "balloon.h"			  // Para Balloon, BALLOON_SPEED
#include "balloon_manager.h"	  // Para BalloonManager
#include "bullet.h"				  // Para Bullet, BulletType, BulletMoveStatus
#include "fade.h"				  // Para Fade, FadeType, FadeMode
#include "global_events.h"		  // Para check
#include "global_inputs.h"		  // Para check, update
#include "input.h"				  // Para InputAction, Input, INPUT_DO_NOT_A...
#include "item.h"				  // Para Item, ItemType
#include "audio.h"				  // Para Audio::icStateplaynclude "lang.h"				  // Para getText
#include "manage_hiscore_table.h" // Para ManageHiScoreTable, HiScoreEntry
#include "notifier.h"			  // Para Notifier
#include "param.h"				  // Para Param, param, ParamGame, ParamFade
#include "path_sprite.h"		  // Para Path, PathSprite, createPath, Path...
#include "player.h"				  // Para Player, PlayerState
#include "resource.h"			  // Para Resource
#include "scoreboard.h"			  // Para Scoreboard, ScoreboardMode, SCOREB...
#include "screen.h"				  // Para Screen
#include "section.h"			  // Para Name, name, AttractMode, Options
#include "smart_sprite.h"		  // Para SmartSprite
#include "stage.h"				  // Para number, get, Stage, total_power
#include "tabe.h"				  // Para Tabe, TabeState
#include "text.h"				  // Para Text
#include "texture.h"			  // Para Texture
#include "lang.h"

// Constructor
Game::Game(int player_id, int current_stage, bool demo)
	: renderer_(Screen::get()->getRenderer()),
	  screen_(Screen::get()),
	  input_(Input::get()),
	  background_(std::make_unique<Background>()),
	  canvas_(SDL_CreateTexture(renderer_, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, param.game.play_area.rect.w, param.game.play_area.rect.h)),
	  fade_in_(std::make_unique<Fade>()),
	  fade_out_(std::make_unique<Fade>()),
	  balloon_manager_(std::make_unique<BalloonManager>()),
	  tabe_(std::make_unique<Tabe>())
{
	// Pasa variables
	demo_.enabled = demo;

	// Otras variables
	section::name = section::Name::GAME;
	section::options = section::Options::NONE;
	Stage::init();
	Stage::number = current_stage;

	// Asigna texturas y animaciones
	setResources();

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

	fade_in_->setColor(FADE_COLOR.r, FADE_COLOR.g, FADE_COLOR.b);
	fade_in_->setPreDuration(demo_.enabled ? 80 : 0);
	fade_in_->setPostDuration(0);
	fade_in_->setType(FadeType::RANDOM_SQUARE);
	fade_in_->setMode(FadeMode::IN);
	fade_in_->activate();

	fade_out_->setColor(FADE_COLOR.r, FADE_COLOR.g, FADE_COLOR.b);
	fade_out_->setPostDuration(param.fade.post_duration);
	fade_out_->setType(FadeType::VENETIAN);

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

	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 < Stage::number; ++i)
		{
			Stage::total_power += Stage::get(i).power_to_complete;
		}
#endif
}

Game::~Game()
{
	// [Modo DEMO] Vuelve a activar los sonidos
	if (demo_.enabled)
	{
		Audio::get().enableSound();
	}
	else
	{
		// [Modo JUEGO] Guarda puntuaciones y transita a modo título
		auto manager = std::make_unique<ManageHiScoreTable>(options.game.hi_score_table);
		manager->saveToFile(Asset::get().get("score.bin"));
		section::attract_mode = section::AttractMode::TITLE_TO_DEMO;
		Audio::get().stopMusic();
	}

#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"));
		game_text_textures_.emplace_back(Resource::get()->getTexture("game_text_100000_points"));
	}

	// 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_debian.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 -- 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_debian.ani"));
		item_animations_.emplace_back(Resource::get()->getAnimation("item_coffee_machine.ani"));
	}
}

// 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();
			hi_score_.name.clear();
			;

			// Si se supera la máxima puntuación emite sonido
			if (hi_score_achieved_ == false)
			{
				hi_score_achieved_ = true;
				Audio::get().playSound("hi_score_achieved.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_out_->setType(FadeType::RANDOM_SQUARE);
					fade_out_->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 (Stage::power >= Stage::get(Stage::number).power_to_complete)
	{
		// Cambio de fase
		Stage::power = Stage::get(Stage::number).power_to_complete - Stage::power;
		++Stage::number;
		Audio::get().playSound("stage_change.wav");
		balloon_manager_->resetBalloonSpeed();
		screen_->flash(FLASH_COLOR, 3);
		screen_->shake();

		// Escribe el texto por pantalla
		if (Stage::number < 10)
		{
			std::vector<Path> paths = {paths_.at(2), paths_.at(3)};
			if (Stage::number == 9)
			{
				createMessage(paths, Resource::get()->getTexture("game_text_last_stage"));
			}
			else
			{
				auto text = Resource::get()->getText("04b_25_2x");
				const std::string caption = std::to_string(10 - Stage::number) + lang::getText(38);
				createMessage(paths, text->writeToTexture(caption, 1, -4));
			}
		}

		// Modifica el color de fondo al llegar a la Fase 10
		if (Stage::number == 9)
		{
			background_->setColor(Color(0xdd, 0x19, 0x1d).darken());
			background_->setAlpha(96);
		}
	}
}

// Actualiza el estado de fin de la partida
void Game::updateGameStateGameOver()
{
	fade_out_->update();
	updatePlayers();
	updateScoreboard();
	updateBackground();
	balloon_manager_->update();
	tabe_->update();
	updateBullets();
	updateItems();
	updateSmartSprites();
	updatePathSprites();
	updateTimeStopped();
	checkBulletCollision();
	cleanVectors();

	if (game_over_counter_ > 0)
	{
		if (game_over_counter_ == GAME_OVER_COUNTER_)
		{
			createMessage({paths_.at(2), paths_.at(3)}, Resource::get()->getTexture("game_text_game_over"));
			Audio::get().fadeOutMusic(1000);
			balloon_manager_->setSounds(true);
		}

		game_over_counter_--;

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

	if (fade_out_->isEnabled())
	{
		if (options.audio.enabled)
		{
			const float VOL = static_cast<float>(64 * (100 - fade_out_->getValue())) / 100.0f;
			Audio::get().setSoundVolume(static_cast<int>(VOL));
		}
	}

	if (fade_out_->hasEnded())
	{
		if (game_completed_counter_ > 0)
		{
			// Los jugadores han completado el juego
			section::name = section::Name::CREDITS;
		}
		else
		{
			// La partida ha terminado con la derrota de los jugadores
			section::name = section::Name::HI_SCORE_TABLE;
		}
		section::options = section::Options::HI_SCORE_AFTER_PLAYING;
		if (options.audio.enabled)
		{
			Audio::get().stopAllSounds();
			Audio::get().setSoundVolume(options.audio.sound.volume);
		}
	}
}

// Gestiona eventos para el estado del final del juego
void Game::updateGameStateCompleted()
{
	constexpr int START_CELEBRATIONS = 400;
	constexpr int END_CELEBRATIONS = START_CELEBRATIONS + 300;

	updatePlayers();
	updateScoreboard();
	updateBackground();
	balloon_manager_->update();
	tabe_->update();
	updateBullets();
	updateItems();
	updateSmartSprites();
	updatePathSprites();
	cleanVectors();

	// Para la música y elimina todos los globos e items
	if (game_completed_counter_ == 0)
	{
		stopMusic();
		Stage::number = 9;						// Deja el valor dentro de los limites
		balloon_manager_->destroyAllBalloons(); // Destruye a todos los globos
		destroyAllItems();						// Destruye todos los items
		Stage::power = 0;						// Vuelve a dejar el poder a cero, por lo que hubiera podido subir al destruir todos los globos
		background_->setAlpha(0);				// Elimina el tono rojo de las últimas pantallas
	}

	// Comienza las celebraciones
	// Muestra el mensaje de felicitación y da los puntos a los jugadores
	if (game_completed_counter_ == START_CELEBRATIONS)
	{
		createMessage({paths_.at(4), paths_.at(5)}, Resource::get()->getTexture("game_text_congratulations"));
		createMessage({paths_.at(6), paths_.at(7)}, Resource::get()->getTexture("game_text_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_ == END_CELEBRATIONS)
	{
		for (auto &player : players_)
		{
			if (player->isCelebrating())
			{
				player->setPlayingState(player->IsEligibleForHighScore() ? PlayerState::ENTERING_NAME_GAME_COMPLETED : PlayerState::LEAVING_SCREEN);
			}
		}
	}

	// Si los jugadores ya no estan y no quedan mensajes en pantalla
	if (allPlayersAreGameOver() && path_sprites_.size() == 0)
	{
		setState(GameState::GAME_OVER);
	}

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

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

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

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

// Comprueba la colisión entre el jugador y los globos activos
bool Game::checkPlayerBalloonCollision(std::shared_ptr<Player> &player)
{
	for (auto &balloon : balloon_manager_->getBalloons())
	{
		if (!balloon->isStopped() && !balloon->isInvulnerable() && !balloon->isPowerBall())
		{
			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_.at(0)->getWidth()) / 2;
					createItemText(x, game_text_textures_.at(0));
					Audio::get().playSound("item_pickup.wav");
					break;
				}
				case ItemType::GAVINA:
				{
					player->addScore(2500);
					const auto x = item->getPosX() + (item->getWidth() - game_text_textures_.at(1)->getWidth()) / 2;
					createItemText(x, game_text_textures_.at(1));
					Audio::get().playSound("item_pickup.wav");
					break;
				}
				case ItemType::PACMAR:
				{
					player->addScore(5000);
					const auto x = item->getPosX() + (item->getWidth() - game_text_textures_.at(2)->getWidth()) / 2;
					createItemText(x, game_text_textures_.at(2));
					Audio::get().playSound("item_pickup.wav");
					break;
				}
				case ItemType::DEBIAN:
				{
					player->addScore(100000);
					const auto x = item->getPosX() + (item->getWidth() - game_text_textures_.at(6)->getWidth()) / 2;
					createItemText(x, game_text_textures_.at(6));
					Audio::get().playSound("debian_pickup.wav");
					break;
				}
				case ItemType::CLOCK:
				{
					enableTimeStopItem();
					const auto x = item->getPosX() + (item->getWidth() - game_text_textures_.at(5)->getWidth()) / 2;
					createItemText(x, game_text_textures_.at(5));
					Audio::get().playSound("item_pickup.wav");
					break;
				}
				case ItemType::COFFEE:
				{
					if (player->getCoffees() == 2)
					{
						player->addScore(5000);
						const auto x = item->getPosX() + (item->getWidth() - game_text_textures_.at(2)->getWidth()) / 2;
						createItemText(x, game_text_textures_.at(2));
					}
					else
					{
						player->giveExtraHit();
						const auto x = item->getPosX() + (item->getWidth() - game_text_textures_.at(4)->getWidth()) / 2;
						createItemText(x, game_text_textures_.at(4));
					}
					Audio::get().playSound("voice_coffee.wav");
					break;
				}
				case ItemType::COFFEE_MACHINE:
				{
					player->setPowerUp();
					coffee_machine_enabled_ = false;
					const auto x = item->getPosX() + (item->getWidth() - game_text_textures_.at(3)->getWidth()) / 2;
					createItemText(x, game_text_textures_.at(3));
					Audio::get().playSound("voice_power_up.wav");
					break;
				}
				default:
					break;
				}

				updateHiScore();
				item->disable();
			}
		}
	}
}

// Comprueba y procesa la colisión de las balas
void Game::checkBulletCollision()
{
	for (auto &bullet : bullets_)
	{
		// Comprueba la colisión con el Tabe
		if (bullet->isEnabled() && tabe_->isEnabled())
			if (checkCollision(bullet->getCollider(), tabe_->getCollider()))
			{
				tabe_->setState(TabeState::HIT);
				bullet->disable();
				auto pos = tabe_->getCollider();
				if (tabe_->tryToGetBonus())
				{
					createItem(ItemType::DEBIAN, pos.x, pos.y);
					Audio::get().playSound("debian_drop.wav");
				}
				else
				{
					if (rand() % 3 == 0)
					{
						createItem(ItemType::COFFEE, pos.x, pos.y);
					}
					Audio::get().playSound("tabe_hit.wav");
				}
				break;
			}

		// Comprueba la colisión con los globos
		for (auto &balloon : balloon_manager_->getBalloons())
		{
			if (balloon->isEnabled() && (!balloon->isInvulnerable()) && bullet->isEnabled())
			{
				if (checkCollision(balloon->getCollider(), bullet->getCollider()))
				{
					// Obtiene al jugador que disparó la bala
					auto player = getPlayer(bullet->getOwner());

					// Suelta el item si se da el caso
					const auto dropped_item = dropItem();
					if (dropped_item != ItemType::NONE && !demo_.recording)
					{
						if (dropped_item != ItemType::COFFEE_MACHINE)
						{
							createItem(dropped_item, balloon->getPosX(), balloon->getPosY());
							Audio::get().playSound("item_drop.wav");
						}
						else
						{
							createItem(dropped_item, player->getPosX(), param.game.game_area.rect.y - param.game.coffee_machine_h);
							coffee_machine_enabled_ = true;
						}
					}

					// Explota el globo
					const auto score = balloon_manager_->popBalloon(balloon);
					evaluateAndSetMenace();

					// Otorga los puntos al jugador que disparó la bala
					if (player->isPlaying())
					{
						player->addScore(score * player->getScoreMultiplier() * difficulty_score_multiplier_);
						player->incScoreMultiplier();
					}
					updateHiScore();

					// Sonido de explosión
					Audio::get().playSound("balloon.wav");

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

					break;
				}
			}
		}
	}
}

// Mueve las balas activas
void Game::updateBullets()
{
	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())
			{
				Audio::get().playSound("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 = 160 - (h / 2);
	const int y2 = -h;

	// Ajusta para que no se dibuje fuera de pantalla
	x = std::clamp(x, 2, static_cast<int>(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, static_cast<float>(w), static_cast<float>(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()->setRotate(true);
	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));
		Audio::get().playSound("coffee_out.wav");
		screen_->shake();
	}
	else
	{
		// Si no tiene cafes, muere
		balloon_manager_->stopAllBalloons();
		Audio::get().playSound("player_collision.wav");
		screen_->shake();
		Audio::get().playSound("voice_no.wav");
		player->setPlayingState(PlayerState::DYING);
		if (allPlayersAreNotPlaying())
		{
			// No se puede subir poder de fase si no hay nadie jugando
			Stage::power_can_be_added = false;
		}
	}
}

// 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)
			{
				Audio::get().playSound("clock.wav");
			}
		}
		else
		{
			if (time_stopped_counter_ % 30 == 0)
			{
				balloon_manager_->normalColorsToAllBalloons();
				Audio::get().playSound("clock.wav");
			}
			else if (time_stopped_counter_ % 30 == 15)
			{
				balloon_manager_->reverseColorsToAllBalloons();
				Audio::get().playSound("clock.wav");
			}
		}
	}
	else
	{
		disableTimeStopItem();
	}
}

// Actualiza el juego
void Game::update()
{
	if (SDL_GetTicks() - ticks_ > param.game.speed)
	{
		ticks_ = SDL_GetTicks();

		updateDemo();
#ifdef RECORDING
		updateRecording();
#endif
		if (!paused_)
		{
			switch (state_)
			{
			case GameState::FADE_IN:
				updateGameStateFadeIn();
				break;
			case GameState::ENTERING_PLAYER:
				updateGameStateEnteringPlayer();
				break;
			case GameState::SHOWING_GET_READY_MESSAGE:
				updateGameStateShowingGetReadyMessage();
				break;
			case GameState::PLAYING:
				updateGameStatePlaying();
				break;
			case GameState::COMPLETED:
				updateGameStateCompleted();
				break;
			case GameState::GAME_OVER:
				updateGameStateGameOver();
				break;
			default:
				break;
			}
		}

		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)
	{
		Stage::total_power = (Stage::total_power > 200) ? (Stage::total_power - 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>(Stage::total_power) / total_power_to_complete_game_));
	background_->setCloudsSpeed(cloudsSpeed);

	// Calcula la transición de los diferentes fondos
	constexpr float num = 1525.0f; // total_power_to_complete div 4
	const float gradient_number = std::min(Stage::total_power / num, 3.0f);
	const float percent = gradient_number - static_cast<int>(gradient_number);
	background_->setGradientNumber(static_cast<int>(gradient_number));
	background_->setTransition(percent);

	// Calcula la posición del sol
	constexpr float sun_final_power = num * 2;
	background_->setSunProgression(Stage::total_power / sun_final_power);
	background_->setMoonProgression(Stage::total_power / static_cast<float>(total_power_to_complete_game_));

	// 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();
	balloon_manager_->render();
	tabe_->render();
	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_RenderTexture(renderer_, canvas_, nullptr, &param.game.play_area.rect);

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

	// Dibuja el fade
	fade_in_->render();
	fade_out_->render();

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

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

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

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

// 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("game_text_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("game_text_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("game_text_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("game_text_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))
	{
		switch (event.type)
		{
		case SDL_EVENT_WINDOW_FOCUS_LOST:
		{
			pause(!demo_.enabled);
			break;
		}
		case SDL_EVENT_WINDOW_FOCUS_GAINED:
		{
			pause(false);
			break;
		}
		default:
			break;
		}

#ifdef DEBUG
		checkDebugEvents(event);
#endif
		globalEvents::check(event);
	}
}

// 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(Stage::number + 1);
	scoreboard_->setPower((float)Stage::power / (float)Stage::get(Stage::number).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::shared_ptr<Player> &player)
{
	const auto entry = HiScoreEntry(trim(player->getRecordName()), player->getScore(), player->get1CC());
	auto manager = std::make_unique<ManageHiScoreTable>(options.game.hi_score_table);
	options.game.last_hi_score_entry.at(player->getId() - 1) = manager->add(entry);
	manager->saveToFile(Asset::get().get("score.bin"));
	hi_score_.name = options.game.hi_score_table.front().name;
}

// 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(InputAction::SERVICE, INPUT_ALLOW_REPEAT, InputDeviceToUse::CONTROLLER, i) &&
			input_->checkInput(InputAction::PAUSE, INPUT_DO_NOT_ALLOW_REPEAT, InputDeviceToUse::CONTROLLER, i))
		{
			pause(!paused_);
			return;
		}
	}

	// Comprueba el teclado
	if (input_->checkInput(InputAction::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.
			section::attract_mode = section::AttractMode::TITLE_TO_DEMO; // El juego volverá a mostrar la demo
			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(InputAction::LEFT);
	}
	else if (demoData.right == 1)
	{
		player->setInput(InputAction::RIGHT);
	}
	else if (demoData.no_input == 1)
	{
		player->setInput(InputAction::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 ? InputAction::FIRE_CENTER : bulletType == BulletType::LEFT ? InputAction::FIRE_LEFT
																												  : InputAction::FIRE_RIGHT);
		createBullet(player->getPosX() + (player->getWidth() / 2) - 6, player->getPosY() + (player->getHeight() / 2), bulletType, player->isPowerUp(), player->getId());
		Audio::get().playSound("bullet.wav");

		// Establece un tiempo de espera para el próximo disparo.
		const int cooldown = player->isPowerUp() ? 5 : options.game.autofire ? 10
																			 : 7;
		player->setFireCooldown(cooldown);
	}
}

// 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() || player->isShowingName())
		{
			// 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(InputAction::LEFT, INPUT_ALLOW_REPEAT, controller.type, controller.index))
	{
		player->setInput(InputAction::LEFT);
#ifdef RECORDING
		demo_.keys.left = 1;
#endif
	}
	else if (input_->checkInput(InputAction::RIGHT, INPUT_ALLOW_REPEAT, controller.type, controller.index))
	{
		player->setInput(InputAction::RIGHT);
#ifdef RECORDING
		demo_.keys.right = 1;
#endif
	}
	else
	{
		player->setInput(InputAction::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(InputAction::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(InputAction::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(InputAction::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(InputAction::START, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		player->setPlayingState(PlayerState::PLAYING);
		player->addCredit();
	}

	// Disminuye el contador de continuación si se presiona cualquier botón de disparo.
	if (input_->checkInput(InputAction::FIRE_LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index) ||
		input_->checkInput(InputAction::FIRE_CENTER, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index) ||
		input_->checkInput(InputAction::FIRE_RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		if (player->getContinueCounter() < 7)
		{
			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(InputAction::FIRE_LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index) ||
		input_->checkInput(InputAction::FIRE_CENTER, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index) ||
		input_->checkInput(InputAction::FIRE_RIGHT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		if (player->isShowingName())
		{
			player->setPlayingState(PlayerState::CONTINUE);
		}
		else if (player->getEnterNamePositionOverflow())
		{
			player->setInput(InputAction::START);
			addScoreToScoreBoard(player);
			player->setPlayingState(PlayerState::SHOWING_NAME);
		}
		else
		{
			player->setInput(InputAction::RIGHT);
		}
	}
	else if (input_->checkInput(InputAction::UP, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		player->setInput(InputAction::UP);
	}
	else if (input_->checkInput(InputAction::DOWN, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		player->setInput(InputAction::DOWN);
	}
	else if (input_->checkInput(InputAction::LEFT, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		player->setInput(InputAction::LEFT);
	}
	else if (input_->checkInput(InputAction::START, INPUT_DO_NOT_ALLOW_REPEAT, options.controllers[controllerIndex].type, options.controllers[controllerIndex].index))
	{
		player->setInput(InputAction::START);
		addScoreToScoreBoard(player);
		player->setPlayingState(PlayerState::SHOWING_NAME);
	}
}

// Inicializa las variables para el modo DEMO
void Game::initDemo(int player_id)
{
	if (demo_.enabled)
	{
		// Cambia el estado del juego
		setState(GameState::PLAYING);

		// 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 NUM_DEMOS = 3;
			const auto DEMO = rand() % NUM_DEMOS;
			const int STAGES[NUM_DEMOS] = {0, 3, 5};
			Stage::number = STAGES[DEMO];
		}

		// Actualiza el numero de globos explotados según la fase del modo demostración
		for (int i = 0; i < Stage::number; ++i)
		{
			Stage::total_power += Stage::get(i).power_to_complete;
		}

		// Activa o no al otro jugador
		if (rand() % 3 != 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(true);
		}

		// Deshabilita los sonidos
		Audio::get().disableSound();

		// 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 (const auto &stage : Stage::stages)
	{
		total_power_to_complete_game_ += stage.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:
	{
		balloon_manager_->setDefaultBalloonSpeed(BALLOON_SPEED[0]);
		difficulty_score_multiplier_ = 0.5f;
		scoreboard_->setColor(SCOREBOARD_EASY_COLOR);
		break;
	}

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

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

	default:
		break;
	}

	balloon_manager_->resetBalloonSpeed();
}

// 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((demo_.enabled) ? PlayerState::PLAYING : PlayerState::ENTERING_SCREEN);
	player->setInvulnerable(false);
}

// Hace sonar la música
void Game::playMusic()
{
	Audio::get().playMusic("playing.ogg");
}

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

// Actualiza las variables durante el modo demo
void Game::updateDemo()
{
	if (demo_.enabled)
	{
		balloon_manager_->setCreationTimeEnabled((balloon_manager_->getNumBalloons() == 0) ? false : true);

		// Actualiza ambos fades
		fade_in_->update();
		fade_out_->update();

		// 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_out_->setType(FadeType::RANDOM_SQUARE);
			fade_out_->activate();
		}

		// Si ha terminado el fundido, cambia de sección
		if (fade_out_->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 dicho estado
void Game::updateGameStateFadeIn()
{
	fade_in_->update();
	updateScoreboard();
	updateBackground();
	if (fade_in_->hasEnded())
	{
		setState(GameState::ENTERING_PLAYER);
		balloon_manager_->createTwoBigBalloons();
		evaluateAndSetMenace();
	}
}

// Actualiza las variables durante dicho estado
void Game::updateGameStateEnteringPlayer()
{
	balloon_manager_->update();
	updatePlayers();
	updateScoreboard();
	updateBackground();
	for (const auto &player : players_)
	{
		if (player->isPlaying())
		{
			setState(GameState::SHOWING_GET_READY_MESSAGE);
			createMessage({paths_.at(0), paths_.at(1)}, Resource::get()->getTexture("game_text_get_ready"));
			Audio::get().playSound("voice_get_ready.wav");
		}
	}
}

// Actualiza las variables durante dicho estado
void Game::updateGameStateShowingGetReadyMessage()
{
	balloon_manager_->update();
	updatePathSprites();
	updatePlayers();
	updateBullets();
	updateScoreboard();
	updateBackground();
	freePathSprites();
	if (path_sprites_.size() == 0)
	{
		setState(GameState::PLAYING);
	}
	if (counter_ == 100)
	{
		playMusic();
	}
	++counter_;
}

// Actualiza las variables durante el transcurso normal del juego
void Game::updateGameStatePlaying()
{
#ifdef DEBUG
	if (auto_pop_balloons_)
	{
		Stage::addPower(5);
	}
#endif
	updatePlayers();
	checkPlayersStatusPlaying();
	updateScoreboard();
	updateBackground();
	balloon_manager_->update();
	tabe_->update();
	updateBullets();
	updateItems();
	updateStage();
	updateSmartSprites();
	updatePathSprites();
	updateTimeStopped();
	updateHelper();
	checkBulletCollision();
	updateMenace();
	checkAndUpdateBalloonSpeed();
	checkState();
	cleanVectors();
	// playMusic();
}

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

// Gestiona el nivel de amenaza
void Game::updateMenace()
{
	if (state_ == GameState::PLAYING)
	{
		const auto stage = Stage::get(Stage::number);
		const float percent = Stage::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
			balloon_manager_->deployBalloonFormation(Stage::number);

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

// Calcula y establece el valor de amenaza en funcion de los globos activos
void Game::evaluateAndSetMenace()
{
	menace_current_ = balloon_manager_->getMenace();
}

// 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>(Stage::power) / Stage::get(Stage::number).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_manager_->getBalloonSpeed() == BALLOON_SPEED[i] && percent > thresholds[i])
		{
			balloon_manager_->setBalloonSpeed(BALLOON_SPEED[i + 1]);
			break; // Salir del bucle una vez actualizada la velocidad y aplicada
		}
	}
}

// Cambia el estado del juego
void Game::setState(GameState state)
{
	state_ = state;
	counter_ = 0;
}

#ifdef DEBUG
// Comprueba los eventos en el modo DEBUG
void Game::checkDebugEvents(const SDL_Event &event)
{
	if (event.type == SDL_EVENT_KEY_DOWN && event.key.repeat == 0)
	{
		switch (event.key.key)
		{
		case SDLK_1: // Crea una powerball
		{
			balloon_manager_->createPowerBall();
			break;
		}
		case SDLK_2: // Crea dos globos gordos
		{
			balloon_manager_->createTwoBigBalloons();
			break;
		}
		case SDLK_3: // Activa el modo para pasar el juego automaticamente
		{
			auto_pop_balloons_ = !auto_pop_balloons_;
			Notifier::get()->show({"auto advance: " + boolToString(auto_pop_balloons_)});
			if (auto_pop_balloons_)
			{
				balloon_manager_->destroyAllBalloons();
			}
			balloon_manager_->setDeployBalloons(!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: // 5.000
		{
			const int X = players_.at(0)->getPosX() + (players_.at(0)->getWidth() - game_text_textures_[3]->getWidth()) / 2;
			createItemText(X, game_text_textures_.at(2));
			break;
		}
		case SDLK_6: // Crea un mensaje
		{
			createMessage({paths_.at(0), paths_.at(1)}, Resource::get()->getTexture("game_text_get_ready"));
			break;
		}
		case SDLK_7: // 100.000
		{
			const int X = players_.at(0)->getPosX() + (players_.at(0)->getWidth() - game_text_textures_[3]->getWidth()) / 2;
			createItemText(X, game_text_textures_.at(6));
			break;
		}
		case SDLK_8:
		{
			for (const auto &player : players_)
			{
				if (player->isPlaying())
				{
					createItem(ItemType::COFFEE_MACHINE, player->getPosX(), param.game.game_area.rect.y - param.game.coffee_machine_h);
					break;
				}
			}
			break;
		}
		case SDLK_9:
		{
			tabe_->enable();
			break;
		}
		default:
			break;
		}
	}
}
#endif