/**
   * Computes the Y coordinate so that the EIP is centered with respect to its children.
   *
   * @param eip the EIP
   * @param dim the dimension of the figure associated to the EIP
   * @return the new Y coordinate, -1 if there is no child
   */
  private int computeCenteredYCoordinate(EipNode eip, Dimension dim) {

    // Find the minimal and maximal values
    int childMinY = -1, childMaxY = -1;
    AbstractNode lastNode = null;
    for (EipConnection conn : eip.getOutgoingConnections()) {

      lastNode = conn.getTarget();
      int childY = this.nodeToCoY.get(lastNode);
      childMaxY = childY;
      if (childMinY == -1) childMinY = childY;
    }

    // Put it at the middle...
    int y = childMaxY + childMinY;

    // ... but take the figure's height in account.
    // When there is only 1 child, the EIP and the child should be aligned
    if (eip.getOutgoingConnections().size() == 1) {
      Dimension childDim = getFigureSize(lastNode);
      y -= dim.height - childDim.height;

    } else if (eip.getOutgoingConnections().size() > 1) {
      Dimension childDim = getFigureSize(lastNode);
      y += childDim.height - dim.height;
    }

    y = y / 2;
    return y;
  }
  /**
   * Computes the level and the coordinates for a node of the sub-graph.
   *
   * <p>The algorithm relies on the following predicates:
   *
   * <ol>
   *   <li>The X coordinate of a node depends on the level and the width of the previous levels.
   *   <li>The width of a level depends on the width of the biggest figure of this level.
   *   <li>The Y coordinate of a node depends on whether this node is a leaf or an intermediate node
   *       in the tree.
   *   <li>The Y coordinate of a leaf is the highest computed Y + a specific padding + the height of
   *       the preceding figure.
   *   <li>The Y coordinate of an intermediate node is the middle of the max and min Y of the node's
   *       children.
   * </ol>
   *
   * <p>Mathematically, it gives:
   *
   * <ol>
   *   <li>X( node ) = X( node-1 ) + padding + Width( node-1 )
   *   <li>Width( level ) = Math.max( Width( node )) where node is any node in the level / column.
   *   <li>...
   *   <li>Y( node ) = Math.max( Y( node2 )) where node2 is any node that has been processed before
   *       the current node.
   *   <li>Y( node ) = [ Y( node_child_max ) + Y( node_child_min ) - Height( node ) ] / 2
   * </ol>
   *
   * @param node
   * @param level the tree level of the node (1 for the root level)
   * @param maxY the biggest computed Y coordinate (for any column or level)
   * @return the new biggest computed Y coordinate (can be the same than the received one)
   */
  private int computeNodeStatistics(AbstractNode node, int level, int maxY) {

    // Register this node
    this.nodeToLevel.put(node, level);
    ArrayList<AbstractNode> nodes = this.levelToNodes.get(level);
    if (nodes == null) nodes = new ArrayList<AbstractNode>();

    nodes.add(node);
    this.levelToNodes.put(level, nodes);

    // Get the size of this figure
    Dimension dim = getFigureSize(node);

    // Update the width of the tree level.
    // The X coordinate of a node depends on the width for this level.
    // This is why it can only be computed once the entire tree has been explored.
    Integer levelWidth = this.levelToWidth.get(level);
    if (levelWidth == null) levelWidth = -1;

    if (dim.width > levelWidth) {
      levelWidth = dim.width;
      this.levelToWidth.put(level, levelWidth);
    }

    // Compute the Y coordinate of the node
    // This last one depends on the Y coordinates of the children
    // Note that there are only two sub-classes of AbstractNode
    int y;
    if (node instanceof Endpoint) {
      maxY += GRID_PADDING_Y;
      y = maxY;
      maxY += dim.height;

    } else {

      // Process all the children
      for (EipConnection conn : ((EipNode) node).getOutgoingConnections())
        maxY = computeNodeStatistics(conn.getTarget(), level + 1, maxY);

      // Determine the Y coordinate of the current EIP
      // The parent's location depends on the children
      y = computeCenteredYCoordinate((EipNode) node, dim);

      // No child found => treat it as an end-point
      if (y == -1) {
        maxY += GRID_PADDING_Y;
        y = maxY;
        maxY += dim.height;
      }
    }

    // Store the Y coordinate for a later use
    this.nodeToCoY.put(node, y);

    return maxY;
  }