/** {@inheritDoc} */
  public void process(final LGraph layeredGraph, final IKielerProgressMonitor monitor) {
    monitor.begin("Comment pre-processing", 1);

    Iterator<LNode> nodeIter = layeredGraph.getLayerlessNodes().iterator();
    while (nodeIter.hasNext()) {
      LNode node = nodeIter.next();
      if (node.getProperty(LayoutOptions.COMMENT_BOX)) {
        int edgeCount = 0;
        LEdge edge = null;
        LPort oppositePort = null;
        for (LPort port : node.getPorts()) {
          edgeCount += port.getDegree();
          if (port.getIncomingEdges().size() == 1) {
            edge = port.getIncomingEdges().get(0);
            oppositePort = edge.getSource();
          }
          if (port.getOutgoingEdges().size() == 1) {
            edge = port.getOutgoingEdges().get(0);
            oppositePort = edge.getTarget();
          }
        }

        if (edgeCount == 1
            && oppositePort.getDegree() == 1
            && !oppositePort.getNode().getProperty(LayoutOptions.COMMENT_BOX)) {
          // found a comment that has exactly one connection
          processBox(node, edge, oppositePort, oppositePort.getNode());
          nodeIter.remove();
        } else {
          // reverse edges that are oddly connected
          List<LEdge> revEdges = Lists.newArrayList();
          for (LPort port : node.getPorts()) {
            for (LEdge outedge : port.getOutgoingEdges()) {
              if (!outedge.getTarget().getOutgoingEdges().isEmpty()) {
                revEdges.add(outedge);
              }
            }

            for (LEdge inedge : port.getIncomingEdges()) {
              if (!inedge.getSource().getIncomingEdges().isEmpty()) {
                revEdges.add(inedge);
              }
            }
          }

          for (LEdge re : revEdges) {
            re.reverse(layeredGraph, true);
          }
        }
      }
    }

    monitor.done();
  }
  /**
   * Process a comment box by putting it into a property of the corresponding node.
   *
   * @param box a comment box
   * @param edge the edge that connects the box with the real node
   * @param oppositePort the port of the real node to which the edge is incident
   * @param realNode the normal node that is connected with the comment
   */
  private void processBox(
      final LNode box, final LEdge edge, final LPort oppositePort, final LNode realNode) {
    boolean topFirst, onlyTop = false, onlyBottom = false;
    if (realNode.getProperty(LayoutOptions.PORT_CONSTRAINTS).isSideFixed()) {
      boolean hasNorth = false, hasSouth = false;
      portLoop:
      for (LPort port1 : realNode.getPorts()) {
        for (LPort port2 : port1.getConnectedPorts()) {
          if (!port2.getNode().getProperty(LayoutOptions.COMMENT_BOX)) {
            if (port1.getSide() == PortSide.NORTH) {
              hasNorth = true;
              break portLoop;
            }
            if (port1.getSide() == PortSide.SOUTH) {
              hasSouth = true;
              break portLoop;
            }
          }
        }
      }
      onlyTop = hasSouth && !hasNorth;
      onlyBottom = hasNorth && !hasSouth;
    }
    if (!onlyTop && !onlyBottom && !realNode.getLabels().isEmpty()) {
      double labelPos = 0;
      for (LLabel label : realNode.getLabels()) {
        labelPos += label.getPosition().y + label.getSize().y / 2;
      }
      labelPos /= realNode.getLabels().size();
      topFirst = labelPos >= realNode.getSize().y / 2;
    } else {
      topFirst = !onlyBottom;
    }

    List<LNode> boxList;
    if (topFirst) {
      // determine the position to use, favoring the top position
      List<LNode> topBoxes = realNode.getProperty(InternalProperties.TOP_COMMENTS);
      if (topBoxes == null) {
        boxList = Lists.newArrayList();
        realNode.setProperty(InternalProperties.TOP_COMMENTS, boxList);
      } else if (onlyTop) {
        boxList = topBoxes;
      } else {
        List<LNode> bottomBoxes = realNode.getProperty(InternalProperties.BOTTOM_COMMENTS);
        if (bottomBoxes == null) {
          boxList = Lists.newArrayList();
          realNode.setProperty(InternalProperties.BOTTOM_COMMENTS, boxList);
        } else {
          if (topBoxes.size() <= bottomBoxes.size()) {
            boxList = topBoxes;
          } else {
            boxList = bottomBoxes;
          }
        }
      }
    } else {
      // determine the position to use, favoring the bottom position
      List<LNode> bottomBoxes = realNode.getProperty(InternalProperties.BOTTOM_COMMENTS);
      if (bottomBoxes == null) {
        boxList = Lists.newArrayList();
        realNode.setProperty(InternalProperties.BOTTOM_COMMENTS, boxList);
      } else if (onlyBottom) {
        boxList = bottomBoxes;
      } else {
        List<LNode> topBoxes = realNode.getProperty(InternalProperties.TOP_COMMENTS);
        if (topBoxes == null) {
          boxList = Lists.newArrayList();
          realNode.setProperty(InternalProperties.TOP_COMMENTS, boxList);
        } else {
          if (bottomBoxes.size() <= topBoxes.size()) {
            boxList = bottomBoxes;
          } else {
            boxList = topBoxes;
          }
        }
      }
    }

    // add the comment box to one of the two possible lists
    boxList.add(box);

    // set the opposite port as property for the comment box
    box.setProperty(InternalProperties.COMMENT_CONN_PORT, oppositePort);
    // detach the edge and the opposite port
    if (edge.getTarget() == oppositePort) {
      edge.setTarget(null);
      if (oppositePort.getDegree() == 0) {
        oppositePort.setNode(null);
      }
    } else {
      edge.setSource(null);
      if (oppositePort.getDegree() == 0) {
        oppositePort.setNode(null);
      }
    }
    edge.getBendPoints().clear();
  }