/**
   * Creates all edges in the internal model
   *
   * @param layout reference to the layout algorithm
   * @param vertices the vertices whom are to have an internal representation created
   * @param internalVertices the blank internal vertices to have their information filled in using
   *     the real vertices
   */
  protected void createInternalCells(
      mxHierarchicalLayout layout, Object[] vertices, mxGraphHierarchyNode[] internalVertices) {
    mxGraph graph = layout.getGraph();

    // Create internal edges
    for (int i = 0; i < vertices.length; i++) {
      internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
      vertexMapper.put(vertices[i], internalVertices[i]);

      // If the layout is deterministic, order the cells
      Object[] conns = graph.getConnections(vertices[i], parent);
      List<Object> outgoingCells = Arrays.asList(graph.getOpposites(conns, vertices[i]));
      internalVertices[i].connectsAsSource =
          new LinkedHashSet<mxGraphHierarchyEdge>(outgoingCells.size());

      // Create internal edges, but don't do any rank assignment yet
      // First use the information from the greedy cycle remover to
      // invert the leftward edges internally
      Iterator<Object> iter = outgoingCells.iterator();

      while (iter.hasNext()) {
        // Don't add self-loops
        Object cell = iter.next();

        if (cell != vertices[i]
            && graph.getModel().isVertex(cell)
            && !layout.isVertexIgnored(cell)) {
          // Allow for parallel edges
          Object[] edges = graph.getEdgesBetween(vertices[i], cell, true);

          if (edges != null && edges.length > 0) {
            ArrayList<Object> listEdges = new ArrayList<Object>(edges.length);

            for (int j = 0; j < edges.length; j++) {
              listEdges.add(edges[j]);
            }

            mxGraphHierarchyEdge internalEdge = new mxGraphHierarchyEdge(listEdges);
            Iterator<Object> iter2 = listEdges.iterator();

            while (iter2.hasNext()) {
              Object edge = iter2.next();
              edgeMapper.put(edge, internalEdge);

              // Resets all point on the edge and disables the edge style
              // without deleting it from the cell style
              graph.resetEdge(edge);

              if (layout.isDisableEdgeStyle()) {
                layout.setEdgeStyleEnabled(edge, false);
                layout.setOrthogonalEdge(edge, true);
              }
            }

            internalEdge.source = internalVertices[i];
            internalVertices[i].connectsAsSource.add(internalEdge);
          }
        }
      }

      // Ensure temp variable is cleared from any previous use
      internalVertices[i].temp[0] = 0;
    }
  }
  /**
   * Creates an internal ordered graph model using the vertices passed in. If there are any,
   * leftward edge need to be inverted in the internal model
   *
   * @param layout the enclosing layout object
   * @param vertices the vertices for this hierarchy
   * @param ordered whether or not the vertices are already ordered
   * @param deterministic whether or not this layout should be deterministic on each
   * @param scanRanksFromSinks Whether the rank assignment is done from the sinks or sources. usage
   */
  public mxGraphHierarchyModel(
      mxHierarchicalLayout layout,
      Object[] vertices,
      List<Object> roots,
      Object parent,
      boolean ordered,
      boolean deterministic,
      boolean scanRanksFromSinks) {
    mxGraph graph = layout.getGraph();
    this.deterministic = deterministic;
    this.scanRanksFromSinks = scanRanksFromSinks;
    this.roots = roots;
    this.parent = parent;

    if (vertices == null) {
      vertices = graph.getChildVertices(parent);
    }

    if (ordered) {
      formOrderedHierarchy(layout, vertices, parent);
    } else {
      // map of cells to internal cell needed for second run through
      // to setup the sink of edges correctly. Guess size by number
      // of edges is roughly same as number of vertices.
      vertexMapper = new Hashtable<Object, mxGraphHierarchyNode>(vertices.length);
      edgeMapper = new Hashtable<Object, mxGraphHierarchyEdge>(vertices.length);
      if (scanRanksFromSinks) {
        maxRank = 0;
      } else {
        maxRank = SOURCESCANSTARTRANK;
      }
      mxGraphHierarchyNode[] internalVertices = new mxGraphHierarchyNode[vertices.length];
      createInternalCells(layout, vertices, internalVertices);

      // Go through edges set their sink values. Also check the
      // ordering if and invert edges if necessary
      for (int i = 0; i < vertices.length; i++) {
        Collection<mxGraphHierarchyEdge> edges = internalVertices[i].connectsAsSource;
        Iterator<mxGraphHierarchyEdge> iter = edges.iterator();

        while (iter.hasNext()) {
          mxGraphHierarchyEdge internalEdge = iter.next();
          Collection<Object> realEdges = internalEdge.edges;
          Iterator<Object> iter2 = realEdges.iterator();

          if (iter2.hasNext()) {
            Object realEdge = iter2.next();
            Object targetCell = graph.getView().getVisibleTerminal(realEdge, false);
            mxGraphHierarchyNode internalTargetCell = vertexMapper.get(targetCell);

            if (internalTargetCell != null && internalVertices[i] != internalTargetCell) {
              internalEdge.target = internalTargetCell;

              if (internalTargetCell.connectsAsTarget.size() == 0) {
                internalTargetCell.connectsAsTarget = new LinkedHashSet<mxGraphHierarchyEdge>(4);
              }

              internalTargetCell.connectsAsTarget.add(internalEdge);
            }
          }
        }

        // Use the temp variable in the internal nodes to mark this
        // internal vertex as having been visited.
        internalVertices[i].temp[0] = 1;
      }
    }
  }
  /**
   * Creates an internal ordered graph model using the vertices passed in. If there are any,
   * leftward edge need to be inverted in the internal model
   *
   * @param layout reference to the layout algorithm
   * @param vertices the vertices to be laid out
   */
  public void formOrderedHierarchy(mxHierarchicalLayout layout, Object[] vertices, Object parent) {
    mxGraph graph = layout.getGraph();

    // map of cells to internal cell needed for second run through
    // to setup the sink of edges correctly. Guess size by number
    // of edges is roughly same as number of vertices.
    vertexMapper = new Hashtable<Object, mxGraphHierarchyNode>(vertices.length * 2);
    edgeMapper = new Hashtable<Object, mxGraphHierarchyEdge>(vertices.length);
    maxRank = 0;
    mxGraphHierarchyNode[] internalVertices = new mxGraphHierarchyNode[vertices.length];
    createInternalCells(layout, vertices, internalVertices);

    // Go through edges set their sink values. Also check the
    // ordering if and invert edges if necessary
    // Need a temporary list to store which of these edges have been
    // inverted in the internal model. If connectsAsSource were changed
    // in the following while loop we'd get a
    // ConcurrentModificationException
    List<mxGraphHierarchyEdge> tempList = new ArrayList<mxGraphHierarchyEdge>();

    for (int i = 0; i < vertices.length; i++) {
      Collection<mxGraphHierarchyEdge> edges = internalVertices[i].connectsAsSource;
      Iterator<mxGraphHierarchyEdge> iter = edges.iterator();

      while (iter.hasNext()) {
        mxGraphHierarchyEdge internalEdge = iter.next();
        Collection<Object> realEdges = internalEdge.edges;
        Iterator<Object> iter2 = realEdges.iterator();

        if (iter2.hasNext()) {
          Object realEdge = iter2.next();
          Object targetCell = graph.getView().getVisibleTerminal(realEdge, false);
          mxGraphHierarchyNode internalTargetCell = vertexMapper.get(targetCell);

          if (internalTargetCell != null && internalVertices[i] != internalTargetCell) {
            internalEdge.target = internalTargetCell;

            if (internalTargetCell.connectsAsTarget.size() == 0) {
              internalTargetCell.connectsAsTarget = new ArrayList<mxGraphHierarchyEdge>(4);
            }

            // The vertices passed in were ordered, check that the
            // target cell has not already been marked as visited
            if (internalTargetCell.temp[0] == 1) {
              // Internal Edge is leftward, reverse it
              internalEdge.invert();
              // There must be a connectsAsSource list already
              internalTargetCell.connectsAsSource.add(internalEdge);
              tempList.add(internalEdge);
              internalVertices[i].connectsAsTarget.add(internalEdge);
            } else {
              internalTargetCell.connectsAsTarget.add(internalEdge);
            }
          }
        }
      }

      // Remove the inverted edges as sources from this node
      Iterator<mxGraphHierarchyEdge> iter2 = tempList.iterator();

      while (iter2.hasNext()) {
        internalVertices[i].connectsAsSource.remove(iter2.next());
      }

      tempList.clear();

      // Use the temp variable in the internal nodes to mark this
      // internal vertex as having been visited.
      internalVertices[i].temp[0] = 1;
    }
  }