/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * This is GNU Go, a Go program. Contact gnugo@gnu.org, or see       *
 * http://www.gnu.org/software/gnugo/ for more information.          *
 *                                                                   *
 * Copyright 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,   *
 * 2008 and 2009 by the Free Software Foundation.                    *
 *                                                                   *
 * This program is free software; you can redistribute it and/or     *
 * modify it under the terms of the GNU General Public License as    *
 * published by the Free Software Foundation - version 3             *
 * or (at your option) any later version.                            *
 *                                                                   *
 * This program is distributed in the hope that it will be useful,   *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of    *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     *
 * GNU General Public License in file COPYING for more details.      *
 *                                                                   *
 * You should have received a copy of the GNU General Public         *
 * License along with this program; if not, write to the Free        *
 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,       *
 * Boston, MA 02111, USA.                                            *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

/*
 * This program sits between a GTP client and a GTP engine,
 * passing commands back and forth and modifying them in 
 * some cases.
 *
 * To the client it appears to be a GTP engine. 
 *                
 *             stdin             pipe a
 *  GTP client ----> metamachine -----> GTP engine
 *             <----             <-----
 *            stdout             pipe b
 *
 * Most commands are passed verbatim to the engine. The
 * exception is gg_genmove, which is intercepted then
 * processed differently. The top two moves are both
 * tried, the position evaluated by estimate_score,
 * and the move yielding the higher score is selected.
 *
 * Usage: no arguments gives normal GTP behavior.
 * 'metamachine --debug' sends diagnostics to stderr.  */

#include <stdio.h>
#include <unistd.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>

void error(const char *msg);
void gprintf(FILE *outputfile, const char *fmt, ...);
void trace(const char *fmt, ...);
void tell_gnugo(char *gnugo_line, const char *msg);

int boardsize = 19;
char delimiters[] = " \t\r\n";
char gnugo_line[128], client_line[128];
FILE *to_gnugo_stream, *from_gnugo_stream;

int debug = 0;

#define EMPTY        0
#define WHITE        1
#define BLACK        2

#define GTP_BUFSIZE  1000

void ask_gnugo(char *line, int verbose, const char *msg);

int
main(int argc, char *const *argv)
{
  int pfd_a[2];
  int pfd_b[2];
  int id;
  int k;
  char command[GTP_BUFSIZE];

  for (k = 1; k < argc; k++)
    if (argc > 1 && strstr(argv[k], "debug"))
      debug = 1;
  if (pipe(pfd_a) == -1)
    error("can't open pipe a");
  if (pipe(pfd_b) == -1)
    error("can't open pipe b");
  switch (fork()) {
  case -1:
    error("fork failed (try chopsticks)");
  case 0:
    /* Attach pipe a to stdin */
    if (dup2(pfd_a[0], 0) == -1) 
      error("dup pfd_a[0] failed");
    /* attach pipe b to stdout" */
    if (dup2(pfd_b[1], 1) == -1)
      error("dup pfd_b[1] failed");
    execlp("gnugo", "gnugo", "--mode", "gtp", "--quiet", NULL);
    error("execlp failed");
  }
  /* Attach pipe a to to_gnugo_stream  */
  to_gnugo_stream = fdopen(pfd_a[1], "w");
  /* Attach pipe b to from_gnugo_stream */
  from_gnugo_stream = fdopen(pfd_b[0], "r");
  
  while (1) {
    char *p;
    int n;

    if (!fgets(client_line, GTP_BUFSIZE, stdin)
	|| (strstr(client_line, "quit") == client_line)) {
      tell_gnugo("quit\n", "a");
	return 1;
    }

    /* remove comments */
    if ((p = strchr(client_line, '#')) != NULL)
      *p = 0;
    
    p = client_line;

    /* Look for an identification number. */
    if (sscanf(p, "%d%n", &id, &n) == 1)
      p += n;
    else
      id = -1; /* No identification number. */
    trace("id = %d\n", id);

    /* Look for command name. */
    if (sscanf(p, " %s %n", command, &n) < 1)
      continue; /* Whitespace only on this line, ignore. */
    p += n;
    trace("command: %s\n", command);

    if (!strncmp(command, "boardsize", 9)) {
      char *token;
      tell_gnugo(client_line, "b");
      ask_gnugo(gnugo_line, 1, "1");

      token = strtok(client_line, delimiters);
      token = strtok(NULL, delimiters);
      boardsize = atoi(token);
    }
    else if (!strncmp(command, "gg_genmove", 10)) {
      int move_i[10], move_j[10];
      float move_value[10], position_value[10];
      int moves_considered;
      int k;
      char *token;
      int line_length = 0;
      int color;

      if (strstr(client_line, "black"))
	color = BLACK;
      else if (strstr(client_line, "white"))
	color = WHITE;
      else {
	color = EMPTY;
	printf("?\n\n");
      }

      if (color == BLACK)
	tell_gnugo("top_moves_black\n", "c");
      else 
	tell_gnugo("top_moves_white\n", "d");

      ask_gnugo(gnugo_line, 0, "2");
      token = strtok(gnugo_line, delimiters);
      for (k = 0; k < 10; k++) {
	move_i[k] = -1;
	move_j[k] = -1;
	move_value[k] = 0.0;
      }
      for (k = 0; k < 10; k++) {
	token = strtok(NULL, delimiters);
	if (!token)
	  break;
	string_to_location(boardsize, token, move_i+k, move_j+k);
	token = strtok(NULL, delimiters);
	if (!token)
	  break;
	sscanf(token, "%f", move_value+k);
	trace("move %d: %m valued %f\n", k,
		  move_i[k], move_j[k], move_value[k]);
      }
      moves_considered = k;
      if (debug)
	fprintf(stderr, "moves considered: %d\n",
		moves_considered);
      for (k = 0; k < 2 && k < moves_considered; k++) {
	float upper, lower;
	int n;

	trace("%s %m\n", color == BLACK ? "black" : "white",
		  move_i[k], move_j[k]);
	gprintf(to_gnugo_stream, "%s %m\n", color == BLACK ? "black" : "white",
		move_i[k], move_j[k]);
	fflush(to_gnugo_stream);
	ask_gnugo(gnugo_line, 0, "3");
	tell_gnugo("estimate_score\n", "e");
	ask_gnugo(gnugo_line, 0, "4");
	strtok(gnugo_line, "()\n");
	token = strtok(NULL, "()\n");
	trace("%s\n", token);
	sscanf(token, "upper bound: %f, lower: %f%n",
	       &upper, &lower, &n);
	if (n < 2)
	  error("can't read territory");
	trace("upper %f, lower %f\n", upper, lower);
	tell_gnugo("undo\n", "f");
	ask_gnugo(gnugo_line, 0, "5");
	fflush(stdout);
	if (color == BLACK)
	  position_value[k] = - upper;
	else
	  position_value[k] = lower;
	trace("position value %f\n", position_value[k]);
      }
      if (moves_considered == 0) {
	if (id == -1)
	  gprintf(stdout, "= PASS\n\n");
	else
	  gprintf(stdout, "=%d PASS\n\n", id);
	fflush(stdout);
      }
      else if (moves_considered == 1
	       || position_value[0] > position_value[1]) {
	gprintf(to_gnugo_stream, "%s %m\n", color == BLACK ? "black" : "white",
		move_i[0], move_j[0]);
	ask_gnugo(gnugo_line, 0, "6");
	if (id == -1)
	  gprintf(stdout, "= %m\n\n", move_i[0], move_j[0]);
	else
	  gprintf(stdout, "=%d %m\n\n", id, move_i[0], move_j[0]);
	fflush(stdout);
      }
      else {
	gprintf(to_gnugo_stream, "%s %m\n", color == BLACK ? "black" : "white",
		move_i[1], move_j[1]);
	ask_gnugo(gnugo_line, 0, "7");
	if (id == -1)
	  gprintf(stdout, "= %m\n\n", move_i[1], move_j[1]);
	else
	  gprintf(stdout, "=%d %m\n\n", id, move_i[1], move_j[1]);
	fflush(stdout);
      }
    }
    else {
      tell_gnugo(client_line, "g");
      ask_gnugo(gnugo_line, 1, "8");
    }
    /* loadsgf commands could change the boardsize, so we get
     * it from the engine, after the command is run. */
    if (!strncmp(command, "loadsgf", 7)) {
      tell_gnugo("query_boardsize\n", "i");
      ask_gnugo(gnugo_line, 0, "10");
      if (!sscanf(gnugo_line, "= %d", &boardsize))
	error("can't get boardsize");
      trace("setting boardsize %d\n", boardsize);
      fflush(stderr);
    }
  }
}

 
/* bark and barf */

void
error(const char *msg)      
{
  fprintf(stderr, "metamachine: %s\n", msg);
  tell_gnugo("quit\n", msg);
  abort();
}

/* Send a GTP command to the engine. */

void
tell_gnugo(char *gnugo_line, const char *msg)
{
  gprintf(to_gnugo_stream, gnugo_line);
  fflush(to_gnugo_stream);
  if (debug) {
    fprintf(stderr, "%s: %s", msg, gnugo_line);
    fflush(stderr);
  }
}

/* Obtains the engine's response to a GTP command. If verbose is true,
 * the reply is echoed to stdout.
 */

void
ask_gnugo(char *gnugo_line, int verbose, const char *msg)
{
  int line_length = 0;
  char line[GTP_BUFSIZE];

  while (line_length != 1) {
    if (!fgets(line, 128, from_gnugo_stream))
      error("can't get response");
    line_length = strlen(line);
    if (line_length > 1 
	&& (line[0] == '=' || line[0] == '?'))
      strncpy(gnugo_line, line, 128);
    if (verbose)
      printf(line);
    if (debug)
      fprintf(stderr, "%s: %s\n", msg, gnugo_line);
  }
  if (verbose)
    fflush(stdout);
  fflush(stderr);
}


/* Adapted from GNU Go. Formatted output with %m format. */

void 
gprintf(FILE *outputfile, const char *fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  vgprintf(outputfile, fmt, ap);
  fflush(outputfile);
  va_end(ap);
}

/* print diagnostic */

void
trace(const char *fmt, ...)
{
  va_list ap;
  if (debug) {
    va_start(ap, fmt);
    vgprintf(stderr, fmt, ap);
    fflush(stderr);
    va_end(ap);
  }
}

int
vgprintf(FILE *outputfile, const char *fmt, va_list ap)
{
  for ( ; *fmt; ++fmt) {
    if (*fmt == '%') {
      switch (*++fmt) {
      case 'c':
	{
	  /* rules of promotion => passed as int, not char */

	  int c = va_arg(ap, int);
	  putc(c, outputfile);
	  break;
	}
      case 'd':
	{
	  int d = va_arg(ap, int);
	  fprintf(outputfile, "%d", d);
	  break;
	}
      case 'f':
	{
	  double f = va_arg(ap, double); /* passed as double, not float */
	  fprintf(outputfile, "%.2f", f);
	  break;
	}
      case 's':
	{
	  char *s = va_arg(ap, char *);
	  fputs(s, outputfile);
	  break;
	}
      case 'm':
      case 'M':
	{
	  char movename[4];
	  int m = va_arg(ap, int);
	  int n = va_arg(ap, int);
	  if (m == -1 && n == -1)
	    fputs("PASS", outputfile);
	  else if (m < 0 || n < 0 || m >= 19 || n >= 19)
	    fprintf(outputfile, "[%d,%d]", m, n);
	  else {
	    /* Generate the move name. */
	    if (n < 8)
	      movename[0] = n + 65;
	    else
	      movename[0] = n + 66;
	    if (*fmt == 'm')
	      sprintf(movename+1, "%d", boardsize-m);
	    else
	      sprintf(movename+1, "%-2d", boardsize-m);
	    fputs(movename, outputfile);
	  }
	  break;
	}
      default:
	{
	  fprintf(outputfile, "\n\nUnknown format character: '%c'\n", *fmt);
	  abort();
	}
      }
    }
    else
      putc(*fmt, outputfile);
  }
  fflush(outputfile);
}

/* Extracts coordinates from a location in algebraic notation */

int
string_to_location(int boardsize, char *str, int *m, int *n)
{
  if (*str == '\0')
    return 0;

  if (!isalpha((int) *str))
    return 0;
  *n = tolower((int) *str) - 'a';
  if (tolower((int) *str) >= 'i')
    --*n;
  if (*n < 0 || *n > boardsize - 1)
    return 0;

  if (!isdigit((int) *(str+1)))
    return 0;
  *m = boardsize - atoi(str + 1);
  if (*m < 0 || *m > boardsize - 1)
    return 0;

  return 1;
}