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

	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 "gdiff.h"
#include <coreTools/toolPhysic.h>

/**
 * SECTION:gdiff
 * @short_description: a #VisuNodeValues object to store displacements
 * between to #VisuData objects.
 *
 * <para></para>
 */

struct _VisuDataDiffPrivate
{
  gboolean dispose_has_run;

  gboolean empty;
  
  VisuBox *box;
  gulong sig_units;
};

static void visu_data_diff_dispose(GObject *obj);

G_DEFINE_TYPE_WITH_CODE(VisuDataDiff, visu_data_diff, VISU_TYPE_NODE_VALUES_VECTOR,
                        G_ADD_PRIVATE(VisuDataDiff))

static void visu_data_diff_class_init(VisuDataDiffClass *klass)
{
  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose = visu_data_diff_dispose;
}

static void visu_data_diff_init(VisuDataDiff *self)
{
  self->priv = visu_data_diff_get_instance_private(self);
  self->priv->dispose_has_run = FALSE;
  self->priv->empty = TRUE;
  self->priv->box = (VisuBox*)0;
}

static void visu_data_diff_dispose(GObject *obj)
{
  VisuDataDiff *self = VISU_DATA_DIFF(obj);

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

  g_object_unref(self->priv->box);

  G_OBJECT_CLASS(visu_data_diff_parent_class)->dispose(obj);
}

static void _setBox(VisuDataDiff *self, VisuBox *box)
{
  g_return_if_fail(VISU_IS_DATA_DIFF(self));

  if (self->priv->box == box)
    return;

  if (self->priv->box)
    {
      g_signal_handler_disconnect(self->priv->box, self->priv->sig_units);
      g_object_unref(self->priv->box);
    }
  if (box)
    {
      g_object_ref(box);
      self->priv->sig_units =
        g_signal_connect_swapped(box, "UnitChanged",
                                 G_CALLBACK(visu_node_values_farray_scale), self);
    }
  visu_node_values_farray_scale(VISU_NODE_VALUES_FARRAY(self),
                                (self->priv->box && box)
                                ? tool_physic_getUnitConversionFactor
                                  (visu_box_getUnit(self->priv->box),
                                   visu_box_getUnit(box))
                                : 1.f);
  self->priv->box = box;
}

static void _getPeriodicDistance(float diff[3],
                                 const VisuData *data1, const VisuNode *node1,
                                 const VisuData *data2, const VisuNode *node2,
                                 float factor)
{
  float xyz1[3], xyz2[3];

  visu_data_getNodePosition(data1, node1, xyz1);
  visu_data_getNodePosition(data2, node2, xyz2);
  diff[0] = xyz1[0] - xyz2[0] * factor;
  diff[1] = xyz1[1] - xyz2[1] * factor;
  diff[2] = xyz1[2] - xyz2[2] * factor;

  visu_box_getPeriodicVector(visu_boxed_getBox(VISU_BOXED(data1)), diff);
}

/**
 * visu_data_diff_new:
 * @dataRef: a #VisuData object.
 * @data: a #VisuData object.
 * @reorder: a boolean.
 * @label: a label.
 *
 * Compute the node per node position differences between @dataRef and
 * @data. If @reorder is %TRUE, the node in @data are reordered to
 * minimize the distance with @dataRef. If @dataRef and @data are
 * incompatible, the returned object will be empty, see visu_data_diff_isEmpty().
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a newly created #VisuDataDiff object.
 **/
VisuDataDiff* visu_data_diff_new(const VisuData *dataRef, VisuData *data, gboolean reorder, const gchar *label)
{
  VisuDataDiff *data_diff;
  VisuNodeArrayIter iter, iterRef;
  float diff[3], factor;

  data_diff = g_object_new(VISU_TYPE_DATA_DIFF, "nodes", data, "label", label,
                           "type", G_TYPE_FLOAT, "n-elements", 6, NULL);

  /* Check compatibility of population. */
  if (visu_node_array_getNNodes(VISU_NODE_ARRAY(data)) !=
      visu_node_array_getNNodes(VISU_NODE_ARRAY_CONST(dataRef)))
    return data_diff;

  /* if (iter.nElements != iterRef.nElements) */
  /*   return data_diff; */
  /* for (visu_node_array_iterStart(VISU_NODE_ARRAY(data), &iter), */
  /*        visu_node_array_iterStart(VISU_NODE_ARRAY(dataRef), &iterRef); */
  /*      iter.element && iterRef.element; */
  /*      visu_node_array_iterNextElement(VISU_NODE_ARRAY(data), &iter, FALSE), */
  /*        visu_node_array_iterNextElement(VISU_NODE_ARRAY(dataRef), &iterRef, FALSE)) */
  /*   if (iter.nStoredNodes != iterRef.nStoredNodes) */
  /*     return data_diff; */

  if (reorder)
    visu_data_reorder(data, dataRef);

  factor = tool_physic_getUnitConversionFactor
    (visu_box_getUnit(visu_boxed_getBox(VISU_BOXED(dataRef))),
     visu_box_getUnit(visu_boxed_getBox(VISU_BOXED(data))));
  visu_node_array_iter_new(VISU_NODE_ARRAY(data), &iter);
  visu_node_array_iter_new(VISU_NODE_ARRAY((VisuNodeArray*)dataRef), &iterRef);
  for (visu_node_array_iterStartNumber(VISU_NODE_ARRAY(data), &iter),
         visu_node_array_iterStartNumber(VISU_NODE_ARRAY((VisuNodeArray*)dataRef), &iterRef);
       iter.node && iterRef.node;
       visu_node_array_iterNextNodeNumber(VISU_NODE_ARRAY(data), &iter),
         visu_node_array_iterNextNodeNumber(VISU_NODE_ARRAY((VisuNodeArray*)dataRef), &iterRef))
    {
      _getPeriodicDistance(diff, data, iter.node, dataRef, iterRef.node, factor);
      visu_node_values_vector_setAt(VISU_NODE_VALUES_VECTOR(data_diff), iter.node, diff);
    }

  g_signal_connect_object(data, "setBox", G_CALLBACK(_setBox),
                          data_diff, G_CONNECT_SWAPPED);
  _setBox(data_diff, visu_boxed_getBox(VISU_BOXED(data)));

  data_diff->priv->empty = FALSE;

  return data_diff;
}

/**
 * visu_data_diff_isEmpty:
 * @self: a #VisuDataDiff object.
 *
 * When creating a #VisuDataDiff, if the reference and the data don't
 * have the same number of types or atoms, the difference cannot be
 * computed and the created object is empty.
 *
 * Since: 3.8
 *
 * Returns: TRUE if there is no difference data stored.
 **/
gboolean visu_data_diff_isEmpty(const VisuDataDiff *self)
{
  g_return_val_if_fail(VISU_IS_DATA_DIFF(self), TRUE);

  return self->priv->empty;
}

/**
 * visu_data_diff_export:
 * @self: a #VisuDataDiff object.
 *
 * Create a string with differences of coordinates stored in @self in
 * cartesian coordinates.
 *
 * Since: 3.6
 *
 * Returns: a new string that should be freed after use.
 */
gchar* visu_data_diff_export(const VisuDataDiff *self)
{
  GString *output;
  gboolean start;
  VisuNodeValuesIter iter;
  gfloat *data;
  gfloat zeros[3] = {0.f, 0.f, 0.f};

  g_return_val_if_fail(VISU_IS_DATA_DIFF(self), (gchar*)0);

  if (self->priv->empty)
    return (gchar*)0;

  start = TRUE;
  output = g_string_new("#metaData: diff=[ \\\n");

  for (visu_node_values_iter_new(&iter, ITER_NODES_BY_TYPE,
                                 VISU_NODE_VALUES(self));
       iter.iter.node; visu_node_values_iter_next(&iter))
    {
        data = (gfloat*)g_value_get_pointer(&iter.value);
        if (!data)
          data = zeros;

        if (!start)
          output = g_string_append(output, "; \\\n");
        g_string_append_printf(output, "# %12.8f; %12.8f; %12.8f",
                               data[0], data[1], data[2]);
        start = FALSE;
    }
  output = g_string_append(output, " \\\n# ]\n");

  return g_string_free(output, FALSE);
}

/**
 * visu_data_diff_applyWithFinder:
 * @geodiff: a #VisuDataDiff object with a displacement field.
 * @finder: a #VisuNodeFinder to apply the displacement on.
 * @tol: a tolerance.
 *
 * For each node displacement of @geodiff, if a matching node in
 * @finder is found within the tolerance @tol, then the displacement
 * is applied.
 *
 * Since: 3.8
 **/
void visu_data_diff_applyWithFinder(const VisuDataDiff *geodiff,
                                    VisuNodeFinder *finder, gfloat tol)
{
  VisuData *data;
  VisuNodeArray *target;
  VisuNodeValuesIter iter;
  gfloat xyz[3];
  const gfloat *diff;
  gint id;

  data = VISU_DATA(visu_node_values_getArray(VISU_NODE_VALUES(geodiff)));
  target = VISU_NODE_ARRAY(visu_node_finder_getData(finder));

  visu_node_array_startMoving(target);
  for (visu_node_values_iter_new(&iter, ITER_NODES_BY_TYPE,
                                 VISU_NODE_VALUES(geodiff));
       iter.iter.node; visu_node_values_iter_next(&iter))
    {
      visu_data_getNodePosition(data, iter.iter.node, xyz);
      id = visu_node_finder_lookup(finder, xyz, tol);
      if (id >= 0 && (diff = visu_node_values_farray_getAtIter
                      (VISU_NODE_VALUES_FARRAY(geodiff), &iter)))
        visu_node_array_shiftNode(target, id, diff);
    }
  visu_node_array_completeMoving(target);
  g_object_unref(data);
}
