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

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

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

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

/*   LICENCE SUM UP
	Copyright CEA, contributors : Damien
	CALISTE, laboratoire L_Sim, (2016)

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

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

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

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

#include "visu_pairset.h"

#include <string.h>

/**
 * SECTION: visu_pairset
 * @short_description: a base class for all loadable representation of
 * #VisuData objects.
 *
 * <para>This object is storing dynamically all the #VisuPair objects
 * that exist for a given #VisuData object.</para>
 */


/**
 * VisuPairSetClass:
 * @parent: the parent class;
 *
 * A short way to identify #_VisuPairSetClass structure.
 */
/**
 * VisuPairSet:
 *
 * An opaque structure.
 */
/**
 * VisuPairSetIter:
 * @set: the #VisuPairSet this iterator is based on.
 * @pair: the current #VisuPair.
 * @link: the current #VisuPairLink.
 *
 * Iterator structure to run over all #VisuPairLink of a given set of #VisuPair.
 */
/**
 * VisuPairSetPrivate:
 * @minMax: storage for the length bounds for drawn pairs ;
 * @drawn: a boolean to say if the pair is drawn or not ;
 * @printLength: a boolean to say if length of pairs are drawn near
 * them ;
 *
 * This structure is used to describe a link between two elements. A
 * link is drawn only its length is between the minimum and the
 * maximum value stored in the minMax array.
 */
struct _VisuPairSetPrivate
{
  gboolean dispose_has_run;

  GArray *pairs;

  VisuData *nodes;
  gulong popDef_sig, box_sig;
  VisuBox *box;
  gulong unit_sig;
};

struct _PairData
{
  VisuPair *pair;
  gulong links_sig;
};
static void _freePairData(struct _PairData *dt)
{
  g_signal_handler_disconnect(G_OBJECT(dt->pair), dt->links_sig);
  g_object_unref(G_OBJECT(dt->pair));
}

static void visu_pair_set_dispose(GObject* obj);
static void visu_pair_set_finalize(GObject* obj);
static void visu_pair_set_get_property(GObject* obj, guint property_id,
                                        GValue *value, GParamSpec *pspec);

static void _setup(VisuPairSet *set, GParamSpec *pspec, VisuNodeArray *nodes);
static void _setBox(VisuPairSet *set, VisuBox *box);

static void onLinksNotified(VisuPair *pair, GParamSpec *pspec, VisuPairSet *set);
static void onUnits(VisuPairSet *set, gfloat fact, VisuBox *box);

static VisuPair* _pair_pool_get(VisuElement *ele1, VisuElement *ele2);

enum
  {
    PROP_0,
    PAIRS_PROP,
    DATA_PROP,
    N_PROP
  };
static GParamSpec *_properties[N_PROP];

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

G_DEFINE_TYPE_WITH_CODE(VisuPairSet, visu_pair_set, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuPairSet))

static void visu_pair_set_class_init(VisuPairSetClass *klass)
{
  DBG_fprintf(stderr, "Visu PairSet: creating the class of the object.\n");

  DBG_fprintf(stderr, "                - adding new signals ;\n");

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_pair_set_dispose;
  G_OBJECT_CLASS(klass)->finalize  = visu_pair_set_finalize;
  G_OBJECT_CLASS(klass)->get_property = visu_pair_set_get_property;

  /**
   * VisuPairSet::pairs:
   *
   * Store the set of #VisuPair for the current model.
   *
   * Since: 3.8
   */
  _properties[PAIRS_PROP] = g_param_spec_boxed("pairs", "Pairs", "set of pairs",
                                               G_TYPE_ARRAY, G_PARAM_READABLE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), PAIRS_PROP,
				  _properties[PAIRS_PROP]);
  /**
   * VisuPairSet::data:
   *
   * Store the #VisuData from which the #VisuElement are extracted.
   *
   * Since: 3.8
   */
  _properties[DATA_PROP] = g_param_spec_object("data", "Data", "data elements come from",
                                               VISU_TYPE_DATA, G_PARAM_READABLE);
  g_object_class_install_property(G_OBJECT_CLASS(klass), DATA_PROP,
				  _properties[DATA_PROP]);

  /**
   * VisuPairSet::links-changed:
   * @set: the object which emits the signal ;
   * @pair: (type VisuPair): the #VisuPair that has changing links.
   *
   * Gets emitted when any of the #VisuPair of @set has a change
   * (addition or removal) in its list of #VisuPairLink.
   *
   * Since: 3.8
   */
  _signals[LINKS_SIGNAL] =
    g_signal_new("links-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);
}
static void visu_pair_set_init(VisuPairSet *obj)
{
  DBG_fprintf(stderr, "Visu PairSet: initializing a new object (%p).\n",
	      (gpointer)obj);
  
  obj->priv = visu_pair_set_get_instance_private(obj);
  obj->priv->dispose_has_run = FALSE;

  /* Private data. */
  obj->priv->pairs = g_array_new(FALSE, FALSE, sizeof(struct _PairData));
  obj->priv->nodes = (VisuData*)0;
  obj->priv->box   = (VisuBox*)0;
}
static void visu_pair_set_dispose(GObject* obj)
{
  VisuPairSet *data;

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

  data = VISU_PAIR_SET(obj);
  if (data->priv->dispose_has_run)
    return;
  data->priv->dispose_has_run = TRUE;

  visu_pair_set_setModel(data, (VisuData*)0);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_pair_set_parent_class)->dispose(obj);
}
static void visu_pair_set_finalize(GObject* obj)
{
  VisuPairSet *data;

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

  data = VISU_PAIR_SET(obj);

  g_array_free(data->priv->pairs, TRUE);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_pair_set_parent_class)->finalize(obj);
}
static void visu_pair_set_get_property(GObject* obj, guint property_id,
                                        GValue *value, GParamSpec *pspec)
{
  VisuPairSet *self = VISU_PAIR_SET(obj);
  GArray *pairs;
  guint i;

  DBG_fprintf(stderr, "Visu PairSet: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case PAIRS_PROP:
      pairs = g_array_sized_new(FALSE, FALSE, sizeof(VisuPair*), self->priv->pairs->len);
      for (i = 0; i < self->priv->pairs->len; i++)
        g_array_append_val(pairs, g_array_index(self->priv->pairs, struct _PairData, i).pair);
      g_value_take_boxed(value, pairs);
      DBG_fprintf(stderr, "%p.\n", (gpointer)self->priv->pairs);
      break;
    case DATA_PROP:
      g_value_set_object(value, self->priv->nodes);
      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;
    }
}
/**
 * visu_pair_set_new:
 *
 * Define an object to handle all possible #VisuPair for a given
 * #VisuNodeArray.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a new #VisuPairSet object.
 */
VisuPairSet* visu_pair_set_new()
{
  return VISU_PAIR_SET(g_object_new(VISU_TYPE_PAIR_SET, NULL));
}

/**
 * visu_pair_set_setModel:
 * @set: a #VisuPairSet object.
 * @nodes: a #VisuData object.
 *
 * Binds @nodes population and unit to @set.
 *
 * Since: 3.8
 *
 * Returns: TRUE if model is actually changed.
 **/
gboolean visu_pair_set_setModel(VisuPairSet *set, VisuData *nodes)
{
  g_return_val_if_fail(VISU_IS_PAIR_SET(set), FALSE);

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

  if (set->priv->nodes)
    {
      g_signal_handler_disconnect(set->priv->nodes, set->priv->popDef_sig);
      g_signal_handler_disconnect(set->priv->nodes, set->priv->box_sig);
      g_object_unref(G_OBJECT(set->priv->nodes));
      _setBox(set, (VisuBox*)0);
    }
  if (nodes)
    {
      g_object_ref(G_OBJECT(nodes));
      set->priv->popDef_sig = g_signal_connect_swapped(nodes, "notify::elements",
                                                       G_CALLBACK(_setup), set);
      set->priv->box_sig = g_signal_connect_swapped(nodes, "setBox",
                                                    G_CALLBACK(_setBox), set);
      _setBox(set, visu_boxed_getBox(VISU_BOXED(nodes)));
    }
  set->priv->nodes = nodes;
  _setup(set, (GParamSpec*)0, VISU_NODE_ARRAY(nodes));
  g_object_notify_by_pspec(G_OBJECT(set), _properties[DATA_PROP]);

  return TRUE;
}

/**
 * visu_pair_set_getNthPair:
 * @set: a #VisuPairSet object.
 * @pos: an integer.
 *
 * Retrieve the nth #VisuPair stored in @set.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the nth #VisuPair in @set.
 **/
VisuPair* visu_pair_set_getNthPair(VisuPairSet *set, guint pos)
{
  g_return_val_if_fail(VISU_IS_PAIR_SET(set), (VisuPair*)0);
  
  return (pos < set->priv->pairs->len) ?
    g_array_index(set->priv->pairs, struct _PairData, pos).pair : (VisuPair*)0;
}
/**
 * visu_pair_set_get:
 * @set: a #VisuPairSet object.
 * @ele1: a #VisuElement object.
 * @ele2: a #VisuElement object.
 *
 * Retrieve the #VisuPair linking @ele1 and @ele2 as stroed in @set if
 * it exists, or %NULL if not.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the #VisuPair stored in @set linking
 * @ele1 and @ele2.
 **/
VisuPair* visu_pair_set_get(VisuPairSet *set, const VisuElement *ele1, const VisuElement *ele2)
{
  guint i;
  VisuElement *e1, *e2;

  g_return_val_if_fail(VISU_IS_PAIR_SET(set), (VisuPair*)0);

  for (i = 0; i < set->priv->pairs->len; i++)
    {
      visu_pair_getElements(g_array_index(set->priv->pairs, struct _PairData, i).pair,
                            &e1, &e2);
      if ((e1 == ele1 && e2 == ele2 ) || (e1 == ele2 && e2 == ele1))
        return g_array_index(set->priv->pairs, struct _PairData, i).pair;
    }
  return (VisuPair*)0;
}
/**
 * visu_pair_set_getFromLink:
 * @set: a #VisuPairSet object.
 * @link: a #VisuPairLink object.
 *
 * Retrieve the #VisuPair stored in @set that contains @link, or %NULL
 * if none.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the #VisuPair stored in @set containing @link.
 **/
VisuPair* visu_pair_set_getFromLink(VisuPairSet *set, const VisuPairLink *link)
{
  guint i;

  g_return_val_if_fail(VISU_IS_PAIR_SET(set), (VisuPair*)0);

  for (i = 0; i < set->priv->pairs->len; i++)
    if (visu_pair_contains(g_array_index(set->priv->pairs, struct _PairData, i).pair, link))
      return g_array_index(set->priv->pairs, struct _PairData, i).pair;
  return (VisuPair*)0;
}

static void _setup(VisuPairSet *set, GParamSpec *pspec _U_, VisuNodeArray *nodes)
{
  guint i, j;
  GArray *pairs, *elements;
  struct _PairData dt;

  if (nodes)
    {
      g_object_get(nodes, "elements", &elements, NULL);
      pairs = g_array_sized_new(FALSE, FALSE, sizeof(struct _PairData),
                                elements->len * (elements->len + 1) / 2);

      for(i = 0; i < elements->len; i++)
        for(j = i; j < elements->len; j++)
          {
            dt.pair = visu_pair_set_get(set, g_array_index(elements, VisuElement*, i),
                                        g_array_index(elements, VisuElement*, j));
            if (!dt.pair)
              dt.pair = _pair_pool_get(g_array_index(elements, VisuElement*, i),
                                       g_array_index(elements, VisuElement*, j));
            g_object_ref(dt.pair);
            dt.links_sig = g_signal_connect(G_OBJECT(dt.pair), "notify::links",
                                            G_CALLBACK(onLinksNotified), set);
            g_array_append_val(pairs, dt);
          }
      g_array_unref(elements);
    }
  else
    pairs = g_array_new(FALSE, FALSE, sizeof(struct _PairData));
  g_array_set_clear_func(pairs, (GDestroyNotify)_freePairData);

  if (set->priv->pairs)
    g_array_free(set->priv->pairs, TRUE);
  set->priv->pairs = pairs;
  DBG_fprintf(stderr, "Visu Pair Set: new set of %d pairs.\n", pairs->len);
  g_object_notify_by_pspec(G_OBJECT(set), _properties[PAIRS_PROP]);

  if (set->priv->box)
    onUnits(set, 0.f, set->priv->box);
}
static void _setBox(VisuPairSet *set, VisuBox *box)
{
  if (set->priv->box)
    {
      g_signal_handler_disconnect(G_OBJECT(set->priv->box), set->priv->unit_sig);
      g_object_unref(G_OBJECT(set->priv->box));
    }
  set->priv->box = box;
  if (box)
    {
      g_object_ref(G_OBJECT(box));
      set->priv->unit_sig = g_signal_connect_swapped(G_OBJECT(box), "UnitChanged",
                                                     G_CALLBACK(onUnits), set);
    }
}

static void onLinksNotified(VisuPair *pair, GParamSpec *pspec _U_, VisuPairSet *set)
{
  g_signal_emit(G_OBJECT(set), _signals[LINKS_SIGNAL], 0, pair);
}
static void onUnits(VisuPairSet *set, gfloat fact _U_, VisuBox *box)
{
  VisuPairSetIter iter;
  ToolUnits units;
  
  units = visu_box_getUnit(box);
  for (visu_pair_set_iter_new(set, &iter, TRUE); iter.link;
       visu_pair_set_iter_next(&iter))
    visu_pair_link_setUnits(iter.link, units);
}

/**
 * visu_pair_set_iter_new:
 * @set: a #VisuPairSet object.
 * @iter: (out caller-allocates): a #VisuPairSetIter location.
 * @all: a boolean.
 *
 * Creates an iterator over all the #VisuPairLink of a set of
 * #VisuPair. If @all is %FALSE, only visible #VisuPairLink, see
 * visu_pair_link_setDrawn(), are iterated on.
 *
 * Since: 3.8
 **/
void visu_pair_set_iter_new(VisuPairSet *set, VisuPairSetIter *iter, gboolean all)
{
  VisuElement *ele1, *ele2;

  g_return_if_fail(VISU_IS_PAIR_SET(set) && iter);

  iter->all = all;
  iter->set = set;
  iter->i = 0;
  iter->j = 0;
  do
    {
      iter->pair = visu_pair_set_getNthPair(iter->set, (iter->i)++);
      if (iter->pair)
        visu_pair_getElements(iter->pair, &ele1, &ele2);
    } while (iter->pair && (!visu_element_getRendered(ele1) ||
                            !visu_element_getRendered(ele2)));
  visu_pair_set_iter_next(iter);
}
/**
 * visu_pair_set_iter_next:
 * @iter: a #VisuPairSetIter object.
 *
 * Iterates to the next #VisuPairLink.
 *
 * Since: 3.8
 **/
void visu_pair_set_iter_next(VisuPairSetIter *iter)
{
  VisuElement *ele1, *ele2;

  g_return_if_fail(iter);

  if (!iter->pair)
    {
      iter->link = (VisuPairLink*)0;
      return;
    }

  do
    {
      iter->link = visu_pair_getNthLink(iter->pair, (iter->j)++);
    } while (iter->link && !iter->all && !visu_pair_link_isDrawn(iter->link));
  if (!iter->link)
    {
      do
        {
          iter->pair = visu_pair_set_getNthPair(iter->set, (iter->i)++);
          if (iter->pair)
            visu_pair_getElements(iter->pair, &ele1, &ele2);
        } while (iter->pair && (!visu_element_getRendered(ele1) ||
                                !visu_element_getRendered(ele2)));
      iter->j = 0;
      visu_pair_set_iter_next(iter);
    }
}


/*******************/
/* Pair set stuff. */
/*******************/
/* This hashtable as VisuElement* as keys and pointer to other hashtable as value.
   The main idea is to have an hashtable of 2 dimension, each two keys are
   VisuElement* and the final values are PairsData. Nevertheless, this hashtable
   must not be access directly but through the get and put methods. */
static GHashTable *DminDmax = NULL;
static void _pair_pool_init()
{
  DminDmax = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_object_unref);
}
/**
 * visu_pair_pool_finalize: (skip)
 *
 * Free all associated memory.
 *
 * Since: 3.8
 **/
void visu_pair_pool_finalize()
{
  if (DminDmax)
    g_hash_table_destroy(DminDmax);
  DminDmax = NULL;
}
static VisuPair* _pair_pool_get(VisuElement *ele1, VisuElement *ele2)
{
  VisuPair *pair;
  gchar *key;

  g_return_val_if_fail(ele1 && ele2, (VisuPair*)0);

  if (!DminDmax)
    _pair_pool_init();

  if (strcmp(ele1->name, ele2->name) < 0)
    key = g_strdup_printf("%s %s", ele1->name, ele2->name);
  else
    key = g_strdup_printf("%s %s", ele2->name, ele1->name);
  pair = (VisuPair*)g_hash_table_lookup(DminDmax, (gpointer)key);
  DBG_fprintf(stderr, "Visu Pair Set: test key '%s' -> %p.\n", key, (gpointer)pair);
  if (!pair)
    {
      /* Ok, create one if none found. */
      pair = visu_pair_new(ele1, ele2);
      g_hash_table_insert(DminDmax, (gpointer)key, (gpointer)pair);
    }
  else
    g_free(key);

  return pair;
}
struct foreachPairsData_struct
{
  VisuPairPoolForeachFunc func;
  gpointer userData;
};
static void foreachLevel2(gpointer key _U_, gpointer value, gpointer userData)
{
  struct foreachPairsData_struct *storage;

  storage = (struct foreachPairsData_struct *)userData;
  storage->func((VisuPair*)value, storage->userData);
}
/**
 * visu_pair_pool_foreach:
 * @whatToDo: (scope call): a VisuPairSetForeachFunc() method ;
 * @user_data: some user defined data.
 *
 * The way #VisuPair are stored in V_Sim is private and could changed between version.
 * This method is used to apply some method each pairs.
 */
void visu_pair_pool_foreach(VisuPairPoolForeachFunc whatToDo, gpointer user_data)
{
  struct foreachPairsData_struct storage;

  if (!DminDmax)
    _pair_pool_init();

  storage.func = whatToDo;
  storage.userData = user_data;
  g_hash_table_foreach(DminDmax, (GHFunc)foreachLevel2, (gpointer)(&storage));
}
/**
 * visu_pair_pool_readLinkFromLabel:
 * @label: a string.
 * @data: (out callee-allocates): a #VisuPairLink location.
 * @errorMessage: (out callee-allocates): a string location.
 *
 * Reads @label and parses it to detect the definition of a
 * #VisuPairLink. This pair link is added to the #VisuPair from the pool.
 *
 * Since: 3.8
 *
 * Returns: TRUE on success.
 **/
gboolean visu_pair_pool_readLinkFromLabel(const gchar *label, VisuPairLink **data,
                                          gchar **errorMessage)
{
  gchar **tokens;
  VisuElement *ele[2];
  float minMax[2];
  guint i, n;
  VisuPair *pair;

  g_return_val_if_fail(data && errorMessage, FALSE);
  DBG_fprintf(stderr, "Visu PairSet: look for link from label '%s'.\n", label);

  if (!label)
    return FALSE;

  *data = (VisuPairLink*)0;
  tokens = g_strsplit_set(label, " \n", TOOL_MAX_LINE_LENGTH);

  /* Read the two elements. */
  for (i = 0, n = 0; tokens[i] && n < 2; i++)
    if (tokens[i][0])
      {
        ele[n] = visu_element_retrieveFromName(tokens[i], (gboolean*)0);
        if (!ele[n])
          {
            *errorMessage = g_strdup_printf(_("'%s' wrong element name"), tokens[i]);
            g_strfreev(tokens);
            return FALSE;
          }
        n += 1;
      }
  if (n != 2)
    {
      *errorMessage = g_strdup_printf(_("2 elements should appear here"
                                       " but %d has been found"),n);
      g_strfreev(tokens);
      return FALSE;
    }
  /* Read the two distances. */
  for (n = 0; tokens[i] && n < 2; i++)
    if (tokens[i][0])
      {
        if (sscanf(tokens[i], "%f", minMax + n) != 1 || minMax[n] < 0.f)
          {
            *errorMessage = g_strdup_printf(_("'%s' wrong floating point value"), tokens[i]);
            g_strfreev(tokens);
            return FALSE;
          }
        n += 1;
      }
  if (n != 2)
    {
      *errorMessage = g_strdup_printf(_("2 floating point values should appear here"
                                       " but %d has been found"),n);
      g_strfreev(tokens);
      return FALSE;
    }
  g_strfreev(tokens);

  /* Set the values. */
  pair = _pair_pool_get(ele[0], ele[1]);
  *data = visu_pair_addLink(pair, minMax);

  return (*data != (VisuPairLink*)0);
}
