/*
 *  $Id: graphops.c 29754 2026-04-09 09:35:42Z yeti-dn $
 *  Copyright (C) 2003-2025 David Necas (Yeti), Petr Klapetek, Daniil Bratashov.
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net, dn2010@gmail.com.
 *
 *  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 <glib/gi18n-lib.h>
#include <gwy.h>

static gboolean module_register (void);
static void     module_main     (GwyGraph *graph);
static void     level_one_curve (GwyGraphCurveModel *gcmodel,
                                 GArray *buffer);
static void     filter_one_curve(GwyGraphCurveModel *gcmodel,
                                 GArray *buffer);
static void     flip_one_curve  (GwyGraphCurveModel *gcmodel,
                                 GArray *buffer);
static void     invert_one_curve(GwyGraphCurveModel *gcmodel,
                                 GArray *buffer);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Simple graph operations (inversion, levelling or filtering)."),
    "Yeti <yeti@gwyddion.net>",
    "1.0",
    "David Nečas (Yeti) & Petr Klapetek & Daniil Bratashov",
    "2025",
};

GWY_MODULE_QUERY2(module_info, graphops)

static gboolean
module_register(void)
{
    gwy_graph_func_register("graph_level",
                            module_main,
                            N_("/_Correct Data/_Level"),
                            GWY_ICON_GRAPH_LEVEL,
                            GWY_MENU_FLAG_GRAPH_CURVE,
                            N_("Level graph curves"));
    gwy_graph_func_register("graph_filter",
                            module_main,
                            N_("/_Correct Data/_Smooth"),
                            GWY_ICON_GRAPH_FILTER,
                            GWY_MENU_FLAG_GRAPH_CURVE,
                            N_("Remove noise from graph curves"));
    gwy_graph_func_register("graph_flip",
                            module_main,
                            N_("/_Basic Operations/_Flip"),
                            NULL,
                            GWY_MENU_FLAG_GRAPH_CURVE,
                            N_("Flip graph along the y axis"));
    gwy_graph_func_register("graph_invert",
                            module_main,
                            N_("/_Basic Operations/_Invert"),
                            NULL,
                            GWY_MENU_FLAG_GRAPH_CURVE,
                            N_("Invert graph curves"));

    return TRUE;
}

static void
module_main(GwyGraph *graph)
{
    const gchar *name = gwy_graph_func_current();
    GwyFile *data;
    GQuark quark;

    gwy_data_browser_get_current(GWY_APP_FILE, &data,
                                 GWY_APP_GRAPH_MODEL_KEY, &quark,
                                 0);
    gwy_app_undo_qcheckpointv(GWY_DICT(data), 1, &quark);

    GwyGraphModel *gmodel = gwy_graph_get_model(graph);
    guint ncurves = gwy_graph_model_get_n_curves(gmodel);
    GArray *buffer = g_array_new(FALSE, FALSE, sizeof(gdouble));

    for (guint i = 0; i < ncurves; i++) {
        GwyGraphCurveModel *gcmodel = gwy_graph_model_get_curve(gmodel, i);
        g_array_set_size(buffer, 0);

        if (gwy_strequal(name, "graph_level"))
            level_one_curve(gcmodel, buffer);
        else if (gwy_strequal(name, "graph_filter"))
            filter_one_curve(gcmodel, buffer);
        else if (gwy_strequal(name, "graph_invert"))
            invert_one_curve(gcmodel, buffer);
        else if (gwy_strequal(name, "graph_flip"))
            flip_one_curve(gcmodel, buffer);
        else {
            g_assert_not_reached();
        }
    }

    for (guint i = 0; i < ncurves; i++) {
        GwyGraphCurveModel *gcmodel = gwy_graph_model_get_curve(gmodel, i);
        g_signal_emit_by_name(gcmodel, "data-changed");
    }

    g_array_free(buffer, TRUE);
}

static void
level_one_curve(GwyGraphCurveModel *gcmodel, GArray *buffer)
{
    const gdouble *xdata = gwy_graph_curve_model_get_xdata(gcmodel);
    const gdouble *ydata = gwy_graph_curve_model_get_ydata(gcmodel);
    guint n = gwy_graph_curve_model_get_n_data(gcmodel);

    g_array_append_vals(buffer, ydata, n);
    gdouble *y = &g_array_index(buffer, gdouble, 0);

    gdouble coeff[2];
    gwy_math_fit_poly(xdata, y, n, 1, coeff);

    for (guint i = 0; i < n; i++)
        y[i] -= coeff[0] + coeff[1]*xdata[i];

    gwy_graph_curve_model_set_data(gcmodel, xdata, y, n);
}

static void
filter_one_curve(GwyGraphCurveModel *gcmodel, GArray *buffer)
{
    enum {
        KERNEL_SIZE = 5
    };
    const gdouble *xdata = gwy_graph_curve_model_get_xdata(gcmodel);
    const gdouble *ydata = gwy_graph_curve_model_get_ydata(gcmodel);
    guint n = gwy_graph_curve_model_get_n_data(gcmodel);

    g_array_append_vals(buffer, ydata, n);
    gdouble *y = &g_array_index(buffer, gdouble, 0);

    for (guint i = 0; i < n; i++) {
        guint min = (i < KERNEL_SIZE ? 0 : i - KERNEL_SIZE);
        guint max = (i + KERNEL_SIZE+1 > n ? n : i + KERNEL_SIZE+1);
        gdouble s = 0.0;
        for (guint j = min; j < max; j++)
            s += ydata[i];
        y[i] = s/(max - min);
    }

    gwy_graph_curve_model_set_data(gcmodel, xdata, y, n);
}

static void
flip_one_curve(GwyGraphCurveModel *gcmodel, GArray *buffer)
{
    const gdouble *xdata = gwy_graph_curve_model_get_xdata(gcmodel);
    const gdouble *ydata = gwy_graph_curve_model_get_ydata(gcmodel);
    guint n = gwy_graph_curve_model_get_n_data(gcmodel);

    g_array_append_vals(buffer, xdata, n);
    gdouble *x = &g_array_index(buffer, gdouble, 0);

    for (guint i = 0; i < n; i++)
        x[i] = -x[i];

    gwy_graph_curve_model_set_data(gcmodel, x, ydata, n);
}

static void
invert_one_curve(GwyGraphCurveModel *gcmodel, GArray *buffer)
{
    const gdouble *xdata = gwy_graph_curve_model_get_xdata(gcmodel);
    const gdouble *ydata = gwy_graph_curve_model_get_ydata(gcmodel);
    guint n = gwy_graph_curve_model_get_n_data(gcmodel);

    g_array_append_vals(buffer, ydata, n);
    gdouble *y = &g_array_index(buffer, gdouble, 0);

    for (guint i = 0; i < n; i++)
        y[i] = -y[i];

    gwy_graph_curve_model_set_data(gcmodel, xdata, y, n);
}

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