import { Point } from '../../../math/geometry/point';
import { Rectangle } from '../../../math/geometry/rectangle';
import { UniformOneDimensionalSolver } from '../../../math/projectionSolver/UniformOneDimensionalSolver';
import { CompassVector } from '../../../math/geometry/compassVector';
import { Curve, PointLocation } from '../../../math/geometry/curve';
import { DebugCurve } from '../../../math/geometry/debugCurve';
import { Direction } from '../../../math/geometry/direction';
import { GeomConstants } from '../../../math/geometry/geomConstants';
import { LineSegment } from '../../../math/geometry/lineSegment';
import { Polyline } from '../../../math/geometry/polyline';
// import {Assert} from '../../../utils/assert'
import { closeDistEps } from '../../../utils/compare';
import { CombinatorialNudger } from './CombinatorialNudger';
import { FreeSpaceFinder } from './FreeSpaceFinder';
import { LongestNudgedSegment } from './LongestNudgedSegment';
import { PathRefiner } from './PathRefiner';
import { StaircaseRemover } from './StaircaseRemover';
import { HitTestBehavior } from '../../../math/geometry/RTree/HitTestBehavior';
import { CreateRectangleNodeOnEnumeration, mkRectangleNode } from '../../../math/geometry/RTree/RectangleNode';
//  following paper "Orthogonal Connector Routing" which is included  of the project
export class Nudger {
    //   "nudges" paths to decrease the number of intersections and stores the results inside WidePaths of "paths"
    //  paths through the graph
    //  two parallel paths should be separated by this distance if it is feasible
    //  polygonal convex obstacles organized  of a tree; the obstacles here are padded original obstacles
    //
    //  <returns></returns>
    constructor(paths, cornerFitRad, obstacles, ancestorsSets) {
        this.AncestorsSets = ancestorsSets;
        this.HierarchyOfGroups = CreateRectangleNodeOnEnumeration(Array.from(ancestorsSets.keys())
            .filter((shape) => shape.IsGroup)
            .map((group) => mkRectangleNode(group, group.BoundingBox)));
        this.Obstacles = obstacles;
        this.EdgeSeparation = 2 * cornerFitRad;
        this.Paths = paths;
        this.HierarchyOfObstacles = CreateRectangleNodeOnEnumeration(obstacles.map((p) => mkRectangleNode(p, p.boundingBox)));
        this.MapPathsToTheirObstacles();
    }
    get HasGroups() {
        return null != this.HierarchyOfGroups && this.HierarchyOfGroups.Count > 0;
    }
    MapPathsToTheirObstacles() {
        this.PathToObstacles = new Map();
        for (const path of this.Paths) {
            this.MapPathToItsObstacles(path);
        }
    }
    MapPathToItsObstacles(path) {
        if (!path.PathPoints || path.PathPoints.length == 0)
            return;
        const fr = path.PathPoints;
        const startNode = this.HierarchyOfObstacles.FirstHitNodeWithPredicate(fr[0], Nudger.ObstacleTest);
        const endNode = this.HierarchyOfObstacles.FirstHitNodeWithPredicate(fr[fr.length - 1], Nudger.ObstacleTest);
        if (null != startNode && null != endNode) {
            this.PathToObstacles.set(path, [startNode.UserData, endNode.UserData]);
        }
    }
    static ObstacleTest(pnt, polyline) {
        return Curve.PointRelativeToCurveLocation(pnt, polyline) != PointLocation.Outside ? HitTestBehavior.Stop : HitTestBehavior.Continue;
    }
    Calculate(direction, mergePaths) {
        this.NudgingDirection = direction;
        PathRefiner.RefinePaths(this.Paths, mergePaths);
        this.GetPathOrdersAndPathGraph();
        this.MapAxisEdgesToTheirObstacles();
        this.DrawPaths();
    }
    MapAxisEdgesToTheirObstacles() {
        this.axisEdgesToObstaclesTheyOriginatedFrom = new Map();
        for (const path of this.Paths) {
            this.MapPathEndAxisEdgesToTheirObstacles(path);
        }
        // The assignment above was too greedy. An edge belonging to interiour edges of some path can be marked by mistake.
        for (const path of this.Paths) {
            this.UmmapPathInteriourFromStrangerObstacles(path);
        }
    }
    UmmapPathInteriourFromStrangerObstacles(path) {
        const firstUnmappedEdge = this.FindFirstUnmappedEdge(path);
        if (firstUnmappedEdge == null) {
            return;
        }
        const lastUnmappedEdge = this.FindLastUnmappedEdge(path);
        for (let edge = firstUnmappedEdge; edge != null && edge != lastUnmappedEdge; edge = edge.Next) {
            this.axisEdgesToObstaclesTheyOriginatedFrom.delete(edge.AxisEdge);
        }
    }
    FindLastUnmappedEdge(path) {
        for (let edge = path.LastEdge; edge != null; edge = edge.Prev) {
            if (edge.AxisEdge.Direction != this.NudgingDirection) {
                return edge;
            }
        }
        return null;
    }
    FindFirstUnmappedEdge(path) {
        for (let edge = path.FirstEdge; edge != null; edge = edge.Next) {
            if (edge.AxisEdge.Direction != this.NudgingDirection) {
                return edge;
            }
        }
        return null;
    }
    MapPathEndAxisEdgesToTheirObstacles(path) {
        const coupleOfObstacles = this.PathToObstacles.get(path);
        if (coupleOfObstacles) {
            this.ProcessThePathStartToMapAxisEdgesToTheirObstacles(path, coupleOfObstacles[0]);
            this.ProcessThePathEndToMapAxisEdgesToTheirObstacles(path, coupleOfObstacles[1]);
        }
    }
    ProcessThePathEndToMapAxisEdgesToTheirObstacles(path, endPolyline) {
        for (let edge = path.LastEdge; edge != null && CompassVector.DirectionsAreParallel(edge.Direction, this.NudgingDirection); edge = edge.Prev) {
            this.axisEdgesToObstaclesTheyOriginatedFrom.set(edge.AxisEdge, endPolyline);
        }
    }
    ProcessThePathStartToMapAxisEdgesToTheirObstacles(path, startPolyline) {
        for (let edge = path.FirstEdge; edge != null && CompassVector.DirectionsAreParallel(edge.Direction, this.NudgingDirection); edge = edge.Next) {
            this.axisEdgesToObstaclesTheyOriginatedFrom.set(edge.AxisEdge, startPolyline);
        }
        // possible bug here because an edge might ignore two obstacles if it connects them
    }
    GetPathOrdersAndPathGraph() {
        const combinatorialNudger = new CombinatorialNudger(this.Paths);
        this.PathOrders = combinatorialNudger.GetOrder();
        this.PathVisibilityGraph = combinatorialNudger.PathVisibilityGraph;
    }
    static GetCurvesForShow(paths, obstacles) {
        const ret = new Array();
        for (const path of paths) {
            const poly = new Polyline();
            for (const point of path.PathPoints) {
                poly.addPoint(point);
            }
            ret.push(poly);
        }
        return ret.concat(Array.from(obstacles));
    }
    DrawPaths() {
        this.SetWidthsOfArrowheads();
        this.CreateLongestNudgedSegments();
        this.FindFreeSpaceInDirection(Array.from(this.PathVisibilityGraph.Edges));
        this.MoveLongestSegsIdealPositionsInsideFeasibleIntervals();
        this.PositionShiftedEdges();
    }
    SetWidthsOfArrowheads() {
        for (const edgePath of this.Paths) {
            Nudger.SetWidthsOfArrowheadsForEdge(edgePath);
        }
    }
    static SetWidthsOfArrowheadsForEdge(path) {
        const edgeGeom = path.EdgeGeometry;
        if (edgeGeom.targetArrowhead != null) {
            const pathEdge = path.LastEdge;
            pathEdge.Width = Math.max(edgeGeom.targetArrowhead.width, pathEdge.Width);
        }
        if (edgeGeom.sourceArrowhead != null) {
            const pathEdge = path.FirstEdge;
            pathEdge.Width = Math.max(edgeGeom.sourceArrowhead.width, pathEdge.Width);
        }
    }
    PositionShiftedEdges() {
        // we are using 2*cornerFitRadius for the minimal edge separation
        this.Solver = new UniformOneDimensionalSolver(this.EdgeSeparation);
        for (let i = 0; i < this.LongestNudgedSegs.length; i++) {
            this.CreateVariablesOfLongestSegment(this.LongestNudgedSegs[i]);
        }
        this.CreateConstraintsOfTheOrder();
        this.CreateConstraintsBetweenLongestSegments();
        this.Solver.SolveByRegularSolver();
        this.ShiftPathEdges();
    }
    MoveLongestSegsIdealPositionsInsideFeasibleIntervals() {
        for (let i = 0; i < this.LongestNudgedSegs.length; i++) {
            const seg = this.LongestNudgedSegs[i];
            Nudger.MoveLongestSegIdealPositionsInsideFeasibleInterval(seg);
        }
    }
    static MoveLongestSegIdealPositionsInsideFeasibleInterval(seg) {
        if (seg.IsFixed) {
            return;
        }
        const leftBound = seg.GetLeftBound();
        const rightBound = seg.GetRightBound();
        if (seg.IdealPosition < leftBound) {
            seg.IdealPosition = leftBound;
        }
        else if (seg.IdealPosition > rightBound) {
            seg.IdealPosition = rightBound;
        }
    }
    ShiftPathEdges() {
        for (const path of this.Paths) {
            path.PathPoints = this.GetShiftedPoints(path);
        }
    }
    GetShiftedPoints(path) {
        return Nudger.RemoveSwitchbacksAndMiddlePoints(this.GetShiftedPointsSimple(path));
    }
    //  sometimes we have very small mistakes  of the positions that have to be fixed
    //  <returns></returns>
    static Rectilinearise(a, b) {
        if (a.x == b.x || a.y == b.y)
            return b;
        const dx = Math.abs(a.x - b.x);
        const dy = Math.abs(a.y - b.y);
        return dx < dy ? new Point(a.x, b.y) : new Point(b.x, a.y);
    }
    GetShiftedPointsSimple(path) {
        const ret = [];
        const edge = path.FirstEdge;
        ret.push(this.ShiftedPoint(edge.Source, edge.LongestNudgedSegment));
        for (const e of path.PathEdges()) {
            ret.push(this.ShiftedEdgePositionOfTarget(e));
        }
        return ret;
    }
    ShiftedEdgePositionOfTarget(e) {
        return e.LongestNudgedSegment != null || e.Next == null
            ? this.ShiftedPoint(e.Target, e.LongestNudgedSegment)
            : this.ShiftedPoint(e.Next.Source, e.Next.LongestNudgedSegment);
    }
    ShiftedPoint(point, segment) {
        if (segment == null) {
            return point;
        }
        const t = this.Solver.GetVariablePosition(segment.Id);
        return this.NudgingDirection == Direction.North ? new Point(t, point.y) : new Point(point.x, -t);
    }
    //  static ShowPathsFromPoints(paths: Array<Path>, enumerable: Array<Polyline>) {
    //     let dd = new Array<DebugCurve>();
    //     if ((enumerable != null)) {
    //         dd=dd.concat(Nudger.GetObstacleBoundaries(enumerable, "grey"));
    //     }
    //     let i: number = 0;
    //     for (let p  of paths) {
    //         dd = dd.concat(Nudger.PathDebugCurvesFromPoints(p, DebugCurve.colors[Math.min(DebugCurve.colors.length, i++)]));
    //     }
    //     LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(dd);
    // }
    // static PathDebugCurvesFromPoints(path: Path, color: string): Array<DebugCurve> {
    //     const let startWidth: number = 0.01;
    //     const let endWidth: number = 3;
    //     let pts = path.PathPoints.toArray();
    //     let delta: number = ((endWidth - startWidth)
    //                 / (pts.length - 1));
    //     for (let i: number = 0; (i
    //                 < (pts.length - 1)); i++) {
    //         yield;
    //     }
    //     return new DebugCurve((startWidth
    //                     + (delta * i)), color, new LineSegment(pts[i], pts[(i + 1)]));
    // }
    //  static ShowParamPaths(s: Point, e: Point, params paths: Path[]) {
    //     Nudger.ShowOrderedPaths(null, paths, s, e);
    // }
    // //          ReSharper disable UnusedMember.Local
    //  static ShowOrderedPaths(obstacles: Array<Polyline>, paths: Array<Path>, s: Point, e: Point) {
    //     //            ReSharper restore UnusedMember.Local
    //     let colors: string[] = [
    //             "red",
    //             "green",
    //             "blue",
    //             "violet",
    //             "rose",
    //             "black"];
    //     const let startWidth: number = 0.001;
    //     const let endWidth: number = 0.1;
    //     let dd = new Array<DebugCurve>();
    //     if ((obstacles != null)) {
    //         dd.AddRange(Nudger.GetObstacleBoundaries(obstacles, "grey"));
    //     }
    //     let i: number = 0;
    //     for (let path  of paths) {
    //         dd.AddRange(Nudger.GetTestPathAsDebugCurve(startWidth, endWidth, colors[Math.min((colors.length - 1), i++)], path));
    //     }
    //     let ell = new DebugCurve(1, "black", new Ellipse(0.01, 0.01, s));
    //     dd.Add(ell);
    //     dd.Add(new DebugCurve(1, "black", new Ellipse(0.02, 0.02, e)));
    //     LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(dd.concat(Nudger.GetObstacleBoundaries(obstacles, "lightblue")));
    // }
    // static GetTestPathAsDebugCurve(startWidth: number, endWidth: number, color: string, path: Path): Array<DebugCurve> {
    //     if ((path.PathEdges.Count() > 0)) {
    //         let count: number = path.PathEdges.Count();
    //         let deltaW: number = ((endWidth - startWidth)
    //                     / (count - 1));
    //         // TODO: Warning!!!, inline IF is not supported ?
    //         (count > 1);
    //         1;
    //         // if count ==1 the value of deltaW does not matter
    //         let i: number = 0;
    //         for (let e  of path.PathEdges) {
    //             yield;
    //         }
    //         return new DebugCurve(150, (startWidth
    //                         + (deltaW
    //                         * (i + 1))), color, new LineSegment(e.Source, e.Target));
    //     }
    //     else {
    //         let count: number = path.PathPoints.count();
    //         let pts = path.PathPoints.toArray();
    //         let deltaW = ((endWidth - startWidth)
    //                     / (count - 1));
    //         // TODO: Warning!!!, inline IF is not supported ?
    //         (count > 1);
    //         1;
    //         // if count ==1 the value of deltaW does not matter
    //         for (let i: number = 0; (i
    //                     < (count - 1)); i++) {
    //             yield;
    //         }
    //         return new DebugCurve(150, (startWidth
    //                         + (deltaW * i)), color, new LineSegment(pts[i], pts[(i + 1)]));
    //     }
    // }
    //  static GetTestEdgePathAsDebugCurves(startWidth: number, endWidth: number, color: string, path: Path): Array<DebugCurve> {
    //     let count: number = path.PathPoints.count();
    //     let deltaW: number = ((endWidth - startWidth)
    //                 / (count - 1));
    //     // TODO: Warning!!!, inline IF is not supported ?
    //     (count > 1);
    //     1;
    //     // if count ==1 the value of deltaW does not matter
    //     let points = path.PathPoints.toArray();
    //     for (let i: number = 0; (i
    //                 < (points.length - 1)); i++) {
    //         yield;
    //     }
    //     return new DebugCurve(125, (startWidth
    //                     + (deltaW * i)), color, new LineSegment(points[i], points[(i + 1)]));
    // }
    // @SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")
    // static GetEdgePathFromPathEdgesAsDebugCurves(startWidth: number, endWidth: number, color: string, path: Path): Array<DebugCurve> {
    //     let points = path.PathPoints.toArray();
    //     let count: number = points.length;
    //     let deltaW: number = ((endWidth - startWidth)
    //                 / (count - 1));
    //     // TODO: Warning!!!, inline IF is not supported ?
    //     (count > 1);
    //     1;
    //     // if count ==1 the value of deltaW does not matter
    //     for (let i: number = 0; (i
    //                 < (points.length - 1)); i++) {
    //         yield;
    //     }
    //     return new DebugCurve(120, (startWidth
    //                     + (deltaW * i)), color, new LineSegment(points[i], points[(i + 1)]));
    // }
    // //  ReSharper disable UnusedMember.Local
    //  static ShowEdgePaths(obstacles: Array<Polyline>, edgePaths: Array<Path>) {
    //     //  ReSharper restore UnusedMember.Local
    //     let debCurves: Array<DebugCurve> = Nudger.GetDebCurvesOfPaths(obstacles, edgePaths);
    //     LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(debCurves);
    // }
    //  static GetDebCurvesOfPaths(enumerable: Array<Polyline>, edgePaths: Array<Path>): Array<DebugCurve> {
    //     let debCurves = Nudger.GetObstacleBoundaries(enumerable, "black");
    //     let i: number = 0;
    //     for (let edgePath  of edgePaths) {
    //         debCurves.AddRange(Nudger.GetTestEdgePathAsDebugCurves(0.2, 4, DebugCurve.colors[((i + 1)
    //                             % DebugCurve.colors.length)], edgePath));
    //     }
    //     return debCurves;
    // }
    //  static ShowPathsInLoop(enumerable: Array<Polyline>, edgePaths: Array<Path>, point: Point) {
    //     for (let edgePath  of edgePaths.where(() => {  }, (((path.PathPoints.First() - point).Length < 1)
    //                     || ((path.PathPoints.Last() - point).Length < 1)))) {
    //         let debCurves = Nudger.GetObstacleBoundaries(enumerable, "black");
    //         debCurves.AddRange(Nudger.GetTestEdgePathAsDebugCurves(0.1, 4, "red", edgePath));
    //         LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(debCurves);
    //     }
    // }
    // //  ReSharper disable UnusedMember.Local
    // @SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")
    // ShowLongSegsWithIdealPositions(dir: Direction) {
    //     //  ReSharper restore UnusedMember.Local
    //     let debCurves = Nudger.GetObstacleBoundaries(this.Obstacles, "black");
    //     let i: number = 0;
    //     debCurves.AddRange(this.LongestNudgedSegs.Select(() => {  }, Nudger.DebugCurveOfLongSeg(ls, DebugCurve.colors[i++, Percent, DebugCurve.colors.length], dir)));
    //     DebugCurveCollection.WriteToFile(debCurves, "c:/tmp/longSegs");
    //     LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(debCurves);
    // }
    // static DebugCurveOfLongSeg(ls: LongestNudgedSegment, s: string, dir: Direction): DebugCurve {
    //     return new DebugCurve(1, s, Nudger.LineSegOfLongestSeg(ls, dir));
    // }
    static LineSegOfLongestSeg(ls, dir) {
        const projectionToDir = dir == Direction.East ? (p) => p.x : (p) => p.y;
        const mm = { min: Number.POSITIVE_INFINITY, max: Number.NEGATIVE_INFINITY };
        for (const edge of ls.Edges) {
            Nudger.UpdateMinMaxWithPoint(mm, projectionToDir, edge.Source);
            Nudger.UpdateMinMaxWithPoint(mm, projectionToDir, edge.Target);
        }
        return dir == Direction.East
            ? new LineSegment(mm.min, -ls.IdealPosition, mm.max, -ls.IdealPosition)
            : new LineSegment(ls.IdealPosition, mm.min, ls.IdealPosition, mm.max);
    }
    static UpdateMinMaxWithPoint(mm, projectionToDir, point) {
        const p = projectionToDir(point);
        if (mm.min > p) {
            mm.min = p;
        }
        if (mm.max < p) {
            mm.max = p;
        }
    }
    // ShowPathsDebug(edgePaths: Array<Path>) {
    //     let debCurves = Nudger.GetObstacleBoundaries(this.Obstacles, "black");
    //     let i: number = 0;
    //     for (let edgePath  of edgePaths) {
    //         debCurves.AddRange(Nudger.GetEdgePathFromPathEdgesAsDebugCurves(0.01, 0.4, DebugCurve.colors[((i + 1)
    //                             % DebugCurve.colors.length)], edgePath));
    //     }
    //     LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(debCurves);
    // }
    // static PathDebugCurves(path: Path, color: string): Array<DebugCurve> {
    //     let d = path.PathEdges.Select(() => {  }, new DebugCurve(70, 0.5, color, new LineSegment(e.Source, e.Target)));
    //     return d.Concat(Nudger.MarkPathVerts(path));
    // }
    // @SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")
    // private static MarkPathVerts(path: Path): Array<DebugCurve> {
    //     let first: boolean = true;
    //     let p = new Point();
    //     for (let p0  of path.PathPoints) {
    //         if (first) {
    //             yield;
    //             return new DebugCurve(200, 1, "violet", CurveFactory.CreateDiamond(5, 5, p0));
    //             first = false;
    //         }
    //         else {
    //             yield;
    //         }
    //         return new DebugCurve(100, 0.5, "brown", CurveFactory.CreateEllipse(1.5, 1.5, p0));
    //         p = p0;
    //     }
    //     yield;
    //     return new DebugCurve(200, 1, "green", CurveFactory.CreateDiamond(3, 3, p));
    // }
    //  static PathDebugCurvesFromPoint(path: Path): Array<DebugCurve> {
    //     let l = new Array<Point>(path.PathPoints);
    //     for (let i: number = 0; (i
    //                 < (l.Count - 1)); i++) {
    //         yield;
    //     }
    //     return new DebugCurve(4, "red", new LineSegment(l[i], l[(i + 1)]));
    // }
    //
    //  ReSharper disable UnusedMember.Local
    //         void ShowEdgesOfEdgePath(Path path){
    //  ReSharper restore UnusedMember.Local
    //             string[] colors = {"red", "brown", "purple"};
    //             const double w0 = 1;
    //             const double w1 = 3;
    //             double dw = (w1 - w0)/path.OrientedSubpaths.Count;
    //             int i = 0;
    //             var dc = new Array<DebugCurve>();
    //             foreach (var s  of path.OrientedSubpaths){
    //                 dc.AddRange(SubpathDebugCurves(w0 + dw*i, colors[Math.Min(i++, colors.Length - 1)], s));
    //             }
    //             LayoutAlgorithmSettings.ShowDebugCurves(dc.ToArray());
    //         }
    //
    //         static Array<DebugCurve> SubpathDebugCurves(double w, string color, OrientedSubpath subpath){
    //             return subpath.LinkedPath.Select(e => new DebugCurve(w, color, new LineSegment(e.Source.Point, e.Target.Point)));
    //         }
    //  static GetObstacleBoundaries(obstacles: Array<Polyline>, color: string): Array<DebugCurve> {
    //     let debugCurves = new Array<DebugCurve>();
    //     if ((obstacles != null)) {
    //         debugCurves.AddRange(obstacles.select(() => {  }, new DebugCurve(50, 0.3, color, poly)));
    //     }
    //     return debugCurves;
    // }
    CreateConstraintsBetweenLongestSegments() {
        for (const segment of this.LongestNudgedSegs) {
            this.CreateConstraintsBetweenLongestSegmentsForSegment(segment);
        }
    }
    CreateConstraintsBetweenLongestSegmentsForSegment(segment) {
        const rightNeighbors = new Set();
        for (const pathEdge of segment.Edges) {
            const axisEdge = pathEdge.AxisEdge;
            if (axisEdge != null) {
                for (const rightNeiAxisEdge of axisEdge.RightNeighbors) {
                    for (const longSeg of rightNeiAxisEdge.LongestNudgedSegments) {
                        rightNeighbors.add(longSeg);
                    }
                }
            }
        }
        for (const seg of rightNeighbors) {
            this.ConstraintTwoLongestSegs(segment, seg);
        }
    }
    CreateConstraintsOfTheOrder() {
        for (const kv of this.PathOrders) {
            if (Nudger.ParallelToDirection(kv[0], this.NudgingDirection)) {
                this.CreateConstraintsOfThePathOrder(kv[1]);
            }
        }
    }
    static ParallelToDirection(edge, direction) {
        switch (direction) {
            case Direction.North:
            case Direction.South:
                return closeDistEps(edge.SourcePoint.x, edge.TargetPoint.x);
                break;
            default:
                return closeDistEps(edge.SourcePoint.y, edge.TargetPoint.y);
                break;
        }
    }
    CreateConstraintsOfThePathOrder(pathOrder) {
        let prevEdge = null;
        for (const pathEdge of pathOrder.filter((p) => p.LongestNudgedSegment != null)) {
            if (prevEdge != null) {
                this.ConstraintTwoLongestSegs(prevEdge.LongestNudgedSegment, pathEdge.LongestNudgedSegment);
            }
            prevEdge = pathEdge;
        }
    }
    ConstraintTwoLongestSegs(prevSeg, seg) {
        if (!prevSeg.IsFixed || !seg.IsFixed) {
            this.Solver.AddConstraint(prevSeg.Id, seg.Id);
        }
    }
    CreateVariablesOfLongestSegment(segment) {
        if (!segment.IsFixed) {
            const leftBound = segment.GetLeftBound();
            const rightBound = segment.GetRightBound();
            if (leftBound >= rightBound) {
                // don't move the segment from the way it was generated
                this.Solver.AddFixedVariable(segment.Id, Nudger.SegmentPosition(segment, this.NudgingDirection));
                segment.IsFixed = true;
            }
            else {
                this.Solver.AddVariableNNNN(segment.Id, Nudger.SegmentPosition(segment, this.NudgingDirection), segment.IdealPosition, segment.Width);
                //            Assert.assert(leftBound + Curve.DistanceEpsilon < rightBound); //this assert does not hold for overlaps
                if (leftBound != Number.NEGATIVE_INFINITY) {
                    this.Solver.SetLowBound(leftBound, segment.Id);
                }
                if (rightBound != Number.POSITIVE_INFINITY) {
                    this.Solver.SetUpperBound(segment.Id, rightBound);
                }
            }
        }
        else {
            this.Solver.AddFixedVariable(segment.Id, Nudger.SegmentPosition(segment, this.NudgingDirection));
        }
    }
    static SegmentPosition(segment, direction) {
        return direction == Direction.North ? segment.Start.x : -segment.Start.y;
    }
    FindFreeSpaceInDirection(axisEdges) {
        this.BoundAxisEdgesByRectsKnownInAdvance();
        const freeSpaceFinder = new FreeSpaceFinder(this.NudgingDirection, this.Obstacles, this.axisEdgesToObstaclesTheyOriginatedFrom, this.PathOrders, axisEdges);
        freeSpaceFinder.FindFreeSpace();
    }
    BoundAxisEdgesByRectsKnownInAdvance() {
        for (const path of this.Paths) {
            if (this.HasGroups) {
                this.BoundPathByMinCommonAncestors(path);
            }
            this.BoundAxisEdgesAdjacentToSourceAndTargetOnEdge(path);
        }
    }
    BoundPathByMinCommonAncestors(path) {
        for (const sh of this.GetMinCommonAncestors(path.EdgeGeometry)) {
            const rect = sh.BoundingBox;
            for (const e of path.PathEdges()) {
                const edge = e.AxisEdge;
                if (edge.Direction == this.NudgingDirection) {
                    this.BoundAxisEdgeByRect(rect, edge);
                }
            }
        }
    }
    GetMinCommonAncestors(edgeGeometry) {
        if (this.PortToShapes == null) {
            this.PortToShapes = Nudger.MapPortsToShapes(this.AncestorsSets.keys());
        }
        const commonAncestors = IntersectSets(this.AncestorsForPort(edgeGeometry.sourcePort), this.AncestorsForPort(edgeGeometry.targetPort));
        return Array.from(commonAncestors).filter((anc) => !anc.Children.some((child) => commonAncestors.has(child)));
    }
    AncestorsForPort(port) {
        const shape = this.PortToShapes.get(port);
        if (shape) {
            return this.AncestorsSets.get(shape);
        }
        //  This is a FreePort or Waypoint; return all spatial parents.
        return new Set(this.HierarchyOfGroups.AllHitItems(Rectangle.mkPP(port.Location, port.Location), null));
    }
    BoundAxisEdgeAdjacentToObstaclePort(port, axisEdge) {
        if (port.Curve == null) {
            this.BoundAxisByPoint(port.Location, axisEdge);
        }
        else if (port.Curve.boundingBox.contains(port.Location)) {
            this.BoundAxisEdgeByRect(port.Curve.boundingBox, axisEdge);
        }
    }
    BoundAxisByPoint(point, axisEdge) {
        if (axisEdge != null && axisEdge.Direction == this.NudgingDirection) {
            if (this.NudgingDirection == Direction.North) {
                axisEdge.BoundFromLeft(point.x);
                axisEdge.BoundFromRight(point.x);
            }
            else {
                axisEdge.BoundFromLeft(-point.y);
                axisEdge.BoundFromRight(-point.y);
            }
        }
    }
    BoundAxisEdgesAdjacentToSourceAndTargetOnEdge(path) {
        this.BoundAxisEdgeAdjacentToObstaclePort(path.EdgeGeometry.sourcePort, path.FirstEdge.AxisEdge);
        this.BoundAxisEdgeAdjacentToObstaclePort(path.EdgeGeometry.targetPort, path.LastEdge.AxisEdge);
    }
    BoundAxisEdgeByRect(rectangle, axisEdge) {
        if (axisEdge != null && axisEdge.Direction == this.NudgingDirection) {
            if (this.NudgingDirection == Direction.North) {
                axisEdge.BoundFromLeft(rectangle.left);
                axisEdge.BoundFromRight(rectangle.right);
            }
            else {
                axisEdge.BoundFromLeft(rectangle.top * -1);
                axisEdge.BoundFromRight(rectangle.bottom * -1);
            }
        }
    }
    CreateLongestNudgedSegments() {
        const projectionToPerp = this.NudgingDirection == Direction.East ? (p) => -p.y : (p) => p.x;
        this.LongestNudgedSegs = new Array();
        for (let i = 0; i < this.Paths.length; i++) {
            this.CreateLongestNudgedSegmentsForPath(this.Paths[i], projectionToPerp);
        }
    }
    CreateLongestNudgedSegmentsForPath(path, projectionToPerp) {
        // ShowEdgesOfEdgePath(path);
        this.GoOverPathAndCreateLongSegs(path);
        Nudger.CalculateIdealPositionsForLongestSegs(path, projectionToPerp);
    }
    static CalculateIdealPositionsForLongestSegs(path, projectionToPerp) {
        let currentLongSeg = null;
        let ret = null;
        let prevOffset = projectionToPerp(path.Start);
        for (const edge of path.PathEdges()) {
            if (edge.LongestNudgedSegment != null) {
                currentLongSeg = edge.LongestNudgedSegment;
                if (ret != null) {
                    let t;
                    Nudger.SetIdealPositionForSeg(ret, (t = projectionToPerp(ret.start)), prevOffset, projectionToPerp(currentLongSeg.Start));
                    prevOffset = t;
                    ret = null;
                }
            }
            else if (currentLongSeg != null) {
                ret = currentLongSeg;
                currentLongSeg = null;
            }
        }
        if (ret != null) {
            Nudger.SetIdealPositionForSeg(ret, projectionToPerp(ret.Start), prevOffset, projectionToPerp(path.End));
        }
        else if (currentLongSeg != null) {
            currentLongSeg.IdealPosition = projectionToPerp(currentLongSeg.Start);
        }
    }
    static SetIdealPositionForSeg(segment, segPosition, offset0, offset1) {
        const max = Math.max(offset0, offset1);
        const min = Math.min(offset0, offset1);
        if (min + GeomConstants.distanceEpsilon < segPosition) {
            if (segPosition < max) {
                segment.IdealPosition = 0.5 * (max + min);
            }
            else {
                segment.IdealPosition = max;
            }
        }
        else {
            segment.IdealPosition = min;
        }
    }
    GoOverPathAndCreateLongSegs(path) {
        let currentLongestSeg = null;
        const oppositeDir = CompassVector.OppositeDir(this.NudgingDirection);
        for (const edge of path.PathEdges()) {
            const edgeDir = edge.Direction;
            if (edgeDir == this.NudgingDirection || edgeDir == oppositeDir) {
                if (currentLongestSeg == null) {
                    edge.LongestNudgedSegment = currentLongestSeg = new LongestNudgedSegment(this.LongestNudgedSegs.length);
                    this.LongestNudgedSegs.push(currentLongestSeg);
                }
                else {
                    edge.LongestNudgedSegment = currentLongestSeg;
                }
                if (edge.IsFixed) {
                    currentLongestSeg.IsFixed = true;
                }
            }
            else {
                // the edge is perpendicular to "direction"
                edge.LongestNudgedSegment = null;
                currentLongestSeg = null;
            }
        }
    }
    static BuildPolylineForPath(path) {
        const t = { points: path.PathPoints.map((p) => p.clone()) };
        Nudger.ExtendPolylineToPorts(t, path);
        /* for (let i = 0; i < t.points.length - 1; i++) {
          Assert.assert(
            CompassVector.IsPureDirectionPP(t.points[i], t.points[i + 1]),
          )
        }*/
        return t.points;
    }
    static ExtendPolylineToPorts(t, path) {
        Nudger.ExtendPolylineToSourcePort(t, path.EdgeGeometry.sourcePort.Location);
        Nudger.ExtendPolylineToTargetPort(t, path.EdgeGeometry.targetPort.Location);
        //  In some overlapped cases where the source or target vertex used for the path
        //  coincides with the target or source port location, we can end up with a single-point
        //  path.  In that case, we just force a straightline path.
        if (t.points.length < 2) {
            t.points = new Array(2);
            t.points[0] = path.EdgeGeometry.sourcePort.Location;
            t.points[1] = path.EdgeGeometry.targetPort.Location;
        }
    }
    static ExtendPolylineToTargetPort(t, location) {
        const n = t.points.length - 1;
        const dir = CompassVector.VectorDirectionPP(t.points[n - 1], t.points[n]);
        if (Nudger.ProjectionsAreClose(t.points[n - 1], dir, location)) {
            // it might be that the last point on polyline is at the port already
            // then we just drop the last point
            t.points = t.points.slice(0, n);
            return;
        }
        const p = t.points[n];
        if (dir == Direction.East || dir == Direction.West) {
            t.points[n] = new Point(location.x, p.y);
        }
        else {
            t.points[n] = new Point(p.x, location.y);
        }
    }
    static ProjectionsAreClose(a, dir, b) {
        if (dir == Direction.East || dir == Direction.West) {
            return closeDistEps(a.x, b.x);
        }
        return closeDistEps(a.y, b.y);
    }
    static ExtendPolylineToSourcePort(t, location) {
        const dir = CompassVector.VectorDirectionPP(t.points[0], t.points[1]);
        if (Nudger.ProjectionsAreClose(t.points[1], dir, location)) {
            // it might be that the second point on polyline is at the port already
            // then we just drop the first point
            t.points = t.points.slice(1);
            return;
        }
        const p = t.points[0];
        if (dir == Direction.East || dir == Direction.West) {
            t.points[0] = new Point(location.x, p.y);
        }
        else {
            t.points[0] = new Point(p.x, location.y);
        }
    }
    static RemoveSwitchbacksAndMiddlePoints(points) {
        const ret = [];
        let a = points[0];
        ret.push(a);
        let b = points[1];
        let prevDir = CompassVector.VectorDirectionPP(a, b);
        let i = 1;
        while (++i < points.length) {
            const dir = CompassVector.VectorDirectionPP(b, points[i]);
            if (!(dir == prevDir || CompassVector.OppositeDir(dir) == prevDir || dir == Direction.None)) {
                if (!Point.closeDistEps(a, b)) {
                    // make sure that we are not returning the same point twice
                    ret.push((a = Nudger.Rectilinearise(a, b)));
                }
                prevDir = dir;
            }
            b = points[i];
        }
        if (!Point.closeDistEps(a, b)) {
            ret.push(Nudger.Rectilinearise(a, b));
        }
        return ret;
    }
    //  this function defines the final path coordinates
    //  the set of paths, point sequences
    //  the radius of the arc inscribed into the path corners
    //  an enumeration of padded obstacles
    //
    //
    //  <returns>the mapping of the path to its modified path</returns>
    static NudgePaths(paths, cornerFitRadius, paddedObstacles, ancestorsSets, removeStaircases) {
        if (paths.length == 0) {
            return;
        }
        const nudger = new Nudger(paths, cornerFitRadius, paddedObstacles, ancestorsSets);
        nudger.Calculate(Direction.North, true);
        nudger.Calculate(Direction.East, false);
        nudger.Calculate(Direction.North, false);
        if (removeStaircases) {
            nudger.RemoveStaircases();
        }
        for (const path of paths) {
            path.EdgeGeometry.curve = Polyline.mkFromPoints(Nudger.BuildPolylineForPath(path));
        }
    }
    RemoveStaircases() {
        StaircaseRemover.RemoveStaircases(this.Paths, this.HierarchyOfObstacles);
    }
    static MapPortsToShapes(listOfShapes) {
        const portToShapes = new Map();
        for (const shape of listOfShapes) {
            for (const port of shape.Ports) {
                portToShapes.set(port, shape);
            }
        }
        return portToShapes;
    }
    // ShowPathsDebug(edgePaths: Iterable<Path>, fn: string) {
    //   const debCurves = GetObstacleBoundaries(this.Obstacles, 'black')
    //   const i = 0
    //   for (const edgePath of edgePaths) {
    //     for (const c of Nudger.GetEdgePathFromPathEdgesAsDebugCurves(
    //       0.1,
    //       1.0,
    //       DebugCurve.colors[(i + 1) % DebugCurve.colors.length],
    //       edgePath,
    //     )) {
    //       debCurves.push(c)
    //     }
    //   }
    //   SvgDebugWriter.dumpDebugCurves(fn, debCurves)
    // }
    static *GetEdgePathFromPathEdgesAsDebugCurves(startWidth, endWidth, color, path) {
        const points = path.ArrayOfPathPoints();
        const count = points.length;
        const deltaW = count > 1 ? (endWidth - startWidth) / (count - 1) : 1;
        // if count ==1 the value of deltaW does not matter
        for (let i = 0; i < points.length - 1; i++) {
            yield DebugCurve.mkDebugCurveTWCI(200, startWidth + deltaW * i, color, LineSegment.mkPP(points[i], points[i + 1]));
        }
    }
}
// function GetObstacleBoundaries(
//   obstacles: Array<Polyline>,
//   color: string,
// ): Array<DebugCurve> {
//   const debugCurves = new Array<DebugCurve>()
//   if (obstacles != null) {
//     for (const o of obstacles)
//       debugCurves.push(DebugCurve.mkDebugCurveTWCI(50, 0.3, color, o))
//   }
//   return debugCurves
// }
function IntersectSets(a, b) {
    const r = new Set();
    if (a.size < b.size) {
        for (const x of a)
            if (b.has(x))
                r.add(x);
    }
    else {
        for (const x of b)
            if (a.has(x))
                r.add(x);
    }
    return r;
}
//# sourceMappingURL=Nudger.js.map