/**
   * (Re-)Adds the ports to the node of a component, having at least one port with a non-loop edge.
   * The ports are added as the direct neighbor of a port they are connected to via an edge.
   *
   * @param component The component whose ports have to get re-added.
   */
  public void addComponentWithNonLoopEdges(final ConnectedSelfLoopComponent component) {
    // the node we are working on
    final LNode node = component.getNode();
    // Set of all ports of the components that are hidden. Initially all ports that CAN be hidden.
    final Set<LPort> hiddenPorts = Sets.newHashSet(component.getHidablePorts());
    // Iterator holding all ports that already have a portSide specified. Initially these are
    // all ports with an non-loop edge connected to them. While processing the elements, we will
    // add new ports to this Iterator. So we need a ListIterator.
    final ListIterator<LPort> portsWithSideIter =
        Lists.newLinkedList(component.getNonLoopPorts()).listIterator();

    while (portsWithSideIter.hasNext()) {
      final LPort portWithSide = portsWithSideIter.next();
      if (portWithSide.getOutgoingEdges().isEmpty()) {
        // inbound port
        for (final LEdge edgeWithHiddenPort : portWithSide.getIncomingEdges()) {
          final LPort hiddenPort = edgeWithHiddenPort.getSource();
          if (hiddenPorts.contains(hiddenPort)) {
            final ListIterator<LPort> nodePortIter = node.getPorts().listIterator();
            LPort portOnNode = nodePortIter.next();
            // find the port with side ...
            while (!portOnNode.equals(portWithSide)) {
              portOnNode = nodePortIter.next();
            }
            // ... and add the hidden port on it's right side
            nodePortIter.add(hiddenPort);
            portsWithSideIter.add(hiddenPort);
            setSideOfPort(hiddenPort, portWithSide.getSide());
            // ensure the next element will be our new added element
            portsWithSideIter.previous();
            portsWithSideIter.previous();
            hiddenPorts.remove(hiddenPort);
          }
        }
      } else {
        // outbound port
        for (final LEdge edgeWithHiddenPort : portWithSide.getOutgoingEdges()) {
          final LPort hiddenPort = edgeWithHiddenPort.getTarget();
          if (hiddenPorts.contains(hiddenPort)) {
            final ListIterator<LPort> nodePortIter = node.getPorts().listIterator();
            LPort portOnNode = nodePortIter.next();
            // find the port with side ...
            while (!portOnNode.equals(portWithSide)) {
              portOnNode = nodePortIter.next();
            }
            // ... and add the hidden port on it's left side
            nodePortIter.previous();
            nodePortIter.add(hiddenPort);
            portsWithSideIter.add(hiddenPort);
            setSideOfPort(hiddenPort, portWithSide.getSide());
            // ensure the next element will be our new added element
            portsWithSideIter.previous();
            portsWithSideIter.previous();
            hiddenPorts.remove(hiddenPort);
          }
        }
      }
    }
  }
  /**
   * 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;
        }
      }
    }
  }
  /**
   * 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;
    }
  }
  /**
   * Sorts the linear segments of the given layered graph by finding a topological ordering in the
   * corresponding segment ordering graph.
   *
   * @param layeredGraph layered graph to process
   * @return a sorted array of linear segments
   */
  private LinearSegment[] sortLinearSegments(final LGraph layeredGraph) {
    // set the identifier and input / output priority for all nodes
    List<LinearSegment> segmentList = Lists.newArrayList();
    for (Layer layer : layeredGraph) {
      for (LNode node : layer) {
        node.id = -1;
        int inprio = Integer.MIN_VALUE, outprio = Integer.MIN_VALUE;
        for (LPort port : node.getPorts()) {
          for (LEdge edge : port.getIncomingEdges()) {
            int prio = edge.getProperty(InternalProperties.PRIORITY);
            inprio = Math.max(inprio, prio);
          }
          for (LEdge edge : port.getOutgoingEdges()) {
            int prio = edge.getProperty(InternalProperties.PRIORITY);
            outprio = Math.max(outprio, prio);
          }
        }
        node.setProperty(INPUT_PRIO, inprio);
        node.setProperty(OUTPUT_PRIO, outprio);
      }
    }

    // create linear segments for the layered graph, ignoring odd port side dummies
    int nextLinearSegmentID = 0;
    for (Layer layer : layeredGraph) {
      for (LNode node : layer) {
        // Test for the node ID; calls to fillSegment(...) may have caused the node ID
        // to be != -1.
        if (node.id < 0) {
          LinearSegment segment = new LinearSegment();
          segment.id = nextLinearSegmentID++;
          fillSegment(node, segment);
          segmentList.add(segment);
        }
      }
    }

    // create and initialize segment ordering graph
    List<List<LinearSegment>> outgoingList = Lists.newArrayListWithCapacity(segmentList.size());
    List<Integer> incomingCountList = Lists.newArrayListWithCapacity(segmentList.size());
    for (int i = 0; i < segmentList.size(); i++) {
      outgoingList.add(new ArrayList<LinearSegment>());
      incomingCountList.add(0);
    }

    // create edges for the segment ordering graph
    createDependencyGraphEdges(layeredGraph, segmentList, outgoingList, incomingCountList);

    // turn lists into arrays
    LinearSegment[] segments = segmentList.toArray(new LinearSegment[segmentList.size()]);

    @SuppressWarnings("unchecked")
    List<LinearSegment>[] outgoing = outgoingList.toArray(new List[outgoingList.size()]);

    int[] incomingCount = new int[incomingCountList.size()];
    for (int i = 0; i < incomingCount.length; i++) {
      incomingCount[i] = incomingCountList.get(i);
    }

    // gather the sources of the segment ordering graph
    int nextRank = 0;
    List<LinearSegment> noIncoming = Lists.newArrayList();
    for (int i = 0; i < segments.length; i++) {
      if (incomingCount[i] == 0) {
        noIncoming.add(segments[i]);
      }
    }

    // find a topological ordering of the segment ordering graph
    int[] newRanks = new int[segments.length];
    while (!noIncoming.isEmpty()) {
      LinearSegment segment = noIncoming.remove(0);
      newRanks[segment.id] = nextRank++;

      while (!outgoing[segment.id].isEmpty()) {
        LinearSegment target = outgoing[segment.id].remove(0);
        incomingCount[target.id]--;

        if (incomingCount[target.id] == 0) {
          noIncoming.add(target);
        }
      }
    }

    // apply the new ordering to the array of linear segments
    linearSegments = new LinearSegment[segments.length];
    for (int i = 0; i < segments.length; i++) {
      assert outgoing[i].isEmpty();
      LinearSegment ls = segments[i];
      int rank = newRanks[i];
      linearSegments[rank] = ls;
      ls.id = rank;
      for (LNode node : ls.nodes) {
        node.id = rank;
      }
    }

    return linearSegments;
  }