package com.elab.spring.componts;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.util.ReflectionUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * bean的依赖分析
 *
 * @author ： liukx
 * @time ： 2020/9/18 - 11:38
 */
public class BeanAnalyse implements ApplicationContextAware {

    private static Logger logger = LoggerFactory.getLogger(BeanAnalyse.class);
    private ApplicationContext applicationContext;

    private List<Class<? extends Annotation>> annoList;

    private Map<Class, Class> classConvertMap = new HashMap<>();

    private String[] excludePackages;

    private Set<Class> clazzFieldList = new HashSet<>();

    private Set<Class> basicClazzList = new HashSet<>();

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public BeanAnalyse(List<Class<? extends Annotation>> annotationList) {
        this.annoList = annotationList;
        basicClazzList.add(String.class);
        basicClazzList.add(Integer.class);
        basicClazzList.add(Long.class);
        basicClazzList.add(Double.class);
        basicClazzList.add(Boolean.class);
    }

    public void setExcludePackages(String[] excludePackages) {
        this.excludePackages = excludePackages;
    }

    private void addData(Map<Class, Map<Class, Class>> classMap, Class classStr, Class fieldClass,
        Class fieldClassImpl) {

        Class convertClass = getClassConvertMap().get(fieldClassImpl);

        if (convertClass != null) {
            fieldClassImpl = convertClass;
        }

        Map<Class, Class> classClassMap = classMap.get(classStr);
        if (classClassMap == null) {
            classClassMap = new LinkedHashMap<>();
            classMap.put(classStr, classClassMap);
        }
        classClassMap.put(fieldClass, fieldClassImpl);
    }

    private boolean isBasicType(Class clazz) {
        return String.class.equals(clazz);
    }

    /**
     * 是否排除
     *
     * @param clazz
     * @param dependOnModel
     * @return
     */
    private boolean isExclude(Class<?> clazz, BeanDependOnModel dependOnModel) {
        Map<String, Set<Class>> excludeClassMap = dependOnModel.getExcludeClassMap();
        if (this.excludePackages != null) {
            for (int i = 0; i < this.excludePackages.length; i++) {
                String name = clazz.getName();
                String excludePackage = this.excludePackages[i];
                if (name.startsWith(excludePackage)) {
                    Set<Class> clazzList = excludeClassMap.get(excludePackage);
                    if (clazzList == null) {
                        clazzList = new HashSet<>();
                        excludeClassMap.put(excludePackage, clazzList);
                    }
                    clazzList.add(clazz);
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * 获取bean的依赖
     *
     * @param clazz 类名
     * @return
     */
    public BeanDependOnModel getBeanDependOn(Class clazz) {
        BeanDependOnModel dependOnModel = new BeanDependOnModel();
        dependOnModel.setClazz(clazz);
        buildAutowiringMetadata(clazz, dependOnModel);
        return dependOnModel;
    }

    private void buildAutowiringMetadata(Class clazz, BeanDependOnModel dependOnModel) {
        Class<?> targetClass = clazz;

        final LinkedList<InjectionMetadata.InjectedElement> currElements =
            new LinkedList<InjectionMetadata.InjectedElement>();

        ReflectionUtils.doWithLocalFields(targetClass, new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                AnnotationAttributes ann = findAutowiredAnnotation(field, annoList);
                if (ann != null) {
                    // 如果是静态方法
                    if (Modifier.isStatic(field.getModifiers())) {
                        if (logger.isWarnEnabled()) {
                            logger.warn("Autowired annotation is not supported on static fields: " + field);
                        }
                        return;
                    }

                    Map<Class, Map<Class, Class>> fieldMapList = dependOnModel.getFieldMapList();
                    // 类的名称
                    Class<?> clazzName = field.getDeclaringClass();
                    // 类的字段类型
                    Class<?> fieldClazz = field.getType();
                    if (fieldMapList.get(clazzName) == null || !clazzFieldList.contains(fieldClazz)) {
                        // 是否属于需要排除的类
                        if (!isExclude(fieldClazz, dependOnModel)) {
                            // 判断是否是Value,Qualifier
                            if (isBasicType(fieldClazz)) {
                                Map<Class, Set<String>> annotationValueList = dependOnModel.getAnnotationValueList();
                                if (field.getAnnotation(Qualifier.class) != null) {
                                    addAnnoList(annotationValueList, Qualifier.class,
                                        field.getAnnotation(Qualifier.class).value());
                                } else if (field.getAnnotation(Value.class) != null) {
                                    addAnnoList(annotationValueList, Value.class,
                                        field.getAnnotation(Value.class).value());
                                }
                            } else {
                                clazzFieldList.add(fieldClazz);
                                try {
                                    Class<?> aopClazz = getBeanClass(fieldClazz);
                                    if (aopClazz != null) {
                                        if (!isExclude(aopClazz, dependOnModel)) {
                                            System.out.println(clazzName + "\t" + fieldClazz + "\t" + aopClazz);
                                            clazzFieldList.add(aopClazz);
                                            buildAutowiringMetadata(aopClazz, dependOnModel);
                                        }
                                        addData(fieldMapList, clazzName, fieldClazz, aopClazz);
                                    }
                                } catch (Exception e) {
                                    System.out
                                        .println("原因: " + e.getMessage() + "\t" + clazzName + " -> " + fieldClazz);
                                }
                            }
                        } else {
                            addData(fieldMapList, clazzName, fieldClazz, fieldClazz);
                        }
                    }
                }
            }

            private void addAnnoList(Map<Class, Set<String>> annotationValueList, Class anno, String value) {
                Set<String> annoList = annotationValueList.get(anno);
                if (annoList == null) {
                    annoList = new HashSet<>();
                    annotationValueList.put(anno, annoList);
                }
                annoList.add(value);
            }
        });
    }

    private Class<?> getBeanClass(Class<?> fieldClazz) {
        Object bean = applicationContext.getBean(fieldClazz);

        Class<?> aopClazz = null;
        if (AopUtils.isAopProxy(bean)) {
            aopClazz = AopUtils.getTargetClass(bean);
        } else {
            aopClazz = bean.getClass();
        }
        return aopClazz;
    }

    public void setClassConvertMap(Map<Class, Class> classConvertMap) {
        this.classConvertMap = classConvertMap;
    }

    public Map<Class, Class> getClassConvertMap() {
        return classConvertMap;
    }

    private static AnnotationAttributes findAutowiredAnnotation(AccessibleObject ao,
        List<Class<? extends Annotation>> autowiredAnnotationTypes) {
        if (ao.getAnnotations().length > 0) {
            for (Class<? extends Annotation> type : autowiredAnnotationTypes) {
                AnnotationAttributes attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(ao, type);
                if (attributes != null) {
                    return attributes;
                }
            }
        }
        return null;
    }

    public void printClassLog(Class clazz) {
        BeanDependOnModel beanDependOn = this.getBeanDependOn(clazz);
        Set<Class> dependOnClassList = beanDependOn.getDependOnClassList(excludePackages);
        System.out.println("===================================");
        System.out.println(clazz.getSimpleName() + ".class");
        for (Class aClass : dependOnClassList) {
            System.out.println(", " + aClass.getSimpleName() + ".class ");
        }
    }
}
