package com.alibaba.fastjson.codegen;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;

import com.alibaba.fastjson.util.JavaBeanInfo;
import com.alibaba.fastjson.util.FieldInfo;
import com.alibaba.fastjson.util.TypeUtils;

public class DeserializerGen extends ClassGen {

    private JavaBeanInfo beanInfo;
    private String              genClassName;

    public DeserializerGen(Class<?> clazz, Appendable out){
        super(clazz, out);
    }

    @Override
    public void gen() throws IOException {
        beanInfo = JavaBeanInfo.build(clazz, type, null);
        genClassName = clazz.getSimpleName() + "GenDecoder";

        print("package ");
        print(clazz.getPackage().getName());
        println(";");
        println();

        println("import java.lang.reflect.Type;");
        println();

        println("import com.alibaba.fastjson.parser.DefaultJSONParser;");
        println("import com.alibaba.fastjson.parser.DefaultJSONParser.ResolveTask;");
        println("import com.alibaba.fastjson.parser.ParserConfig;");
        println("import com.alibaba.fastjson.parser.Feature;");
        println("import com.alibaba.fastjson.parser.JSONLexerBase;");
        println("import com.alibaba.fastjson.parser.JSONToken;");
        println("import com.alibaba.fastjson.parser.ParseContext;");
        println("import com.alibaba.fastjson.parser.deserializer.ASMJavaBeanDeserializer;");
        println("import com.alibaba.fastjson.parser.deserializer.ObjectDeserializer;");
        println();

        print("public class ");
        print(genClassName);
        print(" extends ASMJavaBeanDeserializer implements ObjectDeserializer {");
        incrementIndent();
        println();

        genConstructor();

        genCreateInstance();

        genDeserialze();

        endClass();
    }

    protected void genCreateInstance() throws IOException {
        println();
        print("public Object createInstance(DefaultJSONParser parser, Type type) {");
        incrementIndent();
        println();

        print("return new ");
        print(clazz.getSimpleName());
        print("();");
        println();

        decrementIndent();
        println();
        print("}");
    }

    protected void genDeserialze() throws IOException {
        if (beanInfo.fields.length == 0) {
            return;
        }

        for (FieldInfo fieldInfo : beanInfo.fields) {
            Class<?> fieldClass = fieldInfo.fieldClass;
            Type fieldType = fieldInfo.fieldType;

            if (fieldClass == char.class) {
                return;
            }

            if (Collection.class.isAssignableFrom(fieldClass)) {
                if (fieldType instanceof ParameterizedType) {
                    Type itemType = ((ParameterizedType) fieldType).getActualTypeArguments()[0];
                    if (itemType instanceof Class) {
                        continue;
                    } else {
                        return;
                    }
                } else {
                    return;
                }
            }
        }

        FieldInfo[] fieldList = beanInfo.sortedFields;

        println();
        print("public Object deserialze(DefaultJSONParser parser, Type type, Object fieldName) {");
        incrementIndent();
        println();

        println("JSONLexerBase lexer = (JSONLexerBase) parser.getLexer();");
        println();

        println("if (!lexer.isEnabled(Feature.SortFeidFastMatch)) {");
        println("\treturn super.deserialze(parser, type, fieldName);");
        println("}");

        println();

        println("if (isSupportArrayToBean(lexer)) {");
        println("\t// deserialzeArrayMapping");
        println("}");

        println();
        println("if (lexer.scanType(\"Department\") == JSONLexerBase.NOT_MATCH) {");
        println("\treturn super.deserialze(parser, type, fieldName);");
        println("}");

        println();

        println("ParseContext mark_context = parser.getContext();");
        println("int matchedCount = 0;");

        print(clazz.getSimpleName());
        print(" instance = ");
        Constructor<?> defaultConstructor = beanInfo.defaultConstructor;
        if (Modifier.isPublic(defaultConstructor.getModifiers())) {
            print("new ");
            print(clazz.getSimpleName());
            println("();");
        } else {
            print("(");
            print(clazz.getSimpleName());
            print(") createInstance(parser);");
        }

        println();

        println("ParseContext context = parser.getContext();");
        println("ParseContext childContext = parser.setContext(context, instance, fieldName);");

        println();

        println("if (lexer.matchStat == JSONLexerBase.END) {");
        println("\treturn instance;");
        println("}");

        println();

        println("int matchStat = 0;");

        int fieldListSize = fieldList.length;
        for (int i = 0; i < fieldListSize; i += 32) {
            print("int _asm_flag_");
            print(Integer.toString(i / 32));
            println(" = 0;");
        }

        for (int i = 0; i < fieldListSize; ++i) {
            FieldInfo fieldInfo = fieldList[i];
            Class<?> fieldClass = fieldInfo.fieldClass;

            if (fieldClass == boolean.class) {
                print("boolean ");
                printFieldVarName(fieldInfo);
                println(" = false;");
            } else if (fieldClass == byte.class //
                       || fieldClass == short.class //
                       || fieldClass == int.class //
                       || fieldClass == long.class //
                       || fieldClass == float.class //
                       || fieldClass == double.class //
            ) {
                print(fieldClass.getSimpleName());
                print(" ");
                printFieldVarName(fieldInfo);
                println(" = 0;");
            } else {
                if (fieldClass == String.class) {
                    print("String ");
                    printFieldVarName(fieldInfo);
                    println(";");

                    println("if (lexer.isEnabled(Feature.InitStringFieldAsEmpty)) {");
                    print("\t");
                    printFieldVarName(fieldInfo);
                    println(" = lexer.stringDefaultValue();");
                    print("\t");
                    genSetFlag(i);
                    println("} else {");
                    print("\t");
                    printFieldVarName(fieldInfo);
                    println(" = null;");
                    println("}");
                } else {
                    printClassName(fieldClass);
                    print(" ");
                    printFieldVarName(fieldInfo);
                    print(" = null;");
                    println();
                }
            }
        }

        println("boolean endFlag = false, restFlag = false;");
        println();

        for (int i = 0; i < fieldListSize; ++i) {
            print("if ((!endFlag) && (!restFlag)) {");
            incrementIndent();
            println();

            FieldInfo fieldInfo = fieldList[i];
            Class<?> fieldClass = fieldInfo.fieldClass;
            Type fieldType = fieldInfo.fieldType;

            if (fieldClass == boolean.class) {
                printFieldVarName(fieldInfo);
                print(" = lexer.scanFieldBoolean(");
                printFieldPrefix(fieldInfo);
                println(");");
            } else if (fieldClass == byte.class || fieldClass == short.class || fieldClass == int.class) {
                printFieldVarName(fieldInfo);
                print(" = lexer.scanFieldInt(");
                printFieldPrefix(fieldInfo);
                println(");");
            } else if (fieldClass == long.class) {
                printFieldVarName(fieldInfo);
                print(" = lexer.scanFieldLong(");
                printFieldPrefix(fieldInfo);
                println(");");
            } else if (fieldClass == float.class) {
                printFieldVarName(fieldInfo);
                print(" = lexer.scanFieldFloat(");
                printFieldPrefix(fieldInfo);
                println(");");
            } else if (fieldClass == double.class) {
                printFieldVarName(fieldInfo);
                print(" = lexer.scanFieldDouble(");
                printFieldPrefix(fieldInfo);
                println(");");
            } else if (fieldClass == String.class) {
                printFieldVarName(fieldInfo);
                print(" = lexer.scanFieldString(");
                printFieldPrefix(fieldInfo);
                println(");");
            } else if (fieldClass.isEnum()) {
                print("String ");
                printFieldVarEnumName(fieldInfo);

                print(" = lexer.scanFieldSymbol(");
                printFieldPrefix(fieldInfo);
                println(", parser.getSymbolTable());");

                print("if (");
                printFieldVarEnumName(fieldInfo);
                println(" == null) {");
                print("\t");
                printFieldVarName(fieldInfo);
                print(" = ");
                printClassName(fieldClass);
                print(".valueOf(");
                printFieldVarEnumName(fieldInfo);
                println(");");
                println("}");
            } else if (Collection.class.isAssignableFrom(fieldClass)) {
                Class<?> itemClass = TypeUtils.getCollectionItemClass(fieldType);

                if (itemClass == String.class) {
                    printFieldVarName(fieldInfo);
                    print(" = (");
                    printClassName(fieldClass);
                    print(") lexer.scanFieldStringArray(");
                    printFieldPrefix(fieldInfo);
                    print(", ");
                    printClassName(fieldClass);
                    print(".class);");
                    println();
                } else {
                    genDeserialzeList(fieldInfo, i, itemClass);
                    if (i == fieldListSize - 1) {
                        genEndCheck();
                    }
                }
            } else {
                genDeserialzeObject(fieldInfo, i);

                if (i == fieldListSize - 1) {
                    genEndCheck();
                }
            }

            println("if(lexer.matchStat > 0) {");
            print("\t");
            genSetFlag(i);
            println("\tmatchedCount++;");
            println("}");

            println("if(lexer.matchStat == JSONLexerBase.NOT_MATCH) {");
            println("\trestFlag = true;");
            println("}");

  
            println("if(lexer.matchStat != JSONLexerBase.END) {");
            println("\tendFlag = true;");
            println("}");
            

            decrementIndent();
            println();
            println("}");
        }

        genBatchSet(fieldList, true);

        println();
        println("if (restFlag) {");
        println("\treturn super.parseRest(parser, type, fieldName, instance);");
        println("}");

        println();
        print("return instance;");
        println();

        decrementIndent();
        println();
        print("}");
    }

    private void genBatchSet(FieldInfo[] fieldList, boolean flag) throws IOException {
        for (int i = 0, size = fieldList.length; i < size; ++i) {
            FieldInfo fieldInfo = fieldList[i];
            
            String varName = "_asm_flag_" + (i / 32);
            if (flag) {
                print("if ((");
                print(varName);
                print(" & ");
                print(Integer.toString(1 << i));
                print(") != 0) {");
                println();
                incrementIndent();
            }
            
            if (fieldInfo.method != null) {
                print("\tinstance.");
                print(fieldInfo.method.getName());
                print("(");
                printFieldVarName(fieldInfo);
                println(");");
            } else {
                print("\tinstance.");
                print(fieldInfo.field.getName());
                print(" = ");
                printFieldVarName(fieldInfo);
                println(";");
            }

            if (flag) {
                decrementIndent();
                println();
                println("}");
            }
        }
    }

    private void genEndCheck() throws IOException {
        println("if (matchedCount <= 0 || lexer.token() != JSONToken.RBRACE) {");
        println("\trestFlag = true;");
        println("} else if (lexer.token() == JSONToken.COMMA) {");
        println("\tlexer.nextToken();");
        println("}");
    }

    protected void genDeserialzeList(FieldInfo fieldInfo, int i, Class<?> itemClass) throws IOException {
        print("if (lexer.matchField(");
        printFieldPrefix(fieldInfo);
        print(")) {");
        println();
        print("\t");
        genSetFlag(i);
        println("\tif (lexer.token() == JSONToken.NULL) {");
        println("\t\tlexer.nextToken(JSONToken.COMMA);");
        println("\t} else {");
        println("\t\tif (lexer.token() == JSONToken.LBRACKET) {");
        print("\t\t\tif(");
        printListFieldItemDeser(fieldInfo);
        print(" == null) {");
        println();

        print("\t\t\t\t");
        printListFieldItemDeser(fieldInfo);
        print(" = parser.getConfig().getDeserializer(");
        printClassName(itemClass);
        print(".class);");
        println();

        print("\t\t\t}");
        println();

        print("\t\t\tfinal int fastMatchToken = ");
        printListFieldItemDeser(fieldInfo);
        print(".getFastMatchToken();");
        println();
        println("\t\t\tlexer.nextToken(fastMatchToken);");

        // _newCollection
        print("\t\t\t");
        printFieldVarName(fieldInfo);
        print(" = ");
        Class<?> fieldClass = fieldInfo.fieldClass;
        if (fieldClass.isAssignableFrom(ArrayList.class)) {
            print("new java.util.ArrayList();");
        } else if (fieldClass.isAssignableFrom(LinkedList.class)) {
            print("new java.util.LinkedList();");
        } else if (fieldClass.isAssignableFrom(HashSet.class)) {
            print("new java.util.HashSet();");
        } else if (fieldClass.isAssignableFrom(TreeSet.class)) {
            print("new java.util.TreeSet();");
        } else {
            print("new ");
            printClassName(fieldClass);
            print("();");
        }
        println();

        println("\t\t\tParseContext listContext = parser.getContext();");
        print("\t\t\tparser.setContext(");
        printFieldVarName(fieldInfo);
        print(", \"");
        print(fieldInfo.name);
        print("\");");
        println();

        println();
        println("\t\t\tfor(int i = 0; ;++i) {");

        println("\t\t\t\tif (lexer.token() == JSONToken.RBRACKET) {");
        println("\t\t\t\t\tbreak;");
        println("\t\t\t\t}");
        print("\t\t\t\t");
        printClassName(itemClass);
        print(" itemValue = ");
        printListFieldItemDeser(fieldInfo);
        print(".deserialze(parser, ");
        printListFieldItemType(fieldInfo);
        println(", i);");

        print("\t\t\t\t");
        printFieldVarName(fieldInfo);
        println(".add(itemValue);");

        print("\t\t\t\tparser.checkListResolve(");
        printFieldVarName(fieldInfo);
        println(");");

        println("\t\t\t\tif (lexer.token() == JSONToken.COMMA) {");
        println("\t\t\t\t\tlexer.nextToken(fastMatchToken);");
        println("\t\t\t\t}");

        // end for
        println("\t\t\t}");

        println("\t\t\tparser.setContext(listContext);");

        println("\t\t\tif (lexer.token() != JSONToken.RBRACKET) {");
        println("\t\t\t\trestFlag = true;");
        println("\t\t\t}");
        println("\t\t\tlexer.nextToken(JSONToken.COMMA);");

        println();
        println("\t\t} else {");
        println("\t\t\trestFlag = true;");
        println("\t\t}");
        println("\t}");
        println("}");
    }

    protected void genDeserialzeObject(FieldInfo fieldInfo, int i) throws IOException {
        print("if (lexer.matchField(");
        printFieldPrefix(fieldInfo);
        print(")) {");
        println();
        print("\t");
        genSetFlag(i);
        println("\tmatchedCount++;");

        // _deserObject
        print("if (");
        printFieldDeser(fieldInfo);
        print(" == null) {");
        println();

        print("\t");
        printFieldDeser(fieldInfo);
        print(" = parser.getConfig().getDeserializer(");
        printClassName(fieldInfo.fieldClass);
        println(".class);");
        println("}");

        print("\t");
        printFieldDeser(fieldInfo);
        print(".deserialze(parser, ");
        if (fieldInfo.fieldType instanceof Class) {
            printClassName(fieldInfo.fieldClass);
            print(".class");
        } else {
            print("getFieldType(\"");
            println(fieldInfo.name);
            print("\")");
        }
        print(",\"");
        print(fieldInfo.name);
        println("\");");

        println("\tif(parser.getResolveStatus() == DefaultJSONParser.NeedToResolve) {");
        println("\t\tResolveTask resolveTask = parser.getLastResolveTask();");
        println("\t\tresolveTask.setOwnerContext(parser.getContext());");
        print("\t\tresolveTask.setFieldDeserializer(this.getFieldDeserializer(\"");
        print(fieldInfo.name);
        println("\"));");
        println("\t\tparser.setResolveStatus(DefaultJSONParser.NONE);");
        println("\t}");
        println("}");
    }

    private void printFieldVarName(FieldInfo fieldInfo) throws IOException {
        print(fieldInfo.name);
        print("_gen");
    }

    private void printFieldVarEnumName(FieldInfo fieldInfo) throws IOException {
        print(fieldInfo.name);
        print("_gen_enum_name");
    }

    private void printFieldPrefix(FieldInfo fieldInfo) throws IOException {
        print(fieldInfo.name);
        print("_gen_prefix__");
    }

    private void printListFieldItemDeser(FieldInfo fieldInfo) throws IOException {
        print(fieldInfo.name);
        print("_gen_list_item_deser__");
    }

    private void printFieldDeser(FieldInfo fieldInfo) throws IOException {
        print(fieldInfo.name);
        print("_gen_deser__");
    }

    private void printListFieldItemType(FieldInfo fieldInfo) throws IOException {
        print(fieldInfo.name);
        print("_gen_list_item_type__");
    }

    private void genSetFlag(int flag) throws IOException {
        String varName = "_asm_flag_" + (flag / 32);
        print(varName);
        print(" |= ");
        print(Integer.toString(1 << flag));
        print(";");
        println();
    }

    protected void genConstructor() throws IOException {
        for (int i = 0, size = beanInfo.fields.length; i < size; ++i) {
            FieldInfo fieldInfo = beanInfo.fields[i];
            print("private char[] ");
            printFieldPrefix(fieldInfo);
            print(" = \"\\\"");
            print(fieldInfo.name);
            print("\\\":\".toCharArray();");
            println();
        }

        println();

        boolean fieldDeserFlag = false;
        for (int i = 0, size = beanInfo.fields.length; i < size; ++i) {
            FieldInfo fieldInfo = beanInfo.fields[i];
            Class<?> fieldClass = fieldInfo.fieldClass;

            if (fieldClass.isPrimitive()) {
                continue;
            }

            if (fieldClass.isEnum()) {
                continue;
            }

            print("private ObjectDeserializer ");

            if (Collection.class.isAssignableFrom(fieldClass)) {
                printListFieldItemDeser(fieldInfo);
            } else {
                printFieldDeser(fieldInfo);
            }
            println(";");
            fieldDeserFlag = true;

            if (Collection.class.isAssignableFrom(fieldClass)) {
                print("private Type ");
                printListFieldItemType(fieldInfo);
                print(" = ");
                Class<?> fieldItemClass = TypeUtils.getCollectionItemClass(fieldInfo.fieldType);
                printClassName(fieldItemClass);
                println(".class;");
            }
        }

        if (fieldDeserFlag) {
            println();
        }

        // constructor
        print("public ");
        print(genClassName);
        print(" (ParserConfig config, Class clazz) {");
        incrementIndent();
        println();

        println("super(config, clazz);");

        decrementIndent();
        println();
        print("}");
        println();
    }

}
