package com.brilliance.mda.support.td;


import com.brilliance.mda.runtime.mda.FieldHolder;
import com.brilliance.mda.runtime.mda.IPanel;
import com.brilliance.mda.runtime.mda.IStream;
import com.brilliance.mda.runtime.mda.ModuleHolder;
import com.brilliance.mda.runtime.mda.impl.AbstractModule;
import com.brilliance.mda.runtime.mda.impl.ModuleList;
import com.brilliance.mda.runtime.mda.util.Reflects;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.io.IOUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public class ModuleInfoManager {

    private static Logger log = LoggerFactory.getLogger(ModuleInfoManager.class);

    //缓存模型信息
    private static final Map<String, Map<String, Map<String, Object>>> cache = new ConcurrentHashMap<>();
    //缓存 字段名->字段会触发的
    private static final Map<String, Map<String, List<String>>> defaultRule = new HashMap<>();
    //缓存  defaultRule->依赖的字段
    private static final Map<String, Map<String, List<String>>> dependencies = new HashMap<>();
    //文件存放目录
    private static final String path = "moduleInfo/";
    private static final String rulePattern = "classpath*:defaultRule/*";
    private static Map<Class<?>, TDModuleInfo> moduleInfoCacheMap = new HashMap<>();

    static {
        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        try {
            Resource[] resources = resourceResolver.getResources(rulePattern);
            for (Resource dp : resources) {
                try {
                    Document document = new SAXReader().read(dp.getInputStream());
                    Iterator<Element> iterator = document.getRootElement().elementIterator();   //获取根元素下的迭代器
                    Map<String, List<String>> defaultRuleMap = new HashMap<>();
                    Map<String, List<String>> dependencyMap = new HashMap<>();
                    while (iterator.hasNext()) {
                        Element next = iterator.next();         //Rule元素
                        Iterator<Element> elementIterator = next.elementIterator();
                        Element element = elementIterator.next();       //target元素
                        String defaultRule = element.getText();
                        List<String> dependency = new ArrayList<>();
                        while (elementIterator.hasNext()) {
                            String path = elementIterator.next().getText();
                            //defaultRuleMap 添加元素
                            List<String> list = defaultRuleMap.get(path);
                            if (list == null) {
                                List<String> rules = new ArrayList<>();
                                rules.add(defaultRule);
                                defaultRuleMap.put(path, rules);
                            } else {
                                list.add(defaultRule);
                            }
                            //dependencyMap 添加元素
                            dependency.add(path);
                        }
                        dependencyMap.put(defaultRule, dependency);
                    }
                    String transaction = dp.getFilename().replace(".xml", "");
                    dependencies.put(transaction, dependencyMap);
                    defaultRule.put(transaction, defaultRuleMap);
                } catch (Exception e) {
                    log.error("解析defaultRule配置异常", e);
                }
            }
        } catch (IOException e) {
            log.error("解析defaultRule配置异常", e);
        }
    }

    /**
     * @param transaction 交易名
     * @param path        路径名
     * @return 对应的default rule
     */
    public static List<String> getDefaultRule(String transaction, String path) {
        if (path.charAt(0) == '\\')
            path = path.substring(1);
        path = path.toLowerCase(Locale.ROOT).replace("\\", ".");
        Map<String, List<String>> transDefaultSet = defaultRule.get(transaction.toLowerCase(Locale.ROOT));
        if (transDefaultSet == null) {
            return null;
        }
        return transDefaultSet.get(path);
    }

    public static Map<String, List<String>> getDefaultRule(String transaction) {
        return defaultRule.get(transaction.toLowerCase(Locale.ROOT));
    }

    public static Map<String, Map<String, List<String>>> getDefaultRule() {
        return defaultRule;
    }

    /**
     * @param transaction 交易名
     * @param target      路径名
     * @return 对应的依赖
     */
    public static List<String> getDependency(String transaction, String target) {
        if (target.charAt(0) == '\\')
            target = target.substring(1);
        target = target.toLowerCase(Locale.ROOT).replace("\\", ".");
        return dependencies.get(transaction.toLowerCase(Locale.ROOT)).get(target);
    }

    public static Map<String, List<String>> getDependency(String transaction) {
        return dependencies.get(transaction.toLowerCase(Locale.ROOT));
    }

    public static Map<String, Map<String, List<String>>> getDependency() {
        return dependencies;
    }

    /**
     * 传入模型名称，从resources/moduleInfo下查找文件
     *
     * @param moduleName 模型名称
     * @return 模型对应的map
     */
    public static Map<String, Map<String, Object>> getModuleInfo(String moduleName) {
        if (!StringUtils.hasLength(moduleName))
            throw new NullPointerException("模型名不能为空");
        if (cache.containsKey(moduleName))
            return cache.get(moduleName);
        try (InputStream stream = new ClassPathResource(path + moduleName + ".info").getInputStream()) {
            Map map = new ObjectMapper().readValue(IOUtils.toString(stream, StandardCharsets.UTF_8), Map.class);
            cache.put(moduleName, map);
            return map;
        } catch (IOException e) {
            throw new IllegalArgumentException(e.getCause());
        }
    }

    /**
     * 传入模型名称，和字段名称 从resources/moduleInfo下查找文件，并在文件中查找对应字段<br/>
     * 返回的map中key包含{type, viewLine, viewType, dataType, length, line, codeTable}
     *
     * @param moduleName 模型名称
     * @param fieldName  模型中的字段名称
     * @return 模型moduleName#fileName字段对应的map集合
     */
    public static Map<String, Object> getModuleFieldInfo(String moduleName, String fieldName) {
        Map<String, Map<String, Object>> moduleInfo = getModuleInfo(moduleName);
        if (moduleInfo == null)
            throw new IllegalArgumentException("模型名错误或模型文件不存在");
        if (!StringUtils.hasLength(fieldName))
            throw new IllegalArgumentException("字段名称参数名不能为空");
        return moduleInfo.get(fieldName);
    }

    public static int getModuleInfoCount() {
        return cache.size();
    }

    public static void loadModuleInfo(Class<?> moduleClass) {
        Method[] methods = moduleClass.getMethods();
        //防止同名函数,要用List
        Map<String, List<Method>> tdMethods = new HashMap<>();
        Map<String, List<Method>> allPublicMethods = new HashMap<>();
        Map<String, Method> allPublicLowerCaseMethods = new HashMap<>();
        for (Method method : methods) {
            List<Method> methodList = allPublicMethods.computeIfAbsent(method.getName(), k -> new ArrayList<>());
            methodList.add(method);

            allPublicLowerCaseMethods.put(method.getName().toLowerCase() + "#" + method.getParameterCount(), method);

            methodList = tdMethods.computeIfAbsent(method.getName(), k -> new ArrayList<Method>());
            methodList.add(method);
        }

        //IModule字段集合
        Map<String, TDFieldInfo> tdModules = new HashMap<>();
        //IModuleList字段集合
        Map<String, TDFieldInfo> tdModuleLists = new HashMap<>();
        //简单字段集合
        Map<String, TDFieldInfo> tdPrimitiveFields = new HashMap<>();
        //IStream字段集合
        Map<String, TDFieldInfo> tdStreamFields = new HashMap<>();
        //IPanel字段集合
        Map<String, TDFieldInfo> tdPanelFields = new HashMap<>();
        //ModuleHolder字段集合
        Map<String, TDFieldInfo> tdModuleHolderFields = new HashMap<>();
        //FieldHolder字段集合
        Map<String, TDFieldInfo> tdFieldHolderFields = new HashMap<>();

        Field[] fields = moduleClass.getDeclaredFields();
        for (Field field : fields) {
            String fieldName = field.getName();
            String getMethodName = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
            String setMethodName = getMethodName.replaceFirst("get", "set");
            Method getMethod = Reflects.getMethod(moduleClass, getMethodName);
            Method setMethod = Reflects.getMethod(moduleClass, setMethodName, field.getType());
            if (AbstractModule.class.isAssignableFrom(field.getType())) {
                tdModules.put(field.getName(), new TDFieldInfo(field.getName(), field.getType(), field, getMethod, setMethod));
            } else if (ModuleList.class.isAssignableFrom(field.getType())) {
                Class<?> actualType = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
                tdModuleLists.put(field.getName(), new TDFieldInfo(field.getName(), actualType, field, getMethod, setMethod));
            } else if (IStream.class.isAssignableFrom(field.getType())) {
                tdStreamFields.put(field.getName(), new TDFieldInfo(field.getName(), field.getType(), field, getMethod, setMethod));
            } else if (IPanel.class.isAssignableFrom(field.getType())) {
                tdPanelFields.put(field.getName(), new TDFieldInfo(field.getName(), field.getType(), field, getMethod, setMethod));
            } else if (ModuleHolder.class.isAssignableFrom(field.getType())) {
                tdModuleHolderFields.put(field.getName(), new TDFieldInfo(field.getName(), field.getType(), field, getMethod, setMethod));
            } else if (FieldHolder.class.isAssignableFrom(field.getType())) {
                tdFieldHolderFields.put(field.getName(), new TDFieldInfo(field.getName(), field.getType(), field, getMethod, setMethod));
            } else {
                tdPrimitiveFields.put(field.getName(), new TDFieldInfo(field.getName(), field.getType(), field, getMethod, setMethod));
            }
        }

        moduleInfoCacheMap.put(moduleClass, new TDModuleInfo(moduleClass.getSimpleName(),
                moduleClass, tdMethods, allPublicMethods, allPublicLowerCaseMethods, tdModules, tdModuleLists, tdPrimitiveFields,
                tdStreamFields, tdPanelFields, tdModuleHolderFields, tdFieldHolderFields));
    }

    public static TDModuleInfo getTDModuleInfo(Class<?> moduleClass) {
        return moduleInfoCacheMap.getOrDefault(moduleClass, null);
    }

    public static Map<String, List<Method>> getTDMethods(Class<?> moduleClass) {
        TDModuleInfo moduleInfo = getTDModuleInfo(moduleClass);
        return moduleInfo != null ? moduleInfo.getTdMethods() : null;
    }

    public static Map<String, List<Method>> getAllPublicMethods(Class<?> moduleClass) {
        TDModuleInfo moduleInfo = getTDModuleInfo(moduleClass);
        return moduleInfo != null ? moduleInfo.getAllPublicMethods() : null;
    }

    public static Map<String, Method> getAllPublicLowerCaseMethods(Class<?> moduleClass) {
        TDModuleInfo moduleInfo = getTDModuleInfo(moduleClass);
        return moduleInfo != null ? moduleInfo.getAllPublicLowerCaseMethods() : null;
    }

    public static Map<String, TDFieldInfo> getTDModules(Class<?> moduleClass) {
        TDModuleInfo moduleInfo = getTDModuleInfo(moduleClass);
        return moduleInfo != null ? moduleInfo.getTdModules() : null;
    }
}
