package sfs3.extensions.examples.tictactoe.game;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import com.smartfoxserver.entities.data.ISFSArray;
import com.smartfoxserver.entities.data.SFSArray;

/**
 * Class representing the game board.
 */
public final class Board
{
	private final Mark[][] board;
	private int winner;
	
	/**
	 * Initialize the board.
	 * We use a 4x4 matrix, so we can have 1-based indexes (rows and columns)
	 */
	public Board()
    {
		board = new Mark[4][4];
		
		// Add empty mark in all board slots
		reset();
	}
	
	/**
	 * Return the mark contained in a board slot.
	 */
	public Mark getMarkAt(int row, int col) 
	{
		return board[row][col];
	}
	
	/**
	 * Set the mark in a board slot.
	 */
	public void setMarkAt(int row, int col, Mark mark)
	{
		// Validate coordinates
		checkCoords(row, col);
		
		// Set mark
		board[row][col] = mark;
	}
	
	/**
	 * Return the last winner:
	 * - 0: no winner (tie or game still in progress)
	 * - 1: player 1 was the winner
	 * - 2: player 2 was the winner
	 */
	public int getWinner()
    {
	    return winner;
    }
	
	/**
	 * Get the current board state.
	 */
	public State getState(int moveCount)
	{
		Iterator<String> iter = getAllSolutions().iterator();
		State state = State.RUNNING;
		
		while(iter.hasNext())
		{
			String solution = iter.next();
			iter.remove();
			
			// Player 1 wins
			if (solution.equals("111"))
			{
				state = State.END_WITH_WINNER;
				winner = 1;
				break;
			}
			
			// Player 2 wins
			else if (solution.equals("222"))
			{
				state = State.END_WITH_WINNER;
				winner = 2;
				break;
			}
		}
		
		if (winner == 0 && moveCount == 9)
			state = State.END_WITH_TIE;
		
		return state;
	}
	
	/**
	 * Reset the board adding an empty mark in all board slots.
	 */
	public void reset()
	{
		winner = 0;
		
		for (int r = 1; r <= 3; r++)
		{
			Mark[] boardRow = board[r];
			
			for (int c = 1; c <= 3; c++)
			{
				boardRow[c] = Mark.EMPTY;
			}
		}
	}
	
	/**
	 * Validate the passed board slot coordinates.
	 */
	private void checkCoords(int row, int col)
	{
		if (row < 1 || row > 3)
			throw new IllegalArgumentException("Mark row position out of range: " + row);
		
		if (col < 1 || col > 3)
			throw new IllegalArgumentException("Mark column position out of range: " + col);
	}
	
	/**
	 * Get all completed rows, columns and diagonals to check if the game ended.
	 */
	private List<String> getAllSolutions()
	{
		List<String> solutions = new ArrayList<String>();
		
		//--- All rows ------------------------------------------------
		for (int r = 1; r <= 3; r++)
		{
			solutions.add
			(
				String.format
				(
					"%s%s%s", 
					String.valueOf(board[r][1].getId()),
					String.valueOf(board[r][2].getId()),
					String.valueOf(board[r][3].getId())
				)
			);
		}
		
		//--- All columns ---------------------------------------------
		for (int c = 1; c <= 3; c++)
		{
			solutions.add
			(
				String.format
				(
					"%s%s%s", 
					String.valueOf(board[1][c].getId()),
					String.valueOf(board[2][c].getId()),
					String.valueOf(board[3][c].getId())
				)
			);
		}
		
		//--- All diagonals ------------------------------------------
		solutions.add
		(
			String.format
			(
				"%s%s%s", 
				String.valueOf(board[1][1].getId()),
				String.valueOf(board[2][2].getId()),
				String.valueOf(board[3][3].getId())
			)
		);
		
		solutions.add
		(
			String.format
			(
				"%s%s%s", 
				String.valueOf(board[1][3].getId()),
				String.valueOf(board[2][2].getId()),
				String.valueOf(board[3][1].getId())
			)
		);
		
		return solutions;
	}
	
	/**
	 * Return the game board as a SFSArray.
	 * As SFSArrays use 0-based indexes and we can't add entries starting from index 1,
	 * the client receiving the data will need to transform it to rows/columns. 
	 */
	public ISFSArray toSFSArray()
	{
		ISFSArray sfsa = new SFSArray();
		
		for (int r = 1; r <= 3; r++)
			sfsa.addIntArray(getRowAsList(r));
		
		return sfsa;
	}
	
	private List<Integer> getRowAsList(int r)
	{
		List<Integer> row = new ArrayList<Integer>();
		
		for (int c = 1; c <= 3; c++)
			row.add( board[r][c].getId() );
		
		return row;
	}
}
