/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Luc BILLARD et Damien
	CALISTE, laboratoire L_Sim, (2001-2006)
  
	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-2006)

	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 "plane.h"

#include <stdlib.h>
#include <math.h>

#include <opengl.h>
#include <visu_object.h>

int comparePolygonPoint(gconstpointer pointA, gconstpointer pointB, gpointer data);

int plane_hidingMode = plane_hideUnion;

struct Plane_struct
{
  /* Normal vector, unitary. */
  float nVect[3];
  /* Normal vector, user given. */
  float nVectUser[3];

  /* Distance between origin and intersection of the plane
     and the line made by origin and normal vector. */
  float dist;

  /* Color of that plane. */
  Color *color;

  /* Internal variables */
  /* Intersections with the bounding box.
     Consist of a GList of float[3], can be NULL if
     there is no interstection. */
  GList *inter;
  /* Isobarycenter G of all intersection points, required to order
     these points to form a convex polygon. */
  float pointG[3];
  /* The plane can hide the nodes on one of its side.
     This variable can be PLANE_SIDE_PLUS or PLANE_SIDE_MINUS or
     PLANE_SIDE_NONE. It codes the side of the plane which hides the nodes.
     If PLANE_SIDE_NONE is selected all nodes are rendered. */
  int hiddenSide;
};

Plane* planeNew_empty()
{
  Plane *plane;

  plane = g_malloc(sizeof(Plane));
  DBG_fprintf(stderr, "Visu Planes : creating a new plane (%p).\n", (gpointer)plane);
  plane->inter = (GList*)0;
  plane->hiddenSide = PLANE_SIDE_NONE;
  plane->nVectUser[0] = 0.;
  plane->nVectUser[1] = 0.;
  plane->nVectUser[2] = 0.;
  plane->dist = 0.;
  plane->color = (Color*)0;
  return plane;
}

Plane* planeNew(OpenGLView *view, float vect[3], float dist, Color *color)
{
  Plane *plane;
  int res;

  g_return_val_if_fail(view && color, (Plane*)0);

  plane = planeNew_empty();
  res = (planeSet_normalVector(plane, vect) < 0);
  res = (planeSet_distanceFromOrigin(plane, dist) < 0) || res;
  res = (planeSet_color(plane, color) < 0) || res;
  res = planeComputeInter(view, plane) || res;
  
  if (res)
    {
      planeFree(plane);
      return (Plane*)0;
    }

  return plane;
}

void planeFree(Plane* plane)
{
  GList *tmpLst;

  if (!plane)
    {
      fprintf(stderr, "WARNING! '%s' has been called with a null value for parameter '%s'.\n",
	      "planeFree", "plane");
      return;
    }
  tmpLst = plane->inter;
  while (tmpLst)
    {
      free(tmpLst->data);
      tmpLst = g_list_next(tmpLst);
    }
  g_free(plane);
}

int planeSet_normalVector(Plane *plane, float vect[3])
{
  int i;
  float norm;

  if (!plane)
    {
      fprintf(stderr, "WARNING! '%s' has been called with a null value for parameter '%s'.\n",
	      "planeSet_normalVector", "plane");
      return -1;
    }
  if (vect[0] == plane->nVectUser[0] &&
      vect[1] == plane->nVectUser[1] &&
      vect[2] == plane->nVectUser[2])
    return 0;
  if (vect[0] * vect[0] + vect[1] * vect[1] + vect[2] * vect[2] == 0.)
    {
      fprintf(stderr, "WARNING! '%s' has been called with a null value for parameter '%s'.\n",
	      "planeSet_normalVector", "vect");
      return -1;
    }

  norm = 0.;
  for (i = 0; i < 3; i++)
    {
      norm += vect[i] * vect[i];
      plane->nVect[i] = vect[i];
      plane->nVectUser[i] = vect[i];
    }
  norm = sqrt(norm);
  for (i = 0; i < 3; i++)
    plane->nVect[i] /= norm;

  DBG_fprintf(stderr, "Visu Plane : set normal vector (%f,%f,%f) for plane %p.\n",
	      plane->nVect[0], plane->nVect[1], plane->nVect[2], (gpointer)plane);
  return 1;
}

int planeSet_distanceFromOrigin(Plane *plane, float dist)
{
  if (!plane)
    {
      fprintf(stderr, "WARNING! '%s' has been called with a null value for parameter '%s'.\n",
	      "planeSet_distanceFromOrigin", "plane");
      return -1;
    }
  if (plane->dist == dist)
    return 0;

  plane->dist = dist;
  DBG_fprintf(stderr, "Visu Plane : set distance from origin %f for plane %p.\n",
	      dist, (gpointer)plane);
  return 1;
}

int planeSet_color(Plane *plane, Color *color)
{
  if (!plane)
    {
      fprintf(stderr, "WARNING! '%s' has been called with a null value for parameter '%s'.\n",
	      "planeSet_color", "plane");
      return -1;
    }
  if (color == plane->color)
    return 0;

  plane->color = color;
  return 1;
}

int planeComputeInter(OpenGLView *view, Plane* plane)
{
  float lambda, denom;
  float l[12][3], a[12][3];
  int i, j, n;
  float *inter;
  GList *tmpLst;

  g_return_val_if_fail(plane && view, 1);

  /* RAZ old intersection list. */
  if (plane->inter)
    {
      tmpLst = plane->inter;
      while(tmpLst)
	{
	  free((float*)tmpLst->data);
	  tmpLst = g_list_next(tmpLst);
	}
      g_list_free(plane->inter);
      plane->inter = (GList*)0;
    }
  /* Compute, vector and position for the box. */
  for (i = 0; i < 3; i++)
    {
      l[0][i] = view->box->p2[i] - view->box->p1[i];
      l[1][i] = view->box->p3[i] - view->box->p2[i];
      l[2][i] = view->box->p4[i] - view->box->p3[i];
      l[3][i] = view->box->p1[i] - view->box->p4[i];
      l[4][i] = view->box->p5[i] - view->box->p1[i];
      l[5][i] = view->box->p6[i] - view->box->p2[i];
      l[6][i] = view->box->p7[i] - view->box->p3[i];
      l[7][i] = view->box->p8[i] - view->box->p4[i];
      l[8][i] = view->box->p6[i] - view->box->p5[i];
      l[9][i] = view->box->p7[i] - view->box->p6[i];
      l[10][i] = view->box->p8[i] - view->box->p7[i];
      l[11][i] = view->box->p5[i] - view->box->p8[i];
      a[0][i] = view->box->p1[i];
      a[1][i] = view->box->p2[i];
      a[2][i] = view->box->p3[i];
      a[3][i] = view->box->p4[i];
      a[4][i] = view->box->p1[i];
      a[5][i] = view->box->p2[i];
      a[6][i] = view->box->p3[i];
      a[7][i] = view->box->p4[i];
      a[8][i] = view->box->p5[i];
      a[9][i] = view->box->p6[i];
      a[10][i] = view->box->p7[i];
      a[11][i] = view->box->p8[i];
    }
  n = 0;
  for (i = 0; i < 3; i++)
    plane->pointG[i] = 0.;
  /*
    The plane is defined by n1x+n2y+n3z-d=0 with (n1,n2,n3) the normal vector (unitary)
    and d the algebric distance from the origin.
    A segment {P} of the box is defined by P=A+lambda.l with A a vertex, l a vector
    in the direction of the segment and lambda a real.
    If it exists a lambda that can solve P(lambda) in the plane equation, and this lambda
    is in [0;1] then this is the intersection.
   */
  for (i = 0; i < 12; i++)
    {
      denom = 0.;
      for (j = 0; j < 3; j++)
	denom += plane->nVect[j] * l[i][j];
      if (denom != 0.)
	{
	  lambda = plane->dist;
	  for (j = 0; j < 3; j++)
	    lambda -= plane->nVect[j] * a[i][j];
	  lambda /= denom;
	  if (lambda >= 0. && lambda <= 1.)
	    {
	      inter = malloc(sizeof(float) * 3);
	      if (!inter)
		{
		  allocationProblems();
		  exit(1);
		}
	      for (j = 0; j < 3; j++)
		{
		  inter[j] = a[i][j] + lambda * l[i][j];
		  plane->pointG[j] += inter[j];
		}
	      n += 1;
	      plane->inter = g_list_append(plane->inter, (gpointer)inter);
	      DBG_fprintf(stderr, "Visu Plane : a new intersection (%f,%f,%f) for plane %p.\n",
			  inter[0], inter[1], inter[2], (gpointer)plane);
	    }
	}
    }
  if (n > 0)
    {
      for (i = 0; i < 3; i++)
	plane->pointG[i] /= (float)n;
      plane->inter = g_list_sort_with_data(plane->inter, comparePolygonPoint, (gpointer)plane);
    }

  return 0;
}
int planesComputeInter_list(OpenGLView *view, GList *list)
{
  int res;
  GList *tmpLst;

  g_return_val_if_fail(view, 1);
  
  res = 0;
  tmpLst = list;
  while(tmpLst)
    {
      res = planeComputeInter(view, (Plane*)tmpLst->data) || res;
      tmpLst = g_list_next(tmpLst);
    }
  return res;
}

void planeDraw(OpenGLView *view, Plane* plane)
{
  GList *tmpLst;
  float mat[5] = {1., 0.0, 1.0, 0.0, 0.0};

  g_return_if_fail(view && plane);

  tmpLst = plane->inter;
  if (tmpLst)
    {
      glDisable(GL_CULL_FACE);
      setOpenGlMaterial(mat, plane->color->rgba);
      glPushMatrix();
      glTranslated(-view->box->dxxs2, -view->box->dyys2, -view->box->dzzs2);
      glBegin(GL_POLYGON);
      while (tmpLst)
	{
	  glVertex3f(((float*)tmpLst->data)[0],
		     ((float*)tmpLst->data)[1],
		     ((float*)tmpLst->data)[2]);
	  tmpLst = g_list_next(tmpLst);
	}
      glEnd();
      glPopMatrix();  
      glEnable(GL_CULL_FACE);
      glCullFace(GL_BACK);
    }
}
void planesDraw_list(OpenGLView *view, GList *list)
{
  GList *tmpLst;

  g_return_if_fail(view);

  DBG_fprintf(stderr, "Plane : drawing list of planes.\n");
  tmpLst = list;
  while(tmpLst)
    {
      DBG_fprintf(stderr, " | plane %p\n", tmpLst->data);
      planeDraw(view, (Plane*)tmpLst->data);
      tmpLst = g_list_next(tmpLst);
    }
}

int comparePolygonPoint(gconstpointer pointA, gconstpointer pointB, gpointer data)
{
  Plane *plane;
  float vectGA[3], vectGB[3];
  int i;
  float det;

  plane = (Plane*)data;
  for (i = 0; i < 3; i++)
    {
      vectGA[i] = ((float*)pointA)[i] - plane->pointG[i];
      vectGB[i] = ((float*)pointB)[i] - plane->pointG[i];
    }
  det = vectGA[0] * vectGB[1] * plane->nVect[2] + vectGB[0] * plane->nVect[1] * vectGA[2] +
    plane->nVect[0] * vectGA[1] * vectGB[2] - vectGA[2] * vectGB[1] * plane->nVect[0] -
    vectGA[1] * vectGB[0] * plane->nVect[2] - vectGA[0] * vectGB[2] * plane->nVect[1];
  if (det < 0.)
    return -1;
  else if (det > 0.)
    return 1;
  else
    return 0;
}

void planeGet_nVectUser(Plane *plane, float *vect)
{
  int i;

  if (!plane)
    {
      fprintf(stderr, "WARNING! '%s' has been called with a null value for parameter '%s'.\n",
	      "planeComputeInter", "plane");
      return;
    }
  for (i = 0; i < 3; i++)
    vect[i] = plane->nVectUser[i];
}

void planeGet_color(Plane *plane, Color **color)
{
  if (!plane)
    {
      fprintf(stderr, "WARNING! '%s' has been called with a null value for parameter '%s'.\n",
	      "planeGet_color", "plane");
      return;
    }
  *color = plane->color;
}

void planeGet_distanceFromOrigin(Plane *plane, float *dist)
{
  if (!plane)
    {
      fprintf(stderr, "WARNING! '%s' has been called with a null value for parameter '%s'.\n",
	      "planeComputeInter", "plane");
      return;
    }
  *dist = plane->dist;
}

int planeSet_hiddenState(Plane *plane, int side)
{
  if (!plane ||
      (side != PLANE_SIDE_NONE &&
       side != PLANE_SIDE_PLUS &&
       side != PLANE_SIDE_MINUS))
    {
      fprintf(stderr, "WARNING! 'planeSet_hiddenState' has been called with a null"
	      " value for argument 'plane' or has a wrong 'side' argument'.\n");
      return 0;
    }
  DBG_fprintf(stderr, "Visu Plane : hide state (%d, old %d) for plane %p.\n",
	      side, plane->hiddenSide, (gpointer)plane);
  if (plane->hiddenSide == side)
    return 0;
  plane->hiddenSide = side;
  return 1;
}
int planeGet_hiddenState(Plane *plane)
{
  if (!plane)
    {
      fprintf(stderr, "WARNING! 'planeGet_hiddenState' has been called with a null"
	      " value for argument 'plane'.\n");
      return 0;
    }
  return plane->hiddenSide;
}
int planeShowHide_all(VisuData *visuData, GList *listOfPlanes)
{
  int i, j, k;
  int reDraw;
  float point[3];
  float pScal;
  gboolean hidingMode, visibility;
  GList *tmpLst;

  DBG_fprintf(stderr, "Planes : applying masking properties of planes.\n");

  /* Remove planes of the given list that doesn't have a masking action. */
  tmpLst = listOfPlanes;
  listOfPlanes = (GList*)0;
  while(tmpLst)
    {
      if (((Plane*)tmpLst->data)->hiddenSide != PLANE_SIDE_NONE)
	listOfPlanes = g_list_append(listOfPlanes, tmpLst->data);
      else
	DBG_fprintf(stderr, " | plane %p has a masking property.\n", tmpLst->data);
      tmpLst = g_list_next(tmpLst);
    }

  hidingMode = TRUE;
  switch (plane_hidingMode)
    {
    case plane_hideUnion: hidingMode = TRUE; break;
    case plane_hideInter: hidingMode = FALSE; break;
    }

  reDraw = 0;
  /* We change the rendered attribute of all nodes, very expensive... */
  if (listOfPlanes)
    for (i = 0; i < visuData->ntype; i++)
      for(j = 0; j < visuData->numberOfStoredNodes[i]; j++)
	{
	  visuDataGet_nodePosition(visuData, &visuData->nodes[i][j], point);
	
	  visibility = hidingMode;
	  tmpLst = listOfPlanes;
	  while(tmpLst)
	    {
	      pScal = 0.;
	      for (k = 0; k < 3; k++)
		pScal += ((Plane*)tmpLst->data)->nVect[k] *
		  (point[k] - ((Plane*)tmpLst->data)->pointG[k]);
	      switch (plane_hidingMode)
		{
		case plane_hideUnion:
		  visibility =
		    visibility && (pScal * ((Plane*)tmpLst->data)->hiddenSide > 0.);
		  break;
		case plane_hideInter:
		  visibility =
		    visibility || (pScal * ((Plane*)tmpLst->data)->hiddenSide > 0.);
		  break;
		}
	      tmpLst = g_list_next(tmpLst);
	    }
	  if (!visibility)
	    reDraw = visuNodeSet_visibility(&visuData->nodes[i][j], visibility) || reDraw;
	}
  if (reDraw)
    return 1;
  else
    return 0;
}
int planeSet_hidingMode(int mode)
{
  g_return_val_if_fail(mode >= 0 && mode < plane_nbHidingMode, 0);
  if (mode == plane_hidingMode)
    return 0;

  plane_hidingMode = mode;
  return 1;
}

/***************************************************
 * Parsing of data files containing list of planes *
 ***************************************************/
/* Know elements. */
#define PLANES_PARSER_ELEMENT_PLANES    "planes"
#define PLANES_PARSER_ELEMENT_PLANE     "plane"
#define PLANES_PARSER_ELEMENT_GEOMETRY  "geometry"
#define PLANES_PARSER_ELEMENT_HIDE      "hide"
#define PLANES_PARSER_ELEMENT_COLOR     "color"
/* Known attributes. */
#define PLANES_PARSER_ATTRIBUTES_VECTOR   "normal-vector"
#define PLANES_PARSER_ATTRIBUTES_DISTANCE "distance"
#define PLANES_PARSER_ATTRIBUTES_STATUS   "status"
#define PLANES_PARSER_ATTRIBUTES_INVERT   "invert"
#define PLANES_PARSER_ATTRIBUTES_RGBA     "rgba"

/* This method is called for every element that is parsed.
   The user_data must be a GList of planes. When a 'plane'
   element, a new plane is created and prepend in the list.
   When 'geometry' or other qualificative elements are
   found, the first plane of the list is modified accordingly. */
void listOfPlanes_element(GMarkupParseContext *context,
                          const gchar         *element_name,
                          const gchar        **attribute_names,
                          const gchar        **attribute_values,
                          gpointer             user_data,
                          GError             **error)
{
  GList **planesList;
  Plane *plane;
  float normalVector[3];
  float distance;
  float colorRGBA[4];
  Color *color;
  int i, res;
  int side, set;

  g_return_if_fail(user_data);
  planesList = (GList **)user_data;

  DBG_fprintf(stderr, "Planes parser : found '%s' element.\n", element_name);
  if (!strcmp(element_name, PLANES_PARSER_ELEMENT_PLANES))
    {
      /* Should have no attributes. */
      if (attribute_names[0])
	{
	  g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
		      _("Unexpected attribute '%s' for element '%s'."),
		      attribute_names[0], PLANES_PARSER_ELEMENT_PLANES);
	  return;
	}
      /* Initialise planeList. */
      if (*planesList)
	g_warning("Unexpected non null pointer as user_data for the "
		  "plane parser.");
      *planesList = (GList*)0;
    }
  else
    {
      if (!strcmp(element_name, PLANES_PARSER_ELEMENT_PLANE))
	{
	  /* Should have no attributes. */
	  if (attribute_names[0])
	    {
	      g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
			  _("Unexpected attribute '%s' for element '%s'."),
			  attribute_names[0], PLANES_PARSER_ELEMENT_PLANE);
	      return;
	    }
	  plane = planeNew_empty();
	  DBG_fprintf(stderr, "Planes parser : adding plane %p to list %p.\n",
		      (gpointer)plane, (gpointer)(*planesList));
	  *planesList = g_list_prepend(*planesList, (gpointer)plane);
	  DBG_fprintf(stderr, " | new plane list : %p.\n", (gpointer)(*planesList));
	}
      else
	{
	  if (!*planesList || !(*planesList)->data)
	    {
	      g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
			  _("DTD error : parent element '%s' of element '%s' is missing."),
			  PLANES_PARSER_ELEMENT_PLANE, element_name);
	      return;
	    }
	  if (!strcmp(element_name, PLANES_PARSER_ELEMENT_GEOMETRY))
	    {
	      DBG_fprintf(stderr, "Planes parser : associated plane : %p.\n", (*planesList)->data);
	      for(i = 0; attribute_names[i]; i++)
		{
		  if (!strcmp(attribute_names[i], PLANES_PARSER_ATTRIBUTES_VECTOR))
		    {
		      res = sscanf(attribute_values[i], "%g %g %g",
				   normalVector, normalVector + 1, normalVector + 2);
		      if (res != 3)
			g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
				    _("Invalid value '%s' for attribute '%s'."),
				    attribute_values[i], PLANES_PARSER_ATTRIBUTES_VECTOR);
		      planeSet_normalVector((Plane*)(*planesList)->data, normalVector);
		    }
		  else if (!strcmp(attribute_names[i], PLANES_PARSER_ATTRIBUTES_DISTANCE))
		    {
		      res = sscanf(attribute_values[i], "%g", &distance);
		      if (res != 1)
			g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
				    _("Invalid value '%s' for attribute '%s'."),
				    attribute_values[i], PLANES_PARSER_ATTRIBUTES_DISTANCE);
		      planeSet_distanceFromOrigin((Plane*)(*planesList)->data, distance);
		    }
		  else
		    g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
				_("Unexpected attribute '%s' for element '%s'."),
				attribute_names[i], PLANES_PARSER_ELEMENT_GEOMETRY);
		}
	    }
	  else if (!strcmp(element_name, PLANES_PARSER_ELEMENT_HIDE))
	    {
	      set = 0;
	      side = 1;
	      for(i = 0; attribute_names[i]; i++)
		{
		  if (!strcmp(attribute_names[i], PLANES_PARSER_ATTRIBUTES_STATUS))
		    {
		      if (!strcmp(attribute_values[i], "yes"))
			set = 1;
		      else if (!strcmp(attribute_values[i], "no"))
			set = 0;
		      else
			g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
				    _("Invalid value '%s' for attribute '%s'."),
				    attribute_values[i], PLANES_PARSER_ATTRIBUTES_STATUS);
		    }
		  else if (!strcmp(attribute_names[i], PLANES_PARSER_ATTRIBUTES_INVERT))
		    {
		      if (!strcmp(attribute_values[i], "yes"))
			side = -1;
		      else if (!strcmp(attribute_values[i], "no"))
			side = 1;
		      else
			g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
				    _("Invalid value '%s' for attribute '%s'."),
				    attribute_values[i], PLANES_PARSER_ATTRIBUTES_INVERT);
		    }
		  else
		    g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
				_("Unexpected attribute '%s' for element '%s'."),
				attribute_names[i], PLANES_PARSER_ELEMENT_HIDE);
		}
	      ((Plane*)(*planesList)->data)->hiddenSide = side * set;
	    }
	  else if (!strcmp(element_name, "color"))
	    {
	      for(i = 0; attribute_names[i]; i++)
		{
		  if (!strcmp(attribute_names[i], PLANES_PARSER_ATTRIBUTES_RGBA))
		    {
		      res = sscanf(attribute_values[i], "%g %g %g %g",
				   colorRGBA, colorRGBA + 1, colorRGBA + 2, colorRGBA + 3);
		      if (res != 4)
			g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
				    _("Invalid value '%s' for attribute '%s'."),
				    attribute_values[i], PLANES_PARSER_ATTRIBUTES_RGBA);
		      color = colorAdd_floatRGBA(colorRGBA, &res);
		      planeSet_color(((Plane*)(*planesList)->data), color);
		    }
		  else
		    g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
				_("Unexpected attribute '%s' for element '%s'."),
				attribute_names[i], PLANES_PARSER_ELEMENT_COLOR);
		}
	    }
	  else
	    g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ELEMENT,
			_("Unexpected element '%s'."), element_name);
	}
    }
}

/* Check when a element is closed that everything required has been set. */
void listOfPlanes_end(GMarkupParseContext *context,
		      const gchar         *element_name,
		      gpointer             user_data,
		      GError             **error)
{
  GList **planesList;
  float *vect;

  g_return_if_fail(user_data);
  planesList = (GList**)user_data;
  
  g_return_if_fail(*planesList && (*planesList)->data);

  if (!strcmp(element_name, "plane"))
    {
      if (!((Plane*)(*planesList)->data)->color)
	{
	  g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
		      _("DTD error : missing or wrong child element '%s'."),
		      PLANES_PARSER_ELEMENT_COLOR);
	  return;
	}
      vect = ((Plane*)(*planesList)->data)->nVectUser;
      if (vect[0] == 0. && vect[1] == 1. && vect[2] == 0.)
	{
	  g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_UNKNOWN_ATTRIBUTE,
		      _("DTD error : missing or wrong child element '%s'."),
		      PLANES_PARSER_ELEMENT_GEOMETRY);
	  return;
	}
    }
  
}

/* What to do when an error is raised. */
void listOfPlanes_error(GMarkupParseContext *context,
                          GError              *error,
                          gpointer             user_data)
{
  DBG_fprintf(stderr, "Planes parser : error raised '%s'.\n", error->message);
}

gboolean planesParse_XMLFile(gchar* filename, GList **list, GError **error)
{
  GMarkupParseContext* xmlContext;
  GMarkupParser parser;
  gboolean res;
  GIOChannel *xmlData;
  gsize size;
  gchar *buffer;
  GIOStatus ioRes;

  g_return_val_if_fail(filename && list, FALSE);

  xmlData = g_io_channel_new_file(filename, "r", error);
  if (!xmlData)
    return FALSE;

  buffer = (gchar*)0;
  ioRes = g_io_channel_read_to_end(xmlData, &buffer, &size, error);
  if (ioRes != G_IO_STATUS_NORMAL)
    {
      g_io_channel_unref(xmlData);
      return FALSE;
    }

  ioRes = g_io_channel_shutdown(xmlData, FALSE, error);
  if (ioRes != G_IO_STATUS_NORMAL)
    {
      g_io_channel_unref(xmlData);
      return FALSE;
    }
  
  g_io_channel_unref(xmlData);

  /* Create context. */
  *list = (GList*)0;
  parser.start_element = listOfPlanes_element;
  parser.end_element   = listOfPlanes_end;
  parser.text          = NULL;
  parser.passthrough   = NULL;
  parser.error         = listOfPlanes_error;
  xmlContext = g_markup_parse_context_new(&parser, 0, list, NULL);

  /* Parse data. */
  res = g_markup_parse_context_parse(xmlContext, buffer, size, error);

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

  /* Need to reverse the list since elements have been prepended. */
  *list = g_list_reverse(*list);

  return res;
}
gboolean planesExport_XMLFile(gchar* filename, GList *list, GError **error)
{
  GIOChannel *xmlData;
  GIOStatus ioRes;
  GString *buffer;
  Plane *plane;
  gsize sizeWritten;

  g_return_val_if_fail(filename && list, FALSE);

  xmlData = g_io_channel_new_file(filename, "w", error);
  if (!xmlData)
    return FALSE;

  buffer = g_string_new("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n");
  g_string_append(buffer, "<planes>\n");
  while (list)
    {
      g_string_append(buffer, "  <plane>\n");
      plane = (Plane*)list->data;
      g_string_append_printf(buffer, "    <geometry normal-vector=\"%g %g %g\""
			     " distance=\"%g\" />\n", plane->nVectUser[0],
			     plane->nVectUser[1], plane->nVectUser[2],
			     plane->dist);
      switch (plane->hiddenSide)
	{
	case PLANE_SIDE_NONE:
	  g_string_append(buffer, "    <hide status=\"no\" invert=\"no\" />\n");
	  break;
	case PLANE_SIDE_MINUS:
	  g_string_append(buffer, "    <hide status=\"yes\" invert=\"yes\" />\n");
	  break;
	case PLANE_SIDE_PLUS:
	  g_string_append(buffer, "    <hide status=\"yes\" invert=\"no\" />\n");
	  break;
	default:
	  g_warning("Unknown hiddenSide attribute ofr the given plane.");
	};
      g_string_append_printf(buffer, "    <color rgba=\"%g %g %g %g\" />\n",
			     plane->color->rgba[0], plane->color->rgba[1],
			     plane->color->rgba[2], plane->color->rgba[3]);
      g_string_append(buffer, "  </plane>\n");
      list = g_list_next(list);
    }
  g_string_append(buffer, "</planes>\n");

  /* Write buffer to the file. */
  ioRes = g_io_channel_write_chars(xmlData, buffer->str, buffer->len,
				   &sizeWritten, error);
  if (ioRes != G_IO_STATUS_NORMAL)
    {
      g_string_free(buffer, TRUE);
      g_io_channel_unref(xmlData);
      return FALSE;
    }
  
  /* Close the file. */
  ioRes = g_io_channel_shutdown(xmlData, TRUE, error);
  if (ioRes != G_IO_STATUS_NORMAL)
    {
      g_string_free(buffer, TRUE);
      g_io_channel_unref(xmlData);
      return FALSE;
    }
  
  /* Free buffers. */
  g_io_channel_unref(xmlData);
  g_string_free(buffer, TRUE);

  return TRUE;
}
