//  GREKO Chess Engine
//  (c) 2002-2012 Vladimir Medvedev <vrm@bk.ru>
//  http://greko.110mb.com

//  position.cpp: position and game representation
//  modified: 31-Dec-2012

#include "eval.h"
#include "moves.h"
#include "notation.h"
#include "utils.h"

U64 Position::s_hashSide[2];
U64 Position::s_hash[64][14];
U32 Position::s_hashPawn[64][14];

void Position::Clear()
{
  m_castlings = 0;
  m_ep = NF;
  m_fifty = 0;
  m_hash = 0;
  m_hashPawn = 0;
  m_Kings[WHITE] = m_Kings[BLACK] = 0;
  m_material[WHITE] = m_material[BLACK] = 0;
  m_matIndex[WHITE] = m_matIndex[BLACK] = 0;
  m_ply = 0;
  m_side = WHITE;
  _undoSize = 0;

  for (int f = 0; f < 64; ++f)
  {
    m_board[f] = NOPIECE;
  }

  for (int p = 0; p < 14; ++p)
  {
    m_bits[p] = 0;
    m_count[p] = 0;
  }

  m_bitsAll[WHITE] = m_bitsAll[BLACK] = 0;
}

FLD Position::FindLeastAttacker(FLD to, COLOR side, const U64& mask) const
{
  U64 x, occ = BitsAll();

  // Pawn?
  x = BB_PAWN_ATTACKS[to][side ^ 1] & Bits(PW | side) & mask;
  if (x) return LSB(x);

  // Knight?
  x = BB_KNIGHT_ATTACKS[to] & Bits(NW | side) & mask;
  if (x) return LSB(x);

  // Bishop?
  x = BishopAttacks(to, occ) & Bits(BW | side) & mask;
  if (x) return LSB(x);

  // Rook?
  x = RookAttacks(to, occ) & Bits(RW | side) & mask;
  if (x) return LSB(x);

  // Queen?
  x = QueenAttacks(to, occ) & Bits(QW | side) & mask;
  if (x) return LSB(x);

  // King?
  x = BB_KING_ATTACKS[to] & Bits(KW | side) & mask;
  if (x) return LSB(x);

  return NF;
}

bool Position::IsGameOver(std::string& message)
{
  bool hasMoves = false;
  MoveList mvlist;
  mvlist.GenAllMoves(*this);
  for (int i = 0; i < mvlist.Size(); ++i)
  {
    if (MakeMove(mvlist[i].m_mv))
    {
      hasMoves = true;
      UnmakeMove();
      break;
    }
  }
  
  if (hasMoves == false)
  {
    if (InCheck())
      message = (m_side == WHITE)? "0-1 {Black mates}" : "1-0 {White mates}";
    else
      message = "1/2-1/2 {Draw: stalemate}";
    return true;
  }

  if (EstimateDraw(*this) == EVAL_THEORETICAL_DRAW)
  {
    message = "1/2-1/2 {Draw: material}";
    return true;
  }

  if (GetRepetitions() >= 3)
  {
    message = "1/2-1/2 {Draw: 3rd repetition}";
    return true;
  }

  return false;
}

U64 Position::GetAttacks(FLD to, COLOR side, U64 occ) const
{
  U64 att = 0;

  att |= BB_PAWN_ATTACKS[to][side ^ 1] & Bits(PW | side);
  att |= BB_KNIGHT_ATTACKS[to] & Bits(NW | side);
  att |= BB_KING_ATTACKS[to] & Bits(KW | side);
  att |= BishopAttacks(to, occ) & (Bits(BW | side) | Bits(QW | side));
  att |= RookAttacks(to, occ) & (Bits(RW | side) | Bits(QW | side));

  return att;
}

std::string Position::Fen() const
{
  const std::string names = "-?PpNnBbRrQqKk";
  std::string fen;
  int empty = 0;
  for (int f = 0; f < 64; ++f)
  {
    PIECE p = m_board[f];
    if (p)
    {
      if (empty)
      {
        char t1[3];
        sprintf(t1, "%d", empty);
        fen += std::string(t1);
        empty = 0;
      }
      fen += names.substr(p, 1);
    }
    else
      ++empty;

    if (Col(f) == 7)
    {
      if (empty)
      {
        char t1[3];
        sprintf(t1, "%d", empty);
        fen += std::string(t1);
        empty = 0;
      }
      if (f != 63)
        fen += "/";
    }
  }

  if (m_side == WHITE)
    fen += " w";
  else
    fen += " b";

  if (m_castlings & 0x33)
  {
    fen += " ";
    if (m_castlings & WHITE_O_O)   
      fen += "K";
    if (m_castlings & WHITE_O_O_O) 
      fen += "Q";
    if (m_castlings & BLACK_O_O)   
      fen += "k";
    if (m_castlings & BLACK_O_O_O) 
      fen += "q";
  }
  else
    fen += " -";

  if (m_ep == NF)
    fen += " -";
  else
  {
    fen += " ";
    fen += FldToStr(m_ep);
  }

  return fen;
}

int Position::GetRepetitions() const
{ 
  int total = 1;
  for (int i = _undoSize - 1; i >= 0; --i)
  {
    if (_undos[i].m_hash == m_hash) 
    {
      ++total;
    }

    if (_undos[i].m_mv == 0)
      return 0;

    if (_undos[i].m_mv.Captured())
      break;

    if (_undos[i].m_mv.Piece() == PW)
      break;

    if (_undos[i].m_mv.Piece() == PB)
      break;
   }

  return total;
}

void Position::InitHashNumbers()
{
  RandSeed32(42);
  for (int f = 0; f < 64; f++)
  {
    for (int p = 0; p < 14; p++)
    {
      s_hash[f][p] = Rand64();
      s_hashPawn[f][p] = (p == PW || p == PB)? Rand32() : 0;
    }
  }
  s_hashSide[0] = 0;
  s_hashSide[1] = Rand64();
}

bool Position::IsAttacked(FLD to, COLOR side) const
{
  U64 x;
  FLD from;

  if (BB_PAWN_ATTACKS[to][side ^ 1] & Bits(PW | side))
    return true;

  if (BB_KNIGHT_ATTACKS[to] & Bits(NW | side))
    return true;

  if (BB_KING_ATTACKS[to] & Bits(KW | side))
    return true;

  U64 occ = BitsAll();
  x = BB_BISHOP_ATTACKS[to] & (Bits(QW | side) | Bits(BW | side));
  while (x)
  {
    from = PopLSB(x);
    if ((BB_BETWEEN[from][to] & occ) == 0)
      return true;
  }

  x = BB_ROOK_ATTACKS[to] & (Bits(QW | side) | Bits(RW | side));
  while (x)
  {
    from = PopLSB(x);
    if ((BB_BETWEEN[from][to] & occ) == 0)
      return true;
  }

  return false;
}

bool Position::MakeMove(Move mv)
{
  Undo& undo = _undos[_undoSize++];
  undo.m_castlings = m_castlings;
  undo.m_ep = m_ep;
  undo.m_fifty = m_fifty;
  undo.m_hash = m_hash;
  undo.m_mv = mv;

  FLD from = mv.From();
  FLD to = mv.To();
  PIECE piece = mv.Piece();
  PIECE captured = mv.Captured();
  PIECE promotion = mv.Promotion();

  COLOR side = m_side;
  COLOR opp = side ^ 1;
  m_hash ^= s_hashSide[1];

  ++m_fifty;
  if (captured)
  {
    m_fifty = 0;
    if (to == m_ep)
      Remove(to + 8 - 16 * side);
    else
      Remove(to);
  }
  Remove(from);
  Put(to, piece);

  m_ep = NF;
  switch (piece)
  {
  case PW:
  case PB:
    m_fifty = 0;
    if (to - from == -16 + 32 * side)
      m_ep = to + 8 - 16 * side;
    else if (promotion)
    {
      Remove(to);
      Put(to, promotion);
    }
    break;

  case KW:
    m_Kings[WHITE] = to;
    if (from == E1)
    {
      if (to == G1)
      {
        Remove(H1);
        Put(F1, RW);
        m_castlings ^= WHITE_DID_O_O;
      }
      else if (to == C1)
      {
        Remove(A1);
        Put(D1, RW);
        m_castlings ^= WHITE_DID_O_O_O;        
      }
    }
    break;

  case KB:
    m_Kings[BLACK] = to;
    if (from == E8)
    {
      if (to == G8)
      {
        Remove(H8);
        Put(F8, RB);
        m_castlings ^= BLACK_DID_O_O;        
      }
      else if (to == C8)
      {
        Remove(A8);
        Put(D8, RB);
        m_castlings ^= BLACK_DID_O_O_O;        
      }
    }
    break;

  default:
    break;
  }

  static const U8 castlingDelta[64] =
  {
    0xf7, 0xff, 0xff, 0xff, 0xf3, 0xff, 0xff, 0xfb,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
    0xfd, 0xff, 0xff, 0xff, 0xfc, 0xff, 0xff, 0xfe
  };

  m_castlings &= castlingDelta[from];
  m_castlings &= castlingDelta[to];

  ++m_ply;
  m_side ^= 1;
  
  if (IsAttacked(m_Kings[side], opp))
  {
    UnmakeMove();
    return false;
  }

  return true;
}

void Position::MakeNullMove()
{
  Undo& undo = _undos[_undoSize++];
  undo.m_castlings = m_castlings;
  undo.m_ep = m_ep;
  undo.m_fifty = m_fifty;
  undo.m_mv = 0;

  m_ep = NF;
  m_side ^= 1;
  ++m_ply;
  m_hash ^= s_hashSide[1];
}

void Position::Mirror()
{
  Position old = *this;

  Clear();
  for (int f = 0; f < 64; f++)
  {
    PIECE p = old[f];
    if (p == NOPIECE)
      continue;

    Put(FLIP[f], p ^ 1);

    if (p == KW)
      m_Kings[BLACK] = FLIP[f];
    else if (p == KB)
      m_Kings[WHITE] = FLIP[f];
  }

  m_side = old.Side() ^ 1;
  m_fifty = old.Fifty();
  if (old.Ep() != NF)
    m_ep = FLIP[old.Ep()];

  m_ply = old.Ply();

  int bit0 = old.m_castlings & 0x01;
  int bit1 = (old.m_castlings & 0x02) >> 1;
  int bit2 = (old.m_castlings & 0x04) >> 2;
  int bit3 = (old.m_castlings & 0x08) >> 3;
  int bit4 = (old.m_castlings & 0x10) >> 4;
  int bit5 = (old.m_castlings & 0x20) >> 5;
  int bit6 = (old.m_castlings & 0x40) >> 6;
  int bit7 = (old.m_castlings & 0x80) >> 7;

  m_castlings = static_cast<U8>(
                (bit5 << 7) |
                (bit4 << 6) |
                (bit7 << 5) |
                (bit6 << 4) |
                (bit1 << 3) |
                (bit0 << 2) |
                (bit3 << 1) |
                (bit2 << 0));
}


void Position::Print() const
{
  const char names[] = "-?PpNnBbRrQqKk";
  out("\n");
  for (int f = 0; f < 64; ++f)
  {
    PIECE p = m_board[f];
    Highlight (p && GetColor(p) == WHITE);
    out(" %c", names[p]);
    Highlight(false);
    if (Col(f) == 7)
      out("\n");
  }

  if (_undoSize)
  {
    out("\n ");
    for (int m = 0; m < _undoSize; ++m)
      out("%s ", MoveToStrLong(_undos[m].m_mv));
    out("\n ");
  }
  out("\n");
}

bool Position::SetFen(const std::string& fen)
{
  if (fen.length() < 5)
    return 0;
  
  Position tmp = *this;
  Clear();

  TokenString s(fen);
  std::string token = s.GetToken();

  FLD f = A8;

  for (size_t i = 0; i < token.length(); ++i)
  {
    PIECE piece = NOPIECE;

    switch (token[i])
    {
      case 'P': piece = PW; break;
      case 'p': piece = PB; break;
      case 'N': piece = NW; break;
      case 'n': piece = NB; break;
      case 'B': piece = BW; break;
      case 'b': piece = BB; break;
      case 'R': piece = RW; break;
      case 'r': piece = RB; break;
      case 'Q': piece = QW; break;
      case 'q': piece = QB; break;
      case 'K': piece = KW; break;
      case 'k': piece = KB; break;

      case '1': case '2': case '3': case '4':
      case '5': case '6': case '7': case '8':
        f += static_cast<U8>(token[i] - '0');
        break;

      case '/':
      if (Col(f) != 0)
        f = static_cast<U8>(8 * (Row(f) + 1));
      break;

    default:
      goto ILLEGAL_FEN;
    }

    if (piece)
    {
      if (f >= 64)
        goto ILLEGAL_FEN;

      Put(f, piece);

      if (piece == KW)
        m_Kings[WHITE] = f;
      else if (piece == KB)
        m_Kings[BLACK] = f;
      
      ++f;
    }
  }

  token = s.GetToken();
  if (token.empty())
    goto FINAL_CHECK;

  if (token[0] == 'w')
    m_side = WHITE;
  else if (token[0] == 'b')
    m_side = BLACK;
  else
    goto ILLEGAL_FEN;

  m_hash ^= s_hashSide[m_side];

  token = s.GetToken();
  if (token.empty())
    goto FINAL_CHECK;

  for (size_t i = 0; i < token.length(); ++i)
  {
    switch (token[i])
    {
      case 'K' : m_castlings |= WHITE_O_O;   break;
      case 'Q' : m_castlings |= WHITE_O_O_O; break;
      case 'k' : m_castlings |= BLACK_O_O;   break;
      case 'q' : m_castlings |= BLACK_O_O_O; break;
      case '-': break;
      default: goto ILLEGAL_FEN;
    }
  }
  
  token = s.GetToken();
  if (token.empty())
    goto FINAL_CHECK;
  if (token == "-")
    ;
  else
  {
    m_ep = StrToFld(token);
  }

  token = s.GetToken();
  if (token.empty())
    goto FINAL_CHECK;

  m_fifty = atoi(token.c_str());
  if (m_fifty < 0)
    m_fifty = 0;

  token = s.GetToken();
  if (token.empty())
    goto FINAL_CHECK;

  m_ply = (atoi(token.c_str()) - 1) * 2;
  if (m_side == BLACK)
    ++m_ply;

  if (m_ply < 0)
    m_ply = 0;

  goto FINAL_CHECK;

ILLEGAL_FEN:
  *this = tmp;
  return false;

FINAL_CHECK:
  return true;
}

void Position::UnmakeMove()
{
  if (_undoSize == 0)
    return;

  Undo& undo = _undos[--_undoSize];
  Move mv = undo.m_mv;
  m_ep = undo.m_ep;
  m_castlings = undo.m_castlings;
  m_fifty = undo.m_fifty;

  FLD from = mv.From();
  FLD to = mv.To();
  PIECE piece = mv.Piece();
  PIECE captured = mv.Captured();
  COLOR side = m_side ^ 1;

  m_hash ^= s_hashSide[1];

  Remove(to);
  Put(from, piece);
  if (captured)
  {
    if (to == m_ep)
      Put(to + 8 - 16 * side, captured);
    else
      Put(to, captured);
  }
  
  --m_ply;
  m_side ^= 1;

  if (piece == KW)
  {
    m_Kings[WHITE] = from;
    if (from == E1 && to == G1)
    {
      Remove(F1);
      Put(H1, RW);
    }
    else if (from == E1 && to == C1)
    {
      Remove(D1);
      Put(A1, RW);
    }
  }
  else if (piece == KB)
  {
    m_Kings[BLACK] = from;
    if (from == E8 && to == G8)
    {
      Remove(F8);
      Put(H8, RB);
    }
    else if (from == E8 && to == C8)
    {
      Remove(D8);
      Put(A8, RB);
    }
  }
}

void Position::UnmakeNullMove()
{
  if (_undoSize == 0)
    return;

  Undo& undo = _undos[--_undoSize];
  m_ep = undo.m_ep;
  m_castlings = undo.m_castlings;
  m_fifty = undo.m_fifty;

  m_side ^= 1;
  --m_ply;
  m_hash ^= s_hashSide[1];
}
