/*   EXTRAITS DE LA LICENCE
	Copyright CEA, contributeurs : Damien
	CALISTE, laboratoire L_Sim, (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, (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 "nodeList.h"
#include <visu_data.h>

#include <math.h>

/**
 * SECTION:nodeList
 * @short_description: Defines an object to store a set of nodes.
 *
 * <para></para>
 */

enum
  {
    PROP_0,
    NODES_PROP,
    DIM_PROP,
    IDS_PROP,
    N_PROP
  };
static GParamSpec *_properties[N_PROP];

struct _VisuNodeListPrivate
{
  gboolean dispose_has_run;

  GWeakRef nodes;
  gulong decSig, posSig;
  GArray *list;

  VisuScalarFieldData *field;
};

/* Local routines. */
static void _dispose (GObject* obj);
static void _finalize(GObject* obj);
static void _get_property(GObject* obj, guint property_id,
                          GValue *value, GParamSpec *pspec);
static void _set_property(GObject* obj, guint property_id,
                          const GValue *value, GParamSpec *pspec);
static void _setArray(VisuNodeList *list, VisuNodeArray *array);
static void _popDecrease(VisuNodeList *list, const GArray *ids);
static void _posChanged(VisuNodeList *list, const GArray *ids);
static void _computeField(VisuNodeList *list);

G_DEFINE_TYPE_WITH_CODE(VisuNodeList, visu_node_list, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuNodeList))

static void visu_node_list_class_init(VisuNodeListClass *klass)
{
  DBG_fprintf(stderr, "Visu NodeList: creating the class of the object.\n");
  /* DBG_fprintf(stderr, "                - adding new signals ;\n"); */

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose      = _dispose;
  G_OBJECT_CLASS(klass)->finalize     = _finalize;
  G_OBJECT_CLASS(klass)->set_property = _set_property;
  G_OBJECT_CLASS(klass)->get_property = _get_property;

  /**
   * VisuNodeList::nodes:
   *
   * Holds the #VisuNodeArray the nodes are taken from.
   *
   * Since: 3.8
   */
  _properties[NODES_PROP] =
    g_param_spec_object("nodes", "Nodes", "nodes values are related to",
                        VISU_TYPE_NODE_ARRAY,
                        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
  /**
   * VisuNodeList::n-nodes:
   *
   * Holds the number of nodes stored in the list.
   *
   * Since: 3.8
   */
  _properties[DIM_PROP] =
    g_param_spec_uint("n-nodes", "N-nodes", "number of nodes",
                      0, G_MAXINT, 0, G_PARAM_READABLE);
  /**
   * VisuNodeList::ids:
   *
   * The current ids stored in the object.
   *
   * Since: 3.8
   */
  _properties[IDS_PROP] =
    g_param_spec_boxed("ids", "Ids", "node ids",
                       G_TYPE_ARRAY, G_PARAM_READWRITE);

  g_object_class_install_properties(G_OBJECT_CLASS(klass), N_PROP, _properties);
}

static void visu_node_list_init(VisuNodeList *obj)
{
  DBG_fprintf(stderr, "Visu NodeList: initializing a new object (%p).\n",
	      (gpointer)obj);
  
  obj->priv = visu_node_list_get_instance_private(obj);
  obj->priv->dispose_has_run = FALSE;

  /* Private data. */
  g_weak_ref_init(&obj->priv->nodes, (gpointer)0);
  obj->priv->list = g_array_new(FALSE, FALSE, sizeof(guint));
  obj->priv->field = (VisuScalarFieldData*)0;
}

static void _dispose(GObject* obj)
{
  VisuNodeList *self = VISU_NODE_LIST(obj);

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

  if (self->priv->dispose_has_run)
    return;
  self->priv->dispose_has_run = TRUE;

  _setArray(self, (VisuNodeArray*)0);
  g_weak_ref_clear(&self->priv->nodes);
  if (self->priv->field)
    g_object_unref(self->priv->field);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_node_list_parent_class)->dispose(obj);
}
/* This method is called once only. */
static void _finalize(GObject* obj)
{
  VisuNodeList *self = VISU_NODE_LIST(obj);

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

  g_array_unref(self->priv->list);

  /* Chain up to the parent class */
  DBG_fprintf(stderr, "Visu NodeList: chain to parent.\n");
  G_OBJECT_CLASS(visu_node_list_parent_class)->finalize(obj);
  DBG_fprintf(stderr, "Visu NodeList: freeing ... OK.\n");
}
static void _get_property(GObject* obj, guint property_id,
                          GValue *value, GParamSpec *pspec)
{
  VisuNodeList *self = VISU_NODE_LIST(obj);

  DBG_fprintf(stderr, "Visu NodeList: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case NODES_PROP:
      g_value_take_object(value, g_weak_ref_get(&self->priv->nodes));
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      break;
    case DIM_PROP:
      g_value_set_uint(value, self->priv->list->len);
      DBG_fprintf(stderr, "%d.\n", g_value_get_uint(value));
      break;
    case IDS_PROP:
      g_value_set_boxed(value, self->priv->list);
      DBG_fprintf(stderr, "%p.\n", g_value_get_boxed(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void _set_property(GObject* obj, guint property_id,
                          const GValue *value, GParamSpec *pspec)
{
  VisuNodeList *self = VISU_NODE_LIST(obj);
  guint len;
  GArray *boxed;

  DBG_fprintf(stderr, "Visu NodeList: set property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case NODES_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      _setArray(self, g_value_dup_object(value));
      g_object_unref(g_value_get_object(value));
      break;
    case IDS_PROP:
      DBG_fprintf(stderr, "%p.\n", g_value_get_boxed(value));
      len = self->priv->list->len;
      boxed = (GArray*)g_value_dup_boxed(value);
      if (boxed)
        {
          g_array_unref(self->priv->list);
          self->priv->list = boxed;
        }
      else
        g_array_set_size(self->priv->list, 0);
      if (len != self->priv->list->len)
        g_object_notify_by_pspec(obj, _properties[DIM_PROP]);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

static void _setArray(VisuNodeList *list, VisuNodeArray *array)
{
  VisuNodeArray *old = VISU_NODE_ARRAY(g_weak_ref_get(&list->priv->nodes));
  if (array)
    {
      g_return_if_fail(!old);

      g_weak_ref_set(&list->priv->nodes, array);

      list->priv->decSig = g_signal_connect_swapped(array, "PopulationDecrease",
                                                    G_CALLBACK(_popDecrease), list);
      list->priv->posSig = g_signal_connect_swapped(array, "position-changed",
                                                    G_CALLBACK(_posChanged), list);
    }
  else if (old)
    {
      g_signal_handler_disconnect(old, list->priv->decSig);
      g_signal_handler_disconnect(old, list->priv->posSig);
      g_object_unref(old);
    }
}

static void _popDecrease(VisuNodeList *list, const GArray *ids)
{
  guint i = 0, len = list->priv->list->len;
  while (i < list->priv->list->len)
    {
      guint j;
      for (j = 0; j < ids->len; j++)
        if (g_array_index(ids, guint, j) == g_array_index(list->priv->list, guint, i))
          break;
      if (j < ids->len)
        g_array_remove_index_fast(list->priv->list, i);
      else
        i += 1;
    }
  if (list->priv->list->len < len)
    g_object_notify_by_pspec(G_OBJECT(list), _properties[DIM_PROP]);
}

static void _posChanged(VisuNodeList *list, const GArray *ids)
{
  guint i;
  for (i = 0; i < list->priv->list->len; i++)
    {
      guint j;
      for (j = 0; j < ids->len; j++)
        if (g_array_index(ids, guint, j) == g_array_index(list->priv->list, guint, i))
          break;
      if (j < ids->len)
        break;
    }
  if (i < list->priv->list->len && list->priv->field)
    _computeField(list);
}

/**
 * visu_node_list_new:
 * @array: a #VisuNodeArray object.
 *
 * Creates a new #VisuNodeList storing indexes of nodes from @array..
 *
 * Since: 3.8
 *
 * Returns: a pointer to the #VisuNodeList it created or
 * %NULL otherwise.
 */
VisuNodeList* visu_node_list_new(VisuNodeArray *array)
{
  DBG_fprintf(stderr,"Visu NodeList: new object.\n");
  
  return g_object_new(VISU_TYPE_NODE_LIST, "nodes", array, NULL);
}

/**
 * visu_node_list_new_fromFrag:
 * @frag: a #VisuNodeValuesFrag object.
 * @label: a label.
 *
 * Create a list of nodes from the node labelled by @label in
 * @frag. When the fragment definition is chaged or the node
 * population is changed, the node list is updated accordingly.
 *
 * Since: 3.8
 *
 * Returns: a newly created #VisuNodeList object.
 **/
VisuNodeList* visu_node_list_new_fromFrag(VisuNodeValuesFrag *frag,
                                          const gchar *label)
{
  VisuNodeList *list;
  GArray *ids;
  VisuNodeArray *array = visu_node_values_getArray(VISU_NODE_VALUES(frag));
  if (!array)
    return (VisuNodeList*)0;

  ids = visu_node_values_frag_getNodeIds(frag, label);
  list = g_object_new(VISU_TYPE_NODE_LIST, "nodes", array, "ids", ids, NULL);
  g_object_unref(array);
  g_array_unref(ids);

  return list;
}

/**
 * visu_node_list_add:
 * @list: a #VisuNodeList object.
 * @id: a node id.
 *
 * Add @id to the list of nodes.
 *
 * Since: 3.8
 *
 * Returns: %TRUE if node is not already existing in the list.
 **/
gboolean visu_node_list_add(VisuNodeList *list, guint id)
{
  guint i;
  
  g_return_val_if_fail(VISU_IS_NODE_LIST(list), FALSE);

  for (i = 0; i < list->priv->list->len; i++)
    if (g_array_index(list->priv->list, guint, i) == id)
      return FALSE;

  g_array_append_val(list->priv->list, id);
  g_object_notify_by_pspec(G_OBJECT(list), _properties[DIM_PROP]);
  return TRUE;
}

/**
 * visu_node_list_remove:
 * @list: a #VisuNodeList object.
 * @id: a node id.
 *
 * Remove @id from the list of nodes.
 *
 * Since: 3.8
 *
 * Returns: %TRUE if the id was actually in the list.
 **/
gboolean visu_node_list_remove(VisuNodeList *list, guint id)
{
  g_return_val_if_fail(VISU_IS_NODE_LIST(list), FALSE);

  guint i;
  for (i = 0; i < list->priv->list->len; i++)
    if (g_array_index(list->priv->list, guint, i) == id)
      break;

  if (i < list->priv->list->len)
    {
      g_array_remove_index_fast(list->priv->list, i);
      g_object_notify_by_pspec(G_OBJECT(list), _properties[DIM_PROP]);
    }
  return (i < list->priv->list->len);
}

static void _computeField(VisuNodeList *list)
{
  const guint size[3] = {50, 50, 50};
  GArray *data;
  guint i, j, k, id;
  double *at;
  VisuData *dataObj;
  VisuBox *box;
  const float top[3] = {1.f, 1.f, 1.f};
  float blen[3];

  dataObj = g_weak_ref_get(&list->priv->nodes);
  if (!dataObj)
    return;

  data = g_array_sized_new(FALSE, FALSE, sizeof(double), size[0] * size[1] * size[2]);
  box = visu_boxed_getBox(VISU_BOXED(dataObj));
  visu_box_convertBoxCoordinatestoXYZ(box, blen, top);
  for (k = 0; k < size[2]; k++)
    for (j = 0; j < size[1]; j++)
      for (i = 0; i < size[0]; i++)
        {
          double pt[3] = {blen[0] * i / size[0],
                          blen[1] * j / size[1],
                          blen[2] * k / size[2]};
          at = &g_array_index(data, double, k * size[0] * size[1] + j * size[0] + i);
          *at = G_MAXDOUBLE;
          for (id = 0; id < list->priv->list->len; id++)
            {
              const VisuNode *node;
              node = visu_node_array_getFromId(VISU_NODE_ARRAY(dataObj), g_array_index(list->priv->list, guint, id));
              if (node)
                {
                  float coord[3];
                  visu_data_getNodePosition(dataObj, node, coord);
                  coord[0] -= pt[0];
                  coord[1] -= pt[1];
                  coord[2] -= pt[2];
                  visu_box_getPeriodicVector(box, coord);
                  *at = MIN(*at, sqrt(coord[0] * coord[0] + coord[1] * coord[1] + coord[2] * coord[2]));
                }
            }
        }
  g_array_set_size(data, size[0] * size[1] * size[2]);
  g_object_unref(dataObj);
  if (!list->priv->field)
    {
      list->priv->field = g_object_new(VISU_TYPE_SCALAR_FIELD_DATA, NULL);
      visu_boxed_setBox(VISU_BOXED(list->priv->field), VISU_BOXED(dataObj));
      visu_scalar_field_setGridSize(VISU_SCALAR_FIELD(list->priv->field), size);
      g_signal_connect(list, "notify::n-nodes",
                       G_CALLBACK(_computeField), (gpointer)0);
    }
  visu_scalar_field_data_set(list->priv->field, data, VISU_SCALAR_FIELD_DATA_XYZ);
  g_array_unref(data);
}

/**
 * visu_node_list_envelope:
 * @list: a #VisuNodeList object.
 *
 * Compute a scalarfield corresponding to the envelope of the node
 * list. When the node positions are changed, the scalar field is
 * updated accordingly.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a newly created #VisuScalarFieldData.
 **/
VisuScalarFieldData* visu_node_list_envelope(VisuNodeList *list)
{
  g_return_val_if_fail(VISU_IS_NODE_LIST(list), (VisuScalarFieldData*)0);

  if (!list->priv->field)
    _computeField(list);

  return list->priv->field;
}
