@Override
  @SuppressWarnings("unchecked")
  protected AbstractGroup[][] prepareGroups() {

    // Check
    String valid = isValid();
    if (valid != null) {
      throw new IllegalArgumentException(valid);
    }

    // Create adjustments
    Range<T>[] ranges = getAdjustedRanges();
    Range<T> tempLower = ranges[0];
    Range<T> tempUpper = ranges[1];

    // Build leaf level index
    ArrayList<IndexNode> nodes = new ArrayList<IndexNode>();
    for (int i = 0, len = intervals.size(); i < len; i += INDEX_FANOUT) {
      int min = i;
      int max = Math.min(i + INDEX_FANOUT - 1, len - 1);

      List<Interval<T>> leafs = new ArrayList<Interval<T>>();
      for (int j = min; j <= max; j++) {
        leafs.add(intervals.get(j));
      }

      nodes.add(
          new IndexNode(
              intervals.get(min).min,
              intervals.get(max).max,
              leafs.toArray(new Interval[leafs.size()])));
    }

    // Builder inner nodes
    while (nodes.size() > 1) {
      List<IndexNode> current = (List<IndexNode>) nodes.clone();
      nodes.clear();
      for (int i = 0, len = current.size(); i < len; i += INDEX_FANOUT) {
        int min = i;
        int max = Math.min(i + INDEX_FANOUT - 1, len - 1);
        List<IndexNode> temp = new ArrayList<IndexNode>();
        for (int j = min; j <= max; j++) {
          temp.add(current.get(j));
        }

        nodes.add(
            new IndexNode(
                current.get(min).min,
                current.get(max).max,
                temp.toArray(new HierarchyBuilderIntervalBased.IndexNode[temp.size()])));
      }
    }

    // Prepare
    String[] data = getData();
    List<AbstractGroup[]> result = new ArrayList<AbstractGroup[]>();
    IndexNode index = nodes.get(0);

    // Prepare
    DataTypeWithRatioScale<T> type = (DataTypeWithRatioScale<T>) getDataType();
    Map<AbstractGroup, AbstractGroup> cache = new HashMap<AbstractGroup, AbstractGroup>();

    // Create snap intervals
    Interval<T> lowerSnap = getInterval(index, type, tempLower.repeatBound);
    lowerSnap =
        new Interval<T>(
            this, getDataType(), tempLower.snapBound, lowerSnap.max, lowerSnap.function);

    Interval<T> upperSnap = getIntervalUpperSnap(index, type, tempUpper.repeatBound);
    upperSnap =
        new Interval<T>(
            this, getDataType(), upperSnap.min, tempUpper.snapBound, upperSnap.function);

    // Overlapping snaps -> one interval
    if (type.compare(lowerSnap.max, upperSnap.min) > 0) {
      // We could use lowerSnap.function or upperSnap.function
      lowerSnap =
          new Interval<T>(this, getDataType(), lowerSnap.min, upperSnap.max, lowerSnap.function);
      upperSnap = lowerSnap;
    }

    // Create first column
    AbstractGroup[] first = new AbstractGroup[data.length];
    for (int i = 0; i < data.length; i++) {
      T value = type.parse(data[i]);
      Interval<T> interval;

      if (value == null) {
        interval = new Interval<T>(this);
      } else if (type.compare(value, tempLower.labelBound) < 0) {
        throw new IllegalArgumentException(type.format(value) + " is < lower label bound");
      } else if (type.compare(value, tempLower.snapBound) < 0) {
        interval = new Interval<T>(this, true, tempLower.snapBound);
      } else if (type.compare(value, tempUpper.labelBound) >= 0) {
        throw new IllegalArgumentException(type.format(value) + " is >= upper label bound");
      } else if (type.compare(value, tempUpper.snapBound) >= 0) {
        interval = new Interval<T>(this, false, tempUpper.snapBound);
      } else {
        interval = getInterval(index, type, value);
      }

      if (interval.min != null && interval.max != null) {
        if (type.compare(interval.min, lowerSnap.max) < 0) {
          interval = lowerSnap;
        } else if (type.compare(interval.max, upperSnap.min) > 0) {
          interval = upperSnap;
        }
      }

      first[i] = getGroup(cache, interval);
    }
    result.add(first);

    // Clean
    index = null;

    // Create other columns
    List<Group<T>> groups = new ArrayList<Group<T>>();
    if (!super.getLevels().isEmpty()) groups = super.getLevels().get(0).getGroups();
    if (cache.size() > 1 && !groups.isEmpty()) {

      // Prepare
      List<Interval<T>> newIntervals = new ArrayList<Interval<T>>();
      int intervalIndex = 0;
      int multiplier = 0;
      T width = type.subtract(intervals.get(intervals.size() - 1).max, intervals.get(0).min);

      // Merge intervals
      for (Group<T> group : groups) {

        // Find min and max
        T min = null;
        T max = null;
        for (int i = 0; i < group.getSize(); i++) {
          Interval<T> current = intervals.get(intervalIndex++);
          T offset = type.multiply(width, multiplier);
          T cMin = type.add(current.min, offset);
          T cMax = type.add(current.max, offset);
          if (min == null || type.compare(min, cMin) > 0) {
            min = cMin;
          }
          if (max == null || type.compare(max, cMax) < 0) {
            max = cMax;
          }
          if (intervalIndex == intervals.size()) {
            intervalIndex = 0;
            multiplier++;
          }
        }

        // Add interval
        newIntervals.add(new Interval<T>(this, getDataType(), min, max, group.getFunction()));
      }

      // Compute next column
      HierarchyBuilderIntervalBased<T> builder =
          new HierarchyBuilderIntervalBased<T>(getDataType(), tempLower, tempUpper);
      for (Interval<T> interval : newIntervals) {
        builder.addInterval(interval.min, interval.max, interval.function);
      }

      for (int i = 1; i < super.getLevels().size(); i++) {
        for (Group<T> sgroup : super.getLevel(i).getGroups()) {
          builder.getLevel(i - 1).addGroup(sgroup.getSize(), sgroup.getFunction());
        }
      }

      // Copy data
      builder.prepare(data);
      AbstractGroup[][] columns = builder.getPreparedGroups();
      for (AbstractGroup[] column : columns) {
        result.add(column);
      }
    } else {
      if (cache.size() > 1) {
        AbstractGroup[] column = new AbstractGroup[data.length];
        @SuppressWarnings("serial")
        AbstractGroup element = new AbstractGroup(DataType.ANY_VALUE) {};
        for (int i = 0; i < column.length; i++) {
          column[i] = element;
        }
        result.add(column);
      }
    }

    // Return
    return result.toArray(new AbstractGroup[0][0]);
  }