/*
 *  $Id: extend.c 30047 2026-06-11 08:30:45Z yeti-dn $
 *  Copyright (C) 2010-2021 David Necas (Yeti).
 *  E-mail: yeti@gwyddion.net.
 *
 *  This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public
 *  License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any
 *  later version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 *  warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 *  details.
 *
 *  You should have received a copy of the GNU General Public License along with this program; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

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

#define RUN_MODES (GWY_RUN_IMMEDIATE | GWY_RUN_INTERACTIVE)

enum {
    EXTEND_MAX = 2048
};

enum {
    PARAM_UP,
    PARAM_DOWN,
    PARAM_LEFT,
    PARAM_RIGHT,
    PARAM_SYMMETRIC,
    PARAM_EXTERIOR,
    PARAM_KEEP_OFFSETS,
    PARAM_NEW_CHANNEL,
    PARAM_UPDATE,
    INFO_NEWDIM,
};

typedef struct {
    GwyParams *params;
    GwyField *field;
    GwyField *result;
    /* Cached values for dimensions display. */
    gint xres;
    gint yres;
} ModuleArgs;

typedef struct {
    ModuleArgs *args;
    GtkWidget *dialog;
    GwyParamTable *table;
    GtkWidget *view;
    gint last_active;
} 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             param_changed       (ModuleGUI *gui,
                                             gint id);
static void             preview             (gpointer user_data);
static void             sanitise_params     (ModuleArgs *args);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Extends image by adding borders."),
    "Yeti <yeti@gwyddion.net>",
    "2.2",
    "David Nečas (Yeti)",
    "2012",
};

GWY_MODULE_QUERY2(module_info, extend)

static gboolean
module_register(void)
{
    gwy_image_func_register("extend",
                            module_main,
                            N_("/_Basic Operations/E_xtend..."),
                            GWY_ICON_EXTEND,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Extend by adding borders"));

    return TRUE;
}

static GwyParamDef*
define_module_params(void)
{
    static const GwyEnum exteriors[] = {
        { N_("Mean"),                     GWY_EXTERIOR_FIXED_VALUE,   },
        { gwy_NC("exterior", "Border"),   GWY_EXTERIOR_BORDER,        },
        { gwy_NC("exterior", "Mirror"),   GWY_EXTERIOR_MIRROR,        },
        { gwy_NC("exterior", "Periodic"), GWY_EXTERIOR_PERIODIC,      },
        { gwy_NC("exterior", "Laplace"),  GWY_EXTERIOR_LAPLACE,       },
        { gwy_NC("exterior", "Connect"),  GWY_EXTERIOR_CONNECT,       },
    };

    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_int(paramdef, PARAM_UP, "up", _("_Up"), 0, EXTEND_MAX, 0);
    gwy_param_def_add_int(paramdef, PARAM_DOWN, "down", _("_Down"), 0, EXTEND_MAX, 0);
    gwy_param_def_add_int(paramdef, PARAM_LEFT, "left", _("_Left"), 0, EXTEND_MAX, 0);
    gwy_param_def_add_int(paramdef, PARAM_RIGHT, "right", _("_Right"), 0, EXTEND_MAX, 0);
    gwy_param_def_add_boolean(paramdef, PARAM_SYMMETRIC, "symmetric", _("Extend _symmetrically"), TRUE);
    gwy_param_def_add_gwyenum(paramdef, PARAM_EXTERIOR, "exterior", _("_Exterior type"),
                              exteriors, G_N_ELEMENTS(exteriors), GWY_EXTERIOR_MIRROR);
    gwy_param_def_add_boolean(paramdef, PARAM_KEEP_OFFSETS, "keep_offsets", _("Keep lateral offsets"), FALSE);
    gwy_param_def_add_boolean(paramdef, PARAM_NEW_CHANNEL, "new_channel", _("Create new image"), FALSE);
    gwy_param_def_add_instant_updates(paramdef, PARAM_UPDATE, "update", NULL, FALSE);
    return paramdef;
}

static void
module_main(GwyFile *data, GwyRunModeFlags mode)
{
    GwyField *field, *show, *result;
    GwyNield *mask;
    GQuark quarks[3];
    GwyParams *params;
    ModuleArgs args;
    GwyDialogOutcome outcome = GWY_DIALOG_PROCEED;
    gint n, id, newid;

    g_return_if_fail(mode & RUN_MODES);
    gwy_clear1(args);
    gwy_data_browser_get_current(GWY_APP_FIELD, &field,
                                 GWY_APP_MASK_FIELD, &mask,
                                 GWY_APP_SHOW_FIELD, &show,
                                 GWY_APP_FIELD_KEY, quarks + 0,
                                 GWY_APP_MASK_FIELD_KEY, quarks + 1,
                                 GWY_APP_SHOW_FIELD_KEY, quarks + 2,
                                 GWY_APP_FIELD_ID, &id,
                                 0);
    g_return_if_fail(field);

    args.field = field;
    args.xres = gwy_field_get_xres(args.field);
    args.yres = gwy_field_get_yres(args.field);
    args.params = params = gwy_params_new_from_settings(define_module_params());
    sanitise_params(&args);

    if (mode == GWY_RUN_INTERACTIVE) {
        args.result = gwy_field_copy(args.field);
        outcome = run_gui(&args, data, id);
        gwy_params_save_to_settings(params);
        if (outcome == GWY_DIALOG_CANCEL)
            goto end;
    }

    gint up = gwy_params_get_int(params, PARAM_UP);
    gint down = gwy_params_get_int(params, PARAM_DOWN);
    gint left = gwy_params_get_int(params, PARAM_LEFT);
    gint right = gwy_params_get_int(params, PARAM_RIGHT);
    gint exterior = gwy_params_get_int(params, PARAM_EXTERIOR);
    gboolean new_channel = gwy_params_get_boolean(params, PARAM_NEW_CHANNEL);
    gboolean keep_offsets = gwy_params_get_boolean(params, PARAM_KEEP_OFFSETS);

    if (!new_channel) {
        quarks[1] *= !!mask;
        quarks[2] *= !!show;
        n = gwy_compactify_quarks(quarks, 3);
        gwy_app_undo_qcheckpointv(GWY_DICT(data), n, quarks);
    }

    if (outcome == GWY_DIALOG_HAVE_RESULT) {
        result = args.result;
        args.result = NULL;
    }
    else
        result = gwy_field_extend(field, left, right, up, down, exterior, gwy_field_mean(field), keep_offsets);

    if (!new_channel) {
        gwy_field_assign(field, result);
        gwy_field_data_changed(field);
        gwy_log_add(data, GWY_FILE_IMAGE, id, id);
    }
    else {
        newid = gwy_file_add_image(data, 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,
                            GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_MASK_COLOR | GWY_FILE_ITEM_RANGE
                            | GWY_FILE_ITEM_REAL_SQUARE | GWY_FILE_ITEM_SELECTIONS,
                            FALSE);
        gwy_file_set_title(data, GWY_FILE_IMAGE, newid, _("Extended"), TRUE);
        gwy_log_add(data, GWY_FILE_IMAGE, id, newid);
    }
    g_object_unref(result);

    if (mask) {
        /* Do not apply Laplace interpolation to masks, it makes no sense. */
        gint xres = gwy_nield_get_xres(mask), yres = gwy_nield_get_yres(mask);
        GwyExteriorType mask_exterior = exterior;
        if (exterior == GWY_EXTERIOR_LAPLACE || exterior == GWY_EXTERIOR_CONNECT)
            mask_exterior = GWY_EXTERIOR_FIXED_VALUE;

        /* Set fill_value to 1 for fields that are mostly masked, 0 for fields that are mostly unmasked. */
        gdouble fill_value = (mask_exterior == GWY_EXTERIOR_FIXED_VALUE ? gwy_nield_count(mask) > 0.5*xres*yres : 0);

        mask = gwy_nield_extend(mask, left, right, up, down, mask_exterior, fill_value);
        gwy_nield_number_contiguous(mask);
        gwy_file_pass_image_mask(data, new_channel ? newid : id, mask);
    }

    if (show) {
        result = gwy_field_extend(show, left, right, up, down, exterior, gwy_field_mean(show), keep_offsets);
        if (!new_channel) {
            gwy_field_assign(show, result);
            gwy_field_data_changed(show);
        }
        else {
            gwy_dict_set_object(GWY_DICT(data), gwy_file_key_image_picture(newid), result);
        }
        g_object_unref(result);
    }

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

static GwyDialogOutcome
run_gui(ModuleArgs *args, GwyFile *data, gint id)
{
    GtkWidget *hbox;
    GwyDialog *dialog;
    GwyParamTable *table;
    ModuleGUI gui;

    gwy_clear1(gui);
    gui.args = args;
    gui.last_active = PARAM_UP;

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

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

    table = gui.table = gwy_param_table_new(args->params);
    gwy_param_table_append_header(table, -1, _("Borders"));
    gwy_param_table_append_slider(table, PARAM_UP);
    gwy_param_table_slider_add_alt(table, PARAM_UP);
    gwy_param_table_alt_set_field_pixel_y(table, PARAM_UP, args->field);
    gwy_param_table_append_slider(table, PARAM_DOWN);
    gwy_param_table_slider_add_alt(table, PARAM_DOWN);
    gwy_param_table_alt_set_field_pixel_y(table, PARAM_DOWN, args->field);
    gwy_param_table_append_slider(table, PARAM_LEFT);
    gwy_param_table_slider_add_alt(table, PARAM_LEFT);
    gwy_param_table_alt_set_field_pixel_x(table, PARAM_LEFT, args->field);
    gwy_param_table_append_slider(table, PARAM_RIGHT);
    gwy_param_table_slider_add_alt(table, PARAM_RIGHT);
    gwy_param_table_alt_set_field_pixel_x(table, PARAM_RIGHT, args->field);
    gwy_param_table_append_checkbox(table, PARAM_SYMMETRIC);

    gwy_param_table_append_separator(table);
    gwy_param_table_append_info(table, INFO_NEWDIM, _("New dimensions"));
    gwy_param_table_set_unitstr(table, INFO_NEWDIM, _("px"));

    gwy_param_table_append_separator(table);
    gwy_param_table_append_header(table, -1, _("Options"));
    gwy_param_table_append_combo(table, PARAM_EXTERIOR);
    gwy_param_table_append_checkbox(table, PARAM_KEEP_OFFSETS);
    gwy_param_table_append_checkbox(table, PARAM_NEW_CHANNEL);
    gwy_param_table_append_checkbox(table, PARAM_UPDATE);

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

    g_signal_connect_swapped(table, "param-changed", G_CALLBACK(param_changed), &gui);
    gwy_dialog_set_preview_func(dialog, GWY_PREVIEW_IMMEDIATE, preview, &gui, NULL);

    return gwy_dialog_run(dialog);
}

static void
param_changed(ModuleGUI *gui, gint id)
{
    GwyParamTable *table = gui->table;
    ModuleArgs *args = gui->args;
    GwyParams *params = args->params;
    gboolean symmetric = gwy_params_get_boolean(params, PARAM_SYMMETRIC);
    gint extend_by = 0;
    gchar *s;

    if ((id < 0 || id == PARAM_SYMMETRIC) && symmetric)
        extend_by = gwy_params_get_int(params, gui->last_active);

    if (id == PARAM_UP || id == PARAM_DOWN || id == PARAM_LEFT || id == PARAM_RIGHT) {
        extend_by = gwy_params_get_int(params, id);
        gui->last_active = id;
    }

    if ((id < 0 || id == PARAM_SYMMETRIC || id == PARAM_UP || id == PARAM_DOWN || id == PARAM_LEFT || id == PARAM_RIGHT)
        && symmetric) {
        gwy_param_table_set_int(table, PARAM_UP, extend_by);
        gwy_param_table_set_int(table, PARAM_DOWN, extend_by);
        gwy_param_table_set_int(table, PARAM_LEFT, extend_by);
        gwy_param_table_set_int(table, PARAM_RIGHT, extend_by);
    }

    if (id < 0 || id == PARAM_UP || id == PARAM_DOWN || id == PARAM_LEFT || id == PARAM_RIGHT) {
        gint up = gwy_params_get_int(params, PARAM_UP);
        gint down = gwy_params_get_int(params, PARAM_DOWN);
        gint left = gwy_params_get_int(params, PARAM_LEFT);
        gint right = gwy_params_get_int(params, PARAM_RIGHT);
        s = g_strdup_printf(_("%d × %d"), args->xres + left + right, args->yres + up + down);
        gwy_param_table_info_set_valuestr(table, INFO_NEWDIM, s);
        g_free(s);
    }

    if (id != PARAM_NEW_CHANNEL && 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;
    GwyField *field = args->field, *result;
    gint up = gwy_params_get_int(params, PARAM_UP);
    gint down = gwy_params_get_int(params, PARAM_DOWN);
    gint left = gwy_params_get_int(params, PARAM_LEFT);
    gint right = gwy_params_get_int(params, PARAM_RIGHT);
    GwyExteriorType exterior = gwy_params_get_int(params, PARAM_EXTERIOR);
    gboolean keep_offsets = gwy_params_get_boolean(params, PARAM_KEEP_OFFSETS);

    result = gwy_field_extend(field, left, right, up, down, exterior, gwy_field_mean(field), keep_offsets);
    gwy_field_assign(args->result, result);
    g_object_unref(result);
    gwy_field_data_changed(args->result);
    gwy_set_data_preview_size(GWY_DATA_VIEW(gui->view), PREVIEW_SIZE);
    gwy_dialog_have_result(GWY_DIALOG(gui->dialog));
}

static void
sanitise_params(ModuleArgs *args)
{
    GwyParams *params = args->params;

    if (gwy_params_get_int(params, PARAM_UP) != gwy_params_get_int(params, PARAM_DOWN)
        || gwy_params_get_int(params, PARAM_LEFT) != gwy_params_get_int(params, PARAM_RIGHT)
        || gwy_params_get_int(params, PARAM_LEFT) != gwy_params_get_int(params, PARAM_UP))
        gwy_params_set_boolean(params, PARAM_SYMMETRIC, FALSE);
}

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