// Copyright 1994 by Jon Dart.  All Rights Reserved.

#include "stdafx.h"
#include "board.h"
#include "chessio.h"
#include "rmove.h"
#include "constant.h"
#include "bhash.h"
#include "util.h"
#include "bearing.h" 
#include "debug.h"
#include <strstrea.h>
#include <ctype.h>
#include <memory.h>

enum {BoardSize = 64};

void Board::set_secondary_vars()
{
    int index[2], i;

    index[Black] = index[White] = 0;

    for (i=0;i<16;i++)
       my_PiecePos[i][White] = my_PiecePos[i][Black] = Square::Invalid();
    my_Material[White].clear();
    my_Material[Black].clear();
    pawn_bits[White].clear();
    pawn_bits[Black].clear();
    for (i=0;i<8;i++)
    {
      my_PFileCount[i][White] = my_PFileCount[i][Black] = 0;
      my_RFileCount[i][White] = my_RFileCount[i][Black] = 0;
    }
    for (i=0;i<BoardSize;i++)
    {
        Square sq(i);
        if (!my_Contents[sq].IsEmpty())
        {
            const Piece piece = my_Contents[sq];
            const ColorType color = piece.Color();
#ifdef _MSC_VER
            // workaround for Microsoft C++ compiler bug
            int &x = (color == White) ? index[White] : index[Black];
            my_PiecePos[x][color] = sq;
            ++x;
#else
            my_PiecePos[index[color]++][color] = sq;
#endif
            my_Material[color].add_piece(piece.Type());
            if (piece.Type() == Piece::King)
               my_KingPos[color] = sq;
            if (piece.Type() == Piece::Pawn)
            {
               my_PFileCount[sq.File()-1][color]++;
	       pawn_bits[color].set(sq);
            }
            else if (piece.Type() == Piece::Rook)
            {
               my_RFileCount[sq.File()-1][color]++;
            }
        }
    }
    my_HashCode = Board_Hash::HashCode(*this);
    my_attacks.clear();
    my_attacks.compute_attacks(*this,White);
    my_attacks.compute_attacks(*this,Black);
}

void Board::Reset()
{
    static Piece::PieceType pieces[] =
    {
       Piece::Rook,
       Piece::Knight,
       Piece::Bishop,
       Piece::Queen,
       Piece::King,
       Piece::Bishop,
       Piece::Knight,
       Piece::Rook
    };

    my_Side = White;

    for (int i=0;i<BoardSize;i++)
    {
       const Square sq(i);
       if (sq.Rank(White) == 1)
           my_Contents[sq] = Piece( pieces[sq.File()-1], White );
       else if (sq.Rank(Black) == 1)
           my_Contents[sq] = Piece( pieces[sq.File()-1], Black );
       else if (sq.Rank(White) == 2)
           my_Contents[sq] = Piece( Piece::Pawn, White);
       else if (sq.Rank(Black) == 2)
           my_Contents[sq] = Piece( Piece::Pawn, Black);
       else
           my_Contents[sq] = Piece::EmptyPiece();
    }
    my_EnPassantSq[White] = my_EnPassantSq[Black] = Square::Invalid();
    my_CastleStatus[White] = my_CastleStatus[Black] = CanCastleEitherSide;
    set_secondary_vars();
}

Board::Board()
{
    Reset();
}

#ifdef _DEBUG
const Piece &Board::operator[]( const Square sq ) const
{
     ASSERT(sq.OnBoard());
     return my_Contents[sq];
}
#endif

Board::~Board()
{
}

void Board::UpdateCastleStatus( const ColorType side, 
                                const Square sq )
// after a move of or capture of the rook on 'sq', update castle status
// for 'side'
{
     CastleType cs = CastleStatus(side);
     if ((cs != CastledKSide) && (cs != CastledQSide))
     {
         if (sq.File() == 1) // Queen Rook moved or captured
         {
             if (cs == CanCastleEitherSide)
                   my_CastleStatus[side] = CanCastleKSide;
             else if (cs == CanCastleQSide)
                   my_CastleStatus[side] = CantCastleEitherSide;
         }
         else if (sq.File() == 8) // King Rook moved or captured
         {
             if (cs == CanCastleEitherSide)
                   my_CastleStatus[side] = CanCastleQSide;
             else if (cs == CanCastleKSide)
                   my_CastleStatus[side] = CantCastleEitherSide;
         }
     }
}

int Board::Direction( const Square &sq1, const Square &sq2 ) const
{
     if (sq2 == sq1)
        return 0;
     int offset;
     int abs = (sq2 > sq1) ? (int)sq2 - (int)sq1 : (int)sq1 - (int)sq2;
     if (sq1.File() == sq2.File())
        offset = RankIncr;
     else if (sq1.Rank(White) == sq2.Rank(White))
        offset = 1;
     else if (sq1.Color() == sq2.Color())
     {
       if (abs % (RankIncr+1) == 0)
          offset = RankIncr+1;
       else if (abs % (RankIncr-1) == 0)
          offset = RankIncr-1;
       else
          return 0;
     }
     else 
        return 0;
     return (sq2 > sq1) ? offset : -offset;
}

int Board::Between( const Square &sq1, const Square &sq2, 
                Square *squares) const
{
     int offset = Direction(sq1,sq2);
     Square sq(sq1);
     int n = 0;
     for (;;)
     {
         sq += offset;
         if (sq == sq2)
            break;
         squares[n++] = sq;
     }
     return n;
}


BOOL Board::Clear( const Square &sq1, const Square &sq2 ) const
{
     Square BtwnSquares[9];
     int m = Between(sq1,sq2,BtwnSquares);
     BOOL is_clear = TRUE;
     if (m == 0)
        return TRUE;
     else
     {
        int k = 0;
        do
        {
            is_clear = my_Contents[BtwnSquares[k++]].IsEmpty();
        } while (is_clear && (k<m));
     }
     return is_clear;
}

void Board::MakeMove( const ExtendedMove &move )
{
     int i,j;
     const ColorType oside = OppositeSide();
     const ColorType side = Side();
     Square kp,target;

     if (move.IsNull())
     {
     	 my_Side = oside;
         Board_Hash::UpdateHash( *this, move, my_HashCode, my_HashCode );
         return;
     }
     else
         Board_Hash::UpdateHash( *this, move, my_HashCode, my_HashCode );


     if (move.Special() == ExtendedMove::KCastle)
     {
	const Square kp = KingPos(side);
	my_attacks.remove_attacks(*this,kp,side);
	my_attacks.remove_attacks(*this,kp+3,side);
        i = 0;
	while (i < 16 && PiecePos(side,i) != kp)
	  i++;
	UNCOND_ASSERT(i<16);
	my_KingPos[side] = kp + 2;
	my_PiecePos[i][side] = my_KingPos[side];
	my_CastleStatus[side] = CastledKSide;
	// find old square of rook
	Square oldrooksq = kp + 3;
	Square newrooksq = kp + 1;
	i = 0;
	while (i < 16 && PiecePos(side,i) != oldrooksq)
	   i++;
	UNCOND_ASSERT(i<16);
        my_PiecePos[i][side] = newrooksq; // update rook position
	my_Contents[kp] = my_Contents[oldrooksq] = Piece::EmptyPiece();
	my_Contents[newrooksq] = Piece(Piece::Rook,side);
	my_Contents[kp+2] = Piece(Piece::King,side);
	my_RFileCount[oldrooksq.File()-1][side]--;
	my_RFileCount[newrooksq.File()-1][side]++;
	my_attacks.add_attacks(*this,kp+1,side);
	my_attacks.add_attacks(*this,kp+2,side);
        my_attacks.add_discovered_attacks(*this,
	   kp,kp+1);
     }
     else if (move.Special() == ExtendedMove::QCastle)
     {
	const Square kp = KingPos(side);
	my_attacks.remove_attacks(*this,kp,side);
	my_attacks.remove_attacks(*this,kp-4,side);
	i = 0;
	while (i < 16 && PiecePos(side,i) != kp)
	  i++;
	UNCOND_ASSERT(i < 16);
	my_KingPos[side] = kp - 2;
	my_PiecePos[i][side] = my_KingPos[side];
	my_CastleStatus[side] = CastledQSide;
	// find old square of rook
	Square oldrooksq = kp - 4;
	Square newrooksq = kp - 1;
	i = 0;
	while (i < 16 && PiecePos(side,i) != oldrooksq)
	   i++;
	UNCOND_ASSERT(i<16);
        my_PiecePos[i][side] = newrooksq; // update rook position
	my_Contents[kp] = my_Contents[oldrooksq] = Piece::EmptyPiece();
	my_Contents[newrooksq] = Piece(Piece::Rook,side);
	my_Contents[kp-2] = Piece(Piece::King,side);
	my_RFileCount[oldrooksq.File()-1][side]--;
	my_RFileCount[newrooksq.File()-1][side]++;
	my_attacks.add_attacks(*this,kp-1,side);
	my_attacks.add_attacks(*this,kp-2,side);
        my_attacks.add_discovered_attacks(*this,
	   kp,kp-1);
     }
     else // not castling
     {
        const BOOL no_pieces = my_Material[side].no_pieces() &&
            my_Material[oside].no_pieces();
        Square target;
        if (move.Special() == ExtendedMove::EnPassant)
	    target = my_EnPassantSq[oside];
        else
	    target = move.DestSquare();
	my_attacks.remove_attacks(*this,move.StartSquare(),side);
	Square sq;
        i = 0;
	j = -1;
	while (i < 16 && ((sq = PiecePos(side,i)) != move.StartSquare()))
	{
	    if (sq == Square::Invalid() && j==-1)
	        j = i;
	    i++;
	}
        UNCOND_ASSERT(i<16);
	// keep pieces near the start of the PiecePos array, if possible:
	if (j == -1)
	    my_PiecePos[i][side] = move.DestSquare();
	else
	{
	    my_PiecePos[j][side] = move.DestSquare();
	    my_PiecePos[i][side] = Square::Invalid();
	}
	if (!move.Capture().IsEmpty())
	    my_attacks.remove_attacks(*this,target,oside);
	if (move.Special() == ExtendedMove::Promotion)
	   my_Contents[move.DestSquare()] = Piece(move.PromoteTo(),side);
        else
	   my_Contents[move.DestSquare()] = move.PieceMoved();
	my_Contents[move.StartSquare()] = Piece::EmptyPiece();
	my_EnPassantSq[side] = Square::Invalid();
	if (move.PieceMoved().Type() == Piece::King)
	{
	    my_KingPos[side] = move.DestSquare();
	    if ((CastleStatus(side) != CastledQSide) &&
	        (CastleStatus(side) != CastledKSide))
	        my_CastleStatus[side] = CantCastleEitherSide;
	}
	else if (move.PieceMoved().Type() == Piece::Rook)
	{
	    my_RFileCount[move.StartSquare().File()-1][side]--;
	    my_RFileCount[move.DestSquare().File()-1][side]++;
	    UpdateCastleStatus(side,move.StartSquare());
	}
	else if (move.PieceMoved().Type() == Piece::Pawn)
	{
	    pawn_bits[side].clear(move.StartSquare());
	    if (Util::Abs(move.StartSquare().Rank(side) -
	   	 	  move.DestSquare().Rank(side)) == 2)
            {
	       my_EnPassantSq[side] = move.DestSquare();
	    }
	    if (move.Special() == ExtendedMove::Promotion)
	    {
	       my_Material[side].remove_piece(Piece::Pawn);
	       my_Material[side].add_piece(move.PromoteTo());
	       if (move.PromoteTo() == Piece::Rook)
	       {
	         my_RFileCount[move.StartSquare().File()-1][side]--;
	         my_RFileCount[move.DestSquare().File()-1][side]++;
	       }
	       my_PFileCount[move.StartSquare().File()-1][side]--;
	    }
	    else
	       pawn_bits[side].set(move.DestSquare());
	}
	if (!move.Capture().IsEmpty())
	{
	    my_Material[oside].remove_piece(move.Capture().Type());
	    i = 0;
	    while (i < 16 && PiecePos(oside,i) != target)
	       i++;
	    UNCOND_ASSERT(i < 16);
	    my_PiecePos[i][oside] = Square::Invalid();
	    if (move.Capture().Type() == Piece::Pawn)
            {
	       my_PFileCount[target.File()-1][oside]--;
	       pawn_bits[oside].clear(target);
	    }
	    else if (move.Capture().Type() == Piece::Rook)
	    {
	       my_RFileCount[move.DestSquare().File()-1][oside]--;
	       UpdateCastleStatus(oside,move.DestSquare());
	    }
	    if (my_Contents[move.DestSquare()].Type() == Piece::Pawn &&
	        move.Special() != ExtendedMove::Promotion)
            {
	       my_PFileCount[move.StartSquare().File()-1][side]--;
	       my_PFileCount[move.DestSquare().File()-1][side]++;
	    }
	}
	my_attacks.add_attacks(*this,move.DestSquare(),side);
        if (move.Special() == ExtendedMove::EnPassant)
        {
            if (no_pieces)
            {
       	       my_Contents[target] = Piece::EmptyPiece();
            }
            else
            {
	    my_attacks.add_discovered_attacks(*this,
	       move.StartSquare(),move.DestSquare());
 	    my_attacks.remove_discovered_attacks(*this,
 	       move.DestSquare(),move.StartSquare());
	    my_Contents[target] = Piece::EmptyPiece();

  	    my_attacks.add_discovered_attacks(*this,
	      target,Square::Invalid());
            }
        }
	else if (!no_pieces)
	{
  	    my_attacks.add_discovered_attacks(*this,
	        move.StartSquare(),move.DestSquare());
	    if (move.Capture().IsEmpty())
  	        my_attacks.remove_discovered_attacks(*this,
 	           move.DestSquare(),move.StartSquare());
	}
     }
     my_EnPassantSq[oside] = Square::Invalid();
     my_Side = OppositeSide();
#ifdef DEBUG_ATTACKS
     {
         Attacks new_attacks;
         new_attacks.compute_attacks(*this,White);
         new_attacks.compute_attacks(*this,Black);
	 ASSERT(new_attacks == my_attacks);
     }
#endif
     ASSERT(my_HashCode == Board_Hash::HashCode(*this));
}

void Board::undo_castling(const Square &kp,
        const Square &oldkingsq, const Square &newrooksq,
        const Square &oldrooksq)
{
        my_attacks.remove_attacks(*this,kp,my_Side);
        my_attacks.remove_attacks(*this,newrooksq,my_Side);

        my_Contents[kp] = Piece::EmptyPiece();
        my_Contents[oldrooksq] = Piece(Piece::Rook,my_Side);
        my_Contents[newrooksq] = Piece::EmptyPiece();
        my_Contents[oldkingsq] = Piece(Piece::King,my_Side);
        my_KingPos[my_Side] = oldkingsq;
        
        my_RFileCount[oldrooksq.File()-1][my_Side]++;
        my_RFileCount[newrooksq.File()-1][my_Side]--;
        int i = 0;
        BOOL fixed_rook = FALSE;
        BOOL fixed_king = FALSE;
        while (i<16)
        {
           Square sq(PiecePos(my_Side,i));
           if (sq == newrooksq)
           {
              my_PiecePos[i][my_Side] = Square::Invalid();
              if (fixed_rook && fixed_king) i++;
           }
           else if (sq == kp)
           {
              my_PiecePos[i][my_Side] = Square::Invalid();
              if (fixed_rook && fixed_king) i++;
           }
           else if (sq == Square::Invalid() && !fixed_rook)
           {
              my_PiecePos[i][my_Side] = oldrooksq;
              fixed_rook = TRUE;
              i++;
           }
           else if (sq == Square::Invalid() && !fixed_king)
           {
              my_PiecePos[i][my_Side] = oldkingsq;
              fixed_king = TRUE;
              i++;
           }
           else
             i++;
        }
        my_attacks.add_attacks(*this,oldkingsq,my_Side);
        my_attacks.add_attacks(*this,oldrooksq,my_Side);
        my_attacks.remove_discovered_attacks(*this,oldkingsq,newrooksq);
}

void Board::UndoMove( const ReversibleMove &rmove )
{
    my_Side = OppositeColor(my_Side);
    if (!rmove.IsNull())
    {

    const ColorType opp_side = OppositeColor(my_Side);
    int i;
    if (rmove.Special() == ExtendedMove::KCastle)
    {
        Square kp = KingPos(my_Side);
	Square oldrooksq(kp+1);
	Square newrooksq(kp-1);
	Square oldkingsq(kp-2);
	undo_castling(kp,oldkingsq,newrooksq,oldrooksq);
     }
    else if (rmove.Special() == ExtendedMove::QCastle)
    {
        Square kp = KingPos(my_Side);
	Square oldrooksq(kp-2);
	Square newrooksq(kp+1);
	Square oldkingsq(kp+2);
	undo_castling(kp,oldkingsq,newrooksq,oldrooksq);
    }
    else
    {
        // not castling
	my_attacks.remove_attacks(*this,rmove.DestSquare(),my_Side);
	Square target;
	if (!rmove.Capture().IsEmpty())
	{
  	   if (rmove.Special() == ExtendedMove::EnPassant)
	      target = (my_Side == White) ? rmove.DestSquare() + RankIncr :
	      		           rmove.DestSquare() - RankIncr;
           else
	      target = rmove.DestSquare();
	}
	// fix up start square:
	if (rmove.Special() == ExtendedMove::Promotion)
        {
	   my_Contents[rmove.StartSquare()] = Piece(Piece::Pawn,my_Side);
	   my_PFileCount[rmove.StartSquare().File()-1][my_Side]++;
	   pawn_bits[my_Side].set(rmove.StartSquare());
        }
        else
        {
	   if (rmove.Special() != ExtendedMove::EnPassant)
	      my_Contents[rmove.StartSquare()] = rmove.PieceMoved();
	   switch (rmove.PieceMoved().Type())
	   {
	   case Piece::Pawn:
	      my_PFileCount[rmove.StartSquare().File()-1][my_Side]++;
	      my_PFileCount[rmove.DestSquare().File()-1][my_Side]--;
	      pawn_bits[my_Side].set(rmove.StartSquare());
	      pawn_bits[my_Side].clear(rmove.DestSquare());
	      break;
	   case Piece::Rook:
	      my_RFileCount[rmove.StartSquare().File()-1][my_Side]++;
	      my_RFileCount[rmove.DestSquare().File()-1][my_Side]--;
	      break;
	   case Piece::King:
	      my_KingPos[my_Side] = rmove.StartSquare();
	      break;
	   default:
	      break;
           }
        }
	BOOL start_fix_up = FALSE;
	i = 0; int j = 0;
	while (i<16)
	{
	   if ( !start_fix_up && PiecePos(my_Side,i) == Square::Invalid() )
	   {
	       my_PiecePos[i][my_Side] = rmove.StartSquare();
	       start_fix_up = TRUE;
	       i++;
	   }
	   else if (PiecePos(my_Side,i) == rmove.DestSquare())
	   {
	       my_PiecePos[i][my_Side] = Square::Invalid();
	       if (start_fix_up) i++;
	       j++;
	   }
	   else
	     i++;
	}
	UNCOND_ASSERT(j>0);
	// fix up dest square
        if (rmove.Special() == ExtendedMove::EnPassant)
	{
	   my_Contents[rmove.DestSquare()] = Piece::EmptyPiece();
           my_Contents[target] = Piece(Piece::Pawn,opp_side);
	   pawn_bits[opp_side].set(target);
	   my_PFileCount[rmove.DestSquare().File()-1][opp_side]++;
	   my_Material[opp_side].add_piece(Piece::Pawn);
	   for (i = 0; i < 16; i++)
	   {
	      if( PiecePos(opp_side,i) == Square::Invalid())
	      {
	           my_PiecePos[i][opp_side] = target;
	           break;
	      }
	   }
	   // It is tricky fixing up the attacks.  Pretend we
	   // made two moves: a capture "sideways" then a
	   // pawn move forward, and undo them in reverse order.
           my_attacks.add_attacks(*this,target,opp_side);
           my_attacks.add_discovered_attacks(*this,
	       rmove.DestSquare(),target);
           my_attacks.remove_discovered_attacks(
               *this,target,rmove.DestSquare());

	   my_Contents[rmove.StartSquare()] = rmove.PieceMoved();

           my_attacks.add_attacks(*this,rmove.StartSquare(),my_Side);
           my_attacks.remove_discovered_attacks(
               *this,rmove.StartSquare(),target);
	}
	else
	{
	   my_Contents[rmove.DestSquare()] = rmove.Capture();
	   my_EnPassantSq[opp_side] = Square::Invalid();
	   if (rmove.Special() == ExtendedMove::Promotion)
	   {
	      my_Material[my_Side].add_piece(Piece::Pawn);
	      my_Material[my_Side].remove_piece(rmove.PromoteTo());
	   }
	   if (!rmove.Capture().IsEmpty())
	   {
	      my_Material[opp_side].add_piece(rmove.Capture().Type());
	      for (i = 0; i < 16; i++)
	      {
	        if( PiecePos(opp_side,i) == Square::Invalid())
	        {
	           my_PiecePos[i][opp_side] = rmove.DestSquare();
	           break;
		}
	      }
	      if (rmove.Capture().Type() == Piece::Rook)
	      {
	        my_RFileCount[rmove.DestSquare().File()-1][opp_side]++;
	      }
	      else if (rmove.Capture().Type() == Piece::Pawn)
	      {
	        my_PFileCount[rmove.DestSquare().File()-1][opp_side]++;
		pawn_bits[opp_side].set(rmove.DestSquare());
	      }
              my_attacks.add_attacks(*this,rmove.StartSquare(),my_Side);
              my_attacks.add_attacks(*this,target,OppositeColor(my_Side));
              my_attacks.remove_discovered_attacks(
	         *this,rmove.StartSquare(),rmove.DestSquare());
	   }
  	   else
 	   {
	       // not a capture move
               my_attacks.add_attacks(*this,rmove.StartSquare(),my_Side);
               my_attacks.add_discovered_attacks(*this,
	          rmove.DestSquare(),rmove.StartSquare());
               my_attacks.remove_discovered_attacks(
	          *this,rmove.StartSquare(),rmove.DestSquare());
	   }
	}
    }
    my_EnPassantSq[my_Side] = rmove.Old_EnPassantSq(my_Side);
    my_EnPassantSq[OppositeColor(my_Side)] = rmove.Old_EnPassantSq(
    	OppositeColor(my_Side));
    my_CastleStatus[my_Side] = rmove.Old_CastleStatus(my_Side);
    my_CastleStatus[OppositeColor(my_Side)] = 
        rmove.Old_CastleStatus(OppositeColor(my_Side));
    }
    my_HashCode = rmove.Old_HashCode();
#ifdef DEBUG_ATTACKS
     {
         Attacks new_attacks;
         new_attacks.compute_attacks(*this,White);
         new_attacks.compute_attacks(*this,Black);
	 ASSERT (new_attacks == my_attacks);
     }
#endif
}
	
Board::CheckStatusType Board::CheckStatus() const
{
     if (num_attacks(KingPos(Side()),OppositeSide()) > 0)
        return InCheck;
     else
        return NotInCheck;
}

static void set_bad( istream &i )
{
    i.clear( ios::badbit | i.rdstate() );
}

istream & operator >> (istream &i, Board &board)
{
    // read in a board position in Forsythe-Edwards (FEN) notation.
    static char buf[128];

    char *bp = buf;
    int c;
    int fields = 0; int count = 0;
    while (i.good() && fields < 4 && (c = i.get()) != '\n' && 
           ++count < 128)
    {
       *bp++ = c;
       if (isspace(c))
          fields++;
    }
    *bp = '\0';
    if (!i)
      return i;
    if (!ChessIO::read_fen(board, buf))
      set_bad(i);
    return i;
}

ostream & operator << (ostream &o, Board &board)
{
    char buf[128];
    memset(buf,'\0',128);
    ChessIO::write_fen(board,buf,TRUE);
    o << buf;
    return o;
}

