#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;
unsigned max_turn;
unsigned char prevgameloaded = 0;

#include "defines.c"
#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

#include "setup.c"

#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"
#if defined (SDLFUNCS) || defined (DOS)
#include "timectrl.c"
#endif
#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[MAX_MOVES];
  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 ^ n) & 1) != game_status->turn)
          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);
        move_count = legal_moves(brd, pc_coords, &king_pos, move_list);
//        if(!move_list)
//          outofmemory();
        if(move_count)
        {
//          free(move_list);
          return retval;
        }
      }
    }

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

  return retval;
}

char blstr[] = "Black";
char whstr[] = "White";

// 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 ? whstr : blstr);
      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 && !setup.black_ai) || (!game_status->turn && !setup.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 movecoords *move, unsigned chk)
{
  game_status->last_moves.position[game_status->last_moves.index].board = board;

  memcpy(&game_status->last_moves.lastmoves[game_status->last_moves.index].coords, move,
         sizeof(struct movecoords));
  game_status->last_moves.lastmoves[game_status->last_moves.index].chk = chk;

  check_opening(move);

  game_status->last_moves.index++;
}

void get_move(struct coord king_pos, struct pmove *ret)
{
  char buff[80];
  struct coord pc_coords;
  struct coord new_coords;
  struct coord move_list[MAX_MOVES];
  unsigned char move_count = 0;
  unsigned char *piece;
  struct coord seltile;
  int s;

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

#if defined(SDLFUNCS) || defined(DOS)
  if(graphics)
  {
    if(s = select_tile(move_list, move_count, &seltile, 0))
    {
      sret:if(s == 1)
        *(signed short*)ret = -1;
      return;
    }
  }
  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);
    }

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

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

  pc_coords = seltile;

  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);
  move_count = legal_moves(&board, pc_coords, &king_pos, move_list);
//  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, &seltile, 0);

  if(!move_count) goto selectpc;

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

#if defined(SDLFUNCS) || defined(DOS)
  if(graphics)
  {
    s = select_tile(move_list, move_count, &new_coords, &seltile);
    if(s) goto sret;
  }
  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 == seltile.row && new_coords.col == seltile.col)
  {
    if(move_count)
    {
//      free(move_list);
      move_count = 0;
    }
    draw_board(&board, 0, 0, &seltile, 0);
    goto selectpc;
  }

  if(!move_in_list(move_list, move_count, &new_coords))
  {
    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 promoted. 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->coords.src = pc_coords;
  ret->coords.dst = new_coords;
  ret->resign = 0;
}

void game(void)
{
  struct coord king_pos;
//  struct move move;
  struct move move;
  char chk = 0;
#if defined(SDLFUNCS) || defined(DOS)
  char buf[80];
#endif

  if(!prevgameloaded)
    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

    if(!prevgameloaded)
      turn_count++;
    else
      prevgameloaded = 0;
    if(max_turn > turn_count)
      memset(&game_status->last_moves.position[game_status->last_moves.index], 0,
             (LAST_MOVES_COUNT-game_status->last_moves.index)*sizeof(struct game_position));
    max_turn = turn_count;

    if(setup.show_threatened)
      list_threatened_pieces();
    else
      threat_count = 0;

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

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

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

#if defined(SDLFUNCS) || defined(DOS)
    save_move_start_time();
#endif

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

#if defined(SDLFUNCS) || defined(DOS)
    set_player_time();
    if(playeroutoftime())
    {
      sprintf(buf, "%s ran out of time. %s won.", game_status->turn ? blstr : whstr,
              game_status->turn ? whstr : blstr);
      show_dialog_nokey(buf);
      break;
    }
#endif

    if(*(signed short*)&move == -1 || move.resign)
    {
      if(prevgameloaded) continue;
      else 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.coords);
#endif

    if(chk >= 5) chk = 0;
    log_move(&move.coords, chk);

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

int parse_args(int argc, char *argv[])
{
  unsigned n;
  struct setup old_setup;

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

  old_setup = setup;

  for(n=0;n<argc;n++)
  {
    if(!strcmp(argv[n], "GRAPHICS"))
      flags |= 1;
    else if(!strcmp(argv[n], "WHITE_AI"))
      setup.white_ai = 1;
    else if(!strcmp(argv[n], "BLACK_HUMAN"))
      setup.black_ai = 0;
    else if(!strncmp(argv[n], "DEPTH1=", 7))
      setup.depth1 = atoi(&argv[n][7]);
    else if(!strncmp(argv[n], "DEPTH2=", 7))
      setup.depth2 = atoi(&argv[n][7]);
    else if(!strncmp(argv[n], "BLUNDERS=", 9))
      setup.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;
  }

  return memcmp(&old_setup, &setup, sizeof(struct setup));;
}

int main(int argc, char *argv[])
{

  if(!parse_args(argc-1, argv+1))
#if defined(SDLFUNCS) || defined(DOS)
    load_setup()
#endif
      ;

  srand(time(0));

  game_status = calloc(1, sizeof(struct game_status));
  all_moves = malloc(64*sizeof(struct piece_all_moves));
  if(!game_status || !all_moves) outofmemory();

#ifdef SDLFUNCS
  if(graphics)
  {
    memset(&movekeys, 0, sizeof(struct movekeys));
    init_screen_sdl("ClippyChess");
    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;
}
