package net.sf.jasperreports.engine.fill;

import java.util.Iterator;
import java.util.List;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRGroup;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JasperReport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class JRHorizontalFiller extends JRBaseFiller {
  private static final Log log = LogFactory.getLog(JRHorizontalFiller.class);
  
  private int lastDetailOffsetX = -1;
  
  private int lastDetailOffsetY = -1;
  
  protected JRHorizontalFiller(JasperReport jasperReport) throws JRException {
    this(jasperReport, (JREvaluator)null, (JRBaseFiller)null);
  }
  
  protected JRHorizontalFiller(JasperReport jasperReport, JRBaseFiller parentFiller) throws JRException {
    super(jasperReport, null, parentFiller);
    setPageHeight(this.pageHeight);
  }
  
  protected JRHorizontalFiller(JasperReport jasperReport, JREvaluator evaluator, JRBaseFiller parentFiller) throws JRException {
    super(jasperReport, evaluator, parentFiller);
    setPageHeight(this.pageHeight);
  }
  
  protected void setPageHeight(int pageHeight) {
    this.pageHeight = pageHeight;
    this.columnFooterOffsetY = pageHeight - this.bottomMargin;
    if (this.pageFooter != null)
      this.columnFooterOffsetY -= this.pageFooter.getHeight(); 
    if (this.columnFooter != null)
      this.columnFooterOffsetY -= this.columnFooter.getHeight(); 
    this.lastPageColumnFooterOffsetY = pageHeight - this.bottomMargin;
    if (this.lastPageFooter != null)
      this.lastPageColumnFooterOffsetY -= this.lastPageFooter.getHeight(); 
    if (this.columnFooter != null)
      this.lastPageColumnFooterOffsetY -= this.columnFooter.getHeight(); 
  }
  
  protected synchronized void fillReport() throws JRException {
    setLastPageFooter(false);
    if (next()) {
      fillReportStart();
      while (next())
        fillReportContent(); 
      fillReportEnd();
    } else {
      if (log.isDebugEnabled())
        log.debug("Fill " + this.fillerId + ": no data"); 
      switch (this.whenNoDataType) {
        case 3:
          if (log.isDebugEnabled())
            log.debug("Fill " + this.fillerId + ": all sections"); 
          this.scriptlet.callBeforeReportInit();
          this.calculator.initializeVariables((byte)1);
          this.scriptlet.callAfterReportInit();
          this.printPage = newPage();
          addPage(this.printPage);
          setFirstColumn();
          this.offsetY = this.topMargin;
          fillBackground();
          fillTitle();
          fillPageHeader((byte)3);
          fillColumnHeaders((byte)3);
          fillGroupHeaders(true);
          fillGroupFooters(true);
          fillSummary();
          break;
        case 2:
          if (log.isDebugEnabled())
            log.debug("Fill " + this.fillerId + ": blank page"); 
          this.printPage = newPage();
          addPage(this.printPage);
          break;
        case 4:
          if (log.isDebugEnabled())
            log.debug("Fill " + this.fillerId + ": NoData section"); 
          this.scriptlet.callBeforeReportInit();
          this.calculator.initializeVariables((byte)1);
          this.scriptlet.callAfterReportInit();
          this.printPage = newPage();
          addPage(this.printPage);
          setFirstColumn();
          this.offsetY = this.topMargin;
          fillBackground();
          fillNoData();
          break;
        default:
          if (log.isDebugEnabled())
            log.debug("Fill " + this.fillerId + ": no pages"); 
          break;
      } 
    } 
    if (isSubreport()) {
      this.printPageStretchHeight = this.offsetY + this.bottomMargin;
      if (this.fillContext.isUsingVirtualizer())
        removePageIdentityDataProvider(); 
    } 
    if (this.fillContext.isIgnorePagination())
      this.jasperPrint.setPageHeight(this.offsetY + this.bottomMargin); 
  }
  
  private void fillReportStart() throws JRException {
    this.scriptlet.callBeforeReportInit();
    this.calculator.initializeVariables((byte)1);
    this.scriptlet.callAfterReportInit();
    this.printPage = newPage();
    addPage(this.printPage);
    setFirstColumn();
    this.offsetY = this.topMargin;
    fillBackground();
    fillTitle();
    fillPageHeader((byte)3);
    fillColumnHeaders((byte)3);
    fillGroupHeaders(true);
    fillDetail();
  }
  
  private void setFirstColumn() {
    this.columnIndex = 0;
    this.offsetX = this.leftMargin;
    setColumnNumberVariable();
  }
  
  private void fillReportContent() throws JRException {
    this.calculator.estimateGroupRuptures();
    fillGroupFooters(false);
    resolveGroupBoundElements((byte)1, false);
    this.scriptlet.callBeforeGroupInit();
    this.calculator.initializeVariables((byte)4);
    this.scriptlet.callAfterGroupInit();
    fillGroupHeaders(false);
    fillDetail();
  }
  
  private void fillReportEnd() throws JRException {
    fillGroupFooters(true);
    fillSummary();
  }
  
  private void fillTitle() throws JRException {
    if (log.isDebugEnabled() && !this.title.isEmpty())
      log.debug("Fill " + this.fillerId + ": title"); 
    this.title.evaluatePrintWhenExpression((byte)3);
    if (this.title.isToPrint()) {
      while (this.title.getHeight() > this.pageHeight - this.bottomMargin - this.offsetY)
        addPage(false); 
      this.title.evaluate((byte)3);
      JRPrintBand printBand = this.title.fill(this.pageHeight - this.bottomMargin - this.offsetY - this.title.getHeight());
      if (this.title.willOverflow() && !this.title.isSplitAllowed() && isSubreport()) {
        resolveGroupBoundElements((byte)3, false);
        resolveColumnBoundElements((byte)3);
        resolvePageBoundElements((byte)3);
        this.scriptlet.callBeforePageInit();
        this.calculator.initializeVariables((byte)2);
        this.scriptlet.callAfterPageInit();
        addPage(false);
        printBand = this.title.refill(this.pageHeight - this.bottomMargin - this.offsetY - this.title.getHeight());
      } 
      fillBand(printBand);
      this.offsetY += printBand.getHeight();
      while (this.title.willOverflow()) {
        resolveGroupBoundElements((byte)3, false);
        resolveColumnBoundElements((byte)3);
        resolvePageBoundElements((byte)3);
        this.scriptlet.callBeforePageInit();
        this.calculator.initializeVariables((byte)2);
        this.scriptlet.callAfterPageInit();
        addPage(false);
        printBand = this.title.fill(this.pageHeight - this.bottomMargin - this.offsetY - this.title.getHeight());
        fillBand(printBand);
        this.offsetY += printBand.getHeight();
      } 
      resolveBandBoundElements(this.title, (byte)3);
      if (this.isTitleNewPage) {
        resolveGroupBoundElements((byte)3, false);
        resolveColumnBoundElements((byte)3);
        resolvePageBoundElements((byte)3);
        this.scriptlet.callBeforePageInit();
        this.calculator.initializeVariables((byte)2);
        this.scriptlet.callAfterPageInit();
        addPage(false);
      } 
    } 
  }
  
  private void fillPageHeader(byte evaluation) throws JRException {
    if (log.isDebugEnabled() && !this.pageHeader.isEmpty())
      log.debug("Fill " + this.fillerId + ": page header"); 
    setNewPageColumnInBands();
    this.pageHeader.evaluatePrintWhenExpression((byte)3);
    if (this.pageHeader.isToPrint()) {
      int reattempts = getMasterColumnCount();
      if (this.isCreatingNewPage)
        reattempts--; 
      boolean filled = fillBandNoOverflow(this.pageHeader, evaluation);
      for (int i = 0; !filled && i < reattempts; i++) {
        resolveGroupBoundElements(evaluation, false);
        resolveColumnBoundElements(evaluation);
        resolvePageBoundElements(evaluation);
        this.scriptlet.callBeforePageInit();
        this.calculator.initializeVariables((byte)2);
        this.scriptlet.callAfterPageInit();
        addPage(false);
        filled = fillBandNoOverflow(this.pageHeader, evaluation);
      } 
      if (!filled)
        throw new JRRuntimeException("Infinite loop creating new page due to page header overflow."); 
    } 
    this.columnHeaderOffsetY = this.offsetY;
    this.isNewPage = true;
    this.isFirstPageBand = true;
  }
  
  private boolean fillBandNoOverflow(JRFillBand band, byte evaluation) throws JRException {
    int availableStretch = this.columnFooterOffsetY - this.offsetY - band.getHeight();
    boolean overflow = (availableStretch < 0);
    if (!overflow) {
      band.evaluate(evaluation);
      JRPrintBand printBand = band.fill(availableStretch);
      overflow = band.willOverflow();
      if (overflow) {
        band.rewind();
      } else {
        fillBand(printBand);
        this.offsetY += printBand.getHeight();
        resolveBandBoundElements(band, evaluation);
      } 
    } 
    return !overflow;
  }
  
  private void fillColumnHeaders(byte evaluation) throws JRException {
    if (log.isDebugEnabled() && !this.columnHeader.isEmpty())
      log.debug("Fill " + this.fillerId + ": column headers"); 
    setNewPageColumnInBands();
    for (this.columnIndex = 0; this.columnIndex < this.columnCount; this.columnIndex++) {
      setColumnNumberVariable();
      this.columnHeader.evaluatePrintWhenExpression(evaluation);
      if (this.columnHeader.isToPrint()) {
        int reattempts = getMasterColumnCount();
        if (this.isCreatingNewPage)
          reattempts--; 
        boolean fits = (this.columnHeader.getHeight() <= this.columnFooterOffsetY - this.offsetY);
        for (int i = 0; !fits && i < reattempts; i++) {
          fillPageFooter(evaluation);
          resolveGroupBoundElements(evaluation, false);
          resolveColumnBoundElements(evaluation);
          resolvePageBoundElements(evaluation);
          this.scriptlet.callBeforePageInit();
          this.calculator.initializeVariables((byte)2);
          this.scriptlet.callAfterPageInit();
          addPage(false);
          fillPageHeader(evaluation);
          fits = (this.columnHeader.getHeight() <= this.columnFooterOffsetY - this.offsetY);
        } 
        if (!fits)
          throw new JRRuntimeException("Infinite loop creating new page due to column header size."); 
        this.offsetX = this.leftMargin + this.columnIndex * (this.columnSpacing + this.columnWidth);
        this.offsetY = this.columnHeaderOffsetY;
        fillFixedBand(this.columnHeader, evaluation, false);
      } 
    } 
    setFirstColumn();
    this.isNewColumn = true;
    this.isFirstColumnBand = true;
  }
  
  private void fillGroupHeaders(boolean isFillAll) throws JRException {
    if (this.groups != null && this.groups.length > 0)
      for (int i = 0; i < this.groups.length; i++) {
        if (isFillAll) {
          fillGroupHeader(this.groups[i]);
        } else if (this.groups[i].hasChanged()) {
          fillGroupHeader(this.groups[i]);
        } 
      }  
  }
  
  private void fillGroupHeader(JRFillGroup group) throws JRException {
    JRFillBand groupHeader = (JRFillBand)group.getGroupHeader();
    if (log.isDebugEnabled() && !groupHeader.isEmpty())
      log.debug("Fill " + this.fillerId + ": " + group.getName() + " header"); 
    byte evalPrevPage = group.isTopLevelChange() ? 1 : 3;
    if (((group.isStartNewPage() || group.isResetPageNumber()) && !this.isNewPage) || (group.isStartNewColumn() && !this.isNewColumn))
      fillPageBreak(group.isResetPageNumber(), evalPrevPage, (byte)3, true); 
    groupHeader.evaluatePrintWhenExpression((byte)3);
    if (groupHeader.isToPrint())
      while (groupHeader.getHeight() > this.columnFooterOffsetY - this.offsetY || group.getMinHeightToStartNewPage() > this.columnFooterOffsetY - this.offsetY)
        fillPageBreak(false, evalPrevPage, (byte)3, true);  
    setNewGroupInBands(group);
    group.setFooterPrinted(false);
    if (groupHeader.isToPrint()) {
      setFirstColumn();
      fillColumnBand(groupHeader, (byte)3);
    } 
    group.setHeaderPrinted(true);
    this.isNewGroup = true;
    this.isFirstPageBand = false;
  }
  
  private void fillGroupHeadersReprint(byte evaluation) throws JRException {
    if (this.groups != null && this.groups.length > 0)
      for (int i = 0; i < this.groups.length; i++)
        fillGroupHeaderReprint(this.groups[i], evaluation);  
  }
  
  private void fillGroupHeaderReprint(JRFillGroup group, byte evaluation) throws JRException {
    if (group.isReprintHeaderOnEachPage() && (!group.hasChanged() || (group.hasChanged() && group.isHeaderPrinted()))) {
      JRFillBand groupHeader = (JRFillBand)group.getGroupHeader();
      groupHeader.evaluatePrintWhenExpression(evaluation);
      if (groupHeader.isToPrint()) {
        setFirstColumn();
        while (groupHeader.getHeight() > this.columnFooterOffsetY - this.offsetY || group.getMinHeightToStartNewPage() > this.columnFooterOffsetY - this.offsetY)
          fillPageBreak(false, evaluation, evaluation, true); 
        fillColumnBand(groupHeader, evaluation);
      } 
      this.isFirstPageBand = false;
    } 
  }
  
  private void fillDetail() throws JRException {
    if (log.isDebugEnabled() && !this.detail.isEmpty())
      log.debug("Fill " + this.fillerId + ": detail"); 
    if (!this.detail.isPrintWhenExpressionNull()) {
      this.calculator.estimateVariables();
      this.detail.evaluatePrintWhenExpression((byte)2);
    } 
    if (this.detail.isToPrint())
      while ((this.columnIndex == this.columnCount - 1 || this.isNewGroup) && this.detail.getHeight() > this.columnFooterOffsetY - this.offsetY) {
        byte evalPrevPage = this.isNewGroup ? 3 : 1;
        fillPageBreak(false, evalPrevPage, (byte)3, true);
      }  
    this.scriptlet.callBeforeDetailEval();
    this.calculator.calculateVariables();
    this.scriptlet.callAfterDetailEval();
    if (!this.detail.isPrintWhenExpressionNull())
      this.detail.evaluatePrintWhenExpression((byte)3); 
    if (this.detail.isToPrint()) {
      if (this.offsetX == this.lastDetailOffsetX && this.offsetY == this.lastDetailOffsetY)
        if (this.columnIndex == this.columnCount - 1) {
          setFirstColumn();
        } else {
          this.columnIndex++;
          this.offsetX += this.columnWidth + this.columnSpacing;
          this.offsetY -= this.detail.getHeight();
          setColumnNumberVariable();
        }  
      fillFixedBand(this.detail, (byte)3, false);
      this.lastDetailOffsetX = this.offsetX;
      this.lastDetailOffsetY = this.offsetY;
    } 
    this.isNewPage = false;
    this.isNewColumn = false;
    this.isNewGroup = false;
    this.isFirstPageBand = false;
    this.isFirstColumnBand = false;
  }
  
  private void fillGroupFooters(boolean isFillAll) throws JRException {
    if (this.groups != null && this.groups.length > 0) {
      byte evaluation = isFillAll ? 3 : 1;
      for (int i = this.groups.length - 1; i >= 0; i--) {
        if (isFillAll) {
          fillGroupFooter(this.groups[i], evaluation);
        } else if (this.groups[i].hasChanged()) {
          fillGroupFooter(this.groups[i], evaluation);
        } 
      } 
    } 
  }
  
  private void fillGroupFooter(JRFillGroup group, byte evaluation) throws JRException {
    JRFillBand groupFooter = (JRFillBand)group.getGroupFooter();
    if (log.isDebugEnabled() && !groupFooter.isEmpty())
      log.debug("Fill " + this.fillerId + ": " + group.getName() + " footer"); 
    groupFooter.evaluatePrintWhenExpression(evaluation);
    if (groupFooter.isToPrint()) {
      setFirstColumn();
      if (groupFooter.getHeight() > this.columnFooterOffsetY - this.offsetY)
        fillPageBreak(false, evaluation, evaluation, true); 
      fillColumnBand(groupFooter, evaluation);
    } 
    this.isNewPage = false;
    this.isNewColumn = false;
    this.isFirstPageBand = false;
    this.isFirstColumnBand = false;
    group.setHeaderPrinted(false);
    group.setFooterPrinted(true);
  }
  
  private void fillColumnFooters(byte evaluation) throws JRException {
    if (log.isDebugEnabled() && !this.columnFooter.isEmpty())
      log.debug("Fill " + this.fillerId + ": column footers"); 
    if (isSubreport())
      this.columnFooterOffsetY = this.offsetY; 
    int tmpColumnFooterOffsetY = this.columnFooterOffsetY;
    if (this.isFloatColumnFooter || this.fillContext.isIgnorePagination())
      tmpColumnFooterOffsetY = this.offsetY; 
    for (this.columnIndex = 0; this.columnIndex < this.columnCount; this.columnIndex++) {
      setColumnNumberVariable();
      this.offsetX = this.leftMargin + this.columnIndex * (this.columnSpacing + this.columnWidth);
      this.offsetY = tmpColumnFooterOffsetY;
      this.columnFooter.evaluatePrintWhenExpression(evaluation);
      if (this.columnFooter.isToPrint())
        fillFixedBand(this.columnFooter, evaluation, false); 
    } 
  }
  
  private void fillPageFooter(byte evaluation) throws JRException {
    JRFillBand crtPageFooter = getCurrentPageFooter();
    if (log.isDebugEnabled() && !crtPageFooter.isEmpty())
      log.debug("Fill " + this.fillerId + ": " + (this.isLastPageFooter ? "last " : "") + "page footer"); 
    this.offsetX = this.leftMargin;
    if (!isSubreport() && !this.fillContext.isIgnorePagination())
      this.offsetY = this.pageHeight - crtPageFooter.getHeight() - this.bottomMargin; 
    crtPageFooter.evaluatePrintWhenExpression(evaluation);
    if (crtPageFooter.isToPrint())
      fillFixedBand(crtPageFooter, evaluation); 
  }
  
  private void fillSummary() throws JRException {
    if (log.isDebugEnabled() && !this.summary.isEmpty())
      log.debug("Fill " + this.fillerId + ": summary"); 
    this.offsetX = this.leftMargin;
    if (this.lastPageFooter == this.missingFillBand) {
      if (!this.isSummaryNewPage && this.summary.getHeight() <= this.columnFooterOffsetY - this.offsetY) {
        fillSummarySamePage();
      } else {
        fillSummaryNewPage();
      } 
    } else if (!this.isSummaryNewPage && this.summary.getHeight() <= this.lastPageColumnFooterOffsetY - this.offsetY) {
      setLastPageFooter(true);
      fillSummarySamePage();
    } else if (!this.isSummaryNewPage && this.summary.getHeight() <= this.columnFooterOffsetY - this.offsetY) {
      fillSummarySamePageMixedFooters();
    } else if (this.offsetY <= this.lastPageColumnFooterOffsetY) {
      setLastPageFooter(true);
      fillSummaryNewPage();
    } else {
      fillPageBreak(false, (byte)3, (byte)3, false);
      setLastPageFooter(true);
      if (this.isSummaryNewPage) {
        fillSummaryNewPage();
      } else {
        fillSummarySamePage();
      } 
    } 
    resolveGroupBoundElements((byte)3, true);
    resolveColumnBoundElements((byte)3);
    resolvePageBoundElements((byte)3);
    resolveReportBoundElements();
  }
  
  private void fillSummarySamePage() throws JRException {
    this.summary.evaluatePrintWhenExpression((byte)3);
    if (this.summary != this.missingFillBand && this.summary.isToPrint()) {
      this.summary.evaluate((byte)3);
      JRPrintBand printBand = this.summary.fill(this.columnFooterOffsetY - this.offsetY - this.summary.getHeight());
      if (this.summary.willOverflow() && !this.summary.isSplitAllowed()) {
        fillColumnFooters((byte)3);
        fillPageFooter((byte)3);
        resolveGroupBoundElements((byte)3, true);
        resolveColumnBoundElements((byte)3);
        resolvePageBoundElements((byte)3);
        this.scriptlet.callBeforePageInit();
        this.calculator.initializeVariables((byte)2);
        this.scriptlet.callAfterPageInit();
        addPage(false);
        printBand = this.summary.refill(this.pageHeight - this.bottomMargin - this.offsetY - this.summary.getHeight());
        fillBand(printBand);
        this.offsetY += printBand.getHeight();
      } else {
        fillBand(printBand);
        this.offsetY += printBand.getHeight();
        fillColumnFooters((byte)3);
        fillPageFooter((byte)3);
      } 
      while (this.summary.willOverflow()) {
        resolveGroupBoundElements((byte)3, true);
        resolveColumnBoundElements((byte)3);
        resolvePageBoundElements((byte)3);
        this.scriptlet.callBeforePageInit();
        this.calculator.initializeVariables((byte)2);
        this.scriptlet.callAfterPageInit();
        addPage(false);
        printBand = this.summary.fill(this.pageHeight - this.bottomMargin - this.offsetY - this.summary.getHeight());
        fillBand(printBand);
        this.offsetY += printBand.getHeight();
      } 
      resolveBandBoundElements(this.summary, (byte)3);
    } else {
      fillColumnFooters((byte)3);
      fillPageFooter((byte)3);
    } 
  }
  
  private void fillSummarySamePageMixedFooters() throws JRException {
    this.summary.evaluatePrintWhenExpression((byte)3);
    if (this.summary != this.missingFillBand && this.summary.isToPrint()) {
      this.summary.evaluate((byte)3);
      JRPrintBand printBand = this.summary.fill(this.columnFooterOffsetY - this.offsetY - this.summary.getHeight());
      if (this.summary.willOverflow() && !this.summary.isSplitAllowed()) {
        if (this.offsetY <= this.lastPageColumnFooterOffsetY) {
          setLastPageFooter(true);
          fillColumnFooters((byte)3);
          fillPageFooter((byte)3);
          resolveGroupBoundElements((byte)3, true);
          resolveColumnBoundElements((byte)3);
          resolvePageBoundElements((byte)3);
          this.scriptlet.callBeforePageInit();
          this.calculator.initializeVariables((byte)2);
          this.scriptlet.callAfterPageInit();
          addPage(false);
          printBand = this.summary.refill(this.pageHeight - this.bottomMargin - this.offsetY - this.summary.getHeight());
          fillBand(printBand);
          this.offsetY += printBand.getHeight();
        } else {
          fillPageBreak(false, (byte)3, (byte)3, false);
          setLastPageFooter(true);
          printBand = this.summary.refill(this.lastPageColumnFooterOffsetY - this.offsetY - this.summary.getHeight());
          fillBand(printBand);
          this.offsetY += printBand.getHeight();
          fillColumnFooters((byte)3);
          fillPageFooter((byte)3);
        } 
      } else {
        fillBand(printBand);
        this.offsetY += printBand.getHeight();
        fillPageBreak(false, (byte)3, (byte)3, false);
        setLastPageFooter(true);
        if (this.summary.willOverflow()) {
          printBand = this.summary.fill(this.lastPageColumnFooterOffsetY - this.offsetY - this.summary.getHeight());
          fillBand(printBand);
          this.offsetY += printBand.getHeight();
        } 
        fillColumnFooters((byte)3);
        fillPageFooter((byte)3);
      } 
      while (this.summary.willOverflow()) {
        resolveGroupBoundElements((byte)3, true);
        resolveColumnBoundElements((byte)3);
        resolvePageBoundElements((byte)3);
        this.scriptlet.callBeforePageInit();
        this.calculator.initializeVariables((byte)2);
        this.scriptlet.callAfterPageInit();
        addPage(false);
        printBand = this.summary.fill(this.pageHeight - this.bottomMargin - this.offsetY - this.summary.getHeight());
        fillBand(printBand);
        this.offsetY += printBand.getHeight();
      } 
      resolveBandBoundElements(this.summary, (byte)3);
    } else {
      if (this.offsetY > this.lastPageColumnFooterOffsetY)
        fillPageBreak(false, (byte)3, (byte)3, false); 
      setLastPageFooter(true);
      fillColumnFooters((byte)3);
      fillPageFooter((byte)3);
    } 
  }
  
  private void fillSummaryNewPage() throws JRException {
    fillColumnFooters((byte)3);
    fillPageFooter((byte)3);
    this.summary.evaluatePrintWhenExpression((byte)3);
    if (this.summary != this.missingFillBand && this.summary.isToPrint()) {
      resolveGroupBoundElements((byte)3, true);
      resolveColumnBoundElements((byte)3);
      resolvePageBoundElements((byte)3);
      this.scriptlet.callBeforePageInit();
      this.calculator.initializeVariables((byte)2);
      this.scriptlet.callAfterPageInit();
      addPage(false);
      this.columnIndex = -1;
      this.summary.evaluate((byte)3);
      JRPrintBand printBand = this.summary.fill(this.pageHeight - this.bottomMargin - this.offsetY - this.summary.getHeight());
      if (this.summary.willOverflow() && !this.summary.isSplitAllowed() && isSubreport()) {
        resolveGroupBoundElements((byte)3, true);
        resolveColumnBoundElements((byte)3);
        resolvePageBoundElements((byte)3);
        this.scriptlet.callBeforePageInit();
        this.calculator.initializeVariables((byte)2);
        this.scriptlet.callAfterPageInit();
        addPage(false);
        printBand = this.summary.refill(this.pageHeight - this.bottomMargin - this.offsetY - this.summary.getHeight());
      } 
      fillBand(printBand);
      this.offsetY += printBand.getHeight();
      while (this.summary.willOverflow()) {
        resolveGroupBoundElements((byte)3, true);
        resolveColumnBoundElements((byte)3);
        resolvePageBoundElements((byte)3);
        this.scriptlet.callBeforePageInit();
        this.calculator.initializeVariables((byte)2);
        this.scriptlet.callAfterPageInit();
        addPage(false);
        printBand = this.summary.fill(this.pageHeight - this.bottomMargin - this.offsetY - this.summary.getHeight());
        fillBand(printBand);
        this.offsetY += printBand.getHeight();
      } 
      resolveBandBoundElements(this.summary, (byte)3);
    } 
  }
  
  private void fillBackground() throws JRException {
    if (log.isDebugEnabled() && !this.background.isEmpty())
      log.debug("Fill " + this.fillerId + ": background"); 
    if (this.background.getHeight() <= this.pageHeight - this.bottomMargin - this.offsetY) {
      this.background.evaluatePrintWhenExpression((byte)3);
      if (this.background.isToPrint()) {
        this.background.evaluate((byte)3);
        JRPrintBand printBand = this.background.fill(this.pageHeight - this.bottomMargin - this.offsetY - this.background.getHeight());
        fillBand(printBand);
      } 
    } 
  }
  
  private void addPage(boolean isResetPageNumber) throws JRException {
    if (isSubreport()) {
      if (!this.parentFiller.isBandOverFlowAllowed())
        throw new JRRuntimeException("Subreport overflowed on a band that does not support overflow."); 
      this.printPageStretchHeight = this.offsetY + this.bottomMargin;
      if (this.fillContext.isUsingVirtualizer())
        removePageIdentityDataProvider(); 
      suspendSubreportRunner();
    } 
    this.printPage = newPage();
    if (isSubreport() && this.fillContext.isUsingVirtualizer())
      addPageIdentityDataProvider(); 
    if (isResetPageNumber) {
      this.calculator.getPageNumber().setValue(new Integer(1));
    } else {
      this.calculator.getPageNumber().setValue(new Integer(((Number)this.calculator.getPageNumber().getValue()).intValue() + 1));
    } 
    this.calculator.getPageNumber().setOldValue(this.calculator.getPageNumber().getValue());
    addPage(this.printPage);
    setFirstColumn();
    this.offsetY = this.topMargin;
    this.lastDetailOffsetX = -1;
    this.lastDetailOffsetY = -1;
    fillBackground();
  }
  
  private void setColumnNumberVariable() {
    JRFillVariable columnNumberVar = this.calculator.getColumnNumber();
    columnNumberVar.setValue(new Integer(this.columnIndex + 1));
    columnNumberVar.setOldValue(columnNumberVar.getValue());
  }
  
  private void fillPageBreak(boolean isResetPageNumber, byte evalPrevPage, byte evalNextPage, boolean isReprintGroupHeaders) throws JRException {
    if (this.isCreatingNewPage)
      throw new JRException("Infinite loop creating new page."); 
    this.isCreatingNewPage = true;
    fillColumnFooters(evalPrevPage);
    fillPageFooter(evalPrevPage);
    resolveGroupBoundElements(evalPrevPage, false);
    resolveColumnBoundElements(evalPrevPage);
    resolvePageBoundElements(evalPrevPage);
    this.scriptlet.callBeforePageInit();
    this.calculator.initializeVariables((byte)2);
    this.scriptlet.callAfterPageInit();
    addPage(isResetPageNumber);
    fillPageHeader(evalNextPage);
    fillColumnHeaders(evalNextPage);
    if (isReprintGroupHeaders)
      fillGroupHeadersReprint(evalNextPage); 
    this.isCreatingNewPage = false;
  }
  
  protected void fillPageBand(JRFillBand band, byte evaluation) throws JRException {
    band.evaluate(evaluation);
    JRPrintBand printBand = band.fill(this.columnFooterOffsetY - this.offsetY - band.getHeight());
    if (band.willOverflow() && !band.isSplitAllowed()) {
      fillPageBreak(false, evaluation, evaluation, true);
      printBand = band.refill(this.columnFooterOffsetY - this.offsetY - band.getHeight());
    } 
    fillBand(printBand);
    this.offsetY += printBand.getHeight();
    while (band.willOverflow()) {
      fillPageBreak(false, evaluation, evaluation, true);
      printBand = band.fill(this.columnFooterOffsetY - this.offsetY - band.getHeight());
      fillBand(printBand);
      this.offsetY += printBand.getHeight();
    } 
    resolveBandBoundElements(band, evaluation);
  }
  
  protected void fillColumnBand(JRFillBand band, byte evaluation) throws JRException {
    band.evaluate(evaluation);
    JRPrintBand printBand = band.fill(this.columnFooterOffsetY - this.offsetY - band.getHeight());
    if (band.willOverflow() && !band.isSplitAllowed()) {
      fillPageBreak(false, evaluation, evaluation, true);
      printBand = band.refill(this.columnFooterOffsetY - this.offsetY - band.getHeight());
    } 
    fillBand(printBand);
    this.offsetY += printBand.getHeight();
    while (band.willOverflow()) {
      fillPageBreak(false, evaluation, evaluation, true);
      printBand = band.fill(this.columnFooterOffsetY - this.offsetY - band.getHeight());
      fillBand(printBand);
      this.offsetY += printBand.getHeight();
    } 
    resolveBandBoundElements(band, evaluation);
  }
  
  protected void fillFixedBand(JRFillBand band, byte evaluation) throws JRException {
    fillFixedBand(band, evaluation, true);
  }
  
  protected void fillFixedBand(JRFillBand band, byte evaluation, boolean allowShrinking) throws JRException {
    band.evaluate(evaluation);
    JRPrintBand printBand = band.fill();
    fillBand(printBand);
    this.offsetY += allowShrinking ? printBand.getHeight() : band.getHeight();
    resolveBandBoundElements(band, evaluation);
  }
  
  protected void fillBand(JRPrintBand band) {
    List elements = band.getElements();
    if (elements != null && elements.size() > 0) {
      JRPrintElement element = null;
      for (Iterator it = elements.iterator(); it.hasNext(); ) {
        element = it.next();
        element.setX(element.getX() + this.offsetX);
        element.setY(element.getY() + this.offsetY);
        this.printPage.addElement(element);
      } 
    } 
  }
  
  private void setNewPageColumnInBands() {
    this.title.setNewPageColumn(true);
    this.pageHeader.setNewPageColumn(true);
    this.columnHeader.setNewPageColumn(true);
    this.detail.setNewPageColumn(true);
    this.columnFooter.setNewPageColumn(true);
    this.pageFooter.setNewPageColumn(true);
    this.lastPageFooter.setNewPageColumn(true);
    this.summary.setNewPageColumn(true);
    this.noData.setNewPageColumn(true);
    if (this.groups != null && this.groups.length > 0)
      for (int i = 0; i < this.groups.length; i++) {
        ((JRFillBand)this.groups[i].getGroupHeader()).setNewPageColumn(true);
        ((JRFillBand)this.groups[i].getGroupFooter()).setNewPageColumn(true);
      }  
  }
  
  private void setNewGroupInBands(JRGroup group) {
    this.title.setNewGroup(group, true);
    this.pageHeader.setNewGroup(group, true);
    this.columnHeader.setNewGroup(group, true);
    this.detail.setNewGroup(group, true);
    this.columnFooter.setNewGroup(group, true);
    this.pageFooter.setNewGroup(group, true);
    this.lastPageFooter.setNewGroup(group, true);
    this.summary.setNewGroup(group, true);
    if (this.groups != null && this.groups.length > 0)
      for (int i = 0; i < this.groups.length; i++) {
        ((JRFillBand)this.groups[i].getGroupHeader()).setNewGroup(group, true);
        ((JRFillBand)this.groups[i].getGroupFooter()).setNewGroup(group, true);
      }  
  }
  
  private JRFillBand getCurrentPageFooter() {
    return this.isLastPageFooter ? this.lastPageFooter : this.pageFooter;
  }
  
  private void setLastPageFooter(boolean isLastPageFooter) {
    this.isLastPageFooter = isLastPageFooter;
    if (isLastPageFooter)
      this.columnFooterOffsetY = this.lastPageColumnFooterOffsetY; 
  }
  
  private void fillNoData() throws JRException {
    if (log.isDebugEnabled() && !this.noData.isEmpty())
      log.debug("Fill " + this.fillerId + ": noData"); 
    this.noData.evaluatePrintWhenExpression((byte)3);
    if (this.noData.isToPrint()) {
      while (this.noData.getHeight() > this.pageHeight - this.bottomMargin - this.offsetY)
        addPage(false); 
      this.noData.evaluate((byte)3);
      JRPrintBand printBand = this.noData.fill(this.pageHeight - this.bottomMargin - this.offsetY - this.noData.getHeight());
      if (this.noData.willOverflow() && !this.noData.isSplitAllowed() && isSubreport()) {
        resolveGroupBoundElements((byte)3, false);
        resolveColumnBoundElements((byte)3);
        resolvePageBoundElements((byte)3);
        this.scriptlet.callBeforePageInit();
        this.calculator.initializeVariables((byte)2);
        this.scriptlet.callAfterPageInit();
        addPage(false);
        printBand = this.noData.refill(this.pageHeight - this.bottomMargin - this.offsetY - this.noData.getHeight());
      } 
      fillBand(printBand);
      this.offsetY += printBand.getHeight();
      while (this.noData.willOverflow()) {
        resolveGroupBoundElements((byte)3, false);
        resolveColumnBoundElements((byte)3);
        resolvePageBoundElements((byte)3);
        this.scriptlet.callBeforePageInit();
        this.calculator.initializeVariables((byte)2);
        this.scriptlet.callAfterPageInit();
        addPage(false);
        printBand = this.noData.fill(this.pageHeight - this.bottomMargin - this.offsetY - this.noData.getHeight());
        fillBand(printBand);
        this.offsetY += printBand.getHeight();
      } 
      resolveBandBoundElements(this.noData, (byte)3);
    } 
  }
}
