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

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

#define RUN_MODES GWY_RUN_INTERACTIVE

enum {
    NARGS = 8,
    HISTSIZE = 96,
    USER_UNITS_ID = -1,
};

enum {
    SENS_EXPR_OK = 1 << 0,
    SENS_USERUINTS = 1 << 1
};

enum {
    ARITHMETIC_VALUE,
    ARITHMETIC_MASK,
    ARITHMETIC_DER_X,
    ARITHMETIC_DER_Y,
    ARITHMETIC_NVARS,
};

enum {
    COMMON_COORD_X = 0,
    COMMON_COORD_Y = 1,
    COMMON_COORD_NCOORDS
};

enum {
    ARITHMETIC_NARGS = NARGS * ARITHMETIC_NVARS + COMMON_COORD_NCOORDS
};

enum {
    ARITHMETIC_OK      = 0,
    ARITHMETIC_DATA    = 1,
    ARITHMETIC_EXPR    = 2,
    ARITHMETIC_NUMERIC = 4
};

enum {
    PARAM_EXPRESSION,
    PARAM_DATAUNITS,
    PARAM_USERUNITS,
    PARAM_FIXED_FILLER,
    PARAM_FILLER_VALUE,
    PARAM_IMAGE,
};

typedef GwyField* (*MakeFieldFunc)(GwyField *dfield);

typedef struct {
    GwyParams *params;
    GtkListStore *history;
    GwyField *result;
    GwyNield *mask;
} ModuleArgs;

typedef struct {
    GwyExpr *expr;
    guint err;
    gchar *name[ARITHMETIC_NARGS];
    guint pos[ARITHMETIC_NARGS];
    GwyAppDataId first;
} EvaluationData;

typedef struct {
    ModuleArgs *args;
    EvaluationData *evdata;
    GtkWidget *dialog;
    GtkWidget *view;
    GtkWidget *expression;
    GtkWidget *userunits;
    GtkWidget *userunits_label;
    GwyParamTable *table;
    GtkWidget *result;
    GSList *dataunits;
} ModuleGUI;

static gboolean         module_register     (void);
static GwyParamDef*     define_module_params(void);
static void             module_main         (GwyFile *data,
                                             GwyRunModeFlags mode);
static GwyDialogOutcome run_gui             (GwyFile *data,
                                             gint id,
                                             ModuleArgs *args,
                                             EvaluationData *evdata);
static void             param_changed       (ModuleGUI *gui,
                                             gint id);
static void             execute             (ModuleArgs *args,
                                             EvaluationData *evdata);
static void             data_chosen         (ModuleGUI *gui,
                                             GwyDataChooser *chooser);
static void             expr_changed        (ModuleGUI *gui,
                                             GtkWidget *entry);
static void             userunits_changed   (ModuleGUI *gui,
                                             GtkEntry *entry);
static void             dataunits_selected  (ModuleGUI *gui);
static void             show_state          (ModuleGUI *gui,
                                             const gchar *message);
static const gchar*     check_fields        (ModuleArgs *args,
                                             EvaluationData *evdata);
static void             preview             (gpointer user_data);
static void             make_mask_if_needed (ModuleArgs *args);
static void             need_data           (const EvaluationData *evdata,
                                             gboolean *nd);
static void             update_history      (ModuleArgs *args);
static GwyField*        make_x              (GwyField *dfield);
static GwyField*        make_y              (GwyField *dfield);
static GwyField*        make_x_der          (GwyField *dfield);
static GwyField*        make_y_der          (GwyField *dfield);
static void             sanitise_params     (ModuleArgs *args);
static GtkListStore*    load_history        (void);
static void             save_history        (GtkListStore *history);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Simple arithmetic operations with data fields."),
    "Yeti <yeti@gwyddion.net>",
    "4.2",
    "David Nečas (Yeti)",
    "2004",
};

GWY_MODULE_QUERY2(module_info, arithmetic)

static gboolean
module_register(void)
{
    gwy_image_func_register("arithmetic",
                            module_main,
                            N_("/M_ultidata/_Arithmetic..."),
                            GWY_ICON_ARITHMETIC,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Arithmetic operations on data"));

    return TRUE;
}

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

    if (paramdef)
        return paramdef;

    paramdef = gwy_param_def_new();
    gwy_param_def_set_function_name(paramdef, gwy_image_func_current());
    gwy_param_def_add_string(paramdef, PARAM_EXPRESSION, "expression", _("_Expression"),
                             GWY_PARAM_STRING_NULL_IS_EMPTY, NULL, "d1-d2");
    gwy_param_def_add_int(paramdef, PARAM_DATAUNITS, "dataunits", NULL, -1, NARGS-1, 0);
    gwy_param_def_add_unit(paramdef, PARAM_USERUNITS, "userunits", _("Specify un_its"), NULL);
    gwy_param_def_add_boolean(paramdef, PARAM_FIXED_FILLER, "fixed_filler", _("_Fixed filler value"), FALSE);
    gwy_param_def_add_double(paramdef, PARAM_FILLER_VALUE, "filler_value", _("_Fixed filler value"),
                             -G_MAXDOUBLE, G_MAXDOUBLE, 0.0);
    for (i = 0; i < NARGS; i++) {
        gchar *s = g_strdup_printf("image%u", i);
        gwy_param_def_add_image_id(paramdef, PARAM_IMAGE + i, s, NULL);
    }
    return paramdef;
}

static void
module_main(GwyFile *data, GwyRunModeFlags mode)
{
    ModuleArgs args;
    EvaluationData evdata;
    GwyAppDataId dataid;
    GwyDialogOutcome outcome;
    gint newid;
    guint i;

    g_return_if_fail(mode & RUN_MODES);
    gwy_clear1(args);
    gwy_data_browser_get_current(GWY_APP_FIELD_ID, &dataid.id,
                                 GWY_APP_FILE_ID, &dataid.datano,
                                 0);
    g_return_if_fail(gwy_app_data_id_verify_channel(&dataid));
    args.params = gwy_params_new_from_settings(define_module_params());
    gwy_params_set_image_id(args.params, PARAM_IMAGE, dataid);
    args.history = load_history();
    args.result = gwy_field_new(PREVIEW_SIZE, PREVIEW_SIZE, 1.0, 1.0, TRUE);
    sanitise_params(&args);

    evdata.expr = gwy_expr_new();
    gwy_expr_define_constant(evdata.expr, "pi", G_PI, NULL);
    gwy_expr_define_constant(evdata.expr, "π", G_PI, NULL);

    for (i = 0; i < NARGS; i++) {
        evdata.name[i] = g_strdup_printf("d%d", i+1);
        evdata.name[NARGS*ARITHMETIC_MASK + i] = g_strdup_printf("m%d", i+1);
        evdata.name[NARGS*ARITHMETIC_DER_X + i] = g_strdup_printf("bx%d", i+1);
        evdata.name[NARGS*ARITHMETIC_DER_Y + i] = g_strdup_printf("by%d", i+1);
    }
    evdata.name[NARGS*ARITHMETIC_NVARS + 0] = g_strdup("x");
    evdata.name[NARGS*ARITHMETIC_NVARS + 1] = g_strdup("y");

    outcome = run_gui(data, dataid.id, &args, &evdata);
    gwy_params_save_to_settings(args.params);
    save_history(args.history);
    if (outcome == GWY_DIALOG_CANCEL)
        goto end;
    if (outcome != GWY_DIALOG_HAVE_RESULT)
        execute(&args, &evdata);

    newid = gwy_file_add_image(data, args.result);

    gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
    gwy_file_set_title(data, GWY_FILE_IMAGE, newid, _("Calculated"), TRUE);
    if (args.mask) {
        gwy_nield_number_contiguous(args.mask);
        gwy_file_set_image_mask(data, newid, args.mask);
        gwy_file_sync_items(gwy_data_browser_get_file(evdata.first.datano), GWY_FILE_IMAGE, evdata.first.id,
                            data, GWY_FILE_IMAGE, newid,
                            GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_MASK_COLOR | GWY_FILE_ITEM_REAL_SQUARE, FALSE);
    }
    gwy_log_add(data, GWY_FILE_IMAGE, -1, newid);

end:
    g_clear_object(&args.result);
    g_clear_object(&args.mask);
    g_object_unref(args.params);
    g_object_unref(args.history);
    gwy_expr_free(evdata.expr);
    for (i = 0; i < ARITHMETIC_NARGS; i++)
        g_free(evdata.name[i]);
}

static GwyDialogOutcome
run_gui(GwyFile *data, gint id,
        ModuleArgs *args, EvaluationData *evdata)
{
    gint dataunits = gwy_params_get_int(args->params, PARAM_DATAUNITS);
    const gchar *userunits = gwy_params_get_string(args->params, PARAM_USERUNITS);
    GtkWidget *hbox, *hbox2, *grid, *chooser, *entry, *label, *button;
    ModuleGUI gui;
    GwyDialog *dialog;
    guint i, row;
    gchar *s;

    gui.args = args;
    gui.evdata = evdata;

    gui.dialog = gwy_dialog_new(_("Arithmetic"));
    dialog = GWY_DIALOG(gui.dialog);
    gwy_dialog_add_stock_buttons(dialog, GWY_STOCK_UPDATE, GWY_STOCK_CANCEL, GWY_STOCK_OK, 0);

    gui.view = gwy_create_preview(args->result, args->mask, PREVIEW_SIZE);
    gwy_setup_data_view(GWY_DATA_VIEW(gui.view), data, GWY_FILE_IMAGE, id,
                        GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_MASK_COLOR | GWY_FILE_ITEM_REAL_SQUARE);
    hbox = gwy_create_dialog_preview_hbox(GTK_DIALOG(dialog), GWY_DATA_VIEW(gui.view), FALSE);

    grid = gtk_grid_new();
    gtk_grid_set_row_spacing(GTK_GRID(grid), 2);
    gtk_grid_set_column_spacing(GTK_GRID(grid), 6);
    gtk_container_set_border_width(GTK_CONTAINER(grid), 4);
    gtk_box_pack_start(GTK_BOX(hbox), grid, TRUE, TRUE, 4);
    row = 0;

    label = gtk_label_new_with_mnemonic(_("_Expression:"));
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_grid_attach(GTK_GRID(grid), label, 0, row++, 2, 1);

    entry = gtk_combo_box_new_with_model_and_entry(GTK_TREE_MODEL(args->history));
    gtk_combo_box_set_entry_text_column(GTK_COMBO_BOX(entry), 0);
    gui.expression = entry;
    gtk_combo_box_set_active(GTK_COMBO_BOX(gui.expression), 0);
    gtk_grid_attach(GTK_GRID(grid), entry, 0, row++, 3, 1);
    g_signal_connect_swapped(entry, "changed", G_CALLBACK(expr_changed), &gui);
    g_signal_connect_swapped(gtk_bin_get_child(GTK_BIN(entry)), "activate", G_CALLBACK(preview), &gui);
    gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);

    gui.result = label = gtk_label_new(NULL);
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_grid_attach(GTK_GRID(grid), label, 0, row++, 3, 1);

    label = gtk_label_new(_("Operands"));
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gwy_set_widget_margins(label, -1, -1, 8, -1);
    gtk_grid_attach(GTK_GRID(grid), label, 0, row, 2, 1);

    label = gtk_label_new(_("Units"));
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_grid_attach(GTK_GRID(grid), label, 2, row++, 1, 1);

    gui.dataunits = NULL;
    for (i = 0; i < NARGS; i++) {
        GwyAppDataId dataid;

        /* VALUE is 0 */
        s = g_strdup_printf("d_%d", i+1);
        label = gtk_label_new_with_mnemonic(evdata->name[i]);
        g_free(s);
        gtk_label_set_xalign(GTK_LABEL(label), 0.0);
        gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);

        chooser = gwy_data_chooser_new(GWY_FILE_IMAGE);
        dataid = gwy_params_get_data_id(args->params, PARAM_IMAGE + i);
        gwy_data_chooser_set_active_id(GWY_DATA_CHOOSER(chooser), &dataid);
        g_signal_connect_swapped(chooser, "changed", G_CALLBACK(data_chosen), &gui);
        g_object_set_data(G_OBJECT(chooser), "index", GUINT_TO_POINTER(i));
        gtk_grid_attach(GTK_GRID(grid), chooser, 1, row, 1, 1);
        gtk_label_set_mnemonic_widget(GTK_LABEL(label), chooser);

        button = gtk_radio_button_new(gui.dataunits);
        gui.dataunits = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button));
        gwy_radio_button_set_value(button, i);
        s = g_strdup_printf(_("Take result units from data d%d"), i+1);
        gtk_widget_set_tooltip_text(button, s);
        g_free(s);
        gtk_grid_attach(GTK_GRID(grid), button, 2, row++, 1, 1);
        g_signal_connect_swapped(button, "clicked", G_CALLBACK(dataunits_selected), &gui);
    }

    hbox2 = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
    gtk_grid_attach(GTK_GRID(grid), hbox2, 0, row, 2, 1);

    label = gtk_label_new_with_mnemonic(_("Specify un_its:"));
    gtk_box_pack_start(GTK_BOX(hbox2), label, FALSE, FALSE, 0);
    gui.userunits_label = label;
    gtk_widget_set_sensitive(gui.userunits_label, dataunits == USER_UNITS_ID);

    gui.userunits = entry = gtk_entry_new();
    gtk_entry_set_text(GTK_ENTRY(entry), userunits ? userunits : "");
    gtk_box_pack_start(GTK_BOX(hbox2), entry, TRUE, TRUE, 0);
    gtk_label_set_mnemonic_widget(GTK_LABEL(label), entry);
    gtk_widget_set_sensitive(entry, dataunits == USER_UNITS_ID);
    g_signal_connect_swapped(entry, "changed", G_CALLBACK(userunits_changed), &gui);

    button = gtk_radio_button_new(gui.dataunits);
    gui.dataunits = gtk_radio_button_get_group(GTK_RADIO_BUTTON(button));
    gwy_radio_button_set_value(button, USER_UNITS_ID);
    gtk_widget_set_tooltip_text(button, _("Specify result units explicitly"));
    gtk_grid_attach(GTK_GRID(grid), button, 2, row++, 1, 1);
    g_signal_connect_swapped(button, "clicked", G_CALLBACK(dataunits_selected), &gui);

    gui.table = gwy_param_table_new(args->params);
    gwy_param_table_append_entry(gui.table, PARAM_FILLER_VALUE);
    gwy_param_table_add_enabler(gui.table, PARAM_FIXED_FILLER, PARAM_FILLER_VALUE);
    gwy_param_table_entry_set_width(gui.table, PARAM_FILLER_VALUE, 12);
    gtk_grid_attach(GTK_GRID(grid), gwy_param_table_widget(gui.table), 0, row++, 3, 1);

    g_signal_connect_swapped(gui.table, "param-changed", G_CALLBACK(param_changed), &gui);
    gwy_dialog_set_preview_func(dialog, GWY_PREVIEW_UPON_REQUEST, preview, &gui, NULL);
    gtk_widget_grab_focus(gui.expression);
    gwy_radio_buttons_set_current(gui.dataunits, dataunits);
    expr_changed(&gui, gui.expression); /* resolves variables */

    return gwy_dialog_run(dialog);
}

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

    if (id < 0 || id == PARAM_FILLER_VALUE) {
        gdouble filler_value = gwy_params_get_double(params, PARAM_FILLER_VALUE);
        if (gwy_isinf(filler_value) || gwy_isnan(filler_value))
            gwy_param_table_set_double(gui->table, PARAM_FILLER_VALUE, 0.0);
    }
    gwy_dialog_invalidate(GWY_DIALOG(gui->dialog));
}

static void
data_chosen(ModuleGUI *gui, GwyDataChooser *chooser)
{
    ModuleArgs *args = gui->args;
    guint i = GPOINTER_TO_UINT(g_object_get_data(G_OBJECT(chooser), "index"));
    GwyAppDataId dataid;

    gwy_data_chooser_get_active_id(chooser, &dataid);
    gwy_params_set_image_id(args->params, PARAM_IMAGE + i, dataid);
    gwy_dialog_invalidate(GWY_DIALOG(gui->dialog));
    if (!(gui->evdata->err & ARITHMETIC_EXPR))
        show_state(gui, NULL);
}

static void
expr_changed(ModuleGUI *gui, GtkWidget *entry)
{
    ModuleArgs *args = gui->args;
    EvaluationData *evdata = gui->evdata;
    const gchar *expr = gtk_entry_get_text(GTK_ENTRY(gtk_bin_get_child(GTK_BIN(entry))));
    GError *error = NULL;
    const gchar *message = NULL;
    gchar *s = NULL;

    gwy_params_set_string(args->params, PARAM_EXPRESSION, expr);
    evdata->err = ARITHMETIC_OK;

    if (gwy_expr_compile(evdata->expr, expr, &error)) {
        guint nvars = gwy_expr_get_variables(evdata->expr, NULL);
        g_return_if_fail(nvars);
        if (nvars == 1) {
            gdouble v = gwy_expr_execute(evdata->expr, NULL);
            message = s = g_strdup_printf("%.8g", v);
            evdata->err = ARITHMETIC_NUMERIC;
        }
        else {
            if (gwy_expr_resolve_variables(evdata->expr,
                                           ARITHMETIC_NARGS, (const gchar*const*)evdata->name, evdata->pos)) {
                evdata->err = ARITHMETIC_EXPR;
                message = _("Expression contains unknown identifiers");
            }
#ifdef DEBUG
            {
                gint i;
                for (i = 0; i < ARITHMETIC_NARGS; i++)
                    gwy_debug("pos[%u] = %d", i, evdata->pos[i]);
            }
#endif
        }
    }
    else {
        evdata->err = ARITHMETIC_EXPR;
        message = error->message;
    }

    show_state(gui, message);
    g_clear_error(&error);
    g_free(s);
    gwy_dialog_invalidate(GWY_DIALOG(gui->dialog));
}

static void
userunits_changed(ModuleGUI *gui, GtkEntry *entry)
{
    gwy_params_set_unit(gui->args->params, PARAM_USERUNITS, gtk_entry_get_text(entry));
    gwy_dialog_invalidate(GWY_DIALOG(gui->dialog));
}

static void
dataunits_selected(ModuleGUI *gui)
{
    ModuleArgs *args = gui->args;
    gint dataunits = gwy_radio_buttons_get_current(gui->dataunits);

    gwy_params_set_int(args->params, PARAM_DATAUNITS, dataunits);
    gtk_widget_set_sensitive(gui->userunits, dataunits == USER_UNITS_ID);
    gtk_widget_set_sensitive(gui->userunits_label, dataunits == USER_UNITS_ID);
    gwy_dialog_invalidate(GWY_DIALOG(gui->dialog));
}

static void
show_state(ModuleGUI *gui, const gchar *message)
{
    GtkDialog *dialog = GTK_DIALOG(gui->dialog);
    ModuleArgs *args = gui->args;
    EvaluationData *evdata = gui->evdata;

    if (!message && evdata->err != ARITHMETIC_NUMERIC) {
        message = check_fields(args, evdata);
        if (!evdata->err)
            message = NULL;
    }
    gboolean ok = (evdata->err == ARITHMETIC_OK || evdata->err == ARITHMETIC_NUMERIC);
    gtk_dialog_set_response_sensitive(dialog, GTK_RESPONSE_OK, ok);
    gtk_dialog_set_response_sensitive(dialog, GWY_RESPONSE_UPDATE, ok);
    gwy_set_message_label(GTK_LABEL(gui->result), message, ok ? GTK_MESSAGE_INFO : GTK_MESSAGE_ERROR, FALSE);
}

static const gchar*
check_fields(ModuleArgs *args, EvaluationData *evdata)
{
    guint first = 0, i;
    GwyField *dfirst, *dfield;
    GwyDataMismatchFlags diff;
    gboolean nd[NARGS];

    if (evdata->err & (ARITHMETIC_EXPR | ARITHMETIC_NUMERIC))
        return NULL;

    need_data(evdata, nd);
    for (i = 0; i < NARGS; i++) {
        if (nd[i]) {
            first = i;
            break;
        }
    }
    if (i == NARGS) {
        /* no variables */
        evdata->err &= ~ARITHMETIC_DATA;
        return NULL;
    }

    /* Each image must match the first. This is transitive. */
    dfirst = gwy_params_get_image(args->params, PARAM_IMAGE + first);
    for (i = first+1; i < NARGS; i++) {
        if (!nd[i])
            continue;

        dfield = gwy_params_get_image(args->params, PARAM_IMAGE + i);
        diff = gwy_field_is_incompatible(dfirst, dfield,
                                         GWY_DATA_MISMATCH_RES
                                         | GWY_DATA_MISMATCH_REAL
                                         | GWY_DATA_MISMATCH_LATERAL);
        if (diff) {
            evdata->err |= ARITHMETIC_DATA;
            if (diff & GWY_DATA_MISMATCH_RES)
                return _("Pixel dimensions differ");
            if (diff & GWY_DATA_MISMATCH_LATERAL)
                return _("Lateral dimensions are different physical quantities");
            if (diff & GWY_DATA_MISMATCH_REAL)
                return _("Physical dimensions differ");
        }
    }

    evdata->err &= ~ARITHMETIC_DATA;
    return NULL;
}

static void
preview(gpointer user_data)
{
    ModuleGUI *gui = (ModuleGUI*)user_data;
    ModuleArgs *args = gui->args;

    /* We can also get here by activation of the entry so check again. */
    if (gui->evdata->err != ARITHMETIC_OK)
        return;

    execute(args, gui->evdata);
    g_return_if_fail(args->result);

    gwy_field_data_changed(args->result);
    gwy_data_view_set_mask(GWY_DATA_VIEW(gui->view), args->mask);
    gwy_set_data_preview_size(GWY_DATA_VIEW(gui->view), PREVIEW_SIZE);
    gwy_dialog_have_result(GWY_DIALOG(gui->dialog));
}

static void
update_field(GwyField *src, GwyField *target, gint *n, gdouble **tdata)
{
    gint xres = gwy_field_get_xres(src), yres = gwy_field_get_yres(src);

    gwy_field_resize(target, xres, yres);
    gwy_field_copy_units(src, target);
    gwy_field_set_xreal(target, gwy_field_get_xreal(src));
    gwy_field_set_yreal(target, gwy_field_get_yreal(src));
    gwy_field_set_xoffset(target, gwy_field_get_xoffset(src));
    gwy_field_set_yoffset(target, gwy_field_get_yoffset(src));
    *n = xres*yres;
    *tdata = gwy_field_get_data(target);
}

static void
execute(ModuleArgs *args, EvaluationData *evdata)
{
    static MakeFieldFunc derivers[ARITHMETIC_NVARS] = { NULL, NULL, make_x_der, make_y_der, };
    GwyField **fields, *field, *empty = NULL;
    GwyNield *mask;
    const gdouble **d;
    gboolean nd[NARGS];
    gdouble *r = NULL;
    gboolean first = TRUE;
    gint n = 0, i, dataunits;

    g_return_if_fail(evdata->err == ARITHMETIC_OK);

    need_data(evdata, nd);
    /* We know the expression can't contain more variables */
    fields = g_new0(GwyField*, ARITHMETIC_NARGS);
    d = g_new0(const gdouble*, ARITHMETIC_NARGS + 1);
    g_clear_object(&args->mask);

    /* First get all the data fields we directly have */
    for (i = 0; i < NARGS; i++) {
        gwy_debug("field[%u]: %s", i, nd[i] ? "NEEDED" : "not needed");
        if (!nd[i])
            continue;

        field = fields[i] = gwy_params_get_image(args->params, PARAM_IMAGE + i);
        d[evdata->pos[i]] = gwy_field_get_data_const(field);
        gwy_debug("d[%u] set to PRIMARY %u", evdata->pos[i], i);
        if (first) {
            first = FALSE;
            update_field(field, args->result, &n, &r);
            evdata->first = gwy_params_get_data_id(args->params, PARAM_IMAGE + i);
        }
    }

    /* Then the mask fields */
    for (i = NARGS*ARITHMETIC_MASK; i < NARGS*(ARITHMETIC_MASK + 1); i++) {
        GwyField *mask_field;

        gwy_debug("mask[%u]: %s", i, nd[i % NARGS] ? "NEEDED" : "not needed");
        if (!nd[i % NARGS])
            continue;

        field = gwy_params_get_image(args->params, PARAM_IMAGE + i % NARGS);
        if (first) {
            first = FALSE;
            update_field(field, args->result, &n, &r);
            evdata->first = gwy_params_get_data_id(args->params, PARAM_IMAGE + i);
        }

        /* Expr evaluation requires arrays of doubles. We have to create a Field from the Nield. */
        if (!(mask = gwy_params_get_image_mask(args->params, PARAM_IMAGE + i % NARGS))) {
            if (!empty)
                empty = gwy_field_new_alike(field, TRUE);
            mask_field = empty;
        }
        else {
            mask_field = fields[i] = gwy_field_new_alike(field, TRUE);
            gwy_field_area_fill(mask_field, mask, GWY_MASK_INCLUDE,
                                0, 0, gwy_field_get_xres(field), gwy_field_get_yres(field), 1.0);
        }

        d[evdata->pos[i]] = gwy_field_get_data_const(mask_field);
    }

    /* Derive derived fields */
    for (i = NARGS*ARITHMETIC_DER_X; i < NARGS*(ARITHMETIC_DER_Y + 1); i++) {
        if (!evdata->pos[i])
            continue;

        g_return_if_fail(fields[i % NARGS]);
        field = derivers[i/NARGS](fields[i % NARGS]);
        gwy_debug("d[%u] set to DERIVED %u (type %u)", evdata->pos[i], i/NARGS, i%NARGS);
        fields[i] = field;
        d[evdata->pos[i]] = gwy_field_get_data_const(field);
    }

    i = NARGS*ARITHMETIC_NVARS + COMMON_COORD_X;
    if (evdata->pos[i]) {
        field = make_x(fields[0]);
        fields[i] = field;
        d[evdata->pos[i]] = gwy_field_get_data_const(field);
    }

    i = NARGS*ARITHMETIC_NVARS + COMMON_COORD_Y;
    if (evdata->pos[i]) {
        field = make_y(fields[0]);
        fields[i] = field;
        d[evdata->pos[i]] = gwy_field_get_data_const(field);
    }

    /* Execute */
    gwy_expr_vector_execute(evdata->expr, n, d, r);
    gwy_field_invalidate(args->result);

    /* Set units. */
    dataunits = gwy_params_get_int(args->params, PARAM_DATAUNITS);
    if (dataunits == USER_UNITS_ID) {
        gwy_unit_set_from_string(gwy_field_get_unit_z(args->result),
                                 gwy_params_get_string(args->params, PARAM_USERUNITS));
    }
    else
        gwy_field_copy_units(gwy_params_get_image(args->params, PARAM_IMAGE + dataunits), args->result);

    /* Free stuff */
    g_clear_object(&empty);
    for (i = NARGS; i < ARITHMETIC_NARGS; i++)
        g_clear_object(fields + i);
    g_free(fields);
    g_free(d);

    make_mask_if_needed(args);
}

static void
make_mask_if_needed(ModuleArgs *args)
{
    GwyField *result = args->result;
    GwyNield *mask;

    args->mask = mask = gwy_mask_nans_in_field(result, FALSE);
    if (!mask)
        return;

    if (gwy_params_get_boolean(args->params, PARAM_FIXED_FILLER)) {
        gint xres = gwy_field_get_xres(result);
        gint yres = gwy_field_get_yres(result);
        gdouble filler_value = gwy_params_get_double(args->params, PARAM_FILLER_VALUE);
        gwy_field_area_fill(result, mask, GWY_MASK_INCLUDE, 0, 0, xres, yres, filler_value);
    }
    else
        gwy_field_laplace_solve(result, mask, GWY_LAPLACE_MASKED, 0.25);
}

/* Find which data we need, for derivatives or otherwise */
static void
need_data(const EvaluationData *evdata, gboolean *nd)
{
    guint i;

    gwy_clear(nd, NARGS);
    for (i = 0; i < NARGS*ARITHMETIC_NVARS; i++) {
        if (evdata->pos[i])
            nd[i % NARGS] = TRUE;
    }

    // When x and y are needed, always take them from field 1.  This also ensures the expression is considered to be
    // a field expression.
    for (i = NARGS*ARITHMETIC_NVARS; i < ARITHMETIC_NARGS; i++) {
        if (evdata->pos[i]) {
            nd[0] = TRUE;
            break;
        }
    }
}

static GwyField*
make_x(GwyField *dfield)
{
    gint xres = gwy_field_get_xres(dfield), yres = gwy_field_get_yres(dfield);
    GwyField *result = gwy_field_new_alike(dfield, FALSE);
    gdouble dx = gwy_field_get_dx(dfield);
    gdouble xoff = gwy_field_get_xoffset(dfield);
    gdouble *data = gwy_field_get_data(result);
    gint i;

    gwy_math_linspace(data, xres, xoff + 0.5*dx, dx);
    for (i = 1; i < yres; i++)
        gwy_assign(data + i*xres, data, xres);

    return result;
}

static GwyField*
make_y(GwyField *dfield)
{
    gint xres = gwy_field_get_xres(dfield), yres = gwy_field_get_yres(dfield);
    GwyField *result = gwy_field_new_alike(dfield, FALSE);
    gdouble dy = gwy_field_get_dy(dfield);
    gdouble yoff = gwy_field_get_yoffset(dfield);
    gdouble *data = gwy_field_get_data(result);
    gint i, j;

    for (i = 0; i < yres; i++) {
        gdouble y = (i + 0.5)*dy + yoff;
        gdouble *rrow = data + i*xres;
        for (j = 0; j < xres; j++, rrow++)
            *rrow = y;
    }

    return result;
}

static GwyField*
make_x_der(GwyField *dfield)
{
    gint xres = gwy_field_get_xres(dfield), yres = gwy_field_get_yres(dfield);
    GwyField *result = gwy_field_new_alike(dfield, FALSE);
    gdouble h = 2.0*gwy_field_get_dx(dfield);
    const gdouble *drow, *d = gwy_field_get_data_const(dfield);
    gdouble *rrow, *r = gwy_field_get_data(result);
    gint i, j;

    if (xres < 2) {
        gwy_field_clear(result);
        return result;
    }

    for (i = 0; i < yres; i++) {
        drow = d + i*xres;
        rrow = r + i*xres;
        rrow[0] = 2.0*(drow[1] - drow[0])/h;
        for (j = 1; j < xres-1; j++)
            rrow[j] = (drow[j+1] - drow[j-1])/h;
        rrow[xres-1] = 2.0*(drow[xres-1] - drow[xres-2])/h;
    }

    return result;
}

static GwyField*
make_y_der(GwyField *dfield)
{
    gint xres = gwy_field_get_xres(dfield), yres = gwy_field_get_yres(dfield);
    GwyField *result = gwy_field_new_alike(dfield, FALSE);
    gdouble h = 2.0*gwy_field_get_dy(dfield);
    const gdouble *drow, *drowp, *drowm, *d = gwy_field_get_data_const(dfield);
    gdouble *rrow, *r = gwy_field_get_data(result);
    guint i, j;

    if (yres < 2) {
        gwy_field_clear(result);
        return result;
    }

    rrow = r;
    drow = d;
    drowp = d + xres;
    for (j = 0; j < xres; j++)
        rrow[j] = 2.0*(drowp[j] - drow[j])/h;

    for (i = 1; i < yres-1; i++) {
        drowm = drow;
        drow = drowp;
        drowp += xres;
        rrow += xres;
        for (j = 0; j < xres; j++)
            rrow[j] = (drowp[j] - drowm[j])/h;
    }

    drowm = drow;
    drow = drowp;
    rrow += xres;
    for (j = 0; j < xres; j++)
        rrow[j] = 2.0*(drow[j] - drowm[j])/h;

    return result;
}

static void
update_history(ModuleArgs *args)
{
    const gchar *expr = gwy_params_get_string(args->params, PARAM_EXPRESSION);
    GtkListStore *store = args->history;
    GtkTreeModel *model = GTK_TREE_MODEL(store);
    GtkTreeIter iter;
    gchar *s;

    if (!expr)
        return;

    gtk_list_store_prepend(store, &iter);
    gtk_list_store_set(store, &iter, 0, expr, -1);

    while (gtk_tree_model_iter_next(model, &iter)) {
        gtk_tree_model_get(model, &iter, 0, &s, -1);
        if (gwy_strequal(s, expr)) {
            gtk_list_store_remove(store, &iter);
            g_free(s);
            break;
        }
        g_free(s);
    }
}

static GtkListStore*
load_history(void)
{
    GtkListStore *store;
    gchar *buffer, *line, *p;
    gsize size;

    store = gtk_list_store_new(1, G_TYPE_STRING);
    if (!gwy_module_data_load("arithmetic", "history", &buffer, &size, NULL))
        return store;

    p = buffer;
    for (line = gwy_str_next_line(&p); line; line = gwy_str_next_line(&p)) {
        if (*g_strstrip(line)) {
            GtkTreeIter iter;
            gtk_list_store_append(store, &iter);
            gtk_list_store_set(store, &iter, 0, line, -1);
        }
    }
    g_free(buffer);
    return store;
}

static void
sanitise_params(ModuleArgs *args)
{
    GwyParams *params = args->params;
    GwyAppDataId dataid;
    guint i;

    dataid = gwy_params_get_data_id(params, PARAM_IMAGE);
    for (i = 1; i < NARGS; i++) {
        /* Replace lost images with d1. */
        if (!gwy_params_get_image(params, PARAM_IMAGE + i))
            gwy_params_set_image_id(params, PARAM_IMAGE + i, dataid);
    }
    /* Ensures args->expression comes first */
    update_history(args);
}

static void
save_history(GtkListStore *history)
{
    GtkTreeModel *model = GTK_TREE_MODEL(history);
    GtkTreeIter iter;
    gchar *s;
    FILE *fh;
    gint i = 0;

    if (!(fh = gwy_module_data_fopen("arithmetic", "history", "w", NULL)))
        return;

    if (gtk_tree_model_get_iter_first(model, &iter)) {
        do {
            gtk_tree_model_get(model, &iter, 0, &s, -1);
            fputs(s, fh);
            fputc('\n', fh);
            g_free(s);
        } while (++i < HISTSIZE && gtk_tree_model_iter_next(model, &iter));
    }
    fclose(fh);
}

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