
#include <string.h>
#include <limits.h>
#include <ctype.h>

#include "best.h"
#include "utils.h"
#include "log.h"
#include "pst.h"

#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))

#define VALUE_MAX  99999
#define VALUE_MIN -99999

int piece_value(const char piece)
{
  /* http://hgm.nubati.net/rules/Capablanca.html */
  /* http://hgm.nubati.net/rules/CRC.html */
  int value;
  
  switch(piece) {
    case 'A': value = 875; break;
    case 'B': value = 350; break;
    case 'C': value = 900; break;
    case 'K': value =   0; break;
    case 'N': value = 300; break;
    case 'P': value = 100; break;
    case 'Q': value = 950; break;
    case 'R': value = 500; break;
    case 'a': value =-875; break;
    case 'b': value =-350; break;
    case 'c': value =-900; break;
    case 'k': value =   0; break;
    case 'n': value =-300; break;
    case 'p': value =-100; break;
    case 'q': value =-950; break;
    case 'r': value =-500; break;
    default : value =   0;
  }
  
  return value;
}

int evaluate_placement(const struct t_position position, const char player)
{
  const int A1 =  35;
  const int A8 =  42;
  const int J8 = 177;
  char pawn   = player == 'w' ? 'P' : 'p';
  char king   = player == 'w' ? 'K' : 'k';
  char knight = player == 'w' ? 'N' : 'n';
  int center_x = position.board_10x8 ? 5 : 4;
  int last_col = position.board_10x8 ? 9 : 7;
  int i, x, y;
  int result = 0;
  
  for (i = A1; i <= J8; i ++)
  {
    x = sqr2x(i);
    y = sqr2y(i);
    
    if (position.board[i] == pawn)
    {
      if ((player == 'w' && x < center_x) || (player == 'b' && x >= center_x))
      {
        result += (position.board[i - 16] == pawn);
        result += (position.board[i + 16] == pawn);
      }
      if ((player == 'b' && x < center_x) || (player == 'w' && x >= center_x))
      {
        result += (position.board[i - 14] == pawn);
        result += (position.board[i + 14] == pawn);
      }
      
      if (position.board[i - 14] == pawn && position.board[i + 16] == pawn)
      {
        if (player == 'w')
        {
          result --;
        }
        else
        {
          result ++;
        }
      }
      
      if (position.board[i + 14] == pawn && position.board[i - 16] == pawn)
      {
        if (player == 'w')
        {
          result ++;
        }
        else
        {
          result --;
        }
      }
      
      if (player == 'w')
      {
        result += (position.board[i - 16] == king || position.board[i + 14] == king || position.board[i - 1] == king || position.board[i - 2] == king);
      }
      else
      {
        result += (position.board[i + 16] == king || position.board[i - 14] == king || position.board[i + 1] == king || position.board[i + 2] == king);
      }
    }
    else if (position.board[i] == knight)
    {
      if (x == 0 || x == last_col)
      {
        result --;
      }
      /*
      if ((player == 'w' && y == 0) || (player == 'b' && y == 7))
      {
        result --;
      }
      */
    }
  }
  
  if (player == 'w')
  {
    for (i = A1; position.board[i] != BORDER; i += 15)
    {
      if (position.board[i] != EMPTY_SQUARE && strchr("NBAC", position.board[i]) != NULL) result --;
    }
  }
  else
  {
    for (i = A8; position.board[i] != BORDER; i += 15)
    {
      if (position.board[i] != EMPTY_SQUARE && strchr("nbac", position.board[i]) != NULL) result --;
    }
  }
  
  return result;
}

int evaluate_placement_from_tables(const struct t_position position, const char player)
{
  const int A1 =  35;
  //const int A8 =  42;
  const int J8 = 177;
  
  int i, x, y;
  int result = 0;
  
  for (i = A1; i <= J8; i ++)
  {
    if (position.board[i] != BORDER && position.board[i] != EMPTY_SQUARE)
    {
      x = sqr2x(i);
      y = sqr2y(i);
      result += get_value_pst(position.board_10x8, position.board[i], x, y);
    }
  }
  
  return result;
}

int evaluate_material(const struct t_position position, const char player)
{
  const int A1 =  35;
  const int J8 = 177;
  int i, result = 0;
  
  for (i = A1; i <= J8; i ++)
  {
    if (position.board[i] != BORDER && position.board[i] != EMPTY_SQUARE)
    {
      result += piece_value(position.board[i]);
    }
  }
  
  if (player == 'b')
  {
    result *= -1;
  }
  
  return result;
}

struct t_move best_move(struct t_position position)
{
  struct t_position_info info1;
  struct t_move moves1[256];
  int movecount1;
  
  struct t_position position2;
  struct t_position_info info2;
  struct t_move moves2[256];
  int movecount2;
  
  struct t_position position3;
  struct t_position_info info3;
  struct t_move moves3[256];
  int movecount3;
  
  struct t_position position4;
  struct t_position_info info4;
  struct t_move moves4[256];
  int movecount4;
  
  struct t_move move1, move2, move3;
  int eval, eval_max;
  int eval_max_min;
  int placement;
  int piece_type;
  int move_eval[256];
  int best_eval;
  int best_moves[256];
  char move_str[6];
  int i1, i2, i3;
  int castling_available;
  
  info1 = get_position_info(&position, 0);
  movecount1 = generate_legal_moves(position, info1, moves1, sizeof(moves1) / sizeof(struct t_move), 0);
  LOG_LINE("DEBUG Found %d legal moves\n", movecount1)
  if (movecount1 == 0)
  {
    memset(&move1, '\0', sizeof(move1));
    return move1;
  }
  for (i1 = 0; i1 < movecount1; i1 ++)
  {
    move1 = moves1[i1];
    position2 = position;
    do_move(move1, &position2);
    info2 = get_position_info(&position2, 0);
    movecount2 = generate_legal_moves(position2, info2, moves2, sizeof(moves2) / sizeof(struct t_move), 0);
    if (info2.check && movecount2 == 0)
    {
      move_eval[i1] = VALUE_MAX;
      continue;
    }
    eval_max_min = INT_MAX;
    for (i2 = 0; i2 < movecount2; i2 ++)
    {
      move2 = moves2[i2];
      position3 = position2;
      do_move(move2, &position3);
      info3 = get_position_info(&position3, 0);
      movecount3 = generate_legal_moves(position3, info3, moves3, sizeof(moves3) / sizeof(struct t_move), 0);
      if (info3.check && movecount3 == 0)
      {
        move_eval[i1] = VALUE_MIN;
        eval_max_min = move_eval[i1];
        break;
      }
      eval_max = INT_MIN;
      for (i3 = 0; i3 < movecount3; i3 ++)
      {
        move3 = moves3[i3];
        position4 = position3;
        do_move(move3, &position4);
        info4 = get_position_info(&position4, 0);
        movecount4 = generate_legal_moves(position4, info4, moves4, sizeof(moves4) / sizeof(struct t_move), 1);
        if (info4.check && movecount4 == 0)
        {
          eval = VALUE_MAX;
        }
        else
        {
          eval = evaluate_material(position4, position.active);
        }
        if (eval > eval_max)
        {
          eval_max = eval;
        }
      }
      if (eval_max < eval_max_min)
      {
        eval_max_min = eval_max;
      }
    }
    
    if (position.active == 'w')
    {
      castling_available = (position.white.k_rook_x != NIL || position.white.q_rook_x != NIL);
    }
    else
    {
      castling_available = (position.black.k_rook_x != NIL || position.black.q_rook_x != NIL);
    }
    switch (tolower(position.board[move1.from]))
    {
      case 'k': piece_type = (move1.capture & CASTLING) == CASTLING ? 20 : (castling_available) ? -20 : 0; break;
      case 'r': piece_type = (castling_available) ? -20 : 0; break;
      case 'p': piece_type = 0; break;
      case 'n': piece_type = 0; break;
      case 'b': piece_type = 0; break;
      case 'a': piece_type = 0; break;
      case 'c': piece_type = 0; break;
      case 'q': piece_type = 0; break;
      default : piece_type = 0;
    }
    /*
    placement = evaluate_placement(position2, position.active);
    move_eval[i1] = eval_max_min + placement + piece_type;
    move2str(move1, move_str, sizeof(move_str));
    LOG_LINE("DEBUG Evaluation of move %s = %6d + %2d + %2d = %6d\n", move_str, eval_max_min, placement, piece_type, move_eval[i1])
    */
    placement = evaluate_placement_from_tables(position2, position.active);
    move_eval[i1] = eval_max_min + placement;
    move2str(move1, move_str, sizeof(move_str));
    LOG_LINE("DEBUG Evaluation of move %s = %6d + %2d = %6d\n", move_str, eval_max_min, placement, move_eval[i1])
  }
  best_eval = INT_MIN;
  for (i1 = 0; i1 < movecount1; i1 ++)
  {
    if (move_eval[i1] > best_eval)
    {
      best_eval = move_eval[i1];
    }
  }
  i2 = 0;
  for (i1 = 0; i1 < movecount1; i1 ++)
  {
    if (move_eval[i1] == best_eval)
    {
      best_moves[i2 ++] = i1;
    }
  }
  LOG_LINE("DEBUG %d move%s with best evaluation (%d)\n", i2, i2 > 1 ? "s" : "", best_eval)
  i1 = rand() % i2;
  move1 = moves1[best_moves[i1]];
  return move1;
}
