package org.gsonformat.intellij;

import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.PsiClassReferenceType;
import com.intellij.psi.util.PsiTypesUtil;
import org.apache.http.util.TextUtils;
import org.gsonformat.intellij.action.DataWriter;
import org.gsonformat.intellij.common.CheckUtil;
import org.gsonformat.intellij.common.PsiClassUtil;
import org.gsonformat.intellij.common.StringUtils;
import org.gsonformat.intellij.common.Utils;
import org.gsonformat.intellij.config.Config;
import org.gsonformat.intellij.entity.ClassEntity;
import org.gsonformat.intellij.entity.DataType;
import org.gsonformat.intellij.entity.FieldEntity;
import org.gsonformat.intellij.entity.IterableFieldEntity;
import org.gsonformat.intellij.ui.FieldsDialog;
import org.json.JSONArray;
import org.json.JSONObject;

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;


/**
 * Created by dim on 2015/8/21.
 * 把 json 转成 实体类
 */
public class ConvertBridge {

    private PsiClass targetClass;
    private PsiClass currentClass;
    private PsiElementFactory factory;
    private Project project;
    private PsiFile file;
    private String jsonStr;
    private HashMap<String, FieldEntity> declareFields;
    private HashMap<String, ClassEntity> declareClass;
    private String generateClassName;
    private ClassEntity generateClassEntity = new ClassEntity();
    private StringBuilder fullFilterRegex = null;
    private StringBuilder briefFilterRegex = null;
    private String filterRegex = null;
    private Operator operator;
    private String packageName;

    public ConvertBridge(Operator operator,
                         String jsonStr, PsiFile file, Project project,
                         PsiClass targetClass,
                         PsiClass currentClass, String generateClassName) {

        factory = JavaPsiFacade.getElementFactory(project);
        this.file = file;
        this.generateClassName = generateClassName;
        this.operator = operator;
        this.jsonStr = jsonStr;
        this.project = project;
        this.targetClass = targetClass;
        this.currentClass = currentClass;
        declareFields = new HashMap<>();
        declareClass = new HashMap<>();
        packageName = StringUtils.getPackage(generateClassName);
        fullFilterRegex = new StringBuilder();
        briefFilterRegex = new StringBuilder();
        CheckUtil.getInstant().cleanDeclareData();
        String[] arg = Config.getInstant().getAnnotationStr().replace("{filed}", "(\\w+)").split("\\.");

        for (int i = 0; i < arg.length; i++) {
            String s = arg[i];
            if (i == arg.length - 1) {
                briefFilterRegex.append(s);
                fullFilterRegex.append(s);
                Matcher matcher = Pattern.compile("\\w+").matcher(s);
                if (matcher.find()) {
                    filterRegex = matcher.group();
                }
            } else {
                fullFilterRegex.append(s).append("\\s*\\.\\s*");
            }
        }


    }

    public void run() {
        JSONObject json = null;
        operator.cleanErrorInfo();
        try {

            json = parseJSONObject(jsonStr);
        } catch (Exception e) {
            String jsonTS = removeComment(jsonStr);
            jsonTS = jsonTS.replaceAll("^.*?\\{", "{");
            try {
                json = parseJSONObject(jsonTS);
            } catch (Exception e2) {
                handleDataError(e2);
            }
        }
        if (json != null) {
            try {
                ClassEntity classEntity = collectClassAttribute(targetClass, Config.getInstant().isReuseEntity());
                if (classEntity != null) {
                    for (FieldEntity item : classEntity.getFields()) {
                        declareFields.put(item.getKey(), item);
                        CheckUtil.getInstant().addDeclareFieldName(item.getKey());
                    }
                }
                if (Config.getInstant().isSplitGenerate()) {
                    collectPackAllClassName();
                }
                operator.setVisible(false);
                parseJson(json);
            } catch (Exception e2) {
                handleDataError(e2);
                operator.setVisible(true);
            }
        }
        declareFields = null;
        declareClass = null;
    }

    private JSONObject parseJSONObject(String jsonStr) {
        if (jsonStr.startsWith("{")) {
            return new JSONObject(jsonStr);
        } else if (jsonStr.startsWith("[")) {
            JSONArray jsonArray = new JSONArray(jsonStr);

            if (jsonArray.length() > 0 && jsonArray.get(0) instanceof JSONObject) {
                return getJsonObject(jsonArray);
            }
        }
        return null;

    }

    private JSONObject getJsonObject(JSONArray jsonArray) {
        JSONObject resultJSON = jsonArray.getJSONObject(0);

        for (int i = 1; i < jsonArray.length(); i++) {
            Object value = jsonArray.get(i);
            if (!(value instanceof JSONObject)) {
                break;
            }
            JSONObject json = (JSONObject) value;
            for (String key : json.keySet()) {
                if (!resultJSON.keySet().contains(key)) {
                    resultJSON.put(key, json.get(key));
                }
            }
        }
        return resultJSON;
    }

    private void collectPackAllClassName() {
        File packageFile = PsiClassUtil.getPackageFile(file, packageName);
        if (packageFile != null) {
            File[] files = packageFile.listFiles();
            if (files != null) {
                for (File file1 : files) {
                    if (packageName == null) {
                        CheckUtil.getInstant().addDeclareClassName(file1.getName());
                    } else {
                        CheckUtil.getInstant().addDeclareClassName(packageName + "." + file1.getName());
                    }
                }
            }
        }

    }

    private void handleDataError(Exception e2) {
        e2.printStackTrace();
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        e2.printStackTrace(printWriter);
        printWriter.close();
        operator.showError(Error.DATA_ERROR);
        operator.setErrorInfo(writer.toString());
    }


    private ClassEntity collectClassAttribute(PsiClass psiClass, boolean collectInnerClass) {
        if (psiClass == null) {
            return null;
        }
        ClassEntity innerClass = new ClassEntity();
        innerClass.setLock(true);
        declareClass.put(psiClass.getQualifiedName(), innerClass);
        CheckUtil.getInstant().addDeclareClassName(psiClass.getQualifiedName());
        innerClass.setClassName(psiClass.getName());
        innerClass.addAllFields(collectDeclareFields(psiClass));
        innerClass.setPsiClass(psiClass);
        innerClass.setPackName(getPackName(psiClass));
        if (collectInnerClass) {
            recursionInnerClass(innerClass);
        }
        return innerClass;
    }

    private void recursionInnerClass(ClassEntity classEntity) {
        PsiClass[] innerClassArray = classEntity.getPsiClass().getAllInnerClasses();
        for (PsiClass psiClass : innerClassArray) {
            ClassEntity item = new ClassEntity();
            item.setLock(true);
            if (declareClass.containsKey(psiClass.getQualifiedName())) {
                return;
            }
            declareClass.put(psiClass.getQualifiedName(), item);
            CheckUtil.getInstant().addDeclareClassName(psiClass.getQualifiedName());
            item.setClassName(psiClass.getName());
            item.addAllFields(collectDeclareFields(psiClass));
            item.setPsiClass(psiClass);
            item.setPackName(getPackName(psiClass));
            recursionInnerClass(item);
        }
    }

    public String getPackName(PsiClass psiClass) {
        String packName = null;
        if (psiClass.getQualifiedName() != null) {
            int i = psiClass.getQualifiedName().lastIndexOf(".");
            if (i >= 0) {
                packName = psiClass.getQualifiedName().substring(0, i);
            } else {
                packName = psiClass.getQualifiedName();
            }
        }
        return packName;

    }

    /**
     * 过滤掉// 和/** 注释
     *
     * @param str
     * @return
     */
    public String removeComment(String str) {
        String temp = str.replaceAll("/\\*" +
                "[\\S\\s]*?" +
                "\\*/", "");
        return temp.replaceAll("//[\\S\\s]*?\n", "");
    }

    private List<FieldEntity> collectDeclareFields(PsiClass mClass) {

        ArrayList<FieldEntity> filterFieldList = new ArrayList<>();
        if (mClass != null) {
            PsiField[] psiFields = mClass.getAllFields();
            for (PsiField psiField : psiFields) {
                String fileName = null;

                String psiFieldText = removeComment(psiField.getText());
                if (filterRegex != null && psiFieldText.contains(filterRegex)) {
                    boolean isSerializedName = false;
                    psiFieldText = psiFieldText.trim();
                    Pattern pattern = Pattern.compile(fullFilterRegex.toString());
                    Matcher matcher = pattern.matcher(psiFieldText);
                    if (matcher.find()) {
                        fileName = matcher.group(1);
                        isSerializedName = true;
                    }
                    pattern = Pattern.compile(briefFilterRegex.toString());
                    matcher = pattern.matcher(psiFieldText);
                    if (matcher.find()) {
                        fileName = matcher.group(1);
                        isSerializedName = true;
                    }
                    if (!isSerializedName) {
                        fileName = psiField.getName();
                    }
                } else {
                    fileName = psiField.getName();
                }
                FieldEntity fieldEntity = evalFieldEntity(null, psiField.getType());
                fieldEntity.setKey(fileName);
                fieldEntity.setFieldName(fileName);
                filterFieldList.add(fieldEntity);
            }
        }

        return filterFieldList;
    }

    private FieldEntity evalFieldEntity(FieldEntity fieldEntity, PsiType type) {

        if (type instanceof PsiPrimitiveType) {
            if (fieldEntity == null) {
                fieldEntity = new FieldEntity();
            }
            fieldEntity.setType(type.getPresentableText());
            return fieldEntity;
        } else if (type instanceof PsiArrayType) {
            if (fieldEntity == null) {
                fieldEntity = new IterableFieldEntity();
            }
            IterableFieldEntity iterableFieldEntity = (IterableFieldEntity) fieldEntity;
            iterableFieldEntity.setDeep(iterableFieldEntity.getDeep() + 1);
            return evalFieldEntity(fieldEntity, ((PsiArrayType) type).getComponentType());
        } else if (type instanceof PsiClassReferenceType) {
            PsiClass psi = ((PsiClassReferenceType) type).resolveGenerics().getElement();

            if (isCollection(psi)) {
                if (fieldEntity == null) {
                    fieldEntity = new IterableFieldEntity();
                }
                IterableFieldEntity iterableFieldEntity = (IterableFieldEntity) fieldEntity;
                iterableFieldEntity.setDeep(iterableFieldEntity.getDeep() + 1);
                PsiType[] parameters = ((PsiClassReferenceType) type).getParameters();
                if (parameters.length > 0) {
                    PsiType parameter = parameters[0];
                    if (parameter instanceof PsiWildcardType) {
                        if (((PsiWildcardType) parameter).isExtends()) {
                            final PsiType extendsBound = ((PsiWildcardType) parameter).getExtendsBound();

                            evalFieldEntity(fieldEntity, extendsBound);
                        }
                        if (((PsiWildcardType) parameter).isSuper()) {
                            final PsiType superBound = ((PsiWildcardType) parameter).getSuperBound();
                            evalFieldEntity(fieldEntity, superBound);
                        }
                    } else if (parameter instanceof PsiClassReferenceType) {

                        PsiClass element = ((PsiClassReferenceType) parameter).resolveGenerics().getElement();
                        handleClassReferenceType(fieldEntity, element);
                    }
                }
                return fieldEntity;
            } else {

                if (fieldEntity == null) {
                    fieldEntity = new FieldEntity();
                }
                handleClassReferenceType(fieldEntity, psi);
                return fieldEntity;
            }

        }
        if (fieldEntity == null) {
            fieldEntity = new IterableFieldEntity();
        }
        return fieldEntity;
    }

    private void handleClassReferenceType(FieldEntity fieldEntity, PsiClass psi) {
        if (psi == null || psi.getQualifiedName() == null) {
            return;
        }
        switch (psi.getQualifiedName()) {
            case "java.lang.String":
                fieldEntity.setType("String");
                break;
            case "java.lang.Boolean":
                fieldEntity.setType("Boolean");
                break;
            case "java.lang.Integer":
                fieldEntity.setType("Integer");
                break;
            case "java.lang.Double":
                fieldEntity.setType("Double");
                break;
            case "java.lang.Long":
                fieldEntity.setType("Long");
                break;
            default:
                ClassEntity classEntity = declareClass.get(psi.getQualifiedName());
                if (classEntity == null) {
                    classEntity = collectClassAttribute(psi, true);
                }
                fieldEntity.setTargetClass(classEntity);
                break;
        }
    }

    private boolean isCollection(PsiClass element) {

        if ("java.util.Collection".equals(element.getQualifiedName())) {
            return true;
        }
        for (PsiClass psiClass : element.getInterfaces()) {
            if (isCollection(psiClass)) {
                return true;
            }
        }
        return false;
    }

    private void parseJson(JSONObject json) {
        List<String> generateFiled = collectGenerateFiled(json);
        if (Config.getInstant().isVirgoMode()) {
            handleVirgoMode(json, generateFiled);
        } else {
            handleNormal(json, generateFiled);
        }
    }

    private void handleVirgoMode(JSONObject json, List<String> fieldList) {
        generateClassEntity.setClassName("");
        generateClassEntity.setPsiClass(targetClass);
        generateClassEntity.addAllFields(createFields(json, fieldList, generateClassEntity));
        FieldsDialog fieldsDialog = new FieldsDialog(operator, generateClassEntity, factory,
                targetClass, currentClass, file, project, generateClassName);
        fieldsDialog.setSize(800, 500);
        fieldsDialog.setLocationRelativeTo(null);
        fieldsDialog.setVisible(true);
    }

    private void handleNormal(JSONObject json, List<String> generateFiled) {
        WriteCommandAction.runWriteCommandAction(project, new Runnable() {
            @Override
            public void run() {
                if (targetClass == null) {
                    try {
                        targetClass = PsiClassUtil.getPsiClass(file, project, generateClassName);
                    } catch (Throwable throwable) {
                        handlePathError(throwable);
                    }
                }
                if (targetClass != null) {
                    generateClassEntity.setPsiClass(targetClass);
                    try {
                        generateClassEntity.addAllFields(createFields(json, generateFiled, generateClassEntity));
                        operator.setVisible(false);
                        DataWriter dataWriter = new DataWriter(file, project, targetClass);
                        dataWriter.execute(generateClassEntity);
                        Config.getInstant().saveCurrentPackPath(packageName);
                        operator.dispose();
                    } catch (Exception e) {
                        throw e;
                    }
                }
            }
        });
    }

    private List<String> collectGenerateFiled(JSONObject json) {
        Set<String> keySet = json.keySet();
        List<String> fieldList = new ArrayList<String>();
        for (String key : keySet) {
            if (!existDeclareField(key, json)) {
                fieldList.add(key);
            }
        }
        return fieldList;
    }

    private boolean existDeclareField(String key, JSONObject json) {
        FieldEntity fieldEntity = declareFields.get(key);
        if (fieldEntity == null) {
            return false;
        }
        return fieldEntity.isSameType(json.get(key));
    }

    private void handlePathError(Throwable throwable) {
        throwable.printStackTrace();
        Writer writer = new StringWriter();
        PrintWriter printWriter = new PrintWriter(writer);
        throwable.printStackTrace(printWriter);
        printWriter.close();
        operator.setErrorInfo(writer.toString());
        operator.setVisible(true);
        operator.showError(Error.PATH_ERROR);
    }

    private List<FieldEntity> createFields(JSONObject json, List<String> fieldList, ClassEntity parentClass) {

        List<FieldEntity> fieldEntityList = new ArrayList<FieldEntity>();
        List<String> listEntityList = new ArrayList<String>();
        boolean writeExtra = Config.getInstant().isGenerateComments();

        for (int i = 0; i < fieldList.size(); i++) {
            String key = fieldList.get(i);
            Object value = json.get(key);
            if (value instanceof JSONArray) {
                listEntityList.add(key);
                continue;
            }
            FieldEntity fieldEntity = createField(parentClass, key, value);
            fieldEntityList.add(fieldEntity);
            if (writeExtra) {
                writeExtra = false;
                parentClass.setExtra(Utils.createCommentString(json, fieldList));
            }
        }

        for (int i = 0; i < listEntityList.size(); i++) {
            String key = listEntityList.get(i);
            Object type = json.get(key);
            FieldEntity fieldEntity = createField(parentClass, key, type);
            fieldEntityList.add(fieldEntity);
        }

        return fieldEntityList;
    }


    private FieldEntity createField(ClassEntity parentClass, String key, Object type) {
        //过滤 不符合规则的key
        String fieldName = CheckUtil.getInstant().handleArg(key);
        if (Config.getInstant().isUseSerializedName()) {
            fieldName = StringUtils.captureStringLeaveUnderscore(convertSerializedName(fieldName));
        }
        fieldName = handleDeclareFieldName(fieldName, "");

        FieldEntity fieldEntity = typeByValue(parentClass, key, type);
        fieldEntity.setFieldName(fieldName);
        return fieldEntity;
    }

    private String convertSerializedName(String fieldName) {
        if (Config.getInstant().isUseFieldNamePrefix() &&
                !TextUtils.isEmpty(Config.getInstant().getFiledNamePreFixStr())) {
            fieldName = Config.getInstant().getFiledNamePreFixStr() + "_" + fieldName;
        }
        return fieldName;
    }

    private FieldEntity typeByValue(ClassEntity parentClass, String key, Object type) {
        FieldEntity result;
        if (type instanceof JSONObject) {
            ClassEntity classEntity = existDeclareClass((JSONObject) type);
            if (classEntity == null) {
                FieldEntity fieldEntity = new FieldEntity();
                ClassEntity innerClassEntity = createInnerClass(createSubClassName(key, type), (JSONObject) type, parentClass);
                fieldEntity.setKey(key);
                fieldEntity.setTargetClass(innerClassEntity);
                result = fieldEntity;
            } else {
                FieldEntity fieldEntity = new FieldEntity();
                fieldEntity.setKey(key);
                fieldEntity.setTargetClass(classEntity);
                result = fieldEntity;
            }
        } else if (type instanceof JSONArray) {
            result = handleJSONArray(parentClass, (JSONArray) type, key, 1);
        } else {
            FieldEntity fieldEntity = new FieldEntity();
            fieldEntity.setKey(key);
            String vType;
            if (Config.getInstant().isUseWrapperClass()) {
                vType = PsiTypesUtil.boxIfPossible(DataType.getWrapperTypeSimpleName(DataType.typeOfObject(type)));
            } else {
                vType = DataType.typeOfObject(type).getValue();
            }
            fieldEntity.setType(vType);
            result = fieldEntity;
            if (type != null) {
                result.setValue(type.toString());
            }
        }
        result.setKey(key);
        return result;
    }

    private ClassEntity existDeclareClass(JSONObject jsonObject) {
        for (ClassEntity classEntity : declareClass.values()) {
            Iterator<String> keys = jsonObject.keys();
            boolean had = false;
            while (keys.hasNext()) {
                String key = keys.next();
                Object value = jsonObject.get(key);
                had = false;
                for (FieldEntity fieldEntity : classEntity.getFields()) {
                    if (fieldEntity.getKey().equals(key) && DataType.isSameDataType(DataType.typeOfString(fieldEntity.getType()), DataType.typeOfObject(value))) {
                        had = true;
                        break;
                    }
                }
                if (!had) {
                    break;
                }
            }
            if (had) {
                return classEntity;
            }
        }
        return null;
    }

    /**
     * @param className
     * @param json
     * @param parentClass
     * @return
     */
    private ClassEntity createInnerClass(String className, JSONObject json, ClassEntity parentClass) {

        if (Config.getInstant().isSplitGenerate()) {
            String qualifiedName = packageName == null ? className : packageName + "." + className;
            if (CheckUtil.getInstant().containsDeclareClassName(qualifiedName)) {
                //存在同名。
                PsiClass psiClass = PsiClassUtil.exist(file, qualifiedName);
                if (psiClass != null) {
                    ClassEntity classEntity = collectClassAttribute(psiClass, false);
                    classEntity.setLock(true);
                    if (classEntity.isSame(json)) {
//                        if (Config.getInstant().isReuseEntity()) {
                        declareClass.put(classEntity.getQualifiedName(), classEntity);
//                        }
                        return classEntity;
                    }
                }
            }
        }

        ClassEntity subClassEntity = new ClassEntity();

        Set<String> set = json.keySet();
        List<String> list = new ArrayList<String>(set);
        List<FieldEntity> fields = createFields(json, list, subClassEntity);
        subClassEntity.addAllFields(fields);
        if (Config.getInstant().isSplitGenerate()) {
            subClassEntity.setPackName(packageName);
        } else {
            subClassEntity.setPackName(parentClass.getQualifiedName());
        }
        subClassEntity.setClassName(className);
        if (handleDeclareClassName(subClassEntity, "")) {
            CheckUtil.getInstant().addDeclareClassName(subClassEntity.getQualifiedName());
        }
        if (Config.getInstant().isReuseEntity()) {
            declareClass.put(subClassEntity.getQualifiedName(), subClassEntity);
        }
        parentClass.addInnerClass(subClassEntity);

        return subClassEntity;
    }

    private boolean handleDeclareClassName(ClassEntity classEntity, String appendName) {

        classEntity.setClassName(classEntity.getClassName() + appendName);
        if (CheckUtil.getInstant().containsDeclareClassName(classEntity.getQualifiedName())) {
            return handleDeclareClassName(classEntity, "X");
        }
        return true;
    }

    private String handleDeclareFieldName(String fieldName, String appendName) {
        fieldName += appendName;
        if (CheckUtil.getInstant().containsDeclareFieldName(fieldName)) {
            return handleDeclareFieldName(fieldName, "X");
        }
        return fieldName;
    }

    private String createSubClassName(String key, Object o) {
        String name = "";
        if (o instanceof JSONObject) {
            if (TextUtils.isEmpty(key)) {
                return key;
            }
            String[] strings = key.split("_");
            StringBuilder stringBuilder = new StringBuilder();
            for (int i = 0; i < strings.length; i++) {
                stringBuilder.append(StringUtils.captureName(strings[i]));
            }
            name = stringBuilder.toString() + Config.getInstant().getSuffixStr();
        }
        return name;

    }

    private FieldEntity handleJSONArray(ClassEntity parentClass, JSONArray jsonArray, String key, int deep) {

        FieldEntity fieldEntity;
        if (jsonArray.length() > 0) {
            Object item = jsonArray.get(0);
            if (item instanceof JSONObject) {
                item = getJsonObject(jsonArray);
            }
            fieldEntity = listTypeByValue(parentClass, key, item, deep);
        } else {
            fieldEntity = new IterableFieldEntity();
            fieldEntity.setKey(key);
            fieldEntity.setType("?");
            ((IterableFieldEntity) fieldEntity).setDeep(deep);
        }
        return fieldEntity;
    }

    private FieldEntity listTypeByValue(ClassEntity parentClass, String key, Object type, int deep) {

        FieldEntity item = null;
        if (type instanceof JSONObject) {
            ClassEntity classEntity = existDeclareClass((JSONObject) type);
            if (classEntity == null) {
                IterableFieldEntity iterableFieldEntity = new IterableFieldEntity();
                ClassEntity innerClassEntity = createInnerClass(createSubClassName(key, type), (JSONObject) type, parentClass);
                iterableFieldEntity.setKey(key);
                iterableFieldEntity.setDeep(deep);
                iterableFieldEntity.setTargetClass(innerClassEntity);
                item = iterableFieldEntity;
            } else {
                IterableFieldEntity fieldEntity = new IterableFieldEntity();
                fieldEntity.setKey(key);
                fieldEntity.setTargetClass(classEntity);
                fieldEntity.setType(classEntity.getQualifiedName());
                fieldEntity.setDeep(deep);
                item = fieldEntity;
            }

        } else if (type instanceof JSONArray) {
            FieldEntity fieldEntity = handleJSONArray(parentClass, (JSONArray) type, key, ++deep);
            fieldEntity.setKey(key);
            item = fieldEntity;
        } else {
            IterableFieldEntity fieldEntity = new IterableFieldEntity();
            fieldEntity.setKey(key);
            fieldEntity.setType(type.getClass().getSimpleName());
            fieldEntity.setDeep(deep);
            item = fieldEntity;
        }
        return item;
    }


    public interface Operator {

        void showError(Error err);

        void dispose();

        void setVisible(boolean visible);

        void setErrorInfo(String error);

        void cleanErrorInfo();
    }

    public enum Error {
        DATA_ERROR, PARSE_ERROR, PATH_ERROR;
    }
}