/* GTS - Library for the manipulation of triangulated surfaces
 * Copyright (C) 1999 Stéphane Popinet
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include <stdlib.h>
#include <string.h>

#include "gts.h"
#include "gts-private.h"
#include "config.h"

const guint gts_major_version = GTS_MAJOR_VERSION;
const guint gts_minor_version = GTS_MINOR_VERSION;
const guint gts_micro_version = GTS_MICRO_VERSION;
const guint gts_interface_age = GTS_INTERFACE_AGE;
const guint gts_binary_age = GTS_BINARY_AGE;

static gboolean char_in_string (char c, const char * s)
{
  while (*s != '\0')
    if (*(s++) == c)
      return TRUE;
  return FALSE;
}

static GtsFile * file_new (void)
{
  GtsFile * f;

  f = g_malloc (sizeof (GtsFile));
  f->fp = NULL;
  f->buf = NULL;
  f->len = 0;
  f->curline = 1;
  f->curpos = 1;
  f->token = g_string_new ("");
  f->type = '\0';
  f->error = NULL;
  f->next_token = '\0';

  f->scope = f->scope_max = 0;
  f->delimiters = g_strdup (" \t");
  f->comments = g_strdup (GTS_COMMENTS);
  f->tokens = g_strdup ("\n{}()=");
  return f;
}

/**
 * gts_file_new:
 * @fp: a file pointer.
 *
 * Returns: a new #GtsFile.
 */
GtsFile * gts_file_new (FILE * fp)
{
  GtsFile * f;

  g_return_val_if_fail (fp != NULL, NULL);

  f = file_new ();
  f->fp = fp;
  gts_file_next_token (f);

  return f;
}

/**
 * gts_file_new_from_string:
 * @s: a string.
 *
 * Returns: a new #GtsFile.
 */
GtsFile * gts_file_new_from_string (gchar * s)
{
  g_return_val_if_fail (s != NULL, NULL);

  return gts_file_new_from_buffer (s, strlen (s));
}

/**
 * gts_file_new_from_buffer:
 * @buf: a buffer.
 * @len: the size of the buffer.
 *
 * Returns: a new #GtsFile.
 */
GtsFile * gts_file_new_from_buffer (gchar * buf, size_t len)
{
  GtsFile * f;

  g_return_val_if_fail (buf != NULL, NULL);

  f = file_new ();
  f->buf = buf;
  f->len = len;
  gts_file_next_token (f);

  return f;
}

/**
 * gts_file_destroy:
 * @f: a #GtsFile.
 *
 * Frees all the memory allocated for @f.
 */
void gts_file_destroy (GtsFile * f)
{
  g_return_if_fail (f != NULL);

  g_free (f->delimiters);
  g_free (f->comments);
  g_free (f->tokens);
  if (f->error)
    g_free (f->error);
  g_string_free (f->token, TRUE);
  g_free (f);
}

/**
 * gts_file_verror:
 * @f: a @GtsFile.
 * @format: the standard sprintf() format string.
 * @args: the list of parameters to insert into the format string.
 *
 * Sets the @error field of @f using g_strdup_vprintf().
 *
 * This function can be called only once and disables any other
 * operation on @f (gts_file_close() excepted).
 */
void gts_file_verror (GtsFile * f,
		      const gchar * format,
		      va_list args)
{
  g_return_if_fail (f != NULL);
  g_return_if_fail (format != NULL);

  g_assert (f->type != GTS_ERROR);
  f->error = g_strdup_vprintf (format, args);
  f->type = GTS_ERROR;
}

/**
 * gts_file_error:
 * @f: a @GtsFile.
 * @format: the standard sprintf() format string.
 * @...: the parameters to insert into the format string.
 *
 * Sets the @error field of @f using gts_file_verror().
 *
 * This function can be called only once and disables any other
 * operation on @f (gts_file_close() excepted).
 */
void gts_file_error (GtsFile * f,
		     const gchar * format,
		     ...)
{
  va_list args;

  g_return_if_fail (f != NULL);
  g_return_if_fail (format != NULL);

  va_start (args, format);  
  gts_file_verror (f, format, args);
  va_end (args);
}

static gint next_char (GtsFile * f)
{
  if (f->fp)
    return fgetc (f->fp);
  else {
    if (f->len == 0)
      return EOF;
    f->len--;
    return *(f->buf++);
  }
}

/**
 * gts_file_getc :
 * @f: a #GtsFile.
 *
 * Returns: the next character in @f or EOF if the end of the file is
 * reached or if an error occured.
 */
gint gts_file_getc (GtsFile * f)
{
  gint c;

  g_return_val_if_fail (f != NULL, EOF);

  if (f->type == GTS_ERROR)
    return EOF;

  c = next_char (f);
  f->curpos++;
  while (char_in_string (c, f->comments)) {
    while (c != EOF && c != '\n')
      c = next_char (f);
    if (c == '\n') {
      f->curline++;
      f->curpos = 1;
      c = next_char (f);
    }
  }
  switch (c) {
  case '\n': 
    f->curline++;
    f->curpos = 1; 
    break;
  case '{':
    f->scope++; 
    break;
  case '}':
    if (f->scope == 0) {
      f->line = f->curline;
      f->pos = f->curpos - 1;
      gts_file_error (f, "no matching opening brace");
      c = EOF;
    }
    else
      f->scope--;
  }
  return c;
}

/**
 * gts_file_read:
 * @f: a #GtsFile.
 * @ptr: a pointer.
 * @size: size of an element.
 * @nmemb: number of elements.
 *
 * Reads @nmemb elements of data, each @size bytes long, from @f,
 * storing them at the location given by @ptr.
 *
 * Returns: the number of elements read.
 */
guint gts_file_read (GtsFile * f, gpointer ptr, guint size, guint nmemb)
{
  guint i, n;
  gchar * p;

  g_return_val_if_fail (f != NULL, 0);
  g_return_val_if_fail (ptr != NULL, 0);
  g_return_val_if_fail (f->fp != NULL || f->buf != NULL, 0);

  if (f->type == GTS_ERROR)
    return 0;

  if (f->fp)
    n = fread (ptr, size, nmemb, f->fp);
  else {
    n = MIN (f->len/size, nmemb);
    if (n > 0) {
      memcpy (ptr, f->buf, n*size);
      f->buf += n*size;
      f->len -= n*size;
    }
  }

  for (i = 0, p = ptr; i < n*size; i++, p++) {
    f->curpos++;
    if (*p == '\n') {
      f->curline++;
      f->curpos = 1;
    }
  }
  return n;
}

/**
 * gts_file_getc_scope :
 * @f: a #GtsFile.
 *
 * Returns: the next character in @f in the scope defined by
 * @f->scope_max or EOF if the end of the file is reached or if an
 * error occured.
 */
gint gts_file_getc_scope (GtsFile * f)
{
  gint c;

  g_return_val_if_fail (f != NULL, EOF);

  if (f->type == GTS_ERROR)
    return EOF;
  
  if (f->scope <= f->scope_max)
    c = gts_file_getc (f);
  else {
    c = gts_file_getc (f);
    while (c != EOF && f->scope > f->scope_max)
      c = gts_file_getc (f);    
  }
  return c;
}

/**
 * gts_file_next_token:
 * @f: a #GtsFile.
 *
 * Reads next token from @f and updates its @token and @delim fields.
 */
void gts_file_next_token (GtsFile * f)
{
  gint c;
  gboolean in_string = FALSE;

  g_return_if_fail (f != NULL);

  if (f->type == GTS_ERROR)
    return;
  f->token->str[0] = '\0';
  f->token->len = 0;
  if (f->next_token != '\0') {
    if (char_in_string (f->next_token, f->tokens)) {
      f->line = f->curline;
      f->pos = f->curpos - 1;
      g_string_append_c (f->token, f->next_token);
      f->type = f->next_token;
      f->next_token = '\0';
      return;
    }
    else {
      c = f->next_token;
      f->next_token = '\0';
    }
  }
  else
    c = gts_file_getc_scope (f);
  f->type = GTS_NONE;
  while (c != EOF && (!in_string || !char_in_string (c, f->delimiters))) {
    if (in_string) {
      if (char_in_string (c, f->tokens)) {
	f->next_token = c;
	break;
      }
      g_string_append_c (f->token, c);
    }
    else if (!char_in_string (c, f->delimiters)) {
      in_string = TRUE;
      f->line = f->curline;
      f->pos = f->curpos - 1;
      g_string_append_c (f->token, c);
      if (char_in_string (c, f->tokens)) {
	f->type = c;
	break;
      }
    }
    c = gts_file_getc_scope (f);
  }
  if (f->type == GTS_NONE && f->token->len > 0) {
    gchar * a;

    a = f->token->str;
    while (*a != '\0' && char_in_string (*a, "+-")) a++;
    if (*a == '\0') {
      f->type = GTS_STRING;
      return;
    }
    a = f->token->str;
    while (*a != '\0' && char_in_string (*a, "+-0123456789")) a++;
    if (*a == '\0') {
      f->type = GTS_INT;
      return;
    }
    a = f->token->str;
    while (*a != '\0' && char_in_string (*a, "+-eE.")) a++;
    if (*a == '\0') {
      f->type = GTS_STRING;
      return;
    }
    a = f->token->str;
    while (*a != '\0' && char_in_string (*a, "+-0123456789eE.")) a++;
    if (*a == '\0') {
      f->type = GTS_FLOAT;
      return;
    }
    a = f->token->str;
    if (!strncmp (a, "0x", 2) || 
	!strncmp (a, "-0x", 3) || 
	!strncmp (a, "+0x", 3)) {
      while (*a != '\0' && char_in_string (*a, "+-0123456789abcdefx")) a++;
      if (*a == '\0') {
	f->type = GTS_INT;
	return;
      }
      a = f->token->str;
      while (*a != '\0' && char_in_string (*a, "+-0123456789abcdefx.p")) a++;
      if (*a == '\0') {
	f->type = GTS_FLOAT;
	return;
      }
    }
    f->type = GTS_STRING;
  }
}

/**
 * gts_file_first_token_after:
 * @f: a #GtsFile.
 * @type: a #GtsTokenType.
 *
 * Finds and sets the first token of a type different from @type 
 * occuring after a token of type @type.
 */
void gts_file_first_token_after (GtsFile * f, GtsTokenType type)
{
  g_return_if_fail (f != NULL);

  while (f->type != GTS_ERROR && 
	 f->type != GTS_NONE &&
	 f->type != type)
    gts_file_next_token (f);
  while (f->type == type)
    gts_file_next_token (f);
}

/**
 * gts_file_assign_start:
 * @f: a #GtsFile.
 * @vars: a %GTS_NONE terminated array of #GtsFileVariable.
 *
 * Opens a block delimited by braces to read a list of optional
 * arguments specified by @vars.  
 *
 * If an error is encountered the @error field of @f is set.
 */
void gts_file_assign_start (GtsFile * f, GtsFileVariable * vars)
{
  GtsFileVariable * var;

  g_return_if_fail (f != NULL);
  g_return_if_fail (vars != NULL);

  var = vars;
  while (var->type != GTS_NONE)
    (var++)->set = FALSE;

  if (f->type != '{') {
    gts_file_error (f, "expecting an opening brace");
    return;
  }

  f->scope_max++;
  gts_file_next_token (f);
}

/**
 * gts_file_assign_next:
 * @f: a #GtsFile.
 * @vars: a %GTS_NONE terminated array of #GtsFileVariable.
 *
 * Assigns the next optional argument of @vars read from @f.
 *
 * Returns: the variable of @vars which has been assigned or %NULL if
 * no variable has been assigned (if an error has been encountered the
 * @error field of @f is set).  
 */
GtsFileVariable * gts_file_assign_next (GtsFile * f, GtsFileVariable * vars)
{
  GtsFileVariable * var;
  gboolean found = FALSE;

  g_return_val_if_fail (f != NULL, NULL);
  g_return_val_if_fail (vars != NULL, NULL);

  while (f->type == '\n')
    gts_file_next_token (f);
  if (f->type == '}') {
    f->scope_max--;
    gts_file_next_token (f);
    return NULL;
  }
  if (f->type == GTS_ERROR)
    return NULL;

  var = vars;
  while (f->type != GTS_ERROR && var->type != GTS_NONE && !found) {
    if (!strcmp (var->name, f->token->str)) {
      found = TRUE;
      if (var->unique && var->set)
	gts_file_error (f, "variable `%s' was already set at line %d:%d", 
			var->name, var->line, var->pos);
      else {
	var->line = f->line;
	var->pos = f->pos;
	gts_file_next_token (f);
	if (f->type != '=')
	  gts_file_error (f, "expecting `='");
	else {
	  var->set = TRUE;
	  switch (var->type) {
	  case GTS_FILE:
	    break;
	  case GTS_INT:
	    gts_file_next_token (f);
	    if (f->type != GTS_INT) {
	      gts_file_error (f, "expecting an integer");
	      var->set = FALSE;
	    }
	    else if (var->data)
	      *((gint *) var->data) = atoi (f->token->str); 
	    break;
	  case GTS_UINT:
	    gts_file_next_token (f);
	    if (f->type != GTS_INT) {
	      gts_file_error (f, "expecting an integer");
	      var->set = FALSE;
	    }
	    else if (var->data)
	      *((guint *) var->data) = atoi (f->token->str); 
	    break;
	  case GTS_FLOAT:
	    gts_file_next_token (f);
	    if (f->type != GTS_INT && f->type != GTS_FLOAT) {
	      gts_file_error (f, "expecting a number");
	      var->set = FALSE;
	    }
	    else if (var->data)
	      *((gfloat *) var->data) = atof (f->token->str); 
	    break;
	  case GTS_DOUBLE:
	    gts_file_next_token (f);
	    if (f->type != GTS_INT && f->type != GTS_FLOAT) {
	      gts_file_error (f, "expecting a number");
	      var->set = FALSE;
	    }
	    else if (var->data)
	      *((gdouble *) var->data) = atof (f->token->str); 
	    break;
	  case GTS_OBJ:
	    gts_file_next_token (f);
	    if (var->data) {
	      GtsObject ** object = var->data;
	      g_return_val_if_fail (GTS_IS_OBJECT (*object), NULL);
	      (* (*object)->klass->read) (object, f);
	    }
	    break;
	  case GTS_STRING:
	    gts_file_next_token (f);
	    if (f->type != GTS_INT && 
		f->type != GTS_FLOAT && 
		f->type != GTS_STRING) {
	      gts_file_error (f, "expecting a string");
	      var->set = FALSE;
	    }
	    else {
	      if (f->token->str[0] != '"') { /* simple string */
		if (var->data)
		  *((gchar **) var->data) = g_strdup (f->token->str); 
	      }
	      else { 
		/* quoted string */
		gint len = strlen (f->token->str);
		if (f->token->str[len - 1] == '"') { 
		  /* simple quoted string */
		  f->token->str[len - 1] = '\0';
		  if (var->data)
		    *((gchar **) var->data) = g_strdup (&(f->token->str[1]));
		  f->token->str[len - 1] = '"';
		}
		else { 
		  /* multiple parts */
		  GString * s = g_string_new (&(f->token->str[1]));
		  g_string_append_c (s, ' ');
		  int c = gts_file_getc (f);
		  while (c != '"' && c != EOF) {
		    g_string_append_c (s, c);
		    c = gts_file_getc (f);
		  }
		  if (var->data)
		    *((gchar **) var->data) = g_strdup (s->str);
		  g_string_free (s, TRUE);
		}
	      }
	    }
	    break;
	  default:
	    g_assert_not_reached ();
	  }
	}
      }
    }
    else
      var++;
  }
  if (!found)
    gts_file_error (f, "unknown identifier `%s'", f->token->str);
  else if (f->type != GTS_ERROR) {
    g_assert (var->set);
    if (f->type != '}')
      gts_file_next_token (f);
    return var;
  }
  return NULL;
}

/**
 * gts_file_assign_variables:
 * @f: a #GtsFile.
 * @vars: an array of #GtsFileVariable.
 *
 * Assigns all the variables belonging to @vars found in @f.
 *
 * If an error is encountered the @error field of @f is set.
 */
void gts_file_assign_variables (GtsFile * f, GtsFileVariable * vars)
{
  g_return_if_fail (f != NULL);
  g_return_if_fail (vars != NULL);

  gts_file_assign_start (f, vars);
  while (gts_file_assign_next (f, vars))
    ;
}

/**
 * gts_file_variable_error:
 * @f: a #GtsFile.
 * @vars: an array of #GtsFileVariable.
 * @name: the name of a variable in @vars.
 * @format: the standard sprintf() format string.
 * @...: the parameters to insert into the format string.
 *
 * Sets the @error field of @f using gts_file_verror().
 *
 * String @name must match one of the variable names in @vars.
 *
 * If variable @name has been assigned (using gts_file_assign_variables())
 * sets the @line and @pos fields of @f to the line and position where
 * it has been assigned.
 */
void gts_file_variable_error (GtsFile * f, 
			      GtsFileVariable * vars,
			      const gchar * name,
			      const gchar * format,
			      ...)
{
  va_list args;
  GtsFileVariable * var;

  g_return_if_fail (f != NULL);
  g_return_if_fail (vars != NULL);
  g_return_if_fail (name != NULL);
  g_return_if_fail (format != NULL);

  var = vars;
  while (var->type != GTS_NONE && strcmp (var->name, name))
    var++;

  g_return_if_fail (var->type != GTS_NONE); /* @name not found in @vars */

  if (var->set) {
    f->line = var->line;
    f->pos = var->pos;
  }

  va_start (args, format);  
  gts_file_verror (f, format, args);
  va_end (args);
}

#ifdef DEBUG_FUNCTIONS
static GHashTable * ids = NULL;
static guint next_id = 1;

guint id (gpointer p)
{
  g_return_val_if_fail (p != NULL, 0);
  g_return_val_if_fail (ids != NULL, 0);
  g_assert (g_hash_table_lookup (ids, p));
  return GPOINTER_TO_UINT (g_hash_table_lookup (ids, p));
}

void id_insert (gpointer p)
{
  g_return_if_fail (p != NULL);
  if (ids == NULL) ids = g_hash_table_new (NULL, NULL);
  g_assert (g_hash_table_lookup (ids, p) == NULL);
  g_hash_table_insert (ids, p, GUINT_TO_POINTER (next_id++));
}

void id_remove (gpointer p)
{
  g_assert (g_hash_table_lookup (ids, p));  
  g_hash_table_remove (ids, p);
}

void gts_write_triangle (GtsTriangle * t, 
			 GtsPoint * o,
			 FILE * fptr)
{
  gdouble xo = o ? o->x : 0.0;
  gdouble yo = o ? o->y : 0.0;
  gdouble zo = o ? o->z : 0.0;

  g_return_if_fail (t != NULL && fptr != NULL);

  fprintf (fptr, "(hdefine geometry \"t%d\" { =\n", id (t));
  fprintf (fptr, "OFF 3 1 0\n"
	   "%g %g %g\n%g %g %g\n%g %g %g\n3 0 1 2\n})\n"
	   "(geometry \"t%d\" { : \"t%d\"})\n"
	   "(normalization \"t%d\" none)\n",
	   GTS_POINT (GTS_SEGMENT (t->e1)->v1)->x - xo, 
	   GTS_POINT (GTS_SEGMENT (t->e1)->v1)->y - yo,
	   GTS_POINT (GTS_SEGMENT (t->e1)->v1)->z - zo,
	   GTS_POINT (GTS_SEGMENT (t->e1)->v2)->x - xo, 
	   GTS_POINT (GTS_SEGMENT (t->e1)->v2)->y - yo, 
	   GTS_POINT (GTS_SEGMENT (t->e1)->v2)->z - zo,
	   GTS_POINT (gts_triangle_vertex (t))->x - xo,
	   GTS_POINT (gts_triangle_vertex (t))->y - yo,
	   GTS_POINT (gts_triangle_vertex (t))->z - zo,
	   id (t), id (t), id (t));
}

void gts_write_segment (GtsSegment * s, 
			GtsPoint * o,
			FILE * fptr)
{
  gdouble xo = o ? o->x : 0.0;
  gdouble yo = o ? o->y : 0.0;
  gdouble zo = o ? o->z : 0.0;

  g_return_if_fail (s != NULL && fptr != NULL);

  fprintf (fptr, "(geometry \"s%d\" { =\n", id (s));
  fprintf (fptr, "VECT 1 2 0 2 0 %g %g %g %g %g %g })\n"
	   "(normalization \"s%d\" none)\n",
	   GTS_POINT (s->v1)->x - xo, 
	   GTS_POINT (s->v1)->y - yo, 
	   GTS_POINT (s->v1)->z - zo,
	   GTS_POINT (s->v2)->x - xo, 
	   GTS_POINT (s->v2)->y - yo, 
	   GTS_POINT (s->v2)->z - zo,
	   id (s));
}
#endif /* DEBUG_FUNCTIONS */
