/*
 *  $Id: string-list.c 29725 2026-04-03 07:03:21Z yeti-dn $
 *  Copyright (C) 2004-2024 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@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.
 */

/* FIXME: we should use a better backend and also add more methods, for the original reason this was implemented, they
 * were not necessary.
 *
 * Generally, this is a stupid data structure. Use some general array thingy which can be easily wrapped by a
 * GtkTreeModel. There is a GListModel, but almost nothing in GTK+ eats those. So we probably still want some
 * non-confusing GArray-based model or something. */

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

#include "libgwyddion/macros.h"
#include "libgwyddion/math.h"
#include "libgwyddion/string-list.h"
#include "libgwyddion/serializable-utils.h"

#define TYPE_NAME "GwyStringList"

enum {
    SGNL_VALUE_CHANGED,
    NUM_SIGNALS
};

enum {
    ITEM_STRINGS,
    NUM_ITEMS
};

struct _GwyStringListPrivate {
    GPtrArray *strings;
};

static void             finalize              (GObject *object);
static void             serializable_init     (GwySerializableInterface *iface);
static void             serializable_itemize  (GwySerializable *serializable,
                                               GwySerializableGroup *group);
static gboolean         serializable_construct(GwySerializable *serializable,
                                               GwySerializableGroup *group,
                                               GwyErrorList **error_list);
static GwySerializable* serializable_copy     (GwySerializable *serializable);
static void             serializable_assign   (GwySerializable *destination,
                                               GwySerializable *source);

static guint signals[NUM_SIGNALS];
static GObjectClass *parent_class = NULL;

static const GwySerializableItem serializable_items[NUM_ITEMS] = {
    { .name = "strings", .ctype = GWY_SERIALIZABLE_STRING_ARRAY,
                         .flags = GWY_SERIALIZABLE_BIG_DATA      },
};

G_DEFINE_TYPE_WITH_CODE(GwyStringList, gwy_string_list, G_TYPE_OBJECT,
                        G_ADD_PRIVATE(GwyStringList)
                        G_IMPLEMENT_INTERFACE(GWY_TYPE_SERIALIZABLE, serializable_init))

static void
serializable_init(GwySerializableInterface *iface)
{
    iface->itemize   = serializable_itemize;
    iface->construct = serializable_construct;
    iface->copy      = serializable_copy;
    iface->assign    = serializable_assign;
}

static void
gwy_string_list_class_init(GwyStringListClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
    GType type = G_TYPE_FROM_CLASS(klass);

    parent_class = gwy_string_list_parent_class;

    gobject_class->finalize = finalize;

    /**
     * GwyStringList::value-changed:
     * @gwystrlist: The #GwyStringList which received the signal.
     *
     * The ::value-changed signal is emitted whenever a string list changes.
     */
    signals[SGNL_VALUE_CHANGED] = g_signal_new("value-changed", type,
                                               G_SIGNAL_RUN_FIRST,
                                               G_STRUCT_OFFSET(GwyStringListClass, value_changed),
                                               NULL, NULL,
                                               g_cclosure_marshal_VOID__VOID,
                                               G_TYPE_NONE, 0);
    g_signal_set_va_marshaller(signals[SGNL_VALUE_CHANGED], type, g_cclosure_marshal_VOID__VOIDv);
}

static void
gwy_string_list_init(GwyStringList *strlist)
{
    GwyStringListPrivate *priv;

    priv = strlist->priv = gwy_string_list_get_instance_private(strlist);
    priv->strings = g_ptr_array_new();
    g_ptr_array_set_free_func(priv->strings, g_free);
}

static void
finalize(GObject *object)
{
    GwyStringList *strlist = (GwyStringList*)object;

    g_ptr_array_free(strlist->priv->strings, TRUE);

    G_OBJECT_CLASS(parent_class)->finalize(object);
}

/**
 * gwy_string_list_new:
 *
 * Creates a new string list.
 *
 * Returns: A new empty string list.
 **/
GwyStringList*
gwy_string_list_new(void)
{
    return g_object_new(GWY_TYPE_STRING_LIST, NULL);
}

/**
 * gwy_string_list_append:
 * @strlist: A string list.
 * @string: A string to add.
 *
 * Appends a string to the end of a string list.
 **/
void
gwy_string_list_append(GwyStringList *strlist,
                       const gchar *string)
{
    g_return_if_fail(GWY_IS_STRING_LIST(strlist));
    g_return_if_fail(string);

    g_ptr_array_add(strlist->priv->strings, g_strdup(string));
    g_signal_emit(strlist, signals[SGNL_VALUE_CHANGED], 0);
}

/**
 * gwy_string_list_append_take:
 * @strlist: A string list.
 * @string: A string to add.
 *
 * Appends a string to the end of a string list, taking ownership of the string.
 *
 * The string must be dynamically allocated and will be eventually freed with g_free().
 **/
void
gwy_string_list_append_take(GwyStringList *strlist,
                            gchar *string)
{
    g_return_if_fail(GWY_IS_STRING_LIST(strlist));
    g_return_if_fail(string);

    g_ptr_array_add(strlist->priv->strings, string);
    g_signal_emit(strlist, signals[SGNL_VALUE_CHANGED], 0);
}

/**
 * gwy_string_list_get_length:
 * @strlist: A string list.
 *
 * Gets the number of strings in a string list.
 *
 * Returns: The number of strings in @strlist.
 **/
guint
gwy_string_list_get_length(GwyStringList *strlist)
{
    g_return_val_if_fail(GWY_IS_STRING_LIST(strlist), 0);
    return strlist->priv->strings->len;
}

/**
 * gwy_string_list_get:
 * @strlist: A string list.
 * @i: The position of string to get.
 *
 * Gets a string from a string list by position.
 *
 * Returns: The string, owned by @strlist.  It is valid only until @strlist changes.
 **/
const gchar*
gwy_string_list_get(GwyStringList *strlist,
                    guint i)
{
    g_return_val_if_fail(GWY_IS_STRING_LIST(strlist), NULL);

    GPtrArray *strings = strlist->priv->strings;
    g_return_val_if_fail(i < strings->len, NULL);
    return g_ptr_array_index(strings, i);
}

/**
 * gwy_string_list_clear:
 * @strlist: A string list.
 *
 * Clears the contents of a string list, removing all strings.
 **/
void
gwy_string_list_clear(GwyStringList *strlist)
{
    g_return_if_fail(GWY_IS_STRING_LIST(strlist));

    GPtrArray *strings = strlist->priv->strings;
    if (!strings->len)
        return;
    g_ptr_array_set_size(strings, 0);
    g_signal_emit(strlist, signals[SGNL_VALUE_CHANGED], 0);
}

static void
serializable_itemize(GwySerializable *serializable, GwySerializableGroup *group)
{
    GwyStringListPrivate *priv = GWY_STRING_LIST(serializable)->priv;

    gwy_serializable_group_append_string_array(group, serializable_items + ITEM_STRINGS,
                                               (gchar**)priv->strings->pdata, priv->strings->len);
}

static gboolean
serializable_construct(GwySerializable *serializable, GwySerializableGroup *group, GwyErrorList **error_list)
{
    GwySerializableItem its[NUM_ITEMS], *it;
    gwy_assign(its, serializable_items, NUM_ITEMS);
    gwy_deserialize_filter_items(its, NUM_ITEMS, group, TYPE_NAME, error_list);

    GwyStringListPrivate *priv = GWY_STRING_LIST(serializable)->priv;

    /* We could create a new GPtrArray which also takes the container array, but we already have one we would have to
     * free now. So, free the container array.
     *
     * The default item content is an empty array so getting nothing from gwy_deserialize_filter_items() is OK. */
    it = its + ITEM_STRINGS;
    if (it->array_size) {
        g_ptr_array_set_size(priv->strings, it->array_size);
        gchar **strings = (gchar**)&g_ptr_array_index(priv->strings, 0);
        gwy_assign(strings, it->value.v_string_array, it->array_size);
        g_free(it->value.v_string_array);
    }

    return TRUE;
}

static void
deep_copy_strings(GwyStringList *source, GwyStringList *destination)
{
    GPtrArray *strsrc = source->priv->strings, *strdest = destination->priv->strings;
    guint n = strsrc->len;
    /* Make sure we do not leak any old strings. */
    g_ptr_array_set_size(strdest, 0);
    g_ptr_array_set_size(strdest, n);
    for (guint i = 0; i < n; i++)
        g_ptr_array_index(strdest, i) = g_strdup(g_ptr_array_index(strsrc, i));
}

static GwySerializable*
serializable_copy(GwySerializable *serializable)
{
    GwyStringList *copy = gwy_string_list_new();
    deep_copy_strings(GWY_STRING_LIST(serializable), GWY_STRING_LIST(copy));
    return GWY_SERIALIZABLE(copy);
}

static void
serializable_assign(GwySerializable *destination, GwySerializable *source)
{
    deep_copy_strings(GWY_STRING_LIST(source), GWY_STRING_LIST(destination));
    g_signal_emit(destination, signals[SGNL_VALUE_CHANGED], 0);
}

/**
 * gwy_string_list_copy:
 * @strlist: A string list to duplicate.
 *
 * Create a new string list as a copy of an existing one.
 *
 * This function is a convenience gwy_serializable_copy() wrapper.
 *
 * Returns: (transfer full):
 *          A copy of the string list.
 **/
GwyStringList*
gwy_string_list_copy(GwyStringList *strlist)
{
    /* Try to return a valid object even on utter failure. Returning NULL probably would crash something soon. */
    if (!GWY_IS_STRING_LIST(strlist)) {
        g_assert(GWY_IS_STRING_LIST(strlist));
        return g_object_new(GWY_TYPE_STRING_LIST, NULL);
    }
    return GWY_STRING_LIST(gwy_serializable_copy(GWY_SERIALIZABLE(strlist)));
}

/**
 * gwy_string_list_assign:
 * @destination: Target string list.
 * @source: Source string list.
 *
 * Makes one string list equal to another.
 *
 * This function is a convenience gwy_serializable_assign() wrapper.
 **/
void
gwy_string_list_assign(GwyStringList *destination, GwyStringList *source)
{
    g_return_if_fail(GWY_IS_STRING_LIST(destination));
    g_return_if_fail(GWY_IS_STRING_LIST(source));
    if (destination != source)
        gwy_serializable_assign(GWY_SERIALIZABLE(destination), GWY_SERIALIZABLE(source));
}

/**
 * SECTION: string-list
 * @title: GwyStringList
 * @short_description: A string list object wrapper
 *
 * #GwyStringList object represents a reference-counted, serializable list of strings.
 **/

/* 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 : */
