/** {@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();
  }
  /**
   * Collects the positions and dimensions of {@link LNode}s and vertical segments in the layered
   * graph and writes them to the {@link CNode}s.
   */
  private void readNodes() {
    List<VerticalSegment> verticalSegments = Lists.newArrayList();
    // resetting to avoid problems if this is called repeatedly
    cGraph.cNodes.clear();

    // 1. collecting positions of graph elements
    for (Layer layer : layeredGraph) {
      for (LNode node : layer) {
        // add all nodes
        CLNode cNode = new CLNode(node, layeredGraph);
        cGraph.cNodes.add(cNode);

        // add vertical edge segments
        for (LEdge edge : node.getOutgoingEdges()) {

          Iterator<KVector> bends = edge.getBendPoints().iterator();

          // infer vertical segments from positions of bendpoints
          if (bends.hasNext()) {
            KVector bend1 = bends.next();

            // get segment of source n/s port
            if (edge.getSource().getSide() == PortSide.NORTH
                || edge.getSource().getSide() == PortSide.SOUTH) {

              verticalSegments.add(
                  new VerticalSegment(bend1, edge.getSource().getAbsoluteAnchor(), cNode, edge));
            }

            // get regular segments
            while (bends.hasNext()) {
              KVector bend2 = bends.next();
              if (!CompareFuzzy.eq(bend1.y, bend2.y)) {
                verticalSegments.add(new VerticalSegment(bend1, bend2, null, edge));
              }

              bend1 = bend2;
            }
          }
        }

        // same for incoming edges to get NSSegments on target side
        for (LEdge edge : node.getIncomingEdges()) {
          if (!edge.getBendPoints().isEmpty()) {

            // get segment of target n/s port
            if (edge.getTarget().getSide() == PortSide.NORTH
                || edge.getTarget().getSide() == PortSide.SOUTH) {

              KVector bend1 = edge.getBendPoints().getLast();
              verticalSegments.add(
                  new VerticalSegment(bend1, edge.getTarget().getAbsoluteAnchor(), cNode, edge));
            }
          }
        }
      }
    }

    // 2. combining intersecting segments in CLEdges to process them as one
    if (!verticalSegments.isEmpty()) {
      // sorting the segments by position in ascending order
      Collections.sort(verticalSegments);

      // merging intersecting segments in the same CLEdge
      VerticalSegment last = verticalSegments.get(0);
      CLEdge c = new CLEdge(last, layeredGraph);

      for (int i = 1; i < verticalSegments.size(); i++) {

        VerticalSegment verticalSegment = verticalSegments.get(i);

        if (verticalSegment.intersects(last)) {
          c.addSegment(verticalSegment);
        } else {
          cGraph.cNodes.add(c);
          c = new CLEdge(verticalSegment, layeredGraph);
        }

        last = verticalSegment;
      }
      cGraph.cNodes.add(c);
    }

    verticalSegments.clear();

    // 3. grouping nodes with their connected north/south segments
    groupCNodes();
  }