// 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 components

import (
	
	
	

	
	
)

const (
	connectedComponentsMaxIterations = 10
	connectedComponentsEpsilon       = 0.0001
)

var (
	positiveInfinity = math.Inf(1)
	negativeInfinity = math.Inf(-1)
)

// componentCompactDirection is ELK's Direction restricted to the values used
// by OneDimensionalComponentsCompaction.
type componentCompactDirection uint8

const (
	componentDirectionUndefined componentCompactDirection = iota
	componentDirectionLeft
	componentDirectionRight
	componentDirectionUp
	componentDirectionDown
)

func ( componentCompactDirection) () bool {
	return  == componentDirectionLeft ||  == componentDirectionRight
}

type componentCompactionGraph struct {
	nodes  []*componentCompactionNode
	groups []*componentCompactionGroup
}

type componentCompactionNode struct {
	id          int
	hitbox      geometry.Rectangle
	group       *componentCompactionGroup
	groupOffset geometry.Point
	constraints []*componentCompactionNode
	spacing     *float64
	start       float64
	reposition  bool
	lock        [5]bool
}

type componentCompactionGroup struct {
	nodes           []*componentCompactionNode
	reference       *componentCompactionNode
	start           float64
	delta           float64
	deltaNormalized float64
	outDegree       int
	reposition      bool
}

func ( *componentCompactionGraph) (
	 ...*componentCompactionNode,
) *componentCompactionGroup {
	 := &componentCompactionGroup{
		nodes:      append([]*componentCompactionNode(nil), ...),
		reposition: true,
	}
	for ,  := range  {
		if .group != nil {
			panic("components: compaction node belongs to another group")
		}
		.group = 
		if .reference == nil {
			.reference = 
		}
	}
	.groups = append(.groups, )
	return 
}

type compactedComponent struct {
	graphID     layered.LGraphID
	oldPosition geometry.Point
	offsetNode  *componentCompactionNode
}

// compactConnectedComponents is the no-external-extension path through ELK
// 0.8.1's ComponentsCompactor, ComponentsToCGraphTransformer, and
// OneDimensionalComponentsCompaction. Component placement with external ports
// is selected earlier by componentsProcessor and never reaches SimpleRow.
func compactConnectedComponents(
	 *layered.LGraphArena,
	 []layered.LGraphID,
	 float64,
) (geometry.Point, error) {
	 := &componentCompactionGraph{}
	 := make([]compactedComponent, 0, len())

	for ,  := range  {
		 := componentHullPoints(, )
		 := splitRectilinearHull()
		if len() == 0 {
			return geometry.Point{}, fmt.Errorf(
				"components: component %d has no rectilinear hull", )
		}

		 := make([]*componentCompactionNode, 0, len())
		for ,  := range  {
			 := &componentCompactionNode{
				hitbox:     ,
				reposition: true,
				// Components without external extensions are locked in all
				// directions by ComponentsToCGraphTransformer.setLock().
				lock: [5]bool{false, true, true, true, true},
			}
			.nodes = append(.nodes, )
			 = append(, )
		}
		.addGroup(...)
		 = append(, compactedComponent{
			graphID:     ,
			oldPosition: [0].Position(),
			offsetNode:  [0],
		})
	}

	 := newComponentOneDimensionalCompactor(, )
	.compactComponents()
	.changeDirection(componentDirectionLeft)

	 := geometry.Point{X: positiveInfinity, Y: positiveInfinity}
	 := geometry.Point{X: negativeInfinity, Y: negativeInfinity}
	for ,  := range .nodes {
		.X = math.Min(.X, .hitbox.X)
		.Y = math.Min(.Y, .hitbox.Y)
		.X = math.Max(.X, .hitbox.MaxX())
		.Y = math.Max(.Y, .hitbox.MaxY())
	}
	 := geometry.Point{X: -.X, Y: -.Y}
	for ,  := range  {
		 := .offsetNode.hitbox.Position()
		 := .Sub(.oldPosition).Add()
		offsetGraph(, .graphID, )
	}

	return .Sub(), nil
}

// componentHullPoints is ComponentsCompactor.componentHullPoints(). Node
// margins already include labels, ports, and their labels at this phase.
func componentHullPoints(
	 *layered.LGraphArena,
	 layered.LGraphID,
) []hullPoint {
	 := make([]hullPoint, 0)
	for ,  := range .Graph().LayerlessNodes {
		 := .Node()
		if .Type == layered.ExternalPortNode {
			continue
		}
		 := .Position.X - .Margin.Left
		 := .Position.Y - .Margin.Top
		 := .Position.X + .Size.X + .Margin.Right
		 := .Position.Y + .Size.Y + .Margin.Bottom
		 = append(,
			newHullPoint(, ),
			newHullPoint(, ),
			newHullPoint(, ),
			newHullPoint(, ),
		)

		for ,  := range .NodeOutgoingEdges() {
			if .Node(.EdgeSourceNode()).Type == layered.ExternalPortNode ||
				.Node(.EdgeTargetNode()).Type == layered.ExternalPortNode {
				continue
			}
			for ,  := range .Edge().BendPoints {
				 = append(, newHullPoint(.X, .Y))
			}
		}
	}
	return 
}

type componentOneDimensionalCompactor struct {
	graph           *componentCompactionGraph
	direction       componentCompactDirection
	spacing         float64
	lockingStrategy func(*componentCompactionNode, componentCompactDirection) bool
}

func newComponentOneDimensionalCompactor(
	 *componentCompactionGraph,
	 float64,
) *componentOneDimensionalCompactor {
	 := &componentOneDimensionalCompactor{
		graph:   ,
		spacing: ,
	}
	.lockingStrategy = func(
		 *componentCompactionNode,
		 componentCompactDirection,
	) bool {
		return .group.outDegree != 0
	}
	.calculateGroupOffsets()
	return 
}

func ( *componentOneDimensionalCompactor) () {
	// OneDimensionalComponentsCompaction repeats alternating horizontal and
	// vertical passes until the normalized movement converges.
	 := 0
	for {
		 := .compactRun()
		++
		if !(( < 2 ||  > connectedComponentsEpsilon) &&
			 < connectedComponentsMaxIterations) {
			break
		}
	}

	// The final pass aligns the drawing's top-left. With no external
	// extensions every directional lock is true.
	.lockingStrategy = func(
		 *componentCompactionNode,
		 componentCompactDirection,
	) bool {
		return .lock[] ||
			(.group.outDegree != 0 && .lock[])
	}
	.compactRun()
}

// compactRun is OneDimensionalComponentsCompaction.compact(int), with the
// extension and placeholder collections empty in SimpleRow's supported path.
func ( *componentOneDimensionalCompactor) () float64 {
	for ,  := range .graph.groups {
		.delta = 0
		.deltaNormalized = 0
	}

	.calculateGroupOffsets()
	.forceConstraintsRecalculation()
	.changeDirection(componentDirectionLeft)
	.compact()
	.changeDirection(componentDirectionRight)
	.applyLockingStrategy()
	.compact()
	.changeDirection(componentDirectionLeft)
	.applyLockingStrategy()
	.compact()
	.changeDirection(componentDirectionLeft)

	.calculateGroupOffsets()
	 := 0.0
	for ,  := range .graph.groups {
		 += math.Abs(.deltaNormalized)
		.delta = 0
		.deltaNormalized = 0
	}

	.changeDirection(componentDirectionUp)
	.forceConstraintsRecalculation()
	.compact()
	.changeDirection(componentDirectionDown)
	.applyLockingStrategy()
	.compact()
	.changeDirection(componentDirectionUp)
	.applyLockingStrategy()
	.compact()
	.changeDirection(componentDirectionLeft)
	.forceConstraintsRecalculation()

	for ,  := range .graph.groups {
		 += math.Abs(.deltaNormalized)
	}
	return 
}

func ( *componentOneDimensionalCompactor) () {
	if .direction == componentDirectionUndefined {
		.changeDirection(componentDirectionLeft)
	}
	for ,  := range .graph.groups {
		.outDegree = 0
	}
	for ,  := range .graph.nodes {
		.start = negativeInfinity
		for ,  := range .constraints {
			.group.outDegree++
		}
	}
	.longestPathCompact()
	for ,  := range .graph.nodes {
		.reposition = true
	}
}

func ( *componentOneDimensionalCompactor) (
	 componentCompactDirection,
) {
	if  == .direction {
		return
	}
	 := .direction
	.direction = 

	switch  {
	case componentDirectionUndefined:
		switch  {
		case componentDirectionLeft:
			.calculateConstraints()
		case componentDirectionRight:
			.mirrorHitboxes()
			.calculateConstraints()
		case componentDirectionUp:
			.transposeHitboxes()
			.calculateConstraints()
		case componentDirectionDown:
			.transposeHitboxes()
			.mirrorHitboxes()
			.calculateConstraints()
		}
	case componentDirectionLeft:
		switch  {
		case componentDirectionRight:
			.mirrorHitboxes()
			.reverseConstraints()
		case componentDirectionUp:
			.transposeHitboxes()
			.calculateConstraints()
		case componentDirectionDown:
			.transposeHitboxes()
			.mirrorHitboxes()
			.calculateConstraints()
		}
	case componentDirectionRight:
		switch  {
		case componentDirectionLeft:
			.mirrorHitboxes()
			.reverseConstraints()
		case componentDirectionUp:
			.mirrorHitboxes()
			.transposeHitboxes()
			.calculateConstraints()
		case componentDirectionDown:
			.mirrorHitboxes()
			.transposeHitboxes()
			.mirrorHitboxes()
			.calculateConstraints()
		}
	case componentDirectionUp:
		switch  {
		case componentDirectionLeft:
			.transposeHitboxes()
			.calculateConstraints()
		case componentDirectionRight:
			.transposeHitboxes()
			.mirrorHitboxes()
			.calculateConstraints()
		case componentDirectionDown:
			.mirrorHitboxes()
			.reverseConstraints()
		}
	case componentDirectionDown:
		switch  {
		case componentDirectionLeft:
			.mirrorHitboxes()
			.transposeHitboxes()
			.calculateConstraints()
		case componentDirectionRight:
			.mirrorHitboxes()
			.transposeHitboxes()
			.mirrorHitboxes()
			.calculateConstraints()
		case componentDirectionUp:
			.mirrorHitboxes()
			.reverseConstraints()
		}
	}
}

func ( *componentOneDimensionalCompactor) () {
	for ,  := range .graph.groups {
		.reposition = true
	}
	for ,  := range .graph.nodes {
		.reposition = .lockingStrategy(, .direction)
		.group.reposition = .group.reposition &&
			.lockingStrategy(, .direction)
	}
}

func ( *componentOneDimensionalCompactor) () {
	for ,  := range .graph.groups {
		.reference = nil
		for ,  := range .nodes {
			.groupOffset = geometry.Point{}
			if .reference == nil || .hitbox.X < .reference.hitbox.X {
				.reference = 
			}
		}
		for ,  := range .nodes {
			.groupOffset.X = .hitbox.X - .reference.hitbox.X
			.groupOffset.Y = .hitbox.Y - .reference.hitbox.Y
		}
	}
}

func ( *componentOneDimensionalCompactor) () {
	for ,  := range .graph.nodes {
		.hitbox.X = -.hitbox.X - .hitbox.Width
	}
	.calculateGroupOffsets()
}

func ( *componentOneDimensionalCompactor) () {
	for ,  := range .graph.nodes {
		.hitbox.X, .hitbox.Y = .hitbox.Y, .hitbox.X
		.hitbox.Width, .hitbox.Height = .hitbox.Height, .hitbox.Width
		.groupOffset.X, .groupOffset.Y =
			.groupOffset.Y, .groupOffset.X
	}
	.calculateGroupOffsets()
}

func ( *componentOneDimensionalCompactor) () {
	.calculateConstraints()
}

func ( *componentOneDimensionalCompactor) () {
	for ,  := range .graph.nodes {
		.constraints = .constraints[:0]
	}
	.calculateScanlineConstraints()
	.calculateConstraintsForGroups()
}

func ( *componentOneDimensionalCompactor) () {
	for ,  := range .graph.groups {
		.outDegree = 0
	}
	for ,  := range .graph.groups {
		for ,  := range .nodes {
			for ,  := range .constraints {
				if .group !=  {
					.group.outDegree++
				}
			}
		}
	}
}

func ( *componentOneDimensionalCompactor) () {
	 := make(map[*componentCompactionNode][]*componentCompactionNode,
		len(.graph.nodes))
	for ,  := range .graph.nodes {
		.start = negativeInfinity
		for ,  := range .constraints {
			[] = append([], )
		}
	}
	for ,  := range .graph.nodes {
		.constraints = []
	}
	.calculateConstraintsForGroups()
}

func ( *componentOneDimensionalCompactor) () {
	 := positiveInfinity
	for ,  := range .graph.nodes {
		 = math.Min(,
			.group.reference.hitbox.X+.groupOffset.X)
	}

	 := make([]*componentCompactionGroup, 0, len(.graph.groups))
	for ,  := range .graph.groups {
		.start = 
		if .outDegree == 0 {
			 = append(, )
		}
	}

	for len() > 0 {
		 := [0]
		 = [1:]
		 := .reference.hitbox.X

		for ,  := range .nodes {
			 := .start + .groupOffset.X
			if .reposition || .hitbox.X <  {
				.start = 
			} else {
				.start = .hitbox.X
			}
		}
		 -= .reference.start
		.delta += 
		if .direction == componentDirectionRight ||
			.direction == componentDirectionDown {
			.deltaNormalized += 
		} else {
			.deltaNormalized -= 
		}

		for ,  := range .nodes {
			for ,  := range .constraints {
				 := .spacing
				if .spacing != nil && *.spacing <  {
					 = *.spacing
				}
				if .spacing != nil && *.spacing <  {
					 = *.spacing
				}
				.group.start = math.Max(.group.start,
					.start+.hitbox.Width+-
						.groupOffset.X)
				if !.reposition {
					.group.start = math.Max(.group.start,
						.hitbox.X-.groupOffset.X)
				}
				.group.outDegree--
				if .group.outDegree == 0 {
					 = append(, .group)
				}
			}
		}
	}

	for ,  := range .graph.nodes {
		.hitbox.X = .start
	}
}

type componentCompactionTimestamp struct {
	node *componentCompactionNode
	low  bool
}

func ( componentCompactionTimestamp) () float64 {
	if .low {
		return .node.hitbox.Y
	}
	return .node.hitbox.Y + .node.hitbox.Height
}

type componentIntervalSet []*componentCompactionNode

func componentIntervalCenter( *componentCompactionNode) float64 {
	return .hitbox.X + .hitbox.Width/2
}

func ( componentIntervalSet) ( float64) int {
	return sort.Search(len(), func( int) bool {
		return componentIntervalCenter([]) >= 
	})
}

func ( componentIntervalSet) ( *componentCompactionNode) *componentCompactionNode {
	 := .firstAtOrAfter(componentIntervalCenter())
	if  == 0 {
		return nil
	}
	return [-1]
}

func ( componentIntervalSet) ( *componentCompactionNode) *componentCompactionNode {
	 := componentIntervalCenter()
	 := sort.Search(len(), func( int) bool {
		return componentIntervalCenter([]) > 
	})
	if  == len() {
		return nil
	}
	return []
}

func ( *componentIntervalSet) ( *componentCompactionNode) {
	 := componentIntervalCenter()
	 := .firstAtOrAfter()
	// TreeSet's center-only comparator treats equal centers as the same key.
	if  < len(*) && componentIntervalCenter((*)[]) ==  {
		return
	}
	* = append(*, nil)
	copy((*)[+1:], (*)[:])
	(*)[] = 
}

func ( *componentIntervalSet) ( *componentCompactionNode) {
	 := componentIntervalCenter()
	 := .firstAtOrAfter()
	if  == len(*) || componentIntervalCenter((*)[]) !=  {
		return
	}
	copy((*)[:], (*)[+1:])
	* = (*)[:len(*)-1]
}

// calculateScanlineConstraints is ELK 0.8.1's
// ScanlineConstraintCalculator with spacingFun(n) == 0.
func ( *componentOneDimensionalCompactor) () {
	 := make([]componentCompactionTimestamp, 0, len(.graph.nodes)*2)
	for ,  := range .graph.nodes {
		.id = 
		 = append(,
			componentCompactionTimestamp{node: , low: true},
			componentCompactionTimestamp{node: , low: false},
		)
	}
	sort.SliceStable(, func(,  int) bool {
		 := [].coordinate()
		 := [].coordinate()
		if  !=  {
			return  < 
		}
		// At equal positions the high (deletion) timestamp precedes low.
		return ![].low && [].low
	})

	 := componentIntervalSet{}
	 := make([]*componentCompactionNode, len(.graph.nodes))
	for ,  := range  {
		 := .node
		if .low {
			.add()
			[.id] = .lower()
			if  := .higher();  != nil {
				[.id] = 
			}
			continue
		}

		 := .lower()
		if  != nil &&  == [.id] && .group != .group {
			.constraints = append(.constraints, )
		}
		 := .higher()
		if  != nil && [.id] ==  && .group != .group {
			.constraints = append(.constraints, )
		}
		.remove()
	}
}