// Copyright (c) 2012, 2020 Kiel University and others.
// Copyright (c) 2015, 2017 Kiel University and others.
// Copyright (c) 2017 Kiel University and others.
// Copyright (c) 2017, 2019 Kiel University and others.
// Copyright (c) 2020 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 
	
	
)

// The types below are the small, end-label-specific subset of ELK 0.8.1's
// nodespacing LabelCell. Keeping the cell alive between phases four and five
// is important: EndLabelSorter reorders its labels without recomputing its
// dimensions, then EndLabelPostprocessor applies the final node offset.
type horizontalLabelAlignment uint8

const (
	horizontalLeft horizontalLabelAlignment = iota
	horizontalCenter
	horizontalRight
)

type verticalLabelAlignment uint8

const (
	verticalTop verticalLabelAlignment = iota
	verticalCenter
	verticalBottom
)

type endLabelCell struct {
	labels           []layered.LLabelID
	rect             geometry.Rectangle
	minimum          geometry.Point
	gap              float64
	horizontalLayout bool
	horizontalAlign  horizontalLabelAlignment
	verticalAlign    verticalLabelAlignment
}

var endLabelCells = basegraph.NewProperty[map[layered.LPortID]*endLabelCell]("endLabels")

func newEndLabelCell(
	 *layered.LGraphArena,
	 []layered.LLabelID,
	 float64,
	 bool,
) *endLabelCell {
	if len() == 0 {
		return nil
	}
	 := &endLabelCell{
		labels:           append([]layered.LLabelID(nil), ...),
		gap:              ,
		horizontalLayout: ,
		horizontalAlign:  horizontalCenter,
		verticalAlign:    verticalCenter,
	}
	for ,  := range .labels {
		 := .Label().Size
		if  {
			.minimum.X = math.Max(.minimum.X, .X)
			.minimum.Y += .Y
			if  > 0 {
				.minimum.Y += 
			}
		} else {
			.minimum.X += .X
			.minimum.Y = math.Max(.minimum.Y, .Y)
			if  > 0 {
				.minimum.X += 
			}
		}
	}
	.rect.Width = .minimum.X
	.rect.Height = .minimum.Y
	return 
}

// PreprocessEndLabels is the exact ELK 0.8.1 EndLabelPreprocessor pipeline for
// the arena graph. It gathers HEAD and TAIL labels into per-port cells, places
// those cells relative to their nodes, removes strip overlaps, and extends the
// node margins to cover the result.
func ( *layered.LGraphArena,  layered.LGraphID) {
	 := .Graph()
	 := basegraph.Get(&.Properties, layered.SpacingEdgeLabel)
	 := basegraph.Get(&.Properties, layered.SpacingLabelLabel)
	 := directionIsVertical(basegraph.Get(&.Properties, layered.DirectionOption))

	for ,  := range .Layers {
		for ,  := range .Layer().Nodes {
			preprocessNodeEndLabels(, , , , )
		}
	}
}

func preprocessNodeEndLabels(
	 *layered.LGraphArena,
	 layered.LNodeID,
	,  float64,
	 bool,
) {
	 := .Node()
	 := make([]*endLabelCell, len(.Ports))
	for ,  := range .Ports {
		.Port().ID = 
		,  := gatherPortEndLabels(, )
		[] = newEndLabelCell(, , , !)
	}

	placeEndLabels(, , , , , )

	 := make(map[layered.LPortID]*endLabelCell)
	for ,  := range  {
		if  != nil {
			[.Ports[]] = 
		}
	}
	if len() == 0 {
		return
	}
	basegraph.Set(&.Properties, endLabelCells, )
	updateEndLabelNodeMargins(, )
}

func placeEndLabels(
	 *layered.LGraphArena,
	 layered.LNodeID,
	 []*endLabelCell,
	,  float64,
	 bool,
) {
	for ,  := range .Node().Ports {
		 := .Port()
		if .ID >= 0 && .ID < len() && [.ID] != nil {
			placePortEndLabels(, , [.ID], )
		}
	}

	// In horizontal layouts north/south labels can overlap. In vertical
	// layouts the same is true of east/west labels.
	if  {
		removeEndLabelOverlaps(, , , layered.EastSide,
			2*, )
		removeEndLabelOverlaps(, , , layered.WestSide,
			2*, )
	} else {
		removeEndLabelOverlaps(, , , layered.NorthSide,
			2*, )
		removeEndLabelOverlaps(, , , layered.SouthSide,
			2*, )
	}
}

func placePortEndLabels(
	 *layered.LGraphArena,
	 layered.LPortID,
	 *endLabelCell,
	 float64,
) {
	 := .Port()
	if .Node == nil {
		return
	}
	 := .Node(*.Node)
	 := .Position.Add(.Anchor)
	 := basegraph.Get(&.Properties, layered.MaximumEdgeThickness)
	 := basegraph.Get(&.Label(.labels[0]).Properties, layered.InternalLabelSide)

	switch .Side {
	case layered.NorthSide:
		.verticalAlign = verticalBottom
		.rect.Y = -.Margin.Top -  - .rect.Height
		if  == layered.LabelSideAbove {
			.horizontalAlign = horizontalRight
			.rect.X = .X -  -  - .rect.Width
		} else {
			.horizontalAlign = horizontalLeft
			.rect.X = .X +  + 
		}
	case layered.EastSide:
		.horizontalAlign = horizontalLeft
		.rect.X = .Size.X + .Margin.Right + 
		if  == layered.LabelSideAbove {
			.verticalAlign = verticalBottom
			.rect.Y = .Y -  -  - .rect.Height
		} else {
			.verticalAlign = verticalTop
			.rect.Y = .Y +  + 
		}
	case layered.SouthSide:
		.verticalAlign = verticalTop
		.rect.Y = .Size.Y + .Margin.Bottom + 
		if  == layered.LabelSideAbove {
			.horizontalAlign = horizontalRight
			.rect.X = .X -  -  - .rect.Width
		} else {
			.horizontalAlign = horizontalLeft
			.rect.X = .X +  + 
		}
	case layered.WestSide:
		.horizontalAlign = horizontalRight
		.rect.X = -.Margin.Left -  - .rect.Width
		if  == layered.LabelSideAbove {
			.verticalAlign = verticalBottom
			.rect.Y = .Y -  -  - .rect.Height
		} else {
			.verticalAlign = verticalTop
			.rect.Y = .Y +  + 
		}
	}
}

type overlapRemovalDirection uint8

const (
	overlapUp overlapRemovalDirection = iota
	overlapDown
	overlapLeft
	overlapRight
)

type endLabelOverlapNode struct {
	cell     *endLabelCell
	x        float64
	width    float64
	height   float64
	y        float64
	overlaps []*endLabelOverlapNode
}

func removeEndLabelOverlaps(
	 *layered.LGraphArena,
	 layered.LNodeID,
	 []*endLabelCell,
	 layered.PortSide,
	,  float64,
) {
	 := overlapDirectionForPortSide()
	 := overlapStartCoordinate(.Node(), , )
	 := make([]*endLabelOverlapNode, 0)
	for ,  := range .NodePortsOnSide(, ) {
		 := .Port()
		if .ID < 0 || .ID >= len() || [.ID] == nil {
			continue
		}
		 := [.ID]
		 := &endLabelOverlapNode{cell: }
		if  == overlapUp ||  == overlapDown {
			.x, .width, .height = .rect.X, .rect.Width, .rect.Height
		} else {
			.x, .width, .height = .rect.Y, .rect.Height, .rect.Width
		}
		 = append(, )
	}
	if len() == 0 {
		return
	}

	// RectangleStripOverlapRemover first stably orders left borders and uses a
	// right-border-ordered scanline set to construct the overlap graph.
	sort.SliceStable(, func(,  int) bool {
		return javacompat.DoubleCompare([].x, [].x) < 0
	})
	 := make([]*endLabelOverlapNode, 0, len())
	for ,  := range  {
		 := [:0]
		for ,  := range  {
			if .x+.width >= .x {
				 = append(, )
			}
		}
		 = 
		sort.SliceStable(, func(,  int) bool {
			 := [].x + [].width
			 := [].x + [].width
			return javacompat.DoubleCompare(, ) < 0
		})
		for ,  := range  {
			.overlaps = append(.overlaps, )
			.overlaps = append(.overlaps, )
		}
		// The source uses a TreeSet whose comparator only considers the right
		// border. A rectangle with an equal right border is therefore not added
		// to the active scanline set, even though it remains in the main list.
		 := .x + .width
		 := false
		for ,  := range  {
			if javacompat.DoubleCompare(.x+.width, ) == 0 {
				 = true
				break
			}
		}
		if ! {
			 = append(, )
		}
	}

	 := make(map[*endLabelOverlapNode]bool, len())
	for ,  := range  {
		.y = 0
		sort.SliceStable(.overlaps, func(,  int) bool {
			return javacompat.DoubleCompare(.overlaps[].y, .overlaps[].y) < 0
		})
		for ,  := range .overlaps {
			if ![] {
				continue
			}
			if .y < .y+.height+ &&
				.y+.height+ > .y {
				.y = .y + .height + 
			}
		}
		[] = true
	}

	for ,  := range  {
		switch  {
		case overlapUp:
			.cell.rect.Y =  - .height - .y
		case overlapDown:
			.cell.rect.Y =  + .y
		case overlapLeft:
			.cell.rect.X =  - .height - .y
		case overlapRight:
			.cell.rect.X =  + .y
		}
	}
}

func overlapDirectionForPortSide( layered.PortSide) overlapRemovalDirection {
	switch  {
	case layered.NorthSide:
		return overlapUp
	case layered.SouthSide:
		return overlapDown
	case layered.EastSide:
		return overlapRight
	case layered.WestSide:
		return overlapLeft
	default:
		panic("layered: end-label overlap removal requested for undefined port side")
	}
}

func overlapStartCoordinate( *layered.LNode,  layered.PortSide,  float64) float64 {
	switch  {
	case layered.NorthSide:
		return -.Margin.Top - 
	case layered.SouthSide:
		return .Size.Y + .Margin.Bottom + 
	case layered.EastSide:
		return .Size.X + .Margin.Right + 
	case layered.WestSide:
		return -.Margin.Left - 
	default:
		panic("layered: end-label overlap start requested for undefined port side")
	}
}

func updateEndLabelNodeMargins( *layered.LNode,  []*endLabelCell) {
	 := geometry.Rectangle{
		X:      -.Margin.Left,
		Y:      -.Margin.Top,
		Width:  .Margin.Left + .Size.X + .Margin.Right,
		Height: .Margin.Top + .Size.Y + .Margin.Bottom,
	}
	for ,  := range  {
		if  != nil {
			 = .Union(.rect)
		}
	}
	.Margin.Left = -.X
	.Margin.Top = -.Y
	.Margin.Right = .Width - .Margin.Left - .Size.X
	.Margin.Bottom = .Height - .Margin.Top - .Size.Y
}

// SortEndLabels ports ELK 0.8.1's EndLabelSorter. Groups of labels belonging
// to one edge retain their input order; groups are ordered by the opposite
// endpoint's final layer/port order.
func ( *layered.LGraphArena,  layered.LGraphID) {
	for ,  := range .Graph().Layers {
		for ,  := range .Layer().Nodes {
			if .Node().Type == layered.NormalNode {
				sortNodeEndLabels(, , )
			}
		}
	}
}

func sortNodeEndLabels( *layered.LGraphArena,  layered.LGraphID,  layered.LNodeID) {
	,  := basegraph.TryGet(&.Node().Properties, endLabelCells)
	if ! {
		return
	}
	 := false
	for ,  := range .Node().Ports {
		if !endLabelsNeedSorting(, ) {
			continue
		}
		if ! {
			initializeEndLabelOrderIDs(, )
			 = true
		}
		if  := [];  != nil {
			sortEndLabelCell(, )
		}
	}
}

func endLabelsNeedSorting( *layered.LGraphArena,  layered.LPortID) bool {
	 := 0
	 := .Port()
	for ,  := range .IncomingEdges {
		if edgeHasLabelAt(, , layered.EdgeLabelPlacementHead) {
			++
		}
	}
	for ,  := range .OutgoingEdges {
		if edgeHasLabelAt(, , layered.EdgeLabelPlacementTail) {
			++
		}
	}
	return  >= 2
}

func edgeHasLabelAt( *layered.LGraphArena,  layered.LEdgeID,  layered.EdgeLabelPlacement) bool {
	for ,  := range .Edge().Labels {
		if basegraph.Get(&.Label().Properties, layered.EdgeLabelsPlacement) ==  {
			return true
		}
	}
	return false
}

func initializeEndLabelOrderIDs( *layered.LGraphArena,  layered.LGraphID) {
	 := 0
	for ,  := range .Graph().Layers {
		for ,  := range .Layer().Nodes {
			.Node().ID = 
			++
			for ,  := range .Node().Ports {
				.Port().ID = 
				++
			}
		}
	}
}

type endLabelGroup struct {
	edge    layered.LEdgeID
	hasEdge bool
	labels  []layered.LLabelID
}

func sortEndLabelCell( *layered.LGraphArena,  *endLabelCell) {
	 := make([]endLabelGroup, 0)
	 := make(map[layered.LEdgeID]int)
	 := -1
	for ,  := range .labels {
		,  := basegraph.TryGet(&.Label().Properties, layered.EndLabelEdge)
		 := -1
		if  {
			if ,  := [];  {
				 = 
			} else {
				 = len()
				[] = 
				 = append(, endLabelGroup{edge: , hasEdge: true})
			}
		} else if  >= 0 {
			 = 
		} else {
			 = len()
			 = 
			 = append(, endLabelGroup{})
		}
		[].labels = append([].labels, )
	}

	sort.SliceStable(, func(,  int) bool {
		,  := [], []
		if !.hasEdge || !.hasEdge {
			return .hasEdge && !.hasEdge
		}
		,  := .Edge(.edge), .Edge(.edge)
		 := .Port(*.Source).ID
		 := .Port(*.Source).ID
		if  !=  {
			return  < 
		}
		 := .Node(*.Port(*.Target).Node).ID
		 := .Node(*.Port(*.Target).Node).ID
		if  !=  {
			return  < 
		}
		return .Port(*.Target).ID > .Port(*.Target).ID
	})

	.labels = .labels[:0]
	for ,  := range  {
		.labels = append(.labels, .labels...)
	}
}

// PostprocessEndLabels offsets all cells by their final node positions and
// applies the LabelCell layout to the individual labels.
func ( *layered.LGraphArena,  layered.LGraphID) {
	for ,  := range .Graph().Layers {
		for ,  := range .Layer().Nodes {
			 := .Node()
			if .Type != layered.NormalNode {
				continue
			}
			,  := basegraph.TryGet(&.Properties, endLabelCells)
			if ! {
				continue
			}
			for ,  := range .Ports {
				if  := [];  != nil {
					.rect = .rect.Move(.Position)
					applyEndLabelCell(, )
				}
			}
			basegraph.Unset(&.Properties, endLabelCells)
		}
	}
}

func applyEndLabelCell( *layered.LGraphArena,  *endLabelCell) {
	if .horizontalLayout {
		 := .rect.Y
		switch .verticalAlign {
		case verticalCenter:
			 += (.rect.Height - .minimum.Y) / 2
		case verticalBottom:
			 += .rect.Height - .minimum.Y
		}
		for ,  := range .labels {
			 := .Label()
			.Position.Y = 
			 += .Size.Y + .gap
			switch .horizontalAlign {
			case horizontalLeft:
				.Position.X = .rect.X
			case horizontalCenter:
				.Position.X = .rect.X + (.rect.Width-.Size.X)/2
			case horizontalRight:
				.Position.X = .rect.X + .rect.Width - .Size.X
			}
		}
		return
	}

	 := .rect.X
	switch .horizontalAlign {
	case horizontalCenter:
		 += (.rect.Width - .minimum.X) / 2
	case horizontalRight:
		 += .rect.Width - .minimum.X
	}
	for ,  := range .labels {
		 := .Label()
		.Position.X = 
		 += .Size.X + .gap
		switch .verticalAlign {
		case verticalTop:
			.Position.Y = .rect.Y
		case verticalCenter:
			.Position.Y = .rect.Y + (.rect.Height-.Size.Y)/2
		case verticalBottom:
			.Position.Y = .rect.Y + .rect.Height - .Size.Y
		}
	}
}

// ManageEndNodePortLabels corresponds to LabelManagementProcessor(false).
// JSON ELK graphs cannot carry Java's ILabelManager callback, so the exact
// elkjs/D2-active behavior is an intentional no-op.
func ( *layered.LGraphArena,  layered.LGraphID) {}