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

#include <GL/glu.h>
#include <string.h>
#include <math.h>

#include <visu_configFile.h>
#include <visu_basic.h>
#include <opengl.h>
#include <openGLFunctions/objectList.h>
#include <visu_dataspin.h>
#include "spinMethod.h"

/**
 * SECTION:elementSpin
 * @short_description: a class implementing rendering for
 * #VisuDataSpin method.
 *
 * <para>This class implements the virtual method of
 * #VisuElementRenderer class to display nodes as 1D objects at a
 * given point in a given direction. The specific shape is defined by
 * #VisuElementSpinShapeId enumeration. The size and orientation of
 * the 1D object is given per node by a #VisuNodeValuesVector
 * property. </para>
 * <para>visu_element_spin_getFromPool() is a specific function to
 * associate a unique #VisuElementSpin to a given #VisuElement.</para>
 */

/**
 * VisuElementSpinClass:
 * @parent: its parent.
 *
 * Interface for class that can represent #VisuElement of the same
 * kind in the same way (usually spheres).
 *
 * Since: 3.8
 */

#define FLAG_RESOURCES_SPIN "spin_resources"
#define DESC_RESOURCES_SPIN "Global or element resource for rendering spin module"

#define FLAG_SPIN_SHAPE "spin_shape"
#define DESC_SPIN_SHAPE "shape used to render spin for one element."
static VisuElementSpinShapeId _shape = VISU_ELEMENT_SPIN_ARROW_SMOOTH;

/* Default values. */
#define SPIN_ELEMENT_HAT_RADIUS_DEFAULT  0.8
#define SPIN_ELEMENT_HAT_LENGTH_DEFAULT  2.0
#define SPIN_ELEMENT_TAIL_RADIUS_DEFAULT 0.33
#define SPIN_ELEMENT_TAIL_LENGTH_DEFAULT 0.8
#define SPIN_ELEMENT_HAT_COLOR_DEFAULT   FALSE
#define SPIN_ELEMENT_TAIL_COLOR_DEFAULT  FALSE

#define FLAG_SPIN_ARROW "spin_arrow"
#define DESC_SPIN_ARROW "arrow definition used to render spin for one element."
static gfloat _arrow[] = {SPIN_ELEMENT_HAT_LENGTH_DEFAULT, SPIN_ELEMENT_HAT_RADIUS_DEFAULT,
                          SPIN_ELEMENT_TAIL_LENGTH_DEFAULT, SPIN_ELEMENT_TAIL_RADIUS_DEFAULT,
                          SPIN_ELEMENT_HAT_COLOR_DEFAULT, SPIN_ELEMENT_TAIL_COLOR_DEFAULT};

#define SPIN_ELEMENT_AAXIS_DEFAULT       1.5
#define SPIN_ELEMENT_BAXIS_DEFAULT       0.6
#define SPIN_ELEMENT_ELIP_COLOR_DEFAULT  FALSE

#define FLAG_SPIN_ELIP "spin_elipsoid"
#define DESC_SPIN_ELIP "elipsoid definition used to render spin for one element."
static gfloat _elip[] = {SPIN_ELEMENT_AAXIS_DEFAULT, SPIN_ELEMENT_BAXIS_DEFAULT,
                         SPIN_ELEMENT_ELIP_COLOR_DEFAULT};

static const char* _shapeName[VISU_ELEMENT_SPIN_N_SHAPES + 1] =
  {"Rounded", "Edged", "Elipsoid", "Torus", (const char*)0};
static const char* _shapeNameI18n[VISU_ELEMENT_SPIN_N_SHAPES + 1] = {NULL};

/* Read routines for the config file. */

/**
 * VisuElementSpin:
 *
 * Structure used to define #VisuElementSpin objects.
 *
 * Since: 3.8
 */
struct _VisuElementSpinPrivate
{
  VisuElementAtomic *fromPool;

  /* Params for the arrow shapes. */
  /* The radius and the length of the hat. */
  float length, height;
  /* The radius and the length of the tail. */
  float u_length, u_height;
  /* The coloring pattern. */
  gboolean use_element_color, use_element_color_hat;

  /* Params for the elipsoid shapes. */
  /* The long and the short axis. */
  float aAxis, bAxis;
  /* The coloring pattern. */
  gboolean elipsoidColor;

  /* The shape used. */
  VisuElementSpinShapeId shape;

  GLuint glElement;
};

enum {
  PROP_0,
  PROP_H_LENGTH,
  PROP_H_RADIUS,
  PROP_T_LENGTH,
  PROP_T_RADIUS,
  PROP_H_USE,
  PROP_T_USE,
  PROP_A,
  PROP_B,
  PROP_USE,
  PROP_SHAPE,
  N_PROPS,
  PROP_CACHE,
  N_PROP_TOT
};
static GParamSpec *_properties[N_PROP_TOT];

static gboolean _shapeFromName(const gchar *name, VisuElementSpinShapeId *shape);

static void visu_element_spin_get_property(GObject* obj, guint property_id,
                                             GValue *value, GParamSpec *pspec);
static void visu_element_spin_set_property(GObject* obj, guint property_id,
                                             const GValue *value, GParamSpec *pspec);
static void _compile(VisuElementRenderer *element, const VisuGlView *view);
static void _call(const VisuElementRenderer *element);
static void _callAt(const VisuElementRenderer *element,
                    const VisuDataColorizer *colorizer,
                    const VisuData *data, const VisuNode *node);
static gfloat _getExtent  (const VisuElementRenderer *self);

static void onEntryShape(VisuConfigFile *obj, VisuConfigFileEntry *entry, gpointer data);
static void onEntryArrow(VisuConfigFile *obj, VisuConfigFileEntry *entry, gpointer data);
static void onEntryElip(VisuConfigFile *obj, VisuConfigFileEntry *entry, gpointer data);
static void exportSpin(GString *data, VisuData *dataObj);

static GList *_pool;

G_DEFINE_TYPE_WITH_CODE(VisuElementSpin, visu_element_spin, VISU_TYPE_ELEMENT_ATOMIC,
                        G_ADD_PRIVATE(VisuElementSpin))

static void visu_element_spin_class_init(VisuElementSpinClass *klass)
{
  VisuConfigFileEntry *resourceEntry;
  gfloat rgArrow[2] = {0.f, 999.f};

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->set_property = visu_element_spin_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_element_spin_get_property;
  VISU_ELEMENT_RENDERER_CLASS(klass)->compile = _compile;
  VISU_ELEMENT_RENDERER_CLASS(klass)->call = _call;
  VISU_ELEMENT_RENDERER_CLASS(klass)->callAt = _callAt;
  VISU_ELEMENT_RENDERER_CLASS(klass)->getExtent = _getExtent;

  /**
   * VisuElementSpin::spin-shape:
   *
   * The shape used to represent a given element.
   *
   * Since: 3.8
   */
  _properties[PROP_SHAPE] =
    g_param_spec_uint("spin-shape", "Spin shape", "spin shape",
                      0, VISU_ELEMENT_SPIN_N_SHAPES - 1, _shape,
                      G_PARAM_READWRITE);
  /**
   * VisuElementSpin::hat-length:
   *
   * .
   *
   * Since: 3.8
   */
  _properties[PROP_H_LENGTH] =
    g_param_spec_float("hat-length", "Hat length", "hat length",
                       0.f, G_MAXFLOAT, SPIN_ELEMENT_HAT_LENGTH_DEFAULT, G_PARAM_READWRITE);
  /**
   * VisuElementSpin::hat-radius:
   *
   * .
   *
   * Since: 3.8
   */
  _properties[PROP_H_RADIUS] =
    g_param_spec_float("hat-radius", "Hat radius", "hat radius",
                       0.f, G_MAXFLOAT, SPIN_ELEMENT_HAT_RADIUS_DEFAULT, G_PARAM_READWRITE);
  /**
   * VisuElementSpin::tail-length:
   *
   * .
   *
   * Since: 3.8
   */
  _properties[PROP_T_LENGTH] =
    g_param_spec_float("tail-length", "Tail length", "tail length",
                       0.f, G_MAXFLOAT, SPIN_ELEMENT_TAIL_LENGTH_DEFAULT, G_PARAM_READWRITE);
  /**
   * VisuElementSpin::tail-radius:
   *
   * .
   *
   * Since: 3.8
   */
  _properties[PROP_T_RADIUS] =
    g_param_spec_float("tail-radius", "Tail radius", "tail radius",
                       0.f, G_MAXFLOAT, SPIN_ELEMENT_TAIL_RADIUS_DEFAULT, G_PARAM_READWRITE);
  /**
   * VisuElementSpin::hat-spin-colored:
   *
   * .
   *
   * Since: 3.8
   */
  _properties[PROP_H_USE] =
    g_param_spec_boolean("hat-spin-colored", "Hat spin colored", "hat is colored by spin",
                         SPIN_ELEMENT_HAT_COLOR_DEFAULT, G_PARAM_READWRITE);
  /**
   * VisuElementSpin::tail-spin-colored:
   *
   * .
   *
   * Since: 3.8
   */
  _properties[PROP_T_USE] =
    g_param_spec_boolean("tail-spin-colored", "Tail spin colored", "tail is colored by spin",
                         SPIN_ELEMENT_TAIL_COLOR_DEFAULT, G_PARAM_READWRITE);
  /**
   * VisuElementSpin::a-axis:
   *
   * .
   *
   * Since: 3.8
   */
  _properties[PROP_A] =
    g_param_spec_float("a-axis", "A axis", "A axis length",
                       0.f, G_MAXFLOAT, SPIN_ELEMENT_AAXIS_DEFAULT, G_PARAM_READWRITE);
  /**
   * VisuElementSpin::b-axis:
   *
   * .
   *
   * Since: 3.8
   */
  _properties[PROP_B] =
    g_param_spec_float("b-axis", "B axis", "B axis length",
                       0.f, G_MAXFLOAT, SPIN_ELEMENT_BAXIS_DEFAULT, G_PARAM_READWRITE);
  /**
   * VisuElementSpin::spin-colored:
   *
   * .
   *
   * Since: 3.8
   */
  _properties[PROP_USE] =
    g_param_spec_boolean("spin-colored", "Spin colored", "shape is colored by spin",
                         SPIN_ELEMENT_ELIP_COLOR_DEFAULT, G_PARAM_READWRITE);

  g_object_class_install_properties(G_OBJECT_CLASS(klass), N_PROPS, _properties);

  g_object_class_override_property(G_OBJECT_CLASS(klass),
                                   PROP_CACHE, "cache-material");

  /* Dealing with config files. */
  resourceEntry = visu_config_file_addEnumEntry(VISU_CONFIG_FILE_RESOURCE,
                                                FLAG_SPIN_SHAPE, DESC_SPIN_SHAPE,
                                                &_shape, _shapeFromName, TRUE);
  visu_config_file_entry_setVersion(resourceEntry, 3.8f);
  g_signal_connect(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_SPIN_SHAPE,
                   G_CALLBACK(onEntryShape), (gpointer)0);
  resourceEntry = visu_config_file_addFloatArrayEntry(VISU_CONFIG_FILE_RESOURCE,
                                                      FLAG_SPIN_ARROW, DESC_SPIN_ARROW,
                                                      6, _arrow, rgArrow, TRUE);
  visu_config_file_entry_setVersion(resourceEntry, 3.8f);
  g_signal_connect(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_SPIN_ARROW,
                   G_CALLBACK(onEntryArrow), (gpointer)0);
  resourceEntry = visu_config_file_addFloatArrayEntry(VISU_CONFIG_FILE_RESOURCE,
                                                      FLAG_SPIN_ELIP, DESC_SPIN_ELIP,
                                                      3, _elip, rgArrow, TRUE);
  visu_config_file_entry_setVersion(resourceEntry, 3.8f);
  g_signal_connect(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_SPIN_ELIP,
                   G_CALLBACK(onEntryElip), (gpointer)0);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_RESOURCE,
                                     exportSpin);

  _pool = (GList*)0;
}
static void visu_element_spin_init(VisuElementSpin *obj)
{
  DBG_fprintf(stderr, "Visu Element Spin: initializing a new object (%p).\n",
	      (gpointer)obj);
  
  obj->priv = visu_element_spin_get_instance_private(obj);

  /* Private data. */
  obj->priv->fromPool = (VisuElementAtomic*)0;
  obj->priv->length = _arrow[1];
  obj->priv->height = _arrow[0];
  obj->priv->u_length = _arrow[3];
  obj->priv->u_height = _arrow[2];
  obj->priv->use_element_color = (_arrow[4] == TRUE);
  obj->priv->use_element_color_hat = (_arrow[5] == TRUE);
  obj->priv->aAxis = SPIN_ELEMENT_AAXIS_DEFAULT;
  obj->priv->bAxis = SPIN_ELEMENT_BAXIS_DEFAULT;
  obj->priv->elipsoidColor = SPIN_ELEMENT_ELIP_COLOR_DEFAULT;
  obj->priv->shape = _shape;
}
static void visu_element_spin_get_property(GObject* obj, guint property_id,
                                             GValue *value, GParamSpec *pspec)
{
  VisuElementSpin *self = VISU_ELEMENT_SPIN(obj);

  DBG_fprintf(stderr, "Visu Element Spin: get property '%s'\n",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case PROP_H_LENGTH:
      g_value_set_float(value, self->priv->height);
      break;
    case PROP_H_RADIUS:
      g_value_set_float(value, self->priv->length);
      break;
    case PROP_T_LENGTH:
      g_value_set_float(value, self->priv->u_height);
      break;
    case PROP_T_RADIUS:
      g_value_set_float(value, self->priv->u_length);
      break;
    case PROP_T_USE:
      g_value_set_boolean(value, self->priv->use_element_color);
      break;
    case PROP_H_USE:
      g_value_set_boolean(value, self->priv->use_element_color_hat);
      break;
    case PROP_A:
      g_value_set_float(value, self->priv->aAxis);
      break;
    case PROP_B:
      g_value_set_float(value, self->priv->bAxis);
      break;
    case PROP_USE:
      g_value_set_boolean(value, self->priv->elipsoidColor);
      break;
    case PROP_SHAPE:
      g_value_set_uint(value, self->priv->shape);
      break;
    case PROP_CACHE:
      g_value_set_boolean(value, FALSE);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_element_spin_set_property(GObject* obj, guint property_id,
                                             const GValue *value, GParamSpec *pspec)
{
  VisuElementSpin *self = VISU_ELEMENT_SPIN(obj);

  DBG_fprintf(stderr, "Visu Element Spin: set property '%s'\n",
	      g_param_spec_get_name(pspec));
  switch (property_id)
    {
    case PROP_H_LENGTH:
      self->priv->height = g_value_get_float(value);
      if (self->priv->shape == VISU_ELEMENT_SPIN_ARROW_SMOOTH ||
          self->priv->shape == VISU_ELEMENT_SPIN_ARROW_SHARP)
        {
          g_signal_emit_by_name(self, "size-changed", _getExtent(VISU_ELEMENT_RENDERER(self)));
          _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
        }
      break;
    case PROP_H_RADIUS:
      self->priv->length = g_value_get_float(value);
      if (self->priv->shape == VISU_ELEMENT_SPIN_ARROW_SMOOTH ||
          self->priv->shape == VISU_ELEMENT_SPIN_ARROW_SHARP)
        _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
      break;
    case PROP_T_LENGTH:
      self->priv->u_height = g_value_get_float(value);
      if (self->priv->shape == VISU_ELEMENT_SPIN_ARROW_SMOOTH ||
          self->priv->shape == VISU_ELEMENT_SPIN_ARROW_SHARP)
        {
          g_signal_emit_by_name(self, "size-changed", _getExtent(VISU_ELEMENT_RENDERER(self)));
          _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
        }
      break;
    case PROP_T_RADIUS:
      self->priv->u_length = g_value_get_float(value);
      if (self->priv->shape == VISU_ELEMENT_SPIN_ARROW_SMOOTH ||
          self->priv->shape == VISU_ELEMENT_SPIN_ARROW_SHARP)
        _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
      break;
    case PROP_T_USE:
      self->priv->use_element_color = g_value_get_boolean(value);
      if (self->priv->shape == VISU_ELEMENT_SPIN_ARROW_SMOOTH ||
          self->priv->shape == VISU_ELEMENT_SPIN_ARROW_SHARP)
        _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
      break;
    case PROP_H_USE:
      self->priv->use_element_color_hat = g_value_get_boolean(value);
      if (self->priv->shape == VISU_ELEMENT_SPIN_ARROW_SMOOTH ||
          self->priv->shape == VISU_ELEMENT_SPIN_ARROW_SHARP)
        _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
      break;
    case PROP_A:
      self->priv->aAxis = g_value_get_float(value);
      if (self->priv->shape == VISU_ELEMENT_SPIN_ELLIPSOID ||
          self->priv->shape == VISU_ELEMENT_SPIN_TORUS)
        {
          g_signal_emit_by_name(self, "size-changed", _getExtent(VISU_ELEMENT_RENDERER(self)));
          _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
        }
      break;
    case PROP_B:
      self->priv->bAxis = g_value_get_float(value);
      if (self->priv->shape == VISU_ELEMENT_SPIN_ELLIPSOID ||
          self->priv->shape == VISU_ELEMENT_SPIN_TORUS)
        {
          g_signal_emit_by_name(self, "size-changed", _getExtent(VISU_ELEMENT_RENDERER(self)));
          _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
        }
      break;
    case PROP_USE:
      self->priv->elipsoidColor = g_value_get_boolean(value);
      if (self->priv->shape == VISU_ELEMENT_SPIN_ELLIPSOID ||
          self->priv->shape == VISU_ELEMENT_SPIN_TORUS)
        _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
      break;
    case PROP_SHAPE:
      self->priv->shape = g_value_get_uint(value);
      g_signal_emit_by_name(self, "size-changed", _getExtent(VISU_ELEMENT_RENDERER(self)));
      _compile(VISU_ELEMENT_RENDERER(self), visu_element_renderer_getConstGlView(VISU_ELEMENT_RENDERER(self)));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_element_spin_new:
 * @element: a #VisuElement object.
 *
 * Creates a new #VisuElementSpin object to draw @element.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a newly created #VisuElementSpin object.
 **/
VisuElementSpin* visu_element_spin_new(VisuElement *element)
{
  return VISU_ELEMENT_SPIN(g_object_new(VISU_TYPE_ELEMENT_SPIN, "element", element, NULL));
}
/**
 * visu_element_spin_getFromPool:
 * @element: a #VisuElement object.
 *
 * Retrieve a #VisuElementSpin representing @element. This
 * #VisuElementSpin is unique and its parent properties are bound to
 * the unique #VisuElementAtomic for @element.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): a #VisuElementSpin for @element.
 **/
VisuElementSpin* visu_element_spin_getFromPool(VisuElement *element)
{
  GList *lst;
  VisuElementSpin *spin;

  for (lst = _pool; lst; lst = g_list_next(lst))
    if (visu_element_renderer_getElement(VISU_ELEMENT_RENDERER(lst->data)) == element)
      return VISU_ELEMENT_SPIN(lst->data);
  
  spin = visu_element_spin_new(element);
  visu_element_atomic_bindToPool(VISU_ELEMENT_ATOMIC(spin));
  _pool = g_list_prepend(_pool, spin);  
  return spin;
}
/**
 * visu_element_spin_bindToPool:
 * @spin: A #VisuElementSpin object.
 *
 * Bind all properties of @spin to the properties of the
 * #VisuElementSpin object from the pool used for the same #VisuElement.
 *
 * Since: 3.8
 **/
void visu_element_spin_bindToPool(VisuElementSpin *spin)
{
  VisuElementSpin *pool;

  visu_element_atomic_bindToPool(VISU_ELEMENT_ATOMIC(spin));
  pool = visu_element_spin_getFromPool(visu_element_renderer_getElement(VISU_ELEMENT_RENDERER(spin)));
  g_object_bind_property(pool, "spin-shape", spin, "spin-shape",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "hat-length", spin, "hat-length",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "hat-radius", spin, "hat-radius",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "tail-length", spin, "tail-length",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "tail-radius", spin, "tail-radius",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "hat-spin-colored", spin, "hat-spin-colored",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "tail-spin-colored", spin, "tail-spin-colored",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "a-axis", spin, "a-axis",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "b-axis", spin, "b-axis",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
  g_object_bind_property(pool, "spin-colored", spin, "spin-colored",
                         G_BINDING_SYNC_CREATE | G_BINDING_BIDIRECTIONAL);
}

/**
 * visu_element_spin_getShape:
 * @self: a #VisuElementSpin object.
 *
 * Retrieves the shape of @self.
 *
 * Since: 3.8
 *
 * Returns: a #VisuElementSpinShapeId value.
 **/
VisuElementSpinShapeId visu_element_spin_getShape(const VisuElementSpin *self)
{
  g_return_val_if_fail(VISU_IS_ELEMENT_SPIN(self), _shape);

  return self->priv->shape;
}

static gfloat _getExtent(const VisuElementRenderer *self)
{
  VisuElementSpin *spin;

  g_return_val_if_fail(VISU_IS_ELEMENT_SPIN(self), 0.f);

  spin = VISU_ELEMENT_SPIN(self);
  
  if (spin->priv->shape == VISU_ELEMENT_SPIN_ARROW_SMOOTH ||
      spin->priv->shape == VISU_ELEMENT_SPIN_ARROW_SHARP)
    return MAX(VISU_ELEMENT_RENDERER_CLASS(visu_element_spin_parent_class)->getExtent(self),
               (spin->priv->height + spin->priv->u_height) / 2.f);
  else if (spin->priv->shape == VISU_ELEMENT_SPIN_ELLIPSOID)
    return MAX(VISU_ELEMENT_RENDERER_CLASS(visu_element_spin_parent_class)->getExtent(self),
               MAX(spin->priv->aAxis, spin->priv->bAxis) / 2.f);
  else
    return MAX(VISU_ELEMENT_RENDERER_CLASS(visu_element_spin_parent_class)->getExtent(self),
               (spin->priv->aAxis - spin->priv->bAxis * spin->priv->bAxis / spin->priv->aAxis) / 2.f);
}
static void _compile(VisuElementRenderer *element, const VisuGlView *view)
{
  int nlatl=0, nlatul=0, nlatoh=0;
  GLUquadricObj *obj;
  VisuElementSpin *ele;

  g_return_if_fail(VISU_IS_ELEMENT_SPIN(element));

  ele = VISU_ELEMENT_SPIN(element);
  if (ele->priv->glElement)
    glDeleteLists(ele->priv->glElement, 1);

  if (!view)
    {
      VISU_ELEMENT_RENDERER_CLASS(visu_element_spin_parent_class)->compile(element, view);
      return;
    }

  nlatul = visu_gl_view_getDetailLevel(view, ele->priv->u_length) ;
  nlatl = visu_gl_view_getDetailLevel(view, ele->priv->length);
  nlatoh = visu_gl_view_getDetailLevel(view, ele->priv->height);
  
  DBG_fprintf(stderr, "Rendering Spin : creating arrow for %s,  nlatl = %d,"
	      " nlatul = %d, nlatoh = %d\n",
	      visu_element_getName(visu_element_renderer_getElement((VisuElementRenderer*)element)),
              nlatl, nlatul, nlatoh);

  
  if (!ele->priv->glElement)
    ele->priv->glElement = visu_gl_objectlist_new(1);

  glNewList(ele->priv->glElement, GL_COMPILE);

  obj = gluNewQuadric();
  switch (ele->priv->shape)
    {
    case VISU_ELEMENT_SPIN_ARROW_SMOOTH:
      visu_gl_drawSmoothArrow(obj, VISU_GL_ARROW_CENTERED,
                              ele->priv->u_height, ele->priv->u_length, nlatul,
                              (ele->priv->use_element_color) ? element : NULL,
                              ele->priv->height, ele->priv->length, nlatl,
                              (ele->priv->use_element_color_hat) ? element : NULL);
      break;
    case VISU_ELEMENT_SPIN_ARROW_SHARP:
      visu_gl_drawEdgeArrow(VISU_GL_ARROW_CENTERED,
                            ele->priv->u_height, ele->priv->u_length,
                            (ele->priv->use_element_color) ? element : NULL,
                            ele->priv->height, ele->priv->length,
                            (ele->priv->use_element_color_hat) ? element : NULL);
      break;
    case VISU_ELEMENT_SPIN_ELLIPSOID:
      nlatl = visu_gl_view_getDetailLevel(view, ele->priv->bAxis);
      visu_gl_drawEllipsoid(obj, ele->priv->aAxis, ele->priv->bAxis, nlatl,
                            (ele->priv->elipsoidColor) ? element : NULL);
      break;
    case VISU_ELEMENT_SPIN_TORUS:
      nlatul = visu_gl_view_getDetailLevel(view, ele->priv->aAxis);
      nlatl = visu_gl_view_getDetailLevel(view, ele->priv->bAxis);
      visu_gl_drawTorus(obj, ele->priv->aAxis - ele->priv->bAxis,
                        ele->priv->aAxis / ele->priv->bAxis,
                        nlatul, nlatl, (ele->priv->elipsoidColor) ? element : NULL);
      break;
    default:
      g_warning("Unknown shape.");
      break;
    }
  gluDeleteQuadric(obj);

  glEndList();

  /* We always build atomic shapes in case we need them. */
  VISU_ELEMENT_RENDERER_CLASS(visu_element_spin_parent_class)->compile(element, view);
}
static void _call(const VisuElementRenderer *element)
{
  VisuElementSpin *ele;

  ele = VISU_ELEMENT_SPIN(element);
  g_return_if_fail(ele->priv->glElement);

  glCallList(ele->priv->glElement);
}

static void _callAt(const VisuElementRenderer *element,
                    const VisuDataColorizer *colorizer,
                    const VisuData *data, const VisuNode *node)
{
  VisuElementSpin *self = VISU_ELEMENT_SPIN(element);
  const VisuDataSpin *dataSpin;

  float mm[4];
  float rgba[4];
  float xyz[3];

  const gfloat *spinValues;
  float scale, ratio;
  gboolean withAtomic;

  const gfloat *material;
  const ToolColor *color;

  color = visu_element_renderer_getColor(element);
  material = visu_element_renderer_getMaterial(element);
  dataSpin = VISU_DATA_SPIN(data);

  /* Test the modulus. */
  scale = (colorizer) ? visu_data_colorizer_getScalingFactor(colorizer, data, node) : 1.f;
  visu_data_getNodePosition(data, node, xyz);
  if ((spinValues = visu_method_spin_getSpinVector(visu_method_spin_getDefault(),
                                                   dataSpin, node, &ratio,
                                                   mm, &withAtomic)))
    {
      /* The following is the part responsible for the rotation
	 of the atoms according to their spins. */
      glPushMatrix();

      /* Translate to the rendering position. */
      glTranslated(xyz[0], xyz[1], xyz[2]);

      /* If we need to draw also an atom shape. */
      if (withAtomic)
	{
          visu_element_renderer_colorize(element, VISU_ELEMENT_RENDERER_NO_EFFECT);
          VISU_ELEMENT_RENDERER_CLASS(visu_element_spin_parent_class)->call(element);
	}

      /* We rotate the spin shape into the right direction. */
      glRotated(spinValues[TOOL_MATRIX_SPHERICAL_PHI], 0, 0, 1);
      glRotated(spinValues[TOOL_MATRIX_SPHERICAL_THETA], 0, 1, 0);

      /* We change its color if required. */
      if (colorizer && visu_data_colorizer_getColor(colorizer, rgba, data, node))
	visu_gl_setColor((VisuGl*)0, material, rgba);
      else
        {
          mm[3] = color->rgba[3];
          visu_gl_setColor((VisuGl*)0, material, mm);
        }

      /* We finaly put the spin shape. */
      glScalef(scale * ratio, scale * ratio, scale * ratio);
      glCallList(self->priv->glElement);

      glPopMatrix();
    }
  else if (withAtomic)
    {
      glPushMatrix();
  
      glTranslated(xyz[0], xyz[1], xyz[2]);

      glScalef(scale, scale, scale);
      if (colorizer && visu_data_colorizer_getColor(colorizer, rgba, data, node))
        visu_gl_setColor((VisuGl*)0, material, rgba);
      else
        visu_element_renderer_colorize(element, VISU_ELEMENT_RENDERER_NO_EFFECT);
      VISU_ELEMENT_RENDERER_CLASS(visu_element_spin_parent_class)->call(element);
      glPopMatrix();
    }
}

/**
 * visu_element_spin_getShapeNames:
 * @asLabel: a boolean.
 *
 * Get the string defining #VisuElementSpinShapeId. If @asLabel is
 * %TRUE, then the string are translated and stored in UTF8.
 *
 * Since: 3.8
 *
 * Returns: (transfer none) (array zero-terminated=1): strings
 * representing #VisuElementSpinShapeId.
 **/
const gchar ** visu_element_spin_getShapeNames(gboolean asLabel)
{
  if (!_shapeNameI18n[0])
    {
      _shapeNameI18n[0] = _("Rounded arrow");
      _shapeNameI18n[1] = _("Edged arrow");
      _shapeNameI18n[2] = _("Elipsoid");
      _shapeNameI18n[3] = _("Torus");
      _shapeNameI18n[4] = (const char*)0;
    }
  if (asLabel)
    return _shapeNameI18n;
  else
    return _shapeName;
}

/*****************************************/
/* Dealing with parameters and resources */
/*****************************************/
static gboolean _shapeFromName(const gchar *name, VisuElementSpinShapeId *shape)
{
  g_return_val_if_fail(name && shape, FALSE);

  for (*shape = 0; *shape < VISU_ELEMENT_SPIN_N_SHAPES; *shape += 1)
    if (!strcmp(name, _shapeName[*shape]))
      return TRUE;
  return FALSE;
}
static VisuElementSpin* _fromEntry(VisuConfigFileEntry *entry)
{
  const gchar *label;
  VisuElement *ele;

  label = visu_config_file_entry_getLabel(entry);
  ele = visu_element_retrieveFromName(label, (gboolean*)0);
  if (!ele)
    {
      visu_config_file_entry_setErrorMessage(entry, _("'%s' wrong element name"), label);
      return (VisuElementSpin*)0;
    }
  return visu_element_spin_getFromPool(ele);
}
static void onEntryShape(VisuConfigFile *obj _U_, VisuConfigFileEntry *entry, gpointer data _U_)
{
  VisuElementSpin *ele;

  ele = _fromEntry(entry);
  if (!ele)
    return;

  g_object_set(ele, "shape", _shape, NULL);
}
static void onEntryArrow(VisuConfigFile *obj _U_, VisuConfigFileEntry *entry, gpointer data _U_)
{
  VisuElementSpin *ele;

  ele = _fromEntry(entry);
  if (!ele)
    return;

  g_object_set(ele, "hat-length", _arrow[0], "hat-radius", _arrow[1],
               "tail-length", _arrow[2], "tail-radius", _arrow[3],
               "hat-spin-colored", (_arrow[4] == TRUE),
               "tail-spin-colored", (_arrow[5] == TRUE), NULL);
}
static void onEntryElip(VisuConfigFile *obj _U_, VisuConfigFileEntry *entry, gpointer data _U_)
{
  VisuElementSpin *ele;

  ele = _fromEntry(entry);
  if (!ele)
    return;

  g_object_set(ele, "a-axis", _elip[0], "b-axis", _elip[1],
               "spin-colored", (_elip[2] == TRUE), NULL);
}
/* These functions write all the element list to export there associated resources. */
static void exportSpin(GString *data, VisuData* dataObj)
{
  GList *pos;
  VisuElementSpin *spin;
  const VisuElement *ele;

  /* If dataObj is given and the rendering method is not spin,
     we return. */
  if (dataObj && VISU_IS_DATA_SPIN(dataObj))
    return;

  DBG_fprintf(stderr, "Rendering Spin: exporting element resources...\n");

  visu_config_file_exportComment(data, DESC_RESOURCES_SPIN);
  /* We create a list of elements, or get the whole list. */
  for (pos = _pool; pos; pos = g_list_next(pos))
    {
      spin = VISU_ELEMENT_SPIN(pos->data);
      ele = visu_element_renderer_getConstElement(VISU_ELEMENT_RENDERER(spin));
      if (!dataObj || visu_node_array_containsElement(VISU_NODE_ARRAY(dataObj), ele))
        {
          if (spin->priv->shape != _shape)
            visu_config_file_exportEntry(data, FLAG_SPIN_SHAPE, visu_element_getName(ele),
                                         "%s", _shapeName[spin->priv->shape]);
          if (ABS(spin->priv->height - SPIN_ELEMENT_HAT_LENGTH_DEFAULT) > 1e-6 ||
              ABS(spin->priv->u_height - SPIN_ELEMENT_TAIL_LENGTH_DEFAULT) > 1e-6 ||
              ABS(spin->priv->length - SPIN_ELEMENT_HAT_RADIUS_DEFAULT) > 1e-6 ||
              ABS(spin->priv->u_length - SPIN_ELEMENT_TAIL_RADIUS_DEFAULT) > 1e-6 ||
              spin->priv->use_element_color != SPIN_ELEMENT_TAIL_COLOR_DEFAULT ||
              spin->priv->use_element_color_hat != SPIN_ELEMENT_HAT_COLOR_DEFAULT)
            visu_config_file_exportEntry(data, FLAG_SPIN_ARROW, visu_element_getName(ele),
                                         "%f %f %f %f %d %d",
                                         spin->priv->height, spin->priv->u_height, spin->priv->length, spin->priv->u_length,
                                         spin->priv->use_element_color, spin->priv->use_element_color_hat);
          if (ABS(spin->priv->aAxis - SPIN_ELEMENT_AAXIS_DEFAULT) > 1e-6 ||
              ABS(spin->priv->bAxis - SPIN_ELEMENT_BAXIS_DEFAULT) > 1e-6 ||
              spin->priv->elipsoidColor != SPIN_ELEMENT_ELIP_COLOR_DEFAULT)
            visu_config_file_exportEntry(data, FLAG_SPIN_ELIP, visu_element_getName(ele),
                                         "%f %f %d",
                                         spin->priv->aAxis, spin->priv->bAxis, spin->priv->elipsoidColor);
        }
    }

  visu_config_file_exportComment(data, "");
}
/**
 * visu_element_spin_pool_finalize: (skip)
 *
 * Destroy the internal list of known #VisuElementSpin objects, see
 * visu_element_spin_getFromPool().
 *
 * Since: 3.8
 **/
void visu_element_spin_pool_finalize(void)
{
  g_list_free_full(_pool, (GDestroyNotify)g_object_unref);
  _pool = (GList*)0;
}
