/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\
 * 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.                                            *
\* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */


/* The functions in this file implement a mechanism whereby
 * GNU Go can fork a second gnugo process, called the oracle.
 * The two processes communicate by means of the GTP.
 * The functions oracle_trymove() and oracle_popgo() call
 * trymove and popgo in the primary gnugo processes but
 * actually play and undo the move in the oracle. This
 * the oracle can be queried for information which is
 * normally only available at the top level.
 */

#include "config.h"

#if ORACLE

#include "gnugo.h"
#include "liberty.h"
#include "patterns.h"

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

#define USE_POSIX 1

FILE *to_gnugo_stream, *from_gnugo_stream;
char gnugo_line[128];
int gnugo_line_length;

int pfd_a[2];
int pfd_b[2];

#define TELL_ORACLE(x, args...) do { \
              if (debug & DEBUG_ORACLE_STREAM) fprintf(stderr, x, ##args);  \
                if (fprintf(to_gnugo_stream, x, ##args) < 0) \
                   error("can't write command in to_gnugo_stream"); \
                fflush(to_gnugo_stream); \
              } while (0)

#define ASK_ORACLE    do { \
                        gnugo_line_length = 0;   \
                        while (gnugo_line_length != 1) { \
                          if (!fgets(gnugo_line, 128, from_gnugo_stream)) \
                             error("can't get response"); \
                          gnugo_line_length = strlen(gnugo_line); \
                          if (debug & DEBUG_ORACLE_STREAM) \
                            fprintf(stderr, gnugo_line); \
                          } \
                        } while (0)

#define MAX_ORACLE_MOVES 10

struct oracle_move_data {
  int pos;            /* move coordinate */
  int color;          /* color to play */
  int value;          /* value */
  int ab_value;       /* alpha-beta value */
  const char *reason; /* why this move */
};

static void oracle_callback(int anchor, int color, struct pattern *pattern,
			    int ll, void *data);
static void oracle_add_move(struct oracle_move_data *moves, 
			    int this_move, int this_value, 
			    const char *this_reason);
void do_consult_oracle(int color);
void error(const char *msg);
static int oracle_trymove(int pos, int color, const char *message, int str,
			  int komaster, int kom_pos);
static void oracle_popgo(void);
static void tell_oracle(const char *fmt, ...);
static void ask_oracle(void);
static int search_width(void);


/*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***\
 *                    Primary Oracle Functions                       *
\*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***/

/* Forks and attaches pipes to a new GNU Go process in gtp mode.
 * Loads the sgf file
 */

void
summon_oracle(void)
{
  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");
  }
  oracle_exists = 1;
  /* Attach pipe a to to_gnugo_stream  */
  to_gnugo_stream = (FILE *) fdopen(pfd_a[1], "w");
  /* Attach pipe b to from_gnugo_stream */
  from_gnugo_stream = (FILE *) fdopen(pfd_b[0], "r");
}

/* load an sgf file */

void
oracle_loadsgf(char *infilename, char *untilstring)
{
  if (untilstring)
    TELL_ORACLE("loadsgf %s %s\n", infilename, untilstring);
  else
    TELL_ORACLE("loadsgf %s\n", infilename);
  ASK_ORACLE;
  fflush(to_gnugo_stream);
  gnugo_line_length = 0;
}

/* Tell the oracle to go away. */

void
dismiss_oracle(void)
{
  if (oracle_exists)
    TELL_ORACLE("quit\n");
  oracle_exists = 0;
}

/* complain and die! */

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

/* Call trymove in the primary process, and have the oracle actually
 * play the move.
 */
static int 
oracle_trymove(int pos, int color, const char *message, int str,
	       int komaster, int kom_pos)
{
  if (!trymove(pos, color, message, str))
    return 0;
  if (debug & DEBUG_ORACLE_STREAM)
    gfprintf(stderr, "%o%s %1m\n", 
	     color == BLACK ? "black" : "white", pos);
  gfprintf(to_gnugo_stream, "%o%s %1m\n", 
	   color == BLACK ? "black" : "white", pos);
  fflush(to_gnugo_stream);
  ASK_ORACLE;
  return 1;
}

/* Undo the move. 
 */
static void
oracle_popgo(void)
{
  popgo();
  TELL_ORACLE("undo\n");
  ASK_ORACLE;
}

/* Play the move.
 */

int
oracle_play_move(int pos, int color)
{
  play_move(pos, color);

  if (debug & DEBUG_ORACLE_STREAM)
    gfprintf(stderr, "%o%s %1m\n", 
	     color == BLACK ? "black" : "white", pos);
  gfprintf(to_gnugo_stream, "%o%s %1m\n", 
	   color == BLACK ? "black" : "white", pos);
  fflush(to_gnugo_stream);
  ASK_ORACLE;
  return 1;
}

/* FIXME: Debugging needed. This variadic function doesn't work right if we
 * try to pass a const *char argument, like the infilename in
 * oracle_loadsgf. So for the time being we stick with the variadic macro
 * TELL_ORACLE.
 */
static void
tell_oracle(const char *fmt, ...)
{
  va_list ap;
  va_start(ap, fmt);
  if (debug & DEBUG_ORACLE_STREAM) fprintf(stderr, fmt, ap);
  if (fprintf(to_gnugo_stream, fmt, ap) < 0)
    error("can't write command in to_gnugo_stream");
  fflush(to_gnugo_stream);
  va_end(ap);
}

/* FIXME: Debugging needed. This variadic function seems a little more
 * reliable than the corresponding variadic macro ASK_ORACLE.  
 */

static void
ask_oracle(void)
{
  int line_length = 0;
  char line[128];

  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 (debug & DEBUG_ORACLE_STREAM) {
      fprintf(stderr, line);
      fflush(stderr);
    }
  }
}


/* clear the oracle's board and set the boardsize */

void 
oracle_clear_board(int boardsize)
{
  TELL_ORACLE("boardsize %d\n", boardsize);
  ASK_ORACLE;
}

/*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***\
 *              Demonstration: a pattern matcher                     *
\*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***/

/* Call the pattern matcher */

void
consult_oracle(int color)
{
  do_consult_oracle(color);
}

void
do_consult_oracle(int color)
{
  struct oracle_move_data oracle_moves[MAX_ORACLE_MOVES];
  int k;

  for (k = 0; k < MAX_ORACLE_MOVES; k++)
    oracle_moves[k].value = -1;
  
  matchpat(oracle_callback, color, &oracle_db, oracle_moves, NULL);
  for (k = 0; k < MAX_ORACLE_MOVES; k++)
    if (oracle_moves[k].value > -1) {
      oracle_trymove(oracle_moves[k].pos, color, oracle_moves[k].reason,
		     0, 0, NO_MOVE);
      do_consult_oracle(OTHER_COLOR(color));
      oracle_popgo();
    }
}

static void
oracle_callback(int anchor, int color, struct pattern *pattern,
		int ll, void *data)
{
  int this_move;
  struct oracle_move_data *moves = data;
  UNUSED(color);

  this_move = AFFINE_TRANSFORM(pattern->move_offset, ll, anchor);
  if (within_search_area(this_move))
    oracle_add_move(moves, this_move, pattern->value, pattern->name);
  else
    gprintf("outside the area\n");
}

/* Add a move to a list */

static void
oracle_add_move(struct oracle_move_data moves[MAX_ORACLE_MOVES],
		int this_move, int this_value, const char *this_reason)
{
  int k, l;
  
  for (k = 0; k < MAX_ORACLE_MOVES; k++)
    if (moves[k].value == -1
	|| this_value >= moves[k].value)
      break;
  for (l = MAX_ORACLE_MOVES-1; l > k; l--) {
    moves[l].pos = moves[l-1].pos;
    moves[l].value = moves[l-1].value;
    moves[l].reason = moves[l-1].reason;
  }
  moves[k].pos = this_move;
  moves[k].value = this_value;
  moves[k].reason = this_reason;
}

/*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***\
 *              Demonstration: metamachine                           *
\*** * *** * *** * *** * *** * *** * *** * *** * *** * *** * *** * ***/

#define FIRST_LEVEL_MOVES 3
#define SECOND_LEVEL_MOVES 2

static int
do_metamachine_genmove(int color, int width, float *value);

int
metamachine_genmove(int color, float *value, int limit_search)
{
  int move;
  int pos;

  if (limit_search) {
    TELL_ORACLE("limit_search 1\n");
    ASK_ORACLE;
    for (pos = BOARDMIN; pos < BOARDMAX; pos++)
      if (within_search_area(pos)) {
	if (debug & DEBUG_ORACLE_STREAM)
	  gfprintf(stderr, "%oset_search_limit %1m\n", pos);
	gfprintf(to_gnugo_stream, "%oset_search_limit %1m\n", pos);
	fflush(to_gnugo_stream);
	ASK_ORACLE;
      }	
  }
  count_variations = 1;
  move = do_metamachine_genmove(color, search_width(), value);
  sgffile_enddump(outfilename);
  count_variations = 0;
  return move;
}

static int
do_metamachine_genmove(int color, int width, float *value)
{
  int k, moves_considered;
  float move_value[10];
  float best_score = 0.;
  int best_move = -1;
  char *token;
  int moves[10];
  float score[10];
  char delimiters[] = " \t\r\n";
  char buf[100];
  int i, j;

  if (color == BLACK)
    TELL_ORACLE("top_moves_black\n");
  else
    TELL_ORACLE("top_moves_white\n");
  ask_oracle();
  token = strtok(gnugo_line, delimiters);
  for (k = 0; k < 10; k++) {
    moves[k] = PASS_MOVE;
    move_value[k] = 0.0;
  }
  moves_considered = width;
  if (verbose)
    dump_stack();
  for (k = 0; k < moves_considered; k++) {
    token = strtok(NULL, delimiters);
    if (!token)
      break;
    moves[k] = string_to_location(board_size, token);
    token = strtok(NULL, delimiters);
    if (!token)
      break;
    sscanf(token, "%f", move_value + k);
    TRACE("move %d: %1m valued %f\n", k, moves[k], move_value[k]);
  }
  /* if we left the loop early, k is the number of valid moves */
  moves_considered = k;
  if (moves_considered == 0) {
    *value = 0.0;
    return PASS_MOVE;
  }
  if (moves_considered == 1) {
    *value = 1.0;
    return moves[k];
  }
  for (k = 0; k < moves_considered; k++) {
    if (oracle_trymove(moves[k], color, "", 0, 0, NO_MOVE)) {
      int new_width = search_width();

      if (new_width == 0) {
	TELL_ORACLE("experimental_score %s\n", 
		    color == BLACK ? "black" : "white");
	ask_oracle();
	sscanf(gnugo_line, "= %f", score + k);
      }
      else {
	do_metamachine_genmove(OTHER_COLOR(color), new_width, &score[k]);
      }
      if (verbose)
	dump_stack();
      TRACE("score: %f\n", color == WHITE ? score[k] : -score[k]);
      sprintf(buf, "value %.2f", color == WHITE ? score[k] : -score[k]);
      if (sgf_dumptree)
	sgftreeAddComment(sgf_dumptree, buf);
      oracle_popgo();
    }
    if (best_move == -1
	|| (color == WHITE && score[k] > best_score) 
	|| (color == BLACK && score[k] < best_score)) {
      best_move = k;
      best_score = score[k];
    }
  }
  TRACE("best: %f at %1m\n", best_score, moves[best_move]);
  *value = score[best_move];
  return moves[best_move];
}

/* decide how wide to search */

static int
search_width(void)
{
  if (stackp == 0)
    return 3;
  else if (stackp == 1)
    return 2;
  else
    return 0;
}


#endif



/*
 * Local Variables:
 * tab-width: 8
 * c-basic-offset: 2
 * End:
 */
