package placement

import (
	
	

	
	

	
)

// alignAxes tries to align connected nodes that currently differ on both axes.
// Below, a and b can be lined up. If b has more connections, consider its subgraph.
// .
// . ┌──────┐
// . │      │
// . │ a    │
// . │      │
// . └───┬──┘
// .     │
// .     │           ┌─────┐       ┌─────┐
// .     │           │     │       │     │
// .     └───────────►  b  ├───────►     │
// .                 └─────┘       └─────┘
func alignAxes( context.Context,  *layoutgraph.Graph,  *layoutgraph.Transaction) (bool, error) {
	if  := .Err();  != nil {
		return false, 
	}
	 := false

	 := .FixedNodes()

	// Note: the edge from rootSentinel to tree root is not excluded here since if the root moves,
	// all connected nodes are moved so it should move the whole tree with it which would be okay
	 := .TreeEdgeMap()
	.AddIsolatedTreeEdges()
	// Tree nodes may be connected nodes when they are containers, so exclude them from the connected-node search.
	 := make([]*layoutgraph.Node, 0, len(.NodeToTree)+len()+1)
	for ,  := range .Nodes {
		if ,  := .NodeToTree[];  {
			 = append(, )
		}
	}
	 = append(, ...)
	 = append(, nil) // last node is replaced with e.To/e.From below

	var  []*layoutgraph.EdgeAbduction
	for ,  := range .Sequences {
		 = append(, .EdgeAbductions...)
	}

	// need to compute it here because as we align a single edge, it may end up aligning other edges
	// and it would affect the actual count on the fly
	 := false
	,  := placementcost.EdgeLength(, , placementcost.EdgeLengthOptions{EdgeAbductions: , IncludeNodeSizes: true, EnforceMinimumGap: false, PenalizeDirection: true})
	if  != nil {
		return false, 
	}
	 := 0
	for ,  := range .Edges {
		if .HasTableColumn() {
			 = true
			if .IsAxisAligned() {
				++
			}
		}
	}

	for ,  := range .Edges {
		if ,  := [];  {
			continue
		}
		if .IsAxisAligned() {
			continue
		}
		if .From.Hierarchy != nil || .To.Hierarchy != nil {
			continue
		}
		.Clear()
		if  := .UpdateState();  != nil {
			return false, 
		}

		,  := alignmentDeltas(, )

		var ,  float64
		var  []*layoutgraph.Node
		,  := placementcost.EdgeLength(, , placementcost.EdgeLengthOptions{EdgeAbductions: , IncludeNodeSizes: true, EnforceMinimumGap: false, PenalizeDirection: true})
		if  != nil {
			return false, 
		}
		,  := placementcost.ContainerAlignmentCost(, )
		if  != nil {
			return false, 
		}
		 += 
		// Try to move e.From
		{
			[len()-1] = .To
			 := .From.ConnectedNodeSet(, )
			, , ,  := tryMove(, , , , , , , )
			if  != nil {
				return false, 
			}
			if geo.PrecisionCompare(, , geo.PRECISION) < 0 {
				 = 
				,  = , 
				 = 
			} else if .HasTableColumn() && geo.PrecisionCompare(, , geo.PRECISION) == 0 {
				// aligning ports in tables might be a sort of micromovement that doesn't change that much and it results in
				// the new length being equal to the old one at our precision level, but still, the aligned ports look better
				 = 
				,  = , 
				 = 
			}
		}

		// Try to move e.To
		{
			[len()-1] = .From
			 := .To.ConnectedNodeSet(, )
			, , ,  := tryMove(, , , , , , -, -)
			if  != nil {
				return false, 
			}
			if geo.PrecisionCompare(, , geo.PRECISION) < 0 {
				,  = , 
				 = 
			} else if .HasTableColumn() && geo.PrecisionCompare(, , geo.PRECISION) == 0 {
				,  = , 
				 = 
			}
		}

		if len() > 0 {
			.AddOp(func() error {
				 = true
				for ,  := range  {
					.Translate(, )
				}
				return nil
			})
			if  := .Commit();  != nil {
				return false, 
			}
		}
	}

	if  &&  {
		// column alignment can cause small changes in edge length that that "stay the same"
		// at our precision comparison. To handle it properly, we also count the number of
		// aligned edges
		 := 0
		for ,  := range .Edges {
			if .HasTableColumn() && .IsAxisAligned() {
				++
			}
		}
		// Without more aligned columns, accept only a strict edge-length improvement.
		if  <=  {
			,  := placementcost.EdgeLength(, , placementcost.EdgeLengthOptions{EdgeAbductions: , IncludeNodeSizes: true, EnforceMinimumGap: false, PenalizeDirection: true})
			if  != nil {
				return false, 
			}
			,  := placementcost.ContainerAlignmentCost(, )
			if  != nil {
				return false, 
			}
			 += 
			 = geo.PrecisionCompare(, , geo.PRECISION) == -1
		}
	}

	return , nil
}

// alignmentDeltas computes the X and Y deltas required to center-align the connected nodes.
// Exceptions:
// - for edges between table columns, return the delta to align the columns
// - for sequences, return the delta to align with the sequence step
func alignmentDeltas( *layoutgraph.Edge,  *layoutgraph.Graph) (float64, float64) {
	if .HasTableColumn() {
		, , , ,  := .FacingTablePortValues(nil, nil)
		if  &&  {
			return 0, .Y - .Y
		} else if  {
			return 0, (.To.TopLeft.Y + .To.Height/2) - .Y
		} else if  {
			return 0, .Y - (.From.TopLeft.Y + .From.Height/2)
		} else {
			// Align tables to the left to avoid issues like the one below
			// that can happen if we center align the tables
			//              ┌───────────────────────┐
			//              │                       │
			//              │                       ├─────┐
			//              └───────────────────────┘     │
			//                                  ┌─────────┘
			// ┌────────┐          ┌────────┐   │      ┌────────┐
			// │        ├──────────┤        ├───┘      │        │
			// │        │          │        ├──────────┤        │
			// └────────┘          └────────┘          └────────┘
			return .To.TopLeft.X - .From.TopLeft.X, 0
		}
	}
	 := .From
	if ,  := .Sequences[];  {
		 = .AbductedNodeByEdge()
	}
	 := .To
	if ,  := .Sequences[];  {
		 = .AbductedNodeByEdge()
	}

	 := math.Round((.TopLeft.Y + .Height/2) - (.TopLeft.Y + .Height/2))
	 := math.Round((.TopLeft.X + .Width/2) - (.TopLeft.X + .Width/2))

	if .From.Orientation(.To).IsHorizontal() {
		 = 0
	} else if .From.Orientation(.To).IsVertical() {
		 = 0
	} else {
		,  := .From.OrthogonalDistanceTo(.To)
		if  < layoutgraph.ConnectedNodeGap {
			 = 0
		}
		if  < layoutgraph.ConnectedNodeGap {
			 = 0
		}
	}
	return , 
}

func tryMove( context.Context,  *layoutgraph.Transaction,  *layoutgraph.Graph,  *layoutgraph.Edge,  []*layoutgraph.Node,  []*layoutgraph.EdgeAbduction, ,  float64) (, ,  float64,  error) {
	 := math.Inf(1)
	var  geo.Point
	 := make([]*geo.Point, 0, 2)

	if geo.PrecisionCompare(, 0.0, geo.PRECISION) != 0 {
		// e.From tries to slide up or down to e.To
		 = append(, geo.NewPoint(0, ))
	}

	if geo.PrecisionCompare(, 0.0, geo.PRECISION) != 0 {
		// e.From tries to slide left or right to e.To
		 = append(, geo.NewPoint(, 0))
	}

	for ,  := range  {
		.AddOp(func() error {
			if !attemptShift(, , , .X, .Y) {
				return layoutgraph.ErrInvalidCandidate
			}
			return nil
		})

		if  := .Commit();  == nil {
			,  := placementcost.EdgeLength(, , placementcost.EdgeLengthOptions{EdgeAbductions: , IncludeNodeSizes: true, EnforceMinimumGap: false, PenalizeDirection: true})
			if  != nil {
				.Rollback()
				.Clear()
				return 0, 0, 0, 
			}
			,  := placementcost.ContainerAlignmentCost(, )
			if  != nil {
				.Rollback()
				.Clear()
				return 0, 0, 0, 
			}
			 += 
			if geo.PrecisionCompare(, , geo.PRECISION) < 1 {
				 = 
				 = *
			}
		} else if !layoutgraph.IsCandidateRejection() {
			return 0, 0, 0, 
		}
		.Clear()
		.Rollback()
	}

	return , .X, .Y, nil
}

// Returns true if no obvious problems with the shift
func attemptShift( *layoutgraph.Graph,  *layoutgraph.Edge,  []*layoutgraph.Node, ,  float64) bool {
	for ,  := range  {
		.Translate(, )
	}

	if !withinMaxSize() {
		return false
	}

	// Shifting is only good if it results in a direct line. It'd be undesirable if, after shifting, there are nodes in between obscuring the alignment
	// e.g. if there was a tiny box to the left of (b), moving (a) down would be not as good as moving (a) right, or (b) up
	// This includes any connected nodes
	// Note that this function already excludes ancestor containers by default
	if intersectsOtherNode(, .From, .To) {
		return false
	}

	return true
}