import java.util.ArrayList;
import java.util.Set;
import java.util.LinkedHashSet;

public class Checkerboard {
    
    private final boolean debug = false;
    
    private final int empty = 0;
    private final int pieceMask = 3;
    private final int addKing = 3;
    private final int numSquares = 32;
    private final int numStartingPieces = 12;
    
    private int currentPlayer;
    private boolean turnRepeating;
    private int repeatingRow;
    private int repeatingCol;
    private int board[];
    private ArrayList<Move> moveSequence;
    
    // Useful public constants
    public final static int BLACK = 1;
    public final static int RED = 2;
    
    // Pre: player == RED or BLACK
    // Post: If RED, returns BLACK; if BLACK, returns RED
    public static int opponent(int player) {
        checkPlayer(player);
        return (player == RED) ? BLACK : RED;
    }
    
    // Invariants:
    //   minRow() < maxRow(); minCol() < maxCol()
    //   For all row, col s.t. minRow() <= row <= maxRow(); 
    //     minCol() <= col <= maxCol(), exactly one of the "pieceAt" 
    //     methods returns true.
    //   if (row + col) % 2 == 0, then noPieceAt (row, col) == true
    //   (Such a square will also be colored black when drawn)
    //   !gameOver() if and only if getNextBoards().size() > 0
    //   getNextBoards().size() == getCurrentPlayerMoves().size()
    
    // Pre: None
    // Post: Red pieces in top three rows; black pieces in bottom three rows;
    //       no pieces in middle two rows
    public Checkerboard () {
        board = new int[numSquares];
        newGame();
    }
    
    public ArrayList<Checkerboard> getNextBoards() {
        ArrayList<Checkerboard> futures = new ArrayList<Checkerboard>();
        Set<Move> moves = getCurrentPlayerMoves ();
        for (Move m: moves) {
            Checkerboard c = duplicate();
            c.move(m);
            futures.add(c);
        }
        return futures;
    }
    
    // Pre: None
    // Post: Returns a duplicate of this Checkerboard
    public Checkerboard duplicate () {
        Checkerboard dup = new Checkerboard();
        for (int i = 0; i < numSquares; ++i) {
            dup.board[i] = board[i];
        }
        dup.currentPlayer = currentPlayer;
        dup.repeatingRow = repeatingRow;
        dup.repeatingCol = repeatingCol;
        dup.turnRepeating = turnRepeating;
        dup.moveSequence = new ArrayList<Move>();
        for (Move m: moveSequence) {dup.moveSequence.add(m);}
        return dup;
    }
    
    // Pre: None
    // Post: Red pieces in top three rows; black pieces in bottom three rows;
    //       no pieces in middle two rows
    public void newGame () {
        for (int i = 0; i < numStartingPieces; ++i) {
            board[i] = RED;
        }
        int blackStart = numSquares - numStartingPieces;
        for (int i = numStartingPieces; i < blackStart; ++i) {
            board[i] = empty;
        }
        for (int i = blackStart; i < numSquares; ++i) {
            board[i] = BLACK;
        }
        currentPlayer = BLACK;
        turnRepeating = false;
        moveSequence = new ArrayList<Move>();
    }
    
    public int minRow () {return 0;}
    public int maxRow () {return 7;}
    public int minCol () {return 0;}
    public int maxCol () {return 7;}
    
    public int numRows () {return maxRow() - minRow() + 1;}
    public int numCols () {return maxCol() - minCol() + 1;}
    
    public int getNumMovesMade() {
        return moveSequence.size();
    }
    
    // Pre: 0 <= n < getNumMovesMade()
    // Post: Returns nth move
    public Move getNthMove(int n) {
        return moveSequence.get(n);
    }
    
    // Pre: getNumMovesMade() >= 1
    // Post: Returns most recent move
    public Move getLastMove() {
        return moveSequence.get(getNumMovesMade() - 1);
    }
    
    // Pre: getRedMoves().contains (move) || getBlackMoves.contains (move)
    // Post: Piece is removed from move's start and appears at move's end
    //       If move hops over an opposing piece, the opposing piece is 
    //       removed.  If the move results in another legal capture, the
    //       current player gets another turn.  Otherwise, the turn switches
    //       to the other player.
    public void move (Move move) {
        int start = getIndex (move.getStartRow(), move.getStartCol());
        int end = getIndex (move.getEndRow(), move.getEndCol());
        board[end] = board[start];
        board[start] = empty;
        if (!kingAt (move.getEndRow(), move.getEndCol()) &&
	    canKing (move.getEndRow(), move.getEndCol())) {
            makeKing (move.getEndRow(), move.getEndCol());
        }
        boolean changeTurn = !move.isCapture();
        
        if (!changeTurn) {
            int captureRow = getCaptureRow (move);
            int captureCol = getCaptureCol (move);
            board[getIndex (captureRow, captureCol)] = empty;
            
            if (pieceCanStillCapture (move.getEndRow(), move.getEndCol())) {
                repeatingRow = move.getEndRow();
                repeatingCol = move.getEndCol();
                turnRepeating = true;
                
            } else {
                changeTurn = true;
            }
            
        } 
        
        if (changeTurn) {
            currentPlayer = (currentPlayer == BLACK) ? RED : BLACK;
            turnRepeating = false;
        }
        
        moveSequence.add(move);
    }
    
    // Pre: None
    // Post: Returns true if 
    //       minRow() <= row <= maxRow(); minCol() <= col <= maxCol()
    public boolean legal (int row, int col) {
        return (row >= minRow() && row <= maxRow() && 
		col >= minCol() && col <= maxCol());
    }
    
    // Pre: None
    // Post: Returns code for current player, either BLACK or RED
    public int getCurrentPlayer () {
        return currentPlayer;
    }
    
    // Pre: player == BLACK or player == RED
    // Post: Returns true if it is player's turn
    public boolean isTurnFor(int player) {
        checkPlayer(player);
        return (currentPlayer == player);
    }
    
    // Pre: None
    // Post: Returns true if the current player gets another turn
    public boolean turnIsRepeating() {return turnRepeating;}
    
    // Pre: None
    // Post: Returns an array of legal moves for the current player
    public Set<Move> getCurrentPlayerMoves () {
        return getLegalMoves (getCurrentPlayer());
    }
    
    // Pre: player == RED || player == BLACK
    // Post: Returns an appropriate array of legal moves
    public Set<Move> getLegalMoves (int player) {
        checkPlayer(player);
        Set<Move> captureMoves = allCaptureMoves (player);
        if (captureMoves.size() > 0) {return captureMoves;}
        return allRegularMoves (player);
    }
    
        // Pre: player is red or black
    // Post: Returns all legal capture moves for player
    public Set<Move> allCaptureMoves (int player) {
        Set<Move> captureMoves = new LinkedHashSet<Move>();
        if (turnRepeating) {
            addCaptureMoves (captureMoves, repeatingRow, repeatingCol);
            
        } else {
            for (int i = 0; i < numSquares; ++i) {
                int row = getRow(i);
                int col = getCol(i);
                if (pieceAt (row, col, player)) {
                    addCaptureMoves (captureMoves, row, col);
                }
            }
        }
        
        return captureMoves;
    }
    
    // Pre: player is red or black
    // Post: Returns all legal regular moves for player
    public Set<Move> allRegularMoves (int player) {
        Set<Move> regularMoves = new LinkedHashSet<Move>();
        if (!turnRepeating) {
            for (int i = 0; i < numSquares; ++i) {
                int row = getRow(i);
                int col = getCol(i);
                if (debug) {System.out.println ("row: " + row + " col: " + col);}
                if (pieceAt (row, col, player)) {
                    if (debug) {System.out.println ("adding regular...");}
                    addRegularMoves (regularMoves, row, col);
		} else if (debug) {System.out.println ("Not considering");}
            }
        }
        return regularMoves;
    }
    
    // Pre: player == RED or BLACK
    // Post: Returns true if it is opponent(player)'s turn and it can't move
    //       Returns false otherwise
    public boolean playerWins(int player) {
        checkPlayer(player);
        int otherPlayer = opponent(player);
        return isTurnFor(otherPlayer) && (getLegalMoves(otherPlayer).size() == 0);
    }
    
    // Pre: None
    // Post: Returns true if the game is over; returns false otherwise
    public boolean gameOver () {return playerWins(RED) || playerWins(BLACK);}
    
    // Pre: legal (row, col)
    // Post: Returns true if the current player can still capture
    //       from the position that terminated the previous move
    public boolean pieceCanStillCapture (int row, int col) {
        if (!pieceAt (row, col, getCurrentPlayer())) {return false;}
        Set<Move> captureMoves = new LinkedHashSet<Move>();
        addCaptureMoves (captureMoves, row, col);
        return (captureMoves.size() > 0);
    }
    
    // Pre: legal (row, col)
    // Post: Returns true if square is black
    public boolean blackSquareAt (int row, int col) {
        return (row + col) % 2 == 0;
    }
    
    // Pre: legal (row, col)
    // Post: Returns true if square is red
    public boolean redSquareAt (int row, int col) {
        return !blackSquareAt (row, col);
    }
    
    // Pre: legal (row, col)
    // Post: Returns true if piece there; false otherwise    
    public boolean pieceAt (int row, int col, int piece) {
        return redSquareAt (row, col) && 
	    board[getIndex (row, col)] % pieceMask == piece;
    }
    
    // Pre: legal (row, col)
    // Post: Returns true if red piece there; false otherwise
    public boolean redPieceAt (int row, int col) {
        return pieceAt (row, col, RED);
    }
    
    // Pre: legal (row, col)
    // Post: Returns true if black piece there; false otherwise
    public boolean blackPieceAt (int row, int col) {
        return pieceAt (row, col, BLACK);
    }
    
    // Pre: legal (row, col)
    // Post: Returns true if no piece there; false otherwise
    public boolean noPieceAt (int row, int col) {
        return blackSquareAt (row, col) || board[getIndex (row, col)] == empty;
    }
    
    // Pre: legal (row, col)
    // Post: Returns true if there is a piece and it is a king
    public boolean kingAt (int row, int col) {
        return !noPieceAt (row, col) && 
	    board[getIndex (row, col)] / pieceMask > 0;
    }
    
    // Pre: color == RED or BLACK
    // Post: Returns total number of pieces of the specified color
    public int numPiecesOf (int color) {
        checkPlayer(color);
        int count = 0;
        for (int row = minRow(); row <= maxRow(); ++row) {
            for (int col = minCol(); col <= maxCol(); ++col) {
                if (pieceAt (row, col, color)) {++count;}
            }
        }
        return count;
    }
    
    // Pre: color == RED or BLACK
    // Post: Returns total number of kings of the specified color
    public int numKingsOf (int color) {
        checkPlayer(color);
        int count = 0;
        for (int row = minRow(); row <= maxRow(); ++row) {
            for (int col = minCol(); col <= maxCol(); ++col) {
                if (pieceAt (row, col, color) && kingAt (row, col)) {++count;}
            }
        }
        return count;
    }
    
    // Private methods
    
    // Pre: player == RED || player == BLACK
    // Post: None
    private static void checkPlayer(int player) {
        if (player != RED && player != BLACK) {
            throw new IllegalArgumentException("A player must be RED or BLACK");
        }
    }
    
    // Pre: move != null
    // Post: Returns average of start and end row
    private int getCaptureRow (Move move) {
        return (move.getStartRow() + move.getEndRow()) / 2;
    }
    
    // Pre: move != null
    // Post: Returns average of start and end column
    private int getCaptureCol (Move move) {
        return (move.getStartCol() + move.getEndCol()) / 2;
    }
    
    // Pre: captureMoves != null; legal (row, col); !noPieceAt (row, col)
    // Post: All legal capture moves for this player at (row, col) are 
    //       added to captureMoves
    private void addCaptureMoves (Set<Move> captureMoves, int row, int col) {
        Set<Move> candidates = getCandidateCaptures (row, col);
        for (Move m: candidates) {
            int captureRow = getCaptureRow (m);
            int captureCol = getCaptureCol (m);
            if (legal (m.getEndRow(), m.getEndCol()) &&
                noPieceAt (m.getEndRow(), m.getEndCol()) &&
            ((redPieceAt (m.getStartRow(), m.getStartCol()) &&
            blackPieceAt (captureRow, captureCol)) ||
            (blackPieceAt (m.getStartRow(), m.getStartCol()) &&
            redPieceAt (captureRow, captureCol)))) {
                captureMoves.add (m);
            }		
        }
    }
    
    // Pre: regularMoves != null; legal (row, col); !noPieceAt (row, col)
    // Post: All legal non-capturing moves for this player at (row, col) are 
    //       added to regularMoves
    private void addRegularMoves (Set<Move> regularMoves, int row, int col) {
        Set<Move> candidates = getCandidateRegularMoves (row, col);
        for (Move m: candidates) {
            if (legal (m.getEndRow(), m.getEndCol()) &&
            noPieceAt (m.getEndRow(), m.getEndCol())) {
                regularMoves.add (m);
            }
        }
    }
    
    // Pre: legal (row, col); piece at (row, col); 
    // Post: Returns all possible candidate moves
    //       For a non-king, the candidate moves are the two adjacent diagonals
    //       in the next row
    //       For a king, the adjacent diagonals in the preceding row are also
    //       included
    private Set<Move> getCandidateRegularMoves (int row, int col) {
        Move upLeft = new Move (this, row, col, row - 1, col - 1);
        Move upRight = new Move (this, row, col, row - 1, col + 1);
        Move downLeft = new Move (this, row, col, row + 1, col - 1);
        Move downRight = new Move (this, row, col, row + 1, col + 1);
        return getCandidateMoves (row, col, upLeft, upRight, 
        downLeft, downRight);
    }
    
    // Pre: legal (row, col); piece at (row, col); 
    // Post: Returns all possible candidate moves
    //       For a non-king, the candidate moves are the two adjacent diagonals
    //       two rows ahead
    //       For a king, the adjacent diagonals two rows behind are also
    //       included
    private Set<Move> getCandidateCaptures (int row, int col) {
        Move upLeft = new Move (this, row, col, row - 2, col - 2);
        Move upRight = new Move (this, row, col, row - 2, col + 2);
        Move downLeft = new Move (this, row, col, row + 2, col - 2);
        Move downRight = new Move (this, row, col, row + 2, col + 2);
        return getCandidateMoves (row, col, upLeft, upRight, 
        downLeft, downRight);
    }
    
    // Pre: legal (row, col); piece at (row, col); 
    //      upLeft, upRight, downLeft, downRight make sense semantically
    //      and start at (row, col)
    // Post: Returns all possible candidate moves
    //       For a non-king, the candidate moves are the two forward moves
    //       For a king, the backward moves are also included
    private Set<Move> getCandidateMoves (int row, int col, Move upLeft,
    Move upRight, Move downLeft,
    Move downRight) {
        Set<Move> moves = new LinkedHashSet<Move>();
        if (kingAt (row, col)) {
            moves.add (downRight);
            moves.add (downLeft);
            moves.add (upLeft);
            moves.add (upRight);
            
        } else if (redPieceAt (row, col)) {
            moves.add (downRight);
            moves.add (downLeft);
            
        } else if (blackPieceAt (row, col)) {
            moves.add (upRight);
            moves.add (upLeft);
            
        } else {
            System.out.println ("Player does not exist at row: " + row 
            + " col: " + col);
        }
        return moves;
    }
    
    // Pre: None
    // Post: Returns true if:
    //      (row == maxRow() && redPieceAt (row, col)) || 
    //      (row == minRow() && blackPieceAt (row, col)) && legal (row, col)
    private boolean canKing (int row, int col) {
        return (row == maxRow() && redPieceAt (row, col)) || 
	    (row == minRow() && blackPieceAt (row, col)) && legal (row, col);
    }
    
    // Pre: canKing (row, col)
    // Post: Transforms piece into a king
    private void makeKing (int row, int col) {
        board[getIndex (row, col)] += addKing;
    }
    
    // Pre: legal (row, col)
    // Post: Returns appropriate array index for board
    private int getIndex (int row, int col) {
        return (row * 8 + col) / 2;
    }
    
    // Pre: 0 <= index < numSquares
    // Post: Returns row corresponding to index
    private int getRow (int index) {
        return index / 4;
    }
    
    // Pre: 0 <= index < numSquares
    // Post: Returns column corresponding to index
    private int getCol (int index) {
        return ((index % 4) * 2) + (1 - (getRow (index) % 2));
    }
}
