/*
 * Clutter.
 *
 * An OpenGL based 'interactive canvas' library.
 *
 * Copyright (C) 2008  Intel Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 * Author:
 *   Emmanuele Bassi <ebassi@linux.intel.com>
 */

/**
 * ClutterInterval:
 * 
 * An object holding an interval of two values
 *
 * #ClutterInterval is a simple object that can hold two values
 * defining an interval. #ClutterInterval can hold any value that
 * can be enclosed inside a #GValue.
 *
 * Once a #ClutterInterval for a specific #GType has been instantiated
 * the #ClutterInterval:value-type property cannot be changed anymore.
 *
 * #ClutterInterval starts with a floating reference; this means that
 * any object taking a reference on a #ClutterInterval instance should
 * also take ownership of the interval by using g_object_ref_sink().
 *
 * #ClutterInterval can be subclassed to override the validation
 * and value computation.
 */

#include "clutter/clutter-build-config.h"

#include <stdlib.h>
#include <string.h>

#include <glib.h>
#include <glib-object.h>
#include <gobject/gvaluecollector.h>

#include "clutter/clutter-color.h"
#include "clutter/clutter-interval.h"
#include "clutter/clutter-private.h"
#include "clutter/clutter-units.h"
#include "clutter/clutter-scriptable.h"
#include "clutter/clutter-script-private.h"

enum
{
  PROP_0,

  PROP_VALUE_TYPE,
  PROP_INITIAL,
  PROP_FINAL,

  PROP_LAST
};

static GParamSpec *obj_props[PROP_LAST];

enum
{
  INITIAL = 0,
  FINAL,
  RESULT,

  N_VALUES
};

struct _ClutterIntervalPrivate
{
  GType value_type;

  GValue *values;
};

static void clutter_scriptable_iface_init (ClutterScriptableIface *iface);

G_DEFINE_TYPE_WITH_CODE (ClutterInterval,
                         clutter_interval,
                         G_TYPE_INITIALLY_UNOWNED,
                         G_ADD_PRIVATE (ClutterInterval)
                         G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_SCRIPTABLE,
                                                clutter_scriptable_iface_init));


static gboolean
clutter_interval_real_validate (ClutterInterval *interval,
                                GParamSpec      *pspec)
{
  GType pspec_gtype = G_PARAM_SPEC_VALUE_TYPE (pspec);

  /* then check the fundamental types */
  switch (G_TYPE_FUNDAMENTAL (pspec_gtype))
    {
    case G_TYPE_INT:
      {
        GParamSpecInt *pspec_int = G_PARAM_SPEC_INT (pspec);
        gint a, b;

        a = b = 0;
        clutter_interval_get_interval (interval, &a, &b);
        if ((a >= pspec_int->minimum && a <= pspec_int->maximum) &&
            (b >= pspec_int->minimum && b <= pspec_int->maximum))
          return TRUE;
        else
          return FALSE;
      }
      break;

    case G_TYPE_INT64:
      {
        GParamSpecInt64 *pspec_int = G_PARAM_SPEC_INT64 (pspec);
        gint64 a, b;

        a = b = 0;
        clutter_interval_get_interval (interval, &a, &b);
        if ((a >= pspec_int->minimum && a <= pspec_int->maximum) &&
            (b >= pspec_int->minimum && b <= pspec_int->maximum))
          return TRUE;
        else
          return FALSE;
      }
      break;

    case G_TYPE_UINT:
      {
        GParamSpecUInt *pspec_uint = G_PARAM_SPEC_UINT (pspec);
        guint a, b;

        a = b = 0;
        clutter_interval_get_interval (interval, &a, &b);
        if ((a >= pspec_uint->minimum && a <= pspec_uint->maximum) &&
            (b >= pspec_uint->minimum && b <= pspec_uint->maximum))
          return TRUE;
        else
          return FALSE;
      }
      break;

    case G_TYPE_UINT64:
      {
        GParamSpecUInt64 *pspec_int = G_PARAM_SPEC_UINT64 (pspec);
        guint64 a, b;

        a = b = 0;
        clutter_interval_get_interval (interval, &a, &b);
        if ((a >= pspec_int->minimum && a <= pspec_int->maximum) &&
            (b >= pspec_int->minimum && b <= pspec_int->maximum))
          return TRUE;
        else
          return FALSE;
      }
      break;

    case G_TYPE_CHAR:
      {
        GParamSpecChar *pspec_char = G_PARAM_SPEC_CHAR (pspec);
        guchar a, b;

        a = b = 0;
        clutter_interval_get_interval (interval, &a, &b);
        if ((a >= pspec_char->minimum && a <= pspec_char->maximum) &&
            (b >= pspec_char->minimum && b <= pspec_char->maximum))
          return TRUE;
        else
          return FALSE;
      }
      break;

    case G_TYPE_UCHAR:
      {
        GParamSpecUChar *pspec_uchar = G_PARAM_SPEC_UCHAR (pspec);
        guchar a, b;

        a = b = 0;
        clutter_interval_get_interval (interval, &a, &b);
        if ((a >= pspec_uchar->minimum && a <= pspec_uchar->maximum) &&
            (b >= pspec_uchar->minimum && b <= pspec_uchar->maximum))
          return TRUE;
        else
          return FALSE;
      }
      break;

    case G_TYPE_FLOAT:
      {
        GParamSpecFloat *pspec_flt = G_PARAM_SPEC_FLOAT (pspec);
        float a, b;

        a = b = 0.f;
        clutter_interval_get_interval (interval, &a, &b);
        if ((a >= pspec_flt->minimum && a <= pspec_flt->maximum) &&
            (b >= pspec_flt->minimum && b <= pspec_flt->maximum))
          return TRUE;
        else
          return FALSE;
      }
      break;

    case G_TYPE_DOUBLE:
      {
        GParamSpecDouble *pspec_flt = G_PARAM_SPEC_DOUBLE (pspec);
        double a, b;

        a = b = 0;
        clutter_interval_get_interval (interval, &a, &b);
        if ((a >= pspec_flt->minimum && a <= pspec_flt->maximum) &&
            (b >= pspec_flt->minimum && b <= pspec_flt->maximum))
          return TRUE;
        else
          return FALSE;
      }
      break;

    case G_TYPE_BOOLEAN:
      return TRUE;

    default:
      break;
    }

  return TRUE;
}

static gboolean
clutter_interval_real_compute_value (ClutterInterval *interval,
                                     gdouble          factor,
                                     GValue          *value)
{
  GValue *initial, *final;
  GType value_type;
  gboolean retval = FALSE;

  initial = clutter_interval_peek_initial_value (interval);
  final = clutter_interval_peek_final_value (interval);

  value_type = clutter_interval_get_value_type (interval);

  if (_clutter_has_progress_function (value_type))
    {
      retval = _clutter_run_progress_function (value_type,
                                               initial,
                                               final,
                                               factor,
                                               value);
      if (retval)
        return TRUE;
    }

  switch (G_TYPE_FUNDAMENTAL (value_type))
    {
    case G_TYPE_INT:
      {
        gint ia, ib, res;

        ia = g_value_get_int (initial);
        ib = g_value_get_int (final);

        res = (factor * (ib - ia)) + ia;

        g_value_set_int (value, res);

        retval = TRUE;
      }
      break;

    case G_TYPE_CHAR:
      {
        gchar ia, ib, res;

        ia = g_value_get_schar (initial);
        ib = g_value_get_schar (final);

        res = (factor * (ib - (gdouble) ia)) + ia;

        g_value_set_schar (value, res);

        retval = TRUE;
      }
      break;

    case G_TYPE_UINT:
      {
        guint ia, ib, res;

        ia = g_value_get_uint (initial);
        ib = g_value_get_uint (final);

        res = (factor * (ib - (gdouble) ia)) + ia;

        g_value_set_uint (value, res);

        retval = TRUE;
      }
      break;

    case G_TYPE_UCHAR:
      {
        guchar ia, ib, res;

        ia = g_value_get_uchar (initial);
        ib = g_value_get_uchar (final);

        res = (factor * (ib - (gdouble) ia)) + ia;

        g_value_set_uchar (value, res);

        retval = TRUE;
      }
      break;

    case G_TYPE_FLOAT:
    case G_TYPE_DOUBLE:
      {
        gdouble ia, ib, res;

        if (value_type == G_TYPE_DOUBLE)
          {
            ia = g_value_get_double (initial);
            ib = g_value_get_double (final);
          }
        else
          {
            ia = g_value_get_float (initial);
            ib = g_value_get_float (final);
          }

        res = (factor * (ib - ia)) + ia;

        if (value_type == G_TYPE_DOUBLE)
          g_value_set_double (value, res);
        else
          g_value_set_float (value, res);

        retval = TRUE;
      }
      break;

    case G_TYPE_BOOLEAN:
      if (factor > 0.5)
        g_value_set_boolean (value, TRUE);
      else
        g_value_set_boolean (value, FALSE);

      retval = TRUE;
      break;

    case G_TYPE_BOXED:
      break;

    default:
      break;
    }

  /* We're trying to animate a property without knowing how to do that. Issue
   * a warning with a hint to what could be done to fix that */
  if (G_UNLIKELY (retval == FALSE))
    {
      g_warning ("%s: Could not compute progress between two %s. You can "
                 "register a progress function to instruct ClutterInterval "
                 "how to deal with this GType",
                 G_STRLOC,
                 g_type_name (value_type));
    }

  return retval;
}

static void
clutter_interval_finalize (GObject *gobject)
{
  ClutterIntervalPrivate *priv = CLUTTER_INTERVAL (gobject)->priv;

  if (G_IS_VALUE (&priv->values[INITIAL]))
    g_value_unset (&priv->values[INITIAL]);

  if (G_IS_VALUE (&priv->values[FINAL]))
    g_value_unset (&priv->values[FINAL]);

  if (G_IS_VALUE (&priv->values[RESULT]))
    g_value_unset (&priv->values[RESULT]);

  g_free (priv->values);

  G_OBJECT_CLASS (clutter_interval_parent_class)->finalize (gobject);
}

static void
clutter_interval_set_property (GObject      *gobject,
                               guint         prop_id,
                               const GValue *value,
                               GParamSpec   *pspec)
{
  ClutterInterval *self = CLUTTER_INTERVAL (gobject);
  ClutterIntervalPrivate *priv = clutter_interval_get_instance_private (self);

  switch (prop_id)
    {
    case PROP_VALUE_TYPE:
      priv->value_type = g_value_get_gtype (value);
      break;

    case PROP_INITIAL:
      if (g_value_get_boxed (value) != NULL)
        clutter_interval_set_initial_value (self, g_value_get_boxed (value));
      else if (G_IS_VALUE (&priv->values[INITIAL]))
        g_value_unset (&priv->values[INITIAL]);
      break;

    case PROP_FINAL:
      if (g_value_get_boxed (value) != NULL)
        clutter_interval_set_final_value (self, g_value_get_boxed (value));
      else if (G_IS_VALUE (&priv->values[FINAL]))
        g_value_unset (&priv->values[FINAL]);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
      break;
    }
}

static void
clutter_interval_get_property (GObject    *gobject,
                               guint       prop_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  ClutterIntervalPrivate *priv;
  
  priv = clutter_interval_get_instance_private (CLUTTER_INTERVAL (gobject));

  switch (prop_id)
    {
    case PROP_VALUE_TYPE:
      g_value_set_gtype (value, priv->value_type);
      break;

    case PROP_INITIAL:
      if (G_IS_VALUE (&priv->values[INITIAL]))
        g_value_set_boxed (value, &priv->values[INITIAL]);
      break;

    case PROP_FINAL:
      if (G_IS_VALUE (&priv->values[FINAL]))
        g_value_set_boxed (value, &priv->values[FINAL]);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
      break;
    }
}

static gboolean
clutter_interval_parse_custom_node (ClutterScriptable *scriptable,
                                    ClutterScript     *script,
                                    GValue            *value,
                                    const gchar       *name,
                                    JsonNode          *node)
{
  ClutterIntervalPrivate *priv = CLUTTER_INTERVAL (scriptable)->priv;

  if ((strcmp (name, "initial") == 0) || (strcmp (name, "final") == 0))
    {
      g_value_init (value, priv->value_type);
      return _clutter_script_parse_node (script, value, name, node, NULL);
    }

  return FALSE;
}

static void
clutter_interval_set_custom_property (ClutterScriptable *scriptable,
                                      ClutterScript     *script,
                                      const gchar       *name,
                                      const GValue      *value)
{
  ClutterInterval *self = CLUTTER_INTERVAL (scriptable);

  if (strcmp (name, "initial") == 0)
    clutter_interval_set_initial_value (self, value);
  else if (strcmp (name, "final") == 0)
    clutter_interval_set_final_value (self, value);
  else
    g_object_set_property (G_OBJECT (scriptable), name, value);
}

static void
clutter_scriptable_iface_init (ClutterScriptableIface *iface)
{
  iface->parse_custom_node = clutter_interval_parse_custom_node;
  iface->set_custom_property = clutter_interval_set_custom_property;
}

static void
clutter_interval_class_init (ClutterIntervalClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  klass->validate = clutter_interval_real_validate;
  klass->compute_value = clutter_interval_real_compute_value;

  gobject_class->set_property = clutter_interval_set_property,
  gobject_class->get_property = clutter_interval_get_property;
  gobject_class->finalize = clutter_interval_finalize;

  /**
   * ClutterInterval:value-type:
   *
   * The type of the values in the interval.
   */
  obj_props[PROP_VALUE_TYPE] =
    g_param_spec_gtype ("value-type", NULL, NULL,
                        G_TYPE_NONE,
                        G_PARAM_READWRITE |
                        G_PARAM_CONSTRUCT_ONLY |
                        G_PARAM_STATIC_STRINGS);

  /**
   * ClutterInterval:initial:
   *
   * The initial value of the interval.
   */
  obj_props[PROP_INITIAL] =
    g_param_spec_boxed ("initial", NULL, NULL,
                        G_TYPE_VALUE,
                        G_PARAM_READWRITE |
                        G_PARAM_STATIC_STRINGS);

  /**
   * ClutterInterval:final:
   *
   * The final value of the interval.
   */
  obj_props[PROP_FINAL] =
    g_param_spec_boxed ("final", NULL, NULL,
                        G_TYPE_VALUE,
                        G_PARAM_READWRITE |
                        G_PARAM_STATIC_STRINGS);

  g_object_class_install_properties (gobject_class, PROP_LAST, obj_props);
}

static void
clutter_interval_init (ClutterInterval *self)
{
  self->priv = clutter_interval_get_instance_private (self);

  self->priv->value_type = G_TYPE_INVALID;
  self->priv->values = g_malloc0 (sizeof (GValue) * N_VALUES);
}

static inline void
clutter_interval_set_value_internal (ClutterInterval *interval,
                                     gint             index_,
                                     const GValue    *value)
{
  ClutterIntervalPrivate *priv = interval->priv;
  GType value_type;

  g_assert (index_ >= INITIAL && index_ <= RESULT);

  if (G_IS_VALUE (&priv->values[index_]))
    g_value_unset (&priv->values[index_]);

  g_value_init (&priv->values[index_], priv->value_type);

  value_type = G_VALUE_TYPE (value);
  if (value_type != priv->value_type ||
      !g_type_is_a (value_type, priv->value_type))
    {
      if (g_value_type_compatible (value_type, priv->value_type))
        {
          g_value_copy (value, &priv->values[index_]);
          return;
        }

      if (g_value_type_transformable (value_type, priv->value_type))
        {
          GValue transform = G_VALUE_INIT;

          g_value_init (&transform, priv->value_type);

          if (g_value_transform (value, &transform))
            g_value_copy (&transform, &priv->values[index_]);
          else
            {
              g_warning ("%s: Unable to convert a value of type '%s' into "
                         "the value type '%s' of the interval.",
                         G_STRLOC,
                         g_type_name (value_type),
                         g_type_name (priv->value_type));
            }

          g_value_unset (&transform);
        }
    }
  else
    g_value_copy (value, &priv->values[index_]);
}

static inline void
clutter_interval_get_value_internal (ClutterInterval *interval,
                                     gint             index_,
                                     GValue          *value)
{
  ClutterIntervalPrivate *priv = interval->priv;

  g_assert (index_ >= INITIAL && index_ <= RESULT);

  g_value_copy (&priv->values[index_], value);
}

static gboolean
clutter_interval_set_initial_internal (ClutterInterval *interval,
                                       va_list         *args)
{
  GType gtype = interval->priv->value_type;
  GValue value = G_VALUE_INIT;
  gchar *error;

  /* initial value */
  G_VALUE_COLLECT_INIT (&value, gtype, *args, 0, &error);

  if (error)
    {
      g_warning ("%s: %s", G_STRLOC, error);

      /* we leak the value here as it might not be in a valid state
       * given the error and calling g_value_unset() might lead to
       * undefined behaviour
       */
      g_free (error);
      return FALSE;
    }

  clutter_interval_set_value_internal (interval, INITIAL, &value);
  g_value_unset (&value);

  return TRUE;
}

static gboolean
clutter_interval_set_final_internal (ClutterInterval *interval,
                                     va_list         *args)
{
  GType gtype = interval->priv->value_type;
  GValue value = G_VALUE_INIT;
  gchar *error;

  /* initial value */
  G_VALUE_COLLECT_INIT (&value, gtype, *args, 0, &error);

  if (error)
    {
      g_warning ("%s: %s", G_STRLOC, error);

      /* we leak the value here as it might not be in a valid state
       * given the error and calling g_value_unset() might lead to
       * undefined behaviour
       */
      g_free (error);
      return FALSE;
    }

  clutter_interval_set_value_internal (interval, FINAL, &value);
  g_value_unset (&value);

  return TRUE;
}

static void
clutter_interval_get_interval_valist (ClutterInterval *interval,
                                      va_list          var_args)
{
  GType gtype = interval->priv->value_type;
  GValue value = G_VALUE_INIT;
  gchar *error;

  /* initial value */
  g_value_init (&value, gtype);
  clutter_interval_get_initial_value (interval, &value);
  G_VALUE_LCOPY (&value, var_args, 0, &error);
  if (error)
    {
      g_warning ("%s: %s", G_STRLOC, error);
      g_free (error);
      g_value_unset (&value);
      return;
    }

  g_value_unset (&value);

  /* final value */
  g_value_init (&value, gtype);
  clutter_interval_get_final_value (interval, &value);
  G_VALUE_LCOPY (&value, var_args, 0, &error);
  if (error)
    {
      g_warning ("%s: %s", G_STRLOC, error);
      g_free (error);
      g_value_unset (&value);
      return;
    }

  g_value_unset (&value);
}

/**
 * clutter_interval_new:
 * @gtype: the type of the values in the interval
 * @...: the initial value and the final value of the interval
 *
 * Creates a new #ClutterInterval holding values of type @gtype.
 *
 * This function avoids using a #GValue for the initial and final values
 * of the interval:
 *
 * ```c
 *   interval = clutter_interval_new (G_TYPE_FLOAT, 0.0, 1.0);
 *   interval = clutter_interval_new (G_TYPE_BOOLEAN, FALSE, TRUE);
 *   interval = clutter_interval_new (G_TYPE_INT, 0, 360);
 * ```
 *
 * Return value: the newly created #ClutterInterval
 */
ClutterInterval *
clutter_interval_new (GType gtype,
                      ...)
{
  ClutterInterval *retval;
  va_list args;

  g_return_val_if_fail (gtype != G_TYPE_INVALID, NULL);

  retval = g_object_new (CLUTTER_TYPE_INTERVAL, "value-type", gtype, NULL);

  va_start (args, gtype);

  if (!clutter_interval_set_initial_internal (retval, &args))
    goto out;

  clutter_interval_set_final_internal (retval, &args);

out:
  va_end (args);

  return retval;
}

/**
 * clutter_interval_new_with_values:
 * @gtype: the type of the values in the interval
 * @initial: (allow-none): a #GValue holding the initial value of the interval
 * @final: (allow-none): a #GValue holding the final value of the interval
 *
 * Creates a new #ClutterInterval of type @gtype, between @initial
 * and @final.
 *
 * This function is useful for language bindings.
 *
 * Return value: the newly created #ClutterInterval
 */
ClutterInterval *
clutter_interval_new_with_values (GType         gtype,
                                  const GValue *initial,
                                  const GValue *final)
{
  g_return_val_if_fail (gtype != G_TYPE_INVALID, NULL);
  g_return_val_if_fail (initial == NULL || G_VALUE_TYPE (initial) == gtype, NULL);
  g_return_val_if_fail (final == NULL || G_VALUE_TYPE (final) == gtype, NULL);

  return g_object_new (CLUTTER_TYPE_INTERVAL,
                       "value-type", gtype,
                       "initial", initial,
                       "final", final,
                       NULL);
}

/**
 * clutter_interval_clone:
 * @interval: a #ClutterInterval
 *
 * Creates a copy of @interval.
 *
 * Return value: (transfer full): the newly created #ClutterInterval
 */
ClutterInterval *
clutter_interval_clone (ClutterInterval *interval)
{
  ClutterInterval *retval;
  GType gtype;
  GValue *tmp;

  g_return_val_if_fail (CLUTTER_IS_INTERVAL (interval), NULL);
  g_return_val_if_fail (interval->priv->value_type != G_TYPE_INVALID, NULL);

  gtype = interval->priv->value_type;
  retval = g_object_new (CLUTTER_TYPE_INTERVAL, "value-type", gtype, NULL);

  tmp = clutter_interval_peek_initial_value (interval);
  clutter_interval_set_initial_value (retval, tmp);

  tmp = clutter_interval_peek_final_value (interval);
  clutter_interval_set_final_value (retval, tmp);

  return retval;
}

/**
 * clutter_interval_get_value_type:
 * @interval: a #ClutterInterval
 *
 * Retrieves the #GType of the values inside @interval.
 *
 * Return value: the type of the value, or G_TYPE_INVALID
 */
GType
clutter_interval_get_value_type (ClutterInterval *interval)
{
  g_return_val_if_fail (CLUTTER_IS_INTERVAL (interval), G_TYPE_INVALID);

  return interval->priv->value_type;
}

/**
 * clutter_interval_set_initial_value: (rename-to clutter_interval_set_initial)
 * @interval: a #ClutterInterval
 * @value: a #GValue
 *
 * Sets the initial value of @interval to @value. The value is copied
 * inside the #ClutterInterval.
 */
void
clutter_interval_set_initial_value (ClutterInterval *interval,
                                    const GValue    *value)
{
  g_return_if_fail (CLUTTER_IS_INTERVAL (interval));
  g_return_if_fail (value != NULL);

  clutter_interval_set_value_internal (interval, INITIAL, value);
}

/**
 * clutter_interval_set_initial: (skip)
 * @interval: a #ClutterInterval
 * @...: the initial value of the interval.
 *
 * Variadic arguments version of clutter_interval_set_initial_value().
 *
 * This function is meant as a convenience for the C API.
 *
 * Language bindings should use clutter_interval_set_initial_value()
 * instead.
 */
void
clutter_interval_set_initial (ClutterInterval *interval,
                              ...)
{
  va_list args;

  g_return_if_fail (CLUTTER_IS_INTERVAL (interval));

  va_start (args, interval);
  clutter_interval_set_initial_internal (interval, &args);
  va_end (args);
}

/**
 * clutter_interval_get_initial_value:
 * @interval: a #ClutterInterval
 * @value: (out caller-allocates): a #GValue
 *
 * Retrieves the initial value of @interval and copies
 * it into @value.
 *
 * The passed #GValue must be initialized to the value held by
 * the #ClutterInterval.
 */
void
clutter_interval_get_initial_value (ClutterInterval *interval,
                                    GValue          *value)
{
  g_return_if_fail (CLUTTER_IS_INTERVAL (interval));
  g_return_if_fail (value != NULL);

  clutter_interval_get_value_internal (interval, INITIAL, value);
}

/**
 * clutter_interval_peek_initial_value:
 * @interval: a #ClutterInterval
 *
 * Gets the pointer to the initial value of @interval
 *
 * Return value: (transfer none): the initial value of the interval.
 *   The value is owned by the #ClutterInterval and it should not be
 *   modified or freed
 */
GValue *
clutter_interval_peek_initial_value (ClutterInterval *interval)
{
  g_return_val_if_fail (CLUTTER_IS_INTERVAL (interval), NULL);

  return interval->priv->values + INITIAL;
}

/**
 * clutter_interval_set_final_value: (rename-to clutter_interval_set_final)
 * @interval: a #ClutterInterval
 * @value: a #GValue
 *
 * Sets the final value of @interval to @value. The value is
 * copied inside the #ClutterInterval.
 */
void
clutter_interval_set_final_value (ClutterInterval *interval,
                                  const GValue    *value)
{
  g_return_if_fail (CLUTTER_IS_INTERVAL (interval));
  g_return_if_fail (value != NULL);

  clutter_interval_set_value_internal (interval, FINAL, value);
}

/**
 * clutter_interval_get_final_value:
 * @interval: a #ClutterInterval
 * @value: (out caller-allocates): a #GValue
 *
 * Retrieves the final value of @interval and copies
 * it into @value.
 *
 * The passed #GValue must be initialized to the value held by
 * the #ClutterInterval.
 */
void
clutter_interval_get_final_value (ClutterInterval *interval,
                                  GValue          *value)
{
  g_return_if_fail (CLUTTER_IS_INTERVAL (interval));
  g_return_if_fail (value != NULL);

  clutter_interval_get_value_internal (interval, FINAL, value);
}

/**
 * clutter_interval_set_final: (skip)
 * @interval: a #ClutterInterval
 * @...: the final value of the interval
 *
 * Variadic arguments version of clutter_interval_set_final_value().
 *
 * This function is meant as a convenience for the C API.
 *
 * Language bindings should use clutter_interval_set_final_value() instead.
 */
void
clutter_interval_set_final (ClutterInterval *interval,
                            ...)
{
  va_list args;

  g_return_if_fail (CLUTTER_IS_INTERVAL (interval));

  va_start (args, interval);
  clutter_interval_set_final_internal (interval, &args);
  va_end (args);
}

/**
 * clutter_interval_peek_final_value:
 * @interval: a #ClutterInterval
 *
 * Gets the pointer to the final value of @interval
 *
 * Return value: (transfer none): the final value of the interval.
 *   The value is owned by the #ClutterInterval and it should not be
 *   modified or freed
 */
GValue *
clutter_interval_peek_final_value (ClutterInterval *interval)
{
  g_return_val_if_fail (CLUTTER_IS_INTERVAL (interval), NULL);

  return interval->priv->values + FINAL;
}

/**
 * clutter_interval_set_interval:
 * @interval: a #ClutterInterval
 * @...: the initial and final values of the interval
 *
 * Variable arguments wrapper for clutter_interval_set_initial_value()
 * and clutter_interval_set_final_value() that avoids using the
 * #GValue arguments:
 *
 * ```c
 *   clutter_interval_set_interval (interval, 0, 50);
 *   clutter_interval_set_interval (interval, 1.0, 0.0);
 *   clutter_interval_set_interval (interval, FALSE, TRUE);
 * ```
 *
 * This function is meant for the convenience of the C API; bindings
 * should reimplement this function using the #GValue-based API.
 */
void
clutter_interval_set_interval (ClutterInterval *interval,
                               ...)
{
  va_list args;

  g_return_if_fail (CLUTTER_IS_INTERVAL (interval));
  g_return_if_fail (interval->priv->value_type != G_TYPE_INVALID);

  va_start (args, interval);

  if (!clutter_interval_set_initial_internal (interval, &args))
    goto out;

  clutter_interval_set_final_internal (interval, &args);

out:
  va_end (args);
}

/**
 * clutter_interval_get_interval:
 * @interval: a #ClutterInterval
 * @...: return locations for the initial and final values of
 *   the interval
 *
 * Variable arguments wrapper for clutter_interval_get_initial_value()
 * and clutter_interval_get_final_value() that avoids using the
 * #GValue arguments:
 *
 * ```c
 *   gint a = 0, b = 0;
 *   clutter_interval_get_interval (interval, &a, &b);
 * ```
 *
 * This function is meant for the convenience of the C API; bindings
 * should reimplement this function using the #GValue-based API.
 */
void
clutter_interval_get_interval (ClutterInterval *interval,
                               ...)
{
  va_list args;

  g_return_if_fail (CLUTTER_IS_INTERVAL (interval));
  g_return_if_fail (interval->priv->value_type != G_TYPE_INVALID);

  va_start (args, interval);
  clutter_interval_get_interval_valist (interval, args);
  va_end (args);
}

/**
 * clutter_interval_validate:
 * @interval: a #ClutterInterval
 * @pspec: a #GParamSpec
 *
 * Validates the initial and final values of @interval against
 * a #GParamSpec.
 *
 * Return value: %TRUE if the #ClutterInterval is valid, %FALSE otherwise
 */
gboolean
clutter_interval_validate (ClutterInterval *interval,
                           GParamSpec      *pspec)
{
  g_return_val_if_fail (CLUTTER_IS_INTERVAL (interval), FALSE);
  g_return_val_if_fail (G_IS_PARAM_SPEC (pspec), FALSE);

  return CLUTTER_INTERVAL_GET_CLASS (interval)->validate (interval, pspec);
}

/**
 * clutter_interval_compute_value:
 * @interval: a #ClutterInterval
 * @factor: the progress factor, between 0 and 1
 * @value: (out caller-allocates): return location for an initialized #GValue
 *
 * Computes the value between the @interval boundaries given the
 * progress @factor and copies it into @value.
 *
 * Return value: %TRUE if the operation was successful
 */
gboolean
clutter_interval_compute_value (ClutterInterval *interval,
                                gdouble          factor,
                                GValue          *value)
{
  g_return_val_if_fail (CLUTTER_IS_INTERVAL (interval), FALSE);
  g_return_val_if_fail (value != NULL, FALSE);

  return CLUTTER_INTERVAL_GET_CLASS (interval)->compute_value (interval,
                                                               factor,
                                                               value);
}

/**
 * clutter_interval_compute:
 * @interval: a #ClutterInterval
 * @factor: the progress factor, between 0 and 1
 *
 * Computes the value between the @interval boundaries given the
 * progress @factor
 *
 * Unlike clutter_interval_compute_value(), this function will
 * return a const pointer to the computed value
 *
 * You should use this function if you immediately pass the computed
 * value to another function that makes a copy of it, like
 * g_object_set_property()
 *
 * Return value: (transfer none): a pointer to the computed value,
 *   or %NULL if the computation was not successful
 */
const GValue *
clutter_interval_compute (ClutterInterval *interval,
                          gdouble          factor)
{
  GValue *value;
  gboolean res;

  g_return_val_if_fail (CLUTTER_IS_INTERVAL (interval), NULL);

  value = &(interval->priv->values[RESULT]);

  if (G_VALUE_TYPE (value) == G_TYPE_INVALID)
    g_value_init (value, interval->priv->value_type);

  res = CLUTTER_INTERVAL_GET_CLASS (interval)->compute_value (interval,
                                                              factor,
                                                              value);

  if (res)
    return interval->priv->values + RESULT;

  return NULL;
}

/**
 * clutter_interval_is_valid:
 * @interval: a #ClutterInterval
 *
 * Checks if the @interval has a valid initial and final values.
 *
 * Return value: %TRUE if the #ClutterInterval has an initial and
 *   final values, and %FALSE otherwise
 */
gboolean
clutter_interval_is_valid (ClutterInterval *interval)
{
  ClutterIntervalPrivate *priv;

  g_return_val_if_fail (CLUTTER_IS_INTERVAL (interval), FALSE);

  priv = interval->priv;

  return G_IS_VALUE (&priv->values[INITIAL]) &&
         G_IS_VALUE (&priv->values[FINAL]);
}
