/*
  SAN Kit Minimal Engine
  ======================
  
  Minimal CECP engine built on Steven Edwards' SAN Kit library */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#include "sandef.h"
#include "sanvar.h"

#include "sanint.h"
#include "sangen.h"
#include "sanmpu.h"
#include "sanfen.h"
#include "sanpce.h"
#include "sanrst.h"
#include "sansel.h"

/*--> MoveToCoord: format a move as plain coordinate notation
      ("e2e4", or "e7e8q" for a promotion) rather than SAN, since
      that is what the CECP protocol expects back */
static void
MoveToCoord (mptrT mptr, char *buf)
{
  fileT ffile, tfile;
  rankT frank, trank;
  pT promo;

  ffile = map_file_sq (mptr->m_frsq);
  frank = map_rank_sq (mptr->m_frsq);
  tfile = map_file_sq (mptr->m_tosq);
  trank = map_rank_sq (mptr->m_tosq);

  buf[0] = acfv[ffile];
  buf[1] = acrv[frank];
  buf[2] = acfv[tfile];
  buf[3] = acrv[trank];
  buf[4] = '\0';

  promo = cv_p_scmvv[mptr->m_scmv];
  if (promo != p_x)
    {
      buf[4] = (char) tolower ((unsigned char) acpv[promo]);
      buf[5] = '\0';
    };

  return;
}

/*--> LooksLikeCoordMove: strict syntax check for "e2e4"-style text,
      so protocol keywords (e.g. "easy", "draw", "hint") are never
      mistaken for a badly-typed move just because they start with a
      letter that happens to also be a file letter */
static siT
LooksLikeCoordMove (const char *s)
{
  size_t len;

  len = strlen (s);
  if ((len != 4) && (len != 5))
    return (0);
  if (!SANCheckFile (s[0]) || !SANCheckRank (s[1]))
    return (0);
  if (!SANCheckFile (s[2]) || !SANCheckRank (s[3]))
    return (0);
  if (len == 5)
    {
      char pc = (char) tolower ((unsigned char) s[4]);
      if ((pc != 'q') && (pc != 'r') && (pc != 'b') && (pc != 'n'))
        return (0);
    };

  return (1);
}

/*--> FindMoveByCoord: locate the legal move matching a coordinate
      string, by generating the legal move list and comparing
      from-square/to-square/promotion piece against each entry --
      SANDecodeAux() only understands SAN text, not coordinates, so
      this is the CECP-side counterpart to it */
static mptrT
FindMoveByCoord (const char *s)
{
  siT i;
  mptrT mptr, found;
  fileT ffile, tfile;
  rankT frank, trank;
  sqT frsq, tosq;
  pT promo;

  if (!LooksLikeCoordMove (s))
    return (NULL);

  ffile = SANEvalFile (s[0]);
  frank = SANEvalRank (s[1]);
  tfile = SANEvalFile (s[2]);
  trank = SANEvalRank (s[3]);
  frsq = map_sq_rank_file (frank, ffile);
  tosq = map_sq_rank_file (trank, tfile);

  promo = p_x;
  if (strlen (s) == 5)
    {
      char pc = (char) tolower ((unsigned char) s[4]);
      if (pc == 'q') promo = p_q;
      else if (pc == 'r') promo = p_r;
      else if (pc == 'b') promo = p_b;
      else if (pc == 'n') promo = p_n;
    };

  SANGenMoves ();

  found = NULL;
  for (i = 0; (found == NULL) && (i < curr_g.g_gmvc); i++)
    {
      mptr = &curr_g.g_base[i];
      if ((mptr->m_frsq == frsq) && (mptr->m_tosq == tosq))
        {
          if (promo == p_x)
            found = mptr;
          else if (cv_p_scmvv[mptr->m_scmv] == promo)
            found = mptr;
        };
    };

  return (found);
}

/*--> AnnounceResultIfOver: tell the GUI when the game has ended
      (checkmate, stalemate, draw by the fifty-move rule, etc.) */
static void
AnnounceResultIfOver (void)
{
  gsT gs;

  gs = SANStatus ();
  if (gs != gs_norm)
    printf ("%s {game over}\n", gtcstrv[cv_gtc_gsv[gs]]);

  return;
}

/*--> EngineMove: ask the active strategy for a move, play it, and
      report it to the GUI in coordinate notation */
static void
EngineMove (void)
{
  char coord[8];

  SANSelect ();
  SANPlayMove (&select_m);
  MoveToCoord (&select_m, coord);
  printf ("move %s\n", coord);
  AnnounceResultIfOver ();

  return;
}

static void
Usage (const char *progname)
{
  ssT s;

  fprintf (stderr, "usage: %s [strategy]\n", progname);
  fprintf (stderr, "  runs a CECP (xboard/WinBoard) engine on stdin/stdout\n");
  fprintf (stderr, "  known strategies (default: eat):");
  for (s = 0; s < ssL; s++)
    fprintf (stderr, " %s", ssstrv[s]);
  fprintf (stderr, "\n");

  return;
}

int
main (int argc, char *argv[])
{
  char line[256];
  ssT strategy;
  siT playing;
  mptrT mptr;

  /* CECP is a line-oriented protocol over stdin/stdout: never let
     output buffering delay a reply */
  setvbuf (stdout, NULL, _IONBF, 0);

  SANInit ();

  strategy = ss_eat;
  if ((argc > 1) && ((strcmp (argv[1], "-h") == 0) ||
                      (strcmp (argv[1], "--help") == 0)))
    {
      Usage (argv[0]);
      return (0);
    };
  if (argc > 1)
    {
      if (SANCheckStrategy (argv[1]))
        strategy = SANEvalStrategy (argv[1]);
      else
        {
          fprintf (stderr, "%s: unknown strategy '%s'\n", argv[0], argv[1]);
          Usage (argv[0]);
          return (1);
        };
    };

  ssv[c_w] = ssv[c_b] = strategy;

  /* the CECP protocol has its own move/board reporting; suppress the
     SAN Kit's own automatic board and move display so it doesn't get
     mixed into the protocol stream */
  optnv[optn_nabd] = 1;
  optnv[optn_namd] = 1;

  playing = 1;          /* auto-reply to the opponent's moves */

  while (fgets (line, sizeof (line), stdin) != NULL)
    {
      line[strcspn (line, "\r\n")] = '\0';

      if (strcmp (line, "xboard") == 0)
        {
          /* nothing to do */
        }
      else if (strncmp (line, "protover", 8) == 0)
        {
          printf ("feature myname=\"SME-A (%s)\" ", ssstrv[strategy]);
          printf ("setboard=1 usermove=1 sigint=0 sigterm=0 ");
          printf ("colors=0 draw=0 analyze=0 variants=\"normal\" done=1\n");
        }
      else if (strcmp (line, "new") == 0)
        {
          SANInitGround ();
          playing = 1;
        }
      else if (strncmp (line, "setboard ", 9) == 0)
        {
          if (!SANFENSet (line + 9))
            fprintf (stderr, "tellusererror illegal FEN\n");
        }
      else if (strcmp (line, "force") == 0)
        {
          playing = 0;
        }
      else if (strcmp (line, "go") == 0)
        {
          playing = 1;
          EngineMove ();
        }
      else if (strcmp (line, "undo") == 0)
        {
          if (hn > 0)
            SANUnplayMove ();
        }
      else if (strcmp (line, "remove") == 0)
        {
          if (hn > 0) SANUnplayMove ();
          if (hn > 0) SANUnplayMove ();
        }
      else if ((strncmp (line, "usermove ", 9) == 0) ||
               LooksLikeCoordMove (line))
        {
          char *mv;

          mv = (strncmp (line, "usermove ", 9) == 0) ? line + 9 : line;
          mptr = FindMoveByCoord (mv);
          if (mptr == NULL)
            printf ("Illegal move: %s\n", mv);
          else
            {
              SANPlayMove (mptr);
              AnnounceResultIfOver ();
              if (playing && (SANStatus () == gs_norm))
                EngineMove ();
            };
        }
      else if (strncmp (line, "ping ", 5) == 0)
        {
          printf ("pong %s\n", line + 5);
        }
      else if (strcmp (line, "quit") == 0)
        {
          break;
        }
      else
        {
          /* silently ignore anything else: level, time, otim, st,
             sd, hard, easy, post, nopost, hint, draw, result,
             black, white, computer, name, rating, ics, accepted,
             rejected, etc. -- see the file header comment */
        };
    };

  return (0);
}
