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

/*  Parts of this code were given to us by Tommy Thorn */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

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


#if TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# if HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include "sgftree.h"
#include "gg_utils.h"

#define STRICT_SGF 's'
#define LAX_SGF    'l'

/* Set this to 1 if you want warnings for missing GM and FF properties. */
#define VERBOSE_WARNINGS 0

/* ================================================================ */
/*                     Some utility functions.                      */
/* ================================================================ */

/*
 * Utility: a checking, initializing malloc
 */

void *
xalloc(unsigned int size)
{
  void *pt = malloc(size);

  if (!pt) {
    fprintf(stderr, "xalloc: Out of memory!\n");
    exit(EXIT_FAILURE);
  }

  memset(pt, 0, (unsigned long) size);
  return pt;
}

void *
xrealloc(void *pt, unsigned int size)
{
  void *ptnew = realloc(pt, size);

  if (!ptnew) {
    fprintf(stderr, "xrealloc: Out of memory!\n");
    exit(EXIT_FAILURE);
  }
  return ptnew;
}


/* ================================================================ */
/*                           SGF Nodes                              */
/* ================================================================ */


/*
 * Allocate memory for a new SGF node.
 */

SGFNode *
sgfNewNode()
{
  SGFNode *newnode;
  newnode = xalloc(sizeof(SGFNode));
  newnode->next = NULL;
  newnode->props = NULL;
  newnode->parent = NULL;
  newnode->child = NULL;
  return newnode;
}

/*
 * Recursively free an sgf node
 */

void
sgfFreeNode(SGFNode *node)
{
  if (node == NULL)
    return;
  sgfFreeNode(node->next);
  sgfFreeNode(node->child);
  sgfFreeProperty(node->props);
  free(node);
}


/*
 * Add a generic text property to an SGF node.
 */

void
sgfAddProperty(SGFNode *node, const char *name, const char *value)
{
  SGFProperty *prop = node->props;

  if (prop)
    while (prop->next)
      prop = prop->next;

  sgfMkProperty(name, value, node, prop);
}


/*
 * Add an integer property to an SGF node.
 */

void
sgfAddPropertyInt(SGFNode *node, const char *name, long val)
{
  char buffer[10];

  gg_snprintf(buffer, 10, "%ld", val);
  sgfAddProperty(node, name, buffer);
}


/*
 * Add a float property to an SGF node.
 */

void
sgfAddPropertyFloat(SGFNode *node, const char *name, float val)
{
  char buffer[10];

  gg_snprintf(buffer, 10, "%3.1f", val);
  sgfAddProperty(node, name, buffer);
}


/*
 * Read a property as int from an SGF node.
 */

int
sgfGetIntProperty(SGFNode *node, const char *name, int *value)
{
  SGFProperty *prop;
  short nam = name[0] | name[1] << 8;

  for (prop = node->props; prop; prop = prop->next)
    if (prop->name == nam) {
      *value = atoi(prop->value);
      return 1;
    }

  return 0;
}


/*
 * Read a property as float from an SGF node.
 */

int
sgfGetFloatProperty(SGFNode *node, const char *name, float *value)
{
  SGFProperty *prop;
  short nam = name[0] | name[1] << 8;

  for (prop = node->props; prop; prop = prop->next)
    if (prop->name == nam) {
      *value = (float) atof(prop->value);
      /* MS-C warns of loss of data (double to float) */
      return 1;
    }

  return 0;
}


/*
 * Read a property as text from an SGF node.
 */

int
sgfGetCharProperty(SGFNode *node, const char *name, char **value)
{
  SGFProperty *prop;
  short nam = name[0] | name[1] << 8;

  for (prop = node->props; prop; prop = prop->next)
    if (prop->name == nam) {
      *value = prop->value;
      return 1;
    }

  return 0;
}


/*
 * Is there a property of this type in the node?
 */

static int
sgfHasProperty(SGFNode *node, const char *name)
{
  SGFProperty *prop;
  short nam = name[0] | name[1] << 8;

  for (prop = node->props; prop; prop = prop->next)
    if (prop->name == nam)
      return 1;

  return 0;
}


/*
 * Overwrite a property from an SGF node with text or create a new
 * one if it does not exist.
 */

void
sgfOverwriteProperty(SGFNode *node, const char *name, const char *text)
{
  SGFProperty *prop;
  short nam = name[0] | name[1] << 8;

  for (prop = node->props; prop; prop = prop->next)
    if (prop->name == nam) {
      prop->value = xrealloc(prop->value, strlen(text)+1);
      strcpy(prop->value, text);
      return;
    }

  sgfAddProperty(node, name, text);
}


/*
 * Overwrite an int property in an SGF node with val or create a new 
 * one if it does not exist.
 */

void
sgfOverwritePropertyInt(SGFNode *node, const char *name, int val)
{
  SGFProperty *prop;
  short nam = name[0] | name[1] << 8;

  for (prop = node->props; prop; prop = prop->next)
    if (prop->name == nam) {
      prop->value = xrealloc(prop->value, 12);
      gg_snprintf(prop->value, 12, "%d", val);
      return;
   }

  sgfAddPropertyInt(node, name, val);
}


/* 
 * Overwrite a float property in the gametree with val or create
 * a new one if it does not exist.
 */

void
sgfOverwritePropertyFloat(SGFNode *node, const char *name, float val)
{
  SGFProperty *prop;
  short nam = name[0] | name[1] << 8;

  for (prop = node->props; prop; prop = prop->next)
    if (prop->name == nam) {
      prop->value = xrealloc(prop->value, 15);
      gg_snprintf(prop->value, 15, "%3.1f", val);
      return;
    }

  sgfAddPropertyFloat(node, name, val);
}


/*
 * Goto previous node.
 */

SGFNode *
sgfPrev(SGFNode *node)
{
  SGFNode *q;
  SGFNode *prev;

  if (!node->parent)
    return NULL;

  q = node->parent->child;
  prev = NULL;
  while (q && q != node) {
    prev = q;
    q = q->next;
  }

  return prev;
}


/*
 * Goto root node.
 */

SGFNode *
sgfRoot(SGFNode *node)
{
  while (node->parent)
    node = node->parent;

  return node;
}


/* ================================================================ */
/*                         SGF Properties                           */
/* ================================================================ */


/*
 * Make an SGF property.
 */
static SGFProperty *
do_sgf_make_property(short sgf_name,  const char *value,
		     SGFNode *node, SGFProperty *last)
{
  SGFProperty *prop;

  prop = (SGFProperty *) xalloc(sizeof(SGFProperty));
  prop->name = sgf_name;
  prop->value = xalloc(strlen(value) + 1);
  strcpy(prop->value, value);
  prop->next = NULL;

  if (last == NULL)
    node->props = prop;
  else
    last->next = prop;

  return prop;
}


/* Make an SGF property.  In case of a property with a range it
 * expands it and makes several properties instead.
 */
SGFProperty *
sgfMkProperty(const char *name, const  char *value,
	      SGFNode *node, SGFProperty *last)
{
  static const short properties_allowing_ranges[12] = {
    /* Board setup properties. */
    SGFAB, SGFAW, SGFAE,

    /* Markup properties. */
    SGFCR, SGFMA, SGFSQ, SGFTR, SGFDD, SGFSL,

    /* Miscellaneous properties. */
    SGFVW,

    /* Go-specific properties. */
    SGFTB, SGFTW
  };

  int k;
  short sgf_name;

  if (strlen(name) == 1)
    sgf_name = name[0] | (short) (' ' << 8);
  else
    sgf_name = name[0] | name[1] << 8;

  for (k = 0; k < 12; k++) {
    if (properties_allowing_ranges[k] == sgf_name)
      break;
  }

  if (k < 12
      && strlen(value) == 5
      && value[2] == ':') {
    char x1 = value[0];
    char y1 = value[1];
    char x2 = value[3];
    char y2 = value[4];
    char new_value[] = "xy";

    if (x1 <= x2 && y1 <= y2) {
      for (new_value[0] = x1; new_value[0] <= x2; new_value[0]++) {
	for (new_value[1] = y1; new_value[1] <= y2; new_value[1]++)
	  last = do_sgf_make_property(sgf_name, new_value, node, last);
      }

      return last;
    }
  }

  /* Not a range property. */
  return do_sgf_make_property(sgf_name, value, node, last);
}


/*
 * Recursively free an SGF property.
 *
 */

void
sgfFreeProperty(SGFProperty *prop)
{
  if (prop == NULL)
    return;
  sgfFreeProperty(prop->next);
  free(prop->value);
  free(prop);
}


/* ================================================================ */
/*                        High level functions                      */
/* ================================================================ */


/*
 * Add a stone to the current or the given node.
 * Return the node where the stone was added.
 */

SGFNode *
sgfAddStone(SGFNode *node, int color, int movex, int movey)
{
  char move[3];

  sprintf(move, "%c%c", movey + 'a', movex + 'a');
  sgfAddProperty(node, (color == BLACK) ? "AB" : "AW", move);

  return node;
}


/*
 * Add a move to the gametree.
 */

SGFNode *
sgfAddPlay(SGFNode *node, int who, int movex, int movey)
{
  char move[3];
  SGFNode *new;
  
  /* a pass move? */
  if (movex == -1 && movey == -1)
    move[0] = 0;
  else
    sprintf(move, "%c%c", movey + 'a', movex + 'a');

  if (node->child)
    new = sgfStartVariantFirst(node->child);
  else {
    new = sgfNewNode();
    node->child = new;
    new->parent = node;
  }
  
  sgfAddProperty(new, (who == BLACK) ? "B" : "W", move);

  return new;
}


/*
 * Add a move to the gametree. New variations are added after the old
 * ones rather than before.
 */

SGFNode *
sgfAddPlayLast(SGFNode *node, int who, int movex, int movey)
{
  char move[3];
  SGFNode *new;

  /* a pass move? */
  if (movex == -1 && movey == -1)
    move[0] = 0;
  else
    sprintf(move, "%c%c", movey + 'a', movex + 'a');

  new = sgfAddChild(node);
  sgfAddProperty(new, (who == BLACK) ? "B" : "W", move);

  return new;
}


SGFNode *
sgfCreateHeaderNode(int boardsize, float komi, int handicap)
{
    SGFNode *root = sgfNewNode();

    sgfAddPropertyInt(root, "SZ", boardsize);
    sgfAddPropertyFloat(root, "KM", komi);
    sgfAddPropertyInt(root, "HA", handicap);

    return root;
}


/*
 * Add a comment to an SGF node.
 */

SGFNode *
sgfAddComment(SGFNode *node, const char *comment)
{
  sgfAddProperty(node, "C ", comment);

  return node;
}


/*
 * Place text on the board at position (i, j).
 */

SGFNode *
sgfBoardText(SGFNode *node, int i, int j, const char *text)
{
  void *str = xalloc(strlen(text) + 3);

  sprintf(str, "%c%c:%s", j+'a', i+'a', text);
  sgfAddProperty(node, "LB", str);
  free(str);

  return node;
}


/*
 * Place a character on the board at position (i, j).
 */

SGFNode *
sgfBoardChar(SGFNode *node, int i, int j, char c)
{
  char text[2] = "";

  text[0] = c;
  text[1] = 0;

  return sgfBoardText(node, i, j, text);
}


/*
 * Place a number on the board at position (i, j).
 */

SGFNode *
sgfBoardNumber(SGFNode *node, int i, int j, int number)
{
  char text[10];

  gg_snprintf(text, 10, "%c%c:%i", j+'a', i+'a', number);
  sgfAddProperty(node, "LB", text);

  return node;
}


/*
 * Place a triangle mark on the board at position (i, j).
 */

SGFNode *
sgfTriangle(SGFNode *node, int i, int j)
{
  char text[3];

  gg_snprintf(text, 3, "%c%c", j+'a', i+'a');
  sgfAddProperty(node, "TR", text);

  return node;
}


/*
 * Place a label on the board at position (i, j).
 */

SGFNode *
sgfLabel(SGFNode *node, const char *label, int i, int j)
{
  /* allows 12 chars labels - more than enough */
  char text[16];

  gg_snprintf(text, 16, "%c%c:%s", j+'a', i+'a', label);
  sgfAddProperty(node, "LB", text);

  return node;
}


/*
 * Place a numeric label on the board at position (i, j).
 */

SGFNode *
sgfLabelInt(SGFNode *node, int num, int i, int j)
{
  char text[16];

  gg_snprintf(text, 16, "%c%c:%d", j+'a', i+'a', num);
  sgfAddProperty(node, "LB", text);

  return node;
}


/*
 * Place a circle mark on the board at position (i, j).
 */

SGFNode *
sgfCircle(SGFNode *node, int i, int j)
{
  char text[3];

  gg_snprintf(text, 3, "%c%c", j+'a', i+'a');
  sgfAddProperty(node, "CR", text);

  return node;
}


/*
 * Place a square mark on the board at position (i, j).
 */

SGFNode *
sgfSquare(SGFNode *node, int i, int j)
{
  return sgfMark(node, i, j);   /* cgoban 1.9.5 does not understand SQ */
}


/*
 * Place a (square) mark on the board at position (i, j).
 */

SGFNode *
sgfMark(SGFNode *node, int i, int j)
{
  char text[3];

  gg_snprintf(text, 3, "%c%c", j+'a', i+'a');
  sgfAddProperty(node, "MA", text);

  return node;
}


/*
 * Start a new variant. Returns a pointer to the new node.
 */

SGFNode *
sgfStartVariant(SGFNode *node)
{
  assert(node);
  assert(node->parent);

  while (node->next)
    node = node->next;
  node->next = sgfNewNode();
  node->next->parent = node->parent;

  return node->next;
}


/*
 * Start a new variant as first child. Returns a pointer to the new node.
 */

SGFNode *
sgfStartVariantFirst(SGFNode *node)
{
  SGFNode *old_first_child = node;
  SGFNode *new_first_child = sgfNewNode();

  assert(node);
  assert(node->parent);

  new_first_child->next = old_first_child;
  new_first_child->parent = old_first_child->parent;

  new_first_child->parent->child = new_first_child;

  return new_first_child;
}


/*
 * If no child exists, add one. Otherwise add a sibling to the
 * existing children. Returns a pointer to the new node.
 */

SGFNode *
sgfAddChild(SGFNode *node)
{
  SGFNode *new_node = sgfNewNode();
  assert(node);

  new_node->parent = node;
  
  if (!node->child)
    node->child = new_node;
  else {
    node = node->child;
    while (node->next)
      node = node->next;
    node->next = new_node;
  }

  return new_node;
}


/*
 * Write result of the game to the game tree.
 */

void
sgfWriteResult(SGFNode *node, float score, int overwrite)
{
  char text[8];
  char winner;
  float s;
  int dummy;

  /* If not writing to the SGF file, skip everything and return now. */
  if (!node)
    return;

  /* If not overwriting and there already is a result property, return. */
  if (!overwrite)
    if (sgfGetIntProperty(node, "RE", &dummy))
      return;

  if (score > 0.0) {
    winner = 'W';
    s = score;
  }
  else if (score < 0.0) {
    winner = 'B';
    s = -score;
  }
  else {
    winner = '0';
    s = 0;
  }

  if (winner == '0')
    gg_snprintf(text, 8, "0");
  else if (score < 1000.0 && score > -1000.0)
    gg_snprintf(text, 8, "%c+%3.1f", winner, s);
  else
    gg_snprintf(text, 8, "%c+%c", winner, 'R');
  sgfOverwriteProperty(node, "RE", text);
}


static void
sgf_write_header_reduced(SGFNode *root, int overwrite)
{
  time_t curtime = time(NULL);
  struct tm *loctime = localtime(&curtime);
  char str[128];
  int dummy;

  gg_snprintf(str, 128, "%4.4i-%2.2i-%2.2i",
	      loctime->tm_year+1900, loctime->tm_mon+1, loctime->tm_mday);
  if (overwrite || !sgfGetIntProperty(root, "DT", &dummy))
    sgfOverwriteProperty(root, "DT", str);
  if (overwrite || !sgfGetIntProperty(root, "AP", &dummy))
    sgfOverwriteProperty(root, "AP", "GNU Go:"VERSION);
  sgfOverwriteProperty(root, "FF", "4");
}


void
sgf_write_header(SGFNode *root, int overwrite, int seed, float komi,
		 int handicap, int level, int rules)
{
  char str[128];
  int dummy;

  gg_snprintf(str, 128, "GNU Go %s Random Seed %d level %d", 
	      VERSION, seed, level);
  if (overwrite || !sgfGetIntProperty(root, "GN", &dummy))
    sgfOverwriteProperty(root, "GN", str);
  if (overwrite || !sgfGetIntProperty(root, "RU", &dummy))
    sgfOverwriteProperty(root, "RU", rules ? "Chinese" : "Japanese");
  sgfOverwritePropertyFloat(root, "KM", komi);
  sgfOverwritePropertyInt(root, "HA", handicap);

  sgf_write_header_reduced(root, overwrite);
}


/* ================================================================ */
/*                          Read SGF tree                           */
/* ================================================================ */


#define MAX_FILE_BUFFER 200000 /* buffer for reading SGF file. */

/*
 * SGF grammar:
 *
 * Collection = GameTree { GameTree }
 * GameTree   = "(" Sequence { GameTree } ")"
 * Sequence   = Node { Node }
 * Node       = ";" { Property }
 * Property   = PropIdent PropValue { PropValue }
 * PropIdent  = UcLetter { UcLetter }
 * PropValue  = "[" CValueType "]"
 * CValueType = (ValueType | Compose)
 * ValueType  = (None | Number | Real | Double | Color | SimpleText |
 *               Text | Point  | Move | Stone)
 *
 * The above grammar has a number of simple properties which enables us
 * to write a simpler parser:
 *   1) There is never a need for backtracking
 *   2) The only recursion is on gametree.
 *   3) Tokens are only one character
 * 
 * We will use a global state to keep track of the remaining input
 * and a global char variable, `lookahead' to hold the next token.  
 * The function `nexttoken' skips whitespace and fills lookahead with 
 * the new token.
 */


static void parse_error(const char *msg, int arg);
static void nexttoken(void);
static void match(int expected);


static FILE *sgffile;


#define sgf_getch() (getc(sgffile))


static char *sgferr;
#ifdef TEST_SGFPARSER
static int sgferrarg;
#endif
static int sgferrpos;

static int lookahead;


/* ---------------------------------------------------------------- */
/*                       Parsing primitives                         */
/* ---------------------------------------------------------------- */


static void
parse_error(const char *msg, int arg)
{
  fprintf(stderr, msg, arg);
  fprintf(stderr, "\n");
  exit(EXIT_FAILURE);
}


static void
nexttoken()
{
  do
    lookahead = sgf_getch();
  while (isspace(lookahead));
}


static void
match(int expected)
{
  if (lookahead != expected)
    parse_error("expected: %c", expected);
  else
    nexttoken();
}

/* ---------------------------------------------------------------- */
/*                        The parser proper                         */
/* ---------------------------------------------------------------- */


static void
propident(char *buffer, int size)
{
  if (lookahead == EOF || !isupper(lookahead)) 
    parse_error("Expected an upper case letter.", 0);
  
  while (lookahead != EOF && isalpha(lookahead)) {
    if (isupper(lookahead) && size > 1) {
      *buffer++ = lookahead;
      size--;
    }
    nexttoken();
  }
  *buffer = '\0';
}


static void
propvalue(char *buffer, int size)
{
  char *p = buffer;

  match('[');
  while (lookahead != ']' && lookahead != EOF) {
    if (lookahead == '\\') {
      lookahead = sgf_getch();
      /* Follow the FF4 definition of backslash */
      if (lookahead == '\r') {
	lookahead = sgf_getch();
	if (lookahead == '\n') 
	  lookahead = sgf_getch();
      }
      else if (lookahead == '\n') {
	lookahead = sgf_getch();
	if (lookahead == '\r') 
	  lookahead = sgf_getch();
      }
    }
    if (size > 1) {
      *p++ = lookahead;
      size--;
    }
    lookahead = sgf_getch();
  }
  match(']');
  
  /* Remove trailing whitespace. The double cast below is needed
   * because "char" may be represented as a signed char, in which case
   * characters between 128 and 255 would be negative and a direct
   * cast to int would cause a negative value to be passed to isspace,
   * possibly causing an assertion failure.
   */
  --p;
  while (p > buffer && isspace((int) (unsigned char) *p))
    --p;
  *++p = '\0';
}


static SGFProperty *
property(SGFNode *n, SGFProperty *last)
{
  char name[3];
  char buffer[4000];

  propident(name, sizeof(name));
  do {
    propvalue(buffer, sizeof(buffer));
    last = sgfMkProperty(name, buffer, n, last);
  } while (lookahead == '[');
  return last;
}


static void
node(SGFNode *n)
{
  SGFProperty *last = NULL;
  match(';');
  while (lookahead != EOF && isupper(lookahead))
    last = property(n, last);
}


static SGFNode *
sequence(SGFNode *n)
{
  node(n);
  while (lookahead == ';') {
    SGFNode *new = sgfNewNode();
    new->parent = n;
    n->child = new;
    n = new;
    node(n);
  }
  return n;
}


static void
gametree(SGFNode **p, SGFNode *parent, int mode) 
{
  if (mode == STRICT_SGF)
    match('(');
  else
    for (;;) {
      if (lookahead == EOF) {
	parse_error("Empty file?", 0);
	break;
      }
      if (lookahead == '(') {
	while (lookahead == '(')
	  nexttoken();
	if (lookahead == ';')
	  break;
      }
      nexttoken();
    }

  /* The head is parsed */
  {
    SGFNode *head = sgfNewNode();
    SGFNode *last;

    head->parent = parent;
    *p = head;

    last = sequence(head);
    p = &last->child;
    while (lookahead == '(') {
      gametree(p, last, STRICT_SGF);
      p = &((*p)->next);
    }
    if (mode == STRICT_SGF)
      match(')');
  }
}


/*
 * Fuseki readers
 * Reads an SGF file for extract_fuseki in a compact way
 */

static void
gametreefuseki(SGFNode **p, SGFNode *parent, int mode, 
	       int moves_per_game, int i)
{
  if (mode == STRICT_SGF)
    match('(');
  else
    for (;;) {
      if (lookahead == EOF) {
	parse_error("Empty file?", 0);
	break;
      }
      if (lookahead == '(') {
	while (lookahead == '(')
	  nexttoken();
	if (lookahead == ';')
	  break;
      }
      nexttoken();
    }
  
  /* The head is parsed */
  {

    SGFNode *head = sgfNewNode();
    SGFNode *last;
    head->parent = parent;
    *p = head;
    
    last = sequence(head);
    p = &last->child;
    while (lookahead == '(') {
      if (last->props 
	  && (last->props->name == SGFB || last->props->name == SGFW))
	i++;
      /* break after number_of_moves moves in SGF file */
      if (i >= moves_per_game) { 
	last->child = NULL;
	last->next = NULL;
	break;
      }
      else {
	gametreefuseki(p, last, mode, moves_per_game, i);
	p = &((*p)->next);
      }
    }
    if (mode == STRICT_SGF)
      match(')');
  }
}

SGFNode *
readsgffilefuseki(const char *filename, int moves_per_game)
{
  SGFNode *root;
  int tmpi = 0;

  if (strcmp(filename, "-") == 0)
    sgffile = stdin;
  else
    sgffile = fopen(filename, "r");

  if (!sgffile)
    return NULL;


  nexttoken();
  gametreefuseki(&root, NULL, LAX_SGF, moves_per_game, 0);

  fclose(sgffile);

  if (sgferr) {
    fprintf(stderr, "Parse error: %s at position %d\n", sgferr, sgferrpos);
    sgfFreeNode(root);
    return NULL;
  }

  /* perform some simple checks on the file */
  if (!sgfGetIntProperty(root, "GM", &tmpi)) {
    if (VERBOSE_WARNINGS)
      fprintf(stderr, "Couldn't find the game type (GM) attribute!\n");
  }
  else if (tmpi != 1) {
    fprintf(stderr, "SGF file might be for game other than go: %d\n", tmpi);
    fprintf(stderr, "Trying to load anyway.\n");
  }

  if (!sgfGetIntProperty(root, "FF", &tmpi)) {
    if (VERBOSE_WARNINGS)
      fprintf(stderr, "Can not determine SGF spec version (FF)!\n");
  }
  else if ((tmpi < 3 || tmpi > 4) && VERBOSE_WARNINGS)
    fprintf(stderr, "Unsupported SGF spec version: %d\n", tmpi);

  return root;
}





/*
 * Wrapper around readsgf which reads from a file rather than a string.
 * Returns NULL if file will not open, or some other parsing error.
 * Filename "-" means read from stdin, and leave it open when done.
 */

SGFNode *
readsgffile(const char *filename)
{
  SGFNode *root;
  int tmpi = 0;

  if (strcmp(filename, "-") == 0)
    sgffile = stdin;
  else
    sgffile = fopen(filename, "r");

  if (!sgffile)
    return NULL;


  nexttoken();
  gametree(&root, NULL, LAX_SGF);

  if (sgffile != stdin)
    fclose(sgffile);

  if (sgferr) {
    fprintf(stderr, "Parse error: %s at position %d\n", sgferr, sgferrpos);
    sgfFreeNode(root);
    return NULL;
  }

  /* perform some simple checks on the file */
  if (!sgfGetIntProperty(root, "GM", &tmpi)) {
    if (VERBOSE_WARNINGS)
      fprintf(stderr, "Couldn't find the game type (GM) attribute!\n");
  }
  else if (tmpi != 1) {
    fprintf(stderr, "SGF file might be for game other than go: %d\n", tmpi);
    fprintf(stderr, "Trying to load anyway.\n");
  }

  if (!sgfGetIntProperty(root, "FF", &tmpi)) {
    if (VERBOSE_WARNINGS)
      fprintf(stderr, "Can not determine SGF spec version (FF)!\n");
  }
  else if ((tmpi < 3 || tmpi > 4) && VERBOSE_WARNINGS)
    fprintf(stderr, "Unsupported SGF spec version: %d\n", tmpi);

  return root;
}



/* ================================================================ */
/*                          Write SGF tree                          */
/* ================================================================ */


#define OPTION_STRICT_FF4 0

static int sgf_column = 0;

static void
sgf_putc(int c, FILE *file)
{
  if (c == '\n' && sgf_column == 0)
    return;

  fputc(c, file);

  if (c == '\n')
    sgf_column = 0;
  else
    sgf_column++;

  if (c == ']' && sgf_column > 60) {
    fputc('\n', file);
    sgf_column = 0;
  }
}

static void
sgf_puts(const char *s, FILE *file)
{
  for (; *s; s++) {
    if (*s == '[' || *s == ']' || *s == '\\') {
      fputc('\\', file);
      sgf_column++;
    }
    fputc((int) *s, file);
    sgf_column++;
  }
}

/* Print all properties with the given name in a node to file and mark
 * them as printed.
 *
 * If is_comment is 1, multiple properties are concatenated with a
 * newline. I.e. we write
 *
 * C[comment1
 * comment2]
 *
 * instead of
 *
 * C[comment1][comment2]
 *
 * Most other property types should be written in the latter style.
 */

static void
sgf_print_name(FILE *file, short name)
{
  sgf_putc(name & 0xff, file);
  if (name >> 8 != ' ')
    sgf_putc(name >> 8, file);
}

static void
sgf_print_property(FILE *file, SGFNode *node, short name, int is_comment)
{
  int n = 0;
  SGFProperty *prop;

  for (prop = node->props; prop; prop = prop->next) {
    if (prop->name == name) {
      prop->name |= 0x20;  /* Indicate already printed. */
      if (n == 0) {
	sgf_print_name(file, name);
	sgf_putc('[', file);
      }
      else if (is_comment)
	sgf_putc('\n', file);
      else {
	sgf_putc(']', file);
	sgf_putc('[', file);
      }
      
      sgf_puts(prop->value, file);
      n++;
    }
  }

  if (n > 0)
    sgf_putc(']', file);

  /* Add a newline after certain properties. */
  if (name == SGFAB || name == SGFAW || name == SGFAE || (is_comment && n > 1))
    sgf_putc('\n', file);
}

/*
 * Print all remaining unprinted property values at node N to file.
 */

static void
sgfPrintRemainingProperties(FILE *file, SGFNode *node)
{
  SGFProperty *prop;

  for (prop = node->props; prop; prop = prop->next)
    if (!(prop->name & 0x20))
      sgf_print_property(file, node, prop->name, 0);
}


/*
 * Print the property values of NAME at node N and mark it as printed. 
 */

static void
sgfPrintCharProperty(FILE *file, SGFNode *node, const char *name)
{
  short nam = name[0] | name[1] << 8;
  
  sgf_print_property(file, node, nam, 0);
}


/*
 * Print comments from Node node.
 *
 * NOTE: cgoban does not print "C[comment1][comment2]" and I don't know
 *       what the sgfspec says.
 */

static void
sgfPrintCommentProperty(FILE *file, SGFNode *node, const char *name)
{
  short nam = name[0] | name[1] << 8;
  
  sgf_print_property(file, node, nam, 1);
}


static void
unparse_node(FILE *file, SGFNode *node)
{
  sgf_putc(';', file);
  sgfPrintCharProperty(file, node, "B ");
  sgfPrintCharProperty(file, node, "W ");
  sgfPrintCommentProperty(file, node, "N ");
  sgfPrintCommentProperty(file, node, "C ");
  sgfPrintRemainingProperties(file, node);
}


static void
unparse_root(FILE *file, SGFNode *node)
{
  sgf_putc(';', file);
  
  if (sgfHasProperty(node, "GM"))
    sgfPrintCharProperty(file, node, "GM");
  else {
    fputs("GM[1]", file);
    sgf_column += 5;
  }
  
  sgfPrintCharProperty(file, node, "FF");
  sgf_putc('\n', file);

  sgfPrintCharProperty(file, node, "SZ");
  sgf_putc('\n', file);
  
  sgfPrintCharProperty(file, node, "GN");
  sgf_putc('\n', file);
  
  sgfPrintCharProperty(file, node, "DT");
  sgf_putc('\n', file);
  
  sgfPrintCommentProperty(file, node, "PB");
  sgfPrintCommentProperty(file, node, "BR");
  sgf_putc('\n', file);
  
  sgfPrintCommentProperty(file, node, "PW");
  sgfPrintCommentProperty(file, node, "WR");
  sgf_putc('\n', file);
  
  sgfPrintCommentProperty(file, node, "N ");
  sgfPrintCommentProperty(file, node, "C ");
  sgfPrintRemainingProperties(file, node);

  sgf_putc('\n', file);
}


/*
 * p->child is the next move.
 * p->next  is the next variation
 */

static void
unparse_game(FILE *file, SGFNode *node, int root)
{
  if (!root)
    sgf_putc('\n', file);
  sgf_putc('(', file);
  if (root)
    unparse_root(file, node);
  else
    unparse_node(file, node);

  node = node->child;
  while (node != NULL && node->next == NULL) {
    unparse_node(file, node);
    node = node->child;
  } 

  while (node != NULL) {
    unparse_game(file, node, 0);
    node = node->next;
  }
  sgf_putc(')', file);
  if (root)
    sgf_putc('\n', file);
}

/* Printed properties are marked by adding the 0x20 bit to the
 * property name (changing an upper case letter to lower case). This
 * function removes this mark so that we can print the property next
 * time too. It recurses to all properties in the linked list.
 */
static void
restore_property(SGFProperty *prop)
{
  if (prop) {
    restore_property(prop->next);
    prop->name &= ~0x20;
  }
}

/* When called with the tree root, recurses to all properties in the
 * tree and removes all print marks.
 */
static void
restore_node(SGFNode *node)
{
  if (node) {
    restore_property(node->props);
    restore_node(node->child);
    restore_node(node->next);
  }
}


/*
 * Opens filename and writes the game stored in the sgf structure.
 */

int
writesgf(SGFNode *root, const char *filename)
{
  FILE *outfile;

  if (strcmp(filename, "-") == 0) 
    outfile = stdout;
  else
    outfile = fopen(filename, "w");

  if (!outfile) {
    fprintf(stderr, "Can not open %s\n", filename);
    return 0;
  }

  sgf_write_header_reduced(root, 0);

  sgf_column = 0;
  unparse_game(outfile, root, 1);
  if (outfile != stdout)
    fclose(outfile);
  
  /* Remove "printed" marks so that the tree can be written multiple
   * times.
   */
  restore_node(root);
  
  return 1;
}


#ifdef TEST_SGFPARSER
int
main()
{
  static char buffer[25000];
  static char output[25000];
  SGFNode *game;

  sgffile = stdin;

  nexttoken();
  gametree(&game, LAX_SGF);
  if (sgferr) {
    fprintf(stderr, "Parse error:");
    fprintf(stderr, sgferr, sgferrarg);
    fprintf(stderr, " at position %d\n", sgferrpos);
  }
  else {
    unparse_game(stdin, game, 1);
    write(1, output, outputp - output);
  }
}
#endif



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