/*
 *  $Id: graph_polylevel.c 30047 2026-06-11 08:30:45Z yeti-dn $
 *  Copyright (C) 2019-2023 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 <glib/gi18n-lib.h>
#include <stdlib.h>
#include <string.h>
#include <gwy.h>

typedef enum {
    OUTPUT_DATA_FIT = 0,
    OUTPUT_LEVELLED = 1,
    OUTPUT_NTYPES
} OutputType;

enum {
    PARAM_CURVE,
    PARAM_RANGE_FROM,
    PARAM_RANGE_TO,
    PARAM_ALL,
    PARAM_ORDER,
    PARAM_OUTPUT_TYPE,
    PARAM_TARGET_GRAPH,
    WIDGET_RESULTS,
};

typedef struct {
    GwyParams *params;
    GwyGraphModel *gmodel;
    GwyGraphModel *result;
} ModuleArgs;

typedef struct {
    ModuleArgs *args;
    GtkWidget *dialog;
    GwyResults *results;
    GwyParamTable *table;
} ModuleGUI;

static gboolean         module_register     (void);
static GwyParamDef*     define_module_params(void);
static void             module_main         (GwyGraph *graph);
static GwyDialogOutcome run_gui             (ModuleArgs *args,
                                             GwyFile *data);
static void             execute             (ModuleArgs *args,
                                             GwyResults *results);
static void             param_changed       (ModuleGUI *gui,
                                             gint id);
static GwyResults*      create_results      (GwyUnit *xunit,
                                             GwyUnit *yunit);
static void             preview             (gpointer user_data);

static const gchar* fitresults[] = { "p0", "p1", "p2", "p3", "p4", "p5" };

static const GwyEnum output_types[] = {
    { N_("Data + fit"),      OUTPUT_DATA_FIT,   },
    { N_("Leveled data"),    OUTPUT_LEVELLED,   },
};

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Remove polynomial background"),
    "Petr Klapetek <klapetek@gwyddion.net>",
    "1.2",
    "David Nečas (Yeti) & Petr Klapetek",
    "2021",
};

GWY_MODULE_QUERY2(module_info, graph_polylevel)

static gboolean
module_register(void)
{
    gwy_graph_func_register("graph_polylevel",
                            module_main,
                            N_("/_Force Distance/Remove _Polynomial Background..."),
                            NULL,
                            GWY_MENU_FLAG_GRAPH_CURVE,
                            N_("Remove polynomial background from FZ curve"));

    return TRUE;
}

static GwyParamDef*
define_module_params(void)
{
    static GwyParamDef *paramdef = NULL;

    if (paramdef)
        return paramdef;

    paramdef = gwy_param_def_new();
    gwy_param_def_set_function_name(paramdef, gwy_graph_func_current());
    gwy_param_def_add_graph_curve(paramdef, PARAM_CURVE, "curve", _("Curve to fit"));
    gwy_param_def_add_boolean(paramdef, PARAM_ALL, "all", _("_All curves"), FALSE);
    gwy_param_def_add_int(paramdef, PARAM_ORDER, "order", _("_Degree"), 0, 5, 2);
    gwy_param_def_add_gwyenum(paramdef, PARAM_OUTPUT_TYPE, "output_type", _("_Output"),
                              output_types, G_N_ELEMENTS(output_types), OUTPUT_DATA_FIT);
    gwy_param_def_add_target_graph(paramdef, PARAM_TARGET_GRAPH, "target_graph", NULL);
    /* Not saved to settings. */
    gwy_param_def_add_double(paramdef, PARAM_RANGE_FROM, NULL, _("Range"), -G_MAXDOUBLE, G_MAXDOUBLE, 0.0);
    gwy_param_def_add_double(paramdef, PARAM_RANGE_TO, NULL, NULL, -G_MAXDOUBLE, G_MAXDOUBLE, 0.0);

    return paramdef;
}

static void
module_main(GwyGraph *graph)
{
    GwyDialogOutcome outcome;
    GwyFile *data;
    ModuleArgs args;

    gwy_data_browser_get_current(GWY_APP_FILE, &data, 0);
    gwy_clear1(args);
    args.params = gwy_params_new_from_settings(define_module_params());
    args.gmodel = gwy_graph_get_model(graph);

    args.result = gwy_graph_model_new_alike(args.gmodel);

    outcome = run_gui(&args, data);
    gwy_params_save_to_settings(args.params);

    if (outcome == GWY_DIALOG_CANCEL)
        goto end;
    if (outcome != GWY_DIALOG_HAVE_RESULT)
        execute(&args, NULL);

end:
    g_object_unref(args.params);
    g_object_unref(args.result);
}

static GwyDialogOutcome
run_gui(ModuleArgs *args, GwyFile *data)
{
    GwyDialogOutcome outcome;
    GtkWidget *hbox, *graph;
    GwyDialog *dialog;
    GwyParamTable *table;
    GwyAppDataId target_graph_id;
    GwyUnit *xunit, *yunit;
    ModuleGUI gui;

    /* This is to get the target graph filter right. */
    execute(args, NULL);

    gwy_clear1(gui);
    gui.args = args;
    g_object_set(args->result, "label-visible", FALSE, NULL);

    g_object_get(args->gmodel, "unit-x", &xunit, "unit-y", &yunit, NULL);
    gui.results = create_results(xunit, yunit);

    gui.dialog = gwy_dialog_new(_("Remove Polynomial Background"));
    dialog = GWY_DIALOG(gui.dialog);
    gwy_dialog_add_stock_buttons(dialog, GWY_STOCK_RESET, GWY_STOCK_CANCEL, GWY_STOCK_OK, 0);
    gwy_dialog_have_result(dialog);

    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_container_set_border_width(GTK_CONTAINER(hbox), 4);
    gwy_dialog_add_content(GWY_DIALOG(gui.dialog), hbox, FALSE, FALSE, 0);

    graph = gwy_graph_new(args->result);
    gtk_widget_set_size_request(graph, 480, 300);
    gtk_box_pack_end(GTK_BOX(hbox), graph, TRUE, TRUE, 0);
    gwy_graph_enable_user_input(GWY_GRAPH(graph), FALSE);

    table = gui.table = gwy_param_table_new(args->params);

    gwy_param_table_append_graph_curve(table, PARAM_CURVE, args->gmodel);
    gwy_param_table_append_checkbox(table, PARAM_ALL);
    gwy_create_graph_xrange_with_params(table, PARAM_RANGE_FROM, PARAM_RANGE_TO, GWY_GRAPH(graph), args->gmodel);
    gwy_param_table_append_slider(table, PARAM_ORDER);
    gwy_param_table_append_target_graph(table, PARAM_TARGET_GRAPH, args->result);
    gwy_param_table_append_combo(table, PARAM_OUTPUT_TYPE);
    gwy_param_table_append_header(table, -1, _("Fit results"));
    gwy_param_table_append_resultsv(table, WIDGET_RESULTS, gui.results, fitresults, G_N_ELEMENTS(fitresults));

    gtk_box_pack_start(GTK_BOX(hbox), gwy_param_table_widget(table), FALSE, TRUE, 0);


    g_signal_connect_swapped(table, "param-changed", G_CALLBACK(param_changed), &gui);

    gwy_dialog_set_preview_func(dialog, GWY_PREVIEW_IMMEDIATE, preview, &gui, NULL);
    outcome = gwy_dialog_run(dialog);

    if (outcome == GWY_DIALOG_CANCEL)
        goto end;
    if (outcome != GWY_DIALOG_HAVE_RESULT)
        execute(args, gui.results);

    g_object_set(args->result, "label-visible", TRUE, NULL);

    target_graph_id = gwy_params_get_data_id(args->params, PARAM_TARGET_GRAPH);
    gwy_data_browser_get_current(GWY_APP_FILE, &data, 0);
    gwy_app_add_graph_or_curves(args->result, data, &target_graph_id, 1);

end:
    g_object_unref(gui.results);
    g_object_unref(xunit);
    g_object_unref(yunit);

    return outcome;
}

static void
param_changed(ModuleGUI *gui, gint id)
{
    ModuleArgs *args = gui->args;
    GwyParams *params = args->params;

    if (id < 0 || id == PARAM_CURVE || id == PARAM_ALL) {
        gint curve = gwy_params_get_int(params, PARAM_CURVE);
        gwy_graph_model_remove_all_curves(args->result);
        gwy_graph_model_add_curve(args->result, gwy_graph_model_get_curve(args->gmodel, curve));
    }
    if (id != PARAM_TARGET_GRAPH)
        gwy_dialog_invalidate(GWY_DIALOG(gui->dialog));
}

static void
preview(gpointer user_data)
{
    ModuleGUI *gui = (ModuleGUI*)user_data;

    execute(gui->args, gui->results);
    gwy_dialog_have_result(GWY_DIALOG(gui->dialog));
    gwy_param_table_results_fill(gui->table, WIDGET_RESULTS);
}

static void
execute(ModuleArgs *args, GwyResults *results)
{
    GwyParams *params = args->params;
    GwyGraphModel *gmodel = args->gmodel, *result = args->result;
    gboolean all_curves = gwy_params_get_boolean(params, PARAM_ALL);
    gint degree = gwy_params_get_int(params, PARAM_ORDER);
    OutputType output_type = gwy_params_get_enum(params, PARAM_OUTPUT_TYPE);

    gint curve = gwy_params_get_int(params, PARAM_CURVE);
    gint ifrom = (all_curves ? 0 : curve);
    gint ito = (all_curves ? gwy_graph_model_get_n_curves(gmodel) : curve+1);
    gdouble from = gwy_params_get_double(params, PARAM_RANGE_FROM);
    gdouble to = gwy_params_get_double(params, PARAM_RANGE_TO);

    const gdouble *xdata, *ydata;
    gdouble *nxdata, *nydata, x = 0;
    gint ndata;
    GwyGraphCurveModel *gcmodel, *ngcmodel;
    gint i, j, n, fitstart;
    gdouble *param;
    gdouble xmin, xmax, ymin, ymax, allxmin, allxmax;

    gwy_graph_model_remove_all_curves(result);

    gcmodel = gwy_graph_model_get_curve(gmodel, curve);
    xdata = gwy_graph_curve_model_get_xdata(gcmodel);
    ydata = gwy_graph_curve_model_get_ydata(gcmodel);
    ndata = gwy_graph_curve_model_get_n_data(gcmodel);

    n = 0;
    fitstart = -1;
    xmin = ymin = G_MAXDOUBLE;
    xmax = ymax = -G_MAXDOUBLE;
    for (i = 0; i < ndata; i++) {
        if (xdata[i] >= from && xdata[i] < to) {
            if (xdata[i] < xmin)
                xmin = xdata[i];
            if (ydata[i] < ymin)
                ymin = ydata[i];

            if (xdata[i] > xmax)
                xmax = xdata[i];
            if (ydata[i] > ymax)
                ymax = ydata[i];

            if (fitstart < 0)
                fitstart = i;
            n++;
        }
    }
    if (fitstart < 1)
        fitstart = 0;

    param = g_new(gdouble, 6);
    param[0] = (ymin+ymax)/2.0;
    for (i = 1; i < 6; i++) {
        param[i] = 0; //no init yet
    }
    param = gwy_math_fit_poly(xdata + fitstart, ydata + fitstart, n, degree, param);

    if (results) {
        gwy_results_fill_values(results,
                                "p0", param[0],
                                "p1", param[1],
                                "p2", param[2],
                                "p3", param[3],
                                "p4", param[4],
                                "p5", param[5],
                                NULL);
    }

    allxmin = G_MAXDOUBLE;
    allxmax = -G_MAXDOUBLE;

    for (i = ifrom; i < ito; i++) {
        gcmodel = gwy_graph_model_get_curve(gmodel, i);

        ngcmodel = gwy_graph_curve_model_copy(gcmodel);

        xdata = gwy_graph_curve_model_get_xdata(gcmodel);
        ydata = gwy_graph_curve_model_get_ydata(gcmodel);
        ndata = gwy_graph_curve_model_get_n_data(gcmodel);

        nxdata = g_new(gdouble, ndata);
        nydata = g_new(gdouble, ndata);

        if (output_type == OUTPUT_DATA_FIT) {
           for (j = 0; j < ndata; j++) {
               nxdata[j] = xdata[j];
               nydata[j] = ydata[j];

               if (xdata[j] < allxmin)
                   allxmin = xdata[j];
               if (xdata[j] > allxmax)
                   allxmax = xdata[j];
           }
        }
        else {
            for (j = 0; j < ndata; j++) {
                nxdata[j] = xdata[j];
                x = nxdata[j];
                nydata[j] = ydata[j] - (param[0] + x*(param[1] + x*(param[2] + x*(param[3]
                                                                                  + x*(param[4] + x*param[5])))));
           }
        }

        gwy_graph_curve_model_set_data(ngcmodel, nxdata, nydata, ndata);
        g_free(nxdata);
        g_free(nydata);

        g_object_set(ngcmodel, "mode", GWY_GRAPH_CURVE_LINE, NULL);

        if (all_curves) {
            g_object_set(ngcmodel,
                         "color", gwy_graph_get_preset_color(i),
                         NULL);
        }
        else
            g_object_set(ngcmodel, "description", g_strdup(_("FD curve")), NULL);
        gwy_graph_model_add_curve(result, ngcmodel);

        g_object_unref(ngcmodel);
    }

    if (output_type == OUTPUT_DATA_FIT) {
        ngcmodel = gwy_graph_curve_model_new_alike(gcmodel);

        ndata = 100;
        nxdata = gwy_math_linspace(NULL, ndata, allxmin, (allxmax - allxmin)/ndata);
        nydata = g_new(gdouble, ndata);
        for (j = 0; j < ndata; j++) {
            x = nxdata[j];
            nydata[i] = param[0] + x*(param[1] + x*(param[2] + x*(param[3] + x*(param[4] + x*param[5]))));
        }

        gwy_graph_curve_model_set_data(ngcmodel, nxdata, nydata, ndata);
        g_free(nxdata);
        g_free(nydata);

        g_object_set(ngcmodel, "mode", GWY_GRAPH_CURVE_LINE, NULL);

        g_object_set(ngcmodel, "description", g_strdup(_("fit")), NULL);
        gwy_graph_model_add_curve(result, ngcmodel);

        g_object_unref(ngcmodel);
    }
}

static GwyResults*
create_results(GwyUnit *xunit, GwyUnit *yunit)
{
    GwyResults *results;

    results = gwy_results_new();
    gwy_results_add_header(results, N_("Fit results"));
    gwy_results_add_value_z(results, "p0", N_("P0"));
    gwy_results_add_value(results, "p1", N_("P1"), "power-x", -1, "power-z", 1, NULL);
    gwy_results_add_value(results, "p2", N_("P2"), "power-x", -2, "power-z", 1, NULL);
    gwy_results_add_value(results, "p3", N_("P3"), "power-x", -3, "power-z", 1, NULL);
    gwy_results_add_value(results, "p4", N_("P4"), "power-x", -4, "power-z", 1, NULL);
    gwy_results_add_value(results, "p5", N_("P5"), "power-x", -5, "power-z", 1, NULL);

    gwy_results_set_unit(results, "x", xunit);
    gwy_results_set_unit(results, "z", yunit);

    return results;
}

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