package placement

import (
	
	
	

	
	
	
	
	
)

type clusterDesiredArrangementSnapshot struct {
	cluster     *layoutgraph.Cluster
	arrangement layoutgraph.ClusterArrangement
	set         bool
}

type optimizeClustersRollback struct {
	graph              *layoutgraph.Graph
	graphState         *layoutgraph.GraphState
	desiredArrangement clusterDesiredArrangementSnapshot
	cellSize           float64
	costs              *layoutgraph.PlacementCostSnapshot
}

func ( *optimizeClustersRollback) ( *layoutgraph.Cluster,  layoutgraph.ClusterArrangement) {
	// The first transaction snapshot contains every later cluster's entry value.
	// Only a change made before that snapshot exists needs a separate journal.
	if .graphState != nil || .desiredArrangement.set || .DesiredArrangement ==  {
		return
	}
	.desiredArrangement = clusterDesiredArrangementSnapshot{
		cluster:     ,
		arrangement: .DesiredArrangement,
		set:         true,
	}
}

func ( *optimizeClustersRollback) ( *layoutgraph.Transaction) bool {
	if .graphState != nil {
		return false
	}
	.graphState = .PriorGraphState
	return true
}

func ( *optimizeClustersRollback) ( *limits.WorkGuard) error {
	if .costs != nil {
		return nil
	}
	 := .graph.EdgeLengthCacheEntries()
	if int64() > layoutgraph.MaxTopologyReferences {
		return fmt.Errorf("TALA OptimizeClustersTransactions edge-length cache entries exceed limit %d", layoutgraph.MaxTopologyReferences)
	}
	for range  {
		if  := .Step();  != nil {
			return 
		}
	}
	 := .graph.SnapshotPlacementCosts()
	if  := .Finish();  != nil {
		return 
	}
	.costs = 
	return nil
}

func ( *optimizeClustersRollback) () {
	if .graphState != nil {
		layoutgraph.RestoreGraphState(.graph, .graphState)
	}
	if .desiredArrangement.set {
		.desiredArrangement.cluster.DesiredArrangement = .desiredArrangement.arrangement
	}
	.graph.CellSize = .cellSize
	if .costs != nil {
		.costs.Restore()
	}
}

func alignConnectedNodes( *layoutgraph.Cluster,  bool) {
	 := clusterExternalConnectedNodes()
	if len() == 0 {
		return
	}

	// align every node center with cluster center
	for ,  := range  {
		if .FixedTopLeft != nil {
			continue
		}
		// Don't want it to travel too far, risk of fucking up other things
		if !.OverlapsAlongDimension(.Vessel, !, true) {
			continue
		}
		 := 0.0
		 := 0.0
		if  {
			 = math.Round(.Vessel.TopLeft.X + .Vessel.Width/2 - .TopLeft.X - .Width/2)
		} else {
			 = math.Round(.Vessel.TopLeft.Y + .Vessel.Height/2 - .TopLeft.Y - .Height/2)
		}
		.MoveWithChildren(, )
	}
}

func alignVessel( *layoutgraph.Cluster,  bool) {
	 := clusterExternalConnectedNodes()
	if len() == 0 {
		return
	}

	 := 0.0
	 := 0.0
	for ,  := range  {
		 += .Center().X
		 += .Center().Y
	}
	 /= float64(len())
	 /= float64(len())

	 := 0.0
	 := 0.0
	// align cluster center with avg external node center
	if  {
		 =  - .Vessel.Width/2 - .Vessel.TopLeft.X
	} else {
		 =  - .Vessel.Height/2 - .Vessel.TopLeft.Y
	}

	.Vessel.TopLeft.X += math.Round()
	.Vessel.TopLeft.Y += math.Round()
}

// A cluster is considered optimized when
// - its orientation is perpendicular to its edges
// - it's aligned symmetrically to its connected nodes
// If we cannot rotate to the right orientation, then give up (wrongly oriented + symmetrical looks worse than nonsymmetrical)
// If we are able to rotate to the right orientation (or we already are), then try to align
// If it's diagonal, use the one that matches the aspect ratio
func optimizeCluster( context.Context,  *layoutgraph.Cluster,  bool) (bool, error) {
	return optimizeClusterWithRollback(, , , nil)
}

func optimizeClusterWithRollback( context.Context,  *layoutgraph.Cluster,  bool,  *optimizeClustersRollback) (bool, error) {
	, ,  := layoutgraph.EnsureTransactionWorkGuard(, "OptimizeClusterTransactions")
	if  != nil {
		return false, 
	}
	 := 0
	 := 0
	 := false
	for ,  := range clusterExternalConnectedNodes() {
		 := .Vessel.Orientation()
		if  == geo.NONE {
			continue
		}

		switch  {
		case geo.Left, geo.Right:
			++
		case geo.Top, geo.Bottom:
			++
		default:
			// Diagonal
			 := math.Abs(.Center().X - .Vessel.Center().X)
			 := math.Abs(.Center().Y - .Vessel.Center().Y)
			if  >  {
				++
			} else if  >  {
				++
			}
		}
	}

	 := .DesiredArrangement
	if  >  {
		 = layoutgraph.Row
	} else if  >  {
		 = layoutgraph.Column
	}
	if  != nil {
		.recordDesiredArrangement(, )
	}
	.DesiredArrangement = 

	var  *layoutgraph.Transaction
	 := false
	if .Arrangement !=  {
		var  error
		,  = .Graph.NewRequestTransaction(, layoutgraph.TransactionOptions{AffectContainers: true})
		if  != nil {
			return , 
		}
		if  != nil {
			 = .recordGraphState()
		}
		// first try flipping around center, if that fails try flipping around top left
		 := true
		.AddOp(func() error {
			switch .Arrangement {
			case layoutgraph.Column:
				.Arrangement = layoutgraph.Row
			case layoutgraph.Row:
				.Arrangement = layoutgraph.Column
			}
			.Padding = grouping.PaddingBetween(, true)
			.SyncGeometry()

			if  {
				// sync from column to row (top left is fixed):
				// ┌┼┐ ┌┼────┐  ┼: original centerX
				// │ │ │     │
				// │ │ └─────┘
				// │ │
				// └─┘
				// then centering:
				// ┌──┼──┐
				// │     │
				// └─────┘
				switch .Arrangement {
				case layoutgraph.Column:
					,  := .OriginalDimensions(.Vessel)
					 :=  - .Vessel.Height
					.Vessel.MoveWithChildren(0, math.Round(/2))
				case layoutgraph.Row:
					,  := .OriginalDimensions(.Vessel)
					 :=  - .Vessel.Width
					.Vessel.MoveWithChildren(math.Round(/2), 0)
				}
			}

			if .Container != nil && .Container.TopLeft != nil {
				 := layoutgraph.Nodes(.Graph.Containers[.Container])
				,  := .FixedBoundingBox()
				 := .Graph.ContainerPadding(.Container, false)
				.Container.FitToBoundingBox(, , )
				.Container.PositionContainerChildren(true)
			}
			return nil
		})
		if  := .Commit();  != nil {
			if !layoutgraph.IsCandidateRejection() {
				return , 
			}
			 = false
			if  := .Commit();  != nil {
				if !layoutgraph.IsCandidateRejection() {
					return , 
				}
				return , nil
			}
		}
		if  := .UpdateState();  != nil {
			return , 
		}
		.Clear()
		 = true
	}

	if ! && .Arrangement ==  {
		// There's two ways alignment can happen, vessel moves or external nodes move
		// First try vessel move
		if  == nil {
			var  error
			,  = .Graph.NewRequestTransaction(, layoutgraph.TransactionOptions{AffectContainers: true})
			if  != nil {
				return , 
			}
			if  != nil {
				 = .recordGraphState()
			}
		}
		if  != nil {
			if  := .capturePlacementCosts();  != nil {
				return , 
			}
		}
		,  := placementcost.EdgeLength(, .Graph, placementcost.EdgeLengthOptions{EdgeAbductions: nil, IncludeNodeSizes: true, EnforceMinimumGap: false, PenalizeDirection: true})
		if  != nil {
			return , 
		}
		 := false
		 := false

		.AddOp(func() error {
			switch .Arrangement {
			case layoutgraph.Column:
				alignVessel(, false)
			case layoutgraph.Row:
				alignVessel(, true)
			}
			return nil
		})
		 = .Commit()
		if  == nil {
			,  := placementcost.EdgeLength(, .Graph, placementcost.EdgeLengthOptions{EdgeAbductions: nil, IncludeNodeSizes: true, EnforceMinimumGap: false, PenalizeDirection: true})
			if  != nil {
				.Rollback()
				.Clear()
				return , 
			}
			if geo.PrecisionCompare(, , geo.PRECISION) < 0 {
				 = 
				 = true
			}
		} else if !layoutgraph.IsCandidateRejection() {
			return , 
		}
		.Rollback()
		.Clear()

		.AddOp(func() error {
			switch .Arrangement {
			case layoutgraph.Column:
				alignConnectedNodes(, false)
			case layoutgraph.Row:
				alignConnectedNodes(, true)
			}

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

		.Rollback()
		.Clear()

		if  {
			.AddOp(func() error {
				switch .Arrangement {
				case layoutgraph.Column:
					alignConnectedNodes(, false)
				case layoutgraph.Row:
					alignConnectedNodes(, true)
				}

				return nil
			})
			if  := .Commit();  != nil {
				return , 
			}
			 = true
		} else if  {
			.AddOp(func() error {
				switch .Arrangement {
				case layoutgraph.Column:
					alignVessel(, false)
				case layoutgraph.Row:
					alignVessel(, true)
				}
				return nil
			})
			if  := .Commit();  != nil {
				return , 
			}
			 = true
		}

		if  ||  {
			// after flipping we may need to pull connected nodes closer
			.Clear()
			if  {
				// The first transaction's original graph state is the stage rollback
				// point. Detach before a second refresh could recycle that state as
				// transaction scratch.
				var  error
				,  = .CloneGeometryContext()
				if  != nil {
					return , 
				}
			}
			if  := .UpdateState();  != nil {
				return , 
			}
			.AddOp(func() error {
				, ,  := reduceGapToNeighbors(, .Vessel, nil, gapReductionOptions{
					axis:                   axisForArrangement(.Arrangement),
					direction:              forwardDirection,
					attemptRecoverSymmetry: true,
				})
				return 
			})
			if  := .Commit();  != nil {
				if !layoutgraph.IsCandidateRejection() {
					return , 
				}
			}
		}
	}

	return , nil
}

// make clusters horizontal if cluster connections are above or below cluster
func ( context.Context,  *layoutgraph.Graph) (bool, error) {
	, ,  := layoutgraph.EnsureTransactionWorkGuard(, "OptimizeClustersTransactions")
	if  != nil {
		return false, 
	}
	 := .ClusterRDFSOrder()
	if len() == 0 {
		return false, nil
	}
	return optimizeClustersAtomic(, , )
}

func optimizeClustersAtomic( context.Context,  *layoutgraph.Graph,  []*layoutgraph.Node) (bool, error) {
	 := &optimizeClustersRollback{graph: , cellSize: .CellSize}
	 := false
	defer func() {
		if ! {
			.restore()
		}
	}()
	 := false
	for ,  := range  {
		 := .Clusters[]
		,  := optimizeClusterWithRollback(, , false, )
		if  != nil {
			return false, 
		}
		if  {
			 = true
		}
	}

	 = true
	return , nil
}