/*
  gnuan.c - Analysis interface for gnuchess.

  Revision: 1990-04-18

  Copyright (C) 1986, 1987, 1988, 1989, 1990 Free Software Foundation, Inc.
  Copyright (c) 1988, 1989, 1990  John Stanback

  This file is part of CHESS.

  CHESS is distributed in the hope that it will be useful, but WITHOUT ANY
  WARRANTY.  No author or distributor accepts responsibility to anyone for
  the consequences of using it or for whether it serves any particular
  purpose or works at all, unless he says so in writing.  Refer to the CHESS
  General Public License for full details.

  Everyone is granted permission to copy, modify and redistribute CHESS, but
  only under the conditions described in the CHESS General Public License.
  A copy of this license is supposed to have been given to you along with
  CHESS so you can know your rights and responsibilities.  It should be in a
  file named COPYING.  Among other things, the copyright notice and this
  notice must be preserved on all copies.
*/

/*
  This is a front end for a chess analysis program.  It takes a file of moves
  in algebraic notation and creates a file containing each move that was
  made, the move it would have made, the score it would give itself after
  making the move it recommended, and the depth it searched for the move.

  This is a modification of nondsp.c.  I have cut out code that was not needed
  for this application such as the help and edit functions.  Most of the
  modifications were done in InputCommand.
*/

/*
  This file will generate two different analysis programs.  One is meant to
  be run as a background process inwhich the in and out files are predefined.
  The other is meant to be run in the foreground where it will prompt you for
  the name of the file with the game in it, the maximum depth the search should
  be conducted to and the time limit per move.  It would be nice to be able to
  give these parameters on the command line, but that would mean changing main
  which is in the gnuchess.c file.

  For each move it will analyse the move until either it has run out of time
  or it has reached the maximum depth.

  To build the version for background processing define BACKGROUND_ANALYSIS
  either at the top of this file, or in compilation.  The files and depth
  used are defined below.  They are MAX_DEPTH, MAX_TIME, OUT_FILE, IN_FILE
  and PROG_FILE.  The PROG_FILE is a file that will be updated as each move is
  analysed so you can check its progress.  This is only updated when running
  the BACKGROUND_ANALYSIS version.  In the version where the filename and
  depth are entered at runtime, the output goes to stdout.
*/

/* #define BACKGROUND_ANALYSIS 1 */

#define MAX_DEPTH  15
#define MAX_TIME   20
#define OUT_FILE   "/pad/gnuan.out"
#define IN_FILE    "/pad/gnuan.in"
#define PROG_FILE  "/pad/gnuan.prog"


#include <ctype.h>
#include <signal.h>
#ifdef MSDOS
#include <dos.h>
#include <conio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#else
#include <sys/param.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/ioctl.h>
void TerminateSearch (int), Die (int);
#endif /* MSDOS */

#include "gnuchess.h"

#define pxx " PNBRQK"
#define qxx " pnbrqk"
#define rxx "12345678"
#define cxx "abcdefgh"
#define printz printf
#define scanz scanf

static char mvstr[4][6];
static char* ColorStr[2] = {"White", "Black"};
static long evrate;
static int mycnt1, mycnt2;
static int ahead;

static FILE *fpin;
static FILE *fpout;

#ifdef BACKGROUND_ANALYSIS
static FILE *fpprog;
#endif

static char white_actual_move[20];
static char black_actual_move[20];
static char white_suggest_move[20];
static char black_suggest_move[20];
static int  white_score;
static int  black_score;
static int  white_moving;
static int  current_depth;
static int  current_score;
static int  enable_update_display = 0;


void
Initialize (void)
{
  mycnt1 = mycnt2 = 0;
}

void
ExitChess (void)
{
  ListGame ();
  exit (0);
}

#ifndef MSDOS                           /* never called!!! */
void
Die (int sig)
{
    ExitChess ();
}
#endif /* MSDOS */

void
TerminateSearch (int sig)
{
#ifdef MSDOS
  sig++;                                /* shut up the compiler */
#endif /* MSDOS */
  flag.timeout   = true;
  flag.bothsides = false;
}

void
algbr (short int f, short int t, short int flag)

/*
   Generate move strings in different formats.
*/

{
  int m3p;

  if (f != t)
    {
      /* algebraic notation */
      mvstr[0][0] = cxx[column (f)];
      mvstr[0][1] = rxx[row (f)];
      mvstr[0][2] = cxx[column (t)];
      mvstr[0][3] = rxx[row (t)];
      mvstr[0][4] = mvstr[3][0] = '\0';
      if ((mvstr[1][0] = pxx[board[f]]) == 'P')
        {
          if (mvstr[0][0] == mvstr[0][2])       /* pawn did not eat */
            {
              mvstr[2][0] = mvstr[1][0] = mvstr[0][2];  /* to column */
              mvstr[2][1] = mvstr[1][1] = mvstr[0][3];  /* to row */
              m3p = 2;
            }
          else
            /* pawn ate */
            {
              mvstr[2][0] = mvstr[1][0] = mvstr[0][0];  /* from column */
              mvstr[2][1] = mvstr[1][1] = mvstr[0][2];  /* to column */
              mvstr[2][2] = mvstr[0][3];
              m3p = 3;          /* to row */
            }
          mvstr[2][m3p] = mvstr[1][2] = '\0';
          if (flag & promote)
            {
              mvstr[0][4] = mvstr[1][2] = mvstr[2][m3p] = qxx[flag & pmask];
              mvstr[1][3] = mvstr[2][m3p + 1] = mvstr[0][5] = '\0';
            }
        }
      else
        /* not a pawn */
        {
          mvstr[2][0] = mvstr[1][0];
          mvstr[2][1] = mvstr[0][1];
          mvstr[2][2] = mvstr[1][1] = mvstr[0][2];      /* to column */
          mvstr[2][3] = mvstr[1][2] = mvstr[0][3];      /* to row */
          mvstr[2][4] = mvstr[1][3] = '\0';
          strcpy (mvstr[3], mvstr[2]);
          mvstr[3][1] = mvstr[0][0];
          if (flag & cstlmask)
            {
              if (t > f)
                {
                  strcpy (mvstr[1], "o-o");
                  strcpy (mvstr[2], "O-O");
                }
              else
                {
                  strcpy (mvstr[1], "o-o-o");
                  strcpy (mvstr[2], "O-O-O");
                }
            }
        }
    }
  else
    mvstr[0][0] = mvstr[1][0] = mvstr[2][0] = mvstr[3][0] = '\0';
}


int
VerifyMove (char *s, short int iop, unsigned short int *mv)

/*
   Compare the string 's' to the list of legal moves available for the
   opponent. If a match is found, make the move on the board.
*/

{
  static short pnt, tempb, tempc, tempsf, tempst, cnt;
  static struct leaf xnode;
  struct leaf *node;

  *mv = 0;
  if (iop == 2)
    {
      UnmakeMove (opponent, &xnode, &tempb, &tempc, &tempsf, &tempst);
      return (false);
    }
  cnt = 0;
  MoveList (opponent, 2);
  pnt = TrPnt[2];
  while (pnt < TrPnt[3])
    {
      node = &Tree[pnt++];
      algbr (node->f, node->t, (short) node->flags);
      if (strcmp (s, mvstr[0]) == 0 || strcmp (s, mvstr[1]) == 0 ||
          strcmp (s, mvstr[2]) == 0 || strcmp (s, mvstr[3]) == 0)
        {
          cnt++;
          xnode = *node;
        }
    }
  if (cnt == 1)
    {
      MakeMove (opponent, &xnode, &tempb, &tempc, &tempsf, &tempst, &INCscore);
      if (SqAtakd (PieceList[opponent][0], computer))
        {
          UnmakeMove (opponent, &xnode, &tempb, &tempc, &tempsf, &tempst);
          printz ("Illegal move\n");
          return (false);
        }
      else
        {
          if (iop == 1)
            return (true);
/*        UpdateDisplay (xnode.f, xnode.t, 0, (short) xnode.flags); */
          if ((board[xnode.t] == pawn)
              || (xnode.flags & capture)
              || (xnode.flags & cstlmask))
            {
              Game50 = GameCnt;
              ZeroRPT ();
            }
          GameList[GameCnt].depth = GameList[GameCnt].score = 0;
          GameList[GameCnt].nodes = 0;
          ElapsedTime (1);
          GameList[GameCnt].time = (short) et;
          TimeControl.clock[opponent] -= et;
          --TimeControl.moves[opponent];
          *mv = (xnode.f << 8) | xnode.t;
          algbr (xnode.f, xnode.t, false);
          return (true);
        }
    }
#ifdef CHESSTOOL
  printz ("Illegal move\n");
#else
  if (cnt > 1)
    ShowMessage ("Ambiguous Move!");
#endif
  return (false);
}

void
help (void)
{
}

void
EditBoard (void)
/*
   Set up a board position. Pieces are entered by typing the piece followed
   by the location. For example, Nf3 will place a knight on square f3.
*/

{
}

void
SetupBoard (void)

/*
   Compatibility with Unix chess and the nchesstool.
   Set up a board position. Eight lines of eight characters are used
   to setup the board. a8-h8 is the first line.
   Black pieces are  represented  by  uppercase characters.
*/

{
}

void
ShowDepth (char ch)
{
#ifdef MSDOS
  ch++;                                 /* shut up the compiler */
#endif /* MSDOS */
}

void
ShowResults (short int score, unsigned short int *bstline, char ch)
{
    current_score = score;
    current_depth = Sdepth;
}

void
SearchStartStuff (short int side)
{
  signal (SIGINT, TerminateSearch);
#ifndef MSDOS
  signal (SIGQUIT, TerminateSearch);
#endif /* MSDOS */
  if (flag.post)
    {
      fprintf (stderr, "\nMove# %d    Target= %ld    Clock: %ld\n",
               TCmoves - TimeControl.moves[side] + 1,
               ResponseTime, TimeControl.clock[side]);
    }
}

void
OutputMove (void)
{
    if(white_moving) {
        strcpy(white_suggest_move, mvstr[0]);
        white_score = current_score;
    } else {
        strcpy(black_suggest_move, mvstr[0]);
        black_score = current_score;
    }
}

void
ElapsedTime (short int iop)
/*
   Determine the time that has passed since the search was started. If the
   elapsed time exceeds the target (ResponseTime+ExtraTime) then set timeout
   to true which will terminate the search.
*/
{
  if (ahead)
    {
#ifndef MSDOS
      long nchar;
      ioctl(0, FIONREAD, &nchar);
      if (nchar)
#else
      if (kbhit())
#endif /* MSDOS */
        {
          flag.timeout = true;
          flag.bothsides = false;
        }
    }
  et = time ((long *) 0) - time0;
  if (et < 0)
    et = 0;
  ETnodes += 50;
  if (et > et0 || iop == 1)
    {
      if (et > ResponseTime + ExtraTime && Sdepth > 1)
        flag.timeout = true;
      et0 = et;
      if (iop == 1)
        {
          time0 = time ((long *) 0);
          et0 = 0;
        }
      if (et > 0)
        /* evrate used to be Nodes / cputime I dont` know why */
        evrate = NodeCnt / (et + ft);
      else
        evrate = 0;
      ETnodes = NodeCnt + 50;
    }
}

void
SetTimeControl (void)
{
  if (TCflag)
    {
      TimeControl.moves[white] = TimeControl.moves[black] = TCmoves;
      TimeControl.clock[white] = TimeControl.clock[black] = 60L * TCminutes;
    }
  else
    {
      TimeControl.moves[white] = TimeControl.moves[black] = 0;
      TimeControl.clock[white] = TimeControl.clock[black] = 0;
      Level = 60L * TCminutes;
    }
  et = 0;
  ElapsedTime (1);
}

void
ClrScreen (void)
{
#ifndef CHESSTOOL
  printz ("\n");
#endif
}

void
UpdateDisplay (short int f, short int t, short int redraw, short int isspec)
{
#ifndef CHESSTOOL
  short r, c, l;
  if(!enable_update_display) return;
  if (redraw)
    {
      fprintf(fpout, "\n");
      for (r = 7; r >= 0; r--)
        {
          for (c = 0; c <= 7; c++)
            {
              if (flag.reverse)
                l = locn (7 - r, 7 - c);
              else
                l = locn (r, c);
              if (color[l] == neutral)
                fprintf (fpout, " -");
              else if (color[l] == white)
                fprintf (fpout, " %c", qxx[board[l]]);
              else
                fprintf (fpout, " %c", pxx[board[l]]);
            }
          fprintf(fpout, "\n");
        }
      fprintf(fpout, "\n");
    }
#endif /* CHESSTOOL */
#ifdef MSDOS
  f++; t++; isspec++;                   /* shut up the compiler */
#endif /* MSDOS */
}

void
GetGame (void)
{
}

void
SaveGame (void)
{
}

void
ListGame (void)
{
}

void
Undo (void)
/*
   Undo the most recent half-move.
*/

{
  short f, t;
  f = GameList[GameCnt].gmove >> 8;
  t = GameList[GameCnt].gmove & 0xFF;
  if (board[t] == king && distance (t, f) > 1)
    (void) castle (GameList[GameCnt].color, f, t, 2);
  else
    {
      /* Check for promotion: */
      if ((row (f) == 6 && row (t) == 7) || (row (f) == 1 && row (t) == 0))
        {
          int g, from = f, c= color[t];
          for (g = GameCnt - 1; g >= 0; g--)
            if (GameList[g].gmove & 0xFF == from)
              from = GameList[g].gmove >> 8;
          if ((row(from) == 1 && row(f) == 6)
             || (row(from) == 6 && row(f) == 1))
            board[t] = pawn;
        }

      board[f] = board[t];
      color[f] = color[t];
      board[t] = GameList[GameCnt].piece;
      color[t] = GameList[GameCnt].color;
      if (color[t] != neutral)
      Mvboard[t]--;
      Mvboard[f]--;
    }
  if (TCflag)
    ++TimeControl.moves[color[f]];
  GameCnt--;
  computer = otherside[computer];
  opponent = otherside[opponent];
  flag.mate = false;
  Sdepth = 0;
/*  UpdateDisplay (0, 0, 1, 0); */
  InitializeStats ();
}

void
ShowMessage (char *s)
{
  fprintf (stderr, "%s\n", s);
}

void
ShowSidetomove (void)
{
}

void
PromptForMove (void)
{
#ifndef CHESSTOOL
  printz ("\nYour move is? ");
#endif /* CHESSTOOL */
}


void
ShowCurrentMove (short int pnt, short int f, short int t)
{
#ifdef MSDOS
  f++; t++; pnt++;                      /* shut up the compiler */
#endif /* MSDOS */
}

void
ChangeAlphaWindow (void)
{
  printz ("window: ");
  scanz ("%hd", &Awindow);
}

void
ChangeBetaWindow (void)
{
  printz ("window: ");
  scanz ("%hd", &Bwindow);
}

void
GiveHint (void)
{
  algbr ((short) (hint >> 8), (short) (hint & 0xFF), false);
  fprintf (stderr, "Hint: %s\n", mvstr[0]);
}

void
SelectLevel (void)
{
}

void
ChangeSearchDepth (void)
{
  printz ("depth= ");
  scanz ("%hd", &MaxSearchDepth);
}

void
SetContempt (void)
{
  printz ("contempt= ");
  scanz ("%hd", &contempt);
}

void
ChangeXwindow (void)
{
  printz ("xwndw= ");
  scanz ("%hd", &xwndw);
}

void
TestSpeed(void (*f) (short int side, short int ply))
{
  short i;
  long cnt, rate, t1, t2;

  t1 = time (0);
  for (i = 0; i < 10000; i++)
    {
      f (opponent, 2);
    }
  t2 = time (0);
  cnt = 10000L * (TrPnt[3] - TrPnt[2]);
  rate = cnt / (t2 - t1);
  printz ("Nodes= %ld Nodes/sec= %ld\n", cnt, rate);
}


int GetNextMove(char *buffer)
    {
        char current;

        while((current = fgetc(fpin)) == ' ' || current == '\n') ;
        if(current == EOF) return(0);
        while(current != ' ' && current != '\n' && current != EOF) {
            if(!(current == '-' || current == 'x' || current == 'X')) {
                *buffer++ = current;
            }
            current = fgetc(fpin);
        }
        *buffer = '\0';
        return(1);
    }

void InputCommand(void)
/*
    Open the file of moves to analyse.  Go through the file move by move
    and do the following for each move.  See what gnuchess would have done
    in that position.  Record the move it would have made along with the
    score it would have given and the search depth.  Take back its move.
    Force the move that was actually made.  Keep track of statistics such
    as how many moves agreed.
*/

{
    int i;
    short ok,tmp;
    int search_depth, max_minutes;
    long cnt,rate,t1,t2;
    unsigned short mv;
    char s[80];
    char infilename[255];
    char outfilename[255];
    char progfilename[255];
    int move_number = 1;
    long start_time, end_time, elapsed_time;
    int total_black_moves, total_white_moves;
    int same_black_moves, same_white_moves;
    float white_percent, black_percent;

    /* Initialize necessary variables. */

    flag.quit = false;
    flag.beep = false;
    player = opponent;
    enable_update_display = 1;
    ft = 0;
    Book = NULL;
    total_black_moves = 0;
    total_white_moves = 0;
    same_black_moves  = 0;
    same_white_moves  = 0;

#ifdef BACKGROUND_ANALYSIS

    /* Set the in files to standard ones for analysis if background processing selected. */

    strcpy(infilename,   IN_FILE);
    strcpy(outfilename,  OUT_FILE);
    strcpy(progfilename, PROG_FILE);
    fpout = fopen(outfilename, "w");
    if(fpout == NULL) {
        fprintf(fpout,"This file does not exist : %s\n", outfilename);
        flag.quit = 1;
        return;
    }

    MaxSearchDepth = MAX_DEPTH;
    TCminutes = MAX_TIME;

#else

    /* Request information on where the file is and the depth an time. */

    fpout = stderr;
    fprintf(fpout, "Input the file with the algebraic moves of the game.\n");
    scanf("%s", infilename);

    fprintf(fpout, "\n");
    do {
        fprintf(fpout, "Input the search depth you want to use.  (1 to 29)\n");
        scanf("%d", &search_depth);
    } while(search_depth < 1 || search_depth > 29);
    MaxSearchDepth = search_depth;

    fprintf(fpout, "\n");
    do {
        fprintf(fpout, "Input the maximun number of minutes per move you want to use.\n");
        scanf("%d", &max_minutes);
    } while(max_minutes < 1);
    TCminutes = max_minutes;

    fprintf(fpout, "\n\n");

#endif

    fpin  = fopen(infilename, "r");
    if(fpin == NULL) {
        fprintf(fpout,"This file does not exist : %s\n", infilename);
        flag.quit = 1;
        return;
    }

    TCmoves = 1;
    TCflag = (TCmoves > 1);
    OperatorTime = 0;
    SetTimeControl();

    fpout = stdout;

    fprintf(fpout, "           Move Actual   Suggest Score Depth     Actual   Suggest Score Depth\n");
    fprintf(fpout, "           ------------------------------------------------------------------\n");

#ifdef BACKGROUND_ANALYSIS

    /* Update progress in the progress file if this is running in the background. */

    fpprog = fopen(progfilename, "w");
    fprintf(fpprog, "Done with move #%d.\n", move_number-1);
    fclose(fpprog);

#endif

    time(&start_time);
    while(1) {
        opponent = black;
        computer = white;
        player = computer;
        white_moving = 1;
        if(!GetNextMove(s)) {
            flag.quit = 1;
            break;
        }
        flag.force = 0;
        SelectMove(computer,1);
        Undo();
        flag.force = 1;
        opponent = white;
        computer = black;
        player = opponent;
        ok = VerifyMove(s,0,&mv);
        if(!ok) {
            fprintf(fpout, "\nBad move.  %s  Board position is: \n",s);
            UpdateDisplay(0, 0, 1, 0);
            flag.quit = 1;
            break;
        } else {
            strcpy(white_actual_move, s);
            fprintf(fpout, "           %3d   %-9s%-7s%5d%5d      ",move_number, white_actual_move, white_suggest_move, white_score, current_depth);
            fflush(fpout);
            move_number++;
            total_white_moves++;
            if(!strcmp(white_actual_move, white_suggest_move)) same_white_moves++;
            Sdepth = 0;
            ft = 0;
        }
        player = computer;
        white_moving = 0;
        if(!GetNextMove(s)) {
            flag.quit = 1;
            break;
        }
        flag.force = 0;
        SelectMove(computer,1);
        Undo();
        flag.force = 1;
        opponent = black;
        computer = white;
        player = opponent;
        ok = VerifyMove(s,0,&mv);
        if(!ok) {
            fprintf(fpout, "\nBad move.  %s  Board position is: \n",s);
            UpdateDisplay(0, 0, 1, 0);
            flag.quit = 1;
            break;
        } else {
            strcpy(black_actual_move, s);
            fprintf(fpout, " %-9s%-7s%5d%5d\n", black_actual_move, black_suggest_move, black_score, current_depth);
            fflush(fpout);

#ifdef BACKGROUND_ANALYSIS

            /* Update progress in the progress file if this is running in the background. */

            fpprog = fopen(progfilename, "w");
            fprintf(fpprog, "Done with move #%d.\n", move_number-1);
            fclose(fpprog);
#else
/*            fprintf(stderr, "Done with move #%d.\n", move_number-1); */

#endif

            total_black_moves++;
            if(!strcmp(black_actual_move, black_suggest_move)) same_black_moves++;
            Sdepth = 0;
            ft = 0;
        }
    }

    white_percent = ((float) same_white_moves) * 100 / (float) total_white_moves;
    black_percent = ((float) same_black_moves) * 100 / (float) total_black_moves;
    fprintf(fpout, "\n           White's percentage was %5.2f%%.     Black's percentage was %5.2f%%.\n",  white_percent, black_percent);
    time(&end_time);
    elapsed_time = end_time - start_time;
    fprintf(fpout, "\n           Elapsed time was %d seconds.\n", elapsed_time);
    fclose(fpin);
    fclose(fpout);
    return;
}

