package sfs3.extensions.examples.tictactoe;

import com.smartfoxserver.core.SFSEventType;
import com.smartfoxserver.entities.User;
import com.smartfoxserver.entities.data.ISFSObject;
import com.smartfoxserver.entities.data.SFSObject;
import com.smartfoxserver.extensions.SFSExtension;

import sfs3.extensions.examples.tictactoe.game.Board;
import sfs3.extensions.examples.tictactoe.game.State;
import sfs3.extensions.examples.tictactoe.handlers.MoveHandler;
import sfs3.extensions.examples.tictactoe.handlers.OnSpectatorToPlayerHandler;
import sfs3.extensions.examples.tictactoe.handlers.OnUserGoneHandler;
import sfs3.extensions.examples.tictactoe.handlers.ReadyHandler;
import sfs3.extensions.examples.tictactoe.handlers.RestartHandler;

public class TicTacToeExtension extends SFSExtension
{
	private Board gameBoard;
	private User whoseTurn;
	private volatile boolean gameStarted;
	private int moveCount;
	private int[] wins;
	
	/**
	 * Initialize the Extension; set initial state and add request and event listeners.
	 */
	@Override
	public void init()
	{
		trace("TicTacToe Game Extension launched");
		
		// Initialize properties
		moveCount = 0;
		wins = new int[] {0,0,0};
		gameBoard = new Board();
		
		// Add request handlers
	    addRequestHandler("ready", ReadyHandler.class);
	    addRequestHandler("move", MoveHandler.class);
	    addRequestHandler("restart", RestartHandler.class);
	    
	    // Register server event handlers
	    addEventHandler(SFSEventType.USER_DISCONNECT, OnUserGoneHandler.class);
	    addEventHandler(SFSEventType.USER_LEAVE_ROOM, OnUserGoneHandler.class);
	    addEventHandler(SFSEventType.SPECTATOR_TO_PLAYER, OnSpectatorToPlayerHandler.class);
	}
	
	/**
	 * Destroy the Extension.
	 */
	@Override
	public void destroy() 
	{
		super.destroy();
		
		trace("TicTacToe Game Extension destroyed");
	}
	
	/**
	 * Return reference to game board, accessed by request/event handlers.
	 */
	public Board getGameBoard()
    {
	    return gameBoard;
    }
	
	/**
	 * Return game running state, accessed by request/event handlers.
	 */
	public boolean isGameStarted()
	{
		return gameStarted;
	}
	
	/**
	 * Return active player, accessed by request/event handlers.
	 */
	public User getWhoseTurn()
    {
	    return whoseTurn;
    }
	
	/**
	 * Return total player wins, accessed by request/event handlers.
	 */
	public int getPlayerWins(int playerId)
	{
		return wins[playerId];
	}
	
	/**
	 * Return the id (1 or 2) of the last winner.
	 * Can be 0 if the game is still in progress or it ended with a tie.
	 */
	public int getLastWinnerId()
	{
		return gameBoard.getWinner();
	}
	
	/**
	 * Start the game and send a notification to all users in the Room (both players and spectators).
	 */
	public void startGame()
	{
		if (gameStarted)
			throw new IllegalStateException("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
		ISFSObject 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).
	 */
	public void stopGame(boolean 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. 
	 */
	public void 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).
	 */
	private void checkGameOver()
	{
		State state = gameBoard.getState(moveCount);
		
		if (state != State.RUNNING)
		{
			trace("Game ended");
			
			// Stop game
			stopGame(false);
			
			// Get winner id
			int winnerId = getLastWinnerId();
			
			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");
			}
			
			ISFSObject outParams = new SFSObject();
			outParams.putInt("w", winnerId);
			
			// Send update to clients
			send("over", outParams, getParentRoom().getUserList());
		}
	}
}
