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

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

#include <math.h>

/**
 * SECTION:floatProp
 * @short_description: define a #VisuNodeValues object to handle any
 * array of floats.
 *
 * <para>Defines a #VisuNodeValues object to store floating point
 * arrays on every nodes and get notification for them.</para>
 */

struct _VisuNodeValuesFarrayPrivate
{
  gboolean dispose_has_run;

  gboolean dirty;
  gfloat min, max, nrm2;
  gfloat *zeros;

  GArray *readMinMax; /* Values min and max read for each column. */

  gchar *file;
};

enum
  {
    PROP_0,
    MIN_PROP,
    MAX_PROP,
    NRM2_PROP,
    READ_MM_PROP,
    FILE_PROP,
    N_PROP
  };
static GParamSpec *_properties[N_PROP];

static void visu_node_values_farray_finalize (GObject* obj);
static void visu_node_values_farray_get_property(GObject* obj, guint property_id,
                                                 GValue *value, GParamSpec *pspec);
static gboolean _setAt(VisuNodeValues *vals, const VisuNode *node,
                       GValue *value);
static gfloat _nrm2(const VisuNodeValuesFarray *vect, const GValue *value);
static void _compute(VisuNodeValuesFarray *vect);
static void onDimensionSet(VisuNodeValuesFarray *vect, const GParamSpec *pspec, gpointer data);

G_DEFINE_TYPE_WITH_CODE(VisuNodeValuesFarray, visu_node_values_farray, VISU_TYPE_NODE_VALUES,
                        G_ADD_PRIVATE(VisuNodeValuesFarray))

static void visu_node_values_farray_class_init(VisuNodeValuesFarrayClass *klass)
{
  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->finalize = visu_node_values_farray_finalize;
  /* G_OBJECT_CLASS(klass)->set_property = visu_node_values_farray_set_property; */
  G_OBJECT_CLASS(klass)->get_property = visu_node_values_farray_get_property;
  VISU_NODE_VALUES_CLASS(klass)->setAt = _setAt;
  klass->nrm2 = _nrm2;
  
  /**
   * VisuNodeValuesFarray::minimum:
   *
   * Holds the norm of the minimum value.
   *
   * Since: 3.8
   */
  _properties[MIN_PROP] =
    g_param_spec_float("minimum", "Minimum", "minimum norm",
                       0.f, G_MAXFLOAT, 0.f, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuNodeValuesFarray::maximum:
   *
   * Holds the norm of the maximum value.
   *
   * Since: 3.8
   */
  _properties[MAX_PROP] =
    g_param_spec_float("maximum", "Maximum", "maximum norm",
                       0.f, G_MAXFLOAT, 0.f, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuNodeValuesFarray::square-norm:
   *
   * Holds the square norm of the values.
   *
   * Since: 3.8
   */
  _properties[NRM2_PROP] =
    g_param_spec_float("square-norm", "Square-norm", "Square norm",
                       0.f, G_MAXFLOAT, 0.f, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuNodeValuesFarray::data-min-max:
   *
   * Min / max values per dimension of the stored data.
   *
   * Since: 3.8
   */
  _properties[READ_MM_PROP] =
    g_param_spec_boxed("data-min-max", "Data min/max",
                       "min / max values of data",
                       G_TYPE_ARRAY,
                       G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuNodeValuesFarray::source-file:
   *
   * Name of the source file the data come from.
   *
   * Since: 3.8
   */
  _properties[FILE_PROP] = g_param_spec_string("source-file", "Source file",
                                               "Source file if any",
                                               (const gchar*)0,
                                               G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

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

static void visu_node_values_farray_init(VisuNodeValuesFarray *self)
{
  DBG_fprintf(stderr, "Node Values Float : initialise %p.\n", (gpointer)self);

  self->priv = visu_node_values_farray_get_instance_private(self);

  self->priv->readMinMax      = g_array_new(FALSE, FALSE, sizeof(float) * 2);
  self->priv->file            = (gchar*)0;
  self->priv->dirty           = TRUE;

  g_signal_connect(G_OBJECT(self), "notify::n-elements",
                   G_CALLBACK(onDimensionSet), (gpointer)0);
}
static void onDimensionSet(VisuNodeValuesFarray *vect,
                           const GParamSpec *pspec _U_, gpointer data _U_)
{
  vect->priv->zeros = g_malloc0(sizeof(float) * visu_node_values_getDimension(VISU_NODE_VALUES(vect)));
}

/* This method is called once only. */
static void visu_node_values_farray_finalize(GObject* obj)
{
  VisuNodeValuesFarray *self;

  DBG_fprintf(stderr, "Node Values Float : finalize %p.\n", (gpointer)obj);

  g_return_if_fail(obj);

  self = VISU_NODE_VALUES_FARRAY(obj);
  g_free(self->priv->zeros);
  g_array_unref(self->priv->readMinMax);
  g_free(self->priv->file);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_node_values_farray_parent_class)->finalize(obj);
}
static void visu_node_values_farray_get_property(GObject* obj, guint property_id,
                                                 GValue *value, GParamSpec *pspec)
{
  VisuNodeValuesFarray *self = VISU_NODE_VALUES_FARRAY(obj);

  switch (property_id)
    {
    case MIN_PROP:
      g_value_set_float(value, visu_node_values_farray_min(self));
      break;
    case MAX_PROP:
      g_value_set_float(value, visu_node_values_farray_max(self));
      break;
    case NRM2_PROP:
      g_value_set_float(value, visu_node_values_farray_nrm2(self));
      break;
    case READ_MM_PROP:
      _compute(self);
      g_value_set_boxed(value, self->priv->readMinMax);
      break;
    case FILE_PROP:
      g_value_set_static_string(value, self->priv->file);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static gfloat _nrm2(const VisuNodeValuesFarray *vect, const GValue *value)
{
  gfloat *diff, nrm2;
  guint i, ln;

  diff = (gfloat*)g_value_get_pointer(value);
  if (diff)
    {
      ln = visu_node_values_getDimension(VISU_NODE_VALUES(vect));
      nrm2 = 0.f;
      for (i = 0; i < ln; i++)
        nrm2 += diff[i] * diff[i];
      return nrm2;
    }
  else
    return 0.f;
}

/**
 * visu_node_values_farray_new:
 * @arr: a #VisuNodeArray object.
 * @label: a translatable label.
 * @dimension: a integer value.
 *
 * Create a new farray field located on nodes, storing @dimension
 * floats per node.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a newly created #VisuNodeValuesFarray object.
 **/
VisuNodeValuesFarray* visu_node_values_farray_new(VisuNodeArray *arr,
                                                  const gchar *label,
                                                  guint dimension)
{
  VisuNodeValuesFarray *vals;

  vals = VISU_NODE_VALUES_FARRAY(g_object_new(VISU_TYPE_NODE_VALUES_FARRAY,
                                              "nodes", arr, "label", label,
                                              "type", G_TYPE_FLOAT,
                                              "n-elements", dimension, NULL));
  return vals;
}
/**
 * visu_node_values_farray_new_fromFile:
 * @arr: a #VisuNodeArray object.
 * @label: a label.
 * @filename: a filename.
 * @error: an error location.
 *
 * Parse @filename to read floating point values and creates a new
 * #VisuNodeValuesFarray object based on @arr. If an error occurs, an
 * empty #VisuNodeValuesFarray object is created.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a newly created #VisuNodeValuesFarray object.
 **/
VisuNodeValuesFarray* visu_node_values_farray_new_fromFile(VisuNodeArray *arr,
                                                           const gchar *label,
                                                           const gchar *filename,
                                                           GError **error)
{
  guint nColumns, nbNodes;
  GArray *data;
  VisuNodeValuesFarray *vals;

  nbNodes = visu_node_array_getNNodes(arr);
  data = tool_array_sizedFromFile(filename, nbNodes, &nColumns, error);
  if (!data)
    return visu_node_values_farray_new(arr, label, 1);
  
  vals = visu_node_values_farray_new(arr, label, nColumns);
  visu_node_values_farray_set(vals, data);
  vals->priv->file = g_strdup(filename);

  g_array_free(data, TRUE);

  return vals;
}

/**
 * visu_node_values_farray_min:
 * @vect: a #VisuNodeValuesFarray object.
 *
 * Computes and returns the smallest farray in the field.
 *
 * Since: 3.8
 *
 * Returns: the minimum farray norm.
 **/
gfloat visu_node_values_farray_min(VisuNodeValuesFarray *vect)
{
  g_return_val_if_fail(VISU_IS_NODE_VALUES_FARRAY(vect), G_MAXFLOAT);
  _compute(vect);
  return vect->priv->min;
}
/**
 * visu_node_values_farray_max:
 * @vect: a #VisuNodeValuesFarray object.
 *
 * Computes and returns the longest farray in the field.
 *
 * Since: 3.8
 *
 * Returns: the maximum farray norm.
 **/
gfloat visu_node_values_farray_max(VisuNodeValuesFarray *vect)
{
  g_return_val_if_fail(VISU_IS_NODE_VALUES_FARRAY(vect), -1.f);
  _compute(vect);
  return vect->priv->max;
}
/**
 * visu_node_values_farray_nrm2:
 * @vect: a #VisuNodeValuesFarray object.
 *
 * Computes and returns the sum of square norm all farrays in the field.
 *
 * Since: 3.8
 *
 * Returns: the square norm of the farray field.
 **/
gfloat visu_node_values_farray_nrm2(VisuNodeValuesFarray *vect)
{
  g_return_val_if_fail(VISU_IS_NODE_VALUES_FARRAY(vect), -1.f);
  _compute(vect);
  return vect->priv->nrm2;
}
/**
 * visu_node_values_farray_getColumnMinMax:
 * @vect: the #VisuNodeValuesFarray object.
 * @minMax: (array fixed-size=2) (out): an allocated array of two
 * floating point values ;
 * @column: an integer.
 *
 * This method is used to retrieve the minimum and the maximum
 * values of the column designed by the @column argument. Column
 * are numbered beginning at 0.
 *
 * Returns: FALSE if @column < 0 or if @column is greater than the number
 *          of read column or if no file has been set.
 */
gboolean visu_node_values_farray_getColumnMinMax(VisuNodeValuesFarray *vect,
                                                 float minMax[2], guint column)
{
  float *minmax;

  g_return_val_if_fail(VISU_IS_NODE_VALUES_FARRAY(vect), FALSE);

  _compute(vect);
  g_return_val_if_fail(column < vect->priv->readMinMax->len, FALSE);

  minmax = &g_array_index(vect->priv->readMinMax, float, column * 2);
  minMax[0] = minmax[0];
  minMax[1] = minmax[1];
  return TRUE;
}
static void _compute(VisuNodeValuesFarray *vect)
{
  float nrm2;
  gboolean valid;
  guint i, ln;
  VisuNodeValuesIter iter;
  VisuNodeValuesFarrayClass *klass = VISU_NODE_VALUES_FARRAY_GET_CLASS(vect);
  float init[2] = {G_MAXFLOAT, -G_MAXFLOAT};
  float *data, *minmax;

  DBG_fprintf(stderr, "Node Values Float (%p): recompute %d.\n",
              (gpointer)vect, vect->priv->dirty);
  if (!vect->priv->dirty)
    return;
  vect->priv->dirty = FALSE;

  vect->priv->nrm2 = 0.f;
  vect->priv->min = G_MAXFLOAT;
  vect->priv->max = 0.f;

  valid = visu_node_values_iter_new(&iter, ITER_NODES_BY_TYPE,
                                    VISU_NODE_VALUES(vect));
  while (valid)
    {
      nrm2 = klass->nrm2(vect, &iter.value);
      if (nrm2 >= 0.f)
        {
          vect->priv->nrm2 += nrm2;
          vect->priv->min = MIN(vect->priv->min, nrm2);
          vect->priv->max = MAX(vect->priv->max, nrm2);
        }
      valid = visu_node_values_iter_next(&iter);
    }
  vect->priv->min = sqrt(vect->priv->min);
  vect->priv->max = sqrt(vect->priv->max);

  ln = visu_node_values_getDimension(VISU_NODE_VALUES(vect));
  g_array_set_size(vect->priv->readMinMax, ln);
  /* Check for minMax values for each column. */
  for (i = 0; i < ln; i++)
    g_array_insert_vals(vect->priv->readMinMax, i, init, 1);
  for (visu_node_values_iter_new(&iter, ITER_NODES_BY_TYPE,
                                 VISU_NODE_VALUES(vect));
       iter.iter.node; visu_node_values_iter_next(&iter))
    {
      data = (float*)g_value_get_pointer(&iter.value);
      if (!data)
        continue;
      for (i = 0; i < ln; i++)
        {
          minmax = &g_array_index(vect->priv->readMinMax, float, i * 2);
          if (data[i] < minmax[0])
            minmax[0] = data[i];
          if (data[i] > minmax[1])
            minmax[1] = data[i];
        }
    }
}
/**
 * visu_node_values_farray_getAt:
 * @vect: a #VisuNodeValuesFarray object.
 * @node: a #VisuNode object.
 *
 * Retrieves the float array hosted on @node.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the coordinates of
 * float array for @node.
 **/
const gfloat* visu_node_values_farray_getAt(VisuNodeValuesFarray *vect,
                                            const VisuNode *node)
{
  GValue diffValue = G_VALUE_INIT;
  const gfloat *diff;

  g_return_val_if_fail(VISU_IS_NODE_VALUES_FARRAY(vect), NULL);

  visu_node_values_getAt(VISU_NODE_VALUES(vect), node, &diffValue);
  diff = (const gfloat*)g_value_get_pointer(&diffValue);
  return diff;  /* ? diff : vect->priv->zeros; */
}

/**
 * visu_node_values_farray_getAtIter:
 * @vect: a #VisuNodeValuesFarray object.
 * @iter: an iterator on @vect.
 *
 * Provide the floats stored in @vect at @iter.
 *
 * Since: 3.8
 *
 * Returns: a pointer to the float array.
 **/
const gfloat* visu_node_values_farray_getAtIter(const VisuNodeValuesFarray *vect,
                                                const VisuNodeValuesIter *iter)
{
  g_return_val_if_fail(VISU_IS_NODE_VALUES_FARRAY(vect), NULL);
  g_return_val_if_fail(iter, NULL);

  return (const gfloat*)g_value_get_pointer(&iter->value);
}

/**
 * visu_node_values_farray_getFloatAt:
 * @vect: a #VisuNodeValuesFarray object.
 * @node: a #VisuNode pointer.
 * @column: a column id.
 *
 * Retrieves the float value stored for @node at @column, if any.
 *
 * Since: 3.8
 *
 * Returns: a float value.
 **/
gfloat visu_node_values_farray_getFloatAt(const VisuNodeValuesFarray *vect,
                                          const VisuNode *node,
                                          guint column)
{
  GValue diffValue = G_VALUE_INIT;
  const gfloat *vals;

  g_return_val_if_fail(column < visu_node_values_getDimension(VISU_NODE_VALUES(vect)), 0.f);

  visu_node_values_getAt(VISU_NODE_VALUES(vect), node, &diffValue);
  vals = (const gfloat*)g_value_get_pointer(&diffValue);
  
  return vals ? vals[column] : 0.f;
}

/**
 * visu_node_values_farray_getFloatAtIter:
 * @vect: a #VisuNodeValuesFarray object.
 * @iter: a #VisuNodeValuesIter object.
 * @column: a column id.
 *
 * Retrieves the float value stored for the current iteration of @iter
 * for the column @column. @iter must be running on @vect.
 *
 * Since: 3.8
 *
 * Returns: a float value.
 **/
gfloat visu_node_values_farray_getFloatAtIter(const VisuNodeValuesFarray *vect,
                                              const VisuNodeValuesIter *iter,
                                              guint column)
{
  const gfloat *vals;

  g_return_val_if_fail(iter && iter->vals == VISU_NODE_VALUES(vect), 0.f);
  g_return_val_if_fail(VISU_IS_NODE_VALUES_FARRAY(vect), 0.f);
  g_return_val_if_fail(column < visu_node_values_getDimension(VISU_NODE_VALUES(vect)), 0.f);

  vals = (const gfloat*)g_value_get_pointer(&iter->value);
  
  return vals ? vals[column] : 0.f;
}

static gboolean _setAt(VisuNodeValues *vect, const VisuNode *node,
                       GValue *value)
{  
  gboolean res;

  VISU_NODE_VALUES_FARRAY(vect)->priv->dirty = TRUE;

  /* Chain up to parent. */
  res = VISU_NODE_VALUES_CLASS(visu_node_values_farray_parent_class)->setAt(vect, node, value);
  
  if (res)
    {
      g_object_notify_by_pspec(G_OBJECT(vect), _properties[READ_MM_PROP]);
      g_object_notify_by_pspec(G_OBJECT(vect), _properties[MIN_PROP]);
      g_object_notify_by_pspec(G_OBJECT(vect), _properties[MAX_PROP]);
      g_object_notify_by_pspec(G_OBJECT(vect), _properties[NRM2_PROP]);
    }

  return res;
}
/**
 * visu_node_values_farray_setAt:
 * @vect: a #VisuNodeValuesFarray object.
 * @node: a #VisuNode object.
 * @vals: (array length=ln): farray coordinates.
 * @ln: a length.
 *
 * Changes the float array hosted at @node for one of values defined
 * by @vals.
 *
 * Since: 3.8
 *
 * Returns: TRUE if farray for @node is indeed changed.
 **/
gboolean visu_node_values_farray_setAt(VisuNodeValuesFarray *vect,
                                       const VisuNode *node,
                                       const gfloat *vals,
                                       guint ln)
{
  gfloat *old;
  guint i;
  gboolean changed;
  GValue value = {0, {{0}, {0}}};

  g_return_val_if_fail(visu_node_values_getDimension(VISU_NODE_VALUES(vect)) == ln, FALSE);

  visu_node_values_getAt(VISU_NODE_VALUES(vect), node, &value);
  old = (gfloat*)g_value_get_pointer(&value);
  if (old)
    {
      changed = FALSE;
      for (i = 0; i < ln && !changed; i++)
        changed = (old[i] != vals[i]);
      if (!changed)
        return FALSE;
    }

  g_value_set_pointer(&value, (gpointer)vals);
  return visu_node_values_setAt(VISU_NODE_VALUES(vect), node, &value);
}
/**
 * visu_node_values_farray_setAtDbl:
 * @vect: a #VisuNodeValuesFarray object.
 * @node: a #VisuNode object.
 * @vals: (array fixed-size=3): farray coordinates.
 * @ln: a length.
 *
 * Same as visu_node_values_farray_setAt() but for double values.
 *
 * Since: 3.8
 *
 * Returns: TRUE if farray for @node is indeed changed.
 **/
gboolean visu_node_values_farray_setAtDbl(VisuNodeValuesFarray *vect,
                                          const VisuNode *node,
                                          const double *vals,
                                          guint ln)
{
  guint i;
  gfloat *farr;
  gboolean ret;

  g_return_val_if_fail(visu_node_values_getDimension(VISU_NODE_VALUES(vect)) == ln, FALSE);
  
  farr = g_malloc(sizeof(gfloat) * ln);
  for (i = 0; i < ln; i++)
    farr[i] = vals[i];
  ret = visu_node_values_farray_setAt(vect, node, farr, ln);
  g_free(farr);
  return ret;
}
/**
 * visu_node_values_farray_set:
 * @vect: a #VisuNodeValuesFarray object.
 * @data: (element-type float): some farray coordinates.
 *
 * Assigns the coordinates stored in @data to each nodes in @vect.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @data has the same size as @vect.
 **/
gboolean visu_node_values_farray_set(VisuNodeValuesFarray *vect,
                                     const GArray *data)
{
  guint i, ln;
  gboolean valid;
  VisuNodeValuesIter iter;

  ln = visu_node_values_getDimension(VISU_NODE_VALUES(vect));
  g_return_val_if_fail(data && data->len % ln == 0, FALSE);

  g_object_freeze_notify(G_OBJECT(vect));

  i = 0;
  valid = visu_node_values_iter_new(&iter, ITER_NODES_BY_NUMBER,
                                    VISU_NODE_VALUES(vect));
  while (valid && i + ln <= data->len)
    {
      visu_node_values_farray_setAt(vect, iter.iter.node,
                                    &g_array_index(data, float, i),
                                    ln);
      i += ln;
      valid = visu_node_values_iter_next(&iter);
    }

  g_object_thaw_notify(G_OBJECT(vect));

  return (i == data->len);
}

/**
 * visu_node_values_farray_getFile:
 * @vect: a #VisuNodeValuesFarray object.
 *
 * Retrieve the filename from which the values have been read, if any.
 *
 * Since: 3.8
 *
 * Returns: a filename.
 **/
const gchar* visu_node_values_farray_getFile(const VisuNodeValuesFarray *vect)
{
  g_return_val_if_fail(VISU_IS_NODE_VALUES_FARRAY(vect), (const gchar*)0);

  return vect->priv->file;
}

/**
 * visu_node_values_farray_scale:
 * @vect: a #VisuNodeValuesFarray object.
 * @factor: a factor.
 *
 * Multiply every element of @vect by @factor.
 *
 * Since: 3.8
 **/
void visu_node_values_farray_scale(VisuNodeValuesFarray *vect, gfloat factor)
{
  guint i, ln;
  VisuNodeValuesIter iter;
  gfloat *data;

  g_return_if_fail(VISU_IS_NODE_VALUES_FARRAY(vect));

  if (factor == 1.f)
    return;

  vect->priv->dirty = TRUE;

  ln = visu_node_values_getDimension(VISU_NODE_VALUES(vect));
  for (visu_node_values_iter_new(&iter, ITER_NODES_BY_TYPE,
                                 VISU_NODE_VALUES(vect));
       iter.iter.node; visu_node_values_iter_next(&iter))
    {
        data = (gfloat*)g_value_get_pointer(&iter.value);
        if (!data)
          continue;
        for (i = 0; i < ln; i++)
          data[i] *= factor;
    }
}
