/*
 *  $Id: mark_with.c 30047 2026-06-11 08:30:45Z yeti-dn $
 *  Copyright (C) 2009-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 <gtk/gtk.h>
#include <gwy.h>
#include "../preview.h"

#define RUN_MODES GWY_RUN_INTERACTIVE

enum {
    PARAM_MARK_WITH,
    PARAM_OPERATION,
    PARAM_MIN,
    PARAM_MAX,
    PARAM_OPERAND_IMAGE,
    PARAM_OPERAND_MASK,
    PARAM_OPERAND_SHOW,
    PARAM_UPDATE,
    PARAM_MASK_COLOR,
    LABEL_MARK_WITH,
    LABEL_RANGE,
};

typedef enum {
    MARK_WITH_MASK  = 0,
    MARK_WITH_IMAGE = 1,
    MARK_WITH_SHOW  = 2,
} MarkWithWhat;

typedef struct {
    GwyParams *params;
    GwyField *field;
    GwyNield *mask;
    GwyNield *source;
    GwyNield *result;
} ModuleArgs;

typedef struct {
    ModuleArgs *args;
    GtkWidget *dialog;
    GtkWidget *srcview;
    GwyParamTable *table;
    GSList *withwhat;
    GtkWidget *operand[3];
    gboolean has_any[3];
    GtkSizeGroup *sizegroup;
    GwyField *empty;
} ModuleGUI;

static gboolean         module_register                (void);
static GwyParamDef*     define_module_params           (void);
static void             module_main                    (GwyFile *data,
                                                        GwyRunModeFlags mode);
static GwyDialogOutcome run_gui                        (ModuleArgs *args,
                                                        GwyFile *data,
                                                        gint id);
static void             execute                        (ModuleArgs *args);
static void             param_changed                  (ModuleGUI *gui,
                                                        gint id);
static void             preview                        (gpointer user_data);
static GObject*         get_other_field                (GwyParams *params,
                                                        GwyFile **pdata,
                                                        gint *id);
static void             dialog_response                (GwyDialog *dialog,
                                                        gint response,
                                                        ModuleGUI *gui);
static void             ensure_something_is_selected   (ModuleGUI *gui);
static GtkWidget*       create_operand_row_mask        (gpointer user_data);
static GtkWidget*       create_operand_row_image       (gpointer user_data);
static GtkWidget*       create_operand_row_presentation(gpointer user_data);
static void             operand_changed                (GwyDataChooser *chooser,
                                                        ModuleGUI *gui);
static gboolean         operand_filter_mask            (GwyFile *source,
                                                        gint id,
                                                        gpointer user_data);
static gboolean         operand_filter_image           (GwyFile *source,
                                                        gint id,
                                                        gpointer user_data);
static gboolean         operand_filter_presentation    (GwyFile *source,
                                                        gint id,
                                                        gpointer user_data);
static void             with_what_selected             (GtkRadioButton *button,
                                                        ModuleGUI *gui);

static const GwyEnum withwhats[] = {
    { gwy_NC("with", "_Mask:"),         MARK_WITH_MASK,  },
    { gwy_NC("with", "_Image:"),        MARK_WITH_IMAGE, },
    { gwy_NC("with", "_Presentation:"), MARK_WITH_SHOW,  },
};

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Creates or modifies a mask using other channels."),
    "Yeti <yeti@gwyddion.net>",
    "3.1",
    "David Nečas (Yeti)",
    "2009",
};

GWY_MODULE_QUERY2(module_info, mark_with)

static gboolean
module_register(void)
{
    gwy_image_func_register("mark_with",
                            module_main,
                            N_("/_Mask/Mark _With..."),
                            GWY_ICON_MARK_WITH,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Mask combining and modification"));

    return TRUE;
}

static GwyParamDef*
define_module_params(void)
{
    static const GwyEnum operations[] = {
        { N_("Se_t mask"),        GWY_MERGE_COPY,         },
        { N_("_Add mask"),        GWY_MERGE_UNION,        },
        { N_("_Subtract mask"),   GWY_MERGE_SUBTRACT,     },
        { N_("_Intersect masks"), GWY_MERGE_INTERSECTION, },
    };

    static GwyParamDef *paramdef = NULL;

    if (paramdef)
        return paramdef;

    paramdef = gwy_param_def_new();
    gwy_param_def_set_function_name(paramdef, gwy_image_func_current());
    gwy_param_def_add_gwyenum(paramdef, PARAM_MARK_WITH, "mark_with", _("Mark with"),
                              withwhats, G_N_ELEMENTS(withwhats), MARK_WITH_MASK);
    gwy_param_def_add_gwyenum(paramdef, PARAM_OPERATION, "operation", _("Operation"),
                              operations, G_N_ELEMENTS(operations), GWY_MERGE_COPY);
    gwy_param_def_add_percentage(paramdef, PARAM_MIN, "min", _("_Minimum"), 0.0);
    gwy_param_def_add_percentage(paramdef, PARAM_MAX, "max", _("M_aximum"), 1.0);
    gwy_param_def_add_image_id(paramdef, PARAM_OPERAND_MASK, "operand_mask", NULL);
    gwy_param_def_add_image_id(paramdef, PARAM_OPERAND_IMAGE, "operand_image", NULL);
    gwy_param_def_add_image_id(paramdef, PARAM_OPERAND_SHOW, "operand_presentation", NULL);
    gwy_param_def_add_instant_updates(paramdef, PARAM_UPDATE, "update", NULL, TRUE);
    gwy_param_def_add_mask_color(paramdef, PARAM_MASK_COLOR, NULL, NULL);
    return paramdef;
}

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

    g_return_if_fail(mode & RUN_MODES);

    gwy_data_browser_get_current(GWY_APP_FIELD_ID, &id,
                                 GWY_APP_FIELD, &args.field,
                                 GWY_APP_MASK_FIELD, &args.mask,
                                 GWY_APP_MASK_FIELD_KEY, &mquark,
                                 0);
    g_return_if_fail(args.field && mquark);

    args.source = gwy_field_new_nield_alike(args.field);
    args.result = gwy_field_new_nield_alike(args.field);
    args.params = gwy_params_new_from_settings(define_module_params());

    outcome = run_gui(&args, data, id);
    if (outcome == GWY_DIALOG_CANCEL)
        goto end;
    if (outcome == GWY_DIALOG_PROCEED)
        execute(&args);

    gwy_app_undo_qcheckpointv(GWY_DICT(data), 1, &mquark);
    gwy_dict_set_object(GWY_DICT(data), mquark, args.result);
    gwy_log_add(data, GWY_FILE_IMAGE, id, id);

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

static GwyDialogOutcome
run_gui(ModuleArgs *args, GwyFile *data, gint id)
{
    GtkWidget *hbox, *vbox, *label, *vbox2, *dataview;
    GwyDialog *dialog;
    GwyParamTable *table;
    ModuleGUI gui;
    gchar *s;

    gwy_clear1(gui);
    gui.args = args;
    gui.empty = gwy_field_new_alike(args->field, TRUE);

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

    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
    gtk_container_set_border_width(GTK_CONTAINER(hbox), 4);
    gwy_dialog_add_content(dialog, hbox, FALSE, FALSE, 0);

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

    /* Source mask preview. */
    vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
    gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, FALSE, 0);

    dataview = gui.srcview = gwy_create_preview(args->field, args->source, PREVIEW_HALF_SIZE);
    gwy_setup_data_view(GWY_DATA_VIEW(dataview), data, GWY_FILE_IMAGE, id,
                        GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_MASK_COLOR
                        | GWY_FILE_ITEM_RANGE | GWY_FILE_ITEM_REAL_SQUARE);
    gtk_box_pack_start(GTK_BOX(vbox2), dataview, FALSE, FALSE, 0);

    label = gtk_label_new(_("Operand"));
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 0);

    /* Result preview */
    vbox2 = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
    gtk_box_pack_start(GTK_BOX(vbox), vbox2, FALSE, FALSE, 0);

    dataview = gwy_create_preview(args->field, args->result, PREVIEW_HALF_SIZE);
    gwy_setup_data_view(GWY_DATA_VIEW(dataview), data, GWY_FILE_IMAGE, id,
                        GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_MASK_COLOR
                        | GWY_FILE_ITEM_RANGE | GWY_FILE_ITEM_REAL_SQUARE);
    gtk_box_pack_start(GTK_BOX(vbox2), dataview, FALSE, FALSE, 0);

    label = gtk_label_new(_("Result"));
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gtk_box_pack_start(GTK_BOX(vbox2), label, FALSE, FALSE, 0);

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

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

    gwy_param_table_append_separator(table);
    s = g_strconcat(_("Mark with"), ":", NULL);
    gwy_param_table_append_message(table, LABEL_MARK_WITH, s);
    g_free(s);
    gui.withwhat = gwy_radio_buttons_create(withwhats, G_N_ELEMENTS(withwhats), G_CALLBACK(with_what_selected), &gui,
                                            gwy_params_get_enum(args->params, PARAM_MARK_WITH));
    gui.sizegroup = gtk_size_group_new(GTK_SIZE_GROUP_HORIZONTAL);
    gwy_param_table_append_foreign(table, PARAM_OPERAND_MASK, create_operand_row_mask, &gui, NULL);
    gwy_param_table_append_foreign(table, PARAM_OPERAND_IMAGE, create_operand_row_image, &gui, NULL);
    gwy_param_table_append_foreign(table, PARAM_OPERAND_SHOW, create_operand_row_presentation, &gui, NULL);

    s = g_strconcat(_("Marked data range"), ":", NULL);
    gwy_param_table_append_message(table, LABEL_RANGE, s);
    g_free(s);
    gwy_param_table_append_slider(table, PARAM_MIN);
    gwy_param_table_slider_set_mapping(table, PARAM_MIN, GWY_SCALE_MAPPING_LINEAR);
    gwy_param_table_append_slider(table, PARAM_MAX);
    gwy_param_table_slider_set_mapping(table, PARAM_MAX, GWY_SCALE_MAPPING_LINEAR);

    gwy_param_table_append_separator(table);
    gwy_param_table_append_checkbox(table, PARAM_UPDATE);
    /* The selectable mask colour is for the result.  Source is displayed as-is. */
    gwy_param_table_append_image_mask_color(table, PARAM_MASK_COLOR, GWY_DATA_VIEW(dataview), data, id);

    gtk_box_pack_start(GTK_BOX(vbox), gwy_param_table_widget(gui.table), FALSE, TRUE, 0);

    g_signal_connect_swapped(gui.table, "param-changed", G_CALLBACK(param_changed), &gui);
    g_signal_connect_after(dialog, "response", G_CALLBACK(dialog_response), &gui);
    gwy_dialog_set_preview_func(dialog, GWY_PREVIEW_IMMEDIATE, preview, &gui, NULL);
    ensure_something_is_selected(&gui);

    GwyDialogOutcome outcome = gwy_dialog_run(dialog);

    g_object_unref(gui.empty);
    // Do not free it prematurely: widgets are created put into the size group only when the table widget is created.
    g_object_unref(gui.sizegroup);

    return outcome;
}

static void
execute(ModuleArgs *args)
{
    GwyParams *params = args->params;
    GwyMergeType operation = gwy_params_get_enum(params, PARAM_OPERATION);
    MarkWithWhat withwhat = gwy_params_get_enum(params, PARAM_MARK_WITH);
    gdouble min = gwy_params_get_double(params, PARAM_MIN);
    gdouble max = gwy_params_get_double(params, PARAM_MAX);
    gdouble data_min, data_max, d;
    GObject *otherfield;

    /* XXX: This does not have to be done when just the operation changes.  But well… */
    otherfield = get_other_field(params, NULL, NULL);
    if (withwhat == MARK_WITH_MASK)
        gwy_nield_copy_data(GWY_NIELD(otherfield), args->source);
    else {
        gwy_field_min_max(GWY_FIELD(otherfield), &data_min, &data_max);
        d = data_max - data_min;
        gwy_nield_mark_range(args->source, GWY_FIELD(otherfield), data_min + d*min, data_min + d*max);
    }

    /* Simple cases. */
    if (!args->mask || operation == GWY_MERGE_COPY) {
        if (operation == GWY_MERGE_COPY || operation == GWY_MERGE_UNION)
            gwy_nield_copy_data(args->source, args->result);
        else
            gwy_nield_clear(args->result);
        return;
    }

    gwy_nield_copy_data(args->mask, args->result);
    gwy_nield_merge(args->result, args->source, operation);
}

static void
param_changed(ModuleGUI *gui, gint id)
{
    GwyParams *params = gui->args->params;
    GwyParamTable *table = gui->table;
    MarkWithWhat withwhat = gwy_params_get_enum(params, PARAM_MARK_WITH);

    if (id < 0 || id == PARAM_MARK_WITH) {
        gwy_param_table_set_sensitive(table, LABEL_RANGE, withwhat != MARK_WITH_MASK);
        gwy_param_table_set_sensitive(table, PARAM_MIN, withwhat != MARK_WITH_MASK);
        gwy_param_table_set_sensitive(table, PARAM_MAX, withwhat != MARK_WITH_MASK);
    }
    if (id < 0) {
        gwy_param_table_set_sensitive(table, PARAM_OPERAND_MASK, gui->has_any[0]);
        gwy_param_table_set_sensitive(table, PARAM_OPERAND_IMAGE, gui->has_any[1]);
        gwy_param_table_set_sensitive(table, PARAM_OPERAND_SHOW, gui->has_any[2]);
    }

    if (id != PARAM_MASK_COLOR && id != PARAM_UPDATE)
        gwy_dialog_invalidate(GWY_DIALOG(gui->dialog));
}

static void
preview(gpointer user_data)
{
    ModuleGUI *gui = (ModuleGUI*)user_data;
    ModuleArgs *args = gui->args;
    GwyParams *params = args->params;
    GwyFile *data;
    GObject *otherfield;
    gint id;

    if (!(otherfield = get_other_field(params, &data, &id)))
        return;
    execute(args);
    gwy_nield_data_changed(args->source);
    gwy_nield_data_changed(args->result);
    if (GWY_IS_NIELD(otherfield)) {
        gwy_data_view_set_field(GWY_DATA_VIEW(gui->srcview), gui->empty);
        gwy_data_view_set_mask(GWY_DATA_VIEW(gui->srcview), GWY_NIELD(otherfield));
    }
    else if (GWY_IS_FIELD(otherfield)) {
        gwy_data_view_set_field(GWY_DATA_VIEW(gui->srcview), GWY_FIELD(otherfield));
        gwy_data_view_set_mask(GWY_DATA_VIEW(gui->srcview), NULL);
    }
    else {
        g_assert_not_reached();
    }
    gwy_setup_data_view(GWY_DATA_VIEW(gui->srcview), data, GWY_FILE_IMAGE, id,
                        GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_MASK_COLOR | GWY_FILE_ITEM_RANGE);
    gwy_dialog_have_result(GWY_DIALOG(gui->dialog));
}

static GObject*
get_other_field(GwyParams *params, GwyFile **pdata, gint *id)
{
    MarkWithWhat withwhat = gwy_params_get_enum(params, PARAM_MARK_WITH);
    GwyAppDataId dataid;
    GwyFile *data;
    GQuark quark;

    if (withwhat == MARK_WITH_MASK) {
        if (gwy_params_data_id_is_none(params, PARAM_OPERAND_MASK))
            return NULL;
        dataid = gwy_params_get_data_id(params, PARAM_OPERAND_MASK);
        quark = gwy_file_key_image_mask(dataid.id);
    }
    else if (withwhat == MARK_WITH_SHOW) {
        if (gwy_params_data_id_is_none(params, PARAM_OPERAND_SHOW))
            return NULL;
        dataid = gwy_params_get_data_id(params, PARAM_OPERAND_SHOW);
        quark = gwy_file_key_image_picture(dataid.id);
    }
    else {
        if (gwy_params_data_id_is_none(params, PARAM_OPERAND_IMAGE))
            return NULL;
        dataid = gwy_params_get_data_id(params, PARAM_OPERAND_IMAGE);
        quark = gwy_file_key_image(dataid.id);
    }
    data = gwy_data_browser_get_file(dataid.datano);
    if (pdata)
        *pdata = data;
    if (id)
        *id = dataid.id;

    return gwy_dict_get_object(GWY_DICT(data), quark);
}

static void
dialog_response(G_GNUC_UNUSED GwyDialog *dialog, gint response, ModuleGUI *gui)
{
    if (response == GWY_RESPONSE_RESET)
        ensure_something_is_selected(gui);
}

static void
ensure_something_is_selected(ModuleGUI *gui)
{
    if (!get_other_field(gui->args->params, NULL, NULL))
        gwy_radio_buttons_set_current(gui->withwhat, MARK_WITH_IMAGE);
}

static GtkWidget*
create_operand_row(ModuleGUI *gui, gint i, gint param_id)
{
    static const GwyDataChooserFilterFunc filters[] = {
        operand_filter_mask, operand_filter_image, operand_filter_presentation,
    };
    GwyDataChooser *chooser;
    GwyAppDataId dataid;
    GtkWidget *hbox;
    GSList *l;

    l = g_slist_nth(gui->withwhat, i);
    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 6);
    gtk_box_pack_start(GTK_BOX(hbox), GTK_WIDGET(l->data), FALSE, FALSE, 0);

    gui->operand[i] = gwy_data_chooser_new(GWY_FILE_IMAGE);
    g_object_set_data(G_OBJECT(gui->operand[i]), "param-id", GINT_TO_POINTER(param_id));
    chooser = GWY_DATA_CHOOSER(gui->operand[i]);
    gtk_size_group_add_widget(gui->sizegroup, gui->operand[i]);
    gwy_data_chooser_set_filter(chooser, filters[i], gui, NULL);
    gui->has_any[i] = !!gwy_data_chooser_get_active_id(chooser, &dataid);
    if (!gwy_params_data_id_is_none(gui->args->params, param_id)) {
        dataid = gwy_params_get_data_id(gui->args->params, param_id);
        gwy_data_chooser_set_active_id(chooser, &dataid);
        gwy_data_chooser_get_active_id(chooser, &dataid);
    }
    gwy_params_set_image_id(gui->args->params, param_id, dataid);
    gtk_box_pack_end(GTK_BOX(hbox), gui->operand[i], FALSE, FALSE, 0);
    g_signal_connect(chooser, "changed", G_CALLBACK(operand_changed), gui);

    return hbox;
}

static void
operand_changed(GwyDataChooser *chooser, ModuleGUI *gui)
{
    gint param_id = GPOINTER_TO_INT(g_object_get_data(G_OBJECT(chooser), "param-id"));
    GwyAppDataId dataid;
    gwy_data_chooser_get_active_id(chooser, &dataid);
    if (gwy_params_set_image_id(gui->args->params, param_id, dataid))
        gwy_param_table_param_changed(gui->table, param_id);
}

static GtkWidget*
create_operand_row_mask(gpointer user_data)
{
    return create_operand_row((ModuleGUI*)user_data, 0, PARAM_OPERAND_MASK);
}

static GtkWidget*
create_operand_row_image(gpointer user_data)
{
    return create_operand_row((ModuleGUI*)user_data, 1, PARAM_OPERAND_IMAGE);
}

static GtkWidget*
create_operand_row_presentation(gpointer user_data)
{
    return create_operand_row((ModuleGUI*)user_data, 2, PARAM_OPERAND_SHOW);
}

static gboolean
operand_filter_image(GwyFile *source, gint id, gpointer user_data)
{
    ModuleGUI *gui = (ModuleGUI*)user_data;
    GwyField *field = gwy_file_get_image(source, id);
    return !gwy_field_is_incompatible(field, gui->args->field,
                                      GWY_DATA_MISMATCH_RES | GWY_DATA_MISMATCH_REAL | GWY_DATA_MISMATCH_LATERAL);
}

static gboolean
operand_filter_presentation(GwyFile *source, gint id, gpointer user_data)
{
    ModuleGUI *gui = (ModuleGUI*)user_data;
    GwyField *field = NULL;
    if (!gwy_dict_gis_object(GWY_DICT(source), gwy_file_key_image_picture(id), &field))
        return FALSE;
    return !gwy_field_is_incompatible(field, gui->args->field,
                                      GWY_DATA_MISMATCH_RES | GWY_DATA_MISMATCH_REAL | GWY_DATA_MISMATCH_LATERAL);
}

static gboolean
operand_filter_mask(GwyFile *source, gint id, gpointer user_data)
{
    ModuleGUI *gui = (ModuleGUI*)user_data;
    GwyNield *nield = gwy_file_get_image_mask(source, id);
    return nield && !gwy_nield_is_incompatible_with_field(nield, gui->args->field, GWY_DATA_MISMATCH_RES);
}

static void
with_what_selected(G_GNUC_UNUSED GtkRadioButton *button, ModuleGUI *gui)
{
    if (gwy_params_set_enum(gui->args->params, PARAM_MARK_WITH, gwy_radio_buttons_get_current(gui->withwhat)))
        gwy_param_table_param_changed(gui->table, PARAM_MARK_WITH);
}

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