/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
 * @module Curve
 */
import { Geometry } from "../../Geometry";
import { Point3d } from "../../geometry3d/Point3dVector3d";
import { Ray3d } from "../../geometry3d/Ray3d";
import { Newton1dUnboundedApproximateDerivative } from "../../numerics/Newton";
import { CurveExtendOptions } from "../CurveExtendMode";
import { CurveLocationDetail } from "../CurveLocationDetail";
import { NewtonRtoRStrokeHandler } from "./NewtonRtoRStrokeHandler";
/**
 * Context for searching for the closest point to a CurvePrimitive.
 * @internal
 */
export class ClosestPointStrokeHandler extends NewtonRtoRStrokeHandler {
    _curve;
    _closestPoint;
    _spacePoint;
    _extend;
    // fraction and function value on one side of an interval that may bracket a root
    _fractionA = 0;
    _functionA = 0;
    // fraction and function value on the other side of an interval that may bracket a root
    _fractionB = 0;
    _functionB = 0;
    _numThisCurve = 0;
    // scratch vars to use within methods
    _workPoint;
    _workRay;
    _newtonSolver;
    /** Constructor */
    constructor(spacePoint, extend, result) {
        super();
        this._spacePoint = spacePoint;
        this._workPoint = Point3d.create();
        this._workRay = Ray3d.createZero();
        this._closestPoint = result;
        this._extend = extend ?? false;
        this.startCurvePrimitive(undefined);
        this._newtonSolver = new Newton1dUnboundedApproximateDerivative(this);
    }
    claimResult() {
        if (this._closestPoint) {
            this._newtonSolver.setX(this._closestPoint.fraction);
            this._curve = this._closestPoint.curve;
            if (this._newtonSolver.runIterations()) {
                let fraction = this._newtonSolver.getX();
                fraction = CurveExtendOptions.correctFraction(this._extend, fraction);
                this.announceSolutionFraction(fraction);
            }
        }
        return this._closestPoint;
    }
    needPrimaryGeometryForStrokes() {
        return true;
    }
    startCurvePrimitive(curve) {
        this._curve = curve;
        this._fractionA = 0.0;
        this._numThisCurve = 0;
        this._functionA = 0.0;
    }
    endCurvePrimitive() {
    }
    announceIntervalForUniformStepStrokes(cp, numStrokes, fraction0, fraction1) {
        this.startCurvePrimitive(cp);
        this.announceSolutionFraction(0.0); // test start point as closest
        this.announceSolutionFraction(1.0); // test end point as closest
        if (numStrokes < 1)
            numStrokes = 1;
        const df = 1.0 / numStrokes;
        for (let i = 0; i <= numStrokes; i++) {
            const fraction = Geometry.interpolate(fraction0, i * df, fraction1);
            cp.fractionToPointAndDerivative(fraction, this._workRay);
            this.announceRay(fraction, this._workRay);
        }
    }
    announceCandidate(cp, fraction, point) {
        const distance = this._spacePoint.distance(point);
        if (this._closestPoint && distance > this._closestPoint.a)
            return;
        this._closestPoint = CurveLocationDetail.createCurveFractionPoint(cp, fraction, point, this._closestPoint);
        this._closestPoint.a = distance;
        if (this._parentCurvePrimitive !== undefined)
            this._closestPoint.curve = this._parentCurvePrimitive;
    }
    announceSegmentInterval(cp, point0, point1, _numStrokes, fraction0, fraction1) {
        let localFraction = this._spacePoint.fractionOfProjectionToLine(point0, point1, 0.0);
        // only consider extending the segment if the immediate caller says we are at endpoints
        if (!this._extend)
            localFraction = Geometry.clampToStartEnd(localFraction, 0.0, 1.0);
        else {
            if (fraction0 !== 0.0)
                localFraction = Math.max(localFraction, 0.0);
            if (fraction1 !== 1.0)
                localFraction = Math.min(localFraction, 1.0);
        }
        this._workPoint = point0.interpolate(localFraction, point1);
        const globalFraction = Geometry.interpolate(fraction0, localFraction, fraction1);
        this.announceCandidate(cp, globalFraction, this._workPoint);
    }
    /**
     * Given a function `f` and (unordered) fractions `a` and `b`, search for and announce a root of `f` in this
     * fractional interval.
     * * This method searches for a root of `f` if and only if the stroke segment defined by `(a, f(a))` and
     * `(b, f(b))` has a root. This is a HEURISTIC: given continuous `f` between `a` and `b`, a root of the stroke
     * segment implies a root of `f`, but not vice-versa. Therefore, if the strokes are not sufficiently dense,
     * this method can miss a root of `f`.
     */
    searchInterval() {
        if (this._functionA * this._functionB > 0)
            return; // stroke segment has no root; ASSUME the function has no root either
        if (this._functionA === 0)
            this.announceSolutionFraction(this._fractionA);
        if (this._functionB === 0)
            this.announceSolutionFraction(this._fractionB);
        // by the Intermediate Value Theorem, a root lies between fractionA and fractionB; use Newton to find it.
        if (this._functionA * this._functionB < 0) {
            const fraction = Geometry.inverseInterpolate(this._fractionA, this._functionA, this._fractionB, this._functionB);
            if (fraction) {
                this._newtonSolver.setX(fraction);
                if (this._newtonSolver.runIterations())
                    this.announceSolutionFraction(this._newtonSolver.getX());
            }
        }
    }
    announceSolutionFraction(fraction) {
        if (this._curve)
            this.announceCandidate(this._curve, fraction, this._curve.fractionToPoint(fraction));
    }
    /**
     * Evaluate the univariate real-valued function for which we are finding roots.
     * * For finding the closest point to curve X from point Q, this function is `f(t) := Q-X(t) dot X'(t)`.
     * * Either `pointAndDerivative` must be defined, or both `fraction` and `curve`.
     * @param pointAndDerivative pre-evaluated curve
     * @param fraction fraction at which to evaluate `curve`
     * @param curve curve to evaluate at `fraction`
     */
    evaluateFunction(pointAndDerivative, fraction, curve) {
        if (pointAndDerivative)
            this._workRay.setFrom(pointAndDerivative);
        else if (fraction !== undefined && curve)
            this._workRay = curve.fractionToPointAndDerivative(fraction, this._workRay);
        else
            return undefined;
        return this._workRay.dotProductToPoint(this._spacePoint);
    }
    evaluate(fraction) {
        let curve = this._curve;
        if (this._parentCurvePrimitive)
            curve = this._parentCurvePrimitive;
        const value = this.evaluateFunction(undefined, fraction, curve);
        if (value === undefined)
            return false;
        this.currentF = value;
        return true;
    }
    announceRay(fraction, data) {
        this._functionB = this.evaluateFunction(data);
        this._fractionB = fraction;
        if (this._numThisCurve++ > 0) // after the first stroke point, a stroke segment is defined, so we have an interval
            this.searchInterval();
        this._functionA = this._functionB;
        this._fractionA = this._fractionB;
    }
    announcePointTangent(point, fraction, tangent) {
        this._workRay.set(point, tangent);
        this.announceRay(fraction, this._workRay);
    }
}
//# sourceMappingURL=ClosestPointStrokeHandler.js.map