package hierarchy

import (
	
	
	
	

	

	
	
)

// The routines below implement ideas described in:
// - "Fast and Simple Horizontal Coordinate Assignment" by Ulrik Brandes and Boris Kopf
// - the authors' erratum to that paper
// - "Node and Label Placement in a Layered Layout Algorithm" by John Julian Carstens
// 	- The code is based on the pseudo-code available in (page 28, section 3.2.1 The approach of Brandes and Kopf)
//
// The idea is to align nodes vertically so that we make the tallest vertical chain possible because straight edges are better/easier to read.
// To achieve this, the hierarchy is aligned 4 times chaging directions:
// * Top Left: the hierarchy is traversed from top to bottom, left to right.
// * Bottom Right: the hierarchy is traversed from bottom to top, right to left.
// * Top Right: just follow the same as above
// * Bottom left: just follow the same as above
//
// On each iteration the nodes are aligned to the median node above/below (depending on the direction) if possible.
// At the end, get the alignment that resulted in the narrowest layout to balance all alignments within its bounding box.
// Then, align all nodes to the median computed from the 4 alignments above.
//
// Attention: We only perform leaf descendants alignment, no containers are considered during the routine.
// The container edges are copied to the middle descendant and this node is aligned.
// At the end, we just wrap the containers around the descendants back again.

// Just a `tuple` to index the results of a given vertical/horizontal alignment
type alignmentDirection struct {
	vertical   geo.Orientation
	horizontal geo.Orientation
}

func alignHierarchy( context.Context,  *layoutgraph.Graph,  map[int][]*placementNode) error {
	,  := limits.NewWorkGuard(, "AlignHierarchy", limits.MaxEngineWorkUnits)
	if  != nil {
		return 
	}
	 := &alignmentDirection{geo.Top, geo.Left}
	 := &alignmentDirection{geo.Top, geo.Right}
	 := &alignmentDirection{geo.Bottom, geo.Right}
	 := &alignmentDirection{geo.Bottom, geo.Left}

	 := []*alignmentDirection{, , , }

	,  := markConflicts(, )
	if  != nil {
		return 
	}
	 := map[*alignmentDirection]float64{}
	 := map[*alignmentDirection]float64{}
	 := math.Inf(1)
	var  *alignmentDirection
	 := map[*placementNode][]float64{}
	for ,  := range  {
		if  := .Step();  != nil {
			return 
		}
		[] = math.Inf(1)
		[] = math.Inf(-1)
		,  := createAlignmentNodes(, , .vertical, .horizontal)
		if  != nil {
			return 
		}
		if  := verticalAlignment(, , , .horizontal);  != nil {
			return 
		}
		if  := horizontalCompaction(, , .horizontal);  != nil {
			return 
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return 
			}
			 := .x + (.root.blockSize / 2.0) - (.graphNode.Width / 2.0)
			[.placementNode] = append([.placementNode], )
			[] = math.Min([], .x)
			[] = math.Max([], .x+.blockSize)
		}
		 := [] - []
		if  <  {
			 = 
			 = 
		}
	}

	// balance
	 := map[*alignmentDirection]float64{}
	for ,  := range  {
		if  := .Step();  != nil {
			return 
		}
		if .horizontal == geo.Left {
			[] = [] - []
		} else {
			[] = [] - []
		}
	}

	// final placement
	type  struct {
		 *layoutgraph.Node
		    float64
	}
	 := make([], 0, len())
	for ,  := range  {
		if  := .Step();  != nil {
			return 
		}
		for  := 0;  < len([]); ++ {
			if  := .Step();  != nil {
				return 
			}
			[] += [[]]
		}
		 = append(, {: .graphNode, : math.Round(median([]))})
	}
	if  := .Finish();  != nil {
		return 
	}
	for ,  := range  {
		..TopLeft.X = .
	}
	return syncContainers(, )
}

// Reposition and resize containers based on the position of the aligned children.
// This routine goes from most to least nested container.
func syncContainers( *layoutgraph.Graph,  *limits.WorkGuard) error {
	var  []*layoutgraph.Node
	 := make([]*layoutgraph.Node, 0, len(.Nodes))
	 = append(, .Nodes...)
	for len() > 0 {
		if  := .Step();  != nil {
			return 
		}
		 := [0]
		if .IsContainer() {
			 = append(, )
			 = append(, .Containers[]...)
		}
		 = [1:]
	}

	// most nested containers are the last ones added to the list
	for  := len() - 1;  > -1; -- {
		if  := .Step();  != nil {
			return 
		}
		[].WrapChildren()
	}
	return .Finish()
}

func median( []float64) float64 {
	if len() == 0 {
		return 0
	} else if len() == 1 {
		return [0]
	}

	 := make([]float64, len())
	copy(, )

	slices.Sort()

	if len()%2 == 1 {
		return [(len() / 2)]
	}

	 := len() / 2
	return [-1]/2 + []/2
}

type alignmentNode struct {
	*placementNode

	prevSibling     *alignmentNode
	root            *alignmentNode
	alignedWith     *alignmentNode
	sink            *alignmentNode
	medianNeighbors []*alignmentNode
	shift           float64
	x               float64
	blockSize       float64
	rightPad        float64
	leftPad         float64
}

func newAlignmentNode( *placementNode) *alignmentNode {
	 := .graphNode.Width

	 := &alignmentNode{
		placementNode:   ,
		root:            nil,
		sink:            nil,
		alignedWith:     nil,
		shift:           0,
		x:               0,
		blockSize:       ,
		prevSibling:     nil,
		medianNeighbors: nil,
		rightPad:        0,
		leftPad:         0,
	}

	.root = 
	.alignedWith = 
	.sink = 

	return 
}

// Given layered nodes, creates a slice of alignment nodes sorted in the desired direction
func createAlignmentNodes( context.Context,  map[int][]*placementNode, ,  geo.Orientation) ([]*alignmentNode, error) {
	,  := limits.NewWorkGuard(, "CreateAlignmentNodes", limits.MaxEngineWorkUnits)
	if  != nil {
		return nil, 
	}
	 := make(map[*placementNode]*alignmentNode)
	var  []*alignmentNode
	for  := 0;  < len(); ++ {
		if  := .Step();  != nil {
			return nil, 
		}
		,  := leafNodesContext([], )
		if  != nil {
			return nil, 
		}
		for ,  := range  {
			if  := .Step();  != nil {
				return nil, 
			}
			 = append(, )
			[.placementNode] = 
		}
	}

	sortAlignmentNodes(, , )
	if  := .Finish();  != nil {
		return nil, 
	}

	 := math.Inf(-1)
	 := math.Inf(1)
	if  == geo.Right {
		 = math.Inf(1)
		 = math.Inf(-1)
	}
	var  *alignmentNode
	for ,  := range  {
		if  := .Step();  != nil {
			return nil, 
		}
		if  != nil && .level != .level {
			 = nil
		}
		,  := medianNeighborsContext(, , , , )
		if  != nil {
			return nil, 
		}
		.medianNeighbors = 
		.prevSibling = 
		.x = 
		.shift = 
		 = 
	}
	if  := .Finish();  != nil {
		return nil, 
	}
	return , nil
}

func absInt( int) int {
	if  < 0 {
		return -
	}
	return 
}

func leafNodesContext( []*placementNode,  *limits.WorkGuard) ([]*alignmentNode, error) {
	var  []*alignmentNode
	for  := 0;  < len(); ++ {
		if  := .Step();  != nil {
			return nil, 
		}
		 := []
		if !.isContainer {
			 = append(, newAlignmentNode())
			continue
		}
		// Instead of picking a single 'middle' child, distribute container edges
		// among children whose rank is closest to the other endpoint's rank.
		,  := (.children, ) // recursively collect leaf descendants
		if  != nil {
			return nil, 
		}

		 := func( *placementNode,  []*alignmentNode) (*alignmentNode, error) {
			// simple heuristic: pick child with rank closest to edgeEndpoint's rank
			 := [0]
			 := absInt(.rank - .placementNode.rank)
			for ,  := range  {
				if  := .Step();  != nil {
					return nil, 
				}
				 := absInt(.rank - .placementNode.rank)
				if  <  {
					 = 
					 = 
				}
			}
			return , nil
		}

		// rewire "aboves" from container to children
		for  := range .aboves {
			if  := .Step();  != nil {
				return nil, 
			}
			delete(.belows, )
			,  := (, )
			if  != nil {
				return nil, 
			}
			.belows[.placementNode] = struct{}{}
			.aboves[] = struct{}{}
		}

		// rewire "belows" from container to children
		for  := range .belows {
			if  := .Step();  != nil {
				return nil, 
			}
			delete(.aboves, )
			,  := (, )
			if  != nil {
				return nil, 
			}
			.aboves[.placementNode] = struct{}{}
			.belows[] = struct{}{}
		}

		// Add the descendants to the final leaves
		 = append(, ...)
	}
	// we need to consider the container padding to properly align the nodes
	// the first container child has the left pad and the last one has the right pad
	// this applied recursively (as this function is recursive) because we can have containers inside containers
	,  := [0], [len()-1]
	.leftPad += .containerPadding().Left()
	.rightPad += .containerPadding().Right()
	return , nil
}

func ( *placementNode) () layoutgraph.Spacing {
	if  == nil || .isDummy {
		return layoutgraph.UniformSpacing(layoutgraph.ContainerPadding)
	}
	var  *layoutgraph.Node
	if .container != nil {
		 = .container.graphNode
	}
	return .graphNode.Graph.ContainerPadding(, true)
}

func sortAlignmentNodes( []*alignmentNode, ,  geo.Orientation) {
	if  == geo.Top &&  == geo.Left {
		sort.Slice(, func(,  int) bool {
			if [].level == [].level {
				return [].rank < [].rank
			}
			return [].level < [].level
		})
	} else if  == geo.Top &&  == geo.Right {
		sort.Slice(, func(,  int) bool {
			if [].level == [].level {
				return [].rank < [].rank
			}
			return [].level < [].level
		})
	} else if  == geo.Bottom &&  == geo.Left {
		sort.Slice(, func(,  int) bool {
			if [].level == [].level {
				return [].rank < [].rank
			}
			return [].level < [].level
		})
	} else if  == geo.Bottom &&  == geo.Right {
		sort.Slice(, func(,  int) bool {
			if [].level == [].level {
				return [].rank < [].rank
			}
			return [].level < [].level
		})
	}
}

func medianNeighborsContext(
	 *alignmentNode,
	,  geo.Orientation,
	 map[*placementNode]*alignmentNode,
	 *limits.WorkGuard,
) ([]*alignmentNode, error) {
	var  []*alignmentNode

	if  == geo.Top {
		for  := range .aboves {
			if  := .Step();  != nil {
				return nil, 
			}
			if .level == .level-1 {
				 = append(, [])
			}
		}
	} else {
		for  := range .belows {
			if  := .Step();  != nil {
				return nil, 
			}
			if .level == .level+1 {
				 = append(, [])
			}
		}
	}

	if len() == 0 {
		return , nil
	}

	sort.Slice(, func(,  int) bool {
		return [].graphNode.TopLeft.X < [].graphNode.TopLeft.X
	})

	 := float64(len()+1) / 2.0
	if len()%2 == 1 {
		// exact median
		 := int() - 1
		return []*alignmentNode{[]}, nil
	} else {
		// consider left and right median
		 := int(math.Floor()) - 1
		 := int(math.Ceil()) - 1
		if  == geo.Right {
			,  = , 
		}
		return []*alignmentNode{[], []}, nil
	}
}

func allAbove( *placementNode) []*placementNode {
	var  []*placementNode
	for  := range .aboves {
		 = append(, )
	}
	return 
}

func markConflicts( context.Context,  map[int][]*placementNode) (map[*placementNode]map[*placementNode]struct{}, error) {
	,  := limits.NewWorkGuard(, "MarkHierarchyConflicts", limits.MaxEngineWorkUnits)
	if  != nil {
		return nil, 
	}
	 := make(map[*placementNode]map[*placementNode]struct{})
	 := func(,  *placementNode) {
		if ,  := []; ! {
			[] = make(map[*placementNode]struct{})
		}
		[][] = struct{}{}
	}

	// skip the first and last level because there are no dummy nodes in them
	for  := 1;  < len()-1; ++ {
		if  := .Step();  != nil {
			return nil, 
		}
		 :=  + 1
		 := 0
		 := 0
		for ,  := range [] {
			if  := .Step();  != nil {
				return nil, 
			}
			var  int
			if  == len([])-1 {
				 := len([]) - 1
				 = [][].rank
			} else if .isDummy {
				for ,  := range allAbove() {
					if  := .Step();  != nil {
						return nil, 
					}
					// dummy nodes have only one edge above and one below
					if !.isDummy {
						continue
					}
					 = .rank
				}
			} else {
				continue
			}
			for ;  <= ; ++ {
				if  := .Step();  != nil {
					return nil, 
				}
				 := [][]
				for  := range .aboves {
					if  := .Step();  != nil {
						return nil, 
					}
					if .rank <  || .rank >  {
						(, )
						(, )
					}
				}
			}
			 = 
		}
	}

	if  := .Finish();  != nil {
		return nil, 
	}
	return , nil
}

func verticalAlignment( context.Context,  []*alignmentNode,  map[*placementNode]map[*placementNode]struct{},  geo.Orientation) error {
	,  := limits.NewWorkGuard(, "VerticalAlignment", limits.MaxEngineWorkUnits)
	if  != nil {
		return 
	}
	 := func(,  *alignmentNode) bool {
		if ,  := [.placementNode];  {
			,  := [.placementNode]
			return 
		}
		return false
	}

	 := math.MaxInt
	for ,  := range  {
		if  := .Step();  != nil {
			return 
		}
		// given a level with nodes
		// a b c
		// when horizontal = Left
		// a.previousSibling = nil
		// b.previousSibling = a
		// c.previousSibling = b
		// when horizontal = Right
		// a.previousSibling = b
		// b.previousSibling = c
		// c.previousSibling = nil
		// This is how `createAlignmentNodes` creates the slice
		if .prevSibling == nil {
			if  == geo.Left {
				 = math.MinInt
			} else {
				 = math.MaxInt
			}
		}

		for ,  := range .medianNeighbors {
			if  := .Step();  != nil {
				return 
			}
			if (, ) {
				continue
			} else if .alignedWith !=  {
				// already aligned
				continue
			} else if  == geo.Left &&  >= .rank {
				// this would align crossing edges, skip
				continue
			} else if  == geo.Right &&  <= .rank {
				// this would align crossing edges, skip
				continue
			}
			.alignedWith = 
			.root = .root
			.alignedWith = .root
			.root.blockSize = math.Max(.root.blockSize, .blockSize)
			.root.leftPad = math.Max(.root.leftPad, .leftPad)
			.root.rightPad = math.Max(.root.rightPad, .rightPad)
			 = .rank
		}
	}
	return .Finish()
}

func horizontalCompaction( context.Context,  []*alignmentNode,  geo.Orientation) error {
	,  := limits.NewWorkGuard(, "HorizontalCompaction", limits.MaxEngineWorkUnits)
	if  != nil {
		return 
	}
	for ,  := range  {
		if  := .Step();  != nil {
			return 
		}
		if .root ==  {
			if  := placeBlock(, , );  != nil {
				return 
			}
		}
	}
	for ,  := range  {
		if  := .Step();  != nil {
			return 
		}
		.x = .root.x
		if .root ==  && !math.IsInf(.sink.shift, 0) {
			.x += .sink.shift
		}
	}
	return .Finish()
}

func placeBlock( *alignmentNode,  geo.Orientation,  *limits.WorkGuard) error {
	if  := .Step();  != nil {
		return 
	}
	if !math.IsInf(.x, 0) {
		return nil
	}
	.x = 0
	 := 
	for {
		if  := .Step();  != nil {
			return 
		}
		if .prevSibling != nil {
			 := .prevSibling.root
			if  := (, , );  != nil {
				return 
			}
			if .sink ==  {
				.sink = .sink
			}
			 := distanceFromPreviousRoot(, , )
			if .sink != .sink {
				if  == geo.Left {
					.sink.shift = math.Min(.sink.shift, .x-.x-.blockSize-)
				} else {
					.sink.shift = math.Max(.sink.shift, .x-.x+.blockSize+)
				}
			} else {
				if  == geo.Left {
					.x = math.Max(.x, .x+.blockSize+)
				} else {
					.x = math.Min(.x, .x-.blockSize-)
				}
			}
		}
		 = .alignedWith
		if  ==  {
			break
		}
	}
	return nil
}

/*
By default, nodes in hierarchies are siblingSpacing from their siblings.
Though, when a node is inside a container, this distance must be increased by the container padding.
For the first node of a container, this pad is added to the left.
For the last node of a container, the pad is added to the right.
The recursiveness of the pad (containers inside containers) is considered when creating the node.
This function only combines poadding in the given direction.
*/
func distanceFromPreviousRoot(,  *alignmentNode,  geo.Orientation) float64 {
	 := siblingSpacing
	if .isDummy || (.prevSibling != nil && .prevSibling.isDummy) {
		 = siblingDummySpacing
	}
	if  == geo.Left {
		 += .rightPad + .leftPad
	} else {
		 += .leftPad + .rightPad
	}
	return 
}