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

	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 COPYING.
*/
#include "pairs.h"

#include <math.h>
#include <GL/gl.h>

#include <visu_configFile.h>
#include <visu_pairset.h>

#include <pairsModeling/wire_renderer.h>
#include <pairsModeling/cylinder_renderer.h>
#include <openGLFunctions/text.h>
#include <openGLFunctions/objectList.h>

/**
 * SECTION:pairs
 * @short_description: Draw links between nodes.
 *
 * <para>This extension draws links between nodes, depending on
 * #VisuPairLinkRenderer drawing capabilities.</para>
 *
 * Since: 3.7
 */

/**
 * VisuGlExtPairsClass:
 * @parent: the parent class;
 *
 * A short way to identify #_VisuGlExtPairsClass structure.
 *
 * Since: 3.7
 */
/**
 * VisuGlExtPairs:
 *
 * An opaque structure.
 *
 * Since: 3.7
 */
/**
 * VisuGlExtPairsPrivate:
 *
 * Private fields for #VisuGlExtPairs objects.
 *
 * Since: 3.7
 */
struct _VisuGlExtPairsPrivate
{
  gboolean dispose_has_run;

  /* Pairs definition. */
  VisuNodeArrayRenderer *nodeRenderer;
  VisuData *data;
  gulong popInc_signal, popDec_signal;
  gulong visChg_signal, posChg_signal;
  VisuPairSet *pairs;
  gulong link_signal;

  GArray *links; /* Contains all drawn links, through struct
                    _LinkData. */
  GHashTable *linkRenderers; /* Contains an association link ->
                                renderer for all links, not just
                                drawn. */

  /* Renderer listeners. */
  GList *renderers;
  VisuPairLinkRenderer *defaultRenderer;
  GHashTable *rendererData;
};

struct _RendererData
{
  VisuPairLinkRenderer *renderer;
  guint usage;
  gulong dirty_sig;
};

struct _LinkData
{
  VisuPairLink *link;
  gulong param_sig;

  VisuNodeArrayRenderer *nodes;
  VisuElementRenderer *ele1, *ele2;

  VisuPair *pair;
  struct _RendererData *rdt;

  GLuint id;
  gboolean dirty;
};

static VisuGlExtPairs *defaultPairs = NULL;
static GArray *glIds;

#define BOND_PROP_ID _("Bonds")

#define FLAG_RESOURCES_PAIRS    "pairs_are_on"
#define DESC_RESOURCES_PAIRS    "Ask the opengl engine to draw pairs between elements ; boolean 0 or 1"
static gboolean pairsAreOn = FALSE;

#define FLAG_RESOURCES_FAV_PAIRS  "pairs_favoriteMethod"
#define DESC_RESOURCES_FAV_PAIRS  "Favorite method used to render files ; string"
static gchar *favRenderer = NULL;

#define FLAG_RESOURCES_PAIRS_DATA "pair_data"
#define DESC_RESOURCES_PAIRS_DATA "Draw pairs between [ele1] [ele2] [0. <= dmin] [0. <= dmax] [0. <= RGB <= 1.]x3"
#define FLAG_RESOURCES_PAIR_LINK "pair_link"
#define DESC1_RESOURCES_PAIR_LINK "Draw a link between [ele1] [ele2] [0. <= dmin] [0. <= dmax]"
#define DESC2_RESOURCES_PAIR_LINK "                    [0. <= RGB <= 1.]x3 [bool: drawn] [bool: printLength] [string: method]"
static GHashTable *_linkRenderers;
static void exportResourcesPairs(GString *data, VisuData *dataObj);

static void visu_gl_ext_pairs_finalize(GObject* obj);
static void visu_gl_ext_pairs_dispose(GObject* obj);
static void visu_gl_ext_pairs_rebuild(VisuGlExt *ext);
static void visu_gl_ext_pairs_draw(VisuGlExt *pairs);
static gboolean visu_gl_ext_pairs_setGlView(VisuGlExt *pairs, VisuGlView *view);

enum {
  RENDERER_SIGNAL,
  LAST_SIGNAL
};
static guint _signals[LAST_SIGNAL] = { 0 };

/* Local callbacks. */
static void onLinkNotified(VisuGlExtPairs *ext, GParamSpec *pspec, VisuPairLink *link);
static void onDirtyRenderer(VisuGlExtPairs *ext, VisuPairLinkRenderer *renderer);
static void onPairsNotified(VisuGlExtPairs *ext, GParamSpec *pspec, VisuPairSet *pairs);
static void onLinksChanged(VisuGlExtPairs *ext, VisuPair *pair, VisuPairSet *pairs);
static void _setDirty(VisuGlExtPairs *ext);
static void onActiveChanged(VisuGlExtPairs *pairs, GParamSpec *pspec, gpointer data);
static void onEntryUsed(VisuGlExtPairs *pairs, VisuConfigFileEntry *entry, VisuConfigFile *obj);
static void onEntryFav(VisuGlExtPairs *pairs, VisuConfigFileEntry *entry, VisuConfigFile *obj);
static void onEntryLink(VisuConfigFile *obj, VisuConfigFileEntry *entry, gpointer data);

/* Local routines. */
static gboolean _setLinkRenderer(VisuGlExtPairs *pairs, struct _LinkData *dt,
                                 VisuPairLinkRenderer *renderer);
static VisuPairLinkRenderer* _rendererByName(VisuGlExtPairs *pairs, const gchar *name);

G_DEFINE_TYPE_WITH_CODE(VisuGlExtPairs, visu_gl_ext_pairs, VISU_TYPE_GL_EXT,
                        G_ADD_PRIVATE(VisuGlExtPairs))

static void visu_gl_ext_pairs_class_init(VisuGlExtPairsClass *klass)
{
  VisuConfigFileEntry *resourceEntry, *oldEntry;

  DBG_fprintf(stderr, "Extension Pairs: creating the class of the object.\n");
  DBG_fprintf(stderr, "                - adding new signals ;\n");
  /**
   * VisuGlExtPairs::renderer-changed:
   * @ext: the object which emits the signal ;
   * @link: the #VisuPairLink which is changed.
   *
   * Gets emitted when the renderer used for @link is changed.
   *
   * Since: 3.8
   */
  _signals[RENDERER_SIGNAL] =
    g_signal_new("renderer-changed", 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_PAIR_LINK);

  /* Add resources. */
  visu_config_file_addBooleanEntry(VISU_CONFIG_FILE_RESOURCE,
                                   FLAG_RESOURCES_PAIRS,
                                   DESC_RESOURCES_PAIRS,
                                   &pairsAreOn, FALSE);
  visu_config_file_addStringEntry(VISU_CONFIG_FILE_RESOURCE,
                                  FLAG_RESOURCES_FAV_PAIRS,
                                  DESC_RESOURCES_FAV_PAIRS,
                                  &favRenderer);
  oldEntry = visu_config_file_addEntry(VISU_CONFIG_FILE_RESOURCE,
				     FLAG_RESOURCES_PAIRS_DATA,
				     DESC_RESOURCES_PAIRS_DATA,
				     1, NULL);
  resourceEntry = visu_config_file_addTokenizedEntry(VISU_CONFIG_FILE_RESOURCE,
                                                     FLAG_RESOURCES_PAIR_LINK,
                                                     DESC1_RESOURCES_PAIR_LINK,
                                                     TRUE);
  visu_config_file_entry_setVersion(resourceEntry, 3.4f);
  visu_config_file_entry_setReplace(resourceEntry, oldEntry);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_RESOURCE,
                                     exportResourcesPairs);
  g_signal_connect(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_RESOURCES_PAIR_LINK,
                   G_CALLBACK(onEntryLink), (gpointer)0);

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_gl_ext_pairs_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_gl_ext_pairs_finalize;
  VISU_GL_EXT_CLASS(klass)->rebuild = visu_gl_ext_pairs_rebuild;
  VISU_GL_EXT_CLASS(klass)->draw = visu_gl_ext_pairs_draw;
  VISU_GL_EXT_CLASS(klass)->setGlView = visu_gl_ext_pairs_setGlView;

  g_type_class_ref(VISU_TYPE_PAIR_LINK);

  glIds = g_array_new(FALSE, FALSE, sizeof(GLuint));
  _linkRenderers = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, g_free);
}

static void _addLink(VisuPair *pair, VisuPairLink *link, gpointer data)
{
  VisuGlExtPairs *ext = VISU_GL_EXT_PAIRS(data);
  struct _LinkData dt;
  guint i;
  GLuint id;
  VisuElement *ele;
  VisuPairLinkRenderer *renderer;
  gchar *name;

  DBG_fprintf(stderr, " | adding link %p\n", (gpointer)link);
  if (ext->priv->links->len >= glIds->len)
    for (i = glIds->len; i <= ext->priv->links->len; i++)
      {
        id = visu_gl_objectlist_new(1);
        g_array_append_val(glIds, id);
      }

  renderer = (VisuPairLinkRenderer*)g_hash_table_lookup(ext->priv->linkRenderers, link);
  if (!renderer)
    {
      name = (gchar*)g_hash_table_lookup(_linkRenderers, link);
      if (name)
        renderer = _rendererByName(ext, name);
    }

  dt.id = g_array_index(glIds, GLuint, ext->priv->links->len);
  dt.link = g_object_ref(link);
  dt.param_sig = g_signal_connect_swapped(G_OBJECT(link), "notify",
                                           G_CALLBACK(onLinkNotified), ext);
  dt.nodes = ext->priv->nodeRenderer;
  ele = visu_pair_link_getFirstElement(dt.link);
  dt.ele1 = visu_node_array_renderer_get(ext->priv->nodeRenderer, ele);
  g_object_unref(ele);
  ele = visu_pair_link_getSecondElement(dt.link);
  dt.ele2 = visu_node_array_renderer_get(ext->priv->nodeRenderer, ele);
  g_object_unref(ele);
  dt.pair = pair;
  dt.dirty = TRUE;
  dt.rdt = g_hash_table_lookup(ext->priv->rendererData, (renderer) ? renderer : ext->priv->defaultRenderer);
  if (!dt.rdt->usage)
    g_signal_handler_unblock(G_OBJECT(dt.rdt->renderer), dt.rdt->dirty_sig);
  dt.rdt->usage += 1;

  g_array_append_val(ext->priv->links, dt);
}
static void _freeLink(struct _LinkData *dt)
{
  g_signal_handler_disconnect(G_OBJECT(dt->link), dt->param_sig);
  g_clear_object(&dt->link);
  dt->rdt->usage -= 1;
  if (!dt->rdt->usage)
    g_signal_handler_block(G_OBJECT(dt->rdt->renderer), dt->rdt->dirty_sig);
}
static void _freeRendererData(struct _RendererData *dt)
{
  DBG_fprintf(stderr, "Extension Pairs: deleting sig %lu on %p (%d).\n",
              dt->dirty_sig, (gpointer)dt->renderer, G_OBJECT(dt->renderer)->ref_count);
  g_signal_handler_disconnect(G_OBJECT(dt->renderer), dt->dirty_sig);
  g_object_unref(dt->renderer);
}

static void visu_gl_ext_pairs_init(VisuGlExtPairs *obj)
{
  GList *renderer;
  struct _RendererData *dt;
  gchar *id;

  DBG_fprintf(stderr, "Extension Pairs: initializing a new object (%p).\n",
	      (gpointer)obj);
  
  obj->priv = visu_gl_ext_pairs_get_instance_private(obj);
  obj->priv->dispose_has_run = FALSE;

  /* Private data. */
  obj->priv->data          = (VisuData*)0;
  obj->priv->popInc_signal = 0;
  obj->priv->popDec_signal = 0;
  obj->priv->visChg_signal = 0;
  obj->priv->posChg_signal = 0;
  obj->priv->pairs         = visu_pair_set_new();
  g_signal_connect_object(obj->priv->pairs, "notify::pairs",
                          G_CALLBACK(onPairsNotified), obj, G_CONNECT_SWAPPED);
  g_signal_connect_object(obj->priv->pairs, "links-changed",
                          G_CALLBACK(onLinksChanged), obj, G_CONNECT_SWAPPED);

  obj->priv->links         = g_array_new(FALSE, FALSE, sizeof(struct _LinkData));
  g_array_set_clear_func(obj->priv->links, (GDestroyNotify)_freeLink);
  obj->priv->linkRenderers = g_hash_table_new(g_direct_hash, g_direct_equal);

  obj->priv->rendererData  = g_hash_table_new_full(g_direct_hash, g_direct_equal,
                                                   NULL, (GDestroyNotify)_freeRendererData);
  obj->priv->renderers     = (GList*)0;
  obj->priv->renderers = g_list_append(obj->priv->renderers, visu_pair_wire_renderer_new());
  obj->priv->renderers = g_list_append(obj->priv->renderers, visu_pair_cylinder_renderer_new());
  obj->priv->defaultRenderer = VISU_PAIR_LINK_RENDERER(obj->priv->renderers->data);
  for (renderer = obj->priv->renderers; renderer; renderer = g_list_next(renderer))
    {
      dt = g_malloc(sizeof(struct _RendererData));
      g_object_ref(renderer->data);
      dt->renderer = VISU_PAIR_LINK_RENDERER(renderer->data);
      dt->usage = 0;
      dt->dirty_sig =
        g_signal_connect_object(G_OBJECT(renderer->data), "dirty",
                                G_CALLBACK(onDirtyRenderer), obj, G_CONNECT_SWAPPED);
      g_signal_handler_block(G_OBJECT(renderer->data), dt->dirty_sig);
      DBG_fprintf(stderr, "Extension Pairs: creating sig %lu on %p.\n",
                  dt->dirty_sig, renderer->data);
      g_hash_table_insert(obj->priv->rendererData, renderer->data, dt);
      if (favRenderer)
        {
          g_object_get(dt->renderer, "id", &id, NULL);
          if (!g_ascii_strcasecmp(id, favRenderer))
            obj->priv->defaultRenderer = dt->renderer;
          g_free(id);
        }
    }

  g_signal_connect(G_OBJECT(obj), "notify::active",
                   G_CALLBACK(onActiveChanged), (gpointer)0);

  g_signal_connect_object(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_RESOURCES_PAIRS,
                          G_CALLBACK(onEntryUsed), (gpointer)obj, G_CONNECT_SWAPPED);
  g_signal_connect_object(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_RESOURCES_FAV_PAIRS,
                          G_CALLBACK(onEntryFav), (gpointer)obj, G_CONNECT_SWAPPED);

  if (!defaultPairs)
    defaultPairs = obj;
}

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

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

  pairs = VISU_GL_EXT_PAIRS(obj);
  if (pairs->priv->dispose_has_run)
    return;
  pairs->priv->dispose_has_run = TRUE;

  /* Disconnect signals. */
  DBG_fprintf(stderr, "Extension Pairs: connected Data has %d ref count.\n",
              (pairs->priv->data)?G_OBJECT(pairs->priv->data)->ref_count:0);
  visu_gl_ext_pairs_setData(pairs, (VisuData*)0);
  visu_gl_ext_pairs_setDataRenderer(pairs, (VisuNodeArrayRenderer*)0);

  g_object_unref(pairs->priv->pairs);
  /* Need to do it here, because the structures contains signals that
     must be disconnected first. */
  g_hash_table_remove_all(pairs->priv->rendererData);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_gl_ext_pairs_parent_class)->dispose(obj);
}
/* This method is called once only. */
static void visu_gl_ext_pairs_finalize(GObject* obj)
{
  VisuGlExtPairs *pairs;

  g_return_if_fail(obj);

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

  pairs = VISU_GL_EXT_PAIRS(obj);

  /* Free privs elements. */
  DBG_fprintf(stderr, "Extension Pairs: free private pairs.\n");
  g_array_free(pairs->priv->links, TRUE);
  g_hash_table_destroy(pairs->priv->linkRenderers);
  g_hash_table_destroy(pairs->priv->rendererData);
  g_list_free_full(pairs->priv->renderers, (GDestroyNotify)g_object_unref);

  /* Chain up to the parent class */
  DBG_fprintf(stderr, "Extension Pairs: chain to parent.\n");
  G_OBJECT_CLASS(visu_gl_ext_pairs_parent_class)->finalize(obj);
  DBG_fprintf(stderr, "Extension Pairs: freeing ... OK.\n");
}

/**
 * visu_gl_ext_pairs_new:
 * @name: (allow-none): the name to give to the extension (default is #VISU_GL_EXT_PAIRS_ID).
 *
 * Creates a new #VisuGlExt to draw a pairs.
 *
 * Since: 3.7
 *
 * Returns: a pointer to the #VisuGlExt it created or
 * NULL otherwise.
 */
VisuGlExtPairs* visu_gl_ext_pairs_new(const gchar *name)
{
  char *name_ = VISU_GL_EXT_PAIRS_ID;
  char *description = _("Draw pairs between elements with a criterion of distance.");

  DBG_fprintf(stderr,"Extension Pairs: new object.\n");
  
  return g_object_new(VISU_TYPE_GL_EXT_PAIRS,
                      "name", (name) ? name : name_,
                      "label", (name) ? name : _(name_),
                      "description", description,
                      "nGlObj", 1, "active", pairsAreOn, NULL);
}
static void _addBondValues(VisuData *data, const gchar *label)
{
  VisuNodeValues *bonds;
  
  bonds = visu_node_values_new(VISU_NODE_ARRAY(data), label, G_TYPE_INT, 1);
  visu_node_values_setEditable(bonds, FALSE);
  visu_data_addNodeProperties(data, bonds);
}
/**
 * visu_gl_ext_pairs_setDataRenderer:
 * @pairs: a #VisuGlExtPairs object.
 * @renderer: a #VisuNodeArrayRenderer object.
 *
 * Specify the @renderer that may be used to draw pairs in the same
 * colour and material than elements.
 *
 * Since: 3.8
 *
 * Returns: TRUE if value is actually changed.
 **/
gboolean visu_gl_ext_pairs_setDataRenderer(VisuGlExtPairs *pairs,
                                           VisuNodeArrayRenderer *renderer)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_PAIRS(pairs), FALSE);

  if (pairs->priv->nodeRenderer == renderer)
    return FALSE;

  if (pairs->priv->nodeRenderer)
    g_object_unref(pairs->priv->nodeRenderer);
  pairs->priv->nodeRenderer = renderer;
  if (renderer)
    g_object_ref(renderer);

  return TRUE;
}
/**
 * visu_gl_ext_pairs_getDataRenderer:
 * @pairs: a #VisuGlExtPairs object.
 *
 * Retrieve the #VisuNodeArrayRenderer this @pairs is using the
 * rendering properties from.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the #VisuNodeArrayRenderer this @pairs is
 * using the rendering properties from.
 **/
VisuNodeArrayRenderer* visu_gl_ext_pairs_getDataRenderer(VisuGlExtPairs *pairs)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_PAIRS(pairs), (VisuNodeArrayRenderer*)0);
  
  return pairs->priv->nodeRenderer;
}
/**
 * visu_gl_ext_pairs_setData:
 * @pairs: The #VisuGlExtPairs to attached to.
 * @data: the nodes to get the population of.
 *
 * Attach a #VisuData to render to and setup the pairs to get the
 * node population also.
 *
 * Since: 3.7
 *
 * Returns: TRUE if @data is actually changed.
 **/
gboolean visu_gl_ext_pairs_setData(VisuGlExtPairs *pairs, VisuData *data)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_PAIRS(pairs), FALSE);

  if (pairs->priv->data)
    {
      g_signal_handler_disconnect(G_OBJECT(pairs->priv->data), pairs->priv->popInc_signal);
      g_signal_handler_disconnect(G_OBJECT(pairs->priv->data), pairs->priv->popDec_signal);
      g_signal_handler_disconnect(G_OBJECT(pairs->priv->data), pairs->priv->visChg_signal);
      g_signal_handler_disconnect(G_OBJECT(pairs->priv->data), pairs->priv->posChg_signal);
      visu_data_removeNodeProperties(pairs->priv->data, BOND_PROP_ID);
      g_object_unref(pairs->priv->data);
    }
  if (data)
    {
      g_object_ref(data);
      pairs->priv->popInc_signal =
        g_signal_connect_swapped(G_OBJECT(data), "PopulationIncrease",
                                 G_CALLBACK(_setDirty), (gpointer)pairs);
      pairs->priv->popDec_signal =
        g_signal_connect_swapped(G_OBJECT(data), "PopulationDecrease",
                                 G_CALLBACK(_setDirty), (gpointer)pairs);
      pairs->priv->visChg_signal =
        g_signal_connect_swapped(data, "visibility-changed",
                                 G_CALLBACK(_setDirty), (gpointer)pairs);
      pairs->priv->posChg_signal =
        g_signal_connect_swapped(G_OBJECT(data), "position-changed",
                                 G_CALLBACK(_setDirty), (gpointer)pairs);
      if (visu_gl_ext_getActive(VISU_GL_EXT(pairs)))
        _addBondValues(data, BOND_PROP_ID);
    }
  pairs->priv->data = data;
  visu_pair_set_setModel(pairs->priv->pairs, data);

  visu_gl_ext_setDirty(VISU_GL_EXT(pairs), TRUE);
  return TRUE;
}
static gboolean _setLinkRenderer(VisuGlExtPairs *pairs, struct _LinkData *dt,
                                 VisuPairLinkRenderer *renderer)
{
  if (dt->rdt->renderer == ((renderer) ? renderer : pairs->priv->defaultRenderer))
    return FALSE;
  
  dt->rdt->usage -= 1;
  if (!dt->rdt->usage)
    g_signal_handler_block(G_OBJECT(dt->rdt->renderer), dt->rdt->dirty_sig);

  dt->rdt = g_hash_table_lookup(pairs->priv->rendererData, (renderer) ? renderer : pairs->priv->defaultRenderer);

  if (!dt->rdt->usage)
    g_signal_handler_unblock(G_OBJECT(dt->rdt->renderer), dt->rdt->dirty_sig);
  dt->rdt->usage += 1;

  if (renderer)
    g_hash_table_insert(pairs->priv->linkRenderers, dt->link, renderer);
  else
    g_hash_table_remove(pairs->priv->linkRenderers, dt->link);

  g_signal_emit(G_OBJECT(pairs), _signals[RENDERER_SIGNAL], 0, dt->link);

  dt->dirty = TRUE;
  return TRUE;
}
/**
 * visu_gl_ext_pairs_setLinkRenderer:
 * @pairs: the rendering #VisuGlExtPairs object.
 * @data: a #VisuPairLink object.
 * @renderer: a #VisuPairLinkRenderer object.
 * 
 * Set the drawing method of a pair.
 *
 * Since: 3.6
 *
 * Returns: TRUE if drawing method is changed.
 */
gboolean visu_gl_ext_pairs_setLinkRenderer(VisuGlExtPairs *pairs, VisuPairLink *data,
                                           VisuPairLinkRenderer *renderer)
{
  guint i;
  gboolean res;

  g_return_val_if_fail(VISU_IS_GL_EXT_PAIRS(pairs) && data, FALSE);

  DBG_fprintf(stderr, "Extension Pairs: set method %p for %p.\n",
	      (gpointer)renderer, (gpointer)data);
  for (i = 0; i < pairs->priv->links->len; i++)
    if (g_array_index(pairs->priv->links, struct _LinkData, i).link == data)
      {
        res = _setLinkRenderer(pairs, &g_array_index(pairs->priv->links, struct _LinkData, i),
                               renderer);
        if (res)
          visu_gl_ext_setDirty(VISU_GL_EXT(pairs), TRUE);
        return res;
      }
  return FALSE;
}
/**
 * visu_gl_ext_pairs_getLinkRenderer:
 * @pairs: the rendering #VisuGlExtPairs object.
 * @data: a #VisuPairLink object.
 * 
 * Get the drawing method of a pair.
 *
 * Since: 3.6
 *
 * Returns: (transfer none): a drawing method.
 */
VisuPairLinkRenderer* visu_gl_ext_pairs_getLinkRenderer(VisuGlExtPairs *pairs, VisuPairLink *data)
{
  guint i;

  g_return_val_if_fail(VISU_IS_GL_EXT_PAIRS(pairs) && data, (VisuPairLinkRenderer*)0);

  for (i = 0; i < pairs->priv->links->len; i++)
    if (g_array_index(pairs->priv->links, struct _LinkData, i).link == data)
      return g_array_index(pairs->priv->links, struct _LinkData, i).rdt->renderer;
  return (VisuPairLinkRenderer*)0;
}
/**
 * visu_gl_ext_pairs_getSet:
 * @pairs: a #VisuGlExtPairs object.
 *
 * Retrieve the #VisuPairSet this @pairs is based on.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the #VisuPairSet this @pairs is based on.
 **/
VisuPairSet* visu_gl_ext_pairs_getSet(VisuGlExtPairs *pairs)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_PAIRS(pairs), (VisuPairSet*)0);

  return pairs->priv->pairs;
}

/************/
/* Signals. */
/************/
static void onLinkNotified(VisuGlExtPairs *ext, GParamSpec *pspec _U_, VisuPairLink *link)
{
  guint i;

  for (i = 0; i < ext->priv->links->len; i++)
    if (g_array_index(ext->priv->links, struct _LinkData, i).link == link)
      {
        g_array_index(ext->priv->links, struct _LinkData, i).dirty = TRUE;
        visu_gl_ext_setDirty(VISU_GL_EXT(ext), TRUE);
        return;
      }
}
static void onDirtyRenderer(VisuGlExtPairs *ext, VisuPairLinkRenderer *renderer)
{
  guint i;

  for (i = 0; i < ext->priv->links->len; i++)
    if (g_array_index(ext->priv->links, struct _LinkData, i).rdt->renderer == renderer)
      g_array_index(ext->priv->links, struct _LinkData, i).dirty = TRUE;
  visu_gl_ext_setDirty(VISU_GL_EXT(ext), TRUE);
}
static void onPairsNotified(VisuGlExtPairs *ext, GParamSpec *pspec _U_, VisuPairSet *pairs)
{
  VisuPairSetIter iter;

  DBG_fprintf(stderr, "Extension Pairs: recreate link list from pair notify.\n");
  g_array_set_size(ext->priv->links, 0);
  for (visu_pair_set_iter_new(pairs, &iter, TRUE); iter.link; visu_pair_set_iter_next(&iter))
    _addLink(iter.pair, iter.link, ext);
  visu_gl_ext_setDirty(VISU_GL_EXT(ext), TRUE);
}
static void onLinksChanged(VisuGlExtPairs *ext, VisuPair *pair, VisuPairSet *pairs _U_)
{
  guint i;

  DBG_fprintf(stderr, "Extension Pairs: recreate link for pair %p.\n", (gpointer)pair);
  /* Remove all old VisuPairLink related to pair. */
  i = 0;
  while (i < ext->priv->links->len)
    {
      if (g_array_index(ext->priv->links, struct _LinkData, i).pair == pair)
        g_array_remove_index_fast(ext->priv->links, i);
      else
        i += 1;
    };
  /* Add all VisuPairLink of pair. */
  visu_pair_foreach(pair, _addLink, ext);
  visu_gl_ext_setDirty(VISU_GL_EXT(ext), TRUE);
}
static void _setDirty(VisuGlExtPairs *ext)
{
  guint i;
  
  for (i = 0; i < ext->priv->links->len; i++)
    g_array_index(ext->priv->links, struct _LinkData, i).dirty = TRUE;
  visu_gl_ext_setDirty(VISU_GL_EXT(ext), TRUE);
}
static void onActiveChanged(VisuGlExtPairs *pairs, GParamSpec *pspec _U_, gpointer data _U_)
{
  if (!pairs->priv->data)
    return;

  if (visu_gl_ext_getActive(VISU_GL_EXT(pairs)))
    _addBondValues(pairs->priv->data, BOND_PROP_ID);
  else
    visu_data_removeNodeProperties(pairs->priv->data, BOND_PROP_ID);
}
static void onEntryUsed(VisuGlExtPairs *pairs, VisuConfigFileEntry *entry _U_, VisuConfigFile *obj _U_)
{
  visu_gl_ext_setActive(VISU_GL_EXT(pairs), pairsAreOn);
}
static void onEntryFav(VisuGlExtPairs *pairs, VisuConfigFileEntry *entry, VisuConfigFile *obj _U_)
{
  VisuPairLinkRenderer *fav;

  DBG_fprintf(stderr, "Extension Pairs: got '%s' as favorite method.\n", favRenderer);
  fav = _rendererByName(pairs, favRenderer);
  if (!fav)
    visu_config_file_entry_setErrorMessage(entry,
                                           _("the method '%s' is unknown"), favRenderer);
  else
    pairs->priv->defaultRenderer = fav;
}
static void onEntryLink(VisuConfigFile *obj _U_, VisuConfigFileEntry *entry, gpointer data _U_)
{
  VisuPairLink *link;
  gchar *errMess;
  gboolean flags[2];
  gchar *favLabel;
  const ToolColor *color;
  
  if (!visu_pair_pool_readLinkFromLabel(visu_config_file_entry_getLabel(entry), &link, &errMess))
    {
      visu_config_file_entry_setErrorMessage(entry, errMess);
      g_free(errMess);
      return;
    }

  if (!visu_config_file_entry_popTokenAsColor(entry, &color))
    return;
  if (!visu_config_file_entry_popTokenAsBoolean(entry, 2, flags))
    return;
  favLabel = visu_config_file_entry_popAllTokens(entry);
  g_strstrip(favLabel);
  
  /* Set the values. */
  visu_pair_link_setColor(link, color);
  visu_pair_link_setDrawn(link, flags[0]);
  visu_pair_link_setPrintLength(link, flags[1]);

  if (favLabel[0])
    g_hash_table_insert(_linkRenderers, link, favLabel);
  else
    g_free(favLabel);
}

static gboolean visu_gl_ext_pairs_setGlView(VisuGlExt *pairs, VisuGlView *view)
{
  GList *renderer;

  for (renderer = VISU_GL_EXT_PAIRS(pairs)->priv->renderers; renderer;
       renderer = g_list_next(renderer))
    visu_pair_link_renderer_setGlView(VISU_PAIR_LINK_RENDERER(renderer->data), view);

  return FALSE;
}

static void visu_gl_ext_pairs_rebuild(VisuGlExt *ext)
{
  guint i;
  VisuGlExtPairs *self = VISU_GL_EXT_PAIRS(ext);
  
  for (i = 0; i < self->priv->links->len; i++)
    g_array_index(self->priv->links, struct _LinkData, i).dirty = TRUE;

  visu_gl_text_rebuildFontList();
  visu_gl_ext_setDirty(ext, TRUE);
  visu_gl_ext_pairs_draw(ext);
}

static void _drawLink(struct _LinkData *dt, VisuData *data, VisuNodeValues *bonds)
{
  VisuPairLinkIter iter;
  gboolean valid;
  char distStr[8];

  glDeleteLists(dt->id, 1);

  DBG_fprintf(stderr, "Extension Pairs: drawing link %p (%d).\n", (gpointer)dt->link,
              visu_pair_link_getDrawn(dt->link));
  if (!visu_pair_link_getDrawn(dt->link))
    return;

  glNewList(dt->id, GL_COMPILE);

  visu_pair_link_renderer_start(dt->rdt->renderer, dt->link, dt->ele1, dt->ele2,
                                visu_node_array_renderer_getColorizer(dt->nodes));
  for (valid = visu_pair_link_iter_new(dt->link, data, &iter, TRUE); valid;
       valid = visu_pair_link_iter_next(&iter))
    {
      if (iter.coeff == 1.f && bonds)
        {
          (*(int*)visu_node_values_getPtrAt(bonds, iter.iter1.node)) += 1;
          (*(int*)visu_node_values_getPtrAt(bonds, iter.iter2.node)) += 1;
        }

      visu_pair_link_renderer_draw(dt->rdt->renderer, &iter);
      if (visu_pair_link_getPrintLength(dt->link))
        {
          sprintf(distStr, "%7.3f", sqrt(iter.d2));
          glRasterPos3f(iter.xyz1[0] + iter.dxyz[0] / 2.,
                        iter.xyz1[1] + iter.dxyz[1] / 2.,
                        iter.xyz1[2] + iter.dxyz[2] / 2.);
          visu_gl_text_drawChars(distStr, VISU_GL_TEXT_NORMAL);
          if (iter.periodic)
            {
              glRasterPos3f(iter.xyz2[0] - iter.dxyz[0] / 2.,
                            iter.xyz2[1] - iter.dxyz[1] / 2.,
                            iter.xyz2[2] - iter.dxyz[2] / 2.);
              visu_gl_text_drawChars(distStr, VISU_GL_TEXT_NORMAL);
            }
        }
    }
  visu_pair_link_renderer_stop(dt->rdt->renderer, dt->link);

  glEndList();

  dt->dirty = FALSE;
}

static void visu_gl_ext_pairs_draw(VisuGlExt *pairs)
{
  VisuGlExtPairs *self = VISU_GL_EXT_PAIRS(pairs);
  VisuGlExtPairsPrivate *priv = self->priv;
  VisuNodeValues *bonds;
  struct _LinkData *dt;
  guint i;

  /* Cleans. */
  glDeleteLists(visu_gl_ext_getGlList(pairs), 1);
  visu_gl_ext_setDirty(pairs, FALSE);

  /* Nothing to draw if no data is associated to
     the current rendering window. */
  if (!priv->data)
    return;

  visu_gl_text_initFontList();

  /* We get the counting array for bonds. */
  bonds = visu_data_getNodeProperties(priv->data, BOND_PROP_ID);
  if (bonds)
    visu_node_values_reset(bonds);
  for (i = 0; i < priv->links->len; i++)
    if (g_array_index(priv->links, struct _LinkData, i).dirty)
      _drawLink(&g_array_index(priv->links, struct _LinkData, i), priv->data, bonds);

  visu_gl_ext_startDrawing(pairs);
  for (i = 0; i < priv->links->len; i++)
    {
      dt = &g_array_index(priv->links, struct _LinkData, i);
      DBG_fprintf(stderr, "Extension Pairs: call list %d.\n", dt->id);
      glCallList(dt->id);
    }
  visu_gl_ext_completeDrawing(pairs);
}

/**************/
/* Resources. */
/**************/
static void exportResourcesPairs(GString *data, VisuData *dataObj)
{
  GList *pairsMeth;
  GString *buf;
  VisuElement *ele1, *ele2;
  ToolColor *color;
  gchar *buf2, *id;
  guint i;
  struct _LinkData *dt;

  visu_config_file_exportComment(data, DESC_RESOURCES_PAIRS);
  visu_config_file_exportEntry(data, FLAG_RESOURCES_PAIRS, NULL,
                               "%i", (defaultPairs)?visu_gl_ext_getActive(VISU_GL_EXT(defaultPairs)):pairsAreOn);

  if (defaultPairs && defaultPairs->priv->defaultRenderer)
    {
      buf = g_string_new("");
      g_string_append_printf(buf, "%s (", DESC_RESOURCES_FAV_PAIRS);
      for (pairsMeth = defaultPairs->priv->renderers; pairsMeth;
           pairsMeth = g_list_next(pairsMeth))
	{
          g_object_get(G_OBJECT(pairsMeth->data), "id", &id, NULL);
	  g_string_append_printf(buf, "'%s'", id);
          g_free(id);
	  if (pairsMeth->next)
	    g_string_append_printf(buf, ", ");	  
	}
      g_string_append_printf(buf, ")");
      visu_config_file_exportComment(data, buf->str);
      g_string_free(buf, TRUE);
      g_object_get(G_OBJECT(defaultPairs->priv->defaultRenderer), "id", &id, NULL);
      visu_config_file_exportEntry(data, FLAG_RESOURCES_FAV_PAIRS, NULL,
                                   "%s", id);
      g_free(id);
    }

  visu_config_file_exportComment(data, DESC1_RESOURCES_PAIR_LINK);
  visu_config_file_exportComment(data, DESC2_RESOURCES_PAIR_LINK);
  for (i = 0; i < defaultPairs->priv->links->len; i++)
    {
      dt = &g_array_index(defaultPairs->priv->links, struct _LinkData, i);
      visu_pair_getElements(dt->pair, &ele1, &ele2);
  
      /* We export only if the two elements of the pair are used in the given dataObj. */
      if (dataObj &&
          (!visu_node_array_containsElement(VISU_NODE_ARRAY(dataObj), ele1) ||
           !visu_node_array_containsElement(VISU_NODE_ARRAY(dataObj), ele2)))
        continue;

      buf2 = g_strdup_printf("%s %s  %4.3f %4.3f", visu_element_getName(ele1), 
                            visu_element_getName(ele2),
                            visu_pair_link_getDistance(dt->link, VISU_DISTANCE_MIN),
                            visu_pair_link_getDistance(dt->link, VISU_DISTANCE_MAX));
  
      color = visu_pair_link_getColor(dt->link);
      g_object_get(G_OBJECT(dt->rdt->renderer), "id", &id, NULL);
      visu_config_file_exportEntry(data, FLAG_RESOURCES_PAIR_LINK, buf2,
                                   "%s  %d  %d  %s",
                                   tool_color_asStr(color),
                                   visu_pair_link_getDrawn(dt->link),
                                   visu_pair_link_getPrintLength(dt->link),
                                   id);
      g_free(buf2);
      g_free(id);
    }

  visu_config_file_exportComment(data, "");
}


/*****************************************/
/* Methods to organize pairs extensions. */
/*****************************************/
/**
 * visu_gl_ext_pairs_getAllLinkRenderer:
 * @pairs: a #VisuGlExtPairs object.
 * 
 * Useful to know all #VisuPairLinkRenderer used by @pairs.
 *
 * Since: 3.8
 *
 * Returns: (element-type VisuPairLinkRenderer*) (transfer none): a list
 * of all the known #VisuPairLinkRenderer. This list should be considered
 * read-only.
 */
GList* visu_gl_ext_pairs_getAllLinkRenderer(VisuGlExtPairs *pairs)
{
  g_return_val_if_fail(VISU_IS_GL_EXT_PAIRS(pairs), (GList*)0);
  return pairs->priv->renderers;
}
static gint _cmpRenderers(gconstpointer a, gconstpointer b)
{
  gchar *id;
  gint res;
  
  g_object_get(VISU_PAIR_LINK_RENDERER(a), "id", &id, NULL);
  res = g_ascii_strcasecmp(id, b);
  g_free(id);
  return res;
}
static VisuPairLinkRenderer* _rendererByName(VisuGlExtPairs *pairs, const gchar *name)
{
  GList *pairsMethods;
  gchar *name_;

  name_ = g_strstrip(g_strdup(name));
  pairsMethods = g_list_find_custom(pairs->priv->renderers, name_, _cmpRenderers);
  g_free(name_);
  return (pairsMethods) ? VISU_PAIR_LINK_RENDERER(pairsMethods->data) : (VisuPairLinkRenderer*)0;
}
