/*
 *  $Id: xyz_volumize.c 29407 2026-01-30 15:34:09Z yeti-dn $
 *  Copyright (C) 2016-2023 David Necas (Yeti).
 *
 *  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 <string.h>
#include <gtk/gtk.h>
#include <gwy.h>

#define RUN_MODES (GWY_RUN_INTERACTIVE)

enum {
    PREVIEW_WIDTH = 500,
    PREVIEW_HEIGHT = 300,
    RESPONSE_SQUARE_PIXELS,
};

typedef enum {
    DISPLAY_X      = 0,
    DISPLAY_Y      = 1,
    DISPLAY_VALUES = 2,
} VolumizeDisplay;


enum {
    PARAM_DATA,
    PARAM_DISPLAY,
    PARAM_FRAMES,
    PARAM_FROM,
    PARAM_TO,
    PARAM_BORDER,
    PARAM_XRES,
    PARAM_YRES,
    BUTTON_SQUARE_PIXELS,
};

typedef enum {
    LAST_UPDATED_X,
    LAST_UPDATED_Y
} XYZRasLastUpdated;

typedef struct {
    GwyParams *params;
    GwyScatter *scatter;
    GwyBrick *result;
} ModuleArgs;

typedef struct {
    ModuleArgs *args;
    GtkWidget *dialog;
    GwyParamTable *table_view;
    GwyParamTable *table_options;
    GwyParamTable *table_rasterize;
    GwyFile *data;
    GwyGraphModel *gmodel;
    GwySelection *graph_selection;
    XYZRasLastUpdated last_updated;
} ModuleGUI;

static gboolean         module_register        (void);
static GwyParamDef*     define_module_params   (void);
static void             execute                (ModuleArgs *args);
static GwyDialogOutcome run_gui                (ModuleArgs *args);
static void             dialog_response        (ModuleGUI *gui,
                                                gint response);
static void             param_changed          (ModuleGUI *gui,
                                                gint id);
static void             update_graph_curve     (ModuleGUI *gui);
static void             update_graph_selection (ModuleGUI *gui);
static void             module_main            (GwyFile *data,
                                                GwyRunModeFlags mode);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Split XYZ data stream image stack."),
    "Petr Klapetek <klapetek@gwyddion.net>",
    "1.0",
    "Petr Klapetek",
    "2025",
};

GWY_MODULE_QUERY2(module_info, xyz_volumize)

static gboolean
module_register(void)
{
    gwy_xyz_func_register("xyz_volumize",
                          module_main,
                          N_("/Split to Image _Stack..."),
                          NULL,
                          RUN_MODES,
                          GWY_MENU_FLAG_XYZ,
                          N_("Split XYZ data stream to image stack."));

    return TRUE;
}

static GwyParamDef*
define_module_params(void)
{
    static GwyParamDef *paramdef = NULL;
    static const GwyEnum displays[] = {
        { N_("X"),      DISPLAY_X,      },
        { N_("Y"),      DISPLAY_Y,      },
        { N_("Values"), DISPLAY_VALUES, },
    };

    if (paramdef)
        return paramdef;

    paramdef = gwy_param_def_new();
    gwy_param_def_set_function_name(paramdef, gwy_xyz_func_current());
    gwy_param_def_add_scatter_data(paramdef, PARAM_DATA, "data", _("Series data"));
    gwy_param_def_add_int(paramdef, PARAM_FRAMES, "frames", _("F_rames"), 2, 1000, 10);
    gwy_param_def_add_int(paramdef, PARAM_BORDER, "border", _("Border _width"), -G_MAXINT, G_MAXINT, 10);
    gwy_param_def_add_percentage(paramdef, PARAM_FROM, "from", _("_From"), 0);
    gwy_param_def_add_percentage(paramdef, PARAM_TO, "to", _("_To"), 1);
    gwy_param_def_add_int(paramdef, PARAM_XRES, "xres", _("_Horizontal size"), 2, 16384, 512);
    gwy_param_def_add_int(paramdef, PARAM_YRES, "yres", _("_Vertical size"), 2, 16384, 512);
    gwy_param_def_add_gwyenum(paramdef, PARAM_DISPLAY, "dir", _("_Show"),
                              displays, G_N_ELEMENTS(displays), DISPLAY_X);


    return paramdef;
}

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

    g_return_if_fail(mode & RUN_MODES);

    gwy_clear1(args);
    gwy_data_browser_get_current(GWY_APP_SCATTER, &args.scatter,
                                 GWY_APP_SCATTER_ID, &id,
                                 0);

    g_return_if_fail(GWY_IS_SCATTER(args.scatter));
    if (!gwy_scatter_get_n_values(args.scatter))
        return;

    args.params = gwy_params_new_from_settings(define_module_params());
    args.result = NULL;

    if (mode == GWY_RUN_INTERACTIVE) {
        outcome = run_gui(&args);
        gwy_params_save_to_settings(args.params);
        if (outcome == GWY_DIALOG_CANCEL)
            goto end;
    }

    if (outcome == GWY_DIALOG_PROCEED)
        execute(&args);

    if (args.result) {
        newid = gwy_file_add_volume(data, args.result, NULL);

        gwy_file_set_visible(data, GWY_FILE_VOLUME, newid, TRUE);
        gwy_file_set_title(data, GWY_FILE_VOLUME, newid, _("Stack from XYZ data"), TRUE);
        gwy_file_sync_items(data, GWY_FILE_XYZ, id,
                            data, GWY_FILE_VOLUME, newid,
                            GWY_FILE_ITEM_PALETTE, FALSE);
        gwy_log_add_full(data, GWY_FILE_VOLUME, -1, newid, "xyz::xyz_volumize", NULL);

        g_object_unref(args.result);
    }

end:
    g_object_unref(args.params);
}

static GwyDialogOutcome
run_gui(ModuleArgs *args)
{
    gint n;
    GwyDialog *dialog;
    ModuleGUI gui;
    GwyParamTable *table;
    GtkWidget *graph, *area, *hbox;
    GwyGraphCurveModel *gcmodel;
    GwyDialogOutcome outcome;

    gui.dialog = gwy_dialog_new(_("Split to Image Stack"));
    dialog = GWY_DIALOG(gui.dialog);
    gwy_dialog_add_stock_buttons(dialog, GWY_STOCK_CANCEL, GWY_STOCK_OK, 0);

    gui.args = args;
    gui.last_updated = LAST_UPDATED_X;
    gui.gmodel = gwy_graph_model_new();
    g_object_set(gui.gmodel,
                 "label-visible", FALSE,
                 NULL);

    gcmodel = gwy_graph_curve_model_new();
    g_object_set(gcmodel, "mode", GWY_GRAPH_CURVE_LINE, NULL);
    gwy_graph_model_add_curve(gui.gmodel, gcmodel);
    g_object_unref(gcmodel);
    graph = gwy_graph_new(gui.gmodel);
    gtk_widget_set_size_request(graph, PREVIEW_WIDTH, PREVIEW_HEIGHT);
    gwy_dialog_add_content(dialog, graph, FALSE, FALSE, 0);
    gwy_graph_enable_user_input(GWY_GRAPH(graph), FALSE);
    gwy_graph_set_status(GWY_GRAPH(graph), GWY_GRAPH_STATUS_XLINES);
    area = gwy_graph_get_area(GWY_GRAPH(graph));
    gwy_graph_area_set_selection_editable(GWY_GRAPH_AREA(area), FALSE);
    gui.graph_selection = gwy_graph_area_get_selection(GWY_GRAPH_AREA(area), GWY_GRAPH_STATUS_XLINES);

    hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    table = gui.table_view = gwy_param_table_new(args->params);
    gwy_param_table_append_radio(table, PARAM_DISPLAY);
    gwy_param_table_append_scatter_data(table, PARAM_DATA, args->scatter);

    gtk_box_pack_start(GTK_BOX(hbox), gwy_param_table_widget(table), FALSE, FALSE, 0);

    table = gui.table_options = gwy_param_table_new(args->params);
    gwy_param_table_append_slider(table, PARAM_FROM);
    gwy_param_table_append_slider(table, PARAM_TO);
    gwy_param_table_append_slider(table, PARAM_FRAMES);
    gwy_param_table_append_slider(table, PARAM_BORDER);
    n = gwy_scatter_get_n_points(args->scatter);
    gwy_param_table_slider_restrict_range(table, PARAM_BORDER, -n/5, n/5);
    gtk_box_pack_start(GTK_BOX(hbox), gwy_param_table_widget(table), FALSE, FALSE, 0);

    table = gui.table_rasterize = gwy_param_table_new(args->params);
    gwy_param_table_append_header(table, -1, _("Resolution"));
    gwy_param_table_append_slider(table, PARAM_XRES);
    gwy_param_table_slider_set_mapping(table, PARAM_XRES, GWY_SCALE_MAPPING_LOG);
    gwy_param_table_set_unitstr(table, PARAM_XRES, _("px"));
    gwy_param_table_append_slider(table, PARAM_YRES);
    gwy_param_table_slider_set_mapping(table, PARAM_YRES, GWY_SCALE_MAPPING_LOG);
    gwy_param_table_set_unitstr(table, PARAM_YRES, _("px"));
    gwy_param_table_append_button(table, BUTTON_SQUARE_PIXELS, -1, RESPONSE_SQUARE_PIXELS, _("Make Pixels S_quare"));
    gtk_box_pack_start(GTK_BOX(hbox), gwy_param_table_widget(table), FALSE, FALSE, 0);

    gwy_dialog_add_content(dialog, hbox, FALSE, TRUE, 0);

    g_signal_connect_swapped(gui.table_view, "param-changed", G_CALLBACK(param_changed), &gui);
    g_signal_connect_swapped(gui.table_options, "param-changed", G_CALLBACK(param_changed), &gui);
    g_signal_connect_swapped(gui.table_rasterize, "param-changed", G_CALLBACK(param_changed), &gui);
    g_signal_connect_swapped(dialog, "response", G_CALLBACK(dialog_response), &gui);

    outcome = gwy_dialog_run(dialog);

    g_object_unref(gui.gmodel);
    return outcome;
}

static void
dialog_response(ModuleGUI *gui, gint response)
{
    ModuleArgs *args = gui->args;
    GwyParams *params = args->params;
    gdouble xmin, xmax, ymin, ymax;
    gint xres, yres;

    if (response == RESPONSE_SQUARE_PIXELS) {
        gwy_scatter_xrange(args->scatter, &xmin, &xmax);
        gwy_scatter_yrange(args->scatter, &ymin, &ymax);
        xres = gwy_params_get_int(params, PARAM_XRES);
        yres = gwy_params_get_int(params, PARAM_YRES);

        if (gui->last_updated == LAST_UPDATED_X) {
            gint res = GWY_ROUND((ymax - ymin)/(xmax - xmin)*xres);
            gwy_param_table_set_int(gui->table_rasterize, PARAM_YRES, CLAMP(res, 2, 16384));
            gui->last_updated = LAST_UPDATED_X;
        }
        else {
            gint res = GWY_ROUND((xmax - xmin)/(ymax - ymin)*yres);
            gwy_param_table_set_int(gui->table_rasterize, PARAM_XRES, CLAMP(res, 2, 16384));
            gui->last_updated = LAST_UPDATED_Y;
        }
    }
}

static void
param_changed(ModuleGUI *gui, gint id)
{
    GwyParams *params = gui->args->params;

    if (id < 0 || id == PARAM_DISPLAY) {
        gwy_param_table_set_sensitive(gui->table_view, PARAM_DATA,
                                      gwy_params_get_enum(gui->args->params, PARAM_DISPLAY) == DISPLAY_VALUES);
    }

    if (id < 0 || id == PARAM_DISPLAY || id == PARAM_DATA)
        update_graph_curve(gui);
    if (id < 0 || id == PARAM_DISPLAY || id == PARAM_DATA || id == PARAM_FRAMES || id == PARAM_FROM || id == PARAM_TO)
        update_graph_selection(gui);
    if (id == PARAM_XRES)
        gui->last_updated = LAST_UPDATED_X;
    if (id == PARAM_YRES)
        gui->last_updated = LAST_UPDATED_Y;

    if (id == PARAM_FROM || id == PARAM_TO) {
        gdouble from = gwy_params_get_double(params, PARAM_FROM);
        gdouble to = gwy_params_get_double(params, PARAM_TO);
        if (to < from) {
            if (id == PARAM_TO)
                gwy_param_table_set_double(gui->table_options, PARAM_FROM, to);
            else
                gwy_param_table_set_double(gui->table_options, PARAM_TO, from);
        }
    }

    gwy_dialog_invalidate(GWY_DIALOG(gui->dialog));
}

static void
update_graph_curve(ModuleGUI *gui)
{
    ModuleArgs *args = gui->args;
    GwyGraphCurveModel *gcmodel = gwy_graph_model_get_curve(gui->gmodel, 0);
    GwyLine *line;
    gint display = gwy_params_get_enum(gui->args->params, PARAM_DISPLAY);
    gint idata = gwy_params_get_int(args->params, PARAM_DATA);
    GwyScatter *scatter = args->scatter;
    const GwyXY *xy = gwy_scatter_get_xy_const(scatter);
    const gdouble *z = gwy_scatter_get_data_const(scatter, idata);
    gint nvals = gwy_scatter_get_n_points(scatter);
    gdouble *linedata;
    gint k, n, m, ndiv = 1;

    if (nvals > 5000000)
        ndiv = 10000;
    else if (nvals > 500000)
        ndiv = 1000;
    else if (nvals > 50000)
        ndiv = 100;
    else if (nvals > 5000)
        ndiv = 10;

    line = gwy_line_new(nvals/ndiv, nvals, FALSE);
    linedata = gwy_line_get_data(line);

    m = 0;
    for (n = 0; n < nvals/ndiv; n++) {
        linedata[n] = 0;
        if (display == DISPLAY_X)
            for (k = 0; k < ndiv; k++)
                linedata[n] += xy[m++].x;
        else if (display == DISPLAY_Y)
            for (k = 0; k < ndiv; k++)
                linedata[n] += xy[m++].y;
        else
            for (k = 0; k < ndiv; k++)
                linedata[n] += z[m++];

         if (ndiv > 1)
             linedata[n] /= ndiv;
    }

    gwy_graph_curve_model_set_data_from_line(gcmodel, line, 0, 0);
    g_object_unref(line);
}


static void
update_graph_selection(ModuleGUI *gui)
{
    ModuleArgs *args = gui->args;
    GwyParams *params = args->params;
    gdouble *sel;
    gint ndata = gwy_graph_curve_model_get_n_data(gwy_graph_model_get_curve(gui->gmodel, 0));
    const gdouble *xdata = gwy_graph_curve_model_get_xdata(gwy_graph_model_get_curve(gui->gmodel, 0));
    gint nsel = gwy_params_get_int(params, PARAM_FRAMES) + 1;
    gdouble from = gwy_params_get_double(params, PARAM_FROM);
    gdouble to = gwy_params_get_double(params, PARAM_TO);
    gint i;

    gwy_selection_set_max_objects(gui->graph_selection, nsel);
    gwy_selection_clear(gui->graph_selection);

    if (to > from) {
        sel = g_new(gdouble, nsel);
        for (i = 0; i < nsel; i++) {
            sel[i] = from*xdata[ndata-1] + i*(to-from)*(xdata[ndata-1])/(nsel-1);
        }

        gwy_selection_set_data(gui->graph_selection, nsel, sel);
        g_free(sel);
    }
}

static void
execute(ModuleArgs *args)
{
    GwyScatter *scatter = args->scatter;
    GwyParams *params = args->params;
    GwyBrick *result;
    GwyField *dfield;
    gint idata = gwy_params_get_int(args->params, PARAM_DATA);
    const GwyXY *xy = gwy_scatter_get_xy_const(scatter);
    const gdouble *z = gwy_scatter_get_data_const(scatter, idata);
    guint n = gwy_scatter_get_n_points(scatter);
    GwyXYZ *xyz_section;
    guint i, k, nres;
    gint nframes = gwy_params_get_int(params, PARAM_FRAMES);
    gint xres = gwy_params_get_int(params, PARAM_XRES);
    gint yres = gwy_params_get_int(params, PARAM_YRES);
    gdouble from = gwy_params_get_double(params, PARAM_FROM);
    gdouble to = gwy_params_get_double(params, PARAM_TO);
    gint border = gwy_params_get_int(params, PARAM_BORDER);
    gint ifrom = 0, ito = 0;
    gdouble xmin, xmax, ymin, ymax;

    gwy_scatter_xrange(scatter, &xmin, &xmax);
    gwy_scatter_yrange(scatter, &ymin, &ymax);

    dfield = gwy_field_new(xres, yres, xmax-xmin, ymax-ymin, FALSE);
    gwy_field_set_xoffset(dfield, xmin);
    gwy_field_set_yoffset(dfield, ymin);
    result = gwy_brick_new(xres, yres, nframes,
                           xmax-xmin, ymax-ymin, nframes,
                           FALSE);

    gwy_unit_assign(gwy_brick_get_unit_x(result), gwy_scatter_get_unit_xy(scatter));
    gwy_unit_assign(gwy_brick_get_unit_y(result), gwy_scatter_get_unit_xy(scatter));
    gwy_unit_assign(gwy_brick_get_unit_z(result), gwy_scatter_get_unit_data(scatter, idata));

    for (i = 0; i < nframes; i++) {
        ifrom = from*n + i*(to-from)*n/nframes + border/2;
        ito = from*n + (i+1)*(to-from)*n/nframes - border/2;

        if (ito < ifrom)
            ito = ifrom;

        if (ifrom < 0)
            ifrom = 0;
        if (ito < 0)
            ito = 0;

        if (ifrom > n)
            ifrom = n;
        if (ito > n)
            ito = n;

        nres = ito - ifrom;
        xyz_section = g_new(GwyXYZ, nres);

        for (k = ifrom; k < ito; k++) {
            xyz_section[k-ifrom].x = xy[k].x;
            xyz_section[k-ifrom].y = xy[k].y;
            xyz_section[k-ifrom].z = z[k];
        }

        gwy_field_average_xyz(dfield, NULL,
                              xyz_section,
                              nres);

        gwy_brick_set_xy_plane(result, dfield, i);
        g_free(xyz_section);
    }
    args->result = result;

    g_object_unref(dfield);
}



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