/*
 *  $Id: gradient.c 29754 2026-04-09 09:35:42Z 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"

void
test_gradient_inventory(void)
{
    static const GwyGradientPoint gradient_point_red = { 0.5, { 1, 0, 0, 1 } };
    const GwyInventoryItemType *item_type;
    GwyGradient *gradient;
    GwyResource *resource;

    gwy_init();

    GwyInventory *gradients = gwy_gradients();
    g_assert_true(GWY_IS_INVENTORY(gradients));
    item_type = gwy_inventory_get_item_type(gradients);
    g_assert_nonnull(item_type);
    g_assert_cmpuint(item_type->type, ==, GWY_TYPE_GRADIENT);
    g_assert_true(gwy_inventory_can_make_copies(gradients));
    g_assert_cmpstr(gwy_inventory_get_default_item_name(gradients), ==, GWY_GRADIENT_DEFAULT);

    item_type = gwy_resource_class_get_item_type(g_type_class_peek(GWY_TYPE_GRADIENT));
    g_assert_nonnull(item_type);
    g_assert_cmpuint(item_type->type, ==, GWY_TYPE_GRADIENT);
    gradient = gwy_gradients_get_gradient(NULL);
    g_assert_true(GWY_IS_GRADIENT(gradient));
    resource = GWY_RESOURCE(gradient);
    g_assert_cmpstr(gwy_resource_get_name(resource), ==, GWY_GRADIENT_DEFAULT);
    g_assert_true(gwy_resource_is_managed(resource));
    g_assert_false(gwy_resource_is_modifiable(resource));

    gradient = (GwyGradient*)gwy_inventory_get_default_item(gradients);
    g_assert_true(GWY_IS_GRADIENT(gradient));
    resource = GWY_RESOURCE(gradient);
    g_assert_cmpstr(gwy_resource_get_name(resource), ==, GWY_GRADIENT_DEFAULT);
    g_assert_true(gwy_resource_is_managed(resource));
    g_assert_false(gwy_resource_is_modifiable(resource));

    g_assert_false(gwy_resource_get_preferred(resource));
    gwy_resource_set_preferred(resource, TRUE);
    g_assert_true(gwy_resource_get_preferred(resource));

    gwy_inventory_new_item(gradients, GWY_GRADIENT_DEFAULT, "Another");
    g_assert_cmpuint(gwy_inventory_get_n_items(gradients), ==, 2);
    gradient = (GwyGradient*)gwy_inventory_get_item(gradients, "Another");
    resource = GWY_RESOURCE(gradient);
    g_assert_cmpstr(gwy_resource_get_name(resource), ==, "Another");
    g_assert_true(gwy_resource_is_managed(resource));
    g_assert_true(gwy_resource_is_modifiable(resource));
    g_assert_true(gwy_resource_is_modified(resource));
    gwy_gradient_insert_point_sorted(gradient, &gradient_point_red);
    g_assert_cmpuint(gwy_gradient_get_n_points(gradient), ==, 3);

    g_object_ref(gradient);
    gwy_inventory_delete_item(gradients, "Another");
    g_assert_true(GWY_IS_GRADIENT(gradient));
    g_assert_false(gwy_resource_is_managed(resource));
    g_assert_true(gwy_resource_is_modifiable(resource));
    g_object_unref(gradient);
}

void
test_gradient_edit(void)
{
    static const GwyGradientPoint gradient_point_red0 = { 0, { 0.8, 0, 0, 1 } };
    static const GwyGradientPoint gradient_point_p1 = { 0.5, { 0, 0.6, 0, 1 } };
    static const GwyGradientPoint gradient_point_p2 = { 0.6, { 0, 0.5, 0, 1 } };
    static const GwyGradientPoint gradient_point_blue1 = { 1, { 0, 0, 1, 1 } };

    GwyGradient *gradient = g_object_new(GWY_TYPE_GRADIENT, NULL);
    GwyResource *resource = GWY_RESOURCE(gradient);

    gwy_resource_rename(resource, "Unstable");
    g_assert_cmpstr(gwy_resource_get_name(resource), ==, "Unstable");

    gwy_gradient_set_point_color(gradient, 0, &gradient_point_red0.color);
    gwy_gradient_set_point_color(gradient, 1, &gradient_point_blue1.color);
    gwy_gradient_insert_point(gradient, 1, &gradient_point_p2);
    g_assert_cmpuint(gwy_gradient_get_n_points(gradient), ==, 3);
    GwyGradientPoint pt = gwy_gradient_get_point(gradient, 1);
    g_assert_cmpint(memcmp(&pt, &gradient_point_p2, sizeof(GwyGradientPoint)), ==, 0);

    gwy_gradient_insert_point(gradient, 1, &gradient_point_p1);
    g_assert_cmpuint(gwy_gradient_get_n_points(gradient), ==, 4);
    pt = gwy_gradient_get_point(gradient, 1);
    g_assert_cmpint(memcmp(&pt, &gradient_point_p1, sizeof(GwyGradientPoint)), ==, 0);
    pt = gwy_gradient_get_point(gradient, 2);
    g_assert_cmpint(memcmp(&pt, &gradient_point_p2, sizeof(GwyGradientPoint)), ==, 0);

    GwyRGBA color;
    gwy_gradient_get_color(gradient, gradient_point_red0.x, &color);
    g_assert_cmpint(memcmp(&color, &gradient_point_red0.color, sizeof(GwyRGBA)), ==, 0);
    gwy_gradient_get_color(gradient, gradient_point_p1.x, &color);
    g_assert_cmpint(memcmp(&color, &gradient_point_p1.color, sizeof(GwyRGBA)), ==, 0);
    gwy_gradient_get_color(gradient, gradient_point_p2.x, &color);
    g_assert_cmpint(memcmp(&color, &gradient_point_p2.color, sizeof(GwyRGBA)), ==, 0);
    gwy_gradient_get_color(gradient, gradient_point_blue1.x, &color);
    g_assert_cmpint(memcmp(&color, &gradient_point_blue1.color, sizeof(GwyRGBA)), ==, 0);

    guint n;
    const GwyGradientPoint *gdata = gwy_gradient_get_points(gradient, &n);
    g_assert_cmpuint(n, ==, 4);
    g_assert_nonnull(gdata);
    GwyGradientPoint *mydata = g_memdup(gdata, n*sizeof(GwyGradientPoint));

    gwy_gradient_delete_point(gradient, 1);
    g_assert_cmpuint(gwy_gradient_get_n_points(gradient), ==, 3);
    pt = gwy_gradient_get_point(gradient, 1);
    g_assert_cmpint(memcmp(&pt, &gradient_point_p2, sizeof(GwyGradientPoint)), ==, 0);

    gwy_gradient_delete_point(gradient, 1);
    g_assert_cmpuint(gwy_gradient_get_n_points(gradient), ==, 2);

    gwy_gradient_set_points(gradient, n, mydata);
    gdata = gwy_gradient_get_points(gradient, &n);
    g_assert_cmpuint(n, ==, 4);
    g_assert_nonnull(gdata);
    g_assert_cmpint(memcmp(gdata, mydata, n*sizeof(GwyGradientPoint)), ==, 0);

    g_free(mydata);
    g_assert_finalize_object(gradient);
}

static void
gradient_assert_equal(GObject *object, GObject *reference)
{
    g_assert_true(GWY_IS_GRADIENT(object));
    g_assert_true(GWY_IS_GRADIENT(reference));

    GwyGradient *gradient = GWY_GRADIENT(object), *gradient_ref = GWY_GRADIENT(reference);
    g_assert_cmpuint(gwy_gradient_get_n_points(gradient), ==, gwy_gradient_get_n_points(gradient_ref));
    guint n = gwy_gradient_get_n_points(gradient_ref);
    for (guint i = 0; i < n; i++) {
        GwyGradientPoint pt = gwy_gradient_get_point(gradient, i);
        GwyGradientPoint pt_ref = gwy_gradient_get_point(gradient_ref, i);
        g_assert_cmpfloat(pt.x, ==, pt_ref.x);
        g_assert_true(gwy_rgba_equal(&pt.color, &pt_ref.color));
    }
}

static GwyGradient*
create_gradient_for_serialisation(void)
{
    static const GwyGradientPoint points[4] = {
        { 0.0, { 0.8, 0, 0, 1 }, },
        { 0.5, { 0, 0.6, 0, 1 }, },
        { 0.6, { 0, 0.5, 0, 1 }, },
        { 1.0, { 0, 0, 1, 1 }, },
    };
    GwyGradient *gradient = g_object_new(GWY_TYPE_GRADIENT, NULL);
    gwy_resource_rename(GWY_RESOURCE(gradient), "Name");
    gwy_gradient_set_points(gradient, G_N_ELEMENTS(points), points);

    return gradient;
}

void
test_gradient_serialization(void)
{
    GwyGradient *gradient = create_gradient_for_serialisation();
    GObject *reconstructed = serialize_object_and_back(G_OBJECT(gradient), gradient_assert_equal, TRUE, NULL);
    g_assert_cmpstr(gwy_resource_get_name(GWY_RESOURCE(reconstructed)), ==, "Name");
    g_assert_true(gwy_resource_is_modifiable(GWY_RESOURCE(reconstructed)));
    g_assert_false(gwy_resource_is_managed(GWY_RESOURCE(reconstructed)));

    g_assert_finalize_object(reconstructed);
    g_assert_finalize_object(gradient);
}

void
test_gradient_copy(void)
{
    GwyGradient *gradient = create_gradient_for_serialisation();
    GObject *copy = G_OBJECT(gwy_serializable_copy(GWY_SERIALIZABLE(gradient)));
    g_assert_cmpstr(gwy_resource_get_name(GWY_RESOURCE(copy)), ==, "Name");
    g_assert_true(gwy_resource_is_modifiable(GWY_RESOURCE(copy)));
    g_assert_false(gwy_resource_is_managed(GWY_RESOURCE(copy)));

    g_assert_finalize_object(copy);
    g_assert_finalize_object(gradient);
}

void
test_gradient_assign(void)
{
    GwyGradient *gradient = create_gradient_for_serialisation();
    GObject *copy = g_object_new(GWY_TYPE_GRADIENT, "name", "Different", NULL);
    gwy_serializable_assign(GWY_SERIALIZABLE(copy), GWY_SERIALIZABLE(gradient));
    // Name is unchanged. This is the documented behaviour.
    g_assert_cmpstr(gwy_resource_get_name(GWY_RESOURCE(copy)), ==, "Different");
    g_assert_true(gwy_resource_is_modifiable(GWY_RESOURCE(copy)));
    g_assert_false(gwy_resource_is_managed(GWY_RESOURCE(copy)));

    g_assert_finalize_object(copy);
    g_assert_finalize_object(gradient);
}

void
test_gradient_get_color(void)
{
    /* XXX: We keep A = 1 here to avoid the messy weighting for general A. These are tested for RGBA interpolation
     * so let just assume Gradient does it the same way. We are more concerned with finding the right interpolation
     * interval, etc. */
    static const GwyGradientPoint gradient_point_red0 = { 0, { 1, 0, 0, 1 } };
    static const GwyGradientPoint gradient_point_green = { 0.25, { 0, 1, 0, 1 } };
    static const GwyGradientPoint gradient_point_grey = { 0.7, { 0.4, 0.4, 0.4, 1 } };
    static const GwyGradientPoint gradient_point_blue1 = { 1, { 0, 0, 1, 1 } };

    GwyGradient *gradient = g_object_new(GWY_TYPE_GRADIENT, NULL);
    gwy_gradient_set_point_color(gradient, 0, &gradient_point_red0.color);
    gwy_gradient_set_point_color(gradient, 1, &gradient_point_blue1.color);
    gwy_gradient_insert_point(gradient, 1, &gradient_point_green);
    gwy_gradient_insert_point(gradient, 2, &gradient_point_grey);
    g_assert_cmpuint(gwy_gradient_get_n_points(gradient), ==, 4);

    guint nsamples = 120;
    for (guint i = 0; i < nsamples; i++) {
        gdouble t = i/(nsamples - 1.0);
        GwyRGBA color;
        gwy_gradient_get_color(gradient, t, &color);

        gdouble expected_r = 0.0, expected_g = 0.0, expected_b = 0.0, expected_a = 1.0;
        if (t <= 0.25) {
            t /= 0.25;
            expected_r = 1.0 - t;
            expected_g = t;
        }
        else if (t <= 0.7) {
            t = (t - 0.25)/(0.7 - 0.25);
            expected_g = 1.0 - t + 0.4*t;
            expected_r = 0.4*t;
            expected_b = 0.4*t;
        }
        else {
            t = (t - 0.7)/(1.0 - 0.7);
            expected_g = 0.4*(1.0 - t);
            expected_r = 0.4*(1.0 - t);
            expected_b = 0.4 + 0.6*t;
        }

        g_assert_cmpfloat_with_epsilon(color.r, expected_r, 1e-12);
        g_assert_cmpfloat_with_epsilon(color.g, expected_g, 1e-12);
        g_assert_cmpfloat_with_epsilon(color.b, expected_b, 1e-12);
        g_assert_cmpfloat(color.a, ==, expected_a);
    }

    g_assert_finalize_object(gradient);
}

void
test_gradient_sample(void)
{
    static const GwyGradientPoint gradient_point_red0 = { 0, { 1, 0, 0, 1 } };
    static const GwyGradientPoint gradient_point_green = { 0.25, { 0, 1, 0, 1 } };
    static const GwyGradientPoint gradient_point_grey = { 0.7, { 0.4, 0.4, 0.4, 1 } };
    static const GwyGradientPoint gradient_point_blue1 = { 1, { 0, 0, 1, 1 } };

    GwyGradient *gradient = g_object_new(GWY_TYPE_GRADIENT, NULL);
    gwy_gradient_set_point_color(gradient, 0, &gradient_point_red0.color);
    gwy_gradient_set_point_color(gradient, 1, &gradient_point_blue1.color);
    gwy_gradient_insert_point(gradient, 1, &gradient_point_green);
    gwy_gradient_insert_point(gradient, 2, &gradient_point_grey);
    g_assert_cmpuint(gwy_gradient_get_n_points(gradient), ==, 4);

    guint nsamples = 120;
    guint8 *samples = gwy_gradient_sample(gradient, nsamples, NULL);

    for (guint i = 0; i < nsamples; i++) {
        gdouble t = i/(nsamples - 1.0);
        /* Assume get_color() is correct here. We test it independently. */
        GwyRGBA color;
        gwy_gradient_get_color(gradient, t, &color);
        guint expected_r = (guint)floor(255.9999999*color.r);
        guint expected_g = (guint)floor(255.9999999*color.g);
        guint expected_b = (guint)floor(255.9999999*color.b);

        g_assert_cmpuint(samples[4*i+0], ==, expected_r);
        g_assert_cmpuint(samples[4*i+1], ==, expected_g);
        g_assert_cmpuint(samples[4*i+2], ==, expected_b);
        g_assert_cmpuint(samples[4*i+3], ==, 255);
    }

    g_free(samples);
    g_assert_finalize_object(gradient);
}

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