include("GameObjects.js");

var gameBoard;
var whoseTurn;
var gameStarted;
var moveCount;
var wins;

/**
 * Initialize the Extension; set initial state and add request and event listeners.
 */
function init()
{
	trace("TicTacToe Game Extension launched");
	
	// Initialize properties
	moveCount = 0;
	wins = [0,0,0];
	gameBoard = new Board();
	
	// Add request handlers
    addRequestHandler("move", onMoveRequest);
    addRequestHandler("restart", onRestartRequest);
    addRequestHandler("ready", onReadyRequest);
	
	// Register server event handlers
	addEventHandler(SFSEventType.USER_DISCONNECT, onUserGoneEvent);
	addEventHandler(SFSEventType.USER_LEAVE_ROOM, onUserGoneEvent);
	addEventHandler(SFSEventType.SPECTATOR_TO_PLAYER, onSpectatorToPlayerEvent);
}

/**
 * Destroy the Extension.
 */
function destroy()
{
	trace("TicTacToe Game Extension destroyed");
}

/**
 * Start the game and send a notification to all users in the Room (both players and spectators).
 */
function startGame()
{
	if (gameStarted)
		throw ("Game is already started");
		
	// Reset state
	gameStarted = true;
	gameBoard.reset();
	
	// No turn assigned? let's start with player 1	
	if (whoseTurn == null)
		whoseTurn = getParentRoom().getUserByPlayerId(1);
	
	// Send START event to clients with:
	// - the active player
	// - the total wins of player 1 and 2
	var outParams = new SFSObject();
	outParams.putInt("t", whoseTurn.getPlayerId());
	outParams.putInt("p1w", wins[1]);
	outParams.putInt("p2w", wins[2]);
	
	send("start", outParams, getParentRoom().getUserList());
}

/**
 * Stop the game if its ending was signaled (tie or win or user lost).
 */
function stopGame(resetPlayers)
{
	gameStarted = false;
	moveCount = 0;
	
	// In case a player left the game (see OnUserGoneHandler handler),
	// the turn and total wins are reset
	if (resetPlayers)
	{
		whoseTurn = null;
		wins[1] = 0;
		wins[2] = 0;
	}
}

/**
 * Update game state (moves count and turn) and check if the game ended. 
 */
function updateGameState()
{
	// Increase moves counter				
	moveCount++;
	
	// Switch turn
	whoseTurn = getParentRoom().getUserByPlayerId(whoseTurn.getPlayerId() == 1 ? 2 : 1);
	
	// Check if game is over
	checkGameOver();
}

/**
 * Check if the game is over based on the board state.
 * If game is over, send outcome to all users in the Room (players and spectators).
 */
function checkGameOver()
{
	var state = gameBoard.getState(moveCount);
	
	if (state != State.RUNNING)
	{
		trace("Game ended");
		
		// Stop game
		stopGame(false);
		
		// Get winner id
		var winnerId = gameBoard.getWinner();
		
		if (state == State.END_WITH_WINNER)
		{
			// Update wins counter
			wins[winnerId]++;
			
			trace("Winner is player ", winnerId);
		}
		else if (state == State.END_WITH_TIE)
		{
			trace("It's a tie");
		}
		
		var outParams = new SFSObject();
		outParams.putInt("w", winnerId);
		
		// Send update to clients
		send("over", outParams, getParentRoom().getUserList());
	}
}

// === Client request handlers ====================================

/**
 * This handler receives, validates and dispatches to the other users in the Room a game move sent by the active player.
 */
function onMoveRequest(params, user)
{
	// Request must contain the 'r' and 'c' parameters, indicating which board slot was clicked by the player in turn
	if (!params.containsKey("r") || !params.containsKey("c"))
		throw ("Invalid request, one or more mandatory params are missing; required 'r' and 'c'");
	
	// Extract parameters
	var	row = params.getInt("r");
	var col = params.getInt("c");
	
	trace("Handling move from player " + user.getPlayerId() + " (" + row + ", " + col + ")");
	
	// Game must be started
	if (gameStarted)
	{
		// The player sending the move must be the active one
		if (whoseTurn == user)
		{
			// The board slot corresponding to the passed coordinates must be empty
			if (gameBoard.getMarkAt(row, col) == Mark.EMPTY)
			{
				// Set game board mark
				gameBoard.setMarkAt(row, col, user.getPlayerId() == 1 ? Mark.CROSS : Mark.RING);
				
				// Send move to all users in the Room, including the player who made it
				var outParams = new SFSObject();
				outParams.putInt("r", row);
				outParams.putInt("c", col);
				outParams.putInt("t", user.getPlayerId());
				
				send("move", outParams, getParentRoom().getUserList());
				
				// Update game state
				updateGameState();
			}
			
			// Wrong move
			else
				trace("Wrong move error; slot " + row + "," + col + " is not empty");
		}
		
		// Wrong turn
		else
			trace("Wrong move error; expected turn of player " + whoseTurn + ", received move from: " + user);
	}
	
	// Wrong game state
	else
		trace("Wrong move error; game not yet started");
}

/**
 * This handler receives the "restart" request from a player after the game end.
 */
function onRestartRequest(params, user)
{
	// Check if two players are available and start game
	if (getParentRoom().getSize().getUserCount() == 2)
		startGame();
}

/**
 * This handler receives the "ready" notification from clients entering the Room.
 */
function onReadyRequest(params, user)
{
	if (user.isPlayer())
	{
		// Check if two players are available and start game
		if (getParentRoom().getSize().getUserCount() == 2)
			startGame();
	}
	else
	{
		// Send game board state to spectator
		var outParams = new SFSObject();
		outParams.putInt("t", whoseTurn == null ? 0 : whoseTurn.getPlayerId());
		outParams.putBool("run", gameStarted);
		outParams.putSFSArray("board", gameBoard.toSFSArray());
		outParams.putInt("p1w", wins[1]);
		outParams.putInt("p2w", wins[2]);
	
		// If the game ended right before the spectator joined, also send its outcome
		if (!gameStarted && getParentRoom().getSize().getUserCount() == 2)
			outParams.putInt("w", gameExt.getLastWinnerId());
	
		send("state", outParams, user);
	}
}

// === Server event handlers ======================================

/**
 * This handler receives the signal that a user disconnected or left the game Room.
 */
function onUserGoneEvent(event)
{
	var gameRoom = getParentRoom();

	// Get id of the player who left the game
	// This can't be extracted from the User object available in event parameters, so we have to rely on other parameters 
	var oldPlayerId;

	// User disconnected
	if (event.getType() == SFSEventType.USER_DISCONNECT)
	{
		var playerIdsByRoom = event.getParameter(SFSEventParam.PLAYER_IDS_BY_ROOM);
		
		oldPlayerId = playerIdsByRoom.get(gameRoom);
	}

	// User left the room
	else
		oldPlayerId = event.getParameter(SFSEventParam.PLAYER_ID);

	if (oldPlayerId != null)
	{
		// Check if gone user was a player
		if (oldPlayerId > 0)
		{
			// Stop game resetting turn and players wins
			stopGame(true);
		
			// If a player is still inside the game room, notify them that the game is now stopped
			if (gameRoom.getSize().getUserCount() > 0)
				send("stop", null, gameRoom.getUserList());
		}
	}
}

/**
 * This handler receives the signal that a spectator turned into a player.
 */
function onSpectatorToPlayerEvent(event)
{
	// Check if two players are available and start game
	if (getParentRoom().getSize().getUserCount() == 2)
		startGame();
}
