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

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

#include <string.h>

#include <visu_nodes.h>
#include <visu_dataatomic.h>
#include <visu_dataspin.h>
#include <opengl.h>
#include <coreTools/toolColor.h>
#include <openGLFunctions/objectList.h>
#include <renderingMethods/elementAtomic.h>
#include <renderingMethods/elementSpin.h>
#include <renderingMethods/spinMethod.h>

#undef near
#undef far

/**
 * SECTION:nodes
 * @short_description: Defines methods to draw a set of nodes.
 *
 * <para></para>
 */

typedef struct _GlIds
{
  VisuElementRenderer *renderer;
  gulong notify_sig, size_sig, colorize_sig;
  GLuint material, nodes;
} GlIds;

enum
  {
    PROP_0,
    DATA_PROP,
    TYPE_PROP,
    MAX_SIZE_PROP,
    COLORIZER_PROP,
    N_PROP
  };
/* static GParamSpec *_properties[N_PROP]; */

struct _VisuGlExtNodesPrivate
{
  gboolean dispose_has_run;

  VisuElementRendererEffects effect;

  guint nEle;
  GArray *glIds;

  VisuGlView *view;
  VisuData *dataObj;
  gulong popId, posId, popIncId, popDecId, visId, boxId;
  gulong renId, detailsId;
  VisuBox *box;
  gulong unitId;
  GList *colorizers;
  gulong dirtyId;
};

#define VISU_GL_EXT_NODES_ID "Nodes"

/* Local routines. */
static void visu_gl_ext_nodes_dispose (GObject* obj);
static void visu_gl_ext_nodes_finalize(GObject* obj);
static void visu_gl_ext_nodes_get_property(GObject* obj, guint property_id,
                                           GValue *value, GParamSpec *pspec);
static void visu_gl_ext_nodes_set_property(GObject* obj, guint property_id,
                                           const GValue *value, GParamSpec *pspec);
static void visu_gl_ext_nodes_rebuild (VisuGlExt *ext);
static void visu_gl_ext_nodes_draw(VisuGlExt *ext);
static gboolean visu_gl_ext_nodes_setGlView(VisuGlExt *ext, VisuGlView *view);
static void visu_node_array_renderer_interface_init(VisuNodeArrayRendererInterface *iface);

static void _setPopulation(VisuGlExtNodes *nodes);
static void compileElementMaterial(VisuGlExtNodes *ext,
                                   GLuint displayList, VisuElementRenderer *ele);

static void createAllElements(VisuGlExtNodes *ext);
static void createNodes(VisuGlExtNodes *ext, GlIds *ids);
static void createAllNodes(VisuGlExtNodes *ext);
static void _freeGlIds(GlIds *ids);
static VisuNodeArray* _getNodeArray(VisuNodeArrayRenderer *self);
static gboolean _setNodeArray(VisuNodeArrayRenderer *self, VisuNodeArray *array);
static VisuElementRenderer* _getElementRenderer(VisuNodeArrayRenderer *self,
                                                const VisuElement *element);
static gfloat _getMaxElementSize(VisuNodeArrayRenderer *node_array, guint *n);
static void _setColorizer(VisuNodeArrayRenderer *nodes,
                          VisuDataColorizer *colorizer);
static gboolean _pushColorizer(VisuNodeArrayRenderer *nodes,
                               VisuDataColorizer *colorizer);
static gboolean _removeColorizer(VisuNodeArrayRenderer *nodes,
                                 VisuDataColorizer *colorizer);
static VisuDataColorizer* _getColorizer(VisuNodeArrayRenderer *nodes);

/* Signal callbacks. */
static void _setDirty(VisuGlExt *ext);
static void onPopulationInc(VisuData *dataObj, GArray *ids, gpointer data);
static void onPopulationDec(VisuData *dataObj, GArray *ids, gpointer data);
static void onPositionChanged(VisuData *dataObj, GArray *ids, gpointer data);
static void onVisibilityChanged(VisuData *dataObj, gpointer data);
static void onRenderingChanged(VisuGlExtNodes *nodes);
static void onColorize(VisuGlExtNodes *ext, GParamSpec *pspec, VisuElement *element);
static void onRenderer(VisuGlExtNodes *ext, GParamSpec *pspec, VisuElementRenderer *renderer);
static void onSize(VisuGlExtNodes *ext, gfloat extent, VisuElementRenderer *renderer);
static void onUnits(VisuGlExtNodes *ext, gfloat fact, VisuBox *box);

G_DEFINE_TYPE_WITH_CODE(VisuGlExtNodes, visu_gl_ext_nodes, VISU_TYPE_GL_EXT,
                        G_ADD_PRIVATE(VisuGlExtNodes)
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_NODE_ARRAY_RENDERER,
                                              visu_node_array_renderer_interface_init))

static void visu_gl_ext_nodes_class_init(VisuGlExtNodesClass *klass)
{
  DBG_fprintf(stderr, "Extension Nodes: creating the class of the object.\n");
  /* DBG_fprintf(stderr, "                - adding new signals ;\n"); */

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose      = visu_gl_ext_nodes_dispose;
  G_OBJECT_CLASS(klass)->finalize     = visu_gl_ext_nodes_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_gl_ext_nodes_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_gl_ext_nodes_get_property;
  VISU_GL_EXT_CLASS(klass)->rebuild = visu_gl_ext_nodes_rebuild;
  VISU_GL_EXT_CLASS(klass)->draw = visu_gl_ext_nodes_draw;
  VISU_GL_EXT_CLASS(klass)->setGlView = visu_gl_ext_nodes_setGlView;
  
  g_object_class_override_property(G_OBJECT_CLASS(klass), DATA_PROP, "data");
  g_object_class_override_property(G_OBJECT_CLASS(klass), TYPE_PROP, "type");
  g_object_class_override_property(G_OBJECT_CLASS(klass), MAX_SIZE_PROP, "max-element-size");
  g_object_class_override_property(G_OBJECT_CLASS(klass), COLORIZER_PROP, "colorizer");
}
static void visu_node_array_renderer_interface_init(VisuNodeArrayRendererInterface *iface)
{
  iface->getNodeArray = _getNodeArray;
  iface->setNodeArray = _setNodeArray;
  iface->getElement   = _getElementRenderer;
  iface->getExtent    = _getMaxElementSize;
  iface->getColorizer = _getColorizer;
  iface->pushColorizer = _pushColorizer;
  iface->removeColorizer = _removeColorizer;
}

static void visu_gl_ext_nodes_init(VisuGlExtNodes *obj)
{
  DBG_fprintf(stderr, "Extension Nodes: initializing a new object (%p).\n",
	      (gpointer)obj);
  
  obj->priv = visu_gl_ext_nodes_get_instance_private(obj);
  obj->priv->dispose_has_run = FALSE;

  /* Private data. */
  obj->priv->effect = VISU_ELEMENT_RENDERER_NO_EFFECT;
  obj->priv->glIds = g_array_new(FALSE, FALSE, sizeof(GlIds));
  g_array_set_clear_func(obj->priv->glIds, (GDestroyNotify)_freeGlIds);
  obj->priv->nEle = 0;
  obj->priv->view = (VisuGlView*)0;
  obj->priv->dataObj = (VisuData*)0;
  obj->priv->popId = 0;
  obj->priv->posId = 0;
  obj->priv->popIncId = 0;
  obj->priv->popDecId = 0;
  obj->priv->visId = 0;
  obj->priv->boxId = 0;
  obj->priv->box = (VisuBox*)0;
  obj->priv->colorizers = (GList*)0;
}

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

  DBG_fprintf(stderr, "Extension Nodes: dispose object %p.\n", (gpointer)obj);

  nodes = VISU_GL_EXT_NODES(obj);
  if (nodes->priv->dispose_has_run)
    return;
  nodes->priv->dispose_has_run = TRUE;

  visu_gl_ext_nodes_setGlView(VISU_GL_EXT(nodes), (VisuGlView*)0);
  _setNodeArray(VISU_NODE_ARRAY_RENDERER(nodes), (VisuNodeArray*)0);
  _setColorizer(VISU_NODE_ARRAY_RENDERER(nodes), (VisuDataColorizer*)0);
  g_list_free_full(VISU_GL_EXT_NODES(nodes)->priv->colorizers, g_object_unref);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_gl_ext_nodes_parent_class)->dispose(obj);
}
/* This method is called once only. */
static void visu_gl_ext_nodes_finalize(GObject* obj)
{
  VisuGlExtNodesPrivate *nodes;

  g_return_if_fail(obj);

  DBG_fprintf(stderr, "Extension Nodes: finalize object %p.\n", (gpointer)obj);

  nodes = VISU_GL_EXT_NODES(obj)->priv;

  /* Free privs elements. */
  DBG_fprintf(stderr, "Extension Nodes: free private nodes.\n");
  g_array_free(nodes->glIds, TRUE);

  /* Chain up to the parent class */
  DBG_fprintf(stderr, "Extension Nodes: chain to parent.\n");
  G_OBJECT_CLASS(visu_gl_ext_nodes_parent_class)->finalize(obj);
  DBG_fprintf(stderr, "Extension Nodes: freeing ... OK.\n");
}
static void visu_gl_ext_nodes_get_property(GObject* obj, guint property_id,
                                           GValue *value, GParamSpec *pspec)
{
  VisuGlExtNodes *self = VISU_GL_EXT_NODES(obj);

  DBG_fprintf(stderr, "Extension Nodes: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case DATA_PROP:
      g_value_set_object(value, self->priv->dataObj);
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      break;
    case TYPE_PROP:
      g_value_set_gtype(value, (self->priv->dataObj) ?
                        G_OBJECT_TYPE(self->priv->dataObj) : G_TYPE_NONE);
      DBG_fprintf(stderr, "%d.\n", (gint)g_value_get_gtype(value));
      break;
    case MAX_SIZE_PROP:
      g_value_set_float(value, visu_node_array_renderer_getMaxElementSize(VISU_NODE_ARRAY_RENDERER(obj), (guint*)0));
      DBG_fprintf(stderr, "%g.\n", g_value_get_float(value));
      break;
    case COLORIZER_PROP:
      g_value_set_object(value, self->priv->colorizers ? self->priv->colorizers->data : NULL);
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_gl_ext_nodes_set_property(GObject* obj, guint property_id,
                                           const GValue *value, GParamSpec *pspec)
{
  DBG_fprintf(stderr, "Extension Nodes: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case DATA_PROP:
      DBG_fprintf(stderr, "%p\n", g_value_get_object(value));
      visu_node_array_renderer_setNodeArray(VISU_NODE_ARRAY_RENDERER(obj),
                                            VISU_NODE_ARRAY(g_value_get_object(value)));
      break;
    case COLORIZER_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      _pushColorizer(VISU_NODE_ARRAY_RENDERER(obj), g_value_get_object(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_gl_ext_nodes_new:
 *
 * Creates a new #VisuGlExt to draw a set of nodes. It can be used
 * also for picking, see visu_gl_ext_nodes_getSelection().
 *
 * Since: 3.7
 *
 * Returns: a pointer to the VisuGlExt it created or
 * NULL otherwise.
 */
VisuGlExtNodes* visu_gl_ext_nodes_new()
{
  char *name = VISU_GL_EXT_NODES_ID;
  char *description = _("Draw all the nodes.");

  DBG_fprintf(stderr,"Extension Nodes: new object.\n");
  
  return g_object_new(VISU_TYPE_GL_EXT_NODES, "name", name, "label", _(name),
                      "description", description, "nGlObj", 1,
                      "priority", VISU_GL_EXT_PRIORITY_NODES, NULL);
}

static void visu_gl_ext_nodes_rebuild(VisuGlExt *ext)
{
  VisuGlExtNodes *extNodes;
  VisuNodeArrayIter iter;
  GlIds *ids;
  guint i;

  extNodes = VISU_GL_EXT_NODES(ext);
  DBG_fprintf(stderr, "Extension Nodes: rebuilding object list for visuData %p.\n",
              (gpointer)extNodes->priv->dataObj);

  if (!extNodes->priv->dataObj)
    glDeleteLists(visu_gl_ext_getGlList(ext), 1);
  else
    {
      visu_node_array_iter_new(VISU_NODE_ARRAY(extNodes->priv->dataObj), &iter);
      createAllElements(extNodes);
      for (i = 0; i < extNodes->priv->glIds->len; i++)
        {
          ids = &g_array_index(extNodes->priv->glIds, GlIds, i);
          visu_element_renderer_rebuild(ids->renderer, extNodes->priv->view);
        }
      createAllNodes(extNodes);
    }
  visu_gl_ext_setDirty(ext, FALSE);
}

static gboolean _setGlIdRenderer(GlIds *ids, VisuElementRenderer *renderer,
                                 VisuGlExtNodes *ext)
{
  DBG_fprintf(stderr, "Extension Nodes: defining renderer for %p.\n", (gpointer)ids);
  if (ids->renderer == renderer)
    return FALSE;

  if (ids->renderer)
    {
      g_signal_handler_disconnect(visu_element_renderer_getElement(ids->renderer),
                                  ids->colorize_sig);
      g_signal_handler_disconnect(ids->renderer, ids->notify_sig);
      g_signal_handler_disconnect(ids->renderer, ids->size_sig);
      visu_element_renderer_setGlView(ids->renderer, (VisuGlView*)0);
      DBG_fprintf(stderr, "Extension Nodes: free renderer %p.\n", (gpointer)ids->renderer);
      g_object_unref(ids->renderer);
    }
  ids->renderer = renderer;
  if (renderer)
    {
      g_return_val_if_fail(VISU_IS_GL_EXT_NODES(ext), FALSE);
      DBG_fprintf(stderr, "Extension Nodes: ref renderer %p.\n", (gpointer)renderer);
      g_object_ref(renderer);
      visu_element_renderer_setGlView(renderer, ext->priv->view);
      ids->notify_sig = g_signal_connect_swapped(renderer, "notify",
                                                 G_CALLBACK(onRenderer), ext);
      ids->size_sig = g_signal_connect_swapped(renderer, "size-changed",
                                               G_CALLBACK(onSize), ext);
      ids->colorize_sig = g_signal_connect_swapped(visu_element_renderer_getElement(renderer), "notify::colorizable",
                                                   G_CALLBACK(onColorize), ext);
    }
  return TRUE;
}
static void _freeGlIds(GlIds *ids)
{
  DBG_fprintf(stderr, "Extension Nodes: free ids %p.\n", (gpointer)ids);
  _setGlIdRenderer(ids, (VisuElementRenderer*)0, (VisuGlExtNodes*)0);
}
static GlIds* _getGlIds(VisuGlExtNodes *ext, const VisuElement *element)
{
  GlIds *ids;
  guint i;

  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(ext), (GlIds*)0);
  for (i = 0; i < ext->priv->nEle; i++)
    {
      ids = &g_array_index(ext->priv->glIds, GlIds, i);
      if (visu_element_renderer_getElement(ids->renderer) == element)
        return ids;
    }
  return (GlIds*)0;
}
static GlIds* _getGlIdsByRenderer(VisuGlExtNodes *ext,
                                  const VisuElementRenderer *renderer)
{
  GlIds *ids;
  guint i;

  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(ext), (GlIds*)0);
  for (i = 0; i < ext->priv->nEle; i++)
    {
      ids = &g_array_index(ext->priv->glIds, GlIds, i);
      if (ids->renderer == renderer)
        return ids;
    }
  return (GlIds*)0;
}

/**
 * createAllElements:
 * @data: a #VisuData object.
 * @view: a #VisuGlView object.
 *
 * This method will call the visu_rendering_createElement() method of the
 * current #RenderingMethod on all the elements of the given #VisuData.
 */
static void createAllElements(VisuGlExtNodes *ext)
{
  VisuNodeArray *array;
  GlIds *ids;
  guint i;
  guint len;
  GArray *elements;
  VisuElementRenderer *renderer;

  g_return_if_fail(VISU_IS_GL_EXT_NODES(ext));

  array = VISU_NODE_ARRAY(ext->priv->dataObj);

  elements = (GArray*)0;
  if (array)
    g_object_get(G_OBJECT(array), "elements", &elements, NULL);
  ext->priv->nEle = elements ? elements->len : 0;
  DBG_fprintf(stderr, "Extension Nodes (%p): setup population at %d.\n",
              (gpointer)ext, ext->priv->nEle);

  if (ext->priv->nEle > ext->priv->glIds->len)
    {
      DBG_fprintf(stderr, " | increase nodes gl ids (%d).\n", ext->priv->glIds->len);
      len = ext->priv->glIds->len;
      /* Change glIds size according to nEle. */
      ext->priv->glIds = g_array_set_size(ext->priv->glIds, ext->priv->nEle);
      for (; len < ext->priv->nEle; len++)
        {
          ids = &g_array_index(ext->priv->glIds, GlIds, len);
          ids->renderer = (VisuElementRenderer*)0;
          ids->material = visu_gl_objectlist_new(1);
          ids->nodes = visu_gl_objectlist_new(1);
        }
    }

  DBG_fprintf(stderr, "Extension Nodes: create OpenGl elements for"
	      " all VisuElement used in given VisuNodeArray %p.\n", (gpointer)array);
  i = 0;
  if (array)
    {
      g_return_if_fail(ext->priv->glIds->len >= elements->len);
      for (; i < elements->len; i++)
        {
          ids = &g_array_index(ext->priv->glIds, GlIds, i);
          
          renderer = (VisuElementRenderer*)0;
          if (VISU_IS_DATA_SPIN(ext->priv->dataObj) &&
              (!VISU_IS_ELEMENT_SPIN(ids->renderer) ||
               visu_element_renderer_getElement(ids->renderer) !=
               g_array_index(elements, VisuElement*, i)))
            {
              renderer = VISU_ELEMENT_RENDERER(visu_element_spin_new(g_array_index(elements, VisuElement*, i)));
              visu_element_spin_bindToPool(VISU_ELEMENT_SPIN(renderer));
            }
          else if (VISU_IS_DATA_ATOMIC(ext->priv->dataObj) &&
                   ! VISU_IS_DATA_SPIN(ext->priv->dataObj) &&
                   (!VISU_IS_ELEMENT_ATOMIC(ids->renderer) ||
                    VISU_IS_ELEMENT_SPIN(ids->renderer) ||
                    visu_element_renderer_getElement(ids->renderer) !=
                    g_array_index(elements, VisuElement*, i)))
            {
              renderer = VISU_ELEMENT_RENDERER(visu_element_atomic_new(g_array_index(elements, VisuElement*, i)));
              visu_element_atomic_bindToPool(VISU_ELEMENT_ATOMIC(renderer));
              if (ext->priv->box)
                visu_element_atomic_setUnits(VISU_ELEMENT_ATOMIC(renderer),
                                             visu_box_getUnit(ext->priv->box));
            }

          if (renderer)
            {
              _setGlIdRenderer(ids, renderer, ext);
              g_object_unref(renderer);
            }
          if (ids->renderer)
            compileElementMaterial(ext, ids->material, ids->renderer);
        }
    }
  if (elements)
    g_array_unref(elements);
  ext->priv->glIds = g_array_set_size(ext->priv->glIds, ext->priv->nEle);
  DBG_fprintf(stderr, "Extension Nodes: creation done for all elements.\n");
}

/**
 * createAllNodes: 
 * @data: a #VisuData object.
 *
 * This create the glObjectList registered that contains all the
 * nodes. This glObjectList is made of all nodes of all element whose
 * attribute rendered is TRUE and translated to their own positions.
 */
static void createAllNodes(VisuGlExtNodes *ext)
{
  guint i;
#if DEBUG == 1
  GTimer *timer;
  gulong fractionTimer;
#endif

  g_return_if_fail(VISU_IS_GL_EXT_NODES(ext));

  DBG_fprintf(stderr, "##### All node creation #####\n");
  DBG_fprintf(stderr, "Extension Nodes: 'createAllNodes' called.\n");

#if DEBUG == 1
  timer = g_timer_new();
  g_timer_start(timer);
#endif

  DBG_fprintf(stderr, "Extension Nodes: loop on elements.\n");
  for (i = 0; i < ext->priv->nEle; i++)
    createNodes(ext, &g_array_index(ext->priv->glIds, GlIds, i));
  DBG_fprintf(stderr, "Extension Nodes: OK.\n");

  visu_gl_ext_startDrawing(VISU_GL_EXT(ext));
  glEnable(GL_LIGHTING);
  glLineWidth(1);
  DBG_fprintf(stderr, "Extension Nodes: create nodes for %d elements.\n", ext->priv->nEle);
  for (i = 0; i < ext->priv->nEle; i++)
    {
      DBG_fprintf(stderr, " | call list %d.\n", g_array_index(ext->priv->glIds, GlIds, i).nodes);
      glCallList(g_array_index(ext->priv->glIds, GlIds, i).nodes);
    }
  visu_gl_ext_completeDrawing(VISU_GL_EXT(ext));

#if DEBUG == 1
  g_timer_stop(timer);
  fprintf(stderr, "Extension Nodes: lists built in %g micro-s.\n",
          g_timer_elapsed(timer, &fractionTimer)/1e-6);
  g_timer_destroy(timer);
#endif
}

static void compileElementMaterial(VisuGlExtNodes *ext,
                                   GLuint displayList, VisuElementRenderer *ele)
{
  glNewList(displayList, GL_COMPILE);
  visu_element_renderer_colorize(ele, ext->priv->effect);
  glEndList();
}
	
static void createNodes(VisuGlExtNodes *ext, GlIds *ids)
{
  VisuNodeArrayIter iter;
  VisuElement *element;

  g_return_if_fail(VISU_IS_GL_EXT_NODES(ext));

  element = visu_element_renderer_getElement(ids->renderer);
  DBG_fprintf(stderr, "Extension Nodes: create list of element '%s'.\n", element->name);
  DBG_fprintf(stderr, " | list of nodes %d.\n", ids->nodes);
  glNewList(ids->nodes, GL_COMPILE);
  if (visu_element_getRendered(element))
    {
      DBG_fprintf(stderr, " | call material list %d.\n", ids->material);
      glCallList(ids->material);
      DBG_fprintf(stderr, "Extension Nodes: creating glObjectList of nodes for '%s' - %d.\n",
		  element->name, ids->material);

      visu_node_array_iter_new(VISU_NODE_ARRAY(ext->priv->dataObj), &iter);
      iter.element = element;
      for(visu_node_array_iterRestartNode(VISU_NODE_ARRAY(ext->priv->dataObj), &iter);
          iter.node;
          visu_node_array_iterNextNode(VISU_NODE_ARRAY(ext->priv->dataObj), &iter))
        if (iter.node->rendered)
          {
            glLoadName((GLuint)iter.node->number);
            visu_element_renderer_callAt(ids->renderer,
                                         _getColorizer(VISU_NODE_ARRAY_RENDERER(ext)),
                                         ext->priv->dataObj, iter.node);
          }
    }
  else
    DBG_fprintf(stderr, "Extension Nodes: skipping glObjectList of nodes for '%s' - %d.\n",
		element->name, ids->nodes);
  glEndList();
}

static void _setBox(VisuGlExtNodes *nodes, VisuBox *box)
{
  if (nodes->priv->box == box)
    return;

  if (nodes->priv->box)
    {
      g_signal_handler_disconnect(G_OBJECT(nodes->priv->box), nodes->priv->unitId);
      g_object_unref(G_OBJECT(nodes->priv->box));
    }
  nodes->priv->box = box;
  if (box)
    {
      g_object_ref(G_OBJECT(box));
      nodes->priv->unitId = g_signal_connect_swapped(G_OBJECT(box), "UnitChanged",
                                                     G_CALLBACK(onUnits), nodes);
      onUnits(nodes, 0.f, nodes->priv->box);
    }
}
static void _setPopulation(VisuGlExtNodes *nodes)
{
  createAllElements(nodes);
  g_signal_emit_by_name(nodes, "nodes::population", (GArray*)0);
  g_signal_emit_by_name(nodes, "nodes::population-set", (GArray*)0);
}
static gboolean _setNodeArray(VisuNodeArrayRenderer *self, VisuNodeArray *array)
{
  VisuData *dataObj;
  VisuGlExtNodes *nodes;

  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(self), FALSE);

  nodes = VISU_GL_EXT_NODES(self);
  dataObj = VISU_DATA(array);

  if (nodes->priv->dataObj == dataObj)
    return FALSE;

  if (nodes->priv->dataObj)
    {
      g_signal_handler_disconnect(nodes->priv->dataObj, nodes->priv->boxId);
      g_signal_handler_disconnect(nodes->priv->dataObj, nodes->priv->popId);
      g_signal_handler_disconnect(nodes->priv->dataObj, nodes->priv->posId);
      g_signal_handler_disconnect(nodes->priv->dataObj, nodes->priv->popIncId);
      g_signal_handler_disconnect(nodes->priv->dataObj, nodes->priv->popDecId);
      g_signal_handler_disconnect(nodes->priv->dataObj, nodes->priv->visId);
      if (nodes->priv->renId)
        g_signal_handler_disconnect(visu_method_spin_getDefault(), nodes->priv->renId);
      g_object_unref(nodes->priv->dataObj);
    }
  nodes->priv->dataObj = dataObj;
  nodes->priv->renId = 0;
  if (dataObj)
    {
      g_object_ref(dataObj);
      nodes->priv->boxId =
        g_signal_connect_swapped(G_OBJECT(dataObj), "setBox",
                                 G_CALLBACK(_setBox), (gpointer)nodes);
      nodes->priv->popId =
        g_signal_connect_swapped(G_OBJECT(dataObj), "notify::elements",
                                 G_CALLBACK(_setPopulation), (gpointer)nodes);
      nodes->priv->posId =
        g_signal_connect_after(G_OBJECT(dataObj), "position-changed",
                               G_CALLBACK(onPositionChanged), (gpointer)nodes);
      nodes->priv->popIncId =
        g_signal_connect_after(G_OBJECT(dataObj), "PopulationIncrease",
                               G_CALLBACK(onPopulationInc), (gpointer)nodes);
      nodes->priv->popDecId =
        g_signal_connect_after(G_OBJECT(dataObj), "PopulationDecrease",
                               G_CALLBACK(onPopulationDec), (gpointer)nodes);
      nodes->priv->visId =
        g_signal_connect_after(dataObj, "visibility-changed",
                               G_CALLBACK(onVisibilityChanged), (gpointer)nodes);
      if (VISU_IS_DATA_SPIN(dataObj))
        nodes->priv->renId = g_signal_connect_swapped
          (G_OBJECT(visu_method_spin_getDefault()), "notify",
           G_CALLBACK(onRenderingChanged), nodes);
    }
  _setBox(nodes, (dataObj) ? visu_boxed_getBox(VISU_BOXED(dataObj)) : (VisuBox*)0);
  _setPopulation(nodes);
  if (_getColorizer(VISU_NODE_ARRAY_RENDERER(nodes)))
    visu_sourceable_follow(VISU_SOURCEABLE(_getColorizer(VISU_NODE_ARRAY_RENDERER(nodes))), dataObj);
  visu_gl_ext_setDirty(VISU_GL_EXT(nodes), TRUE);

  return TRUE;
}
/**
 * visu_gl_ext_nodes_setMaterialEffect:
 * @nodes: a #VisuGlExtNodes object.
 * @effect: a #VisuElementRendererEffects id.
 *
 * Changes the effect applied on the color used to render #VisuElement.
 *
 * Since: 3.7
 *
 * Returns: TRUE if the effect has been changed.
 **/
gboolean visu_gl_ext_nodes_setMaterialEffect(VisuGlExtNodes *nodes,
                                             VisuElementRendererEffects effect)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(nodes), FALSE);

  if (nodes->priv->effect == effect)
    return FALSE;

  nodes->priv->effect = effect;
  createAllElements(nodes);
  return TRUE;
}
static VisuNodeArray* _getNodeArray(VisuNodeArrayRenderer *self)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(self), (VisuNodeArray*)0);

  return VISU_NODE_ARRAY(VISU_GL_EXT_NODES(self)->priv->dataObj);
}
static VisuElementRenderer* _getElementRenderer(VisuNodeArrayRenderer *self,
                                                const VisuElement *element)
{
  GlIds *ids;

  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(self), (VisuElementRenderer*)0);

  ids = _getGlIds(VISU_GL_EXT_NODES(self), element);
  if (!ids)
    return (VisuElementRenderer*)0;

  return ids->renderer;
}
static gfloat _getMaxElementSize(VisuNodeArrayRenderer *node_array, guint *n)
{
  guint i;
  GlIds *ids;
  gfloat s;

  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(node_array), 0.f);
  
  s = 0.f;
  for (i = 0; i < VISU_GL_EXT_NODES(node_array)->priv->nEle; i += 1)
    {
      ids = &g_array_index(VISU_GL_EXT_NODES(node_array)->priv->glIds, GlIds, i);
      if (ids->renderer)
        s = MAX(s, visu_element_renderer_getExtent(ids->renderer));
    }
  if (n)
    *n = i;
  return s;
}
static void visu_gl_ext_nodes_draw(VisuGlExt *ext)
{
  VisuGlExtNodes *nodes = VISU_GL_EXT_NODES(ext);

  glDeleteLists(visu_gl_ext_getGlList(VISU_GL_EXT(nodes)), 1);
  visu_gl_ext_setDirty(ext, FALSE);

  /* Nothing to draw; */
  if(!nodes->priv->dataObj || !nodes->priv->view)
    return;
  
  createAllNodes(nodes);
}
static gboolean visu_gl_ext_nodes_setGlView(VisuGlExt *ext, VisuGlView *view)
{
  VisuGlExtNodesPrivate *priv = VISU_GL_EXT_NODES(ext)->priv;
  guint i;
  GlIds *ids;

  if (priv->view == view)
    return FALSE;

  if (priv->view)
    {
      g_signal_handler_disconnect(priv->view, priv->detailsId);
      g_object_unref(priv->view);
    }
  if (view)
    {
      g_object_ref(view);
      priv->detailsId = g_signal_connect_swapped(G_OBJECT(view), "DetailLevelChanged",
                                                 G_CALLBACK(_setDirty), ext);
    }
  priv->view = view;

  for (i = 0; i < priv->nEle; i += 1)
    {
      ids = &g_array_index(priv->glIds, GlIds, i);
      if (ids->renderer)
        visu_element_renderer_setGlView(ids->renderer, view);
    }

  visu_gl_ext_setDirty(ext, TRUE);
  return TRUE;
}

/********************/
/* Signal handlers. */
/********************/
static void _setDirty(VisuGlExt *ext)
{
  DBG_fprintf(stderr, "Extension Nodes: set dirty.\n");
  visu_gl_ext_setDirty(ext, TRUE);
}
static void onPopulationInc(VisuData *dataObj _U_, GArray *ids, gpointer data)
{
  createAllNodes(VISU_GL_EXT_NODES(data));
  g_signal_emit_by_name(data, "nodes::population", ids);
  g_signal_emit_by_name(data, "nodes::population-increase", ids);
  g_object_notify(G_OBJECT(data), "dirty");
}
static void onPopulationDec(VisuData *dataObj _U_, GArray *ids, gpointer data)
{
  createAllNodes(VISU_GL_EXT_NODES(data));
  g_signal_emit_by_name(data, "nodes::population", ids);
  g_signal_emit_by_name(data, "nodes::population-decrease", ids);
  g_object_notify(G_OBJECT(data), "dirty");
}
static void onColorize(VisuGlExtNodes *ext, GParamSpec *pspec _U_, VisuElement *element)
{
  GlIds *ids;

  if (!_getColorizer(VISU_NODE_ARRAY_RENDERER(ext)))
    return;
  
  ids = _getGlIds(ext, element);
  g_return_if_fail(ids);
  createNodes(ext, ids);
  g_object_notify(G_OBJECT(ext), "dirty");
}
static void onRenderer(VisuGlExtNodes *ext, GParamSpec *pspec, VisuElementRenderer *renderer)
{
  gchar *sig;
  GlIds *ids;

  DBG_fprintf(stderr, "Extension Nodes: caught 'notify::%s' for ext %p.\n",
              g_param_spec_get_name(pspec), (gpointer)ext);
  ids = _getGlIdsByRenderer(ext, renderer);
  g_return_if_fail(ids);
  if (!strcmp(g_param_spec_get_name(pspec), "rendered") ||
      !visu_element_renderer_featureMaterialCache(renderer))
    createNodes(ext, ids);
  else
    compileElementMaterial(ext, ids->material, renderer);
  sig = g_strdup_printf("%s::%s", "element-notify", g_param_spec_get_name(pspec));
  g_signal_emit_by_name(ext, sig, renderer);
  g_free(sig);
  g_object_notify(G_OBJECT(ext), "dirty");
}
static void onSize(VisuGlExtNodes *ext, gfloat extent, VisuElementRenderer *renderer)
{
  DBG_fprintf(stderr, "Extension Nodes: caught 'size-changed' for ext %p.\n",
              (gpointer)ext);
  g_signal_emit_by_name(ext, "element-size-changed", renderer, extent);
  g_object_notify(G_OBJECT(ext), "max-element-size");
}
static void onPositionChanged(VisuData *dataObj _U_, GArray *ids, gpointer data)
{
  VisuGlExtNodes *ext = VISU_GL_EXT_NODES(data);

  DBG_fprintf(stderr, "Extension Nodes: caught 'onPositionChanged'.\n");
  /* if (ele) */
  /*   createNodes(ext, _getGlIds(ext, ele)); */
  /* else */
  createAllNodes(ext);
  g_signal_emit_by_name(data, "nodes::position", ids);
  g_object_notify(G_OBJECT(data), "dirty");
}
static void onVisibilityChanged(VisuData *dataObj _U_, gpointer data)
{
  createAllNodes(VISU_GL_EXT_NODES(data));
  g_signal_emit_by_name(data, "nodes::visibility", (GArray*)0);
  g_object_notify(G_OBJECT(data), "dirty");
}
static void onRenderingChanged(VisuGlExtNodes *nodes)
{
  createAllNodes(nodes);
  g_object_notify(G_OBJECT(nodes), "dirty");
}
static void onUnits(VisuGlExtNodes *ext, gfloat fact _U_, VisuBox *box)
{
  guint i;
  GlIds *ids;
  ToolUnits units;

  if (!VISU_IS_DATA_ATOMIC(ext->priv->dataObj))
    return;

  units = visu_box_getUnit(box);
  for (i = 0; i < ext->priv->nEle; i++)
    {
      ids = &g_array_index(ext->priv->glIds, GlIds, i);
      visu_element_atomic_setUnits(VISU_ELEMENT_ATOMIC(ids->renderer), units);
    }  
}

/****************/
/* OpenGL pick. */
/****************/
static int _getSelection(VisuGlExtNodes *ext, VisuGlView *view,
                         GLfloat x0, GLfloat y0, GLfloat w, GLfloat h,
                         GLuint *select_buf, GLsizei bufsize)
{
  float centre[3];
  GLint viewport[4] = {0, 0, 0, 0};
  int hits;
 
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(ext), 0);
   
  DBG_fprintf(stderr, "Extension Nodes: get nodes in region %gx%g - %gx%g.\n",
              x0, x0, w, h);

  if ((w == 0) || (h == 0))
    return 0;

  visu_box_getCentre(visu_boxed_getBox(VISU_BOXED(view)), centre);
  
  glSelectBuffer(bufsize, select_buf);
  hits = glRenderMode(GL_SELECT);
  glInitNames();
  glPushName(-1);
   
  viewport[2] = view->window.width;
  viewport[3] = view->window.height;

  glNewList(10, GL_COMPILE);
  gluPickMatrix(x0 , y0, w, h, viewport);
  glEndList();

  glMatrixMode(GL_PROJECTION);
  glPushMatrix();
  glLoadIdentity();
  glCallList(10);
  glFrustum(view->window.left, view->window.right, view->window.bottom,
            view->window.top, view->window.near, view->window.far);
  glMatrixMode(GL_MODELVIEW); 
  glPushMatrix();
  glTranslated(-centre[0], -centre[1], -centre[2]);
  glCallList(visu_gl_ext_getGlList(VISU_GL_EXT(ext)));
  glFlush();

  hits = glRenderMode(GL_RENDER);
  DBG_fprintf(stderr, "%d elements are on the z buffer %gx%g - %gx%g.\n", hits,
              x0, y0, w, h);

  /* return the buffer to normal */
  glPopMatrix();
  glMatrixMode(GL_PROJECTION);
  glPopMatrix();
  glMatrixMode(GL_MODELVIEW);

  return hits;
}

/**
 * visu_gl_ext_nodes_getSelectionByRegion:
 * @ext: a #VisuGlExtNodes object;
 * @x1: a window coordinate;
 * @y1: a window coordinate;
 * @x2: a window coordinate;
 * @y2: a window coordinate.
 *
 * Get the #VisuNode ids in the picked region defined by (x1, y1) -
 * (x2, y2).
 * 
 * Since: 3.7
 *
 * Returns: (transfer full) (element-type guint): an empty list if no
 * node found, or a newly created list of ids if any.
 **/
GArray* visu_gl_ext_nodes_getSelectionByRegion(VisuGlExtNodes *ext,
                                               int x1, int y1, int x2, int y2)
{
  GArray *ids;
  int i, hits, ptr, names;
  guint id;
  GLuint *select_buf;
  GLsizei bufsize;
  
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(ext) && ext->priv->dataObj, (GArray*)0);

  bufsize = visu_node_array_getNNodes(VISU_NODE_ARRAY(ext->priv->dataObj)) * 4;
  select_buf = g_malloc(sizeof(GLuint) * bufsize);
  hits = _getSelection(ext, ext->priv->view, 0.5f * (x1 + x2),
                       (float)ext->priv->view->window.height - 0.5f * (y1 + y2),
                       (float)ABS(x2 - x1), (float)ABS(y2 - y1),
                       select_buf, bufsize);

  ids = g_array_new(FALSE, FALSE, sizeof(guint));
  ptr = 0;
  for(i = 0; i < hits; i++)
    {
      names = select_buf[ptr];
      if (names != 1)
        {
          g_warning("OpenGL picking is not working???\n");
          g_array_unref(ids);
          return (GArray*)0;
        }
      ptr += 3;
      id = select_buf[ptr];
      g_array_append_val(ids, id);
      ptr += 1;
    }
  g_free(select_buf);

  return ids;
}

/**
 * visu_gl_ext_nodes_getSelection:
 * @ext: a #VisuGlExtNodes object;
 * @x: a window coordinate;
 * @y: a window coordinate.
 *
 * Get the id of a #VisuNode on top of the z-buffer.
 *
 * Since: 3.7
 *
 * Returns: -1 if no node found, or its id.
 **/
int visu_gl_ext_nodes_getSelection(VisuGlExtNodes *ext, int x, int y)
{
  int i, hits, ptr, names, number;
  GLuint *select_buf;
  GLsizei bufsize;
  unsigned int z1;
  unsigned int z1_sauve = UINT_MAX;

  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(ext) && ext->priv->dataObj, -1);

  bufsize = visu_node_array_getNNodes(VISU_NODE_ARRAY(ext->priv->dataObj)) * 4;
  select_buf = g_malloc(sizeof(GLuint) * bufsize);
  hits = _getSelection(ext, ext->priv->view, x,
                       (float)ext->priv->view->window.height - y, 2.f, 2.f,
                       select_buf, bufsize);

  ptr = 0;
  number = -1;
  for(i = 0; i < hits; i++)
    {
      names = select_buf[ptr];
      if (names != 1)
        {
          g_warning("OpenGL picking is not working???\n");
          return -1;
        }
      ptr = ptr + 1;
      z1 = select_buf[ptr];
      DBG_fprintf(stderr, " | z position %f for %d\n", (float)z1/0x7fffffff,
                  (int)select_buf[ptr + 2]);
      ptr = ptr + 2;
      if (z1 < z1_sauve) {
        z1_sauve = z1;
        number = (int)select_buf[ptr];
      }
      ptr = ptr + 1;
    }
  return number;
}

static void _setColorizer(VisuNodeArrayRenderer *nodes,
                          VisuDataColorizer *colorizer)
{
  VisuGlExtNodes *self;
  g_return_if_fail(VISU_IS_GL_EXT_NODES(nodes));

  self = VISU_GL_EXT_NODES(nodes);

  if (_getColorizer(nodes) == colorizer)
    return;

  if (_getColorizer(nodes))
    {
      g_signal_handler_disconnect(_getColorizer(nodes), self->priv->dirtyId);
      g_object_unref(_getColorizer(nodes));
    }
  if (colorizer)
    {
      g_object_ref(colorizer);
      self->priv->dirtyId =
        g_signal_connect_swapped(colorizer, "dirty",
                                 G_CALLBACK(onRenderingChanged), self);
      visu_sourceable_follow(VISU_SOURCEABLE(colorizer), self->priv->dataObj);
    }

  return;
}

static gboolean _pushColorizer(VisuNodeArrayRenderer *nodes,
                               VisuDataColorizer *colorizer)
{
  GList *elem;
  VisuGlExtNodes *self;
  
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(nodes), FALSE);
  self = VISU_GL_EXT_NODES(nodes);

  if (_getColorizer(nodes) == colorizer)
    return FALSE;

  if ((elem = g_list_find(self->priv->colorizers, colorizer)))
    {
      g_object_unref(elem->data);
      self->priv->colorizers = g_list_delete_link(self->priv->colorizers, elem);
    }
  _setColorizer(nodes, colorizer);
  self->priv->colorizers = g_list_prepend(self->priv->colorizers, colorizer);
  g_object_ref(colorizer);
  onRenderingChanged(self);

  return TRUE;
}

static gboolean _removeColorizer(VisuNodeArrayRenderer *nodes,
                                 VisuDataColorizer *colorizer)
{
  GList *elem;
  VisuGlExtNodes *self;
  
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(nodes), FALSE);
  self = VISU_GL_EXT_NODES(nodes);

  if (_getColorizer(nodes) != colorizer)
    {
      if ((elem = g_list_find(self->priv->colorizers, colorizer)))
        {
          g_object_unref(elem->data);
          self->priv->colorizers = g_list_delete_link(self->priv->colorizers, elem);
        }
      return FALSE;
    }

  _setColorizer(nodes, self->priv->colorizers->next ? self->priv->colorizers->next->data : (VisuDataColorizer*)0);
  self->priv->colorizers = g_list_delete_link(self->priv->colorizers,
                                              self->priv->colorizers);
  g_object_unref(colorizer);
  onRenderingChanged(self);

  return TRUE;
}

static VisuDataColorizer* _getColorizer(VisuNodeArrayRenderer *nodes)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_NODES(nodes), (VisuDataColorizer*)0);

  return VISU_GL_EXT_NODES(nodes)->priv->colorizers ? VISU_GL_EXT_NODES(nodes)->priv->colorizers->data : (VisuDataColorizer*)0;
}
