// Copyright (c) 2016, 2018 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 p3order

import (
	

	
)

const (
	inLayerLayoutUnitPropertyID = "inLayerLayoutUnit"
	portDummyPropertyID         = "portDummy"
	originPropertyID            = "origin"
)

// CrossingsCounter is the exact ELK 0.8.1 reduction of between-layer,
// in-layer, and north/south crossings to inversion counting. Port IDs are
// scratch indices into PortPositions; AllCrossingsCounter assigns them in
// Java traversal order.
type CrossingsCounter struct {
	PortPositions     []int
	indexTree         *binaryIndexedTree
	ends              []int
	nodeCardinalities []int
}

func ( []int) *CrossingsCounter {
	return &CrossingsCounter{PortPositions: }
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	,  []layered.LNodeID,
) int {
	 := .initPortPositionsCounterClockwise(, , )
	.indexTree = newBinaryIndexedTree(len())
	return .countCrossingsOnPorts(, )
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	 []layered.LNodeID,
	 layered.PortSide,
) int {
	 := .InitPortPositionsForInLayerCrossings(, , )
	return .countInLayerCrossingsOnPorts(, )
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	 []layered.LNodeID,
) int {
	 := .initPositionsForNorthSouthCounting(, )
	.indexTree = newBinaryIndexedTree(len())
	return .countNorthSouthCrossingsOnPorts(, )
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	,  layered.LPortID,
) (int, int) {
	 := .connectedPortsSortedByPosition(, , )
	 := .countCrossingsOnPorts(, )
	.indexTree.clear()
	.SwitchPorts(, , )
	sort.SliceStable(, func(,  int) bool {
		return .positionOf(, []) < .positionOf(, [])
	})
	 := .countCrossingsOnPorts(, )
	.indexTree.clear()
	.SwitchPorts(, , )
	return , 
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	,  layered.LNodeID,
	 layered.PortSide,
) (int, int) {
	 := .connectedInLayerPortsSortedByPosition(, , , )
	 := .countInLayerCrossingsOnPorts(, )
	.SwitchNodes(, , , )
	.indexTree.clear()
	sort.SliceStable(, func(,  int) bool {
		return .positionOf(, []) < .positionOf(, [])
	})
	 := .countInLayerCrossingsOnPorts(, )
	.SwitchNodes(, , , )
	.indexTree.clear()
	return , 
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	,  []layered.LNodeID,
) {
	 := .initPortPositionsCounterClockwise(, , )
	.indexTree = newBinaryIndexedTree(len())
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	 []layered.LNodeID,
	 layered.PortSide,
) []layered.LPortID {
	 := make([]layered.LPortID, 0)
	.initPositions(, , &, , true, true)
	.indexTree = newBinaryIndexedTree(len())
	return 
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	,  layered.LPortID,
) {
	,  := .Port().ID, .Port().ID
	.PortPositions[], .PortPositions[] =
		.PortPositions[], .PortPositions[]
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	,  layered.LNodeID,
	 layered.PortSide,
) {
	for ,  := range portsInNorthSouthEastWestOrder(, , ) {
		 := .Port().ID
		.PortPositions[] += .nodeCardinalities[.Node().ID]
	}
	for ,  := range portsInNorthSouthEastWestOrder(, , ) {
		 := .Port().ID
		.PortPositions[] -= .nodeCardinalities[.Node().ID]
	}
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	,  layered.LNodeID,
	 layered.PortSide,
) []layered.LPortID {
	 := make(map[int]layered.LPortID)
	for ,  := range []layered.LNodeID{, } {
		for ,  := range portsInNorthSouthEastWestOrder(, , ) {
			for ,  := range .PortConnectedEdges() {
				if .EdgeIsSelfLoop() {
					continue
				}
				[.positionOf(, )] = 
				if .EdgeIsInLayer() {
					 := .EdgeOtherPort(, )
					[.positionOf(, )] = 
				}
			}
		}
	}
	return portsSortedByPosition()
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	,  layered.LPortID,
) []layered.LPortID {
	 := make(map[int]layered.LPortID)
	for ,  := range []layered.LPortID{, } {
		[.positionOf(, )] = 
		for ,  := range .PortConnectedEdges() {
			 := .Edge()
			if .Source != nil && .Target != nil && *.Source == *.Target {
				continue
			}
			 := .EdgeOtherPort(, )
			[.positionOf(, )] = 
		}
	}
	return portsSortedByPosition()
}

func portsSortedByPosition( map[int]layered.LPortID) []layered.LPortID {
	 := make([]int, 0, len())
	for  := range  {
		 = append(, )
	}
	sort.Ints()
	 := make([]layered.LPortID, 0, len())
	for ,  := range  {
		 = append(, [])
	}
	return 
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	 []layered.LPortID,
) int {
	 := 0
	for ,  := range  {
		 := .positionOf(, )
		.indexTree.removeAll()
		for ,  := range .PortConnectedEdges() {
			 := .EdgeOtherPort(, )
			 := .positionOf(, )
			if  >  {
				 += .indexTree.rank()
				.ends = append(.ends, )
			}
		}
		.addEnds()
	}
	return 
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	 []layered.LPortID,
) int {
	 := 0
	for ,  := range  {
		 := .positionOf(, )
		.indexTree.removeAll()
		 := 0
		for ,  := range .PortConnectedEdges() {
			if .EdgeIsInLayer() {
				 := .positionOf(, .EdgeOtherPort(, ))
				if  >  {
					 += .indexTree.rank()
					.ends = append(.ends, )
				}
			} else {
				++
			}
		}
		 += .indexTree.size * 
		.addEnds()
	}
	return 
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	 []layered.LPortID,
) int {
	type  struct {
		   layered.LPortID
		 int
	}
	 := 0
	for ,  := range  {
		 := .positionOf(, )
		.indexTree.removeAll()
		 := make([], 0, 2)
		 := *.Port().Node
		switch .Node().Type {
		case layered.NormalNode:
			if ,  := rawNodeID(&.Port().Properties, portDummyPropertyID);  {
				for ,  := range .Node().Ports {
					 = append(, {, len(.PortConnectedEdges())})
				}
			}
		case layered.LongEdgeNode:
			for ,  := range .Node().Ports {
				if  !=  {
					 = append(, {, len(.PortConnectedEdges())})
					break
				}
			}
		case layered.NorthSouthPortNode:
			if ,  := rawPortID(&.Port().Properties, originPropertyID);  {
				 = append(, {, len(.PortConnectedEdges())})
			}
		}

		for ,  := range  {
			 := .positionOf(, .)
			if  >  {
				 += .indexTree.rank() * .
				.ends = append(.ends, )
			}
		}
		.addEnds()
	}
	return 
}

func ( *CrossingsCounter) () {
	for len(.ends) > 0 {
		 := len(.ends) - 1
		.indexTree.add(.ends[])
		.ends = .ends[:]
	}
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	 []layered.LNodeID,
	 *[]layered.LPortID,
	 layered.PortSide,
	,  bool,
) {
	 := len(*)
	if  {
		.nodeCardinalities = make([]int, len())
	}
	, ,  := 0, len(), 1
	if ! {
		, ,  = len()-1, -1, -1
	}
	for  := ;  != ;  +=  {
		 := []
		 := .portsForDirection(, , , )
		if  {
			.nodeCardinalities[.Node().ID] = len()
		}
		for ,  := range  {
			.PortPositions[.Port().ID] = 
			++
		}
		* = append(*, ...)
	}
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	,  []layered.LNodeID,
) []layered.LPortID {
	 := make([]layered.LPortID, 0)
	.initPositions(, , &, layered.EastSide, true, false)
	.initPositions(, , &, layered.WestSide, false, false)
	return 
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	 []layered.LNodeID,
) []layered.LPortID {
	 := make([]layered.LPortID, 0)
	 := make([]layered.LNodeID, 0)
	var  *layered.LNodeID
	 := 0
	for ,  := range  {
		if layoutUnitChanged(, , ) {
			 = .emptyStack(, &, &, layered.EastSide, )
		}
		if ,  := rawNodeID(&.Node().Properties, inLayerLayoutUnitPropertyID);  {
			 := 
			 = &
		}

		switch .Node().Type {
		case layered.NormalNode:
			for ,  := range portsWithDummy(, , layered.NorthSide) {
				.PortPositions[.Port().ID] = 
				++
				 = append(, )
			}
			 = .emptyStack(, &, &, layered.EastSide, )
			for ,  := range portsWithDummy(, , layered.SouthSide) {
				.PortPositions[.Port().ID] = 
				++
				 = append(, )
			}
		case layered.NorthSouthPortNode:
			if  := .NodePortSideView(, layered.WestSide); len() > 0 {
				 := [0]
				.PortPositions[.Port().ID] = 
				++
				 = append(, )
			}
			if len(.NodePortSideView(, layered.EastSide)) > 0 {
				 = append(, )
			}
		case layered.LongEdgeNode:
			for ,  := range .NodePortSideView(, layered.WestSide) {
				.PortPositions[.Port().ID] = 
				++
				 = append(, )
			}
			for range .NodePortSideView(, layered.EastSide) {
				 = append(, )
			}
		}
	}
	.emptyStack(, &, &, layered.EastSide, )
	return 
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	 *[]layered.LNodeID,
	 *[]layered.LPortID,
	 layered.PortSide,
	 int,
) int {
	for len(*) > 0 {
		 := len(*) - 1
		 := (*)[]
		* = (*)[:]
		 := .NodePortSideView(, )[0]
		.PortPositions[.Port().ID] = 
		++
		* = append(*, )
	}
	return 
}

func ( *CrossingsCounter) (
	 *layered.LGraphArena,
	 layered.LNodeID,
	 layered.PortSide,
	 bool,
) []layered.LPortID {
	 := .NodePortSideView(, )
	 :=  == layered.EastSide && ! ||  != layered.EastSide && 
	if ! {
		return 
	}
	 := append([]layered.LPortID(nil), ...)
	for ,  := 0, len()-1;  < ; ,  = +1, -1 {
		[], [] = [], []
	}
	return 
}

func ( *CrossingsCounter) ( *layered.LGraphArena,  layered.LPortID) int {
	return .PortPositions[.Port().ID]
}

func portsWithDummy(
	 *layered.LGraphArena,
	 layered.LNodeID,
	 layered.PortSide,
) []layered.LPortID {
	 := make([]layered.LPortID, 0)
	for ,  := range .NodePortSideView(, ) {
		if .Port().Properties.Has(portDummyPropertyID) {
			 = append(, )
		}
	}
	return 
}

func layoutUnitChanged(
	 *layered.LGraphArena,
	 *layered.LNodeID,
	 layered.LNodeID,
) bool {
	if  == nil || * ==  || !.Node().Properties.Has(inLayerLayoutUnitPropertyID) {
		return false
	}
	,  := rawNodeID(&.Node().Properties, inLayerLayoutUnitPropertyID)
	return  &&  != *
}

type rawPropertyMap interface {
	Raw(string) (any, bool)
}

func rawNodeID( rawPropertyMap,  string) (layered.LNodeID, bool) {
	,  := .Raw()
	if ! {
		return 0, false
	}
	switch typed := .(type) {
	case layered.LNodeID:
		return , true
	case *layered.LNodeID:
		if  != nil {
			return *, true
		}
		return 0, false
	default:
		return 0, false
	}
}

func rawPortID( rawPropertyMap,  string) (layered.LPortID, bool) {
	,  := .Raw()
	if ! {
		return 0, false
	}
	switch typed := .(type) {
	case layered.LPortID:
		return , true
	case *layered.LPortID:
		if  != nil {
			return *, true
		}
		return 0, false
	case layered.Origin:
		if .Kind == layered.OriginLPort {
			return .LPort, true
		}
		return 0, false
	default:
		return 0, false
	}
}