package layoutgraph

import (
	
	

	
	
)

// Cluster is a group of nodes that
// - have the same size
// - are all adjacent to another node in the cluster
// - are evenly spaced apart from each other
// - are arranged according to a ClusterArrangement
type Cluster struct {
	// Vessel is the node that gets moved around during node placement and encapsulates the cluster nodes
	Vessel             *Node
	Nodes              []*Node
	Arrangement        ClusterArrangement
	DesiredArrangement ClusterArrangement
	Graph              *Graph
	EdgeAbductions     []*EdgeAbduction
	Padding            float64
	FixedSize          bool

	Container *Node
}

type ClusterArrangement string

func ( ClusterArrangement) () ClusterArrangement {
	if  == Row {
		return Column
	}
	return Row
}

const (
	Row    ClusterArrangement = "Row"
	Column ClusterArrangement = "Column"
)

// Resize makes the cluster nodes consistent in size, and sizes the vessel to exact fit of cluster nodes
// It should be called after any potential resizing, which may happen if a child node is a container and gets resized
func ( *Cluster) ( *Node) {
	if  := .resizeWithWork(, unmeteredGroupGeometry);  != nil {
		panic()
	}
}

func ( *Cluster) ( *Node,  workStepper) error {
	if  == nil ||  == nil {
		return invariant.New("cluster is missing its vessel")
	}
	if !.FixedSize {
		, ,  := .maximumsWithWork()
		if  != nil {
			return 
		}

		for ,  := range .Nodes {
			if  := .Step();  != nil {
				return 
			}
			.Width = 
			.Height = 
		}
	}
	, ,  := .maximumsWithWork()
	if  != nil {
		return 
	}
	if .Arrangement == Row {
		.Width = *float64(len(.Nodes)) + .Padding*float64(len(.Nodes)-1)
		.Height = 
	}

	if .Arrangement == Column {
		.Width = 
		.Height = *float64(len(.Nodes)) + .Padding*float64(len(.Nodes)-1)
	}
	return nil
}

func ( *Cluster) ( workStepper) (float64, float64, error) {
	,  := 0.0, 0.0
	for ,  := range .Nodes {
		if  := .Step();  != nil {
			return 0, 0, 
		}
		if  == nil {
			return 0, 0, invariant.New("cluster contains a nil node")
		}
		 = math.Max(, .Width)
	}
	for ,  := range .Nodes {
		if  := .Step();  != nil {
			return 0, 0, 
		}
		 = math.Max(, .Height)
	}
	return , , .Finish()
}

// ArrangeClusterNodes moves the inner cluster nodes to their respective positions within the cluster
// This should be called after vessels move
// This is idempotent -- calling it after it's in the right position results in 0's for movements
func ( *Cluster) () {
	if  := .arrangeNodesWithWork(unmeteredGroupGeometry);  != nil {
		panic()
	}
}

func ( *Cluster) ( ClusterGeometryWork) error {
	// Hasn't been initialized yet, which is fine since we arrange all cluster nodes every time for simplicity
	if .Vessel.TopLeft == nil {
		return .Finish()
	}

	if .Arrangement == Row {
		 := .Vessel.TopLeft.X
		 := .Vessel.TopLeft.Y + .Vessel.Height/2

		for ,  := range .Nodes {
			if  := .Step();  != nil {
				return 
			}
			if .TopLeft != nil {
				 :=  - .TopLeft.X
				 := math.Round( - (.TopLeft.Y + .Height/2))
				if  := .MoveNodeWithChildren(, , );  != nil {
					return 
				}
			} else {
				.TopLeft = geo.NewPoint(, math.Round(-.Height/2))
				if  := .PositionContainerChildren();  != nil {
					return 
				}
			}
			 += .Width + .Padding
		}
	}

	if .Arrangement == Column {
		 := .Vessel.TopLeft.Y
		 := .Vessel.TopLeft.X + .Vessel.Width/2

		for ,  := range .Nodes {
			if  := .Step();  != nil {
				return 
			}
			if .TopLeft != nil {
				 := math.Round( - (.TopLeft.X + .Width/2))
				 :=  - .TopLeft.Y
				if  := .MoveNodeWithChildren(, , );  != nil {
					return 
				}
			} else {
				.TopLeft = geo.NewPoint(math.Round(-.Width/2), )
				if  := .PositionContainerChildren();  != nil {
					return 
				}
			}
			 += .Height + .Padding
		}
	}
	return .Finish()
}

func ( *Cluster) () string {
	 := []string{}
	for ,  := range .Nodes {
		 = append(, .DebugID())
	}
	return "[" + strings.Join(, ", ") + "]; Arrangement: " + string(.Arrangement)
}

// SyncGeometry resizes the cluster vessel and arranges its visible members.
func ( *Cluster) () {
	if  := .SyncGeometryWithWork(unmeteredGroupGeometry);  != nil {
		panic()
	}
}

// SyncGeometryWithWork resizes the cluster vessel and arranges its visible
// members through caller-owned cancellation and movement operations.
func ( *Cluster) ( ClusterGeometryWork) error {
	if  == nil {
		return invariant.New("cluster geometry requires work accounting")
	}
	if  := .Step();  != nil {
		return 
	}
	if  == nil || .Vessel == nil {
		return invariant.New("cluster is missing its vessel")
	}
	if  := .resizeWithWork(.Vessel, );  != nil {
		return 
	}
	return .arrangeNodesWithWork()
}

// SyncClusters synchronizes every active cluster in nested graph order.
func ( *Graph) () {
	if len(.Clusters) == 0 {
		return
	}

	 := func( *Node) {
		if .isClusterVessel {
			.Clusters[].SyncGeometry()
		}
	}
	for ,  := range .Nodes {
		.rdfsWalk()
	}
}

// isActive is true while a Cluster's vessel is in the graph replacing the cluster nodes.
// The vessel is a convenience during node placement, which is not kept in sync after deactivation.
func ( *Cluster) () bool {
	return  != nil && .Vessel.Graph != nil
}