/*
 *  $Id: layer-lattice.c 29656 2026-03-26 10:21:10Z yeti-dn $
 *  Copyright (C) 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.
 */

#include "config.h"
#include <gdk/gdkkeysyms.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/math.h"
#include "libgwyddion/selection-lattice.h"

#include "libgwyui/data-view.h"
#include "libgwyui/layer-lattice.h"
#include "libgwyui/cairo-utils.h"
#include "libgwyui/layer-utils.h"

enum {
    OBJECT_SIZE = 4
};

enum {
    PROP_0,
    PROP_N_LINES,
    NUM_PROPERTIES,
};

struct _GwyLayerLatticePrivate {
    /* Properties */
    gint n_lines;

    /* Dynamic state */
    gdouble x0;
    gdouble y0;
    gdouble before[OBJECT_SIZE];
};

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 draw              (GwyVectorLayer *layer,
                               cairo_t *cr);
static void draw_or_invalidate(GwyVectorLayer *layer,
                               cairo_t *cr,
                               GwyDataView *dataview,
                               cairo_region_t *region,
                               const gdouble *xy);
static void invalidate_object (GwyVectorLayer *layer,
                               cairo_region_t *region,
                               const gdouble *xy);
static void pointer_moved     (GwyVectorLayer *layer,
                               gdouble x,
                               gdouble y);
static void button_pressed    (GwyVectorLayer *layer,
                               gdouble x,
                               gdouble y);
static void button_released   (GwyVectorLayer *layer,
                               gdouble x,
                               gdouble y);
static void transform_lattice (GwyLayerLattice *layer,
                               gdouble xreal,
                               gdouble yreal,
                               gdouble *xy);
//static gboolean key_pressed        (GwyVectorLayer *layer,
//                                    GdkEventKey *event);

static GParamSpec *properties[NUM_PROPERTIES] = { NULL, };
static GwyVectorLayerClass *parent_class = NULL;

G_DEFINE_TYPE_WITH_CODE(GwyLayerLattice, gwy_layer_lattice, GWY_TYPE_VECTOR_LAYER,
                        G_ADD_PRIVATE(GwyLayerLattice))

static void
gwy_layer_lattice_class_init(GwyLayerLatticeClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
    GwyVectorLayerClass *vector_class = GWY_VECTOR_LAYER_CLASS(klass);

    parent_class = gwy_layer_lattice_parent_class;

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

    vector_class->selection_type = GWY_TYPE_SELECTION_LATTICE;
    vector_class->draw = draw;
    vector_class->invalidate_object = invalidate_object;
    vector_class->motion = pointer_moved;
    vector_class->button_press = button_pressed;
    vector_class->button_release = button_released;
    //vector_class->key_press = key_pressed;

    properties[PROP_N_LINES] = g_param_spec_uint("n-lines", NULL,
                                                 "Number of lattice lines to draw beside the central ones",
                                                 0, 1024, 12, GWY_GPARAM_RWE);

    g_object_class_install_properties(gobject_class, NUM_PROPERTIES, properties);
}

static void
gwy_layer_lattice_init(GwyLayerLattice *layer)
{
    GwyLayerLatticePrivate *priv;

    layer->priv = priv = gwy_layer_lattice_get_instance_private(layer);
    priv->n_lines = 12;
}

static void
set_property(GObject *object,
             guint prop_id,
             const GValue *value,
             GParamSpec *pspec)
{
    GwyLayerLattice *layer = GWY_LAYER_LATTICE(object);

    switch (prop_id) {
        case PROP_N_LINES:
        gwy_layer_lattice_set_n_lines(layer, g_value_get_int(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)
{
    GwyLayerLatticePrivate *priv = GWY_LAYER_LATTICE(object)->priv;

    switch (prop_id) {
        case PROP_N_LINES:
        g_value_set_int(value, priv->n_lines);
        break;

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

static void
draw(GwyVectorLayer *layer, cairo_t *cr)
{
    GwySelection *selection = gwy_vector_layer_get_selection(layer);
    GtkWidget *parent = gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer));
    if (!parent) {
        g_warning("Standalone drawing is not implemented yet.");
        return;
    }
    g_return_if_fail(GWY_IS_DATA_VIEW(parent));
    GwyDataView *dataview = GWY_DATA_VIEW(parent);

    cairo_set_source_rgb(cr, 1.0, 1.0, 1.0);
    cairo_set_line_width(cr, 1.0);
    cairo_set_operator(cr, CAIRO_OPERATOR_DIFFERENCE);

    gint n = gwy_selection_get_n_objects(selection);
    for (gint i = 0; i < n; i++) {
        gdouble xy[OBJECT_SIZE];
        gwy_selection_get_object(selection, i, xy);
        draw_or_invalidate(layer, cr, dataview, NULL, xy);
    }
}

static void
draw_or_invalidate(GwyVectorLayer *layer,
                   cairo_t *cr, GwyDataView *dataview,
                   cairo_region_t *region,
                   const gdouble *xy)
{
    GwyLayerLatticePrivate *priv = GWY_LAYER_LATTICE(layer)->priv;
    gint nlines = priv->n_lines;
    g_assert(cr || region);

    gdouble xsize, ysize;
    gwy_data_view_get_real_data_sizes(dataview, &xsize, &ysize);

    /* Draw one-vector lines when n-lines is zero. */
    gint xnl = MAX(nlines, 1);
    gdouble xfrom, yfrom, xto, yto, xl0, yl0, xl1, yl1;

    xfrom = -xnl*xy[2] + 0.5*xsize;
    yfrom = -xnl*xy[3] + 0.5*ysize;
    xto = xnl*xy[2] + 0.5*xsize;
    yto = xnl*xy[3] + 0.5*ysize;
    _gwy_transform_line_to_target(dataview, xfrom, yfrom, xto, yto, &xl0, &yl0, &xl1, &yl1);
    gwy_cairo_draw_or_add_thin_line(cr, region, xl0, yl0, xl1, yl1);

    xfrom = -xnl*xy[0] + 0.5*xsize;
    yfrom = -xnl*xy[1] + 0.5*ysize;
    xto = xnl*xy[0] + 0.5*xsize;
    yto = xnl*xy[1] + 0.5*ysize;
    _gwy_transform_line_to_target(dataview, xfrom, yfrom, xto, yto, &xl0, &yl0, &xl1, &yl1);
    gwy_cairo_draw_or_add_thin_line(cr, region, xl0, yl0, xl1, yl1);

    if (cr)
        cairo_stroke(cr);

    if (!nlines)
        return;

    if (cr) {
        static const gdouble dashes[2] = { 3.0, 4.0 };
        cairo_save(cr);
        cairo_set_dash(cr, dashes, G_N_ELEMENTS(dashes), 0.0);
    }
    for (gint j = -nlines; j <= nlines; j++) {
        if (!j)
            continue;

        xfrom = j*xy[0] - nlines*xy[2] + 0.5*xsize;
        yfrom = j*xy[1] - nlines*xy[3] + 0.5*ysize;
        xto = j*xy[0] + nlines*xy[2] + 0.5*xsize;
        yto = j*xy[1] + nlines*xy[3] + 0.5*ysize;
        _gwy_transform_line_to_target(dataview, xfrom, yfrom, xto, yto, &xl0, &yl0, &xl1, &yl1);
        gwy_cairo_draw_or_add_thin_line(cr, region, xl0, yl0, xl1, yl1);
    }

    for (gint i = -nlines; i <= nlines; i++) {
        if (!i)
            continue;

        xfrom = -nlines*xy[0] + i*xy[2] + 0.5*xsize;
        yfrom = -nlines*xy[1] + i*xy[3] + 0.5*ysize;
        xto = nlines*xy[0] + i*xy[2] + 0.5*xsize;
        yto = nlines*xy[1] + i*xy[3] + 0.5*ysize;
        _gwy_transform_line_to_target(dataview, xfrom, yfrom, xto, yto, &xl0, &yl0, &xl1, &yl1);
        gwy_cairo_draw_or_add_thin_line(cr, region, xl0, yl0, xl1, yl1);
    }

    if (cr) {
        cairo_stroke(cr);
        cairo_restore(cr);
    }
}

static void
invalidate_object(GwyVectorLayer *layer,
                  cairo_region_t *region,
                  const gdouble *xy)
{
    GtkWidget *parent = gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer));
    g_return_if_fail(GWY_IS_DATA_VIEW(parent));
    draw_or_invalidate(layer, NULL, GWY_DATA_VIEW(parent), region, xy);
}

/* Anything better? */
static void
set_cursor_according_to_proximity(GwyVectorLayer *layer,
                                  G_GNUC_UNUSED gdouble xreal,
                                  G_GNUC_UNUSED gdouble yreal)
{
    const gchar *name = NULL;
    GwySelection *selection = gwy_vector_layer_get_selection(layer);
    if (selection && gwy_selection_get_n_objects(selection))
        name = "move";
    gwy_data_view_set_named_cursor(GWY_DATA_VIEW(gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer))), name);
}

static void
pointer_moved(GwyVectorLayer *layer, gdouble x, gdouble y)
{
    if (!gwy_vector_layer_get_editable(layer))
        return;

    GwySelection *selection = gwy_vector_layer_get_selection(layer);
    if (!selection)
        return;

    GtkWidget *parent = gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer));
    g_return_if_fail(GWY_IS_DATA_VIEW(parent));
    GwyDataView *dataview = GWY_DATA_VIEW(parent);

    gwy_data_view_coords_widget_clamp(dataview, &x, &y);
    gdouble xreal, yreal, xy[OBJECT_SIZE];
    gwy_data_view_coords_widget_to_real(dataview, x, y, &xreal, &yreal);

    if (!gwy_vector_layer_get_current_button(layer)) {
        set_cursor_according_to_proximity(layer, xreal, yreal);
        return;
    }

    gint iobj = gwy_vector_layer_get_current_object(layer);
    if (iobj < 0)
        return;

    transform_lattice(GWY_LAYER_LATTICE(layer), xreal, yreal, xy);
    gwy_vector_layer_update_object(layer, iobj, xy);
}

static void
button_pressed(GwyVectorLayer *layer, gdouble x, gdouble y)
{
    gint button = gwy_vector_layer_get_current_button(layer);
    if (button != 1)
        return;

    GwySelection *selection = gwy_vector_layer_get_selection(layer);
    GtkWidget *parent = gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer));
    g_return_if_fail(GWY_IS_DATA_VIEW(parent));
    GwyDataView *dataview = GWY_DATA_VIEW(parent);

    /* Do nothing when we are outside. */
    if (!gwy_data_view_coords_widget_clamp(dataview, &x, &y))
        return;
    /* Give up when there is no selection. We do not know how to init a lattice using a single mouse click. */
    if (!gwy_selection_get_n_objects(selection))
        return;

    GwyLayerLatticePrivate *priv = GWY_LAYER_LATTICE(layer)->priv;
    gwy_data_view_coords_widget_to_real(dataview, x, y, &priv->x0, &priv->y0);

    /* Multiple lattices are weird. Edit the first one. */
    gint iobj = 0;
    if (just_choose_object(layer, iobj))
        return;

    gwy_selection_get_object(selection, iobj, priv->before);
    gwy_vector_layer_set_current_object(layer, iobj);
}

static void
button_released(GwyVectorLayer *layer, gdouble x, gdouble y)
{
    gint button = gwy_vector_layer_get_current_button(layer);
    gint iobj = gwy_vector_layer_get_current_object(layer);
    if (button != 1 || iobj < 0)
        return;

    GwySelection *selection = gwy_vector_layer_get_selection(layer);
    GtkWidget *parent = gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer));
    g_return_if_fail(GWY_IS_DATA_VIEW(parent));
    GwyDataView *dataview = GWY_DATA_VIEW(parent);

    gdouble xreal, yreal, xy[OBJECT_SIZE];
    gwy_data_view_coords_widget_to_real(dataview, x, y, &xreal, &yreal);
    transform_lattice(GWY_LAYER_LATTICE(layer), xreal, yreal, xy);
    gwy_vector_layer_update_object(layer, iobj, xy);
    gwy_vector_layer_set_current_object(layer, -1);
    set_cursor_according_to_proximity(layer, xreal, yreal);
    gwy_selection_finished(selection);
}

/* TODO: Later */
#if 0
static gboolean
key_pressed(GwyVectorLayer *layer, GdkEventKey *event)
{
    gboolean large_step = (event->state & (GDK_CONTROL_MASK | GDK_MOD1_MASK));
    gint which = ((event->state & GDK_SHIFT_MASK) ? 2 : 0);
    GwyDataView *dataview;
    guint keyval = event->keyval;
    gint chosen = priv->chosen, xcurr, ycurr, xnew, ynew, move_distance;
    gdouble xy[4];

    if (chosen < 0
        || chosen >= gwy_selection_get_n_objects(priv->selection))
        return FALSE;

    if (keyval != GDK_KEY_Left && keyval != GDK_KEY_Right
        && keyval != GDK_KEY_Up && keyval != GDK_KEY_Down)
        return FALSE;

    dataview = GWY_DATA_VIEW(GWY_DATA_VIEW_LAYER(layer)->parent);
    g_return_val_if_fail(dataview, FALSE);

    gwy_selection_get_object(priv->selection, chosen, xy);
    gwy_data_view_coords_real_to_widget(dataview, xy[which], xy[which+1], &xcurr, &ycurr);
    xnew = xcurr;
    ynew = ycurr;
    move_distance = (large_step ? 16 : 1);
    if (keyval == GDK_KEY_Left)
        xnew -= move_distance;
    else if (keyval == GDK_KEY_Right)
        xnew += move_distance;
    else if (keyval == GDK_KEY_Up)
        ynew -= move_distance;
    else if (keyval == GDK_KEY_Down)
        ynew += move_distance;
    gwy_data_view_coords_widget_clamp(dataview, &xnew, &ynew);

    if (xnew != xcurr || ynew != ycurr) {
        gwy_data_view_coords_widget_to_real(dataview, xnew, ynew, xy+which, xy+which+1);
        gwy_selection_set_object(priv->selection, chosen, xy);
    }

    return TRUE;
}
#endif

/**
 * gwy_layer_lattice_new:
 *
 * Creates a new lattice vector layer.
 *
 * Returns: A newly created lattice vector layer.
 **/
GwyVectorLayer*
gwy_layer_lattice_new(void)
{
    return (GwyVectorLayer*)g_object_new(GWY_TYPE_LAYER_LATTICE, NULL);
}

/**
 * gwy_layer_lattice_get_n_lines:
 * @layer: A lattice vector layer.
 *
 * Gets the number of lattice lines of a lattice vector layer.
 *
 * Returns: The number of lines.
 **/
gint
gwy_layer_lattice_get_n_lines(GwyLayerLattice *layer)
{
    g_return_val_if_fail(GWY_IS_LAYER_LATTICE(layer), 0);
    return layer->priv->n_lines;
}

/**
 * gwy_layer_lattice_set_n_lines:
 * @layer: A lattice vector layer.
 * @n_lines: Number of lines.
 *
 * Sets the number of lattice lines of a lattice vector layer.
 *
 * The basis vectors are always drawn, with both signs (i.e. forming a cross). If @n_lines is zero nothing else is
 * drawn. Otherwise @n_lines extra lines are drawn to each side of the central lines to render the lattice.
 **/
void
gwy_layer_lattice_set_n_lines(GwyLayerLattice *layer,
                              gint n_lines)
{
    g_return_if_fail(GWY_IS_LAYER_LATTICE(layer));
    g_return_if_fail(n_lines >= 0 && n_lines <= 1024);

    GwyLayerLatticePrivate *priv = layer->priv;
    if (n_lines == priv->n_lines)
        return;

    priv->n_lines = n_lines;
    g_object_notify_by_pspec(G_OBJECT(layer), properties[PROP_N_LINES]);
    gwy_data_view_layer_updated(GWY_DATA_VIEW_LAYER(layer));
}

/* Permit dest = src */
static void
matrix_mult(gdouble *dest, const gdouble *src,
            gdouble axx, gdouble axy, gdouble ayx, gdouble ayy)
{
    gdouble xy[2];

    xy[0] = axx*src[0] + axy*src[1];
    xy[1] = ayx*src[0] + ayy*src[1];
    dest[0] = xy[0];
    dest[1] = xy[1];
}

static void
transform_lattice(GwyLayerLattice *layer,
                  gdouble xreal, gdouble yreal,
                  gdouble *xy)
{
    GwyLayerLatticePrivate *priv = layer->priv;
    GtkWidget *parent = gwy_data_view_layer_get_parent(GWY_DATA_VIEW_LAYER(layer));
    GwyDataView *dataview = GWY_DATA_VIEW(parent);

    gdouble xsize, ysize;
    gwy_data_view_get_real_data_sizes(dataview, &xsize, &ysize);
    gdouble xorig = priv->x0 - 0.5*xsize;
    gdouble yorig = priv->y0 - 0.5*ysize;
    xreal -= 0.5*xsize;
    yreal -= 0.5*ysize;

    gdouble alpha = atan2(yreal, xreal) - atan2(yorig, xorig);
    gdouble xr2 = xreal*xreal, yr2 = yreal*yreal, xyr = xreal*yreal;
    gdouble r2 = xr2 + yr2;
    gdouble rho = sqrt(r2/(xorig*xorig + yorig*yorig));
    gdouble ca = cos(alpha)/r2;
    gdouble sa = sin(alpha)/r2;

    matrix_mult(xy + 0, priv->before + 0, ca, -sa, sa, ca);
    matrix_mult(xy + 2, priv->before + 2, ca, -sa, sa, ca);
    matrix_mult(xy + 0, xy + 0, rho*xr2 + yr2, xyr*(rho - 1.0), xyr*(rho - 1.0), rho*yr2 + xr2);
    matrix_mult(xy + 2, xy + 2, rho*xr2 + yr2, xyr*(rho - 1.0), xyr*(rho - 1.0), rho*yr2 + xr2);
}

/**
 * SECTION:layer-lattice
 * @title: GwyLayerLattice
 * @short_description: Data view layer for lattice-wise selections
 *
 * #GwyLayerLattice allows selection of two-dimensional lattices. It uses #GwySelectionLattice selection type.
 *
 * The lattice is drawn over an area given by GwyLayerLattice:n-lines. However, it can be rotated and streched by
 * clicking and dragging anywhere in the plane, not just in the region covered by the lines. Therefore, the layer is
 * only useful with a single latice selection as only the first selection object can be edited.
 *
 * The lattice selection needs to be initialised. The layer can only edit a lattice selection; it cannot create one
 * from scratch.
 **/

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