import { Point } from './../../math/geometry/point';
import { GeomConstants } from './../../math/geometry/geomConstants';
import { Curve } from './../../math/geometry/curve';
import { Ellipse } from './../../math/geometry/ellipse';
import { LineSegment } from './../../math/geometry/lineSegment';
export class Arrowhead {
    constructor() {
        this.length = Arrowhead.defaultArrowheadLength;
        this.width = 0;
    }
    clone() {
        const r = new Arrowhead();
        r.length = this.length;
        r.width = this.width;
        r.tipPosition = this.tipPosition;
        return r;
    }
    // the edgeGeometry.Curve is trimmed already by the node boundaries</param>
    static calculateArrowheads(edgeGeometry) {
        if (edgeGeometry.sourceArrowhead == null && edgeGeometry.targetArrowhead == null)
            return true;
        const parStart = Arrowhead.findTrimStartForArrowheadAtSource(edgeGeometry);
        if (parStart == undefined) {
            return false;
        }
        const parEnd = Arrowhead.findTrimEndForArrowheadAtTarget(edgeGeometry);
        if (parEnd == undefined) {
            return false;
        }
        if (parStart > parEnd - GeomConstants.intersectionEpsilon ||
            Curve.closeIntersectionPoints(edgeGeometry.curve.value(parStart), edgeGeometry.curve.value(parEnd)))
            return false; //after the trim nothing would be left of the curve
        const c = edgeGeometry.curve.trim(parStart, parEnd);
        if (c == null)
            return false;
        if (edgeGeometry.sourceArrowhead != null)
            edgeGeometry.sourceArrowhead.tipPosition = edgeGeometry.curve.start;
        if (edgeGeometry.targetArrowhead != null)
            edgeGeometry.targetArrowhead.tipPosition = edgeGeometry.curve.end;
        edgeGeometry.curve = c;
        return true;
    }
    static getIntersectionsWithArrowheadCircle(curve, arrowheadLength, circleCenter) {
        const e = Ellipse.mkFullEllipseNNP(arrowheadLength, arrowheadLength, circleCenter);
        return Curve.getAllIntersections(e, curve, true);
    }
    // we need to pass arrowhead length here since the original length mibh
    static findTrimEndForArrowheadAtTarget(edgeGeometry) {
        const eps = GeomConstants.distanceEpsilon * GeomConstants.distanceEpsilon;
        //Assert.assert((edgeGeometry.Curve.End - edgeGeometry.Curve.start).LengthSquared > eps);
        let p = edgeGeometry.curve.parEnd;
        if (edgeGeometry.targetArrowhead == null || edgeGeometry.targetArrowhead.length <= GeomConstants.distanceEpsilon)
            return p;
        const curve = edgeGeometry.curve;
        let arrowheadLength = edgeGeometry.targetArrowhead.length;
        let newCurveEnd;
        let intersections;
        let reps = 10;
        do {
            reps--;
            if (reps == 0)
                return;
            intersections = Arrowhead.getIntersectionsWithArrowheadCircle(curve, arrowheadLength, curve.end);
            p = intersections.length != 0 ? Math.max(...intersections.map((x) => x.par1)) : curve.parEnd;
            newCurveEnd = edgeGeometry.curve.value(p);
            arrowheadLength /= 2;
        } while (newCurveEnd.sub(curve.start).lengthSquared < eps || intersections.length == 0);
        //we would like to have at least something left from the curve
        return p;
    }
    static findTrimStartForArrowheadAtSource(edgeGeometry) {
        if (edgeGeometry.sourceArrowhead == null || edgeGeometry.sourceArrowhead.length <= GeomConstants.distanceEpsilon)
            return edgeGeometry.curve.parStart;
        const eps = GeomConstants.distanceEpsilon * GeomConstants.distanceEpsilon;
        /*Assert.assert(
          edgeGeometry.curve.end.sub(edgeGeometry.curve.start).lengthSquared > eps,
        )*/
        let arrowheadLength = edgeGeometry.sourceArrowhead.length;
        let newStart;
        const curve = edgeGeometry.curve;
        let intersections;
        let reps = 10;
        let p;
        while (--reps > 0) {
            intersections = Arrowhead.getIntersectionsWithArrowheadCircle(curve, arrowheadLength, curve.start);
            if (intersections.length == 0)
                return curve.parStart;
            p = Math.min(...intersections.map((x) => x.par1));
            newStart = intersections.filter((x) => x.par1 == p)[0].x;
            // check that something is left from the curve
            if (newStart.sub(curve.end).lengthSquared >= eps)
                return p;
            arrowheadLength /= 2;
        }
    }
    // trim the edge curve with the node boundaries
    static trimSplineAndCalculateArrowheads(edge, spline, narrowestInterval) {
        return Arrowhead.trimSplineAndCalculateArrowheadsII(edge.edgeGeometry, edge.source.boundaryCurve, edge.target.boundaryCurve, spline, narrowestInterval);
    }
    // trim the edge curve with the node boundaries
    static trimSplineAndCalculateArrowheadsII(edgeGeometry, sourceBoundary, targetBoundary, spline, narrowestInterval) {
        edgeGeometry.curve = Curve.trimEdgeSplineWithNodeBoundaries(sourceBoundary, targetBoundary, spline, narrowestInterval);
        if (edgeGeometry.curve == null)
            return false;
        if ((edgeGeometry.sourceArrowhead == null || edgeGeometry.sourceArrowhead.length < GeomConstants.distanceEpsilon) &&
            (edgeGeometry.targetArrowhead == null || edgeGeometry.targetArrowhead.length < GeomConstants.distanceEpsilon))
            return true; //there are no arrowheads
        let success = false;
        const sourceArrowheadSavedLength = edgeGeometry.sourceArrowhead != null ? edgeGeometry.sourceArrowhead.length : 0;
        const targetArrowheadSavedLength = edgeGeometry.targetArrowhead != null ? edgeGeometry.targetArrowhead.length : 0;
        const len = edgeGeometry.curve.end.sub(edgeGeometry.curve.start).length;
        if (edgeGeometry.sourceArrowhead != null)
            edgeGeometry.sourceArrowhead.length = Math.min(len, sourceArrowheadSavedLength);
        if (edgeGeometry.targetArrowhead != null)
            edgeGeometry.targetArrowhead.length = Math.min(len, targetArrowheadSavedLength);
        let count = 10;
        while (((edgeGeometry.sourceArrowhead != null && edgeGeometry.sourceArrowhead.length > GeomConstants.intersectionEpsilon) ||
            (edgeGeometry.targetArrowhead != null && edgeGeometry.targetArrowhead.length > GeomConstants.intersectionEpsilon)) &&
            !success) {
            success = Arrowhead.calculateArrowheads(edgeGeometry);
            if (!success) {
                if (edgeGeometry.sourceArrowhead != null)
                    edgeGeometry.sourceArrowhead.length *= 0.5;
                if (edgeGeometry.targetArrowhead != null)
                    edgeGeometry.targetArrowhead.length *= 0.5;
            }
            count--;
            if (count == 0)
                break;
        }
        if (!success) {
            //to avoid drawing the arrowhead to (0,0)
            if (edgeGeometry.sourceArrowhead != null)
                edgeGeometry.sourceArrowhead.tipPosition = spline.start;
            if (edgeGeometry.targetArrowhead != null)
                edgeGeometry.targetArrowhead.tipPosition = spline.end;
        }
        if (edgeGeometry.sourceArrowhead != null)
            edgeGeometry.sourceArrowhead.length = sourceArrowheadSavedLength;
        if (edgeGeometry.targetArrowhead != null)
            edgeGeometry.targetArrowhead.length = targetArrowheadSavedLength;
        return success;
    }
    // Creates a spline between two nodes big enough to draw arrowheads
    // <
    static createBigEnoughSpline(edge) {
        const a = edge.source.center;
        let b = edge.target.center;
        const bMinA = b.sub(a);
        const l = bMinA.length;
        let perp;
        if (l < 0.001) {
            perp = new Point(1, 0);
            b = a.add(perp.rotate(Math.PI / 2));
        }
        else {
            perp = bMinA.rotate(Math.PI / 2);
        }
        let maxArrowLength = 1;
        if (edge.edgeGeometry.sourceArrowhead != null) {
            maxArrowLength += edge.edgeGeometry.sourceArrowhead.length;
        }
        if (edge.edgeGeometry.targetArrowhead != null) {
            maxArrowLength += edge.edgeGeometry.targetArrowhead.length;
        }
        perp = perp.normalize().mul(1.5 * maxArrowLength);
        for (let i = 1; i < 10000; i = i * 2) {
            const seg = Curve.createBezierSegN(a, b, perp, i);
            if (Arrowhead.trimSplineAndCalculateArrowheadsII(edge.edgeGeometry, edge.source.boundaryCurve, edge.target.boundaryCurve, seg, false)) {
                return;
            }
        }
        Arrowhead.createEdgeCurveWithNoTrimming(edge, a, b);
    }
    // this method should never be called!
    static createEdgeCurveWithNoTrimming(edge, a, b) {
        const ab = b.sub(a).normalize();
        let lineStart = a;
        let lineEnd = b;
        const targetArrow = edge.edgeGeometry.targetArrowhead;
        if (targetArrow != null) {
            targetArrow.tipPosition = b;
            lineEnd = b.sub(ab.mul(targetArrow.length));
        }
        const sourceArrow = edge.edgeGeometry.sourceArrowhead;
        if (sourceArrow != null) {
            sourceArrow.tipPosition = a;
            lineStart = a.add(ab.mul(sourceArrow.length));
        }
        edge.edgeGeometry.curve = LineSegment.mkPP(lineStart, lineEnd);
    }
}
Arrowhead.defaultArrowheadLength = 5;
//# sourceMappingURL=arrowhead.js.map