/*
 *  $Id: null-store.c 27952 2025-05-09 16:41:38Z yeti-dn $
 *  Copyright (C) 2005-2025 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.
 */

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

#include "libgwyddion/macros.h"

#include "libgwyui/null-store.h"

struct _GwyNullStorePrivate {
    gpointer model;
    GDestroyNotify model_destroy;

    guint n;
    gint stamp;
};

static void              finalize       (GObject *object);
static void              tree_model_init(GtkTreeModelIface *iface);
static GtkTreeModelFlags get_flags      (GtkTreeModel *model);
static gint              get_n_columns  (GtkTreeModel *model);
static GType             get_column_type(GtkTreeModel *model,
                                         gint column);
static gboolean          get_tree_iter  (GtkTreeModel *model,
                                         GtkTreeIter *iter,
                                         GtkTreePath *path);
static GtkTreePath*      get_path       (GtkTreeModel *model,
                                         GtkTreeIter *iter);
static void              get_value      (GtkTreeModel *model,
                                         GtkTreeIter *iter,
                                         gint column,
                                         GValue *value);
static gboolean          iter_next      (GtkTreeModel *model,
                                         GtkTreeIter *iter);
static gboolean          iter_children  (GtkTreeModel *model,
                                         GtkTreeIter *iter,
                                         GtkTreeIter *parent);
static gboolean          iter_has_child (GtkTreeModel *model,
                                         GtkTreeIter *iter);
static gint              iter_n_children(GtkTreeModel *model,
                                         GtkTreeIter *iter);
static gboolean          iter_nth_child (GtkTreeModel *model,
                                         GtkTreeIter *iter,
                                         GtkTreeIter *parent,
                                         gint n);
static gboolean          iter_parent    (GtkTreeModel *model,
                                         GtkTreeIter *iter,
                                         GtkTreeIter *child);

static GObjectClass *parent_class = NULL;

G_DEFINE_TYPE_EXTENDED(GwyNullStore, gwy_null_store, G_TYPE_OBJECT, 0,
                       G_ADD_PRIVATE(GwyNullStore)
                       G_IMPLEMENT_INTERFACE(GTK_TYPE_TREE_MODEL, tree_model_init))

static void
gwy_null_store_class_init(GwyNullStoreClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);

    parent_class = gwy_null_store_parent_class;

    gobject_class->finalize = finalize;
}

static void
tree_model_init(GtkTreeModelIface *iface)
{
    iface->get_flags = get_flags;
    iface->get_n_columns = get_n_columns;
    iface->get_column_type = get_column_type;
    iface->get_iter = get_tree_iter;
    iface->get_path = get_path;
    iface->get_value = get_value;
    iface->iter_next = iter_next;
    iface->iter_children = iter_children;
    iface->iter_has_child = iter_has_child;
    iface->iter_n_children = iter_n_children;
    iface->iter_nth_child = iter_nth_child;
    iface->iter_parent = iter_parent;
}

static void
gwy_null_store_init(GwyNullStore *store)
{
    GwyNullStorePrivate *priv;

    store->priv = priv = gwy_null_store_get_instance_private(store);
    store->priv->stamp = g_random_int();
}

static void
finalize(GObject *object)
{
    GwyNullStore *store = GWY_NULL_STORE(object);
    GwyNullStorePrivate *priv = store->priv;
    GDestroyNotify d = priv->model_destroy;

    /* If the model is an object, it would be better to do this in dispose(). But the Null store is meant more for
     * wrapping things like static C arrays. */
    if (d) {
        priv->model_destroy = NULL;
        d(priv->model);
    }

    parent_class->finalize(object);
}

static GtkTreeModelFlags
get_flags(G_GNUC_UNUSED GtkTreeModel *model)
{
    return GTK_TREE_MODEL_ITERS_PERSIST | GTK_TREE_MODEL_LIST_ONLY;
}

static gint
get_n_columns(G_GNUC_UNUSED GtkTreeModel *model)
{
    return 1;
}

static GType
get_column_type(G_GNUC_UNUSED GtkTreeModel *model,
                gint column)
{
    g_return_val_if_fail(column == 0, 0);

    return G_TYPE_UINT;
}

static gboolean
get_tree_iter(GtkTreeModel *model,
              GtkTreeIter *iter,
              GtkTreePath *path)
{
    GwyNullStore *store = GWY_NULL_STORE(model);
    GwyNullStorePrivate *priv = store->priv;

    g_return_val_if_fail(gtk_tree_path_get_depth(path) > 0, FALSE);

    guint i = gtk_tree_path_get_indices(path)[0];
    if (i >= priv->n)
        return FALSE;

    /* GwyNullStore has of course presistent iters.
     *
     * @stamp is set upon initialization.
     * @user_data is the row index.
     * @user_data2 in unused.
     */
    iter->stamp = priv->stamp;
    iter->user_data = GUINT_TO_POINTER(i);

    return TRUE;
}

static GtkTreePath*
get_path(G_GNUC_UNUSED GtkTreeModel *model,
         GtkTreeIter *iter)
{
    GtkTreePath *path = gtk_tree_path_new();
    gtk_tree_path_append_index(path, GPOINTER_TO_UINT(iter->user_data));

    return path;
}

static void
get_value(GtkTreeModel *model,
          GtkTreeIter *iter,
          gint column,
          GValue *value)
{
    GwyNullStore *store = GWY_NULL_STORE(model);
    guint i = GPOINTER_TO_UINT(iter->user_data);

    g_return_if_fail(column == 0);
    g_return_if_fail(i < store->priv->n);

    g_value_init(value, G_TYPE_UINT);
    g_value_set_uint(value, i);
}

static gboolean
iter_next(GtkTreeModel *model,
          GtkTreeIter *iter)
{
    GwyNullStore *store = GWY_NULL_STORE(model);
    guint i = GPOINTER_TO_UINT(iter->user_data) + 1;

    iter->user_data = GUINT_TO_POINTER(i);

    return i < store->priv->n;
}

static gboolean
iter_children(GtkTreeModel *model,
              GtkTreeIter *iter,
              GtkTreeIter *parent)
{
    GwyNullStore *store = GWY_NULL_STORE(model);
    GwyNullStorePrivate *priv = store->priv;

    if (parent || !priv->n)
        return FALSE;

    iter->stamp = priv->stamp;
    iter->user_data = GUINT_TO_POINTER(0);

    return TRUE;
}

static gboolean
iter_has_child(G_GNUC_UNUSED GtkTreeModel *model,
               G_GNUC_UNUSED GtkTreeIter *iter)
{
    return FALSE;
}

static gint
iter_n_children(GtkTreeModel *model,
                GtkTreeIter *iter)
{
    return iter ? 0 : GWY_NULL_STORE(model)->priv->n;
}

static gboolean
iter_nth_child(GtkTreeModel *model,
               GtkTreeIter *iter,
               GtkTreeIter *parent,
               gint n)
{
    GwyNullStore *store = GWY_NULL_STORE(model);
    GwyNullStorePrivate *priv = store->priv;

    if (parent || (guint)n >= priv->n)
        return FALSE;

    iter->stamp = priv->stamp;
    iter->user_data = GUINT_TO_POINTER((guint)n);
    iter->user_data2 = NULL;

    return TRUE;
}

static gboolean
iter_parent(G_GNUC_UNUSED GtkTreeModel *model,
            G_GNUC_UNUSED GtkTreeIter *iter,
            G_GNUC_UNUSED GtkTreeIter *child)
{
    return FALSE;
}

/**
 * gwy_null_store_new:
 * @n: The initial number of rows.
 *
 * Creates a new #GtkTreeModel wrapper around nothing.
 *
 * Returns: The newly created null store.
 **/
GwyNullStore*
gwy_null_store_new(guint n)
{
    GwyNullStore *store = g_object_new(GWY_TYPE_NULL_STORE, NULL);
    store->priv->n = n;
    return store;
}

/**
 * gwy_null_store_get_n_rows:
 * @store: A null store.
 *
 * Gets the number of imaginary rows in a null store.
 *
 * This is a convenience function, the same information can be obtained with gtk_tree_model_iter_n_children().
 *
 * Returns: The number of rows.
 **/
guint
gwy_null_store_get_n_rows(GwyNullStore *store)
{
    g_return_val_if_fail(GWY_IS_NULL_STORE(store), 0);

    return store->priv->n;
}

/**
 * gwy_null_store_set_n_rows:
 * @store: A null store.
 * @n: The new number of rows.
 *
 * Sets the number of imaginary rows in a null store.
 *
 * If the new number of rows is larger than the current one, rows will be sequentially and virtually appended to the
 * end of the store until the requested number of rows is reached.
 *
 * Similarly, if the new number of rows is smaller then the current one, rows will be sequentially and virtually
 * deleted from the end of the store until the requested number of rows is reached.
 *
 * Note for radical changes it is usually more useful to disconnect the model from its view(s), change the number of
 * rows, and then reconnect.
 **/
void
gwy_null_store_set_n_rows(GwyNullStore *store,
                          guint n)
{
    g_return_if_fail(GWY_IS_NULL_STORE(store));

    GwyNullStorePrivate *priv = store->priv;
    if (priv->n == n)
        return;

    GtkTreeModel *model = GTK_TREE_MODEL(store);
    GtkTreePath *path = gtk_tree_path_new();
    GtkTreeIter iter;

    if (priv->n > n) {
        while (priv->n > n) {
            priv->n--;
            gtk_tree_path_append_index(path, priv->n);
            gtk_tree_model_row_deleted(model, path);
            gtk_tree_path_up(path);
        }
    }
    else {
        iter.stamp = priv->stamp;
        while (priv->n < n) {
            iter.user_data = GUINT_TO_POINTER(priv->n);
            gtk_tree_path_append_index(path, priv->n);
            priv->n++;
            gtk_tree_model_row_inserted(model, path, &iter);
            gtk_tree_path_up(path);
        }
    }

    gtk_tree_path_free(path);
}

/**
 * gwy_null_store_get_model:
 * @store: A null store.
 *
 * Gets the model pointer of a null store.
 *
 * Returns: The pointer set with gwy_null_store_set_model().
 **/
gpointer
gwy_null_store_get_model(GwyNullStore *store)
{
    g_return_val_if_fail(GWY_IS_NULL_STORE(store), NULL);
    return store->priv->model;
}

/**
 * gwy_null_store_set_model:
 * @store: A null store.
 * @model: Model pointer.
 * @destroy: Function to call on @model when it is replaced or the store is destroyed.
 *
 * Sets the model pointer of a null store.
 *
 * While the virtual integers in #GwyNullStore can be used directly, a null store typically serves as an adaptor for
 * array-like structures and its rows are used as indices to these structures.  This helper method provides means to
 * attach such a structure to a null store in the common case.
 *
 * The store itself does not interpret nor access the attached data by any means.  No signals are emitted in response
 * to the model pointer change either, particularly because it is expected to be set only once upon creation (null
 * stores are cheap).
 *
 * You are free to keep the model pointer at %NULL if these functions do not suit your needs.
 **/
void
gwy_null_store_set_model(GwyNullStore *store,
                         gpointer model,
                         GDestroyNotify destroy)
{
    g_return_if_fail(GWY_IS_NULL_STORE(store));

    GwyNullStorePrivate *priv = store->priv;
    GDestroyNotify d = priv->model_destroy;

    if (d) {
        priv->model_destroy = NULL;
        d(priv->model);
    }

    priv->model = model;
    priv->model_destroy = destroy;
}

/**
 * gwy_null_store_row_changed:
 * @store: A null store.
 * @i: A row to emit "row-changed" on.
 *
 * Emits "GtkTreeModel::row-changed" signal on a null store.
 *
 * This is a convenience method, with a bit more work the same effect can be achieved with
 * gtk_tree_model_row_changed().
 **/
void
gwy_null_store_row_changed(GwyNullStore *store,
                           guint i)
{
    g_return_if_fail(GWY_IS_NULL_STORE(store));

    GwyNullStorePrivate *priv = store->priv;
    g_return_if_fail(i < priv->n);

    GtkTreePath *path = gtk_tree_path_new();
    GtkTreeIter iter = {
        .stamp = priv->stamp,
        .user_data = GUINT_TO_POINTER(i),
    };

    gtk_tree_path_append_index(path, i);
    gtk_tree_model_row_changed(GTK_TREE_MODEL(store), path, &iter);
    gtk_tree_path_free(path);
}

/**
 * gwy_null_store_rows_changed:
 * @store: A null store.
 * @ifrom: The first row to emit "row-changed" on.
 * @ito: The last row to emit "row-changed" on (inclusive).
 *
 * Emits "GtkTreeModel::row-changed" signal on a block of rows in a null store.
 *
 * This is a convenience method, with a bit more work the same effect can be achieved with
 * gtk_tree_model_row_changed().
 *
 * It is possible to pass @ifrom larger than @ito. The signal is then emitted for the same rows, but in the opposite
 * order.
 **/
void
gwy_null_store_rows_changed(GwyNullStore *store,
                            guint ifrom,
                            guint ito)
{
    g_return_if_fail(GWY_IS_NULL_STORE(store));

    GwyNullStorePrivate *priv = store->priv;
    g_return_if_fail(ifrom < priv->n);
    g_return_if_fail(ito < priv->n);

    GtkTreeIter iter = {
        .stamp = priv->stamp,
    };

    GtkTreePath *path = gtk_tree_path_new();
    gtk_tree_path_append_index(path, ifrom);
    if (ifrom <= ito) {
        for (guint i = ifrom; i <= ito; i++) {
            iter.user_data = GUINT_TO_POINTER(i);
            gtk_tree_model_row_changed(GTK_TREE_MODEL(store), path, &iter);
            if (i < ito)
                gtk_tree_path_next(path);
        }
    }
    else {
        for (guint i = ifrom; i >= ito; i--) {
            iter.user_data = GUINT_TO_POINTER(i);
            gtk_tree_model_row_changed(GTK_TREE_MODEL(store), path, &iter);
            if (i < ito)
                gtk_tree_path_prev(path);
        }
    }
    gtk_tree_path_free(path);
}

/**
 * gwy_null_store_iter_is_valid:
 * @store: A null store.
 * @iter: A #GtkTreeIter.
 *
 * Checks if the given iter is a valid iter for this null store.
 *
 * Returns: %TRUE if the iter is valid, %FALSE if the iter is invalid.
 **/
gboolean
gwy_null_store_iter_is_valid(GwyNullStore *store,
                             GtkTreeIter *iter)
{
    g_return_val_if_fail(GWY_IS_NULL_STORE(store), FALSE);

    GwyNullStorePrivate *priv = store->priv;
    if (!iter || iter->stamp != priv->stamp || GPOINTER_TO_UINT(iter->user_data) >= priv->n)
        return FALSE;

    return TRUE;
}

/**
 * SECTION:null-store
 * @title: GwyNullStore
 * @short_description: GtkTreeModel wrapper around nothing
 * @see_also: #GwyInventoryStore -- #GtkTreeModel wrapper around #GwyInventory
 *
 * #GwyNullStore is a very simple #GtkTreeModel which pretents to have a single column of type %G_TYPE_UINT. Values in
 * the column are equal to row numbers (counted from 0). In reality the column is purely virtual and the store always
 * takes up only a small constant amount of memory.
 *
 * The purpose of #GwyNullStore is to provide a low-overhead #GtkTreeModel interface for array-like (and other
 * indexed) data structures.
 *
 * A new null store can be created with gwy_null_store_new(), then number of virtual rows can be controlled with
 * gwy_null_store_set_n_rows(). For convenience, methods to emit "row-changed" signal on a row or a range of rows by
 * their indices are provided: gwy_null_store_row_changed() and gwy_null_store_rows_changed().
 *
 * Since null stores often serve as wrappers around other data structures, convenience methods to attach and obtain
 * such a data are provided: gwy_null_store_set_model(), gwy_null_store_get_model().
 *
 * A simple example to create a multiplication table with null storage:
 * <informalexample><programlisting>
 * GtkWidget *treeview;
 * GtkTreeViewColumn *column;
 * GtkCellRenderer *renderer;
 * GwyNullStore *store;
 * gint i;
 *
 * store = gwy_null_store_new(10);
 * treeview = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
 * gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(treeview), FALSE);
 *
 * column = gtk_tree_view_column_new();
 * for (i = 1; i <= 10; i++) {
 *     renderer = gtk_cell_renderer_text_new();
 *     g_object_set(renderer, "xalign", 1.0, "width-chars", 4, NULL);
 *     gtk_tree_view_column_pack_start(column, renderer, TRUE);
 *     gtk_tree_view_column_set_cell_data_func(column, renderer,
 *                                             multiply, GINT_TO_POINTER(i),
 *                                             NULL);
 * }
 * gtk_tree_view_append_column(GTK_TREE_VIEW(treeview), column);
 * </programlisting></informalexample>
 *
 * The cell data function multiply<!-- -->() just multiplies the column number with the number of (virtual) null store
 * row:
 * <informalexample><programlisting>
 * static void
 * multiply(GtkTreeViewColumn *column,
 *          GtkCellRenderer *renderer,
 *          GtkTreeModel *model,
 *          GtkTreeIter *iter,
 *          gpointer data)
 * {
 *     gchar buf[20];
 *     gint i;
 *
 *     gtk_tree_model_get(model, iter, 0, &i, -1);
 *     g_snprintf(buf, sizeof(buf), "%d", (i + 1)*GPOINTER_TO_INT(data));
 *     g_object_set(renderer, "text", buf, NULL);
 * }
 * </programlisting></informalexample>
 *
 * To extend the multiplication table to 20 rows, one only needs
 * <informalexample><programlisting>
 * gwy_null_store_set_n_rows(store, 20);
 * </programlisting></informalexample>
 **/

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