"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.BagOfCurves = exports.CurveChain = exports.CurveCollection = void 0;
/** @packageDocumentation
 * @module Curve
 */
const Geometry_1 = require("../Geometry");
const CurveLocationDetail_1 = require("./CurveLocationDetail");
const CurvePrimitive_1 = require("./CurvePrimitive");
const GeometryQuery_1 = require("./GeometryQuery");
const CloneCurvesContext_1 = require("./internalContexts/CloneCurvesContext");
const CloneWithExpandedLineStrings_1 = require("./internalContexts/CloneWithExpandedLineStrings");
const CountLinearPartsSearchContext_1 = require("./internalContexts/CountLinearPartsSearchContext");
const GapSearchContext_1 = require("./internalContexts/GapSearchContext");
const PlaneAltitudeRangeContext_1 = require("./internalContexts/PlaneAltitudeRangeContext");
const SumLengthsContext_1 = require("./internalContexts/SumLengthsContext");
const TransformInPlaceContext_1 = require("./internalContexts/TransformInPlaceContext");
const LineString3d_1 = require("./LineString3d");
const ProxyCurve_1 = require("./ProxyCurve");
const AnnounceTangentStrokeHandler_1 = require("./internalContexts/AnnounceTangentStrokeHandler");
const Matrix3d_1 = require("../geometry3d/Matrix3d");
/**
 * A `CurveCollection` is an abstract (non-instantiable) class for various sets of curves with particular structures:
 * - [[CurveChain]] - a non-instantiable intermediate class for a sequence of [[CurvePrimitive]] joining head-to-tail.
 * The two instantiable forms of `CurveChain` are:
 *   - [[Path]] - a chain of curves. Does not have to be closed or planar. A closed `Path` is not treated as bounding a surface.
 *   - [[Loop]] - a closed and planar chain of curves. A `Loop` is treated as bounding a planar area.
 * - [[ParityRegion]] - a collection of coplanar `Loop`, with "in/out" classification by parity rules.
 * - [[UnionRegion]] - a collection of coplanar `Loop` and/or `ParityRegion`, with "in/out" classification by union rules.
 * - [[BagOfCurves]] - a collection of [[AnyCurve]] with no implied structure.
 *
 * @see [Curve Collections]($docs/learning/geometry/CurveCollection.md) learning article.
 * @public
 */
class CurveCollection extends GeometryQuery_1.GeometryQuery {
    /** String name for schema properties */
    geometryCategory = "curveCollection";
    /** Flag for inner loop status. Only used by `Loop`. */
    isInner = false;
    /** Return the sum of the lengths of all contained curves. */
    sumLengths() {
        return SumLengthsContext_1.SumLengthsContext.sumLengths(this);
    }
    /**
     * Return the closest point on the contained curves.
     * @param spacePoint point in space.
     * @param _extend unused here (pass false), but applicable to overrides in [[Path]] and [[BagOfCurves]].
     * @param result optional pre-allocated detail to populate and return.
     * @returns details of the closest point.
     */
    closestPoint(spacePoint, _extend = false, result) {
        let detailA;
        const detailB = new CurveLocationDetail_1.CurveLocationDetail();
        if (this.children !== undefined) {
            for (const child of this.children) {
                if (child.closestPoint(spacePoint, false, detailB))
                    detailA = result = CurveLocationDetail_1.CurveLocationDetail.chooseSmallerA(detailA, detailB).clone(result);
            }
        }
        return detailA;
    }
    /**
     * Announce all points `P` on the contained curves such that the line containing `spacePoint` and `P` is tangent to
     * the contained curves in the view defined by `options.vectorToEye`.
     * * Strictly speaking, each tangent line lies in the plane through `P` whose normal is the cross product of the curve
     * tangent at `P` and `options.vectorToEye`. This is equivalent to tangency as seen in a view plane perpendicular to
     * `options.vectorToEye`.
     * @param spacePoint point in space.
     * @param announceTangent callback to announce each computed tangent. The received [[CurveLocationDetail]] is reused
     * internally, so it should be cloned in the callback if it needs to be saved.
     * @param options (optional) options for computing tangents. See [[TangentOptions]] for defaults.
     */
    emitTangents(spacePoint, announceTangent, options) {
        const strokeHandler = new AnnounceTangentStrokeHandler_1.AnnounceTangentStrokeHandler(spacePoint, announceTangent, options);
        if (this.children !== undefined) {
            for (const child of this.children) {
                if (child instanceof CurvePrimitive_1.CurvePrimitive)
                    child.emitStrokableParts(strokeHandler, options?.strokeOptions);
                else if (child instanceof CurveCollection)
                    child.emitTangents(spacePoint, announceTangent, options);
            }
        }
    }
    /**
     * Return all points `P` on the contained curves such that the line containing `spacePoint` and `P` is tangent to the
     * contained curves in the view defined by `options.vectorToEye`.
     * * See [[emitTangents]] for the definition of tangency employed.
     * @param spacePoint point in space.
     * @param options (optional) options for computing tangents. See [[TangentOptions]] for defaults.
     * @returns an array of details of all tangent points or undefined if no tangent was found.
     */
    allTangents(spacePoint, options) {
        const tangents = [];
        this.emitTangents(spacePoint, (t) => tangents.push(t.clone()), options);
        return (tangents.length === 0) ? undefined : tangents;
    }
    /**
     * Return the point `P` on the contained curves such that the line containing `spacePoint` and `P` is tangent to the
     * contained curves in the view defined by `options.vectorToEye`, and `P` is closest to `options.hintPoint` in this view.
     * * See [[emitTangents]] for the definition of tangency employed.
     * @param spacePoint point in space.
     * @param options (optional) options for computing tangents. See [[TangentOptions]] for defaults.
     * @returns the detail of the closest tangent point or undefined if no tangent was found.
     */
    closestTangent(spacePoint, options) {
        const hint = options?.hintPoint ?? spacePoint;
        let toLocal;
        if (options?.vectorToEye && !options.vectorToEye.isExactEqual({ x: 0, y: 0, z: 1 }))
            toLocal = Matrix3d_1.Matrix3d.createRigidViewAxesZTowardsEye(options.vectorToEye.x, options.vectorToEye.y, options.vectorToEye.z);
        const measureHintDist2 = (pt) => {
            return toLocal?.multiplyTransposeXYZ(hint.x - pt.x, hint.y - pt.y, hint.z - pt.z).magnitudeSquaredXY() ?? pt.distanceSquaredXY(hint);
        };
        let closestTangent;
        let closestDist2 = Geometry_1.Geometry.largeCoordinateResult;
        const collectClosestTangent = (tangent) => {
            const dist2 = measureHintDist2(tangent.point);
            if (!closestTangent || dist2 < closestDist2) {
                closestTangent = tangent.clone(closestTangent);
                closestDist2 = dist2;
            }
        };
        this.emitTangents(spacePoint, collectClosestTangent, options);
        return closestTangent;
    }
    /** Reverse the collection's data so that each child curve's fractional stroking moves in the opposite direction. */
    reverseInPlace() {
        for (const curve of this.children)
            curve.reverseInPlace();
    }
    /**
     * Return the max gap between adjacent primitives in Path and Loop collections.
     * * In a Path, gaps are computed between consecutive primitives.
     * * In a Loop, gaps are computed between consecutive primitives and between last and first.
     * * Gaps are NOT computed between consecutive CurvePrimitives in "unstructured" collections. The type is
     * "unstructured" so gaps should not be semantically meaningful.
     */
    maxGap() {
        return GapSearchContext_1.GapSearchContext.maxGap(this);
    }
    /** Return true if the curve collection has any primitives other than LineSegment3d and LineString3d  */
    checkForNonLinearPrimitives() {
        return CountLinearPartsSearchContext_1.CountLinearPartsSearchContext.hasNonLinearPrimitives(this);
    }
    /** Apply transform recursively to children */
    tryTransformInPlace(transform) {
        return TransformInPlaceContext_1.TransformInPlaceContext.tryTransformInPlace(this, transform);
    }
    /** Return a deep copy. */
    clone() {
        return CloneCurvesContext_1.CloneCurvesContext.clone(this);
    }
    /** Create a deep copy of transformed curves. */
    cloneTransformed(transform) {
        return CloneCurvesContext_1.CloneCurvesContext.clone(this, transform);
    }
    /** Create a deep copy with all linestrings broken down into multiple LineSegment3d. */
    cloneWithExpandedLineStrings() {
        return CloneWithExpandedLineStrings_1.CloneWithExpandedLineStrings.clone(this);
    }
    /**
     * Push all CurvePrimitives contained in the instance onto the `results` array.
     * * This method is recursive. For example, if the CurveCollection contains a Loop, all CurvePrimitives
     * of the Loop are pushed onto `results`.
     */
    collectCurvePrimitivesGo(results, smallestPossiblePrimitives, explodeLinestrings = false) {
        if (this.children) {
            for (const child of this.children) {
                if (child instanceof CurvePrimitive_1.CurvePrimitive)
                    child.collectCurvePrimitivesGo(results, smallestPossiblePrimitives, explodeLinestrings);
                else if (child instanceof CurveCollection)
                    child.collectCurvePrimitivesGo(results, smallestPossiblePrimitives, explodeLinestrings);
            }
        }
    }
    /**
     * Return an array containing all CurvePrimitives in the instance.
     * * This method is recursive. For example, if the CurveCollection contains a Loop, all CurvePrimitives of
     * the Loop are pushed onto the returned array.
     * @param collectorArray optional array to receive primitives. If present, new primitives are ADDED (without
     * clearing the array).
     * @param smallestPossiblePrimitives if false, CurvePrimitiveWithDistanceIndex returns only itself. If true,
     * it recurses to its (otherwise hidden) children.
     */
    collectCurvePrimitives(collectorArray, smallestPossiblePrimitives = false, explodeLineStrings = false) {
        const results = collectorArray === undefined ? [] : collectorArray;
        this.collectCurvePrimitivesGo(results, smallestPossiblePrimitives, explodeLineStrings);
        return results;
    }
    /**
     * Return true for planar region types:
     * * `Loop`
     * * `ParityRegion`
     * * `UnionRegion`
     * @see isAnyRegion
     */
    get isAnyRegionType() {
        return this.dgnBoundaryType() === 2 || this.dgnBoundaryType() === 4 || this.dgnBoundaryType() === 5;
    }
    /** Type guard for AnyRegion */
    isAnyRegion() {
        return this.isAnyRegionType;
    }
    /**
     * Return true for a `Path`, i.e. a chain of curves joined head-to-tail
     * @see isPath
     */
    get isOpenPath() {
        return this.dgnBoundaryType() === 1;
    }
    /** Type guard for Path */
    isPath() {
        return this.isOpenPath;
    }
    /**
     * Return true for a single-loop planar region type, i.e. `Loop`.
     * * This is NOT a test for physical closure of a `Path`.
     * @see isLoop
     */
    get isClosedPath() {
        return this.dgnBoundaryType() === 2;
    }
    /** Type guard for Loop */
    isLoop() {
        return this.isClosedPath;
    }
    /**
     * Extend (increase) the given range as needed to encompass all curves in the curve collection.
     * @param rangeToExtend the given range.
     * @param transform if supplied, the range is extended with transformed curves.
     */
    extendRange(rangeToExtend, transform) {
        const children = this.children;
        if (children) {
            for (const c of children) {
                c.extendRange(rangeToExtend, transform);
            }
        }
    }
    /**
     * Find any CurvePrimitive in the source and evaluate it at the given fraction.
     * * The first CurvePrimitive found is evaluated. Any other CurvePrimitives are ignored.
     * @param source containing `CurvePrimitive` or `CurveCollection`
     * @param fraction fraction to use in `curve.fractionToPoint(fraction)`
     */
    static createCurveLocationDetailOnAnyCurvePrimitive(source, fraction = 0.5) {
        if (!source)
            return undefined;
        if (source instanceof CurvePrimitive_1.CurvePrimitive) {
            return CurveLocationDetail_1.CurveLocationDetail.createCurveEvaluatedFraction(source, fraction);
        }
        else if (source instanceof CurveCollection && source.children !== undefined)
            for (const child of source.children) {
                const detail = this.createCurveLocationDetailOnAnyCurvePrimitive(child, fraction);
                if (detail)
                    return detail;
            }
        return undefined;
    }
    /**
     * Project instance geometry (via dispatch) onto the given ray, and return the extreme fractional parameters
     * of projection.
     * @param ray ray onto which the instance is projected. A `Vector3d` is treated as a `Ray3d` with zero origin.
     * @param lowHigh optional receiver for output
     * @returns range of fractional projection parameters onto the ray, where 0.0 is start of the ray and 1.0 is the
     * end of the ray.
     */
    projectedParameterRange(ray, lowHigh) {
        return PlaneAltitudeRangeContext_1.PlaneAltitudeRangeContext.findExtremeFractionsAlongDirection(this, ray, lowHigh);
    }
    /** Return the immediate parent of the input curve in the instance, or undefined if it is not a descendant. */
    findParentOfDescendant(descendant) {
        for (const child of this.children) {
            if (child === descendant)
                return this;
            if (child instanceof CurveCollection) {
                const parent = child.findParentOfDescendant(descendant);
                if (parent)
                    return parent;
            }
        }
        return undefined;
    }
    ;
}
exports.CurveCollection = CurveCollection;
/**
 * Shared base class for use by both open and closed paths.
 * * A `CurveChain` contains only CurvePrimitives. No other paths, loops, or regions allowed.
 * * The specific derived classes are `Path` and `Loop`.
 * * `CurveChain` is an intermediate class. It is not instantiable on its own.
 * * The related class `CurveChainWithDistanceIndex` is a `CurvePrimitive` whose API presents well-defined mappings
 * from fraction to xyz over the entire chain, but in fact does all the calculations over multiple primitives.
 * @see [Curve Collections]($docs/learning/geometry/CurveCollection.md) learning article.
 * @public
 */
class CurveChain extends CurveCollection {
    /** The curve primitives in the chain. */
    _curves;
    /** Constructor */
    constructor() {
        super();
        this._curves = [];
    }
    /** Return the array of `CurvePrimitive` */
    get children() {
        return this._curves;
    }
    /** Return the start point of the curve chain (start point of the first child). */
    startPoint(result) {
        const firstChild = this.getChild(0);
        if (firstChild)
            return firstChild.fractionToPoint(0.0, result);
        else
            return undefined;
    }
    /** Return the end point of the curve chain (end point of the last child). */
    endPoint(result) {
        const lastChild = this.getChild(this._curves.length - 1);
        if (lastChild)
            return lastChild.fractionToPoint(1.0, result);
        else
            return undefined;
    }
    /**
     * Whether the chain start and end points are defined and within tolerance.
     * * Does not check for planarity or degeneracy.
     * @param tolerance optional distance tolerance (default is [[Geometry.smallMetricDistance]])
     * @param xyOnly if true, ignore z coordinate (default is `false`)
     */
    isPhysicallyClosedCurve(tolerance = Geometry_1.Geometry.smallMetricDistance, xyOnly = false) {
        const p0 = this.startPoint();
        const p1 = this.endPoint();
        return p0 !== undefined && p1 !== undefined && (xyOnly ? p0.isAlmostEqualXY(p1, tolerance) : p0.isAlmostEqual(p1, tolerance));
    }
    /**
     * Return the start point and derivative of the first child of the curve chain.
     * * For queries interior to the chain, use [[CurveChainWithDistanceIndex.fractionToPointAndDerivative]].
     */
    startPointAndDerivative(result) {
        const firstChild = this.getChild(0);
        if (firstChild)
            return firstChild.fractionToPointAndDerivative(0.0, result);
        else
            return undefined;
    }
    /**
     * Return the end point and derivative of the last child of the curve chain.
     * * For queries interior to the chain, use [[CurveChainWithDistanceIndex.fractionToPointAndDerivative]].
     */
    endPointAndDerivative(result) {
        const lastChild = this.getChild(this._curves.length - 1);
        if (lastChild)
            return lastChild.fractionToPointAndDerivative(1.0, result);
        else
            return undefined;
    }
    /**
     * Return the curve primitive at the given `index`, optionally using `modulo` to map `index` to the cyclic indexing.
     * * In particular, `-1` is the final curve.
     * @param index cyclic index
     * @param cyclic whether to employ modulo operator for wrap-around indexing. Default is `true`.
     */
    cyclicCurvePrimitive(index, cyclic = true) {
        const n = this.children.length;
        if (n === 0)
            return undefined;
        if (index >= 0 && index < n) // try simplest non-cyclic access first
            return this.children[index];
        if (cyclic) {
            const index2 = Geometry_1.Geometry.modulo(index, n);
            return this.children[index2];
        }
        return undefined;
    }
    /**
     * Stroke the chain into a simple xyz array.
     * @param options tolerance parameters controlling the stroking.
     */
    getPackedStrokes(options) {
        /**
         * The object returned by "cloneStroked" has the same type (Loop or Path) but instead of a chain of
         * CurvePrimitives as children, it has a single LineString3d child. "getPackedStrokes" just returns
         * the points of that LineString3d using "packedPoints".
         */
        const tree = this.cloneStroked(options);
        if (tree instanceof CurveChain) {
            const children = tree.children;
            if (children.length === 1) {
                const ls = children[0];
                if (ls instanceof LineString3d_1.LineString3d)
                    return ls.packedPoints;
            }
        }
        return undefined;
    }
    /**
     * Add a child curve.
     * @param child curve to add to the chain. The curve is captured by this instance.
     * @return whether the child was added
     */
    tryAddChild(child) {
        if (child && child instanceof CurvePrimitive_1.CurvePrimitive) {
            this._curves.push(child);
            return true;
        }
        return false;
    }
    /** Return a child by index */
    getChild(i) {
        if (i < this._curves.length)
            return this._curves[i];
        return undefined;
    }
    /** Invoke `curve.extendRange(range, transform)` for each child  */
    extendRange(range, transform) {
        for (const curve of this._curves)
            curve.extendRange(range, transform);
    }
    /**
     * Reverse each child curve (in place).
     * Reverse the order of the children array.
     */
    reverseChildrenInPlace() {
        for (const curve of this._curves)
            curve.reverseInPlace();
        this._curves.reverse();
    }
    /** Same as [[reverseChildrenInPlace]]. */
    reverseInPlace() {
        this.reverseChildrenInPlace();
    }
    /**
     * Return the index where target is found in the array of children.
     * @param alsoSearchProxies whether to also check proxy curves of the children
     */
    childIndex(target, alsoSearchProxies) {
        for (let i = 0; i < this._curves.length; i++) {
            if (this._curves[i] === target)
                return i;
        }
        if (alsoSearchProxies ?? false) {
            for (let i = 0; i < this._curves.length; i++) {
                const childCurve = this._curves[i];
                if (childCurve instanceof ProxyCurve_1.ProxyCurve) {
                    if (childCurve.proxyCurve === target)
                        return i;
                }
            }
        }
        return undefined;
    }
    /** Evaluate an indexed curve at a fraction. Return as a CurveLocationDetail that indicates the primitive. */
    primitiveIndexAndFractionToCurveLocationDetailPointAndDerivative(index, fraction, cyclic = false, result) {
        const primitive = this.cyclicCurvePrimitive(index, cyclic);
        if (primitive) {
            return CurveLocationDetail_1.CurveLocationDetail.createCurveEvaluatedFractionPointAndDerivative(primitive, fraction, result);
        }
        return undefined;
    }
}
exports.CurveChain = CurveChain;
/**
 * * A `BagOfCurves` object is a collection of `AnyCurve` objects.
 * * A `BagOfCurves` has no implied properties such as being planar.
 * @public
 */
class BagOfCurves extends CurveCollection {
    /** String name for schema properties */
    curveCollectionType = "bagOfCurves";
    /** Test if `other` is an instance of `BagOfCurves` */
    isSameGeometryClass(other) {
        return other instanceof BagOfCurves;
    }
    /**
     * Array of children.
     * * No restrictions on type.
     */
    _children;
    /** Construct an empty `BagOfCurves` */
    constructor() {
        super();
        this._children = [];
    }
    /** Return the (reference to) array of children */
    get children() {
        return this._children;
    }
    /** Create with given curves. */
    static create(...data) {
        const result = new BagOfCurves();
        for (const child of data) {
            result.tryAddChild(child);
        }
        return result;
    }
    /** Return the boundary type (0) of a corresponding  MicroStation CurveVector */
    dgnBoundaryType() {
        return 0;
    }
    /** Invoke `processor.announceBagOfCurves(this, indexInParent);` */
    announceToCurveProcessor(processor, indexInParent = -1) {
        return processor.announceBagOfCurves(this, indexInParent);
    }
    /** Clone all children in stroked form. */
    cloneStroked(options) {
        const clone = new BagOfCurves();
        let child;
        for (child of this.children) {
            if (child instanceof CurvePrimitive_1.CurvePrimitive) {
                const ls = LineString3d_1.LineString3d.create();
                child.emitStrokes(ls, options);
                if (ls)
                    clone.children.push(ls);
            }
            else if (child instanceof CurveCollection) {
                const childStrokes = child.cloneStroked(options);
                if (childStrokes)
                    clone.children.push(childStrokes);
            }
        }
        return clone;
    }
    /**
     * Return the closest point on the contained curves.
     * @param spacePoint point in space.
     * @param extend applicable only to children of type [[CurvePrimitive]], [[Path]], or [[BagOfCurves]]
     * @param result optional pre-allocated detail to populate and return.
     * @returns details of the closest point.
     */
    closestPoint(spacePoint, extend = false, result) {
        let detailA;
        const detailB = new CurveLocationDetail_1.CurveLocationDetail();
        if (this.children !== undefined) {
            for (const child of this.children) {
                if (child.closestPoint(spacePoint, extend, detailB))
                    detailA = result = CurveLocationDetail_1.CurveLocationDetail.chooseSmallerA(detailA, detailB).clone(result);
            }
        }
        return detailA;
    }
    /** Return an empty `BagOfCurves` */
    cloneEmptyPeer() {
        return new BagOfCurves();
    }
    /** Add a child  */
    tryAddChild(child) {
        if (child)
            this._children.push(child);
        return true;
    }
    /** Get a child by index */
    getChild(i) {
        if (i < this._children.length)
            return this._children[i];
        return undefined;
    }
    /** Second step of double dispatch:  call `handler.handleBagOfCurves(this)` */
    dispatchToGeometryHandler(handler) {
        return handler.handleBagOfCurves(this);
    }
}
exports.BagOfCurves = BagOfCurves;
//# sourceMappingURL=CurveCollection.js.map