/* LIBPIKA - The PIKA Library
 * Copyright (C) 1995-2003 Peter Mattis and Spencer Kimball
 *
 * pikaparamspecs.c
 *
 * 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 3 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
 * <https://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <gio/gio.h>

#include "pikabase.h"


/*
 * PIKA_TYPE_PARAM_CHOICE
 */

static void       pika_param_choice_class_init        (GParamSpecClass *klass);
static void       pika_param_choice_init              (GParamSpec      *pspec);
static void       pika_param_choice_value_set_default (GParamSpec      *pspec,
                                                       GValue          *value);
static void       pika_param_choice_finalize          (GParamSpec      *pspec);
static gboolean   pika_param_choice_validate          (GParamSpec      *pspec,
                                                      GValue          *value);
static gint       pika_param_choice_values_cmp        (GParamSpec      *pspec,
                                                      const GValue    *value1,
                                                      const GValue    *value2);

GType
pika_param_choice_get_type (void)
{
  static GType type = 0;

  if (! type)
    {
      const GTypeInfo info =
      {
        sizeof (GParamSpecClass),
        NULL, NULL,
        (GClassInitFunc) pika_param_choice_class_init,
        NULL, NULL,
        sizeof (PikaParamSpecChoice),
        0,
        (GInstanceInitFunc) pika_param_choice_init
      };

      type = g_type_register_static (G_TYPE_PARAM_BOXED,
                                     "PikaParamChoice", &info, 0);
    }

  return type;
}

static void
pika_param_choice_class_init (GParamSpecClass *klass)
{
  klass->value_type        = G_TYPE_STRING;
  klass->value_set_default = pika_param_choice_value_set_default;
  klass->finalize          = pika_param_choice_finalize;
  klass->value_validate    = pika_param_choice_validate;
  klass->values_cmp        = pika_param_choice_values_cmp;
}

static void
pika_param_choice_init (GParamSpec *pspec)
{
  PikaParamSpecChoice *choice = PIKA_PARAM_SPEC_CHOICE (pspec);

  choice->choice        = NULL;
  choice->default_value = NULL;
}

static void
pika_param_choice_value_set_default (GParamSpec *pspec,
                                     GValue     *value)
{
  PikaParamSpecChoice *cspec = PIKA_PARAM_SPEC_CHOICE (pspec);

  g_value_set_string (value, cspec->default_value);
}

static void
pika_param_choice_finalize (GParamSpec *pspec)
{
  PikaParamSpecChoice *spec_choice = PIKA_PARAM_SPEC_CHOICE (pspec);

  g_free (spec_choice->default_value);
  g_object_unref (spec_choice->choice);
}

static gboolean
pika_param_choice_validate (GParamSpec *pspec,
                            GValue     *value)
{
  PikaParamSpecChoice *spec_choice = PIKA_PARAM_SPEC_CHOICE (pspec);
  PikaChoice          *choice      = spec_choice->choice;
  const gchar         *strval      = g_value_get_string (value);

  if (! pika_choice_is_valid (choice, strval))
    {
      if (pika_choice_is_valid (choice, spec_choice->default_value))
        {
          g_value_set_string (value, spec_choice->default_value);
        }
      else
        {
          /* This might happen if the default value is set insensitive. Then we
           * should just set any valid random nick.
           */
          GList *nicks;

          nicks = pika_choice_list_nicks (choice);
          for (GList *iter = nicks; iter; iter = iter->next)
            if (pika_choice_is_valid (choice, (gchar *) iter->data))
              {
                g_value_set_string (value, (gchar *) iter->data);
                break;
              }
        }
      return TRUE;
    }

  return FALSE;
}

static gint
pika_param_choice_values_cmp (GParamSpec   *pspec,
                              const GValue *value1,
                              const GValue *value2)
{
  const gchar *choice1 = g_value_get_string (value1);
  const gchar *choice2 = g_value_get_string (value2);

  return g_strcmp0 (choice1, choice2);
}

/**
 * pika_param_spec_choice:
 * @name:  Canonical name of the property specified.
 * @nick:  Nick name of the property specified.
 * @blurb: Description of the property specified.
 * @choice: (transfer full): the %PikaChoice describing allowed choices.
 * @flags: Flags for the property specified.
 *
 * Creates a new #PikaParamSpecChoice specifying a
 * #G_TYPE_STRING property.
 * This %PikaParamSpecChoice takes ownership of the reference on @choice.
 *
 * See g_param_spec_internal() for details on property names.
 *
 * Returns: (transfer full): The newly created #PikaParamSpecChoice.
 *
 * Since: 3.0
 **/
GParamSpec *
pika_param_spec_choice (const gchar *name,
                        const gchar *nick,
                        const gchar *blurb,
                        PikaChoice  *choice,
                        const gchar *default_value,
                        GParamFlags  flags)
{
  PikaParamSpecChoice *choice_spec;

  choice_spec = g_param_spec_internal (PIKA_TYPE_PARAM_CHOICE,
                                       name, nick, blurb, flags);

  g_return_val_if_fail (choice_spec, NULL);

  choice_spec->choice        = choice;
  choice_spec->default_value = g_strdup (default_value);

  return G_PARAM_SPEC (choice_spec);
}


/*
 * PIKA_TYPE_ARRAY
 */

/**
 * pika_array_new:
 * @data: (array length=length):
 * @length:
 * @static_data:
 */
PikaArray *
pika_array_new (const guint8 *data,
                gsize         length,
                gboolean      static_data)
{
  PikaArray *array;

  g_return_val_if_fail ((data == NULL && length == 0) ||
                        (data != NULL && length  > 0), NULL);

  array = g_slice_new0 (PikaArray);

  array->data        = static_data ? (guint8 *) data : g_memdup2 (data, length);
  array->length      = length;
  array->static_data = static_data;

  return array;
}

PikaArray *
pika_array_copy (const PikaArray *array)
{
  if (array)
    return pika_array_new (array->data, array->length, FALSE);

  return NULL;
}

void
pika_array_free (PikaArray *array)
{
  if (array)
    {
      if (! array->static_data)
        g_free (array->data);

      g_slice_free (PikaArray, array);
    }
}

G_DEFINE_BOXED_TYPE (PikaArray, pika_array, pika_array_copy, pika_array_free)


/*
 * PIKA_TYPE_PARAM_ARRAY
 */

static void       pika_param_array_class_init  (GParamSpecClass *klass);
static void       pika_param_array_init        (GParamSpec      *pspec);
static gboolean   pika_param_array_validate    (GParamSpec      *pspec,
                                                GValue          *value);
static gint       pika_param_array_values_cmp  (GParamSpec      *pspec,
                                                const GValue    *value1,
                                                const GValue    *value2);

GType
pika_param_array_get_type (void)
{
  static GType type = 0;

  if (! type)
    {
      const GTypeInfo info =
      {
        sizeof (GParamSpecClass),
        NULL, NULL,
        (GClassInitFunc) pika_param_array_class_init,
        NULL, NULL,
        sizeof (PikaParamSpecArray),
        0,
        (GInstanceInitFunc) pika_param_array_init
      };

      type = g_type_register_static (G_TYPE_PARAM_BOXED,
                                     "PikaParamArray", &info, 0);
    }

  return type;
}

static void
pika_param_array_class_init (GParamSpecClass *klass)
{
  klass->value_type     = PIKA_TYPE_ARRAY;
  klass->value_validate = pika_param_array_validate;
  klass->values_cmp     = pika_param_array_values_cmp;
}

static void
pika_param_array_init (GParamSpec *pspec)
{
}

static gboolean
pika_param_array_validate (GParamSpec *pspec,
                           GValue     *value)
{
  PikaArray *array = value->data[0].v_pointer;

  if (array)
    {
      if ((array->data == NULL && array->length != 0) ||
          (array->data != NULL && array->length == 0))
        {
          g_value_set_boxed (value, NULL);
          return TRUE;
        }
    }

  return FALSE;
}

static gint
pika_param_array_values_cmp (GParamSpec   *pspec,
                             const GValue *value1,
                             const GValue *value2)
{
  PikaArray *array1 = value1->data[0].v_pointer;
  PikaArray *array2 = value2->data[0].v_pointer;

  /*  try to return at least *something*, it's useless anyway...  */

  if (! array1)
    return array2 != NULL ? -1 : 0;
  else if (! array2)
    return array1 != NULL ? 1 : 0;
  else if (array1->length < array2->length)
    return -1;
  else if (array1->length > array2->length)
    return 1;

  return 0;
}

/**
 * pika_param_spec_array:
 * @name:  Canonical name of the property specified.
 * @nick:  Nick name of the property specified.
 * @blurb: Description of the property specified.
 * @flags: Flags for the property specified.
 *
 * Creates a new #PikaParamSpecArray specifying a
 * [type@Array] property.
 *
 * See g_param_spec_internal() for details on property names.
 *
 * Returns: (transfer full): The newly created #PikaParamSpecArray.
 *
 * Since: 3.0
 **/
GParamSpec *
pika_param_spec_array (const gchar *name,
                       const gchar *nick,
                       const gchar *blurb,
                       GParamFlags  flags)
{
  PikaParamSpecArray *array_spec;

  array_spec = g_param_spec_internal (PIKA_TYPE_PARAM_ARRAY,
                                      name, nick, blurb, flags);

  return G_PARAM_SPEC (array_spec);
}

static const guint8 *
pika_value_get_array (const GValue *value)
{
  PikaArray *array = value->data[0].v_pointer;

  if (array)
    return array->data;

  return NULL;
}

static guint8 *
pika_value_dup_array (const GValue *value)
{
  PikaArray *array = value->data[0].v_pointer;

  if (array)
    return g_memdup2 (array->data, array->length);

  return NULL;
}

static void
pika_value_set_array (GValue       *value,
                      const guint8 *data,
                      gsize         length)
{
  PikaArray *array = pika_array_new (data, length, FALSE);

  g_value_take_boxed (value, array);
}

static void
pika_value_set_static_array (GValue       *value,
                             const guint8 *data,
                             gsize         length)
{
  PikaArray *array = pika_array_new (data, length, TRUE);

  g_value_take_boxed (value, array);
}

static void
pika_value_take_array (GValue *value,
                       guint8 *data,
                       gsize   length)
{
  PikaArray *array = pika_array_new (data, length, TRUE);

  array->static_data = FALSE;

  g_value_take_boxed (value, array);
}


/*
 * PIKA_TYPE_INT32_ARRAY
 */

typedef PikaArray PikaInt32Array;
G_DEFINE_BOXED_TYPE (PikaInt32Array, pika_int32_array, pika_array_copy, pika_array_free)

/*
 * PIKA_TYPE_PARAM_INT32_ARRAY
 */

static void   pika_param_int32_array_class_init (GParamSpecClass *klass);
static void   pika_param_int32_array_init       (GParamSpec      *pspec);

GType
pika_param_int32_array_get_type (void)
{
  static GType type = 0;

  if (! type)
    {
      const GTypeInfo info =
      {
        sizeof (GParamSpecClass),
        NULL, NULL,
        (GClassInitFunc) pika_param_int32_array_class_init,
        NULL, NULL,
        sizeof (PikaParamSpecInt32Array),
        0,
        (GInstanceInitFunc) pika_param_int32_array_init
      };

      type = g_type_register_static (PIKA_TYPE_PARAM_ARRAY,
                                     "PikaParamInt32Array", &info, 0);
    }

  return type;
}

static void
pika_param_int32_array_class_init (GParamSpecClass *klass)
{
  klass->value_type = PIKA_TYPE_INT32_ARRAY;
}

static void
pika_param_int32_array_init (GParamSpec *pspec)
{
}

/**
 * pika_param_spec_int32_array:
 * @name:  Canonical name of the property specified.
 * @nick:  Nick name of the property specified.
 * @blurb: Description of the property specified.
 * @flags: Flags for the property specified.
 *
 * Creates a new #PikaParamSpecInt32Array specifying a
 * %PIKA_TYPE_INT32_ARRAY property.
 *
 * See g_param_spec_internal() for details on property names.
 *
 * Returns: (transfer full): The newly created #PikaParamSpecInt32Array.
 *
 * Since: 3.0
 **/
GParamSpec *
pika_param_spec_int32_array (const gchar *name,
                             const gchar *nick,
                             const gchar *blurb,
                             GParamFlags  flags)
{
  PikaParamSpecArray *array_spec;

  array_spec = g_param_spec_internal (PIKA_TYPE_PARAM_INT32_ARRAY,
                                      name, nick, blurb, flags);

  return G_PARAM_SPEC (array_spec);
}

/**
 * pika_value_get_int32_array:
 * @value: A valid value of type %PIKA_TYPE_INT32_ARRAY
 *
 * Gets the contents of a %PIKA_TYPE_INT32_ARRAY #GValue
 *
 * Returns: (transfer none) (array): The contents of @value
 */
const gint32 *
pika_value_get_int32_array (const GValue *value)
{
  g_return_val_if_fail (PIKA_VALUE_HOLDS_INT32_ARRAY (value), NULL);

  return (const gint32 *) pika_value_get_array (value);
}

/**
 * pika_value_dup_int32_array:
 * @value: A valid value of type %PIKA_TYPE_INT32_ARRAY
 *
 * Gets the contents of a %PIKA_TYPE_INT32_ARRAY #GValue
 *
 * Returns: (transfer full) (array): The contents of @value
 */
gint32 *
pika_value_dup_int32_array (const GValue *value)
{
  g_return_val_if_fail (PIKA_VALUE_HOLDS_INT32_ARRAY (value), NULL);

  return (gint32 *) pika_value_dup_array (value);
}

/**
 * pika_value_set_int32_array:
 * @value: A valid value of type %PIKA_TYPE_INT32_ARRAY
 * @data: (array length=length): A #gint32 array
 * @length: The number of elements in @data
 *
 * Sets the contents of @value to @data.
 */
void
pika_value_set_int32_array (GValue       *value,
                            const gint32 *data,
                            gsize         length)
{
  g_return_if_fail (PIKA_VALUE_HOLDS_INT32_ARRAY (value));

  pika_value_set_array (value, (const guint8 *) data,
                        length * sizeof (gint32));
}

/**
 * pika_value_set_static_int32_array:
 * @value: A valid value of type %PIKA_TYPE_INT32_ARRAY
 * @data: (array length=length): A #gint32 array
 * @length: The number of elements in @data
 *
 * Sets the contents of @value to @data, without copying the data.
 */
void
pika_value_set_static_int32_array (GValue       *value,
                                   const gint32 *data,
                                   gsize         length)
{
  g_return_if_fail (PIKA_VALUE_HOLDS_INT32_ARRAY (value));

  pika_value_set_static_array (value, (const guint8 *) data,
                               length * sizeof (gint32));
}

/**
 * pika_value_take_int32_array:
 * @value: A valid value of type %PIKA_TYPE_int32_ARRAY
 * @data: (transfer full) (array length=length): A #gint32 array
 * @length: The number of elements in @data
 *
 * Sets the contents of @value to @data, and takes ownership of @data.
 */
void
pika_value_take_int32_array (GValue *value,
                             gint32 *data,
                             gsize   length)
{
  g_return_if_fail (PIKA_VALUE_HOLDS_INT32_ARRAY (value));

  pika_value_take_array (value, (guint8 *) data,
                         length * sizeof (gint32));
}


/*
 * PIKA_TYPE_FLOAT_ARRAY
 */

typedef PikaArray PikaFloatArray;
G_DEFINE_BOXED_TYPE (PikaFloatArray, pika_float_array, pika_array_copy, pika_array_free)

/*
 * PIKA_TYPE_PARAM_FLOAT_ARRAY
 */

static void   pika_param_float_array_class_init (GParamSpecClass *klass);
static void   pika_param_float_array_init       (GParamSpec      *pspec);

GType
pika_param_float_array_get_type (void)
{
  static GType type = 0;

  if (! type)
    {
      const GTypeInfo info =
      {
        sizeof (GParamSpecClass),
        NULL, NULL,
        (GClassInitFunc) pika_param_float_array_class_init,
        NULL, NULL,
        sizeof (PikaParamSpecFloatArray),
        0,
        (GInstanceInitFunc) pika_param_float_array_init
      };

      type = g_type_register_static (PIKA_TYPE_PARAM_ARRAY,
                                     "PikaParamFloatArray", &info, 0);
    }

  return type;
}

static void
pika_param_float_array_class_init (GParamSpecClass *klass)
{
  klass->value_type = PIKA_TYPE_FLOAT_ARRAY;
}

static void
pika_param_float_array_init (GParamSpec *pspec)
{
}

/**
 * pika_param_spec_float_array:
 * @name:  Canonical name of the property specified.
 * @nick:  Nick name of the property specified.
 * @blurb: Description of the property specified.
 * @flags: Flags for the property specified.
 *
 * Creates a new #PikaParamSpecFloatArray specifying a
 * %PIKA_TYPE_FLOAT_ARRAY property.
 *
 * See g_param_spec_internal() for details on property names.
 *
 * Returns: (transfer full): The newly created #PikaParamSpecFloatArray.
 *
 * Since: 3.0
 **/
GParamSpec *
pika_param_spec_float_array (const gchar *name,
                             const gchar *nick,
                             const gchar *blurb,
                             GParamFlags  flags)
{
  PikaParamSpecArray *array_spec;

  array_spec = g_param_spec_internal (PIKA_TYPE_PARAM_FLOAT_ARRAY,
                                      name, nick, blurb, flags);

  return G_PARAM_SPEC (array_spec);
}

/**
 * pika_value_get_float_array:
 * @value: A valid value of type %PIKA_TYPE_FLOAT_ARRAY
 *
 * Gets the contents of a %PIKA_TYPE_FLOAT_ARRAY #GValue
 *
 * Returns: (transfer none) (array): The contents of @value
 */
const gdouble *
pika_value_get_float_array (const GValue *value)
{
  g_return_val_if_fail (PIKA_VALUE_HOLDS_FLOAT_ARRAY (value), NULL);

  return (const gdouble *) pika_value_get_array (value);
}

/**
 * pika_value_dup_float_array:
 * @value: A valid value of type %PIKA_TYPE_FLOAT_ARRAY
 *
 * Gets the contents of a %PIKA_TYPE_FLOAT_ARRAY #GValue
 *
 * Returns: (transfer full) (array): The contents of @value
 */
gdouble *
pika_value_dup_float_array (const GValue *value)
{
  g_return_val_if_fail (PIKA_VALUE_HOLDS_FLOAT_ARRAY (value), NULL);

  return (gdouble *) pika_value_dup_array (value);
}

/**
 * pika_value_set_float_array:
 * @value: A valid value of type %PIKA_TYPE_FLOAT_ARRAY
 * @data: (array length=length): A #gfloat array
 * @length: The number of elements in @data
 *
 * Sets the contents of @value to @data.
 */
void
pika_value_set_float_array (GValue        *value,
                            const gdouble *data,
                            gsize         length)
{
  g_return_if_fail (PIKA_VALUE_HOLDS_FLOAT_ARRAY (value));

  pika_value_set_array (value, (const guint8 *) data,
                        length * sizeof (gdouble));
}

/**
 * pika_value_set_static_float_array:
 * @value: A valid value of type %PIKA_TYPE_FLOAT_ARRAY
 * @data: (array length=length): A #gfloat array
 * @length: The number of elements in @data
 *
 * Sets the contents of @value to @data, without copying the data.
 */
void
pika_value_set_static_float_array (GValue        *value,
                                   const gdouble *data,
                                   gsize         length)
{
  g_return_if_fail (PIKA_VALUE_HOLDS_FLOAT_ARRAY (value));

  pika_value_set_static_array (value, (const guint8 *) data,
                               length * sizeof (gdouble));
}

/**
 * pika_value_take_float_array:
 * @value: A valid value of type %PIKA_TYPE_FLOAT_ARRAY
 * @data: (transfer full) (array length=length): A #gfloat array
 * @length: The number of elements in @data
 *
 * Sets the contents of @value to @data, and takes ownership of @data.
 */
void
pika_value_take_float_array (GValue  *value,
                             gdouble *data,
                             gsize    length)
{
  g_return_if_fail (PIKA_VALUE_HOLDS_FLOAT_ARRAY (value));

  pika_value_take_array (value, (guint8 *) data,
                         length * sizeof (gdouble));
}

/*
 * PIKA_TYPE_RGB_ARRAY
 */

typedef PikaArray PikaRGBArray;
G_DEFINE_BOXED_TYPE (PikaRGBArray, pika_rgb_array, pika_array_copy, pika_array_free)


/*
 * PIKA_TYPE_PARAM_RGB_ARRAY
 */

static void  pika_param_rgb_array_class_init (GParamSpecClass *klass);
static void  pika_param_rgb_array_init       (GParamSpec      *pspec);

GType
pika_param_rgb_array_get_type (void)
{
  static GType type = 0;

  if (! type)
    {
      const GTypeInfo info =
      {
        sizeof (GParamSpecClass),
        NULL, NULL,
        (GClassInitFunc) pika_param_rgb_array_class_init,
        NULL, NULL,
        sizeof (PikaParamSpecRGBArray),
        0,
        (GInstanceInitFunc) pika_param_rgb_array_init
      };

      type = g_type_register_static (G_TYPE_PARAM_BOXED,
                                     "PikaParamRGBArray", &info, 0);
    }

  return type;
}

static void
pika_param_rgb_array_class_init (GParamSpecClass *klass)
{
  klass->value_type = PIKA_TYPE_RGB_ARRAY;
}

static void
pika_param_rgb_array_init (GParamSpec *pspec)
{
}

/**
 * pika_param_spec_rgb_array:
 * @name:  Canonical name of the property specified.
 * @nick:  Nick name of the property specified.
 * @blurb: Description of the property specified.
 * @flags: Flags for the property specified.
 *
 * Creates a new #PikaParamSpecRGBArray specifying a
 * %PIKA_TYPE_RGB_ARRAY property.
 *
 * See g_param_spec_internal() for details on property names.
 *
 * Returns: (transfer full): The newly created #PikaParamSpecRGBArray.
 *
 * Since: 3.0
 **/
GParamSpec *
pika_param_spec_rgb_array (const gchar *name,
                           const gchar *nick,
                           const gchar *blurb,
                           GParamFlags  flags)
{
  PikaParamSpecRGBArray *array_spec;

  array_spec = g_param_spec_internal (PIKA_TYPE_PARAM_RGB_ARRAY,
                                      name, nick, blurb, flags);

  return G_PARAM_SPEC (array_spec);
}

/**
 * pika_value_get_rgb_array:
 * @value: A valid value of type %PIKA_TYPE_RGB_ARRAY
 *
 * Gets the contents of a %PIKA_TYPE_RGB_ARRAY #GValue
 *
 * Returns: (transfer none) (array): The contents of @value
 */
const PikaRGB *
pika_value_get_rgb_array (const GValue *value)
{
  g_return_val_if_fail (PIKA_VALUE_HOLDS_RGB_ARRAY (value), NULL);

  return (const PikaRGB *) pika_value_get_array (value);
}

/**
 * pika_value_dup_rgb_array:
 * @value: A valid value of type %PIKA_TYPE_RGB_ARRAY
 *
 * Gets the contents of a %PIKA_TYPE_RGB_ARRAY #GValue
 *
 * Returns: (transfer full) (array): The contents of @value
 */
PikaRGB *
pika_value_dup_rgb_array (const GValue *value)
{
  g_return_val_if_fail (PIKA_VALUE_HOLDS_RGB_ARRAY (value), NULL);

  return (PikaRGB *) pika_value_dup_array (value);
}

/**
 * pika_value_set_rgb_array:
 * @value: A valid value of type %PIKA_TYPE_RGB_ARRAY
 * @data: (array length=length): A #PikaRGB array
 * @length: The number of elements in @data
 *
 * Sets the contents of @value to @data.
 */
void
pika_value_set_rgb_array (GValue        *value,
                          const PikaRGB *data,
                          gsize         length)
{
  g_return_if_fail (PIKA_VALUE_HOLDS_RGB_ARRAY (value));

  pika_value_set_array (value, (const guint8 *) data,
                        length * sizeof (PikaRGB));
}

/**
 * pika_value_set_static_rgb_array:
 * @value: A valid value of type %PIKA_TYPE_RGB_ARRAY
 * @data: (array length=length): A #PikaRGB array
 * @length: The number of elements in @data
 *
 * Sets the contents of @value to @data, without copying the data.
 */
void
pika_value_set_static_rgb_array (GValue        *value,
                                 const PikaRGB *data,
                                 gsize          length)
{
  g_return_if_fail (PIKA_VALUE_HOLDS_RGB_ARRAY (value));

  pika_value_set_static_array (value, (const guint8 *) data,
                               length * sizeof (PikaRGB));
}

/**
 * pika_value_take_rgb_array:
 * @value: A valid value of type %PIKA_TYPE_RGB_ARRAY
 * @data: (transfer full) (array length=length): A #PikaRGB array
 * @length: The number of elements in @data
 *
 * Sets the contents of @value to @data, and takes ownership of @data.
 */
void
pika_value_take_rgb_array (GValue  *value,
                           PikaRGB *data,
                           gsize    length)
{
  g_return_if_fail (PIKA_VALUE_HOLDS_RGB_ARRAY (value));

  pika_value_take_array (value, (guint8 *) data,
                         length * sizeof (PikaRGB));
}


/*
 * PIKA_TYPE_OBJECT_ARRAY
 */

/**
 * pika_object_array_new:
 * @data:        (array length=length) (transfer none): an array of objects.
 * @object_type: the array will hold objects of this type
 * @length:      the length of @data.
 * @static_data: whether the objects in @data are static objects and don't
 *               need to be copied.
 *
 * Creates a new #PikaObjectArray containing object pointers, of size @length.
 *
 * If @static_data is %TRUE, @data is used as-is.
 *
 * If @static_data is %FALSE, the object and array will be re-allocated,
 * hence you are expected to free your input data after.
 *
 * Returns: (transfer full): a new #PikaObjectArray.
 */
PikaObjectArray *
pika_object_array_new (GType     object_type,
                       GObject **data,
                       gsize     length,
                       gboolean  static_data)
{
  PikaObjectArray *array;

  g_return_val_if_fail (g_type_is_a (object_type, G_TYPE_OBJECT), NULL);
  g_return_val_if_fail ((data == NULL && length == 0) ||
                        (data != NULL && length  > 0), NULL);

  array = g_slice_new0 (PikaObjectArray);

  array->object_type = object_type;

  if (! static_data && data)
    {
      GObject **tmp = g_new0 (GObject *, length);
      gsize     i;

      for (i = 0; i < length; i++)
        tmp[i] = g_object_ref (data[i]);

      array->data = tmp;
    }
  else
    {
      array->data = data;
    }

  array->length      = length;
  array->static_data = static_data;

  return array;
}

/**
 * pika_object_array_copy:
 * @array: an original #PikaObjectArray of objects.
 *
 * Creates a new #PikaObjectArray containing a deep copy of @array.
 *
 * Returns: (transfer full): a new #PikaObjectArray.
 **/
PikaObjectArray *
pika_object_array_copy (const PikaObjectArray *array)
{
  if (array)
    return pika_object_array_new (array->object_type,
                                  array->data,
                                  array->length, FALSE);

  return NULL;
}

void
pika_object_array_free (PikaObjectArray *array)
{
  if (array)
    {
      if (! array->static_data)
        {
          GObject **tmp = array->data;
          gsize     i;

          for (i = 0; i < array->length; i++)
            g_object_unref (tmp[i]);

          g_free (array->data);
        }

      g_slice_free (PikaObjectArray, array);
    }
}

G_DEFINE_BOXED_TYPE (PikaObjectArray, pika_object_array, pika_object_array_copy, pika_object_array_free)


/*
 * PIKA_TYPE_PARAM_OBJECT_ARRAY
 */

static void       pika_param_object_array_class_init  (GParamSpecClass *klass);
static void       pika_param_object_array_init        (GParamSpec      *pspec);
static gboolean   pika_param_object_array_validate    (GParamSpec      *pspec,
                                                       GValue          *value);
static gint       pika_param_object_array_values_cmp  (GParamSpec      *pspec,
                                                       const GValue    *value1,
                                                       const GValue    *value2);

GType
pika_param_object_array_get_type (void)
{
  static GType type = 0;

  if (! type)
    {
      const GTypeInfo info =
      {
        sizeof (GParamSpecClass),
        NULL, NULL,
        (GClassInitFunc) pika_param_object_array_class_init,
        NULL, NULL,
        sizeof (PikaParamSpecObjectArray),
        0,
        (GInstanceInitFunc) pika_param_object_array_init
      };

      type = g_type_register_static (G_TYPE_PARAM_BOXED,
                                     "PikaParamObjectArray", &info, 0);
    }

  return type;
}

static void
pika_param_object_array_class_init (GParamSpecClass *klass)
{
  klass->value_type     = PIKA_TYPE_OBJECT_ARRAY;
  klass->value_validate = pika_param_object_array_validate;
  klass->values_cmp     = pika_param_object_array_values_cmp;
}

static void
pika_param_object_array_init (GParamSpec *pspec)
{
}

static gboolean
pika_param_object_array_validate (GParamSpec *pspec,
                                  GValue     *value)
{
  PikaParamSpecObjectArray *array_spec = PIKA_PARAM_SPEC_OBJECT_ARRAY (pspec);
  PikaObjectArray          *array      = value->data[0].v_pointer;

  if (array)
    {
      gsize i;

      if ((array->data == NULL && array->length != 0) ||
          (array->data != NULL && array->length == 0))
        {
          g_value_set_boxed (value, NULL);
          return TRUE;
        }

      if (! g_type_is_a (array->object_type, array_spec->object_type))
        {
          g_value_set_boxed (value, NULL);
          return TRUE;
        }

      for (i = 0; i < array->length; i++)
        {
          if (array->data[i] && ! g_type_is_a (G_OBJECT_TYPE (array->data[i]),
                                               array_spec->object_type))
            {
              g_value_set_boxed (value, NULL);
              return TRUE;
            }
        }
    }

  return FALSE;
}

static gint
pika_param_object_array_values_cmp (GParamSpec   *pspec,
                                    const GValue *value1,
                                    const GValue *value2)
{
  PikaObjectArray *array1 = value1->data[0].v_pointer;
  PikaObjectArray *array2 = value2->data[0].v_pointer;

  /*  try to return at least *something*, it's useless anyway...  */

  if (! array1)
    return array2 != NULL ? -1 : 0;
  else if (! array2)
    return array1 != NULL ? 1 : 0;
  else if (array1->length < array2->length)
    return -1;
  else if (array1->length > array2->length)
    return 1;

  return 0;
}

/**
 * pika_param_spec_object_array:
 * @name:        Canonical name of the property specified.
 * @nick:        Nick name of the property specified.
 * @blurb:       Description of the property specified.
 * @object_type: GType of the array's elements.
 * @flags:       Flags for the property specified.
 *
 * Creates a new #PikaParamSpecObjectArray specifying a
 * [type@ObjectArray] property.
 *
 * See g_param_spec_internal() for details on property names.
 *
 * Returns: (transfer full): The newly created #PikaParamSpecObjectArray.
 *
 * Since: 3.0
 **/
GParamSpec *
pika_param_spec_object_array (const gchar *name,
                              const gchar *nick,
                              const gchar *blurb,
                              GType        object_type,
                              GParamFlags  flags)
{
  PikaParamSpecObjectArray *array_spec;

  g_return_val_if_fail (g_type_is_a (object_type, G_TYPE_OBJECT), NULL);

  array_spec = g_param_spec_internal (PIKA_TYPE_PARAM_OBJECT_ARRAY,
                                      name, nick, blurb, flags);

  g_return_val_if_fail (array_spec, NULL);

  array_spec->object_type = object_type;

  return G_PARAM_SPEC (array_spec);
}

/**
 * pika_value_get_object_array:
 * @value: a #GValue holding a object #PikaObjectArray.
 *
 * Returns: (transfer none): the internal array of objects.
 */
GObject **
pika_value_get_object_array (const GValue *value)
{
  PikaObjectArray *array;

  g_return_val_if_fail (PIKA_VALUE_HOLDS_OBJECT_ARRAY (value), NULL);

  array = value->data[0].v_pointer;

  if (array)
    return array->data;

  return NULL;
}

/**
 * pika_value_dup_object_array:
 * @value: a #GValue holding a object #PikaObjectArray.
 *
 * Returns: (transfer full): a deep copy of the array of objects.
 */
GObject **
pika_value_dup_object_array (const GValue *value)
{
  PikaObjectArray *array;

  g_return_val_if_fail (PIKA_VALUE_HOLDS_OBJECT_ARRAY (value), NULL);

  array = value->data[0].v_pointer;

  if (array)
    {
      GObject **ret = g_memdup2 (array->data, (array->length) * sizeof (GObject *));
      gsize    i;

      for (i = 0; i < array->length; i++)
        g_object_ref (ret[i]);

      return ret;
    }

  return NULL;
}

/**
 * pika_value_set_object_array:
 * @value: A valid value of type %PIKA_TYPE_OBJECT_ARRAY
 * @object_type: The #GType of the object elements
 * @data: (array length=length): A #GObject array
 * @length: The number of elements in @data
 *
 * Sets the contents of @value to @data.
 */
void
pika_value_set_object_array (GValue   *value,
                             GType     object_type,
                             GObject **data,
                             gsize     length)
{
  PikaObjectArray *array;

  g_return_if_fail (PIKA_VALUE_HOLDS_OBJECT_ARRAY (value));
  g_return_if_fail (g_type_is_a (object_type, G_TYPE_OBJECT));

  array = pika_object_array_new (object_type, data, length, FALSE);

  g_value_take_boxed (value, array);
}

/**
 * pika_value_set_static_object_array:
 * @value: A valid value of type %PIKA_TYPE_OBJECT_ARRAY
 * @object_type: The #GType of the object elements
 * @data: (array length=length): A #GObject array
 * @length: The number of elements in @data
 *
 * Sets the contents of @value to @data, without copying the data.
 */
void
pika_value_set_static_object_array (GValue   *value,
                                    GType     object_type,
                                    GObject **data,
                                    gsize     length)
{
  PikaObjectArray *array;

  g_return_if_fail (PIKA_VALUE_HOLDS_OBJECT_ARRAY (value));
  g_return_if_fail (g_type_is_a (object_type, G_TYPE_OBJECT));

  array = pika_object_array_new (object_type, data, length, TRUE);

  g_value_take_boxed (value, array);
}

/**
 * pika_value_take_object_array:
 * @value: A valid value of type %PIKA_TYPE_OBJECT_ARRAY
 * @object_type: The #GType of the object elements
 * @data: (transfer full) (array length=length): A #GObject array
 * @length: The number of elements in @data
 *
 * Sets the contents of @value to @data, and takes ownership of @data.
 */
void
pika_value_take_object_array (GValue   *value,
                              GType     object_type,
                              GObject **data,
                              gsize     length)
{
  PikaObjectArray *array;

  g_return_if_fail (PIKA_VALUE_HOLDS_OBJECT_ARRAY (value));
  g_return_if_fail (g_type_is_a (object_type, G_TYPE_OBJECT));

  array = pika_object_array_new (object_type, data, length, TRUE);
  array->static_data = FALSE;

  g_value_take_boxed (value, array);
}
