// Copyright (c) 2016 Kiel University and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0.
//
// SPDX-License-Identifier: EPL-2.0

package intermediate

import (
	

	basegraph 
	
)

// PromoteNodes is the arena-backed port of ELK 0.8.1's NodePromotion
// intermediate processor (Nikolov and Tarassov's dummy-reduction heuristic).
func ( *layered.LGraphArena,  layered.LGraphID) {
	 := nodePromotion{arena: , graphID: }
	.precalculate()
	.strategy = basegraph.Get(&.Graph().Properties, layered.NodePromotionStrategyOption)
	 := basegraph.Get(&.Graph().Properties, layered.NodePromotionMaxIterations)
	 := func(,  int) bool { return true }

	switch .strategy {
	case layered.NodePromotionNikolov, layered.NodePromotionNikolovPixel:
		.run()
	case layered.NodePromotionNikolovImproved:
		.strategy = layered.NodePromotionNoBoundary
		.run()
		 := 0
		for ,  := range .currentWidth {
			 = max(, )
		}
		if  > .maximumWidth {
			.strategy = layered.NodePromotionNikolov
			.run()
		}
	case layered.NodePromotionNikolovImprovedPixel:
		.strategy = layered.NodePromotionNoBoundary
		.run()
		 := 0.0
		for ,  := range .currentWidthPixel {
			 = math.Max(, )
		}
		if  > .maximumWidthPixel {
			.strategy = layered.NodePromotionNikolovPixel
			.run()
		}
	case layered.NodePromotionNodeCountPercentage:
		 := int(math.Ceil(float64(len(.layers)*) / 100.0))
		.run(func(,  int) bool { return  <  })
	case layered.NodePromotionDummyNodePercentage:
		 := int(math.Ceil(float64(.dummyNodeCount*) / 100.0))
		.run(func(,  int) bool { return  <  })
	default:
		// The GraphConfigurator does not schedule NONE, but the Java
		// processor's default arm still runs the unbounded heuristic when
		// invoked directly.
		.run()
	}
	.apply()
}

type nodePromotion struct {
	arena   *layered.LGraphArena
	graphID layered.LGraphID

	nodes                  []layered.LNodeID
	nodesWithIncomingEdges []layered.LNodeID
	nodeIndex              map[layered.LNodeID]int
	layers                 []int
	degreeDifference       [][3]int
	currentWidth           []int
	currentWidthPixel      []float64
	maximumWidth           int
	maximumWidthPixel      float64
	dummyNodeCount         int
	maximumHeight          int
	nodeSizeAffix          float64
	dummySize              float64
	strategy               layered.NodePromotionStrategy
}

func ( *nodePromotion) () {
	 := .arena.Graph(.graphID)
	.nodeSizeAffix = basegraph.Get(&.Properties, layered.SpacingNodeNode)
	.dummySize = basegraph.Get(&.Properties, layered.SpacingEdgeNodeBetweenLayers)
	.maximumHeight = len(.Layers)
	.currentWidth = make([]int, .maximumHeight)
	.currentWidthPixel = make([]float64, .maximumHeight)
	.nodeIndex = make(map[layered.LNodeID]int)

	// ELK numbers layers in reverse and nodes in forward layer order.
	 := make(map[layered.LayerID]int, len(.Layers))
	 := .maximumHeight - 1
	for ,  := range .Layers {
		[] = 
		.arena.Layer().ID = 
		for ,  := range .arena.Layer().Nodes {
			.nodeIndex[] = len(.nodes)
			.nodes = append(.nodes, )
		}
		--
	}

	.layers = make([]int, len(.nodes))
	.degreeDifference = make([][3]int, len(.nodes))
	 := 0
	for ,  := range .Layers {
		 := []
		,  := 0, 0
		 := .arena.Layer().Nodes
		 := 0.0
		for ,  := range  {
			 := .nodeIndex[]
			.layers[] = 
			 := .arena.Node()
			 += .Size.Y + .nodeSizeAffix
			 := len(.arena.NodeIncomingEdges())
			 := len(.arena.NodeOutgoingEdges())
			.degreeDifference[] = [3]int{ - , , }
			 += 
			 += 
			if  > 0 {
				.nodesWithIncomingEdges = append(.nodesWithIncomingEdges, )
			}
		}
		 -= 
		 := len() + 
		 += float64() * .dummySize
		.currentWidth[] = 
		.currentWidthPixel[] = 
		.maximumWidth = max(.maximumWidth, )
		.maximumWidthPixel = math.Max(.maximumWidthPixel, )
		.dummyNodeCount += 
		 += 
	}
}

func ( *nodePromotion) ( func(,  int) bool) {
	 := 0
	 := 0
	 := append([]int(nil), .layers...)
	 := .dummyNodeCount
	 := .maximumHeight
	 := .currentWidth
	 := .currentWidthPixel

	for {
		 := 0
		for ,  := range .nodesWithIncomingEdges {
			,  := .promote()
			 := true
			if .strategy == layered.NodePromotionNikolov ||
				.strategy == layered.NodePromotionNikolovPixel {
				 = 
			}
			if  < 0 &&  {
				++
				 = append([:0], .layers...)
				.dummyNodeCount += 
				 +=  - .dummyNodeCount
				// This second addition is present in ELK 0.8.1 and is
				// intentionally retained for bytecode-equivalent behavior.
				 = .dummyNodeCount + 
				 = .maximumHeight
				 = append([]int(nil), .currentWidth...)
				 = append([]float64(nil), .currentWidthPixel...)
			} else {
				.layers = append(.layers[:0], ...)
				.dummyNodeCount = 
				.currentWidth = append(.currentWidth[:0], ...)
				.currentWidthPixel = append(.currentWidthPixel[:0], ...)
				.maximumHeight = 
			}
		}
		++
		if  == 0 || !(, ) {
			return
		}
	}
}

func ( *nodePromotion) ( layered.LNodeID) ( int,  bool) {
	 = true
	 := .nodeIndex[]
	 := .layers[]
	 := .arena.Node().Size.Y + .nodeSizeAffix
	 := .degreeDifference[][2]

	.currentWidth[] += -1 + 
	.currentWidthPixel[] += - + float64()*.dummySize
	++
	if  >= .maximumHeight {
		.maximumHeight++
		.currentWidth = append(.currentWidth, 1)
		.currentWidthPixel = append(.currentWidthPixel, )
	} else {
		 := .degreeDifference[][1]
		.currentWidth[] += 1 - 
		.currentWidthPixel[] +=  - float64()*.dummySize
	}

	if .strategy == layered.NodePromotionNikolov &&
		(.currentWidth[] > .maximumWidth ||
			.currentWidth[-1] > .maximumWidth) {
		 = false
	} else if .strategy == layered.NodePromotionNikolovPixel &&
		(.currentWidthPixel[] > .maximumWidthPixel ||
			.currentWidthPixel[-1] > .maximumWidthPixel) {
		 = false
	}

	for ,  := range .arena.NodeIncomingEdges() {
		 := .arena.EdgeSourceNode()
		 := .nodeIndex[]
		if .layers[] ==  {
			,  := .()
			 += 
			 =  && 
		}
	}
	.layers[] = 
	 += .degreeDifference[][0]
	return , 
}

func ( *nodePromotion) () {
	// ELK deliberately constructs maximumHeight+1 layers, then removes every
	// empty one after assigning nodes using the reversed ranks.
	 := make([]layered.LayerID, 0, .maximumHeight+1)
	for  := 0;  <= .maximumHeight; ++ {
		 := .arena.AddLayer(.graphID)
		.arena.Layer().ID = .maximumHeight - 
		 = append(, )
	}
	for ,  := range .nodes {
		 := .nodeIndex[]
		.arena.SetNodeLayer(, &[.maximumHeight-.layers[]])
	}
	 := [:0]
	for ,  := range  {
		if len(.arena.Layer().Nodes) != 0 {
			 = append(, )
		}
	}
	.arena.Graph(.graphID).Layers = 
}