/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)
  
	Adresse mèl :
	BILLARD, non joignable par mèl ;
	CALISTE, damien P caliste AT cea P fr.

	Ce logiciel est un programme informatique servant à visualiser des
	structures atomiques dans un rendu pseudo-3D. 

	Ce logiciel est régi par la licence CeCILL soumise au droit français et
	respectant les principes de diffusion des logiciels libres. Vous pouvez
	utiliser, modifier et/ou redistribuer ce programme sous les conditions
	de la licence CeCILL telle que diffusée par le CEA, le CNRS et l'INRIA 
	sur le site "http://www.cecill.info".

	Le fait que vous puissiez accéder à cet en-tête signifie que vous avez 
	pris connaissance de la licence CeCILL, et que vous en avez accepté les
	termes (cf. le fichier Documentation/licence.fr.txt fourni avec ce logiciel).
*/

/*   LICENCE SUM UP
	Copyright CEA, contributors : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2005)

	E-mail address:
	BILLARD, not reachable any more ;
	CALISTE, damien P caliste AT cea P fr.

	This software is a computer program whose purpose is to visualize atomic
	configurations in 3D.

	This software is governed by the CeCILL  license under French law and
	abiding by the rules of distribution of free software.  You can  use, 
	modify and/ or redistribute the software under the terms of the CeCILL
	license as circulated by CEA, CNRS and INRIA at the following URL
	"http://www.cecill.info". 

	The fact that you are presently reading this means that you have had
	knowledge of the CeCILL license and that you accept its terms. You can
	find a copy of this licence shipped with this software at Documentation/licence.en.txt.
*/
#include "geometry.h"

#include <string.h>

#include <coreTools/toolMatrix.h>
#include <coreTools/toolShade.h>
#include <extensions/node_vectors.h>

/**
 * SECTION:geometry
 * @short_description: different routines to do high level geometry
 * studies on a box or a set of boxes.
 *
 * <para>The first possibility of the #geometry section is to make a
 * two by two difference node position difference between two
 * #VisuData objects. The #VisuNode positions are compared (number to number and
 * not closed equivalent to closed equivalent) and stored for
 * visualisation. The visualisation is done through small arrows
 * position on the currently visualised file.</para>
 *
 * Since: 3.5
 */

enum PathItem
  {
    PATH_ITEM_COORD,
    PATH_ITEM_DELTA
  };
struct Item_
{
  enum PathItem type;
  guint time;
  float dxyz[3];
  float energy;
};
typedef struct Path_
{
  guint nodeId;

  /* A per node translation. */
  float translation[3];

  guint size, nItems;
  struct Item_ *items;
} Path;
/**
 * VisuPaths:
 *
 * An opaque structure to save a set of paths.
 *
 * Since: 3.6
 */
struct _VisuPaths
{
  guint refCount;

  /* The current time stamp. */
  guint time;

  /* The global translation. */
  float translation[3];

  /* The min/max for the possible energies along the paths. */
  float minE, maxE;
  ToolShade *shade;

  /* A list of Path elements. */
  GList *lst;
};

#define GEOMETRY_PATH_ALLOC_SIZE 50

/**
 * visu_paths_get_type:
 * @void: nothing.
 *
 * Create and retrieve a #GType for a #VisuPaths object.
 *
 * Since: 3.7
 *
 * Returns: a new type for #VisuPaths structures.
 **/
GType visu_paths_get_type(void)
{
  static GType g_define_type_id = 0;

  if (g_define_type_id == 0)
    g_define_type_id =
      g_boxed_type_register_static("VisuPaths", 
                                   (GBoxedCopyFunc)visu_paths_ref,
                                   (GBoxedFreeFunc)visu_paths_unref);
  return g_define_type_id;
}
/**
 * visu_paths_new:
 * @translation: (in) (array fixed-size=3): the current box translation (cartesian).
 *
 * Create a new #VisuPaths object.
 *
 * Since: 3.6
 *
 * Returns: the newly create object #VisuPaths, to be freed with
 * visu_paths_free().
 */
VisuPaths* visu_paths_new(float translation[3])
{
  VisuPaths *paths;

  paths = g_malloc(sizeof(VisuPaths));
  paths->refCount = 1;
  paths->time = 0;
  paths->translation[0] = translation[0];
  paths->translation[1] = translation[1];
  paths->translation[2] = translation[2];
  paths->shade = (ToolShade*)0;
  paths->minE = G_MAXFLOAT;
  paths->maxE = -G_MAXFLOAT;
  paths->lst = (GList*)0;

  return paths;
}
/**
 * visu_paths_ref:
 * @paths: a #VisuPaths object.
 *
 * Increase the ref counter.
 *
 * Since: 3.7
 *
 * Returns: itself.
 **/
VisuPaths* visu_paths_ref(VisuPaths *paths)
{
  paths->refCount += 1;
  return paths;
}
/**
 * visu_paths_unref:
 * @paths: a #VisuPaths object.
 *
 * Decrease the ref counter, free all memory if counter reachs zero.
 *
 * Since: 3.7
 **/
void visu_paths_unref(VisuPaths *paths)
{
  paths->refCount -= 1;
  if (!paths->refCount)
    visu_paths_free(paths);
}
static Path* newPath()
{
  Path *path;

  path = g_malloc(sizeof(Path));
  path->size    = GEOMETRY_PATH_ALLOC_SIZE;
  path->items   = g_malloc(sizeof(struct Item_) * path->size);
  path->nItems  = 0;

  return path;
}
static Path* addPathItem(Path *path, guint time, float xyz[3],
			 enum PathItem type, float energy)
{
  DBG_fprintf(stderr, "Visu Geometry: add a new item for path %p.\n",
	      (gpointer)path);

  if (!path)
    /* We create a new path for the given node id. */
    path = newPath();
  
  /* We reallocate if necessary. */
  if (path->nItems == path->size)
    {
      path->size  += GEOMETRY_PATH_ALLOC_SIZE;
      path->items  = g_realloc(path->items, sizeof(struct Item_) * path->size);
    }

  /* If previous type was PATH_ITEM_COORD and new is also
     PATH_ITEM_COORD we remove the previous one. */
  if (path->nItems > 0 && path->items[path->nItems - 1].type == PATH_ITEM_COORD &&
      type == PATH_ITEM_COORD)
    path->nItems -= 1;

  /* We add the new delta to the path. */
  path->items[path->nItems].time = time;
  path->items[path->nItems].type = type;
  path->items[path->nItems].dxyz[0] = xyz[0];
  path->items[path->nItems].dxyz[1] = xyz[1];
  path->items[path->nItems].dxyz[2] = xyz[2];
  path->items[path->nItems].energy  = (energy == G_MAXFLOAT)?-G_MAXFLOAT:energy;
  path->nItems += 1;

  return path;
}
/**
 * visu_paths_addNodeStep:
 * @paths: a set of paths.
 * @time: the flag that give the number of expansion to update.
 * @nodeId: the node to expand the path of.
 * @xyz: the current position of the path.
 * @dxyz: the variation in the path.
 * @energy: the energy of the system.
 *
 * This routine expand the path for the given @nodeId at position @xyz
 * of @dxyz. The @energy value will be used only if
 * visu_paths_setToolShade() is used with a non NULL #ToolShade. In that
 * case the @energy value will be used to colourise the provided path.
 *
 * Since: 3.6
 *
 * Returns: TRUE if a new path is started.
 */
gboolean visu_paths_addNodeStep(VisuPaths *paths, guint time, guint nodeId,
			  float xyz[3], float dxyz[3], float energy)
{
  GList *tmpLst;
  Path *path;
  gboolean new;

  /* Look for a Path with the good node id. */
  new = FALSE;
  for (tmpLst = paths->lst; tmpLst && ((Path*)tmpLst->data)->nodeId != nodeId;
       tmpLst = g_list_next(tmpLst));
  if (!tmpLst)
    {
      /* We create a new path for the given node id. */
      path = addPathItem((Path*)0, time, xyz, PATH_ITEM_COORD, energy);
      path->nodeId         = nodeId;
      path->translation[0] = paths->translation[0];
      path->translation[1] = paths->translation[1];
      path->translation[2] = paths->translation[2];
      paths->lst   = g_list_prepend(paths->lst, (gpointer)path);
      new = TRUE;
    }
  else
    path = (Path*)tmpLst->data;

  addPathItem(path, time, dxyz, PATH_ITEM_DELTA, energy);

  if (energy != G_MAXFLOAT)
    {
      paths->minE = MIN(paths->minE, energy);
      paths->maxE = MAX(paths->maxE, energy);
    }

  return new;
}
/**
 * visu_paths_empty:
 * @paths: a #VisuPaths object.
 *
 * Reinitialise internal values of a given @paths.
 *
 * Since: 3.6
 */
void visu_paths_empty(VisuPaths *paths)
{
  GList *tmpLst;
  Path *path;

  g_return_if_fail(paths);

  for (tmpLst = paths->lst; tmpLst; tmpLst = g_list_next(tmpLst))
    {
      path = (Path*)tmpLst->data;
      g_free(path->items);
      g_free(path);
    }
  g_list_free(paths->lst);
  paths->lst = (GList*)0;
  paths->minE = G_MAXFLOAT;
  paths->maxE = -G_MAXFLOAT;
  paths->time = 0;
}
/**
 * visu_paths_free:
 * @paths: a #VisuPaths object.
 *
 * Free a set of paths.
 *
 * Since: 3.6
 */
void visu_paths_free(VisuPaths *paths)
{
  visu_paths_empty(paths);
  if (paths->shade)
    tool_shade_free(paths->shade);
  g_free(paths);
}
/**
 * visu_paths_addFromDiff:
 * @data: a #VisuData object with a geometry difference (see
 * visu_geodiff_new()).
 * @paths: the set of paths to extend.
 * @energy: the current total energy, if any, otherwise use %G_MAXFLOAT.
 *
 * This routine read the geometry difference hold in @data and add a
 * new step in the set of paths. If new paths are created, one
 * should call visu_paths_setTranslation() to be sure that all
 * paths are moved inside the box.
 *
 * Since: 3.6
 *
 * Returns: TRUE if new paths have been added.
 */
gboolean visu_paths_addFromDiff(VisuPaths *paths, VisuDataDiff *data, gdouble energy)
{
  VisuNodeValuesIter iter;
  float *diff, xyz[3];
  gboolean new;

  if (visu_data_diff_isEmpty(data))
    return FALSE;

  if (energy == G_MAXFLOAT)
    energy = paths->minE;

  new = FALSE;
  for (visu_node_values_iter_new(&iter, ITER_NODES_BY_TYPE,
                                 VISU_NODE_VALUES(data));
       iter.iter.node; visu_node_values_iter_next(&iter))
    {
      diff = (gfloat*)g_value_get_pointer(&iter.value);
      if (diff[3] > 0.01f)
	{
	  xyz[0] = iter.iter.node->xyz[0] - diff[0];
	  xyz[1] = iter.iter.node->xyz[1] - diff[1];
	  xyz[2] = iter.iter.node->xyz[2] - diff[2];
	  new = visu_paths_addNodeStep(paths, paths->time, iter.iter.node->number,
                                       xyz, diff, (float)energy) || new;
	}
    }
  paths->time += 1;

  return new;
}
/**
 * visu_paths_getLength:
 * @paths: a #VisuPaths object.
 *
 * Get the number of steps stored in a #VisuPaths.
 *
 * Since: 3.6
 *
 * Returns: the number of steps.
 */
guint visu_paths_getLength(VisuPaths *paths)
{
  g_return_val_if_fail(paths, 0);

  return paths->time;
}
/**
 * visu_paths_setTranslation:
 * @paths: a #VisuPaths object.
 * @cartCoord: three floats.
 *
 * Change the translation of the path, stored in cartesian
 * coordinates.
 *
 * Since: 3.6
 */
void visu_paths_setTranslation(VisuPaths *paths, float cartCoord[3])
{
  g_return_if_fail(paths);

  paths->translation[0] = cartCoord[0];
  paths->translation[1] = cartCoord[1];
  paths->translation[2] = cartCoord[2];
}
/**
 * visu_paths_constrainInBox:
 * @paths: a #VisuPaths object.
 * @data: a #VisuData object.
 *
 * Modify the corrdinates of the path nodes to contraint them in a box
 * (when applying translations for instance).
 *
 * Since: 3.6
 */
void visu_paths_constrainInBox(VisuPaths *paths, VisuData *data)
{
  VisuBox *box;
  float t[3], xyz[3];
  GList *tmpLst;
  Path *path;

  g_return_if_fail(paths && data);

  box = visu_boxed_getBox(VISU_BOXED(data));
  for(tmpLst = paths->lst; tmpLst; tmpLst = g_list_next(tmpLst))
    {
      path = (Path*)tmpLst->data;
      xyz[0] = path->items[0].dxyz[0] + paths->translation[0];
      xyz[1] = path->items[0].dxyz[1] + paths->translation[1];
      xyz[2] = path->items[0].dxyz[2] + paths->translation[2];
      visu_box_constrainInside(box, t, xyz, TRUE);
      path->translation[0] = t[0] + paths->translation[0];
      path->translation[1] = t[1] + paths->translation[1];
      path->translation[2] = t[2] + paths->translation[2];
    }
}
/**
 * visu_paths_pinPositions:
 * @paths: a #VisuPaths object.
 * @data: a #VisuData object.
 *
 * Use the current positions of @data to extend @paths.
 *
 * Since: 3.6
 */
void visu_paths_pinPositions(VisuPaths *paths, VisuData *data)
{
  VisuNodeArrayIter iter;
  GList *tmpLst;
  gdouble energy;

  g_return_if_fail(paths && data);
  
  g_object_get(G_OBJECT(data), "totalEnergy", &energy, NULL);
  if (energy == G_MAXFLOAT)
    energy = paths->minE;

  DBG_fprintf(stderr, "Visu Geometry: pin current positions.\n");
  visu_node_array_iter_new(VISU_NODE_ARRAY(data), &iter);
  for (visu_node_array_iterStart(VISU_NODE_ARRAY(data), &iter); iter.node;
       visu_node_array_iterNext(VISU_NODE_ARRAY(data), &iter))
    {
      for (tmpLst = paths->lst; tmpLst; tmpLst = g_list_next(tmpLst))
	if (((Path*)tmpLst->data)->nodeId == iter.node->number)
	  break;
      if (tmpLst)
	addPathItem((Path*)tmpLst->data, paths->time,
		    iter.node->xyz, PATH_ITEM_COORD, (float)energy);
    }

  if (energy != G_MAXFLOAT)
    {
      paths->minE = MIN(paths->minE, (float)energy);
      paths->maxE = MAX(paths->maxE, (float)energy);
    }
}
/**
 * visu_paths_setToolShade:
 * @paths: a #VisuPaths object.
 * @shade: a #ToolShade object.
 *
 * Set the colourisation scheme for the path.
 *
 * Since: 3.6
 *
 * Returns: TRUE is the scheme is changed.
 */
gboolean visu_paths_setToolShade(VisuPaths *paths, const ToolShade* shade)
{
  g_return_val_if_fail(paths, FALSE);

  if (tool_shade_compare(paths->shade, shade))
    return FALSE;
  
  if (paths->shade)
    tool_shade_free(paths->shade);
  paths->shade = tool_shade_copy(shade);
  return TRUE;
}
/**
 * visu_paths_getToolShade:
 * @paths: a #VisuPaths object.
 *
 * The paths are drawn with a colourisation scheme.
 *
 * Since: 3.6
 *
 * Returns: the #ToolShade used by the @paths.
 */
const ToolShade* visu_paths_getToolShade(const VisuPaths *paths)
{
  g_return_val_if_fail(paths, (const ToolShade*)0);

  return paths->shade;
}

/*********************/
/* Drawing routines. */
/*********************/
static void drawPath(Path *path, ToolShade *shade, float min, float max)
{
  guint i;
  float xyz[3], rgba[4];

  g_return_if_fail(path);

/*   DBG_fprintf(stderr, "Geometry: draw path for node %d.\n", path->nodeId); */
  if (!shade)
    glColor3f(0.f, 0.f, 0.f);

  for (i = 0; i < path->nItems; i++)
    {
      if (path->items[i].type == PATH_ITEM_COORD)
	{
	  if (i > 0)
	    glEnd();
	  glBegin(GL_LINE_STRIP);
	  xyz[0] = path->items[i].dxyz[0] + path->translation[0];
	  xyz[1] = path->items[i].dxyz[1] + path->translation[1];
	  xyz[2] = path->items[i].dxyz[2] + path->translation[2];
	}
      else
	{
	  xyz[0] += path->items[i].dxyz[0];
	  xyz[1] += path->items[i].dxyz[1];
	  xyz[2] += path->items[i].dxyz[2];
	}
      if (shade)
	{
	  tool_shade_valueToRGB
	    (shade, rgba, CLAMP((path->items[i].energy - min) / (max - min), 0., 1.));
	  glColor3fv(rgba);
	}
      glVertex3fv(xyz);
    }
  glEnd();

  glEnable(GL_POINT_SMOOTH);
  glBegin(GL_POINTS);
  for (i = 0; i < path->nItems; i++)
    {
      if (path->items[i].type == PATH_ITEM_COORD)
	{
	  xyz[0] = path->items[i].dxyz[0] + path->translation[0];
	  xyz[1] = path->items[i].dxyz[1] + path->translation[1];
	  xyz[2] = path->items[i].dxyz[2] + path->translation[2];
	}
      else
	{
	  xyz[0] += path->items[i].dxyz[0];
	  xyz[1] += path->items[i].dxyz[1];
	  xyz[2] += path->items[i].dxyz[2];
	}
      if (shade)
	{
	  tool_shade_valueToRGB
	    (shade, rgba, CLAMP((path->items[i].energy - min) / (max - min), 0., 1.));
	  glColor3fv(rgba);
	}
      glVertex3fv(xyz);
    }
  glEnd();
  glDisable(GL_POINT_SMOOTH);
}
/**
 * visu_paths_draw:
 * @paths: a set of paths.
 * @pathWidth: the width for the drawn path
 *
 * OpenGL calls to create the paths.
 *
 * Since: 3.6
 */
void visu_paths_draw(VisuPaths *paths, float pathWidth)
{
  GList *tmpLst;
  ToolShade *shade;

  if (ABS(paths->maxE - paths->minE) < 1e-6)
    shade = (ToolShade*)0;
  else
    shade = paths->shade;

  glDisable(GL_LIGHTING);
  glDepthMask(0);
  glColor3f(0.f, 0.f, 0.f);
  glLineWidth(pathWidth);
  glPointSize(pathWidth);
  for (tmpLst = paths->lst; tmpLst; tmpLst = g_list_next(tmpLst))
    drawPath((Path*)tmpLst->data, shade, paths->minE, paths->maxE);
  glDepthMask(1);
  glEnable(GL_LIGHTING);
}

/*****************************/
/* XML files for iso-values. */
/*****************************/

/*
<paths translat="0.000000;0.000000;0.000000">
  <path nodeId="214" translat="0.000000;0.000000;0.000000">
    <item time="0" type="dot" coordinates="24.46;29.39;29.65" totalEnergy="-23195.20" />
    <item time="0" type="delta" coordinates="0.23;0.05;0.07" totalEnergy="-23195.20" />
  </path>
</paths>
*/

/* Known elements. */
#define GEOMETRY_ELEMENT_PATHES  "paths"
#define GEOMETRY_ELEMENT_PATH    "path"
#define GEOMETRY_ELEMENT_ITEM    "item"
/* Known attributes. */
#define GEOMETRY_ATTRIBUTE_TIME  "time"
#define GEOMETRY_ATTRIBUTE_TRANS "translat"
#define GEOMETRY_ATTRIBUTE_NODE  "nodeId"
#define GEOMETRY_ATTRIBUTE_TYPE  "type"
#define GEOMETRY_ATTRIBUTE_COORD "coordinates"
#define GEOMETRY_ATTRIBUTE_TOT_E "totalEnergy"

static guint timeShift;
static gboolean startVisuPaths;
static Path *currentPath;

/* This method is called for every element that is parsed.
   The user_data must be a GList of _pick_xml. When a 'surface'
   element, a new struct instance is created and prepend in the list.
   When 'hidden-by-planes' or other qualificative elements are
   found, the first surface of the list is modified accordingly. */
static void geometryXML_element(GMarkupParseContext *context _U_,
				const gchar         *element_name,
				const gchar        **attribute_names,
				const gchar        **attribute_values,
				gpointer             user_data,
				GError             **error)
{
  VisuPaths *paths;
  int i, n;
  guint val, time, type;
  float t[3], en;
  gboolean ok;
  GList *tmpLst;

  g_return_if_fail(user_data);
  paths = (VisuPaths*)user_data;

  DBG_fprintf(stderr, "Geometry: found '%s' element.\n", element_name);
  if (!g_ascii_strcasecmp(element_name, GEOMETRY_ELEMENT_PATHES))
    {
      /* Initialise the pathList. */
      if (startVisuPaths)
	{
	  g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
		      _("DTD error: element '%s' should appear only once."),
		      GEOMETRY_ELEMENT_PATHES);
	  return;
	}
      startVisuPaths = TRUE;
      /* Parse the attributes. */
      for (i = 0; attribute_names[i]; i++)
	{
	  /* Possible translation. */
	  if (!g_ascii_strcasecmp(attribute_names[i], GEOMETRY_ATTRIBUTE_TRANS))
	    {
	      n = sscanf(attribute_values[i], "%f;%f;%f", t, t + 1, t + 2);
	      if (n != 3)
		{
		  g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
			      _("DTD error: attribute '%s' has an unknown value '%s'."),
			      GEOMETRY_ATTRIBUTE_TRANS, attribute_values[i]);
		  return;
		}
	      paths->translation[0] = t[0];
	      paths->translation[1] = t[1];
	      paths->translation[2] = t[2];
	    }
	}
    }
  else if (!g_ascii_strcasecmp(element_name, GEOMETRY_ELEMENT_PATH))
    {
      if (!startVisuPaths)
	{
	  g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
		      _("DTD error: parent element '%s' of element '%s' is missing."),
		      GEOMETRY_ELEMENT_PATHES, GEOMETRY_ELEMENT_PATH);
	  return;
	}
      /* We parse the attributes. */
      val = 123456789;
      t[0] = 0.f;
      t[1] = 0.f;
      t[2] = 0.f;
      for (i = 0; attribute_names[i]; i++)
	{
	  ok = TRUE;
	  if (!g_ascii_strcasecmp(attribute_names[i], GEOMETRY_ATTRIBUTE_NODE))
	    ok = (sscanf(attribute_values[i], "%u", &val) == 1);
	  else if (!g_ascii_strcasecmp(attribute_names[i], GEOMETRY_ATTRIBUTE_TRANS))
	    ok = (sscanf(attribute_values[i], "%f;%f;%f", t, t + 1, t + 2) == 3);
	  if (!ok)
	    {
	      g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
			  _("DTD error: attribute '%s' has an unknown value '%s'."),
			  GEOMETRY_ATTRIBUTE_NODE, attribute_values[i]);
	      return;
	    }
	}
      if (val == 123456789)
	{
	  g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
		      _("DTD error: element '%s' have missing mandatory attributes."),
		      GEOMETRY_ELEMENT_PATH);
	  return;
	}
      for (tmpLst = paths->lst; tmpLst && ((Path*)tmpLst->data)->nodeId != val;
	   tmpLst = g_list_next(tmpLst));
      if (!tmpLst)
	{
	  currentPath = newPath();
	  currentPath->nodeId = val;
	  currentPath->translation[0] = t[0];
	  currentPath->translation[1] = t[1];
	  currentPath->translation[2] = t[2];
	  paths->lst = g_list_prepend(paths->lst, (gpointer)currentPath);
	}
      else
	currentPath = (Path*)tmpLst->data;
    }
  else if (!g_ascii_strcasecmp(element_name, GEOMETRY_ELEMENT_ITEM))
    {
      if (!currentPath)
	{
	  g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
		      _("DTD error: parent element '%s' of element '%s' is missing."),
		      GEOMETRY_ELEMENT_PATH, GEOMETRY_ELEMENT_ITEM);
	  return;
	}

      /* We parse the attributes. */
      type = 999;
      time = 123456789;
      t[0] = G_MAXFLOAT;
      en = G_MAXFLOAT;
      for (i = 0; attribute_names[i]; i++)
	{
	  ok = TRUE;
	  if (!g_ascii_strcasecmp(attribute_names[i], GEOMETRY_ATTRIBUTE_TIME))
	    ok = (sscanf(attribute_values[i], "%u", &time) == 1);
	  else if (!g_ascii_strcasecmp(attribute_names[i], GEOMETRY_ATTRIBUTE_TYPE))
	    {
	      if (!g_ascii_strcasecmp(attribute_values[i], "dot"))
		type = PATH_ITEM_COORD;
	      else if (!g_ascii_strcasecmp(attribute_values[i], "delta"))
		type = PATH_ITEM_DELTA;
	      ok = (type != 999);
	    }
	  else if (!g_ascii_strcasecmp(attribute_names[i], GEOMETRY_ATTRIBUTE_COORD))
	    ok = (sscanf(attribute_values[i], "%f;%f;%f", t, t + 1, t + 2) == 3);
	  else if (!g_ascii_strcasecmp(attribute_names[i], GEOMETRY_ATTRIBUTE_TOT_E))
	    ok = (sscanf(attribute_values[i], "%f", &en) == 1);
	  if (!ok)
	    {
	      g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
			  _("DTD error: attribute '%s' has an unknown value '%s'."),
			  GEOMETRY_ATTRIBUTE_NODE, attribute_values[i]);
	      return;
	    }
	}
      if (time == 123456789 || type == 999 || t[0] == G_MAXFLOAT)
	{
	  g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_PARSE,
		      _("DTD error: element '%s' have missing mandatory attributes."),
		      GEOMETRY_ELEMENT_PATH);
	  return;
	}
      addPathItem(currentPath, time + timeShift, t, type, en);
      paths->time = MAX(time + timeShift + 1, paths->time);
      if (en != G_MAXFLOAT)
	{
	  paths->minE = MIN(en, paths->minE);
	  paths->maxE = MAX(en, paths->maxE);
	}
    }
  else if (startVisuPaths)
    {
      /* We silently ignore the element if pathList is unset, but
	 raise an error if pathList has been set. */
      g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
		  _("Unexpected element '%s'."), element_name);
    }
}

/**
 * visu_paths_parseFromXML:
 * @filename: a location on disk.
 * @paths: a #VisuPaths object.
 * @error: a pointer on an error.
 *
 * Read an XML containing a description of @paths. @paths is newly
 * created on success and should be freed with visu_paths_free().
 *
 * Since: 3.6
 * 
 * Returns: TRUE on success.
 */
gboolean visu_paths_parseFromXML(const gchar* filename, VisuPaths *paths, GError **error)
{
  GMarkupParseContext* xmlContext;
  GMarkupParser parser;
  gboolean status;
  gchar *buffer;
  gsize size;

  g_return_val_if_fail(filename, FALSE);
  g_return_val_if_fail(paths, FALSE);

  buffer = (gchar*)0;
  if (!g_file_get_contents(filename, &buffer, &size, error))
    return FALSE;

  /* Create context. */
  currentPath          = (Path*)0;
  timeShift            = paths->time;
  parser.start_element = geometryXML_element;
  parser.end_element   = NULL;
  parser.text          = NULL;
  parser.passthrough   = NULL;
  parser.error         = NULL;
  xmlContext = g_markup_parse_context_new(&parser, 0, paths, NULL);

  /* Parse data. */
  startVisuPaths = FALSE;
  status = g_markup_parse_context_parse(xmlContext, buffer, size, error);

  /* Free buffers. */
  g_markup_parse_context_free(xmlContext);
  g_free(buffer);

  if (!startVisuPaths)
    {
      *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY,
			  _("No paths found."));
      status = FALSE;
    }

  if (!status)
    return FALSE;

  return TRUE;
}
/**
 * visu_paths_exportXMLFile:
 * @paths: a #VisuPaths object.
 * @filename: a location on disk.
 * @error: a pointer on an error.
 *
 * Write an XML file with the description of the given @paths.
 *
 * Since: 3.6
 *
 * Returns: TRUE if no error.
 */
gboolean visu_paths_exportXMLFile(const VisuPaths *paths,
                                  const gchar *filename, GError **error)
{
  GString *output;
  GList *tmpLst;
  Path *path;
  guint i;
  gboolean valid;

  if (!paths)
    return TRUE;

  output = g_string_new("<paths");
  g_string_append_printf(output, " translat=\"%f;%f;%f\">\n", paths->translation[0],
			 paths->translation[1], paths->translation[2]);

  for (tmpLst = paths->lst; tmpLst; tmpLst = g_list_next(tmpLst))
    {
      path = (Path*)tmpLst->data;
      g_string_append_printf(output, "  <path nodeId=\"%d\" translat=\"%f;%f;%f\">\n",
			     path->nodeId, path->translation[0],
			     path->translation[1], path->translation[2]);
      for (i = 0; i < path->nItems; i++)
	if (ABS(path->items[i].energy) != G_MAXFLOAT)
	  g_string_append_printf
	    (output, "    <item time=\"%d\" type=\"%s\" coordinates=\"%f;%f;%f\""
	     " totalEnergy=\"%f\" />\n", path->items[i].time,
	     (path->items[i].type == PATH_ITEM_COORD)?"dot":"delta",
	     path->items[i].dxyz[0], path->items[i].dxyz[1],
	     path->items[i].dxyz[2], path->items[i].energy);
	else
	  g_string_append_printf
	    (output, "    <item time=\"%d\" type=\"%s\""
	     " coordinates=\"%f;%f;%f\" />\n", path->items[i].time,
	     (path->items[i].type == PATH_ITEM_COORD)?"dot":"delta",
	     path->items[i].dxyz[0], path->items[i].dxyz[1],
	     path->items[i].dxyz[2]);
      g_string_append(output, "  </path>\n");
    }

  g_string_append(output, "</paths>");

  valid = tool_XML_substitute(output, filename, "paths", error);
  if (!valid)
    {
      g_string_free(output, TRUE);
      return FALSE;
    }
  
  valid = g_file_set_contents(filename, output->str, -1, error);
  g_string_free(output, TRUE);
  return valid;
}
