/*
 * Copyright 2017-2019, Intel Corporation
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *
 *     * Neither the name of the copyright holder nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "GameController.hpp"
#include "Pool.hpp"

GameController::GameController()
{
	gameStatus = pmem::obj::make_persistent<PongGameStatus>();
}

GameController::~GameController()
{
	pmem::obj::transaction::run(
		Pool::getGamePool()->getPoolToTransaction(), [&] {
			pmem::obj::delete_persistent<PongGameStatus>(
				gameStatus);
		});
}

void
GameController::gameLoop(bool isSimulation)
{
	sf::RenderWindow *gameWindow = new sf::RenderWindow(
		sf::VideoMode(WINDOW_WIDTH, WINDOW_HEIGHT), GAME_NAME);
	gameWindow->setFramerateLimit(FRAMERATE_LIMIT);

	sf::Font font = getFont();

	View *menuView = new MenuView(font);
	View *gameView = new GameView(font);
	View *gameOverView = new GameOverView(font);

	while (gameWindow->isOpen()) {
		sf::Event event;
		while (gameWindow->pollEvent(event)) {
			if (event.type == sf::Event::Closed)
				gameWindow->close();
		}
		gameWindow->clear();
		if (isSimulation) {
			if (gameStatus->getGameState() !=
			    game_state::SIMULATE) {
				resetGameStatus();
				gameStatus->setIsGameToResume(false);
				gameStatus->setGameState(game_state::SIMULATE);
			}
			gameMatchSimulation(gameWindow, gameView);
		} else {
			if (gameStatus->getGameState() == game_state::MATCH) {
				gameMatch(gameWindow, gameView);
			} else if (gameStatus->getGameState() ==
				   game_state::MENU) {
				menu(gameWindow, menuView);
			} else if (gameStatus->getGameState() ==
				   game_state::SIMULATE) {
				gameMatchSimulation(gameWindow, gameView);
			} else if (gameStatus->getGameState() ==
				   game_state::GAME_OVER) {
				gameOver(gameWindow, gameOverView);
			}
		}
	}
	delete menuView;
	delete gameView;
	delete gameOverView;
	delete gameWindow;
}

void
GameController::gameOver(sf::RenderWindow *gameWindow, View *view)
{
	view->prepareView(*gameStatus);
	view->displayView(gameWindow);

	sf::Event event;
	while (gameWindow->pollEvent(event)) {
		if (event.type == sf::Event::KeyPressed) {
			switch (event.key.code) {
				case sf::Keyboard::Return:
					gameStatus->setIsGameToResume(false);
					gameStatus->setGameState(
						game_state::MENU);
					break;
				default:
					break;
			}
		}
		if (event.type == sf::Event::Closed)
			gameWindow->close();
	}
}

void
GameController::menu(sf::RenderWindow *gameWindow, View *view)
{
	view->prepareView(*gameStatus);
	view->displayView(gameWindow);

	sf::Event event;
	while (gameWindow->pollEvent(event)) {
		if (event.type == sf::Event::KeyPressed) {
			handleEventKeypress(event, gameWindow);
		}
		if (event.type == sf::Event::Closed)
			gameWindow->close();
	}
}

void
GameController::handleEventKeypress(sf::Event &event,
				    sf::RenderWindow *gameWindow)
{
	switch (event.key.code) {
		case sf::Keyboard::Up:
			gameStatus->setMenuItem(
				gameStatus->getMenuItem() == 0
					? MENU_ITEMS - 1
					: gameStatus->getMenuItem() - 1);
			break;
		case sf::Keyboard::Down:
			gameStatus->setMenuItem(
				(gameStatus->getMenuItem() + 1) % MENU_ITEMS);
			break;
		case sf::Keyboard::Return:
			if (gameStatus->getMenuItem() == NEW_GAME) {
				resetGameStatus();
				gameStatus->setIsGameToResume(true);
				gameStatus->setGameState(game_state::MATCH);
			} else if (gameStatus->getMenuItem() == RESUME &&
				   gameStatus->getIsGameToResume()) {
				gameStatus->setGameState(game_state::MATCH);
			} else if (gameStatus->getMenuItem() == SIMULATION) {
				resetGameStatus();
				gameStatus->setIsGameToResume(false);
				gameStatus->setGameState(game_state::SIMULATE);
			} else if (gameStatus->getMenuItem() == EXIT) {
				gameWindow->close();
			}
			break;
		default:
			break;
	}
}

void
GameController::gameMatch(sf::RenderWindow *gameWindow, View *view)
{
	if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Space))
		gameStatus->startBall(BALL_PLAYERS_SPEED);
	gameStatus->movePaddles();
	gameStatus->lookForCollisions(true);
	gameStatus->actualizeStatus();

	view->prepareView(*gameStatus);
	view->displayView(gameWindow);

	if (gameStatus->score()) {
		if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Space))
			gameStatus->startBall(BALL_PLAYERS_SPEED);
	}
	if (gameStatus->checkIfAnyPlayerWon()) {
		gameStatus->setGameState(game_state::GAME_OVER);
	} else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) {
		gameStatus->setGameState(game_state::MENU);
	}
}

void
GameController::gameMatchSimulation(sf::RenderWindow *gameWindow, View *view)
{
	gameStatus->startBall(BALL_COMPUTER_SPEED);
	gameStatus->simulate();
	gameStatus->lookForCollisions(false);
	gameStatus->actualizeStatus();
	if (gameStatus->score())
		gameStatus->startBall(BALL_COMPUTER_SPEED);

	view->prepareView(*gameStatus);
	view->displayView(gameWindow);

	if (gameStatus->checkIfAnyPlayerWon()) {
		gameStatus->setGameState(game_state::GAME_OVER);
	} else if (sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Escape)) {
		gameStatus->setGameState(game_state::MENU);
	}
}

void
GameController::resetGameStatus()
{
	pmem::obj::transaction::run(
		Pool::getGamePool()->getPoolToTransaction(), [&] {
			pmem::obj::delete_persistent<PongGameStatus>(
				gameStatus);
			gameStatus =
				pmem::obj::make_persistent<PongGameStatus>();
		});
}
