package com.editev.chess;

import com.editev.util.Enum;
import com.editev.util.Filter;
import com.editev.util.ExceptionWrapper;
import com.editev.chess.piece.*;

/** Represent just the Pieces on the 64 Squares of the chess board as byte indices.
 *
 *  @see See the source <a href="Board.java">here</a>.
 */
public class Board extends Chess implements Cloneable {
    /** The actual 64 squares in an 8x8 array.  Pieces are represented as simple byte indices for efficiency. */
    private final byte[][] squares = {
        { Black.ROOK,   Black.KNIGHT,   Black.BISHOP,   Black.QUEEN,    Black.KING,     Black.BISHOP,   Black.KNIGHT,   Black.ROOK  },
        { Black.PAWN,   Black.PAWN,     Black.PAWN,     Black.PAWN,     Black.PAWN,       Black.PAWN,     Black.PAWN,   Black.PAWN  },
        { NO_PIECE,     NO_PIECE,        NO_PIECE,       NO_PIECE,       NO_PIECE,         NO_PIECE,        NO_PIECE,     NO_PIECE  },   
        { NO_PIECE,     NO_PIECE,        NO_PIECE,       NO_PIECE,       NO_PIECE,         NO_PIECE,        NO_PIECE,     NO_PIECE  },   
        { NO_PIECE,     NO_PIECE,        NO_PIECE,       NO_PIECE,       NO_PIECE,         NO_PIECE,        NO_PIECE,     NO_PIECE  },   
        { NO_PIECE,     NO_PIECE,        NO_PIECE,       NO_PIECE,       NO_PIECE,         NO_PIECE,        NO_PIECE,     NO_PIECE  },   
        { White.PAWN,   White.PAWN,     White.PAWN,     White.PAWN,     White.PAWN,       White.PAWN,     White.PAWN,   White.PAWN  },
        { White.ROOK,   White.KNIGHT,   White.BISHOP,   White.QUEEN,    White.KING,     White.BISHOP,   White.KNIGHT,   White.ROOK  }
    };
    
    /** Get a piece index by Square. */
    public byte    getPieceIndex( Square sq              ) { return squares[ sq.row ][ sq.column ];             }
    
    /** Get a piece index by source of Move. */
    public byte    getPieceIndex( Move   move            ) { return getPieceIndex( move.source );             }
    
    /** Set a piece index by Square. */
    public void    setPieceIndex( Square sq, byte piece  ) {        squares[ sq.row ][ sq.column ] = piece;     }
           
    /** Is there a piece on this square? @return true if there is a piece at this Square */
    public boolean hasPiece( Square sq              ) { return squares[ sq.row ][ sq.column ] != NO_PIECE; }
    
    /** A list of Pieces for each piece index which is
     *  used to translate the indices representing pieces in the Board into Pieces with rules. */    
    public static Piece PIECES[] = {
        null, King.PIECE, Queen.PIECE, Rook.PIECE, Bishop.PIECE, Knight.PIECE, Pawn.Black.PIECE,
        null, King.PIECE, Queen.PIECE, Rook.PIECE, Bishop.PIECE, Knight.PIECE, Pawn.White.PIECE,
    };
    
    /** Gets the Piece at the given square. 
     *  @return the Piece at that square or null if there is no piece at that Square.
     *
     *  @param sq the Square on the Board containing the Piece.
     */
    public Piece getPiece( Square sq ) { return PIECES[ getPieceIndex( sq ) ]; }
    
    /** Gets the Piece being Moved.. 
     *  @return the Piece at the source of the Move or null if there is no piece at the source..
     *
     *  @param sq the Square on the Board containing the Piece.
     */
    public Piece getPiece( Move move ) { return getPiece( move.source ); }

    /** Find a Piece on the board.  
     *  @return a square containing the first matching piece on the board when searched
     *  in row-major order or null if no such piece. */
    public Square findPieceSquare( byte piece ) {
        Square sq   = new Square();
    
        for (sq.row=0; sq.row<8; sq.row++) {                // look through the rows
            for (sq.column=0; sq.column<8; sq.column++) {   // and the columns
                if (getPieceIndex( sq ) == piece) {         // found that piece!
                    return sq;                              // return its location.
                }
            }
        }
        return null;
    }
    
    /** Is the king of this color in check? */
    public boolean inCheck( boolean isWhite ) {
        byte     king       = toColor( isWhite, Black.KING );   // the king for which we are looking!
        Square   square     = findPieceSquare( king );          // the king's location.
        
        if (square == null) {        
            throw new RuntimeException( "Board.resultsInCheck:  no king on the board!" );  // we should never get here.
        }
        
        return isAttacked( isWhite, square );                   // is the king attacked?    
    }

    
    /** Does this move result in check? 
     *  @return true if making this move would result in check. 
     */
    public boolean resultsInCheck( Move move, Piece piece ) {
        boolean  whitesMove = White.is( getPieceIndex( move ) );        // is the move white?
        byte     captured   = piece.applyMoveToBoard( move, this );     // make the move, and store any piece captured
        boolean  inCheck    = inCheck( whitesMove );                    // is the king attacked?

        piece.undoMoveToBoard( move, this, captured );                  // unmove the move regardless.

        return inCheck;                                                 // was the king attacked?
    }
    
    /** Is this square attacked? 
     *  @return true if this row/column square is attacked. 
     */
    public boolean isAttacked( boolean whitesMove, Square square ) {
        Move     move   = new Move();
        Square   source = move.source;
        for ( source.row=0; source.row<8; source.row++) {                         // look through the rows
            for ( source.column=0; source.column<8; source.column++) {            // and the columns
                byte    pieceIndex = getPieceIndex( source );                 // the index of the piece at the fromation.
                boolean whitePiece = White.is( pieceIndex );                // is it a white piece?
                
                if (pieceIndex != NO_PIECE && whitePiece != whitesMove) {   // this piece could attack the square!
                    Piece piece = Board.PIECES[ pieceIndex ];               // the actual piece.
                    piece.firstMoveIndex( move );
                    do {
                        if ( move.target.equals(       square     )         // the piece could be attacking the square!
                         && !piece      .pieceBetween( move, this )         // and there are no pieces in the way!!
                         )
                        {
                            if ( !(piece instanceof Pawn)                   // only Pawns are a special case
                                || piece.isCapture( move, this ) )          // they attack only diagonally.
                            {
                                return true;                                // we did find a capture!
                            }
                        }
                    } while (piece.incrementMoveIndex( move ));             // while there are more possible moves.
                }
            }
        }
        
        return false;   // the square is not attacked!
    }
    
    /** Two boards are equal if all their squares are equal. */
    public boolean equals( Object x ) {
        if (!(x instanceof Board)) return false;
        Board board = (Board) x;
        Square square  = new Square();
        for (square.row=0; square.row<8; square.row++) {
            for (square.column=0; square.column<8; square.column++) {
                if (getPieceIndex( square ) != board.getPieceIndex( square )) {
                    return false;
                }
            }
        }
        return true;
    }

    /** A deep copy clone().  
     *  @return a deep copy clone of the board with entirely new arrays. */
    public Object clone() { return cloneBoard(); }
    
    /** A deep copy clone() that returns a Board. 
     *  @return a deep copy clone of the board with entirely new arrays. */
    public Board cloneBoard() { 
        Board board;
        try                                     { board = (Board) super.clone();   } 
        catch (CloneNotSupportedException e)    { throw new ExceptionWrapper( e ); }    // can't happen!
        for (byte b=0; b<8; b++) {
            board.squares[b] = (byte[]) board.squares[b].clone();
        }
        return board;
    }
    
    /** Copy the contents of another Board into this Board. */
    /*
    public void copyFrom( Board board ) {
        Square sq = new Square();
        for (; sq.row<8; sq.row++) {
            for (sq.column=0; sq.column<8; sq.column++) {
                setPieceIndex( sq, board.getPieceIndex( sq ) );
            }
        }
    }
    */

    /** Character identifiers for the pieces. */
    public static final char[] PIECE_NAMES = {
        '.', 'k', 'q', 'r', 'b', 'n', 'p',
        '.', 'K', 'Q', 'R', 'B', 'N', 'P'
    };
    
    /** @return a String version of the board for debugging purposes. */
    public String toString() {
        StringBuffer buf = new StringBuffer();
        Square sq = new Square();
        for ( sq.row=0; sq.row<8; sq.row++) {                        // look through the rows
            for ( sq.column=0; sq.column<8; sq.column++) {           // and the columns
                buf.append( PIECE_NAMES[ getPieceIndex( sq ) ] );        // the index of the piece at the square.
            }
            buf.append('\n');
        }
        return buf.toString();
    }
}

