/*
 *  $Id: serializable.c 29747 2026-04-07 09:46:06Z yeti-dn $
 *  Copyright (C) 2009-2026 David Nečas (Yeti).
 *  E-mail: yeti@gwyddion.net.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program 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 General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "config.h"
#include <string.h>
#include <glib/gi18n-lib.h>

#include "libgwyddion/gwyddion.h"
#include "libgwyddion/serializable-internal.h"

static GwySerializableItem* append_item(GwySerializableGroup *group,
                                        const GwySerializableItem *item,
                                        GwySerializableCType expected_ctype);
static void                 free_group (GwySerializableGroup *group,
                                        gboolean free_data,
                                        GwyErrorList **error_list);

G_DEFINE_INTERFACE(GwySerializable, gwy_serializable, G_TYPE_OBJECT);

static void
gwy_serializable_default_init(G_GNUC_UNUSED GwySerializableInterface *iface)
{
}

/**
 * gwy_serializable_itemize:
 * @serializable: (transfer none): A serializable object.
 *
 * Creates the itemised representation of a serializable object.
 *
 * This function wraps the virtual table method itemize().  It deals with initialisation and filling the object type
 * name. Method itemize() then only serialises the actual data of the object. It should use
 * gwy_serializable_group_alloc() to allocate the @items array.
 *
 * Returns: (transfer full):
 *          Newly allocated itemised representation of @serializable object components.
 **/
GwySerializableGroup*
gwy_serializable_itemize(GwySerializable *serializable)
{
    GwySerializableInterface *iface = GWY_SERIALIZABLE_GET_INTERFACE(serializable);
    g_return_val_if_fail(iface && iface->itemize, NULL);

    GwySerializableGroup *group = gwy_serializable_group_new(G_OBJECT_TYPE_NAME(G_OBJECT(serializable)), 1);
    iface->itemize(serializable, group);

    return group;
}

/**
 * gwy_serializable_done:
 * @serializable: A serializable object.
 *
 * Frees temporary storage allocated by object itemization.
 *
 * This function calls the virtual table method done(), if the class has any.
 **/
void
gwy_serializable_done(GwySerializable *serializable)
{
    GwySerializableInterface *iface = GWY_SERIALIZABLE_GET_INTERFACE(serializable);
    g_return_if_fail(iface);

    if (iface->done)
        iface->done(serializable);
}

/**
 * gwy_serializable_copy:
 * @serializable: A serializable object.
 *
 * Creates an object with identical value.
 *
 * This is a copy-constructor creating a deep copy.
 *
 * You can duplicate a %NULL, too, but you are discouraged from doing it.
 *
 * #GwySerializable implementations are encouraged to provide my_object_copy() convenience wrappers with argument and
 * return value of the specific type and the corresponding type checking.
 *
 * Returns: (transfer full):
 *          The newly created object copy.
 **/
GwySerializable*
gwy_serializable_copy(GwySerializable *serializable)
{
    if (!serializable)
        return NULL;

    GwySerializableInterface *iface = GWY_SERIALIZABLE_GET_INTERFACE(serializable);
    g_return_val_if_fail(iface && iface->copy, NULL);

    GwySerializable *copy = iface->copy(serializable);
    return copy ? copy : g_object_new(G_TYPE_FROM_INSTANCE(serializable), NULL);
}

/**
 * gwy_serializable_assign:
 * @destination: An object of the same type as @source. More precisely, @source may be subclass of @destination (the
 *               extra information is lost then).
 * @source: A serializable object.
 *
 * Copies the value of an object to another object.
 *
 * If @destination is the same object as @source the function is no-op.
 *
 * What constitutes ‘object value’ depends on the class. The general rule is that @destination will look as the result
 * of gwy_serializable_copy(). However, it will keep its identity (address, connected signal handlers, references,
 * object data, etc.). The operation may be more efficient than constructing a new object but it may be not.
 *
 * #GwySerializable implementations are encouraged to provide my_object_assign() convenience wrappers with arguments
 * of the specific type and the corresponding type checking.
 **/
void
gwy_serializable_assign(GwySerializable *destination,
                        GwySerializable *source)
{
    g_return_if_fail(source);
    g_return_if_fail(destination);
    if (source == destination)
        return;

    GwySerializableInterface *iface = GWY_SERIALIZABLE_GET_INTERFACE(destination);
    g_return_if_fail(iface && iface->assign);
    /* No need to check GWY_IS_SERIALIZABLE(source) if this holds. */
    g_return_if_fail(g_type_is_a(G_TYPE_FROM_INSTANCE(source), G_TYPE_FROM_INSTANCE(destination)));

    iface->assign(destination, source);
}

/**
 * gwy_serializable_group_new:
 * @name: Group name, meanning object or boxed type name. Generally the type name of the #GType.
 * @nitems: Minimum number of items to preallocate.
 *
 * Creates a new group of serializable items.
 *
 * Serialisable classes rarely need this function because their itemize() method already gets a #GwySerializableGroup
 * to fill.
 *
 * Returns: (transfer full):
 *          A newly allocated group of serializable items.
 **/
GwySerializableGroup*
gwy_serializable_group_new(const gchar *name,
                           gsize nitems)
{
    GwySerializableGroup *group = g_new0(GwySerializableGroup, 1);
    group->name = name;
    ensure_group_size(group, nitems);
    return group;
}

/**
 * gwy_serializable_group_free:
 * @group: (nullable):
 *         Itemised object components.
 * @free_data: %TRUE to free item data (deserialisation), %FALSE to only free the groups (serialisation).
 * @error_list: (nullable):
 *              Error list where to report unconsumed items (for deserialisation).
 *
 * Recursively frees lists of serialisation items.
 *
 * Objects (and boxed types) can be either the actual objects or itemised into groups, provided that the
 * %GWY_SERIALIZABLE_IS_GROUP flags are set correctly. Recursive freeing descends into groups, not into objects.
 * Hence, if there is an object-group split, it must have the natural form, where the top levels of the tree are groups
 * and bottom levels are objects.
 *
 * In serialisation, item data remain to be owned by the objects being serialised. They are not consumed. Therefore,
 * one normally passes @free_data=%FALSE and @error_list=%NULL (otherwise every item would be reported as
 * unconsumed).
 *
 * In deserialisation, item data are normally consumed by the objects being constructed. If there any leftovers, they
 * need to be freed. Therefore, one normally passes @free_data=%TRUE and non-%NULL @error_list (except after fatal
 * errors when reporting all unconsumed items would be pointless clutter).
 *
 * For convenience, @group can be %NULL. The function is no-op then.
 *
 * The function does not call gwy_serializable_group_done(). A serialiser must call it beforehand if required.
 **/
void
gwy_serializable_group_free(GwySerializableGroup *group,
                            gboolean free_data,
                            GwyErrorList **error_list)
{
    free_group(group, free_data, error_list);
}

/**
 * gwy_serializable_group_alloc_size:
 * @group: Itemised object components.
 * @nitems: Minimum size of @items array to ensure.
 *
 * Ensures a minimum number of preallocated items in a itemised component list for serialisation.
 *
 * The actual number of items preallocated can be larger than @nitems. The new items are filled with zeros.
 *
 * The function invalidates the pointer returned by gwy_serializable_group_items().
 **/
void
gwy_serializable_group_alloc_size(GwySerializableGroup *group,
                                  gsize nitems)
{
    g_return_if_fail(group);
    ensure_group_size(group, nitems);
}

/**
 * gwy_serializable_group_name:
 * @group: Itemised object components.
 *
 * Gets the name of a group of serialisable items.
 *
 * The name name is the corresponding object or boxed type name. Generally the type name of the #GType.
 *
 * Returns: (transfer none):
 *          The group name.
 **/
const gchar*
gwy_serializable_group_name(GwySerializableGroup *group)
{
    g_return_val_if_fail(group, NULL);
    return group->name;
}

/**
 * gwy_serializable_group_rename:
 * @group: Itemised object components.
 * @name: New group name, meanning object or boxed type name. Generally the type name of the #GType. It is not copied
 *        and must be a static, interned or other long-lived string.
 *
 * Renames a group of serializable items.
 *
 * This function is rarely needed, except for cases like format translation.
 **/
void
gwy_serializable_group_rename(GwySerializableGroup *group,
                              const gchar *name)
{
    g_return_if_fail(group);
    group->name = name;
}

/**
 * gwy_serializable_group_items:
 * @group: Itemised object components.
 * @nitems: (out) (optional):
 *           Location where to store the number of items in the group.
 *
 * Gets the entire array of items in a serialisable group.
 *
 * Returns: (transfer none) (array length=nitems):
 *          Array with all items in the group.
 **/
GwySerializableItem*
gwy_serializable_group_items(GwySerializableGroup *group,
                             gsize *nitems)
{
    g_return_val_if_fail(group, NULL);
    if (nitems)
        *nitems = group->n;
    return group->items;
}

/**
 * gwy_serializable_group_append:
 * @group: Itemised object components.
 * @items: (array size=nitems) (transfer none): Items to append. They will be shallow-copied to the group.
 * @nitems: The number of items in @items.
 *
 * Appends pre-filled serialisable items to a group.
 *
 * This is a low-level function. Objects should usually use functions such as gwy_serializable_group_append_int32()
 * for serialisation.
 **/
void
gwy_serializable_group_append(GwySerializableGroup *group,
                              const GwySerializableItem *items,
                              gsize nitems)
{
    g_return_if_fail(group);
    if (!nitems)
        return;
    g_return_if_fail(items);

    gsize n = group->n;
    ensure_group_size(group, n+nitems);
    gwy_assign(group->items + n, items, nitems);
    group->n += nitems;
}

/**
 * gwy_serializable_group_append_boolean:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_BOOLEAN type).
 * @value: A boolean member.
 *
 * Appends a serialisable item of type boolean to a group.
 **/
void
gwy_serializable_group_append_boolean(GwySerializableGroup *group,
                                      const GwySerializableItem *model,
                                      gboolean value)
{
    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_BOOLEAN);
    item->value.v_boolean = value;
}

/**
 * gwy_serializable_group_append_unit:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_OBJECT type).
 * @unit: (nullable): A member unit.
 *
 * Appends a serialisable item of an unit type to a group.
 *
 * This is a convenience function for the common case where the member object is a unit. It almost identical to
 * gwy_serializable_group_append_object() except the argument type.
 *
 * It is not an error to pass a %NULL as @unit. A %NULL member unit is not serialised and no item is appended.
 *
 * The unit must continue to exist until serialisation is done.
 **/
void
gwy_serializable_group_append_unit(GwySerializableGroup *group,
                                   const GwySerializableItem *model,
                                   GwyUnit *unit)
{
    if (!unit)
        return;

    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_OBJECT);
    item->value.v_object = (GObject*)unit;
}

/**
 * gwy_serializable_group_append_object:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_OBJECT type).
 * @object: (nullable): A member object. It must be serialisable.
 *
 * Appends a serialisable item of an object type to a group.
 *
 * It is not an error to pass a %NULL as @object. A %NULL member object is not serialised and no item is appended.
 *
 * The object must continue to exist until serialisation is done.
 **/
void
gwy_serializable_group_append_object(GwySerializableGroup *group,
                                     const GwySerializableItem *model,
                                     GObject *object)
{
    if (!object)
        return;

    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_OBJECT);
    item->value.v_object = object;
}

/**
 * gwy_serializable_group_append_rgba:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_BOXED type).
 * @rgba: An RGBA colour member struct.
 *
 * Appends a serialisable item of type RGBA colour to a group.
 *
 * The @aux.boxed_type field of @model must be %GWY_TYPE_RGBA or zero. The latter is not useful if the template is
 * also used for deserialisation and is thus not recommended.
 **/
void
gwy_serializable_group_append_rgba(GwySerializableGroup *group,
                                   const GwySerializableItem *model,
                                   const GwyRGBA *rgba)
{
    g_return_if_fail(group && model && rgba);
    g_warn_if_fail(!model->aux.boxed_type || model->aux.boxed_type == GWY_TYPE_RGBA);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_BOXED);
    item->value.v_boxed = (gpointer)rgba;
    item->aux.boxed_type = GWY_TYPE_RGBA;
}

/**
 * gwy_serializable_group_append_boxed:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_BOXED type).
 * @boxed: A boxed member struct.
 *
 * Appends a serialisable item of type boxed to a group.
 *
 * The @aux.boxed_type field of @model must be set to the specific boxed type.
 *
 * The boxed pointer must continue to exist until serialisation is done.
 **/
void
gwy_serializable_group_append_boxed(GwySerializableGroup *group,
                                    const GwySerializableItem *model,
                                    gconstpointer boxed)
{
    g_return_if_fail(group && model && boxed);
    g_return_if_fail(gwy_boxed_type_is_serializable(model->aux.boxed_type));
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_BOXED);
    item->value.v_boxed = (gpointer)boxed;
}

/**
 * gwy_serializable_group_append_string:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_STRING type).
 * @string: (nullable): A string member.
 *
 * Appends a serialisable item of type string to a group.
 *
 * It is not an error to pass a %NULL as @string. A %NULL string is not serialised and no item is appended.
 *
 * The string must continue to exist until serialisation is done.
 **/
void
gwy_serializable_group_append_string(GwySerializableGroup *group,
                                     const GwySerializableItem *model,
                                     const gchar *string)
{
    if (!string)
        return;

    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_STRING);
    item->value.v_string = (gchar*)string;
}

/**
 * gwy_serializable_group_append_int8:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_INT8 type).
 * @value: A 8bit integer member.
 *
 * Appends a serialisable item of type 8bit integer to a group.
 **/
void
gwy_serializable_group_append_int8(GwySerializableGroup *group,
                                   const GwySerializableItem *model,
                                   gint8 value)
{
    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_INT8);
    item->value.v_int8 = value;
}

/**
 * gwy_serializable_group_append_int16:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_INT16 type).
 * @value: A 16bit integer member.
 *
 * Appends a serialisable item of type 16bit integer to a group.
 **/
void
gwy_serializable_group_append_int16(GwySerializableGroup *group,
                                    const GwySerializableItem *model,
                                    gint16 value)
{
    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_INT16);
    item->value.v_int16 = value;
}

/**
 * gwy_serializable_group_append_int32:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_INT32 type).
 * @value: A 32bit integer member.
 *
 * Appends a serialisable item of type 32bit integer to a group.
 **/
void
gwy_serializable_group_append_int32(GwySerializableGroup *group,
                                    const GwySerializableItem *model,
                                    gint32 value)
{
    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_INT32);
    item->value.v_int32 = value;
}

/**
 * gwy_serializable_group_append_int64:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_INT64 type).
 * @value: A 64bit integer member.
 *
 * Appends a serialisable item of type 64bit integer to a group.
 **/
void
gwy_serializable_group_append_int64(GwySerializableGroup *group,
                                    const GwySerializableItem *model,
                                    gint64 value)
{
    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_INT64);
    item->value.v_int64 = value;
}

/**
 * gwy_serializable_group_append_float:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_FLOAT type).
 * @value: A float member.
 *
 * Appends a serialisable item of type float to a group.
 **/
void
gwy_serializable_group_append_float(GwySerializableGroup *group,
                                    const GwySerializableItem *model,
                                    gfloat value)
{
    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_FLOAT);
    item->value.v_float = value;
}

/**
 * gwy_serializable_group_append_double:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_DOUBLE type).
 * @value: A double member.
 *
 * Appends a serialisable item of type double to a group.
 **/
void
gwy_serializable_group_append_double(GwySerializableGroup *group,
                                     const GwySerializableItem *model,
                                     gdouble value)
{
    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_DOUBLE);
    item->value.v_double = value;
}

/**
 * gwy_serializable_group_append_int8_array:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_INT8_ARRAY type).
 * @values: (array length=len) (nullable): An array of 8bit integers member.
 * @len: Number of elements in @values.
 *
 * Appends a serialisable item of type 8bit integer array to a group.
 *
 * It is not an error to pass an empty array as @values. If @len is zero or @values is %NULL, the member is not
 * serialised. It is valid to pass non-zero @len with %NULL @values and vice versa.
 *
 * The array must continue to exist until serialisation is done.
 **/
void
gwy_serializable_group_append_int8_array(GwySerializableGroup *group,
                                         const GwySerializableItem *model,
                                         const gint8 *values,
                                         gsize len)
{
    if (!len || !values)
        return;

    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_INT8_ARRAY);
    item->value.v_int8_array = (gint8*)values;
    item->array_size = len;
}

/**
 * gwy_serializable_group_append_int16_array:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_INT16_ARRAY type).
 * @values: (array length=len) (nullable): An array of 16bit integers member.
 * @len: Number of elements in @values.
 *
 * Appends a serialisable item of type 16bit integer array to a group.
 *
 * It is not an error to pass an empty array as @values. If @len is zero or @values is %NULL, the member is not
 * serialised. It is valid to pass non-zero @len with %NULL @values and vice versa.
 *
 * The array must continue to exist until serialisation is done.
 **/
void
gwy_serializable_group_append_int16_array(GwySerializableGroup *group,
                                          const GwySerializableItem *model,
                                          const gint16 *values,
                                          gsize len)
{
    if (!len || !values)
        return;

    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_INT16_ARRAY);
    item->value.v_int16_array = (gint16*)values;
    item->array_size = len;
}

/**
 * gwy_serializable_group_append_int32_array:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_INT32_ARRAY type).
 * @values: (array length=len) (nullable): An array of 32bit integers member.
 * @len: Number of elements in @values.
 *
 * Appends a serialisable item of type 32bit integer array to a group.
 *
 * It is not an error to pass an empty array as @values. If @len is zero or @values is %NULL, the member is not
 * serialised. It is valid to pass non-zero @len with %NULL @values and vice versa.
 *
 * The array must continue to exist until serialisation is done.
 **/
void
gwy_serializable_group_append_int32_array(GwySerializableGroup *group,
                                          const GwySerializableItem *model,
                                          const gint32 *values,
                                          gsize len)
{
    if (!len || !values)
        return;

    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_INT32_ARRAY);
    item->value.v_int32_array = (gint32*)values;
    item->array_size = len;
}

/**
 * gwy_serializable_group_append_int64_array:
 * @values: (array length=len) (nullable): An array of 64bit integers member.
 * @len: Number of elements in @values.
 * @model: Item template (of %GWY_SERIALIZABLE_INT64_ARRAY type).
 * @group: Itemised object components to append to.
 *
 * Appends a serialisable item of type 64bit integer array to a group.
 *
 * It is not an error to pass an empty array as @values. If @len is zero or @values is %NULL, the member is not
 * serialised. It is valid to pass non-zero @len with %NULL @values and vice versa.
 *
 * The array contents is never modified by this function.
 **/
void
gwy_serializable_group_append_int64_array(GwySerializableGroup *group,
                                          const GwySerializableItem *model,
                                          const gint64 *values,
                                          gsize len)
{
    if (!len || !values)
        return;

    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_INT64_ARRAY);
    item->value.v_int64_array = (gint64*)values;
    item->array_size = len;
}

/**
 * gwy_serializable_group_append_float_array:
 * @values: (array length=len) (nullable): An array of floats member.
 * @len: Number of elements in @values.
 * @model: Item template (of %GWY_SERIALIZABLE_FLOAT_ARRAY type).
 * @group: Itemised object components to append to.
 *
 * Appends a serialisable item of type float array to a group.
 *
 * It is not an error to pass an empty array as @values. If @len is zero or @values is %NULL, the member is not
 * serialised. It is valid to pass non-zero @len with %NULL @values and vice versa.
 *
 * The array contents is never modified by this function.
 **/
void
gwy_serializable_group_append_float_array(GwySerializableGroup *group,
                                          const GwySerializableItem *model,
                                          const gfloat *values,
                                          gsize len)
{
    if (!len || !values)
        return;

    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_FLOAT_ARRAY);
    item->value.v_float_array = (gfloat*)values;
    item->array_size = len;
}

/**
 * gwy_serializable_group_append_double_array:
 * @values: (array length=len) (nullable): An array of doubles member.
 * @len: Number of elements in @values.
 * @model: Item template (of %GWY_SERIALIZABLE_DOUBLE_ARRAY type).
 * @group: Itemised object components to append to.
 *
 * Appends a serialisable item of type double array to a group.
 *
 * It is not an error to pass an empty array as @values. If @len is zero or @values is %NULL, the member is not
 * serialised. It is valid to pass non-zero @len with %NULL @values and vice versa.
 *
 * The array contents is never modified by this function.
 **/
void
gwy_serializable_group_append_double_array(GwySerializableGroup *group,
                                           const GwySerializableItem *model,
                                           const gdouble *values,
                                           gsize len)
{
    if (!len || !values)
        return;

    g_return_if_fail(group && model);
    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_DOUBLE_ARRAY);
    item->value.v_double_array = (gdouble*)values;
    item->array_size = len;
}

static gsize
count_nonnull(gconstpointer *ptrs, gsize n)
{
    gsize n_nonnull = 0;
    for (gsize i = 0; i < n; i++)
        n_nonnull += !!ptrs[i];
    if (!n_nonnull || n_nonnull == n)
        return n_nonnull;

    g_critical("Trying to serialise a mixed array with NULLs and non-NULLs.");
    return 0;
}

/**
 * gwy_serializable_group_append_string_array:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_STRING_ARRAY type).
 * @strings: (array length=len) (nullable): A string array member.
 * @len: Number of elements in @strings.
 *
 * Appends a serialisable item of type string array to a group.
 *
 * It is not an error to pass an empty array as @strings. If @len is zero, @strings is %NULL or @strings is an array
 * containing only %NULL elements, the member is not serialised. Otherwise it is serialised and all the strings must
 * exist. It is an error to pass @strings array with some elements %NULL and some non-%NULL.
 *
 * The array and the strings in it must continue to exist until serialisation is done.
 **/
void
gwy_serializable_group_append_string_array(GwySerializableGroup *group,
                                           const GwySerializableItem *model,
                                           gchar **strings,
                                           gsize len)
{
    if (!len || !strings)
        return;

    g_return_if_fail(group && model);
    if (!count_nonnull((gconstpointer*)strings, len))
        return;

    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_STRING_ARRAY);
    item->value.v_string_array = strings;
    item->array_size = len;
}

/**
 * gwy_serializable_group_append_object_array:
 * @group: Itemised object components to append to.
 * @model: Item template (of %GWY_SERIALIZABLE_OBJECT_ARRAY type).
 * @objects: (array length=len) (nullable): An object array member.
 * @len: Number of elements in @objects.
 *
 * Appends a serialisable item of type object array to a group.
 *
 * It is not an error to pass an empty array as @objects. If @len is zero, @objects is %NULL or @objects is an array
 * containing only %NULL elements, the member is not serialised. Otherwise it is serialised and all the objects must
 * exist. It is an error to pass @objects array with some elements %NULL and some non-%NULL.
 *
 * The array and the objects in it must continue to exist until serialisation is done.
 **/
void
gwy_serializable_group_append_object_array(GwySerializableGroup *group,
                                           const GwySerializableItem *model,
                                           GObject **objects,
                                           gsize len)
{
    if (!len || !objects)
        return;

    g_return_if_fail(group && model);
    if (!count_nonnull((gconstpointer*)objects, len))
        return;

    GwySerializableItem *item = append_item(group, model, GWY_SERIALIZABLE_OBJECT_ARRAY);
    item->value.v_object_array = objects;
    item->array_size = len;
}

/* FIXME: Do we want to offer this as a public helper function for classes which do not use
 * gwy_serializable_group_itemize()? */
static void
gwy_serializable_group_retain_object(GwySerializableGroup *group,
                                     GwySerializable *serializable)
{
    g_return_if_fail(group);
    g_return_if_fail(serializable);
    GwySerializableInterface *iface = GWY_SERIALIZABLE_GET_INTERFACE(serializable);
    if (!iface->done)
        return;

    if (group->nobjects == group->lenobjects) {
        group->lenobjects = MAX(2*group->lenobjects, 8);
        group->objects = g_renew(GwySerializable*, group->objects, group->lenobjects);
    }
    group->objects[group->nobjects++] = serializable;
}

/**
 * gwy_serializable_group_itemize:
 * @group: Shallowly itemised object components.
 *
 * Recursively itemises nested objects and boxed types for serialisation.
 *
 * An object with nested object and boxed members can fill @v_object and @v_boxed fields in #GwySerializableItem and
 * call this function to replace all such fields with the corresponding #GwySerializableGroup. The function also
 * ensures gwy_serializable_done() is called on all objecst that need it after the serialisation is done.
 *
 * Object arrays are also handled. Pay attention to memory management in such case. The function does not modify the
 * elements of @v_object_array. It replaces the entire array with a newly allocated array of groups. So you can have
 * static or otherwise valuable arrays as @v_object_array and it will be kept intact. On the other hand, if you
 * allocated @v_object_array dynamically on heap, you will need to free it. This can be done immediately after calling
 * this function. The new array of groups is freed by gwy_serializable_group_free() and you do not normally need to
 * concern yourself with freeing it.
 *
 * Using this function is not necessary. An object can also directly invoke the nested itemisation during its
 * serialisation. In such case the object must set the %GWY_SERIALIZABLE_IS_GROUP flags and ensure that
 * gwy_serializable_done() is called on object members as needed. The two approaches cannot be mixed.
 **/
void
gwy_serializable_group_itemize(GwySerializableGroup *group)
{
    g_return_if_fail(group);

    gsize n = group->n;
    for (gsize i = 0; i < n; i++) {
        GwySerializableItem *item = group->items + i;
        g_assert(!(item->flags & GWY_SERIALIZABLE_IS_GROUP));
        if (item->ctype == GWY_SERIALIZABLE_OBJECT) {
            GwySerializable *serializable = GWY_SERIALIZABLE(item->value.v_object);
            gwy_serializable_group_retain_object(group, serializable);
            item->value.v_group = gwy_serializable_itemize(serializable);
            item->flags |= GWY_SERIALIZABLE_IS_GROUP;
        }
        else if (item->ctype == GWY_SERIALIZABLE_BOXED) {
            item->value.v_group = gwy_serializable_boxed_itemize(item->aux.boxed_type, item->value.v_boxed);
            item->flags |= GWY_SERIALIZABLE_IS_GROUP;
        }
        else if (item->ctype == GWY_SERIALIZABLE_OBJECT_ARRAY) {
            GObject **objects = item->value.v_object_array;
            gsize array_size = item->array_size;
            GwySerializableGroup **groups = g_new(GwySerializableGroup*, array_size);
            for (gsize j = 0; j < array_size; j++) {
                GwySerializable *serializable = GWY_SERIALIZABLE(objects[j]);
                gwy_serializable_group_retain_object(group, serializable);
                groups[j] = gwy_serializable_itemize(serializable);
            }
            /* NB: The caller must free the original v_object_array if it is dynamically allocated! We do not know
             * whether it is. */
            item->value.v_group_array = groups;
            item->flags |= GWY_SERIALIZABLE_IS_GROUP;
        }
    }
}

/**
 * gwy_serializable_group_done:
 * @group: Itemised object components.
 *
 * Recursively frees temporary object data for a list of serialisation items.
 *
 * The functions is useful for serialisers, not individual classes implementing serialisation. It calls
 * gwy_serializable_done() on all objects in @group that need it, in the correct (i.e. backwards) order. This is
 * normally done at the end of serialisation.
 *
 * Keep in mind that it only releases whatever is inside the top-level serialised object. You also need to finally
 * call gwy_serializable_done() on the top-level object itself to release its temporary data.
 **/
void
gwy_serializable_group_done(GwySerializableGroup *group)
{
    /* First recursively call done on any nested groups. */
    gsize n = group->n;
    for (gsize i = 0; i < n; i++) {
        GwySerializableItem *item = group->items + (n-1 - i);
        if (item->ctype == GWY_SERIALIZABLE_OBJECT || item->ctype == GWY_SERIALIZABLE_BOXED) {
            g_assert(item->flags & GWY_SERIALIZABLE_IS_GROUP);
            gwy_serializable_group_done(item->value.v_group);
        }
        else if (item->ctype == GWY_SERIALIZABLE_OBJECT_ARRAY) {
            g_assert(item->flags & GWY_SERIALIZABLE_IS_GROUP);
            gsize array_size = item->array_size;
            GwySerializableGroup **groups = item->value.v_group_array;
            for (gsize j = 0; j < array_size; j++)
                gwy_serializable_group_done(groups[array_size-1 - j]);
        }
    }

    /* Finally, call done() on all our objects. */
    gsize nobj = group->nobjects;
    for (gsize i = 0; i < nobj; i++)
        gwy_serializable_done(group->objects[nobj-1 - i]);
    GWY_FREE(group->objects);
    group->nobjects = group->lenobjects = 0;
}

/**
 * gwy_serializable_group_size:
 * @group: Shallowly itemised object components.
 *
 * Reports the data size of a list of serialisation items.
 *
 * The size is only available in some context, in particular after gwy_deserialize_group_memory() or after calculating
 * the sizes in serialisation.
 *
 * Returns: Serialised data size, in bytes.
 **/
gsize
gwy_serializable_group_size(GwySerializableGroup *group)
{
    g_return_val_if_fail(group, 0);
    return group->size_bytes;
}

static void
warn_nonzero_array_size(const GwySerializableItem *item)
{
    g_warning("A NULL array ‘%s’ of type 0x%02x has still nonzero size %" G_GSIZE_FORMAT "."
              "Please set item->array_size to zero when clearing the array. "
              "Hoping no memory was leaked...",
              item->name, item->ctype, item->array_size);
}

static void
add_unexpected_item_error(GwyErrorList **error_list,
                          const GwySerializableItem *item,
                          const gchar *group_name)
{
    gwy_error_list_add(error_list, GWY_DESERIALIZE_ERROR, GWY_DESERIALIZE_ERROR_ITEM,
                       // TRANSLATORS: Error message.
                       _("Unexpected item ‘%s’ of type 0x%02x in the representation of object ‘%s’ was ignored."),
                       item->name ? item->name : "(nil)",
                       item->ctype,
                       group_name ? group_name : "(unknown)");
}

static void
free_group_item(GwySerializableItem *item, gboolean free_data)
{
    GwySerializableCType ctype = item->ctype;
    gsize array_size = item->array_size;

    g_return_if_fail(item->flags & GWY_SERIALIZABLE_IS_GROUP);
    if (ctype == GWY_SERIALIZABLE_OBJECT || ctype == GWY_SERIALIZABLE_BOXED) {
        free_group(item->value.v_group, free_data, NULL);
        item->value.v_group = NULL;
    }
    else if (ctype == GWY_SERIALIZABLE_OBJECT_ARRAY) {
        GwySerializableGroup **groups = item->value.v_group_array;
        if (groups) {
            for (gsize j = 0; j < array_size; j++)
                free_group(groups[j], free_data, NULL);
            GWY_FREE(item->value.v_group_array);
        }
    }
    else {
        g_critical("Non-object and non-boxed item %s has the is-group flag set.", item->name);
    }
}

static void
free_item_data(GwySerializableItem *item)
{
    GwySerializableCType ctype = item->ctype;
    gsize array_size = item->array_size;

    g_return_if_fail(!(item->flags & GWY_SERIALIZABLE_IS_GROUP));
    if (ctype == GWY_SERIALIZABLE_OBJECT)
        g_clear_object(&item->value.v_object);
    else if (ctype == GWY_SERIALIZABLE_INT8_ARRAY
             || ctype == GWY_SERIALIZABLE_INT16_ARRAY
             || ctype == GWY_SERIALIZABLE_INT32_ARRAY
             || ctype == GWY_SERIALIZABLE_INT64_ARRAY
             || ctype == GWY_SERIALIZABLE_FLOAT_ARRAY
             || ctype == GWY_SERIALIZABLE_DOUBLE_ARRAY) {
        if (item->value.v_int8_array)
            GWY_FREE(item->value.v_int8_array);
        else if (G_UNLIKELY(array_size))
            warn_nonzero_array_size(item);
    }
    else if (ctype == GWY_SERIALIZABLE_STRING)
        GWY_FREE(item->value.v_string);
    else if (ctype == GWY_SERIALIZABLE_BOXED) {
        if (item->value.v_boxed) {
            g_boxed_free(item->aux.boxed_type, item->value.v_boxed);
            item->value.v_boxed = NULL;
        }
    }
    else if (ctype == GWY_SERIALIZABLE_STRING_ARRAY) {
        if (item->value.v_string_array) {
            for (gsize j = 0; j < array_size; j++)
                GWY_FREE(item->value.v_string_array[j]);
            GWY_FREE(item->value.v_string_array);
        }
        else if (G_UNLIKELY(array_size))
            warn_nonzero_array_size(item);
    }
    else if (ctype == GWY_SERIALIZABLE_OBJECT_ARRAY) {
        if (item->value.v_object_array) {
            for (gsize j = 0; j < array_size; j++)
                g_clear_object(item->value.v_object_array + j);
            GWY_FREE(item->value.v_object_array);
        }
        else if (G_UNLIKELY(array_size))
            warn_nonzero_array_size(item);
    }
    else {
        g_assert(g_ascii_islower(ctype));
    }
    item->array_size = 0;
}

static void
free_group(GwySerializableGroup *group,
           gboolean free_data,
           GwyErrorList **error_list)
{
    if (!group)
        return;

    gsize n = group->n;
    for (gsize i = 0; i < n; i++) {
        GwySerializableItem *item = group->items + (n-1 - i);
        GwySerializableCType ctype = item->ctype;

        if (ctype == GWY_SERIALIZABLE_CONSUMED)
            continue;

        if (free_data && error_list)
            add_unexpected_item_error(error_list, item, group->name);

        /* Always free groups. But only call free_item_data() if item data freeing is requested. */
        if (item->flags & GWY_SERIALIZABLE_IS_GROUP)
            free_group_item(item, free_data);
        else if (free_data)
            free_item_data(item);
    }

    g_warn_if_fail(!group->objects);

    g_free(group->items);
    g_free(group);
}

static GwySerializableItem*
append_item(GwySerializableGroup *group, const GwySerializableItem *item, GwySerializableCType expected_ctype)
{
    if (expected_ctype)
        g_warn_if_fail(item->ctype == expected_ctype);

    gsize n = group->n;
    ensure_group_size(group, n+1);
    group->items[group->n++] = *item;
    return group->items + n;
}

static int
compare_size(const void *pa, const void *pb)
{
    gsize a = *(const gsize*)pa, b = *(const gsize*)pb;
    if (a < b)
        return -1;
    if (a > b)
        return 1;
    return 0;
}

/**
 * gwy_serializable_group_discard:
 * @group: Itemised object components.
 * @indices: Indices of items to discard.
 * @nitems: (array size=indices): The number of items to discard.
 * @free_data: %TRUE to free item data (deserialisation), %FALSE to only remove items (serialisation).
 *
 * Discards items specified by indices from a serialisation group.
 *
 * This is a low level functions useful only in format translation and similar tasks. See
 * gwy_deserialize_filter_items() for a general helper filtering unwanted items during deserialisation. See
 * gwy_serializable_group_free() for discussion of @free_data.
 **/
void
gwy_serializable_group_discard(GwySerializableGroup *group,
                               const gsize *indices,
                               gsize nitems,
                               gboolean free_data)
{
    g_return_if_fail(group);
    if (!nitems)
        return;
    g_return_if_fail(indices);

    gsize n = group->n;
    GwySerializableItem *items = group->items;

    gsize *sorted_indices = g_new(gsize, (nitems + 1)*sizeof(gsize));
    gwy_assign(sorted_indices, indices, nitems);
    qsort(sorted_indices, nitems, sizeof(gsize), compare_size);
    gsize largest_index = sorted_indices[nitems-1];
    if (largest_index >= n) {
        g_free(sorted_indices);
        g_return_if_fail(largest_index < n);
    }
    sorted_indices[nitems] = n;

    // First just free item data if we were asked to.
    if (free_data) {
        for (gsize i = 0; i < nitems; i++) {
            GwySerializableItem *item = items + sorted_indices[i];
            GwySerializableCType ctype = item->ctype;
            if (ctype != GWY_SERIALIZABLE_CONSUMED) {
                /* Always free groups. But only call free_item_data() if item data freeing is requested. */
                if (item->flags & GWY_SERIALIZABLE_IS_GROUP)
                    free_group_item(item, free_data);
                else if (free_data)
                    free_item_data(item);
                // Make it idempotent.
                item->ctype = GWY_SERIALIZABLE_CONSUMED;
            }
        }
    }

    // Then push everything towards the beginning.
    gsize removed = 0;
    for (gsize i = 0; i < nitems; i++) {
        gsize start = sorted_indices[i], len = sorted_indices[i+1] - start;
        if (!len)
            continue;

        len--;
        if (len)
            memmove(items + start - removed, items + start+1, len*sizeof(GwySerializableItem));
        removed++;
    }
    g_free(sorted_indices);

    // If someone passes the same index multitple times, we still want to get it right. So do not set the final
    // length based on @nitems but based on @removed.
    group->n -= removed;
}

/**
 * SECTION: serializable
 * @title: GwySerializable
 * @short_description: Serialisable value-like object interface
 *
 * #GwySerializable is an abstract interface for data-like objects that can be serialised and deserialised.  You can
 * serialise any object implementing this interface with functions such as gwy_serialize_gio() and the restore
 * (deserialise) it with gwy_deserialize_memory().
 *
 * Gwyddion implements a simple serialisation model: only tree-like structures of objects, formed by ownership, can be
 * serialised and restored.  Any inter-object relations other than plain ownership must be stored in some weak form
 * and there is no explicit support for this.  Moreover, only object values are preserved, their identities are lost
 * (in particular, signals, user data and similar attributes are not subject to serialisation and deserialisation).
 * This, on the other hand, means that any saved object can be restored individually and independently and still be in
 * a meaningful state.
 *
 * Beside saving and restoration, all serialisable classes implement a copy-constructor that creates a duplicate of an
 * existing object.  This constructor is invoked with gwy_serializable_copy().  Furthermore, the value of one
 * such object can be transferred to another object of the same class (or a superclass), similarly to overriden
 * assignment operators in OO languages.  This assignment is performed with gwy_serializable_assign().
 *
 * Most classes that implement #GwySerializable define convenience macros for the copy-constructor and assignment
 * operator, called
 * <function>gwy_foo_copy<!-- -->()</function> and <function>gwy_foo_assign<!-- -->()</function>, respectively,
 * where <literal>foo</literal> is the lowercase class name.
 *
 * <refsect2 id='libgwy-serializable-implementing'>
 * <title>Implementing #GwySerializable</title>
 * <para>You can implement serialisation and deserialisation in your classes.  Use the %G_IMPLEMENT_ITERFACE macro
 * to generate the boilerplate interface implementation code. The copy constructor and assignment operators are
 * usually easy so we focus on serialisation and deserialisation (with public object data for simplicity). There
 * are a number of helper functions, such as gwy_deserialize_filter() for getting only the interesting items in
 * deserialisation or gwy_serialize_item_double() for handling the itemisation of a single value.</para>
 * |[
 * // Standard object structure definitions
 * typedef struct _MyObject      MyObject;
 * typedef struct _MyObjectClass MyObjectClass;
 *
 * struct _MyObject {
 *     GObject g_object;
 *     gint32 data;
 * };
 *
 * struct _MyObjectClass {
 *     GObjectClass g_object_class;
 * };
 *
 * // Define the type
 * G_DEFINE_TYPE_WITH_CODE(MyObject, my_object, G_TYPE_OBJECT,
 *                         G_IMPLEMENT_INTERFACE(GWY_TYPE_SERIALIZABLE, serializable_init));
 *
 * // Serialised data specification, used both in itemize() and construct()
 *
 * enum {
 *     ITEM_DATA,
 *     NUM_ITEMS
 * };
 *
 * static const GwySerializableItem serializable_items[NUM_ITEMS] = {
 *     { .name = "data", .ctype = GWY_SERIALIZABLE_INT32, },
 * };
 *
 * // Implement the interface
 * static void
 * serializable_init(GwySerializableInterface *iface)
 * {
 *     iface->itemize   = serializable_itemize;
 *     iface->construct = serializable_construct;
 *     iface->copy      = serializable_copy;
 *     iface->assign    = serializable_assign;
 * }
 *
 * // Duplication is easy, create a new object and set data
 * static GwySerializable*
 * serializable_copy(GwySerializable *serializable)
 * {
 *     MyObject *myobject = GWY_MY_OBJECT(serializable);
 *     MyObject *copy = g_object_newv(MY_TYPE_OBJECT, 0, NULL);
 *
 *     copy->data = myobject->data;
 *     return GWY_SERIALIZABLE(copy);
 * }
 *
 * // Assigning is even easier, just set data
 * static void
 * serializable_assign(GwySerializable *destination,
 *                     GwySerializable *source)
 * {
 *     MyObject *myobject = GWY_MY_OBJECT(serializable);
 *     MyObject *src = GWY_MY_OBJECT(source);
 *
 *     src->data = myobject->data;
 * }
 *
 * // Fill the item list with actual object data.  This is a bit
 * // overcomplicated for such a simple object as MyObject, of course.
 * static void
 * serializable_itemize(GwySerializable *serializable,
 *                      GwySerializableGroup *group)
 * {
 *     MyObject *myobject = GWY_MY_OBJECT(serializable);
 *
 *     // Preallocate the group to avoid reallocations. Not really necessary in this case because we have just one
 *     // item.
 *     gwy_serializable_group_alloc_size(group, NUM_ITEMS);
 *
 *     // Do not store the data if it has the default value.
 *     if (myobject->data)
 *         gwy_serializable_group_append_int32(group, serializable_items + ITEM_DATA, myobject->data);
 *
 *     // If we had member objects, we would call gwy_serializable_group_itemize() here to itemise them.
 * }
 *
 * // Construct the object from item list.  The data item does not need to be present, in this case the default zero
 * // value is used.  Also, for more complicated objects the deserialisation can fail or resources need to be released;
 * // see GwySerializableInterface for the clean-up rules.
 * static gboolean
 * serializable_construct(GwySerializable *serializable,
 *                        GwySerializableGroup *group,
 *                        GwyErrorList **error_list)
 * {
 *     GwySerializableItem it[G_N_ELEMENTS(serializable_items)];
 *
 *     memcpy(it, serializable_items, sizeof(serializable_items));
 *     gwy_deserialize_filter_items(it, G_N_ELEMENTS(it), group, "MyObject", error_list);
 *
 *     MyObject *myobject = GWY_MY_OBJECT(serializable);
 *     myobject->data = it[0].value.v_int32;
 *
 *     // The construction of more complex objects can fail due to a logical inconsitency. We would return FALSE
 *     // in such case and had to free anything in it[].
 *
 *     return TRUE;
 * }
 * ]|
 * </refsect2>
 **/

/**
 * GwySerializable:
 *
 * Formal type of serialisable objects.
 **/

/**
 * GwySerializableCType:
 * @GWY_SERIALIZABLE_CONSUMED: Reserved type value which does not appear in files and denotes already consumed items
 *                             internally.
 * @GWY_SERIALIZABLE_INT8: Denotes a character (8bit integer).
 * @GWY_SERIALIZABLE_INT8_ARRAY: Denotes a character (8bit integer) array.
 * @GWY_SERIALIZABLE_BOOLEAN: Denotes a one-byte boolean.
 * @GWY_SERIALIZABLE_INT16: Denotes a 16bit integer.
 * @GWY_SERIALIZABLE_INT16_ARRAY: Denotes an array of 16bit integers.
 * @GWY_SERIALIZABLE_INT32: Denotes a 32bit integer.
 * @GWY_SERIALIZABLE_INT32_ARRAY: Denotes an array of 32bit integers.
 * @GWY_SERIALIZABLE_INT64: Denotes a 64bit integer.
 * @GWY_SERIALIZABLE_INT64_ARRAY: Denotes an array of 64bit integers.
 * @GWY_SERIALIZABLE_FLOAT: Denotes a IEEE single (float).
 * @GWY_SERIALIZABLE_FLOAT_ARRAY: Denotes an array of IEEE singles (floats).
 * @GWY_SERIALIZABLE_DOUBLE: Denotes a IEEE double.
 * @GWY_SERIALIZABLE_DOUBLE_ARRAY: Denotes an array of IEEE doubles.
 * @GWY_SERIALIZABLE_STRING: Denotes a UTF-8-encoded C string.  If you need a raw sequence of bytes, use
 *                           %GWY_SERIALIZABLE_INT8_ARRAY instead.
 * @GWY_SERIALIZABLE_STRING_ARRAY: Denotes an array of UTF-8 encoded C strings.
 * @GWY_SERIALIZABLE_OBJECT: Denotes an object.
 * @GWY_SERIALIZABLE_OBJECT_ARRAY: Denotes an array of objects.
 * @GWY_SERIALIZABLE_BOXED: Denotes a serialisable boxed type.
 *
 * Type of serialisable value.
 *
 * The type is a single byte, i.e. a value smaller than 256.  It is equal to the character used in GWY files to denote
 * the corresponding type.
 *
 * Signed and usinged types are not distinguished here as their byte order is handled the same way.
 **/

/**
 * GwySerializableValue:
 * @v_boolean: The value as a #gboolean.
 * @v_int8: The value as a #gint8.
 * @v_uint8: The value as a #guint8.
 * @v_int16: The value as a #gint16.
 * @v_uint16: The value as a #guint16.
 * @v_int32: The value as a #gint32.
 * @v_uint32: The value as a #guint32.
 * @v_int64: The value as a #gint64.
 * @v_uint64: The value as a #guint64.
 * @v_float: The value as a #gfloat.
 * @v_double: The value as a #gdouble.
 * @v_string: The value as a #gchar pointer.
 * @v_ustring: The value as a #guchar pointer.
 * @v_object: The value as an object.
 * @v_boxed: The value as a boxed type.
 * @v_size: The value as a #gsize.
 * @v_type: The value as a #GType.
 * @v_int8_array: The value as an array of #gint8<!-- -->s.
 * @v_uint8_array: The value as an array of #guint8<!-- -->s.
 * @v_int16_array: The value as an array of #gint16<!-- -->s.
 * @v_uint16_array: The value as an array of #guint16<!-- -->s.
 * @v_int32_array: The value as an array of #gint32<!-- -->s.
 * @v_uint32_array: The value as an array of #guint32<!-- -->s.
 * @v_int64_array: The value as an array of #gint64<!-- -->s.
 * @v_uint64_array: The value as an array of #guint64<!-- -->s.
 * @v_float_array: The value as an array of #gfloat<!-- -->s.
 * @v_double_array: The value as an array of #gdouble<!-- -->s.
 * @v_string_array: The value as an array of #gchar pointers.
 * @v_ustring_array: The value as an array of #guchar pointers.
 * @v_object_array: The value as an array of objects.
 * @v_group: The value of an object or boxed component as its own item group.
 * @v_group_array: The value of an object or boxed component array as their own item groups.
 *
 * Representation of individual values of object serialisable data.
 *
 * See #GwySerializableCType for corresponding type specifiers.
 *
 * All string values must be valid UTF-8.  If you need raw byte sequences use arrays of #guint8.
 *
 * Signed and unsigned integer members are provided for convenience, the serialisation does not distinguish between
 * signed and unsigned integers.
 *
 * Value type @v_size has currently no use in implementations.
 *
 * Value types @v_group and @v_group_array are used for the intermediate representation during serialisation and
 * deserialisation. In serialisation, objects and boxed types are initially represented as @v_object or @v_boxed.
 * Subsequently, they are transformed to #GwySerializableGroup starting from the top-level object in the tree and
 * going down. In deserialisation, the transformations go in the reverse order. Classes implementing #GwySerializable
 * do not need to deal with the representation of contained objects as groups (aside from calling
 * gwy_serializable_group_itemize() at the end of their itemize()). They should only see objects, boxed types and
 * objects arrays represented as the actual types, not #GwySerializableGroup. The flag %GWY_SERIALIZABLE_IS_GROUP
 * is set when contained objects are itemised to groups.
 **/

/**
 * GwySerializableItem:
 * @name: Component name.
 * @array_size: Array size of the component if of an array type.  Unused otherwise but should be kept zero when not
 *              applicable.
 * @value: Item value, the interpreration depends on the @ctype member.
 * @ctype: Item type, one of the #GwySerializableCType enum.
 * @aux: Auxiliary information about the item type and valid values, mostly optional.
 * @flags: Auxiliary information about arrays and serialisation state.
 *
 * Information about one object component that is subject to serialisation or deserialisation.
 *
 * An item corresponds to ‘item’ in libgwyfile.
 **/

/**
 * GwySerializableAux:
 * @object_type: Expected object type for object-valued components.
 * @boxed_type: Boxed type for boxed-valued components (mandatory).
 * @pspec: Parameter specification used for atomic value range validation.
 *
 * Auxiliary information about a serialised component.
 *
 * It should be kept zero-filled when not used. Values @object_type and @pspec are optional and allow to delegate some
 * type and range checking to the deserialiser, which will reject object or values not conforming to the expected type
 * or range, and serializable_construct() can assume they have already been checked.
 *
 * Value @boxed_type is mandatory for boxed type components.
 **/

/**
 * GwySerializableInterface:
 * @itemize: <para>Creates itemised representation of the object data.  It can employ many helper functions like
 *           gwy_serializable_group_alloc_size(), gwy_serializable_group_itemize() and functions for adding items
 *           of various types. If the object possibly contains member objects it must either call
 *           gwy_serializable_group_itemize() at the end (recommended) or ensure the contained objects are
 *           itemised too.</para>
 * @done: <para>Frees all temporary data created by #GwySerializableInterface.itemize().  This method is optional but
 *        if it is defined it is guaranteed to be called after each #GwySerializableInterface.itemize().</para>
 * @construct: <para>Deserialises an object from array of flattened data items.  The first argument is the object of
 *             the final class (i.e. it may be a derived class), constructed with the default parameterless
 *             constructor.</para>
 *             <para>All strings, objects and arrays in the item list are newly allocated.  The method can (and,
 *             generally, should) take ownership by filling corresponding item values with %NULL, setting their
 *             @array_size to zero and setting the type to %GWY_SERIALIZABLE_CONSUMED.  The caller takes care of
 *             freeing them if they are not consumed by construct().</para>
 *             <para>This method must make any assumptions about the contents of @items.  Commonly, however, only
 *             items from a small predefined set are expected and then gwy_deserialize_filter_items() can be used to
 *             simplify the processing of the item list.</para>
 *             <para>It is possible to chain this method to the parent class and pass it the the item list or a part
 *             of it, if you are careful.</para>
 * @copy: <para>Creates a new object with all data identical to this object.  This method is expected to create
 *        a deep copy. Classes may provide other methods for shallow copies.</para>
 * @assign: <para>Makes all data of this object identical to the data of another object of the same class.
 *          Implementations may assume that the is-a relation is satisfied for the source object.  This method is
 *          expected to perform a deep copy.  Classes may provide other methods for shallow copies.</para>
 *
 * Interface implemented by serialisable objects.
 *
 * The object class must implement all the methods, except #GwySerializableInterface.done() which is optional.
 **/

/**
 * GwySerializeSizeType:
 * @GWY_SERIALIZE_SIZE_32BIT: The 32bit Gwyddion 2 format.
 * @GWY_SERIALIZE_SIZE_64BIT: The 64bit Gwyddion 3 format.
 *
 * Type of size representation in GWY files.
 **/

/**
 * GwySerializableGroup:
 *
 * Representation of one object or boxed type during serialisation and deserialisation.
 *
 * The groups reflect the tree-like structure of object members. They can contain nested groups inside for nested
 * object and boxed type components. A group corresponds to ‘object’ in libgwyfile.
 *
 * The structure has no public fields. Use functions such as gwy_serializable_group_append_double() to add more items
 * during serialisation. Use gwy_deserialize_filter_items() to sort out items during deserialisation.
 **/

/**
 * GWY_DESERIALIZE_ERROR:
 *
 * Error domain for deserialisation.
 *
 * Errors in this domain will be from the #GwyDeserializeError enumeration. See #GError for information on error
 * domains.
 **/

/**
 * GwyDeserializeError:
 * @GWY_DESERIALIZE_ERROR_TRUNCATED: Data ends in the middle of some item or there is not enough data to represent
 *                                   given object.  This error is fatal.
 * @GWY_DESERIALIZE_ERROR_PADDING: There is too much data to represent given object.  This error is non-fatal: the
 *                                 extra data is just ignored.
 * @GWY_DESERIALIZE_ERROR_SIZE_T: Size is not representable on this system. This can occur on legacy 32bit systems,
 *                                however, they are incapable of holding such large data in core anyway.  This error
 *                                is fatal.
 * @GWY_DESERIALIZE_ERROR_OBJECT: Representation of an object of unknown or deserialisation-incapable type was found.
 *                                This error is fatal.
 * @GWY_DESERIALIZE_ERROR_ITEM: Unexpected item was encountered.  This error is non-fatal: such item is just ignored.
 * @GWY_DESERIALIZE_ERROR_REPLACED: Invalid information (for instance an out-of-bounds value) was encountered and
 *                                  replaced (for instance by a default value). This error is non-fatal.
 *                                  Implementations should use it when they encounter invalid information but are able
 *                                  to proceed.
 * @GWY_DESERIALIZE_ERROR_DATA: Uknown data type (#GwySerializableCType) was encountered or the object representation
 *                              is internally inconsistent.  This error is fatal.
 * @GWY_DESERIALIZE_ERROR_UTF8: Serialised string is not valid UTF-8. This error is fatal.
 * @GWY_DESERIALIZE_ERROR_INVALID: Object representation is logicaly inconsistent or otherwise invalid. Reserved for
 *                                 classes to indicate data errors on levels higher than physical representation. This
 *                                 error is fatal.
 * @GWY_DESERIALIZE_ERROR_FATAL_MASK: Distinguishes fatal and non-fatal errors (fatal give non-zero value when masked
 *                                    with this mask).
 *
 * Error codes returned by deserialisation.
 *
 * In the error code descriptions, fatal error means aborting of deserialisation of the object that is just being
 * unpacked.  Non-fatal errors are competely recoverable.  If the deserialisation of a contained object is aborted,
 * the unpacking of the container object still continues. Therefore, fatal errors are truly fatal only if they occur
 * for the top-level object.
 **/

// FIXME: TRANSFORMED is not implemented.
/**
 * GwySerializableFlags:
 * GWY_SERIALIZABLE_BIG_DATA: Should be used to mark large data, often the primary object data array, but sometimes
 *                            also secondary data. Conceptually, if something is BIG DATA, it would be a dataset
 *                            (as opposed to an attribute) in HDF5 or a separate binary chunk (as opposed to inline) in
 *                            zipped XML representation. It does not make a difference in GWY files.
 * GWY_SERIALIZABLE_TRANSFORMED: Can be used to mark large non-native data pieces the object needs to create
 *                               specifically for serialisation. Serialisation then may try to conserve resources by
 *                               asking the object to create these pieces only as needed. Currently not implemented.
 * GWY_SERIALIZABLE_IS_GROUP: Flag set internally to mark that an object or record item is in the intermediate
 *                            representation. Meaning, GwySerializableItem.value.v_group points to an item group,
 *                            as opposed to v_object or v_boxed pointing to an object or boxed type. Only needed by
 *                            serialisers and deserialisers.
 *
 * Flags denoting extra properties of items in intermediate serialisation representation.
 **/

/* vim: set cin columns=120 tw=118 et ts=4 sw=4 cino=>1s,e0,n0,f0,{0,}0,^0,\:1s,=0,g1s,h0,t0,+1s,c3,(0,u0 : */
