/*   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 "mover.h"
#include <iface_animatable.h>

/**
 * SECTION:mover
 * @short_description: Base class defining an undo stack of geometry modifications.
 *
 * <para>This is a base class to define geometry modifications with an
 * undo stack. The geometry modifications can be animated with
 * visu_node_mover_animate() or applied immediately with
 * visu_node_mover_apply().</para>
 */

/**
 * VisuNodeMoverClass:
 * @parent: the parent class;
 *
 * A short way to identify #_VisuNodeMoverClass structure.
 *
 * Since: 3.8
 */
/**
 * VisuNodeMover:
 *
 * An opaque structure.
 *
 * Since: 3.8
 */
/**
 * VisuNodeMoverPrivate:
 *
 * Private fields for #VisuNodeMover objects.
 *
 * Since: 3.8
 */
struct _VisuNodeMoverPrivate
{
  gboolean dispose_has_run;

  GWeakRef nodes;
  GArray *ids;

  GSList *stack;

  gfloat completion;
  VisuAnimation *anim;
};

enum
  {
    PROP_0,
    NODES_PROP,
    IDS_PROP,
    COMPLETION_PROP,
    UNDO_PROP,
    VALID_PROP,
    N_PROP
  };
static GParamSpec *_properties[N_PROP];

static void visu_node_mover_finalize(GObject* obj);
static void visu_node_mover_dispose(GObject* obj);
static void visu_node_mover_get_property(GObject* obj, guint property_id,
                                         GValue *value, GParamSpec *pspec);
static void visu_node_mover_set_property(GObject* obj, guint property_id,
                                         const GValue *value, GParamSpec *pspec);
static void visu_animatable_interface_init(VisuAnimatableInterface *iface);
static VisuAnimation* _getAnimation(const VisuAnimatable *animatable, const gchar *prop);
static gboolean _validate(const VisuNodeMover *mover);

G_DEFINE_TYPE_WITH_CODE(VisuNodeMover, visu_node_mover, VISU_TYPE_OBJECT,
                        G_ADD_PRIVATE(VisuNodeMover)
                        G_IMPLEMENT_INTERFACE(VISU_TYPE_ANIMATABLE,
                                              visu_animatable_interface_init))

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

  /* Connect the overloading methods. */
  G_OBJECT_CLASS(klass)->dispose  = visu_node_mover_dispose;
  G_OBJECT_CLASS(klass)->finalize = visu_node_mover_finalize;
  G_OBJECT_CLASS(klass)->set_property = visu_node_mover_set_property;
  G_OBJECT_CLASS(klass)->get_property = visu_node_mover_get_property;

  /**
   * VisuNodeMover::nodes:
   *
   * #VisuNodeArray to apply the move to.
   *
   * Since: 3.8
   */
  _properties[NODES_PROP] = g_param_spec_object("nodes", "Nodes",
                                                "nodes to apply the move to.",
                                                VISU_TYPE_NODE_ARRAY,
                                                G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuNodeMover::ids:
   *
   * The node ids to move.
   *
   * Since: 3.8
   */
  _properties[IDS_PROP] = g_param_spec_boxed("ids", "Ids",
                                             "node ids to move.",
                                             G_TYPE_ARRAY,
                                             G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuNodeMover::completion:
   *
   * Percentage of completion during animation.
   *
   * Since: 3.8
   */
  _properties[COMPLETION_PROP] = g_param_spec_float("completion", "Completion",
                                                    "percentage of completion during animation.",
                                                    0.f, 1.f, 0.f,
                                                    G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuNodeMover::undo-stack-depth:
   *
   * Percentage of completion during animation.
   *
   * Since: 3.8
   */
  _properties[UNDO_PROP] = g_param_spec_uint("undo-stack-depth", "Undo stack depth",
                                             "undo stack depth.",
                                             0, G_MAXUINT, 0,
                                             G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
  /**
   * VisuNodeMover::valid:
   *
   * If current mover parameter are valid.
   *
   * Since: 3.8
   */
  _properties[VALID_PROP] = g_param_spec_boolean("valid", "Valid",
                                                 "if mover parameters are valid.",
                                                 FALSE,
                                                 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties(G_OBJECT_CLASS(klass), N_PROP, _properties);
}
static void visu_animatable_interface_init(VisuAnimatableInterface *iface)
{
  iface->get_animation = _getAnimation;
}

static void visu_node_mover_init(VisuNodeMover *obj)
{
  DBG_fprintf(stderr, "Visu Mover: initializing a new object (%p).\n",
	      (gpointer)obj);
  
  obj->priv = visu_node_mover_get_instance_private(obj);
  obj->priv->dispose_has_run = FALSE;

  g_weak_ref_init(&obj->priv->nodes, (gpointer)0);
  obj->priv->ids = (GArray*)0;
  obj->priv->stack = (GSList*)0;
  obj->priv->completion = 0.f;
  obj->priv->anim = visu_animation_new(G_OBJECT(obj), "completion");
}
static void visu_node_mover_dispose(GObject* obj)
{
  VisuNodeMover *mover;

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

  mover = VISU_NODE_MOVER(obj);
  if (mover->priv->dispose_has_run)
    return;
  mover->priv->dispose_has_run = TRUE;

  /* Disconnect signals. */
  g_weak_ref_clear(&mover->priv->nodes);
  g_object_unref(mover->priv->anim);

  /* Chain up to the parent class */
  G_OBJECT_CLASS(visu_node_mover_parent_class)->dispose(obj);
}
static void visu_node_mover_finalize(GObject* obj)
{
  VisuNodeMover *mover;

  g_return_if_fail(obj);

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

  mover = VISU_NODE_MOVER(obj);

  /* Free privs elements. */
  if (mover->priv->ids)
    g_array_unref(mover->priv->ids);
  if (mover->priv->stack)
    g_slist_free_full(mover->priv->stack, (GDestroyNotify)g_array_unref);

  /* Chain up to the parent class */
  DBG_fprintf(stderr, "Visu Mover: chain to parent.\n");
  G_OBJECT_CLASS(visu_node_mover_parent_class)->finalize(obj);
  DBG_fprintf(stderr, "Visu Mover: freeing ... OK.\n");
}
static void visu_node_mover_get_property(GObject* obj, guint property_id,
                                         GValue *value, GParamSpec *pspec)
{
  VisuNodeMover *self = VISU_NODE_MOVER(obj);

  switch (property_id)
    {
    case NODES_PROP:
      g_value_take_object(value, g_weak_ref_get(&self->priv->nodes));
      break;
    case IDS_PROP:
      g_value_set_boxed(value, self->priv->ids);
      break;
    case COMPLETION_PROP:
      g_value_set_float(value, self->priv->completion);
      break;
    case UNDO_PROP:
      g_value_set_uint(value, g_slist_length(self->priv->stack));
      break;
    case VALID_PROP:
      g_value_set_boolean(value, _validate(self));
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static void visu_node_mover_set_property(GObject* obj, guint property_id,
                                         const GValue *value, GParamSpec *pspec)
{
  VisuNodeMover *self = VISU_NODE_MOVER(obj);
  VisuNodeArray *arr;

  switch (property_id)
    {
    case NODES_PROP:
      g_weak_ref_set(&self->priv->nodes, g_value_get_object(value));
      break;
    case IDS_PROP:
      visu_node_mover_setNodes(self, g_value_get_boxed(value));
      break;
    case COMPLETION_PROP:
      self->priv->completion = g_value_get_float(value);
      if (!self->priv->ids || !self->priv->ids->len)
        break;
      arr = g_weak_ref_get(&self->priv->nodes);
      if (!arr)
        break;

      if (VISU_NODE_MOVER_GET_CLASS(self)->apply &&
          VISU_NODE_MOVER_GET_CLASS(self)->apply(self, arr, self->priv->ids,
                                                 self->priv->completion) &&
          self->priv->completion == 1.f)
        {
          self->priv->stack = g_slist_prepend(self->priv->stack,
                                              g_array_ref(self->priv->ids));
          g_object_notify_by_pspec(obj, _properties[UNDO_PROP]);
        }
      g_object_unref(arr);
      break;
    default:
      /* We don't have any other property... */
      G_OBJECT_WARN_INVALID_PROPERTY_ID(obj, property_id, pspec);
      break;
    }
}
static VisuAnimation* _getAnimation(const VisuAnimatable *animatable,
                                    const gchar *prop)
{
  g_return_val_if_fail(VISU_IS_NODE_MOVER(animatable), (VisuAnimation*)0);

  return !g_strcmp0(prop, "completion") ? VISU_NODE_MOVER(animatable)->priv->anim : (VisuAnimation*)0;
}

/**
 * visu_node_mover_setNodes:
 * @mover: a #VisuNodeMover object.
 * @ids: (element-type uint): a list of node ids.
 *
 * Defines the nodes that will be affected by the modification in @mover.
 *
 * Since: 3.8
 **/
void visu_node_mover_setNodes(VisuNodeMover *mover, GArray *ids)
{
  g_return_if_fail(VISU_IS_NODE_MOVER(mover));

  if (mover->priv->ids)
    g_array_unref(mover->priv->ids);

  mover->priv->ids = g_array_ref(ids);
  g_object_notify_by_pspec(G_OBJECT(mover), _properties[IDS_PROP]);
  g_object_notify_by_pspec(G_OBJECT(mover), _properties[VALID_PROP]);
}
/**
 * visu_node_mover_getNodes:
 * @mover: a #VisuNodeMover object.
 *
 * Retrieves the node ids impacted by @mover.
 *
 * Since: 3.8
 *
 * Returns: (transfer none) (element-type uint): a list of node ids.
 **/
const GArray* visu_node_mover_getNodes(const VisuNodeMover *mover)
{
  g_return_val_if_fail(VISU_IS_NODE_MOVER(mover), (const GArray*)0);

  return mover->priv->ids;
}

static gboolean _validate(const VisuNodeMover *mover)
{
  g_return_val_if_fail(VISU_IS_NODE_MOVER(mover), FALSE);

  if (!mover->priv->ids || !mover->priv->ids->len)
    return FALSE;

  return VISU_NODE_MOVER_GET_CLASS(mover)->validate &&
    VISU_NODE_MOVER_GET_CLASS(mover)->validate(mover);
}

/**
 * visu_node_mover_apply:
 * @mover: a #VisuNodeMover object.
 *
 * Apply the modification defined by @mover. To animate the
 * transition, use visu_node_mover_animate().
 *
 * Since: 3.8
 **/
void visu_node_mover_apply(VisuNodeMover *mover)
{
  VisuNodeArray *arr;
  
  g_return_if_fail(VISU_IS_NODE_MOVER(mover));

  if (!mover->priv->ids || !mover->priv->ids->len)
    return;
      
  g_return_if_fail(mover->priv->completion == 0.f || mover->priv->completion == 1.f);
  
  arr = g_weak_ref_get(&mover->priv->nodes);
  if (!arr)
    return;

  if (VISU_NODE_MOVER_GET_CLASS(mover)->setup)
    VISU_NODE_MOVER_GET_CLASS(mover)->setup(mover);
  if (VISU_NODE_MOVER_GET_CLASS(mover)->apply &&
      VISU_NODE_MOVER_GET_CLASS(mover)->apply(mover, arr, mover->priv->ids, 1.f))
    mover->priv->stack = g_slist_prepend(mover->priv->stack,
                                         g_array_ref(mover->priv->ids));

  g_object_unref(arr);
}
/**
 * visu_node_mover_animate:
 * @mover: a #VisuNodeMover object.
 *
 * Starts the animation defined by @mover. For an application of the
 * change without animation, use visu_node_mover_apply().
 *
 * Since: 3.8
 **/
void visu_node_mover_animate(VisuNodeMover *mover)
{
  VisuNodeArray *arr;
  
  g_return_if_fail(VISU_IS_NODE_MOVER(mover));

  if (!mover->priv->ids || !mover->priv->ids->len)
    return;
      
  g_return_if_fail(mover->priv->completion == 0.f || mover->priv->completion == 1.f);
  
  arr = g_weak_ref_get(&mover->priv->nodes);
  if (!arr)
    return;

  mover->priv->completion = 0.f;
  if (VISU_NODE_MOVER_GET_CLASS(mover)->setup)
    VISU_NODE_MOVER_GET_CLASS(mover)->setup(mover);
  if (!visu_animatable_animateFloat(VISU_ANIMATABLE(mover), mover->priv->anim,
                                    1.f, 400, FALSE, VISU_ANIMATION_SIN))
    {
      if (VISU_NODE_MOVER_GET_CLASS(mover)->apply &&
          VISU_NODE_MOVER_GET_CLASS(mover)->apply(mover, arr, mover->priv->ids, 1.f))
        {
          mover->priv->stack = g_slist_prepend(mover->priv->stack,
                                               g_array_ref(mover->priv->ids));
          g_object_notify_by_pspec(G_OBJECT(mover), _properties[UNDO_PROP]);
        }
    }

  g_object_unref(arr);
}
/**
 * visu_node_mover_push:
 * @mover: a #VisuNodeMover object.
 *
 * Push the current modification on the undo stack without actually
 * doing the modification.
 *
 * Since: 3.8
 **/
void visu_node_mover_push(VisuNodeMover *mover)
{
  g_return_if_fail(VISU_IS_NODE_MOVER(mover));

  if (VISU_NODE_MOVER_GET_CLASS(mover)->push &&
      VISU_NODE_MOVER_GET_CLASS(mover)->push(mover))
    {
      mover->priv->stack = g_slist_prepend(mover->priv->stack,
                                           g_array_ref(mover->priv->ids));
      g_object_notify_by_pspec(G_OBJECT(mover), _properties[UNDO_PROP]);
    }
}
/**
 * visu_node_mover_undo:
 * @mover: a #VisuNodeMover object.
 *
 * Undo the last modification.
 *
 * Since: 3.8
 **/
void visu_node_mover_undo(VisuNodeMover *mover)
{
  VisuNodeArray *arr;
  GSList *ids;
  
  g_return_if_fail(VISU_IS_NODE_MOVER(mover));

  if (!mover->priv->stack)
    return;
      
  arr = g_weak_ref_get(&mover->priv->nodes);
  if (!arr)
    return;
  
  if (VISU_NODE_MOVER_GET_CLASS(mover)->undo)
    {
      ids = mover->priv->stack;
      mover->priv->stack = g_slist_next(ids);
      VISU_NODE_MOVER_GET_CLASS(mover)->undo(mover, arr, (const GArray*)ids->data);
      g_array_unref((GArray*)ids->data);
      g_slist_free_1(ids);
      g_object_notify_by_pspec(G_OBJECT(mover), _properties[UNDO_PROP]);
    }

  g_object_unref(arr);
}
