/*
 *  $Id: nield.c 30063 2026-06-15 09:06:58Z yeti-dn $
 *  Copyright (C) 2025-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 <string.h>
#include <stdlib.h>

#include "libgwyddion/macros.h"
#include "libgwyddion/serializable-utils.h"
#include "libgwyddion/nield.h"

#include "libgwyddion/omp.h"
#include "libgwyddion/internal.h"

#define TYPE_NAME "GwyNield"

enum {
    SGNL_DATA_CHANGED,
    NUM_SIGNALS
};

enum {
    ITEM_XRES, ITEM_YRES,
    ITEM_DATA,
    NUM_ITEMS
};

static void             finalize                    (GObject *object);
static void             alloc_data                  (GwyNield *nield,
                                                     gsize xres,
                                                     gsize yres,
                                                     gboolean nullme);
static void             serializable_init           (GwySerializableInterface *iface);
static void             serializable_itemize        (GwySerializable *serializable,
                                                     GwySerializableGroup *group);
static gboolean         serializable_construct      (GwySerializable *serializable,
                                                     GwySerializableGroup *group,
                                                     GwyErrorList **error_list);
static GwySerializable* serializable_copy           (GwySerializable *serializable);
static void             serializable_assign         (GwySerializable *destination,
                                                     GwySerializable *source);
static void             set_cache_for_constant_nield(GwyNield *nield,
                                                     gint value);
static GwyNield*        create_uninitialized_nield  (gint xres,
                                                     gint yres);

static guint signals[NUM_SIGNALS];
static GObjectClass *parent_class = NULL;

static GwySerializableItem serializable_items[NUM_ITEMS] = {
    { .name = "xres",    .ctype = GWY_SERIALIZABLE_INT32,        },
    { .name = "yres",    .ctype = GWY_SERIALIZABLE_INT32,        },
    { .name = "data",    .ctype = GWY_SERIALIZABLE_INT32_ARRAY,
                         .flags = GWY_SERIALIZABLE_BIG_DATA      },
};

G_DEFINE_TYPE_WITH_CODE(GwyNield, gwy_nield, G_TYPE_OBJECT,
                        G_ADD_PRIVATE(GwyNield)
                        G_IMPLEMENT_INTERFACE(GWY_TYPE_SERIALIZABLE, serializable_init))

static void
serializable_init(GwySerializableInterface *iface)
{
    iface->itemize   = serializable_itemize;
    iface->construct = serializable_construct;
    iface->copy      = serializable_copy;
    iface->assign    = serializable_assign;

    serializable_items[ITEM_XRES].aux.pspec = g_param_spec_int(serializable_items[ITEM_XRES].name, NULL, NULL,
                                                               1, G_MAXINT, 1,
                                                               G_PARAM_STATIC_STRINGS);
    serializable_items[ITEM_YRES].aux.pspec = g_param_spec_int(serializable_items[ITEM_YRES].name, NULL, NULL,
                                                               1, G_MAXINT, 1,
                                                               G_PARAM_STATIC_STRINGS);
    gwy_fill_serializable_defaults_pspec(serializable_items, NUM_ITEMS, FALSE);
}

static void
gwy_nield_class_init(GwyNieldClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
    GType type = G_TYPE_FROM_CLASS(klass);

    parent_class = gwy_nield_parent_class;

    gobject_class->finalize = finalize;

    /**
    * GwyNield::data-changed:
    * @gwynield: The #GwyNield which received the signal.
    *
    * The ::data-changed signal is never emitted by number field itself.  It is intended as a means to notify others
    * number field users they should update themselves.
    */
    signals[SGNL_DATA_CHANGED] = g_signal_new("data-changed", type,
                                              G_SIGNAL_RUN_FIRST,
                                              G_STRUCT_OFFSET(GwyNieldClass, data_changed),
                                              NULL, NULL,
                                              g_cclosure_marshal_VOID__VOID,
                                              G_TYPE_NONE, 0);
    g_signal_set_va_marshaller(signals[SGNL_DATA_CHANGED], type, g_cclosure_marshal_VOID__VOIDv);
}

static void
gwy_nield_init(GwyNield *nield)
{
    nield->priv = gwy_nield_get_instance_private(nield);

    alloc_data(nield, 1, 1, TRUE);
}

static void
finalize(GObject *object)
{
    GwyNield *nield = (GwyNield*)object;
    GwyNieldPrivate *priv = nield->priv;

    GWY_FREE_ARRAY(priv->sizes);
    GWY_FREE_ARRAY(priv->bboxes);
    g_free(priv->data);

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

static void
alloc_data(GwyNield *nield,
           gsize xres,
           gsize yres,
           gboolean nullme)
{
    gsize n = xres*yres;
    GwyNieldPrivate *priv = nield->priv;

    if (nield->xres == xres && nield->yres == yres) {
        if (nullme)
            gwy_clear(priv->data, n);
        return;
    }

    gsize fieldn = (gsize)nield->xres * (gsize)nield->yres;
    if (fieldn != n) {
        g_free(priv->data);
        priv->data = (nullme ? g_new0(gint, n) : g_new(gint, n));
    }
    else if (nullme)
        gwy_clear(priv->data, n);
    nield->xres = xres;
    nield->yres = yres;
}

/**
 * gwy_nield_new: (constructor)
 * @xres: X-resolution, i.e., the number of columns.
 * @yres: Y-resolution, i.e., the number of rows.
 *
 * Creates a new number field.
 *
 * The field is created as zero-filled.
 *
 * Returns: (transfer full):
 *          A newly created number field.
 **/
GwyNield*
gwy_nield_new(gint xres, gint yres)
{
    GwyNield *nield = g_object_new(GWY_TYPE_NIELD, NULL);
    alloc_data(nield, xres, yres, TRUE);
    return nield;
}

/**
 * gwy_nield_new_alike:
 * @model: A number field to take resolutions from.
 *
 * Creates a new number field similar to an existing one.
 *
 * The field is created as zero-filled. Use gwy_nield_copy() if you want to copy a number field including data.
 *
 * Returns: (transfer full):
 *          A newly created number field.
 **/
GwyNield*
gwy_nield_new_alike(GwyNield *model)
{
    GwyNield *nield = g_object_new(GWY_TYPE_NIELD, NULL);
    g_return_val_if_fail(GWY_IS_NIELD(model), nield);
    alloc_data(nield, model->xres, model->yres, TRUE);
    return nield;
}

// We formally have it as a Field method, but it is more convenient to implement is as a Nield constructor.
/**
 * gwy_field_new_nield_alike:
 * @field: A data field to take resolutions from.
 *
 * Creates a new number field with the same dimensions as a data field.
 *
 * The field is created as zero-filled.
 *
 * Returns: (transfer full):
 *          A newly created number field.
 **/
GwyNield*
gwy_field_new_nield_alike(GwyField *field)
{
    GwyNield *nield = g_object_new(GWY_TYPE_NIELD, NULL);
    g_return_val_if_fail(GWY_IS_FIELD(field), nield);
    alloc_data(nield, field->xres, field->yres, TRUE);
    return nield;
}

/**
 * gwy_nield_data_changed:
 * @nield: A number field.
 *
 * Emits signal "data-changed" on a number field.
 **/
void
gwy_nield_data_changed(GwyNield *nield)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    g_signal_emit(nield, signals[SGNL_DATA_CHANGED], 0);
}

/**
 * gwy_nield_copy_data:
 * @nield: Source number field.
 * @target: Destination number field.
 *
 * Copies the contents of an already allocated number field to a number field of the same size.
 *
 * Only the data are copied.  However, as #GwyNield currently does not have any additional properties, the function
 * has the same effect as gwy_nield_assign().
 **/
void
gwy_nield_copy_data(GwyNield *nield, GwyNield *target)
{
    if (!_gwy_nield_check_other(nield, target, FALSE) || target == nield)
        return;

    GwyNieldPrivate *priv = nield->priv, *tpriv = target->priv;
    gwy_assign(tpriv->data, priv->data, nield->xres*nield->yres);
    tpriv->cached = priv->cached;
    gwy_assign(tpriv->cache, priv->cache, GWY_NIELD_CACHE_size);
}

/**
 * gwy_nield_area_copy:
 * @src: Source number field.
 * @dest: Destination number field.
 * @col: Area upper-left column coordinate in @src.
 * @row: Area upper-left row coordinate @src.
 * @width: Area width (number of columns), pass -1 for full @src widdth.
 * @height: Area height (number of rows), pass -1 for full @src height.
 * @destcol: Destination column in @dest.
 * @destrow: Destination row in @dest.
 *
 * Copies a rectangular area from one number field to another.
 *
 * The area starts at (@col, @row) in @src and its dimension is @width*@height. It is copied to @dest starting from
 * (@destcol, @destrow).
 *
 * The source area has to be completely contained in @src.  No assumptions are made about destination position,
 * however, parts of the source area sticking out the destination number field @dest are cut off.
 *
 * If @src is equal to @dest, the areas may not overlap.
 **/
void
gwy_nield_area_copy(GwyNield *src,
                    GwyNield *dest,
                    gint col, gint row,
                    gint width, gint height,
                    gint destcol, gint destrow)
{
    gint i;

    g_return_if_fail(GWY_IS_NIELD(src));
    g_return_if_fail(GWY_IS_NIELD(dest));
    if (width == -1)
        width = src->xres;
    if (height == -1)
        height = src->yres;
    g_return_if_fail(col >= 0 && row >= 0 && width >= 0 && height >= 0
                     && col + width <= src->xres && row + height <= src->yres);

    if (destcol + width > dest->xres)
        width = dest->xres - destcol;
    if (destrow + height > dest->yres)
        height = dest->yres - destrow;
    if (destcol < 0) {
        col -= destcol;
        width += destcol;
        destcol = 0;
    }
    if (destrow < 0) {
        row -= destrow;
        height += destrow;
        destrow = 0;
    }
    if (width <= 0 || height <= 0)
        return;

    gwy_nield_invalidate(dest);

    const gint *sdata = src->priv->data;
    gint *ddata = dest->priv->data;
    if (width == src->xres && width == dest->xres) {
        /* make it as fast as gwy_nield_copy() whenever possible (and maybe faster, as we don't play with units */
        g_assert(col == 0 && destcol == 0);
        gwy_assign(ddata + width*destrow, sdata + width*row, width*height);
    }
    else {
        for (i = 0; i < height; i++)
            gwy_assign(ddata + dest->xres*(destrow + i) + destcol, sdata + src->xres*(row + i) + col, width);
    }
}

/**
 * gwy_nield_resize:
 * @nield: A number field to be resized
 * @xres: New number of columns.
 * @yres: New number of rows.
 *
 * Resizes a number field.
 *
 * The content becomes undefined and the raw data returned by gwy_nield_get_data() may become invalid pointer.
 **/
void
gwy_nield_resize(GwyNield *nield,
                 gint xres, gint yres)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    alloc_data(nield, xres, yres, FALSE);
    gwy_nield_invalidate(nield);
}

/**
 * gwy_nield_crop:
 * @nield: A number field to be resized
 * @row: Upper-left row coordinate.
 * @col: Upper-left column coordinate.
 * @width: Area width (number of columns).
 * @height: Area height (number of rows).
 *
 * Crops a number field to a smaller size.
 **/
void
gwy_nield_crop(GwyNield *nield,
               gint col, gint row,
               gint width, gint height)
{
    if (!_gwy_nield_check_area(nield, col, row, width, height, FALSE))
        return;

    gint xres = nield->xres, yres = nield->yres;
    if (col == 0 && row == 0 && width == xres && height == yres)
        return;

    const gint *data = nield->priv->data;
    gint *rdata = g_new(gint, width*height);
    for (gint i = 0; i < height; i++)
        gwy_assign(rdata + i*width, data + (i + row)*xres + col, width);
    GWY_SWAP(gint*, rdata, nield->priv->data);
    g_free(rdata);

    nield->xres = width;
    nield->yres = height;
    gwy_nield_invalidate(nield);
}

/**
 * gwy_nield_area_extract:
 * @nield: A number field to be resized
 * @row: Upper-left row coordinate.
 * @col: Upper-left column coordinate.
 * @width: Area width (number of columns).
 * @height: Area height (number of rows).
 *
 * Extracts a rectangular part of a number field to a new number field.
 *
 * Returns: (transfer full): The extracted area as a newly created number field.
 **/
GwyNield*
gwy_nield_area_extract(GwyNield *nield,
                       gint col, gint row,
                       gint width, gint height)
{
    if (!_gwy_nield_check_area(nield, col, row, width, height, FALSE))
        return NULL;

    gint xres = nield->xres, yres = nield->yres;
    if (col == 0 && row == 0 && width == xres && height == yres)
        return gwy_nield_copy(nield);

    GwyNield *result = gwy_nield_new(width, height);
    const gint *data = nield->priv->data;
    gint *rdata = result->priv->data;
    for (gint i = 0; i < height; i++)
        gwy_assign(rdata + i*width, data + (i + row)*xres + col, width);

    return result;
}

/**
 * gwy_nield_get_data:
 * @nield: A number field
 *
 * Gets the raw data buffer of a number field.
 *
 * The returned buffer is not guaranteed to be valid through whole number field life time. Some function may change
 * it, most notably gwy_nield_resize() and gwy_nield_crop().
 *
 * This function invalidates any cached information, use gwy_nield_get_data_const() if you are not going to
 * change the data.
 *
 * See gwy_nield_invalidate() for some discussion.
 *
 * Returns: The number field as a pointer to an array of gwy_nield_get_xres()*gwy_nield_get_yres() #gint's,
 *          ordered by lines.  I.e., they are to be accessed as data[row*xres + column].
 **/
gint*
gwy_nield_get_data(GwyNield *nield)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), NULL);
    gwy_nield_invalidate(nield);
    return nield->priv->data;
}

/**
 * gwy_nield_get_data_const:
 * @nield: A number field.
 *
 * Gets the raw data buffer of a number field, read-only.
 *
 * The returned buffer is not guaranteed to be valid through whole number field life time. Some function may change it,
 * most notably gwy_nield_resize() and gwy_nield_crop().
 *
 * Use gwy_nield_get_data() if you want to change the data.
 *
 * See gwy_nield_invalidate() for some discussion.
 *
 * Returns: The number field as a pointer to an array of gwy_nield_get_xres()*gwy_nield_get_yres() #gint's,
 *          ordered by lines.  I.e., they are to be accessed as data[row*xres + column].
 **/
const gint*
gwy_nield_get_data_const(GwyNield *nield)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), NULL);
    return (const gint*)nield->priv->data;
}

/**
 * gwy_nield_get_data_with_size:
 * @nield: A number field
 * @out_count: (out): Number of items in the returned buffer.
 *
 * Gets the raw data buffer of a number field. Used for gobject-introspection bindings.
 *
 * The returned buffer is not guaranteed to be valid through whole number field life time. Some function may change
 * it, most notably gwy_nield_resize() and gwy_nield_crop().
 *
 * This function invalidates any cached information. See gwy_nield_invalidate() for some discussion.
 *
 * Returns: (transfer none) (array length=out_count):
 *          The number field as a pointer to an array of gwy_nield_get_xres()*gwy_nield_get_yres() #gint's,
 *          ordered by lines.  I.e., they are to be accessed as data[row*xres + column].
 **/
gint*
gwy_nield_get_data_with_size(GwyNield *nield, gsize *out_count)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), NULL);
    gwy_nield_invalidate(nield);
    *out_count = (gsize)nield->xres * (gsize)nield->yres;
    return nield->priv->data;
}

/**
 * gwy_nield_set_data_with_size:
 * @nield: A number field
 * @data: (array length=data_count): The data to be set as a pointer to an array of @data_len #gints.
 * @data_count: Number of items in @data. It has to be equal to gwy_nield_get_xres()*gwy_nield_get_yres(), else the function does nothing.
 *
 * Sets new values to the raw data buffer of a number field. Used for gobject-introspection bindings.
 *
 * This function invalidates any cached information. See gwy_nield_invalidate() for some discussion.
 **/
void
gwy_nield_set_data_with_size(GwyNield *nield, gint *data, gsize data_count)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    if ((gsize)nield->xres * (gsize)nield->yres == data_count) {
        gwy_nield_invalidate(nield);
	memcpy(nield->priv->data, data, data_count*sizeof(gint));
    }
}

/**
 * gwy_nield_get_xres:
 * @nield: A number field.
 *
 * Gets X resolution (number of columns) of a number field.
 *
 * Returns: X resolution.
 **/
gint
gwy_nield_get_xres(GwyNield *nield)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);
    return nield->xres;
}

/**
 * gwy_nield_get_yres:
 * @nield: A number field.
 *
 * Gets Y resolution (number of rows) of the field.
 *
 * Returns: Y resolution.
 **/
gint
gwy_nield_get_yres(GwyNield *nield)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);
    return nield->yres;
}

static inline gboolean
gwy_nield_inside(GwyNield *nield, gint i, gint j)
{
    if (i >= 0 && j >= 0 && i < nield->xres && j < nield->yres)
        return TRUE;
    else
        return FALSE;
}

/**
 * gwy_nield_get_val:
 * @nield: A number field.
 * @col: Column index.
 * @row: Row index.
 *
 * Gets value at given position in a number field.
 *
 * Do not access data with this function inside inner loops, it's slow. Get the raw data buffer with
 * gwy_nield_get_data_const() and access it directly instead.
 *
 * Returns: Value at (@col, @row).
 **/
gint
gwy_nield_get_val(GwyNield *nield, gint col, gint row)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);
    g_return_val_if_fail(gwy_nield_inside(nield, col, row), 0);
    return nield->priv->data[col + nield->xres*row];
}

/**
 * gwy_nield_set_val:
 * @nield: A number field.
 * @col: Column index.
 * @row: Row index.
 * @value: Value to set.
 *
 * Sets value at given position in a number field.
 *
 * Do not set data with this function inside inner loops, it's slow.  Get the raw data buffer with
 * gwy_nield_get_data() and write to it directly instead.
 **/
void
gwy_nield_set_val(GwyNield *nield,
                  gint col, gint row,
                  gint value)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    g_return_if_fail(gwy_nield_inside(nield, col, row));
    gwy_nield_invalidate(nield);
    nield->priv->data[col + nield->xres*row] = value;
}

/**
 * gwy_nield_new_rotated_90:
 * @nield: A number field.
 * @clockwise: %TRUE to rotate clocwise, %FALSE to rotate anti-clockwise.
 *
 * Creates a new number field by rotating a number field by 90 degrees.
 *
 * Returns: (transfer full): A newly created number field.
 **/
GwyNield*
gwy_nield_new_rotated_90(GwyNield *nield,
                         gboolean clockwise)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), NULL);

    GwyNield *result = create_uninitialized_nield(nield->xres, nield->yres);
    gwy_nield_transpose(nield, result, FALSE);
    /* Clockwise = flip + rowinv; Anti-clockwise = flip + colinv. */
    gwy_nield_flip(result, clockwise, !clockwise);

    return result;
}

/**
 * gwy_nield_new_resampled:
 * @nield: A number field.
 * @xres: New number of columns.
 * @yres: New number of rows.
 *
 * Resamples a number field.
 *
 * The result is always flattened (as in gwy_nield_flatten()). Pixels of the new number field which are covered by
 * positive values by at least 1/2 are set to 1. Pixels covered from less than 1/2 are set to 0.
 *
 * Returns: (transfer full): A newly created number field.
 **/
GwyNield*
gwy_nield_new_resampled(GwyNield *nield,
                        gint xres,
                        gint yres)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), NULL);
    gint oldxres = nield->xres, oldyres = nield->yres;
    if (xres == oldxres && yres == oldyres) {
        nield = gwy_nield_copy(nield);
        gwy_nield_flatten(nield);
        return nield;
    }

    GwyNield *resampled = gwy_nield_new(xres, yres);
    const gint *data = nield->priv->data;
    gint *resdata = resampled->priv->data;

    const gdouble qx = (gdouble)oldxres/xres, qy = (gdouble)oldyres/yres;
    for (gint i = 0; i < yres; i++) {
        gdouble yfrom = qy*i, yto = qy*(i + 1);
        gint oldifrom = (gint)floor(yfrom), oldito = (gint)floor(yto) + 1;
        oldito = MIN(oldito, oldyres);
        for (gint j = 0; j < xres; j++) {
            gdouble xfrom = qx*j, xto = qx*(j + 1);
            gint oldjfrom = (gint)floor(xfrom), oldjto = (gint)floor(xto) + 1;
            oldjto = MIN(oldjto, oldxres);
            gdouble s = 0.0, w = 0.0;

            for (gint oldi = oldifrom; oldi < oldito; oldi++) {
                gdouble wy = 1.0;
                /* Incomplete initial and terminal pixels (vertically). */
                if (oldi == oldifrom && oldi < oldito-1)
                    wy = oldifrom+1 - yfrom;
                else if (oldi > oldifrom && oldi == oldito-1)
                    wy = yto - (oldito-1);
                /* Otherwise
                 * (a) The entire contribution is from a single source pixel (vertically) which is larger than the
                 *     target pixel. The weight is yto - yfrom but we do not need bother calculating it because there
                 *     is no summation.
                 * (b) The entire source pixel is contained in the target pixel (vertically), so the weight for this
                 *     particular pixel is really 1. */

                for (gint oldj = oldjfrom; oldj < oldjto; oldj++) {
                    /* The same logic, horizontally. */
                    gdouble wx = 1.0;
                    if (oldj == oldjfrom && oldj < oldjto-1)
                        wx = oldjfrom+1 - xfrom;
                    else if (oldj > oldjfrom && oldj == oldjto-1)
                        wx = xto - (oldjto-1);

                    s += wx*wy*(data[oldi*oldxres + oldj] > 0);
                    w += wx*wy;
                }
            }
            resdata[i*xres + j] = (s/w >= 0.5);
        }
    }

    return resampled;
}

static inline void
invert_int_array_in_place(int *d, guint n)
{
    int *e = d + n-1;

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

/**
 * gwy_nield_flip:
 * @nield: A number field.
 * @xflipped: %TRUE to reflect X, i.e. rows within the XY plane. The image will be left–right mirrored.
 * @yflipped: %TRUE to reflect Y, i.e. columns within the XY plane. The image will be flipped upside down.
 *
 * Reflects a number field.
 **/
void
gwy_nield_flip(GwyNield *nield,
               gboolean xflipped,
               gboolean yflipped)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    gint *data = nield->priv->data;
    gint xres = nield->xres;
    gint yres = nield->yres;
    gint n = xres*yres;

    if (yflipped && xflipped) {
        invert_int_array_in_place(data, n);
    }
    else if (xflipped) {
        for (gint i = 0; i < yres; i++)
            invert_int_array_in_place(data + i*xres, xres);
    }
    else if (yflipped) {
        for (gint i = 0; i < yres/2; i++) {
            gint *beg = data + i*xres;
            gint *end = data + (yres-1 - i)*xres;
            for (gint j = 0; j < xres; j++, end++, beg++)
                GWY_SWAP(gint, *beg, *end);
        }
    }
    else
        return;

    /* No cached value changes. However, renumbering would, in general, result in different numbers. So we cannot
     * keep it cached. */
    nield->priv->cached &= NCBIT(MAX) | NCBIT(COUNT);
}

/* Block sizes are measured in destination, in source, the dims are swapped. */
static inline void
swap_block(const gint *sb, gint *db,
           guint xblocksize, guint yblocksize,
           guint dxres, guint sxres)
{
    guint i, j;

    for (i = 0; i < yblocksize; i++) {
        const gint *s = sb + i;
        gint *d = db + i*dxres;
        for (j = xblocksize; j; j--, d++, s += sxres)
            *d = *s;
    }
}

static void
transpose_to(const GwyNield *source,
             guint col, guint row,
             guint width, guint height,
             GwyNield *dest,
             guint destcol, guint destrow)
{
    enum { BLOCK_SIZE = 64 };

    guint dxres = dest->xres, sxres = source->xres;
    guint jmax = height/BLOCK_SIZE * BLOCK_SIZE;
    guint imax = width/BLOCK_SIZE * BLOCK_SIZE;
    const gint *sbase = source->priv->data + sxres*row + col;
    gint *dbase = dest->priv->data + dxres*destrow + destcol;
    guint ib, jb;

#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            private(ib, jb) \
            shared(sbase,dbase,sxres,dxres,imax,jmax,height)
#endif
    for (ib = 0; ib < imax; ib += BLOCK_SIZE) {
        for (jb = 0; jb < jmax; jb += BLOCK_SIZE)
            swap_block(sbase + (jb*sxres + ib), dbase + (ib*dxres + jb), BLOCK_SIZE, BLOCK_SIZE, dxres, sxres);
        if (jmax != height)
            swap_block(sbase + (jmax*sxres + ib), dbase + (ib*dxres + jmax), height - jmax, BLOCK_SIZE, dxres, sxres);
    }
    if (imax != width) {
#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            private(jb) \
            shared(sbase,dbase,sxres,dxres,imax,jmax,width)
#endif
        for (jb = 0; jb < jmax; jb += BLOCK_SIZE)
            swap_block(sbase + (jb*sxres + imax), dbase + (imax*dxres + jb), BLOCK_SIZE, width - imax, dxres, sxres);
        if (jmax != height) {
            swap_block(sbase + (jmax*sxres + imax), dbase + (imax*dxres + jmax),
                       height - jmax, width - imax, dxres, sxres);
        }
    }
}

/**
 * gwy_nield_transpose:
 * @src: Source number field.
 * @dest: Destination number field.
 * @minor: %TRUE to mirror about the minor diagonal; %FALSE to mirror about major diagonal.
 *
 * Copies data from one number field to another with transposition.
 *
 * The destination number field is resized as necessary.
 **/
void
gwy_nield_transpose(GwyNield *src, GwyNield *dest,
                    gboolean minor)
{
    g_return_if_fail(GWY_IS_NIELD(src));
    g_return_if_fail(GWY_IS_NIELD(dest));

    gint xres = src->xres, yres = src->yres;
    alloc_data(dest, yres, xres, FALSE);
    transpose_to(src, 0, 0, xres, yres, dest, 0, 0);
    if (minor)
        invert_int_array_in_place(dest->priv->data, xres*yres);

    /* No cached value changes. However, renumbering would, in general, result in different numbers. So we cannot
     * keep it cached. */
    gwy_assign(dest->priv->cache, src->priv->cache, GWY_NIELD_CACHE_size);
    dest->priv->cached = src->priv->cached & (NCBIT(MAX) | NCBIT(COUNT));
}

/**
 * gwy_nield_area_transpose:
 * @src: Source number field.
 * @col: Upper-left column coordinate in @src.
 * @row: Upper-left row coordinate in @src.
 * @width: Area width (number of columns) in @src.
 * @height: Area height (number of rows) in @src.
 * @dest: Destination number field.
 * @minor: %TRUE to mirror about the minor diagonal; %FALSE to mirror about
 *         major diagonal.
 *
 * Copies data from a rectangular part of one number field to another with transposition.
 *
 * The destination number field is resized as necessary.
 **/
void
gwy_nield_area_transpose(GwyNield *src,
                         gint col, gint row, gint width, gint height,
                         GwyNield *dest,
                         gboolean minor)
{
    if (!_gwy_nield_check_area(src, col, row, width, height, FALSE))
        return;
    g_return_if_fail(GWY_IS_NIELD(dest));

    alloc_data(dest, height, width, FALSE);
    transpose_to(src, col, row, width, height, dest, 0, 0);
    if (minor)
        invert_int_array_in_place(dest->priv->data, width*height);
}

/**
 * gwy_nield_fill:
 * @nield: A number field.
 * @value: Value to be entered.
 *
 * Fills a number field with given value.
 **/
void
gwy_nield_fill(GwyNield *nield, gint value)
{
    g_return_if_fail(GWY_IS_NIELD(nield));

    gint i;
    gint *p = nield->priv->data;

    for (i = nield->xres * nield->yres; i; i--, p++)
        *p = value;

    /* We can precompute stats */
    set_cache_for_constant_nield(nield, value);
}

static void
set_cache_for_constant_nield(GwyNield *nield, gint value)
{
    nield->priv->cached = NCBIT(MAX) | NCBIT(COUNT);
    NCVAL(nield, MAX) = value;
    NCVAL(nield, COUNT) = (value > 0 ? nield->xres*nield->yres : 0);
}

/**
 * gwy_nield_area_fill:
 * @nield: A number field.
 * @mask: Mask specifying which values to take into account/exclude, or %NULL.
 * @mode: Masking mode to use.  See the introduction for description of masking modes.
 * @col: Upper-left column coordinate.
 * @row: Upper-left row coordinate.
 * @width: Area width (number of columns).
 * @height: Area height (number of rows).
 * @value: Value to be entered
 *
 * Fills a masked rectangular part of a number field with given value.
 **/
void
gwy_nield_area_fill(GwyNield *nield,
                    GwyNield *mask,
                    GwyMaskingType mode,
                    gint col, gint row, gint width, gint height,
                    gint value)
{
    if (!_gwy_nield_check_area(nield, col, row, width, height, TRUE)
        || !_gwy_nield_check_mask(nield, &mask, &mode))
        return;

    gint xres = nield->xres, yres = nield->yres;
    if (!mask && col == 0 && row == 0 && width == xres && height == yres) {
        /* Prefills cache immediately. */
        gwy_nield_fill(nield, value);
        return;
    }

    for (gint i = 0; i < height; i++) {
        gint *drow = nield->priv->data + (row + i)*xres + col;

        if (mask) {
            const gint *mrow = mask->priv->data + (row + i)*xres + col;
            for (gint j = 0; j < width; j++) {
                if (nielded_included(mrow + j, mode))
                    drow[j] = value;
            }
        }
        else {
            for (gint j = 0; j < width; j++)
                drow[j] = value;
        }
    }
    gwy_nield_invalidate(nield);
}

/**
 * gwy_nield_clear:
 * @nield: A number field.
 *
 * Fills a number field with zeroes.
 **/
void
gwy_nield_clear(GwyNield *nield)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    gwy_clear(nield->priv->data, nield->xres*nield->yres);

    /* We can precompute stats */
    set_cache_for_constant_nield(nield, 0);
}

/**
 * gwy_nield_area_clear:
 * @nield: A number field.
 * @col: Upper-left column coordinate.
 * @row: Upper-left row coordinate.
 * @width: Area width (number of columns).
 * @height: Area height (number of rows).
 *
 * Fills a rectangular part of a number field with zeroes.
 *
 * Use gwy_nield_area_fill() to clear an area with masking. Clearing is only more efficient without masking.
 **/
void
gwy_nield_area_clear(GwyNield *nield,
                     gint col, gint row, gint width, gint height)
{
    if (!_gwy_nield_check_area(nield, col, row, width, height, TRUE))
        return;

    gint xres = nield->xres, yres = nield->yres;
    if (col == 0 && row == 0 && width == xres && height == yres) {
        /* Prefills cache immediately. */
        gwy_nield_clear(nield);
        return;
    }

    gwy_nield_invalidate(nield);
    if (height == 1 || (col == 0 && width == nield->xres)) {
        gwy_clear(nield->priv->data + nield->xres*row + col, width*height);
        return;
    }

    for (gint i = 0; i < height; i++) {
        gint *drow = nield->priv->data + (row + i)*nield->xres + col;
        gwy_clear(drow, width);
    }
}

/**
 * gwy_nield_max:
 * @nield: A number field.
 *
 * Finds the maximum value in a number field.
 *
 * This quantity is cached. Although it is not common, the return value can also be negative.
 *
 * Returns: The maximum value.
 **/
gint
gwy_nield_max(GwyNield *nield)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), G_MININT);
    gwy_debug("%s", NCTEST(nield, MAX) ? "cache" : "lame");

    if (!NCTEST(nield, MAX)) {
        const gint *d = nield->priv->data;
        gint max = d[0];
        gint i, n = nield->xres * nield->yres;

        /* Too trivial to parallelise. */
        for (i = 1; i < n; i++)
            max = MAX(max, d[i]);
        NCVAL(nield, MAX) = max;
        nield->priv->cached |= NCBIT(MAX);
    }

    return NCVAL(nield, MAX);
}

/**
 * gwy_nield_count:
 * @nield: A number field.
 *
 * Counts positive values in a number field.
 *
 * Returns: The number of positive values.
 **/
guint
gwy_nield_count(GwyNield *nield)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), 0);

    if (!NCTEST(nield, COUNT)) {
        const gint *d = nield->priv->data;
        gint xres = nield->xres, yres = nield->yres;
        gsize n = (gsize)xres * (gsize)yres;
        guint count = 0;
        for (gsize i = 0; i < n; i++)
            count += (d[i] > 0);

        NCVAL(nield, COUNT) = count;
        nield->priv->cached |= NCBIT(COUNT);
    }
    return NCVAL(nield, COUNT);
}

/**
 * gwy_nield_area_count:
 * @nield: A number field.
 * @mask: Mask specifying which values to take into account/exclude, or %NULL.
 * @mode: Masking mode to use.  See the introduction for description of masking modes.
 * @col: Upper-left column coordinate.
 * @row: Upper-left row coordinate.
 * @width: Area width (number of columns).
 * @height: Area height (number of rows)
 *
 * Counts positive values in a rectangular region of a number field.
 *
 * Returns: The number of positive values.
 **/
guint
gwy_nield_area_count(GwyNield *nield,
                     GwyNield *mask,
                     GwyMaskingType mode,
                     gint col,
                     gint row,
                     gint width,
                     gint height)
{
    if (!_gwy_nield_check_area(nield, col, row, width, height, TRUE)
        || !_gwy_nield_check_mask(nield, &mask, &mode))
        return 0;

    /* Use cache, if possible. */
    gint xres = nield->xres, yres = nield->yres;
    if (!mask && col == 0 && row == 0 && width == xres && height == yres)
        return gwy_nield_count(nield);

    const gint *datapos = nield->priv->data + row*xres + col;
    guint count = 0;
    if (mask) {
        const gint *maskpos = mask->priv->data + row*xres + col;
        for (gint i = 0; i < height; i++) {
            const gint *d = datapos + i*xres;
            const gint *m = maskpos + i*xres;
            for (gint j = 0; j < width; j++) {
                if (nielded_included(m + j, mode))
                    count += (d[j] > 0);
            }
        }
    }
    else {
        for (gint i = 0; i < height; i++) {
            const gint *d = datapos + i*xres;
            for (gint j = 0; j < width; j++)
                count += (d[j] > 0);
        }
    }

    return count;
}

gboolean
_gwy_nield_check_area(GwyNield *nield,
                      gint col, gint row,
                      gint width, gint height,
                      gboolean empty_is_noop)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), FALSE);
    gint xres = nield->xres, yres = nield->yres;
    g_return_val_if_fail(col >= 0 && col < xres, FALSE);
    g_return_val_if_fail(row >= 0 && row < yres, FALSE);
    g_return_val_if_fail(width <= xres - col, FALSE);
    g_return_val_if_fail(height <= yres - row, FALSE);
    if (empty_is_noop) {
        g_return_val_if_fail(width >= 0, FALSE);
        g_return_val_if_fail(height >= 0, FALSE);
        /* If empty_is_noop we still return FALSE for empty areas to indicate the caller should avoid any data
         * processing, but we do not print any error. */
        return width > 0 && height > 0;
    }
    g_return_val_if_fail(width > 0, FALSE);
    g_return_val_if_fail(height > 0, FALSE);

    return TRUE;
}

gboolean
_gwy_nield_check_mask(GwyNield *nield,
                      GwyNield **mask,
                      GwyMaskingType *masking)
{
    /* NULL @mask is a direct caller error.  We allow NULL @masking for old functions that do not have mode so masking
     * is implicitly done in INCLUDE mode. */
    g_assert(mask);
    g_return_val_if_fail(GWY_IS_NIELD(nield), FALSE);
    if (!*mask) {
        if (masking)
            *masking = GWY_MASK_IGNORE;
        return TRUE;
    }
    if (masking) {
        if (*masking == GWY_MASK_IGNORE) {
            *mask = NULL;
            return TRUE;
        }
        g_return_val_if_fail(*masking == GWY_MASK_INCLUDE || *masking == GWY_MASK_EXCLUDE, FALSE);
    }
    g_return_val_if_fail(GWY_IS_NIELD(*mask), FALSE);
    g_return_val_if_fail((*mask)->xres == nield->xres, FALSE);
    g_return_val_if_fail((*mask)->yres == nield->yres, FALSE);
    return TRUE;
}

gboolean
_gwy_nield_check_field(GwyNield *nield,
                       GwyField *field,
                       gboolean nullok)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), FALSE);
    if (nullok && !field)
        return TRUE;

    g_return_val_if_fail(GWY_IS_FIELD(field), FALSE);
    g_return_val_if_fail(field->xres == nield->xres, FALSE);
    g_return_val_if_fail(field->yres == nield->yres, FALSE);
    return TRUE;
}

gboolean
_gwy_nield_check_other(GwyNield *nield,
                       GwyNield *othernield,
                       gboolean nullok)
{
    g_return_val_if_fail(GWY_IS_NIELD(nield), FALSE);
    if (nullok && !othernield)
        return TRUE;

    g_return_val_if_fail(GWY_IS_NIELD(othernield), FALSE);
    g_return_val_if_fail(othernield->xres == nield->xres, FALSE);
    g_return_val_if_fail(othernield->yres == nield->yres, FALSE);
    return TRUE;
}

static void
serializable_itemize(GwySerializable *serializable, GwySerializableGroup *group)
{
    GwyNield *nield = GWY_NIELD(serializable);
    GwyNieldPrivate *priv = nield->priv;
    gsize ndata = (gsize)nield->xres * (gsize)nield->yres;

    gwy_serializable_group_alloc_size(group, NUM_ITEMS);
    gwy_serializable_group_append_int32(group, serializable_items + ITEM_XRES, nield->xres);
    gwy_serializable_group_append_int32(group, serializable_items + ITEM_YRES, nield->yres);
    gwy_serializable_group_append_int32_array(group, serializable_items + ITEM_DATA, priv->data, ndata);
    gwy_serializable_group_itemize(group);
}

static gboolean
serializable_construct(GwySerializable *serializable, GwySerializableGroup *group, GwyErrorList **error_list)
{
    GwySerializableItem its[NUM_ITEMS], *it;
    gboolean ok = FALSE;
    gwy_assign(its, serializable_items, NUM_ITEMS);
    gwy_deserialize_filter_items(its, NUM_ITEMS, group, TYPE_NAME, error_list);

    GwyNield *nield = GWY_NIELD(serializable);
    GwyNieldPrivate *priv = nield->priv;

    /* Botched up data dimensions is a hard fail. */
    guint xres = its[ITEM_XRES].value.v_int32;
    guint yres = its[ITEM_YRES].value.v_int32;

    it = its + ITEM_DATA;
    gsize logical_dims[2] = { xres, yres };
    if (!gwy_check_data_dimension(error_list, TYPE_NAME, 2, it->array_size, logical_dims))
        goto fail;

    nield->xres = xres;
    nield->yres = yres;
    g_free(priv->data);
    priv->data = it->value.v_int32_array;
    it->value.v_int32_array = NULL;

    ok = TRUE;

fail:
    g_free(its[ITEM_DATA].value.v_int32_array);
    return ok;
}

static void
transfer_caches(GwyNield *dest, GwyNield *source)
{
    GwyNieldPrivate *dpriv = dest->priv, *spriv = source->priv;

    dpriv->cached = spriv->cached & GWY_NIELD_CACHE_scalar;
    gwy_assign(dpriv->cache, spriv->cache, GWY_NIELD_CACHE_size);

    if (NCTEST(source, SIZES)) {
        if (!dpriv->sizes)
            dpriv->sizes = g_array_new(FALSE, FALSE, sizeof(guint));
        g_array_set_size(dpriv->sizes, 0);
        g_array_append_vals(dpriv->sizes, spriv->sizes->data, spriv->sizes->len);
        dpriv->cached |= NCBIT(SIZES);
    }
    if (NCTEST(source, BBOXES)) {
        if (!dpriv->bboxes)
            dpriv->bboxes = g_array_new(FALSE, FALSE, sizeof(guint));
        g_array_set_size(dpriv->bboxes, 0);
        g_array_append_vals(dpriv->bboxes, spriv->bboxes->data, spriv->bboxes->len);
        dpriv->cached |= NCBIT(BBOXES);
    }
}

static GwySerializable*
serializable_copy(GwySerializable *serializable)
{
    GwyNield *nield = GWY_NIELD(serializable);
    GwyNield *copy = create_uninitialized_nield(nield->xres, nield->yres);
    GwyNieldPrivate *priv = nield->priv, *cpriv = copy->priv;
    gwy_assign(cpriv->data, priv->data, nield->xres*nield->yres);
    transfer_caches(copy, nield);
    return GWY_SERIALIZABLE(copy);
}

static void
serializable_assign(GwySerializable *destination, GwySerializable *source)
{
    GwyNield *destnield = GWY_NIELD(destination), *srcnield = GWY_NIELD(source);
    alloc_data(destnield, srcnield->xres, srcnield->yres, FALSE);
    gwy_nield_copy_data(srcnield, destnield);
    transfer_caches(destnield, srcnield);
}

/**
 * gwy_nield_copy:
 * @nield: A number field to duplicate.
 *
 * Create a new number field as a copy of an existing one.
 *
 * This function is a convenience gwy_serializable_copy() wrapper.
 *
 * Returns: (transfer full):
 *          A copy of the number field.
 **/
GwyNield*
gwy_nield_copy(GwyNield *nield)
{
    /* Try to return a valid object even on utter failure. Returning NULL probably would crash something soon. */
    if (!GWY_IS_NIELD(nield)) {
        g_assert(GWY_IS_NIELD(nield));
        return g_object_new(GWY_TYPE_NIELD, NULL);
    }
    return GWY_NIELD(gwy_serializable_copy(GWY_SERIALIZABLE(nield)));
}

/**
 * gwy_nield_assign:
 * @destination: Target number field.
 * @source: Source number field.
 *
 * Makes one number field equal to another.
 *
 * This function is a convenience gwy_serializable_assign() wrapper.
 **/
void
gwy_nield_assign(GwyNield *destination, GwyNield *source)
{
    g_return_if_fail(GWY_IS_NIELD(destination));
    g_return_if_fail(GWY_IS_NIELD(source));
    if (destination != source)
        gwy_serializable_assign(GWY_SERIALIZABLE(destination), GWY_SERIALIZABLE(source));
}

/**
 * gwy_nield_invalidate:
 * @nield: A number field to invalidate.
 *
 * Invalidates cached number field stats.
 *
 * User code should rarely need this macro, as all #GwyNield methods do proper invalidation when they change data,
 * as well as gwy_nield_get_data() does.
 *
 * However, if you get raw data with gwy_nield_get_data() and then mix direct changes to it with calls to methods
 * like gwy_nield_max(), you may need to explicitely invalidate cached values to let gwy_nield_get_max()
 * know it has to recompute the maximum.
 **/
void
gwy_nield_invalidate(GwyNield *nield)
{
    g_return_if_fail(GWY_IS_NIELD(nield));
    nield->priv->cached = 0;
}

void
_gwy_nield_fill_field_with_01(GwyNield *nield, GwyField *field)
{
    gint xres = nield->xres, yres = nield->yres;
    gsize n = (gsize)xres * (gsize)yres;
    const gint *m = nield->priv->data;
    gdouble *f = field->priv->data;
    for (gsize k = 0; k < n; k++)
        f[k] = (m[k] > 0);
}

static GwyNield*
create_uninitialized_nield(gint xres, gint yres)
{
    GwyNield *nield = g_object_new(GWY_TYPE_NIELD, NULL);
    alloc_data(nield, xres, yres, FALSE);
    return nield;
}

/**
 * SECTION: nield
 * @title: GwyNield
 * @short_description: Two-dimensional natural number array
 *
 * #GwyNield is a regular two-dimensional data array of natural (or whole) numbers. It represents the result of some
 * classification of two-dimensional data. The uses vary from a simple 0/1 masking to complicated splitting to
 * multiple regions. Usually, zero means empty space and positive numbers marked regions. Occasionally, negative
 * values are used to distinguish different types of empty space.
 *
 * Unlike #GwyField, #GwyNield does not have units or physical dimensions. It is just a matrix of integers.
 **/

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