// Copyright (c) 2012, 2017 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 includeLabel = basegraph.PropertyWithDefault("edgelabelcenterednessanalysis.includelabel", func() bool { return false })

type labelDummyInfo struct {
	labelDummy           layered.LNodeID
	placement            layered.CenterEdgeLabelPlacementStrategy
	leftLongEdgeDummies  []layered.LNodeID
	rightLongEdgeDummies []layered.LNodeID
	leftmostLayerIndex   int
	rightmostLayerIndex  int
}

func ( *labelDummyInfo) () int {
	return .rightmostLayerIndex - .leftmostLayerIndex + 1
}

func ( *labelDummyInfo) ( int) layered.LNodeID {
	if  < len(.leftLongEdgeDummies) {
		return .leftLongEdgeDummies[]
	}
	if  == len(.leftLongEdgeDummies) {
		return .labelDummy
	}
	return .rightLongEdgeDummies[-len(.leftLongEdgeDummies)-1]
}

// SwitchLabelDummies moves center-label dummies to the layer selected by the
// configured ELK strategy while preserving each layer's crossing-minimized
// node position.
func ( *layered.LGraphArena,  layered.LGraphID) {
	 := .Graph()
	for ,  := range .Layers {
		.Layer().ID = 
	}
	 := basegraph.Get(&.Properties, layered.CenterEdgeLabelPlacement)
	 := make([][]*labelDummyInfo, int(layered.CenterLabelCenterLayer)+1)
	for ,  := range .Layers {
		for ,  := range .Layer().Nodes {
			if .Node().Type != layered.LabelNode {
				continue
			}
			 := gatherLabelDummyInfo(, , )
			[int(.placement)] = append([int(.placement)], )
		}
	}

	 := make([]float64, len(.Layers))
	if hasWidthBasedLabelStrategy() {
		for ,  := range .Layers {
			if directionIsVertical(basegraph.Get(&.Properties, layered.DirectionOption)) {
				continue
			}
			for ,  := range .Layer().Nodes {
				if .Node().Type == layered.NormalNode {
					[] = math.Max([], .Node().Size.X)
				}
			}
		}
	}

	// ELK iterates enum values twice: non-width strategies first, then width
	// strategies. Declaration order is observable.
	for  := layered.CenterLabelMedianLayer;  <= layered.CenterLabelCenterLayer; ++ {
		if !labelStrategyUsesWidth() {
			processLabelDummyStrategy(, , [int()], , )
		}
	}
	for  := layered.CenterLabelMedianLayer;  <= layered.CenterLabelCenterLayer; ++ {
		if labelStrategyUsesWidth() {
			processLabelDummyStrategy(, , [int()], , )
		}
	}
}

func hasWidthBasedLabelStrategy( [][]*labelDummyInfo) bool {
	for  := layered.CenterLabelMedianLayer;  <= layered.CenterLabelCenterLayer; ++ {
		if labelStrategyUsesWidth() && len([int()]) != 0 {
			return true
		}
	}
	return false
}

func labelStrategyUsesWidth( layered.CenterEdgeLabelPlacementStrategy) bool {
	return  == layered.CenterLabelWidestLayer ||
		 == layered.CenterLabelCenterLayer ||
		 == layered.CenterLabelSpaceEfficientLayer
}

func gatherLabelDummyInfo(
	 *layered.LGraphArena,
	 layered.LNodeID,
	 layered.CenterEdgeLabelPlacementStrategy,
) *labelDummyInfo {
	 := &labelDummyInfo{labelDummy: , placement: }
	 := 
	for {
		 := .NodeIncomingEdges()
		if len() == 0 {
			panic("layered: label dummy chain has no incoming edge")
		}
		 = .EdgeSourceNode([0])
		if .Node().Type != layered.LongEdgeNode {
			break
		}
		.leftLongEdgeDummies = append(.leftLongEdgeDummies, )
	}
	for ,  := 0, len(.leftLongEdgeDummies)-1;  < ; ,  = +1, -1 {
		.leftLongEdgeDummies[], .leftLongEdgeDummies[] =
			.leftLongEdgeDummies[], .leftLongEdgeDummies[]
	}
	 = 
	for {
		 := .NodeOutgoingEdges()
		if len() == 0 {
			panic("layered: label dummy chain has no outgoing edge")
		}
		 = .EdgeTargetNode([0])
		if .Node().Type != layered.LongEdgeNode {
			break
		}
		.rightLongEdgeDummies = append(.rightLongEdgeDummies, )
	}
	if len(.leftLongEdgeDummies) == 0 {
		.leftmostLayerIndex = .LayerIndex(*.Node().Layer)
	} else {
		.leftmostLayerIndex = .LayerIndex(*.Node(.leftLongEdgeDummies[0]).Layer)
	}
	if len(.rightLongEdgeDummies) == 0 {
		.rightmostLayerIndex = .LayerIndex(*.Node().Layer)
	} else {
		 := .rightLongEdgeDummies[len(.rightLongEdgeDummies)-1]
		.rightmostLayerIndex = .LayerIndex(*.Node().Layer)
	}
	for ,  := range basegraph.Get(&.Node().Properties, layered.RepresentedLabels) {
		if ,  := basegraph.TryGet(&.Label().Properties, layered.CenterEdgeLabelPlacement);  {
			.placement = 
			break
		}
	}
	return 
}

func processLabelDummyStrategy(
	 *layered.LGraphArena,
	 layered.LGraphID,
	 []*labelDummyInfo,
	 layered.CenterEdgeLabelPlacementStrategy,
	 []float64,
) {
	if len() == 0 {
		return
	}
	if  == layered.CenterLabelSpaceEfficientLayer {
		assignSpaceEfficientLabels(, , )
		return
	}
	for ,  := range  {
		 := .leftmostLayerIndex
		switch  {
		case layered.CenterLabelMedianLayer:
			 += (.totalDummyCount() - 1) / 2
		case layered.CenterLabelTailLayer, layered.CenterLabelHeadLayer:
			 := labelDummyPartOfReversedEdge(, )
			 :=  == layered.CenterLabelHeadLayer
			if ( && !) || (! && ) {
				 = .rightmostLayerIndex
			}
			if ( && !) || (! && ) {
				basegraph.Set(&.Node(.labelDummy).Properties, layered.AlignmentOption, layered.AlignmentRight)
			} else {
				basegraph.Set(&.Node(.labelDummy).Properties, layered.AlignmentOption, layered.AlignmentLeft)
			}
		case layered.CenterLabelWidestLayer:
			for  := .leftmostLayerIndex + 1;  <= .rightmostLayerIndex; ++ {
				if [] > [] {
					 = 
				}
			}
		case layered.CenterLabelCenterLayer:
			 := computeLabelLayerWidthSums(, , , )
			 := [len()-1] / 2
			for ,  := range  {
				if  >=  {
					 = .leftmostLayerIndex + 
					break
				}
			}
		}
		assignLabelDummyLayer(, , , )
		markDummiesBeforeLabel(, .labelDummy)
	}
}

func computeLabelLayerWidthSums(
	 *layered.LGraphArena,
	 layered.LGraphID,
	 *labelDummyInfo,
	 []float64,
) []float64 {
	 := .Graph()
	 := math.Max(
		basegraph.Get(&.Properties, layered.SpacingEdgeNodeBetweenLayers)*2,
		basegraph.Get(&.Properties, layered.SpacingNodeNodeBetweenLayers),
	)
	 := make([]float64, .totalDummyCount())
	,  := -, 0
	for ,  := range .leftLongEdgeDummies {
		 += [.LayerIndex(*.Node().Layer)] + 
		[],  = , +1
	}
	 += [.LayerIndex(*.Node(.labelDummy).Layer)] + 
	[],  = , +1
	for ,  := range .rightLongEdgeDummies {
		 += [.LayerIndex(*.Node().Layer)] + 
		[],  = , +1
	}
	return 
}

func labelDummyPartOfReversedEdge( *layered.LGraphArena,  *labelDummyInfo) bool {
	,  := .NodeIncomingEdges(.labelDummy), .NodeOutgoingEdges(.labelDummy)
	if len() == 0 || len() == 0 {
		panic("layered: label dummy has incomplete edge chain")
	}
	return basegraph.Get(&.Edge([0]).Properties, layered.Reversed) ||
		basegraph.Get(&.Edge([0]).Properties, layered.Reversed)
}

func assignSpaceEfficientLabels( *layered.LGraphArena,  []*labelDummyInfo,  []float64) {
	 := make([]*labelDummyInfo, 0, len())
	for ,  := range  {
		if .leftmostLayerIndex == .rightmostLayerIndex {
			assignLabelDummyLayer(, , .leftmostLayerIndex, )
		} else if !assignLabelToAlreadyWideLayer(, , ) {
			 = append(, )
		}
	}
	if len() == 0 {
		return
	}
	sort.SliceStable(, func(,  int) bool {
		return .Node([].labelDummy).Size.X > .Node([].labelDummy).Size.X
	})
	for ,  := range  {
		 := potentiallyWidestLayer(, , , )
		assignLabelDummyLayer(, , , )
	}
}

func assignLabelToAlreadyWideLayer( *layered.LGraphArena,  *labelDummyInfo,  []float64) bool {
	 := .Node(.labelDummy).Size.X
	 := .NodeGraph(.labelDummy)
	for  := .leftmostLayerIndex;  <= .rightmostLayerIndex; ++ {
		// The Java source checks Layer.size here rather than the separately
		// calculated layerWidths array. Preserve that distinction.
		if .Layer(.Graph().Layers[]).Size.X >=  {
			assignLabelDummyLayer(, , , )
			return true
		}
	}
	return false
}

func potentiallyWidestLayer(
	 *layered.LGraphArena,
	 []*labelDummyInfo,
	 int,
	 []float64,
) int {
	 := []
	 := .Node(.labelDummy).Size.X
	,  := .leftmostLayerIndex, 0.0
	for  := .leftmostLayerIndex;  <= .rightmostLayerIndex; ++ {
		if  <= [] {
			return 
		}
		 := []
		var  *labelDummyInfo
		for  :=  + 1;  < len(); ++ {
			 := []
			if .leftmostLayerIndex <=  && .rightmostLayerIndex >=  {
				// ELK overwrites this while scanning the descending list; the last
				// applicable label is therefore observed despite the variable name.
				 = 
			}
		}
		if  != nil {
			 = math.Max(, .Node(.labelDummy).Size.X)
		}
		if  >  {
			,  = , 
		}
	}
	return 
}

func assignLabelDummyLayer(
	 *layered.LGraphArena,
	 *labelDummyInfo,
	 int,
	 []float64,
) {
	 := .leftmostLayerIndex + len(.leftLongEdgeDummies)
	if  !=  {
		swapLabelAndLongEdgeDummies(, .labelDummy, .dummyAt(-.leftmostLayerIndex))
	}
	 := .LayerIndex(*.Node(.labelDummy).Layer)
	[] = math.Max([], .Node(.labelDummy).Size.X)
	for ,  := range basegraph.Get(&.Node(.labelDummy).Properties, layered.RepresentedLabels) {
		basegraph.Set(&.Label().Properties, includeLabel, true)
	}
}

func swapLabelAndLongEdgeDummies( *layered.LGraphArena, ,  layered.LNodeID) {
	,  := *.Node().Layer, *.Node().Layer
	,  := .NodeIndexInLayer(), .NodeIndexInLayer()
	,  := inputOutputPorts(, )
	,  := inputOutputPorts(, )
	 := append([]layered.LEdgeID(nil), .Port().IncomingEdges...)
	 := append([]layered.LEdgeID(nil), .Port().OutgoingEdges...)
	 := append([]layered.LEdgeID(nil), .Port().IncomingEdges...)
	 := append([]layered.LEdgeID(nil), .Port().OutgoingEdges...)

	.SetNodeLayerAt(, &, )
	for ,  := range  {
		.SetEdgeTarget(, &)
	}
	for ,  := range  {
		.SetEdgeSource(, &)
	}
	.SetNodeLayerAt(, &, )
	for ,  := range  {
		.SetEdgeTarget(, &)
	}
	for ,  := range  {
		.SetEdgeSource(, &)
	}
}

func inputOutputPorts( *layered.LGraphArena,  layered.LNodeID) (layered.LPortID, layered.LPortID) {
	var ,  layered.LPortID
	,  := false, false
	for ,  := range .Node().Ports {
		if ! && len(.Port().IncomingEdges) != 0 {
			,  = , true
		}
		if ! && len(.Port().OutgoingEdges) != 0 {
			,  = , true
		}
	}
	if ! || ! {
		panic("layered: dummy node has no input/output port")
	}
	return , 
}

func markDummiesBeforeLabel( *layered.LGraphArena,  layered.LNodeID) {
	 := 
	for {
		 := .NodeIncomingEdges()
		if len() == 0 {
			return
		}
		 = .EdgeSourceNode([0])
		if .Node().Type != layered.LongEdgeNode {
			return
		}
		basegraph.Set(&.Node().Properties, layered.LongEdgeBeforeLabelDummy, true)
	}
}