/*
 *  $Id: line.c 30044 2026-06-10 19:12:24Z vojtakl4 $
 *  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/math.h"
#include "libgwyddion/serializable-utils.h"
#include "libgwyddion/field.h"
#include "libgwyddion/linestats.h"
#include "libgwyddion/interpolation.h"

#include "libgwyddion/internal.h"

#define TYPE_NAME "GwyLine"

enum {
    SGNL_DATA_CHANGED,
    NUM_SIGNALS
};

enum {
    ITEM_RES, ITEM_REAL, ITEM_OFF,
    ITEM_UNIT_X, ITEM_UNIT_Y,
    ITEM_DATA,
    NUM_ITEMS
};

static void             finalize              (GObject *object);
static void             alloc_data            (GwyLine *line,
                                               gint res,
                                               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 guint signals[NUM_SIGNALS];
static GObjectClass *parent_class = NULL;

static GwySerializableItem serializable_items[NUM_ITEMS] = {
    { .name = "res",    .ctype = GWY_SERIALIZABLE_INT32,        },
    { .name = "real",   .ctype = GWY_SERIALIZABLE_DOUBLE,       },
    { .name = "off",    .ctype = GWY_SERIALIZABLE_DOUBLE,       },
    { .name = "unit_x", .ctype = GWY_SERIALIZABLE_OBJECT,       },
    { .name = "unit_y", .ctype = GWY_SERIALIZABLE_OBJECT,       },
    { .name = "data",   .ctype = GWY_SERIALIZABLE_DOUBLE_ARRAY,
                        .flags = GWY_SERIALIZABLE_BIG_DATA      },
};

G_DEFINE_TYPE_WITH_CODE(GwyLine, gwy_line, G_TYPE_OBJECT,
                        G_ADD_PRIVATE(GwyLine)
                        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_RES].aux.pspec = g_param_spec_int(serializable_items[ITEM_RES].name, NULL, NULL,
                                                              1, G_MAXINT, 1,
                                                              G_PARAM_STATIC_STRINGS);
    serializable_items[ITEM_REAL].aux.pspec = g_param_spec_double(serializable_items[ITEM_REAL].name, NULL, NULL,
                                                                  G_MINDOUBLE, G_MAXDOUBLE, 1.0,
                                                                  G_PARAM_STATIC_STRINGS);
    /* No need to add pspecs for the offset because it has unlimited values and default zero. */
    gwy_fill_serializable_defaults_pspec(serializable_items, NUM_ITEMS, FALSE);
    serializable_items[ITEM_UNIT_X].aux.object_type = GWY_TYPE_UNIT;
    serializable_items[ITEM_UNIT_Y].aux.object_type = GWY_TYPE_UNIT;
}

static void
gwy_line_class_init(GwyLineClass *klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
    GType type = G_TYPE_FROM_CLASS(klass);

    parent_class = gwy_line_parent_class;

    gobject_class->finalize = finalize;

    /**
     * GwyLine::data-changed:
     * @gwyline: The #GwyLine which received the signal.
     *
     * The ::data-changed signal is never emitted by the object itself.  It is intended as a means to notify object
     * users they should update themselves, usually when a computation modifying the data is finished.
     */
    signals[SGNL_DATA_CHANGED] = g_signal_new("data-changed", type,
                                              G_SIGNAL_RUN_FIRST,
                                              G_STRUCT_OFFSET(GwyLineClass, 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_line_init(GwyLine *line)
{
    line->priv = gwy_line_get_instance_private(line);
    alloc_data(line, 1, TRUE);
    line->real = 1.0;
}

static void
finalize(GObject *object)
{
    GwyLine *line = (GwyLine*)object;
    GwyLinePrivate *priv = line->priv;

    g_clear_object(&priv->unit_x);
    g_clear_object(&priv->unit_y);
    g_free(priv->data);

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

static void
alloc_data(GwyLine *line, gint res, gboolean nullme)
{
    GwyLinePrivate *priv = line->priv;

    if (line->res == res) {
        if (nullme)
            gwy_clear(priv->data, res);
        return;
    }

    GWY_FREE(priv->data);
    priv->data = (nullme ? g_new0(gdouble, res) : g_new(gdouble, res));
    line->res = res;
}

static void
copy_info(GwyLine *src, GwyLine *dest)
{
    dest->real = src->real;
    dest->off = src->off;
    gwy_line_copy_units(src, dest);
}

/**
 * gwy_line_new: (constructor)
 * @res: Resolution, i.e., the number of samples.
 * @real: Real physical dimension.
 * @nullme: Whether the data line should be initialized to zeroes. If %FALSE, the data will not be initialized.
 *
 * Creates a new data line.
 *
 * Returns: (transfer full):
 *          A newly created data line.
 **/
GwyLine*
gwy_line_new(gint res, gdouble real, gboolean nullme)
{
    GwyLine *line = g_object_new(GWY_TYPE_LINE, NULL);

    alloc_data(line, res, nullme);
    line->real = real;

    return line;
}

/**
 * gwy_line_new_alike:
 * @model: A data line to take resolutions and units from.
 * @nullme: Whether the data line should be initialized to zeroes. If %FALSE, the data will not be initialized.
 *
 * Creates a new data line similar to an existing one.
 *
 * Use gwy_line_duplicate() if you want to copy a data line including data.
 *
 * Returns: (transfer full):
 *          A newly created data line.
 **/
GwyLine*
gwy_line_new_alike(GwyLine *model,
                   gboolean nullme)
{
    GwyLine *line = g_object_new(GWY_TYPE_LINE, NULL);

    g_return_val_if_fail(GWY_IS_LINE(model), line);

    alloc_data(line, model->res, nullme);
    copy_info(model, line);

    return line;
}

/**
 * gwy_line_new_resampled:
 * @line: A data line.
 * @res: Desired resolution.
 * @interpolation: Interpolation method to use.
 *
 * Creates a new data line by resampling an existing one.
 *
 * This method is equivalent to gwy_line_duplicate() followed by gwy_line_resample(), but it is more
 * efficient.
 *
 * Returns: (transfer full):
 *          A newly created data line.
 **/
GwyLine*
gwy_line_new_resampled(GwyLine *line,
                       gint res,
                       GwyInterpolationType interpolation)
{
    g_return_val_if_fail(GWY_IS_LINE(line), NULL);

    if (line->res == res)
        return gwy_line_copy(line);

    g_return_val_if_fail(res > 0, NULL);

    GwyLine *result = gwy_line_new(res, line->real, FALSE);
    result->off = line->off;
    gwy_line_copy_units(line, result);
    gwy_interpolation_resample_block_1d(line->res, line->priv->data, result->res, result->priv->data,
                                        interpolation, TRUE);

    return result;
}

/**
 * gwy_line_data_changed:
 * @line: A data line.
 *
 * Emits signal "data_changed" on a data line.
 **/
void
gwy_line_data_changed(GwyLine *line)
{
    g_signal_emit(line, signals[SGNL_DATA_CHANGED], 0);
}

/**
 * gwy_line_resample:
 * @line: A data line.
 * @res: Desired resolution.
 * @interpolation: Interpolation method to use.
 *
 * Resamples a data line.
 *
 * In other words changes the size of one dimensional field related with data line. The original values are used for
 * resampling using a requested interpolation alorithm.
 **/
void
gwy_line_resample(GwyLine *line,
                  gint res,
                  GwyInterpolationType interpolation)
{
    g_return_if_fail(GWY_IS_LINE(line));
    g_return_if_fail(res > 0);
    if (res == line->res)
        return;

    GwyLinePrivate *priv = line->priv;
    gdouble *bdata = g_new(gdouble, res);
    gwy_interpolation_resample_block_1d(line->res, priv->data, res, bdata, interpolation, FALSE);
    g_free(priv->data);
    priv->data = bdata;
    line->res = res;
}

/**
 * gwy_line_crop:
 * @line: A data line.
 * @pos: Index where to start.
 * @len: Length of extracted segment.
 *
 * Crops a data line to a smaller length.
 *
 * The real size is updated and the offset is reset to zero.
 **/
void
gwy_line_crop(GwyLine *line, gint pos, gint len)
{
    if (!_gwy_line_check_part(line, pos, len, FALSE))
        return;

    line->off = 0.0;

    gint res = line->res;
    if (len == res)
        return;

    gdouble *rdata = g_new(gdouble, len);
    gwy_assign(rdata, line->priv->data + pos, len);
    GWY_SWAP(gdouble*, line->priv->data, rdata);
    g_free(rdata);

    line->real *= (gdouble)len/(double)res;
    line->res = len;
    gwy_line_invalidate(line);
}

/**
 * gwy_line_resize:
 * @line: A data line.
 * @res: Desired resolution.
 *
 * Resizes a data line to different size, discarding data.
 *
 * The real size and offset are kept unchanged. The line contents becomes undefined. The function just makes sure
 * @line has the correct pixel dimension.
 **/
void
gwy_line_resize(GwyLine *line,
                gint res)
{
    g_return_if_fail(GWY_IS_LINE(line));
    g_return_if_fail(res > 0);
    alloc_data(line, res, FALSE);
    gwy_line_invalidate(line);
}

/**
 * gwy_line_make_alike:
 * @line: A data line.
 * @model: A data line to take resolutions and units from.
 *
 * Resizes a data field and updates its properties to match another data field.
 *
 * Physical dimensions, offsets and units are all updated to match @model. Since the function can resize @field, its
 * contents must be considered undefined. See gwy_field_resize() for just pixel resize and gwy_field_assign() for
 * making one field completely identical to another.
 **/
void
gwy_line_make_alike(GwyLine *line,
                    GwyLine *model)
{
    g_return_if_fail(GWY_IS_LINE(line));
    if (model == line)
        return;
    g_return_if_fail(GWY_IS_LINE(model));

    alloc_data(line, model->res, FALSE);
    copy_info(model, line);
    gwy_line_invalidate(line);
}

/**
 * gwy_line_part_extract:
 * @line: A data line.
 * @pos: Index where to start.
 * @len: Length of extracted segment.
 *
 * Extracts a part of a data line to a new data line.
 *
 * Returns: (transfer full): The extracted area as a newly created data line.
 **/
GwyLine*
gwy_line_part_extract(GwyLine *line,
                      gint pos,
                      gint len)
{
    if (!_gwy_line_check_part(line, pos, len, FALSE))
        return NULL;

    gint res = line->res;
    if (pos == 0 && len == res)
        return gwy_line_copy(line);

    GwyLine *result = gwy_line_new(len, line->real*len/res, FALSE);
    gwy_assign(result->priv->data, line->priv->data + pos, len);
    gwy_line_copy_units(line, result);

    return result;
}

/**
 * gwy_line_copy_data:
 * @src: Source data line.
 * @dest: Destination data line.
 *
 * Copies the contents of a data line to another already allocated data line of the same size.
 *
 * Only the data points are copied.  To make a data line completely identical to another, including units and change
 * of dimensions, you can use gwy_line_assign().
 **/
void
gwy_line_copy_data(GwyLine *src, GwyLine *dest)
{
    g_return_if_fail(GWY_IS_LINE(src));
    g_return_if_fail(GWY_IS_LINE(dest));
    g_return_if_fail(src->res == dest->res);

    if (src == dest)
        return;

    gwy_assign(dest->priv->data, src->priv->data, src->res);
    gwy_line_invalidate(dest);
}

/**
 * gwy_line_part_copy:
 * @src: Source data line.
 * @dest: Destination data line.
 * @pos: Index where to start in @src.
 * @len: Length of copied segment. You can also pass -1 for full @src size.
 * @destpos: Destination position in @dest.
 *
 * Copies a segment from one data line to another.
 *
 * The part starts at @pos in @src and its dimension is @len. It is copied to @dest starting from @destpos.
 *
 * The source part has to be completely contained in @src.  No assumptions are made about destination position,
 * however. Parts of the source part sticking out the destination data field @dest are cut off.
 *
 * If @src is the same data line as @dest, the parts may not overlap.
 **/
void
gwy_line_part_copy(GwyLine *src, GwyLine *dest,
                   gint pos, gint len, gint destpos)
{
    g_return_if_fail(GWY_IS_LINE(src));
    g_return_if_fail(GWY_IS_LINE(dest));
    if (len == -1)
        len = src->res;
    g_return_if_fail(pos >= 0 && len >= 0 && pos + len <= src->res);

    if (destpos + len > dest->res)
        len = dest->res - destpos;
    if (destpos < 0) {
        pos -= destpos;
        len += destpos;
        destpos = 0;
    }
    if (len <= 0)
        return;

    gwy_line_invalidate(dest);

    const gdouble *sdata = src->priv->data;
    gdouble *ddata = dest->priv->data;
    gwy_assign(ddata + destpos, sdata + pos, len);
}

/**
 * gwy_line_get_dval:
 * @line: A data line.
 * @x: Position in data line in range [0, resolution].  If the value is outside this range, the nearest border value
 *     is returned.
 * @interpolation: Interpolation method to use.
 *
 * Gets interpolated value at arbitrary data line point indexed by pixel coordinates.
 *
 * Note pixel values are centered in intervals [@j, @j+1], so to get the same value as
 * gwy_line_get_val(@line, @j) returns, it's necessary to add 0.5:
 * gwy_line_get_dval(@line, * @j+0.5, @interpolation).
 *
 * See also gwy_line_get_dval_real() that does the same, but takes real coordinates.
 *
 * Returns: Value interpolated in the data line.
 **/
gdouble
gwy_line_get_dval(GwyLine *line, gdouble x, GwyInterpolationType interpolation)
{
    g_return_val_if_fail(GWY_IS_LINE(line), 0.0);

    const gdouble *data = line->priv->data;
    x -= 0.5;    /* To centered pixel value */
    gint l = floor(x);
    if (G_UNLIKELY(l < 0))
        return data[0];
    if (G_UNLIKELY(l >= line->res - 1))
        return data[line->res - 1];

    gdouble rest = x - l;
    /*simple (and fast) methods*/
    if (interpolation == GWY_INTERPOLATION_ROUND)
        return data[l];
    else if (interpolation == GWY_INTERPOLATION_LINEAR)
        return (1.0 - rest)*data[l] + rest*data[l+1];

    /* Other 4point methods are very similar. */
    /* Use linear in border intervals. */
    if (l < 1 || l >= (line->res - 2))
        return (1.0 - rest)*data[l] + rest*data[l+1];

    return gwy_interpolation_interpolate_equidists(rest, data + l-1, interpolation);
}

/**
 * gwy_line_get_data:
 * @line: A data line.
 *
 * Gets the raw data buffer of a data line.
 *
 * The returned buffer is not guaranteed to be valid through whole data line life time.  Some function may change it,
 * most notably gwy_line_resize() and gwy_line_resample().
 *
 * This function invalidates any cached information, use gwy_line_get_data_const() if you are not going to change
 * the data.
 *
 * Returns: (type gpointer):
 *          The data as an array of doubles of length gwy_line_get_res().
 **/
gdouble*
gwy_line_get_data(GwyLine *line)
{
    g_return_val_if_fail(GWY_IS_LINE(line), NULL);
    gwy_line_invalidate(line);
    return line->priv->data;
}

/**
 * gwy_line_get_data_const:
 * @line: A data line.
 *
 * Gets the raw data buffer of a data line, read-only.
 *
 * The returned buffer is not guaranteed to be valid through whole data line life time.  Some function may change it,
 * most notably gwy_line_resize() and gwy_line_resample().
 *
 * Use gwy_line_get_data() if you want to change the data.
 *
 * Returns: The data as an array of doubles of length gwy_line_get_res().
 **/
const gdouble*
gwy_line_get_data_const(GwyLine *line)
{
    g_return_val_if_fail(GWY_IS_LINE(line), NULL);
    return (const gdouble*)line->priv->data;
}

/**
 * gwy_line_get_data_with_size:
 * @line: A data line.
 * @out_count: (out): Number of items in the returned buffer.
 *
 * Gets the raw data buffer of a data line. Used for gobject-introspection bindings.
 *
 * The returned buffer is not guaranteed to be valid through whole data line life time.  Some function may change it,
 * most notably gwy_line_resize() and gwy_line_resample().
 *
 * This function invalidates any cached information, use gwy_line_get_data_const() if you are not going to change
 * the data.
 *
 * Returns: (transfer none) (array length=out_count):
 *          The data as an array of doubles of length gwy_line_get_res().
 **/
gdouble*
gwy_line_get_data_with_size(GwyLine *line, gsize *out_count)
{
    g_return_val_if_fail(GWY_IS_LINE(line), NULL);
    gwy_line_invalidate(line);
    *out_count = (gsize)line->res;
    return line->priv->data;
}

/**
 * gwy_line_set_data_with_size:
 * @line: A data line.
 * @data: (array length=data_count): The data to be set as a pointer to an array of @data_count #gdoubles.
 * @data_count: Number of items in @data. It has to be equal to gwy_line_get_res(), else the function does nothing.
 *
 * Sets new values to the raw data buffer of a data line. Used for gobject-introspection bindings.
 *
 * This function invalidates any cached information, use gwy_line_get_data_const() if you are not going to change
 * the data.
 **/
void
gwy_line_set_data_with_size(GwyLine *line, gdouble *data, gsize data_count)
{
    g_return_if_fail(GWY_IS_LINE(line));
    if ((gsize)line->res == data_count) {
        gwy_line_invalidate(line);
	memcpy(line->priv->data, data, data_count*sizeof(gdouble));
    }
}



/**
 * gwy_line_invalidate:
 * @line: A data line.
 *
 * Invalidates cached data line stats.
 *
 * User code should rarely need this macro, as all #GwyLine methods do proper invalidation when they change data,
 * as well as gwy_line_get_data() does.
 *
 * However, if you get raw data with gwy_line_get_data() and then mix direct changes to it with calls to methods like
 * gwy_line_get_max(), you may need to explicitely invalidate cached values to let gwy_line_get_max() know it has to
 * recompute the maximum.
 **/
void
gwy_line_invalidate(G_GNUC_UNUSED GwyLine *line)
{
    /* Currently we do not cache anything but we should have an invalidate method in the API anyway. */
}

/**
 * gwy_line_get_res:
 * @line: A data line.
 *
 * Gets the number of data points in a data line.
 *
 * Returns: Resolution (number of data points).
 **/
gint
gwy_line_get_res(GwyLine *line)
{
    g_return_val_if_fail(GWY_IS_LINE(line), 0);
    return line->res;
}

/**
 * gwy_line_get_real:
 * @line: A data line.
 *
 * Gets the physical size of a data line.
 *
 * Returns: Real size of data line.
 **/
gdouble
gwy_line_get_real(GwyLine *line)
{
    g_return_val_if_fail(GWY_IS_LINE(line), 0.0);
    return line->real;
}

/**
 * gwy_line_set_real:
 * @line: A data line.
 * @real: value to be set
 *
 * Sets the real data line size.
 **/
void
gwy_line_set_real(GwyLine *line, gdouble real)
{
    g_return_if_fail(GWY_IS_LINE(line));
    g_return_if_fail(real > 0.0);
    line->real = real;
}

/**
 * gwy_line_get_offset:
 * @line: A data line.
 *
 * Gets the offset of data line origin.
 *
 * Returns: Offset value.
 **/
gdouble
gwy_line_get_offset(GwyLine *line)
{
    g_return_val_if_fail(GWY_IS_LINE(line), 0.0);
    return line->off;
}

/**
 * gwy_line_set_offset:
 * @line: A data line.
 * @offset: New offset value.
 *
 * Sets the offset of a data line origin.
 *
 * Note offsets don't affect any calculation, nor functions like gwy_line_rtoi().
 **/
void
gwy_line_set_offset(GwyLine *line,
                    gdouble offset)
{
    g_return_if_fail(GWY_IS_LINE(line));
    line->off = offset;
}

/**
 * gwy_line_get_dx:
 * @line: A data line.
 *
 * Gets the sample distance (pixel size) of a data line in real units.
 *
 * The result is the same as gwy_line_get_real(line)/gwy_line_get_res(line).
 *
 * Returns: Sampling step size.
 **/
gdouble
gwy_line_get_dx(GwyLine *line)
{
    g_return_val_if_fail(GWY_IS_LINE(line), 0.0);
    return line->real/line->res;
}

/**
 * gwy_line_get_unit_x:
 * @line: A data line.
 *
 * Returns lateral SI unit of a data line.
 *
 * The returned object can be modified to change the line x units.
 *
 * Returns: (transfer none):
 *          SI unit corresponding to the lateral (X) dimension of the data line.  Its reference count is not
 *          incremented.
 **/
GwyUnit*
gwy_line_get_unit_x(GwyLine *line)
{
    g_return_val_if_fail(GWY_IS_LINE(line), NULL);

    GwyLinePrivate *priv = line->priv;
    if (!priv->unit_x)
        priv->unit_x = gwy_unit_new(NULL);

    return priv->unit_x;
}

/**
 * gwy_line_get_unit_y:
 * @line: A data line.
 *
 * Returns value SI unit of a data line.
 *
 * The returned object can be modified to change the line value units.
 *
 * Returns: (transfer none):
 *          SI unit corresponding to the "height" (Z) dimension of the data line.  Its reference count is not
 *          incremented.
 **/
GwyUnit*
gwy_line_get_unit_y(GwyLine *line)
{
    g_return_val_if_fail(GWY_IS_LINE(line), NULL);

    GwyLinePrivate *priv = line->priv;
    if (!priv->unit_y)
        priv->unit_y = gwy_unit_new(NULL);

    return priv->unit_y;
}

/**
 * gwy_line_get_value_format_x:
 * @line: A data line.
 * @style: Unit format style.
 * @format: (nullable): A SI value format to modify, or %NULL to allocate a new one.
 *
 * Finds value format good for displaying coordinates of a data line.
 *
 * Returns: The value format.  If @format is %NULL, a newly allocated format is returned, otherwise (modified) @format
 *          itself is returned.
 **/
GwyValueFormat*
gwy_line_get_value_format_x(GwyLine *line,
                            GwyUnitFormatStyle style,
                            GwyValueFormat *format)
{
    g_return_val_if_fail(GWY_IS_LINE(line), NULL);

    gdouble max = line->real;
    gdouble unit = line->real/line->res;
    return gwy_unit_get_format_with_resolution(gwy_line_get_unit_x(line), style, max, unit, format);
}

/**
 * gwy_line_get_value_format_y:
 * @line: A data line.
 * @style: Unit format style.
 * @format: (nullable): A SI value format to modify, or %NULL to allocate a new one.
 *
 * Finds value format good for displaying values of a data line.
 *
 * Note this functions searches for minimum and maximum value in @line, therefore it's relatively slow.
 *
 * Returns: The value format.  If @format is %NULL, a newly allocated format is returned, otherwise (modified) @format
 *          itself is returned.
 **/
GwyValueFormat*
gwy_line_get_value_format_y(GwyLine *line,
                            GwyUnitFormatStyle style,
                            GwyValueFormat *format)
{
    g_return_val_if_fail(GWY_IS_LINE(line), NULL);

    gdouble min, max;
    gwy_line_min_max(line, &min, &max);
    if (max == min) {
        max = ABS(max);
        min = 0.0;
    }

    return gwy_unit_get_format(gwy_line_get_unit_y(line), style, max - min, format);
}

/**
 * gwy_line_copy_units:
 * @line: A data line.
 * @target: Destination data line.
 *
 * Sets lateral and value units of a data line to match another data line.
 **/
void
gwy_line_copy_units(GwyLine *line,
                    GwyLine *target)
{
    g_return_if_fail(GWY_IS_LINE(line));
    g_return_if_fail(GWY_IS_LINE(target));

    GwyLinePrivate *priv = line->priv, *tpriv = target->priv;
    _gwy_copy_unit(priv->unit_x, &tpriv->unit_x);
    _gwy_copy_unit(priv->unit_y, &tpriv->unit_y);
}

/**
 * gwy_line_copy_units_to_field:
 * @line: A data line to get units from.
 * @field: A data field to set units of.
 *
 * Sets lateral and value units of a data field to match a data line.
 **/
void
gwy_line_copy_units_to_field(GwyLine *line,
                             GwyField *field)
{
    g_return_if_fail(GWY_IS_LINE(line));
    g_return_if_fail(GWY_IS_FIELD(field));

    _gwy_copy_unit(line->priv->unit_x, &field->priv->unit_xy);
    _gwy_copy_unit(line->priv->unit_y, &field->priv->unit_z);
}

/**
 * gwy_line_itor:
 * @line: A data line.
 * @pixpos: Pixel coordinate.
 *
 * Transforms pixel coordinate to real (physical) coordinate.
 *
 * That is it maps range [0..resolution] to range [0..real-size].  It is not suitable for conversion of matrix indices
 * to physical coordinates, you have to use gwy_line_itor(@line, @pixpos + 0.5) for that.
 *
 * Returns: @pixpos in real coordinates.
 **/
gdouble
gwy_line_itor(GwyLine *line, gdouble pixpos)
{
    g_return_val_if_fail(GWY_IS_LINE(line), 0.0);
    return pixpos * line->real/line->res;
}

/**
 * gwy_line_rtoi:
 * @line: A data line.
 * @realpos: Real coordinate.
 *
 * Transforms real (physical) coordinate to pixel coordinate.
 *
 * That is it maps range [0..real-size] to range [0..resolution].
 *
 * Returns: @realpos in pixel coordinates.
 **/
gdouble
gwy_line_rtoi(GwyLine *line, gdouble realpos)
{
    g_return_val_if_fail(GWY_IS_LINE(line), 0.0);
    return realpos * line->res/line->real;
}

/**
 * gwy_line_get_val:
 * @line: A data line.
 * @i: Position in the line (index).
 *
 * Gets value at given position in a data line.
 *
 * Do not access data with this function inside inner loops, it's slow. Get raw data buffer with
 * gwy_line_get_data_const() and access it directly instead.
 *
 * Returns: Value at given index.
 **/
gdouble
gwy_line_get_val(GwyLine *line,
                 gint i)
{
    g_return_val_if_fail(GWY_IS_LINE(line), 0.0);
    g_return_val_if_fail(i >= 0 && i < line->res, 0.0);

    return line->priv->data[i];
}

/**
 * gwy_line_set_val:
 * @line: A data line.
 * @i: Position in the line (index).
 * @value: Value to set.
 *
 * Sets the value at given position in a data line.
 *
 * Do not set data with this function inside inner loops, it's slow.  Get raw data buffer with
 * gwy_line_get_data() and write to it directly instead.
 **/
void
gwy_line_set_val(GwyLine *line,
                 gint i,
                 gdouble value)
{
    g_return_if_fail(GWY_IS_LINE(line));
    g_return_if_fail(i >= 0 && i < line->res);

    line->priv->data[i] = value;
}

/**
 * gwy_line_get_dval_real:
 * @line: A data line.
 * @x: Position in real coordinates.
 * @interpolation: Interpolation method to use.
 *
 * Gets interpolated value at arbitrary data line point indexed by real coordinates.
 *
 * See also gwy_line_get_dval() for interpolation explanation.
 *
 * Returns: Value interpolated in the data line.
 **/
gdouble
gwy_line_get_dval_real(GwyLine *line, gdouble x, GwyInterpolationType interpolation)
{
    return gwy_line_get_dval(line, gwy_line_rtoi(line, x), interpolation);
}

/**
 * gwy_line_flip:
 * @line: A data line.
 *
 * Reflects a data line in place.
 **/
void
gwy_line_flip(GwyLine *line)
{
    g_return_if_fail(GWY_IS_LINE(line));
    invert_array_in_place(line->priv->data, line->res);
}

/**
 * gwy_line_invert_value:
 * @line: A data line.
 *
 * Inverts a data line data in place.
 *
 * Values are inverted about the mean value. Use gwy_line_multiply() to change the signs of all values.
 **/
void
gwy_line_invert_value(GwyLine *line)
{
    g_return_if_fail(GWY_IS_LINE(line));
    gdouble *data = line->priv->data;
    gint res = line->res;
    gdouble avg = gwy_line_mean(line);
    for (gint i = 0; i < res; i++)
        data[i] = 2*avg - data[i];
}

/**
 * gwy_line_fill:
 * @line: A data line.
 * @value: Value to fill data line with.
 *
 * Fills a data line with specified value.
 **/
void
gwy_line_fill(GwyLine *line,
              gdouble value)
{
    g_return_if_fail(GWY_IS_LINE(line));
    gwy_math_fill(line->priv->data, line->res, value);
}

/**
 * gwy_line_clear:
 * @line: A data line.
 *
 * Fills a data line with zeroes.
 **/
void
gwy_line_clear(GwyLine *line)
{
    g_return_if_fail(GWY_IS_LINE(line));
    gwy_clear(line->priv->data, line->res);
}

/**
 * gwy_line_add:
 * @line: A data line.
 * @value: Value to be added.
 *
 * Adds a specified value to all values in a data line.
 **/
void
gwy_line_add(GwyLine *line,
             gdouble value)
{
    g_return_if_fail(GWY_IS_LINE(line));

    gdouble *data = line->priv->data;
    gint res = line->res;
    for (gint i = 0; i < res; i++)
        data[i] += value;
}

/**
 * gwy_line_multiply:
 * @line: A data line.
 * @value: Value to multiply data line with.
 *
 * Multiplies all values in a data line with a specified value.
 **/
void
gwy_line_multiply(GwyLine *line,
                  gdouble value)
{
    g_return_if_fail(GWY_IS_LINE(line));

    gdouble *data = line->priv->data;
    gint res = line->res;
    for (gint i = 0; i < res; i++)
        data[i] *= value;
}

/**
 * gwy_line_part_fill:
 * @line: A data line.
 * @pos: Index where to start.
 * @len: Length of the segment.
 * @value: Value to fill data line part with.
 *
 * Fills specified part of data line with specified number
 **/
void
gwy_line_part_fill(GwyLine *line,
                   gint pos, gint len,
                   gdouble value)
{
    if (!_gwy_line_check_part(line, pos, len, TRUE))
        return;

    gdouble *data = line->priv->data + pos;
    for (gint i = 0; i < len; i++)
        data[i] = value;
}

/**
 * gwy_line_part_clear:
 * @line: A data line.
 * @pos: Index where to start.
 * @len: Length of the segment.
 *
 * Fills a data line part with zeroes.
 **/
void
gwy_line_part_clear(GwyLine *line,
                    gint pos, gint len)
{
    if (!_gwy_line_check_part(line, pos, len, TRUE))
        return;

    memset(line->priv->data + pos, 0, len*sizeof(gdouble));
}

/**
 * gwy_line_part_add:
 * @line: A data line.
 * @pos: Index where to start.
 * @len: Length of the segment.
 * @value: Value to be added
 *
 * Adds specified value to all values in a part of a data line.
 **/
void
gwy_line_part_add(GwyLine *line,
                  gint pos, gint len,
                  gdouble value)
{
    if (!_gwy_line_check_part(line, pos, len, TRUE))
        return;

    gdouble *data = line->priv->data + pos;
    for (gint i = 0; i < len; i++)
        data[i] += value;
}

/**
 * gwy_line_part_multiply:
 * @line: A data line.
 * @pos: Index where to start.
 * @len: Length of the segment.
 * @value: Value multiply data line part with.
 *
 * Multiplies all values in a part of data line by specified value.
 **/
void
gwy_line_part_multiply(GwyLine *line,
                       gint pos, gint len,
                       gdouble value)
{
    if (!_gwy_line_check_part(line, pos, len, TRUE))
        return;

    gdouble *data = line->priv->data + pos;
    for (gint i = 0; i < len; i++)
        data[i] *= value;
}

/**
 * gwy_line_threshold:
 * @line: A data line.
 * @threshval: Threshold value.
 * @bottom: Lower replacement value.
 * @top: Upper replacement value.
 *
 * Sets all the values to @bottom or @top value
 * depending on whether the original values are
 * below or above @threshold value
 *
 * Returns: total number of values above threshold
 **/
gint
gwy_line_threshold(GwyLine *line,
                   gdouble threshval, gdouble bottom, gdouble top)
{
    g_return_val_if_fail(GWY_IS_LINE(line), 0);

    gdouble *data = line->priv->data;
    gint tot = 0, res = line->res;
    for (gint i = 0; i < res; i++) {
        if (data[i] < threshval)
            data[i] = bottom;
        else {
            data[i] = top;
            tot++;
        }
    }
    return tot;
}

/**
 * gwy_line_part_threshold:
 * @line: A data line.
 * @pos: Index where to start.
 * @len: Length of the segment.
 * @threshval: Threshold value.
 * @bottom: Lower replacement value.
 * @top: Upper replacement value.
 *
 * Sets all the values within interval to @bottom or @top value depending on whether the original values are below or
 * above @threshold value.
 *
 * Returns: total number of values above threshold within interval
 **/
gint
gwy_line_part_threshold(GwyLine *line,
                        gint pos, gint len,
                        gdouble threshval, gdouble bottom, gdouble top)
{
    if (!_gwy_line_check_part(line, pos, len, TRUE))
        return 0;

    gdouble *data = line->priv->data + pos;
    gint tot = 0;
    for (gint i = 0; i < len; i++) {
        if (data[i] < threshval)
            data[i] = bottom;
        else {
            data[i] = top;
            tot++;
        }
    }
    return tot;
}

/**
 * gwy_line_der:
 * @line: A data line.
 * @i: Pixel coordinate.
 *
 * Computes central derivaltion at given index in a data line.
 *
 * Returns: Derivation at given position.
 **/
gdouble
gwy_line_der(GwyLine *line, gint i)
{
    g_return_val_if_fail(GWY_IS_LINE(line), 0.0);
    gint res = line->res;
    g_return_val_if_fail(i >= 0 && i < res, 0.0);

    const gdouble *data = line->priv->data;
    gdouble dx = line->real/res;
    if (i == 0)
        return (data[1] - data[0])/dx;
    if (i == res-1)
        return (data[i] - data[i-1])/dx;
    return 0.5*(data[i+1] - data[i-1])/dx;
}

/**
 * gwy_line_cumulate:
 * @line: A data line.
 * @transform_to_cdist: %TRUE to fully transform, including units; %FALSE to just do the cummulative summation.
 *
 * Transforms a distribution in a data line to cummulative distribution.
 *
 * Each element becomes sum of all previous elements in the line, including self. If @transform_to_cdist is %FALSE,
 * this is the only change.
 *
 * If it is %TRUE, the values are also multiplied by gwy_line_dx() and the y-unit is multiplied by the x-unit,
 * corresponding to integration. In addition, the tail is forced to not exceed 1 and the last value is forced to
 * be exactly 1 (working around possible rounding errors). This is useful when @line is normalised as a probability
 * distribution function and it should be fully transformed to a cummulative distribution function.
 *
 * See also gwy_accumulate_counts().
 **/
void
gwy_line_cumulate(GwyLine *line, gboolean transform_to_cdist)
{
    g_return_if_fail(GWY_IS_LINE(line));

    gint res = line->res;
    gdouble *data = line->priv->data;
    for (gint i = 1; i < res; i++)
        data[i] += data[i-1];

    if (transform_to_cdist) {
        gwy_line_multiply(line, gwy_line_get_dx(line));
        gwy_unit_multiply(gwy_line_get_unit_y(line), gwy_line_get_unit_x(line), gwy_line_get_unit_y(line));
        /* Fix rounding errors. */
        for (gint i = res-1; i && data[i] > 1.0; i--)
            data[i] = 1.0;
        data[res-1] = 1.0;
    }
}

/**
 * gwy_line_filter_slope:
 * @line: A data line.
 * @der: Data line where the derivative is to be stored. It will be resized to match @line.
 *
 * Calculates the derivative for an entire data line.
 *
 * The derivatives are in physical units (not pixel-wise) and calculated from simple symmetrical differences, except at
 * the edges where the differences are one-sided.
 **/
void
gwy_line_filter_slope(GwyLine *line,
                      GwyLine *der)
{
    g_return_if_fail(GWY_IS_LINE(line));
    g_return_if_fail(GWY_IS_LINE(der));

    gint res = line->res;
    gwy_line_resize(der, res);
    der->real = line->real;
    der->off = line->off;
    gwy_unit_assign(gwy_line_get_unit_x(der), gwy_line_get_unit_x(line));
    gwy_unit_divide(gwy_line_get_unit_y(der), gwy_line_get_unit_y(line), gwy_line_get_unit_x(line));
    if (res == 1) {
        gwy_line_clear(der);
    }
    else {
        const gdouble *d = line->priv->data;
        gdouble *b = der->priv->data;
        gdouble dx = line->real/res;
        b[0] = (d[1] - d[0])/dx;
        for (gint i = 1; i < res-1; i++)
            b[i] = 0.5*(d[i+1] - d[i-1])/dx;
        b[res-1] = (d[res-1] - d[res-2])/dx;
    }
}

/**
 * gwy_line_sqrt:
 * @line: A data line.
 *
 * Applies sqrt() to each element in a data line.
 **/
void
gwy_line_sqrt(GwyLine *line)
{
    g_return_if_fail(GWY_IS_LINE(line));

    gdouble *data = line->priv->data;
    gint res = line->res;
    for (gint i = 0; i < res; i++) {
        data[i] = sqrt(data[i]);
    }
}

gboolean
_gwy_line_check_part(GwyLine *line,
                     gint pos, gint len,
                     gboolean empty_is_noop)
{
    g_return_val_if_fail(GWY_IS_LINE(line), FALSE);
    gint res = line->res;
    g_return_val_if_fail(pos >= 0 && pos < res, FALSE);
    g_return_val_if_fail(len <= res - pos, FALSE);
    if (empty_is_noop) {
        g_return_val_if_fail(len >= 0, FALSE);
        /* If empty_is_noop we still return FALSE for empty parts to indicate the caller should avoid any data
         * processing, but we do not print any error. */
        return len > 0;
    }
    g_return_val_if_fail(len > 0, FALSE);

    return TRUE;
}

static void
serializable_itemize(GwySerializable *serializable, GwySerializableGroup *group)
{
    GwyLine *line = GWY_LINE(serializable);
    GwyLinePrivate *priv = line->priv;

    gwy_serializable_group_alloc_size(group, NUM_ITEMS);
    gwy_serializable_group_append_int32(group, serializable_items + ITEM_RES, line->res);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_REAL, line->real);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_OFF, line->off);
    gwy_serializable_group_append_unit(group, serializable_items + ITEM_UNIT_X, priv->unit_x);
    gwy_serializable_group_append_unit(group, serializable_items + ITEM_UNIT_Y, priv->unit_y);
    gwy_serializable_group_append_double_array(group, serializable_items + ITEM_DATA, priv->data, line->res);
    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);

    GwyLine *line = GWY_LINE(serializable);
    GwyLinePrivate *priv = line->priv;

    /* Botched up data dimensions is a hard fail.
     * FIXME: There is not really any need to store "res" separately because the array is 1D. We just do it because
     * of tradition (and consistency with multidimensional data). */
    line->res = its[ITEM_RES].value.v_int32;

    it = its + ITEM_DATA;
    gsize logical_dims[1] = { line->res };
    if (!gwy_check_data_dimension(error_list, TYPE_NAME, 1, it->array_size, logical_dims)) {
        line->res = 1;
        g_free(it->value.v_double_array);
        goto fail;
    }
    g_free(priv->data);
    priv->data = it->value.v_double_array;

    /* The rest is already validated by pspec. */
    line->real = its[ITEM_REAL].value.v_double;
    line->off = its[ITEM_OFF].value.v_double;

    priv->unit_x = (GwyUnit*)its[ITEM_UNIT_X].value.v_object;
    its[ITEM_UNIT_X].value.v_object = NULL;
    priv->unit_y = (GwyUnit*)its[ITEM_UNIT_Y].value.v_object;
    its[ITEM_UNIT_Y].value.v_object = NULL;

    ok = TRUE;

fail:
    g_clear_object(&its[ITEM_UNIT_X].value.v_object);
    g_clear_object(&its[ITEM_UNIT_Y].value.v_object);
    return ok;
}

static GwySerializable*
serializable_copy(GwySerializable *serializable)
{
    GwyLine *line = GWY_LINE(serializable);
    GwyLine *copy = gwy_line_new_alike(line, FALSE);
    gwy_assign(copy->priv->data, line->priv->data, line->res);
    return GWY_SERIALIZABLE(copy);
}

static void
serializable_assign(GwySerializable *destination, GwySerializable *source)
{
    GwyLine *destline = GWY_LINE(destination), *srcline = GWY_LINE(source);

    alloc_data(destline, srcline->res, FALSE);
    gwy_assign(destline->priv->data, srcline->priv->data, destline->res);
    copy_info(srcline, destline);
}

/**
 * gwy_line_copy:
 * @line: A data line to duplicate.
 *
 * Create a new data line as a copy of an existing one.
 *
 * This function is a convenience gwy_serializable_copy() wrapper.
 *
 * Returns: (transfer full):
 *          A copy of the data line.
 **/
GwyLine*
gwy_line_copy(GwyLine *line)
{
    /* Try to return a valid object even on utter failure. Returning NULL probably would crash something soon. */
    if (!GWY_IS_LINE(line)) {
        g_assert(GWY_IS_LINE(line));
        return g_object_new(GWY_TYPE_LINE, NULL);
    }
    return GWY_LINE(serializable_copy(GWY_SERIALIZABLE(line)));
}

/**
 * gwy_line_assign:
 * @destination: Target data line.
 * @source: Source data line.
 *
 * Makes one data line equal to another.
 *
 * This function is a convenience gwy_serializable_assign() wrapper.
 **/
void
gwy_line_assign(GwyLine *destination, GwyLine *source)
{
    g_return_if_fail(GWY_IS_LINE(destination));
    g_return_if_fail(GWY_IS_LINE(source));
    if (destination != source)
        serializable_assign(GWY_SERIALIZABLE(destination), GWY_SERIALIZABLE(source));
}

/**
 * SECTION: line
 * @title: GwyLine
 * @short_description: One-dimensional floating point data
 *
 * #GwyLine is a one-dimensional floating point data array. It is used for most of the data processing functions
 * connected with 1D data, graphs, 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 : */
