/*
 *  $Id: scale.c 30111 2026-06-22 11:02:00Z yeti-dn $
 *  Copyright (C) 2003-2026 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@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>

#define RUN_MODES (GWY_RUN_IMMEDIATE | GWY_RUN_INTERACTIVE)

#define MIN_RATIO 0.001
#define MAX_RATIO 100.0

enum {
    PARAM_RATIO,
    PARAM_PROPORTIONAL,
    PARAM_ASPECT_RATIO,
    PARAM_INTERPOLATION,
    PARAM_XRES,
    PARAM_YRES,
};

typedef struct {
    GwyParams *params;
    /* Cached input data properties. */
    gint orig_xres;
    gint orig_yres;
    gdouble minratio;
    gdouble maxratio;
} ModuleArgs;

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

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Scales data by arbitrary factor."),
    "Yeti <yeti@gwyddion.net>",
    "2.2",
    "David Nečas (Yeti) & Petr Klapetek & Dirk Kähler",
    "2003",
};

GWY_MODULE_QUERY2(module_info, scale)

static gboolean
module_register(void)
{
    gwy_image_func_register("scale",
                            module_main,
                            N_("/_Basic Operations/_Scale..."),
                            GWY_ICON_SCALE,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Scale data"));

    return TRUE;
}

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

    if (paramdef)
        return paramdef;

    paramdef = gwy_param_def_new();
    gwy_param_def_set_function_name(paramdef, gwy_image_func_current());
    gwy_param_def_add_double(paramdef, PARAM_RATIO, "ratio", _("Scale by _ratio"), MIN_RATIO, MAX_RATIO, 1.0);
    gwy_param_def_add_boolean(paramdef, PARAM_PROPORTIONAL, "proportional", _("_Proportional scaling"), TRUE);
    /* We save the aspect ratio in settings, but the user does not control it directly. */
    gwy_param_def_add_double(paramdef, PARAM_ASPECT_RATIO, "aspectratio", NULL, G_MINDOUBLE, G_MAXDOUBLE, 1.0);
    /* The user can control directly the pixel dimensions, but we do not save them.  The default for a different
     * image is the same scaling and aspect ratios, not the same dimensions. */
    gwy_param_def_add_int(paramdef, PARAM_XRES, NULL, _("New _width"), 2, 16384, 256);
    gwy_param_def_add_int(paramdef, PARAM_YRES, NULL, _("New _height"), 2, 16384, 256);
    gwy_param_def_add_enum(paramdef, PARAM_INTERPOLATION, "interp", NULL, GWY_TYPE_INTERPOLATION_TYPE,
                           GWY_INTERPOLATION_LINEAR);
    return paramdef;
}

static void
module_main(GwyFile *data, GwyRunModeFlags mode)
{
    gdouble step = 1e-4;
    GwyField *field, *show;
    GwyNield *mask;
    gint oldid, newid;
    gdouble ratio, aspectratio;
    ModuleArgs args;
    gwy_clear1(args);
    GwyParams *params;

    g_return_if_fail(mode & RUN_MODES);
    gwy_data_browser_get_current(GWY_APP_FIELD, &field,
                                 GWY_APP_MASK_FIELD, &mask,
                                 GWY_APP_SHOW_FIELD, &show,
                                 GWY_APP_FIELD_ID, &oldid,
                                 0);
    g_return_if_fail(field);

    args.orig_xres = gwy_field_get_xres(field);
    args.orig_yres = gwy_field_get_yres(field);
    args.minratio = GWY_ROUND(2.0/MIN(args.orig_xres, args.orig_yres)/step)*step;
    args.maxratio = 16384.0/MAX(args.orig_xres, args.orig_yres);
    args.minratio = MAX(args.minratio, MIN_RATIO);
    args.maxratio = MIN(args.maxratio, MAX_RATIO);
    /* The only reasonably safe value is 1. If things seem to get out of hand, just do not allow anything else. */
    if (args.maxratio < args.minratio)
        args.maxratio = args.minratio = 1.0;

    params = args.params = gwy_params_new_from_settings(define_module_params());
    if (gwy_params_get_boolean(params, PARAM_PROPORTIONAL))
        gwy_params_set_double(params, PARAM_ASPECT_RATIO, 1.0);
    ratio = gwy_params_get_double(params, PARAM_RATIO);
    /* Prevent silly-sized images even in non-interactive use. */
    ratio = CLAMP(ratio, args.minratio, args.maxratio);
    aspectratio = gwy_params_get_double(params, PARAM_ASPECT_RATIO);
    gwy_params_set_int(params, PARAM_XRES, GWY_ROUND(ratio*args.orig_xres));
    gwy_params_set_int(params, PARAM_YRES, GWY_ROUND(aspectratio*ratio*args.orig_yres));

    if (mode == GWY_RUN_INTERACTIVE) {
        GwyDialogOutcome outcome = run_gui(&args);
        gwy_params_save_to_settings(params);
        if (outcome != GWY_DIALOG_PROCEED)
            goto end;
    }

    gint xres = gwy_params_get_int(params, PARAM_XRES);
    gint yres = gwy_params_get_int(params, PARAM_YRES);
    GwyInterpolationType interp = gwy_params_get_enum(params, PARAM_INTERPOLATION);

    field = gwy_field_new_resampled(field, xres, yres, interp);
    newid = gwy_file_add_image(data, field);

    gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
    gwy_file_sync_items(data, GWY_FILE_IMAGE, oldid,
                        data, GWY_FILE_IMAGE, newid,
                        GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_RANGE | GWY_FILE_ITEM_MASK_COLOR, FALSE);

    if (mask) {
        mask = gwy_nield_new_resampled(mask, xres, yres);
        gwy_file_pass_image_mask(data, newid, mask);
    }
    if (show) {
        show = gwy_field_new_resampled(show, xres, yres, interp);
        gwy_dict_set_object(GWY_DICT(data), gwy_file_key_image_picture(newid), show);
    }

    gwy_file_set_title(data, GWY_FILE_IMAGE, newid, _("Scaled Data"), TRUE);
    gwy_log_add(data, GWY_FILE_IMAGE, oldid, newid);
    g_object_unref(field);

end:
    g_object_unref(params);
}

static GwyDialogOutcome
run_gui(ModuleArgs *args)
{
    GwyParamTable *table;
    GwyDialog *dialog;
    ModuleGUI gui;

    gui.args = args;
    gui.dialog = gwy_dialog_new(C_("verb", "Scale"));
    dialog = GWY_DIALOG(gui.dialog);
    gwy_dialog_add_stock_buttons(dialog, GWY_STOCK_RESET, GWY_STOCK_CANCEL, GWY_STOCK_OK, 0);

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

    gwy_param_table_append_slider(table, PARAM_RATIO);
    gwy_param_table_slider_set_mapping(table, PARAM_RATIO, GWY_SCALE_MAPPING_LOG);
    gwy_param_table_slider_restrict_range(table, PARAM_RATIO, args->minratio, args->maxratio);
    gwy_param_table_slider_set_digits(table, PARAM_RATIO, 4);
    gwy_param_table_append_checkbox(table, PARAM_PROPORTIONAL);
    gwy_param_table_append_slider(table, PARAM_XRES);
    gwy_param_table_set_unitstr(table, PARAM_XRES, _("px"));
    gwy_param_table_slider_set_mapping(table, PARAM_XRES, GWY_SCALE_MAPPING_LOG);
    gwy_param_table_append_slider(table, PARAM_YRES);
    gwy_param_table_set_unitstr(table, PARAM_YRES, _("px"));
    gwy_param_table_slider_set_mapping(table, PARAM_YRES, GWY_SCALE_MAPPING_LOG);
    gwy_param_table_append_combo(table, PARAM_INTERPOLATION);

    gwy_dialog_add_content(dialog, gwy_param_table_widget(table), FALSE, FALSE, 0);

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

    return gwy_dialog_run(dialog);
}

static void
param_changed(ModuleGUI *gui, gint id)
{
    ModuleArgs *args = gui->args;
    GwyParams *params = args->params;
    GwyParamTable *table = gui->table;
    gboolean proportional = gwy_params_get_boolean(params, PARAM_PROPORTIONAL);

    if (id < 0 || id == PARAM_PROPORTIONAL) {
        gwy_param_table_set_sensitive(table, PARAM_RATIO, proportional);
        if (proportional)
            gwy_params_set_double(params, PARAM_ASPECT_RATIO, 1.0);
    }

    if (id < 0 || id == PARAM_RATIO || (id == PARAM_PROPORTIONAL && proportional)) {
        gdouble ratio = gwy_params_get_double(params, PARAM_RATIO);
        gdouble aspectratio = gwy_params_get_double(params, PARAM_ASPECT_RATIO);
        gwy_param_table_set_int(table, PARAM_XRES, GWY_ROUND(ratio*args->orig_xres));
        gwy_param_table_set_int(table, PARAM_YRES, GWY_ROUND(aspectratio*ratio*args->orig_yres));
    }

    if (id == PARAM_XRES || id == PARAM_YRES) {
        gdouble xres = gwy_params_get_int(params, PARAM_XRES);
        gdouble yres = gwy_params_get_int(params, PARAM_YRES);
        if (proportional) {
            gdouble ratio = (id == PARAM_XRES ? xres/args->orig_xres : yres/args->orig_yres);
            gwy_param_table_set_double(table, PARAM_RATIO, ratio);
            ratio = gwy_params_get_double(params, PARAM_RATIO);
            gwy_param_table_set_int(table, PARAM_YRES, GWY_ROUND(ratio*args->orig_yres));
            gwy_param_table_set_int(table, PARAM_XRES, GWY_ROUND(ratio*args->orig_xres));
        }
        else
            gwy_params_set_double(params, PARAM_ASPECT_RATIO, yres/args->orig_yres * args->orig_xres/xres);
    }
}

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