/*
 *  $Id: change-preview-xyz.c 30046 2026-06-11 08:27:53Z yeti-dn $
 *  Copyright (C) 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_VALUE,
    PARAM_PREVIEW_OUTLIERS,
    PARAM_PREVIEW_UPDATE,
};

typedef enum {
    XYZ_PREVIEW_DATA    = 0,
    XYZ_PREVIEW_DENSITY = 1,
} XYZPreviewType;

typedef struct {
    GtkWidget *dialog;
    GwyParams *params;
    GwyParamTable *table;
    GwyAppImageWindow *window;
    GwyScatter *scatter;

    GwyField *preview;
    GwyField *saved_preview;

    gboolean initial_preview;
} PreviewChooser;

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_xyz_preview(GwyAppImageWindow *window)
{
    GwyAppDataWindow *datawindow = GWY_APP_DATA_WINDOW(window);
    g_return_if_fail(gwy_app_data_window_get_data_kind(datawindow) == GWY_FILE_XYZ);
    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_xyz_picture(id));
    chooser.saved_preview = gwy_field_copy(chooser.preview);
    GwyScatter *scatter = chooser.scatter = gwy_file_get_xyz(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/xyz-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/xyz-preview/outliers", &outliers);
        gwy_params_set_boolean(params, PARAM_PREVIEW_OUTLIERS, outliers);
    }

    _gwy_load_preview_params_from_file(window, PARAM_PREVIEW_TYPE, PARAM_PREVIEW_VALUE, PARAM_PREVIEW_OUTLIERS);

    chooser.dialog = gwy_dialog_new(_("Change XYZ 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_scatter_data(table, PARAM_PREVIEW_VALUE, scatter);
    gwy_param_table_append_radio_row(table, PARAM_PREVIEW_TYPE);
    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_VALUE, PARAM_PREVIEW_OUTLIERS);
    update = gwy_params_get_boolean(params, PARAM_PREVIEW_UPDATE);
    gwy_dict_set_boolean_by_name(settings, "/app/xyz-preview/update", update);
    outliers = gwy_params_get_boolean(params, PARAM_PREVIEW_OUTLIERS);
    gwy_dict_set_boolean_by_name(settings, "/app/xyz-preview/outliers", outliers);

end:
    g_clear_object(&chooser.saved_preview);
}

static void
recalculate_preview(PreviewChooser *chooser)
{
    GwyAppDataWindow *window = GWY_APP_DATA_WINDOW(chooser->window);
    GtkWidget *dataview = gwy_data_window_get_data_view(GWY_DATA_WINDOW(window));
    GwyFile *file = gwy_app_data_window_get_file(window);
    gint id = gwy_app_data_window_get_id(window);
    GwyScatter *scatter = gwy_file_get_xyz(file, id);
    GwyParams *params = chooser->params;
    gboolean outliers = gwy_params_get_boolean(params, PARAM_PREVIEW_OUTLIERS);
    gint valueno = gwy_params_get_int(params, PARAM_PREVIEW_VALUE);
    XYZPreviewType type = gwy_params_get_enum(params, PARAM_PREVIEW_TYPE);
    GwyField *preview = chooser->preview;
    GtkAllocation allocation;
    gtk_widget_get_allocation(dataview, &allocation);

    GwyPreviewScatterFlags flags = GWY_PREVIEW_SCATTER_FILL;
    if (type == XYZ_PREVIEW_DENSITY)
        flags |= GWY_PREVIEW_SCATTER_DENSITY;
    gwy_preview_scatter_to_field(scatter, valueno, preview, allocation.width, allocation.height, flags);
    gwy_scatter_copy_units_to_field(scatter, valueno, preview);

    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_clear_object(&mask);
    }

    gwy_dict_set_object(GWY_DICT(file), gwy_file_key_xyz_picture(id), preview);
    gwy_data_view_set_zoom(GWY_DATA_VIEW(dataview), 1.0);
    gwy_field_data_changed(preview);
}

static void
param_changed(PreviewChooser *chooser,
              gint id,
              GwyParamTable *table)
{
    if (id < 0 || id == PARAM_PREVIEW_TYPE) {
        XYZPreviewType type = gwy_params_get_enum(chooser->params, PARAM_PREVIEW_TYPE);
        gwy_param_table_set_sensitive(table, PARAM_PREVIEW_VALUE, type == XYZ_PREVIEW_DATA);
    }

    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_("Data"),          XYZ_PREVIEW_DATA,    },
        { N_("Point density"), XYZ_PREVIEW_DENSITY, },
    };
    static GwyParamDef *pardef = NULL;

    if (pardef)
        return pardef;

    pardef = gwy_param_def_new();
    gwy_param_def_add_gwyenum(pardef, PARAM_PREVIEW_TYPE, NULL, _("Preview type"),
                              types, G_N_ELEMENTS(types), XYZ_PREVIEW_DATA);
    gwy_param_def_add_scatter_data(pardef, PARAM_PREVIEW_VALUE, 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 : */
