/*
 *  $Id: selection-path.c 28437 2025-08-24 15:57:54Z yeti-dn $
 *  Copyright (C) 2024-2025 David Necas (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 "libgwyddion/macros.h"
#include "libgwyddion/math.h"
#include "libgwyddion/serialize.h"
#include "libgwyddion/serializable-utils.h"
#include "libgwyddion/selection-path.h"

#define TYPE_NAME "GwySelectionPath"

enum {
    OBJECT_SIZE = 2
};

enum {
    PROP_0,
    PROP_SLACKNESS,
    PROP_CLOSED,
    NUM_PROPERTIES,
};

enum {
    ITEM_SLACKNESS,
    ITEM_CLOSED,
    NUM_ITEMS
};

struct _GwySelectionPathPrivate {
    gdouble slackness;
    gboolean closed;
};

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 void             set_property          (GObject *object,
                                               guint prop_id,
                                               const GValue *value,
                                               GParamSpec *pspec);
static void             get_property          (GObject *object,
                                               guint prop_id,
                                               GValue *value,
                                               GParamSpec *pspec);
static void             crop                  (GwySelection *selection,
                                               gdouble xmin,
                                               gdouble ymin,
                                               gdouble xmax,
                                               gdouble ymax);
static void             move                  (GwySelection *selection,
                                               gdouble vx,
                                               gdouble vy);

static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GObjectClass *parent_class = NULL;
static GwySerializableInterface *serializable_parent_iface = NULL;

static GwySerializableItem serializable_items[NUM_ITEMS] = {
    { .name = "slackness", .ctype = GWY_SERIALIZABLE_DOUBLE   },
    { .name = "closed",    .ctype = GWY_SERIALIZABLE_BOOLEAN, },
};

G_DEFINE_TYPE_WITH_CODE(GwySelectionPath, gwy_selection_path, GWY_TYPE_SELECTION,
                        G_ADD_PRIVATE(GwySelectionPath)
                        G_IMPLEMENT_INTERFACE(GWY_TYPE_SERIALIZABLE, serializable_init))

static void
define_properties(void)
{
    if (properties[PROP_SLACKNESS])
        return;

    properties[PROP_SLACKNESS] = g_param_spec_double("slackness", NULL,
                                                     "Slackness parameter of the spline curve",
                                                     0.0, G_SQRT2, 1.0/G_SQRT2, GWY_GPARAM_RWE);

    properties[PROP_CLOSED] = g_param_spec_boolean("closed", NULL,
                                                   "Whether the curve is closed, as opposed to open-ended.",
                                                   FALSE, GWY_GPARAM_RWE);
}

static void
gwy_selection_path_class_init(GwySelectionPathClass *klass)
{
    GwySelectionClass *sel_class = GWY_SELECTION_CLASS(klass);
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);

    parent_class = gwy_selection_path_parent_class;

    gobject_class->set_property = set_property;
    gobject_class->get_property = get_property;

    sel_class->object_size = OBJECT_SIZE;
    sel_class->crop = crop;
    sel_class->move = move;

    define_properties();
    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
}

static void
serializable_init(GwySerializableInterface *iface)
{
    serializable_parent_iface = g_type_interface_peek_parent(iface);

    iface->itemize   = serializable_itemize;
    iface->construct = serializable_construct;
    iface->copy      = serializable_copy;
    iface->assign    = serializable_assign;

    define_properties();
    serializable_items[ITEM_SLACKNESS].aux.pspec = properties[PROP_SLACKNESS];
    serializable_items[ITEM_CLOSED].aux.pspec = properties[PROP_CLOSED];
    gwy_fill_serializable_defaults_pspec(serializable_items, NUM_ITEMS, FALSE);
}

static void
gwy_selection_path_init(GwySelectionPath *selection)
{
    GwySelectionPathPrivate *priv;

    priv = selection->priv = gwy_selection_path_get_instance_private(selection);
    priv->slackness = 1.0/G_SQRT2;
    priv->closed = FALSE;
}

static void
set_property(GObject *object,
             guint prop_id,
             const GValue *value,
             GParamSpec *pspec)
{
    GwySelectionPath *selection = GWY_SELECTION_PATH(object);

    switch (prop_id) {
        case PROP_SLACKNESS:
        gwy_selection_path_set_slackness(selection, g_value_get_double(value));
        break;

        case PROP_CLOSED:
        gwy_selection_path_set_closed(selection, g_value_get_boolean(value));
        break;

        default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

static void
get_property(GObject *object,
             guint prop_id,
             GValue *value,
             GParamSpec *pspec)
{
    GwySelectionPathPrivate *priv = GWY_SELECTION_PATH(object)->priv;

    switch (prop_id) {
        case PROP_SLACKNESS:
        g_value_set_double(value, priv->slackness);
        break;

        case PROP_CLOSED:
        g_value_set_boolean(value, priv->closed);
        break;

        default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
        break;
    }
}

static void
crop(GwySelection *selection, gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax)
{
    guint n = gwy_selection_get_n_objects(selection);
    gdouble *data = g_memdup(gwy_selection_get_data_array(selection), n*OBJECT_SIZE);

    for (guint i = 0; i < n; i++) {
        data[OBJECT_SIZE*i + 0] = fmax(fmin(data[OBJECT_SIZE*i + 0], xmax), xmin);
        data[OBJECT_SIZE*i + 1] = fmax(fmin(data[OBJECT_SIZE*i + 1], ymax), ymin);
    }

    gwy_selection_set_data(selection, n, data);
    g_free(data);
}

static void
move(GwySelection *selection, gdouble vx, gdouble vy)
{
    gdouble v[OBJECT_SIZE] = { vx, vy };
    gwy_selection_move_objects(selection, v);
}

/**
 * gwy_selection_path_new:
 *
 * Creates a new path selection.
 *
 * Returns: A new selection object.
 **/
GwySelection*
gwy_selection_path_new(void)
{
    return (GwySelection*)g_object_new(GWY_TYPE_SELECTION_PATH, NULL);
}

/**
 * gwy_selection_path_set_slackness:
 * @selection: A path selection.
 * @slackness: Curve slackness (from 0 to √2).
 *
 * Sets the curve slackness of a path selection.
 *
 * Slackness of zero means the curve is tight and the segments connecting the points are straight. The default value
 * of 1/√2 gives naturally looking curves. Slackness of 1 means quite slack curves, whereas larger values than 1 more
 * or less means trying to stuff more curve between the points than natural.
 **/
void
gwy_selection_path_set_slackness(GwySelectionPath *selection,
                                 gdouble slackness)
{
    g_return_if_fail(GWY_IS_SELECTION_PATH(selection));
    g_return_if_fail(slackness >= 0.0 && slackness <= G_SQRT2);

    GwySelectionPathPrivate *priv = selection->priv;
    if (slackness == priv->slackness)
        return;

    priv->slackness = slackness;
    g_object_notify_by_pspec(G_OBJECT(selection), properties[PROP_SLACKNESS]);
}

/**
 * gwy_selection_path_get_slackness:
 * @selection: A path selection.
 *
 * Gets the curve slackness of a path selection.
 *
 * See gwy_selection_path_set_slackness() for more details.
 *
 * Returns: The slackness value.
 **/
gdouble
gwy_selection_path_get_slackness(GwySelectionPath *selection)
{
    g_return_val_if_fail(GWY_IS_SELECTION_PATH(selection), 1.0/G_SQRT2);
    return selection->priv->slackness;
}

/**
 * gwy_selection_path_set_closed:
 * @selection: A path selection.
 * @closed: %TRUE for a closed curve; %FALSE for a curve with two open ends.
 *
 * Changes whether a path selection is a closed curve.
 **/
void
gwy_selection_path_set_closed(GwySelectionPath *selection,
                              gboolean closed)
{
    g_return_if_fail(GWY_IS_SELECTION_PATH(selection));

    GwySelectionPathPrivate *priv = selection->priv;
    if (!closed == !priv->closed)
        return;

    priv->closed = !!closed;
    g_object_notify_by_pspec(G_OBJECT(selection), properties[PROP_CLOSED]);
}

/**
 * gwy_selection_path_get_closed:
 * @selection: A path selection.
 *
 * Reports whether a path selection is a closed curve.
 *
 * Returns: %TRUE if it is closed; %FALSE if it is open-ended.
 **/
gboolean
gwy_selection_path_get_closed(GwySelectionPath *selection)
{
    g_return_val_if_fail(GWY_IS_SELECTION_PATH(selection), FALSE);
    return selection->priv->closed;
}

static void
serializable_itemize(GwySerializable *serializable, GwySerializableGroup *group)
{
    GwySelectionPath *selection = GWY_SELECTION_PATH(serializable);
    GwySelectionPathPrivate *priv = selection->priv;

    serializable_parent_iface->itemize(serializable, group);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_SLACKNESS, priv->slackness);
    gwy_serializable_group_append_boolean(group, serializable_items + ITEM_CLOSED, priv->closed);
}

static gboolean
serializable_construct(GwySerializable *serializable, GwySerializableGroup *group, GwyErrorList **error_list)
{
    if (!serializable_parent_iface->construct(serializable, group, error_list))
        return FALSE;

    GwySerializableItem its[NUM_ITEMS];
    gwy_assign(its, serializable_items, NUM_ITEMS);
    gwy_deserialize_filter_items(its, NUM_ITEMS, group, TYPE_NAME, error_list);

    GwySelectionPath *selection = GWY_SELECTION_PATH(serializable);
    selection->priv->slackness = its[ITEM_SLACKNESS].value.v_double;
    selection->priv->closed = its[ITEM_CLOSED].value.v_boolean;

    return TRUE;
}

static GwySerializable*
serializable_copy(GwySerializable *serializable)
{
    GwySelectionPath *selection = GWY_SELECTION_PATH(serializable);
    GwySelectionPath *copy = GWY_SELECTION_PATH(serializable_parent_iface->copy(serializable));
    copy->priv->slackness = selection->priv->slackness;
    copy->priv->closed = selection->priv->closed;
    return GWY_SERIALIZABLE(copy);
}

static void
serializable_assign(GwySerializable *destination, GwySerializable *source)
{
    GwySelectionPath *destselection = GWY_SELECTION_PATH(destination);
    GwySelectionPath *srcselection = GWY_SELECTION_PATH(source);
    destselection->priv->slackness = srcselection->priv->slackness;
    destselection->priv->closed = srcselection->priv->closed;
    serializable_parent_iface->assign(destination, source);
}

/**
 * SECTION:selection-path
 * @title: GwySelectionPath
 * @short_description: Path selection
 *
 * #GwySelectionPath represents a free-form spline path. Unlike most other selections, the entire selection is one
 * path. Individual objects are points on the path. Selection data consist of pairs of coordinates (x, y) of control
 * points on path, in order from the beginning to end.
 **/

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