#include "ChessArbiter.hpp"

namespace chessarbiter {
ChessArbiter::ChessArbiter()
    : wPawn(1), wRook(5), wKnight(3), wBishop(3), wQueen(9), wKing(0), SAN(""),
      capture(' ') {}

void ChessArbiter::Setup(std::string fen) {
  positions.clear();
  SetFEN(fen);
  positions[this->fen.board] = 1;
}

void ChessArbiter::SetFEN(FEN fen) { SetFEN(FENParser::Serialize(fen)); }

void ChessArbiter::SetFEN(std::string newfen) {
  fen = FENParser::Parse(newfen);

  board.Clear();
  for (int i = 0; i < 64; i++) {
    if (fen.board[i] != ' ') {
      char f = 'a' + ((i) % 8);
      char r = '8' - ((i) / 8);
      board.AddPiece(fen.board[i], f + std::string() + r);
    }
  }
}

std::string ChessArbiter::GetFEN() { return (FENParser::Serialize(fen)); }

std::string ChessArbiter::GetBoard() { return (fen.board); }

bool ChessArbiter::IsBlackTurn() { return (fen.player == 'b'); }

bool ChessArbiter::IsCheck(bool isBlack) {
  std::string kingloc = board.GetKingLocation(isBlack);
  return (IsAttacked(kingloc, !isBlack));
}

bool ChessArbiter::Play(std::string move) {
  std::vector<std::string> moves = ListLegalMoves(fen.player);
  if (find(moves.begin(), moves.end(), move) != moves.end()) {
    Piece moved = board.GetPieceAt(move.substr(0, 2)); // This call never fail
    std::string src = move.substr(0, 2);
    std::string dst = move.substr(2, 2);
    bool IsCapture = !board.IsEmpty(dst);
    FEN newFen = fen;
    INIT_BACKUP();
    SAN = "";
    capture = ' ';

    if (IsCapture) {
      capture = board.GetPieceAt(dst).piece;
    }

    // Perform the move
    if ((moved.piece == 'k' || moved.piece == 'K') && move == "e8g8" ||
        move == "e8c8" || move == "e1g1" || move == "e1c1") {
      if (move == "e8g8") {
        board.Move("e8g8");
        board.Move("h8f8");
        SAN = "O-O";
      } else if (move == "e8c8") {
        board.Move("e8c8");
        board.Move("a8d8");
        SAN = "O-O-O";
      } else if (move == "e1g1") {
        board.Move("e1g1");
        board.Move("h1f1");
        SAN = "O-O";
      } else {
        board.Move("e1c1");
        board.Move("a1d1");
        SAN = "O-O-O";
      }
    } else {
      // Update SAN move
      if (moved.piece == 'p' || moved.piece == 'P') {
        if (IsCapture) {
          SAN = src[0];
          SAN += "x" + dst;
        } else {
          SAN = dst;
        }
      } else {
        SAN = std::toupper(moved.piece);
        if (!board.IsPieceMoveUnique(moved.piece, dst)) {
          if (src[0] == dst[0]) {
            SAN += src[1];
          } else {
            SAN += src[0];
          }
        }
        if (IsCapture) {
          SAN += "x";
        }
        SAN += dst;
      }
      // Perform the move
      board.Move(move);
    }

    // Update halfmove
    newFen.halfmove++;
    // Check enpassant
    newFen.en_passant = "-";
    if (moved.piece == 'p' || moved.piece == 'P') {
      if (fen.player && (dst[1] - src[1] == 2)) {
        newFen.en_passant = src[0] + std::string() + (char)(src[1] - 1);
      } else if (!fen.player && (dst[1] - src[1] == 2)) {
        newFen.en_passant = src[0] + std::string() + (char)(src[1] + 1);
      }
      newFen.halfmove = 0; // Pawn moves reset half moves
    }
    // Captures reset half moves
    if (IsCapture) {
      newFen.halfmove = 0;
    }
    if (newFen.player) {
      newFen.move++;
    }
    newFen.board = board.Serialize();
    newFen.player = !newFen.player;
    // Castle update if one is true
    if (newFen.white_castle_long || newFen.white_castle_short ||
        newFen.black_castle_long || newFen.black_castle_short) {
      if (moved.piece == 'R' && src == "a1") {
        newFen.white_castle_long = false;
      } else if (moved.piece == 'R' && src == "h1") {
        newFen.white_castle_short = false;
      } else if (moved.piece == 'r' && src == "a8") {
        newFen.black_castle_long = false;
      } else if (moved.piece == 'r' && src == "h8") {
        newFen.black_castle_short = false;
      } else if (moved.piece == 'K' || (!fen.player && src == "O-")) {
        newFen.white_castle_long = false;
        newFen.white_castle_short = false;
      } else if (moved.piece == 'k' || (fen.player && src == "O-")) {
        newFen.black_castle_long = false;
        newFen.black_castle_short = false;
      }
    }

    // Update fen!
    fen = newFen;

    // Check for illegal move
    if (IsCheck(!fen.player)) {
      RESTORE_BACKUP();
      return (false);
    }

    // Update position map (repetitions draw)
    if (positions.count(fen.board) == 0) {
      positions[fen.board] = 1;
    } else {
      positions[fen.board] += 1;
    }

    return (true);
  }
  return (false);
}

bool ChessArbiter::IsAttacked(std::string square, bool by) {
  std::vector<std::string> moves = board.ListPossibleMoves(by);
  for (std::string &m : moves) {
    std::string src = m.substr(0, 2);
    std::string dst = m.substr(2, 2);
    if (dst == square) {
      // Pawn do not attack forward
      Piece p = board.GetPieceAt(src);
      if (p.piece == 'p' || p.piece == 'P') {
        if (src[0] != dst[0]) {
          return (true);
        }
      } else {
        return (true);
      }
    }
  }
  return (false);
}

bool ChessArbiter::IsCastlePossible(bool isBlack, bool isLong) {

  if (isBlack && isLong && fen.black_castle_long) {
    if (board.IsEmpty("d8") && board.IsEmpty("c8") && board.IsEmpty("b8")) {
      if (!IsAttacked("d8", false) && !IsAttacked("c8", false)) {
        return (true);
      }
    }
  } else if (isBlack && !isLong && fen.black_castle_short) {
    if (board.IsEmpty("f8") && board.IsEmpty("g8")) {
      if (!IsAttacked("f8", false) && !IsAttacked("g8", false)) {
        return (true);
      }
    }
  } else if (!isBlack && isLong && fen.white_castle_long) {
    if (board.IsEmpty("d1") && board.IsEmpty("c1") && board.IsEmpty("b1")) {
      if (!IsAttacked("d1", true) && !IsAttacked("c1", true)) {
        return (true);
      }
    }
  } else if (!isBlack && !isLong && fen.white_castle_short) {
    if (board.IsEmpty("f1") && board.IsEmpty("g1")) {
      if (!IsAttacked("f1", true) && !IsAttacked("g1", true)) {
        return (true);
      }
    }
  }

  return (false);
}

int ChessArbiter::GetMaterialScore() {
  int whiteScore = 0;
  int blackScore = 0;
  for (char i = 0; i < 2; i++) {
    int *score = &whiteScore;
    if (i > 0) {
      score = &blackScore;
    }
    for (Piece &p : board.GetPlayerPieces((bool)i)) {
      switch (tolower(p.piece)) {
      case 'p':
        (*score) += wPawn;
        break;
      case 'r':
        (*score) += wRook;
        break;
      case 'n':
        (*score) += wKnight;
        break;
      case 'b':
        (*score) += wBishop;
        break;
      case 'q':
        (*score) += wQueen;
        break;
      default:
        (*score) += wKing;
      }
    }
  }
  return (whiteScore - blackScore);
}

std::string ChessArbiter::GetCaptures(bool isBlack) {
  std::string captures;
  // Pawn
  char p = 'P';
  if (!isBlack)
    p = 'p';
  for (char i = 8 - board.CountPiece(p); i > 0; i--) {
    captures += p;
  }
  // Rook
  p = 'R';
  if (!isBlack)
    p = 'r';
  for (char i = 2 - board.CountPiece(p); i > 0; i--) {
    captures += p;
  }
  // Knight
  p = 'N';
  if (!isBlack)
    p = 'n';
  for (char i = 2 - board.CountPiece(p); i > 0; i--) {
    captures += p;
  }
  // Bishop
  p = 'B';
  if (!isBlack)
    p = 'b';
  for (char i = 2 - board.CountPiece(p); i > 0; i--) {
    captures += p;
  }
  // Queen
  p = 'Q';
  if (!isBlack)
    p = 'q';
  for (char i = 1 - board.CountPiece(p); i > 0; i--) {
    captures += p;
  }
  // King :D
  p = 'K';
  if (!isBlack)
    p = 'k';
  for (char i = 1 - board.CountPiece(p); i > 0; i--) {
    captures += p;
  }
  return (captures);
}

std::vector<std::string> ChessArbiter::ListLegalMoves(bool isBlack) {
  std::vector<std::string> moves;
  for (std::string &move : board.ListPossibleMoves(isBlack)) {
    std::string src = move.substr(0, 2);
    std::string dst = move.substr(2, 2);
    Piece srcp = board.GetPieceAt(src); // This call never fail
    bool IsDstEmpty = board.IsEmpty(dst);

    // Pawns side  moves
    if ((srcp.piece == 'p' || srcp.piece == 'P') && (src[0] != dst[0])) {
      if (!IsDstEmpty) {
        Piece attacked = board.GetPieceAt(dst);
        if (srcp.isBlack != attacked.isBlack)
          moves.push_back(move);
      } else if (dst == fen.en_passant) {
        moves.push_back(move);
      }
    } else {
      moves.push_back(move);
    }
  }

  // Casling
  if (isBlack) {
    if (IsCastlePossible(isBlack, false)) {
      moves.push_back("e8g8");
    }
    if (IsCastlePossible(isBlack, true)) {
      moves.push_back("e8c8");
    }
  } else {
    if (IsCastlePossible(isBlack, false)) {
      moves.push_back("e1g1");
    }
    if (IsCastlePossible(isBlack, true)) {
      moves.push_back("e1c1");
    }
  }

  return (moves);
}

bool ChessArbiter::IsPlayable() {
  char nK = board.CountPiece('K');
  if (nK == 1 && nK == board.CountPiece('k')) {
    if (!IsCheck(!fen.player)) {
      return (true);
    }
  }
  return (false);
}

bool ChessArbiter::IsDrawByFiftyMoveRule() { return (fen.halfmove >= 100); }

bool ChessArbiter::IsDrawByNoMoves() {
  if (!IsCheck(fen.player)) {
    std::vector<std::string> moves = ListLegalMoves(fen.player);
    for (std::string &move : moves) {
      INIT_BACKUP();
      if (Play(move)) {
        RESTORE_BACKUP();
        return (false);
      }
    }
    return (true);
  }
  return (false);
}

bool ChessArbiter::IsDrawByRepetitions() {
  for (auto const &[key, val] : positions) {
    if (val >= 3) {
      return (true);
    }
  }
  return (false);
}

bool ChessArbiter::IsDraw() {
  return (IsDrawByFiftyMoveRule() || IsDrawByNoMoves() ||
          IsDrawByRepetitions());
}

bool ChessArbiter::IsCheckMate() {
  if (IsCheck(fen.player)) {
    std::vector<std::string> moves = ListLegalMoves(fen.player);
    for (std::string &move : moves) {
      INIT_BACKUP();
      if (Play(move)) {
        RESTORE_BACKUP();
        return (false);
      }
    }
    return (true);
  }
  return (false);
}

std::string ChessArbiter::GetSAN() { return (SAN); }
char ChessArbiter::GetCapture() { return (capture); }
} // namespace chessarbiter