/**
   * Put a node into the given linear segment and check for following parts of a long edge.
   *
   * @param node the node to put into the linear segment
   * @param segment a linear segment
   * @return {@code true} if the given node was not already part of another segment and was thus
   *     added to the given segment.
   */
  private boolean fillSegment(final LNode node, final LinearSegment segment) {
    NodeType nodeType = node.getType();

    // handle initial big nodes as big node type
    if (node.getProperty(InternalProperties.BIG_NODE_INITIAL)) {
      nodeType = NodeType.BIG_NODE;
    }

    if (node.id >= 0) {
      // The node is already part of another linear segment
      return false;
    } else if (segment.nodeType != null
        && (nodeType == NodeType.BIG_NODE && nodeType != segment.nodeType)) {
      // Big nodes are not allowed to share a linear segment with other dummy nodes
      return false;
    } else {
      // Add the node to the given linear segment
      node.id = segment.id;
      segment.nodes.add(node);
    }
    segment.nodeType = nodeType;

    if (nodeType == NodeType.LONG_EDGE
        || nodeType == NodeType.NORTH_SOUTH_PORT
        || nodeType == NodeType.BIG_NODE) {

      // This is a LONG_EDGE, NORTH_SOUTH_PORT or BIG_NODE dummy; check if any of its
      // successors are of one of these types too. If so, we can form a linear segment
      // with one of them. (not with more than one, though)
      // Note 1: LONG_EDGES and NORTH_SOUTH_PORTs can share a common linear segment
      // Note 2: we must take care not to make a segment out of nodes that are in the same layer
      // Note 3: for BIG_NODEs also the first BIG_NODE_INITIAL which is no actual dummy node has
      // to be considered here
      for (LPort sourcePort : node.getPorts()) {
        for (LPort targetPort : sourcePort.getSuccessorPorts()) {
          LNode targetNode = targetPort.getNode();
          NodeType targetNodeType = targetNode.getType();

          if (node.getLayer() != targetNode.getLayer()) {
            if (nodeType == NodeType.BIG_NODE) {
              // current AND the next node are BIG_NODE dummies
              if (targetNodeType == NodeType.BIG_NODE) {
                if (fillSegment(targetNode, segment)) {
                  // We just added another node to this node's linear segment.
                  // That's quite enough.
                  return true;
                }
              }
            } else {
              // current no bignode and next node is LONG_EDGE and NORTH_SOUTH_PORT
              if (targetNodeType == NodeType.LONG_EDGE
                  || targetNodeType == NodeType.NORTH_SOUTH_PORT) {
                if (fillSegment(targetNode, segment)) {
                  // We just added another node to this node's linear segment.
                  // That's quite enough.
                  return true;
                }
              }
            }
          }
        }
      }
    }

    return true;
  }
  /**
   * Calculate the force acting on the given linear segment. The force is stored in the segment's
   * deflection field.
   *
   * @param segment the linear segment whose force is to be calculated
   * @param incoming whether incoming edges should be considered
   * @param outgoing whether outgoing edges should be considered
   * @param deflectionDampening factor by which deflections are dampened
   */
  private void calcDeflection(
      final LinearSegment segment,
      final boolean incoming,
      final boolean outgoing,
      final double deflectionDampening) {

    double segmentDeflection = 0;
    int nodeWeightSum = 0;
    for (LNode node : segment.nodes) {
      double nodeDeflection = 0;
      int edgeWeightSum = 0;
      int inputPrio = incoming ? node.getProperty(INPUT_PRIO) : Integer.MIN_VALUE;
      int outputPrio = outgoing ? node.getProperty(OUTPUT_PRIO) : Integer.MIN_VALUE;
      int minPrio = Math.max(inputPrio, outputPrio);

      // Calculate force for every port/edge
      for (LPort port : node.getPorts()) {
        double portpos = node.getPosition().y + port.getPosition().y + port.getAnchor().y;
        if (outgoing) {
          for (LEdge edge : port.getOutgoingEdges()) {
            LPort otherPort = edge.getTarget();
            LNode otherNode = otherPort.getNode();
            if (segment != linearSegments[otherNode.id]) {
              int otherPrio =
                  Math.max(otherNode.getProperty(INPUT_PRIO), otherNode.getProperty(OUTPUT_PRIO));
              int prio = edge.getProperty(InternalProperties.PRIORITY);
              if (prio >= minPrio && prio >= otherPrio) {
                nodeDeflection +=
                    otherNode.getPosition().y
                        + otherPort.getPosition().y
                        + otherPort.getAnchor().y
                        - portpos;
                edgeWeightSum++;
              }
            }
          }
        }

        if (incoming) {
          for (LEdge edge : port.getIncomingEdges()) {
            LPort otherPort = edge.getSource();
            LNode otherNode = otherPort.getNode();
            if (segment != linearSegments[otherNode.id]) {
              int otherPrio =
                  Math.max(otherNode.getProperty(INPUT_PRIO), otherNode.getProperty(OUTPUT_PRIO));
              int prio = edge.getProperty(InternalProperties.PRIORITY);
              if (prio >= minPrio && prio >= otherPrio) {
                nodeDeflection +=
                    otherNode.getPosition().y
                        + otherPort.getPosition().y
                        + otherPort.getAnchor().y
                        - portpos;
                edgeWeightSum++;
              }
            }
          }
        }
      }

      // Avoid division by zero
      if (edgeWeightSum > 0) {
        segmentDeflection += nodeDeflection / edgeWeightSum;
        nodeWeightSum++;
      }
    }
    if (nodeWeightSum > 0) {
      segment.deflection = deflectionDampening * segmentDeflection / nodeWeightSum;
      segment.weight = nodeWeightSum;
    } else {
      segment.deflection = 0;
      segment.weight = 0;
    }
  }
  /** {@inheritDoc} */
  public void process(final LGraph layeredGraph, final IElkProgressMonitor monitor) {
    monitor.begin("Spline SelfLoop positioning", 1);

    /** Stores which loop placement strategy to choose. */
    final SelfLoopPlacement loopPlacement =
        layeredGraph.getProperty(Properties.SPLINE_SELF_LOOP_PLACEMENT);

    ////////////////////////////////////////////////////////
    // There are two main jobs to be done:
    // 1) Find a loop-side for each component.
    // 2) Position ports in the correct order around the node.
    ////////////////////////////////////////////////////////

    // Process all nodes on all layers.
    for (final Layer layer : layeredGraph) {
      for (final LNode node : layer) {

        // Read self-loops components.
        final List<ConnectedSelfLoopComponent> components =
            node.getProperty(InternalProperties.SPLINE_SELFLOOP_COMPONENTS);

        // Components to be distributed by the placement strategy.
        final List<ConnectedSelfLoopComponent> componentsToBePlaced = Lists.newArrayList();

        for (final ConnectedSelfLoopComponent component : components) {
          // Re-Add all hidden edges to their ports.
          component.unhideEdges();

          if (component.getConstrainedPorts().isEmpty()) {
            // If there is no constraint on any port, we have to process this component later
            componentsToBePlaced.add(component);
          } else {
            // If there is at least one port with a constraint to it's port-side,
            // we will set the port- and loop-sides by this constraint. (Job 1)
            setPortSideByConstraint(component);
            if (!component.getNonLoopPorts().isEmpty()) {
              // Position and re-add all ports to the node, that are part of a component
              // with at least one non-loop edge. (Job 2)
              addComponentWithNonLoopEdges(component);
            }
          }
        }

        // Now we have to find a loop-side (job 1) for the remaining components. They are all
        // stored in componentsToBePlaced. All these components don't have a port with a
        // constraint on it's portSide, so we can arrange them accordingly to the cosen strategy.
        switch (loopPlacement) {
          case EQUALLY_DISTRIBUTED:
            setPortSideSpreadEqually(componentsToBePlaced, node);
            break;
          case NORTH_SEQUENCE:
            for (final ConnectedSelfLoopComponent component : componentsToBePlaced) {
              component.setLoopSide(LoopSide.N, true);
            }
            break;
          case NORTH_STACKED:
            for (final ConnectedSelfLoopComponent component : componentsToBePlaced) {
              component.setLoopSide(LoopSide.N, true);
            }
            break;
          default:
            // Unknown strategy chosen.
            assert false;
            break;
        }

        // Position and re-add all ports to the node.
        // This is job 2)
        switch (loopPlacement) {
          case EQUALLY_DISTRIBUTED:
          case NORTH_STACKED:
            portStackedPositioning(components);
            break;
          case NORTH_SEQUENCE:
            portLinedPositioning(components);
            break;
          default:
            break;
        }
      }
    }
    monitor.done();
  }