package net.sf.jasperreports.engine.fill;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.sf.jasperreports.engine.JRCommonElement;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRDatasetParameter;
import net.sf.jasperreports.engine.JRElement;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExpression;
import net.sf.jasperreports.engine.JRExpressionCollector;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintPage;
import net.sf.jasperreports.engine.JRPrintRectangle;
import net.sf.jasperreports.engine.JRRewindableDataSource;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JRStyle;
import net.sf.jasperreports.engine.JRSubreport;
import net.sf.jasperreports.engine.JRSubreportParameter;
import net.sf.jasperreports.engine.JRSubreportReturnValue;
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.JRDesignSubreportReturnValue;
import net.sf.jasperreports.engine.util.JRLoader;
import net.sf.jasperreports.engine.util.JRProperties;
import net.sf.jasperreports.engine.util.JRSingletonCache;
import net.sf.jasperreports.engine.util.JRStyleResolver;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class JRFillSubreport extends JRFillElement implements JRSubreport {
  private static final Log log = LogFactory.getLog(JRFillSubreport.class);
  
  private static final JRSingletonCache runnerFactoryCache = new JRSingletonCache(JRSubreportRunnerFactory.class);
  
  private Map parameterValues = null;
  
  private JRSubreportParameter[] parameters = null;
  
  private Connection connection = null;
  
  private JRDataSource dataSource = null;
  
  private JasperReport jasperReport = null;
  
  private Map loadedEvaluators = null;
  
  private JRFillSubreportReturnValue[] returnValues = null;
  
  protected JRBaseFiller subreportFiller = null;
  
  private JRPrintPage printPage = null;
  
  private JRSubreportRunner runner;
  
  private Set checkedReports;
  
  protected JRFillSubreport(JRBaseFiller filler, JRSubreport subreport, JRFillObjectFactory factory) {
    super(filler, (JRElement)subreport, factory);
    this.parameters = subreport.getParameters();
    JRSubreportReturnValue[] subrepReturnValues = subreport.getReturnValues();
    if (subrepReturnValues != null) {
      List returnValuesList = new ArrayList(subrepReturnValues.length * 2);
      this.returnValues = new JRFillSubreportReturnValue[subrepReturnValues.length];
      for (int i = 0; i < subrepReturnValues.length; i++)
        addReturnValue(subrepReturnValues[i], returnValuesList, factory); 
      this.returnValues = new JRFillSubreportReturnValue[returnValuesList.size()];
      returnValuesList.toArray((Object[])this.returnValues);
    } 
    this.loadedEvaluators = new HashMap();
    this.checkedReports = new HashSet();
  }
  
  public byte getMode() {
    return JRStyleResolver.getMode((JRCommonElement)this, (byte)2);
  }
  
  public boolean isUsingCache() {
    return ((JRSubreport)this.parent).isUsingCache();
  }
  
  public void setUsingCache(boolean isUsingCache) {}
  
  public JRExpression getParametersMapExpression() {
    return ((JRSubreport)this.parent).getParametersMapExpression();
  }
  
  public JRSubreportParameter[] getParameters() {
    return this.parameters;
  }
  
  public JRExpression getConnectionExpression() {
    return ((JRSubreport)this.parent).getConnectionExpression();
  }
  
  public JRExpression getDataSourceExpression() {
    return ((JRSubreport)this.parent).getDataSourceExpression();
  }
  
  public JRExpression getExpression() {
    return ((JRSubreport)this.parent).getExpression();
  }
  
  protected JRTemplateRectangle getJRTemplateRectangle() {
    JRStyle style = getStyle();
    JRTemplateRectangle template = (JRTemplateRectangle)getTemplate(style);
    if (template == null) {
      template = new JRTemplateRectangle(this.band.getOrigin(), this.filler.getJasperPrint().getDefaultStyleProvider(), this);
      transferProperties(template);
      registerTemplate(style, template);
    } 
    return template;
  }
  
  protected Collection getPrintElements() {
    Collection printElements = null;
    if (this.printPage != null)
      printElements = this.printPage.getElements(); 
    return printElements;
  }
  
  protected void evaluate(byte evaluation) throws JRException {
    reset();
    evaluatePrintWhenExpression(evaluation);
    evaluateProperties(evaluation);
    if (isPrintWhenExpressionNull() || (!isPrintWhenExpressionNull() && isPrintWhenTrue()))
      evaluateSubreport(evaluation); 
  }
  
  protected void evaluateSubreport(byte evaluation) throws JRException {
    JRExpression expression = getExpression();
    Object source = evaluateExpression(expression, evaluation);
    if (source != null) {
      JREvaluator evaluator = null;
      if (isUsingCache() && this.filler.fillContext.hasLoadedSubreport(source)) {
        this.jasperReport = this.filler.fillContext.getLoadedSubreport(source);
        evaluator = (JREvaluator)this.loadedEvaluators.get(this.jasperReport);
        if (evaluator == null) {
          evaluator = JasperCompileManager.loadEvaluator(this.jasperReport);
          this.loadedEvaluators.put(this.jasperReport, evaluator);
        } 
      } else {
        Class expressionClass = expression.getValueClass();
        if (expressionClass.equals(JasperReport.class)) {
          this.jasperReport = (JasperReport)source;
        } else if (expressionClass.equals(InputStream.class)) {
          this.jasperReport = (JasperReport)JRLoader.loadObject((InputStream)source);
        } else if (expressionClass.equals(URL.class)) {
          this.jasperReport = (JasperReport)JRLoader.loadObject((URL)source);
        } else if (expressionClass.equals(File.class)) {
          this.jasperReport = (JasperReport)JRLoader.loadObject((File)source);
        } else if (expressionClass.equals(String.class)) {
          this.jasperReport = (JasperReport)JRLoader.loadObjectFromLocation((String)source, this.filler.reportClassLoader, this.filler.urlHandlerFactory, this.filler.fileResolver);
        } 
        if (this.jasperReport != null)
          evaluator = JasperCompileManager.loadEvaluator(this.jasperReport); 
        if (isUsingCache()) {
          this.filler.fillContext.registerLoadedSubreport(source, this.jasperReport);
          this.loadedEvaluators.put(this.jasperReport, evaluator);
        } 
      } 
      if (this.jasperReport != null) {
        expression = getConnectionExpression();
        this.connection = (Connection)evaluateExpression(expression, evaluation);
        expression = getDataSourceExpression();
        this.dataSource = (JRDataSource)evaluateExpression(expression, evaluation);
        this.parameterValues = getParameterValues(this.filler, getParametersMapExpression(), (JRDatasetParameter[])getParameters(), evaluation, false, (this.jasperReport.getResourceBundle() != null), (this.jasperReport.getFormatFactoryClass() != null));
        if (this.subreportFiller != null)
          this.filler.unregisterSubfiller(this.subreportFiller); 
        initSubreportFiller(evaluator);
        checkReturnValues();
        saveReturnVariables();
      } 
    } 
  }
  
  protected void initSubreportFiller(JREvaluator evaluator) throws JRException {
    if (log.isDebugEnabled())
      log.debug("Fill " + this.filler.fillerId + ": creating subreport filler"); 
    switch (this.jasperReport.getPrintOrder()) {
      case 2:
        this.subreportFiller = new JRHorizontalFiller(this.jasperReport, evaluator, this.filler);
        break;
      case 1:
        this.subreportFiller = new JRVerticalFiller(this.jasperReport, evaluator, this.filler);
        break;
      default:
        throw new JRRuntimeException("Unkown print order " + this.jasperReport.getPrintOrder() + ".");
    } 
    this.runner = getRunnerFactory().createSubreportRunner(this, this.subreportFiller);
    this.subreportFiller.setSubreportRunner(this.runner);
  }
  
  protected void saveReturnVariables() {
    if (this.returnValues != null)
      for (int i = 0; i < this.returnValues.length; i++) {
        String varName = this.returnValues[i].getToVariable();
        this.band.saveVariable(varName);
      }  
  }
  
  public static Map getParameterValues(JRBaseFiller filler, JRExpression parametersMapExpression, JRDatasetParameter[] subreportParameters, byte evaluation, boolean ignoreNullExpressions, boolean removeResourceBundle, boolean removeFormatFactory) throws JRException {
    Map parameterValues = null;
    if (parametersMapExpression != null)
      parameterValues = (Map)filler.evaluateExpression(parametersMapExpression, evaluation); 
    if (parameterValues != null) {
      if (removeResourceBundle)
        parameterValues.remove("REPORT_RESOURCE_BUNDLE"); 
      if (removeFormatFactory)
        parameterValues.remove("REPORT_FORMAT_FACTORY"); 
      parameterValues.remove("REPORT_CONNECTION");
      parameterValues.remove("REPORT_MAX_COUNT");
      parameterValues.remove("REPORT_DATA_SOURCE");
      parameterValues.remove("REPORT_SCRIPTLET");
      parameterValues.remove("REPORT_VIRTUALIZER");
      parameterValues.remove("IS_IGNORE_PAGINATION");
      parameterValues.remove("REPORT_PARAMETERS_MAP");
    } 
    if (parameterValues == null)
      parameterValues = new HashMap(); 
    if (subreportParameters != null && subreportParameters.length > 0) {
      Object parameterValue = null;
      for (int i = 0; i < subreportParameters.length; i++) {
        JRExpression expression = subreportParameters[i].getExpression();
        if (expression != null || !ignoreNullExpressions) {
          parameterValue = filler.evaluateExpression(expression, evaluation);
          if (parameterValue == null) {
            parameterValues.remove(subreportParameters[i].getName());
          } else {
            parameterValues.put(subreportParameters[i].getName(), parameterValue);
          } 
        } 
      } 
    } 
    if (!parameterValues.containsKey("REPORT_LOCALE"))
      parameterValues.put("REPORT_LOCALE", filler.getLocale()); 
    if (!parameterValues.containsKey("REPORT_TIME_ZONE"))
      parameterValues.put("REPORT_TIME_ZONE", filler.getTimeZone()); 
    if (!parameterValues.containsKey("REPORT_FORMAT_FACTORY") && !removeFormatFactory)
      parameterValues.put("REPORT_FORMAT_FACTORY", filler.getFormatFactory()); 
    if (!parameterValues.containsKey("REPORT_CLASS_LOADER") && filler.reportClassLoader != null)
      parameterValues.put("REPORT_CLASS_LOADER", filler.reportClassLoader); 
    if (!parameterValues.containsKey("REPORT_URL_HANDLER_FACTORY") && filler.urlHandlerFactory != null)
      parameterValues.put("REPORT_URL_HANDLER_FACTORY", filler.urlHandlerFactory); 
    if (!parameterValues.containsKey("REPORT_FILE_RESOLVER") && filler.fileResolver != null)
      parameterValues.put("REPORT_FILE_RESOLVER", filler.fileResolver); 
    return parameterValues;
  }
  
  protected void fillSubreport() throws JRException {
    if (getConnectionExpression() != null) {
      this.subreportFiller.fill(this.parameterValues, this.connection);
    } else if (getDataSourceExpression() != null) {
      this.subreportFiller.fill(this.parameterValues, this.dataSource);
    } else {
      this.subreportFiller.fill(this.parameterValues);
    } 
  }
  
  protected boolean prepare(int availableStretchHeight, boolean isOverflow) throws JRException {
    boolean willOverflow = false;
    super.prepare(availableStretchHeight, isOverflow);
    if (this.subreportFiller == null)
      setToPrint(false); 
    if (!isToPrint())
      return willOverflow; 
    if (availableStretchHeight < getRelativeY() - getY() - getBandBottomY()) {
      setToPrint(false);
      return true;
    } 
    boolean filling = this.runner.isFilling();
    boolean toPrint = (!isOverflow || isPrintWhenDetailOverflows() || !isAlreadyPrinted());
    boolean reprinted = (isOverflow && isPrintWhenDetailOverflows());
    if (!filling && toPrint && reprinted)
      rewind(); 
    int availableHeight = getHeight() + availableStretchHeight - getRelativeY() + getY() + getBandBottomY();
    this.subreportFiller.setPageHeight(availableHeight);
    synchronized (this.subreportFiller) {
      JRSubreportRunResult result;
      if (filling) {
        if (log.isDebugEnabled())
          log.debug("Fill " + this.filler.fillerId + ": resuming " + this.subreportFiller.fillerId); 
        result = this.runner.resume();
      } else if (toPrint) {
        setReprinted(reprinted);
        if (log.isDebugEnabled())
          log.debug("Fill " + this.filler.fillerId + ": starting " + this.subreportFiller.fillerId); 
        result = this.runner.start();
      } else {
        this.printPage = null;
        setStretchHeight(getHeight());
        setToPrint(false);
        return willOverflow;
      } 
      if (result.getException() != null) {
        Throwable error = result.getException();
        if (log.isErrorEnabled())
          log.error("Fill " + this.filler.fillerId + ": exception", error); 
        if (error instanceof RuntimeException)
          throw (RuntimeException)error; 
        throw new JRRuntimeException(error);
      } 
      if (result.hasFinished()) {
        if (log.isDebugEnabled())
          log.debug("Fill " + this.filler.fillerId + ": subreport " + this.subreportFiller.fillerId + " finished"); 
        copyValues();
      } else if (log.isDebugEnabled()) {
        log.debug("Fill " + this.filler.fillerId + ": subreport " + this.subreportFiller.fillerId + " to continue");
      } 
      this.printPage = this.subreportFiller.getCurrentPage();
      setStretchHeight(result.hasFinished() ? this.subreportFiller.getCurrentPageStretchHeight() : availableHeight);
      willOverflow = !result.hasFinished();
      if (!willOverflow)
        this.runner.reset(); 
    } 
    Collection printElements = getPrintElements();
    if ((printElements == null || printElements.size() == 0) && isRemoveLineWhenBlank())
      setToPrint(false); 
    return willOverflow;
  }
  
  public void rewind() throws JRException {
    if (this.subreportFiller == null)
      return; 
    if (log.isDebugEnabled())
      log.debug("Fill " + this.filler.fillerId + ": cancelling " + this.subreportFiller.fillerId); 
    this.subreportFiller.setInterrupted(true);
    synchronized (this.subreportFiller) {
      this.runner.cancel();
      this.runner.reset();
    } 
    this.filler.unregisterSubfiller(this.subreportFiller);
    initSubreportFiller((JREvaluator)null);
    if (getConnectionExpression() == null && this.dataSource != null)
      if (this.dataSource instanceof JRRewindableDataSource) {
        ((JRRewindableDataSource)this.dataSource).moveFirst();
      } else {
        throw new JRException("The subreport is placed on a non-splitting band, but it does not have a rewindable data source.");
      }  
  }
  
  protected JRPrintElement fill() {
    JRPrintRectangle printRectangle = new JRTemplatePrintRectangle(getJRTemplateRectangle());
    printRectangle.setX(getX());
    printRectangle.setY(getRelativeY());
    printRectangle.setWidth(getWidth());
    printRectangle.setHeight(getStretchHeight());
    return (JRPrintElement)printRectangle;
  }
  
  public void collectExpressions(JRExpressionCollector collector) {
    collector.collect(this);
  }
  
  public void visit(JRVisitor visitor) {
    visitor.visitSubreport(this);
  }
  
  private JRFillSubreportReturnValue addReturnValue(JRSubreportReturnValue parentReturnValue, List returnValueList, JRFillObjectFactory factory) {
    JRSubreportReturnValue jRSubreportReturnValue1, varianceVal, countVal, sumVal;
    JRFillSubreportReturnValue returnValue = factory.getSubreportReturnValue(parentReturnValue);
    byte calculation = returnValue.getCalculation();
    switch (calculation) {
      case 3:
      case 7:
        jRSubreportReturnValue1 = createHelperReturnValue(parentReturnValue, "_COUNT", (byte)1);
        addReturnValue(jRSubreportReturnValue1, returnValueList, factory);
        sumVal = createHelperReturnValue(parentReturnValue, "_SUM", (byte)2);
        addReturnValue(sumVal, returnValueList, factory);
        this.filler.addVariableCalculationReq(returnValue.getToVariable(), calculation);
        break;
      case 6:
        varianceVal = createHelperReturnValue(parentReturnValue, "_VARIANCE", (byte)7);
        addReturnValue(varianceVal, returnValueList, factory);
        this.filler.addVariableCalculationReq(returnValue.getToVariable(), calculation);
        break;
      case 10:
        countVal = createDistinctCountHelperReturnValue(parentReturnValue);
        addReturnValue(countVal, returnValueList, factory);
        this.filler.addVariableCalculationReq(returnValue.getToVariable(), calculation);
        break;
    } 
    returnValueList.add(returnValue);
    return returnValue;
  }
  
  protected JRSubreportReturnValue createHelperReturnValue(JRSubreportReturnValue returnValue, String nameSuffix, byte calculation) {
    JRDesignSubreportReturnValue helper = new JRDesignSubreportReturnValue();
    helper.setToVariable(returnValue.getToVariable() + nameSuffix);
    helper.setSubreportVariable(returnValue.getSubreportVariable());
    helper.setCalculation(calculation);
    helper.setIncrementerFactoryClassName(helper.getIncrementerFactoryClassName());
    return (JRSubreportReturnValue)helper;
  }
  
  protected JRSubreportReturnValue createDistinctCountHelperReturnValue(JRSubreportReturnValue returnValue) {
    JRDesignSubreportReturnValue helper = new JRDesignSubreportReturnValue();
    helper.setToVariable(returnValue.getToVariable() + "_DISTINCT_COUNT");
    helper.setSubreportVariable(returnValue.getSubreportVariable());
    helper.setCalculation((byte)0);
    helper.setIncrementerFactoryClassName(helper.getIncrementerFactoryClassName());
    return (JRSubreportReturnValue)helper;
  }
  
  public JRSubreportReturnValue[] getReturnValues() {
    return (JRSubreportReturnValue[])this.returnValues;
  }
  
  public boolean usesForReturnValue(String variableName) {
    boolean used = false;
    if (this.returnValues != null)
      for (int j = 0; j < this.returnValues.length; j++) {
        JRSubreportReturnValue returnValue = this.returnValues[j];
        if (returnValue.getToVariable().equals(variableName)) {
          used = true;
          break;
        } 
      }  
    return used;
  }
  
  protected void copyValues() {
    if (this.returnValues != null && this.returnValues.length > 0)
      for (int i = 0; i < this.returnValues.length; i++)
        copyValue(this.returnValues[i]);  
  }
  
  protected void copyValue(JRFillSubreportReturnValue returnValue) {
    try {
      JRFillVariable variable = this.filler.getVariable(returnValue.getToVariable());
      Object value = this.subreportFiller.getVariableValue(returnValue.getSubreportVariable());
      Object newValue = returnValue.getIncrementer().increment(variable, value, AbstractValueProvider.getCurrentValueProvider());
      variable.setOldValue(newValue);
      variable.setValue(newValue);
      variable.setIncrementedValue(newValue);
    } catch (JRException e) {
      throw new JRRuntimeException(e);
    } 
  }
  
  private void checkReturnValues() throws JRException {
    if (this.returnValues != null && this.returnValues.length > 0 && !this.checkedReports.contains(this.jasperReport)) {
      for (int i = 0; i < this.returnValues.length; i++) {
        JRSubreportReturnValue returnValue = this.returnValues[i];
        String subreportVariableName = returnValue.getSubreportVariable();
        JRVariable subrepVariable = this.subreportFiller.getVariable(subreportVariableName);
        if (subrepVariable == null)
          throw new JRException("Subreport variable " + subreportVariableName + " not found."); 
        JRVariable variable = this.filler.getVariable(returnValue.getToVariable());
        if (returnValue.getCalculation() == 1 || returnValue.getCalculation() == 10) {
          if (!Number.class.isAssignableFrom(variable.getValueClass()))
            throw new JRException("Variable " + returnValue.getToVariable() + " must have a numeric type."); 
        } else if (!variable.getValueClass().isAssignableFrom(subrepVariable.getValueClass()) && (!Number.class.isAssignableFrom(variable.getValueClass()) || !Number.class.isAssignableFrom(subrepVariable.getValueClass()))) {
          throw new JRException("Variable " + returnValue.getToVariable() + " is not assignable from subreport variable " + subreportVariableName);
        } 
      } 
      if (isUsingCache())
        this.checkedReports.add(this.jasperReport); 
    } 
  }
  
  protected void resolveElement(JRPrintElement element, byte evaluation) {}
  
  public Boolean isOwnUsingCache() {
    return ((JRSubreport)this.parent).isOwnUsingCache();
  }
  
  public void setUsingCache(Boolean isUsingCache) {}
  
  public JRFillCloneable createClone(JRFillCloneFactory factory) {
    return null;
  }
  
  protected static JRSubreportRunnerFactory getRunnerFactory() throws JRException {
    String factoryClassName = JRProperties.getProperty("net.sf.jasperreports.subreport.runner.factory");
    if (factoryClassName == null)
      throw new JRException("Property \"net.sf.jasperreports.subreport.runner.factory\" must be set"); 
    return (JRSubreportRunnerFactory)runnerFactoryCache.getCachedInstance(factoryClassName);
  }
}
