/** @param grids */
  private void writeGeometry(SuperGrid<BPMNElement> grids) {
    // write cells
    double x = 0;
    double y = 0;
    int row = 0;
    int column = 0;
    for (Row<BPMNElement> r : grids) {
      column = 0;
      double cellHeight = heightOfRow[row];
      for (Cell<BPMNElement> c : r) {
        double cellWidth = widthOfColumn[column];
        if (c.isFilled()) {
          BPMNElement elem = c.getValue();
          LayoutingBounds geom = elem.getGeometry();
          double newX =
              x + (cellWidth / 2.0) - (geom.getWidth() / 2.0) + maxLaneDepth * LANE_HEAD_WIDTH;
          double newY = y + (cellHeight / 2.0) - (geom.getHeight() / 2.0);

          elem.setGeometry(new LayoutingBoundsImpl(newX, newY, geom.getWidth(), geom.getHeight()));
          elem.updateDataModel();
        }
        x += cellWidth;
        column++;
      }
      x = 0;
      y += cellHeight;
      row++;
    }
  }
  private void layoutElements() {
    // System.out.println();

    for (String id : this.orderedIds) {
      // System.out.println(id);
      BPMNElement currentElement = (BPMNElement) this.diagram.getElement(id);
      List<LayoutingElement> precedingElements = currentElement.getPrecedingElements();
      GridContext context = getContextByElement(currentElement);
      Cell<BPMNElement> cellOfElement = null;
      cellOfElement = placeElement(currentElement, precedingElements, context);

      boolean comesFromOtherGrid =
          precedingElements.size() == 1
              && precedingElements.get(0).getParent() != currentElement.getParent();
      if (!currentElement.isJoin() && !comesFromOtherGrid && cellOfElement.getPrevCell() != null) {
        // there is an edge hitting us left, so lets forbid
        // interleaving to use the left cell, if it's empty
        cellOfElement.getPrevCell().setPackable(false);
      }

      if (currentElement.isSplit()) {
        prelayoutSuccessors(currentElement, context, cellOfElement);
      }

      if (BPMNType.isAActivity(currentElement.getType())) {
        // search for attached events
        for (LayoutingElement e : currentElement.getOutgoingLinks()) {
          if (BPMNType.isACatchingIntermediateEvent(e.getType())) {
            context.grid.setCellOfItem((BPMNElement) e, cellOfElement);
          }
        }
      }
    }
  }
  /** @param grids */
  private void calcGeometry(SuperGrid<BPMNElement> grids) {
    grids.pack();
    heightOfRow = new double[grids.getHeight()];
    widthOfColumn = new double[grids.getWidth()];
    // initialize with standard values
    Arrays.fill(heightOfRow, CELL_HEIGHT);
    Arrays.fill(widthOfColumn, CELL_WIDTH);
    // find biggest
    int row = 0;
    int column = 0;
    for (Row<BPMNElement> r : grids) {
      column = 0;
      for (Cell<BPMNElement> c : r) {
        if (c.isFilled()) {
          BPMNElement elem = c.getValue();
          LayoutingBounds geom = elem.getGeometry();
          widthOfColumn[column] = Math.max(widthOfColumn[column], geom.getWidth() + CELL_MARGIN);
          heightOfRow[row] = Math.max(heightOfRow[row], geom.getHeight() + CELL_MARGIN);
        }
        column++;
      }
      row++;
    }

    // calc width / height
    widthOfSuperGrid = 0;
    for (double columnWidth : widthOfColumn) {
      widthOfSuperGrid += columnWidth;
    }
    heightOfSuperGrid = 0;
    for (double rowHeight : heightOfRow) {
      heightOfSuperGrid += rowHeight;
    }

    poolWidth = maxLaneDepth * LANE_HEAD_WIDTH;
    poolWidth += widthOfSuperGrid;
  }
  /**
   * @param currentElement
   * @param precedingElements
   * @param context
   * @return cellOfElement
   */
  private Cell<BPMNElement> placeElement(
      BPMNElement currentElement, List<LayoutingElement> precedingElements, GridContext context) {
    Cell<BPMNElement> newCell;
    if (precedingElements.isEmpty()) {
      // StartEvents
      context.startCell.setValue(currentElement);
      newCell = context.startCell;
      context.startCell = context.startCell.beneath();
    } else {
      Cell<BPMNElement> leftCell;
      newCell = context.grid.getCellOfItem(currentElement); // not
      // null
      // if
      // join
      if (currentElement.isJoin()) {

        Point tmp;
        boolean splitFound = false;
        BPMNElement split = (BPMNElement) currentElement.prevSplit();
        if (split != null) {
          // get all close splits
          Queue<BPMNElement> splits =
              new PriorityQueue<BPMNElement>(
                  precedingElements.size() / 2, // should be a
                  // good rule of
                  // thumb
                  new BackwardDistanceComperator(currentElement));
          splits.add(split);
          for (LayoutingElement elem : precedingElements) {
            split = (BPMNElement) elem.prevSplit();
            if (split != null && !splits.contains(split)) {
              splits.add(split);
            }
          }
          split = null;
          // get split with most connections
          int maxCon = 0;
          for (BPMNElement target : splits) {
            if (target == currentElement) {
              // beeing my own splits only makes trouble
              continue;
            } else if (target.getParent() != currentElement.getParent()) {

              continue;
            }
            int curCon = 0;
            for (LayoutingElement elem : precedingElements) {
              if (elem.backwardDistanceTo(target) < Integer.MAX_VALUE) {
                curCon++;
              }
            }
            if (curCon > maxCon) {
              maxCon = curCon;
              split = target;
            }
          }
          splitFound = split != null;
        }

        int x = 0;
        int yAcc = 0;
        int yCnt = 0;
        for (LayoutingElement el : precedingElements) {
          BPMNElement elem = (BPMNElement) el;
          tmp = context.grid.find(context.grid.getCellOfItem(elem));
          if (tmp == null) {
            Grid<BPMNElement> preGrid = getContextByElement(elem).grid;
            tmp = preGrid.find(preGrid.getCellOfItem(elem));
            if (tmp == null) {
              tmp = new Point(0, 0);
            }
          } else {
            yAcc += tmp.y;
            yCnt++;
          }
          x = Math.max(x, tmp.x);
        }
        if (splitFound) {

          leftCell = context.grid.getCellOfItem(split).getParent().get(x);
          // set path to split unpackable
          for (Cell<BPMNElement> cCell = leftCell;
              cCell.getValue() != split;
              cCell = cCell.getPrevCell()) {
            cCell.setPackable(false);
          }

        } else {
          if (yCnt == 0) {
            leftCell = context.grid.getFirstRow().above().get(x);
          } else {
            leftCell = context.grid.get(yAcc / yCnt).get(x);
          }
        }
        if (newCell != null && newCell.getValue() == currentElement) {
          newCell.setValue(null);
        }
        newCell = leftCell.after();

        // set all incoming pathes unpackable
        for (LayoutingElement e : precedingElements) {
          BPMNElement el = (BPMNElement) e;
          Cell<BPMNElement> target = context.grid.getCellOfItem(el);
          if (target == null) {
            // don't set unpackable in other grids (other edge
            // layout)
            continue;
          }
          Cell<BPMNElement> start = target.getParent().get(x + 1);
          for (Cell<BPMNElement> cCell = start; cCell != target; cCell = cCell.getPrevCell()) {
            cCell.setPackable(false);
          }
        }

        // if not prelayouted
      } else if (newCell == null) {
        BPMNElement preElem = (BPMNElement) precedingElements.get(0);
        leftCell = context.grid.getCellOfItem(preElem);
        if (leftCell == null) {
          Grid<BPMNElement> preGrid = getContextByElement(preElem).grid;
          Cell<BPMNElement> preCell = preGrid.getCellOfItem(preElem);
          if (preCell == null) {
            System.err.println("Cannot find Cell for " + preElem);
          }

          List<Grid<BPMNElement>> grids = superGrid.getGrids();
          Row<BPMNElement> newRow = null;
          if (grids.indexOf(preGrid) < grids.indexOf(context.grid)) {
            newRow = context.grid.addFirstRow();
          } else {
            newRow = context.grid.addLastRow();
          }
          leftCell = newRow.get(Math.max(0, preCell.getParent().find(preCell)));
        }
        newCell = leftCell.after();
      }
      if (newCell.isFilled() && !newCell.getValue().equals(currentElement)) {
        newCell.getParent().insertRowBeneath();
        newCell = newCell.beneath();
      }
      newCell.setValue(currentElement);
    }
    return newCell;
  }
  /**
   * @param currentElement
   * @param context
   * @param cellOfElement
   */
  private void prelayoutSuccessors(
      BPMNElement currentElement, GridContext context, Cell<BPMNElement> cellOfElement) {
    // preLayout following Elements
    Cell<BPMNElement> baseCell = cellOfElement.after();
    Cell<BPMNElement> topCell = baseCell;
    List<LayoutingElement> followingElements = currentElement.getFollowingElements();

    if (BPMNType.isAActivity(currentElement.getType())) {
      // special case for docked events
      List<BPMNElement> dockedEventFollowers = new LinkedList<BPMNElement>();
      for (LayoutingElement el : currentElement.getOutgoingLinks()) {
        BPMNElement element = (BPMNElement) el;
        if (element.isADockedIntermediateEvent()) {
          for (LayoutingElement follower : element.getFollowingElements()) {
            dockedEventFollowers.add((BPMNElement) follower);
          }
        }
      }
      // to avoid crossing edges if there is more than one
      // docked event
      Collections.reverse(dockedEventFollowers);

      // put them under the task
      Cell<BPMNElement> insertCell = baseCell;
      for (BPMNElement dockedEventFollower : dockedEventFollowers) {
        Cell<BPMNElement> oldCell = context.grid.getCellOfItem(dockedEventFollower);
        if (oldCell != null) {
          if (oldCell.getValue() == dockedEventFollower) {
            continue; // Bug-Workaround: Don't prelayout
            // layouted elements;
          }
        }
        insertCell.getParent().insertRowBeneath();
        insertCell = insertCell.beneath();
        context.grid.setCellOfItem(dockedEventFollower, insertCell); // prelayout
      }

      // remove them from the following processing
      followingElements.removeAll(dockedEventFollowers);
    }

    // heuristic for text- & data-objects: put them to the top
    List<BPMNElement> textAnnotations = new LinkedList<BPMNElement>();
    List<BPMNElement> dataObjects = new LinkedList<BPMNElement>();
    for (LayoutingElement el : followingElements) {
      BPMNElement e = (BPMNElement) el;
      if (e.getType().equals(BPMNType.TextAnnotation)) {
        textAnnotations.add(e);
      } else if (e.getType().equals(BPMNType.DataObject)) {
        dataObjects.add(e);
      }
    }
    followingElements.removeAll(textAnnotations);
    followingElements.removeAll(dataObjects);
    // add them at the front
    followingElements.addAll(0, dataObjects);
    followingElements.addAll(0, textAnnotations);

    // heuristic for direct connection to join
    BPMNElement directJoin = null;
    for (LayoutingElement possibleJoin : followingElements) {
      if (possibleJoin.isJoin()) {
        directJoin = (BPMNElement) possibleJoin;
      }
    }
    if (directJoin != null) {
      // put in the middle
      followingElements.remove(directJoin);
      int position = (followingElements.size() / 2);
      followingElements.add(position, directJoin);
    }

    // normal preLayout following Elements
    int follow = 0;
    for (LayoutingElement newElem : followingElements) {
      if (newElem.getParent() == currentElement.getParent()) {
        follow++;
      }
    }
    for (int i = 0; i < follow / 2; i++) {
      topCell.getParent().insertRowAbove();
      baseCell.getParent().insertRowBeneath();
      topCell = topCell.above();
    }

    for (LayoutingElement newElem : followingElements) {
      if (newElem.getParent() != currentElement.getParent()) {
        continue;
      }
      context.grid.setCellOfItem((BPMNElement) newElem, topCell); // prelayout
      topCell = topCell.beneath();
      if (topCell == baseCell && follow % 2 == 0) {
        // skip baseCell if an even amount of elements is
        // following
        topCell = topCell.beneath();
      }
    }
  }
  public void doLayout() {
    superGrid = new SuperGrid<BPMNElement>();
    parent2Context.clear();
    lane2LaneChilds.clear();
    maxLaneDepth = 0;

    if (parent == null) {
      for (LayoutingElement pool : diagram.getElementsOfType(BPMNType.Pool)) {
        prepareLanes((BPMNElement) pool, 1);
      }
    }

    layoutElements();

    if (parent == null) {
      // set collapsed pools
      List<LayoutingElement> collapsedPools =
          this.diagram.getElementsOfType(BPMNType.CollapsedPool);
      Grid<BPMNElement> cpGrid = new Grid<BPMNElement>();
      superGrid.add(0, cpGrid);
      for (LayoutingElement collapsedPool : collapsedPools) {
        // make them small to not disturb finding the biggest ones in
        // each row / column

        collapsedPool.setGeometry(new LayoutingBoundsImpl(0, 0, 0, COLLAPSED_POOL_HEIGHT));
        for (Cell<BPMNElement> insertCell : cpGrid.addLastRow()) {
          insertCell.setValue((BPMNElement) collapsedPool);
        }
      }

      calcGeometry(superGrid);

      poolWidth = Math.max(poolWidth, COLLAPSED_POOL_MIN_WIDTH);

      // place Lanes
      for (LayoutingElement pool : diagram.getElementsOfType(BPMNType.Pool)) {
        Grid<BPMNElement> firstGrid = findFirstGridOfPool((BPMNElement) pool);
        int firstGridFirstRowIndex = superGrid.findRow(firstGrid.getFirstRow());

        double poolY = 0;
        for (int i = 0; i < firstGridFirstRowIndex; i++) {
          poolY += heightOfRow[i];
        }
        placeLane((BPMNElement) pool, poolY, 0);
      }

      writeGeometry(superGrid);

      // set pools to start at x = CELL_MARGIN & correct size

      for (LayoutingElement collapsedPool : collapsedPools) {
        collapsedPool.setGeometry(
            new LayoutingBoundsImpl(
                CELL_MARGIN, collapsedPool.getGeometry().getY(), poolWidth, COLLAPSED_POOL_HEIGHT));
        ((BPMNElement) collapsedPool).updateDataModel();
      }

      // convert Coordinates of Elements in Lanes from absolut to
      // realitive
      for (LayoutingElement pool : diagram.getElementsOfType(BPMNType.Pool)) {
        correctLaneElements((BPMNElement) pool, pool.getGeometry().getY(), 0);
      }

    } else {
      calcGeometry(superGrid);
      writeGeometry(superGrid);
    }
  }