package net.sf.jasperreports.engine.fill;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.jasperreports.engine.JRConditionalStyle;
import net.sf.jasperreports.engine.JRElement;
import net.sf.jasperreports.engine.JRElementGroup;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRFrame;
import net.sf.jasperreports.engine.JROrigin;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintElementContainer;
import net.sf.jasperreports.engine.JRReportFont;
import net.sf.jasperreports.engine.JRStyle;
import net.sf.jasperreports.engine.base.JRBaseStyle;
import net.sf.jasperreports.engine.util.JRStyleResolver;

public abstract class JRFillElementContainer extends JRFillElementGroup {
  protected JRBaseFiller filler;
  
  private JRFillElement[] ySortedElements = null;
  
  private JRFillElement[] stretchElements = null;
  
  private JRFillElement[] bandBottomElements = null;
  
  private JRFillElement[] removableElements = null;
  
  private boolean willOverflow = false;
  
  protected boolean isOverflow = false;
  
  private int stretchHeight = 0;
  
  private int firstY = 0;
  
  protected JRFillElement firstYElement = null;
  
  protected final JRFillExpressionEvaluator expressionEvaluator;
  
  protected JRFillElement[] deepElements;
  
  protected Set stylesToEvaluate = new HashSet();
  
  protected Map evaluatedStyles = new HashMap();
  
  protected boolean hasPrintWhenOverflowElement;
  
  protected JRFillElementContainer(JRBaseFiller filler, JRElementGroup container, JRFillObjectFactory factory) {
    super(container, factory);
    this.expressionEvaluator = factory.getExpressionEvaluator();
    initDeepElements();
    this.filler = filler;
  }
  
  protected JRFillElementContainer(JRFillElementContainer container, JRFillCloneFactory factory) {
    super(container, factory);
    this.expressionEvaluator = container.expressionEvaluator;
    initDeepElements();
    this.filler = container.filler;
  }
  
  private void initDeepElements() {
    if (this.elements == null) {
      this.deepElements = new JRFillElement[0];
    } else {
      List deepElementsList = new ArrayList(this.elements.length);
      collectDeepElements((JRElement[])this.elements, deepElementsList);
      this.deepElements = new JRFillElement[deepElementsList.size()];
      deepElementsList.toArray((Object[])this.deepElements);
    } 
  }
  
  private static void collectDeepElements(JRElement[] elements, List deepElementsList) {
    for (int i = 0; i < elements.length; i++) {
      JRElement element = elements[i];
      deepElementsList.add(element);
      if (element instanceof JRFillFrame) {
        JRFrame frame = (JRFrame)element;
        collectDeepElements(frame.getElements(), deepElementsList);
      } 
    } 
  }
  
  protected final void initElements() {
    this.hasPrintWhenOverflowElement = false;
    if (this.elements != null && this.elements.length > 0) {
      List sortedElemsList = new ArrayList();
      List stretchElemsList = new ArrayList();
      List bandBottomElemsList = new ArrayList();
      List removableElemsList = new ArrayList();
      for (int i = 0; i < this.elements.length; i++) {
        JRFillElement element = this.elements[i];
        sortedElemsList.add(element);
        if (element.getPositionType() == 3)
          bandBottomElemsList.add(element); 
        if (element.getStretchType() != 0)
          stretchElemsList.add(element); 
        if (element.isRemoveLineWhenBlank())
          removableElemsList.add(element); 
        if (element.isPrintWhenDetailOverflows())
          this.hasPrintWhenOverflowElement = true; 
      } 
      Collections.sort(sortedElemsList, new JRYComparator());
      this.ySortedElements = new JRFillElement[this.elements.length];
      sortedElemsList.toArray(this.ySortedElements);
      this.stretchElements = new JRFillElement[stretchElemsList.size()];
      stretchElemsList.toArray(this.stretchElements);
      this.bandBottomElements = new JRFillElement[bandBottomElemsList.size()];
      bandBottomElemsList.toArray(this.bandBottomElements);
      this.removableElements = new JRFillElement[removableElemsList.size()];
      removableElemsList.toArray(this.removableElements);
    } 
    setDependentElements();
    setElementsBandBottomY();
  }
  
  protected final void setElementsBandBottomY() {
    if (this.elements != null && this.elements.length > 0)
      for (int i = 0; i < this.elements.length; i++)
        this.elements[i].setBandBottomY(getContainerHeight() - this.elements[i].getY() - this.elements[i].getHeight());  
  }
  
  private void setDependentElements() {
    if (this.ySortedElements != null && this.ySortedElements.length > 0)
      for (int i = 0; i < this.ySortedElements.length - 1; i++) {
        JRFillElement iElem = this.ySortedElements[i];
        boolean isBreakElem = iElem instanceof JRFillBreak;
        for (int j = i + 1; j < this.ySortedElements.length; j++) {
          JRFillElement jElem = this.ySortedElements[j];
          int left = Math.min(iElem.getX(), jElem.getX());
          int right = Math.max(iElem.getX() + iElem.getWidth(), jElem.getX() + jElem.getWidth());
          if (((isBreakElem && jElem.getPositionType() == 2) || jElem.getPositionType() == 1) && iElem.getY() + iElem.getHeight() <= jElem.getY() && iElem.getWidth() + jElem.getWidth() > right - left)
            iElem.addDependantElement(jElem); 
        } 
      }  
  }
  
  protected void evaluate(byte evaluation) throws JRException {
    JRElement[] allElements = getElements();
    if (allElements != null && allElements.length > 0)
      for (int i = 0; i < allElements.length; i++) {
        JRFillElement element = (JRFillElement)allElements[i];
        element.setCurrentEvaluation(evaluation);
        element.evaluate(evaluation);
      }  
  }
  
  protected void resetElements() {
    if (this.ySortedElements != null && this.ySortedElements.length > 0)
      for (int i = 0; i < this.ySortedElements.length; i++) {
        JRFillElement element = this.ySortedElements[i];
        element.reset();
        if (!this.isOverflow)
          element.setAlreadyPrinted(false); 
      }  
  }
  
  protected boolean willOverflow() {
    return this.willOverflow;
  }
  
  protected void initFill() {
    this.isOverflow = this.willOverflow;
    this.firstY = 0;
    this.firstYElement = null;
  }
  
  protected void prepareElements(int availableStretchHeight, boolean isOverflowAllowed) throws JRException {
    boolean tmpWillOverflow = false;
    int maxBandStretch = 0;
    int bandStretch = 0;
    this.firstY = this.isOverflow ? getContainerHeight() : 0;
    this.firstYElement = null;
    boolean isFirstYFound = false;
    if (this.ySortedElements != null && this.ySortedElements.length > 0)
      for (int i = 0; i < this.ySortedElements.length; i++) {
        JRFillElement element = this.ySortedElements[i];
        tmpWillOverflow = (element.prepare(availableStretchHeight + getElementFirstY(element), this.isOverflow) || tmpWillOverflow);
        element.moveDependantElements();
        if (element.isToPrint()) {
          if (this.isOverflow) {
            if (element.isReprinted()) {
              this.firstY = 0;
            } else if (!isFirstYFound) {
              this.firstY = element.getY();
            } 
            isFirstYFound = true;
          } 
          this.firstYElement = element;
          bandStretch = element.getRelativeY() + element.getStretchHeight() - getContainerHeight() + element.getBandBottomY();
          if (bandStretch > maxBandStretch)
            maxBandStretch = bandStretch; 
        } 
      }  
    if (maxBandStretch > availableStretchHeight + this.firstY)
      tmpWillOverflow = true; 
    if (tmpWillOverflow) {
      this.stretchHeight = getContainerHeight() + availableStretchHeight;
    } else {
      this.stretchHeight = getContainerHeight() + maxBandStretch;
    } 
    this.willOverflow = (tmpWillOverflow && isOverflowAllowed);
  }
  
  private int getElementFirstY(JRFillElement element) {
    int elemFirstY;
    if (!this.isOverflow || this.hasPrintWhenOverflowElement) {
      elemFirstY = 0;
    } else if (element.getY() >= this.firstY) {
      elemFirstY = this.firstY;
    } else {
      elemFirstY = element.getY();
    } 
    return elemFirstY;
  }
  
  protected void setStretchHeight(int stretchHeight) {
    if (stretchHeight > this.stretchHeight)
      this.stretchHeight = stretchHeight; 
  }
  
  protected void stretchElements() {
    if (this.stretchElements != null && this.stretchElements.length > 0)
      for (int i = 0; i < this.stretchElements.length; i++) {
        JRFillElement element = this.stretchElements[i];
        element.stretchElement(this.stretchHeight - getContainerHeight());
        element.moveDependantElements();
      }  
    if (this.ySortedElements != null && this.ySortedElements.length > 0)
      for (int i = 0; i < this.ySortedElements.length; i++) {
        JRFillElement element = this.ySortedElements[i];
        element.stretchHeightFinal();
      }  
  }
  
  protected int getStretchHeight() {
    return this.stretchHeight;
  }
  
  protected void moveBandBottomElements() {
    if (this.bandBottomElements != null && this.bandBottomElements.length > 0)
      for (int i = 0; i < this.bandBottomElements.length; i++) {
        JRFillElement element = this.bandBottomElements[i];
        element.setRelativeY(element.getY() + this.stretchHeight - getContainerHeight());
        element.setToPrint((element.isToPrint() && !this.willOverflow));
      }  
  }
  
  protected void removeBlankElements() {
    JRFillElement[] arrayOfJRFillElement = this.removableElements;
    if (arrayOfJRFillElement != null && arrayOfJRFillElement.length > 0) {
      JRFillElement[] arrayOfJRFillElement1 = this.ySortedElements;
      for (int i = 0; i < arrayOfJRFillElement.length; i++) {
        int blankHeight;
        JRFillElement iElem = arrayOfJRFillElement[i];
        if (iElem.isToPrint()) {
          blankHeight = iElem.getHeight() - iElem.getStretchHeight();
        } else {
          blankHeight = iElem.getHeight();
        } 
        if (blankHeight > 0 && iElem.getRelativeY() + iElem.getStretchHeight() <= this.stretchHeight && iElem.getRelativeY() >= this.firstY) {
          int blankY = iElem.getRelativeY() + iElem.getHeight() - blankHeight;
          boolean isToRemove = true;
          int j;
          for (j = 0; j < arrayOfJRFillElement1.length; j++) {
            JRFillElement jElem = arrayOfJRFillElement1[j];
            if (iElem != jElem && jElem.isToPrint()) {
              int top = Math.min(blankY, jElem.getRelativeY());
              int bottom = Math.max(blankY + blankHeight, jElem.getRelativeY() + jElem.getStretchHeight());
              if (blankHeight + jElem.getStretchHeight() > bottom - top) {
                isToRemove = false;
                break;
              } 
            } 
          } 
          if (isToRemove) {
            for (j = 0; j < arrayOfJRFillElement1.length; j++) {
              JRFillElement jElem = arrayOfJRFillElement1[j];
              if (jElem.getRelativeY() >= blankY + blankHeight)
                jElem.setRelativeY(jElem.getRelativeY() - blankHeight); 
            } 
            this.stretchHeight -= blankHeight;
          } 
        } 
      } 
    } 
  }
  
  protected void fillElements(JRPrintElementContainer printContainer) throws JRException {
    JRElement[] allElements = getElements();
    if (allElements != null && allElements.length > 0)
      for (int i = 0; i < allElements.length; i++) {
        JRFillElement element = (JRFillElement)allElements[i];
        element.setRelativeY(element.getRelativeY() - this.firstY);
        if (element.getRelativeY() + element.getStretchHeight() > this.stretchHeight)
          element.setToPrint(false); 
        element.setAlreadyPrinted((element.isToPrint() || element.isAlreadyPrinted()));
        if (element.isToPrint()) {
          JRPrintElement printElement = element.fill();
          if (printElement != null) {
            printContainer.addElement(printElement);
            if (element instanceof JRFillSubreport) {
              JRFillSubreport subreport = (JRFillSubreport)element;
              List fonts = subreport.subreportFiller.getJasperPrint().getFontsList();
              for (int j = 0; j < fonts.size(); j++)
                this.filler.getJasperPrint().addFont(fonts.get(j), true); 
              List styles = subreport.subreportFiller.getJasperPrint().getStylesList();
              for (int k = 0; k < styles.size(); k++)
                this.filler.addPrintStyle(styles.get(k)); 
              List origins = subreport.subreportFiller.getJasperPrint().getOriginsList();
              for (int m = 0; m < origins.size(); m++)
                this.filler.getJasperPrint().addOrigin(origins.get(m)); 
              Collection printElements = subreport.getPrintElements();
              addSubElements(printContainer, element, printElements);
            } else if (element instanceof JRFillCrosstab) {
              List printElements = ((JRFillCrosstab)element).getPrintElements();
              addSubElements(printContainer, element, printElements);
            } 
          } 
        } 
      }  
    printContainer.setHeight(this.stretchHeight - this.firstY);
  }
  
  protected void addSubElements(JRPrintElementContainer printContainer, JRFillElement element, Collection printElements) {
    if (printElements != null && printElements.size() > 0)
      for (Iterator it = printElements.iterator(); it.hasNext(); ) {
        JRPrintElement printElement = it.next();
        printElement.setX(element.getX() + printElement.getX());
        printElement.setY(element.getRelativeY() + printElement.getY());
        printContainer.addElement(printElement);
      }  
  }
  
  protected void rewind() throws JRException {
    if (this.ySortedElements != null && this.ySortedElements.length > 0)
      for (int i = 0; i < this.ySortedElements.length; i++) {
        JRFillElement element = this.ySortedElements[i];
        element.rewind();
        element.setAlreadyPrinted(false);
      }  
    this.willOverflow = false;
  }
  
  protected int getFirstY() {
    return this.firstY;
  }
  
  protected abstract int getContainerHeight();
  
  protected void initConditionalStyles() {
    this.filler.addDefaultStyleListener(new JRBaseFiller.DefaultStyleListener() {
          private final JRFillElementContainer this$0;
          
          public void defaultStyleSet(JRStyle style) {
            JRFillElementContainer.this.collectConditionalStyle(style);
          }
        });
    int i;
    for (i = 0; i < this.deepElements.length; i++) {
      JRStyle style = (this.deepElements[i]).initStyle;
      collectConditionalStyle(style);
    } 
    if (this.deepElements.length > 0)
      for (i = 0; i < this.deepElements.length; i++)
        this.deepElements[i].setConditionalStylesContainer(this);  
  }
  
  protected void collectConditionalStyle(JRStyle style) {
    if (style != null)
      this.stylesToEvaluate.add(style); 
  }
  
  protected void evaluateConditionalStyles(byte evaluation) throws JRException {
    for (Iterator it = this.stylesToEvaluate.iterator(); it.hasNext();)
      evaluateConditionalStyle(it.next(), evaluation); 
  }
  
  protected JRStyle evaluateConditionalStyle(JRStyle initialStyle, byte evaluation) throws JRException {
    JRBaseStyle jRBaseStyle;
    JRStyle consolidatedStyle = initialStyle;
    StringBuffer code = new StringBuffer();
    List condStylesToApply = new ArrayList();
    boolean anyTrue = buildConsolidatedStyle(initialStyle, evaluation, code, condStylesToApply);
    if (anyTrue) {
      String consolidatedStyleName = initialStyle.getName() + code.toString();
      consolidatedStyle = (JRStyle)this.filler.getJasperPrint().getStylesMap().get(consolidatedStyleName);
      if (consolidatedStyle == null) {
        jRBaseStyle = new JRBaseStyle(consolidatedStyleName);
        for (int j = condStylesToApply.size() - 1; j >= 0; j--)
          JRStyleResolver.appendStyle((JRStyle)jRBaseStyle, condStylesToApply.get(j)); 
        this.filler.addPrintStyle((JRStyle)jRBaseStyle);
      } 
    } 
    this.evaluatedStyles.put(initialStyle, jRBaseStyle);
    return (JRStyle)jRBaseStyle;
  }
  
  protected boolean buildConsolidatedStyle(JRStyle style, byte evaluation, StringBuffer code, List condStylesToApply) throws JRException {
    boolean anyTrue = false;
    JRConditionalStyle[] conditionalStyles = style.getConditionalStyles();
    if (conditionalStyles != null && conditionalStyles.length > 0)
      for (int j = 0; j < conditionalStyles.length; j++) {
        boolean condition;
        JRConditionalStyle conditionalStyle = conditionalStyles[j];
        Boolean expressionValue = (Boolean)this.expressionEvaluator.evaluate(conditionalStyle.getConditionExpression(), evaluation);
        if (expressionValue == null) {
          condition = false;
        } else {
          condition = expressionValue.booleanValue();
        } 
        code.append(condition ? 49 : 48);
        anyTrue |= condition;
        if (condition)
          condStylesToApply.add(conditionalStyle); 
      }  
    condStylesToApply.add(style);
    if (style.getStyle() != null)
      anyTrue |= buildConsolidatedStyle(style.getStyle(), evaluation, code, condStylesToApply); 
    return anyTrue;
  }
  
  public JRStyle getEvaluatedConditionalStyle(JRStyle parentStyle) {
    return (JRStyle)this.evaluatedStyles.get(parentStyle);
  }
}
