package com.lcw.util;

import com.lcw.model.ParameterModel;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import org.apache.commons.lang.time.DateUtils;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author lancw
 */
public class ReflectUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ReflectUtil.class.getName());
    private static final HashMap<String, List<MethodNode>> CACHE = new HashMap<>();

    /**
     * 获取方法参数名称
     *
     * @param theMethod
     * @return
     * @throws Exception
     */
    public static List<ParameterModel> getParameterNames(Method theMethod) throws Exception {
        Class<?> declaringClass = theMethod.getDeclaringClass();
        String key = declaringClass.getName();
        String constructorDescriptor = Type.getMethodDescriptor(theMethod);
        if (CACHE.get(key) == null) {
            ClassLoader declaringClassLoader = declaringClass.getClassLoader();

            Type declaringType = Type.getType(declaringClass);
            String url = declaringType.getInternalName() + ".class";

            InputStream classFileInputStream = declaringClassLoader.getResourceAsStream(url);
            if (classFileInputStream == null) {
                throw new IllegalArgumentException("The constructor's class loader cannot find the bytecode that defined the constructor's class (URL: " + url + ")");
            }

            ClassNode classNode;
            try {
                classNode = new ClassNode();
                ClassReader classReader = new ClassReader(classFileInputStream);
                classReader.accept(classNode, 0);
            } finally {
                classFileInputStream.close();
            }
            CACHE.put(key, classNode.methods);
        }

        List<MethodNode> methods = CACHE.get(key);
        for (MethodNode method : methods) {
            if (method.name.equals(theMethod.getName()) && method.desc.equals(constructorDescriptor)) {
                Type[] argumentTypes = Type.getArgumentTypes(method.desc);
                List<ParameterModel> parameterNames = new ArrayList<>(argumentTypes.length);

                List<LocalVariableNode> localVariables = method.localVariables;
                if (localVariables == null) {
                    for (int i = 0; i < argumentTypes.length; i++) {
                        Type at = argumentTypes[i];
                        String name = theMethod.getParameters()[i].getName();
                        parameterNames.add(new ParameterModel(name, at.toString(), theMethod, i));
                    }
                } else {
                    Collections.sort(localVariables, (LocalVariableNode o1, LocalVariableNode o2) -> Integer.valueOf(o1.index).compareTo(o2.index));
                    for (int i = 1; i <= argumentTypes.length; i++) {
                        LocalVariableNode lvn = localVariables.get(i);
                        parameterNames.add(new ParameterModel(lvn.name, lvn.desc, theMethod, i - 1));
                    }
                }
                return parameterNames;
            }
        }
        return null;
    }

    /**
     * 从jar包中查找指定接口的一个实现类 非反射
     *
     * @param interfaceClass
     * @param jarPath
     * @return
     * @throws Exception
     */
    public static Class findImplementFromJar(Class interfaceClass, URL jarPath) throws Exception {
        URL url = new URL("jar:" + jarPath.toString() + "!/");
        JarURLConnection jarConnection = (JarURLConnection) url.openConnection();
        JarFile jarFile = jarConnection.getJarFile();
        Enumeration<JarEntry> je = jarFile.entries();
        boolean flag = false;
        while (je.hasMoreElements()) {
            JarEntry e = je.nextElement();
            String n = e.getName();
            FileTime ft = e.getLastModifiedTime();
            if (DateUtils.addDays(new Date(), -100).getTime() - ft.toMillis() > 0) {
                LOGGER.info("jar文件时间超过100天,跳过查找实现类:" + jarFile.getName().substring(jarFile.getName().lastIndexOf("\\")) + ft.toString());
                return null;
            } else {
                if (!flag) {
                    flag = true;
                    LOGGER.info("在" + jarFile.getName().substring(jarFile.getName().lastIndexOf("\\")) + "中查找实现类");
                }
            }
            if (n.endsWith(".class")) {
                n = n.substring(0, n.length() - 6);
                n = n.replace('/', '.');
                Class currentClass = ClassLoader.getSystemClassLoader().loadClass(n);
                if (interfaceClass.isAssignableFrom(currentClass) && !n.equals(interfaceClass.getName())) {
                    return currentClass;
                }
            }
        }
        return null;
    }

    /**
     * 添加classPath
     *
     * @param url
     * @throws Exception
     */
    public static void addURL(URL url) throws Exception {
        Class[] parameters = new Class[]{URL.class};
        URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        Class sysclass = URLClassLoader.class;
        try {
            Method method = sysclass.getDeclaredMethod("addURL", parameters);
            method.setAccessible(true);
            method.invoke(sysloader, new Object[]{url});
        } catch (Throwable t) {
            LOGGER.error("Error, could not add URL to system classloader", t);
        }
    }

    /**
     * 获取泛型类型
     *
     * @param t
     * @param index
     * @return
     */
    public static Class getParameterizedTypes(java.lang.reflect.Type t, int index) {
        if (t instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) t;
            Class genericClazz = (Class) pt.getActualTypeArguments()[index];
            return genericClazz;
        }
        return null;
    }
    private static final String[] NOT_ANALYSIS_CLASS = new String[]{"java.lang", "java.math", "java.sql.Date", "java.util.Date"};

    /**
     * 检查是否不需要解析的class类型
     *
     * @param c
     * @return
     */
    public static boolean isNotAnalysisClass(String c) {
        for (String s : NOT_ANALYSIS_CLASS) {
            if (c.contains(s)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 解析class的所有字段转为伪json
     *
     * @param c    要转换的class
     * @param deep 深度
     * @return
     */
    public static String classToJson(Class c, int deep) {
        StringBuilder sb = new StringBuilder();
        Field[] fs = c.getDeclaredFields();
        if (deep > 5 || isNotAnalysisClass(c.getName())) {
            return "";
        }
        sb.append("{");
        for (int i = 0; i < fs.length; i++) {
            Field f = fs[i];
            Class t = f.getType();
            if (t.isPrimitive() || t.isEnum() || isNotAnalysisClass(t.getTypeName())) {
                sb.append(f.getName()).append(":''");
            } else if (t.isArray()) {
                sb.append(f.getName()).append(":[]");
            } else if (t.isInterface()) {
                if (t.isAssignableFrom(List.class) || t.isAssignableFrom(Set.class)) {
                    Class gc = ReflectUtil.getParameterizedTypes(f.getGenericType(), 0);
                    if (deep < 5) {
                        sb.append(f.getName()).append(":[").append(classToJson(gc, deep + 1)).append("]");
                    } else {
                        sb.append(f.getName()).append(":[").append(gc).append("]");
                    }
                } else if (t.isAssignableFrom(Map.class)) {
                    Class gc = ReflectUtil.getParameterizedTypes(f.getGenericType(), 1);
                    if (deep < 3) {
                        sb.append(f.getName()).append(":{key:").append(classToJson(gc, deep + 1)).append("}");
                    } else {
                        sb.append(f.getName()).append(":{key:").append(gc).append("}");
                    }
                } else {
                    sb.append(f.getName()).append(":").append(classToJson(t, deep + 1));
                }
            } else {
                sb.append(f.getName()).append(":").append(classToJson(t, deep + 1));
            }
            if (i != fs.length - 1) {
                sb.append(",");
            }
        }
        sb.append("}");
        return sb.toString();
    }
}