/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module ArraysAndInterfaces
 */
import { Geometry } from "../Geometry";
import { Point3d, Vector3d } from "./Point3dVector3d";
import { Range3d } from "./Range";
class PointsIterator {
    _collection;
    _curIndex = -1;
    constructor(collection) {
        this._collection = collection;
    }
    next() {
        if (++this._curIndex >= this._collection.length) {
            // the ECMAScript spec states that value=undefined is valid if done=true
            // the TypeScript interface violates the spec hence the cast to any and back below
            return { done: true };
        }
        return {
            value: this._collection.getPoint3dAtUncheckedPointIndex(this._curIndex),
            done: false,
        };
    }
    [Symbol.iterator]() { return this; }
}
/**
 * Abstract base class for read-only access to XYZ data with indexed reference.
 * * This allows algorithms to work with Point3d[] or [[GrowableXYZArray]].
 *   * GrowableXYZArray implements these for its data.
 *   * Point3dArrayCarrier carries a (reference to) a Point3d[] and implements the methods with calls on that array reference.
 * * In addition to "point by point" accessors, other abstract members compute commonly useful vector data "between points".
 * * Methods that create vectors among multiple indices allow callers to avoid creating temporaries.
 * @public
 */
export class IndexedXYZCollection {
    /**
     * Return a vector from the point at `indexA` to `target`.
     * @param indexA index of point within the array.
     * @param target target for vector.
     * @param result caller-allocated vector.
     * @returns undefined if index is out of bounds.
     */
    vectorIndexXYAndZ(indexA, target, result) {
        const reversed = this.vectorXYAndZIndex(target, indexA, result);
        return reversed?.negate(reversed);
    }
    /**
     * Return the dot product of the vectors from the point at `origin` to the points at `indexA` and `indexB`.
     * @param origin index of point within the array; origin of both vectors.
     * @param indexA index of point within the array; target of the first vector.
     * @param indexB index of point within the array; target of the second vector.
     * @returns undefined if index is out of bounds.
     */
    dotProductIndexIndexIndex(origin, indexA, indexB) {
        if (origin < 0 || origin >= this.length || indexA < 0 || indexA >= this.length || indexB < 0 || indexB >= this.length)
            return undefined;
        const x0 = this.getXAtUncheckedPointIndex(origin);
        const y0 = this.getYAtUncheckedPointIndex(origin);
        const z0 = this.getZAtUncheckedPointIndex(origin);
        return (this.getXAtUncheckedPointIndex(indexA) - x0) * (this.getXAtUncheckedPointIndex(indexB) - x0) +
            (this.getYAtUncheckedPointIndex(indexA) - y0) * (this.getYAtUncheckedPointIndex(indexB) - y0) +
            (this.getZAtUncheckedPointIndex(indexA) - z0) * (this.getZAtUncheckedPointIndex(indexB) - z0);
    }
    /**
     * Return the dot product of the vectors from the point at `origin` to the point at `indexA` and to `targetB`.
     * @param origin index of point within the array; origin of both vectors.
     * @param indexA index of point within the array; target of the first vector.
     * @param targetB target for second vector.
     * @returns undefined if index is out of bounds.
     */
    dotProductIndexIndexXYAndZ(origin, indexA, targetB) {
        if (origin < 0 || origin >= this.length || indexA < 0 || indexA >= this.length)
            return undefined;
        const x0 = this.getXAtUncheckedPointIndex(origin);
        const y0 = this.getYAtUncheckedPointIndex(origin);
        const z0 = this.getZAtUncheckedPointIndex(origin);
        return (this.getXAtUncheckedPointIndex(indexA) - x0) * (targetB.x - x0) +
            (this.getYAtUncheckedPointIndex(indexA) - y0) * (targetB.y - y0) +
            (this.getZAtUncheckedPointIndex(indexA) - z0) * (targetB.z - z0);
    }
    /**
     * Return the cross product of the vectors from `origin` to the point at `indexA` and to `targetB`.
     * @param origin index of point within the array; origin of both vectors.
     * @param indexA index of point within the array; target of the first vector.
     * @param targetB target of second vector.
     * @param result optional caller-allocated result to fill and return.
     * @returns undefined if an index is out of bounds.
     */
    crossProductIndexIndexXYAndZ(origin, indexA, targetB, result) {
        if (origin < 0 || origin >= this.length || indexA < 0 || indexA >= this.length)
            return undefined;
        const x0 = this.getXAtUncheckedPointIndex(origin);
        const y0 = this.getYAtUncheckedPointIndex(origin);
        const z0 = this.getZAtUncheckedPointIndex(origin);
        return Vector3d.createCrossProduct(this.getXAtUncheckedPointIndex(indexA) - x0, this.getYAtUncheckedPointIndex(indexA) - y0, this.getZAtUncheckedPointIndex(indexA) - z0, targetB.x - x0, targetB.y - y0, targetB.z - z0, result);
    }
    /**
     * Return distance squared between the point at index0 and target.
     * @param index0 first point index.
     * @param target second point.
     */
    distanceSquaredIndexXYAndZ(index0, target) {
        if (index0 < 0 || index0 >= this.length)
            return undefined;
        return Geometry.hypotenuseSquaredXYZ(target.x - this.getXAtUncheckedPointIndex(index0), target.y - this.getYAtUncheckedPointIndex(index0), target.z - this.getZAtUncheckedPointIndex(index0));
    }
    /**
     * Test if index is valid for an xyz within this array.
     * @param index xyz index to test.
     */
    isIndexValid(index) {
        return index >= 0 && index < this.length;
    }
    /** Adjust index into range by modulo with the length. */
    cyclicIndex(i) {
        return (i % this.length);
    }
    /** Return the range of the points. */
    getRange(transform, result) {
        let range = result;
        if (range)
            range.setNull();
        else
            range = Range3d.createNull();
        const n = this.length;
        const point = Point3d.create();
        for (let i = 0; i < n; i++) {
            this.getPoint3dAtUncheckedPointIndex(i, point);
            range.extendPoint(point, transform);
        }
        return range;
    }
    /**
     * For each subset of `k` successive points within tolerance of the first point in the subset, return the indices of
     * the last `k-1` duplicates.
     * * Index `0` is never returned.
     * @param tolerance optional distance tol for compression (default [[Geometry.smallMetricDistance]]).
     * @param preserveLast if the last `k < this.length` points of the instance are duplicates, whether to return the
     * indices of the *first* `k-1` duplicates for this last run. If true, index `this.length - 1` is not returned.
     * unless all points are duplicates, in which case all indices but `0` are returned. Default value is false.
     * @return ordered array of 0-based indices of duplicate points
     */
    findOrderedDuplicates(tolerance = Geometry.smallMetricDistance, preserveLast = false) {
        const indices = [];
        if (this.length > 1) {
            for (let i = 0; i < this.length - 1;) {
                let j = i + 1;
                for (; this.almostEqualIndexIndex(i, j, tolerance); ++j)
                    indices.push(j);
                i = j; // found next unique point
            }
            if (preserveLast && indices.length > 0 && indices.length < this.length - 1) { // not all points are duplicate
                let numLastRun = 0;
                for (; numLastRun <= indices.length - 1 && indices[indices.length - 1 - numLastRun] === this.length - 1 - numLastRun; ++numLastRun)
                    ;
                for (let i = 0; i < numLastRun; ++i)
                    indices[indices.length - 1 - i] -= 1; // decrement the indices of the last run so that the last point is preserved
            }
        }
        return indices;
    }
    /**
     * Compute the linear combination s of the indexed p_i and given scales s_i.
     * @param scales array of scales. For best results, scales should have same length as the instance.
     * @param result optional pre-allocated object to fill and return
     * @return s = sum(p_i * s_i), where i ranges from 0 to min(this.length, scales.length).
    */
    linearCombination(scales, result) {
        const n = Math.min(this.length, scales.length);
        const sum = (result instanceof Vector3d) ? Vector3d.createZero(result) : Point3d.createZero(result);
        for (let i = 0; i < n; ++i) {
            sum.x += scales[i] * this.getXAtUncheckedPointIndex(i);
            sum.y += scales[i] * this.getYAtUncheckedPointIndex(i);
            sum.z += scales[i] * this.getZAtUncheckedPointIndex(i);
        }
        return sum;
    }
    /**
     * Interpolate the points at the given indices.
     * @param index0 index of point p0 within the array.
     * @param fraction fraction f such that returned point is p0 + f * (p1 - p0).
     * @param index1 index of point p1 within the array.
     * @param result optional caller-allocated result to fill and return.
     * @returns undefined if an index is out of bounds.
     */
    interpolateIndexIndex(index0, fraction, index1, result) {
        if (index0 < 0 || index0 >= this.length || index1 < 0 || index1 >= this.length)
            return undefined;
        return Point3d.create(Geometry.interpolate(this.getXAtUncheckedPointIndex(index0), fraction, this.getXAtUncheckedPointIndex(index1)), Geometry.interpolate(this.getYAtUncheckedPointIndex(index0), fraction, this.getYAtUncheckedPointIndex(index1)), Geometry.interpolate(this.getZAtUncheckedPointIndex(index0), fraction, this.getZAtUncheckedPointIndex(index1)), result);
    }
    /**
     * Return iterator over the points in this collection. Usage:
     * ```ts
     *  for (const point: Point3d of collection.points) { ... }
     * ```
     */
    get points() {
        return new PointsIterator(this);
    }
    /** Convert to Point3d[]. */
    getArray() {
        const result = [];
        for (const p of this.points)
            result.push(p);
        return result;
    }
    /** Return the first point, or undefined if the array is empty. */
    front(result) {
        if (this.length === 0)
            return undefined;
        return this.getPoint3dAtUncheckedPointIndex(0, result);
    }
    /** Return the last point, or undefined if the array is empty. */
    back(result) {
        if (this.length === 0)
            return undefined;
        return this.getPoint3dAtUncheckedPointIndex(this.length - 1, result);
    }
    /**
     * Test whether the indexed points are equal within tolerance.
     * @param index0 index of first point.
     * @param index1 index of second point.
     * @param tolerance max coordinate difference to be considered equal. For exact test, pass 0. Defaults to `Geometry.smallMetricDistance`.
     * @returns whether the points are equal within tolerance, or `undefined` if either index is invalid.
     */
    almostEqualIndexIndex(index0, index1, tolerance = Geometry.smallMetricDistance) {
        if (index0 < 0 || index0 >= this.length || index1 < 0 || index1 >= this.length)
            return undefined;
        return Geometry.isSameCoordinate(this.getXAtUncheckedPointIndex(index0), this.getXAtUncheckedPointIndex(index1), tolerance)
            && Geometry.isSameCoordinate(this.getYAtUncheckedPointIndex(index0), this.getYAtUncheckedPointIndex(index1), tolerance)
            && Geometry.isSameCoordinate(this.getZAtUncheckedPointIndex(index0), this.getZAtUncheckedPointIndex(index1), tolerance);
    }
    /**
     * Test whether the xy-coordinates of the indexed points are equal within tolerance. The z-coordinates are ignored.
     * @param index0 index of first point.
     * @param index1 index of second point.
     * @param tolerance max coordinate difference to be considered equal. For exact test, pass 0. Defaults to `Geometry.smallMetricDistance`.
     * @returns whether the xy-coordinates of the points are equal within tolerance, or `undefined` if either index is invalid.
     */
    almostEqualXYIndexIndex(index0, index1, tolerance = Geometry.smallMetricDistance) {
        if (index0 < 0 || index0 >= this.length || index1 < 0 || index1 >= this.length)
            return undefined;
        return Geometry.isSameCoordinate(this.getXAtUncheckedPointIndex(index0), this.getXAtUncheckedPointIndex(index1), tolerance)
            && Geometry.isSameCoordinate(this.getYAtUncheckedPointIndex(index0), this.getYAtUncheckedPointIndex(index1), tolerance);
    }
}
/**
 * Abstract base class extends [[IndexedXYZCollection]] with methods that modify the collection.
 * @public
 */
export class IndexedReadWriteXYZCollection extends IndexedXYZCollection {
}
//# sourceMappingURL=IndexedXYZCollection.js.map