package net.sf.jasperreports.engine.design;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.net.URLStreamHandlerFactory;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TimeZone;
import net.sf.jasperreports.engine.JRAbstractScriptlet;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExpression;
import net.sf.jasperreports.engine.JRField;
import net.sf.jasperreports.engine.JRGroup;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JRQuery;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JRSortField;
import net.sf.jasperreports.engine.JRVariable;
import net.sf.jasperreports.engine.JRVirtualizer;
import net.sf.jasperreports.engine.base.JRBaseDataset;
import net.sf.jasperreports.engine.query.JRQueryExecuterFactory;
import net.sf.jasperreports.engine.util.FileResolver;
import net.sf.jasperreports.engine.util.FormatFactory;
import net.sf.jasperreports.engine.util.JRQueryExecuterUtils;

public class JRDesignDataset extends JRBaseDataset {
  private static final long serialVersionUID = 10200L;
  
  public static final String PROPERTY_FIELDS = "fields";
  
  public static final String PROPERTY_FILTER_EXPRESSION = "filterExpression";
  
  public static final String PROPERTY_GROUPS = "groups";
  
  public static final String PROPERTY_NAME = "name";
  
  public static final String PROPERTY_PARAMETERS = "parameters";
  
  public static final String PROPERTY_QUERY = "query";
  
  public static final String PROPERTY_RESOURCE_BUNDLE = "resourceBundle";
  
  public static final String PROPERTY_SCRIPTLET_CLASS = "scriptletClass";
  
  public static final String PROPERTY_SORT_FIELDS = "sortFields";
  
  public static final String PROPERTY_VARIABLES = "variables";
  
  protected Map parametersMap = new HashMap();
  
  protected List parametersList = new ArrayList();
  
  protected Map fieldsMap = new HashMap();
  
  protected List fieldsList = new ArrayList();
  
  protected Map sortFieldsMap = new HashMap();
  
  protected List sortFieldsList = new ArrayList();
  
  protected Map variablesMap = new HashMap();
  
  protected List variablesList = new ArrayList();
  
  protected Map groupsMap = new HashMap();
  
  protected List groupsList = new ArrayList();
  
  private class QueryLanguageChangeListener implements PropertyChangeListener, Serializable {
    private static final long serialVersionUID = 10200L;
    
    private final JRDesignDataset this$0;
    
    private QueryLanguageChangeListener() {}
    
    public void propertyChange(PropertyChangeEvent evt) {
      JRDesignDataset.this.queryLanguageChanged((String)evt.getOldValue(), (String)evt.getNewValue());
    }
  }
  
  private PropertyChangeListener queryLanguageChangeListener = new QueryLanguageChangeListener();
  
  private static final Object[] BUILT_IN_PARAMETERS = new Object[] { 
      "REPORT_PARAMETERS_MAP", Map.class, "REPORT_CONNECTION", Connection.class, "REPORT_MAX_COUNT", Integer.class, "REPORT_DATA_SOURCE", JRDataSource.class, "REPORT_SCRIPTLET", JRAbstractScriptlet.class, 
      "REPORT_LOCALE", Locale.class, "REPORT_RESOURCE_BUNDLE", ResourceBundle.class, "REPORT_TIME_ZONE", TimeZone.class, "REPORT_FORMAT_FACTORY", FormatFactory.class, "REPORT_CLASS_LOADER", ClassLoader.class, 
      "REPORT_URL_HANDLER_FACTORY", URLStreamHandlerFactory.class, "REPORT_FILE_RESOLVER", FileResolver.class };
  
  private static final Object[] BUILT_IN_PARAMETERS_MAIN = new Object[] { "REPORT_VIRTUALIZER", JRVirtualizer.class, "IS_IGNORE_PAGINATION", Boolean.class, "REPORT_TEMPLATES", Collection.class };
  
  public JRDesignDataset(boolean isMain) {
    super(isMain);
    addBuiltinParameters(BUILT_IN_PARAMETERS);
    if (isMain)
      addBuiltinParameters(BUILT_IN_PARAMETERS_MAIN); 
    try {
      if (isMain) {
        addVariable(createPageNumberVariable());
        addVariable(createColumnNumberVariable());
      } 
      addVariable(createReportCountVariable());
      if (isMain) {
        addVariable(createPageCountVariable());
        addVariable(createColumnCountVariable());
      } 
    } catch (JRException e) {}
  }
  
  private static JRDesignVariable createPageCountVariable() {
    JRDesignVariable variable = new JRDesignVariable();
    variable.setName("PAGE_COUNT");
    variable.setValueClass(Integer.class);
    variable.setResetType((byte)2);
    variable.setCalculation((byte)1);
    variable.setSystemDefined(true);
    JRDesignExpression expression = new JRDesignExpression();
    expression.setValueClass(Integer.class);
    expression.setText("new Integer(1)");
    variable.setExpression((JRExpression)expression);
    expression = new JRDesignExpression();
    expression.setValueClass(Integer.class);
    expression.setText("new Integer(0)");
    variable.setInitialValueExpression((JRExpression)expression);
    return variable;
  }
  
  private static JRDesignVariable createColumnNumberVariable() {
    JRDesignVariable variable = new JRDesignVariable();
    variable.setName("COLUMN_NUMBER");
    variable.setValueClass(Integer.class);
    variable.setResetType((byte)2);
    variable.setCalculation((byte)8);
    variable.setSystemDefined(true);
    JRDesignExpression expression = new JRDesignExpression();
    expression.setValueClass(Integer.class);
    expression.setText("new Integer(1)");
    variable.setInitialValueExpression((JRExpression)expression);
    return variable;
  }
  
  private static JRDesignVariable createPageNumberVariable() {
    JRDesignVariable variable = new JRDesignVariable();
    variable.setName("PAGE_NUMBER");
    variable.setValueClass(Integer.class);
    variable.setResetType((byte)1);
    variable.setCalculation((byte)8);
    variable.setSystemDefined(true);
    JRDesignExpression expression = new JRDesignExpression();
    expression.setValueClass(Integer.class);
    expression.setText("new Integer(1)");
    variable.setInitialValueExpression((JRExpression)expression);
    return variable;
  }
  
  private static JRDesignVariable createColumnCountVariable() {
    JRDesignVariable variable = new JRDesignVariable();
    variable.setName("COLUMN_COUNT");
    variable.setValueClass(Integer.class);
    variable.setResetType((byte)3);
    variable.setCalculation((byte)1);
    variable.setSystemDefined(true);
    JRDesignExpression expression = new JRDesignExpression();
    expression.setValueClass(Integer.class);
    expression.setText("new Integer(1)");
    variable.setExpression((JRExpression)expression);
    expression = new JRDesignExpression();
    expression.setValueClass(Integer.class);
    expression.setText("new Integer(0)");
    variable.setInitialValueExpression((JRExpression)expression);
    return variable;
  }
  
  private void addBuiltinParameters(Object[] parametersArray) {
    for (int i = 0; i < parametersArray.length; i++) {
      JRDesignParameter parameter = new JRDesignParameter();
      parameter.setName((String)parametersArray[i++]);
      parameter.setValueClass((Class)parametersArray[i]);
      parameter.setSystemDefined(true);
      try {
        addParameter((JRParameter)parameter);
      } catch (JRException e) {}
    } 
  }
  
  private static JRDesignVariable createReportCountVariable() {
    JRDesignVariable variable = new JRDesignVariable();
    variable.setName("REPORT_COUNT");
    variable.setValueClass(Integer.class);
    variable.setResetType((byte)1);
    variable.setCalculation((byte)1);
    variable.setSystemDefined(true);
    JRDesignExpression expression = new JRDesignExpression();
    expression.setValueClass(Integer.class);
    expression.setText("new Integer(1)");
    variable.setExpression((JRExpression)expression);
    expression = new JRDesignExpression();
    expression.setValueClass(Integer.class);
    expression.setText("new Integer(0)");
    variable.setInitialValueExpression((JRExpression)expression);
    return variable;
  }
  
  public void setName(String name) {
    Object old = this.name;
    this.name = name;
    getEventSupport().firePropertyChange("name", old, this.name);
  }
  
  public JRParameter[] getParameters() {
    JRParameter[] parametersArray = new JRParameter[this.parametersList.size()];
    this.parametersList.toArray((Object[])parametersArray);
    return parametersArray;
  }
  
  public List getParametersList() {
    return this.parametersList;
  }
  
  public Map getParametersMap() {
    return this.parametersMap;
  }
  
  public void addParameter(JRParameter parameter) throws JRException {
    if (this.parametersMap.containsKey(parameter.getName()))
      throw new JRException("Duplicate declaration of parameter : " + parameter.getName()); 
    this.parametersList.add(parameter);
    this.parametersMap.put(parameter.getName(), parameter);
    getEventSupport().fireCollectionElementAddedEvent("parameters", parameter, this.parametersList.size() - 1);
  }
  
  public JRParameter removeParameter(String parameterName) {
    return removeParameter((JRParameter)this.parametersMap.get(parameterName));
  }
  
  public JRParameter removeParameter(JRParameter parameter) {
    if (parameter != null) {
      int idx = this.parametersList.indexOf(parameter);
      if (idx >= 0) {
        this.parametersList.remove(idx);
        this.parametersMap.remove(parameter.getName());
        getEventSupport().fireCollectionElementRemovedEvent("parameters", parameter, idx);
      } 
    } 
    return parameter;
  }
  
  public void setQuery(JRDesignQuery query) {
    Object old = query;
    String oldLanguage = null;
    if (this.query != null) {
      ((JRDesignQuery)this.query).removePropertyChangeListener("language", this.queryLanguageChangeListener);
      oldLanguage = this.query.getLanguage();
    } 
    this.query = (JRQuery)query;
    String newLanguage = null;
    if (query != null) {
      query.addPropertyChangeListener("language", this.queryLanguageChangeListener);
      newLanguage = query.getLanguage();
    } 
    queryLanguageChanged(oldLanguage, newLanguage);
    getEventSupport().firePropertyChange("query", old, this.query);
  }
  
  public void setScriptletClass(String scriptletClass) {
    Object old = this.scriptletClass;
    this.scriptletClass = scriptletClass;
    if (scriptletClass == null) {
      ((JRDesignParameter)this.parametersMap.get("REPORT_SCRIPTLET")).setValueClass(JRAbstractScriptlet.class);
    } else {
      ((JRDesignParameter)this.parametersMap.get("REPORT_SCRIPTLET")).setValueClassName(scriptletClass);
    } 
    getEventSupport().firePropertyChange("scriptletClass", old, this.scriptletClass);
  }
  
  public JRField[] getFields() {
    JRField[] fieldsArray = new JRField[this.fieldsList.size()];
    this.fieldsList.toArray((Object[])fieldsArray);
    return fieldsArray;
  }
  
  public List getFieldsList() {
    return this.fieldsList;
  }
  
  public Map getFieldsMap() {
    return this.fieldsMap;
  }
  
  public void addField(JRField field) throws JRException {
    if (this.fieldsMap.containsKey(field.getName()))
      throw new JRException("Duplicate declaration of field : " + field.getName()); 
    this.fieldsList.add(field);
    this.fieldsMap.put(field.getName(), field);
    getEventSupport().fireCollectionElementAddedEvent("fields", field, this.fieldsList.size() - 1);
  }
  
  public JRField removeField(String fieldName) {
    return removeField((JRField)this.fieldsMap.get(fieldName));
  }
  
  public JRField removeField(JRField field) {
    if (field != null) {
      int idx = this.fieldsList.indexOf(field);
      if (idx >= 0) {
        this.fieldsList.remove(idx);
        this.fieldsMap.remove(field.getName());
        getEventSupport().fireCollectionElementRemovedEvent("fields", field, idx);
      } 
    } 
    return field;
  }
  
  public JRSortField[] getSortFields() {
    JRSortField[] sortFieldsArray = new JRSortField[this.sortFieldsList.size()];
    this.sortFieldsList.toArray((Object[])sortFieldsArray);
    return sortFieldsArray;
  }
  
  public List getSortFieldsList() {
    return this.sortFieldsList;
  }
  
  public void addSortField(JRSortField sortField) throws JRException {
    if (this.sortFieldsMap.containsKey(sortField.getName()))
      throw new JRException("Duplicate declaration of sort field : " + sortField.getName()); 
    this.sortFieldsList.add(sortField);
    this.sortFieldsMap.put(sortField.getName(), sortField);
    getEventSupport().fireCollectionElementAddedEvent("sortFields", sortField, this.sortFieldsList.size() - 1);
  }
  
  public JRSortField removeSortField(String fieldName) {
    return removeSortField((JRSortField)this.sortFieldsMap.get(fieldName));
  }
  
  public JRSortField removeSortField(JRSortField sortField) {
    if (sortField != null) {
      int idx = this.sortFieldsList.indexOf(sortField);
      if (idx >= 0) {
        this.sortFieldsList.remove(idx);
        this.sortFieldsMap.remove(sortField.getName());
        getEventSupport().fireCollectionElementRemovedEvent("sortFields", sortField, idx);
      } 
    } 
    return sortField;
  }
  
  public JRVariable[] getVariables() {
    JRVariable[] variablesArray = new JRVariable[this.variablesList.size()];
    this.variablesList.toArray((Object[])variablesArray);
    return variablesArray;
  }
  
  public List getVariablesList() {
    return this.variablesList;
  }
  
  public Map getVariablesMap() {
    return this.variablesMap;
  }
  
  public void addVariable(JRDesignVariable variable) throws JRException {
    addVariable(variable, false);
  }
  
  protected void addVariable(JRDesignVariable variable, boolean system) throws JRException {
    int addedIdx;
    if (this.variablesMap.containsKey(variable.getName()))
      throw new JRException("Duplicate declaration of variable : " + variable.getName()); 
    if (system) {
      ListIterator it = this.variablesList.listIterator();
      while (it.hasNext()) {
        JRVariable var = it.next();
        if (!var.isSystemDefined()) {
          it.previous();
          break;
        } 
      } 
      it.add(variable);
      addedIdx = it.previousIndex();
    } else {
      this.variablesList.add(variable);
      addedIdx = this.variablesList.size() - 1;
    } 
    this.variablesMap.put(variable.getName(), variable);
    getEventSupport().fireCollectionElementAddedEvent("variables", variable, addedIdx);
  }
  
  public JRVariable removeVariable(String variableName) {
    return removeVariable((JRVariable)this.variablesMap.get(variableName));
  }
  
  public JRVariable removeVariable(JRVariable variable) {
    if (variable != null) {
      int idx = this.variablesList.indexOf(variable);
      if (idx >= 0) {
        this.variablesList.remove(idx);
        this.variablesMap.remove(variable.getName());
        getEventSupport().fireCollectionElementRemovedEvent("variables", variable, idx);
      } 
    } 
    return variable;
  }
  
  public JRGroup[] getGroups() {
    JRGroup[] groupsArray = new JRGroup[this.groupsList.size()];
    this.groupsList.toArray((Object[])groupsArray);
    return groupsArray;
  }
  
  public List getGroupsList() {
    return this.groupsList;
  }
  
  public Map getGroupsMap() {
    return this.groupsMap;
  }
  
  public void addGroup(JRDesignGroup group) throws JRException {
    if (this.groupsMap.containsKey(group.getName()))
      throw new JRException("Duplicate declaration of group : " + group.getName()); 
    JRDesignVariable countVariable = new JRDesignVariable();
    countVariable.setName(group.getName() + "_COUNT");
    countVariable.setValueClass(Integer.class);
    countVariable.setResetType((byte)4);
    countVariable.setResetGroup((JRGroup)group);
    countVariable.setCalculation((byte)1);
    countVariable.setSystemDefined(true);
    JRDesignExpression expression = new JRDesignExpression();
    expression.setValueClass(Integer.class);
    expression.setText("new Integer(1)");
    countVariable.setExpression((JRExpression)expression);
    expression = new JRDesignExpression();
    expression.setValueClass(Integer.class);
    expression.setText("new Integer(0)");
    countVariable.setInitialValueExpression((JRExpression)expression);
    addVariable(countVariable, true);
    group.setCountVariable((JRVariable)countVariable);
    this.groupsList.add(group);
    this.groupsMap.put(group.getName(), group);
    getEventSupport().fireCollectionElementAddedEvent("groups", group, this.groupsList.size() - 1);
  }
  
  public JRGroup removeGroup(String groupName) {
    return removeGroup((JRGroup)this.groupsMap.get(groupName));
  }
  
  public JRGroup removeGroup(JRGroup group) {
    if (group != null) {
      removeVariable(group.getCountVariable());
      int idx = this.groupsList.indexOf(group);
      if (idx >= 0) {
        this.groupsList.remove(idx);
        this.groupsMap.remove(group.getName());
        getEventSupport().fireCollectionElementRemovedEvent("groups", group, idx);
      } 
    } 
    return group;
  }
  
  public void setResourceBundle(String resourceBundle) {
    Object old = this.resourceBundle;
    this.resourceBundle = resourceBundle;
    getEventSupport().firePropertyChange("resourceBundle", old, this.resourceBundle);
  }
  
  protected void queryLanguageChanged(String oldLanguage, String newLanguage) {
    try {
      if (oldLanguage != null) {
        JRQueryExecuterFactory queryExecuterFactory = JRQueryExecuterUtils.getQueryExecuterFactory(oldLanguage);
        Object[] builtinParameters = queryExecuterFactory.getBuiltinParameters();
        if (builtinParameters != null)
          removeBuiltinParameters(builtinParameters); 
      } 
      if (newLanguage != null) {
        JRQueryExecuterFactory queryExecuterFactory = JRQueryExecuterUtils.getQueryExecuterFactory(newLanguage);
        Object[] builtinParameters = queryExecuterFactory.getBuiltinParameters();
        if (builtinParameters != null) {
          addBuiltinParameters(builtinParameters);
          sortSystemParamsFirst();
        } 
      } 
    } catch (JRException e) {
      throw new JRRuntimeException(e);
    } 
  }
  
  private void sortSystemParamsFirst() {
    Collections.sort(this.parametersList, new Comparator() {
          private final JRDesignDataset this$0;
          
          public int compare(Object o1, Object o2) {
            JRParameter p1 = (JRParameter)o1;
            JRParameter p2 = (JRParameter)o2;
            boolean s1 = p1.isSystemDefined();
            boolean s2 = p2.isSystemDefined();
            return s1 ? (s2 ? 0 : -1) : (s2 ? 1 : 0);
          }
        });
  }
  
  private void removeBuiltinParameters(Object[] builtinParameters) {
    for (int i = 0; i < builtinParameters.length; i += 2) {
      String parameterName = (String)builtinParameters[i];
      JRParameter parameter = (JRParameter)this.parametersMap.get(parameterName);
      if (parameter.isSystemDefined())
        removeParameter(parameter); 
    } 
  }
  
  public void setProperty(String propName, String value) {
    getPropertiesMap().setProperty(propName, value);
  }
  
  public void setFilterExpression(JRExpression expression) {
    Object old = this.filterExpression;
    this.filterExpression = expression;
    getEventSupport().firePropertyChange("filterExpression", old, this.filterExpression);
  }
  
  private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
    in.defaultReadObject();
    if (this.sortFieldsMap == null)
      this.sortFieldsMap = new HashMap(); 
    if (this.sortFieldsList == null)
      this.sortFieldsList = new ArrayList(); 
  }
  
  public Object clone() {
    JRDesignDataset clone = (JRDesignDataset)super.clone();
    if (this.parametersList != null) {
      clone.parametersList = new ArrayList(this.parametersList.size());
      clone.parametersMap = new HashMap(this.parametersList.size());
      for (int i = 0; i < this.parametersList.size(); i++) {
        JRParameter parameter = (JRParameter)((JRParameter)this.parametersList.get(i)).clone();
        clone.parametersList.add(parameter);
        clone.parametersMap.put(parameter.getName(), parameter);
      } 
    } 
    if (this.fieldsList != null) {
      clone.fieldsList = new ArrayList(this.fieldsList.size());
      clone.fieldsMap = new HashMap(this.fieldsList.size());
      for (int i = 0; i < this.fieldsList.size(); i++) {
        JRField field = (JRField)((JRField)this.fieldsList.get(i)).clone();
        clone.fieldsList.add(field);
        clone.fieldsMap.put(field.getName(), field);
      } 
    } 
    if (this.sortFieldsList != null) {
      clone.sortFieldsList = new ArrayList(this.sortFieldsList.size());
      clone.sortFieldsMap = new HashMap(this.sortFieldsList.size());
      for (int i = 0; i < this.sortFieldsList.size(); i++) {
        JRSortField sortField = (JRSortField)((JRSortField)this.sortFieldsList.get(i)).clone();
        clone.sortFieldsList.add(sortField);
        clone.sortFieldsMap.put(sortField.getName(), sortField);
      } 
    } 
    if (this.variablesList != null) {
      clone.variablesList = new ArrayList(this.variablesList.size());
      clone.variablesMap = new HashMap(this.variablesList.size());
      for (int i = 0; i < this.variablesList.size(); i++) {
        JRVariable variable = (JRVariable)((JRVariable)this.variablesList.get(i)).clone();
        clone.variablesList.add(variable);
        clone.variablesMap.put(variable.getName(), variable);
      } 
    } 
    if (this.groupsList != null) {
      clone.groupsList = new ArrayList(this.groupsList.size());
      clone.groupsMap = new HashMap(this.groupsList.size());
      for (int i = 0; i < this.groupsList.size(); i++) {
        JRGroup group = (JRGroup)((JRGroup)this.groupsList.get(i)).clone();
        clone.groupsList.add(group);
        clone.groupsMap.put(group.getName(), group);
      } 
    } 
    return clone;
  }
}
