#include "game.h"

// Constructor
Game::Game(SDL_Renderer *renderer, Screen *screen, Asset *asset, Input *input, Debug *debug)
{
	// Inicia algunas variables
	clock = SDL_GetTicks();
	currentRoom = "01.room";
	spawnPoint = {16, 96, 0, 0, 0, s_standing, SDL_FLIP_NONE};

	currentRoom = "06.room";
	spawnPoint = {240, 96, 0, 0, 0, s_standing, SDL_FLIP_HORIZONTAL};

	// Copia los punteros
	this->renderer = renderer;
	this->asset = asset;
	this->screen = screen;
	this->input = input;
	this->debug = debug;

	// Crea los objetos
	scoreboard = new ScoreBoard(renderer, asset, &playerLives, &itemsPicked, &clock);
	itemTracker = new ItemTracker();
	room = new Room(asset->get(currentRoom), renderer, screen, asset, itemTracker, &itemsPicked, debug);
	player = new Player(spawnPoint, asset->get("player01.png"), asset->get("player01.ani"), renderer, asset, input, room, debug);
	eventHandler = new SDL_Event();
	text = new Text(asset->get("smb2.png"), asset->get("smb2.txt"), renderer);
	music = JA_LoadMusic(asset->get("game.ogg").c_str());

	// Inicializa el resto de variables
	ticks = 0;
	ticksSpeed = 15;
	playerLives = 9;
	itemsPicked = 0;

	debugEnabled = true;
	player->setInvincible(debugEnabled);
	musicEnabled = !debugEnabled;

	section.name = SECTION_PROG_GAME;
	section.subsection = SUBSECTION_GAME_PLAY;
}

Game::~Game()
{
	// Borra las referencias a los punteros
	renderer = nullptr;
	asset = nullptr;
	input = nullptr;

	// Libera la memoria de los objetos
	delete scoreboard;
	delete itemTracker;
	delete room;
	delete player;
	delete eventHandler;
	delete text;
}

// Comprueba los eventos de la cola
void Game::checkEventHandler()
{
	// 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)
		{
			section.name = SECTION_PROG_QUIT;
			screen->setBorderColor(stringToColor("black"));
			break;
		}
		else if ((eventHandler->type == SDL_KEYDOWN) and (eventHandler->key.repeat == 0))
		{
			switch (eventHandler->key.keysym.scancode)
			{
			case SDL_SCANCODE_ESCAPE:
				section.name = SECTION_PROG_TITLE;
				break;

			case SDL_SCANCODE_D:
				debugEnabled = !debugEnabled;
				player->setInvincible(debugEnabled);
				musicEnabled = !debugEnabled;
				musicEnabled ? JA_PlayMusic(music) : JA_StopMusic();
				break;

			case SDL_SCANCODE_M:
				(JA_GetMusicState() == JA_MUSIC_PLAYING) ? JA_PauseMusic() : JA_ResumeMusic();
				break;

			case SDL_SCANCODE_F:
				screen->switchVideoMode();
				reLoadTextures();
				break;

			case SDL_SCANCODE_F1:
				screen->setWindowSize(1);
				reLoadTextures();
				break;

			case SDL_SCANCODE_F2:
				screen->setWindowSize(2);
				reLoadTextures();
				break;

			case SDL_SCANCODE_F3:
				screen->setWindowSize(3);
				reLoadTextures();
				break;

			case SDL_SCANCODE_F4:
				screen->setWindowSize(4);
				reLoadTextures();
				break;

			default:
				break;
			}
		}
	}
}

// Bucle para el juego
section_t Game::run()
{
	if (musicEnabled)
	{
		JA_PlayMusic(music);
	}

	while (section.name == SECTION_PROG_GAME)
	{
		// Sección juego jugando
		if (section.subsection == SUBSECTION_GAME_PLAY)
		{
			update();
			render();
		}
	}

	JA_StopMusic();

	return section;
}

// Actualiza el juego, las variables, comprueba la entrada, etc.
void Game::update()
{
	// Comprueba que la diferencia de ticks sea mayor a la velocidad del juego
	if (SDL_GetTicks() - ticks > ticksSpeed)
	{
		// Actualiza el contador de ticks
		ticks = SDL_GetTicks();

		// Comprueba los eventos de la cola
		checkEventHandler();

		// Actualiza los objetos
		debug->clear();
		room->update();
		player->update();
		checkPlayerOnBorder();
		checkPlayerAndItems();
		checkPlayerAndEnemies();
		scoreboard->update();
		updateDebugInfo();
	}
}

// Pinta los objetos en pantalla
void Game::render()
{
	// Prepara para dibujar el frame
	screen->start();
	screen->clean(room->getBGColor());

	room->renderMap();
	room->renderEnemies();
	room->renderItems();
	player->render();
	renderRoomName();
	scoreboard->render();

	// Debug info
	renderDebugInfo();

	// Actualiza la pantalla
	screen->blit();
}

// Pasa la información de debug
void Game::updateDebugInfo()
{
	debug->add("X = " + std::to_string((int)player->x) + ", Y = " + std::to_string((int)player->y));
	debug->add("VX = " + std::to_string(player->vx).substr(0,4) + ", VY = " + std::to_string(player->vy).substr(0,4));
	debug->add("STATE = " + std::to_string(player->state));
}

// Pone la información de debug en pantalla
void Game::renderDebugInfo()
{
	if (!debugEnabled)
	{
		return;
	}

	// Borra el marcador
	SDL_Rect rect = {0, 18 * BLOCK, PLAY_AREA_WIDTH, GAMECANVAS_HEIGHT - PLAY_AREA_HEIGHT};
	SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255);
	SDL_RenderFillRect(renderer, &rect);

	// Pinta la rejilla
	SDL_SetRenderDrawColor(renderer, 255, 255, 255, 64);
	for (int i = 0; i < PLAY_AREA_BOTTOM; i += 8)
	{ // Lineas horizontales
		SDL_RenderDrawLine(renderer, 0, i, PLAY_AREA_RIGHT, i);
	}
	for (int i = 0; i < PLAY_AREA_RIGHT; i += 8)
	{ // Lineas verticales
		SDL_RenderDrawLine(renderer, i, 0, i, PLAY_AREA_BOTTOM - 1);
	}

	// Pinta mascaras de sprite
	SDL_SetRenderDrawColor(renderer, 0, 255, 0, 192);
	rect = player->getRect();
	SDL_RenderFillRect(renderer, &rect);

	// Pinta el texto
	debug->setPos({1, 18 * 8});
	debug->render();
}

// Escribe el nombre de la pantalla
void Game::renderRoomName()
{
	// Texto en el centro de la pantalla
	SDL_Rect rect = {0, 16 * BLOCK, PLAY_AREA_WIDTH, BLOCK * 2};
	color_t color = stringToColor("white");
	SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, 0xFF);
	SDL_RenderFillRect(renderer, &rect);

	text->writeDX(TXT_CENTER | TXT_COLOR, GAMECANVAS_CENTER_X, 16 * 8 + 4, room->getName(), 1, room->getBGColor());
}

// Cambia de habitación
bool Game::changeRoom(std::string file)
{
	bool success = false;

	// En las habitaciones los limites tienen la cadena del fichero o un 0 en caso de no limitar con nada
	if (file != "0")
		// Verifica que exista el fichero que se va a cargar
		if (asset->get(file) != "")
		{
			// Elimina la habitación actual
			delete room;
			room = nullptr;

			// Crea un objeto habitación nuevo a partir del fichero
			room = new Room(asset->get(file), renderer, screen, asset, itemTracker, &itemsPicked, debug);

			// Pasa la nueva habitación al jugador
			player->setRoom(room);

			success = true;
		}

	return success;
}

// Comprueba si el jugador esta en el borde de la pantalla
void Game::checkPlayerOnBorder()
{
	if (player->getOnBorder())
	{
		const std::string room_name = room->getRoom(player->getBorder());
		if (changeRoom(room_name))
		{
			player->switchBorders();
			currentRoom = room_name;
			spawnPoint = player->getSpawnParams();
		}
	}
}

// Comprueba las colisiones del jugador con los enemigos
bool Game::checkPlayerAndEnemies()
{
	const bool death = room->enemyCollision(player->getCollider());
	if (death)
	{
		killPlayer();
	}
	return death;
}

// Comprueba las colisiones del jugador con los objetos
void Game::checkPlayerAndItems()
{
	room->itemCollision(player->getCollider());
}

// Mata al jugador
void Game::killPlayer()
{
	if (player->getInvincible())
	{
		return;
	}

	playerLives--;

	// Destruye la habitacion y el jugador
	delete room;
	delete player;

	// Crea la nueva habitación y el nuevo jugador
	room = new Room(asset->get(currentRoom), renderer, screen, asset, itemTracker, &itemsPicked, debug);
	player = new Player(spawnPoint, asset->get("player01.png"), asset->get("player01.ani"), renderer, asset, input, room, debug);
}

// Recarga todas las texturas
void Game::reLoadTextures()
{
	player->reLoadTexture();
	room->reLoadTexture();
	scoreboard->reLoadTexture();
	text->reLoadTexture();
}