/*
 *  $Id: xyz.c 29467 2026-02-12 18:47:32Z yeti-dn $
 *  Copyright (C) 2025-2026 David Necas (Yeti).
 *  E-mail: yeti@gwyddion.net.
 *
 *  The quicksort algorithm was copied from GNU C library, Copyright (C) 1991, 1992, 1996, 1997, 1999 Free Software
 *  Foundation, Inc.  See below.
 *
 *  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/math.h"
#include "libgwyddion/serializable-boxed.h"
#include "libgwyddion/serializable-utils.h"
#include "libgwyddion/xyz.h"

#define TYPE_NAME "GwyXYZ"

enum {
    ITEM_X,
    ITEM_Y,
    ITEM_Z,
    NUM_ITEMS,
};

static void     itemize  (gpointer boxed,
                          GwySerializableGroup *group);
static gpointer construct(GwySerializableGroup *group,
                          GwyErrorList **error_list);

static const GwySerializableItem serializable_items[NUM_ITEMS] = {
    { .name = "x", .ctype = GWY_SERIALIZABLE_DOUBLE, },
    { .name = "y", .ctype = GWY_SERIALIZABLE_DOUBLE, },
    { .name = "z", .ctype = GWY_SERIALIZABLE_DOUBLE, },
};

GType
gwy_xyz_get_type(void)
{
    /* Threads: type registered from gwy_types_init(). */
    static GType type = 0;

    if (G_UNLIKELY(!type)) {
        type = g_boxed_type_register_static("GwyXYZ", (GBoxedCopyFunc)gwy_xyz_copy, (GBoxedFreeFunc)gwy_xyz_free);
        static const GwySerializableBoxedInfo boxed_info = {
            sizeof(GwyXYZ), NUM_ITEMS, itemize, construct, NULL, NULL,
        };
        gwy_serializable_boxed_register_static(type, &boxed_info);
    }

    return type;
}

/**
 * gwy_xyz_new:
 * @x: X-coordinate.
 * @y: Y-coordinate.
 * @z: Z-coordinate.
 *
 * Creates Cartesian coordinates in space.
 *
 * This is mostly useful for language bindings.
 *
 * Returns: New XYZ structure.  The result should be freed using gwy_xyz_free().
 **/
GwyXYZ*
gwy_xyz_new(gdouble x, gdouble y, gdouble z)
{
    GwyXYZ *xyz = g_new(GwyXYZ, 1);
    xyz->x = x;
    xyz->y = y;
    xyz->z = z;
    return xyz;
}

/**
 * gwy_xyz_copy:
 * @xyz: Cartesian coordinates in space.
 *
 * Copies Cartesian coordinates in space.
 *
 * Returns: A copy of @xyz. The result should be freed using gwy_xyz_free().
 **/
GwyXYZ*
gwy_xyz_copy(const GwyXYZ *xyz)
{
    g_return_val_if_fail(xyz, NULL);
    return g_memdup2(xyz, sizeof(GwyXYZ));
}

/**
 * gwy_xyz_free:
 * @xyz: Cartesian coordinates in space.
 *
 * Frees Cartesian coordinates in space created with gwy_xyz_copy().
 **/
void
gwy_xyz_free(GwyXYZ *xyz)
{
    g_free(xyz);
}

static void
itemize(gpointer boxed, GwySerializableGroup *group)
{
    GwyXYZ *xyz = (GwyXYZ*)boxed;
    gwy_serializable_group_append_double(group, serializable_items + ITEM_X, xyz->x);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_Y, xyz->y);
    gwy_serializable_group_append_double(group, serializable_items + ITEM_Z, xyz->z);
}

static gpointer
construct(GwySerializableGroup *group, GwyErrorList **error_list)
{
    GwySerializableItem its[NUM_ITEMS];
    gwy_assign(its, serializable_items, NUM_ITEMS);
    gwy_deserialize_filter_items(its, NUM_ITEMS, group, TYPE_NAME, error_list);

    GwyXYZ *xyz = g_new(GwyXYZ, 1);
    xyz->x = its[ITEM_X].value.v_double;
    xyz->y = its[ITEM_Y].value.v_double;
    xyz->z = its[ITEM_Z].value.v_double;
    return xyz;
}

#undef gwy_xyz_len
#undef gwy_xyz_len2
#undef gwy_xyz_dist2
#undef gwy_xyz_dotprod
#undef gwy_xyz_crossprod

/**
 * gwy_xyz_len:
 * @xyz: Cartesian coordinates in space.
 *
 * Computes the length of a space vector.
 *
 * Returns: The vector length.
 **/
gdouble
gwy_xyz_len(const GwyXYZ *xyz)
{
    return _gwy_xyz_len_implementation(xyz);
}

/**
 * gwy_xyz_len2:
 * @xyz: Cartesian coordinates in space.
 *
 * Computes the squared length of a space vector.
 *
 * Returns: The squared vector length.
 **/
gdouble
gwy_xyz_len2(const GwyXYZ *xyz)
{
    return _gwy_xyz_len2_implementation(xyz);
}

/**
 * gwy_xyz_dist2:
 * @xyz: Cartesian coordinates in space.
 * @other: Cartesian coordinates in space.
 *
 * Computes the squared length of the difference of two space vectors.
 *
 * Returns: The squared length.
 **/
gdouble
gwy_xyz_dist2(const GwyXYZ *xyz, const GwyXYZ *other)
{
    return _gwy_xyz_dist2_implementation(xyz, other);
}

/**
 * gwy_xyz_dotprod:
 * @xyz: Cartesian coordinates in space.
 * @other: Cartesian coordinates in space.
 *
 * Computes the inner product of two space vectors.
 *
 * Returns: The inner product @xyz⋅@other.
 **/
gdouble
gwy_xyz_dotprod(const GwyXYZ *xyz, const GwyXYZ *other)
{
    return _gwy_xyz_dotprod_implementation(xyz, other);
}

/**
 * gwy_xyz_crossprod:
 * @xyz: Cartesian coordinates in space.
 * @other: Cartesian coordinates in space.
 * @result: (out): Coordinates to fill with the result.
 *
 * Computes the cross-product of space vectors.
 *
 * The result is filled with @xyz×@other. It may be one of the two input vectors.
 **/
void
gwy_xyz_crossprod(const GwyXYZ *xyz, const GwyXYZ *other, GwyXYZ *result)
{
    _gwy_xyz_crossprod_implementation(xyz, other, result);
}

/**
 * SECTION: xyz
 * @section_id: GwyXYZ
 * @title: GwyXYZ
 * @short_description: Cartesian coordinates in space
 **/

/**
 * GwyXYZ:
 * @x: X-coordinate.
 * @y: Y-coordinate.
 * @z: Z-coordinate.
 *
 * Representation of Cartesian coordinates in space.
 **/

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