#pragma once
/*
 *  $Id: internal.h 30062 2026-06-15 08:57:28Z yeti-dn $
 *  Copyright (C) 2003-2026 David Necas (Yeti), Petr Klapetek.
 *  E-mail: yeti@gwyddion.net, klapetek@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 __GWY_INTERNAL_H__
#define __GWY_INTERNAL_H__

#include "libgwyddion/enums.h"
#include "libgwyddion/field.h"
#include "libgwyddion/nield.h"
#include "libgwyddion/brick.h"
#include "libgwyddion/scatter.h"
#include "libgwyddion/grains.h"
#include "libgwyddion/arithmetic.h"
#include "libgwyddion/distance-transform.h"
#include "libgwyddion/grain-quantities.h"
#include "libgwyddion/gwynlfitpreset.h"

G_BEGIN_DECLS

#define float_to_hex(c) ((guint8)((c)*255.9999999))

enum {
    N_GRAIN_QUANTITIES = 51
};

typedef void (*GwyNLFitGuessFunc)(gint n_dat,
                                  const gdouble *x,
                                  const gdouble *y,
                                  gdouble *param,
                                  gboolean *fres);

typedef GwyUnit* (*GwyNLFitGetUnitFunc)(GwyNLFitPreset *preset,
                                        guint param,
                                        GwyUnit *siunit_x,
                                        GwyUnit *siunit_y);

typedef void (*GwyNLFitWeightFunc)(gint n_dat,
                                   const gdouble *x,
                                   const gdouble *y,
                                   gdouble *weight);

typedef gdouble (*Filter3x3Func)(const gdouble *prev,
                                 const gdouble *row,
                                 const gdouble *next);
typedef gdouble (*Filter5x5Func)(const gdouble *prev2,
                                 const gdouble *prev,
                                 const gdouble *row,
                                 const gdouble *next,
                                 const gdouble *next2);

typedef struct {
    const char *name;
    gint power_x;
    gint power_y;
} GwyNLFitParam;

struct _GwyNLFitPresetBuiltin {
    const gchar *name;
    const gchar *formula;
    GwyNLFitFunc function;
    GwyNLFitDerFunc diff;
    GwyNLFitGuessFunc guess;
    GwyNLFitGetUnitFunc get_unit;
    GwyNLFitWeightFunc set_default_weights;
    guint nparams;
    const GwyNLFitParam *param;
};

/* Cache operations */
#define FCVAL(field, b)  ((field)->priv->cache[GWY_FIELD_CACHE_##b])
#define FCBIT(b)         (1 << GWY_FIELD_CACHE_##b)
#define FCTEST(field, b) ((field)->priv->cached & FCBIT(b))

#define NCVAL(nield, b)  ((nield)->priv->cache[GWY_NIELD_CACHE_##b])
#define NCBIT(b)         (1 << GWY_NIELD_CACHE_##b)
#define NCTEST(nield, b) ((nield)->priv->cached & NCBIT(b))

typedef enum {
    GWY_FIELD_CACHE_MIN = 0,
    GWY_FIELD_CACHE_MAX,
    GWY_FIELD_CACHE_SUM,
    GWY_FIELD_CACHE_RMS,
    GWY_FIELD_CACHE_MED,
    GWY_FIELD_CACHE_ARF,
    GWY_FIELD_CACHE_ART,
    GWY_FIELD_CACHE_ARE,
    GWY_FIELD_CACHE_VAR,
    GWY_FIELD_CACHE_ENT,
    GWY_FIELD_CACHE_MSQ,
    GWY_FIELD_CACHE_size
} GwyFieldCached;

typedef enum {
    GWY_NIELD_CACHE_MAX = 0,
    GWY_NIELD_CACHE_COUNT,
    GWY_NIELD_CACHE_size,
    GWY_NIELD_CACHE_IS_NUM_CONTIGUOUS = GWY_NIELD_CACHE_size,
    GWY_NIELD_CACHE_SIZES,
    GWY_NIELD_CACHE_BBOXES,
} GwyNieldCached;

#define GWY_NIELD_CACHE_scalar ((1u << (GWY_NIELD_CACHE_IS_NUM_CONTIGUOUS + 1)) - 1)

typedef void (*RowExtendFunc)(const gdouble *in,
                              gdouble *out,
                              guint pos,
                              guint width,
                              guint res,
                              guint extend_left,
                              guint extend_right,
                              gdouble value);

typedef void (*RectExtendFunc)(const gdouble *in,
                               guint inrowstride,
                               gdouble *out,
                               guint outrowstride,
                               guint xpos,
                               guint ypos,
                               guint width,
                               guint height,
                               guint xres,
                               guint yres,
                               guint extend_left,
                               guint extend_right,
                               guint extend_up,
                               guint extend_down,
                               gdouble value);

/**
 * GwyFieldCornersFunc:
 * @zul: Upper-left value.
 * @zur: Upper-right value.
 * @zlr: Lower-right value.
 * @zll: Lower-left value.
 * @wul: Upper-left weight (0 or 1).
 * @wur: Upper-right weight (0 or 1).
 * @wlr: Lower-right weight (0 or 1).
 * @wll: Lower-left weight (0 or 1).
 * @param: Parameters passed to the master function.
 *
 * Type of function used in summing fields by quarters, corners, double-rows or in a similar fashion.
 *
 * Note the values and weights are passed in a clock-wise order around the square starting from the top-left corner.
 * This means that successive values share a side of the square but the order is different from a 2×2 field.
 *
 * Returns: Additive value for the given weighted corner. It is assumed the function would return zero if all four
 *          weights were zero.
 **/
typedef gdouble (*GwyFieldSumCornersFunc)(gdouble zul,
                                          gdouble zur,
                                          gdouble zlr,
                                          gdouble zll,
                                          guint wul,
                                          guint wur,
                                          guint wlr,
                                          guint wll,
                                          const gdouble *param);

/**
 * GwyFieldAllCornersFunc:
 * @zul: Upper-left value.
 * @zur: Upper-right value.
 * @zlr: Lower-right value.
 * @zll: Lower-left value.
 * @param: Parameters passed to the master function.
 *
 * Type of function used in summing fields by quarters, corners, double-rows or in a similar fashion with all four
 * included.
 *
 * This is a simpler variant #GwyFieldCornersFunc used in the case of all four quarters are included.  Therefore, no
 * weights need to be passed.
 *
 * Returns: Additiove value for the given corner.
 **/
typedef gdouble (*GwyFieldSumAllCornersFunc)(gdouble zul,
                                             gdouble zur,
                                             gdouble zlr,
                                             gdouble zll,
                                             const gdouble *param);

typedef struct {
    guint col;
    guint row;
    guint width;
    guint height;
} GwyFieldPart;

typedef struct {
    gint i;
    gint j;
} GridPoint;

typedef struct {
    guint size;
    guint len;
    GridPoint *points;
} PixelQueue;

struct _GwyLinePrivate {
    gdouble *data;

    GwyUnit *unit_x;
    GwyUnit *unit_y;
};

struct _GwyFieldPrivate {
    gdouble *data;

    GwyUnit *unit_xy;
    GwyUnit *unit_z;

    guint32 cached;
    gdouble cache[GWY_FIELD_CACHE_size];
};

struct _GwyNieldPrivate {
    gint *data;

    /* Cache. */
    GArray *sizes;
    GArray *bboxes;

    guint32 cached;
    gint cache[GWY_NIELD_CACHE_size];
};

struct _GwyBrickPrivate {
    gdouble *data;

    GwyUnit *unit_x;
    GwyUnit *unit_y;
    GwyUnit *unit_z;
    GwyUnit *unit_w;

    GwyLine *zcalibration;
};

struct _GwySurfacePrivate {
    GwyXYZ *data;
    GwyUnit *unit_xy;
    GwyUnit *unit_z;

    GwyXYZ min;
    GwyXYZ max;
    GChecksum *checksummer;
    guchar checksum[16];
    gboolean cached_ranges : 1;
    gboolean cached_checksum : 1;
};

struct _GwyScatterPrivate {
    GwyXY *xy;
    gdouble *data;

    GwyUnit *unit_xy;
    GwyUnit **units_data; /* nvalues, array always exists, but may contain NULL units */
    gchar **datalabels;   /* nvalues, array may not exist, but if it does, all strings exist too */

    GwyXY xymin;
    GwyXY xymax;
    gdouble *valuemin;
    gdouble *valuemax;
    gint nvalues;

    GChecksum *checksummer;
    guchar checksum[16];
    // TODO: More refined cached flags. We can modify individual channels separately, or modify only the coordinates.
    gboolean cached_ranges : 1;
    gboolean cached_checksum : 1;
};

struct _GwyLawnPrivate {
    GwyUnit *unit_xy;

    gint ncurves;
    gint *curvelengths;    /* (xres * yres) */
    gdouble **curvedata;   /* (xres * yres), each (ncurves * curvelengths[i]) */
    GwyUnit **units_curves; /* ncurves, array always exists, but may contain NULL units */
    gchar **curvelabels;   /* ncurves, array may not exist, but if it does, all strings exist too */

    gint nsegments;
    gint *segments;
    gchar **segmentlabels; /* nsegments, array may not exist, but if it does, all strings exist too */

    /* Temporary data for serialisation (we need to flatten some of the complicated structures). */
    gdouble *ser_curvedata;
};

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wanalyzer-null-dereference"
#ifdef __clang__
#pragma GCC diagnostic ignored "-Wnull-pointer-arithmetic"
#endif
static inline gint
masked_included(const gdouble *m, GwyMaskingType masking)
{
    if (masking == GWY_MASK_INCLUDE)
        return *m > 0.0;
    if (masking == GWY_MASK_EXCLUDE)
        return *m <= 0.0;
    /* Normally, we should not get here because there would be a code path not touching m at all. But if you are
     * careful, do not care about efficiency or the amount of other work per pixel makes this irrelevant, it is
     * possible to use this function also without a mask (NULL mask data) provided you pass masking=GWY_MASK_IGNORE.
     *
     * Anyway, compilers do not seem smart enough to understand the invariant (m != NULL || masking == GWY_MASK_IGNORE).
     * So we need to just disable the diagnostic here. */
    return 1;
}

static inline gint
nielded_included(const gint *m, GwyMaskingType masking)
{
    if (masking == GWY_MASK_INCLUDE)
        return *m > 0;
    if (masking == GWY_MASK_EXCLUDE)
        return *m <= 0;
    /* Often, we do not get here because there is a separate no-mask code path for efficiency. But we can, for example
     * when the amount of other work per pixel makes the mask check negligible. In such case, we must ensure at least
     * the invariant ‘!mask ⇒ ignore’.
     *
     * In such case m is typically an utterely invalid pointer, like NULL + something. Dereferencing it would be
     * undefined and all sorts of disasterous. The invariant ensures that we do no such thing.
     *
     * Compilers do not seem smart enough to understand the invariant ‘!mask ⇒ ignore and ignore ⇒ !mask’ we try to
     * always have, even when it is pretty clear (and sometimes it is inherited from the caller). So we need to just
     * disable the diagnostic here. */
    return 1;
}
#pragma GCC diagnostic pop

G_GNUC_INTERNAL void                 _gwy_copy_unit                    (GwyUnit *source,
                                                                        GwyUnit **dest);
G_GNUC_INTERNAL RowExtendFunc        _gwy_get_row_extend_func          (GwyExteriorType exterior);
G_GNUC_INTERNAL RectExtendFunc       _gwy_get_rect_extend_func         (GwyExteriorType exterior);
G_GNUC_INTERNAL gboolean             _gwy_line_check_part              (GwyLine *line,
                                                                        gint pos,
                                                                        gint len,
                                                                        gboolean allow_empty);
G_GNUC_INTERNAL gboolean             _gwy_field_check_area             (GwyField *field,
                                                                        gint col,
                                                                        gint row,
                                                                        gint width,
                                                                        gint height,
                                                                        gboolean allow_empty);
G_GNUC_INTERNAL gboolean             _gwy_field_check_other            (GwyField *field,
                                                                        GwyField *otherfield,
                                                                        gboolean nullok);
G_GNUC_INTERNAL gboolean             _gwy_field_check_mask             (GwyField *field,
                                                                        GwyNield **mask,
                                                                        GwyMaskingType *masking);
G_GNUC_INTERNAL gboolean             _gwy_nield_check_area             (GwyNield *nield,
                                                                        gint col,
                                                                        gint row,
                                                                        gint width,
                                                                        gint height,
                                                                        gboolean allow_empty);
G_GNUC_INTERNAL gboolean             _gwy_nield_check_mask             (GwyNield *nield,
                                                                        GwyNield **mask,
                                                                        GwyMaskingType *masking);
G_GNUC_INTERNAL gboolean             _gwy_nield_check_field            (GwyNield *nield,
                                                                        GwyField *field,
                                                                        gboolean nullok);
G_GNUC_INTERNAL gboolean             _gwy_nield_check_other            (GwyNield *nield,
                                                                        GwyNield *othernield,
                                                                        gboolean nullok);
G_GNUC_INTERNAL void                 _gwy_nield_fill_field_with_01     (GwyNield *nield,
                                                                        GwyField *field);
G_GNUC_INTERNAL const guint*         _gwy_nield_ensure_sizes           (GwyNield *nield);
G_GNUC_INTERNAL const gint*          _gwy_nield_ensure_bboxes          (GwyNield *nield);
G_GNUC_INTERNAL guint                _gwy_simple_dist_trans            (GwyNield *grains,
                                                                        GwyNield *distances,
                                                                        gboolean merged,
                                                                        gboolean from_border,
                                                                        GwyDistanceTransformType dtype,
                                                                        PixelQueue *inqueue,
                                                                        PixelQueue *outqueue);
G_GNUC_INTERNAL void                 _gwy_tune_rank_filter_method      (const gchar *value);
G_GNUC_INTERNAL void                 _gwy_field_area_filter_5x5        (GwyField *field,
                                                                        GwyField *target,
                                                                        GwyNield *mask,
                                                                        GwyMaskingType masking,
                                                                        gint col,
                                                                        gint row,
                                                                        gint width,
                                                                        gint height,
                                                                        const gdouble *kernel,
                                                                        Filter5x5Func filterfunc);
G_GNUC_INTERNAL GwyDataMismatchFlags _gwy_scatter_check_xy_incompatible(GwyScatter *scatter,
                                                                        GwyScatter *otherscatter);

/* Symmetrically means that
 * - for even @extsize-@size it holds @extend_begining=@extend_end
 *  -for an odd difference it holds @extend_begining+1=@extend_end, i.e. it's extended one pixel more at the end. */
G_GNUC_UNUSED
static void
make_symmetrical_extension(guint size, guint extsize,
                           guint *extend_begining, guint *extend_end)
{
    guint extend = extsize - size;

    *extend_begining = extend/2;
    *extend_end = extend - *extend_begining;
}

G_GNUC_UNUSED
static inline void
extend_kernel_row(const gdouble *kernel, guint klen,
                  gdouble *extended, guint size)
{
    guint llen = klen/2, rlen = klen - llen;
    gwy_assign(extended, kernel + llen, rlen);
    gwy_clear(extended + rlen, size - klen);
    gwy_assign(extended + size - llen, kernel, llen);
}

G_GNUC_UNUSED
static void
extend_kernel_rect(const gdouble *kernel,
                   guint kxlen, guint kylen,
                   gdouble *extended,
                   guint xsize, guint ysize, guint rowstride)
{
    guint ulen = kylen/2, dlen = kylen - ulen;
    guint i;

    for (i = 0; i < dlen; i++)
        extend_kernel_row(kernel + (i + ulen)*kxlen, kxlen, extended + i*rowstride, xsize);
    gwy_clear(extended + dlen*rowstride, (ysize - kylen)*rowstride);
    for (i = 0; i < ulen; i++)
        extend_kernel_row(kernel + i*kxlen, kxlen, extended + (ysize - ulen + i)*rowstride, xsize);
}

#define unit_pointer_if_nonempty(unit) \
    (((unit) && !gwy_unit_equal_string((unit), NULL)) ? &(unit) : NULL)

/* Raw, non-checking variants for the derivatives. */
G_GNUC_UNUSED
static inline gdouble
_gwy_field_xder(GwyField *dfield, gint col, gint row)
{
    gint xres = dfield->xres;
    gdouble *p = dfield->priv->data + row*xres + col;
    gdouble dx = dfield->xreal/xres;

    if (col == 0)
        return (*(p+1) - *p)/dx;
    if (col == xres-1)
        return (*p - *(p-1))/dx;
    return (*(p+1) - *(p-1))/dx/2;
}

G_GNUC_UNUSED
static inline gdouble
_gwy_field_yder(GwyField *dfield, gint col, gint row)
{
    gint xres = dfield->xres, yres = dfield->yres;
    gdouble *p = dfield->priv->data + row*xres + col;
    gdouble dy = dfield->yreal/yres;

    if (row == 0)
        return (*p - *(p+xres))/dy;
    if (row == yres-1)
        return (*(p-xres) - *p)/dy;
    return (*(p-xres) - *(p+xres))/dy/2;
}

G_GNUC_UNUSED
static inline void
invert_array_in_place(gdouble *d, guint n)
{
    gdouble *e = d + n-1;

    n /= 2;
    while (n--) {
        GWY_SWAP(gdouble, *d, *e);
        d++;
        e--;
    }
}

/* Merge grains i and j in map with full backward resolution to the lowest number. */
G_GNUC_UNUSED
static inline void
resolve_grain_map(gssize *m, gint i, gint j)
{
    gint ii, jj, k;

    /* Find what i and j fully resolve to */
    for (ii = i; m[ii] != ii; ii = m[ii])
        ;
    for (jj = j; m[jj] != jj; jj = m[jj])
        ;
    k = MIN(ii, jj);

    /* Fix partial resultions to full */
    for (ii = m[i]; m[ii] != ii; ii = m[ii]) {
        m[i] = k;
        i = ii;
    }
    m[ii] = k;
    for (jj = m[j]; m[jj] != jj; jj = m[jj]) {
        m[j] = k;
        j = jj;
    }
    m[jj] = k;
}

G_GNUC_UNUSED
static inline guint
dist_points_for_n_points(guint n)
{
    return gwy_round(3.49*cbrt(n - 0.6));
}

/* Make sure two string arrays are identical.  Do not do too much extra work when they already are.  The item counts
 * ntarget and nsource are potential counts; either array can either be of the specified size or NULL.   With NULL
 * source this is also a convenient way of freeing the target. */
G_GNUC_UNUSED
static void
assign_string_array(gchar ***ptarget, gsize ntarget, gchar **source, gsize nsource)
{
    gchar **target = *ptarget;
    gint i;

    if (!source)
        nsource = 0;
    if (!target)
        ntarget = 0;

    for (i = nsource; i < ntarget; i++)
        g_free(target[i]);

    if (!nsource) {
        GWY_FREE(*ptarget);
        return;
    }

    if (nsource != ntarget) {
        target = *ptarget = g_renew(gchar*, target, nsource);
        gwy_clear(target + ntarget, nsource - ntarget);
    }

    for (i = 0; i < nsource; i++)
        gwy_assign_string(target + i, source[i]);
}

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