/*
 *  $Id: fit-shape.c 30047 2026-06-11 08:30:45Z yeti-dn $
 *  Copyright (C) 2016-2026 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.
 */

/* TODO:
 * - Gradient adaptation only works for full-range mapping.  Simply enforce
 *   full-range mapping with adapted gradients?
 */

#include "config.h"
#include <glib/gi18n-lib.h>
#include <stdlib.h>
#include <string.h>
#include <gtk/gtk.h>
#include <gwy.h>
#include "../preview.h"

#define RUN_MODES GWY_RUN_INTERACTIVE

#define FIT_GRADIENT_NAME "__GwyFitDiffGradient"

/* Lower symmetric part indexing */
/* i MUST be greater or equal than j */
#define SLi(a, i, j) a[(i)*((i) + 1)/2 + (j)]

enum { NREDLIM = 4096 };

enum { RESPONSE_QUICK_FIT = GWY_RESPONSE_USER };

enum {
    PARAM_FUNCTION,
    PARAM_MASKING,
    PARAM_DISPLAY,
    PARAM_OUTPUT,
    PARAM_REPORT_STYLE,
    PARAM_DIFF_COLOURMAP,
    PARAM_DIFF_EXCLUDED,
    PARAM_DATA,
};

typedef enum {
    FIT_SHAPE_DISPLAY_DATA   = 0,
    FIT_SHAPE_DISPLAY_RESULT = 1,
    FIT_SHAPE_DISPLAY_DIFF   = 2
} FitShapeDisplayType;

typedef enum {
    FIT_SHAPE_OUTPUT_FIT  = 0,
    FIT_SHAPE_OUTPUT_DIFF = 1,
    FIT_SHAPE_OUTPUT_BOTH = 2,
} FitShapeOutputType;

typedef enum {
    FIT_INITIALISED,
    FIT_ESTIMATED,
    FIT_QUICK_FITTED,
    FIT_FITTED,
    FIT_USER_MODIFIED,
    FIT_ESTIMATE_FAILED,
    FIT_QUICK_FIT_FAILED,
    FIT_FIT_FAILED,
    FIT_FIT_CANCELLED,
} FitState;

typedef struct {
    GwyParams *params;
    /* These are always what we display – when run with XYZ data they are just imge previews.  Conversely, a scatter
     * is created for fitting even if the input is images. */
    GwyField *field;
    GwyNield *mask;
    GwyField *result;
    GwyField *diff;
    GwyScatter *scatter;
    /* Function values. */
    gdouble *f;
    /* Cached input properties. */
    GwyDataKind data_kind;
    gboolean same_units;
} ModuleArgs;

/* Struct with data used in fitter functions. */
typedef struct {
    ModuleArgs *args;
    gsize n;
    GwyXYZ *xyz;
} FitShapeContext;

typedef struct {
    ModuleArgs *args;
    GwyFile *data;
    gint id;
    /* These are actually non-GUI and could be separated for some non-interactive use. */
    FitShapeContext *ctx;
    FitState state;
    GwyShapeFitPreset *preset;
    gdouble rss;
    /* This is GUI but we use the fields in data. */
    GwyResults *results;
    GwyGradient *orig_gradient;
    GwyGradient *diff_gradient;
    GwyParamTable *table;
    GtkWidget *dialog;
    GtkWidget *dataview;
    GtkWidget *rss_label;
    GtkWidget *fit_message;
    GtkWidget *revert;
    GtkWidget *recalculate;
    GtkWidget *fit_params;
    GtkWidget *correl_table;
    GtkWidget *secondary_params;
    /* FIXME: This is only needed because secondary parameter calculation wants the correlation table explicitly
     * instead of taking it from the fitter (which is wrong because either we do it immediately after a fit or the
     * user has changed the parameters and the matrix is no longer valid) and for fill_results() (which should be only
     * callable after a successful fit, except we do not keep the fitter around long enough – just fill the results
     * earlier?). */
    gdouble *correl;
} ModuleGUI;

static gboolean         module_register           (void);
static GwyParamDef*     define_module_params      (void);
static GwyDialogOutcome run_gui                   (ModuleArgs *args,
                                                   GwyFile *data,
                                                   gint id);
static void             dialog_response           (ModuleGUI *gui,
                                                   gint response);
static void             param_changed             (ModuleGUI *gui,
                                                   gint id);
static void             module_main_image         (GwyFile *data,
                                                   GwyRunModeFlags mode);
static void             module_main_xyz           (GwyFile *data,
                                                   GwyRunModeFlags mode);
static void             create_output_fields      (ModuleArgs *args,
                                                   GwyFile *data,
                                                   gint id);
static void             create_output_xyz         (ModuleArgs *args,
                                                   GwyFile *data,
                                                   gint id);
static GtkWidget*       basic_tab_new             (ModuleGUI *gui);
static GtkWidget*       parameters_tab_new        (ModuleGUI *gui);
static void             fit_param_table_resize    (ModuleGUI *gui);
static GtkWidget*       correl_tab_new            (ModuleGUI *gui);
static void             fit_correl_table_resize   (ModuleGUI *gui);
static GtkWidget*       secondary_tab_new         (ModuleGUI *gui);
static void             fit_secondary_table_resize(ModuleGUI *gui);
static gboolean         preset_is_available       (const GwyEnum *enumval,
                                                   gpointer user_data);
static void             param_value_activate      (ModuleGUI *gui,
                                                   guint i);
static void             revert_params             (ModuleGUI *gui);
static void             recalculate_image         (ModuleGUI *gui);
static void             update_correl_table       (ModuleGUI *gui,
                                                   GwyNLFitter *fitter);
static void             update_secondary_table    (ModuleGUI *gui);
static void             fit_shape_estimate        (ModuleGUI *gui);
static void             fit_shape_quick_fit       (ModuleGUI *gui);
static void             fit_shape_full_fit        (ModuleGUI *gui);
static void             update_fields             (ModuleGUI *gui);
static void             update_diff_gradient      (ModuleGUI *gui);
static void             update_fit_state          (ModuleGUI *gui);
static void             update_fit_results        (ModuleGUI *gui,
                                                   GwyNLFitter *fitter);
static void             update_context_data       (ModuleGUI *gui);
static GwyNLFitter*     fit                       (GwyShapeFitPreset *preset,
                                                   const FitShapeContext *ctx,
                                                   gdouble *param,
                                                   const gboolean *fixed,
                                                   gdouble *rss,
                                                   GwySetFractionFunc set_fraction,
                                                   GwySetMessageFunc set_message,
                                                   gboolean quick_fit);
static void             calculate_field           (GwyShapeFitPreset *preset,
                                                   const gdouble *params,
                                                   GwyField *field);
static void             create_results            (ModuleGUI *gui);
static void             fill_results              (ModuleGUI *gui);
static void             update_same_units         (ModuleArgs *args);
static void             sanitise_params           (ModuleArgs *args);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Fits predefined geometrical shapes to data."),
    "Yeti <yeti@gwyddion.net>",
    "3.2",
    "David Nečas (Yeti)",
    "2016",
};

GWY_MODULE_QUERY2(module_info, fit_shape)

static gboolean
module_register(void)
{
    gwy_image_func_register("fit_shape",
                            module_main_image,
                            N_("/Measure _Features/_Fit Shape..."),
                            GWY_ICON_FIT_SHAPE,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Fit geometrical shapes"));
    gwy_xyz_func_register("xyz_fit_shape",
                          module_main_xyz,
                          N_("/_Fit Shape..."),
                          GWY_ICON_FIT_SHAPE,
                          RUN_MODES,
                          GWY_MENU_FLAG_XYZ,
                          N_("Fit geometrical shapes"));

    return TRUE;
}

static GwyParamDef*
define_module_params(void)
{
    static const GwyEnum displays[] = {
        { N_("Data"),         FIT_SHAPE_DISPLAY_DATA,   },
        { N_("Fitted shape"), FIT_SHAPE_DISPLAY_RESULT, },
        { N_("Difference"),   FIT_SHAPE_DISPLAY_DIFF,   },
    };
    static const GwyEnum outputs[] = {
        { N_("Fitted shape"), FIT_SHAPE_OUTPUT_FIT,  },
        { N_("Difference"),   FIT_SHAPE_OUTPUT_DIFF, },
        { N_("Both"),         FIT_SHAPE_OUTPUT_BOTH, },
    };
    static GwyParamDef *paramdef = NULL;

    if (paramdef)
        return paramdef;

    paramdef = gwy_param_def_new();
    gwy_param_def_set_function_name(paramdef, "fit_shape");
    /* The default must not require same units because then we could not fall back to it in all cases. */
    gwy_param_def_add_resource(paramdef, PARAM_FUNCTION, "function", _("_Function"),
                               gwy_shape_fit_presets(), "Grating (simple)");
    gwy_param_def_add_enum(paramdef, PARAM_MASKING, "masking", NULL, GWY_TYPE_MASKING_TYPE, GWY_MASK_IGNORE);
    gwy_param_def_add_gwyenum(paramdef, PARAM_DISPLAY, "display", C_("verb", "Display"),
                              displays, G_N_ELEMENTS(displays), FIT_SHAPE_DISPLAY_DIFF);
    gwy_param_def_add_gwyenum(paramdef, PARAM_OUTPUT, "output", _("Output _type"),
                              outputs, G_N_ELEMENTS(outputs), FIT_SHAPE_OUTPUT_BOTH);
    gwy_param_def_add_report_type(paramdef, PARAM_REPORT_STYLE, "report_style", NULL,
                                  GWY_RESULTS_EXPORT_PARAMETERS, GWY_RESULTS_REPORT_COLON | GWY_RESULTS_REPORT_MACHINE);
    gwy_param_def_add_boolean(paramdef, PARAM_DIFF_COLOURMAP, "diff_colourmap",
                              _("Show differences with _adapted color map"), TRUE);
    gwy_param_def_add_boolean(paramdef, PARAM_DIFF_EXCLUDED, "diff_excluded",
                              _("Calculate differences for e_xcluded pixels"), TRUE);
    gwy_param_def_add_scatter_data(paramdef, PARAM_DATA, "data", _("Value to fit"));
    return paramdef;
}

static void
module_main(GwyFile *data, GwyRunModeFlags mode, GwyDataKind data_kind)
{
    GwyDialogOutcome outcome;
    ModuleArgs args;
    gint id;

    g_return_if_fail(mode & RUN_MODES);
    gwy_clear1(args);
    args.data_kind = data_kind;
    if (data_kind == GWY_FILE_XYZ) {
        gwy_data_browser_get_current(GWY_APP_SCATTER, &args.scatter,
                                     GWY_APP_SCATTER_ID, &id,
                                     0);
        g_return_if_fail(args.scatter);
        if (!gwy_scatter_get_n_values(args.scatter))
            return;
    }
    else if (data_kind == GWY_FILE_IMAGE) {
        gwy_data_browser_get_current(GWY_APP_FIELD, &args.field,
                                     GWY_APP_MASK_FIELD, &args.mask,
                                     GWY_APP_FIELD_ID, &id,
                                     0);
        g_return_if_fail(args.field);
    }
    else {
        g_return_if_reached();
    }

    args.params = gwy_params_new_from_settings(define_module_params());
    sanitise_params(&args);
    outcome = run_gui(&args, data, id);
    gwy_params_save_to_settings(args.params);
    /* The user can press OK and we will produce images of whatever currently exists.  This allows just running it and
     * creating an image with the model shape, for instance. */
    if (outcome == GWY_DIALOG_CANCEL)
        goto end;

    if (args.data_kind == GWY_FILE_XYZ)
        create_output_xyz(&args, data, id);
    else
        create_output_fields(&args, data, id);

end:
    g_free(args.f);
    g_object_unref(args.params);
    g_clear_object(&args.field);
    g_clear_object(&args.mask);
    g_clear_object(&args.result);
    g_clear_object(&args.diff);
    g_clear_object(&args.scatter);
}

static void
module_main_image(GwyFile *data, GwyRunModeFlags mode)
{
    module_main(data, mode, GWY_FILE_IMAGE);
}

static void
module_main_xyz(GwyFile *data, GwyRunModeFlags mode)
{
    module_main(data, mode, GWY_FILE_XYZ);
}

static void
create_output_fields(ModuleArgs *args, GwyFile *data, gint id)
{
    FitShapeOutputType output = gwy_params_get_enum(args->params, PARAM_OUTPUT);
    guint64 syncflags = (GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_MASK_COLOR
                         | GWY_FILE_ITEM_REAL_SQUARE | GWY_FILE_ITEM_SELECTIONS);
    gint newid;

    if (output == FIT_SHAPE_OUTPUT_FIT || output == FIT_SHAPE_OUTPUT_BOTH) {
        newid = gwy_file_add_image(data, args->result);
        gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
        gwy_file_sync_items(data, GWY_FILE_IMAGE, id,
                            data, GWY_FILE_IMAGE, newid,
                            syncflags, FALSE);
        gwy_log_add(data, GWY_FILE_IMAGE, id, newid);
        gwy_file_set_title(data, GWY_FILE_IMAGE, newid, _("Fitted shape"), TRUE);
    }

    if (output == FIT_SHAPE_OUTPUT_DIFF || output == FIT_SHAPE_OUTPUT_BOTH) {
        newid = gwy_file_add_image(data, args->diff);
        gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
        gwy_file_sync_items(data, GWY_FILE_IMAGE, id,
                            data, GWY_FILE_IMAGE, newid,
                            syncflags, FALSE);
        gwy_log_add(data, GWY_FILE_IMAGE, id, newid);
        gwy_file_set_title(data, GWY_FILE_IMAGE, newid, _("Difference"), TRUE);
    }
}

static void
create_output_xyz(ModuleArgs *args, GwyFile *data, gint id)
{
    FitShapeOutputType output = gwy_params_get_enum(args->params, PARAM_OUTPUT);
    GwyScatter *scatter = args->scatter;
    const gchar *gradient = gwy_file_get_palette(data, GWY_FILE_XYZ, id);

    gsize n = gwy_scatter_get_n_points(scatter);
    gint nvalues = 0;
    if (output == FIT_SHAPE_OUTPUT_FIT || output == FIT_SHAPE_OUTPUT_BOTH)
        nvalues++;
    if (output == FIT_SHAPE_OUTPUT_DIFF || output == FIT_SHAPE_OUTPUT_BOTH)
        nvalues++;
    if (!nvalues)
        return;

    GwyScatter *result = gwy_scatter_new_sized(nvalues, n);
    gwy_assign(gwy_scatter_get_xy(result), gwy_scatter_get_xy_const(scatter), n);

    nvalues = 0;
    if (output == FIT_SHAPE_OUTPUT_FIT || output == FIT_SHAPE_OUTPUT_BOTH) {
        gdouble *d = gwy_scatter_get_data(result, nvalues);
        for (gsize i = 0; i < n; i++)
            d[i] = args->f[i];
        gwy_scatter_set_data_label(result, nvalues, _("Fitted shape"));
        nvalues++;
    }
    if (output == FIT_SHAPE_OUTPUT_DIFF || output == FIT_SHAPE_OUTPUT_BOTH) {
        gdouble *d = gwy_scatter_get_data(result, nvalues);
        const gdouble *z = gwy_scatter_get_data(scatter, gwy_params_get_int(args->params, PARAM_DATA));
        for (gsize i = 0; i < n; i++)
            d[i] = z[i] - args->f[i];

        gwy_scatter_set_data_label(result, nvalues, _("Difference"));
        nvalues++;
    }

    gint newid = gwy_file_add_xyz(data, result);
    gwy_file_set_visible(data, GWY_FILE_XYZ, newid, TRUE);
    gwy_log_add(data, GWY_FILE_XYZ, id, newid);
    gwy_file_set_title(data, GWY_FILE_XYZ, newid, _("Fit result"), TRUE);
    if (gradient)
        gwy_file_set_palette(data, GWY_FILE_XYZ, newid, gradient);
}

static GwyDialogOutcome
run_gui(ModuleArgs *args, GwyFile *data, gint id)
{
    GtkWidget *vbox, *hbox, *auxbox, *dataview;
    GwyDialog *dialog;
    GtkNotebook *notebook;
    GwyDialogOutcome outcome;
    ModuleGUI gui;
    FitShapeContext ctx;
    const gchar *gradient = NULL;

    gwy_clear1(ctx);
    gwy_clear1(gui);
    gui.args = ctx.args = args;
    gui.data = data;
    gui.id = id;
    gui.ctx = &ctx;

    if (args->data_kind == GWY_FILE_XYZ) {
        g_object_ref(args->scatter);
        args->field = gwy_field_new(1, 1, 1.0, 1.0, FALSE);
        gwy_preview_scatter_to_field(args->scatter, gwy_params_get_int(args->params, PARAM_DATA), args->field,
                                     PREVIEW_SIZE, PREVIEW_SIZE, GWY_PREVIEW_SCATTER_FILL);
        gradient = gwy_file_get_palette(data, GWY_FILE_XYZ, id);
    }
    else {
        g_object_ref(args->field);
        if (args->mask)
            g_object_ref(args->mask);
        gradient = gwy_file_get_palette(data, GWY_FILE_IMAGE, id);
    }
    args->result = gwy_field_new_alike(args->field, TRUE);
    args->diff = gwy_field_new_alike(args->field, TRUE);
    update_context_data(&gui);

    gui.orig_gradient = gwy_gradients_get_gradient(gradient);
    gui.diff_gradient = g_object_new(GWY_TYPE_GRADIENT, "name", FIT_GRADIENT_NAME, NULL);

    gui.dialog = gwy_dialog_new(_("Fit Shape"));
    dialog = GWY_DIALOG(gui.dialog);
    gwy_dialog_add_stock_buttons(dialog, GWY_STOCK_COPY, GWY_STOCK_SAVE, GWY_STOCK_FIT, 0);
    gwy_add_button_to_dialog(GTK_DIALOG(dialog), C_("verb", "_Quick Fit"), NULL, RESPONSE_QUICK_FIT);
    gwy_dialog_add_stock_buttons(dialog, GWY_STOCK_ESTIMATE, GWY_STOCK_CANCEL, GWY_STOCK_OK, 0);

    dataview = gui.dataview = gwy_create_preview(args->field, args->mask, PREVIEW_SIZE);
    gwy_setup_data_view(GWY_DATA_VIEW(dataview), data, args->data_kind, id, GWY_FILE_ITEM_PALETTE);
    if (args->data_kind == GWY_FILE_IMAGE) {
        gwy_setup_data_view(GWY_DATA_VIEW(dataview), data, args->data_kind, id,
                            GWY_FILE_ITEM_MASK_COLOR | GWY_FILE_ITEM_COLOR_MAPPING | GWY_FILE_ITEM_REAL_SQUARE);
    }
    hbox = gwy_create_dialog_preview_hbox(GTK_DIALOG(dialog), GWY_DATA_VIEW(dataview), FALSE);

    vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);
    gtk_box_pack_start(GTK_BOX(hbox), vbox, TRUE, TRUE, 0);

    notebook = GTK_NOTEBOOK(gtk_notebook_new());
    gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(notebook), TRUE, TRUE, 0);

    gtk_notebook_append_page(notebook, basic_tab_new(&gui), gtk_label_new(C_("adjective", "Basic")));
    gtk_notebook_append_page(notebook, parameters_tab_new(&gui), gtk_label_new(_("Parameters")));
    gtk_notebook_append_page(notebook, correl_tab_new(&gui), gtk_label_new(_("Correlation Matrix")));
    gtk_notebook_append_page(notebook, secondary_tab_new(&gui), gtk_label_new(_("Derived Quantities")));

    auxbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
    gtk_box_pack_start(GTK_BOX(vbox), auxbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(auxbox), gtk_label_new(_("Mean square difference:")), FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(auxbox), (gui.rss_label = gtk_label_new(NULL)), FALSE, FALSE, 0);

    auxbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
    gtk_box_pack_start(GTK_BOX(vbox), auxbox, FALSE, FALSE, 0);
    gtk_box_pack_start(GTK_BOX(auxbox), (gui.fit_message = gtk_label_new(NULL)), FALSE, FALSE, 0);

    g_signal_connect_swapped(dialog, "response", G_CALLBACK(dialog_response), &gui);
    g_signal_connect_swapped(gui.table, "param-changed", G_CALLBACK(param_changed), &gui);

    outcome = gwy_dialog_run(dialog);

    g_clear_object(&gui.results);
    g_object_unref(gui.diff_gradient);
    g_free(gui.correl);
    g_free(ctx.xyz);

    return outcome;
}

static void
dialog_response(ModuleGUI *gui, gint response)
{
    if (response == GWY_RESPONSE_REFINE) {
        fit_shape_full_fit(gui);
        if (gui->state == FIT_FITTED)
            fill_results(gui);
    }
    else if (response == RESPONSE_QUICK_FIT)
        fit_shape_quick_fit(gui);
    else if (response == GWY_RESPONSE_EXECUTE)
        fit_shape_estimate(gui);
    else if (response == GWY_RESPONSE_SAVE || response == GWY_RESPONSE_COPY) {
        GwyResultsExportStyle report_style = gwy_params_get_report_type(gui->args->params, PARAM_REPORT_STYLE);
        gchar *report = gwy_results_create_report(gui->results, report_style);

        if (response == GWY_RESPONSE_SAVE)
            gwy_save_auxiliary_data(_("Save Fit Report"), GTK_WINDOW(gui->dialog), report, -1, FALSE);
        else {
            GdkDisplay *display = gtk_widget_get_display(gui->dialog);
            GtkClipboard *clipboard = gtk_clipboard_get_for_display(display, GDK_SELECTION_CLIPBOARD);
            gtk_clipboard_set_text(clipboard, report, -1);
        }
        g_free(report);
    }
}

static void
param_changed(ModuleGUI *gui, gint id)
{
    ModuleArgs *args = gui->args;
    GwyParams *params = args->params;
    FitShapeDisplayType display = gwy_params_get_enum(params, PARAM_DISPLAY);
    gboolean update_colormap = FALSE;

    if (id < 0 || id == PARAM_DATA) {
        /* TODO: Anything else? */
        update_same_units(args);
        const gchar *old_func = gwy_params_get_string(params, PARAM_FUNCTION);
        GwyShapeFitPreset *preset = gwy_inventory_get_item(gwy_shape_fit_presets(), old_func);
        if (!args->same_units && gwy_shape_fit_preset_needs_same_units(preset)) {
            /* XXX: This is silly. */
            gwy_params_reset(params, PARAM_FUNCTION);
            const gchar *new_func = gwy_params_get_string(params, PARAM_FUNCTION);
            if (!gwy_strequal(new_func, old_func)) {
                gwy_param_table_set_string(gui->table, PARAM_FUNCTION, old_func);
                gwy_param_table_set_string(gui->table, PARAM_FUNCTION, new_func);
            }
        }
        update_context_data(gui);
        id = -1;
    }

    if (id < 0 || id == PARAM_FUNCTION) {
        FitShapeContext *ctx = gui->ctx;
        gui->preset = gwy_inventory_get_item(gwy_shape_fit_presets(), gwy_params_get_string(params, PARAM_FUNCTION));
        guint nparams = gwy_shape_fit_preset_get_n_params(gui->preset);
        gdouble param[nparams];
        gwy_shape_fit_preset_setup(gui->preset, ctx->xyz, ctx->n, param);
        gui->state = FIT_INITIALISED;

        fit_param_table_resize(gui);
        for (guint i = 0; i < nparams; i++)
            gwy_fit_table_set_value(GWY_FIT_TABLE(gui->fit_params), i, param[i]);

        fit_correl_table_resize(gui);

        fit_secondary_table_resize(gui);
        update_secondary_table(gui);
        /* FIXME: */
        update_fit_results(gui, NULL);
        update_fields(gui);
        update_fit_state(gui);
        create_results(gui);
    }

    if (id < 0 || id == PARAM_DISPLAY) {
        GwyDataView *dataview = GWY_DATA_VIEW(gui->dataview);
        gwy_data_view_set_mask(dataview, display == FIT_SHAPE_DISPLAY_DATA ? args->mask : NULL);
        update_colormap = TRUE;

        if (display == FIT_SHAPE_DISPLAY_DATA) {
            gwy_data_view_set_field(dataview, args->field);
            if (args->data_kind == GWY_FILE_IMAGE)
                gwy_setup_data_view(dataview, gui->data, args->data_kind, gui->id, GWY_FILE_ITEM_RANGE);
            else
                gwy_data_view_set_color_mapping(dataview, GWY_COLOR_MAPPING_FULL);
        }
        else if (display == FIT_SHAPE_DISPLAY_RESULT) {
            gwy_data_view_set_field(dataview, args->result);
            gwy_data_view_set_color_mapping(dataview, GWY_COLOR_MAPPING_FULL);
        }
        else if (display == FIT_SHAPE_DISPLAY_DIFF) {
            gwy_data_view_set_field(dataview, args->diff);
            gwy_data_view_set_color_mapping(dataview, GWY_COLOR_MAPPING_FIXED);
        }
    }
    if (id == PARAM_MASKING) {
        update_context_data(gui);
        gui->state = FIT_INITIALISED;
        update_fit_results(gui, NULL);
        if (!gwy_params_get_boolean(params, PARAM_DIFF_EXCLUDED))
            update_fields(gui);
        update_fit_state(gui);
    }
    if (id == PARAM_DIFF_EXCLUDED) {
        if (gwy_params_get_enum(params, PARAM_MASKING) != GWY_MASK_IGNORE)
            update_fields(gui);
    }
    if (id == PARAM_DIFF_COLOURMAP || update_colormap) {
        GwyDataView *dataview = GWY_DATA_VIEW(gui->dataview);
        if (display == FIT_SHAPE_DISPLAY_DIFF && gwy_params_get_boolean(params, PARAM_DIFF_COLOURMAP))
            gwy_data_view_set_gradient(dataview, gui->diff_gradient);
        else
            gwy_data_view_set_gradient(dataview, gui->orig_gradient);
    }
}

static GtkWidget*
basic_tab_new(ModuleGUI *gui)
{
    ModuleArgs *args = gui->args;
    GwyParamTable *table;

    table = gui->table = gwy_param_table_new(args->params);
    if (args->scatter)
        gwy_param_table_append_scatter_data(table, PARAM_DATA, args->scatter);
    gwy_param_table_append_combo(table, PARAM_FUNCTION);
    gwy_param_table_combo_set_filter(table, PARAM_FUNCTION, preset_is_available, gui, NULL);
    gwy_param_table_append_combo(table, PARAM_OUTPUT);
    gwy_param_table_append_radio(table, PARAM_DISPLAY);
    gwy_param_table_append_separator(table);
    gwy_param_table_append_checkbox(table, PARAM_DIFF_COLOURMAP);
    if (args->mask) {
        gwy_param_table_append_combo(table, PARAM_MASKING);
        gwy_param_table_append_checkbox(table, PARAM_DIFF_EXCLUDED);
    }

    return gwy_param_table_widget(table);
}

static GtkWidget*
parameters_tab_new(ModuleGUI *gui)
{
    GtkWidget *vbox = gtk_box_new(GTK_ORIENTATION_VERTICAL, 4);
    gtk_container_set_border_width(GTK_CONTAINER(vbox), 4);

    gui->fit_params = gwy_fit_table_new();
    GwyFitTable *table = GWY_FIT_TABLE(gui->fit_params);
    gwy_fit_table_set_values_editable(table, TRUE);
    gwy_fit_table_set_has_checkboxes(table, TRUE);
    gwy_fit_table_set_header(table, _("Fix"), 0, 1);
    gwy_fit_table_set_header(table, _("Parameter"), 1, 3);
    gwy_fit_table_set_header(table, _("Error"), 6, 2);
    gtk_box_pack_start(GTK_BOX(vbox), gui->fit_params, FALSE, TRUE, 0);
    g_signal_connect_swapped(gui->fit_params, "activated", G_CALLBACK(param_value_activate), gui);

    GtkSizeGroup *sizegroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);

    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
    gui->recalculate = gtk_button_new_with_mnemonic(_("_Recalculate Image"));
    gtk_size_group_add_widget(sizegroup, gui->recalculate);
    gtk_box_pack_start(GTK_BOX(hbox), gui->recalculate, FALSE, FALSE, 0);
    g_signal_connect_swapped(gui->recalculate, "clicked", G_CALLBACK(recalculate_image), gui);

    gui->revert = gtk_button_new_with_mnemonic(_("Revert to _Previous Values"));
    gtk_size_group_add_widget(sizegroup, gui->revert);
    gtk_box_pack_start(GTK_BOX(hbox), gui->revert, FALSE, FALSE, 0);
    g_signal_connect_swapped(gui->revert, "clicked", G_CALLBACK(revert_params), gui);

    g_object_unref(sizegroup);

    return vbox;
}

static void
fit_param_table_resize(ModuleGUI *gui)
{
    guint nparams = gwy_shape_fit_preset_get_n_params(gui->preset);
    GwyFitTable *table = GWY_FIT_TABLE(gui->fit_params);
    gwy_fit_table_resize(table, nparams);

    GwyUnit *xyunit = gwy_field_get_unit_xy(gui->args->field);
    GwyUnit *zunit = gwy_field_get_unit_z(gui->args->field);

    for (guint i = 0; i < nparams; i++) {
        GwyNLFitParamFlags flags = gwy_shape_fit_preset_get_param_flags(gui->preset, i);
        gwy_fit_table_set_checked(table, i, FALSE);
        gwy_fit_table_set_name(table, i, gwy_shape_fit_preset_get_param_name(gui->preset, i));
        gwy_fit_table_set_tooltip(table, i, gwy_shape_fit_preset_get_param_description(gui->preset, i));
        gwy_fit_table_set_flags(table, i, flags);
        GwyUnit *unit = gwy_shape_fit_preset_get_param_units(gui->preset, i, xyunit, zunit);
        gwy_fit_table_set_unit(table, i, unit);
        g_object_unref(unit);
    }
    gwy_fit_table_clear_errors(table);
}

static GtkWidget*
correl_tab_new(ModuleGUI *gui)
{
    GtkWidget *scwin = gtk_scrolled_window_new(NULL, NULL);
    gtk_container_set_border_width(GTK_CONTAINER(scwin), 4);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scwin), GTK_POLICY_AUTOMATIC, GTK_POLICY_NEVER);

    gui->correl_table = gwy_correl_table_new();
    gtk_container_set_border_width(GTK_CONTAINER(gui->correl_table), 4);
    gtk_container_add(GTK_CONTAINER(scwin), gui->correl_table);

    return scwin;
}

static void
fit_correl_table_resize(ModuleGUI *gui)
{
    guint nparams = gwy_shape_fit_preset_get_n_params(gui->preset);
    GwyCorrelTable *table = GWY_CORREL_TABLE(gui->correl_table);
    gui->correl = g_renew(gdouble, gui->correl, nparams*(nparams + 1)/2);
    gwy_correl_table_resize(table, nparams);
    for (guint i = 0; i < nparams; i++) {
        const gchar *name = gwy_shape_fit_preset_get_param_name(gui->preset, i);
        gwy_correl_table_set_name(table, i, name);
        gwy_correl_table_set_tooltip(table, i, gwy_shape_fit_preset_get_param_description(gui->preset, i));
    }
}

static GtkWidget*
secondary_tab_new(ModuleGUI *gui)
{
    gui->secondary_params = gwy_fit_table_new();
    gtk_container_set_border_width(GTK_CONTAINER(gui->secondary_params), 4);
    return gui->secondary_params;
}

static void
fit_secondary_table_resize(ModuleGUI *gui)
{
    guint nsecondary = gwy_shape_fit_preset_get_n_secondary(gui->preset);
    GwyFitTable *table = GWY_FIT_TABLE(gui->secondary_params);
    gwy_fit_table_resize(table, nsecondary);

    GwyUnit *xyunit = gwy_field_get_unit_xy(gui->args->field);
    GwyUnit *zunit = gwy_field_get_unit_z(gui->args->field);

    gwy_fit_table_clear_errors(table);
    for (guint i = 0; i < nsecondary; i++) {
        GwyNLFitParamFlags flags = gwy_shape_fit_preset_get_secondary_flags(gui->preset, i);
        gwy_fit_table_set_name(table, i, gwy_shape_fit_preset_get_secondary_name(gui->preset, i));
        gwy_fit_table_set_tooltip(table, i, gwy_shape_fit_preset_get_secondary_description(gui->preset, i));
        gwy_fit_table_set_flags(table, i, flags);
        GwyUnit *unit = gwy_shape_fit_preset_get_secondary_units(gui->preset, i, xyunit, zunit);
        gwy_fit_table_set_unit(table, i, unit);
        g_object_unref(unit);
    }
}

static gboolean
preset_is_available(const GwyEnum *enumval, gpointer user_data)
{
    ModuleGUI *gui = (ModuleGUI*)user_data;
    gboolean same_units = gui->args->same_units;

    if (same_units)
        return TRUE;

    return !gwy_shape_fit_preset_needs_same_units(gwy_inventory_get_item(gwy_shape_fit_presets(), enumval->name));
}

static void
param_value_activate(ModuleGUI *gui, guint i)
{
    /* This (a) clears error labels in the table (b) reformats the parameter, e.g. by moving the power-of-10 base
     * appropriately. */
    gui->state = FIT_USER_MODIFIED;
    gwy_fit_table_clear_error(GWY_FIT_TABLE(gui->fit_params), i);
    update_correl_table(gui, NULL);
    update_secondary_table(gui);
    update_fit_state(gui);
}

static void
revert_params(ModuleGUI *gui)
{
    gwy_fit_table_revert_values(GWY_FIT_TABLE(gui->fit_params));
    gwy_fit_table_clear_errors(GWY_FIT_TABLE(gui->fit_params));
    gwy_fit_table_clear_errors(GWY_FIT_TABLE(gui->secondary_params));

    gui->state = FIT_USER_MODIFIED;
    update_correl_table(gui, NULL);
    update_secondary_table(gui);
    update_fit_state(gui);
}

static void
recalculate_image(ModuleGUI *gui)
{
    gui->state = FIT_USER_MODIFIED;
    update_fit_results(gui, NULL);
    update_fields(gui);
    update_fit_state(gui);
}

static void
update_correl_table(ModuleGUI *gui, GwyNLFitter *fitter)
{
    GwyCorrelTable *table = GWY_CORREL_TABLE(gui->correl_table);
    if (!fitter) {
        gwy_correl_table_clear(table);
        return;
    }

    guint nparams = gwy_shape_fit_preset_get_n_params(gui->preset);
    gboolean fixed[nparams];
    gwy_fit_table_gather(GWY_FIT_TABLE(gui->fit_params), NULL, NULL, fixed);

    for (guint i = 0; i < nparams; i++) {
        gwy_correl_table_set_fixed(table, i, fixed[i]);
        for (guint j = 0; j <= i; j++) {
            gdouble c = gwy_nl_fitter_get_correlations(fitter, i, j);
            gwy_correl_table_set_correl(table, i, j, c);
        }
    }

    /* For some reason, this does not happen automatically after the set-text call so the labels that had initially
     * zero width remain invisible even though there is a number to display now. */
    if (fitter)
        gtk_widget_queue_resize(gui->correl_table);
}

static void
update_secondary_table(ModuleGUI *gui)
{
    gboolean is_fitted = (gui->state == FIT_FITTED || gui->state == FIT_QUICK_FITTED);
    GwyShapeFitPreset *preset = gui->preset;
    guint nparams = gwy_shape_fit_preset_get_n_params(preset);
    guint nsecondary = gwy_shape_fit_preset_get_n_secondary(preset);
    GwyFitTable *table = GWY_FIT_TABLE(gui->secondary_params);

    gdouble param[nparams], error[nparams];
    gwy_fit_table_gather(GWY_FIT_TABLE(gui->fit_params), param, error, NULL);
    for (guint i = 0; i < nsecondary; i++) {
        gdouble v = gwy_shape_fit_preset_get_secondary_value(preset, i, param);
        /* FIXME: This is nonsense. In fact, gwy_shape_fit_preset_get_secondary_error() is probably nonsense.
         * GwyShapeFitPreset has the fitter so it can just use the covariance matrix from the fitter. We may
         * want to calculate secondary parameter *values* from user-entered primary parameters. But once the user
         * starts editing primary parameters, the covariance matrix becomes undefined. */
        gdouble e = (is_fitted ? gwy_shape_fit_preset_get_secondary_error(preset, i, param, error, gui->correl) : -1.0);

        gwy_fit_table_set_value(table, i, v);
        if (is_fitted)
            gwy_fit_table_set_error(table, i, e);
        else
            gwy_fit_table_clear_error(table, i);
    }
}

static void
fit_shape_estimate(ModuleGUI *gui)
{
    const FitShapeContext *ctx = gui->ctx;
    GwyShapeFitPreset *preset = gui->preset;
    GwyFitTable *table = GWY_FIT_TABLE(gui->fit_params);
    guint nparams = gwy_shape_fit_preset_get_n_params(preset);
    gdouble param[nparams];
    gboolean fixed[nparams];
    gwy_fit_table_gather(GWY_FIT_TABLE(gui->fit_params), NULL, NULL, fixed);

    gwy_app_wait_cursor_start(GTK_WINDOW(gui->dialog));
    gwy_debug("start estimate");
    if (gwy_shape_fit_preset_guess(preset, ctx->xyz, ctx->n, param))
        gui->state = FIT_ESTIMATED;
    else
        gui->state = FIT_ESTIMATE_FAILED;

    /* XXX: We honour fixed parameters by ignoring the estimate and pretending nothing happened.  Is it OK? */
    for (guint i = 0; i < nparams; i++) {
        gwy_debug("[%u] %g", i, param[i]);
        if (!fixed[i])
            gwy_fit_table_set_value(table, i, param[i]);
    }

    gwy_fit_table_clear_errors(table);
    /* FIXME: This also does update_param_table() which we do not want here. */
    update_fit_results(gui, NULL);
    update_fields(gui);
    update_fit_state(gui);
    gwy_app_wait_cursor_finish(GTK_WINDOW(gui->dialog));
}

/* TODO: Get rid of. Recalculate secondary, fill Results and do not keep the matrix around. */
static void
fit_copy_correl_matrix(ModuleGUI *gui, GwyNLFitter *fitter)
{
    gboolean is_fitted = (gui->state == FIT_FITTED || gui->state == FIT_QUICK_FITTED);
    guint i, j, nparams;

    nparams = gwy_shape_fit_preset_get_n_params(gui->preset);
    gwy_clear(gui->correl, (nparams + 1)*nparams/2);
    if (is_fitted) {
        g_return_if_fail(fitter && gwy_nl_fitter_get_covar(fitter));
        for (i = 0; i < nparams; i++) {
            for (j = 0; j <= i; j++)
                SLi(gui->correl, i, j) = gwy_nl_fitter_get_correlations(fitter, i, j);
        }
    }
}

static void
fit_shape_quick_fit(ModuleGUI *gui)
{
    const FitShapeContext *ctx = gui->ctx;
    GwyShapeFitPreset *preset = gui->preset;
    GwyFitTable *table = GWY_FIT_TABLE(gui->fit_params);
    guint nparams = gwy_shape_fit_preset_get_n_params(preset);
    gdouble param[nparams];
    gboolean fixed[nparams];
    gwy_fit_table_gather(GWY_FIT_TABLE(gui->fit_params), param, NULL, fixed);

    gwy_app_wait_cursor_start(GTK_WINDOW(gui->dialog));
    gwy_debug("start quick fit");
    gdouble rss;
    GwyNLFitter *fitter = fit(preset, ctx, param, fixed, &rss, NULL, NULL, TRUE);

    if (rss >= 0.0)
        gui->state = FIT_QUICK_FITTED;
    else
        gui->state = FIT_QUICK_FIT_FAILED;

    for (guint i = 0; i < nparams; i++) {
        gwy_debug("[%u] %g", i, param[i]);
        if (!fixed[i])
            gwy_fit_table_set_value(table, i, param[i]);
    }

    fit_copy_correl_matrix(gui, fitter);
    /* FIXME: This also does update_param_table() which we do not want here. */
    update_fit_results(gui, fitter);
    update_fields(gui);
    update_fit_state(gui);
    gwy_nl_fitter_free(fitter);
    gwy_app_wait_cursor_finish(GTK_WINDOW(gui->dialog));
}

static void
fit_shape_full_fit(ModuleGUI *gui)
{
    const FitShapeContext *ctx = gui->ctx;
    GwyShapeFitPreset *preset = gui->preset;
    GwyFitTable *table = GWY_FIT_TABLE(gui->fit_params);
    guint nparams = gwy_shape_fit_preset_get_n_params(preset);
    gdouble param[nparams];
    gboolean fixed[nparams];
    gwy_fit_table_gather(GWY_FIT_TABLE(gui->fit_params), param, NULL, fixed);

    gwy_app_wait_start(GTK_WINDOW(gui->dialog), _("Fitting..."));
    gwy_debug("start fit");
    gdouble rss;
    GwyNLFitter *fitter = fit(preset, ctx, param, fixed, &rss,
                              gwy_app_wait_set_fraction, gwy_app_wait_set_message, FALSE);

    if (rss >= 0.0)
        gui->state = FIT_FITTED;
    else if (rss == -2.0)
        gui->state = FIT_FIT_CANCELLED;
    else
        gui->state = FIT_FIT_FAILED;

    for (guint i = 0; i < nparams; i++) {
        gwy_debug("[%u] %g", i, param[i]);
        if (!fixed[i])
            gwy_fit_table_set_value(table, i, param[i]);
    }

    fit_copy_correl_matrix(gui, fitter);
    /* FIXME: This also does update_param_table() which we do not want here. */
    update_fit_results(gui, fitter);
    update_fields(gui);
    update_fit_state(gui);
    gwy_nl_fitter_free(fitter);
    gwy_app_wait_finish();
}

static void
update_fields(ModuleGUI *gui)
{
    ModuleArgs *args = gui->args;
    GwyField *field = args->field, *result = args->result, *diff = args->diff;
    GwyNield *mask = args->mask;
    GwyMaskingType masking = gwy_params_get_masking(args->params, PARAM_MASKING, &mask);
    gboolean diff_excluded = gwy_params_get_boolean(args->params, PARAM_DIFF_EXCLUDED);
    FitShapeContext *ctx = gui->ctx;

    gsize xres = gwy_field_get_xres(args->field);
    gsize yres = gwy_field_get_yres(args->field);
    gsize n = xres*yres;
    if (args->data_kind == GWY_FILE_IMAGE && !mask) {
        /* We know args->f contains all the theoretical values. */
        g_assert(ctx->n == n);
        gwy_debug("directly copying f[] to result field");
        gwy_assign(gwy_field_get_data(result), args->f, n);
    }
    else if (args->data_kind == GWY_FILE_XYZ) {
        gint idata = gwy_params_get_int(args->params, PARAM_DATA);
        n = gwy_scatter_get_n_points(args->scatter);
        g_assert(ctx->n == n);

        GwyScatter *scatter = gwy_scatter_new_sized(1, n);
        gwy_assign(gwy_scatter_get_xy(scatter), gwy_scatter_get_xy_const(args->scatter), n);
        gwy_assign(gwy_scatter_get_data(scatter, 0), args->f, n);
        gwy_unit_assign(gwy_scatter_get_unit_xy(scatter), gwy_scatter_get_unit_xy(args->scatter));
        gwy_unit_assign(gwy_scatter_get_unit_data(scatter, 0), gwy_scatter_get_unit_data(args->scatter, idata));
        gwy_preview_scatter_to_field(scatter, idata, result, PREVIEW_SIZE, PREVIEW_SIZE, GWY_PREVIEW_SCATTER_FILL);
        g_object_unref(scatter);
    }
    else {
        /* Either the input is XYZ or we are using masking.  Just recalculate everything, even values that are in
         * args->f. */
        gwy_debug("recalculating result field the hard way");
        guint nparams = gwy_shape_fit_preset_get_n_params(gui->preset);
        gdouble param[nparams];
        gwy_fit_table_gather(GWY_FIT_TABLE(gui->fit_params), param, NULL, NULL);
        calculate_field(gui->preset, param, result);
    }

    gwy_field_data_changed(result);
    gwy_field_subtract_fields(diff, field, result);
    if (!diff_excluded && mask) {
        masking = (masking == GWY_MASK_INCLUDE ? GWY_MASK_EXCLUDE : GWY_MASK_INCLUDE);
        gwy_field_area_fill(diff, mask, masking, 0, 0, xres, yres, 0.0);
    }
    gwy_field_data_changed(diff);
    update_diff_gradient(gui);
}

static void
update_diff_gradient(ModuleGUI *gui)
{
    ModuleArgs *args = gui->args;
    GwyNield *mask = args->mask;
    GwyField *diff = args->diff;
    GwyMaskingType masking = gwy_params_get_masking(args->params, PARAM_MASKING, &mask);
    gboolean diff_excluded = gwy_params_get_boolean(args->params, PARAM_DIFF_EXCLUDED);
    gdouble min, max, dispmin, dispmax;

    if (!diff_excluded && mask) {
        gint xres = gwy_nield_get_xres(mask), yres = gwy_nield_get_yres(mask);
        gwy_field_area_min_max(diff, mask, masking, 0, 0, xres, yres, &min, &max);
        gwy_field_area_autorange(diff, mask, masking, 0, 0, xres, yres, &dispmin, &dispmax);
    }
    else {
        gwy_field_min_max(diff, &min, &max);
        gwy_field_autorange(diff, &dispmin, &dispmax);
    }

    gwy_set_gradient_for_residuals(gui->diff_gradient, min, max, &dispmin, &dispmax);
    gwy_data_view_set_fixed_color_range(GWY_DATA_VIEW(gui->dataview), dispmin, dispmax);
}

static void
update_fit_state(ModuleGUI *gui)
{
    const gchar *message = NULL;

    if (gui->state == FIT_ESTIMATE_FAILED)
        message = _("Parameter estimation failed");
    else if (gui->state == FIT_FIT_FAILED || gui->state == FIT_QUICK_FIT_FAILED)
        message = _("Fit failed");
    else if (gui->state == FIT_FIT_CANCELLED)
        message = _("Fit was interrupted");

    gwy_set_message_label(GTK_LABEL(gui->fit_message), message, GTK_MESSAGE_ERROR, FALSE);

    gtk_dialog_set_response_sensitive(GTK_DIALOG(gui->dialog), GWY_RESPONSE_SAVE, gui->state == FIT_FITTED);
    gtk_dialog_set_response_sensitive(GTK_DIALOG(gui->dialog), GWY_RESPONSE_COPY, gui->state == FIT_FITTED);
}

/* This function is called from probably too many different contexts. We should ideally refactor the different uses
 * more cleanly.
 *
 * XXX: But in any case gui->fit_params must contain the correct parameters when it is called! */
static void
update_fit_results(ModuleGUI *gui, GwyNLFitter *fitter)
{
    ModuleArgs *args = gui->args;
    const FitShapeContext *ctx = gui->ctx;
    GwyShapeFitPreset *preset = gui->preset;
    guint nparams = gwy_shape_fit_preset_get_n_params(preset);
    gdouble z, rss = 0.0;
    guint n = ctx->n;
    GwyUnit *zunit;
    GwyValueFormat *vf;
    gboolean is_fitted = (gui->state == FIT_FITTED || gui->state == FIT_QUICK_FITTED);
    const GwyXYZ *xyz = ctx->xyz;
    guchar buf[48];

    if (is_fitted)
        g_return_if_fail(fitter);

    gdouble param[nparams];
    gboolean fixed[nparams];
    gwy_fit_table_gather(GWY_FIT_TABLE(gui->fit_params), param, NULL, fixed);
    gwy_shape_fit_preset_calculate_z(preset, xyz, args->f, n, param);
    for (guint k = 0; k < n; k++) {
        z = args->f[k] - xyz[k].z;
        rss += z*z;
    }
    gui->rss = sqrt(rss/n);

    if (is_fitted) {
        GwyFitTable *table = GWY_FIT_TABLE(gui->fit_params);
        for (guint i = 0; i < nparams; i++)
            gwy_fit_table_set_error(table, i, fixed[i] ? 0.0 : gwy_nl_fitter_get_sigma(fitter, i));
    }

    zunit = gwy_field_get_unit_z(args->field);
    vf = gwy_unit_get_format(zunit, GWY_UNIT_FORMAT_VFMARKUP, gui->rss, NULL);

    g_snprintf(buf, sizeof(buf), "%.*f%s%s",
               vf->precision+1, gui->rss/vf->magnitude,
               *vf->units ? " " : "", vf->units);
    gtk_label_set_markup(GTK_LABEL(gui->rss_label), buf);

    gwy_value_format_free(vf);

    update_correl_table(gui, is_fitted ? fitter : NULL);
    update_secondary_table(gui);
}

/* See libgwyddion/internal.h */
static inline gint
nielded_included(const gint *m, GwyMaskingType masking)
{
    if (masking == GWY_MASK_INCLUDE)
        return *m > 0;
    if (masking == GWY_MASK_EXCLUDE)
        return *m <= 0;
    return 1;
}

static GwyXYZ*
fill_xyz_from_field(GwyField *field, GwyNield *mask, GwyMaskingType masking,
                    GwyXYZ *xyz, gsize *npixels)
{
    gsize xres = gwy_field_get_xres(field), yres = gwy_field_get_yres(field);
    gdouble dx = gwy_field_get_dx(field), dy = gwy_field_get_dy(field);
    gdouble xoff = gwy_field_get_xoffset(field), yoff = gwy_field_get_yoffset(field);
    const gdouble *d = gwy_field_get_data_const(field);
    const gint *m = (mask ? gwy_nield_get_data_const(mask) : NULL);
    gsize n;

    if (m) {
        n = gwy_nield_area_count(mask, NULL, GWY_MASK_IGNORE, 0, 0, xres, yres);
        if (masking == GWY_MASK_EXCLUDE)
            n = xres*yres - n;
    }
    else
        n = xres*yres;
    xyz = g_renew(GwyXYZ, xyz, n);

    gsize count = 0;
    for (gsize i = 0; i < yres; i++) {
        gdouble y = (i + 0.5)*dy + yoff;
        for (gsize j = 0; j < xres; j++) {
            gsize k = i*xres + j;
            if (nielded_included(m + k, masking)) {
                gdouble x = (j + 0.5)*dx + xoff;
                xyz[count].x = x;
                xyz[count].y = y;
                xyz[count].z = d[k];
                count++;
            }
        }
    }
    g_assert(count == n);

    *npixels = n;
    return xyz;
}

static void
update_context_data(ModuleGUI *gui)
{
    ModuleArgs *args = gui->args;
    FitShapeContext *ctx = gui->ctx;

    if (args->data_kind == GWY_FILE_IMAGE) {
        GwyNield *mask = args->mask;
        GwyMaskingType masking = gwy_params_get_masking(args->params, PARAM_MASKING, &mask);
        GwyField *field = args->field;
        ctx->xyz = fill_xyz_from_field(field, mask, masking, ctx->xyz, &ctx->n);
    }
    else if (args->data_kind == GWY_FILE_XYZ) {
        gint idata = gwy_params_get_int(args->params, PARAM_DATA);
        ctx->n = gwy_scatter_get_n_points(args->scatter);
        ctx->xyz = g_renew(GwyXYZ, ctx->xyz, ctx->n);

        const GwyXY *xy = gwy_scatter_get_xy_const(args->scatter);
        const gdouble *d = gwy_scatter_get_data_const(args->scatter, idata);
        for (gsize k = 0; k < ctx->n; k++) {
            ctx->xyz[k].x = xy[k].x;
            ctx->xyz[k].y = xy[k].y;
            ctx->xyz[k].z = d[k];
        }
    }
    args->f = g_renew(gdouble, args->f, ctx->n);
}

static GwyNLFitter*
fit(GwyShapeFitPreset *preset, const FitShapeContext *ctx,
    gdouble *param, const gboolean *fixed, gdouble *rss,
    GwySetFractionFunc set_fraction, GwySetMessageFunc set_message,
    gboolean quick_fit)
{
    GwyNLFitter *fitter;

    fitter = gwy_shape_fit_preset_create_fitter(preset);
    if (set_fraction || set_message)
        gwy_nl_fitter_set_callbacks(fitter, set_fraction, set_message);

    if (quick_fit)
        gwy_shape_fit_preset_quick_fit(preset, fitter, ctx->xyz, ctx->n, param, fixed, rss);
    else
        gwy_shape_fit_preset_fit(preset, fitter, ctx->xyz, ctx->n, param, fixed, rss);
    gwy_debug("rss from nlfit %g", *rss);

    return fitter;
}

static void
calculate_field(GwyShapeFitPreset *preset, const gdouble *params,
                GwyField *field)
{
    gsize n;
    GwyXYZ *xyz = fill_xyz_from_field(field, NULL, GWY_MASK_IGNORE, NULL, &n);
    gwy_shape_fit_preset_calculate_z(preset, xyz, gwy_field_get_data(field), n, params);
    g_free(xyz);
}

static void
create_results(ModuleGUI *gui)
{
    ModuleArgs *args = gui->args;
    GwyShapeFitPreset *preset = gui->preset;
    GwyNLFitParamFlags flags;
    GwyResults *results;
    const gchar *name;
    gint power_xy, power_z;
    guint nparams, i;
    const gchar **names;

    g_clear_object(&gui->results);
    results = gui->results = gwy_results_new();
    gwy_results_add_header(results, N_("Fit Results"));
    gwy_results_add_value_str(results, "file", N_("File"));
    if (args->data_kind == GWY_FILE_XYZ)
        gwy_results_add_value_str(results, "channel", N_("XYZ data"));
    else
        gwy_results_add_value_str(results, "channel", N_("Image"));
    gwy_results_add_format(results, "npts", N_("Number of points"), TRUE,
    /* TRANSLATORS: %{n}i and %{total}i are ids, do NOT translate them. */
                           N_("%{n}i of %{ntotal}i"), NULL);
    gwy_results_add_value_str(results, "func", N_("Fitted function"));
    gwy_results_add_value_z(results, "rss", N_("Mean square difference"));

    gwy_results_add_separator(results);
    gwy_results_add_header(results, N_("Parameters"));
    nparams = gwy_shape_fit_preset_get_n_params(preset);
    names = g_new(const gchar*, nparams);
    for (i = 0; i < nparams; i++) {
        names[i] = name = gwy_shape_fit_preset_get_param_name(preset, i);
        flags = gwy_shape_fit_preset_get_param_flags(preset, i);
        power_xy = gwy_shape_fit_preset_get_param_power_xy(preset, i);
        power_z = gwy_shape_fit_preset_get_param_power_z(preset, i);
        gwy_results_add_value(results, name, "",
                              "symbol", name, "is-fitting-param", TRUE,
                              "power-x", power_xy, "power-z", power_z,
                              "is-angle", (flags & GWY_NLFIT_PARAM_ANGLE),
                              NULL);
    }

    gwy_results_add_separator(results);
    gwy_results_add_correlation_matrixv(results, "correl", N_("Correlation Matrix"), nparams, names);
    g_free(names);

    nparams = gwy_shape_fit_preset_get_n_secondary(preset);
    if (!nparams)
        return;

    gwy_results_add_separator(results);
    gwy_results_add_header(results, N_("Derived Quantities"));
    for (i = 0; i < nparams; i++) {
        name = gwy_shape_fit_preset_get_secondary_name(preset, i);
        flags = gwy_shape_fit_preset_get_secondary_flags(preset, i);
        power_xy = gwy_shape_fit_preset_get_secondary_power_xy(preset, i);
        power_z = gwy_shape_fit_preset_get_secondary_power_z(preset, i);
        gwy_results_add_value(results, name, "",
                              "symbol", name,
                              "power-x", power_xy, "power-z", power_z,
                              "is-angle", (flags & GWY_NLFIT_PARAM_ANGLE),
                              NULL);
    }
}

static void
fill_results(ModuleGUI *gui)
{
    ModuleArgs *args = gui->args;
    GwyShapeFitPreset *preset = gui->preset;
    guint nparams = gwy_shape_fit_preset_get_n_params(gui->preset);
    gdouble param[nparams], error[nparams];
    gboolean fixed[nparams];
    gwy_fit_table_gather(GWY_FIT_TABLE(gui->fit_params), param, error, fixed);

    GwyResults *results = gui->results;
    GwyUnit *xyunit, *zunit;
    const gchar *name;
    guint n;

    if (args->data_kind == GWY_FILE_XYZ) {
        gint idata = gwy_params_get_int(args->params, PARAM_DATA);
        xyunit = gwy_scatter_get_unit_xy(args->scatter);
        zunit = gwy_scatter_get_unit_data(args->scatter, idata);
        n = gwy_scatter_get_n_points(args->scatter);
    }
    else {
        xyunit = gwy_field_get_unit_xy(args->field);
        zunit = gwy_field_get_unit_z(args->field);
        n = gwy_field_get_xres(args->field)*gwy_field_get_yres(args->field);
    }
    gwy_results_fill_data_name(results, "channel", gui->data, args->data_kind, gui->id);
    gwy_results_set_unit(results, "x", xyunit);
    gwy_results_set_unit(results, "y", xyunit);
    gwy_results_set_unit(results, "z", zunit);

    gwy_results_fill_filename(results, "file", gui->data);
    gwy_results_fill_values(results,
                            "func", gwy_resource_get_name(GWY_RESOURCE(preset)),
                            "rss", gui->rss,
                            NULL);
    gwy_results_fill_format(results, "npts",
                            "n", gui->ctx->n,
                            "ntotal", n,
                            NULL);

    for (guint i = 0; i < nparams; i++) {
        name = gwy_shape_fit_preset_get_param_name(preset, i);

        if (fixed[i])
            gwy_results_fill_values(results, name, param[i], NULL);
        else
            gwy_results_fill_values_with_errors(results, name, param[i], error[i], NULL);
    }

    gwy_results_fill_correlation_matrix(results, "correl", fixed, gui->correl);

    nparams = gwy_shape_fit_preset_get_n_secondary(preset);
    GwyFitTable *table = GWY_FIT_TABLE(gui->secondary_params);
    for (guint i = 0; i < nparams; i++) {
        name = gwy_shape_fit_preset_get_secondary_name(preset, i);
        gdouble v = gwy_fit_table_get_value(table, i);
        gdouble e = gwy_fit_table_get_error(table, i);
        gwy_results_fill_values_with_errors(results, name, v, e, NULL);
    }
}

static void
update_same_units(ModuleArgs *args)
{
    GwyUnit *xyunit, *zunit;

    if (args->data_kind == GWY_FILE_XYZ) {
        gint idata = gwy_scatter_find_data_by_label(args->scatter, gwy_params_get_string(args->params, PARAM_DATA));
        idata = MAX(idata, 0);
        xyunit = gwy_scatter_get_unit_xy(args->scatter);
        zunit = gwy_scatter_get_unit_data(args->scatter, idata);
    }
    else if (args->data_kind == GWY_FILE_IMAGE) {
        xyunit = gwy_field_get_unit_xy(args->field);
        zunit = gwy_field_get_unit_z(args->field);
    }
    else {
        g_return_if_reached();
    }

    args->same_units = gwy_unit_equal(xyunit, zunit);
}

static void
sanitise_params(ModuleArgs *args)
{
    GwyParams *params = args->params;
    const gchar *function = gwy_params_get_string(params, PARAM_FUNCTION);
    GwyShapeFitPreset *preset = gwy_inventory_get_item(gwy_shape_fit_presets(), function);

    update_same_units(args);
    if (!args->same_units && gwy_shape_fit_preset_needs_same_units(preset))
        gwy_params_reset(params, PARAM_FUNCTION);
}

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