// IWYU pragma: no_include <bits/chrono.h>
#include "director.h"
#include <SDL3/SDL_audio.h>		  // Para SDL_AudioFormat
#include <SDL3/SDL_error.h>		  // Para SDL_GetError
#include <SDL3/SDL_gamepad.h>	  // Para SDL_GamepadButton
#include <SDL3/SDL_init.h>		  // Para SDL_Init, SDL_Quit, SDL_INIT_AUDIO
#include <SDL3/SDL.h>			  // Para SDL_Log, SDL_LogCategory, SDL_LogE...
#include <SDL3/SDL_scancode.h>	  // Para SDL_Scancode
#include <errno.h>				  // Para errno, EEXIST, EACCES, ENAMETOOLONG
#include <stdio.h>				  // Para printf, perror
#include <sys/stat.h>			  // Para mkdir, stat, S_IRWXU
#include <unistd.h>				  // Para getuid
#include <algorithm>			  // Para min
#include <cstdlib>				  // Para exit, EXIT_FAILURE, size_t, srand
#include <ctime>				  // Para time
#include <memory>				  // Para make_unique, unique_ptr
#include <stdexcept>			  // Para runtime_error
#include <string>				  // Para operator+, allocator, char_traits
#include <vector>				  // Para vector
#include "asset.h"				  // Para Asset, AssetType
#include "audio.h"				  // Para Audio
#include "credits.h"			  // Para Credits
#include "game.h"				  // Para Game, GAME_MODE_DEMO_OFF, GAME_MOD...
#include "hiscore_table.h"		  // Para HiScoreTable
#include "input.h"				  // Para Input, InputAction
#include "instructions.h"		  // Para Instructions
#include "intro.h"				  // Para Intro
#include "lang.h"				  // Para Code, loadFromFile
#include "logo.h"				  // Para Logo
#include "manage_hiscore_table.h" // Para ManageHiScoreTable
#include "notifier.h"			  // Para Notifier
#include "options.h"			  // Para GamepadOptions, Options, options
#include "param.h"				  // Para loadParamsFromFile
#include "resource.h"			  // Para Resource
#include "screen.h"				  // Para Screen
#include "section.h"			  // Para Name, Options, name, options
#include "title.h"				  // Para Title
#include "utils.h"				  // Para Overrides, overrides

#ifndef _WIN32
#include <pwd.h> // Para getpwuid, passwd
#endif

// Constructor
Director::Director(int argc, const char *argv[])
{
#ifdef RECORDING
	section::name = section::Name::GAME;
	section::options = section::Options::GAME_PLAY_1P;
#elif DEBUG
	section::name = section::Name::GAME;
	section::options = section::Options::GAME_PLAY_1P;
#else // NORMAL GAME
	section::name = section::Name::LOGO;
	section::options = section::Options::NONE;
#endif
	section::attract_mode = section::AttractMode::TITLE_TO_DEMO;

	// Establece el nivel de prioridad de la categoría de registro
	SDL_SetLogPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_INFO);
	SDL_SetLogPriority(SDL_LOG_CATEGORY_TEST, SDL_LOG_PRIORITY_ERROR);

	SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Game start");

	// Inicia la semilla aleatoria usando el tiempo actual en segundos
	std::srand(static_cast<unsigned int>(std::time(nullptr)));

	// Comprueba los parametros del programa
	checkProgramArguments(argc, argv);

	// Crea la carpeta del sistema donde guardar los datos persistentes
	createSystemFolder("jailgames");
	createSystemFolder("jailgames/coffee_crisis_arcade_edition");

	init();
}

Director::~Director()
{
	close();
	SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\nBye!");
}

// Inicializa todo
void Director::init()
{
	// Configuración inicial de recursos
	Asset::get().init(executable_path_);			 // Inicializa el sistema de gestión de archivos
	setFileList();									 // Crea el índice de archivos
	loadOptionsFile(Asset::get().get("config.txt")); // Carga el archivo de configuración
	loadParams();									 // Carga los parámetros del programa
	loadScoreFile();								 // Carga el archivo de puntuaciones

	// Inicialización de subsistemas
	lang::loadFromFile(getLangFile(static_cast<lang::Code>(options.game.language))); // Carga el archivo de idioma
	Screen::init();																	 // Inicializa la pantalla y el sistema de renderizado
	Audio::get();																	 // Activa el sistema de audio
	Resource::init();																 // Inicializa el sistema de gestión de recursos
	Input::init(Asset::get().get("gamecontrollerdb.txt"));							 // Carga configuración de controles
	bindInputs();																	 // Asigna los controles a la entrada del sistema

	// Inicialización del sistema de notificaciones
	Notifier::init(std::string(), Resource::get()->getText("8bithud"));

#ifdef DEBUG
	// Configuración adicional en modo depuración
	Screen::get()->initDebugInfo();
	Screen::get()->setDebugInfoEnabled(true);
#endif
}

// Cierra todo
void Director::close()
{
	saveOptionsFile(Asset::get().get("config.txt"));

	Notifier::destroy();
	Input::destroy();
	Resource::destroy();
	Screen::destroy();

	SDL_Quit();

#ifdef ARCADE
	shutdownSystem(section::options == section::Options::QUIT_WITH_CONTROLLER);
#endif
}

// Carga los parametros
void Director::loadParams()
{
	// Carga los parametros para configurar el juego
#ifdef ANBERNIC
	const std::string paramFilePath = asset->get("param_320x240.txt");
#else
	const std::string paramFilePath = overrides.param_file == "--320x240" ? Asset::get().get("param_320x240.txt") : Asset::get().get("param_320x256.txt");
#endif
	loadParamsFromFile(paramFilePath);
}

// Carga el fichero de puntuaciones
void Director::loadScoreFile()
{
	auto manager = std::make_unique<ManageHiScoreTable>(options.game.hi_score_table);
	if (overrides.clear_hi_score_table)
	{
		manager->clear();
	}
	else
	{
		manager->loadFromFile(Asset::get().get("score.bin"));
	}
}

// Asigna los botones y teclas al objeto Input
void Director::bindInputs()
{
	// Teclado - Movimiento del jugador
	Input::get()->bindKey(InputAction::UP, SDL_SCANCODE_UP);
	Input::get()->bindKey(InputAction::DOWN, SDL_SCANCODE_DOWN);
	Input::get()->bindKey(InputAction::LEFT, SDL_SCANCODE_LEFT);
	Input::get()->bindKey(InputAction::RIGHT, SDL_SCANCODE_RIGHT);

	Input::get()->bindKey(InputAction::FIRE_LEFT, SDL_SCANCODE_Q);
	Input::get()->bindKey(InputAction::FIRE_CENTER, SDL_SCANCODE_W);
	Input::get()->bindKey(InputAction::FIRE_RIGHT, SDL_SCANCODE_E);

	Input::get()->bindKey(InputAction::START, SDL_SCANCODE_RETURN);

	// Teclado - Control del programa
	Input::get()->bindKey(InputAction::SERVICE, SDL_SCANCODE_0);
	Input::get()->bindKey(InputAction::EXIT, SDL_SCANCODE_ESCAPE);
	Input::get()->bindKey(InputAction::PAUSE, SDL_SCANCODE_P);

	Input::get()->bindKey(InputAction::WINDOW_DEC_SIZE, SDL_SCANCODE_F1);
	Input::get()->bindKey(InputAction::WINDOW_INC_SIZE, SDL_SCANCODE_F2);
	Input::get()->bindKey(InputAction::WINDOW_FULLSCREEN, SDL_SCANCODE_F3);
	Input::get()->bindKey(InputAction::TOGGLE_VIDEO_SHADERS, SDL_SCANCODE_F4);
	Input::get()->bindKey(InputAction::TOGGLE_VIDEO_INTEGER_SCALE, SDL_SCANCODE_F5);
	Input::get()->bindKey(InputAction::TOGGLE_VIDEO_VSYNC, SDL_SCANCODE_F6);

	Input::get()->bindKey(InputAction::TOGGLE_AUDIO, SDL_SCANCODE_F7);
	Input::get()->bindKey(InputAction::TOGGLE_AUTO_FIRE, SDL_SCANCODE_F8);
	Input::get()->bindKey(InputAction::CHANGE_LANG, SDL_SCANCODE_F9);

	Input::get()->bindKey(InputAction::RESET, SDL_SCANCODE_F10);

	Input::get()->bindKey(InputAction::SHOW_INFO, SDL_SCANCODE_F12);

	// Asigna botones a inputs
	const int NUM_GAMEPADS = Input::get()->getNumControllers();
	for (int i = 0; i < NUM_GAMEPADS; ++i)
	{
		// Mando - Movimiento del jugador
		Input::get()->bindGameControllerButton(i, InputAction::UP, SDL_GAMEPAD_BUTTON_DPAD_UP);
		Input::get()->bindGameControllerButton(i, InputAction::DOWN, SDL_GAMEPAD_BUTTON_DPAD_DOWN);
		Input::get()->bindGameControllerButton(i, InputAction::LEFT, SDL_GAMEPAD_BUTTON_DPAD_LEFT);
		Input::get()->bindGameControllerButton(i, InputAction::RIGHT, SDL_GAMEPAD_BUTTON_DPAD_RIGHT);

		Input::get()->bindGameControllerButton(i, InputAction::FIRE_LEFT, SDL_GAMEPAD_BUTTON_WEST);
		Input::get()->bindGameControllerButton(i, InputAction::FIRE_CENTER, SDL_GAMEPAD_BUTTON_NORTH);
		Input::get()->bindGameControllerButton(i, InputAction::FIRE_RIGHT, SDL_GAMEPAD_BUTTON_EAST);

		Input::get()->bindGameControllerButton(i, InputAction::START, SDL_GAMEPAD_BUTTON_START);

		// Mando - Control del programa
		Input::get()->bindGameControllerButton(i, InputAction::SERVICE, SDL_GAMEPAD_BUTTON_BACK);
	}

	// Mapea las asignaciones a los botones desde el archivo de configuración, si se da el caso
	const size_t max_controllers = std::min(2, NUM_GAMEPADS);
	for (size_t i = 0; i < max_controllers; ++i)
	{
		for (auto &controller : options.controllers)
		{
			if (Input::get()->getControllerName(i) == controller.name)
			{
				for (size_t j = 0; j < controller.inputs.size(); ++j)
				{
					Input::get()->bindGameControllerButton(i, controller.inputs.at(j), controller.buttons.at(j));
				}
			}
		}
	}

	// Asigna botones a inputs desde otros inputs
	for (int i = 0; i < NUM_GAMEPADS; ++i)
	{
		Input::get()->bindGameControllerButton(i, InputAction::EXIT, InputAction::START);
		Input::get()->bindGameControllerButton(i, InputAction::RESET, InputAction::FIRE_CENTER);
		Input::get()->bindGameControllerButton(i, InputAction::PAUSE, InputAction::FIRE_RIGHT);
		Input::get()->bindGameControllerButton(i, InputAction::TOGGLE_VIDEO_SHADERS, InputAction::FIRE_LEFT);
		Input::get()->bindGameControllerButton(i, InputAction::TOGGLE_AUDIO, InputAction::LEFT);
		Input::get()->bindGameControllerButton(i, InputAction::SHOW_INFO, InputAction::RIGHT);
		Input::get()->bindGameControllerButton(i, InputAction::CONFIG, InputAction::DOWN);
		Input::get()->bindGameControllerButton(i, InputAction::SWAP_CONTROLLERS, InputAction::UP);
	}

	// Guarda las asignaciones de botones en las opciones de los dos primeros mandos
	for (size_t i = 0; i < max_controllers; ++i)
	{
		// Variables asociadas al mando
		options.controllers.at(i).index = i;
		options.controllers.at(i).name = Input::get()->getControllerName(i);
		options.controllers.at(i).plugged = true;
		// Asignaciones de botones
		for (size_t j = 0; j < options.controllers.at(i).inputs.size(); ++j)
		{
			options.controllers.at(i).buttons.at(j) = Input::get()->getControllerBinding(i, options.controllers.at(i).inputs.at(j));
		}
	}

	// Asegura que algún jugador tenga el teclado asignado
	if (getPlayerWhoUsesKeyboard() == 0)
	{
		setKeyboardToPlayer(1);
	}
}

// Crea el indice de ficheros
void Director::setFileList()
{
#ifdef MACOS_BUNDLE
	const std::string prefix = "/../Resources";
#else
	const std::string prefix;
#endif

	// Ficheros de configuración
	Asset::get().add(system_folder_ + "/config.txt", AssetType::DATA, false, true);
	Asset::get().add(system_folder_ + "/score.bin", AssetType::DATA, false, true);
	Asset::get().add(prefix + "/data/config/param_320x240.txt", AssetType::DATA);
	Asset::get().add(prefix + "/data/config/param_320x256.txt", AssetType::DATA);
	Asset::get().add(prefix + "/data/config/demo1.bin", AssetType::DATA);
	Asset::get().add(prefix + "/data/config/demo2.bin", AssetType::DATA);
	Asset::get().add(prefix + "/data/config/gamecontrollerdb.txt", AssetType::DATA);

	// Musicas
	Asset::get().add(prefix + "/data/music/intro.ogg", AssetType::MUSIC);
	Asset::get().add(prefix + "/data/music/playing.ogg", AssetType::MUSIC);
	Asset::get().add(prefix + "/data/music/title.ogg", AssetType::MUSIC);
	Asset::get().add(prefix + "/data/music/credits.ogg", AssetType::MUSIC);

	// Sonidos
	Asset::get().add(prefix + "/data/sound/balloon.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/bubble1.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/bubble2.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/bubble3.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/bubble4.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/bullet.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/clock.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/coffee_out.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/continue_clock.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/game_start.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/hi_score_achieved.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/item_drop.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/item_pickup.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/logo.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/notify.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/player_collision.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/power_ball_explosion.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/stage_change.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/tabe.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/title.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/voice_coffee.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/voice_get_ready.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/voice_no.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/voice_power_up.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/walk.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/debian_drop.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/debian_pickup.wav", AssetType::SOUND);
	Asset::get().add(prefix + "/data/sound/tabe_hit.wav", AssetType::SOUND);

	// Shaders
	Asset::get().add(prefix + "/data/shaders/crtpi_256.glsl", AssetType::DATA);
	Asset::get().add(prefix + "/data/shaders/crtpi_240.glsl", AssetType::DATA);

	// Texturas

	{ // Controllers
		Asset::get().add(prefix + "/data/gfx/controllers/controllers.png", AssetType::BITMAP);
	}

	{ // Balloons
		Asset::get().add(prefix + "/data/gfx/balloon/balloon1.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/balloon/balloon1.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/balloon/balloon2.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/balloon/balloon2.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/balloon/balloon3.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/balloon/balloon3.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/balloon/balloon4.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/balloon/balloon4.ani", AssetType::ANIMATION);
	}

	{ // Explosions
		Asset::get().add(prefix + "/data/gfx/balloon/explosion1.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/balloon/explosion1.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/balloon/explosion2.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/balloon/explosion2.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/balloon/explosion3.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/balloon/explosion3.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/balloon/explosion4.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/balloon/explosion4.ani", AssetType::ANIMATION);
	}

	{ // Power Ball
		Asset::get().add(prefix + "/data/gfx/balloon/powerball.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/balloon/powerball.ani", AssetType::ANIMATION);
	}

	{ // Bala
		Asset::get().add(prefix + "/data/gfx/bullet/bullet.png", AssetType::BITMAP);
	}

	{ // Tabe
		Asset::get().add(prefix + "/data/gfx/tabe/tabe.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/tabe/tabe.ani", AssetType::ANIMATION);
	}

	{ // Juego
		Asset::get().add(prefix + "/data/gfx/game/game_buildings.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/game/game_clouds1.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/game/game_clouds2.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/game/game_grass.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/game/game_power_meter.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/game/game_sky_colors.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/game/game_sun.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/game/game_moon.png", AssetType::BITMAP);
	}

	{ // Intro
		Asset::get().add(prefix + "/data/gfx/intro/intro1.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/intro/intro2.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/intro/intro3.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/intro/intro4.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/intro/intro5.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/intro/intro6.png", AssetType::BITMAP);
	}

	{ // Logo
		Asset::get().add(prefix + "/data/gfx/logo/logo_jailgames.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/logo/logo_jailgames_mini.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/logo/logo_since_1998.png", AssetType::BITMAP);
	}

	{ // Items
		Asset::get().add(prefix + "/data/gfx/item/item_points1_disk.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/item/item_points1_disk.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/item/item_points2_gavina.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/item/item_points2_gavina.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/item/item_points3_pacmar.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/item/item_points3_pacmar.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/item/item_clock.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/item/item_clock.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/item/item_coffee.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/item/item_coffee.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/item/item_debian.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/item/item_debian.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/item/item_coffee_machine.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/item/item_coffee_machine.ani", AssetType::ANIMATION);
	}

	{ // Titulo
		Asset::get().add(prefix + "/data/gfx/title/title_bg_tile.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/title/title_coffee.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/title/title_crisis.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/title/title_arcade_edition.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/title/title_dust.png", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/title/title_dust.ani", AssetType::ANIMATION);
	}

	{ // Jugador 1
		Asset::get().add(prefix + "/data/gfx/player/player1.gif", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/player/player1_1_coffee_palette.gif", AssetType::PALETTE);
		Asset::get().add(prefix + "/data/gfx/player/player1_2_coffee_palette.gif", AssetType::PALETTE);
		Asset::get().add(prefix + "/data/gfx/player/player1_invencible_palette.gif", AssetType::PALETTE);
		Asset::get().add(prefix + "/data/gfx/player/player1_power.png", AssetType::BITMAP);
	}

	{ // Jugador 2
		Asset::get().add(prefix + "/data/gfx/player/player2.gif", AssetType::BITMAP);
		Asset::get().add(prefix + "/data/gfx/player/player2_1_coffee_palette.gif", AssetType::PALETTE);
		Asset::get().add(prefix + "/data/gfx/player/player2_2_coffee_palette.gif", AssetType::PALETTE);
		Asset::get().add(prefix + "/data/gfx/player/player2_invencible_palette.gif", AssetType::PALETTE);
		Asset::get().add(prefix + "/data/gfx/player/player2_power.png", AssetType::BITMAP);
	}

	{ // Animaciones del jugador
		Asset::get().add(prefix + "/data/gfx/player/player.ani", AssetType::ANIMATION);
		Asset::get().add(prefix + "/data/gfx/player/player_power.ani", AssetType::ANIMATION);
	}

	// Fuentes de texto
	Asset::get().add(prefix + "/data/font/8bithud.png", AssetType::BITMAP);
	Asset::get().add(prefix + "/data/font/8bithud.txt", AssetType::FONT);
	Asset::get().add(prefix + "/data/font/smb2.gif", AssetType::BITMAP);
	Asset::get().add(prefix + "/data/font/smb2_palette1.pal", AssetType::PALETTE);
	Asset::get().add(prefix + "/data/font/smb2.txt", AssetType::FONT);
	Asset::get().add(prefix + "/data/font/04b_25.png", AssetType::BITMAP);
	Asset::get().add(prefix + "/data/font/04b_25.txt", AssetType::FONT);
	Asset::get().add(prefix + "/data/font/04b_25_2x.png", AssetType::BITMAP);
	Asset::get().add(prefix + "/data/font/04b_25_2x.txt", AssetType::FONT);
	Asset::get().add(prefix + "/data/font/04b_25_metal.png", AssetType::BITMAP);
	Asset::get().add(prefix + "/data/font/04b_25_grey.png", AssetType::BITMAP);

	// Textos
	Asset::get().add(prefix + "/data/lang/es_ES.txt", AssetType::LANG);
	Asset::get().add(prefix + "/data/lang/en_UK.txt", AssetType::LANG);
	Asset::get().add(prefix + "/data/lang/ba_BA.txt", AssetType::LANG);

	// Si falta algun fichero, sale del programa
	if (!Asset::get().check())
	{
		throw std::runtime_error("Falta algun fichero");
	}
}

// Comprueba los parametros del programa
void Director::checkProgramArguments(int argc, const char *argv[])
{
	// Establece la ruta del programa
	executable_path_ = argv[0];

	// Comprueba el resto de parámetros
	for (int i = 1; i < argc; ++i)
	{
		std::string arg = argv[i];

		if (arg == "--320x240")
		{
			overrides.param_file = arg;
		}
		else if (arg == "--clear_score")
		{
			overrides.clear_hi_score_table = true;
		}
	}
}

// Crea la carpeta del sistema donde guardar datos
void Director::createSystemFolder(const std::string &folder)
{
#ifdef _WIN32
	system_folder_ = std::string(getenv("APPDATA")) + "/" + folder;
#elif __APPLE__
	struct passwd *pw = getpwuid(getuid());
	const char *homedir = pw->pw_dir;
	system_folder_ = std::string(homedir) + "/Library/Application Support" + "/" + folder;
#elif __linux__
	struct passwd *pw = getpwuid(getuid());
	const char *homedir = pw->pw_dir;
	system_folder_ = std::string(homedir) + "/.config/" + folder;

	{
		// Intenta crear ".config", per si no existeix
		std::string config_base_folder = std::string(homedir) + "/.config";
		int ret = mkdir(config_base_folder.c_str(), S_IRWXU);
		if (ret == -1 && errno != EEXIST)
		{
			printf("ERROR CREATING CONFIG BASE FOLDER.");
			exit(EXIT_FAILURE);
		}
	}
#endif

	struct stat st = {0};
	if (stat(system_folder_.c_str(), &st) == -1)
	{
		errno = 0;
#ifdef _WIN32
		int ret = mkdir(system_folder_.c_str());
#else
		int ret = mkdir(system_folder_.c_str(), S_IRWXU);
#endif

		if (ret == -1)
		{
			switch (errno)
			{
			case EACCES:
				printf("the parent directory does not allow write");
				exit(EXIT_FAILURE);

			case EEXIST:
				printf("pathname already exists");
				exit(EXIT_FAILURE);

			case ENAMETOOLONG:
				printf("pathname is too long");
				exit(EXIT_FAILURE);

			default:
				perror("mkdir");
				exit(EXIT_FAILURE);
			}
		}
	}
}

// Ejecuta la sección con el logo
void Director::runLogo()
{
	auto logo = std::make_unique<Logo>();
	logo->run();
}

// Ejecuta la sección con la secuencia de introducción
void Director::runIntro()
{
	auto intro = std::make_unique<Intro>();
	intro->run();
}

// Ejecuta la sección con el título del juego
void Director::runTitle()
{
	auto title = std::make_unique<Title>();
	title->run();
}

// Ejecuta la sección donde se juega al juego
void Director::runGame()
{
	const auto player_id = section::options == section::Options::GAME_PLAY_1P ? 1 : 2;
#ifdef DEBUG
	constexpr auto current_stage = 0;
#else
	constexpr auto current_stage = 0;
#endif
	auto game = std::make_unique<Game>(player_id, current_stage, GAME_MODE_DEMO_OFF);
	game->run();
}

// Ejecuta la sección donde se muestran las instrucciones
void Director::runInstructions()
{
	auto instructions = std::make_unique<Instructions>();
	instructions->run();
}

// Ejecuta la sección donde se muestran los creditos del programa
void Director::runCredits()
{
	auto credits = std::make_unique<Credits>();
	credits->run();
}

// Ejecuta la sección donde se muestra la tabla de puntuaciones
void Director::runHiScoreTable()
{
	auto hi_score_table = std::make_unique<HiScoreTable>();
	hi_score_table->run();
}

// Ejecuta el juego en modo demo
void Director::runDemoGame()
{
	const auto player_id = (rand() % 2) + 1;
	constexpr auto current_stage = 0;
	auto game = std::make_unique<Game>(player_id, current_stage, GAME_MODE_DEMO_ON);
	game->run();
}

// Ejecuta la sección init
void Director::runInit()
{
	Audio::get().stopMusic();
	Audio::get().stopAllSounds();
	if (section::options == section::Options::RELOAD || true)
	{
		Resource::get()->reload();
	}
	section::name = section::Name::LOGO;
}

int Director::run()
{
	// Bucle principal
	while (section::name != section::Name::QUIT)
	{
		switch (section::name)
		{
		case section::Name::INIT:
			runInit();
			break;
		case section::Name::LOGO:
			runLogo();
			break;
		case section::Name::INTRO:
			runIntro();
			break;
		case section::Name::TITLE:
			runTitle();
			break;
		case section::Name::GAME:
			runGame();
			break;
		case section::Name::HI_SCORE_TABLE:
			runHiScoreTable();
			break;
		case section::Name::GAME_DEMO:
			runDemoGame();
			break;
		case section::Name::INSTRUCTIONS:
			runInstructions();
			break;
		case section::Name::CREDITS:
			runCredits();
			break;
		default:
			break;
		}
	}

	std::string return_code;
	switch (section::options)
	{
	case section::Options::QUIT_WITH_KEYBOARD:
		return_code = "with keyboard";
		break;
	case section::Options::QUIT_WITH_CONTROLLER:
		return_code = "with controller";
		break;
	default:
		return_code = "from event";
		break;
	}

	SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "\nGame end %s", return_code.c_str());

	return (section::options == section::Options::QUIT_WITH_CONTROLLER) ? 1 : 0;
}

// Obtiene una fichero a partir de un lang::Code
std::string Director::getLangFile(lang::Code code)
{
	switch (code)
	{
	case lang::Code::ba_BA:
		return Asset::get().get("ba_BA.txt");
		break;
	case lang::Code::es_ES:
		return Asset::get().get("es_ES.txt");
		break;
	case lang::Code::en_UK:
		return Asset::get().get("en_UK.txt");
		break;
	default:
		break;
	}

	return Asset::get().get("en_UK.txt");
}

#ifdef ARCADE
// Apaga el sistema
void Director::shutdownSystem(bool should_shutdown)
{
	if (should_shutdown)
	{
#ifdef _WIN32
		// Apaga el sistema en Windows
		system("shutdown /s /t 5");
#elif __APPLE__
		// Apaga el sistema en macOS
		system("sudo shutdown -h +0.1");
#elif __linux__
		// Apaga el sistema en Linux
		system("sleep 5; shutdown -h now");
#else
// Sistema operativo no compatible
#error "Sistema operativo no soportado"
#endif
	}
}
#endif // ARCADE
