/* LIBPIKA - The PIKA Library
 * Copyright (C) 1995-2003 Peter Mattis and Spencer Kimball
 *
 * pikagpparams-body.c
 * Copyright (C) 2019 Michael Natterer <mitch@gimp.org>
 *
 * 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/>.
 */

/*  this file is included by both
 *
 *  libpika/pikagpparams.c
 *  app/plug-in/pikagpparams.c
 */

GParamSpec *
_pika_gp_param_def_to_param_spec (const GPParamDef *param_def)
{
  const gchar *name  = param_def->name;
  const gchar *nick  = param_def->nick;
  const gchar *blurb = param_def->blurb;
  GParamFlags  flags = param_def->flags;

  flags &= ~G_PARAM_STATIC_STRINGS;

  switch (param_def->param_def_type)
    {
    case GP_PARAM_DEF_TYPE_DEFAULT:
      if (! strcmp (param_def->type_name, "PikaParamInt32Array"))
        return pika_param_spec_int32_array (name, nick, blurb, flags);

      if (! strcmp (param_def->type_name, "PikaParamFloatArray"))
        return pika_param_spec_float_array (name, nick, blurb, flags);

      if (! strcmp (param_def->type_name, "PikaParamRGBArray"))
        return pika_param_spec_rgb_array (name, nick, blurb, flags);

      if (! strcmp (param_def->type_name, "PikaParamParasite"))
        return pika_param_spec_parasite (name, nick, blurb, flags);

      if (! strcmp (param_def->type_name, "GParamParam"))
        return g_param_spec_param (name, nick, blurb,
                                   g_type_from_name (param_def->value_type_name),
                                   flags);

      if (! strcmp (param_def->type_name, "GParamObject") &&
          ! strcmp (param_def->value_type_name, "GFile"))
        return g_param_spec_object (name, nick, blurb, G_TYPE_FILE, flags);

      if (! strcmp (param_def->type_name, "GParamBoxed") &&
          ! strcmp (param_def->value_type_name, "PikaRGB"))
        /* Unfortunately this type loses default and alpha info. */
        return pika_param_spec_rgb (name, nick, blurb, TRUE, NULL, flags);

      if (! strcmp (param_def->type_name, "GParamBoxed") &&
          ! strcmp (param_def->value_type_name, "GStrv"))
        return g_param_spec_boxed (name, nick, blurb, G_TYPE_STRV, flags);

      if (! strcmp (param_def->type_name, "GParamBoxed") &&
          ! strcmp (param_def->value_type_name, "GBytes"))
        return g_param_spec_boxed (name, nick, blurb, G_TYPE_BYTES, flags);

      break;

    case GP_PARAM_DEF_TYPE_CHOICE:
      if (! strcmp (param_def->type_name, "PikaParamChoice"))
        {
          return pika_param_spec_choice (name, nick, blurb,
                                         g_object_ref (param_def->meta.m_choice.choice),
                                         param_def->meta.m_choice.default_val,
                                         flags);
        }

      break;

    case GP_PARAM_DEF_TYPE_INT:
      if (! strcmp (param_def->type_name, "GParamInt"))
        return g_param_spec_int (name, nick, blurb,
                                 param_def->meta.m_int.min_val,
                                 param_def->meta.m_int.max_val,
                                 param_def->meta.m_int.default_val,
                                 flags);

      if (! strcmp (param_def->type_name, "GParamUInt"))
        return g_param_spec_uint (name, nick, blurb,
                                  param_def->meta.m_int.min_val,
                                  param_def->meta.m_int.max_val,
                                  param_def->meta.m_int.default_val,
                                  flags);

      if (! strcmp (param_def->type_name, "GParamUChar"))
        return g_param_spec_uchar (name, nick, blurb,
                                   param_def->meta.m_int.min_val,
                                   param_def->meta.m_int.max_val,
                                   param_def->meta.m_int.default_val,
                                   flags);
      break;

    case GP_PARAM_DEF_TYPE_UNIT:
      if (! strcmp (param_def->type_name, "PikaParamUnit"))
        return pika_param_spec_unit (name, nick, blurb,
                                     param_def->meta.m_unit.allow_pixels,
                                     param_def->meta.m_unit.allow_percent,
                                     param_def->meta.m_unit.default_val,
                                     flags);
      break;

    case GP_PARAM_DEF_TYPE_ENUM:
      if (! strcmp (param_def->type_name, "GParamEnum"))
        return g_param_spec_enum (name, nick, blurb,
                                  g_type_from_name (param_def->value_type_name),
                                  param_def->meta.m_enum.default_val,
                                  flags);

      if (! strcmp (param_def->type_name, "PikaParamEnum"))
        /* FIXME PikaParamEnum */
        return g_param_spec_enum (name, nick, blurb,
                                  g_type_from_name (param_def->value_type_name),
                                  param_def->meta.m_enum.default_val,
                                  flags);
      break;

    case GP_PARAM_DEF_TYPE_BOOLEAN:
      if (! strcmp (param_def->type_name, "GParamBoolean"))
        return g_param_spec_boolean (name, nick, blurb,
                                     param_def->meta.m_boolean.default_val,
                                     flags);
      break;

    case GP_PARAM_DEF_TYPE_FLOAT:
      if (! strcmp (param_def->type_name, "GParamDouble"))
        return g_param_spec_double (name, nick, blurb,
                                    param_def->meta.m_float.min_val,
                                    param_def->meta.m_float.max_val,
                                    param_def->meta.m_float.default_val,
                                    flags);
      break;

    case GP_PARAM_DEF_TYPE_STRING:
      if (! strcmp (param_def->type_name, "GParamString"))
        return g_param_spec_string (name, nick, blurb,
                                    param_def->meta.m_string.default_val,
                                    flags);
      break;

    case GP_PARAM_DEF_TYPE_COLOR:
      if (! strcmp (param_def->type_name, "PikaParamRGB"))
        return pika_param_spec_rgb (name, nick, blurb,
                                    param_def->meta.m_color.has_alpha,
                                    &param_def->meta.m_color.default_val,
                                    flags);
      break;

    case GP_PARAM_DEF_TYPE_ID:
      if (! strcmp (param_def->type_name, "PikaParamDisplay"))
        return pika_param_spec_display (name, nick, blurb,
                                        param_def->meta.m_id.none_ok,
                                        flags);

      if (! strcmp (param_def->type_name, "PikaParamImage"))
        return pika_param_spec_image (name, nick, blurb,
                                      param_def->meta.m_id.none_ok,
                                      flags);

      if (! strcmp (param_def->type_name, "PikaParamItem"))
        return pika_param_spec_item (name, nick, blurb,
                                     param_def->meta.m_id.none_ok,
                                     flags);

      if (! strcmp (param_def->type_name, "PikaParamDrawable"))
        return pika_param_spec_drawable (name, nick, blurb,
                                         param_def->meta.m_id.none_ok,
                                         flags);

      if (! strcmp (param_def->type_name, "PikaParamLayer"))
        return pika_param_spec_layer (name, nick, blurb,
                                      param_def->meta.m_id.none_ok,
                                      flags);

      if (! strcmp (param_def->type_name, "PikaParamTextLayer"))
        return pika_param_spec_text_layer (name, nick, blurb,
                                           param_def->meta.m_id.none_ok,
                                           flags);

      if (! strcmp (param_def->type_name, "PikaParamChannel"))
        return pika_param_spec_channel (name, nick, blurb,
                                        param_def->meta.m_id.none_ok,
                                        flags);

      if (! strcmp (param_def->type_name, "PikaParamLayerMask"))
        return pika_param_spec_layer_mask (name, nick, blurb,
                                           param_def->meta.m_id.none_ok,
                                           flags);

      if (! strcmp (param_def->type_name, "PikaParamSelection"))
        return pika_param_spec_selection (name, nick, blurb,
                                          param_def->meta.m_id.none_ok,
                                          flags);

      if (! strcmp (param_def->type_name, "PikaParamVectors"))
        return pika_param_spec_vectors (name, nick, blurb,
                                        param_def->meta.m_id.none_ok,
                                        flags);

      if (! strcmp (param_def->type_name, "PikaParamResource"))
        return pika_param_spec_resource (name, nick, blurb,
                                         param_def->meta.m_id.none_ok, flags);

      if (! strcmp (param_def->type_name, "PikaParamBrush"))
        return pika_param_spec_brush (name, nick, blurb,
                                      param_def->meta.m_id.none_ok, flags);

      if (! strcmp (param_def->type_name, "PikaParamFont"))
        return pika_param_spec_font (name, nick, blurb,
                                     param_def->meta.m_id.none_ok, flags);

      if (! strcmp (param_def->type_name, "PikaParamGradient"))
        return pika_param_spec_gradient (name, nick, blurb,
                                         param_def->meta.m_id.none_ok, flags);

      if (! strcmp (param_def->type_name, "PikaParamPalette"))
        return pika_param_spec_palette (name, nick, blurb,
                                        param_def->meta.m_id.none_ok, flags);

      if (! strcmp (param_def->type_name, "PikaParamPattern"))
        return pika_param_spec_pattern (name, nick, blurb,
                                        param_def->meta.m_id.none_ok, flags);
      break;

    case GP_PARAM_DEF_TYPE_ID_ARRAY:
      if (! strcmp (param_def->type_name, "PikaParamObjectArray"))
        return pika_param_spec_object_array (name, nick, blurb,
                                             g_type_from_name (param_def->meta.m_id_array.type_name),
                                             flags);
      break;
    }

  g_warning ("%s: GParamSpec type unsupported '%s'", G_STRFUNC,
             param_def->type_name);

  return NULL;
}

void
_pika_param_spec_to_gp_param_def (GParamSpec *pspec,
                                  GPParamDef *param_def)
{
  GType pspec_type = G_PARAM_SPEC_TYPE (pspec);
  GType value_type = G_PARAM_SPEC_VALUE_TYPE (pspec);

  param_def->param_def_type  = GP_PARAM_DEF_TYPE_DEFAULT;
  param_def->type_name       = (gchar *) g_type_name (pspec_type);
  param_def->value_type_name = (gchar *) g_type_name (value_type);
  param_def->name            = (gchar *) g_param_spec_get_name (pspec);
  param_def->nick            = (gchar *) g_param_spec_get_nick (pspec);
  param_def->blurb           = (gchar *) g_param_spec_get_blurb (pspec);
  param_def->flags           = pspec->flags;

  if (pspec_type == G_TYPE_PARAM_INT)
    {
      GParamSpecInt *ispec = G_PARAM_SPEC_INT (pspec);

      param_def->param_def_type = GP_PARAM_DEF_TYPE_INT;

      param_def->meta.m_int.min_val     = ispec->minimum;
      param_def->meta.m_int.max_val     = ispec->maximum;
      param_def->meta.m_int.default_val = ispec->default_value;
    }
  else if (pspec_type == G_TYPE_PARAM_UINT)
    {
      GParamSpecUInt *uspec = G_PARAM_SPEC_UINT (pspec);

      param_def->param_def_type = GP_PARAM_DEF_TYPE_INT;

      param_def->meta.m_int.min_val     = uspec->minimum;
      param_def->meta.m_int.max_val     = uspec->maximum;
      param_def->meta.m_int.default_val = uspec->default_value;
    }
  else if (pspec_type == G_TYPE_PARAM_UCHAR)
    {
      GParamSpecUChar *uspec = G_PARAM_SPEC_UCHAR (pspec);

      param_def->param_def_type = GP_PARAM_DEF_TYPE_INT;

      param_def->meta.m_int.min_val     = uspec->minimum;
      param_def->meta.m_int.max_val     = uspec->maximum;
      param_def->meta.m_int.default_val = uspec->default_value;
    }
  else if (pspec_type == PIKA_TYPE_PARAM_UNIT)
    {
      GParamSpecInt     *ispec = G_PARAM_SPEC_INT (pspec);
      PikaParamSpecUnit *uspec = PIKA_PARAM_SPEC_UNIT (pspec);

      param_def->param_def_type = GP_PARAM_DEF_TYPE_UNIT;

      param_def->meta.m_unit.allow_pixels  = (ispec->minimum < PIKA_UNIT_INCH);
      param_def->meta.m_unit.allow_percent = uspec->allow_percent;
      param_def->meta.m_unit.default_val   = ispec->default_value;
    }
  else if (G_IS_PARAM_SPEC_ENUM (pspec))
    {
      GParamSpecEnum *espec = G_PARAM_SPEC_ENUM (pspec);

      param_def->param_def_type = GP_PARAM_DEF_TYPE_ENUM;

      param_def->meta.m_enum.default_val = espec->default_value;
    }
  else if (pspec_type == PIKA_TYPE_PARAM_CHOICE)
    {
      PikaParamSpecChoice *cspec = PIKA_PARAM_SPEC_CHOICE (pspec);

      param_def->param_def_type = GP_PARAM_DEF_TYPE_CHOICE;

      param_def->meta.m_choice.default_val = cspec->default_value;
      param_def->meta.m_choice.choice      = cspec->choice;
    }
  else if (pspec_type == G_TYPE_PARAM_BOOLEAN)
    {
      GParamSpecBoolean *bspec = G_PARAM_SPEC_BOOLEAN (pspec);

      param_def->param_def_type = GP_PARAM_DEF_TYPE_BOOLEAN;

      param_def->meta.m_boolean.default_val = bspec->default_value;
    }
  else if (pspec_type == G_TYPE_PARAM_DOUBLE)
    {
      GParamSpecDouble *dspec = G_PARAM_SPEC_DOUBLE (pspec);

      param_def->param_def_type = GP_PARAM_DEF_TYPE_FLOAT;

      param_def->meta.m_float.min_val     = dspec->minimum;
      param_def->meta.m_float.max_val     = dspec->maximum;
      param_def->meta.m_float.default_val = dspec->default_value;
    }
  else if (G_IS_PARAM_SPEC_STRING (pspec))
    {
      GParamSpecString *gsspec = G_PARAM_SPEC_STRING (pspec);

      if (! strcmp (param_def->type_name, "PikaParamString"))
        param_def->type_name = "GParamString";

      param_def->param_def_type = GP_PARAM_DEF_TYPE_STRING;

      param_def->meta.m_string.default_val = gsspec->default_value;
    }
  else if (pspec_type == PIKA_TYPE_PARAM_RGB)
    {
      param_def->param_def_type = GP_PARAM_DEF_TYPE_COLOR;

      param_def->meta.m_color.has_alpha =
        pika_param_spec_rgb_has_alpha (pspec);

      pika_param_spec_rgb_get_default (pspec,
                                       &param_def->meta.m_color.default_val);
    }
  else if (pspec_type == PIKA_TYPE_PARAM_IMAGE)
    {
      PikaParamSpecImage *ispec = PIKA_PARAM_SPEC_IMAGE (pspec);

      param_def->param_def_type = GP_PARAM_DEF_TYPE_ID;

      param_def->meta.m_id.none_ok = ispec->none_ok;
    }
  else if (PIKA_IS_PARAM_SPEC_ITEM (pspec))
    {
      PikaParamSpecItem *ispec = PIKA_PARAM_SPEC_ITEM (pspec);

      param_def->param_def_type = GP_PARAM_DEF_TYPE_ID;

      param_def->meta.m_id.none_ok = ispec->none_ok;
    }
  else if (pspec_type == PIKA_TYPE_PARAM_DISPLAY)
    {
      PikaParamSpecDisplay *ispec = PIKA_PARAM_SPEC_DISPLAY (pspec);

      param_def->param_def_type = GP_PARAM_DEF_TYPE_ID;

      param_def->meta.m_id.none_ok = ispec->none_ok;
    }
  else if (PIKA_IS_PARAM_SPEC_RESOURCE (pspec))
    {
      PikaParamSpecResource *rspec = PIKA_PARAM_SPEC_RESOURCE (pspec);

      param_def->param_def_type = GP_PARAM_DEF_TYPE_ID;

      param_def->meta.m_id.none_ok = rspec->none_ok;
    }
  else if (PIKA_IS_PARAM_SPEC_OBJECT_ARRAY (pspec))
    {
      param_def->param_def_type = GP_PARAM_DEF_TYPE_ID_ARRAY;

      param_def->meta.m_id_array.type_name =
        (gchar *) g_type_name (PIKA_PARAM_SPEC_OBJECT_ARRAY (pspec)->object_type);
    }
  else if (pspec_type == G_TYPE_PARAM_OBJECT &&
           value_type != G_TYPE_FILE)
    {
      const gchar *type_name  = NULL;

      if (g_type_is_a (value_type, PIKA_TYPE_DISPLAY))
        {
          /* g_type_is_a() because the core has a PikaDisplay subclasses */
          type_name = "PikaParamDisplay";
        }
      else if (value_type == PIKA_TYPE_IMAGE)
        {
          type_name = "PikaParamImage";
        }
      else if (value_type == PIKA_TYPE_ITEM)
        {
          type_name = "PikaParamItem";
        }
      else if (value_type == PIKA_TYPE_DRAWABLE)
        {
          type_name = "PikaParamDrawable";
        }
      else if (g_type_is_a (value_type, PIKA_TYPE_LAYER))
        {
          /* g_type_is_a() because the core has layer subclasses */
          type_name = "PikaParamLayer";
        }
      else if (value_type == PIKA_TYPE_CHANNEL)
        {
          type_name = "PikaParamChannel";
        }
      else if (value_type == PIKA_TYPE_LAYER_MASK)
        {
          type_name = "PikaParamLayerMask";
        }
      else if (value_type == PIKA_TYPE_SELECTION)
        {
          type_name = "PikaParamSelection";
        }
      else if (value_type == PIKA_TYPE_VECTORS)
        {
          type_name = "PikaParamVectors";
        }
      else if (value_type == PIKA_TYPE_RESOURCE)
        {
          type_name = "PikaParamResource";
        }
      else if (value_type == PIKA_TYPE_BRUSH)
        {
          type_name = "PikaParamBrush";
        }
      else if (value_type == PIKA_TYPE_FONT)
        {
          type_name = "PikaParamFont";
        }
      else if (value_type == PIKA_TYPE_GRADIENT)
        {
          type_name = "PikaParamGradient";
        }
      else if (value_type == PIKA_TYPE_PALETTE)
        {
          type_name = "PikaParamPalette";
        }
      else if (value_type == PIKA_TYPE_PATTERN)
        {
          type_name = "PikaParamPattern";
        }

      if (type_name)
        {
          param_def->param_def_type    = GP_PARAM_DEF_TYPE_ID;
          param_def->type_name         = (gchar *) type_name;
          param_def->meta.m_id.none_ok = TRUE;
        }
      else
        {
          g_warning ("%s: GParamSpecObject for unsupported type '%s:%s'",
                     G_STRFUNC,
                     param_def->type_name, param_def->value_type_name);
        }
    }
}

static PikaImage *
get_image_by_id (gpointer pika,
                 gint     id)
{
#ifdef LIBPIKA_COMPILATION
  return pika_image_get_by_id (id);
#else
  return pika_image_get_by_id (pika, id);
#endif
}

static PikaItem *
get_item_by_id (gpointer pika,
                gint     id)
{
#ifdef LIBPIKA_COMPILATION
  return pika_item_get_by_id (id);
#else
  return pika_item_get_by_id (pika, id);
#endif
}

static PikaDisplay *
get_display_by_id (gpointer pika,
                   gint     id)
{
#ifdef LIBPIKA_COMPILATION
  return pika_display_get_by_id (id);
#else
  return pika_display_get_by_id (pika, id);
#endif
}

static GObject *
get_resource_by_id (gint id)
{
#ifdef LIBPIKA_COMPILATION
  return (GObject *) pika_resource_get_by_id (id);
#else
  return (GObject *) pika_data_get_by_id (id);
#endif
}

static gint
get_resource_id (GObject *resource)
{
#ifdef LIBPIKA_COMPILATION
  return pika_resource_get_id (PIKA_RESOURCE (resource));
#else
  return pika_data_get_id (PIKA_DATA (resource));
#endif
}


/* Deserialize a gp_param (from the wire) to an instance of object or
 * primitive type.
 *
 * This is used on both the core and plugin (libpika) side,
 * each having its own class definitions for a same named class.
 * Thus this creates different objects, depending on which side it is.
 * See the conditionally compiled constructors/fetchers above.
 */
static void
pika_gp_param_to_value (gpointer        pika,
                        const GPParam  *param,
                        GType           type,
                        GParamSpec     *pspec,
                        GValue         *value)
{
  g_return_if_fail (param != NULL);
  g_return_if_fail (value != NULL);

  if (type == G_TYPE_NONE || type == G_TYPE_INVALID)
    {
      type = g_type_from_name (param->type_name);
      if (type == 0)
        {
          if (! strcmp (param->type_name, "PikaResource"))
            type = g_type_from_name ("PikaData");
          else if (! strcmp (param->type_name, "PikaData"))
            type = g_type_from_name ("PikaResource");
          else
            g_critical ("%s: type name %s is not registered", G_STRFUNC,
                        param->type_name);
        }
    }
  /* assert type is not G_TYPE_NONE and type is not G_TYPE_INVALID. */

  g_value_init (value, type);

  if (type == G_TYPE_INT ||
      type == PIKA_TYPE_UNIT)
    {
      g_value_set_int (value, param->data.d_int);
    }
  else if (G_VALUE_HOLDS_UINT (value))
    {
      g_value_set_uint (value, param->data.d_int);
    }
  else if (G_VALUE_HOLDS_ENUM (value))
    {
      g_value_set_enum (value, param->data.d_int);
    }
  else if (G_VALUE_HOLDS_BOOLEAN (value))
    {
      g_value_set_boolean (value, param->data.d_int ? TRUE : FALSE);
    }
  else if (G_VALUE_HOLDS_DOUBLE (value))
    {
      g_value_set_double (value, param->data.d_float);
    }
  else if (G_VALUE_HOLDS_STRING (value))
    {
      g_value_set_string (value, param->data.d_string);
    }
  else if (G_VALUE_HOLDS (value, G_TYPE_STRV))
    {
      g_value_set_boxed (value, param->data.d_strv);
    }
  else if (G_VALUE_HOLDS (value, G_TYPE_BYTES))
    {
      g_value_set_boxed (value, param->data.d_bytes);
    }
  else if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_FILE))
    {
      g_value_take_object (value, (param->data.d_string ?
                                   g_file_new_for_uri (param->data.d_string) :
                                   NULL));
    }
  else if (PIKA_VALUE_HOLDS_RGB (value))
    {
      pika_value_set_rgb (value, &param->data.d_color);
    }
  else if (PIKA_VALUE_HOLDS_PARASITE (value))
    {
      g_value_set_boxed (value, &param->data.d_parasite);
    }
  else if (PIKA_VALUE_HOLDS_INT32_ARRAY (value))
    {
      pika_value_set_int32_array (value,
                                  (gint32 *) param->data.d_array.data,
                                  param->data.d_array.size /
                                  sizeof (gint32));
    }
  else if (PIKA_VALUE_HOLDS_FLOAT_ARRAY (value))
    {
      pika_value_set_float_array (value,
                                  (const gdouble *)
                                  param->data.d_array.data,
                                  param->data.d_array.size /
                                  sizeof (gdouble));
    }
  else if (PIKA_VALUE_HOLDS_RGB_ARRAY (value))
    {
      pika_value_set_rgb_array (value,
                                (PikaRGB *)
                                param->data.d_array.data,
                                param->data.d_array.size /
                                sizeof (PikaRGB));
    }
  else if (PIKA_VALUE_HOLDS_OBJECT_ARRAY (value))
    {
      GType     object_type;
      GObject **objects;
      gint      i;

      if (param->data.d_id_array.type_name == NULL)
        {
          g_return_if_fail (param->data.d_id_array.size == 0 && pspec != NULL);
          object_type = PIKA_PARAM_SPEC_OBJECT_ARRAY (pspec)->object_type;
        }
      else
        {
          object_type = g_type_from_name (param->data.d_id_array.type_name);
        }

      objects = g_new (GObject *, param->data.d_id_array.size);

      for (i = 0; i < param->data.d_id_array.size; i++)
        {
          gint id = param->data.d_id_array.data[i];

          if (object_type == PIKA_TYPE_IMAGE)
            {
              objects[i] = (GObject *) get_image_by_id (pika, id);
            }
          else if (g_type_is_a (object_type, PIKA_TYPE_ITEM))
            {
              objects[i] = (GObject *) get_item_by_id (pika, id);
            }
          else if (g_type_is_a (object_type, PIKA_TYPE_DISPLAY))
            {
              objects[i] = (GObject *) get_display_by_id (pika, id);
            }
          else if (g_type_is_a (object_type, PIKA_TYPE_RESOURCE))
            {
              objects[i] = (GObject *) get_resource_by_id (id);
            }

          if (objects[i])
            g_object_ref (objects[i]);
        }

      pika_value_take_object_array (value, object_type, objects,
                                    param->data.d_id_array.size);
    }
  else if (PIKA_VALUE_HOLDS_IMAGE (value))
    {
      g_value_set_object (value, get_image_by_id (pika, param->data.d_int));
    }
  else if (PIKA_VALUE_HOLDS_ITEM (value))
    {
      g_value_set_object (value, get_item_by_id (pika, param->data.d_int));
    }
  else if (PIKA_VALUE_HOLDS_DISPLAY (value))
    {
      g_value_set_object (value, get_display_by_id (pika, param->data.d_int));
    }
  else if (PIKA_VALUE_HOLDS_RESOURCE (value))
    {
      g_value_set_object (value, get_resource_by_id (param->data.d_int));
    }
  else if (G_VALUE_HOLDS_PARAM (value))
    {
      GParamSpec *pspec =
        _pika_gp_param_def_to_param_spec (&param->data.d_param_def);

      g_value_take_param (value, pspec);
    }
  else
    {
      g_warning ("%s: unsupported deserialization to GValue of type '%s'\n",
                 G_STRFUNC, param->type_name);
    }
}

PikaValueArray *
_pika_gp_params_to_value_array (gpointer        pika,
                                GParamSpec    **pspecs,
                                gint            n_pspecs,
                                const GPParam  *params,
                                gint            n_params,
                                gboolean        return_values)
{
  PikaValueArray *args;
  gint            i;

  g_return_val_if_fail ((params != NULL && n_params  > 0) ||
                        (params == NULL && n_params == 0), NULL);

  args = pika_value_array_new (n_params);

  for (i = 0; i < n_params; i++)
    {
      GParamSpec *pspec = NULL;
      GValue      value = G_VALUE_INIT;
      GType       type;

      /*  first get the GType from the passed GPParam  */
      type = g_type_from_name (params[i].type_name);

      /*  then try to try to be more specific by looking at the param
       *  spec (return values have one additional value (the status),
       *  skip that, it's not in the array of param specs)
       */
      if (i > 0 || ! return_values)
        {
          gint pspec_index = i;

          if (return_values)
            pspec_index--;

          /*  are there param specs left?  */
          if (pspec_index < n_pspecs)
            {
              GType pspec_type = G_PARAM_SPEC_VALUE_TYPE (pspecs[pspec_index]);

              pspec = pspecs[pspec_index];

              if (type != pspec_type)
                {
                  if (g_type_is_a (pspec_type, type))
                    {
                      /*  if the param spec's GType is more specific
                       *  than the one from the GPParam, use the param
                       *  spec's GType.
                       */
                      type = pspec_type;
                    }
                  else if (type == G_TYPE_INT)
                    {
                      if (g_type_is_a (pspec_type, G_TYPE_ENUM))
                        {
                          /*  if the param spec's type is enum, but an
                           *  int was passed, use the enum type.
                           */
                          type = pspec_type;
                        }
                      else if (g_type_is_a (pspec_type, G_TYPE_BOOLEAN))
                        {
                          /*  if the param spec's type is boolean, but
                           *  an int was passed, use the boolean
                           *  type.
                           */
                          type = pspec_type;
                        }
                    }
                }
            }
        }

      pika_gp_param_to_value (pika, &params[i], type, pspec, &value);

      pika_value_array_append (args, &value);
      g_value_unset (&value);
    }

  return args;
}

static void
pika_value_to_gp_param (const GValue *value,
                        GPParam      *param,
                        gboolean      full_copy)
{
  GType type;

  g_return_if_fail (value != NULL);
  g_return_if_fail (param != NULL);

  type = G_VALUE_TYPE (value);

  param->param_type = -1;

  if (full_copy)
    param->type_name = g_strdup (g_type_name (type));
  else
    param->type_name = (gchar *) g_type_name (type);

  if (type == G_TYPE_INT ||
      type == PIKA_TYPE_UNIT)
    {
      param->param_type = GP_PARAM_TYPE_INT;

      param->data.d_int = g_value_get_int (value);
    }
  else if (type == G_TYPE_UINT)
    {
      param->param_type = GP_PARAM_TYPE_INT;

      param->data.d_int = g_value_get_uint (value);
    }
  else if (type == G_TYPE_UCHAR)
    {
      param->param_type = GP_PARAM_TYPE_INT;

      param->data.d_int = g_value_get_uchar (value);
    }
  else if (G_VALUE_HOLDS_ENUM (value))
    {
      param->param_type = GP_PARAM_TYPE_INT;

      param->data.d_int = g_value_get_enum (value);
    }
  else if (G_VALUE_HOLDS_BOOLEAN (value))
    {
      param->param_type = GP_PARAM_TYPE_INT;

      param->data.d_int = g_value_get_boolean (value);
    }
  else if (G_VALUE_HOLDS_DOUBLE (value))
    {
      param->param_type = GP_PARAM_TYPE_FLOAT;

      param->data.d_float = g_value_get_double (value);
    }
  else if (G_VALUE_HOLDS_STRING (value))
    {
      param->param_type = GP_PARAM_TYPE_STRING;

      if (full_copy)
        param->data.d_string = g_value_dup_string (value);
      else
        param->data.d_string = (gchar *) g_value_get_string (value);
    }
  else if (g_type_is_a (G_VALUE_TYPE (value), G_TYPE_FILE))
    {
      GFile *file = g_value_get_object (value);

      param->param_type = GP_PARAM_TYPE_FILE;

      param->data.d_string = file ? g_file_get_uri (file) : NULL;
    }
  else if (PIKA_VALUE_HOLDS_RGB (value))
    {
      param->param_type = GP_PARAM_TYPE_COLOR;

      pika_value_get_rgb (value, &param->data.d_color);
    }
  else if (PIKA_VALUE_HOLDS_PARASITE (value))
    {
      PikaParasite *parasite = (full_copy ?
                                g_value_dup_boxed (value) :
                                g_value_get_boxed (value));

      param->param_type = GP_PARAM_TYPE_PARASITE;

      if (parasite)
        {
          param->data.d_parasite.name  = parasite->name;
          param->data.d_parasite.flags = parasite->flags;
          param->data.d_parasite.size  = parasite->size;
          param->data.d_parasite.data  = parasite->data;

          if (full_copy)
            {
              parasite->name  = NULL;
              parasite->flags = 0;
              parasite->size  = 0;
              parasite->data  = NULL;

              pika_parasite_free (parasite);
            }
        }
      else
        {
          param->data.d_parasite.name  = NULL;
          param->data.d_parasite.flags = 0;
          param->data.d_parasite.size  = 0;
          param->data.d_parasite.data  = NULL;
        }
    }
  else if (PIKA_VALUE_HOLDS_INT32_ARRAY (value) ||
           PIKA_VALUE_HOLDS_FLOAT_ARRAY (value) ||
           PIKA_VALUE_HOLDS_RGB_ARRAY (value))
    {
      PikaArray *array = g_value_get_boxed (value);

      param->param_type = GP_PARAM_TYPE_ARRAY;

      if (array)
        {
          param->data.d_array.size = array->length;

          if (full_copy)
            param->data.d_array.data = g_memdup2 (array->data,
                                                  array->length);
          else
            param->data.d_array.data = array->data;
        }
      else
        {
          param->data.d_array.size = 0;
          param->data.d_array.data = NULL;
        }
    }
  else if (G_VALUE_HOLDS (value, G_TYPE_BYTES))
    {
      GBytes *bytes = g_value_get_boxed (value);

      param->param_type = GP_PARAM_TYPE_BYTES;

      if (bytes != NULL)
        {
          if (full_copy)
            param->data.d_bytes = g_bytes_new (g_bytes_get_data (bytes, NULL),
                                               g_bytes_get_size (bytes));
          else
            param->data.d_bytes = g_bytes_new_static (g_bytes_get_data (bytes, NULL),
                                                      g_bytes_get_size (bytes));
        }
    }
  else if (G_VALUE_HOLDS (value, G_TYPE_STRV))
    {
      gchar **array = g_value_get_boxed (value);

      param->param_type = GP_PARAM_TYPE_STRV;

      if (full_copy)
        param->data.d_strv = g_strdupv (array);
      else
        param->data.d_strv = array;
    }
  else if (PIKA_VALUE_HOLDS_OBJECT_ARRAY (value))
    {
      PikaObjectArray *array = g_value_get_boxed (value);

      param->param_type = GP_PARAM_TYPE_ID_ARRAY;

      if (array)
        {
          gint i;

          if (full_copy)
            param->data.d_id_array.type_name =
              g_strdup (g_type_name (array->object_type));
          else
            param->data.d_id_array.type_name =
              (gchar *) g_type_name (array->object_type);

          param->data.d_id_array.size = array->length;

          /* must be free'd also for full_copy == FALSE */
          param->data.d_id_array.data = g_new (gint32, array->length);

          for (i = 0; i < array->length; i++)
            {
              if (PIKA_IS_IMAGE (array->data[i]))
                {
                  param->data.d_id_array.data[i] =
                    pika_image_get_id (PIKA_IMAGE (array->data[i]));
                }
              else if (PIKA_IS_ITEM (array->data[i]))
                {
                  param->data.d_id_array.data[i] =
                    pika_item_get_id (PIKA_ITEM (array->data[i]));
                }
              else if (PIKA_IS_DISPLAY (array->data[i]))
                {
                  param->data.d_id_array.data[i] =
                    pika_display_get_id (PIKA_DISPLAY (array->data[i]));
                }
              else if (PIKA_IS_RESOURCE (array->data[i]))
                {
                  param->data.d_id_array.data[i] = get_resource_id (array->data[i]);
                }
              else
                {
                  param->data.d_id_array.data[i] = -1;
                }
            }
        }
      else
        {
          param->data.d_id_array.size = 0;
          param->data.d_id_array.data = NULL;
        }
    }
  else if (PIKA_VALUE_HOLDS_IMAGE (value))
    {
      PikaImage *image = g_value_get_object (value);

      param->param_type = GP_PARAM_TYPE_INT;

      param->data.d_int = image ? pika_image_get_id (image) : -1;
    }
  else if (PIKA_VALUE_HOLDS_ITEM (value))
    {
      PikaItem *item = g_value_get_object (value);

      param->param_type = GP_PARAM_TYPE_INT;

      param->data.d_int = item ? pika_item_get_id (item) : -1;
    }
  else if (PIKA_VALUE_HOLDS_DISPLAY (value))
    {
      PikaDisplay *display = g_value_get_object (value);

      param->param_type = GP_PARAM_TYPE_INT;

      param->data.d_int = display ? pika_display_get_id (display) : -1;
    }
  else if (PIKA_VALUE_HOLDS_RESOURCE (value))
    {
      GObject *resource = g_value_get_object (value);

      param->param_type = GP_PARAM_TYPE_INT;

      param->data.d_int = resource ? get_resource_id (resource) : -1;
    }
  else if (G_VALUE_HOLDS_PARAM (value))
    {
      param->param_type = GP_PARAM_TYPE_PARAM_DEF;

      _pika_param_spec_to_gp_param_def (g_value_get_param (value),
                                        &param->data.d_param_def);
    }

  if (param->param_type == -1)
    g_warning ("%s: GValue holds unsupported type '%s'", G_STRFUNC, param->type_name);
}

GPParam *
_pika_value_array_to_gp_params (const PikaValueArray  *args,
                                gboolean               full_copy)
{
  GPParam *params;
  gint     length;
  gint     i;

  g_return_val_if_fail (args != NULL, NULL);

  length = pika_value_array_length (args);

  params = g_new0 (GPParam, length);

  for (i = 0; i < length; i++)
    {
      GValue *value = pika_value_array_index (args, i);

      pika_value_to_gp_param (value, &params[i], full_copy);
    }

  return params;
}

void
_pika_gp_params_free (GPParam  *params,
                      gint      n_params,
                      gboolean  full_copy)
{
  gint i;

  for (i = 0; i < n_params; i++)
    {
      if (full_copy)
        g_free (params[i].type_name);

      switch (params[i].param_type)
        {
        case GP_PARAM_TYPE_INT:
        case GP_PARAM_TYPE_FLOAT:
          break;

        case GP_PARAM_TYPE_STRING:
          if (full_copy)
            g_free (params[i].data.d_string);
          break;

        case GP_PARAM_TYPE_FILE:
          /* always free the uri */
          g_free (params[i].data.d_string);
          break;

        case GP_PARAM_TYPE_COLOR:
          break;

        case GP_PARAM_TYPE_ARRAY:
          if (full_copy)
            g_free (params[i].data.d_array.data);
          break;

        case GP_PARAM_TYPE_STRV:
          if (full_copy)
            g_strfreev (params[i].data.d_strv);
          break;

        case GP_PARAM_TYPE_BYTES:
          g_bytes_unref (params[i].data.d_bytes);
          break;

        case GP_PARAM_TYPE_ID_ARRAY:
          if (full_copy)
            g_free (params[i].data.d_id_array.type_name);

          /* always free the array */
          g_free (params[i].data.d_id_array.data);
          break;

        case GP_PARAM_TYPE_PARASITE:
          if (full_copy)
            {
              g_free (params[i].data.d_parasite.name);
              g_free (params[i].data.d_parasite.data);
            }
          break;

        case GP_PARAM_TYPE_PARAM_DEF:
          break;
        }
    }

  g_free (params);
}
