package net.sf.jasperreports.engine.fill;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import net.sf.jasperreports.crosstabs.JRCellContents;
import net.sf.jasperreports.crosstabs.JRCrosstab;
import net.sf.jasperreports.crosstabs.JRCrosstabBucket;
import net.sf.jasperreports.crosstabs.JRCrosstabCell;
import net.sf.jasperreports.crosstabs.JRCrosstabColumnGroup;
import net.sf.jasperreports.crosstabs.JRCrosstabDataset;
import net.sf.jasperreports.crosstabs.JRCrosstabGroup;
import net.sf.jasperreports.crosstabs.JRCrosstabMeasure;
import net.sf.jasperreports.crosstabs.JRCrosstabParameter;
import net.sf.jasperreports.crosstabs.JRCrosstabRowGroup;
import net.sf.jasperreports.crosstabs.base.JRBaseCrosstab;
import net.sf.jasperreports.crosstabs.design.JRDesignCrosstab;
import net.sf.jasperreports.crosstabs.fill.JRCrosstabExpressionEvaluator;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabCell;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabColumnGroup;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabGroup;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabMeasure;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabParameter;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabRowGroup;
import net.sf.jasperreports.crosstabs.fill.calculation.BucketDefinition;
import net.sf.jasperreports.crosstabs.fill.calculation.BucketingService;
import net.sf.jasperreports.crosstabs.fill.calculation.CrosstabCell;
import net.sf.jasperreports.crosstabs.fill.calculation.HeaderCell;
import net.sf.jasperreports.crosstabs.fill.calculation.MeasureDefinition;
import net.sf.jasperreports.engine.JRCommonElement;
import net.sf.jasperreports.engine.JRDatasetParameter;
import net.sf.jasperreports.engine.JRElement;
import net.sf.jasperreports.engine.JRElementDataset;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExpression;
import net.sf.jasperreports.engine.JRExpressionChunk;
import net.sf.jasperreports.engine.JRExpressionCollector;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPrintRectangle;
import net.sf.jasperreports.engine.JRRectangle;
import net.sf.jasperreports.engine.JRReport;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JRStyle;
import net.sf.jasperreports.engine.JRVariable;
import net.sf.jasperreports.engine.JRVisitor;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.design.JRDesignRectangle;
import net.sf.jasperreports.engine.util.JRStyleResolver;
import org.jfree.data.general.Dataset;

public class JRFillCrosstab extends JRFillElement implements JRCrosstab {
  protected final JRCrosstab parentCrosstab;
  
  protected JRFillCrosstabDataset dataset;
  
  protected JRFillCrosstabRowGroup[] rowGroups;
  
  protected Map rowGroupsMap;
  
  protected JRFillCrosstabColumnGroup[] columnGroups;
  
  protected Map columnGroupsMap;
  
  protected JRFillCrosstabMeasure[] measures;
  
  protected BucketingService bucketingService;
  
  protected JRFillVariable[] variables;
  
  protected Map variablesMap;
  
  protected JRFillVariable[][][] totalVariables;
  
  protected boolean[][] retrieveTotal;
  
  protected JRFillCrosstabParameter[] parameters;
  
  protected Map parametersMap;
  
  protected JRCrosstabExpressionEvaluator crosstabEvaluator;
  
  protected JRFillCrosstabCell[][] crossCells;
  
  protected JRFillCellContents headerCell;
  
  protected JRFillCellContents whenNoDataCell;
  
  protected boolean hasData;
  
  protected HeaderCell[][] columnHeadersData;
  
  protected HeaderCell[][] rowHeadersData;
  
  protected CrosstabCell[][] cellData;
  
  protected MeasureDefinition.MeasureValue[] grandTotals;
  
  private boolean percentage;
  
  private CrosstabFiller crosstabFiller;
  
  public JRFillCrosstab(JRBaseFiller filler, JRCrosstab crosstab, JRFillObjectFactory factory) {
    super(filler, (JRElement)crosstab, factory);
    this.parentCrosstab = crosstab;
    loadEvaluator(filler.getJasperReport());
    JRFillObjectFactory crosstabFactory = new JRFillObjectFactory(factory, (JRFillExpressionEvaluator)this.crosstabEvaluator);
    this.headerCell = crosstabFactory.getCell(crosstab.getHeaderCell());
    copyRowGroups(crosstab, crosstabFactory);
    copyColumnGroups(crosstab, crosstabFactory);
    copyMeasures(crosstab, crosstabFactory);
    copyCells(crosstab, crosstabFactory);
    this.whenNoDataCell = crosstabFactory.getCell(crosstab.getWhenNoDataCell());
    this.dataset = factory.getCrosstabDataset(crosstab.getDataset(), this);
    copyParameters(crosstab, factory);
    copyVariables(crosstab, crosstabFactory);
    this.crosstabFiller = new CrosstabFiller();
  }
  
  public byte getMode() {
    return JRStyleResolver.getMode((JRCommonElement)this, (byte)2);
  }
  
  private void copyRowGroups(JRCrosstab crosstab, JRFillObjectFactory factory) {
    JRCrosstabRowGroup[] groups = crosstab.getRowGroups();
    this.rowGroups = new JRFillCrosstabRowGroup[groups.length];
    this.rowGroupsMap = new HashMap();
    for (int i = 0; i < groups.length; i++) {
      JRFillCrosstabRowGroup group = factory.getCrosstabRowGroup(groups[i]);
      group.getFillHeader().setVerticalPositionType(groups[i].getPosition());
      this.rowGroups[i] = group;
      this.rowGroupsMap.put(group.getName(), new Integer(i));
    } 
  }
  
  private void copyColumnGroups(JRCrosstab crosstab, JRFillObjectFactory factory) {
    JRCrosstabColumnGroup[] groups = crosstab.getColumnGroups();
    this.columnGroups = new JRFillCrosstabColumnGroup[groups.length];
    this.columnGroupsMap = new HashMap();
    for (int i = 0; i < groups.length; i++) {
      JRFillCrosstabColumnGroup group = factory.getCrosstabColumnGroup(groups[i]);
      this.columnGroups[i] = group;
      this.columnGroupsMap.put(group.getName(), new Integer(i));
    } 
  }
  
  private void copyMeasures(JRCrosstab crosstab, JRFillObjectFactory factory) {
    JRCrosstabMeasure[] crossMeasures = crosstab.getMeasures();
    this.measures = new JRFillCrosstabMeasure[crossMeasures.length];
    for (int i = 0; i < crossMeasures.length; i++)
      this.measures[i] = factory.getCrosstabMeasure(crossMeasures[i]); 
  }
  
  private void copyParameters(JRCrosstab crosstab, JRFillObjectFactory factory) {
    JRCrosstabParameter[] crossParams = crosstab.getParameters();
    this.parameters = new JRFillCrosstabParameter[crossParams.length];
    this.parametersMap = new HashMap();
    for (int i = 0; i < crossParams.length; i++) {
      this.parameters[i] = factory.getCrosstabParameter(crossParams[i]);
      this.parametersMap.put(this.parameters[i].getName(), this.parameters[i]);
    } 
  }
  
  private void copyCells(JRCrosstab crosstab, JRFillObjectFactory factory) {
    JRCrosstabCell[][] crosstabCells = crosstab.getCells();
    this.crossCells = new JRFillCrosstabCell[this.rowGroups.length + 1][this.columnGroups.length + 1];
    for (int i = 0; i <= this.rowGroups.length; i++) {
      for (int j = 0; j <= this.columnGroups.length; j++) {
        if (crosstabCells[i][j] != null)
          this.crossCells[i][j] = factory.getCrosstabCell(crosstabCells[i][j]); 
      } 
    } 
  }
  
  private void copyVariables(JRCrosstab crosstab, JRFillObjectFactory factory) {
    JRVariable[] vars = crosstab.getVariables();
    this.variables = new JRFillVariable[vars.length];
    this.variablesMap = new HashMap();
    for (int i = 0; i < this.variables.length; i++) {
      this.variables[i] = factory.getVariable(vars[i]);
      this.variablesMap.put(this.variables[i].getName(), this.variables[i]);
    } 
    Map totalVarPos = new HashMap();
    this.totalVariables = new JRFillVariable[this.rowGroups.length + 1][this.columnGroups.length + 1][this.measures.length];
    for (int row = 0; row <= this.rowGroups.length; row++) {
      JRFillCrosstabRowGroup jRFillCrosstabRowGroup = (row == this.rowGroups.length) ? null : this.rowGroups[row];
      for (int col = 0; col <= this.columnGroups.length; col++) {
        JRFillCrosstabColumnGroup jRFillCrosstabColumnGroup = (col == this.columnGroups.length) ? null : this.columnGroups[col];
        if (row < this.rowGroups.length || col < this.columnGroups.length)
          for (int m = 0; m < this.measures.length; m++) {
            String totalVariableName = JRDesignCrosstab.getTotalVariableName((JRCrosstabMeasure)this.measures[m], (JRCrosstabRowGroup)jRFillCrosstabRowGroup, (JRCrosstabColumnGroup)jRFillCrosstabColumnGroup);
            this.totalVariables[row][col][m] = (JRFillVariable)this.variablesMap.get(totalVariableName);
            totalVarPos.put(totalVariableName, new int[] { row, col });
          }  
      } 
    } 
    this.retrieveTotal = new boolean[this.rowGroups.length + 1][this.columnGroups.length + 1];
    List expressions = JRExpressionCollector.collectExpressions((JRReport)this.filler.getJasperReport(), crosstab);
    for (Iterator iter = expressions.iterator(); iter.hasNext(); ) {
      JRExpression expression = iter.next();
      JRExpressionChunk[] chunks = expression.getChunks();
      if (chunks != null)
        for (int j = 0; j < chunks.length; j++) {
          JRExpressionChunk chunk = chunks[j];
          if (chunk.getType() == 4) {
            String varName = chunk.getText();
            int[] pos = (int[])totalVarPos.get(varName);
            if (pos != null)
              this.retrieveTotal[pos[0]][pos[1]] = true; 
          } 
        }  
    } 
  }
  
  protected void loadEvaluator(JasperReport jasperReport) {
    try {
      JREvaluator evaluator = JasperCompileManager.loadEvaluator(jasperReport, this.parentCrosstab);
      this.crosstabEvaluator = new JRCrosstabExpressionEvaluator(evaluator);
    } catch (JRException e) {
      throw new JRRuntimeException("Could not load evaluator for crosstab.", e);
    } 
  }
  
  private BucketingService createService(byte evaluation) throws JRException {
    List rowBuckets = new ArrayList(this.rowGroups.length);
    for (int i = 0; i < this.rowGroups.length; i++)
      rowBuckets.add(createServiceBucket((JRCrosstabGroup)this.rowGroups[i], evaluation)); 
    List colBuckets = new ArrayList(this.columnGroups.length);
    for (int j = 0; j < this.columnGroups.length; j++)
      colBuckets.add(createServiceBucket((JRCrosstabGroup)this.columnGroups[j], evaluation)); 
    this.percentage = false;
    List measureList = new ArrayList(this.measures.length);
    for (int k = 0; k < this.measures.length; k++) {
      measureList.add(createServiceMeasure(this.measures[k]));
      this.percentage |= (this.measures[k].getPercentageOfType() == 1) ? 1 : 0;
    } 
    if (this.percentage) {
      ((BucketDefinition)rowBuckets.get(0)).setComputeTotal();
      ((BucketDefinition)colBuckets.get(0)).setComputeTotal();
    } 
    return new BucketingService(rowBuckets, colBuckets, measureList, this.dataset.isDataPreSorted(), this.retrieveTotal);
  }
  
  private BucketDefinition createServiceBucket(JRCrosstabGroup group, byte evaluation) throws JRException {
    JRCrosstabBucket bucket = group.getBucket();
    Comparator comparator = null;
    JRExpression comparatorExpression = bucket.getComparatorExpression();
    if (comparatorExpression != null)
      comparator = (Comparator)evaluateExpression(comparatorExpression, evaluation); 
    byte totalPosition = group.getTotalPosition();
    return new BucketDefinition(bucket.getExpression().getValueClass(), comparator, bucket.getOrder(), totalPosition);
  }
  
  private MeasureDefinition createServiceMeasure(JRFillCrosstabMeasure measure) {
    return new MeasureDefinition(measure.getValueClass(), measure.getCalculation(), measure.getIncrementerFactory());
  }
  
  protected void reset() {
    super.reset();
    for (int i = 0; i < this.variables.length; i++) {
      this.variables[i].setValue(null);
      this.variables[i].setInitialized(true);
    } 
  }
  
  protected void evaluate(byte evaluation) throws JRException {
    reset();
    evaluatePrintWhenExpression(evaluation);
    evaluateProperties(evaluation);
    if (isPrintWhenExpressionNull() || isPrintWhenTrue()) {
      this.dataset.evaluateDatasetRun(evaluation);
      initEvaluator(evaluation);
      this.bucketingService.processData();
      this.hasData = this.bucketingService.hasData();
      if (this.hasData) {
        this.columnHeadersData = this.bucketingService.getColumnHeaders();
        this.rowHeadersData = this.bucketingService.getRowHeaders();
        this.cellData = this.bucketingService.getCrosstabCells();
        if (this.percentage)
          this.grandTotals = this.bucketingService.getGrandTotals(); 
        this.crosstabFiller.initCrosstab();
      } 
    } 
  }
  
  protected void initEvaluator(byte evaluation) throws JRException {
    Map parameterValues = JRFillSubreport.getParameterValues(this.filler, getParametersMapExpression(), (JRDatasetParameter[])getParameters(), evaluation, true, false, false);
    ResourceBundle resBdl = (ResourceBundle)parameterValues.get("REPORT_RESOURCE_BUNDLE");
    if (resBdl == null) {
      JRFillParameter resourceBundleParam = (JRFillParameter)this.filler.getParametersMap().get("REPORT_RESOURCE_BUNDLE");
      parameterValues.put("REPORT_RESOURCE_BUNDLE", resourceBundleParam.getValue());
    } 
    parameterValues.put("REPORT_PARAMETERS_MAP", parameterValues);
    for (int i = 0; i < this.parameters.length; i++) {
      Object value = parameterValues.get(this.parameters[i].getName());
      this.parameters[i].setValue(value);
    } 
    this.crosstabEvaluator.init(this.parametersMap, this.variablesMap, this.filler.getWhenResourceMissingType());
  }
  
  protected void initBucketingService() {
    if (this.bucketingService == null) {
      try {
        this.bucketingService = createService((byte)1);
      } catch (JRException e) {
        throw new JRRuntimeException("Could not create bucketing service", e);
      } 
    } else {
      this.bucketingService.clear();
    } 
  }
  
  protected boolean prepare(int availableStretchHeight, boolean isOverflow) throws JRException {
    super.prepare(availableStretchHeight, isOverflow);
    if (!isToPrint())
      return false; 
    if (availableStretchHeight < getRelativeY() - getY() - getBandBottomY()) {
      setToPrint(false);
      return true;
    } 
    if (isOverflow && this.crosstabFiller.ended() && isAlreadyPrinted())
      if (isPrintWhenDetailOverflows()) {
        rewind();
        setReprinted(true);
      } else {
        setStretchHeight(getHeight());
        setToPrint(false);
        return false;
      }  
    if (isOverflow && isPrintWhenDetailOverflows())
      setReprinted(true); 
    int availableHeight = getHeight() + availableStretchHeight - getRelativeY() + getY() + getBandBottomY();
    this.crosstabFiller.fill(availableHeight);
    boolean willOverflow = this.crosstabFiller.willOverflow();
    setStretchHeight(willOverflow ? availableHeight : this.crosstabFiller.getUsedHeight());
    return willOverflow;
  }
  
  protected JRPrintElement fill() {
    JRPrintRectangle printRectangle = null;
    printRectangle = new JRTemplatePrintRectangle(getJRTemplateRectangle());
    printRectangle.setX(getX());
    printRectangle.setY(getRelativeY());
    printRectangle.setWidth(getWidth());
    printRectangle.setHeight(getStretchHeight());
    return (JRPrintElement)printRectangle;
  }
  
  protected JRTemplateRectangle getJRTemplateRectangle() {
    JRStyle style = getStyle();
    JRTemplateRectangle template = (JRTemplateRectangle)getTemplate(style);
    if (template == null) {
      JRDesignRectangle rectangle = new JRDesignRectangle();
      rectangle.setKey(getKey());
      rectangle.setPositionType(getPositionType());
      rectangle.setMode(getMode());
      rectangle.setX(getX());
      rectangle.setY(getY());
      rectangle.setWidth(getWidth());
      rectangle.setHeight(getHeight());
      rectangle.setRemoveLineWhenBlank(isRemoveLineWhenBlank());
      rectangle.setPrintInFirstWholeBand(isPrintInFirstWholeBand());
      rectangle.setPrintWhenDetailOverflows(isPrintWhenDetailOverflows());
      rectangle.setPrintWhenGroupChanges(getPrintWhenGroupChanges());
      rectangle.setForecolor(getForecolor());
      rectangle.setBackcolor(getBackcolor());
      rectangle.getLinePen().setLineWidth(0.0F);
      template = new JRTemplateRectangle(this.band.getOrigin(), this.filler.getJasperPrint().getDefaultStyleProvider(), (JRRectangle)rectangle);
      registerTemplate(style, template);
    } 
    return template;
  }
  
  protected void rewind() {
    this.crosstabFiller.initCrosstab();
  }
  
  protected List getPrintElements() {
    List printElements = this.crosstabFiller.getPrintElements();
    if (getRunDirection() == 1)
      mirrorPrintElements(printElements); 
    return printElements;
  }
  
  protected void mirrorPrintElements(List printElements) {
    for (Iterator it = printElements.iterator(); it.hasNext(); ) {
      JRPrintElement element = it.next();
      int mirrorX = getWidth() - element.getX() - element.getWidth();
      element.setX(mirrorX);
    } 
  }
  
  protected void resolveElement(JRPrintElement element, byte evaluation) {}
  
  public void collectExpressions(JRExpressionCollector collector) {
    collector.collect(this);
  }
  
  public void visit(JRVisitor visitor) {
    visitor.visitCrosstab(this);
  }
  
  public int getId() {
    return this.parentCrosstab.getId();
  }
  
  public JRCrosstabDataset getDataset() {
    return this.dataset;
  }
  
  public JRCrosstabRowGroup[] getRowGroups() {
    return (JRCrosstabRowGroup[])this.rowGroups;
  }
  
  public JRCrosstabColumnGroup[] getColumnGroups() {
    return (JRCrosstabColumnGroup[])this.columnGroups;
  }
  
  public JRCrosstabMeasure[] getMeasures() {
    return (JRCrosstabMeasure[])this.measures;
  }
  
  public class JRFillCrosstabDataset extends JRFillElementDataset implements JRCrosstabDataset {
    private Object[] bucketValues;
    
    private Object[] measureValues;
    
    private final JRFillCrosstab this$0;
    
    public JRFillCrosstabDataset(JRCrosstabDataset dataset, JRFillObjectFactory factory) {
      super((JRElementDataset)dataset, factory);
      this.bucketValues = new Object[JRFillCrosstab.this.rowGroups.length + JRFillCrosstab.this.columnGroups.length];
      this.measureValues = new Object[JRFillCrosstab.this.measures.length];
    }
    
    protected void customInitialize() {
      JRFillCrosstab.this.initBucketingService();
    }
    
    protected void customEvaluate(JRCalculator calculator) throws JRExpressionEvalException {
      int i;
      for (i = 0; i < JRFillCrosstab.this.rowGroups.length; i++)
        this.bucketValues[i] = calculator.evaluate(JRFillCrosstab.this.rowGroups[i].getBucket().getExpression()); 
      for (i = 0; i < JRFillCrosstab.this.columnGroups.length; i++)
        this.bucketValues[i + JRFillCrosstab.this.rowGroups.length] = calculator.evaluate(JRFillCrosstab.this.columnGroups[i].getBucket().getExpression()); 
      for (i = 0; i < JRFillCrosstab.this.measures.length; i++)
        this.measureValues[i] = calculator.evaluate(JRFillCrosstab.this.measures[i].getValueExpression()); 
    }
    
    protected void customIncrement() {
      try {
        JRFillCrosstab.this.bucketingService.addData(this.bucketValues, this.measureValues);
      } catch (JRException e) {
        throw new JRRuntimeException("Error incrementing crosstab dataset", e);
      } 
    }
    
    protected Dataset getCustomDataset() {
      return null;
    }
    
    public void collectExpressions(JRExpressionCollector collector) {}
    
    public boolean isDataPreSorted() {
      return ((JRCrosstabDataset)this.parent).isDataPreSorted();
    }
  }
  
  protected class CrosstabFiller {
    private int yOffset;
    
    private boolean willOverflow;
    
    private int[] rowHeadersXOffsets;
    
    private boolean[] columnBreakable;
    
    private boolean[] rowBreakable;
    
    private int[] columnCount;
    
    private int[] rowCount;
    
    private int[] columnXOffsets;
    
    private boolean noDataCellPrinted;
    
    private int startRowIndex;
    
    private int startColumnIndex;
    
    private int lastColumnIndex;
    
    private List columnHeaders;
    
    private List printRows;
    
    private HeaderCell[] spanHeaders;
    
    private int[] spanHeadersStart;
    
    private List rowYs = new ArrayList();
    
    private int rowIdx;
    
    private List preparedRow = new ArrayList();
    
    private int preparedRowHeight;
    
    private boolean printRowHeaders;
    
    private boolean printColumnHeaders;
    
    private JRFillVariable rowCountVar;
    
    private JRFillVariable colCountVar;
    
    private final JRFillCrosstab this$0;
    
    protected CrosstabFiller() {
      setRowHeadersXOffsets();
      this.printRows = new ArrayList();
      this.rowCountVar = (JRFillVariable)JRFillCrosstab.this.variablesMap.get("ROW_COUNT");
      this.colCountVar = (JRFillVariable)JRFillCrosstab.this.variablesMap.get("COLUMN_COUNT");
    }
    
    protected void initCrosstab() {
      this.columnXOffsets = computeOffsets(JRFillCrosstab.this.columnHeadersData, (JRFillCrosstabGroup[])JRFillCrosstab.this.columnGroups, true);
      this.columnBreakable = computeBreakableHeaders(JRFillCrosstab.this.columnHeadersData, (JRFillCrosstabGroup[])JRFillCrosstab.this.columnGroups, this.columnXOffsets, true, true);
      this.columnCount = computeCounts(JRFillCrosstab.this.columnHeadersData);
      int[] rowYOffsets = computeOffsets(JRFillCrosstab.this.rowHeadersData, (JRFillCrosstabGroup[])JRFillCrosstab.this.rowGroups, false);
      this.rowBreakable = computeBreakableHeaders(JRFillCrosstab.this.rowHeadersData, (JRFillCrosstabGroup[])JRFillCrosstab.this.rowGroups, rowYOffsets, false, false);
      this.rowCount = computeCounts(JRFillCrosstab.this.rowHeadersData);
      this.spanHeaders = new HeaderCell[JRFillCrosstab.this.rowGroups.length - 1];
      this.spanHeadersStart = new int[JRFillCrosstab.this.rowGroups.length - 1];
      this.startRowIndex = 0;
      this.startColumnIndex = 0;
      this.lastColumnIndex = 0;
      this.noDataCellPrinted = false;
    }
    
    protected void setRowHeadersXOffsets() {
      this.rowHeadersXOffsets = new int[JRFillCrosstab.this.rowGroups.length + 1];
      this.rowHeadersXOffsets[0] = 0;
      for (int i = 0; i < JRFillCrosstab.this.rowGroups.length; i++)
        this.rowHeadersXOffsets[i + 1] = this.rowHeadersXOffsets[i] + JRFillCrosstab.this.rowGroups[i].getWidth(); 
    }
    
    protected int[] computeOffsets(HeaderCell[][] headersData, JRFillCrosstabGroup[] groups, boolean width) {
      int[] offsets = new int[(headersData[0]).length + 1];
      offsets[0] = 0;
      for (int i = 0; i < (headersData[0]).length; i++) {
        int size = 0;
        for (int j = groups.length - 1; j >= 0; j--) {
          if (headersData[j][i] != null) {
            JRFillCellContents cell = headersData[j][i].isTotal() ? groups[j].getFillTotalHeader() : groups[j].getFillHeader();
            size = (cell == null) ? 0 : (width ? cell.getWidth() : cell.getHeight());
            break;
          } 
        } 
        offsets[i + 1] = offsets[i] + size;
      } 
      return offsets;
    }
    
    protected boolean[] computeBreakableHeaders(HeaderCell[][] headersData, JRFillCrosstabGroup[] groups, int[] offsets, boolean width, boolean startHeaders) {
      boolean[] breakable = new boolean[(headersData[0]).length];
      for (int i = 0; i < breakable.length; i++)
        breakable[i] = true; 
      for (int j = 0; j < groups.length; j++) {
        JRFillCellContents fillHeader = groups[j].getFillHeader();
        if (fillHeader != null) {
          int size = width ? fillHeader.getWidth() : fillHeader.getHeight();
          for (int k = 0; k < (headersData[0]).length; k++) {
            HeaderCell header = headersData[j][k];
            if (header != null && !header.isTotal() && header.getLevelSpan() > 1) {
              int span = header.getLevelSpan();
              if (startHeaders)
                for (int n = k + 1; n < k + span && offsets[n] - offsets[k] < size; n++)
                  breakable[n] = false;  
              for (int m = k + span - 1; m > k && offsets[k + span] - offsets[m] < size; m--)
                breakable[m] = false; 
            } 
          } 
        } 
      } 
      return breakable;
    }
    
    private int[] computeCounts(HeaderCell[][] headersData) {
      int[] counts = new int[(headersData[0]).length];
      HeaderCell[] lastHeaders = headersData[headersData.length - 1];
      for (int i = 0, c = 0; i < counts.length; i++) {
        HeaderCell lastHeader = lastHeaders[i];
        if (lastHeader != null && !lastHeader.isTotal())
          c++; 
        counts[i] = c;
      } 
      return counts;
    }
    
    protected void fill(int availableHeight) throws JRException {
      this.printRows.clear();
      this.yOffset = 0;
      this.willOverflow = false;
      fillVerticalCrosstab(availableHeight);
    }
    
    protected boolean willOverflow() {
      return this.willOverflow;
    }
    
    protected int getUsedHeight() {
      return this.yOffset;
    }
    
    protected boolean ended() {
      return JRFillCrosstab.this.hasData ? ((this.startRowIndex >= (JRFillCrosstab.this.rowHeadersData[0]).length && this.startColumnIndex >= (JRFillCrosstab.this.columnHeadersData[0]).length)) : this.noDataCellPrinted;
    }
    
    protected void fillVerticalCrosstab(int availableHeight) throws JRException {
      if (!JRFillCrosstab.this.hasData) {
        fillNoDataCell(availableHeight);
        return;
      } 
      this.printRowHeaders = (this.startColumnIndex == 0 || JRFillCrosstab.this.isRepeatRowHeaders());
      int rowHeadersXOffset = this.printRowHeaders ? this.rowHeadersXOffsets[JRFillCrosstab.this.rowGroups.length] : 0;
      if (this.startColumnIndex == this.lastColumnIndex) {
        int availableWidth = JRFillCrosstab.this.getWidth();
        this.columnHeaders = getGroupHeaders(availableWidth - rowHeadersXOffset, this.columnXOffsets, this.columnBreakable, this.startColumnIndex, JRFillCrosstab.this.columnHeadersData, (JRFillCrosstabGroup[])JRFillCrosstab.this.columnGroups);
        this.lastColumnIndex = this.startColumnIndex + this.columnHeaders.size();
        if (this.startColumnIndex == this.lastColumnIndex)
          throw new JRRuntimeException("Not enough space to render the crosstab."); 
      } 
      this.printColumnHeaders = (this.startRowIndex == 0 || JRFillCrosstab.this.isRepeatColumnHeaders());
      List columnHeaderRows = null;
      if (this.printColumnHeaders) {
        columnHeaderRows = fillColumnHeaders(rowHeadersXOffset, availableHeight - this.yOffset);
        if (this.willOverflow)
          return; 
      } 
      int lastRowIndex = fillRows(rowHeadersXOffset, availableHeight - this.yOffset);
      if (lastRowIndex == this.startRowIndex) {
        this.willOverflow = true;
        return;
      } 
      if (columnHeaderRows != null)
        this.printRows.addAll(columnHeaderRows); 
      if (lastRowIndex >= (JRFillCrosstab.this.rowHeadersData[0]).length) {
        this.startColumnIndex = this.lastColumnIndex;
        if (this.startColumnIndex < (JRFillCrosstab.this.columnHeadersData[0]).length) {
          this.startRowIndex = lastRowIndex = 0;
          this.yOffset += JRFillCrosstab.this.getColumnBreakOffset();
          fillVerticalCrosstab(availableHeight);
          return;
        } 
      } 
      boolean fillEnded = (lastRowIndex >= (JRFillCrosstab.this.rowHeadersData[0]).length && this.lastColumnIndex >= (JRFillCrosstab.this.columnHeadersData[0]).length);
      if (fillEnded) {
        JRFillCrosstab.this.setStretchHeight(this.yOffset);
      } else {
        JRFillCrosstab.this.setStretchHeight(availableHeight);
      } 
      this.startRowIndex = lastRowIndex;
      this.willOverflow = !fillEnded;
    }
    
    protected List getGroupHeaders(int available, int[] offsets, boolean[] breakable, int firstIndex, HeaderCell[][] headersData, JRFillCrosstabGroup[] groups) {
      List headers = new ArrayList();
      int maxOffset = available + offsets[firstIndex];
      int lastIndex;
      for (lastIndex = firstIndex; lastIndex < (headersData[0]).length && offsets[lastIndex + 1] <= maxOffset; lastIndex++) {
        HeaderCell[] groupHeaders = new HeaderCell[groups.length];
        for (int j = 0; j < groups.length; j++)
          groupHeaders[j] = headersData[j][lastIndex]; 
        headers.add(groupHeaders);
      } 
      if (lastIndex < (headersData[0]).length)
        while (lastIndex > firstIndex && !breakable[lastIndex]) {
          lastIndex--;
          headers.remove(headers.size() - 1);
        }  
      if (lastIndex > firstIndex) {
        if (firstIndex > 0) {
          HeaderCell[] firstHeaders = headers.get(0);
          for (int j = 0; j < groups.length; j++) {
            HeaderCell header = headersData[j][firstIndex];
            if (header == null) {
              int spanIndex = getSpanIndex(firstIndex, j, headersData);
              if (spanIndex >= 0) {
                HeaderCell spanCell = headersData[j][spanIndex];
                int headerEndIdx = spanCell.getLevelSpan() + spanIndex;
                if (headerEndIdx > lastIndex)
                  headerEndIdx = lastIndex; 
                firstHeaders[j] = HeaderCell.createLevelSpanCopy(spanCell, headerEndIdx - firstIndex);
              } 
            } 
          } 
        } 
        if (lastIndex < (headersData[0]).length)
          for (int j = 0; j < groups.length; j++) {
            HeaderCell header = headersData[j][lastIndex];
            if (header == null) {
              int spanIndex = getSpanIndex(lastIndex, j, headersData);
              if (spanIndex >= firstIndex) {
                HeaderCell spanCell = headersData[j][spanIndex];
                HeaderCell[] headerCells = headers.get(spanIndex - firstIndex);
                headerCells[j] = HeaderCell.createLevelSpanCopy(spanCell, lastIndex - spanIndex);
              } 
            } 
          }  
      } 
      return headers;
    }
    
    protected int getSpanIndex(int i, int j, HeaderCell[][] headersData) {
      int spanIndex = i - 1;
      while (spanIndex >= 0 && headersData[j][spanIndex] == null)
        spanIndex--; 
      if (spanIndex >= 0) {
        HeaderCell spanCell = headersData[j][spanIndex];
        int span = spanCell.getLevelSpan();
        if (span > i - spanIndex)
          return spanIndex; 
      } 
      return -1;
    }
    
    protected void fillNoDataCell(int availableHeight) throws JRException {
      if (JRFillCrosstab.this.whenNoDataCell == null) {
        this.noDataCellPrinted = true;
      } else if (availableHeight < JRFillCrosstab.this.whenNoDataCell.getHeight()) {
        this.willOverflow = true;
      } else {
        JRFillCrosstab.this.whenNoDataCell.evaluate((byte)3);
        JRFillCrosstab.this.whenNoDataCell.prepare(availableHeight - JRFillCrosstab.this.whenNoDataCell.getHeight());
        this.willOverflow = JRFillCrosstab.this.whenNoDataCell.willOverflow();
        if (!this.willOverflow) {
          JRFillCrosstab.this.whenNoDataCell.setX(0);
          JRFillCrosstab.this.whenNoDataCell.setY(0);
          JRPrintFrame printCell = JRFillCrosstab.this.whenNoDataCell.fill();
          List noDataRow = new ArrayList(1);
          noDataRow.add(printCell);
          addPrintRow(noDataRow);
          this.yOffset += JRFillCrosstab.this.whenNoDataCell.getPrintHeight();
          this.noDataCellPrinted = true;
        } 
      } 
    }
    
    protected List fillColumnHeaders(int rowHeadersXOffset, int availableHeight) throws JRException {
      List headerRows;
      JRFillCellContents[][] columnHeaderRows = new JRFillCellContents[JRFillCrosstab.this.columnGroups.length][this.lastColumnIndex - this.startColumnIndex + 1];
      this.rowYs.clear();
      this.rowYs.add(new Integer(0));
      if (this.printRowHeaders && JRFillCrosstab.this.headerCell != null) {
        JRFillCellContents contents = fillHeader(availableHeight);
        if (this.willOverflow)
          return null; 
        columnHeaderRows[JRFillCrosstab.this.columnGroups.length - 1][0] = contents;
      } 
      label29: for (this.rowIdx = 0; this.rowIdx < JRFillCrosstab.this.columnGroups.length; this.rowIdx++) {
        for (int columnIdx = this.startColumnIndex; columnIdx < this.lastColumnIndex; columnIdx++) {
          HeaderCell[] headers = this.columnHeaders.get(columnIdx - this.startColumnIndex);
          HeaderCell cell = headers[this.rowIdx];
          if (cell != null) {
            JRFillCellContents contents = prepareColumnHeader(cell, columnIdx, rowHeadersXOffset, availableHeight);
            columnHeaderRows[this.rowIdx + cell.getDepthSpan() - 1][columnIdx - this.startColumnIndex + 1] = contents;
            if (this.willOverflow)
              break label29; 
          } 
        } 
        int rowStretchHeight = stretchColumnHeadersRow(columnHeaderRows[this.rowIdx]);
        this.rowYs.add(new Integer(((Integer)this.rowYs.get(this.rowIdx)).intValue() + rowStretchHeight));
      } 
      if (this.willOverflow) {
        headerRows = null;
        releaseColumnHeaderCells(columnHeaderRows);
      } else {
        headerRows = fillColumnHeaders(columnHeaderRows);
        this.yOffset += ((Integer)this.rowYs.get(JRFillCrosstab.this.columnGroups.length)).intValue();
      } 
      resetVariables();
      return headerRows;
    }
    
    private void setCountVars(int rowIdx, int colIdx) {
      if (rowIdx == -1) {
        this.rowCountVar.setValue(null);
      } else {
        this.rowCountVar.setValue(new Integer(this.rowCount[rowIdx]));
      } 
      if (colIdx == -1) {
        this.colCountVar.setValue(null);
      } else {
        this.colCountVar.setValue(new Integer(this.columnCount[colIdx]));
      } 
    }
    
    private JRFillCellContents fillHeader(int availableHeight) throws JRException {
      setCountVars(-1, -1);
      JRFillCellContents contents = JRFillCrosstab.this.headerCell.getWorkingClone();
      contents.evaluate((byte)3);
      contents.prepare(availableHeight - JRFillCrosstab.this.headerCell.getHeight());
      this.willOverflow = contents.willOverflow();
      if (!this.willOverflow) {
        contents.setX(0);
        contents.setY(this.yOffset);
        contents.setVerticalSpan(JRFillCrosstab.this.columnGroups.length);
      } 
      return contents;
    }
    
    private JRFillCellContents prepareColumnHeader(HeaderCell cell, int columnIdx, int xOffset, int availableHeight) throws JRException {
      JRFillCrosstabColumnGroup group = JRFillCrosstab.this.columnGroups[this.rowIdx];
      JRFillCellContents contents = cell.isTotal() ? group.getFillTotalHeader() : group.getFillHeader();
      int width = this.columnXOffsets[columnIdx + cell.getLevelSpan()] - this.columnXOffsets[columnIdx];
      int height = contents.getHeight();
      if (width <= 0 || height <= 0)
        return null; 
      JRFillCellContents preparedContents = null;
      int rowY = ((Integer)this.rowYs.get(this.rowIdx)).intValue();
      int cellAvailableStretch = availableHeight - rowY - height;
      if (cellAvailableStretch >= 0) {
        setCountVars(-1, columnIdx);
        setGroupVariables((JRFillCrosstabGroup[])JRFillCrosstab.this.columnGroups, cell.getBucketValues());
        contents = contents.getTransformedContents(width, height, group.getPosition(), (byte)1);
        boolean firstOnRow = (columnIdx == this.startColumnIndex && (!this.printRowHeaders || JRFillCrosstab.this.headerCell == null));
        contents = contents.getBoxContents((firstOnRow && JRFillCrosstab.this.getRunDirection() == 0), (firstOnRow && JRFillCrosstab.this.getRunDirection() == 1), false);
        contents = contents.getWorkingClone();
        contents.evaluate((byte)3);
        contents.prepare(cellAvailableStretch);
        if (contents.willOverflow()) {
          this.willOverflow = true;
        } else {
          contents.setX(this.columnXOffsets[columnIdx] - this.columnXOffsets[this.startColumnIndex] + xOffset);
          contents.setY(rowY + this.yOffset);
          contents.setVerticalSpan(cell.getDepthSpan());
          preparedContents = contents;
        } 
      } else {
        this.willOverflow = true;
      } 
      return preparedContents;
    }
    
    private int stretchColumnHeadersRow(JRFillCellContents[] headers) {
      int rowY = ((Integer)this.rowYs.get(this.rowIdx)).intValue();
      int rowStretchHeight = 0;
      int j;
      for (j = 0; j < headers.length; j++) {
        JRFillCellContents contents = headers[j];
        if (contents != null) {
          int startRowY = rowY;
          if (contents.getVerticalSpan() > 1)
            startRowY = ((Integer)this.rowYs.get(this.rowIdx - contents.getVerticalSpan() + 1)).intValue(); 
          int height = contents.getPrintHeight() - rowY + startRowY;
          if (height > rowStretchHeight)
            rowStretchHeight = height; 
        } 
      } 
      for (j = 0; j < headers.length; j++) {
        JRFillCellContents contents = headers[j];
        if (contents != null) {
          int startRowY = rowY;
          if (contents.getVerticalSpan() > 1)
            startRowY = ((Integer)this.rowYs.get(this.rowIdx - contents.getVerticalSpan() + 1)).intValue(); 
          contents.stretchTo(rowStretchHeight + rowY - startRowY);
        } 
      } 
      return rowStretchHeight;
    }
    
    private List fillColumnHeaders(JRFillCellContents[][] columnHeaderRows) throws JRException {
      List headerRows = new ArrayList(JRFillCrosstab.this.columnGroups.length);
      for (int i = 0; i < columnHeaderRows.length; i++) {
        List headerRow = new ArrayList(this.lastColumnIndex - this.startColumnIndex);
        headerRows.add(headerRow);
        for (int j = 0; j < (columnHeaderRows[i]).length; j++) {
          JRFillCellContents contents = columnHeaderRows[i][j];
          if (contents != null) {
            headerRow.add(contents.fill());
            contents.releaseWorkingClone();
          } 
        } 
      } 
      return headerRows;
    }
    
    private void releaseColumnHeaderCells(JRFillCellContents[][] columnHeaderRows) throws JRException {
      for (int i = 0; i < columnHeaderRows.length; i++) {
        for (int j = 0; j < (columnHeaderRows[i]).length; j++) {
          JRFillCellContents contents = columnHeaderRows[i][j];
          if (contents != null) {
            contents.rewind();
            contents.releaseWorkingClone();
          } 
        } 
      } 
    }
    
    protected int fillRows(int xOffset, int availableHeight) throws JRException {
      this.rowYs.clear();
      this.rowYs.add(new Integer(0));
      for (this.rowIdx = 0; this.rowIdx < JRFillCrosstab.this.cellData.length - this.startRowIndex; this.rowIdx++) {
        initPreparedRow();
        prepareRow(xOffset, availableHeight);
        if (this.willOverflow)
          break; 
        fillRow();
        this.rowYs.add(new Integer(((Integer)this.rowYs.get(this.rowIdx)).intValue() + this.preparedRowHeight));
      } 
      if (this.rowIdx < JRFillCrosstab.this.cellData.length - this.startRowIndex) {
        releasePreparedRow();
        if (this.printRowHeaders)
          fillContinuingRowHeaders(xOffset, availableHeight); 
      } 
      this.yOffset += ((Integer)this.rowYs.get(this.rowIdx)).intValue();
      return this.rowIdx + this.startRowIndex;
    }
    
    private void initPreparedRow() {
      this.preparedRow.clear();
      this.preparedRowHeight = 0;
    }
    
    private void removeFilledRows(int rowsToRemove) {
      if (rowsToRemove > 0) {
        for (int i = 0; i < rowsToRemove; i++) {
          this.printRows.remove(this.printRows.size() - 1);
          this.rowYs.remove(this.rowYs.size() - 1);
        } 
        this.rowIdx -= rowsToRemove;
      } 
    }
    
    private void releasePreparedRow() throws JRException {
      for (Iterator it = this.preparedRow.iterator(); it.hasNext(); ) {
        JRFillCellContents cell = it.next();
        cell.rewind();
        cell.releaseWorkingClone();
      } 
      this.preparedRow.clear();
    }
    
    private void fillRow() throws JRException {
      int rowY = ((Integer)this.rowYs.get(this.rowIdx)).intValue();
      List rowPrints = new ArrayList(this.preparedRow.size());
      for (Iterator it = this.preparedRow.iterator(); it.hasNext(); ) {
        JRFillCellContents cell = it.next();
        int spanHeight = 0;
        if (cell.getVerticalSpan() > 1)
          spanHeight = rowY - ((Integer)this.rowYs.get(this.rowIdx - cell.getVerticalSpan() + 1)).intValue(); 
        cell.stretchTo(this.preparedRowHeight + spanHeight);
        rowPrints.add(cell.fill());
        cell.releaseWorkingClone();
      } 
      addPrintRow(rowPrints);
    }
    
    private void prepareRow(int xOffset, int availableHeight) throws JRException {
      for (int col = this.startColumnIndex; col < this.lastColumnIndex; col++) {
        CrosstabCell data = JRFillCrosstab.this.cellData[this.rowIdx + this.startRowIndex][col];
        boolean overflow = prepareDataCell(data, col, availableHeight, xOffset);
        if (overflow) {
          this.willOverflow = true;
          return;
        } 
      } 
      resetVariables();
      if (this.printRowHeaders) {
        int j = 0;
        while (true) {
          boolean overflow;
          if (j < JRFillCrosstab.this.rowGroups.length) {
            HeaderCell cell = JRFillCrosstab.this.rowHeadersData[j][this.rowIdx + this.startRowIndex];
            overflow = false;
            if (cell == null) {
              overflow = prepareClosingRowHeader(j, availableHeight);
            } else {
              if (cell.getLevelSpan() > 1) {
                this.spanHeaders[j] = cell;
                this.spanHeadersStart[j] = this.rowIdx + this.startRowIndex;
              } else {
                overflow = prepareRowHeader(j, cell, 1, availableHeight);
                if (overflow) {
                  this.willOverflow = true;
                  return;
                } 
              } 
              j++;
            } 
          } else {
            break;
          } 
          if (overflow) {
            this.willOverflow = true;
            return;
          } 
        } 
        resetVariables();
      } 
    }
    
    private boolean prepareDataCell(CrosstabCell data, int column, int availableHeight, int xOffset) throws JRException {
      int rowY = ((Integer)this.rowYs.get(this.rowIdx)).intValue();
      JRFillCrosstabCell cell = JRFillCrosstab.this.crossCells[data.getRowTotalGroupIndex()][data.getColumnTotalGroupIndex()];
      JRFillCellContents contents = (cell == null) ? null : cell.getFillContents();
      if (contents == null || contents.getWidth() <= 0 || contents.getHeight() <= 0)
        return false; 
      int cellAvailableStretch = availableHeight - rowY - contents.getHeight();
      boolean overflow = (cellAvailableStretch < 0);
      if (!overflow) {
        boolean leftEmpty = (this.startColumnIndex != 0 && !JRFillCrosstab.this.isRepeatRowHeaders());
        boolean topEmpty = (this.startRowIndex != 0 && !JRFillCrosstab.this.isRepeatColumnHeaders());
        setCountVars(this.rowIdx + this.startRowIndex, column);
        setGroupVariables((JRFillCrosstabGroup[])JRFillCrosstab.this.rowGroups, data.getRowBucketValues());
        setGroupVariables((JRFillCrosstabGroup[])JRFillCrosstab.this.columnGroups, data.getColumnBucketValues());
        setMeasureVariables(data);
        boolean firstOnRow = (leftEmpty && column == this.startColumnIndex);
        contents = contents.getBoxContents((firstOnRow && JRFillCrosstab.this.getRunDirection() == 0), (firstOnRow && JRFillCrosstab.this.getRunDirection() == 1), (topEmpty && this.rowIdx == 0));
        contents = contents.getWorkingClone();
        contents.evaluate((byte)3);
        contents.prepare(cellAvailableStretch);
        this.preparedRow.add(contents);
        overflow = contents.willOverflow();
        if (!overflow) {
          contents.setX(this.columnXOffsets[column] - this.columnXOffsets[this.startColumnIndex] + xOffset);
          contents.setY(rowY + this.yOffset);
          int rowCellHeight = contents.getPrintHeight();
          if (rowCellHeight > this.preparedRowHeight)
            this.preparedRowHeight = rowCellHeight; 
        } 
      } 
      return overflow;
    }
    
    private boolean prepareRowHeader(int rowGroup, HeaderCell cell, int vSpan, int availableHeight) throws JRException {
      JRFillCrosstabRowGroup group = JRFillCrosstab.this.rowGroups[rowGroup];
      JRFillCellContents contents = cell.isTotal() ? group.getFillTotalHeader() : group.getFillHeader();
      if (contents.getWidth() <= 0 || contents.getHeight() <= 0)
        return false; 
      int spanHeight = 0;
      int headerY = ((Integer)this.rowYs.get(this.rowIdx - vSpan + 1)).intValue();
      if (vSpan > 1)
        spanHeight += ((Integer)this.rowYs.get(this.rowIdx)).intValue() - headerY; 
      int rowHeight = spanHeight + this.preparedRowHeight;
      boolean stretchContents = (group.getPosition() == 4);
      int contentsHeight = stretchContents ? rowHeight : contents.getHeight();
      int cellAvailableStretch = availableHeight - headerY - contentsHeight;
      boolean headerOverflow = (cellAvailableStretch < 0 || rowHeight < contents.getHeight());
      if (!headerOverflow) {
        setCountVars(this.rowIdx + this.startRowIndex - vSpan + 1, -1);
        setGroupVariables((JRFillCrosstabGroup[])JRFillCrosstab.this.rowGroups, cell.getBucketValues());
        if (stretchContents)
          contents = contents.getTransformedContents(contents.getWidth(), rowHeight, (byte)1, (byte)4); 
        contents = contents.getBoxContents(false, false, (this.rowIdx + 1 == vSpan && (!this.printColumnHeaders || JRFillCrosstab.this.headerCell == null)));
        contents.getWorkingClone();
        contents.evaluate((byte)3);
        contents.prepare(cellAvailableStretch);
        this.preparedRow.add(contents);
        headerOverflow = contents.willOverflow();
        if (!headerOverflow) {
          contents.setX(this.rowHeadersXOffsets[rowGroup]);
          contents.setY(headerY + this.yOffset);
          contents.setVerticalSpan(vSpan);
          int rowCellHeight = contents.getPrintHeight() - spanHeight;
          if (rowCellHeight > this.preparedRowHeight)
            this.preparedRowHeight = rowCellHeight; 
        } 
      } 
      if (headerOverflow)
        removeFilledRows(vSpan - 1); 
      return headerOverflow;
    }
    
    private boolean prepareClosingRowHeader(int rowGroup, int availableHeight) throws JRException {
      if (rowGroup < JRFillCrosstab.this.rowGroups.length - 1 && this.spanHeaders[rowGroup] != null && this.spanHeaders[rowGroup].getLevelSpan() + this.spanHeadersStart[rowGroup] == this.rowIdx + this.startRowIndex + 1) {
        HeaderCell cell = this.spanHeaders[rowGroup];
        int vSpan = cell.getLevelSpan();
        if (this.spanHeadersStart[rowGroup] < this.startRowIndex)
          vSpan += this.spanHeadersStart[rowGroup] - this.startRowIndex; 
        this.spanHeaders[rowGroup] = null;
        return prepareRowHeader(rowGroup, cell, vSpan, availableHeight);
      } 
      return false;
    }
    
    private void removeExceedingSpanHeaders() {
      for (int j = JRFillCrosstab.this.rowGroups.length - 2; j >= 0; j--) {
        if (this.spanHeaders[j] != null && this.spanHeadersStart[j] >= this.rowIdx + this.startRowIndex)
          this.spanHeaders[j] = null; 
      } 
    }
    
    private void setBackSpanHeaders() {
      for (int j = JRFillCrosstab.this.rowGroups.length - 2; j >= 0 && this.spanHeaders[j] == null; j--) {
        int spanIndex = getSpanIndex(this.rowIdx + this.startRowIndex, j, JRFillCrosstab.this.rowHeadersData);
        if (spanIndex >= 0) {
          this.spanHeaders[j] = JRFillCrosstab.this.rowHeadersData[j][spanIndex];
          this.spanHeadersStart[j] = spanIndex;
        } 
      } 
    }
    
    private void fillContinuingRowHeaders(int xOffset, int availableHeight) throws JRException {
      boolean done = false;
      label27: do {
        removeExceedingSpanHeaders();
        if (!this.rowBreakable[this.rowIdx + this.startRowIndex]) {
          removeFilledRows(1);
          setBackSpanHeaders();
        } else {
          initPreparedRow();
          for (int j = 0; j < JRFillCrosstab.this.rowGroups.length - 1; j++) {
            if (this.spanHeaders[j] != null) {
              boolean headerOverflow = prepareContinuingRowHeader(j, availableHeight);
              if (headerOverflow) {
                releasePreparedRow();
                continue label27;
              } 
            } 
          } 
          if (!this.preparedRow.isEmpty()) {
            int lastRowHeight = ((Integer)this.rowYs.get(this.rowIdx)).intValue() - ((Integer)this.rowYs.get(this.rowIdx - 1)).intValue();
            if (this.preparedRowHeight > lastRowHeight) {
              refillLastRow(xOffset, availableHeight);
            } else {
              fillContinuingHeaders(lastRowHeight);
            } 
          } 
          done = true;
        } 
      } while (!done && this.rowIdx > 0);
    }
    
    private void fillContinuingHeaders(int lastRowHeight) throws JRException {
      int nextToLastHeaderY = ((Integer)this.rowYs.get(this.rowIdx - 1)).intValue();
      List lastPrintRow = getLastPrintRow();
      for (int j = 0; j < this.preparedRow.size(); j++) {
        JRFillCellContents contents = this.preparedRow.get(j);
        int headerY = ((Integer)this.rowYs.get(this.rowIdx - contents.getVerticalSpan())).intValue();
        contents.stretchTo(nextToLastHeaderY - headerY + lastRowHeight);
        lastPrintRow.add(contents.fill());
        contents.releaseWorkingClone();
      } 
    }
    
    private void refillLastRow(int xOffset, int availableHeight) throws JRException {
      removeFilledRows(1);
      setBackSpanHeaders();
      prepareRow(xOffset, availableHeight);
      fillRow();
      this.rowYs.add(new Integer(((Integer)this.rowYs.get(this.rowIdx)).intValue() + this.preparedRowHeight));
      this.rowIdx++;
    }
    
    private boolean prepareContinuingRowHeader(int rowGroup, int availableHeight) throws JRException {
      HeaderCell cell = this.spanHeaders[rowGroup];
      int vSpan = this.rowIdx + this.startRowIndex - this.spanHeadersStart[rowGroup];
      if (this.spanHeadersStart[rowGroup] < this.startRowIndex)
        vSpan += this.spanHeadersStart[rowGroup] - this.startRowIndex; 
      int headerY = ((Integer)this.rowYs.get(this.rowIdx - vSpan)).intValue();
      int lastHeaderY = ((Integer)this.rowYs.get(this.rowIdx)).intValue();
      int headerHeight = lastHeaderY - headerY;
      int nextToLastHeaderY = ((Integer)this.rowYs.get(this.rowIdx - 1)).intValue();
      int stretchHeight = nextToLastHeaderY - headerY;
      JRFillCrosstabRowGroup group = JRFillCrosstab.this.rowGroups[rowGroup];
      JRFillCellContents contents = cell.isTotal() ? group.getFillTotalHeader() : group.getFillHeader();
      boolean stretchContents = (group.getPosition() == 4);
      int contentsHeight = stretchContents ? headerHeight : contents.getHeight();
      int cellAvailableStretch = availableHeight - headerY - contentsHeight;
      boolean headerOverflow = (cellAvailableStretch < 0 || headerHeight < contents.getHeight());
      if (!headerOverflow) {
        setCountVars(this.rowIdx + this.startRowIndex - vSpan, -1);
        setGroupVariables((JRFillCrosstabGroup[])JRFillCrosstab.this.rowGroups, cell.getBucketValues());
        if (stretchContents)
          contents = contents.getTransformedContents(contents.getWidth(), headerHeight, (byte)1, (byte)4); 
        contents = contents.getBoxContents(false, false, (this.rowIdx == vSpan && (!this.printColumnHeaders || JRFillCrosstab.this.headerCell == null)));
        contents.getWorkingClone();
        contents.evaluate((byte)3);
        contents.prepare(cellAvailableStretch);
        this.preparedRow.add(contents);
        headerOverflow = contents.willOverflow();
        if (!headerOverflow) {
          contents.setX(this.rowHeadersXOffsets[rowGroup]);
          contents.setY(headerY + this.yOffset);
          contents.setVerticalSpan(vSpan);
          int rowHeight = contents.getPrintHeight() - stretchHeight;
          if (rowHeight > this.preparedRowHeight)
            this.preparedRowHeight = rowHeight; 
        } 
      } 
      if (headerOverflow)
        removeFilledRows(vSpan); 
      return headerOverflow;
    }
    
    protected void addPrintRow(List printRow) {
      this.printRows.add(printRow);
    }
    
    protected List getLastPrintRow() {
      return this.printRows.get(this.printRows.size() - 1);
    }
    
    protected List getPrintElements() {
      List prints = new ArrayList();
      for (Iterator it = this.printRows.iterator(); it.hasNext(); ) {
        List rowPrints = it.next();
        prints.addAll(rowPrints);
      } 
      return prints;
    }
    
    protected void setGroupVariables(JRFillCrosstabGroup[] groups, BucketDefinition.Bucket[] bucketValues) {
      for (int i = 0; i < groups.length; i++) {
        Object value = null;
        if (bucketValues[i] != null && !bucketValues[i].isTotal())
          value = bucketValues[i].getValue(); 
        groups[i].getFillVariable().setValue(value);
      } 
    }
    
    protected void setMeasureVariables(CrosstabCell cell) {
      MeasureDefinition.MeasureValue[] values = cell.getMesureValues();
      for (int i = 0; i < JRFillCrosstab.this.measures.length; i++) {
        Object value = measureValue(values, i);
        JRFillCrosstab.this.measures[i].getFillVariable().setValue(value);
      } 
      MeasureDefinition.MeasureValue[][][] totals = cell.getTotals();
      for (int row = 0; row <= JRFillCrosstab.this.rowGroups.length; row++) {
        for (int col = 0; col <= JRFillCrosstab.this.columnGroups.length; col++) {
          MeasureDefinition.MeasureValue[] vals = totals[row][col];
          if (JRFillCrosstab.this.retrieveTotal[row][col])
            for (int m = 0; m < JRFillCrosstab.this.measures.length; m++) {
              JRFillVariable totalVar = JRFillCrosstab.this.totalVariables[row][col][m];
              Object value = measureValue(vals, m);
              totalVar.setValue(value);
            }  
        } 
      } 
    }
    
    protected Object measureValue(MeasureDefinition.MeasureValue[] values, int measureIdx) {
      Object value;
      if (JRFillCrosstab.this.measures[measureIdx].getPercentageOfType() == 1) {
        if (values[measureIdx].isInitialized()) {
          value = values[measureIdx].getValue();
        } else {
          value = JRFillCrosstab.this.measures[measureIdx].getPercentageCalculator().calculatePercentage((JRCalculable)values[measureIdx], (JRCalculable)JRFillCrosstab.this.grandTotals[measureIdx]);
        } 
      } else {
        value = values[measureIdx].getValue();
      } 
      return value;
    }
    
    protected void resetVariables() {
      int i;
      for (i = 0; i < JRFillCrosstab.this.rowGroups.length; i++)
        JRFillCrosstab.this.rowGroups[i].getFillVariable().setValue(null); 
      for (i = 0; i < JRFillCrosstab.this.columnGroups.length; i++)
        JRFillCrosstab.this.columnGroups[i].getFillVariable().setValue(null); 
      for (i = 0; i < JRFillCrosstab.this.measures.length; i++)
        JRFillCrosstab.this.measures[i].getFillVariable().setValue(null); 
      for (int row = 0; row <= JRFillCrosstab.this.rowGroups.length; row++) {
        for (int col = 0; col <= JRFillCrosstab.this.columnGroups.length; col++) {
          if (JRFillCrosstab.this.retrieveTotal[row][col])
            for (int j = 0; j < JRFillCrosstab.this.measures.length; j++)
              JRFillCrosstab.this.totalVariables[row][col][j].setValue(null);  
        } 
      } 
    }
  }
  
  public int getColumnBreakOffset() {
    return this.parentCrosstab.getColumnBreakOffset();
  }
  
  public boolean isRepeatColumnHeaders() {
    return this.parentCrosstab.isRepeatColumnHeaders();
  }
  
  public boolean isRepeatRowHeaders() {
    return this.parentCrosstab.isRepeatRowHeaders();
  }
  
  public JRCrosstabCell[][] getCells() {
    return (JRCrosstabCell[][])this.crossCells;
  }
  
  public JRCellContents getWhenNoDataCell() {
    return this.whenNoDataCell;
  }
  
  public JRCrosstabParameter[] getParameters() {
    return (JRCrosstabParameter[])this.parameters;
  }
  
  public JRExpression getParametersMapExpression() {
    return this.parentCrosstab.getParametersMapExpression();
  }
  
  public JRElement getElementByKey(String elementKey) {
    return JRBaseCrosstab.getElementByKey(this, elementKey);
  }
  
  public JRFillCloneable createClone(JRFillCloneFactory factory) {
    return null;
  }
  
  public JRCellContents getHeaderCell() {
    return this.headerCell;
  }
  
  public JRVariable[] getVariables() {
    return (JRVariable[])this.variables;
  }
  
  public byte getRunDirection() {
    return this.parentCrosstab.getRunDirection();
  }
  
  public void setRunDirection(byte direction) {}
}
