/*
 *  $Id: correl-table.c 27953 2025-05-09 16:45:29Z yeti-dn $
 *  Copyright (C) 2025 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 <string.h>
#include <gtk/gtk.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/math.h"

#include "libgwyui/utils.h"
#include "libgwyui/correl-table.h"

/* Lower symmetric part indexing */
/* i MUST be greater or equal than j */
#define SL_index(i, j) ((i)*((i) + 1)/2 + (j))

typedef struct {
    GtkWidget *hname;
    GtkWidget *vname;
    gboolean fixed : 1;
} ParamInfo;

typedef struct {
    GtkWidget *label;
    gdouble correl;
} CorrelInfo;

struct _GwyCorrelTablePrivate {
    GArray *param_info;
    GArray *correl_info;
    GString *str;
    gdouble high_threshold;
    gdouble bad_threshold;
};

static void        finalize     (GObject *object);
static ParamInfo*  get_pinfo    (GwyCorrelTable *table,
                                 guint i);
static CorrelInfo* get_cinfo    (GwyCorrelTable *table,
                                 guint i,
                                 guint j);
static void        format_all   (GwyCorrelTable *table);
static void        format_correl(GwyCorrelTable *table,
                                 guint i,
                                 guint j);

static GtkGridClass *parent_class = NULL;

G_DEFINE_TYPE_WITH_CODE(GwyCorrelTable, gwy_correl_table, GTK_TYPE_GRID,
                        G_ADD_PRIVATE(GwyCorrelTable))

static void
gwy_correl_table_class_init(GwyCorrelTableClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);

    parent_class = gwy_correl_table_parent_class;

    gobject_class->finalize = finalize;
}

static void
gwy_correl_table_init(GwyCorrelTable *table)
{
    GwyCorrelTablePrivate *priv;

    gtk_grid_set_row_spacing(GTK_GRID(table), 2);
    gtk_grid_set_column_spacing(GTK_GRID(table), 6);

    priv = table->priv = gwy_correl_table_get_instance_private(table);
    priv->high_threshold = 0.9;
    priv->bad_threshold = 0.99;
    priv->str = g_string_new(NULL);
    priv->param_info = g_array_new(FALSE, FALSE, sizeof(ParamInfo));
    priv->correl_info = g_array_new(FALSE, FALSE, sizeof(CorrelInfo));
}

static void
finalize(GObject *object)
{
    GwyCorrelTable *table = GWY_CORREL_TABLE(object);
    GwyCorrelTablePrivate *priv = table->priv;

    gwy_correl_table_resize(table, 0);
    g_string_free(priv->str, TRUE);
    g_array_free(priv->param_info, TRUE);
    g_array_free(priv->correl_info, TRUE);

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

/**
 * gwy_correl_table_new:
 *
 * Creates a new fit correlation matrix table.
 *
 * Returns: New fit correlation matrix table widget.
 **/
GtkWidget*
gwy_correl_table_new(void)
{
    return gtk_widget_new(GWY_TYPE_CORREL_TABLE, NULL);
}

/**
 * gwy_correl_table_resize:
 * @table: A fit correlation matrix table.
 * @n: New number of parameters.
 *
 * Resizes a fit correlation matrix table.
 *
 * If the table shrinks then no further setup is necessary, in principle. If it expands, you should usually set row
 * labels, and possibly values. Typically, this function is used when the fit model changes, so all entries are set up
 * afterwards.
 *
 * The table contents is cleared.
 **/
void
gwy_correl_table_resize(GwyCorrelTable *table,
                        guint n)
{
    g_return_if_fail(GWY_IS_CORREL_TABLE(table));
    GwyCorrelTablePrivate *priv = table->priv;

    GArray *parinfo = priv->param_info;
    GArray *corinfo = priv->correl_info;
    guint size = parinfo->len;
    if (n == size) {
        gwy_correl_table_clear(table);
        return;
    }

    GtkGrid *grid = GTK_GRID(table);
    if (n < size) {
        for (guint i = size; i > n; i--) {
            gtk_grid_remove_column(grid, i);
            gtk_grid_remove_row(grid, i-1);
        }
        g_array_set_size(parinfo, n);
        g_array_set_size(corinfo, n*(n + 1)/2);
    }

    while (parinfo->len < n) {
        ParamInfo pinfo;

        guint i = parinfo->len;
        gtk_grid_insert_row(grid, i);

        pinfo.vname = gtk_label_new(NULL);
        gtk_label_set_xalign(GTK_LABEL(pinfo.vname), 1.0);
        gtk_widget_show(pinfo.vname);
        gtk_grid_attach(grid, pinfo.vname, 0, i, 1, 1);

        pinfo.hname = gtk_label_new(NULL);
        gtk_widget_show(pinfo.hname);
        gtk_grid_attach(grid, pinfo.hname, i+1, i+1, 1, 1);

        pinfo.fixed = FALSE;
        g_array_append_val(parinfo, pinfo);

        for (guint j = 0; j < parinfo->len; j++) {
            CorrelInfo cinfo;

            cinfo.label = gtk_label_new(NULL);
            gtk_widget_show(cinfo.label);
            gtk_label_set_xalign(GTK_LABEL(cinfo.label), 1.0);
            gtk_grid_attach(grid, cinfo.label, j+1, i, 1, 1);

            cinfo.correl = 0.0;
            g_array_append_val(corinfo, cinfo);
        }
    }

    gwy_correl_table_clear(table);
}

/**
 * gwy_correl_table_set_name:
 * @table: A fit correlation matrix table.
 * @i: Parameter index in the table.
 * @markup: Parameter name as valid Pango markup.
 *
 * Sets the name of a parameter in a fit correlation matrix table.
 **/
void
gwy_correl_table_set_name(GwyCorrelTable *table,
                          guint i,
                          const gchar *markup)
{
    ParamInfo *info;
    if (!(info = get_pinfo(table, i)))
        return;
    gtk_label_set_markup(GTK_LABEL(info->hname), markup);
    gtk_label_set_markup(GTK_LABEL(info->vname), markup);
}

/**
 * gwy_correl_table_set_tooltip:
 * @table: A fit correlation matrix table.
 * @i: Parameter index in the table.
 * @markup: Parameter tooltip as valid Pango markup.
 *
 * Sets the tooltip of a parameter in a fit correlation matrix table.
 **/
void
gwy_correl_table_set_tooltip(GwyCorrelTable *table,
                             guint i,
                             const gchar *markup)
{
    ParamInfo *info;
    if (!(info = get_pinfo(table, i)))
        return;

    gtk_widget_set_tooltip_markup(info->hname, markup);
    gtk_widget_set_tooltip_markup(info->vname, markup);
}

/**
 * gwy_correl_table_set_fixed:
 * @table: A fit correlation matrix table.
 * @i: Parameter index in the table.
 * @fixed: %TRUE if the parameter is fixed (and thus it cannot be correlated with anything).
 *
 * Sets whether a parameter in a fit correlation matrix table is fixed.
 *
 * Correlations are not displayed for fixed parameters, even when set.
 **/
void
gwy_correl_table_set_fixed(GwyCorrelTable *table,
                           guint i,
                           gboolean fixed)
{
    ParamInfo *info;
    if (!(info = get_pinfo(table, i)))
        return;

    if (!info->fixed == !fixed)
        return;

    info->fixed = fixed;
    format_all(table);
}

/**
 * gwy_correl_table_set_correl:
 * @table: A fit correlation matrix table.
 * @i: Parameter index in the table.
 * @j: Parameter index in the table.
 * @correl: The value of correlation between @i-th and @j-th parameters.
 *
 * Sets the value of a parameter correlation in a fit correlation matrix table.
 *
 * The table will display the value only if neither @i nor @j is fixed.
 **/
void
gwy_correl_table_set_correl(GwyCorrelTable *table,
                            guint i,
                            guint j,
                            gdouble correl)
{
    CorrelInfo *cinfo;
    if (!(cinfo = get_cinfo(table, i, j)))
        return;

    if (cinfo->correl != correl) {
        cinfo->correl = correl;
        format_correl(table, i, j);
    }
}

/**
 * gwy_correl_table_clear:
 * @table: A fit correlation matrix table.
 *
 * Clears all correlation values in a fit correlation matrix table.
 **/
void
gwy_correl_table_clear(GwyCorrelTable *table)
{
    g_return_if_fail(GWY_IS_CORREL_TABLE(table));
    GwyCorrelTablePrivate *priv = table->priv;

    GArray *corinfo = priv->correl_info;
    for (guint i = 0; i < corinfo->len; i++) {
        CorrelInfo *info = &g_array_index(corinfo, CorrelInfo, i);
        gtk_label_set_text(GTK_LABEL(info->label), NULL);
    }
}

/**
 * gwy_correl_table_set_high_threshold:
 * @table: A fit correlation matrix table.
 * @value: High correlation threshold.
 *
 * Sets the threshold for high correlations in a fit correlation matrix table.
 *
 * Values larger or equal to the threshold are styled as a warning. Set the threshold to a value larger to 1.0 to
 * disable it.
 *
 * See also gwy_correl_table_set_bad_threshold().
 **/
void
gwy_correl_table_set_high_threshold(GwyCorrelTable *table,
                                    gdouble value)
{
    g_return_if_fail(GWY_IS_CORREL_TABLE(table));
    GwyCorrelTablePrivate *priv = table->priv;

    if (priv->high_threshold != value) {
        priv->high_threshold = value;
        format_all(table);
    }
}

/**
 * gwy_correl_table_set_bad_threshold:
 * @table: A fit correlation matrix table.
 * @value: Bad correlation threshold.
 *
 * Sets the threshold for high correlations in a fit correlation matrix table.
 *
 * Values larger or equal to the threshold are styled as an error. Set the threshold to a value larger to 1.0 to
 * disable it.
 *
 * See also gwy_correl_table_set_high_threshold().
 **/
void
gwy_correl_table_set_bad_threshold(GwyCorrelTable *table,
                                   gdouble value)
{
    g_return_if_fail(GWY_IS_CORREL_TABLE(table));
    GwyCorrelTablePrivate *priv = table->priv;

    if (priv->bad_threshold != value) {
        priv->bad_threshold = value;
        format_all(table);
    }
}

static void
format_all(GwyCorrelTable *table)
{
    guint n = table->priv->param_info->len;
    for (guint i = 0; i < n; i++) {
        for (guint j = 0; j <= i; j++)
            format_correl(table, i, j);
    }
}

static void
fix_minus(GString *str)
{
    if (str->str[0] != '-')
        return;

    g_string_erase(str, 0, 1);
    g_string_insert(str, 0, "−");
}

static void
format_correl(GwyCorrelTable *table, guint i, guint j)
{
    GwyCorrelTablePrivate *priv = table->priv;
    GWY_ORDER(guint, j, i);
    ParamInfo *info_i = &g_array_index(priv->param_info, ParamInfo, i);
    ParamInfo *info_j = &g_array_index(priv->param_info, ParamInfo, j);
    guint k = SL_index(i, j);
    CorrelInfo *cinfo = &g_array_index(priv->correl_info, CorrelInfo, k);
    GtkMessageType msgtype = GTK_MESSAGE_INFO;
    gdouble xalign = 1.0;

    if (info_i->fixed || info_j->fixed) {
        g_string_assign(priv->str, "–");
        xalign = 0.5;
    }
    else if (i == j)
        g_string_assign(priv->str, "1.000");
    else {
        g_string_printf(priv->str, "%.3f", cinfo->correl);
        fix_minus(priv->str);
        if (fabs(cinfo->correl) >= priv->bad_threshold)
            msgtype = GTK_MESSAGE_ERROR;
        else if (fabs(cinfo->correl) >= priv->high_threshold)
            msgtype = GTK_MESSAGE_WARNING;
    }

    gwy_set_message_label(GTK_LABEL(cinfo->label), priv->str->str, msgtype, FALSE);
    gtk_label_set_xalign(GTK_LABEL(cinfo->label), xalign);
}

static ParamInfo*
get_pinfo(GwyCorrelTable *table, guint i)
{
    g_return_val_if_fail(GWY_IS_CORREL_TABLE(table), NULL);
    GwyCorrelTablePrivate *priv = table->priv;
    GArray *parinfo = priv->param_info;
    g_return_val_if_fail(i < parinfo->len, NULL);
    return &g_array_index(parinfo, ParamInfo, i);
}

static CorrelInfo*
get_cinfo(GwyCorrelTable *table, guint i, guint j)
{
    g_return_val_if_fail(GWY_IS_CORREL_TABLE(table), NULL);
    GwyCorrelTablePrivate *priv = table->priv;
    GWY_ORDER(guint, j, i);
    GArray *corinfo = priv->correl_info;
    g_return_val_if_fail(i < corinfo->len, NULL);
    g_return_val_if_fail(j < corinfo->len, NULL);
    guint k = SL_index(i, j);
    return &g_array_index(corinfo, CorrelInfo, k);
}

/**
 * SECTION:correl-table
 * @title: GwyCorrelTable
 * @short_description: Table with fit correlation matrix
 *
 * #GwyCorrelTable displays parameter correlation matrix, usually obtained as the result of least squares fitting.
 **/

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