#pragma once
/*
 *  $Id: graph-internal.h 30146 2026-06-25 12:51:18Z yeti-dn $
 *  Copyright (C) 2024 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.
 */

/*< private_header >*/

#ifndef __GWYUI_GRAPH_INTERNAL_H__
#define __GWYUI_GRAPH_INTERNAL_H__

#include "libgwyui/graph-curve-model.h"
#include "libgwyui/graph-model.h"
#include "libgwyui/graph-utils.h"
#include "libgwyui/graph-area.h"
#include "libgwyui/graph-axis.h"
#include "libgwyui/cairo-utils.h"

G_BEGIN_DECLS

/* Dialog combo model columns, used across utility widgets. */
enum {
    GWY_GRAPH_COMBO_COLUMN_VALUE,
    GWY_GRAPH_COMBO_COLUMN_NAME,
    GWY_GRAPH_COMBO_COLUMN_PIXBUF
};

typedef enum {
    GWY_GRAPH_CURVE_MODEL_CACHE_XMIN = 0,
    GWY_GRAPH_CURVE_MODEL_CACHE_XMAX,
    GWY_GRAPH_CURVE_MODEL_CACHE_YMIN,
    GWY_GRAPH_CURVE_MODEL_CACHE_YMAX,
    /* X values (x > 0) */
    GWY_GRAPH_CURVE_MODEL_CACHE_XMIN_XPOS,
    /* Y values (x > 0) */
    GWY_GRAPH_CURVE_MODEL_CACHE_YMIN_XPOS,
    /* Absolute y values (y != 0) */
    GWY_GRAPH_CURVE_MODEL_CACHE_YAMIN,
    /* Absolute y values (y != 0, x > 0) */
    GWY_GRAPH_CURVE_MODEL_CACHE_YAMIN_XPOS,
    GWY_GRAPH_CURVE_MODEL_CACHE_SIZE,
} GwyGraphCurveModelCached;

struct _GwyGraphCurveModelPrivate {
    /* data */
    gint n;
    gdouble *xdata;
    gdouble *ydata;

    /* range cache */
    guint32 cached;
    gdouble cache[GWY_GRAPH_CURVE_MODEL_CACHE_SIZE];
    gint order;

    GString *description;
    GwyRGBA color;
    GwyGraphCurveMode mode;
    GwySymbolType point_type;
    GwyLineStyle line_style;
    gdouble point_size;
    gdouble line_width;
};

struct _GwyGraphModelPrivate {
    GPtrArray *curves;
    GArray *curveaux;

    gchar *title;
    GwyGraphGridType grid_type;

    gdouble x_min;
    gdouble x_max;
    gdouble y_min;
    gdouble y_max;

    gboolean x_min_set;
    gboolean x_max_set;
    gboolean y_min_set;
    gboolean y_max_set;

    GwyUnit *x_unit;
    GwyUnit *y_unit;
    gulong x_unit_changed_id;
    gulong y_unit_changed_id;

    gchar *top_label;
    gchar *bottom_label;
    gchar *left_label;
    gchar *right_label;
    GwyXY label_xy;

    /* like GwyGraphKeyParams */
    GwyGraphKeyPosition label_position;
    gboolean label_has_frame;
    gint label_frame_thickness;
    gboolean label_reverse;
    gboolean label_visible;

    /* logarithmic axes */
    gboolean x_is_logarithmic;
    gboolean y_is_logarithmic;
};

/* FIXME GTK3 consolidate. We need some general mapping functions (forward and backward) we can also use for color
 * axis and – in principle – rulers, even though they are going to be linear. Even adjustbars. We have too many
 * disparate mappings between screen coordinates and ranges of real numbers.
 *
 * FIXME: There is also difference between position, interval-start and interval-end mappings. Maybe. We might handle
 * everything as if the middle of the first pixel is the opening edge and the middle of the last pixel is the closing
 * edge of the interval. If it feels right. */

static inline gdouble
real2screen_x(GwyGraphActiveAreaSpecs *specs, gdouble coord)
{
    gdouble r0 = specs->real_xmin, range = specs->real_width;
    gdouble off = specs->area.x + 0.5, size = specs->area.width - 1.0;

    if (!specs->log_x)
        return off + (coord - r0)/range*size;

    if (coord <= 0.0)
        return G_MAXINT;    /* Force sticking out of the drawable area. FIXME: This is stupid. */

    return off + log(coord/r0)/log(1.0 + range/r0)*size;
}

static inline gdouble
real2screen_y(GwyGraphActiveAreaSpecs *specs, gdouble coord)
{
    gdouble r0 = specs->real_ymin, range = specs->real_height;
    gdouble off = specs->area.y + 0.5, size = specs->area.height - 1.0;

    if (!specs->log_y)
        return off + (1.0 - (coord - r0)/range)*size;

    if (coord <= 0.0)
        return G_MAXINT;    /* Force sticking out of the drawable area. FIXME: This is stupid. */

    return off + (1.0 - log(coord/r0)/log(1.0 + range/r0))*size;
}

static inline gdouble
screen2real_x(GwyGraphActiveAreaSpecs *specs, gdouble coord)
{
    gdouble r0 = specs->real_xmin, range = specs->real_width;
    /* FIXME: Should the +0.5 be also here? We most likely get screen coordinates as integers, not as pixel centres
     * (which we need for drawing in the real2screen transformation). */
    gdouble off = specs->area.x, size = specs->area.width - 1.0;

    coord = fmin(fmax(coord, 0.0), size);
    if (!specs->log_x)
        return r0 + (coord - off)/size*range;

    return r0*exp((coord - off)/size*log(1.0 + range/r0));
}

static inline gdouble
screen2real_y(GwyGraphActiveAreaSpecs *specs, gdouble coord)
{
    gdouble r0 = specs->real_ymin, range = specs->real_height;
    /* FIXME: Should the +0.5 be also here? We most likely get screen coordinates as integers, not as pixel centres
     * (which we need for drawing in the real2screen transformation). */
    gdouble off = specs->area.y, size = specs->area.height - 1.0;

    coord = fmin(fmax(size - coord, 0.0), size);
    if (!specs->log_y)
        return r0 + (coord - off)/size*range;

    return r0*exp((coord - off)/size*log(1.0 + range/r0));
}

G_GNUC_INTERNAL void _gwy_graph_axis_adjust(GwyGraphAxis *axis);

G_END_DECLS

#endif

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