package com.editev.chess.piece;

import java.util.NoSuchElementException;
import java.util.Enumeration;
import com.editev.util.Enum;
import com.editev.chess.Move;
import com.editev.chess.Board;
import com.editev.chess.Chess;
import com.editev.chess.Square;
import com.editev.chess.Game;

/** Contains a list of possible piece moves as byte offsets, 
 *  and encompasses all the rules about legal moves and actually performing 
 *  the move as an operation on the Game.
 *
 *  @see See the source here.
 */
abstract public class Piece extends Chess {

    /** Is there a piece between the "from" and "target" squares? 
     *  Only the Knight overrides this method. */
    public boolean pieceBetween( Move move, Board board ) {    
        byte c       = getColumnOffset( move );   // number of columns and rows to check.
        byte r       = getRowOffset(    move );
        
        byte squares = (byte) Math.max( Math.abs( c ), 
                                        Math.abs( r ) );   // number of squares between from and to.
                                                         
        if (squares > 1) {                     // there might be pieces between!
            byte dc = (byte) (c / squares);    // increment for the col and row...
            byte dr = (byte) (r / squares);    // dc and dr can only be 0, +1 or -1.
            
            Square sq = new Square(        // increment through all squares between
                (byte) (move.source.column+dc),   // first column between
                (byte) (move.source.row   +dr)    // first row between.
            );
            
            for (byte s=1;          // first square
                 s < squares;       // up to but not including the end.
                 sq.column += dc,  // increment column, row, square
                 sq.row    += dr,
                 s++) 
            {
                if (board.hasPiece( sq )) return true; // we found a piece in the way!
            }
        }
        return false;   // we found no pieces between "from" and "to".
    }

    /** Adjust the board's pieces only.  @return the captured piece if any.
     * 
     *  Only the King and Pawn override this method. */
    public byte applyMoveToBoard( Move move, Board board ) {
        byte toPiece = board.getPieceIndex( move.target );              // save the captured piece.

        board.setPieceIndex( move.target, board.getPieceIndex( move.source ) );   // move the piece to "target"...
        board.setPieceIndex( move.source, NO_PIECE                           );            // from "from".

        return toPiece;                                                 // return the captured piece on  
    }
    
    /** Undo a move, using the piece captured, if any.  
     * 
     *  Only the King and Pawn override this method. */
    public void undoMoveToBoard( Move move, Board board, byte captured ) {  
        board.setPieceIndex( move.source, board.getPieceIndex( move.target )   );   // undo the move.
        board.setPieceIndex( move.target,   captured                    );   // replace any captured pieces.
    }
    
    /** Adjust the board's game, which is everything that isn't the board's squares: 
     *  handles promotions, e.p., castling rules, 50 move rule.   We DO need the Game and
     *  and not just the game because we need to see if the move is a capture...  
     * 
     *  The King, the Pawn and the Rook override this method. */
    public void applyMoveToState( Move move, Game game ) {
        game.incrementMoves();
        game.clearEP();
    }
    
    /** Go to the first move Index.
     *  @return false if there are no more possible moves to go to. 
     * 
     *  Only the Pawn overrides this method. */
    public void firstMoveIndex( Move move ) { setMoveIndex( move, (byte) 0 ); }

    /** Go to a specific move index.
     *  @return false if there are no more possible moves to go to. 
     * 
     *  Only the Pawn overrides this method. */
    public void setMoveIndex( Move move, byte index ) {
        move.index         = index;
        move.target.column = (byte) (move.source.column + getColumnOffset( move ) );
        move.target.row    = (byte) (move.source.row    + getRowOffset(    move ) );
        move.resetPromotion();
    }
    
    /** Go to next possible move.
     *  @return false if there are no more possible moves to go to. 
     * 
     *  Only the Pawn overrides this. */
    public boolean incrementMoveIndex( Move move ) {
        if (!moreMoves( move ) ) return false;
        setMoveIndex( move, ++move.index );
        return true;
    }
    
    /** Any more possible moves?
     *  @return true if there are more possible moves after this move. 
     * 
     *  Only the Pawn overrides this. */
    public boolean moreMoves( Move move              ) { return move.index < (moves.length-1);  }
    
    /** Is this move a capture on this board?
     *  @return whether this move is a capture for this board.
     * 
     *  Only the Pawn overrides this, for e.p. captures and to prevent capturing by advancing straight */
    public boolean isCapture( Move move, Board board ) { return board.hasPiece( move.target ); }
    
    /** Is this move irreversible?
     *  @return true if this move restarts the 50 move rule.
     * 
     *  Only the Pawn overrides this.
     */
    public boolean isIrreversible( Move move, Board board ) { return isCapture( move, board ); }
    
    /** Is this illegal because of a piece-specific rule?
     *  @return true if the piece makes this move illegal for this game.
     * 
     *  We only override this for King and Pawn. */
    public boolean isIllegal( Move move, Game  game  ) { return false; }

    /** The moves describe all the possible moves in left-to-right, top-to-bottom order. */
    public final  byte[][] moves;
    public Piece( byte[][] moves ) { this.moves = moves; }

    /** Convenience function to translate a move's index into a column offset from the move table.*/
    public final byte getColumnOffset( Move move ) { return moves[ move.index ][ 0 ]; }

    /** Convenience function to translate a move's index into a row offset from the move table.*/
    public final byte getRowOffset(    Move move ) { return moves[ move.index ][ 1 ]; }    
}