/*
 *  $Id: grainmeasure.c 30056 2026-06-12 16:20:11Z yeti-dn $
 *  Copyright (C) 2007-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 <stdarg.h>
#include <gtk/gtk.h>
#include <gwy.h>

enum {
    PARAM_EXPANDED,
};

#define GWY_TYPE_TOOL_GRAIN_MEASURE            (gwy_tool_grain_measure_get_type())
#define GWY_TOOL_GRAIN_MEASURE(obj)            (G_TYPE_CHECK_INSTANCE_CAST((obj), GWY_TYPE_TOOL_GRAIN_MEASURE, GwyToolGrainMeasure))
#define GWY_IS_TOOL_GRAIN_MEASURE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE((obj), GWY_TYPE_TOOL_GRAIN_MEASURE))
#define GWY_TOOL_GRAIN_MEASURE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS((obj), GWY_TYPE_TOOL_GRAIN_MEASURE, GwyToolGrainMeasureClass))

typedef struct _GwyToolGrainMeasure      GwyToolGrainMeasure;
typedef struct _GwyToolGrainMeasureClass GwyToolGrainMeasureClass;

struct _GwyToolGrainMeasure {
    GwyPlainTool parent_instance;

    GwyParams *params;

    GtkTreeView *treeview;
    GPtrArray *values;
    gint value_col;
    gint ngrains;
    gint *grains;
    gint gno;

    gboolean same_units;
    GwyUnit *unit;
    GwyValueFormat *vf;
};

struct _GwyToolGrainMeasureClass {
    GwyPlainToolClass parent_class;
};

static gboolean     module_register                         (void);
static GwyParamDef* define_module_params                    (void);
static GType        gwy_tool_grain_measure_get_type         (void)                       G_GNUC_CONST;
static void         gwy_tool_grain_measure_finalize         (GObject *object);
static void         gwy_tool_grain_measure_init_dialog      (GwyToolGrainMeasure *tool);
static void         gwy_tool_grain_measure_data_switched    (GwyTool *gwytool,
                                                             GwyDataView *data_view);
static void         gwy_tool_grain_measure_data_changed     (GwyPlainTool *plain_tool);
static void         gwy_tool_grain_measure_mask_changed     (GwyPlainTool *plain_tool);
static void         gwy_tool_grain_measure_selection_changed(GwyPlainTool *plain_tool,
                                                             gint hint);
static void         invalidate                              (GwyToolGrainMeasure *tool);
static void         update_units                            (GwyToolGrainMeasure *tool);
static void         recalculate                             (GwyToolGrainMeasure *tool);
static void         group_expanded_collapsed                (GwyToolGrainMeasure *tool);

static GwyModuleInfo module_info = {
    GWY_MODULE_ABI_VERSION,
    &module_register,
    N_("Grain measurement tool, calculates characteristics of selected continuous parts of mask."),
    "Yeti <yeti@gwyddion.net>",
    "2.1",
    "David Nečas (Yeti)",
    "2007",
};

GWY_MODULE_QUERY2(module_info, grainmeasure)

G_DEFINE_TYPE(GwyToolGrainMeasure, gwy_tool_grain_measure, GWY_TYPE_PLAIN_TOOL)

static gboolean
module_register(void)
{
    gwy_tool_func_register(GWY_TYPE_TOOL_GRAIN_MEASURE);

    return TRUE;
}

static GwyParamDef*
define_module_params(void)
{
    static GwyParamDef *paramdef = NULL;

    if (paramdef)
        return paramdef;

    paramdef = gwy_param_def_new();
    gwy_param_def_set_function_name(paramdef, "grainmeasure");
    gwy_param_def_add_grain_groups(paramdef, PARAM_EXPANDED, "expanded", NULL, 0);
    return paramdef;
}

static void
gwy_tool_grain_measure_class_init(GwyToolGrainMeasureClass *klass)
{
    GwyPlainToolClass *ptool_class = GWY_PLAIN_TOOL_CLASS(klass);
    GwyToolClass *tool_class = GWY_TOOL_CLASS(klass);
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);

    gobject_class->finalize = gwy_tool_grain_measure_finalize;

    tool_class->icon_name = GWY_ICON_GRAINS_MEASURE;
    tool_class->title = _("Grain Measure");
    tool_class->tooltip = _("Measure individual grains (continuous parts of mask)");
    tool_class->prefix = "/module/grainmeasure";
    tool_class->data_switched = gwy_tool_grain_measure_data_switched;
    tool_class->default_width = 240;
    tool_class->default_height = 400;

    ptool_class->data_changed = gwy_tool_grain_measure_data_changed;
    ptool_class->mask_changed = gwy_tool_grain_measure_mask_changed;
    ptool_class->selection_changed = gwy_tool_grain_measure_selection_changed;
}

static void
gwy_tool_grain_measure_finalize(GObject *object)
{
    GwyToolGrainMeasure *tool = GWY_TOOL_GRAIN_MEASURE(object);
    guint i;

    gwy_params_save_to_settings(tool->params);
    g_clear_object(&tool->params);
    g_clear_object(&tool->unit);
    if (tool->values) {
        for (i = 0; i < tool->values->len; i++)
            g_free(tool->values->pdata[i]);
        g_ptr_array_free(tool->values, TRUE);
    }
    GWY_FREE_VALUE_FORMAT(tool->vf);

    G_OBJECT_CLASS(gwy_tool_grain_measure_parent_class)->finalize(object);
}

static void
gwy_tool_grain_measure_init(GwyToolGrainMeasure *tool)
{
    GwyPlainTool *plain_tool = GWY_PLAIN_TOOL(tool);

    tool->params = gwy_params_new_from_settings(define_module_params());
    tool->unit = gwy_unit_new(NULL);

    plain_tool->lazy_updates = TRUE;
    gwy_plain_tool_connect_selection(plain_tool, GWY_TYPE_LAYER_POINT, "pointer");

    gwy_tool_grain_measure_init_dialog(tool);
}

static void
render_value(G_GNUC_UNUSED GtkTreeViewColumn *column,
             GtkCellRenderer *renderer,
             GtkTreeModel *model,
             GtkTreeIter *iter,
             gpointer user_data)
{
    GwyToolGrainMeasure *tool = (GwyToolGrainMeasure*)user_data;
    GwyGrainValue *gvalue = NULL;
    gchar buf[64];

    gtk_tree_model_get(model, iter, GWY_GRAIN_STORE_COLUMN_ITEM, &gvalue, -1);
    if (tool->gno <= 0 || !gvalue) {
        g_object_set(renderer, "text", "", NULL);
        g_clear_object(&gvalue);
        return;
    }

    g_object_unref(gvalue);
    if (!tool->same_units && (gwy_grain_value_get_flags(gvalue) & GWY_GRAIN_SAME_UNITS)) {
        g_object_set(renderer, "text", _("N.A."), NULL);
        return;
    }

    GwyGrainQuantity quantity = gwy_grain_value_get_quantity(gvalue);
    const gchar *name = gwy_resource_get_name(GWY_RESOURCE(gvalue));
    gint i = gwy_inventory_get_item_position(gwy_grain_values(), name);
    if (i < 0) {
        g_warning("Grain value not present in inventory.");
        g_object_set(renderer, "text", "", NULL);
        return;
    }

    const gdouble *values = g_ptr_array_index(tool->values, i);
    gdouble value = values[tool->gno];

    if (gwy_grain_value_get_flags(gvalue) & GWY_GRAIN_IS_ANGLE) {
        g_snprintf(buf, sizeof(buf), "%.1f deg", gwy_rad2deg(value));
        g_object_set(renderer, "text", buf, NULL);
    }
    else if (quantity == GWY_GRAIN_PIXEL_AREA || quantity == GWY_GRAIN_ID) {
        g_snprintf(buf, sizeof(buf), "%.0f", value);
        g_object_set(renderer, "text", buf, NULL);
    }
    else {
        GwyUnit *unitxy, *unitz;
        GwyField *field;
        GwyUnitFormatStyle style = GWY_UNIT_FORMAT_VFMARKUP;

        field = GWY_PLAIN_TOOL(tool)->field;
        unitxy = gwy_field_get_unit_xy(field);
        unitz = gwy_field_get_unit_z(field);
        gwy_unit_power_multiply(tool->unit,
                                unitxy, gwy_grain_value_get_power_xy(gvalue),
                                unitz, gwy_grain_value_get_power_z(gvalue));
        tool->vf = gwy_unit_get_format_with_digits(tool->unit, style, value, 3, tool->vf);
        g_snprintf(buf, sizeof(buf), "%.*f%s%s",
                   tool->vf->precision, value/tool->vf->magnitude,
                   *tool->vf->units ? " " : "", tool->vf->units);
        g_object_set(renderer, "markup", buf, NULL);
    }
}

static void
gwy_tool_grain_measure_init_dialog(GwyToolGrainMeasure *tool)
{
    GtkDialog *dialog = GTK_DIALOG(GWY_TOOL(tool)->dialog);
    GtkWidget *treeview, *scwin;
    GtkTreeSelection *selection;
    GtkTreeViewColumn *column;
    GtkCellRenderer *renderer;

    scwin = gtk_scrolled_window_new(NULL, NULL);
    gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scwin), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
    gtk_box_pack_start(GTK_BOX(gtk_dialog_get_content_area(dialog)), scwin, TRUE, TRUE, 0);

    treeview = gwy_grain_value_tree_view_new(TRUE, "name", "symbol_markup", NULL);
    tool->treeview = GTK_TREE_VIEW(treeview);
    gtk_tree_view_set_headers_visible(tool->treeview, FALSE);
    gtk_container_add(GTK_CONTAINER(scwin), treeview);

    column = gtk_tree_view_column_new();
    gtk_tree_view_column_set_sizing(column, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
    gtk_tree_view_append_column(tool->treeview, column);
    renderer = gtk_cell_renderer_text_new();
    g_object_set(renderer, "xalign", 1.0, NULL);
    gtk_tree_view_column_pack_start(column, renderer, TRUE);
    gtk_tree_view_column_set_cell_data_func(column, renderer, render_value, tool, NULL);

    selection = gtk_tree_view_get_selection(tool->treeview);
    gtk_tree_selection_set_mode(selection, GTK_SELECTION_NONE);
    g_signal_connect_swapped(tool->treeview, "row-expanded", G_CALLBACK(group_expanded_collapsed), tool);
    g_signal_connect_swapped(tool->treeview, "row-collapsed", G_CALLBACK(group_expanded_collapsed), tool);

    gwy_grain_value_tree_view_set_expanded_groups(tool->treeview, gwy_params_get_flags(tool->params, PARAM_EXPANDED));

    gwy_plain_tool_add_clear_button(GWY_PLAIN_TOOL(tool));
    gwy_tool_add_hide_button(GWY_TOOL(tool), TRUE);
    gwy_help_add_to_tool_dialog(dialog, GWY_TOOL(tool), GWY_HELP_DEFAULT);

    gtk_widget_show_all(gtk_dialog_get_content_area(dialog));
}

static void
gwy_tool_grain_measure_data_switched(GwyTool *gwytool,
                                     GwyDataView *data_view)
{
    GwyPlainTool *plain_tool = GWY_PLAIN_TOOL(gwytool);
    GwyToolGrainMeasure *tool = GWY_TOOL_GRAIN_MEASURE(gwytool);
    gboolean ignore = (data_view == plain_tool->data_view);

    if (!ignore)
        invalidate(tool);

    GWY_TOOL_CLASS(gwy_tool_grain_measure_parent_class)->data_switched(gwytool, data_view);

    if (ignore || plain_tool->init_failed)
        return;

    if (data_view) {
        gwy_object_set_or_reset(plain_tool->layer, GWY_TYPE_LAYER_POINT,
                                "editable", TRUE,
                                "focus", -1,
                                NULL);
        gwy_selection_set_max_objects(plain_tool->selection, 1);
        update_units(tool);
    }
}

static void
gwy_tool_grain_measure_data_changed(GwyPlainTool *plain_tool)
{
    invalidate(GWY_TOOL_GRAIN_MEASURE(plain_tool));
    gwy_tool_grain_measure_selection_changed(plain_tool, -1);
}

static void
gwy_tool_grain_measure_mask_changed(GwyPlainTool *plain_tool)
{
    invalidate(GWY_TOOL_GRAIN_MEASURE(plain_tool));
    gwy_tool_grain_measure_selection_changed(plain_tool, -1);
}

static gboolean
emit_row_changed(GtkTreeModel *model,
                 GtkTreePath *path,
                 GtkTreeIter *iter,
                 G_GNUC_UNUSED gpointer user_data)
{
    gtk_tree_model_row_changed(model, path, iter);
    return FALSE;
}

static void
gwy_tool_grain_measure_selection_changed(GwyPlainTool *plain_tool,
                                         gint hint)
{
    GwyToolGrainMeasure *tool = GWY_TOOL_GRAIN_MEASURE(plain_tool);
    gint col, row, oldgno = tool->gno;
    gdouble point[2];

    g_return_if_fail(hint <= 0);

    if (!plain_tool->field
        || !plain_tool->mask_field
        || !gwy_selection_get_object(plain_tool->selection, 0, point))
        tool->gno = 0;
    else {
        row = floor(gwy_field_rtoi(plain_tool->field, point[1]));
        col = floor(gwy_field_rtoj(plain_tool->field, point[0]));
        // FIXME: Why are we recalculating all grain values when only the selection changes?
        if ((tool->gno = gwy_nield_get_val(plain_tool->mask_field, col, row)) > 0)
            recalculate(tool);
    }

    if (tool->gno != oldgno) {
        GtkTreeModel *model;

        model = gtk_tree_view_get_model(tool->treeview);
        gtk_tree_model_foreach(model, emit_row_changed, NULL);
    }
}

static void
group_expanded_collapsed(GwyToolGrainMeasure *tool)
{
    gwy_params_set_flags(tool->params, PARAM_EXPANDED, gwy_grain_value_tree_view_get_expanded_groups(tool->treeview));
}

static void
invalidate(GwyToolGrainMeasure *tool)
{
    tool->ngrains = 0;
    tool->gno = -1;
}

static void
update_units(GwyToolGrainMeasure *tool)
{
    GwyField *field = GWY_PLAIN_TOOL(tool)->field;
    GwyUnit *unitxy = gwy_field_get_unit_xy(field);
    GwyUnit *unitz = gwy_field_get_unit_z(field);

    tool->same_units = gwy_unit_equal(unitxy, unitz);
}

static void
recalculate(GwyToolGrainMeasure *tool)
{
    GwyPlainTool *plain_tool = GWY_PLAIN_TOOL(tool);
    GwyField *field = plain_tool->field;
    GwyNield *grains = plain_tool->mask_field;

    tool->ngrains = gwy_nield_max(grains);

    GwyInventory *inventory = gwy_grain_values();
    guint n = gwy_inventory_get_n_items(inventory);

    if (!tool->values) {
        tool->values = g_ptr_array_new();
        g_ptr_array_set_size(tool->values, n);
    }

    GwyGrainValue **gvalues = g_new(GwyGrainValue*, n);
    for (guint i = 0; i < n; i++) {
        gvalues[i] = gwy_inventory_get_nth_item(inventory, i);
        g_ptr_array_index(tool->values, i) = g_renew(gdouble, g_ptr_array_index(tool->values, i), tool->ngrains+1);
    }

    gwy_nield_grain_values(grains, field, gvalues, n, (gdouble**)tool->values->pdata);
    g_free(gvalues);
}

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