package net.sf.jasperreports.engine.fill;

import java.awt.Color;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.sf.jasperreports.crosstabs.JRCellContents;
import net.sf.jasperreports.engine.JRBox;
import net.sf.jasperreports.engine.JRBoxContainer;
import net.sf.jasperreports.engine.JRDefaultStyleProvider;
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.JRLineBox;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRStyle;
import net.sf.jasperreports.engine.JRStyleContainer;
import net.sf.jasperreports.engine.JRStyleSetter;
import net.sf.jasperreports.engine.util.LineBoxWrapper;
import org.apache.commons.collections.ReferenceMap;

public class JRFillCellContents extends JRFillElementContainer implements JRCellContents, JRFillCloneable, JRStyleSetter {
  private final Map transformedContentsCache;
  
  private final Map boxContentsCache;
  
  private final JRClonePool clonePool;
  
  private JRFillCellContents original;
  
  private final JRCellContents parentCell;
  
  private JRLineBox lineBox;
  
  private int height;
  
  private int width;
  
  private int x;
  
  private int y;
  
  private int verticalSpan;
  
  private byte verticalPositionType = 1;
  
  private Map templateFrames;
  
  private JRDefaultStyleProvider defaultStyleProvider;
  
  private JRStyle initStyle;
  
  public JRFillCellContents(JRBaseFiller filler, JRCellContents cell, JRFillObjectFactory factory) {
    super(filler, (JRElementGroup)cell, factory);
    this.defaultStyleProvider = factory.getDefaultStyleProvider();
    this.parentCell = cell;
    this.lineBox = cell.getLineBox();
    this.width = cell.getWidth();
    this.height = cell.getHeight();
    factory.registerDelayedStyleSetter(this, (JRStyleContainer)this.parentCell);
    initElements();
    initConditionalStyles();
    initTemplatesMap();
    this.transformedContentsCache = (Map)new ReferenceMap();
    this.boxContentsCache = new HashMap();
    this.clonePool = new JRClonePool(this, true, true);
  }
  
  private void initTemplatesMap() {
    this.templateFrames = new HashMap();
  }
  
  protected JRFillCellContents(JRFillCellContents cellContents, JRFillCloneFactory factory) {
    super(cellContents, factory);
    this.defaultStyleProvider = cellContents.defaultStyleProvider;
    this.parentCell = cellContents.parentCell;
    this.lineBox = cellContents.lineBox;
    this.width = cellContents.width;
    this.height = cellContents.height;
    this.initStyle = cellContents.initStyle;
    initElements();
    initConditionalStyles();
    this.templateFrames = cellContents.templateFrames;
    this.transformedContentsCache = (Map)new ReferenceMap();
    this.boxContentsCache = new HashMap();
    this.clonePool = new JRClonePool(this, true, true);
    this.verticalPositionType = cellContents.verticalPositionType;
  }
  
  public Color getBackcolor() {
    return this.parentCell.getBackcolor();
  }
  
  public JRBox getBox() {
    return (JRBox)new LineBoxWrapper(getLineBox());
  }
  
  public JRLineBox getLineBox() {
    return this.lineBox;
  }
  
  protected void setBox(JRLineBox box) {
    this.lineBox = box;
    initTemplatesMap();
  }
  
  public int getHeight() {
    return this.height;
  }
  
  public int getWidth() {
    return this.width;
  }
  
  protected void setHeight(int height) {
    this.height = height;
  }
  
  protected void setWidth(int width) {
    this.width = width;
  }
  
  public JRFillCellContents getBoxContents(boolean left, boolean right, boolean top) {
    if (this.lineBox == null)
      return this; 
    boolean copyLeft = (left && this.lineBox.getLeftPen().getLineWidth().floatValue() <= 0.0F && this.lineBox.getRightPen().getLineWidth().floatValue() > 0.0F);
    boolean copyRight = (right && this.lineBox.getRightPen().getLineWidth().floatValue() <= 0.0F && this.lineBox.getLeftPen().getLineWidth().floatValue() > 0.0F);
    boolean copyTop = (top && this.lineBox.getTopPen().getLineWidth().floatValue() <= 0.0F && this.lineBox.getBottomPen().getLineWidth().floatValue() > 0.0F);
    if (!copyLeft && !copyRight && !copyTop)
      return this; 
    Object key = new BoxContents(copyLeft, copyRight, copyTop);
    JRFillCellContents boxContents = (JRFillCellContents)this.boxContentsCache.get(key);
    if (boxContents == null) {
      boxContents = (JRFillCellContents)createClone();
      JRLineBox newBox = this.lineBox.clone((JRBoxContainer)this.parentCell);
      if (copyLeft)
        newBox.copyLeftPen(this.lineBox.getRightPen()); 
      if (copyRight)
        newBox.copyRightPen(this.lineBox.getLeftPen()); 
      if (copyTop)
        newBox.copyTopPen(this.lineBox.getBottomPen()); 
      boxContents.setBox(newBox);
      this.boxContentsCache.put(key, boxContents);
    } 
    return boxContents;
  }
  
  public JRFillCellContents getTransformedContents(int newWidth, int newHeight, byte xPosition, byte yPosition) throws JRException {
    if (getHeight() == newHeight && getWidth() == newWidth)
      return this; 
    if (newHeight < getHeight() || newWidth < getWidth())
      throw new JRException("Cannot shrink cell contents."); 
    Object key = new StretchedContents(newWidth, newHeight, xPosition, yPosition);
    JRFillCellContents transformedCell = (JRFillCellContents)this.transformedContentsCache.get(key);
    if (transformedCell == null) {
      transformedCell = (JRFillCellContents)createClone();
      transformedCell.transform(newWidth, newHeight, xPosition, yPosition);
      transformedCell.setElementsBandBottomY();
      this.transformedContentsCache.put(key, transformedCell);
    } 
    return transformedCell;
  }
  
  private void transform(int newWidth, int newHeight, byte xPosition, byte yPosition) {
    transformElements(newWidth, newHeight, xPosition, yPosition);
    this.width = newWidth;
    this.height = newHeight;
  }
  
  private void transformElements(int newWidth, int newHeight, byte xPosition, byte yPosition) {
    if ((this.height == newHeight || yPosition == 1) && (this.width == newWidth || xPosition == 1))
      return; 
    double scaleX = -1.0D;
    int offsetX = 0;
    switch (xPosition) {
      case 2:
        offsetX = (newWidth - this.width) / 2;
        break;
      case 3:
        offsetX = newWidth - this.width;
        break;
      case 4:
        scaleX = newWidth / this.width;
        break;
    } 
    double scaleY = -1.0D;
    int offsetY = 0;
    switch (yPosition) {
      case 2:
        offsetY = (newHeight - this.height) / 2;
        break;
      case 3:
        offsetY = newHeight - this.height;
        break;
      case 4:
        scaleY = newHeight / this.height;
        break;
    } 
    transformElements(getElements(), scaleX, offsetX, scaleY, offsetY);
  }
  
  private static void transformElements(JRElement[] elements, double scaleX, int offsetX, double scaleY, int offsetY) {
    if (elements != null)
      for (int i = 0; i < elements.length; i++) {
        JRFillElement element = (JRFillElement)elements[i];
        if (scaleX != -1.0D) {
          element.setX((int)(element.getX() * scaleX));
          element.setWidth((int)(element.getWidth() * scaleX));
        } 
        if (offsetX != 0)
          element.setX(element.getX() + offsetX); 
        if (scaleY != -1.0D) {
          element.setY((int)(element.getY() * scaleY));
          element.setHeight((int)(element.getHeight() * scaleY));
        } 
        if (offsetY != 0)
          element.setY(element.getY() + offsetY); 
        if (element instanceof JRFrame) {
          JRElement[] frameElements = ((JRFrame)element).getElements();
          transformElements(frameElements, scaleX, offsetX, scaleY, offsetY);
        } 
      }  
  }
  
  protected void prepare(int availableStretchHeight) throws JRException {
    initFill();
    resetElements();
    prepareElements(availableStretchHeight, true);
  }
  
  protected JRPrintFrame fill() throws JRException {
    stretchElements();
    moveBandBottomElements();
    removeBlankElements();
    JRTemplatePrintFrame printCell = new JRTemplatePrintFrame(getTemplateFrame());
    printCell.setX(this.x);
    printCell.setY(this.y);
    printCell.setWidth(this.width);
    fillElements(printCell);
    verticallyPositionElements(printCell);
    printCell.setHeight(getPrintHeight());
    return printCell;
  }
  
  private JRTemplateFrame getTemplateFrame() {
    JRStyle style = getStyle();
    JRTemplateFrame template = (JRTemplateFrame)this.templateFrames.get(style);
    if (template == null) {
      template = new JRTemplateFrame(null, this.filler.getJasperPrint().getDefaultStyleProvider(), this);
      this.templateFrames.put(style, template);
    } 
    return template;
  }
  
  protected void verticallyPositionElements(JRTemplatePrintFrame printCell) {
    int positionOffset;
    switch (this.verticalPositionType) {
      case 2:
        positionOffset = (getStretchHeight() - getContainerHeight()) / 2;
        break;
      case 3:
        positionOffset = getStretchHeight() - getContainerHeight();
        break;
      default:
        positionOffset = 0;
        break;
    } 
    if (positionOffset != 0) {
      List printElements = printCell.getElements();
      int positionY = getStretchHeight() - positionOffset;
      boolean outside = false;
      for (Iterator it = printElements.iterator(); !outside && it.hasNext(); ) {
        JRPrintElement element = it.next();
        outside = (element.getY() > positionY);
      } 
      if (!outside)
        for (Iterator iterator = printElements.iterator(); iterator.hasNext(); ) {
          JRPrintElement element = iterator.next();
          element.setY(element.getY() + positionOffset);
        }  
    } 
  }
  
  protected int getPrintHeight() {
    return getStretchHeight() + getTopPadding() + getBottomPadding();
  }
  
  protected void stretchTo(int stretchHeight) {
    setStretchHeight(stretchHeight - getTopPadding() - getBottomPadding());
  }
  
  protected static class BoxContents {
    final boolean left;
    
    final boolean right;
    
    final boolean top;
    
    final int hashCode;
    
    public BoxContents(boolean left, boolean right, boolean top) {
      this.left = left;
      this.right = right;
      this.top = top;
      int hash = left ? 1231 : 1237;
      hash = 31 * hash + (right ? 1231 : 1237);
      hash = 31 * hash + (top ? 1231 : 1237);
      this.hashCode = hash;
    }
    
    public boolean equals(Object obj) {
      if (obj == this)
        return true; 
      BoxContents b = (BoxContents)obj;
      return (b.left == this.left && b.right == this.right && b.top == this.top);
    }
    
    public int hashCode() {
      return this.hashCode;
    }
  }
  
  protected static class StretchedContents {
    final int newHeight;
    
    final int newWidth;
    
    final int hashCode;
    
    final byte xPosition;
    
    final byte yPosition;
    
    StretchedContents(int newWidth, int newHeight, byte xPosition, byte yPosition) {
      this.newHeight = newHeight;
      this.newWidth = newWidth;
      this.xPosition = xPosition;
      this.yPosition = yPosition;
      int hash = newHeight;
      hash = 31 * hash + newWidth;
      hash = 31 * hash + xPosition;
      hash = 31 * hash + yPosition;
      this.hashCode = hash;
    }
    
    public boolean equals(Object o) {
      if (o == this)
        return true; 
      StretchedContents s = (StretchedContents)o;
      return (s.newHeight == this.newHeight && s.newWidth == this.newWidth && s.xPosition == this.xPosition && s.yPosition == this.yPosition);
    }
    
    public int hashCode() {
      return this.hashCode;
    }
  }
  
  protected int getContainerHeight() {
    return getHeight() - getTopPadding() - getBottomPadding();
  }
  
  protected int getTopPadding() {
    return (this.lineBox == null) ? 0 : this.lineBox.getTopPadding().intValue();
  }
  
  protected int getBottomPadding() {
    return (this.lineBox == null) ? 0 : this.lineBox.getBottomPadding().intValue();
  }
  
  public JRFillCloneable createClone() {
    JRFillCloneFactory factory = new JRFillCloneFactory();
    return createClone(factory);
  }
  
  public JRFillCloneable createClone(JRFillCloneFactory factory) {
    return new JRFillCellContents(this, factory);
  }
  
  public JRFillCellContents getWorkingClone() {
    JRFillCellContents clone = (JRFillCellContents)this.clonePool.getClone();
    clone.original = this;
    return clone;
  }
  
  public void releaseWorkingClone() {
    this.original.clonePool.releaseClone(this);
  }
  
  public void setX(int x) {
    this.x = x;
  }
  
  public void setY(int y) {
    this.y = y;
  }
  
  public int getVerticalSpan() {
    return this.verticalSpan;
  }
  
  public void setVerticalSpan(int span) {
    this.verticalSpan = span;
  }
  
  public void setVerticalPositionType(byte positionType) {
    this.verticalPositionType = positionType;
  }
  
  protected void evaluate(byte evaluation) throws JRException {
    evaluateConditionalStyles(evaluation);
    super.evaluate(evaluation);
  }
  
  public JRDefaultStyleProvider getDefaultStyleProvider() {
    return this.defaultStyleProvider;
  }
  
  public JRStyle getStyle() {
    JRStyle crtStyle = this.initStyle;
    boolean isUsingDefaultStyle = false;
    if (crtStyle == null) {
      crtStyle = this.filler.getDefaultStyle();
      isUsingDefaultStyle = true;
    } 
    JRStyle evalStyle = getEvaluatedConditionalStyle(crtStyle);
    if (isUsingDefaultStyle && evalStyle == crtStyle)
      evalStyle = null; 
    return evalStyle;
  }
  
  protected void initConditionalStyles() {
    super.initConditionalStyles();
    collectConditionalStyle(this.initStyle);
  }
  
  public Byte getMode() {
    return this.parentCell.getMode();
  }
  
  public String getStyleNameReference() {
    return null;
  }
  
  public void setStyle(JRStyle style) {
    this.initStyle = style;
    collectConditionalStyle(style);
  }
  
  public void setStyleNameReference(String name) {
    throw new UnsupportedOperationException("Style name references not allowed at fill time");
  }
  
  public Color getDefaultLineColor() {
    return this.parentCell.getDefaultLineColor();
  }
}
