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

type hiddenNodeConnections uint8

const (
	hiddenNodeConnectionsNone hiddenNodeConnections = iota
	hiddenNodeConnectionsFirstSeparate
	hiddenNodeConnectionsLastSeparate
	hiddenNodeConnectionsBoth
)

var hiddenNodeConnectionsProperty = basegraph.PropertyWithDefault(
	"separateLayerConnections",
	func() hiddenNodeConnections { return hiddenNodeConnectionsNone },
)

// LayerConstraintPreprocessor hides FIRST_SEPARATE and LAST_SEPARATE nodes
// before layering. It is the arena-backed counterpart of ELK 0.8.1's
// intermediate processor of the same name.
type LayerConstraintPreprocessor struct{}

func (*LayerConstraintPreprocessor) (
	 *layered.LGraphArena,
	 layered.LGraphID,
) error {
	 := .Graph()
	 := make([]layered.LNodeID, 0, len(.LayerlessNodes))
	 := make([]layered.LNodeID, 0)
	for ,  := range .LayerlessNodes {
		if !hasSeparateLayerConstraint(, ) {
			 = append(, )
			continue
		}
		if  := hideLayerConstraintNode(, );  != nil {
			return 
		}
		 = append(, )
	}
	.LayerlessNodes = 
	if len() > 0 {
		basegraph.Set(&.Properties, layered.HiddenNodes, )
	}
	return nil
}

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

func hasSeparateLayerConstraint( *layered.LGraphArena,  layered.LNodeID) bool {
	switch basegraph.Get(&.Node().Properties, layered.LayerConstraintOption) {
	case layered.LayerConstraintFirstSeparate, layered.LayerConstraintLastSeparate:
		return true
	default:
		return false
	}
}

func hideLayerConstraintNode( *layered.LGraphArena,  layered.LNodeID) error {
	if  := ensureAcceptableSeparateLayerEdges(, );  != nil {
		return 
	}
	for ,  := range append([]layered.LEdgeID(nil), .NodeConnectedEdges()...) {
		 := .Edge()
		 := .Source != nil && .Port(*.Source).Node != nil &&
			*.Port(*.Source).Node == 
		var  layered.LPortID
		if  {
			if .Target == nil {
				continue
			}
			 = *.Target
			.SetEdgeTarget(, nil)
		} else {
			if .Source == nil {
				continue
			}
			 = *.Source
			.SetEdgeSource(, nil)
		}
		basegraph.Set(&.Edge().Properties, layered.OriginalOppositePort, )
		if  := .Port().Node;  != nil {
			updateOppositeNodeLayerConstraint(, , *)
		}
	}
	return nil
}

func updateOppositeNodeLayerConstraint(
	 *layered.LGraphArena,
	,  layered.LNodeID,
) {
	 := &.Node().Properties
	if basegraph.Has(, layered.LayerConstraintOption) {
		return
	}
	 := combineHiddenNodeConnections(
		basegraph.Get(, hiddenNodeConnectionsProperty),
		basegraph.Get(&.Node().Properties, layered.LayerConstraintOption),
	)
	basegraph.Set(, hiddenNodeConnectionsProperty, )
	if len(.NodeConnectedEdges()) > 0 {
		return
	}
	switch  {
	case hiddenNodeConnectionsFirstSeparate:
		basegraph.Set(, layered.LayerConstraintOption, layered.LayerConstraintFirst)
	case hiddenNodeConnectionsLastSeparate:
		basegraph.Set(, layered.LayerConstraintOption, layered.LayerConstraintLast)
	}
}

func combineHiddenNodeConnections(
	 hiddenNodeConnections,
	 layered.LayerConstraint,
) hiddenNodeConnections {
	 :=  == layered.LayerConstraintFirstSeparate
	switch  {
	case hiddenNodeConnectionsNone:
		if  {
			return hiddenNodeConnectionsFirstSeparate
		}
		return hiddenNodeConnectionsLastSeparate
	case hiddenNodeConnectionsFirstSeparate:
		if  {
			return hiddenNodeConnectionsFirstSeparate
		}
		return hiddenNodeConnectionsBoth
	case hiddenNodeConnectionsLastSeparate:
		if  {
			return hiddenNodeConnectionsBoth
		}
		return hiddenNodeConnectionsLastSeparate
	default:
		return hiddenNodeConnectionsBoth
	}
}

func ensureAcceptableSeparateLayerEdges(
	 *layered.LGraphArena,
	 layered.LNodeID,
) error {
	 := basegraph.Get(&.Node().Properties, layered.LayerConstraintOption)
	var  []layered.LEdgeID
	var  string
	switch  {
	case layered.LayerConstraintFirstSeparate:
		 = .NodeIncomingEdges()
		 = "FIRST_SEPARATE node has an incoming edge"
	case layered.LayerConstraintLastSeparate:
		 = .NodeOutgoingEdges()
		 = "LAST_SEPARATE node has an outgoing edge"
	default:
		return nil
	}
	for ,  := range  {
		 := .Edge()
		if .Source != nil && .Target != nil &&
			.Node(.EdgeSourceNode()).Type == layered.ExternalPortNode &&
			.Node(.EdgeTargetNode()).Type == layered.ExternalPortNode {
			continue
		}
		return fmt.Errorf("layered: node %d %s", , )
	}
	return nil
}