// Copyright (c) 2010, 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 
	
)

// LayerConstraintPostprocessor moves FIRST and LAST nodes to their boundary
// layers and restores the separate-layer nodes hidden before phase 2.
type LayerConstraintPostprocessor struct{}

func (*LayerConstraintPostprocessor) (
	 *layered.LGraphArena,
	 layered.LGraphID,
) error {
	 := .Graph()
	if len(.Layers) > 0 {
		 := .Layers[0]
		 := .Layers[len(.Layers)-1]
		 := .AddLayer()
		 := .AddLayer()
		if  := moveFirstAndLastLayerConstraintNodes(
			, , , , , ,
		);  != nil {
			return 
		}
		if len(.Layer().Nodes) > 0 {
			.Layers = append([]layered.LayerID{}, .Layers...)
		}
		if len(.Layer().Nodes) > 0 {
			.Layers = append(.Layers, )
		}
	}

	,  := basegraph.TryGet(&.Properties, layered.HiddenNodes)
	if ! {
		return nil
	}
	 := .AddLayer()
	 := .AddLayer()
	if  := restoreHiddenLayerConstraintNodes(
		, , , ,
	);  != nil {
		return 
	}
	if len(.Layer().Nodes) > 0 {
		.Layers = append([]layered.LayerID{}, .Layers...)
	}
	if len(.Layer().Nodes) > 0 {
		.Layers = append(.Layers, )
	}
	return nil
}

// ProcessLayerConstraintsAfterLayering is the function-shaped API used by the
// layered pipeline's intermediate-processor dispatcher.
func (
	 *layered.LGraphArena,
	 layered.LGraphID,
) error {
	return (&LayerConstraintPostprocessor{}).Process(, )
}

func moveFirstAndLastLayerConstraintNodes(
	 *layered.LGraphArena,
	 layered.LGraphID,
	, , ,  layered.LayerID,
) error {
	for ,  := range append([]layered.LayerID(nil), .Graph().Layers...) {
		for ,  := range append([]layered.LNodeID(nil), .Layer().Nodes...) {
			switch basegraph.Get(&.Node().Properties, layered.LayerConstraintOption) {
			case layered.LayerConstraintFirst:
				if  := ensureNoIncomingLayerConstraintEdges(, );  != nil {
					return 
				}
				.SetNodeLayer(, &)
				moveAdjacentLabelDummies(, , true, )
			case layered.LayerConstraintLast:
				if  := ensureNoOutgoingLayerConstraintEdges(, );  != nil {
					return 
				}
				.SetNodeLayer(, &)
				moveAdjacentLabelDummies(, , false, )
			}
		}
	}
	 := make([]layered.LayerID, 0, len(.Graph().Layers))
	for ,  := range .Graph().Layers {
		if len(.Layer().Nodes) > 0 {
			 = append(, )
		}
	}
	.Graph().Layers = 
	return nil
}

func moveAdjacentLabelDummies(
	 *layered.LGraphArena,
	 layered.LNodeID,
	 bool,
	 layered.LayerID,
) {
	 := .NodeOutgoingEdges()
	if  {
		 = .NodeIncomingEdges()
	}
	for ,  := range append([]layered.LEdgeID(nil), ...) {
		 := .Edge()
		if .Source == nil || .Target == nil {
			continue
		}
		 := .EdgeTargetNode()
		if  {
			 = .EdgeSourceNode()
		}
		if .Node().Type == layered.LabelNode {
			.SetNodeLayer(, &)
		}
	}
}

func restoreHiddenLayerConstraintNodes(
	 *layered.LGraphArena,
	 []layered.LNodeID,
	,  layered.LayerID,
) error {
	for ,  := range  {
		switch basegraph.Get(&.Node().Properties, layered.LayerConstraintOption) {
		case layered.LayerConstraintFirstSeparate:
			.SetNodeLayer(, &)
		case layered.LayerConstraintLastSeparate:
			.SetNodeLayer(, &)
		default:
			return fmt.Errorf("layered: hidden node %d has no separate layer constraint", )
		}
		for ,  := range append([]layered.LEdgeID(nil), .NodeConnectedEdges()...) {
			 := .Edge()
			if .Source != nil && .Target != nil {
				continue
			}
			,  := basegraph.TryGet(&.Properties, layered.OriginalOppositePort)
			if ! {
				return fmt.Errorf("layered: hidden edge %d has no original opposite port", )
			}
			if .Target == nil {
				.SetEdgeTarget(, &)
			} else {
				.SetEdgeSource(, &)
			}
		}
	}
	return nil
}

func ensureNoIncomingLayerConstraintEdges(
	 *layered.LGraphArena,
	 layered.LNodeID,
) error {
	for ,  := range .NodeIncomingEdges() {
		 := .Edge()
		if .Source != nil && .Node(.EdgeSourceNode()).Type == layered.LabelNode {
			continue
		}
		return fmt.Errorf("layered: FIRST node %d has a non-label incoming edge", )
	}
	return nil
}

func ensureNoOutgoingLayerConstraintEdges(
	 *layered.LGraphArena,
	 layered.LNodeID,
) error {
	for ,  := range .NodeOutgoingEdges() {
		 := .Edge()
		if .Target != nil && .Node(.EdgeTargetNode()).Type == layered.LabelNode {
			continue
		}
		return fmt.Errorf("layered: LAST node %d has a non-label outgoing edge", )
	}
	return nil
}