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

#define RUN_MODES (GWY_RUN_IMMEDIATE | GWY_RUN_INTERACTIVE)

typedef enum {
    OUTPUT_REAL    = (1 << 0),
    OUTPUT_IMAG    = (1 << 1),
    OUTPUT_MODULUS = (1 << 2),
    OUTPUT_PHASE   = (1 << 3),
} OutputFlags;

enum {
    PARAM_INVERSE_TRANSFORM,
    PARAM_OUT,
    PARAM_PRESERVERMS,
    PARAM_RAW_TRANSFORM,
    PARAM_USE_IMAG_PART,
    PARAM_WINDOW,
    PARAM_ZEROMEAN,
    PARAM_IMAG_PART,
};

typedef struct {
    GwyParams *params;
    GwyField *field;
} 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 gboolean         imagpart_filter     (GwyFile *data,
                                             gint id,
                                             gpointer user_data);
static void             create_output       (GwyFile *data,
                                             gint id,
                                             GwyField *field,
                                             const gchar *output_name,
                                             gboolean itransform,
                                             gboolean is_phase);
static GwyField*        make_modulus        (GwyField *re,
                                             GwyField *im);
static GwyField*        make_phase          (GwyField *re,
                                             GwyField *im);
static void             sanitise_params     (ModuleArgs *args);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Two-dimensional FFT (Fast Fourier Transform)."),
    "Petr Klapetek <klapetek@gwyddion.net>",
    "3.0",
    "David Nečas (Yeti) & Petr Klapetek",
    "2003",
};

GWY_MODULE_QUERY2(module_info, fft)

static gboolean
module_register(void)
{
    gwy_image_func_register("fft",
                            module_main,
                            N_("/_Integral Transforms/2D _FFT..."),
                            GWY_ICON_FFT,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Compute Fast Fourier Transform"));

    return TRUE;
}

static GwyParamDef*
define_module_params(void)
{
    static const GwyEnum outputs[] = {
        { N_("Real"),      OUTPUT_REAL,    },
        { N_("Imaginary"), OUTPUT_IMAG,    },
        { N_("Modulus"),   OUTPUT_MODULUS, },
        { N_("Phase"),     OUTPUT_PHASE,   },
    };
    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_boolean(paramdef, PARAM_INVERSE_TRANSFORM, "inverse_transform", _("_Inverse transform"), FALSE);
    gwy_param_def_add_gwyflags(paramdef, PARAM_OUT, "out", _("Output _type"),
                               outputs, G_N_ELEMENTS(outputs), OUTPUT_MODULUS);
    gwy_param_def_add_boolean(paramdef, PARAM_PRESERVERMS, "preserverms", _("_Preserve RMS"), FALSE);
    gwy_param_def_add_boolean(paramdef, PARAM_RAW_TRANSFORM, "raw_transform", _("Ra_w transform"), FALSE);
    gwy_param_def_add_boolean(paramdef, PARAM_USE_IMAG_PART, "use_imagpart", NULL, FALSE);
    gwy_param_def_add_enum(paramdef, PARAM_WINDOW, "window", NULL, GWY_TYPE_WINDOWING_TYPE, GWY_WINDOWING_HANN);
    gwy_param_def_add_boolean(paramdef, PARAM_ZEROMEAN, "zeromean", _("Subtract mean _value beforehand"), TRUE);
    gwy_param_def_add_image_id(paramdef, PARAM_IMAG_PART, "imagpart", _("I_maginary part"));
    return paramdef;
}

static void
module_main(GwyFile *data, GwyRunModeFlags mode)
{
    GwyField *field, *imagpart, *raout, *ipout;
    GwyDialogOutcome outcome;
    ModuleArgs args;
    GwyParams *params;
    gboolean is_inv, raw_transform, use_imagpart, humanize = TRUE;
    OutputFlags out;
    gint id, datano;

    g_return_if_fail(mode & RUN_MODES);

    gwy_data_browser_get_current(GWY_APP_FIELD, &field,
                                 GWY_APP_FIELD_ID, &id,
                                 GWY_APP_FILE_ID, &datano,
                                 0);
    g_return_if_fail(field);

    args.field = field;
    args.params = params = gwy_params_new_from_settings(define_module_params());
    sanitise_params(&args);

    if (mode == GWY_RUN_INTERACTIVE) {
        outcome = run_gui(&args);
        gwy_params_save_to_settings(params);
        if (outcome == GWY_DIALOG_CANCEL)
            goto end;
    }
    out = gwy_params_get_flags(params, PARAM_OUT);
    if (!out)
        goto end;

    raout = gwy_field_new_alike(field, FALSE);
    ipout = gwy_field_new_alike(field, FALSE);

    use_imagpart = gwy_params_get_boolean(params, PARAM_USE_IMAG_PART);
    raw_transform = gwy_params_get_boolean(params, PARAM_RAW_TRANSFORM);
    is_inv = gwy_params_get_boolean(params, PARAM_INVERSE_TRANSFORM) && raw_transform;
    imagpart = use_imagpart ? gwy_params_get_image(params, PARAM_IMAG_PART) : NULL;

    if (is_inv) {
        GwyField *rein = gwy_field_copy(field);
        GwyField *imin = imagpart ? gwy_field_copy(imagpart) : NULL;

        gwy_field_fft_2d_decenter(rein);
        gwy_field_fft_postprocess(rein, FALSE);
        if (imin) {
            gwy_field_fft_2d_decenter(imin);
            gwy_field_fft_postprocess(imin, FALSE);
        }
        gwy_field_fft_2d_raw(rein, imin, raout, ipout, GWY_TRANSFORM_DIRECTION_BACKWARD);
        g_object_unref(rein);
        g_clear_object(&imin);
        humanize = FALSE;
    }
    else if (raw_transform)
        gwy_field_fft_2d_raw(field, imagpart, raout, ipout, GWY_TRANSFORM_DIRECTION_FORWARD);
    else {
        gboolean preserverms = gwy_params_get_boolean(params, PARAM_PRESERVERMS);
        gboolean zeromean = gwy_params_get_boolean(params, PARAM_ZEROMEAN);
        GwyWindowingType window = gwy_params_get_enum(params, PARAM_WINDOW);
        gwy_field_fft_2d(field, imagpart, raout, ipout,
                         window, GWY_TRANSFORM_DIRECTION_FORWARD, preserverms, zeromean ? 1 : 0);
    }
    gwy_field_fft_postprocess(raout, humanize);
    gwy_field_fft_postprocess(ipout, humanize);

    if (out & OUTPUT_REAL)
        create_output(data, id, g_object_ref(raout), _("FFT Real"), is_inv, FALSE);
    if (out & OUTPUT_IMAG)
        create_output(data, id, g_object_ref(ipout), _("FFT Imaginary"), is_inv, FALSE);
    if (out & OUTPUT_MODULUS)
        create_output(data, id, make_modulus(raout, ipout), _("FFT Modulus"), is_inv, FALSE);
    if (out & OUTPUT_PHASE)
        create_output(data, id, make_phase(raout, ipout), _("FFT Phase"), is_inv, TRUE);

    g_object_unref(raout);
    g_object_unref(ipout);

end:
    g_object_unref(params);
}

static void
create_output(GwyFile *data, gint id, GwyField *field, const gchar *output_name,
              gboolean itransform, gboolean is_phase)
{
    gint newid;

    newid = gwy_file_add_image(data, field);

    gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
    gwy_file_set_title(data, GWY_FILE_IMAGE, newid, output_name, TRUE);
    gwy_log_add(data, GWY_FILE_IMAGE, id, newid);
    g_object_unref(field);

    if (itransform)
        return;

    /* make fft more visible by choosing a good gradient and using auto range */
    gwy_file_set_palette(data, GWY_FILE_IMAGE, newid, "DFit");
    if (!is_phase)
        gwy_dict_set_enum(GWY_DICT(data), gwy_file_key_image_color_mapping(newid), GWY_COLOR_MAPPING_AUTO);
}

static GwyField*
make_modulus(GwyField *re, GwyField *im)
{
    GwyField *modulus = gwy_field_new_alike(re, FALSE);
    gsize n = gwy_field_get_xres(re)*gwy_field_get_yres(re);
    const gdouble *datare = gwy_field_get_data_const(re);
    const gdouble *dataim = gwy_field_get_data_const(im);
    gdouble *data = gwy_field_get_data(modulus);
#ifdef _OPENMP
#pragma omp parallel for if (gwy_threads_are_enabled()) default(none) \
            shared(data,datare,dataim,n)
#endif
    for (gsize i = 0; i < n; i++)
        data[i] = sqrt(datare[i]*datare[i] + dataim[i]*dataim[i]);

    return modulus;
}

static GwyField*
make_phase(GwyField *re, GwyField *im)
{
    GwyField *phase = gwy_field_new_alike(re, FALSE);
    gwy_unit_clear(gwy_field_get_unit_z(phase));
    gsize n = gwy_field_get_xres(re)*gwy_field_get_yres(re);
    const gdouble *datare = gwy_field_get_data_const(re);
    const gdouble *dataim = gwy_field_get_data_const(im);
    gdouble *data = gwy_field_get_data(phase);
#ifdef _OPENMP
#pragma omp parallel for if (gwy_threads_are_enabled()) default(none) \
            shared(data,datare,dataim,n)
#endif
    for (gsize i = 0; i < n; i++)
        data[i] = atan2(dataim[i], datare[i]);

    return phase;
}

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

    gui.args = args;
    gui.dialog = gwy_dialog_new(_("2D FFT"));
    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_header(table, -1, _("2D FFT"));
    gwy_param_table_append_checkbox(table, PARAM_RAW_TRANSFORM);
    gwy_param_table_append_image_id(table, PARAM_IMAG_PART);
    gwy_param_table_data_id_set_filter(table, PARAM_IMAG_PART, imagpart_filter, args->field, NULL);
    gwy_param_table_add_enabler(table, PARAM_USE_IMAG_PART, PARAM_IMAG_PART);
    gwy_param_table_append_checkbox(table, PARAM_INVERSE_TRANSFORM);

    gwy_param_table_append_header(table, -1, _("Output"));
    gwy_param_table_append_checkboxes(table, PARAM_OUT);

    gwy_param_table_append_header(table, -1, _("Options"));
    gwy_param_table_append_combo(table, PARAM_WINDOW);
    gwy_param_table_append_checkbox(table, PARAM_ZEROMEAN);
    gwy_param_table_append_checkbox(table, PARAM_PRESERVERMS);

    gwy_dialog_add_content(dialog, gwy_param_table_widget(table), TRUE, TRUE, 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)
{
    GwyParams *params = gui->args->params;
    GwyParamTable *table = gui->table;

    if (id < 0 || id == PARAM_RAW_TRANSFORM) {
        gboolean is_raw = gwy_params_get_boolean(params, PARAM_RAW_TRANSFORM);
        gwy_param_table_set_sensitive(table, PARAM_PRESERVERMS, !is_raw);
        gwy_param_table_set_sensitive(table, PARAM_ZEROMEAN, !is_raw);
        gwy_param_table_set_sensitive(table, PARAM_WINDOW, !is_raw);
        gwy_param_table_set_sensitive(table, PARAM_INVERSE_TRANSFORM, is_raw);
    }
}

static gboolean
imagpart_filter(GwyFile *data, gint id, gpointer user_data)
{
    GwyField *imagpart, *field = (GwyField*)user_data;

    if (!(imagpart = gwy_file_get_image(data, id)))
        return FALSE;
    return !gwy_field_is_incompatible(imagpart, field,
                                      GWY_DATA_MISMATCH_RES
                                      | GWY_DATA_MISMATCH_REAL
                                      | GWY_DATA_MISMATCH_LATERAL
                                      | GWY_DATA_MISMATCH_VALUE);
}

static void
sanitise_params(ModuleArgs *args)
{
    GwyParams *params = args->params;
    GwyAppDataId imagpart = gwy_params_get_data_id(params, PARAM_IMAG_PART);
    gboolean is_none = gwy_params_data_id_is_none(params, PARAM_IMAG_PART);
    gboolean use_imagpart = gwy_params_get_boolean(params, PARAM_USE_IMAG_PART);

    if (use_imagpart
        && (is_none || !imagpart_filter(gwy_data_browser_get_file(imagpart.datano), imagpart.id, args->field)))
        gwy_params_set_boolean(params, PARAM_USE_IMAG_PART, 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 : */
