package net.sf.jasperreports.engine.design;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExpression;
import net.sf.jasperreports.engine.JRExpressionChunk;
import net.sf.jasperreports.engine.JRField;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JRVariable;
import net.sf.jasperreports.engine.util.JRStringUtil;

public class JRClassGenerator {
  private static final int EXPR_MAX_COUNT_PER_METHOD = 100;
  
  protected static final String SOURCE_EXPRESSION_ID_START = "$JR_EXPR_ID=";
  
  protected static final int sOURCE_EXPRESSION_ID_START_LENGTH = "$JR_EXPR_ID=".length();
  
  protected static final String SOURCE_EXPRESSION_ID_END = "$";
  
  private static Map fieldPrefixMap = null;
  
  private static Map variablePrefixMap = null;
  
  private static Map methodSuffixMap = null;
  
  protected final JRSourceCompileTask sourceTask;
  
  protected Map parametersMap;
  
  protected Map fieldsMap;
  
  protected Map variablesMap;
  
  protected JRVariable[] variables;
  
  static {
    fieldPrefixMap = new HashMap();
    fieldPrefixMap.put(new Byte((byte)1), "Old");
    fieldPrefixMap.put(new Byte((byte)2), "");
    fieldPrefixMap.put(new Byte((byte)3), "");
    variablePrefixMap = new HashMap();
    variablePrefixMap.put(new Byte((byte)1), "Old");
    variablePrefixMap.put(new Byte((byte)2), "Estimated");
    variablePrefixMap.put(new Byte((byte)3), "");
    methodSuffixMap = new HashMap();
    methodSuffixMap.put(new Byte((byte)1), "Old");
    methodSuffixMap.put(new Byte((byte)2), "Estimated");
    methodSuffixMap.put(new Byte((byte)3), "");
  }
  
  protected JRClassGenerator(JRSourceCompileTask sourceTask) {
    this.sourceTask = sourceTask;
    this.parametersMap = sourceTask.getParametersMap();
    this.fieldsMap = sourceTask.getFieldsMap();
    this.variablesMap = sourceTask.getVariablesMap();
    this.variables = sourceTask.getVariables();
  }
  
  public static JRCompilationSourceCode generateClass(JRSourceCompileTask sourceTask) throws JRException {
    JRClassGenerator generator = new JRClassGenerator(sourceTask);
    return generator.generateClass();
  }
  
  protected JRCompilationSourceCode generateClass() throws JRException {
    StringBuffer sb = new StringBuffer();
    generateClassStart(sb);
    generateDeclarations(sb);
    generateInitMethod(sb);
    generateInitParamsMethod(sb);
    if (this.fieldsMap != null)
      generateInitFieldsMethod(sb); 
    generateInitVarsMethod(sb);
    List expressions = this.sourceTask.getExpressions();
    sb.append(generateMethod((byte)3, expressions));
    if (this.sourceTask.isOnlyDefaultEvaluation()) {
      List empty = new ArrayList();
      sb.append(generateMethod((byte)1, empty));
      sb.append(generateMethod((byte)2, empty));
    } else {
      sb.append(generateMethod((byte)1, expressions));
      sb.append(generateMethod((byte)2, expressions));
    } 
    sb.append("}\n");
    String code = sb.toString();
    JRExpression[] lineExpressions = parseSourceLines(code);
    return new JRDefaultCompilationSourceCode(code, lineExpressions);
  }
  
  private void generateInitMethod(StringBuffer sb) {
    sb.append("\n");
    sb.append("\n");
    sb.append("    /**\n");
    sb.append("     *\n");
    sb.append("     */\n");
    sb.append("    public void customizedInit(\n");
    sb.append("        Map pm,\n");
    sb.append("        Map fm,\n");
    sb.append("        Map vm\n");
    sb.append("        )\n");
    sb.append("    {\n");
    sb.append("        initParams(pm);\n");
    if (this.fieldsMap != null)
      sb.append("        initFields(fm);\n"); 
    sb.append("        initVars(vm);\n");
    sb.append("    }\n");
    sb.append("\n");
    sb.append("\n");
  }
  
  protected final void generateClassStart(StringBuffer sb) {
    sb.append("/*\n");
    sb.append(" * Generated by JasperReports - ");
    sb.append((new SimpleDateFormat()).format(new Date()));
    sb.append("\n");
    sb.append(" */\n");
    sb.append("import net.sf.jasperreports.engine.*;\n");
    sb.append("import net.sf.jasperreports.engine.fill.*;\n");
    sb.append("\n");
    sb.append("import java.util.*;\n");
    sb.append("import java.math.*;\n");
    sb.append("import java.text.*;\n");
    sb.append("import java.io.*;\n");
    sb.append("import java.net.*;\n");
    sb.append("\n");
    String[] imports = this.sourceTask.getImports();
    if (imports != null && imports.length > 0)
      for (int i = 0; i < imports.length; i++) {
        sb.append("import ");
        sb.append(imports[i]);
        sb.append(";\n");
      }  
    sb.append("\n");
    sb.append("\n");
    sb.append("/**\n");
    sb.append(" *\n");
    sb.append(" */\n");
    sb.append("public class ");
    sb.append(this.sourceTask.getUnitName());
    sb.append(" extends JREvaluator\n");
    sb.append("{\n");
    sb.append("\n");
    sb.append("\n");
    sb.append("    /**\n");
    sb.append("     *\n");
    sb.append("     */\n");
  }
  
  protected final void generateDeclarations(StringBuffer sb) {
    if (this.parametersMap != null && this.parametersMap.size() > 0) {
      Collection parameterNames = this.parametersMap.keySet();
      for (Iterator it = parameterNames.iterator(); it.hasNext(); ) {
        sb.append("    private JRFillParameter parameter_");
        sb.append(JRStringUtil.getLiteral(it.next()));
        sb.append(" = null;\n");
      } 
    } 
    if (this.fieldsMap != null && this.fieldsMap.size() > 0) {
      Collection fieldNames = this.fieldsMap.keySet();
      for (Iterator it = fieldNames.iterator(); it.hasNext(); ) {
        sb.append("    private JRFillField field_");
        sb.append(JRStringUtil.getLiteral(it.next()));
        sb.append(" = null;\n");
      } 
    } 
    if (this.variables != null && this.variables.length > 0)
      for (int i = 0; i < this.variables.length; i++) {
        sb.append("    private JRFillVariable variable_");
        sb.append(JRStringUtil.getLiteral(this.variables[i].getName()));
        sb.append(" = null;\n");
      }  
  }
  
  protected final void generateInitParamsMethod(StringBuffer sb) throws JRException {
    Iterator parIt = null;
    if (this.parametersMap != null && this.parametersMap.size() > 0) {
      parIt = this.parametersMap.keySet().iterator();
    } else {
      parIt = Collections.EMPTY_SET.iterator();
    } 
    generateInitParamsMethod(sb, parIt, 0);
  }
  
  protected final void generateInitFieldsMethod(StringBuffer sb) throws JRException {
    Iterator fieldIt = null;
    if (this.fieldsMap != null && this.fieldsMap.size() > 0) {
      fieldIt = this.fieldsMap.keySet().iterator();
    } else {
      fieldIt = Collections.EMPTY_SET.iterator();
    } 
    generateInitFieldsMethod(sb, fieldIt, 0);
  }
  
  protected final void generateInitVarsMethod(StringBuffer sb) throws JRException {
    Iterator varIt = null;
    if (this.variables != null && this.variables.length > 0) {
      varIt = Arrays.<JRVariable>asList(this.variables).iterator();
    } else {
      varIt = Collections.EMPTY_LIST.iterator();
    } 
    generateInitVarsMethod(sb, varIt, 0);
  }
  
  private void generateInitParamsMethod(StringBuffer sb, Iterator it, int index) throws JRException {
    sb.append("    /**\n");
    sb.append("     *\n");
    sb.append("     */\n");
    sb.append("    private void initParams");
    if (index > 0)
      sb.append(index); 
    sb.append("(Map pm)\n");
    sb.append("    {\n");
    for (int i = 0; i < 100 && it.hasNext(); i++) {
      String parameterName = it.next();
      sb.append("        parameter_");
      sb.append(JRStringUtil.getLiteral(parameterName));
      sb.append(" = (JRFillParameter)pm.get(\"");
      sb.append(parameterName);
      sb.append("\");\n");
    } 
    if (it.hasNext()) {
      sb.append("        initParams");
      sb.append(index + 1);
      sb.append("(pm);\n");
    } 
    sb.append("    }\n");
    sb.append("\n");
    sb.append("\n");
    if (it.hasNext())
      generateInitParamsMethod(sb, it, index + 1); 
  }
  
  private void generateInitFieldsMethod(StringBuffer sb, Iterator it, int index) throws JRException {
    sb.append("    /**\n");
    sb.append("     *\n");
    sb.append("     */\n");
    sb.append("    private void initFields");
    if (index > 0)
      sb.append(index); 
    sb.append("(Map fm)\n");
    sb.append("    {\n");
    for (int i = 0; i < 100 && it.hasNext(); i++) {
      String fieldName = it.next();
      sb.append("        field_");
      sb.append(JRStringUtil.getLiteral(fieldName));
      sb.append(" = (JRFillField)fm.get(\"");
      sb.append(fieldName);
      sb.append("\");\n");
    } 
    if (it.hasNext()) {
      sb.append("        initFields");
      sb.append(index + 1);
      sb.append("(fm);\n");
    } 
    sb.append("    }\n");
    sb.append("\n");
    sb.append("\n");
    if (it.hasNext())
      generateInitFieldsMethod(sb, it, index + 1); 
  }
  
  private void generateInitVarsMethod(StringBuffer sb, Iterator it, int index) throws JRException {
    sb.append("    /**\n");
    sb.append("     *\n");
    sb.append("     */\n");
    sb.append("    private void initVars");
    if (index > 0)
      sb.append(index); 
    sb.append("(Map vm)\n");
    sb.append("    {\n");
    for (int i = 0; i < 100 && it.hasNext(); i++) {
      String variableName = ((JRVariable)it.next()).getName();
      sb.append("        variable_");
      sb.append(JRStringUtil.getLiteral(variableName));
      sb.append(" = (JRFillVariable)vm.get(\"");
      sb.append(variableName);
      sb.append("\");\n");
    } 
    if (it.hasNext()) {
      sb.append("        initVars");
      sb.append(index + 1);
      sb.append("(vm);\n");
    } 
    sb.append("    }\n");
    sb.append("\n");
    sb.append("\n");
    if (it.hasNext())
      generateInitVarsMethod(sb, it, index + 1); 
  }
  
  protected final String generateMethod(byte evaluationType, List expressionsList) throws JRException {
    StringBuffer sb = new StringBuffer();
    if (expressionsList.size() > 0) {
      sb.append(generateMethod(expressionsList.listIterator(), 0, evaluationType));
    } else {
      sb.append("    /**\n");
      sb.append("     *\n");
      sb.append("     */\n");
      sb.append("    public Object evaluate");
      sb.append((String)methodSuffixMap.get(new Byte(evaluationType)));
      sb.append("(int id) throws Throwable\n");
      sb.append("    {\n");
      sb.append("        return null;\n");
      sb.append("    }\n");
      sb.append("\n");
      sb.append("\n");
    } 
    return sb.toString();
  }
  
  private String generateMethod(Iterator it, int index, byte evaluationType) throws JRException {
    StringBuffer sb = new StringBuffer();
    sb.append("    /**\n");
    sb.append("     *\n");
    sb.append("     */\n");
    if (index > 0) {
      sb.append("    private Object evaluate");
      sb.append((String)methodSuffixMap.get(new Byte(evaluationType)));
      sb.append(index);
    } else {
      sb.append("    public Object evaluate");
      sb.append((String)methodSuffixMap.get(new Byte(evaluationType)));
    } 
    sb.append("(int id) throws Throwable\n");
    sb.append("    {\n");
    sb.append("        Object value = null;\n");
    sb.append("\n");
    sb.append("        switch (id)\n");
    sb.append("        {\n");
    for (int i = 0; it.hasNext() && i < 100; i++) {
      JRExpression expression = it.next();
      sb.append("            case ");
      sb.append(this.sourceTask.getExpressionId(expression));
      sb.append(" : \n");
      sb.append("            {\n");
      sb.append("                value = (");
      sb.append(expression.getValueClassName());
      sb.append(")(");
      sb.append(generateExpression(expression, evaluationType));
      sb.append(");");
      appendExpressionComment(sb, expression);
      sb.append("\n");
      sb.append("                break;\n");
      sb.append("            }\n");
    } 
    sb.append("           default :\n");
    sb.append("           {\n");
    if (it.hasNext()) {
      sb.append("               value = evaluate");
      sb.append((String)methodSuffixMap.get(new Byte(evaluationType)));
      sb.append(index + 1);
      sb.append("(id);\n");
    } 
    sb.append("           }\n");
    sb.append("        }\n");
    sb.append("        \n");
    sb.append("        return value;\n");
    sb.append("    }\n");
    sb.append("\n");
    sb.append("\n");
    if (it.hasNext())
      sb.append(generateMethod(it, index + 1, evaluationType)); 
    return sb.toString();
  }
  
  private String generateExpression(JRExpression expression, byte evaluationType) {
    JRParameter jrParameter = null;
    JRField jrField = null;
    JRVariable jrVariable = null;
    StringBuffer sb = new StringBuffer();
    JRExpressionChunk[] chunks = expression.getChunks();
    JRExpressionChunk chunk = null;
    String chunkText = null;
    if (chunks != null && chunks.length > 0)
      for (int i = 0; i < chunks.length; i++) {
        chunk = chunks[i];
        chunkText = chunk.getText();
        if (chunkText == null)
          chunkText = ""; 
        switch (chunk.getType()) {
          case 1:
            appendExpressionText(expression, sb, chunkText);
            break;
          case 2:
            jrParameter = (JRParameter)this.parametersMap.get(chunkText);
            sb.append("((");
            sb.append(jrParameter.getValueClassName());
            sb.append(")parameter_");
            sb.append(JRStringUtil.getLiteral(chunkText));
            sb.append(".getValue())");
            break;
          case 3:
            jrField = (JRField)this.fieldsMap.get(chunkText);
            sb.append("((");
            sb.append(jrField.getValueClassName());
            sb.append(")field_");
            sb.append(JRStringUtil.getLiteral(chunkText));
            sb.append(".get");
            sb.append((String)fieldPrefixMap.get(new Byte(evaluationType)));
            sb.append("Value())");
            break;
          case 4:
            jrVariable = (JRVariable)this.variablesMap.get(chunkText);
            sb.append("((");
            sb.append(jrVariable.getValueClassName());
            sb.append(")variable_");
            sb.append(JRStringUtil.getLiteral(chunkText));
            sb.append(".get");
            sb.append((String)variablePrefixMap.get(new Byte(evaluationType)));
            sb.append("Value())");
            break;
          case 5:
            sb.append("str(\"");
            sb.append(chunkText);
            sb.append("\")");
            break;
        } 
      }  
    if (sb.length() == 0)
      sb.append("null"); 
    return sb.toString();
  }
  
  protected void appendExpressionText(JRExpression expression, StringBuffer sb, String chunkText) {
    StringTokenizer tokenizer = new StringTokenizer(chunkText, "\n", true);
    while (tokenizer.hasMoreTokens()) {
      String token = tokenizer.nextToken();
      if (token.equals("\n"))
        appendExpressionComment(sb, expression); 
      sb.append(token);
    } 
  }
  
  protected void appendExpressionComment(StringBuffer sb, JRExpression expression) {
    sb.append("//");
    sb.append("$JR_EXPR_ID=");
    sb.append(this.sourceTask.getExpressionId(expression));
    sb.append("$");
  }
  
  protected JRExpression[] parseSourceLines(String sourceCode) {
    List expressions = new ArrayList();
    int start = 0;
    int end = sourceCode.indexOf('\n');
    while (end >= 0) {
      JRExpression expression = null;
      if (start < end) {
        String line = sourceCode.substring(start, end);
        expression = getLineExpression(line);
      } 
      expressions.add(expression);
      start = end + 1;
      end = sourceCode.indexOf('\n', start);
    } 
    return expressions.<JRExpression>toArray(new JRExpression[expressions.size()]);
  }
  
  protected JRExpression getLineExpression(String line) {
    JRExpression expression = null;
    int exprIdStart = line.indexOf("$JR_EXPR_ID=");
    if (exprIdStart >= 0) {
      exprIdStart += sOURCE_EXPRESSION_ID_START_LENGTH;
      int exprIdEnd = line.indexOf("$", exprIdStart);
      if (exprIdEnd >= 0)
        try {
          int exprId = Integer.parseInt(line.substring(exprIdStart, exprIdEnd));
          expression = this.sourceTask.getExpression(exprId);
        } catch (NumberFormatException e) {} 
    } 
    return expression;
  }
}
