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

// ProcessLabelAndNodeSizes performs the post-ordering part of ELK 0.8.1's
// LabelAndNodeSizeProcessor for normal layered nodes. Original node and label
// sizing has already run on the imported ELK graph; the important second pass
// fixes the positions of ports whose order was chosen during phase 3.
//
// D2 either supplies FIXED_POS ports or zero-sized implicit ports. The latter
// use ELK's default DISTRIBUTED alignment. The implementation below also keeps
// the source algorithm's size, margin, spacing, border-offset, and alignment
// arithmetic for non-zero ports.
func ( *layered.LGraphArena,  layered.LGraphID) {
	for ,  := range .GraphAllNodes() {
		 := .Node()
		if .Type != layered.NormalNode {
			continue
		}
		 := basegraph.Get(&.Properties, layered.PortConstraintsOption)
		if .IsRatioFixed() || .IsPositionFixed() {
			// FIXED_RATIO/FIXED_POS preserve the coordinate along the selected
			// side, but NodeDimensionCalculation still snaps the orthogonal
			// coordinate to the (possibly resized) node border.
			for ,  := range .Ports {
				 := .Port()
				placePortOnBorder(, , .Side)
			}
		} else {
			for ,  := range []layered.PortSide{
				layered.NorthSide,
				layered.EastSide,
				layered.SouthSide,
				layered.WestSide,
			} {
				placePortsOnSide(, , , )
			}
			basegraph.Set(&.Properties, layered.PortConstraintsOption, layered.PortConstraintsFixedPosition)
		}
		placePortLabels(, , )
		placeNodeLabels(, , )
	}
}

type nodeLabelLocation struct {
	inside          bool
	horizontalAlign horizontalLabelAlignment
	verticalAlign   verticalLabelAlignment
	outsideSide     layered.PortSide
}

func placeNodeLabels(
	 *layered.LGraphArena,
	 layered.LGraphID,
	 layered.LNodeID,
) {
	 := .Node()
	 := basegraph.Get(&.Properties, nodespacing.NodeLabelsPlacement)
	 := make(map[nodeLabelLocation][]layered.LLabelID)
	 := make([]nodeLabelLocation, 0)
	for ,  := range .Labels {
		 := 
		if ,  := basegraph.TryGet(
			&.Label().Properties, nodespacing.NodeLabelsPlacement);  {
			 = 
		}
		,  := parseNodeLabelLocation()
		if ! {
			continue
		}
		if ,  := []; ! {
			 = append(, )
		}
		[] = append([], )
	}

	 := directionIsVertical(
		basegraph.Get(&.Graph().Properties, layered.DirectionOption))
	 := layered.IndividualOrDefault(, , &layered.SpacingLabelLabel)
	 := layered.IndividualOrDefault(, , &layered.SpacingLabelNode)
	 := individualNodeLabelPadding(, , )
	for ,  := range  {
		 := newEndLabelCell(, [], , !)
		.horizontalAlign = .horizontalAlign
		.verticalAlign = .verticalAlign
		.rect = nodeLabelCellRectangle(, .minimum, , , )
		applyEndLabelCell(, )
	}
}

func parseNodeLabelLocation( []string) (nodeLabelLocation, bool) {
	 := nodeLabelLocation{outsideSide: layered.UndefinedSide}
	, ,  := 0, 0, 0
	 := false
	for ,  := range  {
		switch strings.ToUpper(strings.TrimSpace()) {
		case "H_LEFT":
			.horizontalAlign,  = horizontalLeft, +1
		case "H_CENTER":
			.horizontalAlign,  = horizontalCenter, +1
		case "H_RIGHT":
			.horizontalAlign,  = horizontalRight, +1
		case "V_TOP":
			.verticalAlign,  = verticalTop, +1
		case "V_CENTER":
			.verticalAlign,  = verticalCenter, +1
		case "V_BOTTOM":
			.verticalAlign,  = verticalBottom, +1
		case "INSIDE":
			.inside,  = true, +1
		case "OUTSIDE":
			.inside,  = false, +1
		case "H_PRIORITY":
			 = true
		}
	}
	if  != 1 ||  != 1 ||  != 1 {
		return nodeLabelLocation{}, false
	}
	if .inside {
		return , true
	}
	if .verticalAlign != verticalCenter &&
		(! || .horizontalAlign == horizontalCenter) {
		if .verticalAlign == verticalTop {
			.outsideSide = layered.NorthSide
		} else {
			.outsideSide = layered.SouthSide
		}
	} else if .horizontalAlign != horizontalCenter {
		if .horizontalAlign == horizontalLeft {
			.outsideSide = layered.WestSide
		} else {
			.outsideSide = layered.EastSide
		}
	}
	return , .outsideSide != layered.UndefinedSide
}

func individualNodeLabelPadding(
	 *layered.LGraphArena,
	 layered.LGraphID,
	 layered.LNodeID,
) geometry.Spacing {
	 := .Node()
	if ,  := basegraph.TryGet(&.Properties, layered.SpacingIndividual);  {
		if ,  := basegraph.TryGet(&.Properties, layered.NodeLabelsPadding);  {
			return 
		}
	}
	if ,  := basegraph.TryGet(&.Properties, layered.NodeLabelsPadding);  {
		return 
	}
	return basegraph.Get(&.Graph().Properties, layered.NodeLabelsPadding)
}

func nodeLabelCellRectangle(
	 *layered.LNode,
	 geometry.Point,
	 geometry.Spacing,
	 float64,
	 nodeLabelLocation,
) geometry.Rectangle {
	 := geometry.Rectangle{Width: .X, Height: .Y}
	if .inside {
		switch .horizontalAlign {
		case horizontalLeft:
			.X = .Left
		case horizontalCenter:
			.X = (.Size.X - .X) / 2
		case horizontalRight:
			.X = .Size.X - .Right - .X
		}
		switch .verticalAlign {
		case verticalTop:
			.Y = .Top
		case verticalCenter:
			.Y = (.Size.Y - .Y) / 2
		case verticalBottom:
			.Y = .Size.Y - .Bottom - .Y
		}
		return 
	}
	switch .outsideSide {
	case layered.NorthSide, layered.SouthSide:
		switch .horizontalAlign {
		case horizontalLeft:
			.X = 0
		case horizontalCenter:
			.X = (.Size.X - .X) / 2
		case horizontalRight:
			.X = .Size.X - .X
		}
		if .outsideSide == layered.NorthSide {
			.Y = - - .Y
		} else {
			.Y = .Size.Y + 
		}
	case layered.WestSide, layered.EastSide:
		switch .verticalAlign {
		case verticalTop:
			.Y = 0
		case verticalCenter:
			.Y = (.Size.Y - .Y) / 2
		case verticalBottom:
			.Y = .Size.Y - .Y
		}
		if .outsideSide == layered.WestSide {
			.X = - - .X
		} else {
			.X = .Size.X + 
		}
	}
	return 
}

func placePortLabels(
	 *layered.LGraphArena,
	 layered.LGraphID,
	 layered.LNodeID,
) {
	 := .Node()
	 := append([]string(nil),
		basegraph.Get(&.Properties, nodespacing.PortLabelsPlacement)...)
	if basegraph.Get(&.Properties, core.PortLabelsNextToPortIfPossible) &&
		!hasPlacement(, "NEXT_TO_PORT_IF_POSSIBLE") {
		 = append(, "NEXT_TO_PORT_IF_POSSIBLE")
	}
	,  := hasPlacement(, "INSIDE"), hasPlacement(, "OUTSIDE")
	if  ==  {
		return
	}
	 := directionIsVertical(
		basegraph.Get(&.Graph().Properties, layered.DirectionOption))
	 := layered.IndividualOrDefault(, , &layered.SpacingLabelLabel)
	 := layered.IndividualOrDefault(
		, , &layered.SpacingLabelPortHorizontal)
	 := layered.IndividualOrDefault(
		, , &layered.SpacingLabelPortVertical)
	 := nodeContainerBorderPadding(, )
	 := hasPlacement(, "NEXT_TO_PORT_IF_POSSIBLE")
	 := basegraph.Get(&.Properties, core.PortLabelsTreatAsGroup)
	 := hasPlacement(, "ALWAYS_SAME_SIDE")
	 := hasPlacement(, "ALWAYS_OTHER_SAME_SIDE")
	 := hasPlacement(, "SPACE_EFFICIENT")
	 := .NestedGraph != nil ||
		basegraph.Get(&.Properties, core.InsideSelfLoopsActivate)

	for ,  := range []layered.PortSide{
		layered.NorthSide, layered.EastSide, layered.SouthSide, layered.WestSide,
	} {
		 := portsInPlacementOrder(, , )
		if len() == 0 {
			continue
		}
		 := portLabelsNextToPort(
			, [0], , , )
		 := len() >= 2 && ! && ! &&
			(len() == 2 || )
		for ,  := range  {
			 := .Port()
			if len(.Labels) == 0 {
				continue
			}
			 := newEndLabelCell(, .Labels, , !)
			 := .Label(.Labels[0]).Size.Y
			 := portLabelsNextToPort(, , , , )
			if  {
				placeInsidePortLabelCell(
					, , , , ,
					, , )
			} else {
				placeOutsidePortLabelCell(
					, , , ,
					, , ,
					 || )
			}
			applyEndLabelCell(, )
			 = false
		}
	}
}

func portsInPlacementOrder(
	 *layered.LGraphArena,
	 layered.LNodeID,
	 layered.PortSide,
) []layered.LPortID {
	 := .NodePortsOnSide(, )
	if  == layered.SouthSide ||  == layered.WestSide {
		for ,  := 0, len()-1;  < ; ,  = +1, -1 {
			[], [] = [], []
		}
	}
	return 
}

func hasPlacement( []string,  string) bool {
	for ,  := range  {
		if strings.EqualFold(strings.TrimSpace(), ) {
			return true
		}
	}
	return false
}

func portLabelsNextToPort(
	 *layered.LGraphArena,
	 layered.LPortID,
	, ,  bool,
) bool {
	 := .Port()
	if  {
		if ! {
			return true
		}
		return  && !basegraph.Get(&.Properties, layered.InsideConnections)
	}
	return  && len(.IncomingEdges) == 0 && len(.OutgoingEdges) == 0
}

func nodeContainerBorderPadding(
	 *layered.LGraphArena,
	 layered.LNodeID,
) geometry.Spacing {
	var  geometry.Spacing
	for ,  := range .Node().Ports {
		 := .Port()
		 := basegraph.Get(&.Properties, layered.PortBorderOffset)
		if  >= 0 {
			continue
		}
		 := -
		switch .Side {
		case layered.NorthSide:
			.Top = math.Max(.Top, )
		case layered.EastSide:
			.Right = math.Max(.Right, )
		case layered.SouthSide:
			.Bottom = math.Max(.Bottom, )
		case layered.WestSide:
			.Left = math.Max(.Left, )
		}
	}
	return 
}

func placeInsidePortLabelCell(
	 *endLabelCell,
	 *layered.LPort,
	 geometry.Spacing,
	,  float64,
	,  bool,
	 float64,
) {
	 := basegraph.Get(&.Properties, layered.PortBorderOffset)
	switch .Side {
	case layered.NorthSide:
		.horizontalAlign, .verticalAlign = horizontalCenter, verticalTop
		if  {
			.rect.X = (.Size.X - .rect.Width) / 2
		} else {
			.rect.X = .Size.X + 
		}
		.rect.Y = .Size.Y +  + .Top + 
	case layered.SouthSide:
		.horizontalAlign, .verticalAlign = horizontalCenter, verticalBottom
		if  {
			.rect.X = (.Size.X - .rect.Width) / 2
		} else {
			.rect.X = .Size.X + 
		}
		.rect.Y = - - .Bottom -  - .rect.Height
	case layered.EastSide:
		.horizontalAlign, .verticalAlign = horizontalRight, verticalCenter
		.rect.X = - - .Right -  - .rect.Width
		.rect.Y = verticalPortLabelY(
			, , , , , )
	case layered.WestSide:
		.horizontalAlign, .verticalAlign = horizontalLeft, verticalCenter
		.rect.X = .Size.X +  + .Left + 
		.rect.Y = verticalPortLabelY(
			, , , , , )
	}
}

func placeOutsidePortLabelCell(
	 *endLabelCell,
	 *layered.LPort,
	,  float64,
	,  bool,
	 float64,
	 bool,
) {
	switch .Side {
	case layered.NorthSide:
		.verticalAlign = verticalBottom
		if  {
			.horizontalAlign = horizontalCenter
			.rect.X = (.Size.X - .rect.Width) / 2
		} else if  {
			.horizontalAlign = horizontalRight
			.rect.X = -.rect.Width - 
		} else {
			.horizontalAlign = horizontalLeft
			.rect.X = .Size.X + 
		}
		.rect.Y = -.rect.Height - 
	case layered.SouthSide:
		.verticalAlign = verticalTop
		if  {
			.horizontalAlign = horizontalCenter
			.rect.X = (.Size.X - .rect.Width) / 2
		} else if  {
			.horizontalAlign = horizontalRight
			.rect.X = -.rect.Width - 
		} else {
			.horizontalAlign = horizontalLeft
			.rect.X = .Size.X + 
		}
		.rect.Y = .Size.Y + 
	case layered.EastSide:
		.horizontalAlign = horizontalLeft
		.rect.X = .Size.X + 
		if  {
			.verticalAlign = verticalCenter
			.rect.Y = verticalPortLabelY(
				, , , true, , )
		} else if  {
			.verticalAlign = verticalBottom
			.rect.Y = -.rect.Height - 
		} else {
			.verticalAlign = verticalTop
			.rect.Y = .Size.Y + 
		}
	case layered.WestSide:
		.horizontalAlign = horizontalRight
		.rect.X = -.rect.Width - 
		if  {
			.verticalAlign = verticalCenter
			.rect.Y = verticalPortLabelY(
				, , , true, , )
		} else if  {
			.verticalAlign = verticalBottom
			.rect.Y = -.rect.Height - 
		} else {
			.verticalAlign = verticalTop
			.rect.Y = .Size.Y + 
		}
	}
}

func verticalPortLabelY(
	 *endLabelCell,
	 *layered.LPort,
	 float64,
	,  bool,
	 float64,
) float64 {
	if ! {
		return .Size.Y + 
	}
	 := .rect.Height
	if ! {
		 = 
	}
	return (.Size.Y - ) / 2
}

func placePortsOnSide(
	 *layered.LGraphArena,
	 layered.LGraphID,
	 layered.LNodeID,
	 layered.PortSide,
) {
	 := .Node()
	 := make([]layered.LPortID, 0, len(.Ports))
	for ,  := range .Ports {
		if .Port().Side ==  {
			 = append(, )
		}
	}
	if len() == 0 {
		return
	}
	// NodeContext's TreeMultimap presents ports left-to-right / top-to-bottom,
	// whereas LNode's canonical clockwise order runs backwards on S and W.
	if  == layered.SouthSide ||  == layered.WestSide {
		for ,  := 0, len()-1;  < ; ,  = +1, -1 {
			[], [] = [], []
		}
	}

	 :=  == layered.NorthSide ||  == layered.SouthSide
	 := .Size.Y
	 := basegraph.Get(&.Graph().Properties, layered.SpacingPortsSurrounding).Top
	 := basegraph.Get(&.Graph().Properties, layered.SpacingPortsSurrounding).Bottom
	if  {
		 = .Size.X
		 = basegraph.Get(&.Graph().Properties, layered.SpacingPortsSurrounding).Left
		 = basegraph.Get(&.Graph().Properties, layered.SpacingPortsSurrounding).Right
	}

	 := 0.0
	for ,  := range  {
		 := .Port()
		if  {
			 += .Margin.Left + .Size.X + .Margin.Right
		} else {
			 += .Margin.Top + .Size.Y + .Margin.Bottom
		}
	}
	 :=  -  - 
	 := individualPortSpacing(, , )
	 := portAlignment(, )
	 :=  + *float64(len()-1)
	 := 

	if len() == 1 && ( == "DISTRIBUTED" ||  == "JUSTIFIED") {
		 = "CENTER"
	}
	switch  {
	case "BEGIN":
	case "END":
		 +=  - 
	case "JUSTIFIED":
		if len() > 1 {
			 += ( - ) / float64(len()-1)
		}
	case "CENTER":
		 += ( - ) / 2
	default: // DISTRIBUTED is ELK Layered's effective default for this path.
		 += ( - ( + *float64(len()+1))) / float64(len()+1)
		 += 
	}

	for ,  := range  {
		 := .Port()
		if  {
			 += .Margin.Left
			.Position.X = 
			 += .Size.X + .Margin.Right + 
		} else {
			 += .Margin.Top
			.Position.Y = 
			 += .Size.Y + .Margin.Bottom + 
		}
		placePortOnBorder(, , )
	}
}

func placePortOnBorder( *layered.LPort,  *layered.LNode,  layered.PortSide) {
	 := basegraph.Get(&.Properties, layered.PortBorderOffset)
	switch  {
	case layered.NorthSide:
		.Position.Y = -.Size.Y - 
	case layered.EastSide:
		.Position.X = .Size.X + 
	case layered.SouthSide:
		.Position.Y = .Size.Y + 
	case layered.WestSide:
		.Position.X = -.Size.X - 
	}
}

func individualPortSpacing(
	 *layered.LGraphArena,
	 layered.LGraphID,
	 layered.LNodeID,
) float64 {
	if ,  := basegraph.TryGet(&.Node().Properties, layered.SpacingIndividual);  {
		if ,  := basegraph.TryGet(&.Properties, layered.SpacingPortPort);  {
			return 
		}
	}
	return basegraph.Get(&.Graph().Properties, layered.SpacingPortPort)
}

func portAlignment( *layered.LNode,  layered.PortSide) string {
	 := map[layered.PortSide]string{
		layered.NorthSide: "north",
		layered.EastSide:  "east",
		layered.SouthSide: "south",
		layered.WestSide:  "west",
	}[]
	if ,  := .Properties.Raw("org.eclipse.elk.portAlignment." + );  {
		return strings.ToUpper(strings.TrimSpace(toString()))
	}
	if ,  := .Properties.Raw("org.eclipse.elk.portAlignment.default");  {
		return strings.ToUpper(strings.TrimSpace(toString()))
	}
	return "DISTRIBUTED"
}

func toString( any) string {
	if ,  := .(string);  {
		return 
	}
	return ""
}