/*
 *  $Id: arithmetic.c 29855 2026-04-21 09:20:57Z 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.
 */

#include "config.h"
#include <string.h>

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

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

/* for compatibility checks */
#define EPSILON 5e-6

static gboolean field_compatibility_check_common(GwyField *result,
                                                 GwyField *operand1,
                                                 GwyField *operand2,
                                                 gdouble **resdata,
                                                 gdouble **op1data,
                                                 gdouble **op2data,
                                                 gsize *n);
static gboolean line_compatibility_check_common (GwyLine *result,
                                                 GwyLine *operand1,
                                                 GwyLine *operand2,
                                                 gdouble **resdata,
                                                 gdouble **op1data,
                                                 gdouble **op2data,
                                                 gsize *n);

/**
 * gwy_line_sum_lines:
 * @result: A data line to put the result to.
 * @operand1: First data line operand (summand).
 * @operand2: Second data line operand (summand).
 *
 * Sums two data lines.
 *
 * The result and either operand (or both of them) can be the same line.
 **/
void
gwy_line_sum_lines(GwyLine *result,
                   GwyLine *operand1,
                   GwyLine *operand2)
{
    gdouble *p, *q, *r;
    gsize n;
    if (!line_compatibility_check_common(result, operand1, operand2, &r, &p, &q, &n))
        return;

    /* Too trivial to parallelise. */
    for (gsize i = 0; i < n; i++)
        r[i] = p[i] + q[i];

    gwy_line_invalidate(result);
}

/**
 * gwy_line_subtract_lines:
 * @result: A data line to put the result to.
 * @operand1: First data line operand (minuend).
 * @operand2: Second data line operand (subtrahend).
 *
 * Subtracts two data lines.
 *
 * The result and either operand (or both of them) can be the same line.
 **/
void
gwy_line_subtract_lines(GwyLine *result,
                        GwyLine *operand1,
                        GwyLine *operand2)
{
    gdouble *p, *q, *r;
    gsize n;
    if (!line_compatibility_check_common(result, operand1, operand2, &r, &p, &q, &n))
        return;

    /* Too trivial to parallelise. */
    for (gsize i = 0; i < n; i++)
        r[i] = p[i] - q[i];

    gwy_line_invalidate(result);
}

/**
 * gwy_line_multiply_lines:
 * @result: A data line to put the result to.
 * @operand1: First data line operand (factor).
 * @operand2: Second data line operand (factor).
 *
 * Multiplies two data lines.
 *
 * The result and either operand (or both of them) can be the same line.
 **/
void
gwy_line_multiply_lines(GwyLine *result,
                        GwyLine *operand1,
                        GwyLine *operand2)
{
    gdouble *p, *q, *r;
    gsize n;
    if (!line_compatibility_check_common(result, operand1, operand2, &r, &p, &q, &n))
        return;

    /* Too trivial to parallelise. */
    for (gsize i = 0; i < n; i++)
        r[i] = p[i]*q[i];

    gwy_line_invalidate(result);
}

/**
 * gwy_line_linear_combination:
 * @result: A data line to put the result to.
 * @constant: Constant term to add to the result.
 * @operand1: First data line operand.
 * @coeff1: Factor to multiply the first operand with.
 * @operand2: Second data line operand.
 * @coeff2: Factor to multiply the second operand with.
 *
 * Computes point-wise general linear combination of two data lines.
 *
 * The result is formally @coeff1×@operand1 + @coeff2×@operand2 + @constant.
 *
 * The result and either operand (or both of them) can be the same line.
 **/
void
gwy_line_linear_combination(GwyLine *result,
                            gdouble coeff1,
                            GwyLine *operand1,
                            gdouble coeff2,
                            GwyLine *operand2,
                            gdouble constant)
{
    gdouble *p, *q, *r;
    gsize n;
    if (!line_compatibility_check_common(result, operand1, operand2, &r, &p, &q, &n))
        return;

    /* Too trivial to parallelise? */
    for (gsize i = 0; i < n; i++)
        r[i] = coeff1*p[i] + coeff2*q[i] + constant;

    gwy_line_invalidate(result);
}

/**
 * gwy_field_sum_fields:
 * @result: A data field to put the result to.
 * @operand1: First data field operand (summand).
 * @operand2: Second data field operand (summand).
 *
 * Sums two data fields.
 *
 * The result and either operand (or both of them) can be the same field.
 **/
void
gwy_field_sum_fields(GwyField *result,
                     GwyField *operand1,
                     GwyField *operand2)
{
    gdouble *p, *q, *r;
    gsize n;
    if (!field_compatibility_check_common(result, operand1, operand2, &r, &p, &q, &n))
        return;

    /* Too trivial to parallelise. */
    for (gsize i = 0; i < n; i++)
        r[i] = p[i] + q[i];

    if (FCTEST(operand1, SUM) && FCTEST(operand2, SUM)) {
        result->priv->cached = FCBIT(SUM);
        FCVAL(result, SUM) = FCVAL(operand1, SUM) + FCVAL(operand2, SUM);
    }
    else
        gwy_field_invalidate(result);
}

/**
 * gwy_field_subtract_fields:
 * @result: A data field to put the result to.
 * @operand1: First data field operand (minuend).
 * @operand2: Second data field operand (subtrahend).
 *
 * Subtracts one data field from another.
 *
 * The result and either operand (or both of them) can be the same field.
 **/
void
gwy_field_subtract_fields(GwyField *result,
                          GwyField *operand1,
                          GwyField *operand2)
{
    gdouble *p, *q, *r;
    gsize n;
    if (!field_compatibility_check_common(result, operand1, operand2, &r, &p, &q, &n))
        return;

    /* Too trivial to parallelise. */
    for (gsize i = 0; i < n; i++)
        r[i] = p[i] - q[i];

    if (FCTEST(operand1, SUM) && FCTEST(operand2, SUM)) {
        result->priv->cached = FCBIT(SUM);
        FCVAL(result, SUM) = FCVAL(operand1, SUM) - FCVAL(operand2, SUM);
    }
    else
        gwy_field_invalidate(result);
}

/**
 * gwy_field_multiply_fields:
 * @result: A data field to put the result to.
 * @operand1: First data field operand (factor).
 * @operand2: Second data field operand (factor).
 *
 * Multiplies two data fields.
 *
 * The result and either operand (or both of them) can be the same field.
 **/
void
gwy_field_multiply_fields(GwyField *result,
                          GwyField *operand1,
                          GwyField *operand2)
{
    gdouble *p, *q, *r;
    gsize n;
    if (!field_compatibility_check_common(result, operand1, operand2, &r, &p, &q, &n))
        return;

    /* Too trivial to parallelise. */
    for (gsize i = 0; i < n; i++)
        r[i] = p[i]*q[i];

    gwy_field_invalidate(result);
}

/**
 * gwy_field_divide_fields:
 * @result: A data field to put the result to.
 * @operand1: First data field operand (dividend).
 * @operand2: Second data field operand (divisor).
 *
 * Divides one data field with another.
 *
 * The result and either operand (or both of them) can be the same field.
 **/
void
gwy_field_divide_fields(GwyField *result,
                        GwyField *operand1,
                        GwyField *operand2)
{
    gdouble *p, *q, *r;
    gsize n;
    if (!field_compatibility_check_common(result, operand1, operand2, &r, &p, &q, &n))
        return;

    /* Too trivial to parallelise. */
    for (gsize i = 0; i < n; i++)
        r[i] = p[i]/q[i];

    gwy_field_invalidate(result);
}

/**
 * gwy_field_min_of_fields:
 * @result: A data field to put the result to.
 * @operand1: First data field operand.
 * @operand2: Second data field operand.
 *
 * Finds point-wise maxima of two data fields.
 *
 * The result and either operand (or both of them) can be the same field.
 **/
void
gwy_field_min_of_fields(GwyField *result,
                        GwyField *operand1,
                        GwyField *operand2)
{
    gdouble *p, *q, *r;
    gsize n;
    if (!field_compatibility_check_common(result, operand1, operand2, &r, &p, &q, &n))
        return;

    /* Too trivial to parallelise (with fast-math). */
    for (gsize i = 0; i < n; i++)
        r[i] = fmin(p[i], q[i]);

    if (FCTEST(operand1, MIN) && FCTEST(operand2, MIN)) {
        result->priv->cached = FCBIT(MIN);
        FCVAL(result, MIN) = MIN(FCVAL(operand1, MIN), FCVAL(operand2, MIN));
    }
    else
        gwy_field_invalidate(result);
}

/**
 * gwy_field_max_of_fields:
 * @result: A data field to put the result to.
 * @operand1: First data field operand.
 * @operand2: Second data field operand.
 *
 * Finds point-wise minima of two data fields.
 *
 * The result and either operand (or both of them) can be the same field.
 **/
void
gwy_field_max_of_fields(GwyField *result,
                        GwyField *operand1,
                        GwyField *operand2)
{
    gdouble *p, *q, *r;
    gsize n;
    if (!field_compatibility_check_common(result, operand1, operand2, &r, &p, &q, &n))
        return;

    /* Too trivial to parallelise (with fast-math). */
    for (gsize i = 0; i < n; i++)
        r[i] = fmax(p[i], q[i]);

    if (FCTEST(operand1, MAX) && FCTEST(operand2, MAX)) {
        result->priv->cached = FCBIT(MAX);
        FCVAL(result, MAX) = MAX(FCVAL(operand1, MAX), FCVAL(operand2, MAX));
    }
    else
        gwy_field_invalidate(result);
}

/**
 * gwy_field_hypot_of_fields:
 * @result: A data field to put the result to.
 * @operand1: First data field operand.
 * @operand2: Second data field operand.
 *
 * Finds point-wise hypotenuse of two data fields.
 *
 * The result and either operand (or both of them) can be the same field.
 **/
void
gwy_field_hypot_of_fields(GwyField *result,
                          GwyField *operand1,
                          GwyField *operand2)
{
    gdouble *p, *q, *r;
    gsize n;
    if (!field_compatibility_check_common(result, operand1, operand2, &r, &p, &q, &n))
        return;

#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            shared(r,p,q,n)
#endif
    for (gsize i = 0; i < n; i++)
        r[i] = hypot(p[i], q[i]);

    gwy_field_invalidate(result);
}

/**
 * gwy_field_linear_combination:
 * @result: A data field to put the result to.
 * @constant: Constant term to add to the result.
 * @operand1: First data field operand.
 * @coeff1: Factor to multiply the first operand with.
 * @operand2: Second data field operand.
 * @coeff2: Factor to multiply the second operand with.
 *
 * Computes point-wise general linear combination of two data fields.
 *
 * The result is formally @coeff1×@operand1 + @coeff2×@operand2 + @constant.
 *
 * The result and either operand (or both of them) can be the same field.
 **/
void
gwy_field_linear_combination(GwyField *result,
                             gdouble coeff1,
                             GwyField *operand1,
                             gdouble coeff2,
                             GwyField *operand2,
                             gdouble constant)
{
    gdouble *p, *q, *r;
    gsize n;
    if (!field_compatibility_check_common(result, operand1, operand2, &r, &p, &q, &n))
        return;

    /* Too trivial to parallelise? */
#ifdef _OPENMP
#pragma omp parallel for if(gwy_threads_are_enabled()) default(none) \
            shared(r,p,q,n,constant,coeff1,coeff2)
#endif
    for (gsize i = 0; i < n; i++)
        r[i] = coeff1*p[i] + coeff2*q[i] + constant;

    gwy_field_invalidate(result);
}

static void
check_resolution(gint res1, gint res2,
                 GwyDataMismatchFlags check,
                 GwyDataMismatchFlags *result)
{
    /* Resolution */
    if ((check & GWY_DATA_MISMATCH_RES) && !(*result & GWY_DATA_MISMATCH_RES)) {
        if (res1 != res2)
            *result |= GWY_DATA_MISMATCH_RES;
    }
}

static void
check_basic_properties(gint res1, gint res2,
                       gdouble real1, gdouble real2,
                       GwyDataMismatchFlags check,
                       GwyDataMismatchFlags *result)
{
    check_resolution(res1, res2, check, result);

    /* Real size */
    if (check & GWY_DATA_MISMATCH_REAL && !(*result & GWY_DATA_MISMATCH_REAL)) {
        /* Keeps the condition in negative form to catch NaNs and odd values as incompatible. */
        if (!(fabs(log(real1/real2)) <= EPSILON))
            *result |= GWY_DATA_MISMATCH_REAL;
    }

    /* Measure */
    if (check & GWY_DATA_MISMATCH_MEASURE && !(*result & GWY_DATA_MISMATCH_MEASURE)) {
        if (!(fabs(log(real1/res1*res2/real2)) <= EPSILON))
            *result |= GWY_DATA_MISMATCH_MEASURE;
    }
}

/* Check if two SI Units are equal, accepting also NULLs and considering them equal to empty units. */
static gboolean
units_are_equal(GwyUnit *unit1, GwyUnit *unit2)
{
    if (unit1 == unit2)
        return TRUE;
    if (!unit1)
        return gwy_unit_equal_string(unit2, NULL);
    if (!unit2)
        return gwy_unit_equal_string(unit1, NULL);
    return gwy_unit_equal(unit1, unit2);
}

/**
 * gwy_field_is_incompatible:
 * @field1: A data field.
 * @field2: Another data field.
 * @check: The compatibility tests to perform.
 *
 * Checks whether two data fields are compatible.
 *
 * Returns: Zero if all tested properties are compatible.  Flags corresponding to failed tests if data fields are not
 *          compatible in some of the tested aspects.
 **/
GwyDataMismatchFlags
gwy_field_is_incompatible(GwyField *field1,
                          GwyField *field2,
                          GwyDataMismatchFlags check)
{
    GwyDataMismatchFlags result = 0;

    g_return_val_if_fail(GWY_IS_FIELD(field1), check);
    g_return_val_if_fail(GWY_IS_FIELD(field2), check);

    check_basic_properties(field1->xres, field2->xres, field1->xreal, field2->xreal,
                           check, &result);
    check_basic_properties(field1->yres, field2->yres, field1->yreal, field2->yreal,
                           check, &result);

    GwyFieldPrivate *priv1 = field1->priv, *priv2 = field2->priv;
    if ((check & GWY_DATA_MISMATCH_LATERAL) && !units_are_equal(priv1->unit_xy, priv2->unit_xy))
        result |= GWY_DATA_MISMATCH_LATERAL;

    if ((check & GWY_DATA_MISMATCH_VALUE) && !units_are_equal(priv1->unit_z, priv2->unit_z))
        result |= GWY_DATA_MISMATCH_VALUE;

    return result;
}

/**
 * gwy_nield_is_incompatible:
 * @nield1: A number field.
 * @nield2: Another number field.
 * @check: The compatibility tests to perform.
 *
 * Checks whether two number fields are compatible.
 *
 * Returns: Zero if all tested properties are compatible.  Flags corresponding to failed tests if number fields are not
 *          compatible in some of the tested aspects.
 **/
GwyDataMismatchFlags
gwy_nield_is_incompatible(GwyNield *nield1,
                          GwyNield *nield2,
                          GwyDataMismatchFlags check)
{
    GwyDataMismatchFlags result = 0;

    g_return_val_if_fail(GWY_IS_NIELD(nield1), check);
    g_return_val_if_fail(GWY_IS_NIELD(nield2), check);

    check_resolution(nield1->xres, nield2->xres, check, &result);
    check_resolution(nield1->yres, nield2->yres, check, &result);

    return result;
}

/**
 * gwy_line_is_incompatible:
 * @line1: A data line.
 * @line2: Another data line.
 * @check: The compatibility tests to perform.
 *
 * Checks whether two data lines are compatible.
 *
 * Returns: Zero if all tested properties are compatible.  Flags corresponding to failed tests if data lines are not
 *          compatible in some of the tested aspects.
 **/
GwyDataMismatchFlags
gwy_line_is_incompatible(GwyLine *line1,
                         GwyLine *line2,
                         GwyDataMismatchFlags check)
{
    GwyDataMismatchFlags result = 0;

    g_return_val_if_fail(GWY_IS_LINE(line1), check);
    g_return_val_if_fail(GWY_IS_LINE(line2), check);

    check_basic_properties(line1->res, line2->res, line1->real, line2->real, check, &result);

    GwyLinePrivate *priv1 = line1->priv, *priv2 = line2->priv;
    if ((check & GWY_DATA_MISMATCH_LATERAL) && !units_are_equal(priv1->unit_x, priv2->unit_x))
        result |= GWY_DATA_MISMATCH_LATERAL;

    if ((check & GWY_DATA_MISMATCH_VALUE) && !units_are_equal(priv1->unit_y, priv2->unit_y))
        result |= GWY_DATA_MISMATCH_VALUE;

    return result;
}

static gboolean
lines_equal(GwyLine *line1, GwyLine *line2)
{
    if (gwy_line_is_incompatible(line1, line2, GWY_DATA_MISMATCH_RES | GWY_DATA_MISMATCH_VALUE))
        return FALSE;

    gint res = line1->res;
    const gdouble *data1 = line1->priv->data;
    const gdouble *data2 = line2->priv->data;
    for (gint i = 0; i < res; i++) {
        /* FIXME: Add a tolerance here? */
        if (data2[i] != data1[i])
            return FALSE;
    }

    return TRUE;
}

/**
 * gwy_brick_is_incompatible:
 * @brick1: A data brick.
 * @brick2: Another data brick.
 * @check: The compatibility tests to perform.
 *
 * Checks whether two data bricks are compatible.
 *
 * Real dimensions are checked without regard to calibration.  Calibrations are considered compatible if either both
 * exist and are identical or none exists.
 *
 * Returns: Zero if all tested properties are compatible.  Flags corresponding to failed tests if bricks are not
 *          compatible in some of the tested aspects.
 **/
GwyDataMismatchFlags
gwy_brick_is_incompatible(GwyBrick *brick1,
                          GwyBrick *brick2,
                          GwyDataMismatchFlags check)
{
    GwyDataMismatchFlags result = 0;

    g_return_val_if_fail(GWY_IS_BRICK(brick1), check);
    g_return_val_if_fail(GWY_IS_BRICK(brick2), check);

    check_basic_properties(brick1->xres, brick2->xres, brick1->xreal, brick2->xreal, check, &result);
    check_basic_properties(brick1->yres, brick2->yres, brick1->yreal, brick2->yreal, check, &result);
    check_basic_properties(brick1->zres, brick2->zres, brick1->zreal, brick2->zreal, check, &result);

    GwyBrickPrivate *priv1 = brick1->priv, *priv2 = brick2->priv;
    if ((check & GWY_DATA_MISMATCH_LATERAL)
        && (!units_are_equal(priv1->unit_x, priv2->unit_x)
            || !units_are_equal(priv1->unit_y, priv2->unit_y)
            || !units_are_equal(priv1->unit_z, priv2->unit_z)))
        result |= GWY_DATA_MISMATCH_LATERAL;

    if (check & GWY_DATA_MISMATCH_VALUE && !units_are_equal(priv1->unit_w, priv2->unit_w))
        result |= GWY_DATA_MISMATCH_VALUE;

    /* Z-calibration. */
    if (check & GWY_DATA_MISMATCH_AXISCAL) {
        GwyLine *zcal1 = gwy_brick_get_zcalibration(brick1);
        GwyLine *zcal2 = gwy_brick_get_zcalibration(brick2);
        if ((zcal1 && !zcal2) || (!zcal1 && zcal2))
            result |= GWY_DATA_MISMATCH_AXISCAL;
        else if (zcal1 && zcal2 && !lines_equal(zcal1, zcal2))
            result |= GWY_DATA_MISMATCH_AXISCAL;
        else if (brick1->zres != brick2->zres)
            result |= GWY_DATA_MISMATCH_AXISCAL;
    }

    return result;
}

/**
 * gwy_scatter_is_incompatible:
 * @scatter1: A scattered data.
 * @scatter2: Another scattered data.
 * @check: The compatibility tests to perform.
 *
 * Checks whether two scattered data are compatible.
 *
 * Scattered data do not have pixels, so the only meaningful tests are for units, %GWY_DATA_MISMATCH_POINTS
 * for matching XY coordinate sets and %GWY_DATA_MISMATCH_NCURVES to check if the number of values is the same. The
 * last is is barely meaningful -- if you check for data units, it already implies the same number of values.
 *
 * Returns: Zero if all tested properties are compatible.  Flags corresponding to failed tests if scattered data are
 *          not compatible in some of the tested aspects.
 **/
GwyDataMismatchFlags
gwy_scatter_is_incompatible(GwyScatter *scatter1,
                            GwyScatter *scatter2,
                            GwyDataMismatchFlags check)
{
    GwyDataMismatchFlags result = 0;

    g_return_val_if_fail(GWY_IS_SCATTER(scatter1), check);
    g_return_val_if_fail(GWY_IS_SCATTER(scatter2), check);

    GwyScatterPrivate *priv1 = scatter1->priv, *priv2 = scatter2->priv;
    if ((check & GWY_DATA_MISMATCH_LATERAL) && !units_are_equal(priv1->unit_xy, priv2->unit_xy))
        result |= GWY_DATA_MISMATCH_LATERAL;

    if ((check & GWY_DATA_MISMATCH_NCURVES) && priv1->nvalues != priv2->nvalues)
        result |= GWY_DATA_MISMATCH_NCURVES;

    if (check & GWY_DATA_MISMATCH_VALUE) {
        gint n = gwy_scatter_get_n_values(scatter1);
        if (gwy_scatter_get_n_values(scatter2) != n)
            result |= GWY_DATA_MISMATCH_VALUE;
        else {
            for (gint i = 0; i < n; i++) {
                if (!units_are_equal(gwy_scatter_get_unit_data(scatter1, i), gwy_scatter_get_unit_data(scatter2, i))) {
                    result |= GWY_DATA_MISMATCH_VALUE;
                    break;
                }
            }
        }
    }

    if (check & GWY_DATA_MISMATCH_POINTS)
        result |= _gwy_scatter_check_xy_incompatible(scatter1, scatter2);

    return result;
}

/**
 * gwy_lawn_is_incompatible:
 * @lawn1: A data lawn.
 * @lawn2: Another data lawn.
 * @check: The compatibility tests to perform.
 *
 * Checks whether two data lawns are compatible.
 *
 * Dimensions are checked only in the plane.  To check if the curve lengths match, use the
 * %GWY_DATA_MISMATCH_CURVELEN flag.  Use %GWY_DATA_MISMATCH_NCURVES to check if the two lawns have the
 * same number of curves.
 *
 * Returns: Zero if all tested properties are compatible.  Flags corresponding to failed tests if curve maps are not
 *          compatible in some of the tested aspects.
 **/
GwyDataMismatchFlags
gwy_lawn_is_incompatible(GwyLawn *lawn1,
                         GwyLawn *lawn2,
                         GwyDataMismatchFlags check)
{
    GwyDataMismatchFlags result = 0;

    g_return_val_if_fail(GWY_IS_LAWN(lawn1), check);
    g_return_val_if_fail(GWY_IS_LAWN(lawn2), check);

    check_basic_properties(lawn1->xres, lawn2->xres, lawn1->xreal, lawn2->xreal, check, &result);
    check_basic_properties(lawn1->yres, lawn2->yres, lawn1->yreal, lawn2->yreal, check, &result);

    GwyLawnPrivate *priv1 = lawn1->priv, *priv2 = lawn2->priv;
    if ((check & GWY_DATA_MISMATCH_LATERAL) && !units_are_equal(priv1->unit_xy, priv2->unit_xy))
        result |= GWY_DATA_MISMATCH_LATERAL;

    if (check & GWY_DATA_MISMATCH_NCURVES && gwy_lawn_get_n_curves(lawn1) != gwy_lawn_get_n_curves(lawn2))
        result |= GWY_DATA_MISMATCH_NCURVES;

    if (check & GWY_DATA_MISMATCH_VALUE) {
        gint n = gwy_lawn_get_n_curves(lawn1);
        if (gwy_lawn_get_n_curves(lawn2) != n)
            result |= GWY_DATA_MISMATCH_VALUE;
        else {
            for (gint i = 0; i < n; i++) {
                if (!units_are_equal(gwy_lawn_get_unit_curve(lawn1, i), gwy_lawn_get_unit_curve(lawn2, i))) {
                    result |= GWY_DATA_MISMATCH_VALUE;
                    break;
                }
            }
        }
    }

    if (check & GWY_DATA_MISMATCH_CURVELEN) {
        if (lawn1->xres != lawn2->xres || lawn1->yres != lawn2->yres)
            result |= GWY_DATA_MISMATCH_CURVELEN;
        else {
            gint xres = lawn1->xres, yres = lawn1->yres;
            for (gint i = 0; i < xres*yres; i++) {
                if (gwy_lawn_get_curve_length(lawn1, i % xres, i/xres)
                    != gwy_lawn_get_curve_length(lawn2, i % xres, i/xres)) {
                    result |= GWY_DATA_MISMATCH_CURVELEN;
                    break;
                }
            }
        }
    }

    return result;
}

/**
 * gwy_field_is_incompatible_with_brick_xy:
 * @field: A two-dimensional data field.
 * @brick: A three-dimensional data brick.
 * @check: The compatibility tests to perform.
 *
 * Checks whether a data field is compatible with brick XY-planes.
 *
 * Returns: Zero if all tested properties are compatible.  Flags corresponding to failed tests if the data objects are
 *          not compatible in some of the tested aspects.
 **/
GwyDataMismatchFlags
gwy_field_is_incompatible_with_brick_xy(GwyField *field,
                                        GwyBrick *brick,
                                        GwyDataMismatchFlags check)
{
    GwyDataMismatchFlags result = 0;

    g_return_val_if_fail(GWY_IS_FIELD(field), check);
    g_return_val_if_fail(GWY_IS_BRICK(brick), check);

    check_basic_properties(field->xres, brick->xres, field->xreal, brick->xreal, check, &result);
    check_basic_properties(field->yres, brick->yres, field->yreal, brick->yreal, check, &result);

    GwyFieldPrivate *fpriv = field->priv;
    GwyBrickPrivate *bpriv = brick->priv;
    if ((check & GWY_DATA_MISMATCH_LATERAL)
        && (!units_are_equal(fpriv->unit_xy, bpriv->unit_x)
            || !units_are_equal(fpriv->unit_xy, bpriv->unit_y)))
        result |= GWY_DATA_MISMATCH_LATERAL;

    if (check & GWY_DATA_MISMATCH_VALUE
        && !units_are_equal(fpriv->unit_z, bpriv->unit_w))
        result |= GWY_DATA_MISMATCH_VALUE;

    return result;
}

/**
 * gwy_line_is_incompatible_with_brick_z:
 * @line: A one-dimensional data line.
 * @brick: A three-dimensional data brick.
 * @check: The compatibility tests to perform.
 *
 * Checks whether a data line is compatible with brick Z-profiles.
 *
 * If @check includes %GWY_DATA_MISMATCH_REAL or %GWY_DATA_MISMATCH_LATERAL but not
 * %GWY_DATA_MISMATCH_AXISCAL, @line is simply compared to @brick in the Z direction.
 *
 * If you include %GWY_DATA_MISMATCH_AXISCAL and @brick has a Z-calibration data line, then the value range and
 * units of this data line are compared to @line.  This may not be very useful.
 *
 * Returns: Zero if all tested properties are compatible.  Flags corresponding to failed tests if the data objects are
 *          not compatible in some of the tested aspects.
 **/
GwyDataMismatchFlags
gwy_line_is_incompatible_with_brick_z(GwyLine *line,
                                      GwyBrick *brick,
                                      GwyDataMismatchFlags check)
{
    GwyDataMismatchFlags result = 0;
    GwyLine *zcal;

    g_return_val_if_fail(GWY_IS_LINE(line), check);
    g_return_val_if_fail(GWY_IS_BRICK(brick), check);

    GwyBrickPrivate *bpriv = brick->priv;
    GwyLinePrivate *lpriv = line->priv;
    if (check & GWY_DATA_MISMATCH_VALUE && !units_are_equal(lpriv->unit_y, bpriv->unit_w))
        result |= GWY_DATA_MISMATCH_VALUE;

    /* Without zcalibration compare directly to the brick. */
    zcal = gwy_brick_get_zcalibration(brick);
    if (!zcal || !(check & GWY_DATA_MISMATCH_AXISCAL)) {
        check_basic_properties(line->res, brick->xres, line->real, brick->xreal, check, &result);

        if ((check & GWY_DATA_MISMATCH_LATERAL) && !units_are_equal(lpriv->unit_x, bpriv->unit_z))
            result |= GWY_DATA_MISMATCH_LATERAL;

        return result;
    }

    /* With Z-calibration we compare to @zcal.  The *values* of @zcal are the same thing as *coordinates* of
     * @line.  So this is a mess and possibly not useful at all. */
    GwyLinePrivate *zpriv = zcal->priv;
    g_assert(zcal->res == brick->zres);
    check_basic_properties(line->res, zcal->res, line->real, zpriv->data[zcal->res-1] - zpriv->data[0],
                           check, &result);

    if ((check & GWY_DATA_MISMATCH_LATERAL) && !units_are_equal(lpriv->unit_x, zpriv->unit_y))
        result |= GWY_DATA_MISMATCH_LATERAL;

    return result;
}

/**
 * gwy_field_is_incompatible_with_lawn:
 * @field: A two-dimensional data field.
 * @lawn: A lawn curve map object.
 * @check: The compatibility tests to perform.
 *
 * Checks whether a data field is compatible with lawn in the XY-plane.
 *
 * Field value units are not compared to curves. Value-related flags are invalid in @check.
 *
 * Returns: Zero if all tested properties are compatible.  Flags corresponding to failed tests if the data objects are
 *          not compatible in some of the tested aspects.
 **/
GwyDataMismatchFlags
gwy_field_is_incompatible_with_lawn(GwyField *field,
                                    GwyLawn *lawn,
                                    GwyDataMismatchFlags check)
{
    GwyDataMismatchFlags result = 0;

    g_return_val_if_fail(GWY_IS_FIELD(field), check);
    g_return_val_if_fail(GWY_IS_LAWN(lawn), check);

    check_basic_properties(field->xres, lawn->xres, field->xreal, lawn->xreal, check, &result);
    check_basic_properties(field->yres, lawn->yres, field->yreal, lawn->yreal, check, &result);

    GwyFieldPrivate *fpriv = field->priv;
    GwyLawnPrivate *lpriv = lawn->priv;
    if ((check & GWY_DATA_MISMATCH_LATERAL) && !units_are_equal(fpriv->unit_xy, lpriv->unit_xy))
        result |= GWY_DATA_MISMATCH_LATERAL;

    return result;
}

/**
 * gwy_nield_is_incompatible_with_field:
 * @nield: A two-dimensional number field.
 * @field: A two-dimensional data field.
 * @check: The compatibility tests to perform.
 *
 * Checks whether a number field is compatible with a data field.
 *
 * Since a #GwyNield does not have real dimensions and units, only pixel resolutions make sense to check.
 *
 * Returns: Zero if all tested properties are compatible.  Flags corresponding to failed tests if the data objects are
 *          not compatible in some of the tested aspects.
 **/
GwyDataMismatchFlags
gwy_nield_is_incompatible_with_field(GwyNield *nield,
                                     GwyField *field,
                                     GwyDataMismatchFlags check)
{
    GwyDataMismatchFlags result = 0;

    g_return_val_if_fail(GWY_IS_NIELD(nield), check);
    g_return_val_if_fail(GWY_IS_FIELD(field), check);

    check_resolution(nield->xres, field->xres, check, &result);
    check_resolution(nield->yres, field->yres, check, &result);

    return result;
}

/**
 * gwy_nield_is_incompatible_with_brick_xy:
 * @nield: A two-dimensional number field.
 * @brick: A three-dimensional data brick.
 * @check: The compatibility tests to perform.
 *
 * Checks whether a number field is compatible with brick XY-planes.
 *
 * Since a #GwyNield does not have real dimensions and units, only pixel resolutions make sense to check.
 *
 * Returns: Zero if all tested properties are compatible.  Flags corresponding to failed tests if the data objects are
 *          not compatible in some of the tested aspects.
 **/
GwyDataMismatchFlags
gwy_nield_is_incompatible_with_brick_xy(GwyNield *nield,
                                        GwyBrick *brick,
                                        GwyDataMismatchFlags check)
{
    GwyDataMismatchFlags result = 0;

    g_return_val_if_fail(GWY_IS_NIELD(nield), check);
    g_return_val_if_fail(GWY_IS_BRICK(brick), check);

    check_resolution(nield->xres, brick->xres, check, &result);
    check_resolution(nield->yres, brick->yres, check, &result);

    return result;
}

/**
 * gwy_nield_is_incompatible_with_lawn:
 * @nield: A two-dimensional number field.
 * @lawn: A lawn curve map object.
 * @check: The compatibility tests to perform.
 *
 * Checks whether a number field is compatible with lawn in the XY-plane.
 *
 * Since a #GwyNield does not have real dimensions and units, only pixel resolutions make sense to check.
 *
 * Returns: Zero if all tested properties are compatible.  Flags corresponding to failed tests if the data objects are
 *          not compatible in some of the tested aspects.
 **/
GwyDataMismatchFlags
gwy_nield_is_incompatible_with_lawn(GwyNield *nield,
                                    GwyLawn *lawn,
                                    GwyDataMismatchFlags check)
{
    GwyDataMismatchFlags result = 0;

    g_return_val_if_fail(GWY_IS_NIELD(nield), check);
    g_return_val_if_fail(GWY_IS_LAWN(lawn), check);

    check_resolution(nield->xres, lawn->xres, check, &result);
    check_resolution(nield->yres, lawn->yres, check, &result);

    return result;
}

static gboolean
field_compatibility_check_common(GwyField *result,
                                 GwyField *operand1,
                                 GwyField *operand2,
                                 gdouble **resdata,
                                 gdouble **op1data,
                                 gdouble **op2data,
                                 gsize *n)
{
    if (!_gwy_field_check_other(result, operand1, FALSE)
        || !_gwy_field_check_other(result, operand2, FALSE))
        return FALSE;
    *resdata = result->priv->data;
    *op1data = operand1->priv->data;
    *op2data = operand2->priv->data;
    *n = (gsize)result->xres * (gsize)result->yres;
    return TRUE;
}

static gboolean
line_compatibility_check_common(GwyLine *result,
                                GwyLine *operand1,
                                GwyLine *operand2,
                                gdouble **resdata,
                                gdouble **op1data,
                                gdouble **op2data,
                                gsize *n)
{
    GwyDataMismatchFlags flags = GWY_DATA_MISMATCH_RES;
    g_return_val_if_fail(GWY_IS_LINE(result), FALSE);
    g_return_val_if_fail(!gwy_line_is_incompatible(result, operand1, flags), FALSE);
    g_return_val_if_fail(!gwy_line_is_incompatible(result, operand2, flags), FALSE);
    *resdata = result->priv->data;
    *op1data = operand1->priv->data;
    *op2data = operand2->priv->data;
    *n = (gsize)result->res;
    return TRUE;
}

/**
 * SECTION: arithmetic
 * @title: Arithmetic
 * @short_description: Arithmetic operations with data
 *
 * Data arithmetic functions perform simple operations combining several data fields.  Their sizes have to be
 * size-compatible, i.e. gwy_field_is_incompatible(operand1, operand2, GWY_DATA_MISMATCH_RES) must pass
 * and the same must hold for the data field to store the result to.
 *
 * Functions gwy_field_is_incompatible(), gwy_line_check_compatibility() and
 * gwy_brick_is_incompatible() simplify testing compatibility of data fields, lines and bricks, respectively.
 **/

/**
 * GwyDataMismatchFlags:
 * @GWY_DATA_MISMATCH_RES: Pixel sizes.
 * @GWY_DATA_MISMATCH_REAL: Real (physical) dimensions.
 * @GWY_DATA_MISMATCH_MEASURE: Real to pixel ratios.
 * @GWY_DATA_MISMATCH_LATERAL: Units of lateral dimensions.
 * @GWY_DATA_MISMATCH_VALUE: Units of values (for all curves in the case of lawns).
 * @GWY_DATA_MISMATCH_AXISCAL: Axis calibrations.  At present it only makes sense for #GwyBrick which can have
 *                             Z-calibrations.
 * @GWY_DATA_MISMATCH_NCURVES: The number of lawn curves.
 * @GWY_DATA_MISMATCH_CURVELEN: Lengths of curves in all lawn pixels.
 * @GWY_DATA_MISMATCH_POINTS: Abscissa points or coordinates for irregular data (e.g. #GwyScatter).
 *
 * Compatibility flags for data objects, such as lines, fields, bricks or lawns.
 *
 * The flag set defines all possible incompatibilities that can be checked. However, for every object only a subset
 * is meaningful: images have pixes but do not have curve lengths, scattered data have lateral units but do not have
 * pixels, etc.
 **/

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