/*
 *  $Id: xyz_relate.c 30047 2026-06-11 08:30:45Z yeti-dn $
 *  Copyright (C) 2026 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 {
    PARAM_ABSCISSA,
    PARAM_ORDINATE,
    PARAM_ABSCISSA_DATA,
    PARAM_ORDINATE_DATA,
    PARAM_TARGET_GRAPH,
};

typedef enum {
    COORDINATE_INDEX = 0,
    COORDINATE_X     = 1,
    COORDINATE_Y     = 2,
    COORDINATE_VALUE = 3,
} CoordinateType;

typedef struct {
    GwyParams *params;
    GwyScatter *scatter;
    GwyGraphModel *result;
} 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 void             execute                 (ModuleArgs *args);
static GwyDialogOutcome run_gui                 (ModuleArgs *args);
static void preview(gpointer user_data);
static void             param_changed           (ModuleGUI *gui,
                                                 gint id);
static void             update_graph_model_props(ModuleArgs *args);
static void             sanitise_params         (ModuleArgs *args);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Plots one XYZ value as a function of another."),
    "Yeti <yeti@gwyddion.net>",
    "1.0",
    "David Nečas (Yeti)",
    "2026",
};

GWY_MODULE_QUERY2(module_info, xyz_relate)

static gboolean
module_register(void)
{
    gwy_xyz_func_register("xyz_relate",
                          module_main,
                          N_("/_Value Set/_Relate..."),
                          NULL,
                          RUN_MODES,
                          GWY_MENU_FLAG_XYZ,
                          N_("Plot relation between to XYZ data values"));

    return TRUE;
}

static GwyParamDef*
define_module_params(void)
{
    static const GwyEnum coords[] = {
        { N_("Sample number"), COORDINATE_INDEX, },
        { N_("X-coordinate"),  COORDINATE_X,     },
        { N_("Y-coordinate"),  COORDINATE_Y,     },
        { N_("Value"),         COORDINATE_VALUE, },
    };

    static GwyParamDef *paramdef = NULL;

    if (paramdef)
        return paramdef;

    paramdef = gwy_param_def_new();
    gwy_param_def_set_function_name(paramdef, gwy_xyz_func_current());
    gwy_param_def_add_gwyenum(paramdef, PARAM_ABSCISSA, "abscissa", _("Abscissa"),
                              coords, G_N_ELEMENTS(coords), COORDINATE_INDEX);
    gwy_param_def_add_gwyenum(paramdef, PARAM_ORDINATE, "ordinate", _("Ordinate"),
                              coords, G_N_ELEMENTS(coords), COORDINATE_X);
    gwy_param_def_add_scatter_data(paramdef, PARAM_ABSCISSA_DATA, "abscissa_data", _("Value"));
    gwy_param_def_add_scatter_data(paramdef, PARAM_ORDINATE_DATA, "ordinate_data", _("Value"));
    gwy_param_def_add_target_graph(paramdef, PARAM_TARGET_GRAPH, "target_graph", NULL);

    return paramdef;
}

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

    g_return_if_fail(mode & RUN_MODES);

    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));

    args.params = gwy_params_new_from_settings(define_module_params());
    args.result = gwy_graph_model_new();
    sanitise_params(&args);

    outcome = run_gui(&args);

    gwy_params_save_to_settings(args.params);
    if (outcome == GWY_DIALOG_CANCEL)
        goto end;
    if (outcome != GWY_DIALOG_HAVE_RESULT)
        execute(&args);

    GwyAppDataId target_graph_id = gwy_params_get_data_id(args.params, PARAM_TARGET_GRAPH);
    gint newid = gwy_app_add_graph_or_curves(args.result, data, &target_graph_id, 1);
    gwy_log_add_full(data, GWY_FILE_GRAPH, -1, newid >= 0 ? newid : target_graph_id.id, "xyz::xyz_relate", NULL);

end:
    g_object_unref(args.result);
    g_object_unref(args.params);
}

static GwyDialogOutcome
run_gui(ModuleArgs *args)
{
    ModuleGUI gui;

    gwy_clear1(gui);
    gui.args = args;
    gui.dialog = gwy_dialog_new(_("Relate"));
    GwyDialog *dialog = GWY_DIALOG(gui.dialog);
    gwy_dialog_add_stock_buttons(dialog, GWY_STOCK_CANCEL, GWY_STOCK_OK, 0);

    GtkWidget *hbox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
    gtk_container_set_border_width(GTK_CONTAINER(hbox), 4);
    gwy_dialog_add_content(dialog, hbox, TRUE, TRUE, 0);

    g_object_set(args->result,
                 "axis-label-bottom", "v<sub>1</sub>",
                 "axis-label-left", "v<sub>2</sub>",
                 NULL);
    GtkWidget *graph = gwy_graph_new(args->result);
    gtk_widget_set_size_request(graph, 480, 360);
    gwy_graph_enable_user_input(GWY_GRAPH(graph), FALSE);
    gtk_box_pack_start(GTK_BOX(hbox), graph, TRUE, TRUE, 0);

    GwyParamTable *table = gui.table = gwy_param_table_new(args->params);
    gwy_param_table_append_radio(table, PARAM_ABSCISSA);
    gwy_param_table_append_scatter_data(table, PARAM_ABSCISSA_DATA, args->scatter);
    gwy_param_table_append_separator(table);
    gwy_param_table_append_radio(table, PARAM_ORDINATE);
    gwy_param_table_append_scatter_data(table, PARAM_ORDINATE_DATA, args->scatter);
    if (!gwy_scatter_get_n_values(args->scatter)) {
        gwy_param_table_set_sensitive(table, PARAM_ABSCISSA_DATA, FALSE);
        gwy_param_table_set_sensitive(table, PARAM_ORDINATE_DATA, FALSE);
    }
    gwy_param_table_append_separator(table);
    gwy_param_table_append_target_graph(table, PARAM_TARGET_GRAPH, args->result);

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

    g_signal_connect_swapped(table, "param-changed", G_CALLBACK(param_changed), &gui);
    gwy_dialog_set_preview_func(dialog, GWY_PREVIEW_IMMEDIATE, preview, &gui, NULL);

    return gwy_dialog_run(dialog);
}

static void
param_changed(ModuleGUI *gui, gint id)
{
    if (id != PARAM_TARGET_GRAPH) {
        update_graph_model_props(gui->args);
        gwy_param_table_data_id_refilter(gui->table, PARAM_TARGET_GRAPH);
        gwy_dialog_invalidate(GWY_DIALOG(gui->dialog));
    }
}

static void
preview(gpointer user_data)
{
    ModuleGUI *gui = (ModuleGUI*)user_data;

    execute(gui->args);
}

static const gdouble*
get_data(GwyScatter *scatter, CoordinateType coordtype, gint idata,
         const gdouble *ibuf, const gdouble *xbuf, const gdouble *ybuf)
{
    if (coordtype == COORDINATE_INDEX)
        return ibuf;
    if (coordtype == COORDINATE_X)
        return xbuf;
    if (coordtype == COORDINATE_Y)
        return ybuf;
    return gwy_scatter_get_data_const(scatter, idata);
}

static void
execute(ModuleArgs *args)
{
    GwyParams *params = args->params;
    GwyScatter *scatter = args->scatter;
    GwyGraphModel *gmodel = args->result;
    CoordinateType abscissa_type = gwy_params_get_enum(params, PARAM_ABSCISSA);
    CoordinateType ordinate_type = gwy_params_get_enum(params, PARAM_ORDINATE);
    gint iabscissa = gwy_params_get_int(params, PARAM_ABSCISSA_DATA);
    gint iordinate = gwy_params_get_int(params, PARAM_ORDINATE_DATA);

    GwyGraphCurveModel *gcmodel;
    if (gwy_graph_model_get_n_curves(gmodel))
        gcmodel = gwy_graph_model_get_curve(gmodel, 0);
    else {
        gcmodel = gwy_graph_curve_model_new();
        g_object_set(gcmodel,
                     "mode", GWY_GRAPH_CURVE_LINE,
                     "description", _("Relation"),
                     NULL);
        gwy_graph_model_add_curve(gmodel, gcmodel);
        g_object_unref(gcmodel);
    }

    gsize ndata = gwy_scatter_get_n_points(scatter);
    const GwyXY *xy = gwy_scatter_get_xy_const(scatter);
    if (abscissa_type == COORDINATE_X && ordinate_type == COORDINATE_Y) {
        gwy_graph_curve_model_set_data_interleaved(gcmodel, (const gdouble*)xy, ndata);
        return;
    }

    gdouble *xbuf = NULL, *ybuf = NULL, *ibuf = NULL;
    if (abscissa_type == COORDINATE_INDEX || ordinate_type == COORDINATE_INDEX)
        ibuf = gwy_math_linspace(NULL, ndata, 0, 1);
    if (abscissa_type == COORDINATE_X || ordinate_type == COORDINATE_X) {
        xbuf = g_new(gdouble, ndata);
        for (gsize k = 0; k < ndata; k++)
            xbuf[k] = xy[k].x;
    }
    if (abscissa_type == COORDINATE_Y || ordinate_type == COORDINATE_Y) {
        ybuf = g_new(gdouble, ndata);
        for (gsize k = 0; k < ndata; k++)
            ybuf[k] = xy[k].y;
    }

    const gdouble *xdata = get_data(scatter, abscissa_type, iabscissa, ibuf, xbuf, ybuf);
    const gdouble *ydata = get_data(scatter, ordinate_type, iordinate, ibuf, xbuf, ybuf);
    g_assert(xdata && ydata);
    gwy_graph_curve_model_set_data(gcmodel, xdata, ydata, ndata);

    g_free(xbuf);
    g_free(ybuf);
    g_free(ibuf);
}

static GwyUnit*
get_unit(GwyScatter *scatter, CoordinateType coordtype, gint idata)
{
    if (coordtype == COORDINATE_INDEX)
        return NULL;
    if (coordtype == COORDINATE_X || coordtype == COORDINATE_Y)
        return gwy_scatter_get_unit_xy(scatter);
    return gwy_scatter_get_unit_data(scatter, idata);
}

static const gchar*
get_label(GwyScatter *scatter, CoordinateType coordtype, gint idata)
{
    if (coordtype == COORDINATE_INDEX)
        return _("Index");
    if (coordtype == COORDINATE_X)
        return "x";
    if (coordtype == COORDINATE_Y)
        return "y";
    const gchar *label = gwy_scatter_get_data_label(scatter, idata);
    return label ? label : _("Untitled");
}

static void
update_graph_model_props(ModuleArgs *args)
{
    GwyScatter *scatter = args->scatter;
    GwyParams *params = args->params;
    GwyGraphModel *gmodel = args->result;
    CoordinateType abscissa_type = gwy_params_get_enum(params, PARAM_ABSCISSA);
    CoordinateType ordinate_type = gwy_params_get_enum(params, PARAM_ORDINATE);
    gint iabscissa = gwy_params_get_int(params, PARAM_ABSCISSA_DATA);
    gint iordinate = gwy_params_get_int(params, PARAM_ORDINATE_DATA);
    GwyUnit *xunit = get_unit(scatter, abscissa_type, iabscissa);
    GwyUnit *yunit = get_unit(scatter, ordinate_type, iordinate);

    if (xunit)
        gwy_unit_assign(gwy_graph_model_get_unit_x(gmodel), xunit);
    else
        gwy_unit_clear(gwy_graph_model_get_unit_x(gmodel));

    if (yunit)
        gwy_unit_assign(gwy_graph_model_get_unit_y(gmodel), yunit);
    else
        gwy_unit_clear(gwy_graph_model_get_unit_y(gmodel));

    g_object_set(gmodel,
                 "axis-label-bottom", get_label(scatter, abscissa_type, iabscissa),
                 "axis-label-left", get_label(scatter, ordinate_type, iordinate),
                 NULL);
}

static void
sanitise_params(ModuleArgs *args)
{
    GwyParams *params = args->params;
    gint nvalues = gwy_scatter_get_n_values(args->scatter);

    if (gwy_params_get_enum(params, PARAM_ABSCISSA) == COORDINATE_VALUE
        && gwy_params_get_int(params, PARAM_ABSCISSA_DATA) >= nvalues)
        gwy_params_set_enum(params, PARAM_ABSCISSA, COORDINATE_INDEX);

    if (gwy_params_get_enum(params, PARAM_ORDINATE) == COORDINATE_VALUE
        && gwy_params_get_int(params, PARAM_ORDINATE_DATA) >= nvalues)
        gwy_params_set_enum(params, PARAM_ORDINATE, COORDINATE_X);
}

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