#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <stdint.h>
#include <unistd.h>

#ifdef DOS
#include <conio.h>
#include <malloc.h>
#endif

struct game_status *game_status;
unsigned turn_count;

#include "structs.c"

#include "proto.c"

struct board board;

struct coord find_king(struct board *brd, unsigned char side);

#ifdef DEBUG
void print_last_pos(void);
#endif

unsigned char flags = 0;
#define graphics (flags&1)
#define dontstop (flags&2)

void outofmemory(void);

#define GAME
#if defined(SDLFUNCS)
struct movekeys movekeys;
#include "sdlfuncs.c"
#include "dbuff.c"
#include "fonts.c"
#include "bitmap.c"
#include "draword.c"
#include "clippy.c"
#include "graphics.c"

#elif defined(DOS)


#include "dbuff.c"
#include "dos.c"
#include "fonts.c"
#include "bitmap.c"
#include "draword.c"
#include "clippy.c"
#include "graphics.c"

#else
#include "graphics.c"
#define show_message(x, y, str) puts(str)
#endif

struct menu *mainmenu;
int show_menu(struct menu *menu);

#include "pieces.c"
#include "board.c"
#include "debug.c"
#include "opening.c"
#include "ai.c"
#include "menus.c"

void outofmemory(void)
{
  puts("Out of memory.");
  exit(1);
}

// returns 1 if check, 2 if stalemate, 3 if checkmate, 4 if three repetitions
// 5 if 50 moves without pawn move or capture
// 6 if 75 moves without pawn move or capture
int check_stale(struct board *brd, struct coord king_pos, unsigned char depth)
{
  unsigned char row, col;
  unsigned char move_count;
  struct coord pc_coords;
  struct coord *move_list;
  int retval = 0;
  unsigned char piece;
  unsigned char side = piece_in_tile(brd, king_pos.row, king_pos.col) & PC_BLACK;
  unsigned char reps;
  unsigned n;

  if(depth <= 2)
  {
    for(reps=0,n=0;reps<3&&n<LAST_MOVES_COUNT;n++)
    {
      if(!memcmp(&game_status->last_moves.position[n].board, brd, sizeof(struct board)))
        if(!depth || !(game_status->last_moves.position[n].turn ^ (depth&1)))
          reps++;
    }
    if((!depth && reps >= 3) || (depth && reps >= 2)) return 4;
  }

  if(game_status->moves_pawn_capture > 50)
    return 5;

  if(game_status->moves_pawn_capture > 75)
    return 6;

  if(king_threatened(brd, &king_pos, king_pos))
  {
    retval++;
  }

  for(row=0;row<8;row++)
    for(col=0;col<8;col++)
    {
      if((piece = tile_occupied(brd, row, col))
         && !((piece & PC_BLACK) ^ (side)))
      {
        pc_coords.row = row;
        pc_coords.col = col;
        move_list = legal_moves(brd, pc_coords, &move_count, &king_pos);
        if(!move_list)
          outofmemory();
        if(move_count)
        {
          free(move_list);
          return retval;
        }
      }
    }

  if(!move_count)
  {
    retval += 2;
  }

  return retval;
}

// return 1 if game ended
int check_move_stale(struct board *brd, struct coord king_pos, unsigned char turn)
{
  unsigned char mate;
  struct piece_count white_pieces;
  struct piece_count black_pieces;
  char msg[80];

  if((mate = check_stale(brd, king_pos, 0)))
  {
    if(mate == 2)
    {
      show_dialog_nokey("Stalemate.");
      return 1;
    }
    else if(mate == 3)
    {
      sprintf(msg, "Checkmate. %s won.", game_status->turn ? "White" : "Black");
      show_dialog_nokey(msg);
      return 1;
    }
    else if(mate == 4)
    {
      if(turn_count > 3)
      {
        show_dialog_nokey("Draw: Third repetition.");
        return 1;
      }
    }
    else if(mate == 6)
    {
      if(!(rand() & 0x3))
      {
        sprintf(msg, "%u moves without pawn move or capture - declaring a draw.", game_status->moves_pawn_capture);
        show_dialog_nokey(msg);
        return 1;
      }
    }
    else
    {
      if((game_status->turn && !black_ai) || (!game_status->turn && !white_ai))
        show_dialog_nokey("CHECK.");
      return 0;
    }
  }

  piece_count(&board, 0, &white_pieces);

  piece_count(&board, 1, &black_pieces);

  if(!white_pieces.queen_count && !white_pieces.rook_count && !white_pieces.bishop_count
     && !white_pieces.knight_count && !white_pieces.pawn_count &&
     !black_pieces.queen_count && !black_pieces.rook_count && !black_pieces.bishop_count
     && !black_pieces.knight_count && !black_pieces.pawn_count)
  {
    show_dialog_nokey("Draw: Not enough pieces on the board to continue.");
    return 1;
  }
  return 0;
}

void coord_to_str(struct coord coord, char *str)
{
  str[0] = coord.col + 'a';
  str[1] = 7 - coord.row + '1';
}

void log_move(struct move *move, unsigned chk)
{
  char *ptr;
  unsigned n;
  unsigned char piece;

  game_status->last_moves.position[game_status->last_moves.index].board = board;
  game_status->last_moves.position[game_status->last_moves.index].turn = game_status->turn;
  game_status->last_moves.index++;

  if(game_status->turn)
  {
    memcpy(game_status->last_moves.last_moves_black[0],
           game_status->last_moves.last_moves_black[1],
           sizeof(game_status->last_moves.last_moves_black[0]));
    ptr = game_status->last_moves.last_moves_black[1];
  }
  else
  {
    memcpy(game_status->last_moves.last_moves_white[0],
           game_status->last_moves.last_moves_white[1],
           sizeof(game_status->last_moves.last_moves_white[0]));
    ptr = game_status->last_moves.last_moves_white[1];
  }

  sprintf(ptr, "%u.  ", (1+turn_count)>>1);
  piece = piece_in_tile(&board, move->dst.row, move->dst.col) & 0x7;
  if(piece == PC_KING)
  {
    if(move->dst.col - move->src.col == 2)
    {
      strcpy(ptr+4, "0-0");
      n = 7;
    }
    else if(move->src.col - move->dst.col == 2)
    {
      strcpy(ptr+4, "0-0-0");
      n = 9;
    }
    else goto nospmove;
  }
  else
  {
    nospmove:
    coord_to_str(move->src, ptr+4);
    ptr[6] = '-';
    coord_to_str(move->dst, ptr+7);
    n = 9;
  }
  if(chk == 1) ptr[n++] = '+';
  ptr[n] = 0;

  check_opening(ptr+4);

}

struct move get_move(struct coord king_pos)
{
  char buff[80];
  struct coord pc_coords;
  struct coord new_coords;
  struct coord *move_list;
  unsigned char move_count = 0;
  unsigned char *piece;
  unsigned char selrow;
  unsigned char selcol;
  struct move ret;

  selectpc:
  if(!graphics)
    puts("Select piece.");

#if defined(SDLFUNCS) || defined(DOS)
  if(graphics)
  {
    if(select_tile(move_list, move_count, &selrow, &selcol))
    {
      *(signed short*)&ret = -1;
      return ret;
    }
  }
  else
  {
#endif
    fgets(buff, sizeof(buff), stdin);

    if(buff[0] == '\n')
      goto selectpc;


    if(!strncmp(buff, "draw", 4))
    {
      if(game_status->moves_pawn_capture > 50)
      {
        show_dialog_nokey("Draw.");
        exit(0);
      }
      else
      {
        sprintf(buff, "Only %u moves since pawn moved or piece captured.", game_status->moves_pawn_capture);
        show_dialog_nokey(buff);
        goto selectpc;
      }
    }

    if(!strncmp(buff, "exit", 4))
    {
      exit(0);
    }

    selcol = buff[0]-'a';
    selrow = buff[1]-'1';
    selrow = 7 - selrow;
#if defined(SDLFUNCS) || defined(DOS)
  }
#endif

  if(selcol >= 8 || selrow >= 8)
  {
    show_dialog_nokey("That tile does not exist.");
    goto draw;
  }

  pc_coords.col = selcol;
  pc_coords.row = selrow;

  if(!tile_occupied(&board, pc_coords.row, pc_coords.col))
  {
    show_dialog_nokey("There is no piece in that tile.");
    goto draw;
  }

  piece = ptr_to_tile(&board, pc_coords.row, pc_coords.col);

  if((game_status->turn && !(*piece & PC_BLACK))
     || (!game_status->turn && (*piece & PC_BLACK)))
  {
    show_dialog_nokey("You cannot move your opponent's piece.");
    goto draw;
  }

  move_list = legal_moves(&board, pc_coords, &move_count, &king_pos);
  if(!move_list)
    outofmemory();

  if(!move_count)
  {
    show_dialog_nokey("There are no legal moves for this piece.");
  }

  draw:draw_board(&board, move_list, move_count, selrow, selcol);

  if(!move_count) goto selectpc;

  if(!graphics)
    puts("Select where to move.");

#if defined(SDLFUNCS) || defined(DOS)
  if(graphics)
    select_tile(move_list, move_count, &new_coords.row, &new_coords.col);
  else
  {
#endif
    fgets(buff, sizeof(buff), stdin);

    if(buff[0] == '\n') goto selectpc;

    new_coords.col = (buff[0]-'a') & 0x1F;
    new_coords.row = buff[1]-'1';
    new_coords.row = 7 - new_coords.row;
#if defined(SDLFUNCS) || defined(DOS)
  }
#endif

  if(new_coords.row == selrow && new_coords.col == selcol)
    goto draw;

  if(!move_in_list(move_list, move_count, new_coords.row, new_coords.col))
  {
    show_dialog_nokey("That move is not legal.");
    free(move_list);
    move_count = 0;
    goto draw;
  }
  if(move_piece(&board, pc_coords, new_coords))
  {
    ascend:switch(show_dialog("This pawn must be ascended. Select: [Q]ueen, [K]night, [B]ishop, [R]ook"))
    {
      case 'Q': case 'q':
        ascend_piece(&board, new_coords, PC_QUEEN);
        break;
      case 'K': case 'k':
        ascend_piece(&board, new_coords, PC_KNIGHT);
        break;
      case 'B': case 'b':
        ascend_piece(&board, new_coords, PC_BISHOP);
        break;
      case 'R': case 'r':
        ascend_piece(&board, new_coords, PC_ROOK);
        break;
      default: goto ascend;
    }
  }
  free(move_list);
  ret.src = pc_coords;
  ret.dst = new_coords;

  return ret;
}

void game(void)
{
  struct coord king_pos;
  struct move move;
  char chk = 0;

  reset_openings();

  while(1)
  {
#ifdef DOS
    if(kbhit())
      if(getch() == 27)
        if(show_menu(mainmenu))
          break;
#endif
#ifdef SDLFUNCS
    if(graphics)
      if(poll_keypress() == KEY_ESC)
        if(show_menu(mainmenu))
          break;
#endif

    turn_count++;

    draw_board(&board, 0, 0, -1, -1);

    king_pos = find_king(&board, game_status->turn);
    if(check_move_stale(&board, king_pos, game_status->turn))
      break;

    if(game_status->turn == TURN_WHITE)
    {
      if(!graphics)
        printf("%u: White's turn.\n", turn_count);
      if(!white_ai)
        move = get_move(king_pos);
      else
        move = do_ai_move(king_pos);
    }
    else
    {
      if(!graphics)
        printf("%u: Black's turn.\n", turn_count);
      if(!black_ai)
        move = get_move(king_pos);
      else
        move = do_ai_move(king_pos);
    }

    if(*(signed short*)&move == -1)
      break;


    king_pos = find_king(&board, !game_status->turn);
    chk = check_stale(&board, king_pos, !game_status->turn);
#if defined(SDLFUNCS) || defined(DOS)
    slide_piece(&move);
#endif

    log_move(&move, chk);

    game_status->turn = ~game_status->turn;
  }
}

void parse_args(int argc, char *argv[])
{
  unsigned n;

  black_ai = 1;
  white_ai = 0;
  depth1 = 3;
  depth2 = 6;

  for(n=0;n<argc;n++)
  {
    if(!strcmp(argv[n], "GRAPHICS"))
      flags |= 1;
    else if(!strcmp(argv[n], "WHITE_AI"))
      white_ai = 1;
    else if(!strcmp(argv[n], "BLACK_HUMAN"))
      black_ai = 0;
    else if(!strncmp(argv[n], "DEPTH1=", 7))
      depth1 = atoi(&argv[n][7]);
    else if(!strncmp(argv[n], "DEPTH2=", 7))
      depth2 = atoi(&argv[n][7]);
    else if(!strncmp(argv[n], "BLUNDERS=", 9))
      blunders = atoi(&argv[n][9]);
    else if(!strncmp(argv[n], "DEADEND=", 8))
      global_deadend_score = -atoi(&argv[n][8]);
    else if(!strncmp(argv[n], "LIMIT=", 6))
      deadend_limit = atoi(&argv[n][6]);
    else if(!strcmp(argv[n], "CONT"))
      flags |= 2;
  }
}

int main(int argc, char *argv[])
{
  parse_args(argc-1, argv+1);

  srand(time(0));

  game_status = calloc(1, sizeof(struct game_status));
  if(!game_status) outofmemory();

#ifdef SDLFUNCS
  if(graphics)
  {
    memset(&movekeys, 0, sizeof(struct movekeys));
    init_screen_sdl("Shakki");
    load_sprites();
  }
#endif
#ifdef DOS
  if(graphics)
  {
    puts("Allocating double buffer...");
    __asm
    {
      mov ax, 0x000D
      int 0x10
    }
    puts("Loading sprites...");
    load_sprites();
  }
  atexit(restore_display_mode);
#endif

  init_openings();

#if defined(SDLFUNCS) || defined(DOS)
  if(graphics)
  {
    if(init_menus() || init_clippy())
      outofmemory();
    show_menu(mainmenu);
  }
  else
#endif
    start_game(0);

  return 0;
}
