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

#include <math.h>

/**
 * SECTION:visu_animation
 * @short_description: Defines a class to setup animation of a property.
 *
 * <para>Use this object to animate smoothly a change between to
 * values ofg a given object property.</para>
 */

struct _VisuAnimationPrivate
{
  gboolean dispose_has_run;

  GWeakRef obj;
  gchar *prop;
  GValue from, to;

  gboolean blocked, loop;
  gulong ref, duration;
  VisuAnimationType type;
};

enum
  {
    PROP_0,
    RUNNING_PROP,
    N_PROP
  };
static GParamSpec *_properties[N_PROP];

static void visu_animation_dispose     (GObject* obj);
static void visu_animation_finalize    (GObject* obj);
static void visu_animation_get_property(GObject* obj, guint property_id,
                                        GValue *value, GParamSpec *pspec);

G_DEFINE_TYPE_WITH_CODE(VisuAnimation, visu_animation, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuAnimation))

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

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_animation_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_animation_finalize;
  G_OBJECT_CLASS(klass)->get_property = visu_animation_get_property;

  /**
   * VisuAnimation::running:
   *
   * Wether the animation is running.
   *
   * Since: 3.8
   */
  _properties[RUNNING_PROP] =
    g_param_spec_boolean("running", "Running", "animation is running",
                         FALSE, G_PARAM_READABLE);

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

static void visu_animation_init(VisuAnimation *self)
{
  GValue init = G_VALUE_INIT;
  
  DBG_fprintf(stderr, "Visu Animation: initializing a new object (%p).\n",
	      (gpointer)self);
  self->priv = visu_animation_get_instance_private(self);
  self->priv->dispose_has_run = FALSE;

  self->priv->prop = (gchar*)0;
  self->priv->from = init;
  self->priv->to = init;
  self->priv->ref = 0;
  self->priv->blocked = FALSE;
  self->priv->loop = TRUE;
}

/* This method can be called several times.
   It should unref all of its reference to
   GObjects. */
static void visu_animation_dispose(GObject* obj)
{
  VisuAnimation *self;

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

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

  visu_animation_abort(self);

  /* Chain up to the parent class */
  DBG_fprintf(stderr, "Visu Animation: chain up to parent.\n");
  G_OBJECT_CLASS(visu_animation_parent_class)->dispose(obj);
  DBG_fprintf(stderr, "Visu Animation: dispose ... OK.\n");
}
static void visu_animation_finalize(GObject* obj)
{
  VisuAnimation *self;

  self = VISU_ANIMATION(obj);

  g_free(self->priv->prop);

  G_OBJECT_CLASS(visu_animation_parent_class)->finalize(obj);
}
static void visu_animation_get_property(GObject* obj, guint property_id,
                                        GValue *value, GParamSpec *pspec)
{
  VisuAnimation *self = VISU_ANIMATION(obj);

  switch (property_id)
    {
    case RUNNING_PROP:
      g_value_set_boolean(value, (self->priv->ref > 0));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}

/**
 * visu_animation_new:
 * @obj: (transfer none): a #GObject object.
 * @property: a property name.
 *
 * Create an animation for @property of @obj.
 *
 * Since: 3.8
 *
 * Returns: (transfer full): a new #VisuAnimation object.
 **/
VisuAnimation* visu_animation_new(GObject *obj, const gchar *property)
{
  GParamSpec *pspec;
  VisuAnimation *anim;
  
  pspec = g_object_class_find_property(G_OBJECT_GET_CLASS(obj), property);
  g_return_val_if_fail(pspec, (VisuAnimation*)0);

  anim = g_object_new(VISU_TYPE_ANIMATION, NULL);
  anim->priv->prop = g_strdup(property);
  g_value_init(&anim->priv->from, G_PARAM_SPEC_VALUE_TYPE(pspec));
  g_value_init(&anim->priv->to, G_PARAM_SPEC_VALUE_TYPE(pspec));
  g_weak_ref_set(&anim->priv->obj, obj);

  return anim;
}

/**
 * visu_animation_start:
 * @anim: a #VisuAnimation object.
 * @to: a final value.
 * @tick: the current clock time.
 * @duration: a duration in micro-seconds.
 * @loop: a boolean.
 * @type: a type.
 *
 * Starts @anim, to go to the value @to from its current value
 * following @type evolution. @tick is the current clock time and the
 * animation is scheduled to last for @duration. If @loop is true,
 * when the animation reaches @to it restarts again from the initial
 * value. Use visu_animation_abort() to stop it.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the animation is actually started.
 **/
gboolean visu_animation_start(VisuAnimation *anim, const GValue *to, gulong tick,
                              gulong duration, gboolean loop, VisuAnimationType type)
{
  gpointer obj;
  
  g_return_val_if_fail(VISU_IS_ANIMATION(anim), FALSE);

  if (anim->priv->blocked)
    return FALSE;

  if (!anim->priv->ref)
    {
      obj = g_weak_ref_get(&anim->priv->obj);
      if (!obj)
        return FALSE;
      g_object_get_property(obj, anim->priv->prop, &anim->priv->from);
      g_object_unref(obj);
    }
  else
    {
      anim->priv->ref = 0;
      g_value_copy(&anim->priv->to, &anim->priv->from);
    }
  g_value_copy(to, &anim->priv->to);

  switch (G_VALUE_TYPE(&anim->priv->to))
    {
    case G_TYPE_FLOAT:
      if (g_value_get_float(&anim->priv->from) == g_value_get_float(&anim->priv->to))
        return FALSE;
      break;
    case G_TYPE_DOUBLE:
      if (g_value_get_double(&anim->priv->from) == g_value_get_double(&anim->priv->to))
        return FALSE;
      break;
    default:
      g_warning("Type not implemented in animation for %s.", anim->priv->prop);
    };

  anim->priv->ref = tick;
  anim->priv->duration = duration;
  anim->priv->loop = loop;
  anim->priv->type = type;
  g_object_notify_by_pspec(G_OBJECT(anim), _properties[RUNNING_PROP]);

  DBG_fprintf(stderr, "Visu Animation: start animation at %ld for %ld.\n",
              tick, duration);
  return TRUE;
}

/**
 * visu_animation_animate:
 * @anim: a #VisuAnimation object.
 * @tick: a time clock.
 *
 * Update the property animated by @anim to the value it should take
 * at @tick.
 *
 * Since: 3.8
 *
 * Returns: TRUE if the animation is finished.
 **/
gboolean visu_animation_animate(VisuAnimation *anim, gulong tick)
{
  double alpha;
  gboolean finished;
  gpointer obj;
  GValue at = G_VALUE_INIT;
  
  g_return_val_if_fail(VISU_IS_ANIMATION(anim), FALSE);

  if (!anim->priv->ref)
    return FALSE;

  alpha = (double)(tick - anim->priv->ref) / (double)anim->priv->duration;
  DBG_fprintf(stderr, "Visu Animation: alpha is %g for %s (%ld).\n",
              alpha, anim->priv->prop, tick);
  finished = (alpha > 1.);
  alpha = CLAMP(alpha, 0., 1.);
  switch (anim->priv->type)
    {
    case VISU_ANIMATION_LINEAR:
      break;
    case VISU_ANIMATION_QUAD:
      alpha = (alpha < 0.5) ? 2. * alpha * alpha : 1. - 2. * (1. - alpha) * (1. - alpha) ;
      break;
    case VISU_ANIMATION_SIN:
      alpha = 0.5 - 0.5 * cos(G_PI * alpha);
      break;
    };
  
  g_value_init(&at, G_VALUE_TYPE(&anim->priv->to));
  switch (G_VALUE_TYPE(&anim->priv->to))
    {
    case G_TYPE_FLOAT:
      g_value_set_float(&at, g_value_get_float(&anim->priv->from) + alpha * (g_value_get_float(&anim->priv->to) - g_value_get_float(&anim->priv->from)));
      DBG_fprintf(stderr, "Visu Animation: set float value to %g.\n",
                  g_value_get_float(&at));
      break;
    case G_TYPE_DOUBLE:
      g_value_set_double(&at, g_value_get_double(&anim->priv->from) + alpha * (g_value_get_double(&anim->priv->to) - g_value_get_double(&anim->priv->from)));
      DBG_fprintf(stderr, "Visu Animation: set double value to %g.\n",
                  g_value_get_double(&at));
      break;
    default:
      g_warning("Type not implemented in animation for %s.", anim->priv->prop);
    };
  obj = g_weak_ref_get(&anim->priv->obj);
  if (!obj)
    return FALSE;

  anim->priv->blocked = TRUE;
  g_object_set_property(obj, anim->priv->prop, &at);
  anim->priv->blocked = FALSE;
  g_value_unset(&at);

  if (finished && anim->priv->loop)
    {
      finished = FALSE;
      anim->priv->ref = tick;
      anim->priv->blocked = TRUE;
      g_object_set_property(obj, anim->priv->prop, &anim->priv->from);
      anim->priv->blocked = FALSE;
    }
  else if (finished)
    visu_animation_abort(anim);

  g_object_unref(obj);
  return !finished;
}

/**
 * visu_animation_abort:
 * @anim: a #VisuAnimation object.
 *
 * Stop the current animation.
 *
 * Since: 3.8
 **/
void visu_animation_abort(VisuAnimation *anim)
{
  g_return_if_fail(VISU_IS_ANIMATION(anim));

  anim->priv->ref = 0;
  g_object_notify_by_pspec(G_OBJECT(anim), _properties[RUNNING_PROP]);
  g_value_reset(&anim->priv->from);
  g_value_reset(&anim->priv->to);
}

/**
 * visu_animation_isRunning:
 * @anim: a #VisuAnimation object.
 *
 * Inquires if @anim is currently running.
 *
 * Since: 3.8
 *
 * Returns: TRUE if @anim is running.
 **/
gboolean visu_animation_isRunning(const VisuAnimation *anim)
{
  g_return_val_if_fail(VISU_IS_ANIMATION(anim), FALSE);

  return (anim->priv->ref > 0);
}

/**
 * visu_animation_getFrom:
 * @anim: a #VisuAnimation object.
 * @from: (out caller-allocates): a location to store the initial
 * value of @anim.
 *
 * Retrieves the initial value of @anim. The animation should be
 * running for this value to be defined.
 *
 * Since: 3.8
 **/
void visu_animation_getFrom(const VisuAnimation *anim, GValue *from)
{
  g_return_if_fail(VISU_IS_ANIMATION(anim));

  g_value_copy(&anim->priv->from, from);
}

/**
 * visu_animation_getTo:
 * @anim: a #VisuAnimation object.
 * @to: (out caller-allocates): a location to store the final
 * value of @anim.
 *
 * Retrieves the final value of @anim. The animation should be
 * running for this value to be defined.
 *
 * Since: 3.8
 **/
void visu_animation_getTo(const VisuAnimation *anim, GValue *to)
{
  g_return_if_fail(VISU_IS_ANIMATION(anim));

  g_value_copy(&anim->priv->to, to);
}
