
import java.util.*;

// We are making this an *immutable* class.  The state that an object has when constructed is its state permanently.
// Instead of making changes to objects we create new ones (with attemptMove() and the constructor).

public class GameState {
	Square[] boardSquares; //Tracks the game board as a 1D array of Square objects (effectively 12 x 12) with 2 layers of sentinel squares all around.
	boolean isWhitesTurn;  //Knows whose turn it is

	public GameState() {
		//Set turn to white
		isWhitesTurn = true;

		//Initialize squares with legal starting position.
		boardSquares = new Square[] {
			new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS),
			new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS),
			new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.ROOK, true), new Square(Square.KNIGHT, true), new Square(Square.BISHOP, true), new Square(Square.KING, true), new Square(Square.QUEEN, true), new Square(Square.BISHOP, true), new Square(Square.KNIGHT, true), new Square(Square.ROOK, true), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS),  //white
			new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.PAWN, true), new Square(Square.PAWN, true), new Square(Square.PAWN, true), new Square(Square.PAWN, true), new Square(Square.PAWN, true), new Square(Square.PAWN, true), new Square(Square.PAWN, true), new Square(Square.PAWN, true), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), //white pawns

			new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS),
			new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS),
			new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS),
			new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.EMPTY), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS),

			new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.PAWN, false), new Square(Square.PAWN, false), new Square(Square.PAWN, false), new Square(Square.PAWN, false), new Square(Square.PAWN, false), new Square(Square.PAWN, false), new Square(Square.PAWN, false), new Square(Square.PAWN, false), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), //white pawns
			new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.ROOK, false), new Square(Square.KNIGHT, false), new Square(Square.BISHOP, false), new Square(Square.KING, false), new Square(Square.QUEEN, false), new Square(Square.BISHOP, false), new Square(Square.KNIGHT, false), new Square(Square.ROOK, false), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS),  //black
			new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS),
			new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS), new Square(Square.OUT_OF_BOUNDS),
		};

	}

	//Construct a new GameState based on a previous state and a move made on that previous state.
	private GameState(GameState prevState, Move move) {
		ArrayList<Move> moves = prevState.getAllPossibleMoves();
		if (!moves.contains(move)) {
			throw new IllegalArgumentException("Move "+move+" not available for given game state.");
		}

		isWhitesTurn = !prevState.isWhitesTurn;
		boardSquares = new Square[prevState.boardSquares.length];
		for(int i = 0; i < boardSquares.length; i++) {
			boardSquares[i] = new Square(prevState.boardSquares[i]);
		}
		boardSquares[move.getDestIndex()] = new Square(boardSquares[move.getSourceIndex()]);
		boardSquares[move.getSourceIndex()] = new Square(Square.EMPTY);

		//Queening (no underpromotion for now)
		if ((move.getDestRow() == 0 || move.getDestRow() == 7) && boardSquares[move.getDestIndex()].getType() == Square.PAWN) {
			boolean w = boardSquares[move.getDestIndex()].isWhite();
			boardSquares[move.getDestIndex()] = new Square(Square.QUEEN, w);  //Make that pawn a queen
		}

		boardSquares[move.getDestIndex()].setHasMoved(); //For knowledge about moving pawns two squares and castling.

	}


	// zero-based index for col and row.  Note that -1 and -2 are valid indices into the array, just OUT_OF_BOUNDS.
	public Square getSquare(int col, int row) {
		return boardSquares[12*2 + 2 + col + row * 12];
	}

	public GameState getStateAfterMove(Move m) {
		try {
			return new GameState(this, m); //Create a new object based on this one, update the board state accordingly.
		}
		catch (IllegalArgumentException e) {
			return null; //If it is not a legal move, return null.
		}
	}

	public ArrayList<Move> getAllPossibleMoves() {
		ArrayList<Move> moves = new ArrayList<Move>();

		//Loop through all squares
		for(int index = 0; index < boardSquares.length; index++) {
			int type = boardSquares[index].getType();
			boolean w = boardSquares[index].isWhite();
			boolean pieceMoved = boardSquares[index].isMoved();
			if (type == Square.OUT_OF_BOUNDS) continue;
			if (type == Square.EMPTY) continue;

			//For each square, find all possible places that piece can move to.
			if (type == Square.KING) moves.addAll(findAllMovesFor(index, new int[] {1,-1,12,-12,13,-13,11,-11}, 1, true, true));
			if (type == Square.QUEEN) moves.addAll(findAllMovesFor(index, new int[] {1,-1,12,-12,13,-13,11,-11}, 99, true, true));
			if (type == Square.BISHOP) moves.addAll(findAllMovesFor(index, new int[] {13,-13,11,-11}, 99, true, true));
			if (type == Square.ROOK) moves.addAll(findAllMovesFor(index, new int[] {1,-1,12,-12}, 99, true, true));
			if (type == Square.KNIGHT) moves.addAll(findAllMovesFor(index, new int[] {10,14,23,25,-10,-14,-23,-25}, 1, true, true));

			//white
			if (type == Square.PAWN && w && pieceMoved) moves.addAll(findAllMovesFor(index, new int[] {12}, 1, true, false));
			if (type == Square.PAWN && w && !pieceMoved) moves.addAll(findAllMovesFor(index, new int[] {12}, 2, true, false));
			if (type == Square.PAWN && w) moves.addAll(findAllMovesFor(index, new int[] {11, 13}, 1, false, true));

			//black
			if (type == Square.PAWN && !w && pieceMoved) moves.addAll(findAllMovesFor(index, new int[] {-12}, 1, true, false));
			if (type == Square.PAWN && !w && !pieceMoved) moves.addAll(findAllMovesFor(index, new int[] {-12}, 2, true, false));
			if (type == Square.PAWN && !w) moves.addAll(findAllMovesFor(index, new int[] {-11, -13}, 1, false, true));
		}

		return moves;
	}

	///movementDistance is the number of times that piece can move in the given directions.
	private ArrayList<Move> findAllMovesFor(int index, int[] directions, int movementDistance, boolean canMove, boolean canCapture) {
		ArrayList<Move> moves = new ArrayList<Move>();
		if (boardSquares[index].isWhite() != isWhitesTurn) return moves; //don't move other person's pieces!

		//loop through the directions, see if they work - return the resulting arraylist.
		for(int dir : directions) {
			int destIndex = index;
			int numMoves = movementDistance;
			do {
				destIndex += dir;
				int type = boardSquares[destIndex].getType();
				if (type == Square.OUT_OF_BOUNDS) break;
				if (type == Square.EMPTY && canMove ||
					type != Square.EMPTY && type != Square.OUT_OF_BOUNDS && boardSquares[destIndex].isWhite() != isWhitesTurn && canCapture) {
					moves.add(new Move(index, destIndex));
				}
				numMoves--;
			} while(boardSquares[destIndex].getType() == Square.EMPTY && numMoves > 0);

		}
		return moves;
	}

	public boolean isWhitesTurn() {
		return isWhitesTurn;
	}

	// TODO: should also include information regarding check, checkmate, and stalemate.
	public String getFeedback() {
		return isWhitesTurn ? "White's turn" : "Black's turn";
	}
}
