//  the router between nodes
import { Rectangle, Point } from '..';
import { CurvePort } from '../layout/core/curvePort';
import { EdgeGeometry } from '../layout/core/edgeGeometry';
import { FloatingPort } from '../layout/core/floatingPort';
import { HookUpAnywhereFromInsidePort } from '../layout/core/hookUpAnywhereFromInsidePort';
import { Polyline, LineSegment, Curve, PointLocation, GeomConstants } from '../math/geometry';
import { Ellipse } from '../math/geometry/ellipse';
import { TriangleOrientation } from '../math/geometry/point';
import { PolylinePoint } from '../math/geometry/polylinePoint';
import { HitTestBehavior } from '../math/geometry/RTree/HitTestBehavior';
import { SmoothedPolyline } from '../math/geometry/smoothedPolyline';
import { InteractiveObstacleCalculator } from './interactiveObstacleCalculator';
import { SingleSourceMultipleTargetsShortestPathOnVisibilityGraph } from './SingleSourceMultipleTargetsShortestPathOnVisibilityGraph';
import { SingleSourceSingleTargetShortestPathOnVisibilityGraph } from './SingleSourceSingleTargetShortestPathOnVisibilityGraph';
import { ConeSpanner } from './spline/coneSpanner/ConeSpanner';
import { Polygon } from './visibility/Polygon';
import { TollFreeVisibilityEdge } from './visibility/TollFreeVisibilityEdge';
import { VisibilityGraph } from './visibility/VisibilityGraph';
import { VisibilityKind } from './visibility/VisibilityKind';
import { Algorithm } from '../utils/algorithm';
import { InteractiveTangentVisibilityGraphCalculator } from './visibility/InteractiveTangentVisibilityGraphCalculator';
import { addRange } from '../utils/setOperations';
import { PointVisibilityCalculator } from './visibility/PointVisibilityCalculator';
import { Assert } from '../utils/assert';
import { RelaxedPolylinePoint } from './RelaxedPolylinePoint';
export class InteractiveEdgeRouter extends Algorithm {
    constructor() {
        super(...arguments);
        this.IgnoreTightPadding = false;
        // RectangleNode<Polyline, Point> RootOfTightHierarchy {
        //     get { return this.obstacleCalculator.RootOfTightHierararchy; }
        // }
        this.activeRectangle = Rectangle.mkEmpty();
        // Array<Polyline> activeTightPolylines = new Array<Polyline>();
        this.activePolygons = new Array();
        this.alreadyAddedOrExcludedPolylines = new Set();
        this.UseEdgeLengthMultiplier = false;
        //  if set to true the algorithm will try to shortcut a shortest polyline inner points
        this.UseInnerPolylingShortcutting = true;
        //  if set to true the algorithm will try to shortcut a shortest polyline start and end
        this.UsePolylineEndShortcutting = true;
        this.AllowedShootingStraightLines = true;
        this.LookForRoundedVertices = false;
    }
    get Obstacles() {
        return this.obstacles_;
    }
    set Obstacles(value) {
        this.obstacles_ = value;
    }
    get EnteringAngleBound() {
        return this.enteringAngleBound_;
    }
    set EnteringAngleBound(value) {
        this.enteringAngleBound_ = value;
    }
    get SourceTightPolyline() {
        return this._sourceTightPolyline;
    }
    set SourceTightPolyline(value) {
        this._sourceTightPolyline = value;
    }
    get TargetTightPolyline() {
        return this.targetTightPolyline;
    }
    set TargetTightPolyline(value) {
        this.targetTightPolyline = value;
    }
    get TargetLoosePolyline() {
        return this.targetLoosePolyline;
    }
    set TargetLoosePolyline(value) {
        this.targetLoosePolyline = value;
    }
    get VisibilityGraph() {
        return this.visibilityGraph;
    }
    set VisibilityGraph(value) {
        this.visibilityGraph = value;
    }
    //  the port of the edge start
    get SourcePort() {
        return this.sourcePort;
    }
    set SourcePort(value) {
        this.sourcePort = value;
        if (this.sourcePort != null) {
            this.SourceTightPolyline = InteractiveEdgeRouter.GetFirstHitPolyline(this.sourcePort.Location, this.ObstacleCalculator.RootOfTightHierarchy);
            if (this.sourcePort instanceof FloatingPort) {
                this.alreadyAddedOrExcludedPolylines.add(this.SourceLoosePolyline);
                // we need to exclude the loose polyline around the source port from the tangent visibily graph
                this.StartPointOfEdgeRouting = this.SourcePort.Location;
            }
            else {
                const bp = this.sourcePort;
                this.StartPointOfEdgeRouting = this.TakeBoundaryPortOutsideOfItsLoosePolyline(bp.Curve, bp.Parameter, this.SourceLoosePolyline);
            }
        }
    }
    //  the port of the edge end
    get TargetPort() {
        return this.targetPort;
    }
    set TargetPort(value) {
        this.targetPort = value;
    }
    //  we further pad each node but not more than LoosePadding.
    get LoosePadding() {
        return this.loosePadding;
    }
    set LoosePadding(value) {
        this.loosePadding = value;
        if (this.ObstacleCalculator != null) {
            this.ObstacleCalculator.LoosePadding = value;
        }
    }
    get StartPointOfEdgeRouting() {
        return this.startPointOfRouting_;
    }
    set StartPointOfEdgeRouting(value) {
        this.startPointOfRouting_ = value;
    }
    ExtendVisibilityGraphToLocation(location) {
        if (this.VisibilityGraph == null) {
            this.VisibilityGraph = new VisibilityGraph();
        }
        let addedPolygons = null;
        if (!this.activeRectangle.contains(location)) {
            if (this.activeRectangle.isEmpty) {
                this.activeRectangle = Rectangle.mkPP(this.SourcePort.Location, location);
            }
            else {
                this.activeRectangle.add(location);
            }
            addedPolygons = this.GetAddedPolygonesAndMaybeExtendActiveRectangle();
            for (const polygon of addedPolygons) {
                this.VisibilityGraph.AddHole(polygon.Polyline);
            }
        }
        if (addedPolygons == null || addedPolygons.length == 0) {
            if (this.targetVV != null) {
                this.VisibilityGraph.RemoveVertex(this.targetVV);
            }
            this.CalculateEdgeTargetVisibilityGraph(location);
        }
        else {
            this.RemovePointVisibilityGraphs();
            const visibilityGraphGenerator = new InteractiveTangentVisibilityGraphCalculator(addedPolygons, this.activePolygons, this.VisibilityGraph);
            visibilityGraphGenerator.run();
            addRange(this.activePolygons, addedPolygons);
            this.CalculateEdgeTargetVisibilityGraph(location);
            this.CalculateSourcePortVisibilityGraph();
        }
    }
    RemovePointVisibilityGraphs() {
        if (this.targetVV != null) {
            this.VisibilityGraph.RemoveVertex(this.targetVV);
        }
        if (this.sourceVV != null) {
            this.VisibilityGraph.RemoveVertex(this.sourceVV);
        }
    }
    CalculateEdgeTargetVisibilityGraph(location) {
        this.targetVV = PointVisibilityCalculator.CalculatePointVisibilityGraph(Array.from(this.GetActivePolylines()), this.VisibilityGraph, location, VisibilityKind.Tangent);
    }
    CalculateSourcePortVisibilityGraph() {
        this.sourceVV = PointVisibilityCalculator.CalculatePointVisibilityGraph(Array.from(this.GetActivePolylines()), this.VisibilityGraph, this.StartPointOfEdgeRouting, VisibilityKind.Tangent);
    }
    TakeBoundaryPortOutsideOfItsLoosePolyline(nodeBoundary, parameter, loosePolyline) {
        const location = nodeBoundary.value(parameter);
        let tangent = nodeBoundary
            .leftDerivative(parameter)
            .normalize()
            .add(nodeBoundary.rightDerivative(parameter).normalize())
            .normalize();
        if (Point.getTriangleOrientation(InteractiveEdgeRouter.PointInsideOfConvexCurve(nodeBoundary), location, location.add(tangent)) ==
            TriangleOrientation.Counterclockwise) {
            tangent = tangent.mul(-1);
        }
        tangent = tangent.rotate(Math.PI / 2);
        const len = loosePolyline.boundingBox.diagonal;
        let ls = LineSegment.mkPP(location, location.add(tangent.mul(len)));
        const p = Curve.intersectionOne(ls, loosePolyline, false).x;
        let del = tangent.mul(p.sub(location).length / 2);
        // Point del = tangent * this.OffsetForPolylineRelaxing * 2;
        while (true) {
            ls = LineSegment.mkPP(location, p.add(del));
            let foundIntersectionsOutsideOfSource = false;
            for (const ii of InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolylineLR(ls, this.ObstacleCalculator.RootOfLooseHierarchy)) {
                if (ii.seg1 != loosePolyline) {
                    del = del.div(1.5);
                    foundIntersectionsOutsideOfSource = true;
                    break;
                }
            }
            if (!foundIntersectionsOutsideOfSource) {
                break;
            }
        }
        return ls.end;
    }
    static PointInsideOfConvexCurve(nodeBoundary) {
        return nodeBoundary.value(0).add(nodeBoundary.value(1.5)).div(2);
        // a hack !!!!!!!!!!!!!!!!!!!!!!
    }
    // Point TakeSourcePortOutsideOfLoosePolyline() {
    //     CurvePort bp = SourcePort as CurvePort;
    //     ICurve nodeBoundary = bp.Node.BoundaryCurve;
    //     Point location = bp.Location;
    //     Point tangent = (nodeBoundary.LeftDerivative(bp.Parameter).Normalize() + nodeBoundary.RightDerivative(bp.Parameter).Normalize()).Normalize();
    //     if (Point.GetTriangleOrientation(bp.Node.Center, location, location + tangent) == TriangleOrientation.Counterclockwise)
    //         tangent = -tangent;
    //     tangent = tangent.Rotate(Math.PI / 2);
    //     Number len = this.sourceLoosePolyline.BoundingBox.Diagonal;
    //     Point portLocation = bp.Location;
    //     LineSegment ls = LineSegment.mkPP(portLocation, portLocation + len * tangent);
    //     Point p = Curve.GetAllIntersections(ls, this.SourceLoosePolyline, false)[0].x;
    //     Point del = tangent * this.OffsetForPolylineRelaxing * 2;
    //     while (true) {
    //         ls = LineSegment.mkPP(portLocation, p + del);
    //         bool foundIntersectionsOutsideOfSource = false;
    //         foreach (IntersectionInfo ii in IntersectionsOfLineAndRectangleNodeOverPolyline(ls, this.obstacleCalculator.RootOfLooseHierarchy))
    //             if (ii.seg1 != this.SourceLoosePolyline) {
    //                 del /= 1.5;
    //                 foundIntersectionsOutsideOfSource = true;
    //                 break;
    //             }
    //         if (!foundIntersectionsOutsideOfSource)
    //             break;
    //     }
    //     return ls.End;
    // }
    *GetActivePolylines() {
        for (const polygon of this.activePolygons) {
            yield polygon.Polyline;
        }
    }
    GetAddedPolygonesAndMaybeExtendActiveRectangle() {
        const rect = this.activeRectangle;
        const addedPolygones = new Array();
        let added;
        do {
            added = false;
            for (const loosePoly of this.ObstacleCalculator.RootOfLooseHierarchy.GetNodeItemsIntersectingRectangle(this.activeRectangle)) {
                if (!this.alreadyAddedOrExcludedPolylines.has(loosePoly)) {
                    rect.addRec(loosePoly.boundingBox);
                    addedPolygones.push(new Polygon(loosePoly));
                    this.alreadyAddedOrExcludedPolylines.add(loosePoly);
                    // we register the loose polyline in the set to not add it twice
                    added = true;
                }
            }
            if (added) {
                this.activeRectangle = rect;
            }
        } while (added);
        return addedPolygones;
    }
    RelaxPolyline() {
        let relaxedPolylinePoint = InteractiveEdgeRouter.CreateRelaxedPolylinePoints(this._polyline);
        for (relaxedPolylinePoint = relaxedPolylinePoint.Next; relaxedPolylinePoint.Next != null; relaxedPolylinePoint = relaxedPolylinePoint.Next) {
            this.RelaxPolylinePoint(relaxedPolylinePoint);
        }
    }
    static CreateRelaxedPolylinePoints(polyline) {
        let p = polyline.startPoint;
        const ret = new RelaxedPolylinePoint(p, p.point);
        let currentRelaxed = ret;
        while (p.next != null) {
            p = p.next;
            const r = new RelaxedPolylinePoint(p, p.point);
            r.Prev = currentRelaxed;
            currentRelaxed.Next = r;
            currentRelaxed = r;
        }
        return ret;
    }
    RelaxPolylinePoint(relaxedPoint) {
        if (relaxedPoint.PolylinePoint.prev.prev == null &&
            this.SourcePort instanceof CurvePort &&
            relaxedPoint.PolylinePoint.polyline != this.SourceLoosePolyline) {
            return;
        }
        if (relaxedPoint.PolylinePoint.next.next == null &&
            this.TargetPort instanceof CurvePort &&
            relaxedPoint.PolylinePoint.polyline != this.TargetLoosePolyline) {
            return;
        }
        for (let d = this.OffsetForPolylineRelaxing; d > GeomConstants.distanceEpsilon && !this.RelaxWithGivenOffset(d, relaxedPoint);) {
            d /= 2;
        }
    }
    RelaxWithGivenOffset(offset, relaxedPoint) {
        Assert.assert(offset > GeomConstants.distanceEpsilon);
        // otherwise we are cycling infinitely here
        InteractiveEdgeRouter.SetRelaxedPointLocation(offset, relaxedPoint);
        if (this.StickingSegmentDoesNotIntersectTightObstacles(relaxedPoint)) {
            return true;
        }
        InteractiveEdgeRouter.PullCloserRelaxedPoint(relaxedPoint.Prev);
        return false;
    }
    static PullCloserRelaxedPoint(relaxedPolylinePoint) {
        relaxedPolylinePoint.PolylinePoint.point = relaxedPolylinePoint.OriginalPosition.mul(0.2).add(relaxedPolylinePoint.PolylinePoint.point.mul(0.8));
    }
    StickingSegmentDoesNotIntersectTightObstacles(relaxedPoint) {
        return (!this.PolylineSegmentIntersectsTightHierarchy(relaxedPoint.PolylinePoint.point, relaxedPoint.Prev.PolylinePoint.point) &&
            (relaxedPoint.Next == null ||
                !this.PolylineSegmentIntersectsTightHierarchy(relaxedPoint.PolylinePoint.point, relaxedPoint.Next.PolylinePoint.point)));
    }
    PolylineSegmentIntersectsTightHierarchy(a, b) {
        return this.PolylineIntersectsPolyRectangleNodeOfTightHierarchyPPR(a, b, this.ObstacleCalculator.RootOfTightHierarchy);
    }
    PolylineIntersectsPolyRectangleNodeOfTightHierarchyPPR(a, b, rect) {
        return this.PolylineIntersectsPolyRectangleNodeOfTightHierarchy(LineSegment.mkPP(a, b), rect);
    }
    PolylineIntersectsPolyRectangleNodeOfTightHierarchy(ls, rect) {
        if (!ls.boundingBox.intersects(rect.irect)) {
            return false;
        }
        if (rect.UserData != null) {
            for (const ii of Curve.getAllIntersections(ls, rect.UserData, false)) {
                if (ii.seg1 != this.SourceTightPolyline && ii.seg1 != this.TargetTightPolyline) {
                    return true;
                }
                if ((ii.seg1 == this.SourceTightPolyline && this.SourcePort) instanceof CurvePort) {
                    return true;
                }
                if ((ii.seg1 == this.TargetTightPolyline && this.TargetPort) instanceof CurvePort) {
                    return true;
                }
            }
            return false;
        }
        return (this.PolylineIntersectsPolyRectangleNodeOfTightHierarchy(ls, rect.Left) ||
            this.PolylineIntersectsPolyRectangleNodeOfTightHierarchy(ls, rect.Right));
    }
    static IntersectionsOfLineAndRectangleNodeOverPolylineLR(ls, rectNode) {
        const ret = new Array();
        InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolyline(ls, rectNode, ret);
        return ret;
    }
    static IntersectionsOfLineAndRectangleNodeOverPolyline(ls, rectNode, listOfIntersections) {
        if (rectNode == null) {
            return;
        }
        if (!ls.boundingBox.intersects(rectNode.irect)) {
            return;
        }
        if (rectNode.UserData != null) {
            addRange(listOfIntersections, Curve.getAllIntersections(ls, rectNode.UserData, true));
            return;
        }
        InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolyline(ls, rectNode.Left, listOfIntersections);
        InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolyline(ls, rectNode.Right, listOfIntersections);
    }
    LineCanBeAcceptedForRouting(ls) {
        const sourceIsFloating = this.SourcePort instanceof FloatingPort;
        const targetIsFloating = this.TargetPort instanceof FloatingPort;
        if (!sourceIsFloating && !this.targetIsInsideOfSourceTightPolyline) {
            if (!this.InsideOfTheAllowedConeOfBoundaryPort(ls.end, this.SourcePort)) {
                return false;
            }
        }
        if (!targetIsFloating && this.TargetPort != null && !this.sourceIsInsideOfTargetTightPolyline) {
            if (!this.InsideOfTheAllowedConeOfBoundaryPort(ls.start, this.TargetPort)) {
                return false;
            }
        }
        const xx = InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolylineLR(ls, this.ObstacleCalculator.RootOfTightHierarchy);
        for (const ii of xx) {
            if (ii.seg1 == this.SourceTightPolyline) {
                continue;
            }
            if (ii.seg1 == this.targetTightPolyline) {
                continue;
            }
            return false;
        }
        return true;
    }
    InsideOfTheAllowedConeOfBoundaryPort(pointToTest, port) {
        const boundaryCurve = port.Curve;
        const curveIsClockwise = InteractiveObstacleCalculator.CurveIsClockwise(boundaryCurve, InteractiveEdgeRouter.PointInsideOfConvexCurve(boundaryCurve));
        const portLocation = port.Location;
        const pointOnTheRightConeSide = this.GetPointOnTheRightBoundaryPortConeSide(portLocation, boundaryCurve, curveIsClockwise, port.Parameter);
        const pointOnTheLeftConeSide = this.GetPointOnTheLeftBoundaryPortConeSide(portLocation, boundaryCurve, curveIsClockwise, port.Parameter);
        return (Point.getTriangleOrientation(portLocation, pointOnTheRightConeSide, pointToTest) != TriangleOrientation.Clockwise &&
            Point.getTriangleOrientation(portLocation, pointToTest, pointOnTheLeftConeSide) != TriangleOrientation.Clockwise);
    }
    GetPointOnTheRightBoundaryPortConeSide(portLocation, boundaryCurve, curveIsClockwise, portParam) {
        const tan = curveIsClockwise ? boundaryCurve.rightDerivative(portParam) : boundaryCurve.leftDerivative(portParam).neg();
        return portLocation.add(tan.rotate(this.EnteringAngleBound));
    }
    GetPointOnTheLeftBoundaryPortConeSide(portLocation, boundaryCurve, curveIsClockwise, portParam) {
        const tan = curveIsClockwise ? boundaryCurve.leftDerivative(portParam).neg() : boundaryCurve.rightDerivative(portParam);
        return portLocation.add(tan.rotate(-this.EnteringAngleBound));
    }
    static SetRelaxedPointLocation(offset, relaxedPoint) {
        const leftTurn = Point.getTriangleOrientation(relaxedPoint.Next.OriginalPosition, relaxedPoint.OriginalPosition, relaxedPoint.Prev.OriginalPosition) ==
            TriangleOrientation.Counterclockwise;
        let v = relaxedPoint.Next.OriginalPosition.sub(relaxedPoint.Prev.OriginalPosition)
            .normalize()
            .mul(offset)
            .rotate(Math.PI / 2);
        if (!leftTurn)
            v = v.neg();
        relaxedPoint.PolylinePoint.point = relaxedPoint.OriginalPosition.add(v);
    }
    //  ShowPolylineAndObstacles(params curves: ICurve[]) {
    //     //  ReSharper restore UnusedMember.Local
    //     let ls: Array<DebugCurve> = this.GetDebugCurves(curves);
    //     LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(ls);
    // }
    // GetDebugCurves(params curves: ICurve[]): Array<DebugCurve> {
    //     let ls = this.CreateListWithObstaclesAndPolyline(curves);
    //     // ls.AddRange(this.VisibilityGraph.Edges.Select(e => new DebugCurve(100,0.1, e is TollFreeVisibilityEdge?"red":"green", LineSegment.mkPP(e.SourcePoint, e.TargetPoint))));
    //     if ((this._sourceVisibilityVertex != null)) {
    //         ls.Add(new DebugCurve("red", CurveFactory.CreateDiamond(4, 4, this._sourceVisibilityVertex.point)));
    //     }
    //     if ((this.targetVisibilityVertex != null)) {
    //         ls.Add(new DebugCurve("purple", new Ellipse(4, 4, this.targetVisibilityVertex.Point)));
    //     }
    //     let anywerePort = (<HookUpAnywhereFromInsidePort>(this.targetPort));
    //     if ((anywerePort != null)) {
    //         ls.Add(new DebugCurve("purple", anywerePort.LoosePolyline));
    //     }
    //     return ls;
    // }
    // CreateListWithObstaclesAndPolyline(params curves: ICurve[]): Array<DebugCurve> {
    //     let ls = new Array<DebugCurve>(this.ObstacleCalculator.RootOfLooseHierarchy.GetAllLeaves().select(() => {  }, new DebugCurve(100, 0.01, "green", e)));
    //     ls.AddRange(curves.Select(() => {  }, new DebugCurve(100, 0.01, "red", c)));
    //     ls.AddRange(this.ObstacleCalculator.RootOfTightHierarchy.GetAllLeaves().select(() => {  }, new DebugCurve(100, 0.01, "blue", e)));
    //     //  ls.AddRange(visibilityGraph.Edges.Select(e => (ICurve) LineSegment.mkPP(e.SourcePoint, e.TargetPoint)));
    //     if ((this._polyline != null)) {
    //         ls.Add(new DebugCurve(100, 0.03, "blue", this._polyline));
    //     }
    //     return ls;
    // }
    //  smoothing the corners of the polyline
    SmoothCorners(edgePolyline) {
        let a = edgePolyline.headSite;
        let corner = { b: null, c: null };
        // the corner other end
        while ((corner = Curve.findCorner(a))) {
            a = this.SmoothOneCorner(a, corner.c, corner.b);
        }
    }
    SmoothOneCorner(a, c, b) {
        const mult = 1.5;
        const kMin = 0.01;
        let k = 0.5;
        let seg;
        let v;
        let u;
        if (a.prev == null) {
            // this will allow to the segment to start from site "a"
            u = 2;
            v = 1;
        }
        else if (c.next == null) {
            u = 1;
            v = 2;
            // this will allow to the segment to end at site "c"
        }
        else {
            v = 1;
        }
        u = 1;
        do {
            seg = Curve.createBezierSeg(k * u, k * v, a, b, c);
            b.previouisBezierCoefficient = k * u;
            b.nextBezierCoefficient = k * v;
            k /= mult;
        } while (this.ObstacleCalculator.ObstaclesIntersectICurve(seg) && k > kMin);
        k *= mult;
        // that was the last k
        if (k < 0.5 && k > kMin) {
            // one time try a smoother seg
            k = 0.5 * (k + k * mult);
            seg = Curve.createBezierSeg(k * u, k * v, a, b, c);
            if (!this.ObstacleCalculator.ObstaclesIntersectICurve(seg)) {
                b.previouisBezierCoefficient = k * u;
                b.nextBezierCoefficient = k * v;
            }
        }
        return b;
    }
    TryToRemoveInflectionsAndCollinearSegments(underlyingPolyline) {
        let progress = true;
        const t = { s: null };
        while (progress) {
            progress = false;
            for (t.s = underlyingPolyline.headSite; t.s != null && t.s.next != null; t.s = t.s.next) {
                if (t.s.turn * t.s.next.turn < 0) {
                    progress = this.TryToRemoveInflectionEdge(t) || progress;
                }
            }
        }
    }
    TryToRemoveInflectionEdge(t) {
        if (!this.ObstacleCalculator.ObstaclesIntersectLine(t.s.prev.point, t.s.next.point)) {
            const a = t.s.prev;
            // forget t.s
            const b = t.s.next;
            a.next = b;
            b.prev = a;
            t.s = a;
            return true;
        }
        if (!this.ObstacleCalculator.ObstaclesIntersectLine(t.s.prev.point, t.s.next.next.point)) {
            // forget about t.s and t.s.Next
            const a = t.s.prev;
            const b = t.s.next.next;
            a.next = b;
            b.prev = a;
            t.s = a;
            return true;
        }
        if (!this.ObstacleCalculator.ObstaclesIntersectLine(t.s.point, t.s.next.next.point)) {
            // forget about t.s.Next
            const b = t.s.next.next;
            t.s.next = b;
            b.prev = t.s;
            return true;
        }
        return false;
    }
    // internal Point TargetPoint {
    //     get {
    //         CurvePort tp = this.TargetPort as CurvePort;
    //         if (tp != null)
    //             return this.Target.BoundaryCurve[tp.Parameter];
    //         else
    //             return (this.TargetPort as FloatingPort).Location;
    //     }
    // }
    // internal Point SourcePoint {
    //     get {
    //         CurvePort sp = this.SourcePort as CurvePort;
    //         if (sp != null)
    //             return this.Source.BoundaryCurve[sp.Parameter];
    //         else
    //             return (this.SourcePort as FloatingPort).Location;
    //     }
    // }
    GetShortestPolyline(sourceVisVertex, _targetVisVertex) {
        this.CleanTheGraphForShortestPath();
        const pathCalc = new SingleSourceSingleTargetShortestPathOnVisibilityGraph(this.visibilityGraph, sourceVisVertex, _targetVisVertex);
        const path = pathCalc.GetPath(this.UseEdgeLengthMultiplier);
        if (path == null) {
            // ShowIsPassable(_sourceVisibilityVertex, _targetVisVertex);
            return null;
        }
        // Assert.assert(path[0] == sourceVisVertex && path[path.length - 1] == _targetVisVertex)
        const ret = new Polyline();
        for (const v of path) {
            ret.addPoint(v.point);
        }
        return InteractiveEdgeRouter.RemoveCollinearVertices(ret);
    }
    // private ShowIsPassable(sourceVisVertex: VisibilityVertex, targetVisVertex: VisibilityVertex) {
    //     let dd = new Array<DebugCurve>(this.visibilityGraph.Edges.Select(() => {  }, new DebugCurve(100, 0.5, "green", LineSegment.mkPP(e.SourcePoint, e.TargetPoint))));
    //     // TODO: Warning!!!, inline IF is not supported ?
    //     ((e.IsPassable == null)
    //                 || e.IsPassable());
    //     "red";
    //     if ((sourceVisVertex != null)) {
    //         dd.Add(new DebugCurve(CurveFactory.CreateDiamond(3, 3, sourceVisVertex.point)));
    //     }
    //     if ((targetVisVertex != null)) {
    //         dd.Add(new DebugCurve(CurveFactory.CreateEllipse(3, 3, targetVisVertex.point)));
    //     }
    //     if ((this.Obstacles != null)) {
    //         dd.AddRange(this.Obstacles.Select(() => {  }, new DebugCurve(o)));
    //     }
    //     LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(dd);
    // }
    CleanTheGraphForShortestPath() {
        this.visibilityGraph.ClearPrevEdgesTable();
    }
    static RemoveCollinearVertices(ret) {
        for (let pp = ret.startPoint.next; pp.next != null; pp = pp.next) {
            if (Point.getTriangleOrientation(pp.prev.point, pp.point, pp.next.point) == TriangleOrientation.Collinear) {
                pp.prev.next = pp.next;
                pp.next.prev = pp.prev;
            }
        }
        return ret;
    }
    //  returns true if the nodes overlap or just positioned too close
    get OverlapsDetected() {
        return this.ObstacleCalculator.OverlapsDetected;
    }
    get TightHierarchy() {
        return this.ObstacleCalculator.RootOfTightHierarchy;
    }
    set TightHierarchy(value) {
        this.ObstacleCalculator.RootOfTightHierarchy = value;
    }
    get LooseHierarchy() {
        return this.ObstacleCalculator.RootOfLooseHierarchy;
    }
    set LooseHierarchy(value) {
        this.ObstacleCalculator.RootOfLooseHierarchy = value;
    }
    CalculateObstacles() {
        this.ObstacleCalculator = new InteractiveObstacleCalculator(this.Obstacles, this.TightPadding, this.LoosePadding, this.IgnoreTightPadding);
        this.ObstacleCalculator.Calculate();
    }
    RouteEdgeToLocation(targetLocation) {
        this.TargetPort = new FloatingPort(null, targetLocation);
        // otherwise route edge to a port would be called
        this.TargetTightPolyline = null;
        this.TargetLoosePolyline = null;
        const edgeGeometry = new EdgeGeometry();
        let ls = LineSegment.mkPP(this.SourcePort.Location, targetLocation);
        if (this.LineCanBeAcceptedForRouting(ls)) {
            this._polyline = new Polyline();
            this._polyline.addPoint(ls.start);
            this._polyline.addPoint(ls.end);
            edgeGeometry.smoothedPolyline = SmoothedPolyline.mkFromPoints(this._polyline);
            edgeGeometry.curve = edgeGeometry.smoothedPolyline.createCurve();
            return edgeGeometry;
        }
        // can we do with just two line segments?
        if (this.SourcePort instanceof CurvePort) {
            ls = LineSegment.mkPP(this.StartPointOfEdgeRouting, targetLocation);
            if (InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolylineLR(ls, this.ObstacleCalculator.RootOfTightHierarchy).length ==
                0) {
                this._polyline = new Polyline();
                this._polyline.addPoint(this.SourcePort.Location);
                this._polyline.addPoint(ls.start);
                this._polyline.addPoint(ls.end);
                // RelaxPolyline();
                edgeGeometry.smoothedPolyline = SmoothedPolyline.mkFromPoints(this._polyline);
                edgeGeometry.curve = edgeGeometry.smoothedPolyline.createCurve();
                return edgeGeometry;
            }
        }
        this.ExtendVisibilityGraphToLocation(targetLocation);
        this._polyline = this.GetShortestPolyline(this.sourceVV, this.targetVV);
        this.RelaxPolyline();
        if (this.SourcePort instanceof CurvePort) {
            this._polyline.PrependPoint(this.SourcePort.Location);
        }
        edgeGeometry.smoothedPolyline = SmoothedPolyline.mkFromPoints(this._polyline);
        edgeGeometry.curve = edgeGeometry.smoothedPolyline.createCurve();
        return edgeGeometry;
    }
    //  routes the edge to the port
    //
    RouteEdgeToPort(edgeTargetPort, portLoosePolyline, smooth, t) {
        if (!this.ObstacleCalculator.IsEmpty()) {
            this.TargetPort = edgeTargetPort;
            this.TargetTightPolyline = InteractiveEdgeRouter.GetFirstHitPolyline(edgeTargetPort.Location, this.ObstacleCalculator.RootOfTightHierarchy);
            Assert.assert(this.targetTightPolyline != null);
            if (edgeTargetPort instanceof CurvePort) {
                return this.RouteEdgeToBoundaryPort(portLoosePolyline, smooth, t);
            }
            return this.RouteEdgeToFloatingPortOfNode(portLoosePolyline, smooth, t);
        }
        if (this.sourcePort != null && this.targetPort != null) {
            t.smoothedPolyline = this.SmoothedPolylineFromTwoPoints(this.sourcePort.Location, this.targetPort.Location);
            return LineSegment.mkPP(this.sourcePort.Location, this.targetPort.Location);
        }
        return null;
    }
    SmoothedPolylineFromTwoPoints(s, e) {
        this._polyline = new Polyline();
        this._polyline.addPoint(s);
        this._polyline.addPoint(e);
        return SmoothedPolyline.mkFromPoints(this._polyline);
    }
    RouteEdgeToFloatingPortOfNode(portLoosePolyline, smooth, t) {
        if (this.sourcePort instanceof FloatingPort) {
            return this.RouteFromFloatingPortToFloatingPort(portLoosePolyline, smooth, t);
        }
        return this.RouteFromBoundaryPortToFloatingPort(portLoosePolyline, smooth, t);
    }
    RouteFromBoundaryPortToFloatingPort(targetPortLoosePolyline, smooth, t) {
        const sourcePortLocation = this.SourcePort.Location;
        const targetPortLocation = this.targetPort.Location;
        let ls = LineSegment.mkPP(sourcePortLocation, targetPortLocation);
        if (this.LineCanBeAcceptedForRouting(ls)) {
            t.smoothedPolyline = this.SmoothedPolylineFromTwoPoints(ls.start, ls.end);
            return ls;
        }
        if (!this.targetIsInsideOfSourceTightPolyline) {
            // try a variant with two segments
            const takenOutPoint = this.TakeBoundaryPortOutsideOfItsLoosePolyline(this.SourcePort.Curve, this.SourcePort.Parameter, this.SourceLoosePolyline);
            ls = LineSegment.mkPP(takenOutPoint, targetPortLocation);
            if (this.LineAvoidsTightHierarchyLP(ls, targetPortLoosePolyline)) {
                t.smoothedPolyline = this.SmoothedPolylineFromTwoPoints(ls.start, ls.end);
                return ls;
            }
        }
        // we need to route throw the visibility graph
        this.ExtendVisibilityGraphToLocationOfTargetFloatingPort(targetPortLoosePolyline);
        this._polyline = this.GetShortestPolyline(this.sourceVV, this.targetVV);
        const tmp = this.SourceTightPolyline;
        if (!this.targetIsInsideOfSourceTightPolyline) {
            this.SourceTightPolyline = null;
        }
        this.TryShortcutPolyline();
        this.SourceTightPolyline = tmp;
        this.RelaxPolyline();
        this._polyline.PrependPoint(sourcePortLocation);
        return this.SmoothCornersAndReturnCurve(smooth, t);
    }
    SmoothCornersAndReturnCurve(smooth, t) {
        t.smoothedPolyline = SmoothedPolyline.mkFromPoints(this._polyline);
        if (smooth) {
            this.SmoothCorners(t.smoothedPolyline);
        }
        return t.smoothedPolyline.createCurve();
    }
    RouteFromFloatingPortToFloatingPort(portLoosePolyline, smooth, t) {
        const targetPortLocation = this.TargetPort.Location;
        const ls = LineSegment.mkPP(this.StartPointOfEdgeRouting, targetPortLocation);
        if (this.AllowedShootingStraightLines && this.LineAvoidsTightHierarchyLPP(ls, this.SourceTightPolyline, this.targetTightPolyline)) {
            t.smoothedPolyline = this.SmoothedPolylineFromTwoPoints(ls.start, ls.end);
            return ls;
        }
        // we need to route through the visibility graph
        this.ExtendVisibilityGraphToLocationOfTargetFloatingPort(portLoosePolyline);
        this._polyline = this.GetShortestPolyline(this.sourceVV, this.targetVV);
        if (this._polyline == null) {
            return null;
        }
        if (this.UseSpanner) {
            this.TryShortcutPolyline();
        }
        this.RelaxPolyline();
        t.smoothedPolyline = SmoothedPolyline.mkFromPoints(this._polyline);
        return this.SmoothCornersAndReturnCurve(smooth, t);
    }
    TryShortcutPolyline() {
        if (this.UseInnerPolylingShortcutting) {
            while (this.ShortcutPolylineOneTime()) { }
        }
        if (this.UsePolylineEndShortcutting) {
            this.TryShortCutThePolylineEnds();
        }
    }
    TryShortCutThePolylineEnds() {
        this.TryShortcutPolylineStart();
        this.TryShortcutPolylineEnd();
    }
    TryShortcutPolylineEnd() {
        const a = this._polyline.endPoint;
        const b = a.prev;
        if (b == null) {
            return;
        }
        const c = b.prev;
        if (c == null) {
            return;
        }
        const m = Point.middle(b.point, c.point);
        if (this.LineAvoidsTightHierarchyPPPP(a.point, m, this._sourceTightPolyline, this.targetTightPolyline)) {
            const p = PolylinePoint.mkFromPoint(m);
            p.next = a;
            p.prev = c;
            a.prev = p;
            c.next = p;
        }
    }
    TryShortcutPolylineStart() {
        const a = this._polyline.startPoint;
        const b = a.next;
        if (b == null) {
            return;
        }
        const c = b.next;
        if (c == null) {
            return;
        }
        const m = Point.middle(b.point, c.point);
        if (this.LineAvoidsTightHierarchyPPPP(a.point, m, this._sourceTightPolyline, this.targetTightPolyline)) {
            const p = PolylinePoint.mkFromPoint(m);
            p.prev = a;
            p.next = c;
            a.next = p;
            c.prev = p;
        }
    }
    ShortcutPolylineOneTime() {
        let ret = false;
        for (let pp = this._polyline.startPoint; pp.next != null && pp.next.next != null; pp = pp.next) {
            ret = ret || this.TryShortcutPolyPoint(pp);
        }
        return ret;
    }
    TryShortcutPolyPoint(pp) {
        if (this.LineAvoidsTightHierarchyLPP(LineSegment.mkPP(pp.point, pp.next.next.point), this.SourceTightPolyline, this.targetTightPolyline)) {
            // remove pp.Next
            pp.next = pp.next.next;
            pp.next.prev = pp;
            return true;
        }
        return false;
    }
    ExtendVisibilityGraphToLocationOfTargetFloatingPort(portLoosePolyline) {
        if (this.VisibilityGraph == null) {
            this.VisibilityGraph = new VisibilityGraph();
        }
        let addedPolygons = null;
        const targetLocation = this.targetPort.Location;
        if (!this.activeRectangle.contains(targetLocation)) {
            if (this.activeRectangle.isEmpty) {
                this.activeRectangle = Rectangle.mkPP(this.SourcePort.Location, targetLocation);
            }
            else {
                this.activeRectangle.add(targetLocation);
            }
            addedPolygons = this.GetAddedPolygonesAndMaybeExtendActiveRectangle();
            for (const polygon of addedPolygons) {
                this.VisibilityGraph.AddHole(polygon.Polyline);
            }
        }
        if (addedPolygons == null) {
            if (this.targetVV != null) {
                this.VisibilityGraph.RemoveVertex(this.targetVV);
            }
            this.CalculateEdgeTargetVisibilityGraphForFloatingPort(targetLocation, portLoosePolyline);
            if (this.sourceVV == null) {
                this.CalculateSourcePortVisibilityGraph();
            }
        }
        else {
            this.RemovePointVisibilityGraphs();
            const visibilityGraphGenerator = new InteractiveTangentVisibilityGraphCalculator(addedPolygons, this.activePolygons, this.VisibilityGraph);
            visibilityGraphGenerator.run();
            addRange(this.activePolygons, addedPolygons);
            this.CalculateEdgeTargetVisibilityGraphForFloatingPort(targetLocation, portLoosePolyline);
            this.CalculateSourcePortVisibilityGraph();
        }
    }
    CalculateEdgeTargetVisibilityGraphForFloatingPort(targetLocation, targetLoosePoly) {
        if (this.UseSpanner) {
            this.targetVV = this.AddTransientVisibilityEdgesForPort(targetLocation, targetLoosePoly);
        }
        else {
            this.targetVV = PointVisibilityCalculator.CalculatePointVisibilityGraph(this.GetActivePolylinesWithException(targetLoosePoly), this.VisibilityGraph, targetLocation, VisibilityKind.Tangent);
        }
    }
    AddTransientVisibilityEdgesForPort(point, loosePoly) {
        let v = this.GetVertex(point);
        if (v != null) {
            return v;
        }
        v = this.visibilityGraph.AddVertexP(point);
        if (loosePoly != null)
            //if the edges have not been calculated do it in a quick and dirty mode
            for (const p of loosePoly)
                this.visibilityGraph.AddEdgeF(point, p, (a, b) => new TollFreeVisibilityEdge(a, b));
        else {
            v = PointVisibilityCalculator.CalculatePointVisibilityGraph(this.GetActivePolylines(), this.VisibilityGraph, point, VisibilityKind.Tangent);
            Assert.assert(v != null);
        }
        return v;
    }
    GetVertex(point) {
        let v = this.visibilityGraph.FindVertex(point);
        if (v == null && this.LookForRoundedVertices) {
            v = this.visibilityGraph.FindVertex(GeomConstants.RoundPoint(point));
        }
        return v;
    }
    *GetActivePolylinesWithException(targetLoosePoly) {
        /*
    return from polygon in activePolygons where polygon.Polyline != targetLoosePoly select polygon.Polyline;
          */
        for (const polygon of this.activePolygons) {
            if (polygon.Polyline != targetLoosePoly)
                yield polygon.Polyline;
        }
    }
    RouteEdgeToBoundaryPort(portLoosePolyline, smooth, t) {
        this.TargetLoosePolyline = portLoosePolyline;
        if (this.sourcePort instanceof FloatingPort) {
            return this.RouteFromFloatingPortToBoundaryPort(smooth, t);
        }
        return this.RouteFromBoundaryPortToBoundaryPort(smooth, t);
    }
    RouteFromBoundaryPortToBoundaryPort(smooth, t) {
        const sourcePortLocation = this.SourcePort.Location;
        let curve;
        const targetPortLocation = this.targetPort.Location;
        let ls = LineSegment.mkPP(sourcePortLocation, targetPortLocation);
        if (this.LineCanBeAcceptedForRouting(ls)) {
            this._polyline = new Polyline();
            this._polyline.addPoint(ls.start);
            this._polyline.addPoint(ls.end);
            t.smoothedPolyline = this.SmoothedPolylineFromTwoPoints(ls.start, ls.end);
            curve = SmoothedPolyline.mkFromPoints(this._polyline).createCurve();
        }
        else {
            // try three variants with two segments
            const takenOutPoint = this.TakeBoundaryPortOutsideOfItsLoosePolyline(this.targetPort.Curve, this.targetPort.Parameter, this.TargetLoosePolyline);
            ls = LineSegment.mkPP(sourcePortLocation, takenOutPoint);
            if (this.InsideOfTheAllowedConeOfBoundaryPort(takenOutPoint, this.SourcePort) &&
                this.LineAvoidsTightHierarchyLP(ls, this._sourceTightPolyline)) {
                this._polyline = new Polyline();
                this._polyline.addPoint(ls.start);
                this._polyline.addPoint(ls.end);
                this._polyline.addPoint(targetPortLocation);
                curve = this.SmoothCornersAndReturnCurve(smooth, t);
            }
            else {
                ls = LineSegment.mkPP(this.StartPointOfEdgeRouting, targetPortLocation);
                if (this.InsideOfTheAllowedConeOfBoundaryPort(this.StartPointOfEdgeRouting, this.TargetPort) &&
                    this.LineAvoidsTightHierarchy(ls)) {
                    this._polyline = new Polyline();
                    this._polyline.addPoint(sourcePortLocation);
                    this._polyline.addPoint(ls.start);
                    this._polyline.addPoint(ls.end);
                    curve = this.SmoothCornersAndReturnCurve(smooth, t);
                }
                else {
                    //  we still can make the polyline with two segs when the port sticking segs are intersecting
                    let x;
                    if ((x = LineSegment.IntersectPPPP(sourcePortLocation, this.StartPointOfEdgeRouting, targetPortLocation, takenOutPoint))) {
                        this._polyline = new Polyline();
                        this._polyline.addPoint(sourcePortLocation);
                        this._polyline.addPoint(x);
                        this._polyline.addPoint(targetPortLocation);
                        curve = this.SmoothCornersAndReturnCurve(smooth, t);
                    }
                    else if (Point.closeDistEps(this.StartPointOfEdgeRouting, takenOutPoint)) {
                        this._polyline = new Polyline();
                        this._polyline.addPoint(sourcePortLocation);
                        this._polyline.addPoint(takenOutPoint);
                        this._polyline.addPoint(targetPortLocation);
                        curve = this.SmoothCornersAndReturnCurve(smooth, t);
                    }
                    else if (this.LineAvoidsTightHierarchy(LineSegment.mkPP(this.StartPointOfEdgeRouting, takenOutPoint))) {
                        // can we do three segments?
                        this._polyline = new Polyline();
                        this._polyline.addPoint(sourcePortLocation);
                        this._polyline.addPoint(this.StartPointOfEdgeRouting);
                        this._polyline.addPoint(takenOutPoint);
                        this._polyline.addPoint(targetPortLocation);
                        curve = this.SmoothCornersAndReturnCurve(smooth, t);
                    }
                    else {
                        this.ExtendVisibilityGraphToTargetBoundaryPort(takenOutPoint);
                        this._polyline = this.GetShortestPolyline(this.sourceVV, this.targetVV);
                        const r = { tmpTargetTight: null };
                        const tmpSourceTight = this.HideSourceTargetTightsIfNeeded(r);
                        this.TryShortcutPolyline();
                        this.RecoverSourceTargetTights(tmpSourceTight, r.tmpTargetTight);
                        this.RelaxPolyline();
                        this._polyline.PrependPoint(sourcePortLocation);
                        this._polyline.addPoint(targetPortLocation);
                        curve = this.SmoothCornersAndReturnCurve(smooth, t);
                    }
                }
            }
        }
        return curve;
    }
    RecoverSourceTargetTights(tmpSourceTight, tmpTargetTight) {
        this.SourceTightPolyline = tmpSourceTight;
        this.TargetTightPolyline = tmpTargetTight;
    }
    HideSourceTargetTightsIfNeeded(t) {
        const tmpSourceTight = this.SourceTightPolyline;
        t.tmpTargetTight = this.TargetTightPolyline;
        this.TargetTightPolyline = null;
        this.SourceTightPolyline = null;
        return tmpSourceTight;
    }
    LineAvoidsTightHierarchy(lineSegment) {
        return (InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolylineLR(lineSegment, this.ObstacleCalculator.RootOfTightHierarchy)
            .length == 0);
    }
    RouteFromFloatingPortToBoundaryPort(smooth, r) {
        const targetPortLocation = this.targetPort.Location;
        let ls;
        if (this.InsideOfTheAllowedConeOfBoundaryPort(this.sourcePort.Location, this.targetPort)) {
            ls = LineSegment.mkPP(this.SourcePort.Location, targetPortLocation);
            if (this.LineCanBeAcceptedForRouting(ls)) {
                r.smoothedPolyline = this.SmoothedPolylineFromTwoPoints(ls.start, ls.end);
                return ls;
            }
        }
        const takenOutTargetPortLocation = this.TakeBoundaryPortOutsideOfItsLoosePolyline(this.TargetPort.Curve, this.TargetPort.Parameter, this.TargetLoosePolyline);
        // can we do with just two line segments?
        ls = LineSegment.mkPP(this.SourcePort.Location, takenOutTargetPortLocation);
        if (this.LineAvoidsTightHierarchyLP(ls, this._sourceTightPolyline)) {
            this._polyline = Polyline.mkFromPoints([ls.start, ls.end, targetPortLocation]);
            r.smoothedPolyline = SmoothedPolyline.mkFromPoints(this._polyline);
            return r.smoothedPolyline.createCurve();
        }
        this.ExtendVisibilityGraphToTargetBoundaryPort(takenOutTargetPortLocation);
        this._polyline = this.GetShortestPolyline(this.sourceVV, this.targetVV);
        this.RelaxPolyline();
        this._polyline.addPoint(targetPortLocation);
        const t = { smoothedPolyline: null };
        return this.SmoothCornersAndReturnCurve(smooth, t);
    }
    LineAvoidsTightHierarchyLP(ls, polylineToExclude) {
        let lineIsGood = true;
        for (const ii of InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolylineLR(ls, this.ObstacleCalculator.RootOfTightHierarchy)) {
            if (ii.seg1 != polylineToExclude) {
                lineIsGood = false;
                break;
            }
        }
        return lineIsGood;
    }
    LineAvoidsTightHierarchyLPP(ls, polylineToExclude0, polylineToExclude1) {
        let lineIsGood = true;
        for (const ii of InteractiveEdgeRouter.IntersectionsOfLineAndRectangleNodeOverPolylineLR(ls, this.ObstacleCalculator.RootOfTightHierarchy)) {
            if (!(ii.seg1 == polylineToExclude0 || ii.seg1 == polylineToExclude1)) {
                lineIsGood = false;
                break;
            }
        }
        return lineIsGood;
    }
    LineAvoidsTightHierarchyPPPP(a, b, polylineToExclude0, polylineToExclude1) {
        return this.LineAvoidsTightHierarchyLPP(LineSegment.mkPP(a, b), polylineToExclude0, polylineToExclude1);
    }
    ExtendVisibilityGraphToTargetBoundaryPort(takenOutTargetPortLocation) {
        let addedPolygons = null;
        if (this.VisibilityGraph == null) {
            this.VisibilityGraph = new VisibilityGraph();
        }
        if (!this.activeRectangle.contains(takenOutTargetPortLocation) ||
            !this.activeRectangle.containsRect(this.TargetLoosePolyline.boundingBox)) {
            if (this.activeRectangle.isEmpty) {
                this.activeRectangle = this.TargetLoosePolyline.boundingBox.clone();
                this.activeRectangle.add(this.SourcePort.Location);
                this.activeRectangle.add(this.StartPointOfEdgeRouting);
                this.activeRectangle.add(takenOutTargetPortLocation);
            }
            else {
                this.activeRectangle.add(takenOutTargetPortLocation);
                this.activeRectangle.addRec(this.TargetLoosePolyline.boundingBox);
            }
            addedPolygons = this.GetAddedPolygonesAndMaybeExtendActiveRectangle();
            for (const polygon of addedPolygons) {
                this.VisibilityGraph.AddHole(polygon.Polyline);
            }
        }
        if (addedPolygons == null) {
            if (this.targetVV != null) {
                this.VisibilityGraph.RemoveVertex(this.targetVV);
            }
            this.CalculateEdgeTargetVisibilityGraph(takenOutTargetPortLocation);
        }
        else {
            this.RemovePointVisibilityGraphs();
            const visibilityGraphGenerator = new InteractiveTangentVisibilityGraphCalculator(addedPolygons, this.activePolygons, this.VisibilityGraph);
            visibilityGraphGenerator.run();
            addRange(this.activePolygons, addedPolygons);
            this.CalculateEdgeTargetVisibilityGraph(takenOutTargetPortLocation);
            this.CalculateSourcePortVisibilityGraph();
        }
    }
    //  returns the hit object
    GetHitLoosePolyline(point) {
        if (this.ObstacleCalculator.IsEmpty() || this.ObstacleCalculator.RootOfLooseHierarchy == null) {
            return null;
        }
        return InteractiveEdgeRouter.GetFirstHitPolyline(point, this.ObstacleCalculator.RootOfLooseHierarchy);
    }
    static GetFirstHitPolyline(point, rectangleNode) {
        const rectNode = InteractiveEdgeRouter.GetFirstHitRectangleNode(point, rectangleNode);
        return rectNode ? rectNode.UserData : null;
    }
    static GetFirstHitRectangleNode(point, rectangleNode) {
        if (rectangleNode == null) {
            return null;
        }
        return rectangleNode.FirstHitNodePF(point, (pnt, polyline) => Curve.PointRelativeToCurveLocation(pnt, polyline) != PointLocation.Outside ? HitTestBehavior.Stop : HitTestBehavior.Continue);
    }
    //
    Clean() {
        this.TargetPort = null;
        this.SourcePort = null;
        this.SourceTightPolyline = null;
        this.SourceLoosePolyline = null;
        this.TargetLoosePolyline = null;
        this.targetTightPolyline = null;
        this.VisibilityGraph = null;
        this.targetVV = null;
        this.sourceVV = null;
        this.activePolygons = [];
        this.alreadyAddedOrExcludedPolylines.clear();
        this.activeRectangle.setToEmpty();
    }
    //  setting source port and the loose polyline of the port
    SetSourcePortAndSourceLoosePolyline(port, sourceLoosePolylinePar) {
        this.SourceLoosePolyline = sourceLoosePolylinePar;
        this.sourcePort = port;
        if (this.sourcePort != null) {
            this.SourceTightPolyline = InteractiveEdgeRouter.GetFirstHitPolyline(this.sourcePort.Location, this.ObstacleCalculator.RootOfTightHierarchy);
            if (this.sourcePort instanceof FloatingPort) {
                this.alreadyAddedOrExcludedPolylines.add(this.SourceLoosePolyline);
                // we need to exclude the loose polyline around the source port from the tangent visibily graph
                this.StartPointOfEdgeRouting = this.SourcePort.Location;
            }
            else {
                this.StartPointOfEdgeRouting = this.TakeBoundaryPortOutsideOfItsLoosePolyline(this.SourcePort.Curve, this.sourcePort.Parameter, this.SourceLoosePolyline);
            }
        }
    }
    run() {
        this.CalculateWholeTangentVisibilityGraph();
    }
    CalculateWholeTangentVisibilityGraph() {
        this.VisibilityGraph = new VisibilityGraph();
        this.CalculateWholeVisibilityGraphOnExistingGraph();
    }
    CalculateWholeVisibilityGraphOnExistingGraph() {
        this.activePolygons = Array.from(this.AllPolygons());
        for (const polylineLocal of this.ObstacleCalculator.LooseObstacles) {
            this.VisibilityGraph.AddHole(polylineLocal);
        }
        let visibilityGraphGenerator;
        if (this.UseSpanner) {
            visibilityGraphGenerator = new ConeSpanner(this.ObstacleCalculator.LooseObstacles, this.VisibilityGraph);
        }
        else {
            visibilityGraphGenerator = new InteractiveTangentVisibilityGraphCalculator(new Array(), this.activePolygons, this.visibilityGraph);
        }
        visibilityGraphGenerator.run();
    }
    RouteSplineFromPortToPortWhenTheWholeGraphIsReady(sourcePortLocal, targetPortLocal, smooth, t) {
        const reversed = (sourcePortLocal instanceof FloatingPort && targetPortLocal instanceof CurvePort) ||
            sourcePortLocal instanceof HookUpAnywhereFromInsidePort;
        if (reversed) {
            const tmp = sourcePortLocal;
            sourcePortLocal = targetPortLocal;
            targetPortLocal = tmp;
        }
        this.sourcePort = sourcePortLocal;
        this.targetPort = targetPortLocal;
        this.FigureOutSourceTargetPolylinesAndActiveRectangle();
        let curve = this.GetEdgeGeomByRouting(smooth, t);
        if (curve == null) {
            return null;
        }
        this.targetVV = null;
        this.sourceVV = null;
        if (reversed) {
            curve = curve.reverse();
        }
        return curve;
    }
    GetEdgeGeomByRouting(smooth, t) {
        this.targetIsInsideOfSourceTightPolyline =
            this.SourceTightPolyline == null ||
                Curve.PointRelativeToCurveLocation(this.targetPort.Location, this.SourceTightPolyline) == PointLocation.Inside;
        this.sourceIsInsideOfTargetTightPolyline =
            this.TargetTightPolyline == null ||
                Curve.PointRelativeToCurveLocation(this.sourcePort.Location, this.TargetTightPolyline) == PointLocation.Inside;
        let curve;
        if (this.sourcePort instanceof CurvePort) {
            const curvePort = this.sourcePort;
            this.StartPointOfEdgeRouting = !this.targetIsInsideOfSourceTightPolyline
                ? this.TakeBoundaryPortOutsideOfItsLoosePolyline(curvePort.Curve, curvePort.Parameter, this.SourceLoosePolyline)
                : curvePort.Location;
            this.CalculateSourcePortVisibilityGraph();
            const t = { smoothedPolyline: null };
            if (this.targetPort instanceof CurvePort) {
                curve = this.RouteFromBoundaryPortToBoundaryPort(smooth, t);
            }
            else {
                curve = this.RouteFromBoundaryPortToFloatingPort(this.targetLoosePolyline, smooth, t);
            }
        }
        else if (this.targetPort instanceof FloatingPort) {
            this.ExtendVisibilityGraphFromFloatingSourcePort();
            // Assert.assert(this.sourceVV != null)
            // the edge has to be reversed to route from CurvePort to FloatingPort
            curve = this.RouteFromFloatingPortToFloatingPort(this.targetLoosePolyline, smooth, t);
        }
        else {
            curve = this.RouteFromFloatingPortToAnywherePort(this.targetPort.LoosePolyline, smooth, t, this.targetPort);
        }
        return curve;
    }
    RouteFromFloatingPortToAnywherePort(targetLoosePoly, smooth, t, port) {
        if (!port.Curve.boundingBox.contains(this.sourcePort.Location)) {
            t.smoothedPolyline = null;
            return null;
        }
        this.sourceVV = this.GetVertex(this.sourcePort.Location);
        this._polyline = this.GetShortestPolylineToMulitpleTargets(this.sourceVV, Array.from(this.Targets(targetLoosePoly)));
        if (this._polyline == null) {
            return null;
        }
        if (this.UseSpanner) {
            this.TryShortcutPolyline();
        }
        this.RelaxPolyline();
        this.FixLastPolylinePointForAnywherePort(port);
        if (port.HookSize > 0) {
            this.BuildHook(port);
        }
        return this.SmoothCornersAndReturnCurve(smooth, t);
    }
    BuildHook(port) {
        const curve = port.Curve;
        // creating a hook
        const ellipse = Ellipse.mkFullEllipseNNP(port.HookSize, port.HookSize, this._polyline.end);
        const intersections = Curve.getAllIntersections(curve, ellipse, true);
        Assert.assert(intersections.length == 2);
        if (Point.getTriangleOrientation(intersections[0].x, this._polyline.end, this._polyline.endPoint.prev.point) ==
            TriangleOrientation.Counterclockwise) {
            intersections.reverse();
        }
        // so the [0] point is to the left of the Polyline
        const polylineTangent = this._polyline.end.sub(this._polyline.endPoint.prev.point).normalize();
        const tan0 = curve.derivative(intersections[0].par0).normalize();
        const prj0 = tan0.dot(polylineTangent);
        if (Math.abs(prj0) < 0.2) {
            this.ExtendPolyline(tan0, intersections[0], polylineTangent, port);
        }
        else {
            const tan1 = curve.derivative(intersections[1].par0).normalize();
            const prj1 = tan1.dot(polylineTangent);
            if (prj1 < prj0) {
                this.ExtendPolyline(tan1, intersections[1], polylineTangent, port);
            }
            else {
                this.ExtendPolyline(tan0, intersections[0], polylineTangent, port);
            }
        }
    }
    ExtendPolyline(tangentAtIntersection, x, polylineTangent, port) {
        let normal = tangentAtIntersection.rotate(Math.PI / 2);
        if (normal.dot(polylineTangent) < 0) {
            normal = normal.neg();
        }
        const pointBeforeLast = x.x.add(normal.mul(port.HookSize));
        let pointAfterX;
        if (!(pointAfterX = Point.lineLineIntersection(pointBeforeLast, pointBeforeLast.add(tangentAtIntersection), this._polyline.end, this._polyline.end.add(polylineTangent)))) {
            return;
        }
        this._polyline.addPoint(pointAfterX);
        this._polyline.addPoint(pointBeforeLast);
        this._polyline.addPoint(x.x);
    }
    FixLastPolylinePointForAnywherePort(port) {
        while (true) {
            const lastPointInside = this.GetLastPointInsideOfCurveOnPolyline(port.Curve);
            lastPointInside.next.next = null;
            this._polyline.endPoint = lastPointInside.next;
            let dir = lastPointInside.next.point.sub(lastPointInside.point);
            dir = dir.normalize().mul(port.Curve.boundingBox.diagonal);
            // make it a long vector
            const dir0 = dir.rotate(port.AdjustmentAngle * -1);
            const dir1 = dir.rotate(port.AdjustmentAngle);
            const rx = Curve.intersectionOne(port.Curve, LineSegment.mkPP(lastPointInside.point, lastPointInside.point.add(dir0)), true);
            const lx = Curve.intersectionOne(port.Curve, LineSegment.mkPP(lastPointInside.point, lastPointInside.point.add(dir1)), true);
            if (rx == null || lx == null) {
                return;
            }
            // this.ShowPolylineAndObstacles(Polyline, LineSegment.mkPP(lastPointInside.Point, lastPointInside.Point+dir0), LineSegment.mkPP(lastPointInside.Point, rerPoint+dir1), port.Curve);
            const trimmedCurve = InteractiveEdgeRouter.GetTrimmedCurveForHookingUpAnywhere(port.Curve, lastPointInside, rx, lx);
            const newLastPoint = trimmedCurve.value(trimmedCurve.closestParameter(lastPointInside.point));
            if (!this.LineAvoidsTightHierarchyLPP(LineSegment.mkPP(lastPointInside.point, newLastPoint), this.SourceTightPolyline, null)) {
                const xx = Curve.intersectionOne(port.Curve, LineSegment.mkPP(lastPointInside.point, lastPointInside.next.point), false);
                if (xx == null) {
                    return;
                }
                // this.ShowPolylineAndObstacles(Polyline, port.Curve);
                this._polyline.endPoint.point = xx.x;
                break;
            }
            this._polyline.endPoint.point = newLastPoint;
            if (lastPointInside.prev == null || !this.TryShortcutPolyPoint(lastPointInside.prev)) {
                break;
            }
        }
    }
    static GetTrimmedCurveForHookingUpAnywhere(curve, lastPointInside, x0, x1) {
        const clockwise = Point.getTriangleOrientation(x1.x, x0.x, lastPointInside.point) == TriangleOrientation.Clockwise;
        const rightX = x0.par0;
        const leftX = x1.par0;
        let tr1;
        let tr0;
        let ret;
        if (clockwise) {
            if (rightX < leftX) {
                return curve.trim(rightX, leftX);
            }
            tr0 = curve.trim(rightX, curve.parEnd);
            tr1 = curve.trim(curve.parStart, leftX);
            ret = new Curve();
            return ret.addSegs([tr0, tr1]);
        }
        if (leftX < rightX) {
            return curve.trim(leftX, rightX);
        }
        tr0 = curve.trim(leftX, curve.parEnd);
        tr1 = curve.trim(curve.parStart, rightX);
        ret = new Curve();
        return ret.addSegs([tr0, tr1]);
    }
    GetLastPointInsideOfCurveOnPolyline(curve) {
        for (let p = this._polyline.endPoint.prev; p != null; p = p.prev) {
            if (p.prev == null) {
                return p;
            }
            if (Curve.PointRelativeToCurveLocation(p.point, curve) == PointLocation.Inside) {
                return p;
            }
        }
        throw new Error();
    }
    GetShortestPolylineToMulitpleTargets(sourceVisVertex, targets) {
        this.CleanTheGraphForShortestPath();
        // ShowPolylineAndObstacles(targets.Select(t=>new Ellipse(3,3,t.Point)).ToArray());
        const pathCalc = new SingleSourceMultipleTargetsShortestPathOnVisibilityGraph(sourceVisVertex, targets, this.VisibilityGraph);
        //  { dd = ShowPolylineAndObstacles };
        const path = pathCalc.GetPath();
        if (path == null) {
            return null;
        }
        // Assert.assert(((from(path).first() == sourceVisVertex)
        //                 && targets.contains(path.last())));
        const ret = new Polyline();
        for (const v of path) {
            ret.addPoint(v.point);
        }
        return InteractiveEdgeRouter.RemoveCollinearVertices(ret);
    }
    Targets(targetLoosePoly) {
        return Array.from(targetLoosePoly).map((p) => this.visibilityGraph.FindVertex(p));
    }
    ExtendVisibilityGraphFromFloatingSourcePort() {
        const fp = this.sourcePort;
        // Assert.assert(this.sourcePort instanceof FloatingPort)
        this.StartPointOfEdgeRouting = fp.Location;
        if (this.UseSpanner) {
            this.sourceVV = this.AddTransientVisibilityEdgesForPort(this.sourcePort.Location, this.SourceLoosePolyline);
        }
        else {
            this.sourceVV = PointVisibilityCalculator.CalculatePointVisibilityGraph(Array.from(this.GetActivePolylines()).filter((p) => p != this.SourceLoosePolyline), this.VisibilityGraph, this.StartPointOfEdgeRouting, VisibilityKind.Tangent);
        }
    }
    FigureOutSourceTargetPolylinesAndActiveRectangle() {
        this._sourceTightPolyline = InteractiveEdgeRouter.GetFirstHitPolyline(this.sourcePort.Location, this.ObstacleCalculator.RootOfTightHierarchy);
        this.SourceLoosePolyline = InteractiveEdgeRouter.GetFirstHitPolyline(this.sourcePort.Location, this.ObstacleCalculator.RootOfLooseHierarchy);
        this.targetTightPolyline = InteractiveEdgeRouter.GetFirstHitPolyline(this.targetPort.Location, this.ObstacleCalculator.RootOfTightHierarchy);
        this.targetLoosePolyline = InteractiveEdgeRouter.GetFirstHitPolyline(this.targetPort.Location, this.ObstacleCalculator.RootOfLooseHierarchy);
        this.activeRectangle = Rectangle.mkPP(new Point(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY), new Point(Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY));
    }
    *AllPolygons() {
        for (const p of this.ObstacleCalculator.LooseObstacles) {
            yield new Polygon(p);
        }
    }
    //
    GetVisibilityGraph() {
        return this.VisibilityGraph;
    }
    //  ShowObstaclesAndVisGraph() {
    //     let obs = this.ObstacleCalculator.LooseObstacles.Select(() => {  }, new DebugCurve(100, 1, "blue", o));
    //     let edges = this.visibilityGraph.Edges.Select(() => {  }, new DebugCurve(70, 1, (e instanceof  "red"), LineSegment.mkPP(e.SourcePoint, e.TargetPoint)));
    //     // TODO: Warning!!!, inline IF is not supported ?
    //     TransientVisibilityEdge;
    //     "green";
    //     LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(obs.Concat(edges));
    // }
    AddActivePolygons(polygons) {
        addRange(this.activePolygons, polygons);
    }
    ClearActivePolygons() {
        this.activePolygons = [];
    }
}
//# sourceMappingURL=InteractiveEdgeRouter.js.map