/*
 *  $Id: selections.c 28149 2025-06-25 10:51:15Z yeti-dn $
 *  Copyright (C) 2025 David Nečas (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 "tests/testlibgwy.h"

typedef void (*SetSomePropertiesFunc)(GwySelection *sel);
typedef void (*CheckPropertiesFunc)(GwySelection *sel, GwySelection *reference);

static void
assert_selection_content(GwySelection *sel, const gdouble *ref_data, guint ref_n, gdouble eps)
{
    g_assert_true(GWY_IS_SELECTION(sel));
    guint n = gwy_selection_get_n_objects(sel);
    guint size = gwy_selection_get_object_size(sel);
    g_assert_cmpuint(n*size, ==, ref_n);
    const gdouble *data = gwy_selection_get_data_array(sel);
    if (ref_n)
        g_assert_nonnull(data);

    if (eps > 0.0) {
        for (guint i = 0; i < ref_n; i++)
            g_assert_cmpfloat_with_epsilon(data[i], ref_data[i], eps);
    }
    else {
        for (guint i = 0; i < ref_n; i++)
            g_assert_cmpfloat(data[i], ==, ref_data[i]);
    }
}

static void
selection_assert_equal(GObject *object, GObject *reference)
{
    g_assert_true(GWY_IS_SELECTION(object));
    g_assert_true(GWY_IS_SELECTION(reference));
    g_assert_cmpuint(G_OBJECT_TYPE(object), ==, G_OBJECT_TYPE(reference));

    GwySelection *sel = GWY_SELECTION(object), *sel_ref = GWY_SELECTION(reference);
    g_assert_cmpint(gwy_selection_get_n_objects(sel), ==, gwy_selection_get_n_objects(sel_ref));
    g_assert_cmpint(gwy_selection_get_max_objects(sel), ==, gwy_selection_get_max_objects(sel_ref));
    assert_selection_content(sel, gwy_selection_get_data_array(sel_ref),
                             gwy_selection_get_n_objects(sel_ref)*gwy_selection_get_object_size(sel_ref), 0.0);
    /* Serialization test already does this, but copy and assign do not. */
    assert_properties_equal(object, reference);
}

static void
test_selection_serialization(GwySelection *sel,
                             SetSomePropertiesFunc set_properties)
{
    serialize_object_and_back(G_OBJECT(sel), selection_assert_equal, FALSE, NULL);

    if (set_properties) {
        set_properties(sel);
        serialize_object_and_back(G_OBJECT(sel), selection_assert_equal, FALSE, NULL);
    }

    gwy_selection_clear(sel);
    gwy_selection_set_max_objects(sel, 1);
    serialize_object_and_back(G_OBJECT(sel), selection_assert_equal, FALSE, NULL);

    g_assert_finalize_object(sel);
}

static void
test_selection_copy(GwySelection *sel,
                    SetSomePropertiesFunc set_properties)
{
    serializable_test_copy(GWY_SERIALIZABLE(sel), selection_assert_equal);

    if (set_properties) {
        set_properties(sel);
        serializable_test_copy(GWY_SERIALIZABLE(sel), selection_assert_equal);
    }

    gwy_selection_clear(sel);
    gwy_selection_set_max_objects(sel, 1);
    serializable_test_copy(GWY_SERIALIZABLE(sel), selection_assert_equal);

    g_assert_finalize_object(sel);
}

static void
test_selection_assign(GwySelection *sel,
                      SetSomePropertiesFunc set_properties)
{
    serializable_test_assign(GWY_SERIALIZABLE(sel), NULL, selection_assert_equal);

    if (set_properties) {
        set_properties(sel);
        serializable_test_assign(GWY_SERIALIZABLE(sel), NULL, selection_assert_equal);
    }

    gwy_selection_clear(sel);
    gwy_selection_set_max_objects(sel, 1);
    serializable_test_assign(GWY_SERIALIZABLE(sel), NULL, selection_assert_equal);

    g_assert_finalize_object(sel);
}

static void
test_selection_set_data(GwySelection *sel,
                        const gdouble *ref_data,
                        guint n)
{
    gwy_selection_clear(sel);
    assert_selection_content(sel, ref_data, 0, 0.0);

    guint object_size = gwy_selection_get_object_size(sel);
    g_assert_cmpint(n % object_size, ==, 0);
    gwy_selection_set_data(sel, n/object_size, ref_data);
    assert_selection_content(sel, ref_data, n, 0.0);

    for (guint i = 0; i < n/object_size; i++)
        gwy_selection_set_object(sel, i, ref_data + i*object_size);
    assert_selection_content(sel, ref_data, n, 0.0);
}

static void
test_selection_append(GwySelection *sel,
                      const gdouble *ref_data,
                      guint n)
{
    gwy_selection_clear(sel);
    assert_selection_content(sel, ref_data, 0, 0.0);

    guint object_size = gwy_selection_get_object_size(sel);
    g_assert_cmpint(n % object_size, ==, 0);
    for (guint i = 0; i < n/object_size; i++)
        gwy_selection_set_object(sel, -1, ref_data + i*object_size);
    assert_selection_content(sel, ref_data, n, 0.0);
}

static void
test_selection_get_object(GwySelection *sel,
                          const gdouble *ref_data,
                          guint n)
{
    guint object_size = gwy_selection_get_object_size(sel);
    g_assert_cmpint(n % object_size, ==, 0);
    gdouble xy[object_size];
    gwy_selection_set_data(sel, n/object_size, ref_data);
    for (guint i = 0; i < n/object_size; i++) {
        g_assert_true(gwy_selection_get_object(sel, i, xy));
        for (guint j = 0; j < object_size; j++)
            g_assert_cmpfloat(xy[j], ==, ref_data[i*object_size + j]);
    }
    g_assert_false(gwy_selection_get_object(sel, -1, xy));
    g_assert_false(gwy_selection_get_object(sel, n/object_size, xy));
}

void
test_selection_axis_basic(void)
{
    const gdouble data[4*1] = { 1.0, -5.0, G_PI, G_LN2 };

    GwySelection *sel = gwy_selection_axis_new();
    g_assert_cmpuint(gwy_selection_get_object_size(sel), ==, 1);
    assert_selection_content(sel, data, 0, 0.0);

    gwy_selection_set_max_objects(sel, 7);
    test_selection_set_data(sel, data, G_N_ELEMENTS(data));
    test_selection_append(sel, data, G_N_ELEMENTS(data));
    test_selection_get_object(sel, data, G_N_ELEMENTS(data));
    g_assert_finalize_object(sel);
}

static GwySelection*
create_axis_selection_for_serialization(void)
{
    const gdouble data[4] = { 1.0, -5.0, G_PI, G_LN2 };

    GwySelection *sel = gwy_selection_axis_new();
    gwy_selection_set_max_objects(sel, 5);
    gwy_selection_set_data(sel, 4, data);

    return sel;
}

static void
set_axis_selection_properties(GwySelection *selection)
{
    gwy_selection_axis_set_orientation(GWY_SELECTION_AXIS(selection), GWY_ORIENTATION_VERTICAL);
}

void
test_selection_axis_serialization(void)
{
    test_selection_serialization(create_axis_selection_for_serialization(), set_axis_selection_properties);
}

void
test_selection_axis_copy(void)
{
    test_selection_copy(create_axis_selection_for_serialization(), set_axis_selection_properties);
}

void
test_selection_axis_assign(void)
{
    test_selection_assign(create_axis_selection_for_serialization(), set_axis_selection_properties);
}

void
test_selection_ellipse_basic(void)
{
    const gdouble data[2*4] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, -1e-4, 1e6, -1e7 };

    GwySelection *sel = gwy_selection_ellipse_new();
    g_assert_cmpuint(gwy_selection_get_object_size(sel), ==, 4);
    assert_selection_content(sel, data, 0, 0.0);

    gwy_selection_set_max_objects(sel, 3);
    test_selection_set_data(sel, data, G_N_ELEMENTS(data));
    test_selection_append(sel, data, G_N_ELEMENTS(data));
    test_selection_get_object(sel, data, G_N_ELEMENTS(data));
    g_assert_finalize_object(sel);
}

static GwySelection*
create_ellipse_selection_for_serialization(void)
{
    const gdouble data[8] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, -1e-4, 1e6, -1e7 };

    GwySelection *sel = gwy_selection_ellipse_new();
    gwy_selection_set_max_objects(sel, 4);
    gwy_selection_set_data(sel, 2, data);

    return sel;
}

void
test_selection_ellipse_serialization(void)
{
    test_selection_serialization(create_ellipse_selection_for_serialization(), NULL);
}

void
test_selection_ellipse_copy(void)
{
    test_selection_copy(create_ellipse_selection_for_serialization(), NULL);
}

void
test_selection_ellipse_assign(void)
{
    test_selection_assign(create_ellipse_selection_for_serialization(), NULL);
}

void
test_selection_lattice_basic(void)
{
    const gdouble data[8] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, -1e-4, 1e6, -1e7 };

    GwySelection *sel = gwy_selection_lattice_new();
    g_assert_cmpuint(gwy_selection_get_object_size(sel), ==, 4);
    assert_selection_content(sel, data, 0, 0.0);

    gwy_selection_set_max_objects(sel, 5);
    test_selection_set_data(sel, data, G_N_ELEMENTS(data));
    test_selection_append(sel, data, G_N_ELEMENTS(data));
    test_selection_get_object(sel, data, G_N_ELEMENTS(data));
    g_assert_finalize_object(sel);
}

static GwySelection*
create_lattice_selection_for_serialization(void)
{
    const gdouble data[8] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, -1e-4, 1e6, -1e7 };

    GwySelection *sel = gwy_selection_lattice_new();
    gwy_selection_set_max_objects(sel, 4);
    gwy_selection_set_data(sel, 2, data);

    return sel;
}

void
test_selection_lattice_serialization(void)
{
    test_selection_serialization(create_lattice_selection_for_serialization(), NULL);
}

void
test_selection_lattice_copy(void)
{
    test_selection_copy(create_lattice_selection_for_serialization(), NULL);
}

void
test_selection_lattice_assign(void)
{
    test_selection_assign(create_lattice_selection_for_serialization(), NULL);
}

void
test_selection_line_basic(void)
{
    const gdouble data[3*4] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, -1e-4, 1e6, -1e7, 6.0, 6.1, 5.4, 5.3 };

    GwySelection *sel = gwy_selection_line_new();
    g_assert_cmpuint(gwy_selection_get_object_size(sel), ==, 4);
    assert_selection_content(sel, data, 0, 0.0);

    gwy_selection_set_max_objects(sel, 5);
    test_selection_set_data(sel, data, G_N_ELEMENTS(data));
    test_selection_append(sel, data, G_N_ELEMENTS(data));
    test_selection_get_object(sel, data, G_N_ELEMENTS(data));
    g_assert_finalize_object(sel);
}

static GwySelection*
create_line_selection_for_serialization(void)
{
    const gdouble data[8] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, -1e-4, 1e6, -1e7 };

    GwySelection *sel = gwy_selection_line_new();
    gwy_selection_set_max_objects(sel, 4);
    gwy_selection_set_data(sel, 2, data);

    return sel;
}

void
test_selection_line_serialization(void)
{
    test_selection_serialization(create_line_selection_for_serialization(), NULL);
}

void
test_selection_line_copy(void)
{
    test_selection_copy(create_line_selection_for_serialization(), NULL);
}

void
test_selection_line_assign(void)
{
    test_selection_assign(create_line_selection_for_serialization(), NULL);
}

void
test_selection_path_basic(void)
{
    const gdouble data[3*2] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, 1e6 };

    GwySelection *sel = gwy_selection_path_new();
    g_assert_cmpuint(gwy_selection_get_object_size(sel), ==, 2);
    assert_selection_content(sel, data, 0, 0.0);

    gwy_selection_set_max_objects(sel, 4);
    test_selection_set_data(sel, data, G_N_ELEMENTS(data));
    test_selection_append(sel, data, G_N_ELEMENTS(data));
    test_selection_get_object(sel, data, G_N_ELEMENTS(data));
    g_assert_finalize_object(sel);
}

static GwySelection*
create_path_selection_for_serialization(void)
{
    const gdouble data[6] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, 1e6 };

    GwySelection *sel = gwy_selection_path_new();
    gwy_selection_set_max_objects(sel, 3);
    gwy_selection_set_data(sel, 3, data);

    return sel;
}

static void
set_path_selection_properties(GwySelection *selection)
{
    gwy_selection_path_set_closed(GWY_SELECTION_PATH(selection), TRUE);
    gwy_selection_path_set_slackness(GWY_SELECTION_PATH(selection), 0.444);
}

void
test_selection_path_serialization(void)
{
    test_selection_serialization(create_path_selection_for_serialization(), set_path_selection_properties);
}

void
test_selection_path_copy(void)
{
    test_selection_copy(create_path_selection_for_serialization(), set_path_selection_properties);
}

void
test_selection_path_assign(void)
{
    test_selection_assign(create_path_selection_for_serialization(), set_path_selection_properties);
}

void
test_selection_point_basic(void)
{
    const gdouble data[4*2] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, 1e6, 0.01, 0.03 };

    GwySelection *sel = gwy_selection_point_new();
    g_assert_cmpuint(gwy_selection_get_object_size(sel), ==, 2);
    assert_selection_content(sel, data, 0, 0.0);

    gwy_selection_set_max_objects(sel, 4);
    test_selection_set_data(sel, data, G_N_ELEMENTS(data));
    test_selection_append(sel, data, G_N_ELEMENTS(data));
    test_selection_get_object(sel, data, G_N_ELEMENTS(data));
    g_assert_finalize_object(sel);
}

static GwySelection*
create_point_selection_for_serialization(void)
{
    const gdouble data[6] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, 1e6 };

    GwySelection *sel = gwy_selection_point_new();
    gwy_selection_set_max_objects(sel, 4);
    gwy_selection_set_data(sel, 3, data);

    return sel;
}

void
test_selection_point_serialization(void)
{
    test_selection_serialization(create_point_selection_for_serialization(), NULL);
}

void
test_selection_point_copy(void)
{
    test_selection_copy(create_point_selection_for_serialization(), NULL);
}

void
test_selection_point_assign(void)
{
    test_selection_assign(create_point_selection_for_serialization(), NULL);
}

void
test_selection_quad_basic(void)
{
    const gdouble data[16] = {
        1.0, -5.0, G_PI, G_LN2, 1e-3, 1e6, 2.2, -100.0,
        GWY_SQRT3, -GWY_SQRT3, 3.0, -1.3, 0.4, 4.4, 7.5, -6.0,
    };

    GwySelection *sel = gwy_selection_quad_new();
    g_assert_cmpuint(gwy_selection_get_object_size(sel), ==, 8);
    assert_selection_content(sel, data, 0, 0.0);

    gwy_selection_set_max_objects(sel, 3);
    test_selection_set_data(sel, data, G_N_ELEMENTS(data));
    test_selection_append(sel, data, G_N_ELEMENTS(data));
    test_selection_get_object(sel, data, G_N_ELEMENTS(data));
    g_assert_finalize_object(sel);
}

static GwySelection*
create_quad_selection_for_serialization(void)
{
    const gdouble data[16] = {
        1.0, -5.0, G_PI, G_LN2, 1e-3, 1e6, 2.2, -100.0,
        GWY_SQRT3, -GWY_SQRT3, 3.0, -1.3, 0.4, 4.4, 7.5, -6.0,
    };

    GwySelection *sel = gwy_selection_quad_new();
    gwy_selection_set_max_objects(sel, 5);
    gwy_selection_set_data(sel, 2, data);

    return sel;
}

void
test_selection_quad_serialization(void)
{
    test_selection_serialization(create_quad_selection_for_serialization(), NULL);
}

void
test_selection_quad_copy(void)
{
    test_selection_copy(create_quad_selection_for_serialization(), NULL);
}

void
test_selection_quad_assign(void)
{
    test_selection_assign(create_quad_selection_for_serialization(), NULL);
}

void
test_selection_range_basic(void)
{
    const gdouble data[6] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, 1e6 };

    GwySelection *sel = gwy_selection_range_new();
    g_assert_cmpuint(gwy_selection_get_object_size(sel), ==, 2);
    assert_selection_content(sel, data, 0, 0.0);

    gwy_selection_set_max_objects(sel, 4);
    test_selection_set_data(sel, data, G_N_ELEMENTS(data));
    test_selection_append(sel, data, G_N_ELEMENTS(data));
    test_selection_get_object(sel, data, G_N_ELEMENTS(data));
    g_assert_finalize_object(sel);
}

static GwySelection*
create_range_selection_for_serialization(void)
{
    const gdouble data[6] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, 1e6 };

    GwySelection *sel = gwy_selection_range_new();
    gwy_selection_set_max_objects(sel, 4);
    gwy_selection_set_data(sel, 3, data);

    return sel;
}

static void
set_range_selection_properties(GwySelection *selection)
{
    gwy_selection_range_set_orientation(GWY_SELECTION_RANGE(selection), GWY_ORIENTATION_VERTICAL);
}

void
test_selection_range_serialization(void)
{
    test_selection_serialization(create_range_selection_for_serialization(), set_range_selection_properties);
}

void
test_selection_range_copy(void)
{
    test_selection_copy(create_range_selection_for_serialization(), set_range_selection_properties);
}

void
test_selection_range_assign(void)
{
    test_selection_assign(create_range_selection_for_serialization(), set_range_selection_properties);
}

void
test_selection_rectangle_basic(void)
{
    const gdouble data[8] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, -1e-4, 1e6, -1e7 };

    GwySelection *sel = gwy_selection_rectangle_new();
    g_assert_cmpuint(gwy_selection_get_object_size(sel), ==, 4);
    assert_selection_content(sel, data, 0, 0.0);

    gwy_selection_set_max_objects(sel, 2);
    test_selection_set_data(sel, data, G_N_ELEMENTS(data));
    test_selection_append(sel, data, G_N_ELEMENTS(data));
    test_selection_get_object(sel, data, G_N_ELEMENTS(data));
    g_assert_finalize_object(sel);
}

static GwySelection*
create_rectangle_selection_for_serialization(void)
{
    const gdouble data[8] = { 1.0, -5.0, G_PI, G_LN2, 1e-3, -1e-4, 1e6, -1e7 };

    GwySelection *sel = gwy_selection_rectangle_new();
    gwy_selection_set_max_objects(sel, 4);
    gwy_selection_set_data(sel, 2, data);

    return sel;
}

void
test_selection_rectangle_serialization(void)
{
    test_selection_serialization(create_rectangle_selection_for_serialization(), NULL);
}

void
test_selection_rectangle_copy(void)
{
    test_selection_copy(create_rectangle_selection_for_serialization(), NULL);
}

void
test_selection_rectangle_assign(void)
{
    test_selection_assign(create_rectangle_selection_for_serialization(), NULL);
}

typedef struct {
    gdouble previous_xy[8];
    gboolean have_previous;
    gint hint;
} SelectionChangedData;

static void
selection_changed(GwySelection *selection, gint hint, SelectionChangedData *scdata)
{
    g_assert_cmpint(hint, ==, scdata->hint);
    gdouble xy[8];
    gboolean have_previous = gwy_selection_get_changed_object(selection, xy);
    g_assert_cmpint(have_previous, ==, scdata->have_previous);
    if (have_previous) {
        guint n = gwy_selection_get_object_size(selection);
        for (guint i = 0; i < n; i++)
            g_assert_cmpfloat(xy[i], ==, scdata->previous_xy[i]);
    }
}

void
test_selection_get_changed_object(void)
{
    gdouble xy[4];

    GwySelection *selection = gwy_selection_point_new();
    gwy_selection_set_max_objects(selection, 20);
    SelectionChangedData scdata;
    g_signal_connect(selection, "changed", G_CALLBACK(selection_changed), &scdata);

    /* Adding object has no previous coordinates. */
    xy[0] = 1.1;
    xy[1] = -5.0;
    scdata.have_previous = FALSE;
    scdata.hint = 0;
    gwy_selection_set_object(selection, -1, xy);

    /* Changing a single object has previous coordinates. */
    gwy_assign(scdata.previous_xy, xy, 2);
    xy[0] = G_LN2;
    xy[1] = 0.0;
    scdata.have_previous = TRUE;
    scdata.hint = 0;
    gwy_selection_set_object(selection, 0, xy);

    /* Deleting a single object has previous coordinates. */
    gwy_assign(scdata.previous_xy, xy, 2);
    scdata.have_previous = TRUE;
    scdata.hint = -1;
    gwy_selection_delete_object(selection, 0);

    scdata.have_previous = FALSE;
    scdata.hint = -1;
    xy[0] = 4.4;
    xy[1] = G_PI;
    xy[2] = 3.7;
    xy[3] = 0.6;
    gwy_selection_set_data(selection, 2, xy);

    gwy_assign(scdata.previous_xy, xy + 2, 2);
    scdata.have_previous = TRUE;
    scdata.hint = -1;
    gwy_selection_delete_object(selection, 1);

    gwy_assign(scdata.previous_xy, xy, 2);
    scdata.have_previous = TRUE;
    scdata.hint = -1;
    gwy_selection_clear(selection);

    g_assert_finalize_object(selection);
}

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