package net.sf.jasperreports.crosstabs.fill.calculation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.TreeMap;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.util.JRProperties;

public class BucketingService {
  public static final String PROPERTY_BUCKET_MEASURE_LIMIT = "net.sf.jasperreports.crosstab.bucket.measure.limit";
  
  protected static final byte DIMENSION_ROW = 0;
  
  protected static final byte DIMENSION_COLUMN = 1;
  
  protected static final int DIMENSIONS = 2;
  
  protected final BucketDefinition[] allBuckets;
  
  protected final BucketDefinition[][] buckets;
  
  protected final int rowBucketCount;
  
  protected final int colBucketCount;
  
  protected final boolean[][] retrieveTotal;
  
  private boolean[] rowRetrTotals;
  
  private int rowRetrTotalMin;
  
  private int rowRetrTotalMax;
  
  private int[] rowRetrColMax;
  
  protected final MeasureDefinition[] measures;
  
  protected final int origMeasureCount;
  
  protected final int[] measureIndexes;
  
  protected final boolean sorted;
  
  protected final BucketMap bucketValueMap;
  
  protected long dataCount;
  
  protected boolean processed;
  
  protected HeaderCell[][] colHeaders;
  
  protected HeaderCell[][] rowHeaders;
  
  protected CrosstabCell[][] cells;
  
  private final MeasureDefinition.MeasureValue[] zeroUserMeasureValues;
  
  private final int bucketMeasureLimit;
  
  private int runningBucketMeasureCount = 0;
  
  public BucketingService(List rowBuckets, List columnBuckets, List measures, boolean sorted, boolean[][] retrieveTotal) {
    this.sorted = sorted;
    this.buckets = new BucketDefinition[2][];
    this.rowBucketCount = rowBuckets.size();
    this.buckets[0] = new BucketDefinition[this.rowBucketCount];
    rowBuckets.toArray((Object[])this.buckets[0]);
    this.colBucketCount = columnBuckets.size();
    this.buckets[1] = new BucketDefinition[this.colBucketCount];
    columnBuckets.toArray((Object[])this.buckets[1]);
    this.allBuckets = new BucketDefinition[this.rowBucketCount + this.colBucketCount];
    System.arraycopy(this.buckets[0], 0, this.allBuckets, 0, this.rowBucketCount);
    System.arraycopy(this.buckets[1], 0, this.allBuckets, this.rowBucketCount, this.colBucketCount);
    this.origMeasureCount = measures.size();
    List measuresList = new ArrayList(measures.size() * 2);
    List measureIndexList = new ArrayList(measures.size() * 2);
    int i;
    for (i = 0; i < measures.size(); i++) {
      MeasureDefinition measure = measures.get(i);
      addMeasure(measure, i, measuresList, measureIndexList);
    } 
    this.measures = new MeasureDefinition[measuresList.size()];
    measuresList.toArray((Object[])this.measures);
    this.measureIndexes = new int[measureIndexList.size()];
    for (i = 0; i < this.measureIndexes.length; i++)
      this.measureIndexes[i] = ((Integer)measureIndexList.get(i)).intValue(); 
    this.retrieveTotal = retrieveTotal;
    checkTotals();
    this.bucketValueMap = createBucketMap(0);
    this.zeroUserMeasureValues = initUserMeasureValues();
    this.bucketMeasureLimit = JRProperties.getIntegerProperty("net.sf.jasperreports.crosstab.bucket.measure.limit", 0);
  }
  
  protected void checkTotals() {
    this.rowRetrTotalMin = this.rowBucketCount + 1;
    this.rowRetrTotalMax = -1;
    this.rowRetrTotals = new boolean[this.rowBucketCount + 1];
    this.rowRetrColMax = new int[this.rowBucketCount + 1];
    for (int row = 0; row <= this.rowBucketCount; row++) {
      this.rowRetrColMax[row] = -1;
      boolean total = false;
      for (int i = 0; i <= this.colBucketCount; i++) {
        if (this.retrieveTotal[row][i]) {
          total = true;
          this.rowRetrColMax[row] = i;
        } 
      } 
      this.rowRetrTotals[row] = total;
      if (total) {
        if (row < this.rowRetrTotalMin)
          this.rowRetrTotalMin = row; 
        this.rowRetrTotalMax = row;
        if (row < this.rowBucketCount)
          this.allBuckets[row].setComputeTotal(); 
      } 
    } 
    for (int col = 0; col < this.colBucketCount; col++) {
      BucketDefinition colBucket = this.allBuckets[this.rowBucketCount + col];
      if (!colBucket.computeTotal()) {
        boolean total = false;
        for (int i = 0; !total && i <= this.rowBucketCount; i++)
          total = this.retrieveTotal[i][col]; 
        if (total)
          colBucket.setComputeTotal(); 
      } 
    } 
    for (int d = 0; d < 2; d++) {
      boolean dTotal = false;
      for (int i = 0; i < (this.buckets[d]).length; i++) {
        if (dTotal) {
          this.buckets[d][i].setComputeTotal();
        } else {
          dTotal = this.buckets[d][i].computeTotal();
        } 
      } 
    } 
  }
  
  public void clear() {
    this.bucketValueMap.clear();
    this.processed = false;
    this.dataCount = 0L;
    this.runningBucketMeasureCount = 0;
  }
  
  protected BucketMap createBucketMap(int level) {
    BucketMap map;
    if (this.sorted) {
      map = new BucketListMap(level, false);
    } else {
      map = new BucketTreeMap(level);
    } 
    return map;
  }
  
  protected BucketListMap createCollectBucketMap(int level) {
    return new BucketListMap(level, true);
  }
  
  protected void addMeasure(MeasureDefinition measure, int index, List measuresList, List measureIndexList) {
    MeasureDefinition sumMeasure, varianceMeasure, countMeasure, measureDefinition1;
    switch (measure.getCalculation()) {
      case 3:
      case 7:
        sumMeasure = MeasureDefinition.createHelperMeasure(measure, (byte)2);
        addMeasure(sumMeasure, index, measuresList, measureIndexList);
        measureDefinition1 = MeasureDefinition.createHelperMeasure(measure, (byte)1);
        addMeasure(measureDefinition1, index, measuresList, measureIndexList);
        break;
      case 6:
        varianceMeasure = MeasureDefinition.createHelperMeasure(measure, (byte)7);
        addMeasure(varianceMeasure, index, measuresList, measureIndexList);
        break;
      case 10:
        countMeasure = MeasureDefinition.createDistinctCountHelperMeasure(measure);
        addMeasure(countMeasure, index, measuresList, measureIndexList);
        break;
    } 
    measuresList.add(measure);
    measureIndexList.add(new Integer(index));
  }
  
  public void addData(Object[] bucketValues, Object[] measureValues) throws JRException {
    if (this.processed)
      throw new JRException("Crosstab data has already been processed."); 
    this.dataCount++;
    BucketDefinition.Bucket[] bucketVals = getBucketValues(bucketValues);
    MeasureDefinition.MeasureValue[] values = this.bucketValueMap.insertMeasureValues(bucketVals);
    for (int i = 0; i < this.measures.length; i++)
      values[i].addValue(measureValues[this.measureIndexes[i]]); 
  }
  
  protected void bucketMeasuresCreated() {
    this.runningBucketMeasureCount += this.origMeasureCount;
    checkBucketMeasureCount(this.runningBucketMeasureCount);
  }
  
  protected BucketDefinition.Bucket[] getBucketValues(Object[] bucketValues) {
    BucketDefinition.Bucket[] bucketVals = new BucketDefinition.Bucket[this.allBuckets.length];
    for (int i = 0; i < this.allBuckets.length; i++) {
      BucketDefinition bucket = this.allBuckets[i];
      Object value = bucketValues[i];
      bucketVals[i] = bucket.create(value);
    } 
    return bucketVals;
  }
  
  protected MeasureDefinition.MeasureValue[] initMeasureValues() {
    MeasureDefinition.MeasureValue[] values = new MeasureDefinition.MeasureValue[this.measures.length];
    for (int i = 0; i < this.measures.length; i++) {
      MeasureDefinition measure = this.measures[i];
      measure.getClass();
      values[i] = new MeasureDefinition.MeasureValue(measure);
      switch (measure.getCalculation()) {
        case 3:
        case 7:
          values[i].setHelper(values[i - 2], (byte)1);
          values[i].setHelper(values[i - 1], (byte)0);
          break;
        case 6:
          values[i].setHelper(values[i - 1], (byte)2);
        case 10:
          values[i].setHelper(values[i - 1], (byte)0);
          break;
      } 
    } 
    return values;
  }
  
  protected MeasureDefinition.MeasureValue[] initUserMeasureValues() {
    MeasureDefinition.MeasureValue[] vals = new MeasureDefinition.MeasureValue[this.origMeasureCount];
    for (int c = 0, i = 0; i < this.measures.length; i++) {
      if (!this.measures[i].isSystemDefined()) {
        this.measures[i].getClass();
        vals[c] = new MeasureDefinition.MeasureValue(this.measures[i]);
        c++;
      } 
    } 
    return vals;
  }
  
  public void processData() throws JRException {
    if (!this.processed) {
      if (this.dataCount > 0L) {
        if (this.allBuckets[this.rowBucketCount - 1].computeTotal() || this.allBuckets[this.allBuckets.length - 1].computeTotal())
          computeTotals(this.bucketValueMap); 
        createCrosstab();
      } 
      this.processed = true;
    } 
  }
  
  public boolean hasData() {
    return (this.dataCount > 0L);
  }
  
  public HeaderCell[][] getColumnHeaders() {
    return this.colHeaders;
  }
  
  public HeaderCell[][] getRowHeaders() {
    return this.rowHeaders;
  }
  
  public CrosstabCell[][] getCrosstabCells() {
    return this.cells;
  }
  
  public MeasureDefinition.MeasureValue[] getMeasureValues(BucketDefinition.Bucket[] bucketValues) {
    BucketMap map = this.bucketValueMap;
    for (int i = 0; map != null && i < this.allBuckets.length - 1; i++)
      map = (BucketMap)map.get(bucketValues[i]); 
    return (map == null) ? null : (MeasureDefinition.MeasureValue[])map.get(bucketValues[this.allBuckets.length - 1]);
  }
  
  protected MeasureDefinition.MeasureValue[] getUserMeasureValues(MeasureDefinition.MeasureValue[] values) {
    MeasureDefinition.MeasureValue[] vals = new MeasureDefinition.MeasureValue[this.origMeasureCount];
    for (int c = 0, i = 0; i < this.measures.length; i++) {
      if (!this.measures[i].isSystemDefined()) {
        vals[c] = values[i];
        c++;
      } 
    } 
    return vals;
  }
  
  public MeasureDefinition.MeasureValue[] getGrandTotals() {
    BucketMap map = this.bucketValueMap;
    for (int i = 0; map != null && i < this.allBuckets.length - 1; i++)
      map = (BucketMap)map.getTotalEntry().getValue(); 
    return (map == null) ? null : (MeasureDefinition.MeasureValue[])map.getTotalEntry().getValue();
  }
  
  protected void computeTotals(BucketMap bucketMap) throws JRException {
    byte dimension = (bucketMap.level < this.rowBucketCount) ? 0 : 1;
    if (dimension == 1 && !this.allBuckets[this.allBuckets.length - 1].computeTotal())
      return; 
    if (!bucketMap.last)
      for (Iterator it = bucketMap.entryIterator(); it.hasNext(); ) {
        Map.Entry entry = it.next();
        computeTotals((BucketMap)entry.getValue());
      }  
    if (this.allBuckets[bucketMap.level].computeTotal())
      if (dimension == 1) {
        computeColumnTotal(bucketMap);
      } else {
        computeRowTotals(bucketMap);
      }  
  }
  
  protected void sumVals(MeasureDefinition.MeasureValue[] totals, MeasureDefinition.MeasureValue[] vals) throws JRException {
    for (int i = 0; i < this.measures.length; i++)
      totals[i].addValue(vals[i]); 
  }
  
  protected void computeColumnTotal(BucketMap bucketMap) throws JRException {
    MeasureDefinition.MeasureValue[] totals = initMeasureValues();
    for (Iterator it = bucketMap.entryIterator(); it.hasNext(); ) {
      Map.Entry entry = it.next();
      for (int j = bucketMap.level + 1; j < this.allBuckets.length; j++)
        entry = ((BucketMap)entry.getValue()).getTotalEntry(); 
      sumVals(totals, (MeasureDefinition.MeasureValue[])entry.getValue());
    } 
    for (int i = bucketMap.level + 1; i < this.allBuckets.length; i++)
      bucketMap = bucketMap.addTotalNextMap(); 
    bucketMap.addTotalEntry(totals);
  }
  
  protected void computeRowTotals(BucketMap bucketMap) throws JRException {
    BucketListMap totals = createCollectBucketMap(this.rowBucketCount);
    for (Iterator it = bucketMap.entryIterator(); it.hasNext(); ) {
      Map.Entry entry = it.next();
      for (int j = bucketMap.level + 1; j < this.rowBucketCount; j++)
        entry = ((BucketMap)entry.getValue()).getTotalEntry(); 
      totals.collectVals((BucketMap)entry.getValue(), true);
    } 
    BucketMap totalBucketMap = bucketMap;
    for (int i = bucketMap.level + 1; i < this.rowBucketCount; i++)
      totalBucketMap = totalBucketMap.addTotalNextMap(); 
    totalBucketMap.addTotalEntry(totals);
  }
  
  protected static class MapEntry implements Map.Entry, Comparable {
    final BucketDefinition.Bucket key;
    
    final Object value;
    
    MapEntry(BucketDefinition.Bucket key, Object value) {
      this.key = key;
      this.value = value;
    }
    
    public Object getKey() {
      return this.key;
    }
    
    public Object getValue() {
      return this.value;
    }
    
    public Object setValue(Object value) {
      throw new UnsupportedOperationException();
    }
    
    public int compareTo(Object o) {
      return this.key.compareTo(((MapEntry)o).key);
    }
    
    public String toString() {
      return this.key + ":" + this.value;
    }
  }
  
  protected abstract class BucketMap {
    final int level;
    
    final boolean last;
    
    final BucketDefinition.Bucket totalKey;
    
    private final BucketingService this$0;
    
    BucketMap(int level) {
      this.level = level;
      this.last = (level == BucketingService.this.allBuckets.length - 1);
      this.totalKey = (BucketingService.this.allBuckets[level]).VALUE_TOTAL;
    }
    
    BucketMap addTotalNextMap() {
      BucketMap nextMap = BucketingService.this.createBucketMap(this.level + 1);
      addTotalEntry(nextMap);
      return nextMap;
    }
    
    abstract void set(BucketDefinition.Bucket param1Bucket, Object param1Object);
    
    abstract void clear();
    
    abstract Iterator entryIterator();
    
    abstract Object get(BucketDefinition.Bucket param1Bucket);
    
    abstract MeasureDefinition.MeasureValue[] insertMeasureValues(BucketDefinition.Bucket[] param1ArrayOfBucket);
    
    abstract void addTotalEntry(Object param1Object);
    
    abstract int size();
    
    abstract Map.Entry getTotalEntry();
  }
  
  protected class BucketTreeMap extends BucketMap {
    TreeMap map;
    
    private final BucketingService this$0;
    
    BucketTreeMap(int level) {
      super(level);
      this.map = new TreeMap();
    }
    
    void clear() {
      this.map.clear();
    }
    
    Iterator entryIterator() {
      return this.map.entrySet().iterator();
    }
    
    Object get(BucketDefinition.Bucket key) {
      return this.map.get(key);
    }
    
    MeasureDefinition.MeasureValue[] insertMeasureValues(BucketDefinition.Bucket[] bucketValues) {
      BucketTreeMap levelMap = (BucketTreeMap)BucketingService.this.bucketValueMap;
      for (int i = 0; i < bucketValues.length - 1; i++) {
        BucketTreeMap nextMap = (BucketTreeMap)levelMap.get(bucketValues[i]);
        if (nextMap == null) {
          nextMap = new BucketTreeMap(i + 1);
          levelMap.map.put(bucketValues[i], nextMap);
        } 
        levelMap = nextMap;
      } 
      MeasureDefinition.MeasureValue[] values = (MeasureDefinition.MeasureValue[])levelMap.get(bucketValues[bucketValues.length - 1]);
      if (values == null) {
        values = BucketingService.this.initMeasureValues();
        levelMap.map.put(bucketValues[bucketValues.length - 1], values);
        BucketingService.this.bucketMeasuresCreated();
      } 
      return values;
    }
    
    int size() {
      return this.map.size();
    }
    
    void addTotalEntry(Object value) {
      this.map.put(this.totalKey, value);
    }
    
    Map.Entry getTotalEntry() {
      Object value = get(this.totalKey);
      return (value == null) ? null : new BucketingService.MapEntry(this.totalKey, value);
    }
    
    public String toString() {
      return this.map.toString();
    }
    
    void set(BucketDefinition.Bucket key, Object value) {
      this.map.put(key, value);
    }
  }
  
  protected class BucketListMap extends BucketMap {
    List entries;
    
    private final BucketingService this$0;
    
    BucketListMap(int level, boolean linked) {
      super(level);
      if (linked) {
        this.entries = new LinkedList();
      } else {
        this.entries = new ArrayList();
      } 
    }
    
    void clear() {
      this.entries.clear();
    }
    
    Iterator entryIterator() {
      return this.entries.iterator();
    }
    
    private void add(BucketDefinition.Bucket key, Object value) {
      this.entries.add(new BucketingService.MapEntry(key, value));
    }
    
    Object get(BucketDefinition.Bucket key) {
      int idx = Collections.binarySearch(this.entries, new BucketingService.MapEntry(key, null));
      return (idx >= 0) ? ((BucketingService.MapEntry)this.entries.get(idx)).value : null;
    }
    
    MeasureDefinition.MeasureValue[] insertMeasureValues(BucketDefinition.Bucket[] bucketValues) {
      int i = 0;
      Object levelObj = this;
      BucketListMap map = null;
      while (i < BucketingService.this.allBuckets.length) {
        map = (BucketListMap)levelObj;
        int size = map.entries.size();
        if (size == 0)
          break; 
        BucketingService.MapEntry lastEntry = map.entries.get(size - 1);
        if (!lastEntry.key.equals(bucketValues[i]))
          break; 
        i++;
        levelObj = lastEntry.value;
      } 
      if (i == BucketingService.this.allBuckets.length)
        return (MeasureDefinition.MeasureValue[])levelObj; 
      while (i < BucketingService.this.allBuckets.length - 1) {
        BucketListMap nextMap = new BucketListMap(i + 1, false);
        map.add(bucketValues[i], nextMap);
        map = nextMap;
        i++;
      } 
      MeasureDefinition.MeasureValue[] values = BucketingService.this.initMeasureValues();
      map.add(bucketValues[i], values);
      BucketingService.this.bucketMeasuresCreated();
      return values;
    }
    
    int size() {
      return this.entries.size();
    }
    
    void addTotalEntry(Object value) {
      add(this.totalKey, value);
    }
    
    Map.Entry getTotalEntry() {
      BucketingService.MapEntry lastEntry = this.entries.get(this.entries.size() - 1);
      if (lastEntry.key.isTotal())
        return lastEntry; 
      return null;
    }
    
    void set(BucketDefinition.Bucket key, Object value) {
      BucketingService.MapEntry mapEntry = new BucketingService.MapEntry(key, value);
      int idx = Collections.binarySearch(this.entries, mapEntry);
      int ins = -idx - 1;
      this.entries.add(ins, mapEntry);
    }
    
    void collectVals(BucketingService.BucketMap map, boolean sum) throws JRException {
      ListIterator totalIt = this.entries.listIterator();
      BucketingService.MapEntry totalItEntry = totalIt.hasNext() ? totalIt.next() : null;
      Iterator it = map.entryIterator();
      Map.Entry entry = it.hasNext() ? it.next() : null;
      while (entry != null) {
        BucketDefinition.Bucket key = (BucketDefinition.Bucket)entry.getKey();
        int compare = (totalItEntry == null) ? -1 : key.compareTo(totalItEntry.key);
        if (compare <= 0) {
          Object addVal = null;
          if (this.last) {
            if (sum) {
              MeasureDefinition.MeasureValue[] totalVals = (compare == 0) ? (MeasureDefinition.MeasureValue[])totalItEntry.value : null;
              if (totalVals == null) {
                totalVals = BucketingService.this.initMeasureValues();
                addVal = totalVals;
              } 
              BucketingService.this.sumVals(totalVals, (MeasureDefinition.MeasureValue[])entry.getValue());
            } 
          } else {
            BucketListMap nextTotals = (compare == 0) ? (BucketListMap)totalItEntry.value : null;
            if (nextTotals == null) {
              nextTotals = BucketingService.this.createCollectBucketMap(this.level + 1);
              addVal = nextTotals;
            } 
            nextTotals.collectVals((BucketingService.BucketMap)entry.getValue(), sum);
          } 
          if (compare < 0) {
            if (totalItEntry != null)
              totalIt.previous(); 
            totalIt.add(new BucketingService.MapEntry(key, addVal));
            if (totalItEntry != null)
              totalIt.next(); 
          } 
          entry = it.hasNext() ? it.next() : null;
        } 
        if (compare >= 0)
          totalItEntry = totalIt.hasNext() ? totalIt.next() : null; 
      } 
    }
  }
  
  protected void createCrosstab() throws JRException {
    BucketListMap collectedCols;
    CollectedList[] collectedHeaders = new CollectedList[2];
    collectedHeaders[0] = createHeadersList((byte)0, this.bucketValueMap, 0, false);
    if (this.allBuckets[0].computeTotal()) {
      BucketMap map = this.bucketValueMap;
      for (int i = 0; i < this.rowBucketCount; i++)
        map = (BucketMap)map.getTotalEntry().getValue(); 
      collectedCols = (BucketListMap)map;
    } else {
      collectedCols = createCollectBucketMap(this.rowBucketCount);
      collectCols(collectedCols, this.bucketValueMap);
    } 
    collectedHeaders[1] = createHeadersList((byte)1, collectedCols, 0, false);
    int rowBuckets = (collectedHeaders[0]).span;
    int colBuckets = (collectedHeaders[1]).span;
    int bucketMeasureCount = rowBuckets * colBuckets * this.origMeasureCount;
    checkBucketMeasureCount(bucketMeasureCount);
    this.colHeaders = createHeaders((byte)1, collectedHeaders);
    this.rowHeaders = createHeaders((byte)0, collectedHeaders);
    this.cells = new CrosstabCell[rowBuckets][colBuckets];
    fillCells(collectedHeaders, this.bucketValueMap, 0, new int[] { 0, 0 }, new ArrayList(), new ArrayList());
  }
  
  protected void checkBucketMeasureCount(int bucketMeasureCount) {
    if (this.bucketMeasureLimit > 0 && bucketMeasureCount > this.bucketMeasureLimit)
      throw new JRRuntimeException("Crosstab bucket/measure limit (" + this.bucketMeasureLimit + ") exceeded."); 
  }
  
  protected void collectCols(BucketListMap collectedCols, BucketMap bucketMap) throws JRException {
    if (this.allBuckets[bucketMap.level].computeTotal()) {
      BucketMap map = bucketMap;
      for (int i = bucketMap.level; i < this.rowBucketCount; i++)
        map = (BucketMap)map.getTotalEntry().getValue(); 
      collectedCols.collectVals(map, false);
      return;
    } 
    for (Iterator it = bucketMap.entryIterator(); it.hasNext(); ) {
      Map.Entry entry = it.next();
      BucketMap nextMap = (BucketMap)entry.getValue();
      if (bucketMap.level == this.rowBucketCount - 1) {
        collectedCols.collectVals(nextMap, false);
        continue;
      } 
      collectCols(collectedCols, nextMap);
    } 
  }
  
  protected CollectedList createHeadersList(byte dimension, BucketMap bucketMap, int level, boolean total) {
    CollectedList headers = new CollectedList();
    for (Iterator it = bucketMap.entryIterator(); it.hasNext(); ) {
      Map.Entry entry = it.next();
      BucketDefinition.Bucket bucketValue = (BucketDefinition.Bucket)entry.getKey();
      boolean totalBucket = bucketValue.isTotal();
      byte totalPosition = this.allBuckets[bucketMap.level].getTotalPosition();
      boolean createHeader = (!totalBucket || total || totalPosition != 0);
      if (createHeader) {
        CollectedList nextHeaders;
        if (level + 1 < (this.buckets[dimension]).length) {
          BucketMap nextMap = (BucketMap)entry.getValue();
          nextHeaders = createHeadersList(dimension, nextMap, level + 1, (total || totalBucket));
        } else {
          nextHeaders = new CollectedList();
          nextHeaders.span = 1;
        } 
        nextHeaders.key = bucketValue;
        if (totalBucket) {
          if (totalPosition == 1) {
            headers.addFirst(nextHeaders);
            continue;
          } 
          headers.add(nextHeaders);
          continue;
        } 
        headers.add(nextHeaders);
      } 
    } 
    if (headers.span == 0)
      headers.span = 1; 
    return headers;
  }
  
  protected HeaderCell[][] createHeaders(byte dimension, CollectedList[] headersLists) {
    HeaderCell[][] headers = new HeaderCell[(this.buckets[dimension]).length][(headersLists[dimension]).span];
    List vals = new ArrayList();
    fillHeaders(dimension, headers, 0, 0, headersLists[dimension], vals);
    return headers;
  }
  
  protected void fillHeaders(byte dimension, HeaderCell[][] headers, int level, int col, CollectedList list, List vals) {
    if (level == (this.buckets[dimension]).length)
      return; 
    for (Iterator it = list.iterator(); it.hasNext(); ) {
      CollectedList subList = (CollectedList)it.next();
      vals.add(subList.key);
      int depthSpan = subList.key.isTotal() ? ((this.buckets[dimension]).length - level) : 1;
      BucketDefinition.Bucket[] values = new BucketDefinition.Bucket[(this.buckets[dimension]).length];
      vals.toArray(values);
      headers[level][col] = new HeaderCell(values, subList.span, depthSpan);
      if (!subList.key.isTotal())
        fillHeaders(dimension, headers, level + 1, col, subList, vals); 
      col += subList.span;
      vals.remove(vals.size() - 1);
    } 
  }
  
  protected void fillCells(CollectedList[] collectedHeaders, BucketMap bucketMap, int level, int[] pos, List vals, List bucketMaps) {
    bucketMaps.add(bucketMap);
    byte dimension = (level < this.rowBucketCount) ? 0 : 1;
    boolean last = (level == this.allBuckets.length - 1);
    CollectedList[] nextCollected = null;
    if (!last) {
      nextCollected = new CollectedList[2];
      for (int d = 0; d < 2; d++) {
        if (d != dimension)
          nextCollected[d] = collectedHeaders[d]; 
      } 
    } 
    boolean incrementRow = (level == (this.buckets[0]).length - 1);
    CollectedList collectedList = collectedHeaders[dimension];
    Iterator bucketIt = (bucketMap == null) ? null : bucketMap.entryIterator();
    Map.Entry bucketItEntry = (bucketIt != null && bucketIt.hasNext()) ? bucketIt.next() : null;
    for (Iterator it = collectedList.iterator(); it.hasNext(); ) {
      CollectedList list = (CollectedList)it.next();
      Map.Entry bucketEntry = null;
      if (list.key.isTotal()) {
        if (bucketMap != null)
          bucketEntry = bucketMap.getTotalEntry(); 
      } else if (bucketItEntry != null && bucketItEntry.getKey().equals(list.key)) {
        bucketEntry = bucketItEntry;
        bucketItEntry = bucketIt.hasNext() ? bucketIt.next() : null;
      } 
      vals.add(list.key);
      if (last) {
        fillCell(pos, vals, bucketMaps, bucketEntry);
      } else {
        nextCollected[dimension] = list;
        BucketMap nextMap = (bucketEntry == null) ? null : (BucketMap)bucketEntry.getValue();
        fillCells(nextCollected, nextMap, level + 1, pos, vals, bucketMaps);
      } 
      vals.remove(vals.size() - 1);
      if (incrementRow) {
        pos[0] = pos[0] + 1;
        pos[1] = 0;
      } 
    } 
    bucketMaps.remove(bucketMaps.size() - 1);
  }
  
  protected void fillCell(int[] pos, List vals, List bucketMaps, Map.Entry bucketEntry) {
    Iterator valsIt = vals.iterator();
    BucketDefinition.Bucket[] rowValues = new BucketDefinition.Bucket[(this.buckets[0]).length];
    for (int i = 0; i < rowValues.length; i++)
      rowValues[i] = valsIt.next(); 
    BucketDefinition.Bucket[] columnValues = new BucketDefinition.Bucket[(this.buckets[1]).length];
    for (int j = 0; j < columnValues.length; j++)
      columnValues[j] = valsIt.next(); 
    MeasureDefinition.MeasureValue[] measureVals = (bucketEntry == null) ? this.zeroUserMeasureValues : getUserMeasureValues((MeasureDefinition.MeasureValue[])bucketEntry.getValue());
    MeasureDefinition.MeasureValue[][][] totals = retrieveTotals(vals, bucketMaps);
    this.cells[pos[0]][pos[1]] = new CrosstabCell(rowValues, columnValues, measureVals, totals);
    pos[1] = pos[1] + 1;
  }
  
  protected MeasureDefinition.MeasureValue[][][] retrieveTotals(List vals, List bucketMaps) {
    MeasureDefinition.MeasureValue[][][] totals = new MeasureDefinition.MeasureValue[this.rowBucketCount + 1][this.colBucketCount + 1][];
    for (int row = this.rowRetrTotalMax; row >= this.rowRetrTotalMin; row--) {
      if (this.rowRetrTotals[row]) {
        BucketMap rowMap = bucketMaps.get(row);
        for (int i = row; rowMap != null && i < this.rowBucketCount; i++) {
          Map.Entry totalEntry = rowMap.getTotalEntry();
          rowMap = (totalEntry == null) ? null : (BucketMap)totalEntry.getValue();
        } 
        for (int col = 0; col <= this.rowRetrColMax[row]; col++) {
          BucketMap colMap = rowMap;
          if (col < this.colBucketCount - 1)
            if (row == this.rowBucketCount) {
              rowMap = bucketMaps.get(this.rowBucketCount + col + 1);
            } else if (rowMap != null) {
              rowMap = (BucketMap)rowMap.get(vals.get(this.rowBucketCount + col));
            }  
          if (this.retrieveTotal[row][col]) {
            for (int j = col + 1; colMap != null && j < this.colBucketCount; j++)
              colMap = (BucketMap)colMap.getTotalEntry().getValue(); 
            if (colMap != null)
              if (col == this.colBucketCount) {
                MeasureDefinition.MeasureValue[] measureValues = (MeasureDefinition.MeasureValue[])colMap.get(vals.get(this.rowBucketCount + this.colBucketCount - 1));
                totals[row][col] = getUserMeasureValues(measureValues);
              } else {
                Map.Entry totalEntry = colMap.getTotalEntry();
                if (totalEntry != null) {
                  MeasureDefinition.MeasureValue[] totalValues = (MeasureDefinition.MeasureValue[])totalEntry.getValue();
                  totals[row][col] = getUserMeasureValues(totalValues);
                } 
              }  
            if (totals[row][col] == null)
              totals[row][col] = this.zeroUserMeasureValues; 
          } 
        } 
      } 
    } 
    return totals;
  }
  
  protected static class CollectedList extends LinkedList {
    private static final long serialVersionUID = 10200L;
    
    int span = 0;
    
    BucketDefinition.Bucket key;
    
    public boolean add(Object o) {
      boolean added = super.add(o);
      incrementSpan(o);
      return added;
    }
    
    public void addFirst(Object o) {
      super.addFirst(o);
      incrementSpan(o);
    }
    
    public void addLast(Object o) {
      super.add(o);
      incrementSpan(o);
    }
    
    private void incrementSpan(Object o) {
      if (o != null && o instanceof CollectedList) {
        this.span += ((CollectedList)o).span;
      } else {
        this.span++;
      } 
    }
    
    public String toString() {
      return this.key + "/" + this.span + ": " + super.toString();
    }
  }
}
