/**
   * Creates an unbalanced placement for the sorted linear segments.
   *
   * @param layeredGraph the layered graph to create an unbalanced placement for.
   */
  private void createUnbalancedPlacement(final LGraph layeredGraph) {
    // How many nodes are currently placed in each layer
    int[] nodeCount = new int[layeredGraph.getLayers().size()];

    // The type of the node most recently placed in a given layer
    NodeType[] recentNodeType = new NodeType[layeredGraph.getLayers().size()];

    // Iterate through the linear segments (in proper order!) and place them
    for (LinearSegment segment : linearSegments) {
      // Determine the uppermost placement for the linear segment
      double uppermostPlace = 0.0f;
      for (LNode node : segment.nodes) {
        NodeType nodeType = node.getType();
        int layerIndex = node.getLayer().getIndex();
        nodeCount[layerIndex]++;

        // Calculate how much space to leave between the linear segment and the last
        // node of the given layer
        float spacing = spacings.edgeEdgeSpacing * spacings.inLayerSpacingFactor;
        if (nodeCount[layerIndex] > 0) {
          if (recentNodeType[layerIndex] != null) {
            spacing = spacings.getVerticalSpacing(recentNodeType[layerIndex], nodeType);
          }
        }

        uppermostPlace = Math.max(uppermostPlace, node.getLayer().getSize().y + spacing);
      }

      // Apply the uppermost placement to all elements
      for (LNode node : segment.nodes) {
        // Set the node position
        node.getPosition().y = uppermostPlace + node.getMargin().top;

        // Adjust layer size
        Layer layer = node.getLayer();
        layer.getSize().y =
            uppermostPlace + node.getMargin().top + node.getSize().y + node.getMargin().bottom;

        recentNodeType[layer.getIndex()] = node.getType();
      }
    }
  }
  /**
   * Post-process the balanced placement by moving linear segments where obvious improvements can be
   * made.
   *
   * @param layeredGraph the layered graph
   */
  private void postProcess(final LGraph layeredGraph) {

    // process each linear segment independently
    for (LinearSegment segment : linearSegments) {
      double minRoomAbove = Integer.MAX_VALUE, minRoomBelow = Integer.MAX_VALUE;

      for (LNode node : segment.nodes) {
        double roomAbove, roomBelow;
        int index = node.getIndex();

        // determine the amount by which the linear segment can be moved up without overlap
        if (index > 0) {
          LNode neighbor = node.getLayer().getNodes().get(index - 1);
          float spacing = spacings.getVerticalSpacing(node, neighbor);
          roomAbove =
              node.getPosition().y
                  - node.getMargin().top
                  - (neighbor.getPosition().y
                      + neighbor.getSize().y
                      + neighbor.getMargin().bottom
                      + spacing);
        } else {
          roomAbove = node.getPosition().y - node.getMargin().top;
        }
        minRoomAbove = Math.min(roomAbove, minRoomAbove);

        // determine the amount by which the linear segment can be moved down without
        // overlap
        if (index < node.getLayer().getNodes().size() - 1) {
          LNode neighbor = node.getLayer().getNodes().get(index + 1);
          float spacing = spacings.getVerticalSpacing(node, neighbor);
          roomBelow =
              neighbor.getPosition().y
                  - neighbor.getMargin().top
                  - (node.getPosition().y + node.getSize().y + node.getMargin().bottom + spacing);
        } else {
          roomBelow = 2 * node.getPosition().y;
        }
        minRoomBelow = Math.min(roomBelow, minRoomBelow);
      }

      double minDisplacement = Integer.MAX_VALUE;
      boolean foundPlace = false;

      // determine the minimal displacement that would make one incoming edge straight
      LNode firstNode = segment.nodes.get(0);
      for (LPort target : firstNode.getPorts()) {
        double pos = firstNode.getPosition().y + target.getPosition().y + target.getAnchor().y;
        for (LEdge edge : target.getIncomingEdges()) {
          LPort source = edge.getSource();
          double d =
              source.getNode().getPosition().y
                  + source.getPosition().y
                  + source.getAnchor().y
                  - pos;
          if (Math.abs(d) < Math.abs(minDisplacement)
              && Math.abs(d) < (d < 0 ? minRoomAbove : minRoomBelow)) {
            minDisplacement = d;
            foundPlace = true;
          }
        }
      }

      // determine the minimal displacement that would make one outgoing edge straight
      LNode lastNode = segment.nodes.get(segment.nodes.size() - 1);
      for (LPort source : lastNode.getPorts()) {
        double pos = lastNode.getPosition().y + source.getPosition().y + source.getAnchor().y;
        for (LEdge edge : source.getOutgoingEdges()) {
          LPort target = edge.getTarget();
          double d =
              target.getNode().getPosition().y
                  + target.getPosition().y
                  + target.getAnchor().y
                  - pos;
          if (Math.abs(d) < Math.abs(minDisplacement)
              && Math.abs(d) < (d < 0 ? minRoomAbove : minRoomBelow)) {
            minDisplacement = d;
            foundPlace = true;
          }
        }
      }

      // if such a displacement could be found, apply it to the whole linear segment
      if (foundPlace && minDisplacement != 0) {
        for (LNode node : segment.nodes) {
          node.getPosition().y += minDisplacement;
        }
      }
    }
  }
  /**
   * 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;
  }