package routing

import (
	
	

	
	
)

// SwapAllEdgePorts applies port swapping to every node under one atomic
// route-stage budget and rollback boundary.
func ( context.Context,  *layoutgraph.Graph) error {
	return swapAllEdgePortsWithWorkLimit(, , maxRouteStageWorkUnits)
}

// swapAllEdgePortsWithWorkLimit is SwapAllEdgePorts with an explicit budget
// for deterministic resource-boundary tests inside TALA.
func swapAllEdgePortsWithWorkLimit( context.Context,  *layoutgraph.Graph,  uint64) error {
	return runAtomicRouteStage(, "SwapEdgePorts", , nil, , func( *routeWorkGuard) error {
		for ,  := range .Nodes {
			if  := .step();  != nil {
				return 
			}
			if ,  := swapEdgePortsGuarded(, );  != nil {
				return 
			}
		}
		return nil
	})
}

func swapEdgePortsGuarded( *layoutgraph.Node,  *routeWorkGuard) (bool, error) {
	 := make(map[geo.Orientation][]*layoutgraph.Edge)
	 := make(map[geo.Point][]*layoutgraph.Edge)
	for ,  := range .Edges {
		if  := .step();  != nil {
			return false, 
		}
		// TODO: remove `len(e.Points) < 3` and handle straight edges
		if .IsLoop() || len(.Points) < 3 || .HasTableColumn() {
			continue
		}
		if .HasLargeArrowheadLabel() {
			continue
		}
		, ,  := pointsAt(, )
		var  geo.Orientation
		if .X < .X {
			 = geo.Right
		} else if .X > .X {
			 = geo.Left
		} else if .Y < .Y {
			 = geo.Bottom
		} else {
			 = geo.Top
		}
		[] = append([], )
		[*] = append([*], )
	}

	,  := swapEdgesOnSameSide(, , , )
	if  != nil {
		return false, 
	}
	,  := swapEdgesOnAdjacentSides(, , , )
	if  != nil {
		return false, 
	}

	return  || , nil
}

// swapEdgesOnSameSide swaps edges on the same node side if they can be swapped
// .       input                       output
// .       ┌──────────                ┌────────────
// .    ┌──┼─────────────             |  ┌─────────────
// . ┌──┴──┴──────┐                ┌──┴──┴──────┐
// . │            ├───┐            │            ├───────┐
// . │    n       ├───┼───┐        │     n      ├───┐   │
// . └────────────┘   │   │        └────────────┘   │   │
func swapEdgesOnSameSide( *layoutgraph.Node,  map[geo.Orientation][]*layoutgraph.Edge,  map[geo.Point][]*layoutgraph.Edge,  *routeWorkGuard) (bool, error) {
	 := false
	for ,  := range  {
		if  := .step();  != nil {
			return false, 
		}
		// sorts by orientation
		if .IsVertical() {
			if  := stableSortRouteValues(, func(,  *layoutgraph.Edge) bool {
				, ,  := pointsAt(, )
				, ,  := pointsAt(, )
				if .X == .X {
					return .X < .X
				}
				return .X < .X
			}, );  != nil {
				return false, 
			}
		} else {
			if  := stableSortRouteValues(, func(,  *layoutgraph.Edge) bool {
				, ,  := pointsAt(, )
				, ,  := pointsAt(, )
				if .Y == .Y {
					return .Y < .Y
				}
				return .Y < .Y
			}, );  != nil {
				return false, 
			}
		}
		for  := 0;  < len()-1; ++ {
			if  := .step();  != nil {
				return false, 
			}
			if edgesIntersect([], [+1], , ) && canSwapPorts([], [+1], , ) {
				, ,  := pointsAt([], )
				, ,  := pointsAt([+1], )
				swapCoordinate(, , )
				swapCoordinate(, , )
				,  := doesNotPassThroughNodesGuarded(.Graph, []*layoutgraph.Node{}, , , , , )
				if  != nil {
					return false, 
				}
				if  {
					[], [+1] = [+1], []
					 = true
				} else {
					swapCoordinate(, , )
					swapCoordinate(, , )
				}
			}
		}
	}
	return , nil
}

// pointsAt returns the points composing the two segments starting at n.
// .   segment 1    ┌──────┐
// . ┌──────────────┤   n  │
// . │              └──────┘
// . │ segment 2
func pointsAt( *layoutgraph.Edge,  *layoutgraph.Node) (*geo.Point, *geo.Point, *geo.Point) {
	if .From ==  {
		return .Points[0], .Points[1], .Points[2]
	}
	return .Points[len(.Points)-1], .Points[len(.Points)-2], .Points[len(.Points)-3]
}

// swapCoordinate swaps the coordinate (x/y) opposite to the given orienation, e.g., if `o` is vertical, swaps x
func swapCoordinate(,  *geo.Point,  geo.Orientation) {
	if .IsVertical() {
		.X, .X = .X, .X
	} else {
		.Y, .Y = .Y, .Y
	}
}

func doesNotPassThroughNodesGuarded( *layoutgraph.Graph,  []*layoutgraph.Node, , , ,  *geo.Point,  *routeWorkGuard) (bool, error) {
:
	for ,  := range .Nodes {
		if  := .step();  != nil {
			return false, 
		}
		for ,  := range  {
			if  := .step();  != nil {
				return false, 
			}
			if  ==  || .IsDescendantOf() {
				continue 
			}
		}
		 := 2 * pathNodeProximityFloor
		 := geo.NewBox(
			geo.NewPoint(.Box.TopLeft.X-, .Box.TopLeft.Y-),
			.Box.Width+*2,
			.Box.Height+*2,
		)
		if segmentIntersectsBox(, , ) ||
			segmentIntersectsBox(, , ) {
			return false, nil
		}
	}
	return true, nil
}

// edgesIntersect checks if the given edges intersect at `n` like in the examples below
// .              ┌─────┐
// .        ┌─e1──┤  n  │
// . ┌──e2──┼─────┤     │
// . │      │     └─────┘
//
// or
//
// . │   │   ┌─────┐
// . └e2─┼───┤     │
// .     └─e1┤  n  │
// .         └─────┘
// the same patterns repeat to the four shape sides
func edgesIntersect(,  *layoutgraph.Edge,  *layoutgraph.Node,  geo.Orientation) bool {
	, ,  := pointsAt(, )
	, ,  := pointsAt(, )
	switch  {
	case geo.Left:
		// .                  ┌──────┐
		// .        p2┌───────┤p1    │
		// . q2       │       │      │
		// . ┌────────┼───────┤q1    │
		// . │        │       └──────┘
		// . │        │
		// . │q3      │p3
		 := .X < .X && .Y > .Y
		 := .X < .X && .Y < .Y
		return  || 
	case geo.Bottom:
		 := .Y > .Y && .X < .X
		 := .Y > .Y && .X > .X
		return  || 
	case geo.Top:
		 := .Y < .Y && .X < .X
		 := .Y < .Y && .X > .X
		return  || 
	case geo.Right:
		 := .X > .X && .Y < .Y
		 := .X > .X && .Y > .Y
		return  || 
	}
	return false
}

// canSwapPorts checks if the ports of the given edges can be swapped
// ports can be swapped if they have only 1 edge each, or if they are shared, if the edges have the same arrowhead
func canSwapPorts(,  *layoutgraph.Edge,  *layoutgraph.Node,  map[geo.Point][]*layoutgraph.Edge) bool {
	, ,  := pointsAt(, )
	, ,  := pointsAt(, )

	if len([*]) == 1 && len([*]) == 1 {
		// only one edge at each port, so arrowheads don't matter
		return true
	}

	// check if they have the same arrowheads
	 := .ArrowheadTo()
	 := .ArrowheadTo()
	return  == 
}

func refineEdgeGuarded( *layoutgraph.Graph,  *layoutgraph.Edge,  *routeWorkGuard) (bool, error) {
	if  := .step();  != nil {
		return false, 
	}
	if .IsLoop() || len(.Points) <= 2 || .IsBetweenTableColumns() || .IsTreeEdge() {
		return false, nil
	}
	,  := occupiedPortsGuarded(.From, )
	if  != nil {
		return false, 
	}
	,  := occupiedPortsGuarded(.To, )
	if  != nil {
		return false, 
	}
	for  := range  {
		if  := .step();  != nil {
			return false, 
		}
		[] = struct{}{}
	}

	if isEdgeSShaped() {
		return makeStraightLineGuarded(, , , )
		// TODO: some ideas to improve later on
		// - try to make L-shaped too
		// - if L-shaped and straight line failed, try to move or grow the node a bit
		//   consider that only nodes without FixedTopLeft and fixed size can have these transforms
		// For example, this could be a straight line if the node below moved a bit or if its size increased a bit
		// Note that this should be quite safe to extend/move the node in the direction of the bend as there should be
		// no other nodes there, just need to consider moving other edges on that side so that ports are properly adjusted
		//      ┌────────┐
		//      │        │
		//      └───────┬┘
		// ┌─────────┐  │
		// └─────────┘  │
		//   ┌───────┐  │
		//   │       ├──┘
		//   └───────┘
	} else if isEdgeLShaped() {
		return makeStraightLineGuarded(, , , )
		// TODO: see the straight line strategy above that could also be applied here
	} else if isEdgeUShaped() {
		// TODO: specific improvements for U-shaped routes
		return false, nil
	}

	 := false
	if isSShaped(.Points[:4]) {
		// the first 3 segments are S shaped, try to turn them into L shaped
		//             ┌────────        ┌─────────────
		//     ┌───────┘                │
		// ┌───┴──┐                 ┌───┴──┐
		// │      │                 │      │
		// │      │                 │      │
		// └──────┘                 └──────┘
		 := sToLShapedBendPoint(.Points[:4])
		,  := doesNotPassThroughNodesGuarded(, []*layoutgraph.Node{.From, .To}, .Points[0], , , .Points[3], )
		if  != nil {
			return false, 
		}
		if  {
			if  := removeEdgePointsGuarded(, , 1, 2, 3);  != nil {
				return false, 
			}
			if  := insertEdgePointGuarded(, , 1, );  != nil {
				return false, 
			}
			 = true
		}
	}

	// TODO: try improvements along the edge
	//      input         possible output
	// │                       │
	// │                       │
	// └────┐                  │
	//      │                  │
	//      │                  │
	//      └───────           └────────
	//
	//     ┌────────           ┌────────
	//     │                   │
	//     │                   │
	//     └─────┐             │
	//           │             │
	//           │             │
	// Exceptions to look for:
	// - if the edge new edge doesn't go through other nodes
	// - if the new edge doesn't overlap, or is too close to other segments

	return , nil
}

func occupiedPortsGuarded( *layoutgraph.Node,  *routeWorkGuard) (map[geo.Point]struct{}, error) {
	 := make(map[geo.Point]struct{}, len(.Edges))
	for ,  := range .Edges {
		if  := .step();  != nil {
			return nil, 
		}
		if .From ==  {
			[*.SourcePort()] = struct{}{}
		} else {
			[*.TargetPort()] = struct{}{}
		}
	}
	return , nil
}

func makeStraightLineGuarded( *layoutgraph.Graph,  *layoutgraph.Edge,  map[geo.Point]struct{},  *routeWorkGuard) (bool, error) {
	 := .SourcePort()
	 := .TargetPort()
	 := pathNodeProximityFloor
	, ,  := tunnelRangesBetweenGuarded(, .From, .To, false, )
	if  != nil {
		return false, 
	}
	for ,  := range  {
		if  := .step();  != nil {
			return false, 
		}
		// avoid having edges alined with the node boundaries
		for  := .start + ;  <= .end-;  +=  {
			if  := .step();  != nil {
				return false, 
			}
			var ,  *geo.Point
			if  {
				 = geo.NewPoint(.X, )
				 = geo.NewPoint(.X, )
			} else {
				 = geo.NewPoint(, .Y)
				 = geo.NewPoint(, .Y)
			}
			if ,  := [*];  {
				continue
			}
			if ,  := [*];  {
				continue
			}
			// the ports are free and tunnels ensure there's no node in between
			// so we can make this edge a straight line
			.Points = []*geo.Point{, }
			return true, nil
		}
	}
	return false, nil
}

// sToLShapedBendPoint returns the bend point that transforms an S-shaped route
// into an L-shaped route.
// .             ┌────────        ┌─────────────
// .     ┌───────┘                │
// . ┌───┴──┐                 ┌───┴──┐
// . │      │                 │      │
// . │      │                 │      │
// . └──────┘                 └──────┘
func sToLShapedBendPoint( []*geo.Point) *geo.Point {
	if [0].X == [1].X {
		// vertical
		return geo.NewPoint([1].X, [3].Y)
	}
	return geo.NewPoint([3].X, [1].Y)
}

func isEdgeUShaped( *layoutgraph.Edge) bool {
	return isUShaped(.Points)
}

func isEdgeSShaped( *layoutgraph.Edge) bool {
	return isSShaped(.Points)
}

func isSShaped( []*geo.Point) bool {
	return len() == 4 && !isUShaped()
}

func isUShaped( []*geo.Point) bool {
	if len() != 4 {
		return false
	}

	if [0].X == [1].X {
		// 0        3
		// │        │
		// │        │
		// 1────────2
		 := geo.Sign([0].Y-[1].Y) == geo.Sign([3].Y-[2].Y)
		 := [1].Y == [2].Y
		return  && 
	}

	// 0─────1
	//       │
	//       │
	// 3─────2
	 := geo.Sign([0].X-[1].X) == geo.Sign([3].X-[2].X)
	 := [1].X == [2].X
	return  && 
}

func isEdgeLShaped( *layoutgraph.Edge) bool {
	return isLShaped(.Points)
}

func isLShaped( []*geo.Point) bool {
	if len() != 3 {
		return false
	}

	 := [0].X == [1].X
	 := [1].Y == [2].Y
	// either one of these
	// .               0                          0─────────1
	// .               │                                    │
	// .               │                                    │
	// .               │                                    │
	// .               │                                    2
	// .               1─────2
	return ( && ) || (! && !)
}

// swapPoints swaps both X and Y of the given points
func swapPoints(,  *geo.Point) {
	.X, .X = .X, .X
	.Y, .Y = .Y, .Y
}

func copyEdgePointsGuarded( *layoutgraph.Edge,  *routeWorkGuard) ([]*geo.Point, error) {
	 := make([]*geo.Point, 0, len(.Points))
	for ,  := range .Points {
		if  := .step();  != nil {
			return nil, 
		}
		 = append(, .Copy())
	}
	return , nil
}

func insertEdgePointGuarded( *layoutgraph.Edge,  *geo.Point,  int,  *routeWorkGuard) error {
	if  < 0 ||  > len(.Points) {
		return fmt.Errorf("TALA %s route insertion index %d is out of bounds", .location, )
	}
	 := make([]*geo.Point, 0, len(.Points)+1)
	for ,  := range .Points {
		if  := .step();  != nil {
			return 
		}
		if  ==  {
			 = append(, )
		}
		 = append(, )
	}
	if  == len(.Points) {
		 = append(, )
	}
	.Points = 
	return nil
}

func removeEdgePointsGuarded( *layoutgraph.Edge,  *routeWorkGuard,  ...int) error {
	 := len(.Points) - len()
	if  < 0 {
		return fmt.Errorf("TALA %s route removal count exceeds route length", .location)
	}
	 := make([]*geo.Point, 0, )
:
	for ,  := range .Points {
		if  := .step();  != nil {
			return 
		}
		for ,  := range  {
			if  := .step();  != nil {
				return 
			}
			if  ==  {
				continue 
			}
		}
		 = append(, )
	}
	.Points = 
	return nil
}

// swapEdgesOnAdjacentSides swaps edges on adjacent sides (top/bottom with left/right) if they can be swapped
// .       input                    output
// .           │                      │
// .    ┌──────┼──────          ┌─────┘
// .    │      │                │      ┌───────
// . ┌──┴───┐  │             ┌──┴───┐  │
// . │  n   ├──┘             │  n   ├──┘
// . └──────┘                └──────┘
func swapEdgesOnAdjacentSides( *layoutgraph.Node,  map[geo.Orientation][]*layoutgraph.Edge,  map[geo.Point][]*layoutgraph.Edge,  *routeWorkGuard) (bool, error) {
	 := false
	 := append([geo.Top], [geo.Bottom]...)
	 := append([geo.Left], [geo.Right]...)
	for ,  := range  {
		if  := .step();  != nil {
			return false, 
		}
		for ,  := range  {
			if  := .step();  != nil {
				return false, 
			}
			// we need to check the edge size here in case we were able to improve it
			if len(.Points) <= 2 {
				break
			}
			if len(.Points) <= 2 {
				continue
			}
			if !canSwapPorts(, , , ) {
				continue
			}
			// .          b3
			// .   a2      │
			// .    ┌──────┼──── a3
			// .    │      │
			// . ┌a1┴───┐  │
			// . │  n   b1─┘ b2
			// . └──────┘
			, ,  := pointsAt(, )
			, ,  := pointsAt(, )

			 := geo.IntersectionPoint(, , , )
			if  == nil {
				continue
			}

			,  := copyEdgePointsGuarded(, )
			if  != nil {
				return false, 
			}
			,  := copyEdgePointsGuarded(, )
			if  != nil {
				return false, 
			}

			// swap the first segment of the edges
			swapPoints(, )
			swapPoints(, )

			 := pathNodeProximityFloor / 2.
			// insert the intersection
			 := .Copy()
			if .From ==  {
				if  := insertEdgePointGuarded(, , 2, );  != nil {
					return false, 
				}
			} else {
				if  := insertEdgePointGuarded(, , len(.Points)-2, );  != nil {
					return false, 
				}
			}
			// shifts the intersection segment a bit so that it doesn't look like a crossing anymore
			if .X == .X {
				.X += 
				.X += 
			} else {
				.Y -= 
				.Y -= 
			}

			// same as above, but for the other edge
			 := .Copy()
			if .From ==  {
				if  := insertEdgePointGuarded(, , 2, );  != nil {
					return false, 
				}
			} else {
				if  := insertEdgePointGuarded(, , len(.Points)-2, );  != nil {
					return false, 
				}
			}
			if .X == .X {
				.X += 
				.X += 
			} else {
				.Y -= 
				.Y -= 
			}

			,  := refineEdgeGuarded(.Graph, , )
			if  != nil {
				return false, 
			}
			,  := refineEdgeGuarded(.Graph, , )
			if  != nil {
				return false, 
			}
			if ! && ! {
				// if none of the edges improved after splitting at the intersection
				// return back as we got 2 extra bends in the place of a single intersection
				.Points = 
				.Points = 
			} else {
				 = true
			}
		}
	}
	return , nil
}