// Copyright (c) 2019 Kiel University and others.
//
// SPDX-License-Identifier: EPL-2.0

package ordering

import (
	
	

	basegraph 
	
	
)

type portSideArea uint8

const (
	areaStart portSideArea = iota
	areaMiddle
	areaEnd
)

type addMode uint8

const (
	appendPorts addMode = iota
	prependPorts
)

type restorer struct {
	loopsByType [5][]*loops.HyperLoop
	targetAreas [5][3][]*loops.Port
}

// RestorePorts ports ELK 0.8.1's PortRestorer, including its historical
// split-index predicate and the precise burst order used to merge regular and
// restored ports.
func ( *layered.LGraphArena,  *loops.Holder) error {
	 := &restorer{}
	for ,  := range .HyperLoops {
		.loopsByType[.Type] = append(.loopsByType[.Type], )
	}
	if  := .processOneSideLoops(, );  != nil {
		return 
	}
	.processTwoSideCornerLoops()
	.processThreeSideLoops()
	.processFourSideLoops()
	.processTwoSideOpposingLoops()
	.restore(, )
	for  := layered.UndefinedSide;  <= layered.WestSide; ++ {
		for  := areaStart;  <= areaEnd; ++ {
			for ,  := range .targetAreas[][] {
				.Hidden = false
			}
		}
	}
	.PortsHidden = false
	return nil
}

func ( *restorer) ( *layered.LGraphArena,  *loops.Holder) error {
	 := basegraph.Get(&.Node(.LNode).Properties, layered.SelfLoopOrdering)
	if  == layered.SelfLoopOrderingReverseStacked {
		 := .loopsByType[loops.OneSide]
		for ,  := 0, len()-1;  < ; ,  = +1, -1 {
			[], [] = [], []
		}
	}
	for ,  := range .loopsByType[loops.OneSide] {
		 := .Port(.Ports[0].LPort).Side
		 := append([]*loops.Port(nil), .Ports...)
		sort.SliceStable(, func(,  int) bool {
			return [].NetFlow() < [].NetFlow()
		})
		switch  {
		case layered.SelfLoopOrderingSequenced:
			.addToTargetArea(, , areaMiddle, appendPorts)
		case layered.SelfLoopOrderingStacked, layered.SelfLoopOrderingReverseStacked:
			 := computePortListSplitIndex()
			.addToTargetArea([:], , areaMiddle, prependPorts)
			.addToTargetArea([:], , areaMiddle, appendPorts)
		default:
			return fmt.Errorf("unsupported self-loop ordering strategy")
		}
	}
	return nil
}

func computePortListSplitIndex( []*loops.Port) int {
	 := 0
	for  < len() && [].NetFlow() <= 0 {
		++
	}
	if  > 0 &&  < len()-1 {
		return 
	}

	// The 0.8.1 source says non-negative but tests > 0 here as well, and its
	// second bound references positive. Both quirks are observable.
	 := 0
	for  < len() && [].NetFlow() <= 0 {
		++
	}
	if  > 0 &&  < len()-1 {
		return 
	}
	return len() / 2
}

func ( *restorer) () {
	for ,  := range .loopsByType[loops.TwoSidesCorner] {
		 := SortedTwoSideLoopPortSides()
		.addLoopToTargetArea(, [0], areaEnd, prependPorts)
		.addLoopToTargetArea(, [1], areaStart, appendPorts)
	}
}

func ( *restorer) () {
	for ,  := range .loopsByType[loops.TwoSidesOpposing] {
		 := SortedTwoSideLoopPortSides()
		.addLoopToTargetArea(, [0], areaEnd, prependPorts)
		.addLoopToTargetArea(, [1], areaStart, appendPorts)
	}
}

func ( *loops.HyperLoop) [2]layered.PortSide {
	 := .PortSides()
	 := [2]layered.PortSide{[0], [1]}
	if [0] == layered.NorthSide && [1] == layered.WestSide {
		[0], [1] = [1], [0]
	}
	return 
}

var (
	constellationNES = [3]layered.PortSide{layered.NorthSide, layered.EastSide, layered.SouthSide}
	constellationESW = [3]layered.PortSide{layered.EastSide, layered.SouthSide, layered.WestSide}
	constellationSWN = [3]layered.PortSide{layered.SouthSide, layered.WestSide, layered.NorthSide}
	constellationWNE = [3]layered.PortSide{layered.WestSide, layered.NorthSide, layered.EastSide}
)

func ( *restorer) () {
	for ,  := range .loopsByType[loops.ThreeSides] {
		 := determineLoopConstellation()
		.addLoopToTargetArea(, [0], areaEnd, prependPorts)
		.addLoopToTargetArea(, [1], areaMiddle, appendPorts)
		.addLoopToTargetArea(, [2], areaStart, appendPorts)
	}
}

func determineLoopConstellation( *loops.HyperLoop) [3]layered.PortSide {
	switch {
	case !.HasPortsOnSide(layered.NorthSide):
		return constellationESW
	case !.HasPortsOnSide(layered.EastSide):
		return constellationSWN
	case !.HasPortsOnSide(layered.SouthSide):
		return constellationWNE
	default:
		return constellationNES
	}
}

func ( *restorer) () {
	for ,  := range .loopsByType[loops.FourSides] {
		for ,  := range .PortSides() {
			.addLoopToTargetArea(, , areaMiddle, appendPorts)
		}
	}
}

func ( *restorer) (
	 *loops.HyperLoop,
	 layered.PortSide,
	 portSideArea,
	 addMode,
) {
	.addToTargetArea(.PortsBySide[], , , )
}

func ( *restorer) (
	 []*loops.Port,
	 layered.PortSide,
	 portSideArea,
	 addMode,
) {
	 := make([]*loops.Port, 0, len())
	for ,  := range  {
		if .Hidden {
			 = append(, )
		}
	}
	for ,  := 0, len()-1;  < ; ,  = +1, -1 {
		[], [] = [], []
	}
	if  == prependPorts {
		 = append(, .targetAreas[][]...)
		.targetAreas[][] = 
	} else {
		.targetAreas[][] = append(.targetAreas[][], ...)
	}
}

func ( *restorer) ( *layered.LGraphArena,  *loops.Holder) {
	 := .Node(.LNode)
	 := append([]layered.LPortID(nil), .Ports...)
	 := make([]layered.LPortID, 0, len()+len(.Ports))
	 := 0

	 := func( layered.PortSide,  portSideArea) {
		for ,  := range .targetAreas[][] {
			 := .LPort
			 := .LNode
			.Port().Node = &
			 = append(, )
		}
	}
	 := func( func(layered.LPortID) bool) {
		for  < len() {
			 := []
			if !() {
				return
			}
			 = append(, )
			++
		}
	}

	(layered.NorthSide, areaStart)
	(func( layered.LPortID) bool {
		return .Port().Side == layered.NorthSide && .hasWestConnections(, )
	})
	(layered.NorthSide, areaMiddle)
	(func( layered.LPortID) bool { return .Port().Side == layered.NorthSide })
	(layered.NorthSide, areaEnd)

	(layered.EastSide, areaStart)
	(layered.EastSide, areaMiddle)
	(func( layered.LPortID) bool { return .Port().Side == layered.EastSide })
	(layered.EastSide, areaEnd)

	(layered.SouthSide, areaStart)
	(func( layered.LPortID) bool {
		return .Port().Side == layered.SouthSide && .hasEastConnections(, )
	})
	(layered.SouthSide, areaMiddle)
	(func( layered.LPortID) bool { return .Port().Side == layered.SouthSide })
	(layered.SouthSide, areaEnd)

	(layered.WestSide, areaStart)
	(func( layered.LPortID) bool { return .Port().Side == layered.WestSide })
	(layered.WestSide, areaMiddle)
	(layered.WestSide, areaEnd)

	.Ports = 
	.InvalidatePortSideCache(.LNode)
}

func ( *restorer) ( *layered.LGraphArena,  layered.LPortID) bool {
	 := northSouthPortConnectionSides(, )
	return [layered.WestSide] || [layered.WestSide] && [layered.EastSide]
}

func ( *restorer) ( *layered.LGraphArena,  layered.LPortID) bool {
	return northSouthPortConnectionSides(, )[layered.EastSide]
}

func northSouthPortConnectionSides( *layered.LGraphArena,  layered.LPortID) [5]bool {
	var  [5]bool
	,  := basegraph.TryGet(&.Port().Properties, layered.PortDummy)
	if ! {
		return 
	}
	for ,  := range .Node().Ports {
		,  := basegraph.TryGet(&.Port().Properties, layered.OriginalElement)
		if  && .Kind == layered.OriginLPort && .LPort ==  &&
			len(.PortConnectedEdges()) != 0 {
			[.Port().Side] = true
		}
	}
	return 
}