/**
   * Checks if the given <code>InsertRow</code>s contain cyclic fk constraints.
   *
   * @param rows
   * @return steps of the cycle, or null if there is none
   */
  private static Collection<InsertRow> findCycle(Collection<InsertRow> rows) {

    Iterator<InsertRow> rowsIter = rows.iterator();
    while (rowsIter.hasNext()) {
      InsertRow begin = rowsIter.next();
      Iterator<InsertRow> refIter = begin.getReferencedRows().iterator();
      while (refIter.hasNext()) {
        InsertRow referencedRow = refIter.next();
        List<InsertRow> cycle =
            findCycleRecursion(referencedRow, begin, new ArrayList<InsertRow>());
        if (cycle != null) {
          return cycle;
        }
      }
    }
    return null;
  }
  private static List<InsertRow> findCycleRecursion(
      InsertRow next, InsertRow begin, List<InsertRow> steps) {

    if (steps.contains(next)) {
      steps.add(next);
      return steps;
    }
    steps.add(next);

    Iterator<InsertRow> refIter = next.getReferencedRows().iterator();
    while (refIter.hasNext()) {
      InsertRow referencedRow = refIter.next();
      List<InsertRow> cycle = findCycleRecursion(referencedRow, begin, steps);
      if (cycle != null) {
        return cycle;
      }
    }
    steps.remove(steps.size() - 1);
    return null;
  }
  /**
   * Sorts the given <code>InsertRow</code>s topologically (respecting the foreign key constraints),
   * so they can be inserted in the resulting order without causing foreign key violations.
   *
   * <p>Number of precedessors (pre): number of fields that *are referenced by* this row Number of
   * successors (post) : number of fields that *reference* this row
   *
   * @param inserts insert rows to sort
   * @return the nodes of the graph in topological order if no cycle is present, else in arbitrary
   *     order
   */
  public static List<InsertRow> getInsertOrder(List<InsertRow> inserts) {

    List<InsertRow> result = new ArrayList<InsertRow>();

    // key: inserts, value: number of fields that are referenced by this row
    Map<InsertRow, Integer> preMap = new HashMap<InsertRow, Integer>();

    // key: inserts with no foreign key constraints
    List<InsertRow> noPre = new ArrayList<InsertRow>();

    // build map
    Iterator<InsertRow> insertIter = inserts.iterator();
    while (insertIter.hasNext()) {
      InsertRow insertRow = insertIter.next();
      int pre = insertRow.getReferencedFields().size();
      LOG.logDebug("Adding row to preMap: " + insertRow);
      preMap.put(insertRow, pre);
      if (pre == 0) {
        noPre.add(insertRow);
      }
    }

    while (!noPre.isEmpty()) {
      // select an insert row that has no open fk constraints
      InsertRow insertRow = noPre.get(0);
      noPre.remove(0);
      result.add(insertRow);

      // decrease the number of pending fk constraints for all insert rows that
      // reference the currently processed insert row
      Collection<InsertField> postList = insertRow.getReferencingFields();
      Iterator<InsertField> iter = postList.iterator();
      while (iter.hasNext()) {
        InsertField postField = iter.next();
        if (preMap.get(postField.getRow()) == null) {
          LOG.logDebug("No pre info for: " + postField.getRow());
        }
        int pre = preMap.get(postField.getRow());
        preMap.put(postField.getRow(), --pre);
        if (pre == 0) {
          noPre.add(postField.getRow());
        }
      }
    }

    if (result.size() != inserts.size()) {
      Collection<InsertRow> cycle = InsertRow.findCycle(inserts);
      Iterator<InsertRow> cycleIter = cycle.iterator();
      StringBuffer sb = new StringBuffer();
      while (cycleIter.hasNext()) {
        sb.append(cycleIter.next());
        if (cycle.iterator().hasNext()) {
          sb.append(" -> ");
        }
      }
      String msg = Messages.getMessage("DATASTORE_FK_CYCLE", sb.toString());
      LOG.logWarning(msg);
      result.clear();
      result.addAll(inserts);
    }
    return result;
  }