#include "const.h"
#include "director.h"
#include <iostream>

const Uint8 *keystates;

// Constructor
Director::Director(std::string _path)
{
	// Crea todos los objetos del juego
	initObjects();

	// Inicializa todas las variables
	init(_path);
}

Director::~Director()
{
	// Borra todos los objetos del juego
	deleteObjects();

	// Destruye la ventana
	SDL_DestroyRenderer(renderer);
	SDL_DestroyTexture(backbuffer);
	SDL_DestroyWindow(window);
	backbuffer = nullptr;
	renderer = nullptr;
	window = nullptr;

	// Sal del subsistema SDL
	SDL_Quit();
}

// Inicializa todas las variables
void Director::init(std::string _path)
{
	setPath(_path);
	section = NONE;
	gameControllerFound = false;
	for (int i = 0; i < 360; i++)
		sen[i] = sin(i * 3.14 / 180);
	for (int i = 0; i < TOTAL_SCORE_DATA; i++)
		scoreData[i] = 0;

	initOptions();
	quit = false;
}

// Inicializa las variables de las opciones
void Director::initOptions()
{
	options.fullScreenMode = 0;
	options.fullScreenModePrevious = 0;
	options.windowSize = 0;
	options.windowSizePrevious = 0;
}

// Inicializa las variables del juego
void Director::initGame()
{
	// Variables
	game.score = 0;
	game.section = 0;
	game.paused = false;
	game.ticks = 0;
	game.ticksSpeed = GAME_SPEED;
	game.counter = 0;

	// Player
	resource.texture[TEXTURE_PLAYER].texture = new LTexture();
	loadTextureFromFile(resource.texture[TEXTURE_PLAYER].texture, resource.texture[TEXTURE_PLAYER].file, renderer);
	player = new Player(renderer, resource.texture[TEXTURE_PLAYER].texture);

	// Map
	resource.texture[TEXTURE_ACTORS].texture = new LTexture();
	loadTextureFromFile(resource.texture[TEXTURE_ACTORS].texture, resource.texture[TEXTURE_ACTORS].file, renderer);
	resource.texture[TEXTURE_BKG_SURFACE].texture = new LTexture();
	loadTextureFromFile(resource.texture[TEXTURE_BKG_SURFACE].texture, resource.texture[TEXTURE_BKG_SURFACE].file, renderer);
	resource.texture[TEXTURE_TILES_VOLCANO].texture = new LTexture();
	loadTextureFromFile(resource.texture[TEXTURE_TILES_VOLCANO].texture, resource.texture[TEXTURE_TILES_VOLCANO].file, renderer);
	map = new Map(renderer, resource.texture[TEXTURE_TILES_VOLCANO].texture, resource.texture[TEXTURE_ACTORS].texture,
				  resource.texture[TEXTURE_BKG_SURFACE].texture, resource.file[FILE_MAP_VOLCANO].file);
}

// Limpia las variables del juego
void Director::quitGame()
{
	// Player
	delete player;
	player = nullptr;
	delete resource.texture[TEXTURE_PLAYER].texture;
	resource.texture[TEXTURE_PLAYER].texture = nullptr;

	// Map
	delete map;
	map = nullptr;
	delete resource.texture[TEXTURE_TILES_VOLCANO].texture;
	resource.texture[TEXTURE_TILES_VOLCANO].texture = nullptr;
	delete resource.texture[TEXTURE_ACTORS].texture;
	resource.texture[TEXTURE_ACTORS].texture = nullptr;
	delete resource.texture[TEXTURE_BKG_SURFACE].texture;
	resource.texture[TEXTURE_BKG_SURFACE].texture = nullptr;
}

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

	// Inicializa SDL
	if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO | SDL_INIT_HAPTIC) < 0)
	{
		printf("SDL could not initialize!\nSDL Error: %s\n", SDL_GetError());
		success = false;
	}
	else
	{
		// Establece el filtro de la textura a nearest
		if (!SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "0"))
		{
			printf("Warning: Nearest texture filtering not enabled!\n");
		}

		// Inicializa jail_audio
		JA_Init(48000, AUDIO_S16, 2);

		// Comprueba si hay algun mando conectado
		if (SDL_NumJoysticks() < 1)
		{
			printf("Warning: No joysticks connected!\n");
			gameControllerFound = false;
		}
		else
		{
			// Carga el mando
			gameController = SDL_JoystickOpen(0);
			gameControllerFound = true;

			if (gameController == NULL)
			{
				printf("Warning: Unable to open game controller!\nSDL Error: %s\n", SDL_GetError());
				gameControllerFound = false;
			}
			else
			{
				printf("%i joysticks were found.\n", SDL_NumJoysticks());
				std::cout << SDL_JoystickNumButtons(gameController) << " buttons\n";

				// Get controller haptic device
				controllerHaptic = SDL_HapticOpenFromJoystick(gameController);
				if (controllerHaptic == NULL)
				{
					printf("Warning: Controller does not support haptics!\nSDL Error: %s\n", SDL_GetError());
				}
				else
				{
					printf("Haptics detected\n");

					// Get initialize rumble
					if (SDL_HapticRumbleInit(controllerHaptic) < 0)
					{
						printf("Warning: Unable to initialize rumble!\nSDL Error: %s\n", SDL_GetError());
					}
				}
			}
		}

		// Crea la ventana
		window = SDL_CreateWindow(WINDOW_CAPTION, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, VIEW_WIDTH, VIEW_HEIGHT, SDL_WINDOW_SHOWN | SDL_WINDOW_ALLOW_HIGHDPI);
		if (window == NULL)
		{
			printf("Window could not be created!\nSDL Error: %s\n", SDL_GetError());
			success = false;
		}
		else
		{
			// Crea un renderizador para la ventana con vsync
			renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC);
			if (renderer == NULL)
			{
				printf("Renderer could not be created!\nSDL Error: %s\n", SDL_GetError());
				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, SCREEN_WIDTH, SCREEN_HEIGHT);

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

			// Crea un backbuffer para el renderizador
			backbuffer = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, SCREEN_WIDTH, SCREEN_HEIGHT);
			if (backbuffer == NULL)
			{
				printf("Backbuffer could not be created!\nSDL Error: %s\n", SDL_GetError());
				success = false;
			}
		}
	}

	printf("\n");
	return success;
}

// Crea los objetos del programa
void Director::initObjects()
{
	eventHandler = new SDL_Event();
	text.white = new Text("", nullptr, nullptr);
	screen = new Screen(window, renderer);
	asset = new Asset(path);
}

// Borra los objetos del programa
void Director::deleteObjects()
{
	delete eventHandler;
	delete text.white;
	delete screen;
	delete asset;
	eventHandler = nullptr;
	text.white = nullptr;
	screen = nullptr;
	asset = nullptr;
}

// Crea el indice de ficheros de recursos
void Director::setFileList()
{
	// Ficheros binarios
	asset->add("/data/volcano.map",data);
	asset->add("/data/config.bin".data);

	// Texturas
	asset->add("/media/gfx/actors.png",bitmap);
	asset->add("/media/gfx/bkg_surface.png",bitmap);
	asset->add("/media/gfx/filter.png",bitmap);
	asset->add("/media/gfx/hud.png",bitmap);
	asset->add("/media/gfx/menu_animation.png",bitmap);
	asset->add("/media/gfx/menu.png",bitmap);
	asset->add("/media/gfx/player.png",bitmap);
	asset->add("/media/gfx/tiles_surface.png",bitmap);
	asset->add("/media/gfx/tiles_volcano.png",bitmap);
	
	// Sonidos
	resource.sound[SOUND_COIN].file = path + "/" + "../media/sound/sound_player_coin.wav";
	resource.sound[SOUND_DEATH].file = path + "/" + "../media/sound/sound_player_death.wav";
	resource.sound[SOUND_DROP_ENEMY].file = path + "/" + "../media/sound/sound_drop_enemy.wav";
	resource.sound[SOUND_DROP_SPLAT].file = path + "/" + "../media/sound/sound_drop_splat.wav";
	resource.sound[SOUND_JUMP].file = path + "/" + "../media/sound/sound_player_jump.wav";
	resource.sound[SOUND_MENU_LOGO].file = path + "/" + "../media/sound/sound_menu_logo.wav";
	resource.sound[SOUND_MENU_START].file = path + "/" + "../media/sound/sound_menu_start.wav";
	for (Uint8 i = 0; i < TOTAL_SOUND; i++)
		resource.sound[i].sound = nullptr;

	// Musicas
	resource.music[MUSIC_MENU].file = path + "/" + "../media/music/music_menu.ogg";
	resource.music[MUSIC_SURFACE].file = path + "/" + "../media/music/music_surface.ogg";
	resource.music[MUSIC_VOLCANO].file = path + "/" + "../media/music/music_volcano.ogg";
	for (Uint8 i = 0; i < TOTAL_TEXTURE; i++)
		resource.music[i].music = nullptr;
}

// Comprueba que todos los ficheros de recursos existen
bool Director::checkResourceList()
{
	bool success = true;
	std::string p;
	std::string filename;
	SDL_RWops *file;

	// Comprueba los ficheros de musica
	printf("\n>> MUSIC FILES\n");
	for (Uint8 i = 0; i < TOTAL_MUSIC; i++)
	{
		p = resource.music[i].file.c_str();
		filename = p.substr(p.find_last_of("\\/") + 1);
		file = SDL_RWFromFile(p.c_str(), "r+b");
		if (file != NULL)
		{
			printf("Checking file %-20s [OK]\n", filename.c_str());
		}
		else
		{
			success = false;
			printf("Checking file %-20s [ERROR]\n", filename.c_str());
		}
		SDL_RWclose(file);
	}

	// Comprueba los ficheros de sonidos
	printf("\n>> SOUND FILES\n");
	for (Uint8 i = 0; i < TOTAL_SOUND; i++)
	{
		p = resource.sound[i].file.c_str();
		filename = p.substr(p.find_last_of("\\/") + 1);
		file = SDL_RWFromFile(p.c_str(), "r+b");
		if (file != NULL)
		{
			printf("Checking file %-20s [OK]\n", filename.c_str());
		}
		else
		{
			success = false;
			printf("Checking file %-20s [ERROR]\n", filename.c_str());
		}
		SDL_RWclose(file);
	}

	// Comprueba los ficheros con texturas
	printf("\n>> TEXTURE FILES\n");
	for (Uint8 i = 0; i < TOTAL_TEXTURE; i++)
	{
		p = resource.texture[i].file.c_str();
		filename = p.substr(p.find_last_of("\\/") + 1);
		file = SDL_RWFromFile(p.c_str(), "r+b");
		if (file != NULL)
		{
			printf("Checking file %-20s [OK]\n", filename.c_str());
		}
		else
		{
			success = false;
			printf("Checking file %-20s [ERROR]\n", filename.c_str());
		}
		SDL_RWclose(file);
	}

	// Resultado
	if (success)
	{
		printf("\n** All files OK.\n\n");
	}
	else
	{
		printf("\n** File is missing. Exiting.\n\n");
	}
	return success;
}

// Inicializa la variable con los ficheros de recursos
bool Director::initResourceList()
{
	setResourceList();
	return checkResourceList();
}

// Carga los recursos necesarios
bool Director::loadMedia(Uint8 section)
{
	// Indicador de éxito en la carga
	bool success = true;
	std::string p;

	switch (section)
	{
	case GAME_SECTION_INIT:
	{
		p = resource.file[FILE_CONFIG].file.c_str();
		std::string filename = p.substr(p.find_last_of("\\/") + 1);
		filename = p.substr(p.find_last_of("\\/") + 1);

		// Abre el fichero con la configuracion de las opciones para leer en binario
		SDL_RWops *file = SDL_RWFromFile(p.c_str(), "r+b");

		// El fichero no existe
		if (file == NULL)
		{
			printf("Warning: Unable to open %s file\n", filename.c_str());

			// Crea el fichero para escribir
			file = SDL_RWFromFile(p.c_str(), "w+b");
			if (file != NULL)
			{
				printf("New file (%s) created!\n", filename.c_str());

				// Inicializa los datos
				options.fullScreenMode = 0;
				SDL_RWwrite(file, &options.fullScreenMode, sizeof(options.fullScreenMode), 1);

				options.windowSize = 3;
				SDL_RWwrite(file, &options.windowSize, sizeof(options.windowSize), 1);

				// Cierra el fichero
				SDL_RWclose(file);
			}
			else
			{
				printf("Error: Unable to create file %s\n", filename.c_str());
				success = false;
			}
		}
		// El fichero existe
		else
		{
			// Carga los datos
			printf("Reading file %s\n", filename.c_str());
			SDL_RWread(file, &options.fullScreenMode, sizeof(options.fullScreenMode), 1);
			SDL_SetWindowFullscreen(window, options.fullScreenMode);
			SDL_RWread(file, &options.windowSize, sizeof(options.windowSize), 1);
			SDL_SetWindowSize(window, SCREEN_WIDTH * options.windowSize, SCREEN_HEIGHT * options.windowSize);

			// Cierra el fichero
			SDL_RWclose(file);
		}
		printf("\n");

		// Texturas

		// Sonidos

		// Musicas
	}
	break;

	case GAME_SECTION_TITLE:
	{
	}
	break;

	case GAME_SECTION_PLAYING:
	{
	}
	break;

	case GAME_SECTION_GAME_OVER_SCREEN:
	{
	}
	break;

	case GAME_SECTION_INTRO:
	{
	}
	break;

	case GAME_SECTION_DEMO:
	{
	}
	break;

	case GAME_SECTION_INSTRUCTIONS:
	{
	}
	break;

	case GAME_SECTION_LOGO:
	{
	}
	break;

	default:
	{
	}
	break;
	}

	return success;
}

// Descrga los recursos necesarios
bool Director::unLoadMedia(Uint8 section)
{
	// Indicador de éxito en la carga
	bool success = true;
	std::string p;

	switch (section)
	{
	case GAME_SECTION_INIT:
	{
		p = resource.file[FILE_CONFIG].file;
		std::string filename = p.substr(p.find_last_of("\\/") + 1);

		// Abre el fichero de puntuación para escribir
		SDL_RWops *file = SDL_RWFromFile(p.c_str(), "w+b");

		if (file != NULL)
		{
			// Guardamos los datos
			SDL_RWwrite(file, &options.fullScreenMode, sizeof(Uint32), 1);
			SDL_RWwrite(file, &options.windowSize, sizeof(Uint8), 1);

			printf("Writing file %s\n", filename.c_str());

			// Cerramos el fichero
			SDL_RWclose(file);
		}
		else
		{
			printf("Error: Unable to save %s file! %s\n", filename.c_str(), SDL_GetError());
		}
	}
	break;

	case GAME_SECTION_TITLE:
	{
	}
	break;

	case GAME_SECTION_PLAYING:
	{
	}
	break;

	case GAME_SECTION_GAME_OVER_SCREEN:
	{
	}
	break;

	case GAME_SECTION_INTRO:
	{
	}
	break;

	case GAME_SECTION_DEMO:
	{
	}
	break;

	case GAME_SECTION_INSTRUCTIONS:
	{
	}
	break;

	case GAME_SECTION_LOGO:
	{
	}
	break;

	default:
	{
	}
	break;
	}

	return success;
}

// Establece el valor de la variable
void Director::setPath(std::string _path)
{
	path = _path.substr(0, _path.find_last_of("\\/"));
}

// Obtiene el valor de la variable
Uint8 Director::getGameSection()
{
	return game.section;
}

// Establece el valor de la variable
void Director::changeGameSection(_section sectionPrevious, _section sectionNext)
{
	switch (sectionPrevious)
	{
	case GAME:
		quitGame();

		break;

	default:
		break;
	}

	switch (sectionNext)
	{
	case GAME:
		initGame();

		break;

	default:
		break;
	}

	game.section = sectionNext;
}

// Cambia el valor de la variable de modo de pantalla completa
void Director::changeFullScreenMode()
{
	switch (options.fullScreenMode)
	{
	case 0:
		options.fullScreenMode = SDL_WINDOW_FULLSCREEN;
		break;
	case SDL_WINDOW_FULLSCREEN:
		options.fullScreenMode = SDL_WINDOW_FULLSCREEN_DESKTOP;
		break;
	case SDL_WINDOW_FULLSCREEN_DESKTOP:
		options.fullScreenMode = 0;
		break;

	default:
		options.fullScreenMode = 0;
		break;
	}
}

// Comprueba si hay que salir
bool Director::isRunning()
{
	return !quit;
}

// Bucle para el logo del juego
void Director::runLogo()
{
}

// Bucle para la intro del juego
void Director::runIntro()
{
}

// Bucle para el titulo del juego
void Director::runTitle()
{
}

// Bucle para el juego
void Director::runGame()
{
	// Actualiza la lógica del juego
	updateGame();

	// Pinta la sección de juego
	renderGame();
}

// Actualiza la lógica del juego
void Director::updateGame()
{
	// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
	if (SDL_GetTicks() - game.ticks > game.ticksSpeed)
	{
		// Actualiza el contador de ticks
		game.ticks = SDL_GetTicks();

		// Comprueba los eventos que hay en la cola
		while (SDL_PollEvent(eventHandler) != 0)
		{
			// Evento de salida de la aplicación
			if (eventHandler->type == SDL_QUIT)
			{
				quit = true;
				break;
			}
		}

		game.counter++;
		map->update();
		player->update();
	}
}

// Pinta la sección de juego
void Director::renderGame()
{
	// Limpia la pantalla
	SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0xFF);
	SDL_RenderClear(renderer);

	// Dibuja los objetos
	map->render();
	player->render();

	// Muestra el backbuffer en pantalla
	SDL_RenderPresent(renderer);
}

// Bucle principal
void Director::run()
{
	bool quit = false;

	// Comprueba si existen todos los ficheros de recursos
	if (!initResourceList())
	{
		quit = true;
	}

	// Arranca SDL y crea la ventana
	if ((!quit) && (!initSDL()))
	{
		quit = true;
	}

	// Inicializa las variables del juego
	if (!quit)
	{
		initGame();
	}

	// Bucle del juego
	if (!quit)
	{
		while (isRunning())
		{
			runGame();
		}
	}
	quitGame();
}