/*   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 "sym.h"

#include <msym.h>
#include <string.h>

#include <gio/gio.h>

/**
 * SECTION:mol_symmetry
 * @short_description:
 *
 */

struct _VisuMolSymmetryOpPrivate
{
  gboolean dispose_has_run;

  guint id;
  VisuMolSymmetry *parent;
  msym_symmetry_operation_t op;

  VisuPlane *plane;
};

struct _VisuMolSymmetryPrivate
{
  gboolean dispose_has_run;

  VisuData *data;
  gulong popInc, popDec, posChg;
  GArray *nodes;

  msym_context ctx;
  msym_thresholds_t thresholds;
  GArray *syms;
  
  gboolean dirtyCtx;
  gboolean lock;
};

enum
  {
    PROP_0,
    DATA_PROP,
    NODES_PROP,
    TYPE_PROP,
    THRESH_GEO_PROP,
    THRESH_ANG_PROP,
    NSYM_PROP,
    SYMS_PROP,
    LOCK_PROP,
    N_PROP
  };
static GParamSpec *_properties[N_PROP];

static void visu_mol_symmetry_dispose     (GObject* obj);
static void visu_mol_symmetry_get_property(GObject* obj, guint property_id,
                                            GValue *value, GParamSpec *pspec);
static void visu_mol_symmetry_set_property(GObject* obj, guint property_id,
                                            const GValue *value, GParamSpec *pspec);
static void visu_list_model_interface_init(GListModelInterface *iface);
static GType _getItemType(GListModel *list);
static guint _getNItems(GListModel *list);
static gpointer _getItem(GListModel *list, guint at);

G_DEFINE_TYPE_WITH_CODE(VisuMolSymmetry, visu_mol_symmetry, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuMolSymmetry)
                        G_IMPLEMENT_INTERFACE(G_TYPE_LIST_MODEL,
                                              visu_list_model_interface_init))

/* Local callbacks. */
static void _positionChanged(VisuMolSymmetry *symmetry, GArray *ids);

/* Local routines. */
static gchar* _groupLabel(VisuMolSymmetry *symmetry);
static void _symmetrize(VisuMolSymmetry *symmetry, const gchar *group);
static void _setThresholds(VisuMolSymmetry *symmetry);

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

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_mol_symmetry_dispose;
  G_OBJECT_CLASS(klass)->set_property = visu_mol_symmetry_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_mol_symmetry_get_property;

  /**
   * VisuMolSymmetry::data:
   *
   * The #VisuData this symmetry is operating on.
   *
   * Since: 3.8
   */
  _properties[DATA_PROP] = g_param_spec_object
    ("data", "Data", "data model.", VISU_TYPE_DATA, G_PARAM_READWRITE);
  /**
   * VisuMolSymmetry::nodes: (element-type uint):
   *
   * The ids of selected nodes.
   *
   * Since: 3.8
   */
  _properties[NODES_PROP] = g_param_spec_boxed
    ("nodes", "Nodes", "ids of selected nodes.",
     G_TYPE_ARRAY, G_PARAM_READWRITE);
  /**
   * VisuMolSymmetry::type:
   *
   * The point group type.
   *
   * Since: 3.8
   */
  _properties[TYPE_PROP] = g_param_spec_string
    ("type", "Type", "point group type.", "", G_PARAM_READWRITE);
  /**
   * VisuMolSymmetry::threshold-geometry:
   *
   * The geometry threshold.
   *
   * Since: 3.8
   */
  _properties[THRESH_GEO_PROP] = g_param_spec_double
    ("threshold-geometry", "Geometry threshold", "threshold for geometry structures.",
     0., 1., 0., G_PARAM_READWRITE);
  /**
   * VisuMolSymmetry::threshold-angle:
   *
   * The angle threshold for parallelism.
   *
   * Since: 3.8
   */
  _properties[THRESH_ANG_PROP] = g_param_spec_double
    ("threshold-angle", "Angle threshold", "threshold for parallelism.",
     0., 1., 0., G_PARAM_READWRITE);
  /**
   * VisuMolSymmetry::n-symmetries:
   *
   * The number of symmetry operations.
   *
   * Since: 3.8
   */
  _properties[NSYM_PROP] = g_param_spec_uint
    ("n-symmetries", "N symmetries", "number of symmetry operations.",
     0, G_MAXUINT, 0, G_PARAM_READABLE);
  /**
   * VisuMolSymmetry::symmetry-operations: (element-type VisuMolSymmetryOp*):
   *
   * The symmetry operations.
   *
   * Since: 3.8
   */
  _properties[SYMS_PROP] = g_param_spec_boxed
    ("symmetry-operations", "Symmetry operations", "symmetry operations.",
     G_TYPE_ARRAY, G_PARAM_READABLE);
  /**
   * VisuMolSymmetry::lock-geometry:
   *
   * Wether the node geometry should be locked to given symmetry.
   *
   * Since: 3.8
   */
  _properties[LOCK_PROP] = g_param_spec_boolean
    ("lock-geometry", "Lock geometry", "lock node geometry in the given symmetry.",
     FALSE, G_PARAM_READWRITE);
  
  g_object_class_install_properties(G_OBJECT_CLASS(klass), N_PROP, _properties);
}
static void visu_list_model_interface_init(GListModelInterface *iface)
{
  iface->get_item_type = _getItemType;
  iface->get_n_items   = _getNItems;
  iface->get_item      = _getItem;
}

static void visu_mol_symmetry_init(VisuMolSymmetry *list)
{
  DBG_fprintf(stderr, "Visu MolSymmetry: initializing a new object (%p).\n",
	      (gpointer)list);
  list->priv = visu_mol_symmetry_get_instance_private(list);
  list->priv->dispose_has_run = FALSE;

  list->priv->data  = (VisuData*)0;
  list->priv->nodes = (GArray*)0;
  list->priv->ctx   = NULL;
  list->priv->syms  = (GArray*)0;
  list->priv->lock  = TRUE;
}

static void visu_mol_symmetry_dispose(GObject* obj)
{
  VisuMolSymmetry *list;

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

  list = VISU_MOL_SYMMETRY(obj);
  if (list->priv->dispose_has_run)
    return;
  list->priv->dispose_has_run = TRUE;

  visu_mol_symmetry_setData(list, (VisuData*)0);
  visu_mol_symmetry_setNodes(list, (GArray*)0);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_mol_symmetry_parent_class)->dispose(obj);
}

static void visu_mol_symmetry_get_property(GObject* obj, guint property_id,
                                            GValue *value, GParamSpec *pspec)
{
  VisuMolSymmetry *self = VISU_MOL_SYMMETRY(obj);
  
  DBG_fprintf(stderr, "Visu MolSymmetry: get property '%s' -> ",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case DATA_PROP:
      g_value_set_object(value, self->priv->data);
      DBG_fprintf(stderr, "%p.\n", g_value_get_object(value));
      break;
    case NODES_PROP:
      g_value_set_boxed(value, self->priv->nodes);
      DBG_fprintf(stderr, "%p.\n", g_value_get_boxed(value));
      break;
    case TYPE_PROP:
      g_value_take_string(value, _groupLabel(self));
      DBG_fprintf(stderr, "%s.\n", g_value_get_string(value));
      break;
    case THRESH_GEO_PROP:
      g_value_set_double(value, self->priv->thresholds.geometry);
      DBG_fprintf(stderr, "%g.\n", g_value_get_double(value));
      break;
    case THRESH_ANG_PROP:
      g_value_set_double(value, self->priv->thresholds.angle);
      DBG_fprintf(stderr, "%g.\n", g_value_get_double(value));
      break;
    case NSYM_PROP:
      g_value_set_uint(value, self->priv->syms ? self->priv->syms->len : 0);
      DBG_fprintf(stderr, "%d.\n", g_value_get_uint(value));
      break;
    case SYMS_PROP:
      g_value_set_boxed(value, self->priv->syms);
      DBG_fprintf(stderr, "%p.\n", g_value_get_boxed(value));
      break;
    case LOCK_PROP:
      g_value_set_boolean(value, self->priv->lock);
      DBG_fprintf(stderr, "%d.\n", g_value_get_boolean(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_mol_symmetry_set_property(GObject* obj, guint property_id,
                                            const GValue *value, GParamSpec *pspec)
{
  VisuMolSymmetry *self = VISU_MOL_SYMMETRY(obj);

  DBG_fprintf(stderr, "Visu MolSymmetry: set property '%s'.\n",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case DATA_PROP:
      visu_mol_symmetry_setData(self, VISU_DATA(g_value_get_object(value)));
      break;
    case NODES_PROP:
      visu_mol_symmetry_setNodes(self, (GArray*)g_value_get_boxed(value));
      break;
    case TYPE_PROP:
      _symmetrize(self, g_value_get_string(value));
      break;
    case THRESH_GEO_PROP:
      self->priv->thresholds.geometry = g_value_get_double(value);
      _setThresholds(self);
      break;
    case THRESH_ANG_PROP:
      self->priv->thresholds.angle = g_value_get_double(value);
      _setThresholds(self);
      break;
    case LOCK_PROP:
      visu_mol_symmetry_lock(self, g_value_get_boolean(value));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_mol_symmetry_new:
 *
 * Create a new #GtkListStore to store planes.
 *
 * Since: 3.8
 *
 * Returns: a newly created object.
 **/
VisuMolSymmetry* visu_mol_symmetry_new()
{
  VisuMolSymmetry *list;

  list = VISU_MOL_SYMMETRY(g_object_new(VISU_TYPE_MOL_SYMMETRY, NULL));
  return list;
}

static void _releaseContext(VisuMolSymmetry *symmetry)
{
  guint nold;
  
  g_return_if_fail(VISU_IS_MOL_SYMMETRY_TYPE(symmetry));
  
  if (symmetry->priv->ctx)
    {
      msymReleaseContext(symmetry->priv->ctx);
      symmetry->priv->ctx = NULL;
    }
  nold = 0;
  if (symmetry->priv->syms)
    {
      nold = symmetry->priv->syms->len;
      g_array_unref(symmetry->priv->syms);
    }
  symmetry->priv->syms = (GArray*)0;
  if (nold)
    g_list_model_items_changed(G_LIST_MODEL(symmetry), 0, nold, 0);
}

static void _setContext(VisuMolSymmetry *symmetry)
{
  msym_element_t *elems;
  guint i;
  VisuNode *node;
  const gchar *name;
  msym_error_t err;
  float mass;
  const msym_thresholds_t *thresholds;

  g_return_if_fail(VISU_IS_MOL_SYMMETRY_TYPE(symmetry));
  
  _releaseContext(symmetry);
  
  if (!symmetry->priv->data || !symmetry->priv->nodes ||
      symmetry->priv->nodes->len < 3)
    {
      g_object_notify_by_pspec(G_OBJECT(symmetry), _properties[TYPE_PROP]);
      return;
    }
  
  symmetry->priv->ctx = msymCreateContext();
  g_return_if_fail(symmetry->priv->ctx);

  elems = g_malloc(sizeof(msym_element_t) * symmetry->priv->nodes->len);
  for (i = 0; i < symmetry->priv->nodes->len; i++)
    {
      float xyz[3];

      node = visu_node_array_getFromId(VISU_NODE_ARRAY(symmetry->priv->data),
                                       g_array_index(symmetry->priv->nodes, guint, i));
      g_return_if_fail(node);
      name = visu_element_getName(visu_node_array_getElement(VISU_NODE_ARRAY(symmetry->priv->data),
                                                             node));
      visu_data_getNodePosition(symmetry->priv->data, node, xyz);
      elems[i].v[0] = xyz[0];
      elems[i].v[1] = xyz[1];
      elems[i].v[2] = xyz[2];
      tool_physic_getZFromSymbol(&elems[i].n, NULL, &mass, name);
      elems[i].m = mass;
      strncpy(elems[i].name, name, 3);
      elems[i].name[3] = '\0';
    }
  if ((err = msymSetElements(symmetry->priv->ctx,
                             symmetry->priv->nodes->len, elems)) != MSYM_SUCCESS)
    goto error;
  elems = NULL;

  if ((err = msymGetThresholds(symmetry->priv->ctx, &thresholds)) != MSYM_SUCCESS)
    goto error;
  symmetry->priv->thresholds = *thresholds;

  symmetry->priv->dirtyCtx = TRUE;
  g_object_notify_by_pspec(G_OBJECT(symmetry), _properties[TYPE_PROP]);
  return;

 error:
  g_warning("%s: %s.", msymErrorString(err), msymGetErrorDetails());
  g_free(elems);
  msymReleaseContext(symmetry->priv->ctx);
  symmetry->priv->ctx = NULL;
  return;
}
static gboolean _resetContext(gpointer symmetry)
{
  _setContext(VISU_MOL_SYMMETRY(symmetry));
  return G_SOURCE_REMOVE;
}

/**
 * visu_mol_symmetry_setData:
 * @selection: a #VisuMolSymmetry object.
 * @data: (allow-none): a #VisuData object.
 *
 * Bind the @set object to @list.
 *
 * Returns: TRUE if @set is changed
 **/
gboolean visu_mol_symmetry_setData(VisuMolSymmetry *symmetry, VisuData *data)
{  
  g_return_val_if_fail(VISU_IS_MOL_SYMMETRY_TYPE(symmetry), FALSE);

  if (symmetry->priv->data == data)
    return FALSE;

  _releaseContext(symmetry);

  if (symmetry->priv->data)
    {
      g_signal_handler_disconnect(symmetry->priv->data, symmetry->priv->popInc);
      g_signal_handler_disconnect(symmetry->priv->data, symmetry->priv->popDec);
      g_signal_handler_disconnect(symmetry->priv->data, symmetry->priv->posChg);
      g_object_unref(symmetry->priv->data);
    }
  symmetry->priv->data = data;
  if (data)
    {
      g_object_ref(data);
      symmetry->priv->popInc = g_signal_connect_swapped
        (data, "PopulationIncrease", G_CALLBACK(_setContext), symmetry);
      symmetry->priv->popDec = g_signal_connect_swapped
        (data, "PopulationDecrease", G_CALLBACK(_setContext), symmetry);
      symmetry->priv->posChg = g_signal_connect_swapped
        (data, "position-changed", G_CALLBACK(_positionChanged), symmetry);
      _setContext(symmetry);
    }

  return TRUE;
}

/**
 * visu_mol_symmetry_setData:
 * @selection: a #VisuMolSymmetry object.
 * @data: (allow-none): a #VisuData object.
 *
 * Bind the @set object to @list.
 *
 * Returns: TRUE if @set is changed
 **/
gboolean visu_mol_symmetry_setNodes(VisuMolSymmetry *symmetry, GArray *nodes)
{  
  g_return_val_if_fail(VISU_IS_MOL_SYMMETRY_TYPE(symmetry), FALSE);

  if (symmetry->priv->nodes)
    {
      g_array_unref(symmetry->priv->nodes);
    }
  symmetry->priv->nodes = nodes;
  if (nodes)
    {
      g_array_ref(nodes);
    }
  _setContext(symmetry);

  return TRUE;
}

static void _clearSyms(gpointer *item)
{
  g_object_unref(*item);
}

static void _compute(VisuMolSymmetry *symmetry)
{
  msym_error_t err;
  guint nold;
  int i, nsym;
  const msym_symmetry_operation_t *syms;
  VisuMolSymmetryOp *op;
  
  g_return_if_fail(VISU_IS_MOL_SYMMETRY_TYPE(symmetry));

  if (!symmetry->priv->ctx)
    return;

  DBG_fprintf(stderr, "Msym: computing symmetries.\n");
  if ((err = msymFindSymmetry(symmetry->priv->ctx)) != MSYM_SUCCESS)
    g_message("%s: %s.", msymErrorString(err), msymGetErrorDetails());

  if ((err = msymGetSymmetryOperations(symmetry->priv->ctx, &nsym,
                                       &syms)) != MSYM_SUCCESS)
    g_message("%s: %s.", msymErrorString(err), msymGetErrorDetails());

  nold = 0;
  if (symmetry->priv->syms)
    {
      nold = symmetry->priv->syms->len;
      g_array_unref(symmetry->priv->syms);
    }
  symmetry->priv->syms = g_array_sized_new(FALSE, FALSE,
                                           sizeof(VisuMolSymmetryOp*), nsym);
  g_array_set_clear_func(symmetry->priv->syms, (GDestroyNotify)_clearSyms);
  for (i = 0; i < nsym; i++)
    {
      op = g_object_new(VISU_TYPE_MOL_SYMMETRY_OP, NULL);
      op->priv->id = (guint)i;
      op->priv->parent = g_object_ref(symmetry);
      op->priv->op = syms[i];
      g_array_append_val(symmetry->priv->syms, op);
    }

  if ((err = msymFindEquivalenceSets(symmetry->priv->ctx)) != MSYM_SUCCESS)
    g_message("%s: %s.", msymErrorString(err), msymGetErrorDetails());

  symmetry->priv->dirtyCtx = FALSE;
  
  g_object_notify_by_pspec(G_OBJECT(symmetry), _properties[NSYM_PROP]);
  g_list_model_items_changed(G_LIST_MODEL(symmetry), 0, nold, nsym);
}

static gchar* _groupLabel(VisuMolSymmetry *symmetry)
{
  msym_error_t err;
  char buf[256];
  
  g_return_val_if_fail(VISU_IS_MOL_SYMMETRY_TYPE(symmetry), (gchar*)0);

  if (!symmetry->priv->ctx)
    return (gchar*)0;

  if (symmetry->priv->dirtyCtx)
    _compute(symmetry);

  if ((err = msymGetPointGroupName(symmetry->priv->ctx, 256, buf)) != MSYM_SUCCESS)
    {
      g_message("%s: %s.", msymErrorString(err), msymGetErrorDetails());
      return (gchar*)0;
    }

  return g_strdup(buf);
}

static void _setThresholds(VisuMolSymmetry *symmetry)
{
  msym_error_t err;

  g_return_if_fail(VISU_IS_MOL_SYMMETRY_TYPE(symmetry));

  if (!symmetry->priv->ctx)
    return;
  
  if ((err = msymSetThresholds(symmetry->priv->ctx,
                               &symmetry->priv->thresholds)) != MSYM_SUCCESS)
    g_message("%s: %s.", msymErrorString(err), msymGetErrorDetails());
  
  symmetry->priv->dirtyCtx = TRUE;
  g_object_notify_by_pspec(G_OBJECT(symmetry), _properties[TYPE_PROP]);
}

static void _symmetrize(VisuMolSymmetry *symmetry, const gchar *group)
{
  msym_error_t err;
  double max;
  guint i;
  VisuNode *node;
  GArray *xyz;
  int nelems;
  msym_element_t *elems;

  g_return_if_fail(VISU_IS_MOL_SYMMETRY_TYPE(symmetry));

  if (!symmetry->priv->ctx)
    return;
  
  if ((err = msymSetPointGroupByName(symmetry->priv->ctx, group)) != MSYM_SUCCESS)
    {
      g_message("here");
      g_message("%s: %s.", msymErrorString(err), msymGetErrorDetails());
      return;
    }
  
  if ((err = msymSymmetrizeElements(symmetry->priv->ctx, &max)) != MSYM_SUCCESS)
    {
      g_message("there");
      g_message("%s: %s.", msymErrorString(err), msymGetErrorDetails());
      g_idle_add(_resetContext, symmetry);
      return;
    }

  if ((err = msymGetElements(symmetry->priv->ctx, &nelems, &elems)) != MSYM_SUCCESS)
    {
      g_message("%s: %s.", msymErrorString(err), msymGetErrorDetails());
      return;
    }
  g_return_if_fail((guint)nelems == symmetry->priv->nodes->len);
  
  g_message("Max error when symmetrizing: %g.\n", max);

  xyz = g_array_new(FALSE, FALSE, sizeof(float) * 3 * symmetry->priv->nodes->len);
  for (i = 0; i < symmetry->priv->nodes->len; i++)
    {
      node = visu_node_array_getFromId(VISU_NODE_ARRAY(symmetry->priv->data),
                                       g_array_index(symmetry->priv->nodes, guint, i));
      g_return_if_fail(node);
      g_array_index(xyz, float, 3 * i + 0) = elems[i].v[0];
      g_array_index(xyz, float, 3 * i + 1) = elems[i].v[1];
      g_array_index(xyz, float, 3 * i + 2) = elems[i].v[2];
    }
  visu_node_array_moveNodes(VISU_NODE_ARRAY(symmetry->priv->data),
                            symmetry->priv->nodes, xyz);
  g_array_unref(xyz);
}

static GType _getItemType(GListModel *list _U_)
{
  return VISU_TYPE_MOL_SYMMETRY_OP;
}
static guint _getNItems(GListModel *list)
{
  VisuMolSymmetry *self = VISU_MOL_SYMMETRY(list);
  return self && self->priv->syms ? self->priv->syms->len : 0;
}
static gpointer _getItem(GListModel *list, guint at)
{
  VisuMolSymmetry *self = VISU_MOL_SYMMETRY(list);
  return self && self->priv->syms && at < self->priv->syms->len ? g_object_ref(g_array_index(self->priv->syms, gpointer, at)) : (gpointer)0;
}



static void visu_mol_symmetry_op_dispose(GObject* obj);

G_DEFINE_TYPE_WITH_CODE(VisuMolSymmetryOp, visu_mol_symmetry_op, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuMolSymmetryOp))

static void visu_mol_symmetry_op_class_init(VisuMolSymmetryOpClass *klass)
{
  DBG_fprintf(stderr, "Visu MolSymmetryOp: creating the class of the object.\n");
  G_OBJECT_CLASS(klass)->dispose = visu_mol_symmetry_op_dispose;
}

static void visu_mol_symmetry_op_init(VisuMolSymmetryOp *list)
{
  DBG_fprintf(stderr, "Visu MolSymmetryOp: initializing a new object (%p).\n",
	      (gpointer)list);
  list->priv = visu_mol_symmetry_op_get_instance_private(list);
  list->priv->dispose_has_run = FALSE;

  list->priv->parent = (VisuMolSymmetry*)0;
  list->priv->plane = (VisuPlane*)0;
}

static void visu_mol_symmetry_op_dispose(GObject* obj)
{
  VisuMolSymmetryOp *list;

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

  list = VISU_MOL_SYMMETRY_OP(obj);
  if (list->priv->dispose_has_run)
    return;
  list->priv->dispose_has_run = TRUE;

  if (list->priv->parent)
    g_object_unref(list->priv->parent);
  if (list->priv->plane)
    g_object_unref(list->priv->plane);

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

VisuMolSymmetryOpTypes visu_mol_symmetry_op_getType(const VisuMolSymmetryOp *op)
{
  g_return_val_if_fail(VISU_IS_MOL_SYMMETRY_OP_TYPE(op), OPERATION_IDENTITY);

  return op->priv->op.type;
}
VisuPlane* visu_mol_symmetry_op_getReflexion(const VisuMolSymmetryOp *op)
{
  double cm[3];
  float normal[3], origin[3];
  
  g_return_val_if_fail(VISU_IS_MOL_SYMMETRY_OP_TYPE(op), (VisuPlane*)0);

  if (op->priv->op.type != MSYM_SYMMETRY_OPERATION_TYPE_REFLECTION)
    return (VisuPlane*)0;

  if (op->priv->plane)
    return op->priv->plane;
  
  msymGetCenterOfMass(op->priv->parent->priv->ctx, cm);
  op->priv->plane = visu_plane_newUndefined();
  normal[0] = op->priv->op.v[0];
  normal[1] = op->priv->op.v[1];
  normal[2] = op->priv->op.v[2];
  visu_plane_setNormalVector(op->priv->plane, normal);
  origin[0] = cm[0];
  origin[1] = cm[1];
  origin[2] = cm[2];
  visu_plane_setOrigin(op->priv->plane, origin);
  visu_plane_setColor(op->priv->plane, tool_color_new_bright(op->priv->id));
  visu_plane_setRendered(op->priv->plane, FALSE);
  
  return op->priv->plane;
}
gboolean visu_mol_symmetry_op_getRotation(const VisuMolSymmetryOp *op,
                                          float *angle, float axis[3],
                                          float origin[3])
{
  double cm[3];
  
  g_return_val_if_fail(VISU_IS_MOL_SYMMETRY_OP_TYPE(op), FALSE);

  if (op->priv->op.type != MSYM_SYMMETRY_OPERATION_TYPE_PROPER_ROTATION)
    return FALSE;

  msymGetCenterOfMass(op->priv->parent->priv->ctx, cm);
  origin[0] = cm[0];
  origin[1] = cm[1];
  origin[2] = cm[2];
  *angle = 360 / op->priv->op.order;
  axis[0] = op->priv->op.v[0];
  axis[1] = op->priv->op.v[1];
  axis[2] = op->priv->op.v[2];

  return TRUE;
}

static void _symmetrise(VisuMolSymmetry *symmetry)
{
  msym_error_t err;
  double delta;

  if ((err = msymSymmetrizeElements(symmetry->priv->ctx, &delta)) != MSYM_SUCCESS)
    g_message("%s: %s.", msymErrorString(err), msymGetErrorDetails());

  /* g_signal_handler_block(symmetry->priv->data, symmetry->priv->posChg); */
  /* g_signal_emit_by_name(symmetry->priv->data, "position-changed", */
  /*                       symmetry->priv->nodes); */
  /* g_signal_handler_unblock(symmetry->priv->data, symmetry->priv->posChg); */
}

static void _positionChanged(VisuMolSymmetry *symmetry, GArray *ids)
{
  gboolean reset;
  guint i, j;

  g_return_if_fail(VISU_IS_MOL_SYMMETRY_TYPE(symmetry));
  
  if (ids && symmetry->priv->nodes)
    {
      reset = FALSE;
      for (i = 0; i < ids->len && !reset; i++)
        for (j = 0; j < symmetry->priv->nodes->len && !reset; j++)
          reset = (g_array_index(ids, guint, i) ==
                   g_array_index(symmetry->priv->nodes, guint, j));
    }
  else
    reset = TRUE;

  if (!reset)
    return;

  DBG_fprintf(stderr, "Plugin Sym: got position-changed.\n");
  if (symmetry->priv->lock)
    _symmetrise(symmetry);
  else
    _setContext(symmetry);
}

gboolean visu_mol_symmetry_lock(VisuMolSymmetry *symmetry, gboolean status)
{
  g_return_val_if_fail(VISU_IS_MOL_SYMMETRY_TYPE(symmetry), FALSE);

  if (symmetry->priv->lock == status)
    return FALSE;

  symmetry->priv->lock = status;
  g_object_notify_by_pspec(G_OBJECT(symmetry), _properties[LOCK_PROP]);
  return TRUE;
}
