/*
 *  $Id: change-preview-volume.c 30046 2026-06-11 08:27:53Z yeti-dn $
 *  Copyright (C) 2025-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 "libgwyddion/macros.h"
#include "libgwyui/utils.h"

#include "libgwyapp/gwyapp.h"
#include "libgwyapp/internal.h"

enum {
    PARAM_PREVIEW_TYPE,
    PARAM_PREVIEW_LEVEL,
    PARAM_PREVIEW_IMAGE,
    PARAM_PREVIEW_OUTLIERS,
    PARAM_PREVIEW_UPDATE,
};

typedef enum {
    VOLUME_PREVIEW_SECTION = 0,
    VOLUME_PREVIEW_MEAN    = 1,
    VOLUME_PREVIEW_MINIMUM = 2,
    VOLUME_PREVIEW_MAXIMUM = 3,
    VOLUME_PREVIEW_RMS     = 4,
    VOLUME_PREVIEW_MINPOS  = 5,
    VOLUME_PREVIEW_MAXPOS  = 6,
    VOLUME_PREVIEW_IMAGE   = 100,
} VolumePreviewType;

typedef struct {
    GtkWidget *dialog;
    GwyParams *params;
    GwyParamTable *table;
    GwyAppImageWindow *window;
    GwyBrick *brick;

    GwyField *preview;
    GwyField *saved_preview;

    gboolean initial_preview;
} PreviewChooser;

static gboolean     preview_filter     (GwyFile *data,
                                        gint id,
                                        gpointer user_data);
static void         recalculate_preview(PreviewChooser *window);
static void         param_changed      (PreviewChooser *window,
                                        gint id,
                                        GwyParamTable *table);
static void         render_preview     (gpointer user_data);
static GwyParamDef* define_params      (void);

void
_gwy_app_change_volume_preview(GwyAppImageWindow *window)
{
    static const guint simple_types[] = {
        VOLUME_PREVIEW_MEAN, VOLUME_PREVIEW_MINIMUM, VOLUME_PREVIEW_MAXIMUM,
        VOLUME_PREVIEW_MINPOS, VOLUME_PREVIEW_MAXPOS,
        VOLUME_PREVIEW_RMS,
    };

    GwyAppDataWindow *datawindow = GWY_APP_DATA_WINDOW(window);
    g_return_if_fail(gwy_app_data_window_get_data_kind(datawindow) == GWY_FILE_VOLUME);
    gint id = gwy_app_data_window_get_id(datawindow);
    g_return_if_fail(id >= 0);

    PreviewChooser chooser;
    gwy_clear1(chooser);

    GwyFile *file = gwy_app_data_window_get_file(datawindow);
    chooser.window = window;
    chooser.preview = gwy_dict_get_object(GWY_DICT(file), gwy_file_key_cmap_picture(id));
    chooser.saved_preview = gwy_field_copy(chooser.preview);
    GwyBrick *brick = chooser.brick = gwy_file_get_volume(file, id);

    GwyParams *params = chooser.params = _gwy_app_image_window_get_params(window);
    gboolean did_preview_setup = TRUE;
    if (!gwy_params_get_def(params)) {
        did_preview_setup = FALSE;
        gwy_params_set_def(params, define_params());
    }

    GwyDict *settings = gwy_app_settings_get();

    gboolean update = FALSE;
    if (gwy_dict_gis_boolean_by_name(settings, "/app/volume-preview/update", &update))
        gwy_params_set_boolean(params, PARAM_PREVIEW_UPDATE, update);
    chooser.initial_preview = !update;

    gboolean outliers = FALSE;
    if (!did_preview_setup) {
        gwy_dict_gis_boolean_by_name(settings, "/app/volume-preview/outliers", &outliers);
        gwy_params_set_boolean(params, PARAM_PREVIEW_OUTLIERS, outliers);
    }

    _gwy_load_preview_params_from_file(window, PARAM_PREVIEW_TYPE, PARAM_PREVIEW_LEVEL, PARAM_PREVIEW_OUTLIERS);

    chooser.dialog = gwy_dialog_new(_("Change Volume Data Preview"));
    GwyDialog *dialog = GWY_DIALOG(chooser.dialog);
    gtk_window_set_transient_for(GTK_WINDOW(dialog), GTK_WINDOW(window));
    gwy_dialog_add_stock_buttons(dialog, GWY_STOCK_CANCEL, GWY_STOCK_OK, 0);

    GwyParamTable *table = gwy_param_table_new(params);
    gwy_param_table_append_radio_header(table, PARAM_PREVIEW_TYPE);
    for (guint i = 0; i < G_N_ELEMENTS(simple_types); i++)
        gwy_param_table_append_radio_item(table, PARAM_PREVIEW_TYPE, simple_types[i]);
    gwy_param_table_append_radio_item(table, PARAM_PREVIEW_TYPE, VOLUME_PREVIEW_IMAGE);
    gwy_param_table_append_image_id(table, PARAM_PREVIEW_IMAGE);
    gwy_param_table_data_id_set_filter(table, PARAM_PREVIEW_IMAGE, preview_filter, &chooser, NULL);
    gwy_param_table_append_radio_item(table, PARAM_PREVIEW_TYPE, VOLUME_PREVIEW_SECTION);
    gwy_param_table_append_slider(table, PARAM_PREVIEW_LEVEL);
    gwy_param_table_slider_restrict_range(table, PARAM_PREVIEW_LEVEL, 0, gwy_brick_get_zres(brick)-1);
    gwy_param_table_append_separator(table);
    gwy_param_table_append_checkbox(table, PARAM_PREVIEW_OUTLIERS);
    gwy_param_table_append_checkbox(table, PARAM_PREVIEW_UPDATE);

    gwy_dialog_add_content(dialog, gwy_param_table_widget(table), TRUE, TRUE, 0);
    gwy_dialog_set_preview_func(dialog, GWY_PREVIEW_IMMEDIATE, render_preview, &chooser, NULL);
    g_signal_connect_swapped(table, "param-changed", G_CALLBACK(param_changed), &chooser);

    GwyDialogOutcome outcome = gwy_dialog_run(dialog);
    if (outcome == GWY_DIALOG_CANCEL) {
        /* Make sure we get back the old preview, regardless what the user did in the dialog. */
        gwy_field_assign(chooser.preview, chooser.saved_preview);
        gwy_field_data_changed(chooser.preview);
        goto end;
    }
    if (outcome != GWY_DIALOG_HAVE_RESULT)
        recalculate_preview(&chooser);

    _gwy_save_preview_params_to_file(window, PARAM_PREVIEW_TYPE, PARAM_PREVIEW_LEVEL, PARAM_PREVIEW_OUTLIERS);
    update = gwy_params_get_boolean(params, PARAM_PREVIEW_UPDATE);
    gwy_dict_set_boolean_by_name(settings, "/app/volume-preview/update", update);
    outliers = gwy_params_get_boolean(params, PARAM_PREVIEW_OUTLIERS);
    gwy_dict_set_boolean_by_name(settings, "/app/volume-preview/outliers", outliers);

end:
    g_clear_object(&chooser.saved_preview);
}

static gboolean
preview_filter(GwyFile *data, gint id, gpointer user_data)
{
    const GwyDataMismatchFlags flags = GWY_DATA_MISMATCH_RES | GWY_DATA_MISMATCH_REAL | GWY_DATA_MISMATCH_LATERAL;
    PreviewChooser *chooser = (PreviewChooser*)user_data;
    if (!chooser->brick)
        return FALSE;

    GwyField *field = gwy_file_get_image(data, id);
    return !gwy_field_is_incompatible_with_brick_xy(field, chooser->brick, flags);
}

static void
recalculate_preview(PreviewChooser *chooser)
{
    GwyAppDataWindow *window = GWY_APP_DATA_WINDOW(chooser->window);
    GwyFile *file = gwy_app_data_window_get_file(window);
    gint id = gwy_app_data_window_get_id(window);
    GwyBrick *brick = chooser->brick;
    VolumePreviewType type = gwy_params_get_enum(chooser->params, PARAM_PREVIEW_TYPE);
    gboolean outliers = gwy_params_get_boolean(chooser->params, PARAM_PREVIEW_OUTLIERS);
    GwyField *preview = chooser->preview;

    if (type == VOLUME_PREVIEW_MEAN)
        gwy_brick_mean_xy_plane(brick, preview);
    else if (type == VOLUME_PREVIEW_MINIMUM)
        gwy_brick_min_xy_plane(brick, preview);
    else if (type == VOLUME_PREVIEW_MAXIMUM)
        gwy_brick_max_xy_plane(brick, preview);
    else if (type == VOLUME_PREVIEW_MINPOS)
        gwy_brick_minpos_xy_plane(brick, preview);
    else if (type == VOLUME_PREVIEW_MAXPOS)
        gwy_brick_maxpos_xy_plane(brick, preview);
    else if (type == VOLUME_PREVIEW_RMS)
        gwy_brick_rms_xy_plane(brick, preview);
    else if (type == VOLUME_PREVIEW_IMAGE)
        gwy_field_assign(preview, gwy_params_get_image(chooser->params, PARAM_PREVIEW_IMAGE));
    else if (type == VOLUME_PREVIEW_SECTION)
        gwy_brick_extract_xy_plane(brick, preview, gwy_params_get_int(chooser->params, PARAM_PREVIEW_LEVEL));
    else {
        g_assert_not_reached();
    }

    if (outliers) {
        GwyNield *mask = gwy_field_new_nield_alike(preview);
        gwy_field_mark_outliers_iqr(preview, mask, 3.0, 3.0);
        gwy_field_laplace_solve(preview, mask, GWY_LAPLACE_MASKED, 1.0);
        g_object_unref(mask);
    }

    gwy_dict_set_object(GWY_DICT(file), gwy_file_key_volume_picture(id), preview);
    gwy_field_data_changed(preview);
}

static void
param_changed(PreviewChooser *chooser, gint id, GwyParamTable *table)
{
    gboolean has_image = !!gwy_params_get_image(chooser->params, PARAM_PREVIEW_IMAGE);
    VolumePreviewType type = gwy_params_get_enum(chooser->params, PARAM_PREVIEW_TYPE);

    if (id < 0 && !has_image) {
        if (type == VOLUME_PREVIEW_IMAGE)
            gwy_param_table_set_enum(table, PARAM_PREVIEW_TYPE, (type = VOLUME_PREVIEW_MEAN));
        gwy_param_table_radio_set_sensitive(table, PARAM_PREVIEW_TYPE, VOLUME_PREVIEW_IMAGE, FALSE);
    }
    if (id < 0 || id == PARAM_PREVIEW_TYPE) {
        gwy_param_table_set_sensitive(table, PARAM_PREVIEW_LEVEL, type == VOLUME_PREVIEW_SECTION);
        gwy_param_table_set_sensitive(table, PARAM_PREVIEW_IMAGE, type == VOLUME_PREVIEW_IMAGE);
    }
    gwy_dialog_invalidate(GWY_DIALOG(chooser->dialog));
}

static void
render_preview(gpointer user_data)
{
    PreviewChooser *chooser = (PreviewChooser*)user_data;

    /* GwyDialog runs an initial preview on startup, but we do not really want it here without instant updates enabled
     * because it just feels disorienting. */
    if (chooser->initial_preview) {
        chooser->initial_preview = FALSE;
        return;
    }
    recalculate_preview(chooser);
    gwy_dialog_have_result(GWY_DIALOG(chooser->dialog));
}

static GwyParamDef*
define_params(void)
{
    static const GwyEnum types[] = {
        { N_("Mean"),          VOLUME_PREVIEW_MEAN,    },
        { N_("Minimum"),       VOLUME_PREVIEW_MINIMUM, },
        { N_("Maximum"),       VOLUME_PREVIEW_MAXIMUM, },
        { N_("Min. position"), VOLUME_PREVIEW_MINPOS,  },
        { N_("Max. position"), VOLUME_PREVIEW_MAXPOS,  },
        { N_("RMS"),           VOLUME_PREVIEW_RMS,     },
        { N_("Image"),         VOLUME_PREVIEW_IMAGE,   },
        { N_("Section"),       VOLUME_PREVIEW_SECTION, },
    };
    static GwyParamDef *pardef = NULL;

    if (pardef)
        return pardef;

    pardef = gwy_param_def_new();
    gwy_param_def_add_gwyenum(pardef, PARAM_PREVIEW_TYPE, NULL, _("Preview quantity"),
                              types, G_N_ELEMENTS(types), VOLUME_PREVIEW_MEAN);
    gwy_param_def_add_int(pardef, PARAM_PREVIEW_LEVEL, NULL, NULL, 0, G_MAXINT, 0);
    gwy_param_def_add_image_id(pardef, PARAM_PREVIEW_IMAGE, NULL, NULL);
    gwy_param_def_add_boolean(pardef, PARAM_PREVIEW_OUTLIERS, NULL, _("Ignore outliers"), FALSE);
    gwy_param_def_add_instant_updates(pardef, PARAM_PREVIEW_UPDATE, NULL, NULL, FALSE);

    return pardef;
}

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