#include "director.h"
#include <SDL2/SDL.h>				 // Para SDL_Init, SDL_Quit, SDL_INIT_EV...
#include <SDL2/SDL_audio.h>			 // Para AUDIO_S16
#include <SDL2/SDL_blendmode.h>		 // Para SDL_BLENDMODE_BLEND
#include <SDL2/SDL_error.h>			 // Para SDL_GetError
#include <SDL2/SDL_gamecontroller.h> // Para SDL_CONTROLLER_BUTTON_B, SDL_CO...
#include <SDL2/SDL_hints.h>			 // Para SDL_SetHint, SDL_HINT_RENDER_DR...
#include <SDL2/SDL_scancode.h>		 // Para SDL_SCANCODE_0, SDL_SCANCODE_DOWN
#include <SDL2/SDL_stdinc.h>		 // Para SDL_bool, Uint32
#include <SDL2/SDL_timer.h>			 // Para SDL_GetTicks
#include <errno.h>					 // Para errno, EEXIST, EACCES, ENAMETOO...
#include <stdio.h>					 // Para printf, perror
#include <string.h>					 // Para strcmp
#include <sys/stat.h>				 // Para mkdir, stat, S_IRWXU
#include <unistd.h>					 // Para getuid
#include <cstdlib>					 // Para exit, EXIT_FAILURE, rand, srand
#include <iostream>					 // Para basic_ostream, operator<<, cout
#include <memory>					 // Para make_unique, unique_ptr, make_s...
#include <stdexcept>				 // Para runtime_error
#include <string>					 // Para operator+, allocator, char_traits
#include <vector>					 // Para vector
#include "asset.h"					 // Para Asset, AssetType
#include "dbgtxt.h"					 // Para dbg_init
#include "game.h"					 // Para Game, GAME_MODE_DEMO_OFF, GAME_...
#include "global_inputs.h"			 // Para init
#include "hiscore_table.h"			 // Para HiScoreTable
#include "input.h"					 // Para Input, InputType
#include "instructions.h"			 // Para Instructions
#include "intro.h"					 // Para Intro
#include "jail_audio.h"				 // Para JA_EnableMusic, JA_EnableSound
#include "lang.h"					 // Para Code, loadFromFile
#include "logo.h"					 // Para Logo
#include "manage_hiscore_table.h"	 // Para ManageHiScoreTable
#include "notifier.h"				 // Para Notifier
#include "on_screen_help.h"			 // Para OnScreenHelp
#include "options.h"				 // Para Options, options, OptionsContro...
#include "param.h"					 // Para Param, ParamGame, param, loadPa...
#include "resource.h"				 // Para Resource
#include "screen.h"					 // Para Screen
#include "section.h"				 // Para Name, Options, name, options
#include "text.h"					 // Para Text
#include "title.h"					 // Para Title
#include "utils.h"					 // Para Overrides, overrides
#include <chrono>

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

// Inicia la semilla aleatoria
void initRand()
{
	unsigned int seed = static_cast<unsigned int>(std::chrono::system_clock::now().time_since_epoch().count());
	std::srand(seed);
}

// 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::LOGO;
#else
	section::name = section::Name::LOGO;
	section::attract_mode = section::AttractMode::TITLE_TO_DEMO;
#endif

#ifndef VERBOSE
	// Deshabilita todos los std::cout
	std::ostream null_stream(nullptr);
	orig_buf = std::cout.rdbuf(null_stream.rdbuf());
#endif

	std::cout << "Game start" << std::endl;

	initRand();

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

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

	// Crea el objeto que controla los ficheros de recursos
	Asset::init(executable_path_);

	// Crea el indice de ficheros
	setFileList();

	// Carga el fichero de configuración
	loadOptionsFile(Asset::get()->get("config.txt"));

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

	// Inicializa todo
	initSDL();
	initJailAudio();
	dbg_init(renderer_);
	lang::loadFromFile(getLangFile(static_cast<lang::Code>(options.game.language)));
	Screen::init(window_, renderer_);
	Resource::init();
	Input::init(Asset::get()->get("gamecontrollerdb.txt"));
	bindInputs();
	auto notifier_text = std::make_shared<Text>(Resource::get()->getTexture("8bithud.png"), Resource::get()->getTextFile("8bithud.txt"));
	Notifier::init(std::string(), notifier_text, Asset::get()->get("notify.wav"));
	OnScreenHelp::init();
	globalInputs::init();
}

Director::~Director()
{
	saveOptionsFile(Asset::get()->get("config.txt"));

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

	SDL_DestroyRenderer(renderer_);
	SDL_DestroyWindow(window_);

	SDL_Quit();

	std::cout << "\nBye!" << std::endl;
}

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

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

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

	// Teclado - Control del programa
	Input::get()->bindKey(InputType::SERVICE, SDL_SCANCODE_0);
	Input::get()->bindKey(InputType::EXIT, SDL_SCANCODE_ESCAPE);
	Input::get()->bindKey(InputType::PAUSE, SDL_SCANCODE_P);
	Input::get()->bindKey(InputType::WINDOW_DEC_SIZE, SDL_SCANCODE_F1);
	Input::get()->bindKey(InputType::WINDOW_INC_SIZE, SDL_SCANCODE_F2);
	Input::get()->bindKey(InputType::WINDOW_FULLSCREEN, SDL_SCANCODE_F3);
	Input::get()->bindKey(InputType::VIDEO_SHADERS, SDL_SCANCODE_F4);
	Input::get()->bindKey(InputType::MUTE, SDL_SCANCODE_F5);
	Input::get()->bindKey(InputType::SHOWINFO, SDL_SCANCODE_F6);
	Input::get()->bindKey(InputType::RESET, SDL_SCANCODE_F10);

	// 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, InputType::UP, SDL_CONTROLLER_BUTTON_DPAD_UP);
		Input::get()->bindGameControllerButton(i, InputType::DOWN, SDL_CONTROLLER_BUTTON_DPAD_DOWN);
		Input::get()->bindGameControllerButton(i, InputType::LEFT, SDL_CONTROLLER_BUTTON_DPAD_LEFT);
		Input::get()->bindGameControllerButton(i, InputType::RIGHT, SDL_CONTROLLER_BUTTON_DPAD_RIGHT);

		Input::get()->bindGameControllerButton(i, InputType::FIRE_LEFT, SDL_CONTROLLER_BUTTON_X);
		Input::get()->bindGameControllerButton(i, InputType::FIRE_CENTER, SDL_CONTROLLER_BUTTON_Y);
		Input::get()->bindGameControllerButton(i, InputType::FIRE_RIGHT, SDL_CONTROLLER_BUTTON_B);

		Input::get()->bindGameControllerButton(i, InputType::START, SDL_CONTROLLER_BUTTON_START);

		// Mando - Control del programa
		Input::get()->bindGameControllerButton(i, InputType::SERVICE, SDL_CONTROLLER_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, InputType::EXIT, InputType::START);
		Input::get()->bindGameControllerButton(i, InputType::RESET, InputType::FIRE_CENTER);
		Input::get()->bindGameControllerButton(i, InputType::PAUSE, InputType::FIRE_RIGHT);
		Input::get()->bindGameControllerButton(i, InputType::VIDEO_SHADERS, InputType::FIRE_LEFT);
		Input::get()->bindGameControllerButton(i, InputType::MUTE, InputType::LEFT);
		Input::get()->bindGameControllerButton(i, InputType::SHOWINFO, InputType::RIGHT);
		Input::get()->bindGameControllerButton(i, InputType::CONFIG, InputType::DOWN);
		Input::get()->bindGameControllerButton(i, InputType::SWAP_CONTROLLERS, InputType::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);
	}
}

// Inicializa JailAudio
void Director::initJailAudio()
{
	JA_Init(48000, AUDIO_S16, 2);
	if (options.audio.enabled)
	{
		JA_SetMusicVolume(to_JA_volume(options.audio.music.volume));
		JA_SetSoundVolume(to_JA_volume(options.audio.sound.volume));
	}
	else
	{
		JA_SetMusicVolume(0);
		JA_SetSoundVolume(0);
	}
}

// Arranca SDL y crea la ventana
bool Director::initSDL()
{
	// Indicador de éxito
	auto success = true;

	// Inicializa SDL
	if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
	{
		std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << std::endl;
		success = false;
	}
	else
	{
		/*
		// Muestra información de la pantalla
		std::cout << "\nDisplay modes list:" << std::endl;
		for (int i = 0; i < SDL_GetNumDisplayModes(0); ++i)
		{
			SDL_DisplayMode DM;
			SDL_GetDisplayMode(0,i,&DM);
			std::cout << "  - " << DM.w << "x" << DM.h << " @ " << DM.refresh_rate << "Hz" << std::endl;
		}
		*/

		SDL_DisplayMode DM;
		SDL_GetCurrentDisplayMode(0, &DM);
		std::cout << "\nCurrent display mode: " << DM.w << "x" << DM.h << " @ " << DM.refresh_rate << "Hz" << std::endl;
		std::cout << "Window resolution   : " << param.game.width << "x" << param.game.height << " x" << options.video.window.size << std::endl;

		// Establece el filtro de la textura
		if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, std::to_string(static_cast<int>(options.video.filter)).c_str()))
		{
			std::cout << "Warning: texture filtering not enabled!\n";
		}

#ifndef NO_SHADERS
		if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"))
		{
			std::cout << "Warning: opengl not enabled!\n";
		}
#endif

		// Crea la ventana
		window_ = SDL_CreateWindow(WINDOW_CAPTION, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, param.game.width * options.video.window.size, param.game.height * options.video.window.size, SDL_WINDOW_HIDDEN);
		if (!window_)
		{
			std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
			success = false;
		}
		else
		{
			// Crea un renderizador para la ventana. El vsync se activa en funcion de las opciones
			Uint32 flags = 0;
			if (options.video.v_sync)
			{
				flags = SDL_RENDERER_PRESENTVSYNC;
			}

#ifndef NO_SHADERS
			// La aceleración se activa según el define
			flags = flags | SDL_RENDERER_ACCELERATED | SDL_RENDERER_TARGETTEXTURE;
#endif

			renderer_ = SDL_CreateRenderer(window_, -1, flags);

			if (!renderer_)
			{
				std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
				success = false;
			}
			else
			{
				SDL_SetRenderDrawColor(renderer_, 0x00, 0x00, 0x00, 0xFF);
				SDL_RenderSetLogicalSize(renderer_, param.game.width, param.game.height);
				SDL_RenderSetIntegerScale(renderer_, static_cast<SDL_bool>(options.video.integer_scale));
				SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
			}
		}
	}

	std::cout << std::endl;
	return success;
}

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

	// 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/coffeeout.wav", AssetType::SOUND);
	Asset::get()->add(prefix + "/data/sound/hiscore.wav", AssetType::SOUND);
	Asset::get()->add(prefix + "/data/sound/itemdrop.wav", AssetType::SOUND);
	Asset::get()->add(prefix + "/data/sound/itempickup.wav", AssetType::SOUND);
	Asset::get()->add(prefix + "/data/sound/player_collision.wav", AssetType::SOUND);
	Asset::get()->add(prefix + "/data/sound/stage_change.wav", AssetType::SOUND);
	Asset::get()->add(prefix + "/data/sound/title.wav", AssetType::SOUND);
	Asset::get()->add(prefix + "/data/sound/clock.wav", AssetType::SOUND);
	Asset::get()->add(prefix + "/data/sound/powerball.wav", AssetType::SOUND);
	Asset::get()->add(prefix + "/data/sound/notify.wav", AssetType::SOUND);

	// Shaders
	Asset::get()->add(prefix + "/data/shaders/crtpi.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);
	}

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

	{ // Intro
		Asset::get()->add(prefix + "/data/gfx/intro/intro.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_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_one_coffee_palette.pal", AssetType::PALETTE);
		Asset::get()->add(prefix + "/data/gfx/player/player1_two_coffee_palette.pal", AssetType::PALETTE);
		Asset::get()->add(prefix + "/data/gfx/player/player1_all_white_palette.pal", 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_one_coffee_palette.pal", AssetType::PALETTE);
		Asset::get()->add(prefix + "/data/gfx/player/player2_two_coffee_palette.pal", AssetType::PALETTE);
		Asset::get()->add(prefix + "/data/gfx/player/player2_all_white_palette.pal", 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/nokia.png", AssetType::BITMAP);
	Asset::get()->add(prefix + "/data/font/nokia.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);

	// 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[])
{
	const std::vector<std::string> argument_list = {"--320x240", "--clear_score"};

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

int Director::run()
{
	// Bucle principal
	while (section::name != section::Name::QUIT)
	{
		switch (section::name)
		{
		case section::Name::INIT:
			section::name = section::Name::LOGO;
			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;
		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;
	}

	std::cout << "\nGame end " << return_code << std::endl;

#ifndef VERBOSE
	// Habilita de nuevo los std::cout
	std::cout.rdbuf(orig_buf);
#endif
#ifdef ARCADE
	// Comprueba si ha de apagar el sistema
	if (section::options == section::Options::QUIT_WITH_CONTROLLER)
		shutdownSystem();
#endif

	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()
{
#ifdef _WIN32
	// Apaga el sistema en Windows
	system("shutdown /s /t 0");
#elif __APPLE__
	// Apaga el sistema en macOS
	system("sudo shutdown -h now");
#elif __linux__
	// Apaga el sistema en Linux
	system("shutdown -h now");
#else
// Sistema operativo no compatible
#error "Sistema operativo no soportado"
#endif
}
#endif // ARCADE
