// Copyright (c) 2016 Kiel University.
//
// 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 p3order

import (
	

	basegraph 
	
)

// GreedyPortDistributor is the distributor used by ELK's two-sided greedy
// switch. It repeatedly swaps adjacent ports whenever the swap strictly
// decreases crossings.
type GreedyPortDistributor struct {
	crossingsCounter             *CrossingsCounter
	nPorts                       int
	hierarchicalCrossingsCounter *betweenLayerEdgeTwoNodeCrossingsCounter
}

func () *GreedyPortDistributor { return &GreedyPortDistributor{} }

func ( *GreedyPortDistributor) (
	 *layered.LGraphArena,
	,  int,
	 [][]layered.LNodeID,
) {
	.nPorts += len(.Node([][]).Ports)
}

func ( *GreedyPortDistributor) () {
	.crossingsCounter = NewCrossingsCounter(make([]int, .nPorts))
}

func (
	 *GreedyPortDistributor,
	 *layered.LGraphArena,
	 [][]layered.LNodeID,
) {
	 := 0
	for ,  := range  {
		for ,  := range  {
			.InitAtNodeLevel(, , , )
			for ,  := range .Node().Ports {
				.Port().ID = 
				++
			}
		}
	}
	.InitAfterTraversal()
}

func ( *GreedyPortDistributor) (
	 *layered.LGraphArena,
	 [][]layered.LNodeID,
	 int,
	 bool,
) bool {
	.initialize(, , , )
	return .distributePortsInLayer(, , , )
}

func ( *GreedyPortDistributor) (
	 *layered.LGraphArena,
	 [][]layered.LNodeID,
	 int,
	 bool,
) {
	if .crossingsCounter == nil {
		panic("p3order: GreedyPortDistributor used before InitAfterTraversal")
	}
	if  &&  > 0 {
		.crossingsCounter.InitForCountingBetween(
			, [-1], [],
		)
	} else if ! &&  < len()-1 {
		.crossingsCounter.InitForCountingBetween(
			, [], [+1],
		)
	} else {
		 := layered.EastSide
		if  {
			 = layered.WestSide
		}
		.crossingsCounter.InitPortPositionsForInLayerCrossings(
			, [], ,
		)
	}
}

func ( *GreedyPortDistributor) (
	 *layered.LGraphArena,
	 [][]layered.LNodeID,
	 int,
	 bool,
) bool {
	 := layered.EastSide
	if  {
		 = layered.WestSide
	}
	 := false
	for ,  := range [] {
		if basegraph.Get(&.Node().Properties, layered.PortConstraintsOption).IsOrderFixed() {
			continue
		}
		 := .Node().NestedGraph
		 := len(.NodePortsOnSide(, )) > 0 &&  != nil
		if  {
			 := nodeOrderOfGraph(, *)
			 := len() - 1
			if  {
				 = 0
			}
			.hierarchicalCrossingsCounter =
				newBetweenLayerEdgeTwoNodeCrossingsCounter(, , )
		}
		if .distributePortsOnNode(, , , ) {
			 = true
		}
	}
	return 
}

func ( *GreedyPortDistributor) (
	 *layered.LGraphArena,
	 layered.LNodeID,
	 layered.PortSide,
	 bool,
) bool {
	 := append([]layered.LPortID(nil), .NodePortsOnSide(, )...)
	if  == layered.SouthSide ||  == layered.WestSide {
		reversePorts()
	}
	 := false
	for {
		 := false
		for  := 0; +1 < len(); ++ {
			if !.switchingDecreasesCrossings(
				, [], [+1], ,
			) {
				continue
			}
			 = true
			.switchPorts(, , , , +1)
			 = true
		}
		if ! {
			break
		}
	}
	if  {
		.InvalidatePortSideCache()
	}
	return 
}

func ( *GreedyPortDistributor) (
	 *layered.LGraphArena,
	,  layered.LPortID,
	 bool,
) bool {
	,  := .crossingsCounter.CountCrossingsBetweenPortsInBothOrders(
		, , ,
	)
	if  {
		,  := basegraph.TryGet(&.Port().Properties, layered.PortDummy)
		,  := basegraph.TryGet(&.Port().Properties, layered.PortDummy)
		if  &&  {
			.hierarchicalCrossingsCounter.countBothSideCrossings(, , )
			 += .hierarchicalCrossingsCounter.upperLowerCrossings
			 += .hierarchicalCrossingsCounter.lowerUpperCrossings
		}
	}
	return  > 
}

func ( *GreedyPortDistributor) (
	 *layered.LGraphArena,
	 layered.LNodeID,
	 []layered.LPortID,
	,  int,
) {
	.crossingsCounter.SwitchPorts(, [], [])
	 := .Node().Ports
	,  := -1, -1
	for ,  := range  {
		if  == [] {
			 = 
		}
		if  == [] {
			 = 
		}
	}
	if  < 0 ||  < 0 {
		panic("p3order: port side view does not belong to node")
	}
	[], [] = [], []
	[], [] = [], []
	.Node().Ports = 
}

func nodeOrderOfGraph( *layered.LGraphArena,  layered.LGraphID) [][]layered.LNodeID {
	 := make([][]layered.LNodeID, len(.Graph().Layers))
	for ,  := range .Graph().Layers {
		[] = append([]layered.LNodeID(nil), .Layer().Nodes...)
	}
	return 
}

type betweenLayerEdgeTwoNodeCrossingsCounter struct {
	upperLowerCrossings int
	lowerUpperCrossings int
	freeLayer           []layered.LNodeID
	portPositions       map[layered.LPortID]int
	easternAdjacencies  map[layered.LNodeID]*portAdjacencyList
	westernAdjacencies  map[layered.LNodeID]*portAdjacencyList
}

func newBetweenLayerEdgeTwoNodeCrossingsCounter(
	 *layered.LGraphArena,
	 [][]layered.LNodeID,
	 int,
) *betweenLayerEdgeTwoNodeCrossingsCounter {
	 := &betweenLayerEdgeTwoNodeCrossingsCounter{
		freeLayer:          append([]layered.LNodeID(nil), []...),
		portPositions:      make(map[layered.LPortID]int),
		easternAdjacencies: make(map[layered.LNodeID]*portAdjacencyList),
		westernAdjacencies: make(map[layered.LNodeID]*portAdjacencyList),
	}
	if  > 0 {
		.setPortPositionsForLayer(, [-1], layered.EastSide)
	}
	if  < len()-1 {
		.setPortPositionsForLayer(, [+1], layered.WestSide)
	}
	return 
}

func ( *betweenLayerEdgeTwoNodeCrossingsCounter) (
	 *layered.LGraphArena,
	 []layered.LNodeID,
	 layered.PortSide,
) {
	 := 0
	for ,  := range  {
		for ,  := range portsInNorthSouthEastWestOrder(, , ) {
			.portPositions[] = 
			++
		}
	}
}

func ( *betweenLayerEdgeTwoNodeCrossingsCounter) (
	 *layered.LGraphArena,
	,  layered.LNodeID,
) {
	.upperLowerCrossings = 0
	.lowerUpperCrossings = 0
	if  ==  {
		return
	}
	.addCrossings(, , , layered.WestSide)
	.addCrossings(, , , layered.EastSide)
}

func ( *betweenLayerEdgeTwoNodeCrossingsCounter) (
	 *layered.LGraphArena,
	,  layered.LNodeID,
) {
	.upperLowerCrossings = 0
	.lowerUpperCrossings = 0
	if  !=  {
		.addCrossings(, , , layered.EastSide)
	}
}

func ( *betweenLayerEdgeTwoNodeCrossingsCounter) (
	 *layered.LGraphArena,
	,  layered.LNodeID,
) {
	.upperLowerCrossings = 0
	.lowerUpperCrossings = 0
	if  !=  {
		.addCrossings(, , , layered.WestSide)
	}
}

func ( *betweenLayerEdgeTwoNodeCrossingsCounter) (
	 *layered.LGraphArena,
	,  layered.LNodeID,
	 layered.PortSide,
) {
	 := .westernAdjacencies
	if  == layered.EastSide {
		 = .easternAdjacencies
	}
	if len() == 0 {
		for ,  := range .freeLayer {
			[] = newPortAdjacencyList(
				, , , .portPositions,
			)
		}
	}
	,  := [], []
	.reset()
	.reset()
	for !.empty() && !.empty() {
		if .first() > .first() {
			.upperLowerCrossings += .currentSize
			.removeFirst()
		} else if .first() > .first() {
			.lowerUpperCrossings += .currentSize
			.removeFirst()
		} else {
			.upperLowerCrossings += .adjacenciesBelowFirst()
			.lowerUpperCrossings += .adjacenciesBelowFirst()
			.removeFirst()
			.removeFirst()
		}
	}
}

type portAdjacency struct {
	position           int
	cardinality        int
	currentCardinality int
}

type portAdjacencyList struct {
	entries      []portAdjacency
	size         int
	currentSize  int
	currentIndex int
}

func newPortAdjacencyList(
	 *layered.LGraphArena,
	 layered.LNodeID,
	 layered.PortSide,
	 map[layered.LPortID]int,
) *portAdjacencyList {
	 := &portAdjacencyList{}
	for ,  := range portsInNorthSouthEastWestOrder(, , ) {
		 := .Port().OutgoingEdges
		if  == layered.WestSide {
			 = .Port().IncomingEdges
		}
		for ,  := range  {
			if .EdgeIsSelfLoop() || .EdgeIsInLayer() {
				continue
			}
			 := *.Edge().Target
			if  == layered.WestSide {
				 = *.Edge().Source
			}
			,  := []
			if ! {
				panic("p3order: adjacent port is not in a neighboring layer")
			}
			 := len(.entries) - 1
			if  >= 0 && .entries[].position ==  {
				.entries[].cardinality++
				.entries[].currentCardinality++
			} else {
				.entries = append(.entries, portAdjacency{, 1, 1})
			}
			.size++
			.currentSize++
		}
	}
	sort.SliceStable(.entries, func(,  int) bool {
		return .entries[].position < .entries[].position
	})
	return 
}

func ( *portAdjacencyList) () {
	.currentIndex = 0
	.currentSize = .size
	if !.empty() {
		.entries[0].currentCardinality = .entries[0].cardinality
	}
}

func ( *portAdjacencyList) () bool { return .currentSize == 0 }

func ( *portAdjacencyList) () int { return .entries[.currentIndex].position }

func ( *portAdjacencyList) () int {
	return .currentSize - .entries[.currentIndex].currentCardinality
}

func ( *portAdjacencyList) () {
	if .empty() {
		return
	}
	 := &.entries[.currentIndex]
	if .currentCardinality == 1 {
		.currentIndex++
		if .currentIndex < len(.entries) {
			.entries[.currentIndex].currentCardinality =
				.entries[.currentIndex].cardinality
		}
	} else {
		.currentCardinality--
	}
	.currentSize--
}

func reversePorts( []layered.LPortID) {
	for ,  := 0, len()-1;  < ; ,  = +1, -1 {
		[], [] = [], []
	}
}