/*
 *  $Id: selection-axis.c 28777 2025-11-04 16:46:05Z 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/types.h"

#include "libgwyddion/selection-axis.h"

#define TYPE_NAME "GwySelectionAxis"

#define DEFAULT_ORIENTATION GWY_ORIENTATION_HORIZONTAL

enum {
    OBJECT_SIZE = 1
};

enum {
    PROP_0,
    PROP_ORIENTATION,
    NUM_PROPERTIES
};

enum {
    ITEM_ORIENTATION,
    NUM_ITEMS
};

struct _GwySelectionAxisPrivate {
    GwyOrientation orientation;
};

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 gboolean         crop_object           (GwySelection *selection,
                                               gint i,
                                               gpointer user_data);
static void             crop                  (GwySelection *selection,
                                               gdouble xmin,
                                               gdouble ymin,
                                               gdouble xmax,
                                               gdouble ymax);
static void             move                  (GwySelection *selection,
                                               gdouble vx,
                                               gdouble vy);
static const gchar*     coord_symbol          (GwySelection *selection,
                                               gint i);

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

static GwySerializableItem serializable_items[NUM_ITEMS] = {
    { .name = "orientation", .ctype = GWY_SERIALIZABLE_INT32, .value.v_int32 = DEFAULT_ORIENTATION },
};

G_DEFINE_TYPE_WITH_CODE(GwySelectionAxis, gwy_selection_axis, GWY_TYPE_SELECTION,
                        G_ADD_PRIVATE(GwySelectionAxis)
                        G_IMPLEMENT_INTERFACE(GWY_TYPE_SERIALIZABLE, serializable_init))

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

    /**
     * GwySelectionAxis:orientation:
     *
     * The :orientation property represents the orientation along which the points are selected.
     *
     * The orientation is %GWY_ORIENTATION_HORIZONTAL for selections along the @x-axis, i.e. points on the @x axis or
     * vertical lines. Conversely, %GWY_ORIENTATION_VERTICAL for selections along the @y-axis, i.e. points on the
     * @yaxis or horizontal lines.
     **/
    properties[PROP_ORIENTATION] = g_param_spec_enum("orientation", NULL,
                                                     "Along which directions the coordinates lie",
                                                     GWY_TYPE_ORIENTATION, DEFAULT_ORIENTATION, GWY_GPARAM_RWE);
}

static void
gwy_selection_axis_class_init(GwySelectionAxisClass *klass)
{
    GwySelectionClass *sel_class = GWY_SELECTION_CLASS(klass);
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);

    parent_class = gwy_selection_axis_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;
    sel_class->coord_symbol = coord_symbol;

    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_ORIENTATION].aux.pspec = properties[PROP_ORIENTATION];
    gwy_fill_serializable_defaults_pspec(serializable_items, NUM_ITEMS, FALSE);
}

static void
gwy_selection_axis_init(GwySelectionAxis *selection)
{
    GwySelectionAxisPrivate *priv;

    priv = selection->priv = gwy_selection_axis_get_instance_private(selection);
    priv->orientation = DEFAULT_ORIENTATION;
}

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

    switch (prop_id) {
        case PROP_ORIENTATION:
        gwy_selection_axis_set_orientation(selection, g_value_get_enum(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)
{
    GwySelectionAxisPrivate *priv = GWY_SELECTION_AXIS(object)->priv;

    switch (prop_id) {
        case PROP_ORIENTATION:
        g_value_set_enum(value, priv->orientation);
        break;

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

static gboolean
crop_object(GwySelection *selection, gint i, gpointer user_data)
{
    GwySelectionAxisPrivate *priv = GWY_SELECTION_AXIS(selection)->priv;
    const gdouble *minmax = (const gdouble*)user_data;
    gdouble xy[OBJECT_SIZE];

    gwy_selection_get_object(selection, i, xy);
    if (priv->orientation == GWY_ORIENTATION_HORIZONTAL)
        return xy[0] >= minmax[0] && xy[0] <= minmax[2];
    else
        return xy[0] >= minmax[1] && xy[0] <= minmax[3];
}

static void
crop(GwySelection *selection, gdouble xmin, gdouble ymin, gdouble xmax, gdouble ymax)
{
    gdouble minmax[4] = { xmin, ymin, xmax, ymax };

    gwy_selection_filter(selection, crop_object, minmax);
}

static void
move(GwySelection *selection, gdouble vx, gdouble vy)
{
    GwySelectionAxisPrivate *priv = GWY_SELECTION_AXIS(selection)->priv;
    gdouble v[OBJECT_SIZE] = { priv->orientation == GWY_ORIENTATION_VERTICAL ? vy : vx };
    gwy_selection_move_objects(selection, v);
}

static const gchar*
coord_symbol(GwySelection *selection, G_GNUC_UNUSED gint i)
{
    GwySelectionAxisPrivate *priv = GWY_SELECTION_AXIS(selection)->priv;
    return priv->orientation == GWY_ORIENTATION_VERTICAL ? "y" : "x";
}

/**
 * gwy_selection_axis_set_orientation:
 * @selection: A single-coordinate selection.
 * @orientation: New orientation.
 *
 * Sets the orientation of a single-coordinate selection.
 *
 * Horizontal orientation means points on the @x-axis (or vertical lines). Vertical orientation means points on the
 * @y-axis (or horizontal lines).
 *
 * Returns: A new selection object.
 **/
void
gwy_selection_axis_set_orientation(GwySelectionAxis *selection,
                                   GwyOrientation orientation)
{
    g_return_if_fail(GWY_IS_SELECTION_AXIS(selection));
    g_return_if_fail(orientation == GWY_ORIENTATION_HORIZONTAL || orientation == GWY_ORIENTATION_VERTICAL);

    GwySelectionAxisPrivate *priv = selection->priv;
    if (orientation == priv->orientation)
        return;

    gwy_selection_clear(GWY_SELECTION(selection));
    priv->orientation = orientation;
    g_object_notify_by_pspec(G_OBJECT(selection), properties[PROP_ORIENTATION]);
}

/**
 * gwy_selection_axis_get_orientation:
 * @selection: A single-coordinate selection.
 *
 * Obtains the orientation of a single-coordinate selection.
 *
 * Horizontal orientation means points on the @x-axis (or vertical lines). Vertical orientation means points on the
 * @y-axis (or horizontal lines).
 *
 * Returns: The orientation.
 **/
GwyOrientation
gwy_selection_axis_get_orientation(GwySelectionAxis *selection)
{
    g_return_val_if_fail(GWY_IS_SELECTION_AXIS(selection), DEFAULT_ORIENTATION);

    return selection->priv->orientation;
}

/**
 * gwy_selection_axis_new:
 *
 * Creates a new single-coordinate selection along a cardinal direction.
 *
 * Returns: A new selection object.
 **/
GwySelection*
gwy_selection_axis_new(void)
{
    return (GwySelection*)g_object_new(GWY_TYPE_SELECTION_AXIS, NULL);
}

static void
serializable_itemize(GwySerializable *serializable, GwySerializableGroup *group)
{
    GwySelectionAxis *selection = GWY_SELECTION_AXIS(serializable);
    GwySelectionAxisPrivate *priv = selection->priv;

    serializable_parent_iface->itemize(serializable, group);
    gwy_serializable_group_append_int32(group, serializable_items + ITEM_ORIENTATION, priv->orientation);
}

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);

    GwySelectionAxis *selection = GWY_SELECTION_AXIS(serializable);
    selection->priv->orientation = its[ITEM_ORIENTATION].value.v_int32;

    return TRUE;
}

static GwySerializable*
serializable_copy(GwySerializable *serializable)
{
    GwySelectionAxis *selection = GWY_SELECTION_AXIS(serializable);
    GwySelectionAxis *copy = GWY_SELECTION_AXIS(serializable_parent_iface->copy(serializable));
    copy->priv->orientation = selection->priv->orientation;
    return GWY_SERIALIZABLE(copy);
}

static void
serializable_assign(GwySerializable *destination, GwySerializable *source)
{
    GwySelectionAxis *destselection = GWY_SELECTION_AXIS(destination);
    GwySelectionAxis *srcselection = GWY_SELECTION_AXIS(source);
    destselection->priv->orientation = srcselection->priv->orientation;
    serializable_parent_iface->assign(destination, source);
}

/**
 * SECTION:selection-axis
 * @title: GwySelectionAxis
 * @short_description: Single coordinate selection
 *
 * #GwySelectionAxis is used to represent single coodinate (@x or @y) selections.  Selection data consists of single
 * coordinates. Whether it is @x or @y is determined by the GwySelectionAxis:orientation property.
 **/

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