package com.brilliance.mda.runtime.mda.util;

import com.brilliance.mda.runtime.mda.*;
import com.brilliance.mda.runtime.mda.driver.DCR;
import com.brilliance.mda.runtime.mda.driver.MdaDriver;
import com.brilliance.mda.runtime.mda.driver.MdaEnv;
import com.brilliance.mda.runtime.mda.driver.compile.component.AbstractAutoCompileEmitter;
import com.brilliance.mda.runtime.mda.driver.compile.component.AbstractAutoCompileScopeEmitter;
import com.brilliance.mda.runtime.mda.impl.AbstractScopeEmitter;
import com.brilliance.mda.runtime.mda.impl.Argument;
import com.brilliance.mda.runtime.mda.impl.ModuleList;
import com.brilliance.mda.support.td.ModuleInfoManager;
import com.brilliance.mda.support.td.TDFieldInfo;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
import org.apache.commons.beanutils.MethodUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.brilliance.mda.runtime.mda.Constants.*;

public class Modules {

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

    private static final Map<String, Method> staticPostMethodCache = new ConcurrentHashMap<>();
    private static final Map<String, Method> casecadeMethodCache = new ConcurrentHashMap<>();
    public static List<Class<?>> staticPostArea = new ArrayList<>();
    private static Method EMPTY_METHOD;
    //暂时仅用于：为了实现invokeExpress，获取util目录下所有class
    private static String utilPath = "com.brilliance.mda.runtime.mda.util";

    static {
        try {
            staticPostArea.add(Platform_class);
            //staticPostArea.add(Dynamic_class);
            staticPostArea.addAll(getClasses(utilPath));
            EMPTY_METHOD = Modules.class.getMethod("clearPostQueue");
        } catch (ClassNotFoundException e) {
            log.error(e.getMessage());
        } catch (IOException e) {
            log.error(e.getMessage());
        } catch (NoSuchMethodException e) {
            log.error(e.getMessage());
        }
    }

    public static ObjectMapper getObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        SimpleFilterProvider filterProvider = new SimpleFilterProvider();
        // 在存盘的时候，不需要过滤字段，故 serializeAllExcept 不传参数
        SimpleBeanPropertyFilter fieldFilter = SimpleBeanPropertyFilter.serializeAllExcept();
        filterProvider.addFilter("fieldFilter", fieldFilter);
        objectMapper.setFilterProvider(filterProvider);
        return objectMapper;
    }

    public static String module2Data(IBaseObject moduleOrList) {
        ObjectMapper mapper = getObjectMapper();
        try {
            String dataString = mapper.writeValueAsString(moduleOrList);
            return dataString;
        } catch (Exception e) {

        }
        return null;
    }

    public static Object data2Module(Class clazz, String dataString) {
        ObjectMapper mapper = new ObjectMapper();
        try {
            boolean isEnableNotify = DCR.isEnable();
            if (isEnableNotify)
                DCR.enNotify(false);
            Object obj = mapper.readValue(dataString, clazz);
            if (isEnableNotify)
                DCR.enNotify(true);
            return obj;
        } catch (Exception e) {

        }
        return null;
    }

    //ExecuteDefault
    public static void postDefaultRule(IModule module, String field) {
        if (field != null) {
            if (module.getParent() instanceof IModuleList) {
                //modulelist的儿子模型
                AbstractScopeEmitter scopeEmitter = DCR.getModuleListEmitter(((IModuleList) module.getParent()).getDataClass());
                scopeEmitter.acceptContext(module);
                if (scopeEmitter instanceof AbstractAutoCompileScopeEmitter) {
                    ((AbstractAutoCompileScopeEmitter) scopeEmitter).executePostDefault(field);
                } else {
                    scopeEmitter.executeDefault(field);
                }
                scopeEmitter.removeContext();
            } else {
                String fieldPath = module.getFieldPath(field);
                int leftCommas = fieldPath.indexOf('(');
                if (leftCommas > -1) {
                    //modulelist的孙子模型
                    int rightCommas = fieldPath.indexOf(leftCommas, ')');
                    String relativePath = fieldPath.substring(rightCommas + 1);
                    //String moduleListPath = fieldPath.substring(0, leftCommas);
                    IModule parent = module.getParent();
                    IModule scopeModule = module;
                    while (parent != null && !(parent instanceof IModuleList)) {
                        scopeModule = parent;
                        parent = parent.getParent();
                    }
                    AbstractScopeEmitter scopeEmitter = DCR.getModuleListEmitter(((IModuleList) parent).getDataClass());
                    scopeEmitter.acceptContext(scopeModule);
                    if (scopeEmitter instanceof AbstractAutoCompileScopeEmitter) {
                        ((AbstractAutoCompileScopeEmitter) scopeEmitter).executePostDefault(relativePath);
                    } else {
                        scopeEmitter.executeDefault(relativePath);
                    }
                    scopeEmitter.removeContext();
                } else {
                    if (MdaEnv.getContext().getEmitter() instanceof AbstractAutoCompileEmitter) {
                        ((AbstractAutoCompileEmitter) MdaEnv.getContext().getEmitter()).executePostDefault(fieldPath);
                    } else {
                        MdaEnv.getContext().getEmitter().executeDefault(fieldPath);
                    }
                }
            }
        } else
            MdaEnv.getContext().getEmitter().executeDefault(module.getPath());

    }


    public static void postDefaultRule(IModule t) {
        postDefaultRule(t, null);
    }


    public static void executePostQueue() {
        LinkedList<Map> linkedList = (LinkedList<Map>) MdaEnv.getContext().restoreData(tdContextPOSTQUEUE);
        if (linkedList == null || linkedList.size() == 0) {
            return;
        }
        // 在执行invokeExpress rule时候有可能还往队列里方，所以需要循环调用直到为空
        //建立计数器，防止无限循环
        int count = linkedList.size() + 15;
        while (!linkedList.isEmpty() && linkedList.size() != 0) {
            // 排序，先加入队列并且延迟数小的先执行
            count--;
            if (count == 0) {
                //队列一直有数，超过15次，不干了
                linkedList.clear();
                log.error("无限循环队列执行！！");
                break;
            }
            linkedList.sort((a, b) -> {
                double delayA = (Double) a.get("delay");
                long putTimeA = (long) a.get("putTime");
                double delayB = (Double) b.get("delay");
                long putTimeB = (long) b.get("putTime");
                long delayAMillisecond = new BigDecimal(delayA).multiply(new BigDecimal(1000)).longValue();
                long delayBMillisecond = new BigDecimal(delayB).multiply(new BigDecimal(1000)).longValue();
                long timeTotalA = putTimeA + delayAMillisecond;
                long timeTotalB = putTimeB + delayBMillisecond;
                return Long.compare(timeTotalA, timeTotalB);
            });
            // 采用pop方式拿取，按照顺序执行一个pop一个
            Map item = linkedList.pop();
            IModule module = (IModule) item.get("module");
            String rule = (String) item.get("rule");
            Double delay = (Double) item.get("delay");
            invokeExpress(module, rule, -1);
        }
    }

    public static void clearPostQueue() {
        LinkedList<Map> linkedList = (LinkedList<Map>) MdaEnv.getContext().restoreData(tdContextPOSTQUEUE);
        if (linkedList == null) {
            return;
        }
        linkedList.clear();
    }

    //Post
    public static void invokeExpress(IModule _this, String rule, double delay) {
        if (rule == null || rule.length() == 0) {
            return;
        }
        rule = rule.trim();
        LinkedList<Map> linkedList = (LinkedList<Map>) MdaEnv.getContext().restoreData(tdContextPOSTQUEUE);
        if (linkedList == null) {
            linkedList = new LinkedList<>();
            MdaEnv.getContext().storeData(tdContextPOSTQUEUE, linkedList);
        }
        if (delay >= 0) {
            //>=0的情况，进入队列，不在当前Rule里面执行
            Map<String, Object> map = new HashMap<>();
            map.put("module", _this);
            map.put("rule", rule);
            map.put("delay", delay);
            // 加入队列时间
            map.put("putTime", System.currentTimeMillis());
            linkedList.add(map);
            return;
        }
        try {
            //Rule分三段，belong,function,expressionList
            String segItem = "\\w[\\w\\d]*";
            // 最复杂路径  \SETMOD\SETGLG\SETGLL(1).SETSUMS1     与checkStatement保持一致
            String pathReg = String.format("(\\\\)|([\\\\]?%s((\\\\%s)|(\\(\\d+\\)))*)", segItem, segItem);
            String belongReg = String.format("(?<belong>%s)", pathReg);
            String functionReg = "(?<func>\\w+)";
            String argsReg = "(?<args>.*)";
            String entireReg = String.format("(%s\\s*\\.\\s*)?%s\\s*(\\(%s\\))?", belongReg, functionReg, argsReg);
            //System.out.println(entireReg);
            Matcher m = Pattern.compile(entireReg).matcher(rule);
            if (!m.find() || m.start() != 0) {
                //TODO 错误，设置错误码
                MdaEnv.getContext().setErrorCode(-999);
                return;
                // String[] exprArr = getExpressions(expressionList);
            }
            String belong = m.group("belong");
            String function = m.group("func");
            String argsStr = m.group("args");

            String[] args = Strings.splitArgumentsString(argsStr);

            if (belong != null) {
                belong = belong.toLowerCase();
                IModule subModule = null;
                if (belong.startsWith("\\"))
                    subModule = MdaDriver.getModule(belong);
                else
                    subModule = MdaDriver.getModule(_this, belong);
                //MdaDriver.getModule(belong);
                if (subModule == null) {
                    MdaEnv.getContext().setErrorCode(999);
                    return;
                }
                Method method = findModuleMethod(_this, subModule.getClass(), function, args);
                if (method != null) {
                    invokeExpressMethod(_this, subModule, method, args);
                    MdaEnv.getContext().setErrorCode(NO_ERROR);
                    return;
                }
                MdaEnv.getContext().setErrorCode(130);
            } else {
                //全局函数搜索
                Method method = searchStaticFunction(_this, function, args);
                if (method != null) {
                    invokeExpressMethod(_this, null, method, args); //执行静态方法，Class传null
                    MdaEnv.getContext().setErrorCode(NO_ERROR);
                    return;
                }
                //先从当前模型找
                if (_this != null) {
                    method = findModuleMethod(_this, _this.getClass(), function, args);
                    if (method != null) {
                        invokeExpressMethod(_this, _this, method, args);
                        MdaEnv.getContext().setErrorCode(NO_ERROR);
                        return;
                    }
                }
                MdaEnv.getContext().setErrorCode(130);
                //全局查找
                /*Argument moduleArg = new Argument();
                method = findCasecadeMethod(MdaEnv.getContext().getRoot(), _this, moduleArg, function, args);
                if (method != null) {
                    IModule subModule = (IModule) moduleArg.value;
                    try {
                        invokeExpressMethod(_this, subModule, method, args);
                        MdaEnv.getContext().setErrorCode(NO_ERROR);
                    }catch (Exception e){
                        MdaEnv.getContext().setErrorCode(130);
                        log.warn("执行非交易下的module中的方法！！！");
                    }
                    return;
                }else{
                    MdaEnv.getContext().setErrorCode(130);
                }*/
            }
            log.debug("express cannot be executed:{}", rule);
        } catch (RuleExecuteException re) {
            throw re;
        } catch (Exception e) {
            //表达式执行失败
            //设置错误码
            log.error("表达式执行失败 : " + e.getMessage());
            MdaEnv.getContext().setErrorCode(999);
        }
    }

    private static List<Class<?>> getClasses(String packageName)
            throws ClassNotFoundException, IOException {
        // ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        // assert classLoader != null;
        // String path = packageName.replace('.', '/');
        // Enumeration<URL> resources = classLoader.getResources(path);
        // List<File> dirs = new ArrayList<File>();
        // while (resources.hasMoreElements()) {
        //     URL resource = resources.nextElement();
        //     dirs.add(new File(resource.getFile()));
        // }
        // ArrayList<Class> classes = new ArrayList<Class>();
        // for (File directory : dirs) {
        //     classes.addAll(findClasses(directory, packageName));
        // }
        // return classes.toArray(new Class[classes.size()]);

        ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
        Resource[] resolverResources = resourceResolver.getResources(utilPath.replace(".", Files.separator) + "/**/*");
        List<Class<?>> classes = new ArrayList<>();
        for (Resource resource : resolverResources) {
            String filename = resource.getFilename();
            if (filename.endsWith(".class")) {
                classes.add(Class.forName(packageName + '.' + filename.substring(0, filename.length() - 6)));
            }
        }
        return classes;
    }

    private static List<Class> findClasses(File directory, String packageName) throws ClassNotFoundException {
        List<Class> classes = new ArrayList<Class>();
        if (!directory.exists()) {
            return classes;
        }
        File[] files = directory.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                assert !file.getName().contains(".");
                classes.addAll(findClasses(file, packageName + "." + file.getName()));
            } else if (file.getName().endsWith(".class")) {
                classes.add(Class.forName(packageName + '.' + file.getName().substring(0, file.getName().length() - 6)));
            }
        }
        return classes;
    }
    //util目录下所有class的静态数组
//    public static Class[] utilClass = new Class[]{Archives.class, Caches.class, Databases.class, Datas.class, Dates.class,
//            Decimals.class, Files.class, Formats.class, Grids.class, Inis.class, MdaUtils.class, MdaUtilsEx.class, Modules.class,
//            Numerics.class, Reflects.class, Reports.class, Streams.class, Strings.class, Systems.class};


    private static void invokeExpressMethod(IModule _this, Object target, Method method, String[] args) throws InvocationTargetException, IllegalAccessException {
        List argsList = new ArrayList();
        collectArguments(_this, method, args, argsList);
        log.debug("invoke method:{} , args:{}", target, argsList);
        method.invoke(target, argsList.toArray());
        //将使用到Argument的参数调用之后，赋值到被引用字段，专门应对给拼接的表达式里面带PATH的情况
        for (Object arg : argsList) {
            if (arg instanceof Argument) {
                Argument argument = (Argument) arg;
                if (argument.attachField != null && argument.attachField instanceof IDatafield) {
                    ((IDatafield) argument.attachField).setValue(argument.value);
                }
            }
        }
    }

    public static Method findMethod(IModule _this, Class clazz, String methodName, String[] args) {
        Method[] methods = clazz.getMethods();
        int argsLength = args == null ? 0 : args.length;
        for (Method method : methods) {
            if (method.getParameters().length != argsLength)
                continue;
            if (!method.getName().equalsIgnoreCase(methodName))
                continue;
            //TODO 检验参数
            return method;
        }
        return null;
    }

    public static Method findCasecadeMethod(IModule root, IModule _this, Argument outModule, String methodName, String[] args) {
        String cacheKey = String.format("%s#%s#%s#%d", root == null ? "null" : root.getClass().getName(),
                _this == null ? "null" : _this.getClass().getName(), methodName, args == null ? 0 : args.length);
        if (casecadeMethodCache.containsKey(cacheKey)) {
            return casecadeMethodCache.get(cacheKey);
        }
        Queue<IModule> stack = new LinkedList<>();
        stack.add(root);
        Set<Class<?>> visited = new HashSet<>();
        while (!stack.isEmpty()) {
            IModule module = stack.poll();
            Class<?> clazz = module.getClass();
            Method method = findModuleMethod(_this, clazz, methodName, args);
            if (method != null) {
                outModule.value = module;
                casecadeMethodCache.put(cacheKey, method);
                return method;
            }
            Map<String, TDFieldInfo> tdModules = ModuleInfoManager.getTDModules(clazz);
            if (null != tdModules) {
                for (TDFieldInfo fieldInfo : tdModules.values()) {
                    if (visited.contains(fieldInfo.getFieldType())) //已经访问过
                        continue;

                    try {
                        Method m = fieldInfo.getGetter();
                        if (m != null) {
                            IModule subModule = (IModule) m.invoke(module);
                            stack.add(subModule);
                        }
                        visited.add(fieldInfo.getFieldType());
                    } catch (Exception e) {

                    }
                }
            }
        }
        return null;
    }

    private static boolean collectArguments(IModule _this, Method m, String[] arr, List<Object> argsList) {
        Parameter[] paramClazz = m.getParameters();
        if (arr == null) {
            return paramClazz.length == 0;
        }
        if (paramClazz.length != arr.length)
            return false;

        for (int i = 0; i < arr.length; i++) {
            String item = arr[i];
            item = item.trim();

            // MdaDriver.getBaseObject(null, "\\TRNMOD\\TRNDOC\\DOCEOT")

            //字符串
            if (paramClazz[i].getType().equals(Argument.class)) {
                Class clazz = (Class) ((ParameterizedType) paramClazz[i].getParameterizedType()).getActualTypeArguments()[0];
                Argument argument = new Argument("", null);
                Object val = wrapperValue(_this, item, clazz, argument);
                argument.value = val;
                argsList.add(argument);
            } else {
                Object val = wrapperValue(_this, item, paramClazz[i].getType(), null);

                argsList.add(val);
            }
        }
        return true;
    }

    private static Object wrapperValue(IModule _this, String val, Class basicClass, Argument argument) {
        if ((val.charAt(0) == '"' && val.charAt(val.length() - 1) == '"')
                || (val.charAt(1) == '\'' && val.charAt(val.length() - 1) == '\''))
            return val.substring(1, val.length() - 1);
        if (basicClass.equals(BigDecimal.class))
            return new BigDecimal(val);
        if (basicClass.equals(int.class) || basicClass.equals(Integer.class))
            return Integer.parseInt(val);
        //path 路径，尝试从上下文中获取
        String path = val.toLowerCase();
        if (path.startsWith("\\")) {
            IBaseObject baseObject = MdaDriver.getBaseObject(null, path);
            if (argument != null) {
                argument.attachField = baseObject;
            }
            if (baseObject instanceof IDatafield)
                return ((IDatafield) baseObject).getValue();
            else
                return baseObject;
        } else {
            //相对路径
            IBaseObject baseObject = MdaDriver.getBaseObject(_this, path);
            if (argument != null) {
                argument.attachField = baseObject;
            } else {
                //有可能是个空参数函数
                Method method = searchStaticFunction(_this, val, null);
                if (method != null) {
                    try {
                        Object methodResult = method.invoke(null);
                        return methodResult;
                    } catch (Exception e) {
                        log.warn(e.getMessage());
                        return null;
                    }
                }
            }
            if (baseObject instanceof IDatafield)
                return ((IDatafield) baseObject).getValue();
            else
                return baseObject;
        }
    }

    private static Method searchStaticFunction(IModule _this, String methodName, String[] args) {
        int argLength = 0;
        if (args != null) {
            argLength = args.length;
        }
        String key = methodName.toLowerCase() + "#" + argLength;
        //先从缓存中查找
        Method method = staticPostMethodCache.get(key);
        if (method != null) {
            if (method == EMPTY_METHOD) {
                //有缓存，但是EMPTY_METHOD代表找过为空
                return null;
            }
            return method;
        }
        for (Class<?> aClass : staticPostArea) {
            method = findMethod(_this, aClass, methodName, args);
            if (method != null) {
                staticPostMethodCache.put(key, method);
                return method;
            }
        }
        //未找到，置为EMPTY_METHOD，避免频繁查找
        staticPostMethodCache.put(key, EMPTY_METHOD);
        return null;
    }

    public static Method findModuleMethod(IModule _this, Class clazz, String methodName, String[] args) {
        int argsLength = args == null ? 0 : args.length;
        Map<String, Method> methodMap = ModuleInfoManager.getAllPublicLowerCaseMethods(clazz);
        if (null != methodMap) {
            return methodMap.getOrDefault(methodName.toLowerCase() + "#" + argsLength, null);
        } else {
            return findMethod(_this, clazz, methodName, args);
        }
    }

    public static void postDefaultRule(IModule module, String field, boolean isModuleListItem) {
        if (!isModuleListItem) {
            postDefaultRule(module, field);
            return;
        }
        AbstractScopeEmitter scopeEmitter = DCR.getModuleListEmitter(module.getClass());
        scopeEmitter.acceptContext(module);
        if (scopeEmitter == null) {
            return;
        }
        scopeEmitter.executeDefault(field);
    }

    public static void invokeExpress(IModule _this, String path) {
        invokeExpress(_this, path, -1);
    }

    public static boolean exists(Object object, String methodName, Object... args) {
        return exists(object, methodName, null, args);
    }

    public static boolean exists(Object object, String methodName, Class[] classes, Object[] args) {
        if (classes == null) {
            classes = new Class[args.length];
            for (int i = 0; i < args.length; i++)
                classes[i] = args[i].getClass();
        }
        try {
            if ((object instanceof String))
                return MethodUtils.getAccessibleMethod(Class.forName((String) object), methodName, classes) != null;
            return MethodUtils.getAccessibleMethod(object.getClass(), methodName, classes) != null;
        } catch (Exception e) {
        }
        return false;
    }

    public static int Row(IModule _this) {
        while (!(_this.getParent() instanceof ModuleList) && _this.getParent() != null) {
            _this = _this.getParent();
        }
        ModuleList parent = (ModuleList) _this.getParent();
        return parent.indexOf(_this) + 1;
    }
}
