/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien
	CALISTE, laboratoire L_Sim, (2015)
  
	Adresse 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 : Damien
	CALISTE, laboratoire L_Sim, (2015)

	E-mail address:
	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 "planeset.h"

#include <string.h>

#include <iface_boxed.h>
#include <iface_animatable.h>
#include <visu_tools.h>

/**
 * SECTION:planeset
 * @short_description: Defines a storage object to gather #VisuPlane objects.
 *
 * <para>A #VisuPlaneSet object is a storage object for a bunch of
 * #VisuPlane. This object provides methods to compute hiding
 * properties, like visu_plane_set_getVisibility() ; method to load
 * and save plane definitions into a file, see
 * visu_plane_set_exportXMLFile() ; or to iterate over the internal storage.</para>
 */

/**
 * VisuPlaneSetIter:
 * @set: the #VisuPlaneSet to iter on.
 * @plane: the current #VisuPlane.
 * @next: (element-type gpointer): internal index.
 *
 * Structure to iterate over the stored @plane of @set.
 *
 * Since: 3.8
 */

struct _VisuPlaneSetPrivate
{
  gboolean dispose_has_run;

  /* A GList to store all the planes. */
  GList *set;

  VisuBox *box;

  VisuPlaneSetItemNew newItem;
  VisuPlaneSetItemFree freeItem;
  gpointer data;

  /* Masking properties. */
  gboolean masking;
  VisuPlaneSetHiddingEnum mode;
};

enum
  {
    ADD_SIGNAL,
    REMOVE_SIGNAL,
    NB_SIGNAL
  };
static guint _signals[NB_SIGNAL] = { 0 };

enum
  {
    PROP_0,
    N_PLANES_PROP,
    MASKING_PROP,
    MODE_PROP,
    N_PROP,
    ADJUST_PROP,
    BOX_PROP
  };
static GParamSpec *properties[N_PROP];

static void visu_plane_set_dispose     (GObject* obj);
static void visu_plane_set_get_property(GObject* obj, guint property_id,
                                        GValue *value, GParamSpec *pspec);
static void visu_plane_set_set_property(GObject* obj, guint property_id,
                                        const GValue *value, GParamSpec *pspec);
static void visu_boxed_interface_init(VisuBoxedInterface *iface);
static VisuBox* visu_plane_set_getBox(VisuBoxed *self);
static gboolean visu_plane_set_setBox(VisuBoxed *self, VisuBox *box);
static void visu_node_masker_interface_init(VisuNodeMaskerInterface *iface);
static void visu_animatable_interface_init(VisuAnimatableInterface *iface);
static gboolean _maskApply(const VisuNodeMasker *self, VisuNodeArray *array);

G_DEFINE_TYPE_WITH_CODE(VisuPlaneSet, visu_plane_set, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuPlaneSet)
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_BOXED,
                                              visu_boxed_interface_init)
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_NODE_MASKER,
                                              visu_node_masker_interface_init)
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_ANIMATABLE,
                                              visu_animatable_interface_init))

/* Local callbacks. */
static void onPlaneMoved(VisuPlane *plane, gpointer data);
static void onPlaneHidding(GObject *obj, GParamSpec *pspec, gpointer data);
static gboolean onPlaneAnimate(VisuPlaneSet *set, VisuAnimation *anim, const GValue *to,
                               gulong duration, gboolean loop, VisuAnimationType type);

struct _Item {
  VisuPlane *plane;
  gulong move_sig, hidding_sig, animate_sig;
  gpointer add;
};

/* Local routines. */
static void _freeItem(VisuPlaneSet *set, struct _Item *obj)
{
  if (set->priv->freeItem)
    set->priv->freeItem(obj->plane, obj->add);
  g_signal_handler_disconnect(G_OBJECT(obj->plane), obj->move_sig);
  g_signal_handler_disconnect(G_OBJECT(obj->plane), obj->hidding_sig);
  g_signal_handler_disconnect(G_OBJECT(obj->plane), obj->animate_sig);
  g_object_unref(obj->plane);
#if GLIB_MINOR_VERSION > 9
  g_slice_free1(sizeof(struct _Item), obj);
#else
  g_free(obj);
#endif
}
static gpointer _newItem(VisuPlaneSet *set, VisuPlane *plane)
{
  struct _Item *it;

#if GLIB_MINOR_VERSION > 9
  it = g_slice_alloc(sizeof(struct _Item));
#else
  it = g_malloc(sizeof(struct _Item));
#endif
  g_object_ref(plane);
  it->plane = plane;
  it->move_sig = g_signal_connect(G_OBJECT(plane), "moved",
                                  G_CALLBACK(onPlaneMoved), set);
  it->hidding_sig = g_signal_connect(G_OBJECT(plane), "notify::hidding-side",
                                  G_CALLBACK(onPlaneHidding), set);
  it->animate_sig = g_signal_connect_swapped(G_OBJECT(plane), "animate",
                                             G_CALLBACK(onPlaneAnimate), set);
  if (set->priv->newItem)
    it->add = set->priv->newItem(plane, set->priv->data);
  else
    it->add = (gpointer)0;
  return (gpointer)it;
}
static gint _cmpItem(gconstpointer a, gconstpointer b)
{
  struct _Item *obj = (struct _Item*)a;
  if (obj->plane == b)
    return 0;
  return 1;
}
#define _getItem(lst) ((struct _Item*)lst->data)->plane

static void visu_plane_set_class_init(VisuPlaneSetClass *klass)
{
  DBG_fprintf(stderr, "Visu Plane Set: creating the class of the object.\n");
  /* DBG_fprintf(stderr, "                - adding new signals ;\n"); */

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_plane_set_dispose;
  G_OBJECT_CLASS(klass)->set_property = visu_plane_set_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_plane_set_get_property;

  /**
   * VisuPlaneSet::added:
   * @set: the object emitting the signal.
   * @plane: the added #VisuPlane object.
   *
   * This signal is emitted each time a plane is added to
   * the set.
   *
   * Since: 3.8
   */
  _signals[ADD_SIGNAL] =
    g_signal_new("added", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
                 G_TYPE_NONE, 1, VISU_TYPE_PLANE, NULL);
  /**
   * VisuPlaneSet::removed:
   * @set: the object emitting the signal.
   * @plane: the removed #VisuPlane object.
   *
   * This signal is emitted each time a plane is removed from
   * the set.
   *
   * Since: 3.8
   */
  _signals[REMOVE_SIGNAL] =
    g_signal_new("removed", G_TYPE_FROM_CLASS(klass),
                 G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
                 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT,
                 G_TYPE_NONE, 1, VISU_TYPE_PLANE, NULL);
  
  /**
   * VisuPlaneSet::n-planes:
   *
   * Number of stored planes.
   *
   * Since: 3.8
   */
  properties[N_PLANES_PROP] = g_param_spec_uint("n-planes", "Number of planes",
                                                "number of planes",
                                                0, G_MAXUINT, 0, G_PARAM_READABLE |
                                                G_PARAM_STATIC_STRINGS);
  /**
   * VisuPlaneSet::masking:
   *
   * If the plane set has masking properties or not.
   *
   * Since: 3.8
   */
  properties[MASKING_PROP] = g_param_spec_boolean("masking", "Masking", "masking property",
                                                   FALSE, G_PARAM_READWRITE);
  /**
   * VisuPlaneSet::hidding-mode:
   *
   * How multiple planes are hidding an element.
   *
   * Since: 3.8
   */
  properties[MODE_PROP] = g_param_spec_uint
    ("hidding-mode", "Hidding mode", "how to hide from multiple planes",
     VISU_PLANE_SET_HIDE_UNION, VISU_PLANE_SET_HIDE_INTER, VISU_PLANE_SET_HIDE_UNION,
     G_PARAM_READWRITE);

  g_object_class_install_properties(G_OBJECT_CLASS(klass), N_PROP, properties);

  g_object_class_override_property(G_OBJECT_CLASS(klass), ADJUST_PROP, "auto-adjust");
  g_object_class_override_property(G_OBJECT_CLASS(klass), BOX_PROP, "box");
}
static void visu_boxed_interface_init(VisuBoxedInterface *iface)
{
  iface->get_box = visu_plane_set_getBox;
  iface->set_box = visu_plane_set_setBox;
}
static void visu_node_masker_interface_init(VisuNodeMaskerInterface *iface)
{
  iface->apply = _maskApply;
}
static void visu_animatable_interface_init(VisuAnimatableInterface *iface)
{
  iface->get_animation = NULL;
}

static void visu_plane_set_init(VisuPlaneSet *ext)
{
  DBG_fprintf(stderr, "Visu Plane Set: initializing a new object (%p).\n",
	      (gpointer)ext);
  ext->priv = visu_plane_set_get_instance_private(ext);
  ext->priv->dispose_has_run = FALSE;

  ext->priv->set = (GList*)0;
  ext->priv->box = (VisuBox*)0;
  ext->priv->masking = FALSE;
  ext->priv->mode = VISU_PLANE_SET_HIDE_UNION;

  ext->priv->newItem = (VisuPlaneSetItemNew)0;
  ext->priv->freeItem = (VisuPlaneSetItemFree)0;
  ext->priv->data = (gpointer)0;
}

/* This method can be called several times.
   It should unref all of its reference to
   GObjects. */
static void visu_plane_set_dispose(GObject* obj)
{
  VisuPlaneSet *set;

  DBG_fprintf(stderr, "Visu Plane Set: dispose object %p.\n", (gpointer)obj);

  set = VISU_PLANE_SET(obj);
  if (set->priv->dispose_has_run)
    return;
  set->priv->dispose_has_run = TRUE;

  /* Disconnect signals. */
  visu_plane_set_removeAll(set);
  visu_plane_set_setBox(VISU_BOXED(obj), (VisuBox*)0);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_plane_set_parent_class)->dispose(obj);
}
static void visu_plane_set_get_property(GObject* obj, guint property_id,
                                        GValue *value, GParamSpec *pspec)
{
  VisuPlaneSetPrivate *self = VISU_PLANE_SET(obj)->priv;

  DBG_fprintf(stderr, "Visu PlaneSet: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case BOX_PROP:
      g_value_set_object(value, self->box);
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      break;
    case N_PLANES_PROP:
      g_value_set_uint(value, g_list_length(self->set));
      DBG_fprintf(stderr, "%d.\n", g_value_get_uint(value));
      break;
    case MASKING_PROP:
      g_value_set_boolean(value, self->masking);
      DBG_fprintf(stderr, "%d.\n", self->masking);
      break;
    case MODE_PROP:
      g_value_set_uint(value, self->mode);
      DBG_fprintf(stderr, "%d.\n", self->mode);
      break;
    case ADJUST_PROP:
      g_value_set_boolean(value, FALSE);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_plane_set_set_property(GObject* obj, guint property_id,
                                        const GValue *value, GParamSpec *pspec)
{
  VisuPlaneSetPrivate *self = VISU_PLANE_SET(obj)->priv;

  DBG_fprintf(stderr, "Visu PlaneSet: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case BOX_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      visu_plane_set_setBox(VISU_BOXED(obj), VISU_BOX(g_value_get_object(value)));
      break;
    case MASKING_PROP:
      if (self->masking == g_value_get_boolean(value))
        break;
      self->masking = g_value_get_boolean(value);
      DBG_fprintf(stderr, "%d.\n", self->masking);
      visu_node_masker_emitDirty(VISU_NODE_MASKER(obj));
      break;
    case MODE_PROP:
      visu_plane_set_setHiddingMode(VISU_PLANE_SET(obj), g_value_get_uint(value));
      DBG_fprintf(stderr, "%d.\n", self->mode);
      break;
    case ADJUST_PROP:
      DBG_fprintf(stderr, "%d.\n", FALSE);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static gboolean visu_plane_set_setBox(VisuBoxed *self, VisuBox *box)
{
  VisuPlaneSet *set;
  VisuPlaneSetIter iter;

  g_return_val_if_fail(VISU_IS_PLANE_SET(self), FALSE);
  set = VISU_PLANE_SET(self);

  DBG_fprintf(stderr, "Visu PlaneSet: set box %p.\n", (gpointer)box);

  if (set->priv->box == box)
    return FALSE;

  if (set->priv->box)
    g_object_unref(set->priv->box);
  set->priv->box = box;
  if (!box)
    return TRUE;

  g_object_ref(box);

  visu_plane_set_iter_new(set, &iter);
  for (visu_plane_set_iter_next(&iter); iter.plane; visu_plane_set_iter_next(&iter))
    visu_boxed_setBox(VISU_BOXED(iter.plane), VISU_BOXED(box));

  DBG_fprintf(stderr, "Visu PlaneSet: set box done.\n");
  return TRUE;
}
static VisuBox* visu_plane_set_getBox(VisuBoxed *self)
{
  g_return_val_if_fail(VISU_IS_PLANE_SET(self), (VisuBox*)0);

  return VISU_PLANE_SET(self)->priv->box;
}

/**
 * visu_plane_set_new:
 *
 * Creates an object to store several planes and do hiding operations
 * with them.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): the newly created object.
 **/
VisuPlaneSet* visu_plane_set_new()
{
  VisuPlaneSet *set;

  set = VISU_PLANE_SET(g_object_new(VISU_TYPE_PLANE_SET, NULL));
  return set;
}
/**
 * visu_plane_set_newFull:
 * @newItem: (scope call): a routine to be called when adding a plane
 * to the set.
 * @freeItem: (scope call): a routine to be called when removing a
 * plane from the set.
 * @data: some user data.
 *
 * Creates an object to store several planes and do hiding operations
 * with them.
 *
 * Since: 3.8
 *
 * Returns: (transfer full):
 **/
VisuPlaneSet* visu_plane_set_newFull(VisuPlaneSetItemNew newItem,
                                     VisuPlaneSetItemFree freeItem,
                                     gpointer data)
{
  VisuPlaneSet *set;

  set = VISU_PLANE_SET(g_object_new(VISU_TYPE_PLANE_SET, NULL));
  set->priv->newItem = newItem;
  set->priv->freeItem = freeItem;
  set->priv->data = data;
  return set;
}

/**
 * visu_plane_set_iter_new:
 * @set: a #VisuPlaneSet object.
 * @iter: (out caller-allocates): the iterator to create.
 *
 * Creates an iterator on the internal storage of #VisuPlane objects.
 *
 * Since: 3.8
 *
 * Returns: TRUE if iterator is valid (i.e. there are planes in @set).
 **/
gboolean visu_plane_set_iter_new(const VisuPlaneSet *set, VisuPlaneSetIter *iter)
{
  g_return_val_if_fail(VISU_IS_PLANE_SET(set) && iter, FALSE);

  iter->set = set;
  iter->next = set->priv->set;
  return (iter->next != (GList*)0);
}
/**
 * visu_plane_set_iter_next:
 * @iter: an iterator.
 *
 * Use this function to iterate on plane stored in a #VisuPlaneSet object.
 *
 * Since: 3.8
 *
 * Returns: TRUE if any plane remains.
 **/
gboolean visu_plane_set_iter_next(VisuPlaneSetIter *iter)
{
  g_return_val_if_fail(iter && iter->set, FALSE);

  if (!iter->next)
    iter->plane = (VisuPlane*)0;
  else
    {
      iter->plane = _getItem(iter->next);
      iter->next = g_list_next(iter->next);
    }
  return (iter->next != (GList*)0);
}

/**
 * visu_plane_set_getAt:
 * @set: a #VisuPlaneSet object.
 * @i: an index.
 *
 * Retrieve the plane stored at index @i.
 *
 * Since: 3.8
 *
 * Returns: (transfer none) (allow-none): a #VisuPlane object or NULL
 * index is out of bounds.
 **/
VisuPlane* visu_plane_set_getAt(const VisuPlaneSet *set, guint i)
{
  GList *at;

  g_return_val_if_fail(VISU_IS_PLANE_SET(set), (VisuPlane*)0);

  at = g_list_nth(set->priv->set, i);
  if (at)
    return _getItem(at);
  else
    return (VisuPlane*)0;
}

/**
 * visu_plane_set_add:
 * @set: a #VisuPlaneSet object.
 * @plane: (transfer none): a #VisuPlane object.
 *
 * Adds a @plane to the list of stored planes.
 *
 * Since: 3.8
 *
 * Returns: FALSE if @plane was already registered.
 **/
gboolean visu_plane_set_add(VisuPlaneSet *set, VisuPlane *plane)
{
  GList *lst;

  g_return_val_if_fail(VISU_IS_PLANE_SET(set) && plane, FALSE);

  lst = g_list_find_custom(set->priv->set, plane, _cmpItem);
  if (lst)
    return FALSE;

  if (set->priv->box)
    visu_boxed_setBox(VISU_BOXED(plane), VISU_BOXED(set->priv->box));
  set->priv->set = g_list_append(set->priv->set,
                                 _newItem(set, plane));
  g_signal_emit(G_OBJECT(set), _signals[ADD_SIGNAL], 0, plane);
  g_object_notify_by_pspec(G_OBJECT(set), properties[N_PLANES_PROP]);
  DBG_fprintf(stderr, "Visu Planeset: %p has %d ref counts.\n", (gpointer)plane,
              G_OBJECT(plane)->ref_count);

  if (visu_plane_getHiddenState(plane) != VISU_PLANE_SIDE_NONE && set->priv->masking)
    visu_node_masker_emitDirty(VISU_NODE_MASKER(set));

  return TRUE;
}
/**
 * visu_plane_set_remove:
 * @set: a #VisuPlaneSet object.
 * @plane: a #VisuPlane object.
 *
 * Remove @plane from the list of stored planes.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @plane was found and removed.
 **/
gboolean visu_plane_set_remove(VisuPlaneSet *set, VisuPlane *plane)
{
  gboolean wasMasking;
  GList *lst;

  g_return_val_if_fail(VISU_IS_PLANE_SET(set) && plane, FALSE);
  
  lst = g_list_find_custom(set->priv->set, plane, _cmpItem);
  if (!lst)
    return FALSE;

  wasMasking = (visu_plane_getHiddenState(plane) != VISU_PLANE_SIDE_NONE);
  _freeItem(set, (struct _Item*)lst->data);
  set->priv->set = g_list_delete_link(set->priv->set, lst);
  g_signal_emit(G_OBJECT(set), _signals[REMOVE_SIGNAL], 0, plane, NULL);
  g_object_notify_by_pspec(G_OBJECT(set), properties[N_PLANES_PROP]);

  if (wasMasking && set->priv->masking)
    visu_node_masker_emitDirty(VISU_NODE_MASKER(set));

  return TRUE;
}
/**
 * visu_plane_set_removeAll:
 * @set: a #VisuPlaneSet object.
 *
 * Remove all stored planes.
 *
 * Since: 3.8
 *
 * Returns: TRUE if some planes were actually removed.
 **/
gboolean visu_plane_set_removeAll(VisuPlaneSet *set)
{
  GList *lst;
  gboolean cleaned;
  
  g_return_val_if_fail(VISU_IS_PLANE_SET(set), FALSE);

  cleaned = (set->priv->set != (GList*)0);
  for (lst = set->priv->set; lst; lst = g_list_next(lst))
    {
      g_signal_emit(G_OBJECT(set), _signals[REMOVE_SIGNAL], 0, ((struct _Item*)lst->data)->plane, NULL);
      _freeItem(set, (struct _Item*)lst->data);
    }
  g_list_free(set->priv->set);
  set->priv->set = (GList*)0;

  if (cleaned)
    {
      g_object_notify_by_pspec(G_OBJECT(set), properties[N_PLANES_PROP]);
      visu_node_masker_emitDirty(VISU_NODE_MASKER(set));
    }

  return cleaned;
}

/**
 * visu_plane_set_getIntersection:
 * @set: an array of #VisuPlane, NULL terminated ;
 * @pointA: three cartesian coordinates.
 * @pointB: three cartesian coordinates.
 * @inter: a location to store the intersection point.
 * @inside: a boolean.
 *
 * Compute the location of the intersection point of segment AB with list of
 * planes @set. If there are several intersections, the closest to
 * point A is returned. If @inside is TRUE, then the intersection
 * point must be within the line [AB].
 *
 * Since: 3.8
 *
 * Returns: TRUE if there is an intersection.
 */
gboolean visu_plane_set_getIntersection(const VisuPlaneSet *set, float pointA[3],
                                        float pointB[3], float inter[3], gboolean inside)
{
  float lambda;
  float lambdaMin;
  VisuPlaneSetIter iter;

  g_return_val_if_fail(VISU_IS_PLANE_SET(set), FALSE);

/*   DBG_fprintf(stderr, "VisuPlane: intersection A(%g;%g;%g) B(%g;%g;%g).\n", */
/* 	      pointA[0], pointA[1], pointA[2], pointB[0], pointB[1], pointB[2]); */
  lambdaMin = 2.f;
  visu_plane_set_iter_new(set, &iter);
  for (visu_plane_set_iter_next(&iter); iter.plane; visu_plane_set_iter_next(&iter))
    {
      if (!visu_plane_getLineIntersection(iter.plane, pointA, pointB, &lambda))
        lambda = 2.f;
      if (( inside && lambda >= 0. && lambda <= 1. && lambda < lambdaMin) ||
          (!inside && ((lambda <= 0.f && lambda > lambdaMin) || (lambdaMin > 0.f && lambda < lambdaMin))))
        lambdaMin = lambda;
    }
  if (lambdaMin == 2.f)
    return FALSE;

  inter[0] = pointA[0] + lambdaMin * (pointB[0] - pointA[0]);
  inter[1] = pointA[1] + lambdaMin * (pointB[1] - pointA[1]);
  inter[2] = pointA[2] + lambdaMin * (pointB[2] - pointA[2]);
  return TRUE;
}

/* Masking property of the list of planes. */
static void onPlaneMoved(VisuPlane *plane, gpointer data)
{
  /* No effect. */
  if (visu_plane_getHiddenState(plane) == VISU_PLANE_SIDE_NONE ||
      !VISU_PLANE_SET(data)->priv->masking)
    return;

  if (VISU_PLANE_SET(data)->priv->masking)
    visu_node_masker_emitDirty(VISU_NODE_MASKER(data));
}
static void onPlaneHidding(GObject *obj _U_, GParamSpec *pspec _U_, gpointer data)
{
  if (!VISU_PLANE_SET(data)->priv->masking)
    return;

  if (VISU_PLANE_SET(data)->priv->masking)
    visu_node_masker_emitDirty(VISU_NODE_MASKER(data));
}
static gboolean onPlaneAnimate(VisuPlaneSet *set, VisuAnimation *anim, const GValue *to,
                               gulong duration, gboolean loop, VisuAnimationType type)
{
  return visu_animatable_animate(VISU_ANIMATABLE(set), anim, to, duration / 1000, loop, type);
}
/**
 * visu_plane_set_setHiddingMode:
 * @set: the list of planes.
 * @mode: a value related to the hiding mode (look at the enum #VisuPlaneSetHiddingEnum).
 *
 * This method is used to set the hiding mode flag. In union mode, elements
 * are not drawn if they are hidden by one plane at least. In intersection mode,
 * elements are only hidden if masked by all planes.
 *
 * Since: 3.8
 *
 * Returns: TRUE if status changed.
 */
gboolean visu_plane_set_setHiddingMode(VisuPlaneSet *set, VisuPlaneSetHiddingEnum mode)
{
  g_return_val_if_fail(VISU_IS_PLANE_SET(set), FALSE);

  if (set->priv->mode == mode)
    return FALSE;

  set->priv->mode = mode;
  g_object_notify_by_pspec(G_OBJECT(set), properties[MODE_PROP]);
  if (set->priv->masking)
    visu_node_masker_emitDirty(VISU_NODE_MASKER(set));
  return TRUE;
}
/**
 * visu_plane_set_getHiddingStatus:
 * @set: a #VisuPlaneSet object.
 *
 * Inquire if the @set has any masking planes.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @set has any masking planes.
 **/
gboolean visu_plane_set_getHiddingStatus(const VisuPlaneSet *set)
{
  VisuPlaneSetIter pl;

  g_return_val_if_fail(VISU_IS_PLANE_SET(set), FALSE);

  if (!set->priv->masking)
    return FALSE;

  visu_plane_set_iter_new(set, &pl);
  for (visu_plane_set_iter_next(&pl); pl.plane; visu_plane_set_iter_next(&pl))
    if (visu_plane_getHiddenState(pl.plane) != VISU_PLANE_SIDE_NONE)
      return TRUE;
  return FALSE;
}
/**
 * visu_plane_set_getVisibility:
 * @set: a #VisuPlaneSet object ;
 * @point: three cartesian coordinates.
 *
 * Compute the visibility of the given @point, following the masking scheme
 * of the given plane list.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the point is not masked.
 */
gboolean visu_plane_set_getVisibility(const VisuPlaneSet *set, float point[3])
{
  VisuPlaneSetIter iter;
  gboolean visibility;

  g_return_val_if_fail(VISU_IS_PLANE_SET(set), FALSE);

  visu_plane_set_iter_new(set, &iter);
  switch (set->priv->mode)
    {
    case VISU_PLANE_SET_HIDE_UNION:
      visibility = TRUE;
      for (visu_plane_set_iter_next(&iter); iter.plane; visu_plane_set_iter_next(&iter))
        if (visu_plane_getHiddenState(iter.plane) != VISU_PLANE_SIDE_NONE)
          visibility = visibility && visu_plane_getVisibility(iter.plane, point);
      break;
    case VISU_PLANE_SET_HIDE_INTER:
      visibility = FALSE;
      for (visu_plane_set_iter_next(&iter); iter.plane; visu_plane_set_iter_next(&iter))
        if (visu_plane_getHiddenState(iter.plane) != VISU_PLANE_SIDE_NONE)
          visibility = visibility || visu_plane_getVisibility(iter.plane, point);
      break;
    default:
      visibility = TRUE;
      break;
    }
  return visibility;
}
static gboolean _maskApply(const VisuNodeMasker *self, VisuNodeArray *array)
{
  gboolean reDraw;
  float point[3];
  VisuNodeArrayIter iter;
  VisuPlaneSet *set;

  g_return_val_if_fail(VISU_IS_PLANE_SET(self), FALSE);

  set = VISU_PLANE_SET(self);

  DBG_fprintf(stderr, "VisuPlaneSet : applying masking properties of planes.\n");
  if (!visu_plane_set_getHiddingStatus(set))
    return FALSE;

  reDraw = FALSE;
  /* We change the rendered attribute of all nodes, very expensive... */
  visu_node_array_iter_new(array, &iter);
  for (visu_node_array_iterStart(array, &iter); iter.element;
       visu_node_array_iterNextElement(array, &iter, FALSE))
    if (visu_element_getMaskable(iter.element) && visu_element_getRendered(iter.element))
      {
        DBG_fprintf(stderr, " | element '%s'\n", iter.element->name);
        for (visu_node_array_iterRestartNode(array, &iter); iter.node;
             visu_node_array_iterNextNode(array, &iter))
          {
            /* If node is already hiden, well, we go to the next. */
            if (!iter.node->rendered)
              continue;

            visu_data_getNodePosition(VISU_DATA(array), iter.node, point);
            if (!visu_plane_set_getVisibility(set, point))
              reDraw = visu_node_setVisibility(iter.node, FALSE) || reDraw;
            DBG_fprintf(stderr, " | node '%d' -> %d\n", iter.node->number, iter.node->rendered);
          }
      }
  return reDraw;
}

/***************************************************
 * Parsing of data files containing list of planes *
 ***************************************************/
/* Known 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_RENDERED "rendered"
#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"

static gboolean planesStarted;

/* 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. */
static void listOfVisuPlanes_element(GMarkupParseContext *context _U_,
                                     const gchar         *element_name,
                                     const gchar        **attribute_names,
                                     const gchar        **attribute_values,
                                     gpointer             user_data,
                                     GError             **error)
{
  GList **planesList;
  VisuPlane *plane;
  float normalVector[3];
  float distance;
  float colorRGBA[4];
  ToolColor *color;
  int i, res;
  int side, set;
  gboolean rendered;

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

  DBG_fprintf(stderr, "VisuPlanes 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.");
      planesStarted = TRUE;
      *planesList = (GList*)0;
    }
  else if (!strcmp(element_name, PLANES_PARSER_ELEMENT_PLANE))
    {
      rendered = TRUE;
      /* May have one attribute. */
      if (attribute_names[0])
	{
	  if (!strcmp(attribute_names[0], PLANES_PARSER_ATTRIBUTES_RENDERED) )
	    {
	      if (!strcmp(attribute_values[0], "yes"))
		rendered = TRUE;
	      else if (!strcmp(attribute_values[0], "no"))
		rendered = FALSE;
	      else
		g_set_error(error, G_MARKUP_ERROR, G_MARKUP_ERROR_INVALID_CONTENT,
			    _("Invalid value '%s' for attribute '%s'."),
			    attribute_values[0], PLANES_PARSER_ATTRIBUTES_RENDERED);
	    }
	  else
	    {
	      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 = visu_plane_newUndefined();
      visu_plane_setRendered(plane, rendered);
      DBG_fprintf(stderr, "VisuPlanes 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 (planesStarted && !strcmp(element_name, PLANES_PARSER_ELEMENT_GEOMETRY))
    {
      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;
	}

      DBG_fprintf(stderr, "VisuPlanes 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);
	      visu_plane_setNormalVector(VISU_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);
	      visu_plane_setDistanceFromOrigin(VISU_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 (planesStarted && !strcmp(element_name, PLANES_PARSER_ELEMENT_HIDE))
    {
      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;
	}

      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);
	}
      visu_plane_setHiddenState(VISU_PLANE((*planesList)->data), side * set);
    }
  else if (planesStarted && !strcmp(element_name, PLANES_PARSER_ELEMENT_COLOR))
    {
      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;
	}

      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 = tool_color_addFloatRGBA(colorRGBA, &res);
	      visu_plane_setColor(VISU_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 if (planesStarted)
    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. */
static void listOfVisuPlanes_end(GMarkupParseContext *context _U_,
                                 const gchar         *element_name,
                                 gpointer             user_data,
                                 GError             **error)
{
  GList **planesList;
  float vect[3];

  g_return_if_fail(user_data);
  planesList = (GList**)user_data;
  
  if (!strcmp(element_name, PLANES_PARSER_ELEMENT_PLANE))
    {
      g_return_if_fail(*planesList && (*planesList)->data);

      if (!visu_plane_getColor(VISU_PLANE((*planesList)->data)))
	{
	  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;
	}
      visu_plane_getNVectUser(VISU_PLANE((*planesList)->data), vect);
      if (vect[0] == 0. && vect[1] == 0. && 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;
	}
    }
  else if (!strcmp(element_name, PLANES_PARSER_ELEMENT_PLANES))
    planesStarted = FALSE;
}

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

/**
 * visu_plane_set_parseXMLFile:
 * @set: a #VisuPlaneSet object to store read planes.
 * @filename: (type filename): the file to parse ;
 * @error: a pointer to store the error (can be NULL).
 *
 * Read the given file (syntax in XML) and create a list of planes.
 *
 * Since: 3.8
 *
 * Returns: TRUE if everything goes right, if not and @error (if not NULL)
 *          is set and contains the message of the error.
 */
gboolean visu_plane_set_parseXMLFile(VisuPlaneSet *set, const gchar* filename, GError **error)
{
  GMarkupParseContext* xmlContext;
  GMarkupParser parser;
  gboolean res;
  gsize size;
  gchar *buffer;
  GList *list, *tmpLst;

  g_return_val_if_fail(VISU_IS_PLANE_SET(set) && filename, FALSE);

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

  /* Create context. */
  list = (GList*)0;
  parser.start_element = listOfVisuPlanes_element;
  parser.end_element   = listOfVisuPlanes_end;
  parser.text          = NULL;
  parser.passthrough   = NULL;
  parser.error         = listOfVisuPlanes_error;
  xmlContext = g_markup_parse_context_new(&parser, 0, &list, NULL);

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

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

  if (!res)
    return FALSE;
  if (!list)
    {
      *error = g_error_new(G_MARKUP_ERROR, G_MARKUP_ERROR_EMPTY,
			   _("The file contains no plane.\n"));
      return FALSE;
    }

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

  /* Convert the list to an array. */
  DBG_fprintf(stderr, "Visu PlaneSet: add %d planes to set.\n", g_list_length(list));
  for (tmpLst = list; tmpLst; tmpLst = g_list_next(tmpLst))
    {
      visu_plane_set_add(set, VISU_PLANE(tmpLst->data));
      g_object_unref(tmpLst->data);
    }
  g_list_free(list);

  return TRUE;
}
/**
 * visu_plane_set_exportXMLFile:
 * @set: a #VisuPlaneSet object.
 * @filename: the file to export to ;
 * @error: a pointer to store the error (can be NULL).
 *
 * Export in XML format the given list of planes to the given file.
 *
 * Since: 3.8
 *
 * Returns: TRUE if everything goes right, if not and @error (if not NULL)
 *          is set and contains the message of the error.
 */
gboolean visu_plane_set_exportXMLFile(const VisuPlaneSet *set, const gchar* filename, GError **error)
{
  GString *buffer;
  VisuPlaneSetIter iter;
  gboolean valid;
  float vect[3];
  const ToolColor *color;

  g_return_val_if_fail(VISU_IS_PLANE_SET(set) && filename, FALSE);

  buffer = g_string_new("  <planes>\n");
  visu_plane_set_iter_new(set, &iter);
  for (visu_plane_set_iter_next(&iter); iter.plane; visu_plane_set_iter_next(&iter))
    {
      g_string_append_printf(buffer, "    <plane rendered=\"%s\">\n",
			     (visu_plane_getRendered(iter.plane))?"yes":"no");
      visu_plane_getNVectUser(iter.plane, vect);
      g_string_append_printf(buffer, "      <geometry normal-vector=\"%g %g %g\""
			     " distance=\"%g\" />\n", vect[0], vect[1], vect[2],
			     visu_plane_getDistanceFromOrigin(iter.plane));
      switch (visu_plane_getHiddenState(iter.plane))
	{
	case VISU_PLANE_SIDE_NONE:
	  g_string_append(buffer, "      <hide status=\"no\" invert=\"no\" />\n");
	  break;
	case VISU_PLANE_SIDE_MINUS:
	  g_string_append(buffer, "      <hide status=\"yes\" invert=\"yes\" />\n");
	  break;
	case VISU_PLANE_SIDE_PLUS:
	  g_string_append(buffer, "      <hide status=\"yes\" invert=\"no\" />\n");
	  break;
	default:
	  g_warning("Unknown hiddenSide attribute ofr the given plane.");
	};
      color = visu_plane_getColor(iter.plane);
      g_string_append_printf(buffer, "      <color rgba=\"%g %g %g %g\" />\n",
			     color->rgba[0], color->rgba[1], color->rgba[2], color->rgba[3]);
      g_string_append(buffer, "    </plane>\n");
    }
  g_string_append(buffer, "  </planes>");

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