package hierarchy

import (
	
	
	

	
)

// The routines below implement the algorithm described in `Fig. 1`, and section
// `4 Extending Sifting to k-Layered Directed Graphs`, of
// "Using Sifting for k-Layer Straightline Crossing Minimization" by Matuszewski et al.
// This iterative algorithm tries to find the optimal position for a given node
// initialize a queue of nodes in descending order
// for each node in the queue
//     swap with its siblings all the way to right
//     swap all the way the left
//     swap to the position with best crossings or length (this is the optimal position)
// swapping with neighbors is a local operation, so it should fast to compute new crossings
// however, as we handle containers, swapping container can lead to major changes and then we
// can't take full advantage of the local swaps
// main changes from the algorithm described in the paper:
// - they don't handle edge length
// - they reverse the queue if there's no improvement
//   - my udnerstanding is that the algorithm always process the whole queue, so it doesn't make sense to reverse it
// - if there's no improvement, allow changes if crossings or length stay the same (escape local minima)
// - if there's still no improvement, stop

func globalSifting( map[int][]*placementNode) error {
	 := nodesInDescendingDegreeOrder()
	 := buildNodeToSiblings(, )
	var  crossingScratch
	 := false
	// 10 is just a killswitch (there's no suggested value in the paper)
	for  := 0;  < 10; ++ {
		 := make([]*placementNode, len())
		copy(, )
		 := false
		for len() > 0 {
			 := [0]
			 = [1:]
			 := .rank
			if  := sifting(, [], , , &);  != nil {
				return 
			}
			if .rank !=  {
				 = true
			}
		}
		if ! {
			if  {
				// no improvement even considering reordering for the same crossings count
				break
			}
			 = true
		} else {
			 = false
		}
	}
	return nil
}

func nodesInDescendingDegreeOrder( map[int][]*placementNode) []*placementNode {
	var  []*placementNode
	for  := 0;  < len(); ++ {
		 = append(, allDescendants([], true)...)
	}
	slices.SortStableFunc(, func(,  *placementNode) int {
		if  := cmp.Compare(.degree(), .degree());  != 0 {
			return 
		}
		// If nodes have the same degree, sort by order from top-left.
		if  := cmp.Compare(.level, .level);  != 0 {
			return 
		}
		return cmp.Compare(.rank, .rank)
	})
	return 
}

func buildNodeToSiblings( []*placementNode,  map[int][]*placementNode) map[*placementNode][]*placementNode {
	 := make(map[*placementNode][]*placementNode)
	for ,  := range  {
		if .container != nil {
			[] = .container.children
		} else {
			[] = [.level]
		}
	}
	return 
}

func sifting( *placementNode,  []*placementNode,  map[int][]*placementNode,  bool,  *crossingScratch) error {
	 := .crossLevelSegments(, true, true)
	 := countCrossings()
	 := addLengths()
	 := -1

	if  == 0 || len() == 1 {
		return nil
	} else if  == [len()-1] {
		// handle edge case where the node is already the right most one
		 = len() - 1
	} else {
		// move all the way to the right
		for  := 0;  < len()-1; ++ {
			if  == -1 {
				if [] ==  {
					 = 
				} else {
					continue
				}
			}

			[+1], [] = [], [+1]
			computeLevelRanks([.level])
			 := .crossLevelSegments(, true, true)
			 := countCrossings()
			 := addLengths()
			if improved(, , , , ) {
				 = 
				 =  + 1
				 = 
			}
		}
		if [len()-1] !=  {
			return fmt.Errorf("sifting: expected node %s to have moved to the right of siblings slice", .graphNode.DebugID())
		}
		if  == -1 {
			return fmt.Errorf("sifting: node %s not found", .graphNode.DebugID())
		}
	}

	// move all the way to the left
	for  := len() - 1;  > 0; -- {
		[-1], [] = [], [-1]
		computeLevelRanks([.level])
		 := .crossLevelSegments(, true, true)
		 := countCrossings()
		 := addLengths()
		if improved(, , , , ) {
			 = 
			 =  - 1
			 = 
		}
	}
	if [0] !=  {
		return fmt.Errorf("sifting: expected node %s to have moved to the left of siblings slice", .graphNode.DebugID())
	}

	// swap the node to the best rank and update the rank of all siblings
	for  := 0;  < len(); ++ {
		if  ==  {
			// at this moment, the node was already swapped to bestLocalRank
			break
		}
		[+1], [] = [], [+1]
	}
	computeLevelRanks([.level])
	return nil
}

func improved(,  int64, ,  float64,  bool) bool {
	if ! {
		return  <  || ( ==  && geo.PrecisionCompare(, , geo.PRECISION) == -1)
	}
	return  <=  || ( ==  && geo.PrecisionCompare(, , geo.PRECISION) < 1)
}