/*
 *  $Id: averaging.c 30047 2026-06-11 08:30:45Z yeti-dn $
 *  Copyright (C) 2011, David Necas (Yeti), Petr Klapetek, Daniil Bratashov
 *  E-mail: yeti@gwyddion.net, klapetek@gwyddion.net, dn2010@gmail.com
 *
 *  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

typedef struct {
    gint row;
    gint col;
    gdouble zvalue;
} GwyMaximum;

typedef struct {
    GwyField *field;
    GwyField *result;
    GwySelection *selection;
} ModuleArgs;

typedef struct {
    ModuleArgs *args;
    GtkWidget *dialog;
    GtkWidget *dataview;
} ModuleGUI;

static gboolean         module_register(void);
static void             module_main    (GwyFile *data,
                                        GwyRunModeFlags mode);
static GwyDialogOutcome run_gui        (ModuleArgs *args,
                                        GwyFile *data,
                                        gint id);
static gboolean         execute        (ModuleArgs *args);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Similar structures averaging using autocorrelation"),
    "Daniil Bratashov <dn2010@gmail.com>",
    "0.3",
    "David Nečas (Yeti) & Petr Klapetek & Daniil Bratashov",
    "2011",
};

GWY_MODULE_QUERY2(module_info, averaging)

static gboolean
module_register(void)
{
    gwy_image_func_register("averaging",
                            module_main,
                            N_("/_Correct Data/_Correlation Averaging..."),
                            NULL,
                            RUN_MODES,
                            GWY_MENU_FLAG_IMAGE,
                            N_("Averaging of similar structures"));

    return TRUE;
}

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

    g_return_if_fail(mode & RUN_MODES);
    gwy_clear1(args);
    gwy_data_browser_get_current(GWY_APP_FIELD, &args.field,
                                 GWY_APP_FIELD_ID, &id,
                                 0);
    g_return_if_fail(GWY_IS_FIELD(args.field));

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

    newid = gwy_file_add_image(data, args.result);

    gwy_file_set_visible(data, GWY_FILE_IMAGE, newid, TRUE);
    gwy_file_sync_items(data, GWY_FILE_IMAGE, 0,
                        data, GWY_FILE_IMAGE, newid,
                        GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_MASK_COLOR
                        | GWY_FILE_ITEM_RANGE | GWY_FILE_ITEM_COLOR_MAPPING | GWY_FILE_ITEM_REAL_SQUARE,
                        FALSE);
    gwy_file_set_title(data, GWY_FILE_IMAGE, newid, _("Averaged"), TRUE);
    gwy_log_add(data, GWY_FILE_IMAGE, id, newid);

end:
    g_clear_object(&args.result);
    g_clear_object(&args.selection);
}

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

    gui.args = args;

    gui.dialog = gwy_dialog_new(_("Averaging of Similar Structures"));
    dialog = GWY_DIALOG(gui.dialog);
    gwy_dialog_add_stock_buttons(dialog, GWY_STOCK_CANCEL, GWY_STOCK_OK, 0);

    label = gtk_label_new(_("Select the sample area below"));
    gtk_label_set_xalign(GTK_LABEL(label), 0.0);
    gwy_dialog_add_content(dialog, label, FALSE, FALSE, 2);

    gui.dataview = gwy_create_preview(args->field, NULL, PREVIEW_SIZE);
    gwy_setup_data_view(GWY_DATA_VIEW(gui.dataview), data, GWY_FILE_IMAGE, id,
                        GWY_FILE_ITEM_PALETTE | GWY_FILE_ITEM_REAL_SQUARE);
    args->selection = gwy_create_preview_vector_layer(GWY_DATA_VIEW(gui.dataview), GWY_TYPE_LAYER_RECTANGLE, 1, TRUE);
    /* Preserve selection for the caller. */
    g_object_ref(args->selection);
    gwy_dialog_add_content(dialog, gui.dataview, FALSE, FALSE, 2);

    /* FIXME: We should probably connect to selection changed signal and set OK sensitivity accordingly. */
    return gwy_dialog_run(dialog);
}

static void
find_local_maxima(GwyField *dfield, GArray *maxima)
{
    gint xres = gwy_field_get_xres(dfield), yres = gwy_field_get_yres(dfield);
    const gdouble *data = gwy_field_get_data_const(dfield);
    gdouble m = gwy_field_max(dfield);
    gint row, col;
    GwyMaximum max;

    for (row = 1; row < yres - 1; row++) {
        for (col = 1; col < xres - 1; col++) {
            gint k = row * xres + col;
            gdouble d = data[k];

            if (d < data[k - xres] || d < data[k-1] || d < data[k+1] || d < data[k + xres])
                continue;

            max.col = col;
            max.row = row;
            max.zvalue = data[row * xres + col];
            if (max.zvalue > 0.75 * m)
                g_array_append_val(maxima, max);
        }
    }
}

static gboolean
execute(ModuleArgs *args)
{
    gdouble area[4];
    GwyField *dfield = args->field, *kernel, *correlation_score;
    GwyField *result, *res_kernel;
    gint xtop, ytop, xbottom, ybottom;
    GArray *maxima;
    GwyMaximum maximum;
    gint i;
    gdouble width, height, divider = 0;

    if (!gwy_selection_get_data(args->selection, area))
        return FALSE;

    xtop = gwy_field_rtoj(dfield, area[0]);
    ytop = gwy_field_rtoi(dfield, area[1]);
    xbottom = gwy_field_rtoj(dfield, area[2]);
    ybottom = gwy_field_rtoi(dfield, area[3]);
    kernel = gwy_field_area_extract(dfield, xtop, ytop, xbottom - xtop, ybottom - ytop);
    correlation_score = gwy_field_new_alike(dfield, FALSE);
    gwy_field_correlate(dfield, kernel, correlation_score, GWY_CORRELATION_NORMAL);
    gwy_field_filter_gaussian(correlation_score, 2.0/(2.0*sqrt(2.0*G_LN2)));
    maxima = g_array_new(FALSE, TRUE, sizeof(GwyMaximum));
    find_local_maxima(correlation_score, maxima);
    g_object_unref(correlation_score);

    res_kernel = gwy_field_new_alike(kernel, TRUE);
    width = gwy_field_get_xres(kernel);
    height = gwy_field_get_yres(kernel);
    for (i = 0; i < maxima->len; i++) {
        maximum = g_array_index(maxima, GwyMaximum, i);
        xtop = maximum.col - width/2;
        ytop = maximum.row - height/2;
        kernel = gwy_field_area_extract(dfield, xtop, ytop, width, height);
        gwy_field_linear_combination(res_kernel, 1.0, res_kernel, maximum.zvalue, kernel, 0.0);
        g_object_unref(kernel);
        divider += maximum.zvalue;
    }
    if (divider <= 0.0)
        divider = 1.0;
    gwy_field_multiply(res_kernel, 1.0/divider);

    result = gwy_field_new_alike(dfield, FALSE);
    gwy_field_assign(result, dfield);
    for (i = 0; i < maxima->len; i++) {
        maximum = g_array_index(maxima, GwyMaximum, i);
        xtop = maximum.col - width/2;
        ytop = maximum.row - height/2;
        gwy_field_area_copy(res_kernel, result, 0, 0, width, height, xtop, ytop);
    }

    g_array_free(maxima, TRUE);
    g_object_unref(res_kernel);

    args->result = result;

    return TRUE;
}

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