#include "director.h"
#include <SDL2/SDL.h>				 // for SDL_Init, SDL_Quit, SDL_INIT_EV...
#include <SDL2/SDL_audio.h>			 // for AUDIO_S16
#include <SDL2/SDL_blendmode.h>		 // for SDL_BLENDMODE_BLEND
#include <SDL2/SDL_error.h>			 // for SDL_GetError
#include <SDL2/SDL_gamecontroller.h> // for SDL_CONTROLLER_BUTTON_B, SDL_CO...
#include <SDL2/SDL_hints.h>			 // for SDL_SetHint, SDL_HINT_RENDER_DR...
#include <SDL2/SDL_scancode.h>		 // for SDL_SCANCODE_DOWN, SDL_SCANCODE_E
#include <SDL2/SDL_stdinc.h>		 // for Uint32
#include <SDL2/SDL_timer.h>			 // for SDL_GetTicks
#include <errno.h>					 // for errno, EACCES, EEXIST, ENAMETOO...
#include <stdio.h>					 // for printf, perror, size_t
#include <string.h>					 // for strcmp
#include <sys/stat.h>				 // for stat, mkdir, S_IRWXU
#include <unistd.h>					 // for getuid
#include <cstdlib>					 // for exit, EXIT_FAILURE, rand, srand
#include <iostream>					 // for basic_ostream, operator<<, cout
#include <string>					 // for basic_string, operator+, allocator
#include "asset.h"					 // for Asset, assetType
#include "dbgtxt.h"					 // for dbg_init
#include "game.h"					 // for Game, GAME_MODE_DEMO_OFF, GAME_...
#include "global_inputs.h"
#include "hiscore_table.h"		  // for HiScoreTable
#include "input.h"				  // for inputs_e, Input
#include "instructions.h"		  // for Instructions
#include "intro.h"				  // for Intro
#include "jail_audio.h"			  // for JA_DeleteMusic, JA_DeleteSound
#include "logo.h"				  // for Logo
#include "manage_hiscore_table.h" // for ManageHiScoreTable
#include "on_screen_help.h"		  // for OnScreenHelp
#include "options.h"			  // for options, loadOptionsFile, saveO...
#include "param.h"				  // for param, loadParamsFromFile
#include "screen.h"				  // for Screen
#include "section.h"			  // for name_e, name, options, options_e
#include "title.h"				  // for Title
#include "utils.h"				  // for MusicFile, SoundFile, opt...
#include <memory>

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

// Constructor
Director::Director(int argc, 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;
#endif

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

	// Si falta algún fichero no inicia el programa
	if (!setFileList())
	{
		exit(EXIT_FAILURE);
	}

	// 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 = paramFileArgument == "--320x240" ? Asset::get()->get("param_320x240.txt") : Asset::get()->get("param_320x256.txt");
#endif
	loadParams(paramFilePath);

	// Carga el fichero de puntuaciones
	auto manager = std::make_unique<ManageHiScoreTable>(&options.game.hi_score_table);
	manager->loadFromFile(Asset::get()->get("score.bin"));

	// Inicializa SDL
	initSDL();

	// Inicializa JailAudio
	initJailAudio();

	// Inicializa el texto de debug
	dbg_init(renderer);

	// Crea los objetos
	lang::loadFromFile(getLangFile((lang::lang_e)options.game.language));

	Input::init(Asset::get()->get("gamecontrollerdb.txt"));
	initInput();

	Screen::init(window, renderer);

	OnScreenHelp::init();

	// Carga los sonidos del juego
	loadSounds();

	// Carga las musicas del juego
	loadMusics();

	globalInputs::init();
}

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

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

	sounds.clear();
	musics.clear();

	SDL_DestroyRenderer(renderer);
	SDL_DestroyWindow(window);

	SDL_Quit();
}

/// Inicializa el objeto input
void Director::initInput()
{
	// Establece si ha de mostrar mensajes
#ifdef VERBOSE
	Input::get()->setVerbose(true);
#else
	Input::get()->setVerbose(false);
#endif
	// Busca si hay mandos conectados
	Input::get()->discoverGameControllers();

	// Teclado - Movimiento del jugador
	Input::get()->bindKey(input_up, SDL_SCANCODE_UP);
	Input::get()->bindKey(input_down, SDL_SCANCODE_DOWN);
	Input::get()->bindKey(input_left, SDL_SCANCODE_LEFT);
	Input::get()->bindKey(input_right, SDL_SCANCODE_RIGHT);

	Input::get()->bindKey(input_fire_left, SDL_SCANCODE_Q);
	Input::get()->bindKey(input_fire_center, SDL_SCANCODE_W);
	Input::get()->bindKey(input_fire_right, SDL_SCANCODE_E);

	Input::get()->bindKey(input_start, SDL_SCANCODE_RETURN);

	// Teclado - Control del programa
	Input::get()->bindKey(input_service, SDL_SCANCODE_0);
	Input::get()->bindKey(input_exit, SDL_SCANCODE_ESCAPE);
	Input::get()->bindKey(input_pause, SDL_SCANCODE_P);
	Input::get()->bindKey(input_window_dec_size, SDL_SCANCODE_F1);
	Input::get()->bindKey(input_window_inc_size, SDL_SCANCODE_F2);
	Input::get()->bindKey(input_window_fullscreen, SDL_SCANCODE_F3);
	Input::get()->bindKey(input_video_shaders, SDL_SCANCODE_F4);
	Input::get()->bindKey(input_mute, SDL_SCANCODE_F5);
	Input::get()->bindKey(input_showinfo, SDL_SCANCODE_F6);
	Input::get()->bindKey(input_reset, SDL_SCANCODE_F10);

	// Asigna botones a inputs
	const int numGamePads = Input::get()->getNumControllers();
	for (int i = 0; i < numGamePads; ++i)
	{
		// Mando - Movimiento del jugador
		Input::get()->bindGameControllerButton(i, input_up, SDL_CONTROLLER_BUTTON_DPAD_UP);
		Input::get()->bindGameControllerButton(i, input_down, SDL_CONTROLLER_BUTTON_DPAD_DOWN);
		Input::get()->bindGameControllerButton(i, input_left, SDL_CONTROLLER_BUTTON_DPAD_LEFT);
		Input::get()->bindGameControllerButton(i, input_right, SDL_CONTROLLER_BUTTON_DPAD_RIGHT);

		Input::get()->bindGameControllerButton(i, input_fire_left, SDL_CONTROLLER_BUTTON_X);
		Input::get()->bindGameControllerButton(i, input_fire_center, SDL_CONTROLLER_BUTTON_Y);
		Input::get()->bindGameControllerButton(i, input_fire_right, SDL_CONTROLLER_BUTTON_B);

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

		// Mando - Control del programa
		Input::get()->bindGameControllerButton(i, input_service, SDL_CONTROLLER_BUTTON_BACK);
		Input::get()->bindGameControllerButton(i, input_exit, input_start);
		Input::get()->bindGameControllerButton(i, input_pause, input_fire_right);
		Input::get()->bindGameControllerButton(i, input_video_shaders, input_fire_left);
		Input::get()->bindGameControllerButton(i, input_mute, input_left);
		Input::get()->bindGameControllerButton(i, input_showinfo, input_right);
		Input::get()->bindGameControllerButton(i, input_reset, input_fire_center);
		Input::get()->bindGameControllerButton(i, input_config, input_down);
		Input::get()->bindGameControllerButton(i, input_swap_controllers, input_up);
	}

	// Mapea las asignaciones a los botones desde el archivo de configuración, si se da el caso
	for (int i = 0; i < numGamePads; ++i)
		for (int index = 0; index < (int)options.controller.size(); ++index)
			if (Input::get()->getControllerName(i) == options.controller[index].name)
			{
				options.controller[index].plugged = true;
				for (int j = 0; j < (int)options.controller[index].inputs.size(); ++j)
				{
					Input::get()->bindGameControllerButton(i, options.controller[index].inputs[j], options.controller[index].buttons[j]);
				}
			}

	// Asigna botones a inputs desde otros inputs
	for (int i = 0; i < numGamePads; ++i)
	{
		Input::get()->bindGameControllerButton(i, input_exit, input_start);
		Input::get()->bindGameControllerButton(i, input_reset, input_fire_center);
		Input::get()->bindGameControllerButton(i, input_pause, input_fire_right);
		Input::get()->bindGameControllerButton(i, input_video_shaders, input_fire_left);
		Input::get()->bindGameControllerButton(i, input_mute, input_left);
		Input::get()->bindGameControllerButton(i, input_showinfo, input_right);
		Input::get()->bindGameControllerButton(i, input_config, input_down);
		Input::get()->bindGameControllerButton(i, input_swap_controllers, input_up);
	}

	// Guarda las asignaciones de botones en las opciones
	for (int i = 0; i < numGamePads; ++i)
	{
		options.controller[i].name = Input::get()->getControllerName(i);
		for (int j = 0; j < (int)options.controller[i].inputs.size(); ++j)
		{
			options.controller[i].buttons[j] = Input::get()->getControllerBinding(i, options.controller[i].inputs[j]);
		}
	}
}

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

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

	// Inicializa SDL
	if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
	{
#ifdef VERBOSE
		std::cout << "SDL could not initialize!\nSDL Error: " << SDL_GetError() << std::endl;
#endif
		success = false;
	}
	else
	{
		// Inicia el generador de numeros aleatorios
		std::srand(static_cast<unsigned int>(SDL_GetTicks()));

#ifdef VERBOSE
		// 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 << "  - " + std::to_string(DM.w) + "x" + std::to_string(DM.h) + " @ " + std::to_string(DM.refresh_rate) + "Hz" << std::endl;
		}*/

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

		// 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()))
		{
#ifdef VERBOSE
			std::cout << "Warning: texture filtering not enabled!\n";
#endif
		}
#ifndef NO_SHADERS
		if (!SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"))
		{
#ifdef VERBOSE
			std::cout << "Warning: opengl not enabled!\n";
#endif // VERBOSE
		}
#endif // NO_SHADERS
	   // 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 == nullptr)
		{
#ifdef VERBOSE
			std::cout << "Window could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
#endif
			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 = 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 == nullptr)
			{
#ifdef VERBOSE
				std::cout << "Renderer could not be created!\nSDL Error: " << SDL_GetError() << std::endl;
#endif
				success = false;
			}
			else
			{
				// Inicializa el color de renderizado
				SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);

				// Establece el tamaño del buffer de renderizado
				SDL_RenderSetLogicalSize(renderer, param.game.width, param.game.height);
				SDL_RenderSetIntegerScale(renderer, SDL_TRUE);

				// Establece el modo de mezcla
				SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
			}
		}
	}

#ifdef VERBOSE
	std::cout << std::endl;
#endif
	return success;
}

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

	// Ficheros de configuración
	Asset::get()->add(systemFolder + "/config.txt", AssetType::DATA, false, true);
	Asset::get()->add(systemFolder + "/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
	Asset::get()->add(prefix + "/data/gfx/controllers/controllers.png", AssetType::BITMAP);

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

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

	Asset::get()->add(prefix + "/data/gfx/balloon/powerball.png", AssetType::BITMAP);
	Asset::get()->add(prefix + "/data/gfx/balloon/powerball.ani", AssetType::ANIMATION);

	Asset::get()->add(prefix + "/data/gfx/bullet/bullet.png", AssetType::BITMAP);

	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_text.png", AssetType::BITMAP);

	Asset::get()->add(prefix + "/data/gfx/intro/intro.png", AssetType::BITMAP);

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

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

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

	Asset::get()->add(prefix + "/data/gfx/player/player1.gif", AssetType::BITMAP);
	Asset::get()->add(prefix + "/data/gfx/player/player1_pal1.gif", AssetType::PALETTE);
	Asset::get()->add(prefix + "/data/gfx/player/player1_pal2.gif", AssetType::PALETTE);
	Asset::get()->add(prefix + "/data/gfx/player/player1_pal3.gif", AssetType::PALETTE);

	Asset::get()->add(prefix + "/data/gfx/player/player2.gif", AssetType::BITMAP);
	Asset::get()->add(prefix + "/data/gfx/player/player2_pal1.gif", AssetType::PALETTE);
	Asset::get()->add(prefix + "/data/gfx/player/player2_pal2.gif", AssetType::PALETTE);
	Asset::get()->add(prefix + "/data/gfx/player/player2_pal3.gif", AssetType::PALETTE);

	Asset::get()->add(prefix + "/data/gfx/player/player.ani", AssetType::ANIMATION);

	Asset::get()->add(prefix + "/data/gfx/player/player_power.gif", AssetType::BITMAP);
	Asset::get()->add(prefix + "/data/gfx/player/player_power_pal.gif", AssetType::PALETTE);
	Asset::get()->add(prefix + "/data/gfx/player/player_power.ani", AssetType::ANIMATION);

	// Fuentes de texto
	Asset::get()->add(prefix + "/data/font/8bithud.png", AssetType::FONT);
	Asset::get()->add(prefix + "/data/font/8bithud.txt", AssetType::FONT);
	Asset::get()->add(prefix + "/data/font/nokia.png", AssetType::FONT);
	Asset::get()->add(prefix + "/data/font/nokia_big2.png", AssetType::FONT);
	Asset::get()->add(prefix + "/data/font/nokia.txt", AssetType::FONT);
	Asset::get()->add(prefix + "/data/font/nokia2.png", AssetType::FONT);
	Asset::get()->add(prefix + "/data/font/nokia2.txt", AssetType::FONT);
	Asset::get()->add(prefix + "/data/font/nokia_big2.txt", AssetType::FONT);
	Asset::get()->add(prefix + "/data/font/smb2_big.png", AssetType::FONT);
	Asset::get()->add(prefix + "/data/font/smb2_big.txt", AssetType::FONT);
	Asset::get()->add(prefix + "/data/font/smb2.gif", AssetType::FONT);
	Asset::get()->add(prefix + "/data/font/smb2_pal1.gif", AssetType::PALETTE);
	Asset::get()->add(prefix + "/data/font/smb2.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);

	return Asset::get()->check();
}

// Carga los parametros para configurar el juego
void Director::loadParams(std::string filepath)
{
	loadParamsFromFile(filepath);
}

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

	// Valores por defecto
	paramFileArgument = "";

	// Comprueba el resto de parametros
	for (int i = 1; i < argc; ++i)
	{
		if (strcmp(argv[i], "--320x240") == 0)
		{
			paramFileArgument = argv[i];
		}
	}
}

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

	struct stat st = {0};
	if (stat(systemFolder.c_str(), &st) == -1)
	{
		errno = 0;
#ifdef _WIN32
		int ret = mkdir(systemFolder.c_str());
#else
		int ret = mkdir(systemFolder.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);
			}
		}
	}
}

// Carga los sonidos del juego
void Director::loadSounds()
{
	// Obtiene la lista con las rutas a los ficheros de sonidos
	std::vector<std::string> list = Asset::get()->getListByType(AssetType::SOUND);
	sounds.clear();

	for (auto l : list)
	{
		const size_t lastIndex = l.find_last_of("/") + 1;
		const std::string name = l.substr(lastIndex, std::string::npos);
		SoundFile temp;
		temp.name = name;					 // Añade el nombre del fichero
		temp.file = JA_LoadSound(l.c_str()); // Carga el fichero de audio
		sounds.push_back(temp);
	}
}

// Carga las musicas del juego
void Director::loadMusics()
{
	// Obtiene la lista con las rutas a los ficheros musicales
	std::vector<std::string> list = Asset::get()->getListByType(AssetType::MUSIC);
	musics.clear();

	for (auto l : list)
	{
		const size_t lastIndex = l.find_last_of("/") + 1;
		const std::string name = l.substr(lastIndex, std::string::npos);
		MusicFile temp;
		temp.name = name;					 // Añade el nombre del fichero
		temp.file = JA_LoadMusic(l.c_str()); // Carga el fichero de audio
		musics.push_back(temp);
	}
}

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

// Ejecuta la sección con la secuencia de introducción
void Director::runIntro()
{
	auto intro = new Intro(getMusic(musics, "intro.ogg"));
	intro->run();
	delete intro;
}

// Ejecuta la sección con el titulo del juego
void Director::runTitle()
{
	auto title = new Title(getMusic(musics, "title.ogg"));
	title->run();
	delete title;
}

// Ejecuta la sección donde se juega al juego
void Director::runGame()
{
	const auto playerID = section::options == section::OPTIONS_GAME_PLAY_1P ? 1 : 2;
	constexpr auto currentStage = 0;
	auto game = new Game(playerID, currentStage, GAME_MODE_DEMO_OFF, getMusic(musics, "playing.ogg"));
	game->run();
	delete game;
}

// Ejecuta la sección donde se muestran las instrucciones
void Director::runInstructions()
{
	auto instructions = new Instructions(getMusic(musics, "title.ogg"));
	instructions->run();
	delete instructions;
}

// Ejecuta la sección donde se muestra la tabla de puntuaciones
void Director::runHiScoreTable()
{
	auto hiScoreTable = new HiScoreTable(getMusic(musics, "title.ogg"));
	hiScoreTable->run();
	delete hiScoreTable;
}

// Ejecuta el juego en modo demo
void Director::runDemoGame()
{
	const auto playerID = (rand() % 2) + 1;
	constexpr auto currentStage = 0;
	auto game = new Game(playerID, currentStage, GAME_MODE_DEMO_ON, nullptr);
	game->run();
	delete game;
}

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

	const int returnCode = section::options == section::OPTIONS_QUIT_NORMAL ? 0 : 1;
	return returnCode;
}

// Obtiene una fichero a partir de un lang_e
std::string Director::getLangFile(lang::lang_e lang)
{
	switch (lang)
	{
	case lang::ba_BA:
		return Asset::get()->get("ba_BA.txt");
		break;

	case lang::es_ES:
		return Asset::get()->get("es_ES.txt");
		break;

	case lang::en_UK:
		return Asset::get()->get("en_UK.txt");
		break;

	default:
		break;
	}

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