// Copyright (c) 2011, 2015 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 
	
	
)

var allowNonFlowPortsToSwitchSides = basegraph.PropertyWithDefault(
	"org.eclipse.elk.layered.allowNonFlowPortsToSwitchSides",
	func() bool { return false },
)

// PreprocessNorthSouthPorts ports ELK 0.8.1's NorthSouthPortPreprocessor.
// Every connected north/south port of a side-fixed normal node is represented
// by an in-layer dummy whose west/east ports carry the original edges through
// crossing minimization and routing.
func ( *layered.LGraphArena,  layered.LGraphID) {
	 := .Graph()
	for ,  := range .Layers {
		 := -1
		 := append([]layered.LNodeID(nil), .Layer().Nodes...)
		for ,  := range  {
			++
			 := .Node()
			 := basegraph.Get(&.Properties, layered.PortConstraintsOption)
			if .Type != layered.NormalNode || !.IsSideFixed() {
				continue
			}
			if !.IsOrderFixed() &&
				basegraph.Get(&.Properties, layered.ConsiderModelOrder) == layered.OrderingNone {
				sortNorthSouthPortList(, )
			}

			basegraph.Set(&.Properties, layered.InLayerLayoutUnit, )
			 := make([]layered.LNodeID, 0)
			 := make([]layered.LNodeID, 0)
			 := make([]layered.LNodeID, 0)

			 := .NodePortsOnSide(, layered.NorthSide)
			if basegraph.Get(&.Properties, layered.ConsiderModelOrder) != layered.OrderingNone {
				 = modelOrderNorthSouthInputReversing(, )
			}
			createNorthSouthPortDummies(
				, , , &, &, &)

			 := 
			for ,  := range  {
				.SetNodeLayerAt(, &, )
				++
				basegraph.Set(&.Node().Properties, layered.InLayerLayoutUnit, )
				 := firstNorthSouthDummyOriginPort(, )
				if !basegraph.Get(&.Port().Properties, allowNonFlowPortsToSwitchSides) {
					appendNodeProperty(&.Node().Properties,
						layered.InLayerSuccessorConstraints, )
				}
			}

			 := .NodePortsOnSide(, layered.SouthSide)
			for ,  := 0, len()-1;  < ; ,  = +1, -1 {
				[], [] = [], []
			}
			if basegraph.Get(&.Properties, layered.ConsiderModelOrder) != layered.OrderingNone {
				 = modelOrderNorthSouthInputReversing(, )
			}
			createNorthSouthPortDummies(
				, , , &, nil, &)
			for ,  := range  {
				++
				.SetNodeLayerAt(, &, )
				basegraph.Set(&.Node().Properties, layered.InLayerLayoutUnit, )
				 := firstNorthSouthDummyOriginPort(, )
				if !basegraph.Get(&.Port().Properties, allowNonFlowPortsToSwitchSides) {
					appendNodeProperty(&.Node().Properties,
						layered.InLayerSuccessorConstraints, )
				}
			}

			if len() != 0 {
				basegraph.Set(&.Node().Properties, layered.BarycenterAssociates, )
			}
		}
	}
}

// modelOrderNorthSouthInputReversing ports ELK 0.10's #1067 ordering fix.
// The Java source calls Lists.reverse(incoming).addAll(outgoing), then returns
// the underlying incoming list rather than the reverse view. Guava's reverse
// view inserts each appended value at the front, so the observable result is
// reversed outgoing ports followed by the original incoming order.
func modelOrderNorthSouthInputReversing(
	 *layered.LGraphArena,
	 []layered.LPortID,
) []layered.LPortID {
	 := make([]layered.LPortID, 0, len())
	 := make([]layered.LPortID, 0, len())
	for ,  := range  {
		if len(.Port().IncomingEdges) != 0 {
			 = append(, )
		} else {
			 = append(, )
		}
	}
	for ,  := 0, len()-1;  < ; ,  = +1, -1 {
		[], [] = [], []
	}
	return append(, ...)
}

func appendNodeProperty(
	 *basegraph.PropertyMap,
	 basegraph.Property[[]layered.LNodeID],
	 layered.LNodeID,
) {
	 := basegraph.Get(, )
	 = append(, )
	basegraph.Set(, , )
}

func firstNorthSouthDummyOriginPort( *layered.LGraphArena,  layered.LNodeID) layered.LPortID {
	 := .Node().Ports
	if len() == 0 {
		panic("layered: north/south port dummy has no ports")
	}
	,  := basegraph.TryGet(&.Port([0]).Properties, layered.OriginalElement)
	if ! || .Kind != layered.OriginLPort {
		panic("layered: north/south dummy port has no LPort origin")
	}
	return .LPort
}

func sortNorthSouthPortList( *layered.LGraphArena,  layered.LNodeID) {
	 := .Node()
	 := len(.Ports)
	, ,  := 0, , 2*
	for ,  := range .Ports {
		 := .Port()
		switch .Side {
		case layered.EastSide, layered.WestSide:
			.ID = -1
		case layered.NorthSide, layered.SouthSide:
			 := len(.IncomingEdges) > 0
			 := len(.OutgoingEdges) > 0
			switch {
			case  && :
				.ID = 
				++
			case  || !:
				.ID = 
				++
			default:
				.ID = 
				++
			}
		}
	}

	javacompat.GWTStableSort(.Ports, func(,  layered.LPortID) int {
		,  := .Port(), .Port()
		if .Side != .Side {
			return int(.Side) - int(.Side)
		}
		if .ID == .ID {
			return 0
		}
		if .Side == layered.NorthSide {
			return .ID - .ID
		}
		return .ID - .ID
	})
	.InvalidatePortSideCache()
}

func createNorthSouthPortDummies(
	 *layered.LGraphArena,
	 layered.LGraphID,
	 []layered.LPortID,
	 *[]layered.LNodeID,
	 *[]layered.LNodeID,
	 *[]layered.LNodeID,
) {
	 := make([]layered.LPortID, 0, len())
	 := make([]layered.LPortID, 0, len())
	 := make([]layered.LPortID, 0, len())
	 := make([]layered.LEdgeID, 0, len())
	 := make([]layered.LEdgeID, 0, len())

	for ,  := range  {
		 := .Port()
		for ,  := range append([]layered.LEdgeID(nil), .OutgoingEdges...) {
			 := .Edge()
			if .Source == nil || .Target == nil || .EdgeSourceNode() != .EdgeTargetNode() {
				continue
			}
			 := .Port(*.Target).Side
			if .Side ==  {
				 = append(, )
			} else if .Side == layered.NorthSide &&  == layered.SouthSide {
				 = append(, )
			}
		}
	}

	for ,  := range  {
		if  == nil {
			panic("layered: north/south self-loop encountered while processing south ports")
		}
		createNorthSouthSelfLoopDummies(
			, , , , , layered.EastSide)
	}
	for ,  := range  {
		createSameSideSelfLoopDummy(, , , )
	}

	for ,  := range  {
		 := .Port()
		 := len(.IncomingEdges) > 0
		 := len(.OutgoingEdges) > 0
		switch {
		case  && :
			 = append(, )
		case :
			 = append(, )
		case :
			 = append(, )
		}
	}

	// ELK 0.8.1's USE_NEW_APPROACH is a compile-time true constant: each
	// remaining input/output port receives its own unconstrained dummy.
	for ,  := range  {
		 := createNorthSouthPortDummy(, , &, nil, )
		* = append(*, )
	}
	for ,  := range  {
		 := createNorthSouthPortDummy(, , nil, &, )
		* = append(*, )
	}
	for ,  := range  {
		 := createNorthSouthPortDummy(, , &, &, )
		* = append(*, )
	}
}

func createNorthSouthPortDummy(
	 *layered.LGraphArena,
	 layered.LGraphID,
	,  *layered.LPortID,
	 *[]layered.LNodeID,
) layered.LNodeID {
	 := .AddNode()
	 := .Node()
	.Type = layered.NorthSouthPortNode
	basegraph.Set(&.Properties, layered.PortConstraintsOption, layered.PortConstraintsFixedPosition)
	 := 0

	if  != nil {
		 := .AddPort()
		basegraph.Set(&.Port().Properties, layered.OriginalElement, layered.LPortOrigin(*))
		basegraph.Set(&.Properties, layered.OriginalElement, layered.LNodeOrigin(*.Port(*).Node))
		.SetPortSide(, layered.WestSide)
		.SetPortNode(, &)
		for ,  := range append([]layered.LEdgeID(nil), .Port(*).IncomingEdges...) {
			.SetEdgeTarget(, &)
		}
		basegraph.Set(&.Port(*).Properties, layered.PortDummy, )
		++
	}

	if  != nil {
		 := .AddPort()
		basegraph.Set(&.Properties, layered.OriginalElement, layered.LNodeOrigin(*.Port(*).Node))
		basegraph.Set(&.Port().Properties, layered.OriginalElement, layered.LPortOrigin(*))
		.SetPortSide(, layered.EastSide)
		.SetPortNode(, &)
		for ,  := range append([]layered.LEdgeID(nil), .Port(*).OutgoingEdges...) {
			.SetEdgeSource(, &)
		}
		basegraph.Set(&.Port(*).Properties, layered.PortDummy, )
		++
	}

	basegraph.Set(&.Properties, layered.CrossingHint, )
	* = append(*, )
	return 
}

func createSameSideSelfLoopDummy(
	 *layered.LGraphArena,
	 layered.LGraphID,
	 layered.LEdgeID,
	 *[]layered.LNodeID,
) {
	 := .Edge()
	 := .AddNode()
	 := .Node()
	.Type = layered.NorthSouthPortNode
	basegraph.Set(&.Properties, layered.PortConstraintsOption, layered.PortConstraintsFixedPosition)
	basegraph.Set(&.Properties, layered.OriginalElement, layered.LEdgeOrigin())

	 := .AddPort()
	basegraph.Set(&.Port().Properties, layered.OriginalElement, layered.LPortOrigin(*.Target))
	.SetPortSide(, layered.WestSide)
	.SetPortNode(, &)
	 := .AddPort()
	basegraph.Set(&.Port().Properties, layered.OriginalElement, layered.LPortOrigin(*.Source))
	.SetPortSide(, layered.EastSide)
	.SetPortNode(, &)

	basegraph.Set(&.Port(*.Source).Properties, layered.PortDummy, )
	basegraph.Set(&.Port(*.Target).Properties, layered.PortDummy, )
	.SetEdgeSource(, nil)
	.SetEdgeTarget(, nil)
	* = append(*, )
	basegraph.Set(&.Properties, layered.CrossingHint, 2)
}

func createNorthSouthSelfLoopDummies(
	 *layered.LGraphArena,
	 layered.LGraphID,
	 layered.LEdgeID,
	,  *[]layered.LNodeID,
	 layered.PortSide,
) {
	 := .Edge()
	,  := *.Source, *.Target
	 := *.Port().Node

	 := .AddNode()
	 := .Node()
	.Type = layered.NorthSouthPortNode
	basegraph.Set(&.Properties, layered.PortConstraintsOption, layered.PortConstraintsFixedPosition)
	basegraph.Set(&.Properties, layered.OriginalElement, layered.LNodeOrigin())
	 := .AddPort()
	basegraph.Set(&.Port().Properties, layered.OriginalElement, layered.LPortOrigin())
	.SetPortSide(, )
	.SetPortNode(, &)
	basegraph.Set(&.Port().Properties, layered.PortDummy, )

	 := .AddNode()
	 := .Node()
	.Type = layered.NorthSouthPortNode
	basegraph.Set(&.Properties, layered.PortConstraintsOption, layered.PortConstraintsFixedPosition)
	basegraph.Set(&.Properties, layered.OriginalElement, layered.LNodeOrigin())
	 := .AddPort()
	basegraph.Set(&.Port().Properties, layered.OriginalElement, layered.LPortOrigin())
	.SetPortSide(, )
	.SetPortNode(, &)
	basegraph.Set(&.Port().Properties, layered.PortDummy, )

	.SetEdgeSource(, &)
	.SetEdgeTarget(, &)
	* = append([]layered.LNodeID{}, *...)
	* = append(*, )
	basegraph.Set(&.Node().Properties, layered.CrossingHint, 1)
	basegraph.Set(&.Properties, layered.CrossingHint, 1)
}