package dagro

// networkSimplex assigns ranks and iteratively exchanges negative-cut tree
// edges to reduce weighted edge length. The structure follows
// lib/rank/network-simplex.ts from Dagre 3.1.1.
func networkSimplex( *Graph) {
	 := simplify()
	longestPath()
	 := feasibleTree()
	 := newRankTreeOrder()
	.assignLowLim()
	assignCutValues(, , .post)

	for {
		,  := leaveEdge()
		if ! {
			break
		}
		,  := enterEdge(, , )
		if ! {
			panic("dagro: networkSimplex could not find an entering edge")
		}
		exchangeEdges(, , , , )
	}
}

func initCutValues(,  *Graph) {
	assignCutValues(, , postorder(, .Nodes()))
}

func assignCutValues(,  *Graph,  []string) {
	if len() > 0 {
		 = [:len()-1]
	}
	for ,  := range  {
		assignCutValue(, , )
	}
}

func assignCutValue(,  *Graph,  string) {
	 := asAttrs(.Node())
	 := stringValue(, "parent")
	 := treeEdge(, , )
	 := asAttrs(.Edge())
	["cutvalue"] = calcCutValue(, , )
}

func calcCutValue(,  *Graph,  string) float64 {
	 := asAttrs(.Node())
	 := stringValue(, "parent")
	 := true

	var  Edge
	if .HasEdge(, ) {
		 = Edge{V: , W: }
	} else {
		 = false
		 = Edge{V: , W: }
	}
	 := num(asAttrs(.Edge()), "weight")

	// The input graph is not mutated while computing cuts. Walk its incident
	// edge maps directly, in NodeEdges order, without copying both lists on
	// every tree-edge exchange.
	for ,  := range [2]*edgeMap{.in[], .out[]} {
		if  == nil {
			continue
		}
		for ,  := range .order {
			 := .items[]
			 := .V == 
			 := .V
			if  {
				 = .W
			}
			if  ==  {
				continue
			}

			 :=  == 
			 := num(asAttrs(.Edge()), "weight")
			if  {
				 += 
			} else {
				 -= 
			}
			if isTreeEdge(, , ) {
				 := num(asAttrs(.Edge(treeEdge(, , ))), "cutvalue")
				if  {
					 -= 
				} else {
					 += 
				}
			}
		}
	}

	return 
}

// initLowLimValues accepts an optional root to mirror the JS test hook.
func initLowLimValues( *Graph,  ...string) {
	 := .Nodes()[0]
	if len() > 0 {
		 = [0]
	}
	dfsAssignLowLim(, map[string]bool{}, 1, , "")
}

// A feasible tree stays connected and keeps the same nodes through all edge
// exchanges. Its low/lim traversal is also the traversal used to calculate
// cuts and update ranks. Retain both orders instead of traversing it three
// times, and reuse the traversal storage across exchanges.
type rankTreeOrder struct {
	root      string
	visited   map[string]bool
	pre, post []string
}

func newRankTreeOrder( *Graph) *rankTreeOrder {
	return &rankTreeOrder{
		root: .Nodes()[0], visited: make(map[string]bool, .NodeCount()),
		pre: make([]string, 0, .NodeCount()), post: make([]string, 0, .NodeCount()),
	}
}

func ( *rankTreeOrder) ( *Graph) {
	clear(.visited)
	.pre, .post = .pre[:0], .post[:0]
	dfsAssignLowLim(, .visited, 1, .root, "", )
}

func dfsAssignLowLim( *Graph,  map[string]bool,  float64, ,  string,  ...*rankTreeOrder) float64 {
	 := 
	 := asAttrs(.Node())
	[] = true
	if len() > 0 {
		[0].pre = append([0].pre, )
	}
	for ,  := range .Neighbors() {
		if ![] {
			 = (, , , , , ...)
		}
	}

	["low"] = 
	["lim"] = 
	++
	if  != "" {
		["parent"] = 
	} else {
		delete(, "parent")
	}
	if len() > 0 {
		[0].post = append([0].post, )
	}
	return 
}

func leaveEdge( *Graph) (Edge, bool) {
	for ,  := range .edgeObjs.order {
		 := .edgeObjs.items[]
		if num(asAttrs(.Edge()), "cutvalue") < 0 {
			return , true
		}
	}
	return Edge{}, false
}

func enterEdge(,  *Graph,  Edge) (Edge, bool) {
	,  := .V, .W
	if !.HasEdge(, ) {
		,  = , 
	}

	 := asAttrs(.Node())
	 := asAttrs(.Node())
	 := 
	 := false
	if num(, "lim") > num(, "lim") {
		 = 
		 = true
	}

	var  Edge
	 := 0.0
	 := false
	for ,  := range .edgeObjs.order {
		 := .edgeObjs.items[]
		 := isDescendant(asAttrs(.Node(.V)), )
		 := isDescendant(asAttrs(.Node(.W)), )
		if  !=  ||  ==  {
			continue
		}
		 := slack(, )
		if ! ||  <  {
			, ,  = , , true
		}
	}
	return , 
}

func exchangeEdges(,  *Graph, ,  Edge,  ...*rankTreeOrder) {
	.RemoveEdge()
	.SetEdge(.V, .W, Attrs{})
	if len() > 0 {
		 := [0]
		.assignLowLim()
		assignCutValues(, , .post)
		updateRanksWithOrder(, , .pre)
		return
	}
	initLowLimValues()
	initCutValues(, )
	updateRanks(, )
}

func updateRanks(,  *Graph) {
	updateRanksWithOrder(, , nil)
}

func updateRanksWithOrder(,  *Graph,  []string) {
	,  := "", false
	for ,  := range .Nodes() {
		 := asAttrs(.Node())
		if !rankTruthy(["parent"]) {
			 = 
			 = true
			break
		}
	}
	if ! {
		return
	}

	// The JS hook permits a root selected from input labels to differ from
	// the low/lim root. Preserve that traversal when the roots differ.
	if len() == 0 || [0] !=  {
		 = preorder(, []string{})
	}
	if len() > 0 {
		 = [1:]
	}
	for ,  := range  {
		 := stringValue(asAttrs(.Node()), "parent")
		 := false
		var  Edge
		if .HasEdge(, ) {
			 = Edge{V: , W: }
		} else {
			 = Edge{V: , W: }
			 = true
		}
		 := num(asAttrs(.Edge()), "minlen")
		 := num(asAttrs(.Node()), "rank")
		if  {
			asAttrs(.Node())["rank"] =  + 
		} else {
			asAttrs(.Node())["rank"] =  - 
		}
	}
}

func isTreeEdge( *Graph, ,  string) bool {
	return .HasEdge(, )
}

func isDescendant(,  Attrs) bool {
	return num(, "low") <= num(, "lim") &&
		num(, "lim") <= num(, "lim")
}

func treeEdge( *Graph, ,  string) Edge {
	// Feasible trees are simple and undirected. Locate their canonical edge
	// directly instead of allocating and scanning both adjacency lists.
	// Keep NodeEdges' empty-string filter behavior for the JS test hook.
	if !.directed && !.multigraph &&  != "" {
		if ,  := .edgeObjs.items[edgeArgsToID(false, , , nil)];  &&
			(.V ==  && .W ==  || .V ==  && .W == ) {
			return 
		}
		panic("dagro: expected tree edge")
	}
	for ,  := range .NodeEdges(, ) {
		return 
	}
	panic("dagro: expected tree edge")
}

func rankTruthy( any) bool {
	switch x := .(type) {
	case nil:
		return false
	case bool:
		return 
	case string:
		return  != ""
	case float64:
		return  != 0 &&  == 
	case float32:
		return  != 0 &&  == 
	case int:
		return  != 0
	case int8:
		return  != 0
	case int16:
		return  != 0
	case int32:
		return  != 0
	case int64:
		return  != 0
	case uint:
		return  != 0
	case uint8:
		return  != 0
	case uint16:
		return  != 0
	case uint32:
		return  != 0
	case uint64:
		return  != 0
	default:
		return true
	}
}