/*
 * Copyright (c) 2020. ForteScarlet All rights reserved.
 * Project  simple-robot-core
 * File     AnnotationUtils.java
 *
 * You can contact the author through the following channels:
 * github https://github.com/ForteScarlet
 * gitee  https://gitee.com/ForteScarlet
 * email  [email protected]
 * QQ     1149159218
 *
 */

package com.forte.qqrobot.utils;

import com.forte.lang.Language;
import com.forte.qqrobot.anno.ByNameFrom;
import com.forte.qqrobot.anno.ByNameType;
import com.forte.qqrobot.anno.Constr;
import com.forte.qqrobot.anno.Listen;
import com.forte.qqrobot.anno.depend.Beans;
import com.forte.qqrobot.anno.depend.Depend;
import com.forte.qqrobot.exception.AnnotationException;
import com.forte.utils.reflect.ProxyUtils;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;

/**
 * 对于一些注解的获取等相关的工具类
 *
 * @author ForteScarlet <[163邮箱地址][email protected]>
 * @since JDK1.8
 **/
public class AnnotationUtils {

    /**
     * java原生注解所在包路径
     */
    private static final Package JAVA_ANNOTATION_PACKAGE = Target.class.getPackage();


    /**
     * 注解缓存,记录曾经保存过的注解与其所在类
     */
    private static final Map<AnnotatedElement, Set<Annotation>> ANNOTATION_CACHE = new ConcurrentHashMap<>();

    /**
     * 记录已经证实不存在的注解信息
     */
    private static final Map<AnnotatedElement, Set<Class<Annotation>>> NULL_CACHE = new ConcurrentHashMap<>();

    /**
     * 此工具类中使用到的异常的语言前缀
     */
    private static final String LANG_EX_TAG_HEAD = "exception.annotation";

    /**
     * 获取语言结果字符串
     * @param tag       tag
     * @param format    格式化参数
     */
    private static String getLang(String tag, Object... format){
        return Language.format(LANG_EX_TAG_HEAD, tag, format);
    }




    /**
     * 获取Depend注解。
     * 如果获取不到Depend, 则尝试获取{@link javax.annotation.Resource}
     * @param from
     * @return
     */
    public static Depend getDepend(AnnotatedElement from){
//        ProxyUtils.annotationProxyByDefault()
        // 先获取depend
        final Depend depend = getAnnotation(from, Depend.class);
        if(depend != null){
            return depend;
        }else{
            try {
                final Resource resource = getAnnotation(from, Resource.class);
                if(resource == null){
                    return null;
                }else{
                    final String name = resource.name();
                    final Class<?> type = resource.type();

                    Map<String, BiFunction<Method, Object[], Object>> proxyMap = new HashMap<>();
                    proxyMap.put("value", (m, o) -> name);
                    proxyMap.put("type", (m, o) -> type);
                    proxyMap.put("equals", (m, o) -> {
                       final Object other = o[0];
                       if(other instanceof Annotation){
                           if(((Annotation) other).annotationType() != Depend.class){
                               return false;
                           }else{
                               return resource.hashCode() == other.hashCode();
                           }
                       }else{
                           return false;
                       }
                    });
                    proxyMap.put("toString", (m, o) -> "@Depend->" + resource.toString() + "("+ resource.hashCode() +")");
                    proxyMap.put("hashCode", (m, o) -> resource.hashCode());
                    proxyMap.put("annotationType", (m, o) -> Depend.class);
                    final Depend proxyDepend = ProxyUtils.annotationProxyByDefault(Depend.class, proxyMap);
                    // 计入缓存
                    boolean b = saveCache(from, proxyDepend);
                    return proxyDepend;
                }
            }catch (Throwable e){
                e.printStackTrace();
                return null;
            }
        }
    }


    /**
     * 尝试从一个类对象中获取到@Beans注解
     */
    public static Beans getBeansAnnotationIfListen(Class<?> from) {
        Beans fromClass = getAnnotation(from, Beans.class);
        if (fromClass != null) {
            return fromClass;
        }


        //类上没有,查询所有方法是否存在@Listen注解
        //因为@Listen上的@Beans注解都是一样的
        for (Method method : from.getMethods()) {
            Listen listenAnnotation = getAnnotation(method, Listen.class);
            if (listenAnnotation != null) {
                //Listen注解必定存在@Beans注解
                return listenAnnotation.annotationType().getAnnotation(Beans.class);
            }
        }

        //如果还是没有,返回null
        return null;
    }

    /**
     * 从某个类上获取注解对象,注解可以深度递归
     * 如果存在多个继承注解,则优先获取浅层第一个注解,如果浅层不存在,则返回第一个获取到的注解
     * 请尽可能保证仅存在一个或者一种继承注解,否则获取到的类型将不可控
     *
     * @param from           获取注解的某个类
     * @param annotationType 想要获取的注解类型
     * @return 获取到的第一个注解对象
     */
    public static <T extends Annotation> T getAnnotation(AnnotatedElement from, Class<T> annotationType) {
        return getAnnotation(from, annotationType, (Class<T>[]) new Class[]{});
    }

    /**
     * 从某个类上获取注解对象,注解可以深度递归
     * 如果存在多个继承注解,则优先获取浅层第一个注解,如果浅层不存在,则返回第一个获取到的注解
     * 请尽可能保证仅存在一个或者一种继承注解,否则获取到的类型将不可控
     *
     * @param from           获取注解的某个类
     * @param annotationType 想要获取的注解类型
     * @param ignored        获取注解列表的时候的忽略列表
     * @return 获取到的第一个注解对象
     */
    public static <T extends Annotation> T getAnnotation(AnnotatedElement from, Class<T> annotationType, Class<T>... ignored) {
        // 首先尝试获取缓存
        T cache = getCache(from, annotationType);
        if(cache != null){
            return cache;
        }

        if(isNull(from, annotationType)){
            return null;
        }


        //先尝试直接获取
        T annotation = from.getAnnotation(annotationType);

        //如果存在直接返回,否则查询
        if (annotation != null) {
            saveCache(from, annotation);
            return annotation;
        }

        // 获取target注解
        Target target = annotationType.getAnnotation(Target.class);
        // 判断这个注解能否标注在其他注解上,如果不能,则不再深入获取
        boolean annotationable = false;
        if (target != null) {
            for (ElementType elType : target.value()) {
                if (elType == ElementType.TYPE || elType == ElementType.ANNOTATION_TYPE) {
                    annotationable = true;
                    break;
                }
            }
        }

        Annotation[] annotations = from.getAnnotations();
        annotation = annotationable ? getAnnotationFromArrays(annotations, annotationType, ignored) : null;


        // 如果还是获取不到,看看查询的注解类型有没有对应的ByNameType
        if (annotation == null) {
            annotation = getByNameAnnotation(from, annotationType);
        }

        // 如果无法通过注解本身所指向的byName注解获取,看看有没有反向指向此类型的注解
        // 此情况下不进行深层获取
        if(annotation == null){
            annotation = getAnnotationFromByNames(annotations, annotationType);
        }

        // 如果最终不是null,计入缓存
        if(annotation != null){
            saveCache(from, annotation);
        }else{
            nullCache(from, annotationType);
        }

        return annotation;
    }



    /**
     * 逆向查询,把ByName转化为正常的注解。
     * @param annotations    获取源头上拿到的注解列表,例如类上、字段上等等。
     * @param annotationType 想要获取的注解类型。
     */
    private static <T extends Annotation> T getAnnotationFromByNames(Annotation[] annotations, Class<T> annotationType){
        // 获取所有的注解
        return Arrays.stream(annotations).map(a -> {
            // 这个注解a存在@ByNameFrom
            ByNameFrom byNameFrom = a.annotationType().getAnnotation(ByNameFrom.class);
            if(byNameFrom == null){
                return null;
            }else{
                return new AbstractMap.SimpleEntry<>(a, byNameFrom);
            }
        })
                .filter(Objects::nonNull)
                .filter(a -> a.getValue().value().equals(annotationType))
                .map(a -> AnnotationByNameUtils.byName(a.getKey(), annotationType))
                .findFirst().orElse(null);
    }


    /**
     * 通过参数对象获取,且是通过byName注解获取
     * @param from              来源
     * @param annotationType    父注解类型
     */
    private static <T extends Annotation> T getByNameAnnotation(AnnotatedElement from, Class<T> annotationType){
        // 如果还是获取不到,看看查询的注解类型有没有对应的ByNameType
        ByNameType byNameType = annotationType.getAnnotation(ByNameType.class);
        if (byNameType != null) {
            // 存在byNameType,看看是啥
            Class<? extends Annotation> byNameAnnotationType = byNameType.value();
            // 尝试通过这个ByName获取真正的对应注解
            // 获取ByName注解的时候不再使用深层获取,而是直接获取
            Annotation byNameOnFrom = from.getAnnotation(byNameAnnotationType);
            return AnnotationByNameUtils.byName(byNameOnFrom, annotationType);
        } else {
            return null;
        }
    }

    /**
     * @param array
     * @param annotationType
     * @param <T>
     * @return
     */
    private static <T extends Annotation> T getAnnotationFromArrays(Annotation[] array, Class<T> annotationType, Class<T>... ignored) {
        //先浅查询第一层
        //全部注解
        Annotation[] annotations = Arrays.stream(array)
                .filter(a -> {
                    for (Class<? extends Annotation> atype : ignored) {
                        if (a.annotationType().equals(atype)) {
                            return false;
                        }
                    }
                    return true;
                })
                .filter(a -> {
                    if (a == null) {
                        return false;
                    }
                    //如果此注解的类型就是我要的,直接放过
                    if (a.annotationType().equals(annotationType)) {
                        return true;
                    }
                    //否则,过滤掉java原生注解对象
                    //通过包路径判断
                    if (JAVA_ANNOTATION_PACKAGE.equals(a.annotationType().getPackage())) {
                        return false;
                    }
                    return true;
                }).toArray(Annotation[]::new);


        if (annotations.length == 0) {
            return null;
        }

        Class<? extends Annotation>[] annotationTypes = new Class[annotations.length];
        for (int i = 0; i < annotations.length; i++) {
            annotationTypes[i] = annotations[i].annotationType();
        }

        Class<T>[] newIgnored = new Class[annotationTypes.length + ignored.length];
        System.arraycopy(ignored, 0, newIgnored, 0, ignored.length);
        System.arraycopy(annotationTypes, 0, newIgnored, ignored.length, annotationTypes.length);

        //遍历
        for (Annotation a : annotations) {
            T annotationGet = a.annotationType().getAnnotation(annotationType);
            if (annotationGet != null) {
                return annotationGet;
            }
        }

        //如果浅层查询还是没有,递归查询
        //再次遍历
        for (Annotation a : annotations) {
            T annotationGet = getAnnotation(a.annotationType(), annotationType, newIgnored);
            if (annotationGet != null) {
                return annotationGet;
            }
        }

        //如果还是没有找到,返回null
        return null;
    }

    /**
     * 获取类中标注了@Constr注解的方法。
     * 如果有多个,获取其中某一个;
     * 如果出现了:
     * - 注解不存在静态方法上、
     * - 方法返回值不是这个类本身或者子类
     * 则会抛出异常
     *
     * @param clz class对象
     * @return 可能存在@Constr注解的静态方法
     * @throws AnnotationException 如果不是静态方法、没有返回值、返回值不是这个类型或者这个类型的字类类型却使用了@Constr注解
     *                             便会抛出此异常
     *                             see lang:
     *                             <ul>
     *                              <li>exception.annotation.notStatic</li>
     *                              <li>exception.annotation.needReturn</li>
     *                              <li>exception.annotation.returnTypeWrong</li>
     *                             </ul>
     */
    public static Method getConstrMethod(Class clz) {
        return Arrays.stream(clz.getDeclaredMethods()).filter(m -> {
            Constr constr = getAnnotation(m, Constr.class);
            if (constr != null) {
                if (!Modifier.isStatic(m.getModifiers())) {
                    throw new AnnotationException(clz, m, Constr.class, getLang("notStatic"));
                }

                if (m.getReturnType().equals(void.class)) {
                    throw new AnnotationException(clz, m, Constr.class, getLang("needReturn"));
                }

                if (!FieldUtils.isChild(m.getReturnType(), clz)) {
                    throw new AnnotationException(clz, m, Constr.class, getLang("returnTypeWrong"));
                }

                return true;
            } else {
                return false;
            }
        }).findAny().orElse(null);
    }

    /**
     * 从缓存中获取缓存注解
     * @param from          来源
     * @param annotatedType 注解类型
     * @return  注解缓存,可能为null
     */
    private static <T extends Annotation> T getCache(AnnotatedElement from, Class<T> annotatedType){
        Set<Annotation> list = ANNOTATION_CACHE.get(from);
        if(list != null){
            // 寻找
            for (Annotation a : list) {
                if(a.annotationType().equals(annotatedType)){
                    return (T) a;
                }
            }
        }
        // 找不到,返回null
        return null;
    }

    /**
     * 记录一个得不到的缓存
     * @param from {@link AnnotatedElement}
     * @param annotatedType annotation class
     */
    private static <T extends Annotation> void nullCache(AnnotatedElement from, Class<T> annotatedType){
        final Set<Class<Annotation>> classes = NULL_CACHE.computeIfAbsent(from, k -> new HashSet<>());
        classes.add((Class<Annotation>) annotatedType);
    }

    /**
     * 判断是否获取不到
     * @param from {@link AnnotatedElement}
     * @param annotatedType annotation class
     */
    private static <T extends Annotation> boolean isNull(AnnotatedElement from, Class<T> annotatedType){
        final Set<Class<Annotation>> classes = NULL_CACHE.get(from);
        if(classes == null || classes.isEmpty()){
            return false;
        }
        return classes.contains(annotatedType);
    }



    /**
     * 记录一条缓存记录。
     */
    private static boolean saveCache(AnnotatedElement from, Annotation annotation){
        Set<Annotation> set;
        synchronized (ANNOTATION_CACHE) {
            set = ANNOTATION_CACHE.computeIfAbsent(from, k -> new HashSet<>());
            // 如果为空,新建一个并保存
        }
        // 记录这个注解
        final boolean add = set.add(annotation);
        return add;
    }

    /**
     * 清除缓存
     */
    public static void cleanCache(){
        ANNOTATION_CACHE.clear();
    }

}