/**
  * Assignes the least crowded corner loopSide to the given components. The crowding will be
  * recalculated after each added edge.
  *
  * @param components The components to assign a loopSide for.
  */
 private void assignCornerSide(final List<ConnectedSelfLoopComponent> components) {
   for (final ConnectedSelfLoopComponent component : components) {
     final LoopSide side = loopSides.getLeastCrowdedCorner();
     loopSides.addSize(side, component.getTextWidth(), component.getTextHeight());
     component.setLoopSide(side, true);
   }
 }
  /**
   * Sets the loopSide of all self-loops of the component individually, depending on the side(s)
   * already defined for at least one port in the component. At least one portSide must be defined,
   * otherwise this method will run infinitely.
   *
   * @param component The connected component to process.
   * @return The LoopSide that is equal to the assigned portSides. Undefined, if there is not a
   *     common side for all loops.
   */
  private LoopSide setPortSideByConstraint(final ConnectedSelfLoopComponent component) {
    // Iterator over the edges of the component.
    // The iterator is cycling, as there is no guarantee, that (at least) one port of the current
    // edge has a portSide defined. If both portSides of the current edge are UNDEFINED, we will
    // return to it later.
    final Iterator<LEdge> iter = Iterators.cycle(Lists.newArrayList(component.getEdges()));
    // LoopSides in this component will be collected here
    final EnumSet<LoopSide> loopSidesInComponent = EnumSet.noneOf(LoopSide.class);

    while (iter.hasNext()) {
      final LEdge edge = iter.next();
      final PortSide sourceSide = edge.getSource().getSide();
      final PortSide targetSide = edge.getTarget().getSide();
      if (sourceSide == PortSide.UNDEFINED) {
        // source side is undefined
        if (targetSide != PortSide.UNDEFINED) {
          // only targetSide is defined. Edge becomes a sideLoop on the side of target.
          final LoopSide side = LoopSide.fromPortSides(targetSide);
          edge.setProperty(InternalProperties.SPLINE_LOOPSIDE, side);
          edge.getSource().setSide(targetSide);
          loopSidesInComponent.add(side);
          iter.remove();
        }
      } else {
        // source side is defined
        if (targetSide == PortSide.UNDEFINED) {
          // only sourceSide is defined. Edge becomes a sideLoop on the side of source.
          final LoopSide side = LoopSide.fromPortSides(sourceSide);
          edge.setProperty(InternalProperties.SPLINE_LOOPSIDE, side);
          edge.getTarget().setSide(sourceSide);
          loopSidesInComponent.add(side);
          iter.remove();
        } else {
          // both sides are defined, set edge side resulting from the combination
          final LoopSide side = LoopSide.fromPortSide(sourceSide, targetSide);
          edge.setProperty(InternalProperties.SPLINE_LOOPSIDE, side);
          loopSidesInComponent.add(side);
          iter.remove();
        }
      }
    }

    // Now all edges have a LoopSide assigned.
    // Check if we can find a LoopSide for the whole component.
    LoopSide side;
    if (loopSidesInComponent.size() == 1) {
      side = loopSidesInComponent.iterator().next();
    } else {
      side = LoopSide.UNDEFINED;
    }

    component.setLoopSide(side, false);
    return side;
  }
    /**
     * Distributes the self-loops equally around the node.
     *
     * @param components
     */
    public void calculate(final List<ConnectedSelfLoopComponent> components) {
      final List<ConnectedSelfLoopComponent> withLongText = Lists.newArrayList();
      final List<ConnectedSelfLoopComponent> withShortText = Lists.newArrayList();
      final List<ConnectedSelfLoopComponent> withoutText = Lists.newArrayList();

      // first we are going to check for the size of possible edge labels
      for (final ConnectedSelfLoopComponent component : components) {
        if (component.getTextWidth() > MAX_TEXT_LENGTH) {
          withLongText.add(component);
        } else if (component.getTextWidth() > 0.0) {
          withShortText.add(component);
        } else {
          withoutText.add(component);
        }
      }

      // if there is only one loop with text, handle this loop as a long text loop
      if (withShortText.size() == 1 && withLongText.isEmpty()) {
        withLongText.addAll(withShortText);
        withShortText.clear();
      }

      // try to put the components with long text to one of the horizontal across loop sides.
      // if there are no such sides available, handle long text loops as short text loops.
      if (!withLongText.isEmpty()
          && loopSides.availableSides().contains(LoopSide.N)
          && loopSides.availableSides().contains(LoopSide.S)) {
        assignAcrossSide(withLongText);
      } else {
        withShortText.addAll(withLongText);
      }

      // put the components with short text to one of the corner loop sides
      if (!withShortText.isEmpty()) {
        assignCornerSide(withShortText);
      }

      // Last but not least: loops without text get spread equally.
      if (!withoutText.isEmpty()) {
        // ////////////////////////////////////////////////////////////
        // First of all we try to put those ConnectedSelfLoops who have more than one edge,
        // to one of the available straight sides.
        // ////////////////////////////////////////////////////////////

        final Set<LoopSide> availableStraights = loopSides.availableStraightSides();
        if (!availableStraights.isEmpty()) {
          final Iterator<ConnectedSelfLoopComponent> itrComponent = withoutText.iterator();
          final Iterator<LoopSide> itrAvailable = Iterables.cycle(availableStraights).iterator();

          while (itrComponent.hasNext()) {

            // look for a component with more than one edge
            ConnectedSelfLoopComponent component = itrComponent.next();
            while (itrComponent.hasNext() && component.getEdges().size() < 2) {
              component = itrComponent.next();
            }

            // If we have found a component with more than one edge, assign the next
            // straight side to the component.
            if (component.getEdges().size() > 1) {
              final LoopSide side = itrAvailable.next();
              component.setLoopSide(side, true);
              itrComponent.remove();
              loopSides.removeSide(side);
            }
          }
        }

        // ////////////////////////////////////////////////////////////
        // Now we can distribute the remaining components around the node.
        // ////////////////////////////////////////////////////////////

        // number of required sides
        final int number = withoutText.size();

        // Center of the assigned pattern.
        final LoopSide center = findCenter();

        // The list of portSides we assign to the set of connected components.
        final List<LoopSide> portSides = Lists.newArrayList();

        // How many times must a "full set" of LoopSide-elements be constructed?
        final int fullSets = number / loopSides.availableNotAcrossSides().size();

        // Construct the full sets.
        for (int i = 0; i < fullSets; i++) {
          portSides.addAll(loopSides.availableNotAcrossSides());
        }

        // How many elements are remaining to be constructed after the full sets have been
        // constructed?
        int remainer = number % loopSides.availableNotAcrossSides().size();

        // If more than three elements are remaining, the pattern includes all four corners
        // and the sides that would be included in the pattern for (n-4) elements.
        if (remainer > 3) {
          portSides.addAll(LoopSide.getAllCornerSides());
          remainer -= 4;
        }

        // Create the pattern for 1-3 elements.
        switch (remainer) {
          case 3:
            // opposed side (from center) and the the same sides as for two elements
            portSides.add(center.opposite());
          case 2:
            // the first AVAILABLE neighbors of the neighbors of the opposed side of the
            // center
            LoopSide tmpSide = center.opposite().left();
            do {
              tmpSide = tmpSide.left();
            } while (!loopSides.availableSides().contains(tmpSide));
            portSides.add(tmpSide);

            tmpSide = center.opposite().right();
            do {
              tmpSide = tmpSide.right();
            } while (!loopSides.availableSides().contains(tmpSide));
            portSides.add(tmpSide);
            break;
          case 1:
            // opposed side (from center)
            portSides.add(center.opposite());
            break;
          default:
            break;
        }

        // Now assign the remaining sides to the set of portSides we just have constructed.
        final Iterator<LoopSide> itrSides = portSides.iterator();
        final Iterator<ConnectedSelfLoopComponent> itrComponent = withoutText.iterator();
        while (itrSides.hasNext() && itrComponent.hasNext()) {
          itrComponent.next().setLoopSide(itrSides.next(), true);
        }
      }
    }
  /** {@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();
  }