import {Shape} from './shape'
import {mkRTree} from '../math/geometry/RTree/RTree'
// import { Queue } from 'queue-typescript'
// import { GeomGraph } from '../layout/core/GeomGraph'
// import { IRectangle } from '../core/geometry/IRectangle'
// import { RectangleNode } from '../core/geometry/RTree/RectangleNode'
// import { RTree } from '../core/geometry/RTree/RTree'
// import { BundlingSettings } from '../core/routing/BundlingSettings'
// import { EdgeGeometry } from '../layout/core/edgeGeometry'
import {GeomEdge} from '../layout/core/geomEdge'
import {RelativeFloatingPort} from '../layout/core/relativeFloatingPort'
// import { Curve } from '../math/geometry/curve'
// import { ICurve } from '../math/geometry/icurve'
import {Point} from '../math/geometry/point'
// import { Polyline } from '../math/geometry/polyline'
// import { PolylinePoint } from '../math/geometry/polylinePoint'
// import { Rectangle } from '../math/geometry/rectangle'
// import { SmoothedPolyline } from '../math/geometry/smoothedPolyline'
import {Algorithm} from '../utils/algorithm'
import {Assert} from '../utils/assert'
import {Curve, ICurve, LineSegment, PointLocation, Polyline, Rectangle} from '../math/geometry'
import {PolylinePoint} from '../math/geometry/polylinePoint'
import {closeDistEps} from '../utils/compare'
import {PointSet} from '../utils/PointSet'
import {RTree} from '../math/geometry/RTree/RTree'
import {LineSweeper} from './spline/coneSpanner/LineSweeper'
import {PointMap} from '../utils/PointMap'
import {VisibilityGraph} from './visibility/VisibilityGraph'
import {TightLooseCouple} from './TightLooseCouple'
import {VisibilityEdge} from './visibility/VisibilityEdge'
import {ConeSpanner} from './spline/coneSpanner/ConeSpanner'
import {EdgeGeometry} from '../layout/core/edgeGeometry'
import {HookUpAnywhereFromInsidePort} from '../layout/core/hookUpAnywhereFromInsidePort'
import {ClusterBoundaryPort} from './ClusterBoundaryPort'
import {CreateRectangleNodeOnEnumeration, mkRectangleNode, RectangleNode} from '../math/geometry/RTree/RectangleNode'
import {CurvePort} from '../layout/core/curvePort'
import {BundlingSettings} from './BundlingSettings'
import {CancelToken, GeomGraph} from '..'
import {EdgeRoutingSettings} from './EdgeRoutingSettings'
import {ShapeCreatorForRoutingToParents} from './ShapeCreatorForRoutingToParents'
import {Port} from '../layout/core/port'
import {ShapeObstacleCalculator} from './ShapeObstacleCalculator'
import {InteractiveEdgeRouter} from './InteractiveEdgeRouter'
import {SmoothedPolyline} from '../math/geometry/smoothedPolyline'
import {addRange, uniteSets, insertRange, setIntersection, setsAreEqual} from '../utils/setOperations'
import {Queue} from 'queue-typescript'
import {Arrowhead} from '../layout/core/arrowhead'
import {Polygon} from './visibility/Polygon'
import {PointPairMap} from '../utils/pointPairMap'
import {InteractiveObstacleCalculator} from './interactiveObstacleCalculator'
import {ShapeCreator} from './ShapeCreator'
import {DebugCurve} from '../math/geometry/debugCurve'
import {getEdgeRoutingSettingsFromAncestors} from '../layout/driver'
import {PointPair} from '../math/geometry/pointPair'
import {MultiEdgeRouter} from './MultiEdgeRouter'
import {BundleRouter} from './spline/bundling/BundleRouter'
import {SdShortestPath} from './spline/bundling/SdShortestPath'
import {Cdt} from './ConstrainedDelaunayTriangulation/Cdt'
import {CdtEdge} from './ConstrainedDelaunayTriangulation/CdtEdge'
// import { CancelToken } from '../utils/cancelToken'
// import { Cdt } from './ConstrainedDelaunayTriangulation/Cdt'
// import { CdtEdge } from './ConstrainedDelaunayTriangulation/CdtEdge'
// import { Polygon } from './visibility/Polygon'
// import { VisibilityEdge } from './visibility/VisibilityEdge'

//  routing splines around shapes
export class SplineRouter extends Algorithm {
  //  setting this to true forces the calculation to go on even when node overlaps are present
  //
  continueOnOverlaps = true

  get ContinueOnOverlaps(): boolean {
    return this.continueOnOverlaps
  }
  set ContinueOnOverlaps(value: boolean) {
    this.continueOnOverlaps = value
  }

  rootShapes: Shape[];

  *edgeGeometriesEnumeration(): IterableIterator<EdgeGeometry> {
    if (this._edges != null) {
      for (const e of this._edges) {
        yield e.edgeGeometry
      }
    }
  }

  coneAngle: number

  tightPadding: number
  loosePadding: number
  get LoosePadding(): number {
    return this.loosePadding
  }
  set LoosePadding(value: number) {
    this.loosePadding = value
  }

  rootWasCreated: boolean

  root: Shape

  visGraph: VisibilityGraph

  ancestorSets: Map<Shape, Set<Shape>>

  shapesToTightLooseCouples: Map<Shape, TightLooseCouple> = new Map<Shape, TightLooseCouple>()

  portsToShapes: Map<Port, Shape>
  portsToEnterableShapes: Map<Port, Set<Shape>>

  portRTree: RTree<Point, Point>

  portLocationsToLoosePolylines: PointMap<Polyline> = new PointMap<Polyline>()

  looseRoot: Shape

  BundlingSettings: BundlingSettings
  enterableLoose: Map<EdgeGeometry, Set<Polyline>>
  enterableTight: Map<EdgeGeometry, Set<Polyline>>

  geomGraph: GeomGraph

  multiEdgesSeparation = 5

  routeMultiEdgesAsBundles = true

  UseEdgeLengthMultiplier: boolean

  //  if set to true the algorithm will try to shortcut a shortest polyline inner points
  UsePolylineEndShortcutting = true

  //  if set to true the algorithm will try to shortcut a shortest polyline start and end
  UseInnerPolylingShortcutting = true

  AllowedShootingStraightLines = true

  get MultiEdgesSeparation(): number {
    return this.multiEdgesSeparation
  }
  set MultiEdgesSeparation(value: number) {
    this.multiEdgesSeparation = value
  }

  static mk2(graph: GeomGraph, edgeRoutingSettings: EdgeRoutingSettings) {
    return SplineRouter.mk5(
      graph,
      edgeRoutingSettings.Padding,
      edgeRoutingSettings.PolylinePadding,
      edgeRoutingSettings.ConeAngle,
      edgeRoutingSettings.BundlingSettings,
    )
  }

  static mk4(graph: GeomGraph, tightTightPadding: number, loosePadding: number, coneAngle: number): SplineRouter {
    return new SplineRouter(graph, Array.from(graph.edges()), tightTightPadding, loosePadding, coneAngle, null)
  }

  //  Creates a spline group router for the given graph
  static mk5(graph: GeomGraph, tightTightPadding: number, loosePadding: number, coneAngle: number, bundlingSettings: BundlingSettings) {
    return new SplineRouter(graph, Array.from(graph.edges()), tightTightPadding, loosePadding, coneAngle, bundlingSettings)
  }

  //  Creates a spline group router for a given GeomGraph.
  constructor(
    graph: GeomGraph,
    edges: Array<GeomEdge>,
    tightPadding = 2,
    loosePadding = 1.5,
    coneAngle = 30 * (Math.PI / 180),
    bundlingSettings: BundlingSettings = null,
    cancelToken: CancelToken = null,
  ) {
    super(cancelToken)
    this._edges = edges
    this.BundlingSettings = bundlingSettings
    this.geomGraph = graph
    this.LoosePadding = loosePadding
    this.tightPadding = tightPadding
    this.coneAngle = coneAngle
    const obstacles = ShapeCreator.GetShapes(this.geomGraph, this._edges)
    if (
      this.BundlingSettings == null &&
      graph.layoutSettings &&
      graph.layoutSettings.edgeRoutingSettings &&
      graph.layoutSettings.edgeRoutingSettings.BundlingSettings
    ) {
      this.BundlingSettings = graph.layoutSettings.edgeRoutingSettings.BundlingSettings
    }

    this.Initialize(obstacles, this.coneAngle)
  }

  _edges: GeomEdge[]
  static mk6(
    graph: GeomGraph,
    tightPadding: number,
    loosePadding: number,
    coneAngle: number,
    inParentEdges: Array<GeomEdge>,
    outParentEdges: Array<GeomEdge>,
  ): SplineRouter {
    const ret = SplineRouter.mk4(graph, tightPadding, loosePadding, coneAngle)
    const obstacles = ShapeCreatorForRoutingToParents.GetShapes(inParentEdges, outParentEdges)
    ret.Initialize(obstacles, coneAngle)
    return ret
  }
  Initialize(obstacles: Array<Shape>, coneAngleValue: number) {
    this.rootShapes = obstacles.filter((s) => s.Parents == null || s.Parents.length == 0)
    this.coneAngle = coneAngleValue
    if (this.coneAngle == 0) {
      this.coneAngle = Math.PI / 6
    }
  }

  //  Executes the algorithm.
  run() {
    this.GetOrCreateRoot()
    this.RouteOnRoot()
    this.RemoveRoot()
  }

  RouteOnRoot() {
    this.CalculatePortsToShapes()
    this.CalculatePortsToEnterableShapes()
    this.CalculateShapeToBoundaries(this.root)
    if (this.OverlapsDetected && !this.ContinueOnOverlaps) {
      return
    }
    this.BindLooseShapes()
    this.SetLoosePolylinesForAnywherePorts()
    this.CalculateVisibilityGraph()
    this.RouteOnVisGraph()
  }

  CalculatePortsToEnterableShapes() {
    this.portsToEnterableShapes = new Map<Port, Set<Shape>>()
    for (const [port, shape] of this.portsToShapes) {
      const set = new Set<Shape>()
      if (!SplineRouter.EdgesAttachedToPortAvoidTheNode(port)) {
        set.add(shape)
      }

      this.portsToEnterableShapes.set(port, set)
    }

    for (const rootShape of this.rootShapes) {
      for (const sh of rootShape.Descendants()) {
        for (const port of sh.Ports) {
          const enterableSet = this.portsToEnterableShapes.get(port)
          insertRange(
            enterableSet,
            Array.from(sh.Ancestors()).filter((s) => s.BoundaryCurve != null),
          )
        }
      }
    }
  }

  static EdgesAttachedToPortAvoidTheNode(port: Port): boolean {
    return port instanceof CurvePort || port instanceof ClusterBoundaryPort
  }

  SetLoosePolylinesForAnywherePorts() {
    for (const [shape, cpl] of this.shapesToTightLooseCouples) {
      for (const port of shape.Ports) {
        const isHport = port instanceof HookUpAnywhereFromInsidePort

        if (isHport) {
          const aport = <HookUpAnywhereFromInsidePort>port
          aport.LoosePolyline = <Polyline>cpl.LooseShape.BoundaryCurve
        }

        if (port instanceof ClusterBoundaryPort) {
          const c = <ClusterBoundaryPort>port
          c.LoosePolyline = <Polyline>cpl.LooseShape.BoundaryCurve
        }
      }
    }
  }

  BindLooseShapes() {
    this.looseRoot = new Shape()
    for (const shape of this.root.Children) {
      const looseShape = this.shapesToTightLooseCouples.get(shape).LooseShape
      this.BindLooseShapesUnderShape(shape)
      this.looseRoot.AddChild(looseShape)
    }
  }

  BindLooseShapesUnderShape(shape: Shape) {
    const loose = this.shapesToTightLooseCouples.get(shape).LooseShape
    for (const child of shape.Children) {
      const childLooseShape = this.shapesToTightLooseCouples.get(child).LooseShape
      loose.AddChild(childLooseShape)
      this.BindLooseShapesUnderShape(child)
    }
  }

  CalculateShapeToBoundaries(shape: Shape) {
    this.ProgressStep()
    if (shape.Children.length == 0) {
      return
    }

    for (const child of shape.Children) {
      this.CalculateShapeToBoundaries(child)
    }

    const obstacleCalculator = new ShapeObstacleCalculator(
      shape,
      this.tightPadding,
      this.AdjustedLoosePadding,
      this.shapesToTightLooseCouples,
    )
    obstacleCalculator.Calculate()
    this.OverlapsDetected = this.OverlapsDetected || obstacleCalculator.OverlapsDetected
  }

  private _overlapsDetected = false
  get OverlapsDetected() {
    return this._overlapsDetected
  }
  set OverlapsDetected(value) {
    this._overlapsDetected = value
  }

  get AdjustedLoosePadding(): number {
    return this.BundlingSettings == null ? this.LoosePadding : this.LoosePadding * BundleRouter.SuperLoosePaddingCoefficient
  }

  GroupEdgesByPassport(): Array<{
    passport: Set<Shape>
    edges: Array<GeomEdge>
  }> {
    const ret = new Array<{passport: Set<Shape>; edges: Array<GeomEdge>}>()
    for (const edge of this._edges) {
      const edgePassport = this.EdgePassport(edge) // todo : is ret.find() too slow?
      let pair = ret.find((p) => setsAreEqual(p.passport, edgePassport))
      if (!pair) {
        pair = {passport: edgePassport, edges: []}
        ret.push(pair)
      }
      pair.edges.push(edge)
    }
    return ret
  }

  RouteOnVisGraph() {
    this.ancestorSets = SplineRouter.GetAncestorSetsMap(Array.from(this.root.Descendants()))
    if (this.BundlingSettings == null) {
      for (const edgeGroup of this.GroupEdgesByPassport()) {
        const passport = edgeGroup.passport
        const obstacleShapes: Set<Shape> = this.GetObstaclesFromPassport(passport)
        const interactiveEdgeRouter = this.CreateInteractiveEdgeRouter(Array.from(obstacleShapes))
        this.RouteEdgesWithTheSamePassport(edgeGroup, interactiveEdgeRouter, obstacleShapes)
      }
    } else {
      this.RouteBundles()
    }
  }

  RouteEdgesWithTheSamePassport(
    edgeGeometryGroup: {passport: Set<Shape>; edges: Iterable<GeomEdge>},
    interactiveEdgeRouter: InteractiveEdgeRouter,
    obstacleShapes: Set<Shape>,
  ) {
    const t: {regularEdges: Array<GeomEdge>; multiEdges: Array<GeomEdge[]>} = {
      regularEdges: [],
      multiEdges: [],
    }
    if (this.RouteMultiEdgesAsBundles) {
      this.SplitOnRegularAndMultiedges(edgeGeometryGroup.edges, t)
      for (const edge of t.regularEdges) {
        this.RouteEdge(interactiveEdgeRouter, edge)
      }

      if (t.multiEdges != null) {
        this.ScaleDownLooseHierarchy(interactiveEdgeRouter, obstacleShapes)
        this.RouteMultiEdges(t.multiEdges, interactiveEdgeRouter, edgeGeometryGroup.passport)
      }
    } else {
      for (const eg of edgeGeometryGroup.edges) {
        this.RouteEdge(interactiveEdgeRouter, eg)
      }
    }
  }

  // if set to true routes multi edges as ordered bundles
  get RouteMultiEdgesAsBundles(): boolean {
    return this.routeMultiEdgesAsBundles
  }
  set RouteMultiEdgesAsBundles(value: boolean) {
    this.routeMultiEdgesAsBundles = value
  }

  RouteEdge(interactiveEdgeRouter: InteractiveEdgeRouter, edge: GeomEdge) {
    const transparentShapes = this.MakeTransparentShapesOfEdgeGeometryAndGetTheShapes(edge.edgeGeometry)
    this.ProgressStep()
    this.RouteEdgeGeometry(edge, interactiveEdgeRouter)
    SplineRouter.SetTransparency(transparentShapes, false)
  }

  ScaleDownLooseHierarchy(interactiveEdgeRouter: InteractiveEdgeRouter, obstacleShapes: Set<Shape>) {
    const loosePolys = new Array<Polyline>()
    for (const obstacleShape of obstacleShapes) {
      const tl = this.shapesToTightLooseCouples.get(obstacleShape)
      loosePolys.push(
        InteractiveObstacleCalculator.LoosePolylineWithFewCorners(
          tl.TightPolyline,
          tl.Distance / 1.1, // 1.1 is BundleRouter.SuperLoosePaddingCoefficient,
        ),
      )
    }

    interactiveEdgeRouter.LooseHierarchy = SplineRouter.CreateLooseObstacleHierarachy(loosePolys)
    interactiveEdgeRouter.ClearActivePolygons()
    interactiveEdgeRouter.AddActivePolygons(loosePolys.map((poly) => new Polygon(poly)))
  }

  RouteMultiEdges(multiEdges: Array<GeomEdge[]>, interactiveEdgeRouter: InteractiveEdgeRouter, parents: Set<Shape>) {
    const nodeBoundaries: ICurve[] = []
    for (const p of parents) {
      for (const ch of p.Children) nodeBoundaries.push(ch.BoundaryCurve)
    }
    const bs = new BundlingSettings()
    bs.InkImportance = 0.00001
    bs.EdgeSeparation = this.MultiEdgesSeparation

    const mer = new MultiEdgeRouter(multiEdges, interactiveEdgeRouter, nodeBoundaries, bs, (a) =>
      this.MakeTransparentShapesOfEdgeGeometryAndGetTheShapes(a),
    )
    //giving more importance to ink might produce weird routings with huge detours, maybe 0 is the best value here
    mer.run()
  }

  SplitOnRegularAndMultiedges(edges: Iterable<GeomEdge>, t: {regularEdges: Array<GeomEdge>; multiEdges: Array<GeomEdge[]>}) {
    const portLocationPairsToEdges = new PointPairMap<Array<GeomEdge>>()
    for (const eg of edges) {
      if (SplineRouter.IsEdgeToParent(eg.edgeGeometry)) {
        t.regularEdges.push(eg)
      } else {
        SplineRouter.RegisterInPortLocationsToEdges(eg, portLocationPairsToEdges)
      }
    }

    t.multiEdges = null
    for (const edgeGroup of portLocationPairsToEdges.values()) {
      if (edgeGroup.length == 1 || this.OverlapsDetected) {
        t.regularEdges.push(edgeGroup[0])
      } else {
        if (t.multiEdges == null) {
          t.multiEdges = new Array<GeomEdge[]>()
        }
        t.multiEdges.push(edgeGroup)
      }
    }
  }

  static RegisterInPortLocationsToEdges(eg: GeomEdge, portLocationPairsToEdges: PointPairMap<Array<GeomEdge>>) {
    let list: Array<GeomEdge>
    const pp = new PointPair(eg.sourcePort.Location, eg.targetPort.Location)
    list = portLocationPairsToEdges.get(pp)
    if (!list) {
      list = new Array<GeomEdge>()
      portLocationPairsToEdges.set(pp, list)
    }
    list.push(eg)
  }

  static IsEdgeToParent(e: EdgeGeometry): boolean {
    return e.sourcePort instanceof HookUpAnywhereFromInsidePort || e.targetPort instanceof HookUpAnywhereFromInsidePort
  }

  CreateInteractiveEdgeRouter(obstacleShapes: Array<Shape>): InteractiveEdgeRouter {
    // we need to create a set here because one loose polyline can hold several original shapes
    const loosePolys = new Set<Polyline>(
      obstacleShapes.map((sh) => <Polyline>this.shapesToTightLooseCouples.get(sh).LooseShape.BoundaryCurve),
    )
    const router = new InteractiveEdgeRouter(this.cancelToken)
    router.ObstacleCalculator = new InteractiveObstacleCalculator(
      obstacleShapes.map((sh) => sh.BoundaryCurve),
      this.tightPadding,
      this.loosePadding,
      false,
    )
    ;(router.VisibilityGraph = this.visGraph), (router.TightHierarchy = this.CreateTightObstacleHierarachy(obstacleShapes))
    router.LooseHierarchy = SplineRouter.CreateLooseObstacleHierarachy(Array.from(loosePolys))
    ;(router.UseSpanner = true),
      (router.LookForRoundedVertices = true),
      (router.TightPadding = this.tightPadding),
      (router.LoosePadding = this.LoosePadding),
      (router.UseEdgeLengthMultiplier = this.UseEdgeLengthMultiplier),
      (router.UsePolylineEndShortcutting = this.UsePolylineEndShortcutting),
      (router.UseInnerPolylingShortcutting = this.UseInnerPolylingShortcutting),
      (router.AllowedShootingStraightLines = this.AllowedShootingStraightLines),
      router.AddActivePolygons(Array.from(loosePolys).map((polyline) => new Polygon(polyline)))

    return router
  }

  GetObstaclesFromPassport(passport: Set<Shape>): Set<Shape> {
    if (passport.size == 0) {
      return new Set<Shape>(this.root.Children)
    }

    const commonAncestors = this.GetCommonAncestorsAbovePassport(passport)
    const allAncestors = this.GetAllAncestors(passport)
    const ret = new Set<Shape>()
    for (const p of passport) {
      for (const child of p.Children) {
        if (!allAncestors.has(child)) ret.add(child)
      }
    }
    const enqueued = uniteSets(new Set<Shape>(passport), ret)
    const queue = new Queue<Shape>()

    for (const shape of passport) {
      if (!commonAncestors.has(shape)) queue.enqueue(shape)
    }

    while (queue.length > 0) {
      const a = queue.dequeue()
      for (const parent of a.Parents) {
        for (const sibling of parent.Children) {
          if (!allAncestors.has(sibling)) {
            ret.add(sibling)
          }
        }

        if (!commonAncestors.has(parent) && !enqueued.has(parent)) {
          queue.enqueue(parent)
          enqueued.add(parent)
        }
      }
    }

    return ret
  }

  GetAllAncestors(passport: Set<Shape>): Set<Shape> {
    if (passport.size == 0) {
      return new Set<Shape>()
    }

    let ret = new Set<Shape>(passport)
    for (const shape of passport) {
      ret = uniteSets(ret, this.ancestorSets.get(shape))
    }

    return ret
  }

  GetCommonAncestorsAbovePassport(passport: Set<Shape>): Set<Shape> {
    if (passport.size == 0) {
      return new Set<Shape>()
    }

    const en = Array.from(passport)
    let ret = this.ancestorSets.get(en[0])
    for (let i = 1; i < en.length; i++) {
      const shape = en[i]
      ret = setIntersection(ret, this.ancestorSets.get(shape))
    }

    return ret
  }

  RouteBundles() {
    this.ScaleLooseShapesDown()
    this.CalculateEdgeEnterablePolylines()
    const looseHierarchy = this.GetLooseHierarchy()
    const cdt = BundleRouter.CreateConstrainedDelaunayTriangulation(looseHierarchy)
    //  CdtSweeper.ShowFront(cdt.GetTriangles(), null, null,this.visGraph.Edges.Select(e=>new LineSegment(e.SourcePoint,e.TargetPoint)));
    const shortestPath = new SdShortestPath((a) => this.MakeTransparentShapesOfEdgeGeometryAndGetTheShapes(a), cdt, this.FindCdtGates(cdt))
    const bundleRouter = new BundleRouter(
      this._edges,
      shortestPath,
      this.visGraph,
      this.BundlingSettings,
      this.LoosePadding,
      this.GetTightHierarchy(),
      looseHierarchy,
      this.enterableLoose,
      this.enterableTight,
      (port) => this.LoosePolyOfOriginalShape(this.portsToShapes.get(port)),
    )
    bundleRouter.run()
  }

  CreateTheMapToParentLooseShapes(shape: Shape, loosePolylinesToLooseParentShapeMap: Map<ICurve, Shape>) {
    for (const childShape of shape.Children) {
      const tightLooseCouple = this.shapesToTightLooseCouples.get(childShape)
      const poly = tightLooseCouple.LooseShape.BoundaryCurve
      loosePolylinesToLooseParentShapeMap.set(poly, shape)
      this.CreateTheMapToParentLooseShapes(childShape, loosePolylinesToLooseParentShapeMap)
    }
  }

  FindCdtGates(cdt: Cdt): Set<CdtEdge> {
    const loosePolylinesToLooseParentShapeMap: Map<ICurve, Shape> = new Map<ICurve, Shape>()
    this.CreateTheMapToParentLooseShapes(this.root, loosePolylinesToLooseParentShapeMap)
    // looking for Cdt edges connecting two siblings; only those we define as gates
    const gates = new Set<CdtEdge>()
    for (const cdtSite of cdt.PointsToSites.values()) {
      for (const cdtEdge of cdtSite.Edges) {
        if (cdtEdge.CwTriangle == null && cdtEdge.CcwTriangle == null) {
          continue
        }

        const a = <Polyline>cdtSite.Owner
        const b = <Polyline>cdtEdge.lowerSite.Owner
        if (a == b) {
          continue
        }

        const aParent: Shape = loosePolylinesToLooseParentShapeMap.get(a)
        if (aParent) {
          const bParent: Shape = loosePolylinesToLooseParentShapeMap.get(b)
          if (aParent == bParent) {
            gates.add(cdtEdge)
          }
        }
      }
    }

    // CdtSweeper.ShowFront(cdt.GetTriangles(), null,
    //                     gates.Select(g => new LineSegment(g.upperSite.Point, g.lowerSite.Point)), null);
    return gates
  }

  CalculateEdgeEnterablePolylines() {
    this.enterableLoose = new Map<EdgeGeometry, Set<Polyline>>()
    this.enterableTight = new Map<EdgeGeometry, Set<Polyline>>()
    for (const edgeGeometry of this.edgeGeometriesEnumeration()) {
      const looseSet = new Set<Polyline>()
      const tightSet = new Set<Polyline>()
      this.GetEdgeEnterablePolylines(edgeGeometry, looseSet, tightSet)
      this.enterableLoose.set(edgeGeometry, looseSet)
      this.enterableTight.set(edgeGeometry, tightSet)
    }
  }

  GetEdgeEnterablePolylines(edgeGeometry: EdgeGeometry, looseEnterable: Set<Polyline>, tightEnterable: Set<Polyline>) {
    const sourceShape = this.portsToShapes.get(edgeGeometry.sourcePort)
    const targetShape = this.portsToShapes.get(edgeGeometry.targetPort)
    if (sourceShape != this.root) {
      for (const a of this.ancestorSets.get(sourceShape)) {
        const la = this.LoosePolyOfOriginalShape(a)
        if (la) {
          looseEnterable.add(la)
        }
        const ta = this.TightPolyOfOriginalShape(a)
        if (ta) {
          tightEnterable.add(ta)
        }
      }
    }
    if (targetShape != this.root) {
      for (const a of this.ancestorSets.get(targetShape)) {
        const la = this.LoosePolyOfOriginalShape(a)
        if (la) {
          looseEnterable.add(la)
        }
        const ta = this.TightPolyOfOriginalShape(a)
        if (ta) {
          tightEnterable.add(ta)
        }
      }
    }
  }

  GetTightHierarchy(): RectangleNode<Polyline, Point> {
    return CreateRectangleNodeOnEnumeration(
      Array.from(this.shapesToTightLooseCouples.values()).map((tl) => mkRectangleNode(tl.TightPolyline, tl.TightPolyline.boundingBox)),
    )
  }

  GetLooseHierarchy(): RectangleNode<Polyline, Point> {
    const loosePolylines = new Set<Polyline>()
    for (const t of this.shapesToTightLooseCouples.values()) {
      loosePolylines.add(<Polyline>t.LooseShape.BoundaryCurve)
    }
    return CreateRectangleNodeOnEnumeration(Array.from(loosePolylines).map((p) => mkRectangleNode(p, p.boundingBox)))
  }

  ScaleLooseShapesDown() {
    for (const [, tl] of this.shapesToTightLooseCouples) {
      tl.LooseShape.BoundaryCurve = InteractiveObstacleCalculator.LoosePolylineWithFewCorners(
        tl.TightPolyline,
        tl.Distance / BundleRouter.SuperLoosePaddingCoefficient,
      )
    }
  }

  //   The set of shapes where the edgeGeometry source and target ports shapes are citizens.
  //   In the simple case it is the union of the target port shape parents and the sourceport shape parents.
  //   When one end shape contains another, the passport is the set consisting of the end shape and all other shape parents.
  EdgePassport(edge: GeomEdge): Set<Shape> {
    const edgeGeometry: EdgeGeometry = edge.edgeGeometry
    const ret = new Set<Shape>()
    const sourceShape = this.portsToShapes.get(edgeGeometry.sourcePort)
    const targetShape = this.portsToShapes.get(edgeGeometry.targetPort)
    if (this.IsAncestor(sourceShape, targetShape)) {
      insertRange(ret, targetShape.Parents)
      ret.add(sourceShape)
      return ret
    }

    if (this.IsAncestor(targetShape, sourceShape)) {
      insertRange(ret, sourceShape.Parents)
      ret.add(targetShape)
      return ret
    }

    if (sourceShape != this.looseRoot) {
      insertRange(ret, sourceShape.Parents)
    }

    if (targetShape != this.looseRoot) {
      insertRange(ret, targetShape.Parents)
    }

    return ret
  }

  *AllPorts(): IterableIterator<Port> {
    for (const edgeGeometry of this.edgeGeometriesEnumeration()) {
      yield edgeGeometry.sourcePort
      yield edgeGeometry.targetPort
    }
  }

  CalculatePortsToShapes() {
    this.portsToShapes = new Map<Port, Shape>()
    for (const shape of this.root.Descendants()) {
      for (const port of shape.Ports) {
        this.portsToShapes.set(port, shape)
      }
    }

    // assign all orphan ports to the root
    for (const port of this.AllPorts()) {
      if (!this.portsToShapes.has(port)) {
        this.root.Ports.add(port)
        this.portsToShapes.set(port, this.root)
      }
    }
  }

  RouteEdgeGeometry(edge: GeomEdge, iRouter: InteractiveEdgeRouter) {
    const edgeGeometry = edge.edgeGeometry
    const addedEdges = new Array<VisibilityEdge>()
    if (!(edgeGeometry.sourcePort instanceof HookUpAnywhereFromInsidePort)) {
      addRange(addedEdges, this.AddVisibilityEdgesFromPort(edgeGeometry.sourcePort))
    }

    if (!(edgeGeometry.targetPort instanceof HookUpAnywhereFromInsidePort)) {
      addRange(addedEdges, this.AddVisibilityEdgesFromPort(edgeGeometry.targetPort))
    }

    const t: {smoothedPolyline: SmoothedPolyline} = {smoothedPolyline: null}
    if (!Point.closeDistEps(edgeGeometry.sourcePort.Location, edgeGeometry.targetPort.Location)) {
      edgeGeometry.curve = iRouter.RouteSplineFromPortToPortWhenTheWholeGraphIsReady(
        edgeGeometry.sourcePort,
        edgeGeometry.targetPort,
        true,
        t,
      )
    } else {
      edgeGeometry.curve = GeomEdge.RouteSelfEdge(
        edgeGeometry.sourcePort.Curve,
        Math.max(this.LoosePadding * 2, edgeGeometry.GetMaxArrowheadLength()),
        t,
      )
    }

    edgeGeometry.smoothedPolyline = t.smoothedPolyline
    if (edgeGeometry.curve == null) {
      throw new Error()
    }

    for (const visibilityEdge of addedEdges) {
      VisibilityGraph.RemoveEdge(visibilityEdge)
    }

    Arrowhead.trimSplineAndCalculateArrowheadsII(
      edgeGeometry,
      edgeGeometry.sourcePort.Curve,
      edgeGeometry.targetPort.Curve,
      edgeGeometry.curve,
      false,
    )
    //   SetTransparency(transparentShapes, false);
  }

  KeepOriginalSpline = false

  ArrowHeadRatio = 0
  LineSweeperPorts: Point[];

  *AddVisibilityEdgesFromPort(port: Port): IterableIterator<VisibilityEdge> {
    let portShape: Shape
    let boundaryCouple: TightLooseCouple
    if (
      port instanceof CurvePort ||
      !(portShape = this.portsToShapes.get(port)) ||
      !(boundaryCouple = this.shapesToTightLooseCouples.get(portShape))
    ) {
      return
    }

    const portLoosePoly = boundaryCouple.LooseShape

    for (const point of portLoosePoly.BoundaryCurve as Polyline) {
      if (this.visGraph.FindEdgePP(port.Location, point) == null) yield this.visGraph.AddEdgePP(port.Location, point)
    }
  }

  MakeTransparentShapesOfEdgeGeometryAndGetTheShapes(edgeGeometry: EdgeGeometry): Array<Shape> {
    // it is OK here to repeat a shape in the returned list
    const sourceShape: Shape = this.portsToShapes.get(edgeGeometry.sourcePort)
    const targetShape: Shape = this.portsToShapes.get(edgeGeometry.targetPort)
    const transparentLooseShapes = new Array<Shape>()
    for (const shape of this.GetTransparentShapes(edgeGeometry.sourcePort, edgeGeometry.targetPort, sourceShape, targetShape)) {
      if (shape != null) {
        transparentLooseShapes.push(this.LooseShapeOfOriginalShape(shape))
      }
    }

    for (const shape of this.portsToEnterableShapes.get(edgeGeometry.sourcePort)) {
      transparentLooseShapes.push(this.LooseShapeOfOriginalShape(shape))
    }

    for (const shape of this.portsToEnterableShapes.get(edgeGeometry.targetPort)) {
      transparentLooseShapes.push(this.LooseShapeOfOriginalShape(shape))
    }

    SplineRouter.SetTransparency(transparentLooseShapes, true)
    return transparentLooseShapes
  }

  LooseShapeOfOriginalShape(s: Shape): Shape {
    if (s == this.root) {
      return this.looseRoot
    }

    return this.shapesToTightLooseCouples.get(s).LooseShape
  }

  LoosePolyOfOriginalShape(s: Shape): Polyline {
    return <Polyline>this.LooseShapeOfOriginalShape(s).BoundaryCurve
  }

  TightPolyOfOriginalShape(s: Shape): Polyline {
    if (s == this.root) {
      return null
    }

    return this.shapesToTightLooseCouples.get(s).TightPolyline
  }

  //     static GetEdgeColor(e: VisibilityEdge, sourcePort: Port, targetPort: Port): string {
  //   if (((sourcePort == null)
  //     || (targetPort == null))) {
  //     return "green";
  //   }

  //   if ((closeDistEps(e.SourcePoint, sourcePort.Location)
  //     || (closeDistEps(e.SourcePoint, targetPort.Location)
  //       || (closeDistEps(e.TargetPoint, sourcePort.Location) || closeDistEps(e.TargetPoint, targetPort.Location))))) {
  //     return "lightgreen";
  //   }

  //   return "green";
  //   // TODO: Warning!!!, inline IF is not supported ?
  //   ((e.IsPassable == null)
  //     || e.IsPassable());
  //   "red";
  // }

  *GetTransparentShapes(sourcePort: Port, targetPort: Port, sourceShape: Shape, targetShape: Shape): IterableIterator<Shape> {
    for (const s of this.ancestorSets.get(sourceShape)) {
      yield s
    }

    for (const s of this.ancestorSets.get(targetShape)) {
      yield s
    }

    const routingOutsideOfSourceBoundary = SplineRouter.EdgesAttachedToPortAvoidTheNode(sourcePort)
    const routingOutsideOfTargetBoundary = SplineRouter.EdgesAttachedToPortAvoidTheNode(targetPort)
    if (!routingOutsideOfSourceBoundary && !routingOutsideOfTargetBoundary) {
      yield
      return sourceShape
      yield
      return targetShape
    } else if (routingOutsideOfSourceBoundary) {
      if (this.IsAncestor(sourceShape, targetShape)) {
        yield
      }

      return sourceShape
    } else {
      if (this.IsAncestor(targetShape, sourceShape)) {
        yield
      }

      return targetShape
    }
  }

  static SetTransparency(shapes: Iterable<Shape>, v: boolean) {
    for (const shape of shapes) {
      shape.IsTransparent = v
    }
  }

  IsAncestor(possibleAncestor: Shape, possiblePredecessor: Shape): boolean {
    let ancestors: Set<Shape>

    return (
      possiblePredecessor != null && (ancestors = this.ancestorSets.get(possiblePredecessor)) != null && ancestors.has(possibleAncestor)
    )
  }

  static CreateLooseObstacleHierarachy(loosePolys: Array<Polyline>): RectangleNode<Polyline, Point> {
    return CreateRectangleNodeOnEnumeration(loosePolys.map((poly) => mkRectangleNode(poly, poly.boundingBox)))
  }

  CreateTightObstacleHierarachy(obstacles: Array<Shape>): RectangleNode<Polyline, Point> {
    const tightPolys = obstacles.map((sh) => this.shapesToTightLooseCouples.get(sh).TightPolyline)
    return CreateRectangleNodeOnEnumeration(
      tightPolys.map((tightPoly) => mkRectangleNode<Polyline, Point>(tightPoly, tightPoly.boundingBox)),
    )
  }

  debcount = 0

  CalculateVisibilityGraph() {
    const setOfPortLocations = this.LineSweeperPorts != null ? PointSet.mk(this.LineSweeperPorts) : new PointSet()
    this.ProcessHookAnyWherePorts(setOfPortLocations)
    this.portRTree = mkRTree(Array.from(setOfPortLocations.values()).map((p) => [Rectangle.rectangleOnPoint(p), p]))
    this.visGraph = new VisibilityGraph()
    this.FillVisibilityGraphUnderShape(this.root)
    /*// debug start
    dumpSvg()

    
    function dumpSvg() {
      SplineRouter.ShowVisGraph(
        'c:/tmp/vg.svg',
        this.visGraph,
        Array.from(new Set<Polyline>(Array.from(this.shapesToTightLooseCouples.values()).map((tl) => <Polyline>tl.LooseShape.BoundaryCurve))),
        Array.from(this.GeomGraph.shallowNodes())
          .map((n) => n.boundaryCurve)
          .concat(Array.from(this.root.Descendants()).map((d) => d.BoundaryCurve)),
        null
      )
      throw new Error()
    }*/
  }
  /*
  static ShowVisGraph(
    fileName: string,
    tmpVisGraph: VisibilityGraph,
    obstacles: Array<Polyline>,
    greenCurves: Array<ICurve> = null,
    redCurves: Array<ICurve> = null,
  ) {
    const l = Array.from(tmpVisGraph.Edges).map((e) =>
      DebugCurve.mkDebugCurveTWCI(
        100,
        1,
        e.IsPassable != null && e.IsPassable() ? 'green' : 'black',
        LineSegment.mkPP(e.SourcePoint, e.TargetPoint),
      ),
    )
    if (obstacles != null) {
      for (const p of obstacles) l.push(DebugCurve.mkDebugCurveTWCI(100, 1, 'brown', p))
    }

    if (greenCurves != null) {
      for (const p of greenCurves) {
        l.push(DebugCurve.mkDebugCurveTWCI(100, 10, 'navy', p))
      }
    }

    if (redCurves != null) {
      for (const p of redCurves) l.push(DebugCurve.mkDebugCurveTWCI(100, 10, 'red', p))
    }

    SvgDebugWriter.dumpDebugCurves(fileName, l)
  }*/
  private ProcessHookAnyWherePorts(setOfPortLocations: PointSet) {
    for (const edgeGeometry of this.edgeGeometriesEnumeration()) {
      if (!(edgeGeometry.sourcePort instanceof HookUpAnywhereFromInsidePort || edgeGeometry.sourcePort instanceof ClusterBoundaryPort)) {
        setOfPortLocations.add(edgeGeometry.sourcePort.Location)
      }

      if (!(edgeGeometry.targetPort instanceof HookUpAnywhereFromInsidePort || edgeGeometry.targetPort instanceof ClusterBoundaryPort)) {
        setOfPortLocations.add(edgeGeometry.targetPort.Location)
      }
    }
  }

  //  this function might change the shape's loose polylines by inserting new points
  FillVisibilityGraphUnderShape(shape: Shape) {
    // going depth first
    const children = shape.Children
    for (const child of children) {
      this.FillVisibilityGraphUnderShape(child)
    }

    const tightLooseCouple: TightLooseCouple = this.shapesToTightLooseCouples.get(shape)
    const looseBoundary: Polyline = tightLooseCouple ? <Polyline>tightLooseCouple.LooseShape.BoundaryCurve : null
    const looseShape: Shape = tightLooseCouple ? tightLooseCouple.LooseShape : this.looseRoot
    const obstacles = new Set<Polyline>(looseShape.Children.map((c) => <Polyline>c.BoundaryCurve))
    const portLocations = this.RemoveInsidePortsAndSplitBoundaryIfNeeded(looseBoundary)
    // this run will split the polyline enough to route later from the inner ports
    let tmpVisGraph = new VisibilityGraph()
    let coneSpanner = ConeSpanner.mk([], tmpVisGraph, this.coneAngle, portLocations, looseBoundary)
    coneSpanner.run()
    //SplineRouter.ShowVisGraph('c:/tmp/vg' + this.debcount++ + '.svg', tmpVisGraph, Array.from(obstacles))
    // now run the spanner again to create the correct visibility graph around the inner obstacles
    tmpVisGraph = new VisibilityGraph()
    coneSpanner = ConeSpanner.mk(Array.from(obstacles), tmpVisGraph, this.coneAngle, portLocations, looseBoundary)
    coneSpanner.run()
    //SplineRouter.ShowVisGraph('c:/tmp/vg' + this.debcount++ + '.svg', tmpVisGraph, Array.from(obstacles))

    this.ProgressStep()
    for (const edge of tmpVisGraph.Edges) {
      this.TryToCreateNewEdgeAndSetIsPassable(edge, looseShape)
    }

    this.AddBoundaryEdgesToVisGraph(looseBoundary)
    //             if (obstacles.Count > 0)
    //                 SplineRouter.ShowVisGraph(tmpVisGraph, obstacles, null, null);
  }

  bidirectional = false
  //  If set to true then a smaller visibility graph is created.
  //  An edge is added to the visibility graph only if it is found at least twice:
  //  once sweeping with a direction d and the second time with -d
  get Bidirectional(): boolean {
    return this.bidirectional
  }
  set Bidirectional(value: boolean) {
    this.bidirectional = value
  }

  //  #if TEST_MSAGL
  //      [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
  //      static internal void ShowVisGraph(VisibilityGraph tmpVisGraph, Iterable<Polyline> obstacles, Iterable<ICurve> greenCurves, Iterable<ICurve> redCurves) {
  //        var l = new Array<DebugCurve>(tmpVisGraph.Edges.Select(e => new DebugCurve(100, 1,
  //            e.IsPassable != null && e.IsPassable() ? "green" : "black"
  //            , new LineSegment(e.SourcePoint, e.TargetPoint))));
  //        if (obstacles != null)
  //          l.AddRange(obstacles.Select(p => new DebugCurve(100, 1, "brown", p)));
  //        if (greenCurves != null)
  //          l.AddRange(greenCurves.Select(p => new DebugCurve(100, 10, "navy", p)));
  //        if (redCurves != null)
  //          l.AddRange(redCurves.Select(p => new DebugCurve(100, 10, "red", p)));
  //        LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(l);
  //      }
  //  #endif
  TryToCreateNewEdgeAndSetIsPassable(edge: VisibilityEdge, looseShape: Shape) {
    let e = this.visGraph.FindEdgePP(edge.SourcePoint, edge.TargetPoint)
    if (e != null) {
      return
    }

    e = this.visGraph.AddEdgePP(edge.SourcePoint, edge.TargetPoint)
    if (looseShape != null) e.IsPassable = () => looseShape.IsTransparent
  }

  AddBoundaryEdgesToVisGraph(boundary: Polyline) {
    if (boundary == null) {
      return
    }
    let pn: PolylinePoint
    for (let p = boundary.startPoint; true; p = pn) {
      pn = p.nextOnPolyline
      this.visGraph.AddEdgePP(p.point, pn.point)
      if (pn == boundary.startPoint) {
        break
      }
    }
  }

  RemoveInsidePortsAndSplitBoundaryIfNeeded(boundary: Polyline): PointSet {
    const ret = new PointSet()
    if (boundary == null) {
      for (const point of this.portRTree.GetAllLeaves()) {
        ret.add(point)
      }

      this.portRTree.Clear()
      return ret
    }

    const boundaryBox: Rectangle = boundary.boundingBox
    const portLocationsInQuestion = this.portRTree.GetAllIntersecting(boundaryBox)

    for (const point of portLocationsInQuestion) {
      switch (Curve.PointRelativeToCurveLocation(point, boundary)) {
        case PointLocation.Inside:
          ret.add(point)
          this.portLocationsToLoosePolylines.set(point, boundary)
          this.portRTree.Remove(Rectangle.rectangleOnPoint(point), point)
          break
        case PointLocation.Boundary:
          this.portRTree.Remove(Rectangle.rectangleOnPoint(point), point)
          this.portLocationsToLoosePolylines.set(point, boundary)
          const polylinePoint: PolylinePoint = SplineRouter.FindPointOnPolylineToInsertAfter(boundary, point)
          if (polylinePoint != null) {
            LineSweeper.InsertPointIntoPolylineAfter(boundary, polylinePoint, point)
          } else {
            throw new Error()
          }

          break
      }
    }

    return ret
  }

  static FindPointOnPolylineToInsertAfter(boundary: Polyline, point: Point): PolylinePoint {
    for (let p: PolylinePoint = boundary.startPoint; ; ) {
      const pn: PolylinePoint = p.nextOnPolyline
      if (Point.closeDistEps(point, p.point) || Point.closeDistEps(point, pn.point)) {
        return null
      }

      // the point is already inside
      const dist = Point.distToLineSegment(point, p.point, pn.point).dist
      if (closeDistEps(dist, 0)) {
        return p
      }

      p = pn
      if (p == boundary.startPoint) {
        throw new Error()
      }
    }
  }

  //  creates a root; a shape with BoundaryCurve set to null
  GetOrCreateRoot() {
    if (this.rootShapes.length == 1) {
      const r: Shape = this.rootShapes[0]
      if (r.BoundaryCurve == null) {
        this.root = r
        return
      }
    }

    this.rootWasCreated = true
    this.root = new Shape(null)
    for (const rootShape of this.rootShapes) {
      this.root.AddChild(rootShape)
    }
  }

  RemoveRoot() {
    if (this.rootWasCreated) {
      for (const rootShape of this.rootShapes) {
        rootShape.RemoveParent(this.root)
      }
    }
  }

  //  #if TEST_MSAGL
  //      // ReSharper disable UnusedMember.Local
  //      [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
  //      static void Show(
  //          Iterable<EdgeGeometry> edgeGeometries, Iterable<Shape> listOfShapes) {
  //        // ReSharper restore UnusedMember.Local
  //        var r = new Random(1);
  //        LayoutAlgorithmSettings.ShowDebugCurvesEnumeration(
  //            listOfShapes.Select(s => s.BoundaryCurve).Select(
  //                c => new DebugCurve(50, 1, DebugCurve.Colors[r.Next(DebugCurve.Colors.Length - 1)], c)).Concat(
  //                    edgeGeometries.Select(e => new DebugCurve(100, 1, "red", e.Curve))));
  //      }
  //  #endif
  static GetAncestorSetsMap(shapes: Array<Shape>): Map<Shape, Set<Shape>> {
    const ancSets = new Map<Shape, Set<Shape>>()
    for (const child of shapes.filter((child) => !ancSets.has(child))) {
      ancSets.set(child, SplineRouter.GetAncestorSet(child, ancSets))
    }
    return ancSets
  }

  static GetAncestorSet(child: Shape, ancSets: Map<Shape, Set<Shape>>): Set<Shape> {
    const ret = new Set<Shape>(child.Parents)
    for (const parent of child.Parents) {
      let addition = ancSets.get(parent)
      if (!addition) {
        ancSets.set(parent, (addition = SplineRouter.GetAncestorSet(parent, ancSets)))
      }
      for (const t of addition) ret.add(t)
    }
    return ret
  }

  static CreatePortsIfNeeded(edges: GeomEdge[]) {
    for (const edge of edges) {
      if (edge.sourcePort == null) {
        const ed = edge
        new RelativeFloatingPort(
          () => ed.source.boundaryCurve,
          () => ed.source.center,
          new Point(0, 0),
        )
      }

      if (edge.targetPort == null) {
        const ed = edge
        new RelativeFloatingPort(
          () => ed.target.boundaryCurve,
          () => ed.target.center,
          new Point(0, 0),
        )
      }
    }
  }

  //   computes loosePadding for spline routing obstacles from node separation and EdgePadding.
  static ComputeLooseSplinePadding(nodeSeparation: number, edgePadding: number): number {
    Assert.assert(edgePadding > 0, 'require EdgePadding > 0')
    const twicePadding: number = 2 * edgePadding
    Assert.assert(nodeSeparation > twicePadding, 'require OverlapSeparation > 2*EdgePadding')
    //  the 8 divisor is just to guarantee the final postcondition
    const loosePadding: number = (nodeSeparation - twicePadding) / 8
    Assert.assert(loosePadding > 0, 'require LoosePadding > 0')
    Assert.assert(twicePadding + 2 * loosePadding < nodeSeparation, 'EdgePadding too big!')
    return loosePadding
  }
}
export function routeSplines(gg: GeomGraph, edgesToRoute: GeomEdge[], cancelToken: CancelToken): void {
  const ers = gg.layoutSettings ? gg.layoutSettings.edgeRoutingSettings : getEdgeRoutingSettingsFromAncestors(gg)
  //const ers = gg.layoutSettings.edgeRoutingSettings
  const sr = new SplineRouter(gg, edgesToRoute, ers.Padding, ers.PolylinePadding, ers.coneAngle, ers.BundlingSettings, cancelToken)
  sr.run()
}
