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

#include <math.h>

#include <visu_configFile.h>
#include <coreTools/toolMatrix.h>
#include <coreTools/toolColor.h>

/**
 * SECTION:spinMethod
 * @short_description: a class defining how a set of spin should be rendered.
 *
 * <para>Defines global properties for the rendering of spin data,
 * like should atomic rendering be used in addition? How to orientate
 * the colourisation cone?</para>
 */

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

#define FLAG_SPIN_CONE_ANGLE "spin_global_color_cone"
#define FLAG_SPIN_WHEEL_ANGLE "spin_global_color_wheel"
#define FLAG_SPIN_HIDING_MODE "spin_global_hiding_mode"
#define FLAG_SPIN_AND_ATOMIC "spin_global_atomic"
#define FLAG_SPIN_MODULUS "spin_global_modulus"
static gfloat _coneOrientation[2] = {0.f, 0.f};
static gfloat _colorWheel = 0.f;
static VisuMethodSpinDrawingPolicy _spinPolicy = VISU_METHOD_SPIN_ALWAYS;
static gboolean _spinAndAtomicRendering = FALSE;
static VisuMethodSpinModulusPolicy _spinModulusUsage = VISU_METHOD_SPIN_CONSTANT;

static const char* policyNameSpin[VISU_METHOD_SPIN_N_MODES + 1] = {"always", 
                                                                   "never", 
                                                                   "atomic",
                                                                   (const char*)0};

enum
  {
    PROP_0,
    PROP_CONETHETA,
    PROP_CONEPHI,
    PROP_COLORWHEEL,
    PROP_HIDINGMODE,
    PROP_ATOMIC,
    PROP_MODULUS,
    N_PROP,
  };
static GParamSpec *_properties[N_PROP];

static VisuMethodSpin* _default = NULL;

struct _VisuMethodSpinPrivate
{
  gfloat coneOrientation[2];
  gfloat colorWheel;
  VisuMethodSpinDrawingPolicy spinPolicy;
  gboolean spinAndAtomicRendering;
  VisuMethodSpinModulusPolicy spinModulusUsage;
};

static void visu_method_spin_get_property(GObject* obj, guint property_id,
                                        GValue *value, GParamSpec *pspec);
static void visu_method_spin_set_property(GObject* obj, guint property_id,
                                        const GValue *value, GParamSpec *pspec);

/* Local methods. */
static void onEntrySpin(VisuMethodSpin *method, VisuConfigFileEntry *entry, VisuConfigFile *obj);
static void exportResourcesRenderingSpin(GString *method, VisuData *dataObj);

G_DEFINE_TYPE_WITH_CODE(VisuMethodSpin, visu_method_spin, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuMethodSpin))

static void visu_method_spin_class_init(VisuMethodSpinClass *klass)
{
  VisuConfigFileEntry *resourceEntry;

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->get_property = visu_method_spin_get_property;
  G_OBJECT_CLASS(klass)->set_property = visu_method_spin_set_property;

  /**
   * VisuMethodSpin::cone-theta:
   *
   * The theta angle to orientate the colourisation cone.
   *
   * Since: 3.8
   */
  _properties[PROP_CONETHETA] =
    g_param_spec_float("cone-theta", _("Theta angle"),
                       _("The theta angle to orientate the colourisation cone."), 0, 180, 0,
                       G_PARAM_READWRITE);
  /**
   * VisuMethodSpin::cone-phi:
   *
   * The phi angle to orientate the colourisation cone.
   *
   * Since: 3.8
   */
  _properties[PROP_CONEPHI] =
    g_param_spec_float("cone-phi", _("Phi angle"),
                       _("The phi angle to orientate the colourisation cone."), 0, 360, 0,
                       G_PARAM_READWRITE);
  /**
   * VisuMethodSpin::cone-omega:
   *
   * The omega angle to orientate the colourisation cone.
   *
   * Since: 3.8
   */
  _properties[PROP_COLORWHEEL] =
    g_param_spec_float("cone-omega", _("Omega angle"),
                       _("The omega angle to orientate the colourisation cone."), 0, 360, 0,
                       G_PARAM_READWRITE);
  /**
   * VisuMethodSpin::hiding-mode:
   *
   * The hiding policy for spin with a null modulus.
   *
   * Since: 3.8
   */
  _properties[PROP_HIDINGMODE] =
    g_param_spec_uint("hiding-mode", _("Hiding policy for null modulus"),
                      _("The hiding policy for spin with a null modulus."),
                      0, VISU_METHOD_SPIN_N_MODES, VISU_METHOD_SPIN_ALWAYS,
                      G_PARAM_READWRITE);
  /**
   * VisuMethodSpin::modulus-scaling:
   *
   * The scaling policy based on modulus value.
   *
   * Since: 3.8
   */
  _properties[PROP_MODULUS] =
    g_param_spec_uint("modulus-scaling", _("Scaling of spin depending on modulus value"),
                      _("The scaling policy based on modulus value."),
                      0, VISU_METHOD_SPIN_N_MODULUS_MODES, VISU_METHOD_SPIN_CONSTANT,
                      G_PARAM_READWRITE);
  /**
   * VisuMethodSpin::use-atomic:
   *
   * If atomic rendering is used in addition to spin rendering.
   *
   * Since: 3.8
   */
  _properties[PROP_ATOMIC] =
    g_param_spec_boolean("use-atomic", _("Use atomic rendering"),
                         _("If atomic rendering is used in addition to spin rendering."),
                         FALSE, G_PARAM_READWRITE);
  g_object_class_install_properties(G_OBJECT_CLASS(klass), N_PROP, _properties);

  /* Dealing with config files. */
  resourceEntry = visu_config_file_addTokenizedEntry(VISU_CONFIG_FILE_RESOURCE,
                                                     FLAG_RESOURCES_SPIN,
                                                     DESC_RESOURCES_SPIN,
                                                     TRUE);
  visu_config_file_entry_setVersion(resourceEntry, 3.1f);
  g_signal_connect_swapped(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_RESOURCES_SPIN,
                           G_CALLBACK(onEntrySpin), (gpointer)0);
  visu_config_file_addExportFunction(VISU_CONFIG_FILE_RESOURCE,
                                     exportResourcesRenderingSpin);
}
static void visu_method_spin_init(VisuMethodSpin *obj)
{
  obj->priv = visu_method_spin_get_instance_private(obj);

  /* Private method. */
  obj->priv->spinPolicy             = _spinPolicy;
  obj->priv->spinAndAtomicRendering = _spinAndAtomicRendering;
  obj->priv->spinModulusUsage       = _spinModulusUsage;
  obj->priv->coneOrientation[0]     = _coneOrientation[0];
  obj->priv->coneOrientation[1]     = _coneOrientation[1];
  obj->priv->colorWheel             = _colorWheel;

  g_signal_connect_object(VISU_CONFIG_FILE_RESOURCE, "parsed::" FLAG_RESOURCES_SPIN,
                          G_CALLBACK(onEntrySpin), obj, G_CONNECT_SWAPPED);
}
static void visu_method_spin_get_property(GObject* obj, guint property_id,
                                          GValue *value, GParamSpec *pspec)
{
  VisuMethodSpin *self = VISU_METHOD_SPIN(obj);

  switch (property_id)
    {
    case PROP_CONETHETA:
      g_value_set_float(value, self->priv->coneOrientation[0]);
      break;
    case PROP_CONEPHI:
      g_value_set_float(value, self->priv->coneOrientation[1]);
      break;
    case PROP_COLORWHEEL:
      g_value_set_float(value, self->priv->colorWheel);
      break;
    case PROP_HIDINGMODE:
      g_value_set_uint(value, self->priv->spinPolicy);
      break;
    case PROP_ATOMIC:
      g_value_set_boolean(value, self->priv->spinAndAtomicRendering);
      break;
    case PROP_MODULUS:
      g_value_set_uint(value, self->priv->spinModulusUsage);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_method_spin_set_property(GObject* obj, guint property_id,
                                        const GValue *value, GParamSpec *pspec)
{
  VisuMethodSpin *self = VISU_METHOD_SPIN(obj);

  switch (property_id)
    {
    case PROP_CONETHETA:
      self->priv->coneOrientation[0] = g_value_get_float(value);
      break;
    case PROP_CONEPHI:
      self->priv->coneOrientation[1] = g_value_get_float(value);
      break;
    case PROP_COLORWHEEL:
      self->priv->colorWheel = g_value_get_float(value);
      break;
    case PROP_HIDINGMODE:
      self->priv->spinPolicy = g_value_get_uint(value);
      break;
    case PROP_ATOMIC:
      self->priv->spinAndAtomicRendering = g_value_get_boolean(value);
      break;
    case PROP_MODULUS:
      self->priv->spinModulusUsage = g_value_get_uint(value);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_method_spin_new:
 *
 * Creates a #VisuMethodSpin object.
 *
 * Since: 3.8
 *
 * Returns: a newly allocated #VisuMethodSpin object.
 **/
VisuMethodSpin* visu_method_spin_new(void)
{
  return g_object_new(VISU_TYPE_METHOD_SPIN, NULL);
}
/**
 * visu_method_spin_getDefault:
 *
 * Access the default spin method object.
 *
 * Since: 3.8
 *
 * Returns: (transfer none): the default spin method object.
 **/
VisuMethodSpin* visu_method_spin_getDefault(void)
{
  if (!_default)
    _default = g_object_new(VISU_TYPE_METHOD_SPIN, NULL);
  return _default;
}

static void _getOrientationColor(const VisuMethodSpin *method, gfloat rgb[3],
                                 const gfloat spinValues[3])
{
#ifndef DEG2RAD
#define DEG2RAD(x) (0.01745329251994329509 * x)    /* pi / 180 * x */
#endif
  gfloat spherical[3];
  float cosCone, sinCone;
  float matrix_rot_theta[3][3];
  float matrix_rot_phi[3][3];
  float cartesian[3];
  float cartesian_prime[3];
  float cartesian_second[3];
  float hsl[3];

  g_return_if_fail(VISU_IS_METHOD_SPIN(method));
  
  /* We draw a spin shape. */
  spherical[0] = 1;
  spherical[1] = spinValues[TOOL_MATRIX_SPHERICAL_THETA];
  spherical[2] = spinValues[TOOL_MATRIX_SPHERICAL_PHI];

  cosCone = cos(DEG2RAD(method->priv->coneOrientation[0]));
  sinCone = sin(DEG2RAD(method->priv->coneOrientation[0]));
  matrix_rot_theta[0][0] = cosCone;
  matrix_rot_theta[0][1] = 0;
  matrix_rot_theta[0][2] = -sinCone;
  matrix_rot_theta[1][0] = 0;
  matrix_rot_theta[1][1] = 1;
  matrix_rot_theta[1][2] = 0;
  matrix_rot_theta[2][0] = sinCone;
  matrix_rot_theta[2][1] = 0;
  matrix_rot_theta[2][2] = cosCone;

  cosCone = cos(DEG2RAD(-method->priv->coneOrientation[1]));
  sinCone = sin(DEG2RAD(-method->priv->coneOrientation[1]));
  matrix_rot_phi[0][0] = cosCone;
  matrix_rot_phi[0][1] = -sinCone;
  matrix_rot_phi[0][2] = 0;
  matrix_rot_phi[1][0] = sinCone;
  matrix_rot_phi[1][1] = cosCone;
  matrix_rot_phi[1][2] = 0;
  matrix_rot_phi[2][0] = 0;
  matrix_rot_phi[2][1] = 0;
  matrix_rot_phi[2][2] = 1;

  cartesian[0] = sin(DEG2RAD(spinValues[TOOL_MATRIX_SPHERICAL_THETA])) *
    cos(DEG2RAD(spinValues[TOOL_MATRIX_SPHERICAL_PHI]));
  cartesian[1] = sin(DEG2RAD(spinValues[TOOL_MATRIX_SPHERICAL_THETA])) *
    sin(DEG2RAD(spinValues[TOOL_MATRIX_SPHERICAL_PHI]));
  cartesian[2] = cos(DEG2RAD(spinValues[TOOL_MATRIX_SPHERICAL_THETA]));

  tool_matrix_productVector(cartesian_prime, matrix_rot_phi, cartesian);
  tool_matrix_productVector(cartesian_second, matrix_rot_theta, cartesian_prime);

  tool_matrix_cartesianToSpherical(spherical, cartesian_second);

  hsl[0] = tool_modulo_float(spherical[2] - method->priv->colorWheel, 360.f) / 360.f;
  hsl[1] = 1.f;
  hsl[2] = 1.f - spherical[1] / 180.f;
  
  tool_color_convertHSLtoRGB(rgb, hsl);
}
/**
 * visu_method_spin_getSpinVector:
 * @method: a #VisuMethodSpin object.
 * @dataSpin: a #VisuDataSpin object.
 * @node: a #VisuNode from @dataSpin.
 * @ratio: (out): the ratio to apply when drawing the spin.
 * @rgb: (out): a location to store the colour. 
 * @withAtomic: (out): a location to store if atomic rendering is
 * required.
 *
 * Retrieves the spin of @node in spherical coordinates. According to
 * rendering policy from @method, @ratio, @rgb and @withAtomic will be
 * set to their respective values. If no spin should be rendered for
 * @node, %NULL is returned. 
 *
 * Since: 3.8
 *
 * Returns: three floats representing the spin.
 **/
const gfloat* visu_method_spin_getSpinVector(const VisuMethodSpin *method,
                                             const VisuDataSpin *dataSpin,
                                             const VisuNode *node,
                                             gfloat *ratio, gfloat rgb[3],
                                             gboolean *withAtomic)
{
  const gfloat *spinValues;
  gfloat globalMax;
  guint iele;
  
  spinValues = visu_node_values_vector_getAtSpherical(visu_data_spin_get(dataSpin),
                                                      node);

  if (withAtomic)
    *withAtomic = (method->priv->spinPolicy == VISU_METHOD_SPIN_ATOMIC_NULL &&
                   spinValues[TOOL_MATRIX_SPHERICAL_MODULUS] == 0.f) ||
      method->priv->spinAndAtomicRendering;
  
  if (spinValues && (spinValues[TOOL_MATRIX_SPHERICAL_MODULUS] != 0. ||
		     method->priv->spinPolicy == VISU_METHOD_SPIN_ALWAYS))
    {
      if (ratio)
        /* Get the scaling factor for this element. */
        switch (method->priv->spinModulusUsage)
          {
          case VISU_METHOD_SPIN_PER_TYPE:
            *ratio = spinValues[TOOL_MATRIX_SPHERICAL_MODULUS] /
              visu_data_spin_getMaxModulus(dataSpin, node->posElement);
            break;
          case VISU_METHOD_SPIN_GLOBAL:
            globalMax = -G_MAXFLOAT;
            for (iele = 0; iele < visu_node_array_getNElements(VISU_NODE_ARRAY_CONST(dataSpin), FALSE); iele++)
              globalMax = MAX(visu_data_spin_getMaxModulus(dataSpin, iele), globalMax);
            *ratio = spinValues[TOOL_MATRIX_SPHERICAL_MODULUS] / globalMax;
            break;
          case VISU_METHOD_SPIN_CONSTANT:
            *ratio = 1.f;
            break;
          default:
            *ratio = spinValues[TOOL_MATRIX_SPHERICAL_MODULUS];
            break;
          }
      _getOrientationColor(method, rgb, spinValues);
      return spinValues;
    }
  else
    return (const gfloat*)0;
}
/**
 * visu_method_spin_getHidingPolicyFromName:
 * @name: (type filename): a string.
 * @policy: a location.
 *
 * In the config file, the hiding policy resource is stored with its name (untranslated).
 * This method is used to retrieve the id from the name.
 *
 * Returns: FALSE if the name does not match any value.
 */
gboolean visu_method_spin_getHidingPolicyFromName(const char *name,
                                                VisuMethodSpinDrawingPolicy *policy)
{
  g_return_val_if_fail(name && policy, FALSE);

  for (*policy = 0; *policy < VISU_METHOD_SPIN_N_MODES; *policy += 1)
    if (!g_strcmp0(name, policyNameSpin[*policy]))
      return TRUE;
  return FALSE;
}

static void onEntrySpin(VisuMethodSpin *self, VisuConfigFileEntry *entry, VisuConfigFile *object _U_)
{
  const gchar *label;

  label = visu_config_file_entry_getLabel(entry);

  if (!g_strcmp0(label, FLAG_SPIN_CONE_ANGLE))
    {
      float angles[2];
      float rg[2] = {0.f, 360.f};

      if (!visu_config_file_entry_popTokenAsFloat(entry, 2, angles, rg))
        return;
      if (self)
        g_object_set(self, "cone-theta", angles[0], "cone-phi", angles[1], NULL);
      else
        {
          _coneOrientation[0] = angles[0];
          _coneOrientation[1] = angles[1];
        }
    }
  else if (!g_strcmp0(label, FLAG_SPIN_WHEEL_ANGLE))
    {
      float angle;
      float rg[2] = {0.f, 360.f};

      if (!visu_config_file_entry_popTokenAsFloat(entry, 1, &angle, rg))
        return;
      if (self)
        g_object_set(self, "cone-omega", angle, NULL);
      else
        _colorWheel = angle;
    }
  else if (!g_strcmp0(label, FLAG_SPIN_HIDING_MODE))
    {
      VisuMethodSpinDrawingPolicy modeId;

      if (!visu_config_file_entry_popTokenAsEnum(entry, &modeId, visu_method_spin_getHidingPolicyFromName))
        return;
      if (self)
        g_object_set(self, "hiding-mode", modeId, NULL);
      else
        _spinPolicy = modeId;
    }
  else if (!g_strcmp0(label, FLAG_SPIN_AND_ATOMIC))
    {
      gboolean use;

      if (!visu_config_file_entry_popTokenAsBoolean(entry, 1, &use))
        return;
      if (self)
        g_object_set(self, "use-atomic", use, NULL);
      else
        _spinAndAtomicRendering = use;
    }
  else if (!g_strcmp0(label, FLAG_SPIN_MODULUS))
    {
      int modeId;
      int rg[2] = {0, VISU_METHOD_SPIN_N_MODULUS_MODES - 1};

      if (!visu_config_file_entry_popTokenAsInt(entry, 1, &modeId, rg))
        return;
      if (self)
        g_object_set(self, "modulus-scaling", modeId, NULL);
      else
        _spinModulusUsage = modeId;
    }
  else
    {
      g_warning(_("Unknown flag '%s', value ignored."), label);
    }
}

static void exportResourcesRenderingSpin(GString *method, VisuData* dataObj _U_)
{
  DBG_fprintf(stderr, "Method Spin: exporting resources...\n");

  visu_config_file_exportComment(method, DESC_RESOURCES_SPIN);
  visu_config_file_exportEntry(method, FLAG_RESOURCES_SPIN, FLAG_SPIN_CONE_ANGLE,
                               "%f %f", _coneOrientation[0], _coneOrientation[1]);

  visu_config_file_exportEntry(method, FLAG_RESOURCES_SPIN, FLAG_SPIN_WHEEL_ANGLE,
                               "%f", _colorWheel);

  visu_config_file_exportEntry(method, FLAG_RESOURCES_SPIN, FLAG_SPIN_HIDING_MODE,
                               "%s", policyNameSpin[_spinPolicy]);

  visu_config_file_exportEntry(method, FLAG_RESOURCES_SPIN, FLAG_SPIN_AND_ATOMIC,
                               "%d", _spinAndAtomicRendering);

  visu_config_file_exportEntry(method, FLAG_RESOURCES_SPIN, FLAG_SPIN_MODULUS,
                               "%d", _spinModulusUsage);
  DBG_fprintf(stderr, "Method Spin: resources succesfully exported\n");
}
