package com.brilliance.mda.runtime.mda.driver.compile.processor;

import com.brilliance.mda.runtime.annotation.Check;
import com.brilliance.mda.runtime.annotation.Default;
import com.brilliance.mda.runtime.annotation.Init;
import com.brilliance.mda.runtime.annotation.Rule;
import com.brilliance.mda.runtime.mda.FieldHolder;
import com.brilliance.mda.runtime.mda.IModule;
import com.brilliance.mda.runtime.mda.IModuleList;
import com.brilliance.mda.runtime.mda.ModuleHolder;
import com.brilliance.mda.runtime.mda.driver.compile.component.ModuleRuleContext;
import com.brilliance.mda.runtime.mda.driver.compile.component.RuleItem;
import com.brilliance.mda.runtime.mda.impl.Argument;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

@Data
public class ModuleAnnotationProcess {
    private static Logger logger = LoggerFactory.getLogger(ModuleAnnotationProcess.class);

    public static Map<String, ModuleRuleContext> cache = new HashMap<>();

    private ModuleRuleContext moduleRuleContext;
    private Class<? extends IModule> moduleClass;
    private IModule instatnce;

    public ModuleAnnotationProcess() {

    }

    public ModuleAnnotationProcess(IModule instance) {
        makeInstance(instance);
    }

    /**
     * 编译完成后，将cache清理下，节约内存
     */
    public static void reduceCache() {
        Map<String, ModuleRuleContext> cacheClone = new HashMap<>();
        cache.entrySet().forEach(entry -> {
            ModuleRuleContext moduleRuleContext = entry.getValue();
            if (moduleRuleContext.isUseEmitter()) {
                if (moduleRuleContext.getMergedContext() != null) {
                    moduleRuleContext.setEventRule(moduleRuleContext.getMergedContext().getEventRule());
                    moduleRuleContext.setDefaultRule(moduleRuleContext.getMergedContext().getDefaultRule());
                    moduleRuleContext.setCheckRule(moduleRuleContext.getMergedContext().getCheckRule());
                    moduleRuleContext.setInitList(moduleRuleContext.getMergedContext().getInitList());
                    moduleRuleContext.setMergedContext(null);
                }
                cacheClone.put(entry.getKey(), moduleRuleContext);
            }
        });
        cache = cacheClone;
    }

    /**
     * 设置模型实例
     *
     * @param instance
     */
    public void makeInstance(IModule instance, boolean useEmitter) {
        moduleClass = instance.getClass();
        this.instatnce = instance;
        moduleRuleContext = new ModuleRuleContext(moduleClass.getCanonicalName(), useEmitter);
    }

    public void makeInstance(IModule instance) {
        makeInstance(instance, false);
    }

    public void dealModule() {

    }

    public void walkModule() {
        Arrays.stream(moduleClass.getDeclaredFields()).forEach(this::dealField);
        int initSize = this.moduleRuleContext.getInitList().size();
        Arrays.stream(moduleClass.getMethods()).forEach(this::dealMethod);
        this.moduleRuleContext.getInitList().subList(initSize, this.moduleRuleContext.getInitList().size()).sort(Comparator.comparingInt(RuleItem::getOrder));
        cache.put(getCacheKey(moduleClass), this.moduleRuleContext);
    }

    /**
     * 将带有模型Rule注解的方法纳入库中
     *
     * @param method
     */
    private void dealMethod(Method method) {
        //私有方法不生效
        String target = null;
        String belongPath = "";
        int order = 0;
        if (method.isAnnotationPresent(Rule.class)) {
            Rule rule = method.getAnnotation(Rule.class);
            target = rule.target();
            order = rule.order();
            String[] multiTarget = rule.value();
            if (multiTarget == null || multiTarget.length == 0) {
                multiTarget = new String[]{target};
            }
            method.setAccessible(true);
            putItemToMap(moduleRuleContext.getEventRule(), RuleItem.RULE, multiTarget, belongPath, method, order);
        } else if (method.isAnnotationPresent(Check.class)) {
            Check check = method.getAnnotation(Check.class);
            target = check.target();
            order = check.order();
            String[] multiTarget = check.value();
            if (multiTarget == null || multiTarget.length == 0) {
                multiTarget = new String[]{target};
            }
            method.setAccessible(true);
            putItemToMap(moduleRuleContext.getCheckRule(), RuleItem.CHECK, multiTarget, belongPath, method, order);
        } else if (method.isAnnotationPresent(Default.class)) {
            Default defaultAno = method.getAnnotation(Default.class);
            target = defaultAno.target();
            order = defaultAno.order();
            String[] multiTarget = defaultAno.value();
            if (multiTarget == null || multiTarget.length == 0) {
                multiTarget = new String[]{target};
            }
            method.setAccessible(true);
            putItemToMap(moduleRuleContext.getDefaultRule(), RuleItem.DEFAULT, multiTarget, belongPath, method, order);
        } else if (method.isAnnotationPresent(Init.class)) {
            Init init = method.getAnnotation(Init.class);
            order = init.order();
            method.setAccessible(true);
            putItemInitToList(moduleRuleContext.getInitList(), belongPath, method, order);
        } else if (Modifier.isStatic(method.getModifiers())) {
            //收集全局方法
            moduleRuleContext.putGlobalMethod(method);
        }
    }

    /**
     * 对字段为模型的，进行递归收集
     *
     * @param field 当前字段
     */
    private void dealField(Field field) {
        if (IModuleList.class.isAssignableFrom(field.getType())) {
            //要记录所属泛型
            moduleRuleContext.getModuleList().add(field.getName());
            //需要注册Emitter
            ModuleRuleContext moduleListRuleContext = deepWalkChildField(field);
            if (moduleListRuleContext != null && moduleListRuleContext.getInitList().size() > 0) {
                putModuleListInitToList(moduleRuleContext.getInitList(), field.getName(), "executeInit", 1000);
            }
            if (moduleListRuleContext != null && moduleListRuleContext.getDefaultRule().size() > 0) {
                putItemToMap(moduleRuleContext.getDefaultRule(), RuleItem.DEFAULT, new String[]{field.getName()}, field.getName(), "executeDefault", 1000);
            }
            //ModuleList 只合并全局方法，不合并static rule
            this.moduleRuleContext.getGlobalMethod().putAll(moduleListRuleContext.getGlobalMethod());
        } else if (IModule.class.isAssignableFrom(field.getType())) {
            deepWalkChildField(field);
        } else if (ModuleHolder.class.isAssignableFrom(field.getType())) {

        } else if (FieldHolder.class.isAssignableFrom(field.getType())) {
            //可能要关联真实路径
        }
    }

    /**
     * 获取字段的真实名称
     *
     * @param field
     * @param getterHolder
     * @return
     */
    private String getFieldName(Field field, Argument<Method> getterHolder) {
        String fieldName = field.getName();
        Method getter = getGetterMethod(field);
        if (getterHolder != null) {
            getterHolder.value = getter;
        }
        if (getter != null) {
            return fieldName;
        }
        //特殊处理
        if (IModule.class.isAssignableFrom(field.getType()) && fieldName.startsWith("tmp") && fieldName.length() > 3) {
            fieldName = fieldName.substring(3);
            String getterName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
            try {
                getter = field.getDeclaringClass().getMethod(getterName);
                IModule module = (IModule) getter.invoke(instatnce);
                field.setAccessible(true);
                IModule fieldModule = (IModule) field.get(instatnce);
                if (module == fieldModule) {
//                    return module.getName();
                    return field.getName();
                }
                if (getterHolder != null) {
                    getterHolder.value = getter;
                }
            } catch (Exception e) {
                logger.error("exception:{}", e);
            }
        }
        return fieldName;
    }

    private ModuleRuleContext deepWalkChildField(Field field) {
        String cacheKey = getCacheKey(field.getType());
        ModuleRuleContext oneModuleCache = cache.get(cacheKey);
        if (oneModuleCache != null) {
            //映射一下缓存
            if (!IModuleList.class.isAssignableFrom(field.getType())) {
                this.moduleRuleContext.mergeChild(getFieldName(field, null), oneModuleCache);
            } else {

            }
            return null;
        }
        //遍历当前子模型
        Argument<Method> methodBox = Argument.box(null);
        String fieldName = getFieldName(field, methodBox);
        Method getter = methodBox.value;
        if (getter != null) {
            try {
                //深入一层
                IModule subIns = (IModule) getter.invoke(instatnce);
                //映射当前module至指定实例路径
                //如果是ModuleList，则需要为其模型构建Emitter
                if (subIns instanceof IModuleList) {
                    IModuleList moduleList = (IModuleList) subIns;
                    cacheKey = moduleList.getDataClass().getCanonicalName();
                    oneModuleCache = cache.get(cacheKey);
                    if (oneModuleCache == null) {
                        ModuleAnnotationProcess subProc = new ModuleAnnotationProcess((IModule) moduleList.getDataClass().newInstance());
                        subProc.walkModule();
                        //cache.put(cacheKey, subProc.moduleRuleContext);
                        oneModuleCache = subProc.moduleRuleContext;
                    }
                    if (oneModuleCache.isUseEmitter() || oneModuleCache.isRuleEmpty()) {
                        return oneModuleCache; //该ModuleList 已经建立过emitter，或者Rule都是空的，则退出
                    } else {
                        oneModuleCache.setUseEmitter(true);
                        ModuleListAnnotationProcessor moduleListAnnotationProcessor = new ModuleListAnnotationProcessor();
                        moduleListAnnotationProcessor.buildModuleListEmitter(moduleList.getDataClass(), oneModuleCache);
                    }
                    return oneModuleCache;
                } else {
                    ModuleAnnotationProcess subProc = new ModuleAnnotationProcess(subIns);
                    subProc.walkModule();
                    //cache.put(cacheKey, subProc.moduleRuleContext);
                    //合并子模块
                    this.moduleRuleContext.mergeChild(fieldName, subProc.moduleRuleContext);
                    return subProc.moduleRuleContext;
                }
            } catch (Exception e) {
                logger.error("深度遍历模型发生异常：{}", e);
            } finally {
            }
        }
        return null;
    }

    protected String getCacheKey(Class moduleClass) {
        return moduleClass.getCanonicalName();
    }

    /**
     * 获取字段的get方法
     */
    public static Method getGetterMethod(Field field) {
        String fieldName = field.getName();
        String methodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        try {
            Method method = field.getDeclaringClass().getMethod(methodName);
            if (method.getParameters().length == 0)
                return method;
        } catch (Exception e) {
            //找不到get方法就算了，不打异常了
        }
        return null;
    }

    /**
     * 获取字段的set方法
     */
    public static Method getSetterMethod(Field field) {
        String fieldName = field.getName();
        String methodName = "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
        try {
            Method method = field.getDeclaringClass().getMethod(methodName, field.getType());
            //if(method.getParameters().length == 1)
            return method;
        } catch (Exception e) {
            //找不到set方法就算了，不打异常了
        }
        return null;
    }

    /**
     * 内部调用，叫RuleItem加入到
     *
     * @param container
     * @param ruleType
     * @param targets
     * @param belongPath
     * @param method     String or Method
     * @param order
     */
    private void putItemToMap(Map container, String ruleType, String[] targets, String belongPath, Object method, int order) {
        for (String target : targets) {
            RuleItem ruleItem = new RuleItem();
            ruleItem.setRuleType(ruleType);
            ruleItem.initGetPath(belongPath);
            ruleItem.setEntirePath(target);
            ruleItem.setMethod(method);
            ruleItem.setOrder(order);
            List<RuleItem> ruleItemList = (List<RuleItem>) container.get(target);
            if (ruleItemList == null) {
                ruleItemList = new ArrayList();
                container.put(target, ruleItemList);

            }
            //ruleItem.getBelongClass().push(moduleClass.getCanonicalName());
            ruleItemList.add(ruleItem);
        }
    }

    private void putItemInitToList(List ruleList, String belongPath, Method method, int order) {
        RuleItem ruleItem = new RuleItem();
        String ruleType = RuleItem.INIT;
        ruleItem.setRuleType(ruleType);
        ruleItem.initGetPath(belongPath);
        ruleItem.setMethod(method);
        ruleItem.setOrder(order);
        //ruleItem.getBelongClass().push(moduleClass.getCanonicalName());
        ruleList.add(ruleItem);
    }

    private void putModuleListInitToList(List ruleList, String belongPath, String methodName, int order) {
        RuleItem ruleItem = new RuleItem();
        String ruleType = RuleItem.INIT;
        ruleItem.setRuleType(ruleType);
        ruleItem.initGetPath(belongPath);
        ruleItem.setMethod(methodName);
        ruleItem.setOrder(order);
        ///ruleItem.getBelongClass().push(ModuleList.class.getCanonicalName());
        //ruleItem.getBelongClass().push(moduleClass.getCanonicalName());

        ruleList.add(ruleItem);
    }

    //将收集的static rule合并
    private void putItemToStaticMap(Map<String, RuleItem> staticMap, String belongPath, Method method) {
        RuleItem ruleItem = new RuleItem();
        ruleItem.initGetPath(belongPath);
        ruleItem.setMethod(method);
        staticMap.put(ModuleRuleContext.getMethodKey(method), ruleItem);
    }
}
