package com.trade.common.utils;

import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import cn.hutool.core.util.StrUtil;
import com.trade.common.exception.ServiceException;
import org.apache.commons.collections4.MapUtils;
import org.apache.ibatis.reflection.Reflector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;

/**
 * 反射工具类
 */
public class ReflectionUtil {

    private static final Logger log = LoggerFactory.getLogger(ReflectionUtil.class);
    private static final String TYPE_NAME_PREFIX = "class ";

    public enum MethodPrefixEnum {

        GET("get", "Get method prefix"), SET("set", "Set method prefix");

        MethodPrefixEnum(String prefix, String desc){
            this.prefix = prefix;
            this.desc = desc;
        }

        private String prefix;
        private String desc;

        public void setPrefix(String prefix, String desc) {
            this.prefix = prefix;
            this.desc = desc;
        }

        public String getPrefix() {
            return prefix;
        }

        public String getDesc() {
            return desc;
        }
    }

    /**
     * 反射 method 方法名，例如 getId
     *
     * @param field field
     * @param methodPrefixEnum get/set
     * @param str 属性字符串内容
     * @return
     */
    public static String getMethodCapitalize(Field field, MethodPrefixEnum methodPrefixEnum, String str) {
        Class<?> fieldType = field.getType();
        // 处理boolean.class||Boolean.class
        boolean flag = boolean.class.isAssignableFrom(fieldType) || Boolean.class.isAssignableFrom(fieldType);
        if (flag && str.startsWith("is")) {
            String propertyName = str.replaceFirst("is", "");
            if (StringUtils.isEmpty(propertyName)) {
                str = propertyName;
            } else {
                String beforeChar = propertyName.substring(0, 1).toLowerCase();
                String afterChar = propertyName.substring(1);
                String firstCharToLowerStr = beforeChar + afterChar;
                str = propertyName.equals(firstCharToLowerStr) ? propertyName : firstCharToLowerStr;
            }
        }
        switch (methodPrefixEnum) {
            case GET:
                return StringUtils.concatCapitalize(boolean.class.equals(fieldType) ? "is" : "get", str);
            case SET:
                return StringUtils.concatCapitalize("set", str);
            default:
                throw new ServiceException("Only support reflect get/set method!");
        }
    }

    /**
     * 获取 public get方法的值(cls 与 entity 并不一定完全关联)
     *
     * @param cls
     * @param entity 实体
     * @param str 属性字符串内容
     * @return Object
     */
    public static Object getMethodValue(Class<?> cls, Object entity, String str) {
        Map<String, Field> fieldMaps = getFieldMap(cls);
        try {
            if (MapUtils.isEmpty(fieldMaps)) {
                throw new ServiceException(
                        String.format("Error: NoSuchField in %s for %s.  Cause:", cls.getSimpleName(), str));
            }
            Method method = cls.getMethod(getMethodCapitalize(fieldMaps.get(str), MethodPrefixEnum.GET, str));
            return method.invoke(entity);
        } catch (NoSuchMethodException e) {
            throw new ServiceException(String.format("Error: NoSuchMethod in %s.  Cause:", cls.getSimpleName()) + e);
        } catch (IllegalAccessException e) {
            throw new ServiceException(
                    String.format("Error: Cannot execute a private method. in %s.  Cause:", cls.getSimpleName()) + e);
        } catch (InvocationTargetException e) {
            throw new ServiceException("Error: InvocationTargetException on getMethodValue.  Cause:" + e);
        }
    }

    /**
     * 获取 public get方法的值
     *
     * @param entity 实体
     * @param str 属性字符串内容
     * @return Object
     */
    public static Object getMethodValue(Object entity, String str) {
        if (null == entity) {
            return null;
        }
        return getMethodValue(entity.getClass(), entity, str);
    }

    /**
     * 获取该类的所有属性列表
     *
     * @param clazz 反射类
     * @return
     */
    public static Map<String, Field> getFieldMap(Class<?> clazz) {
        List<Field> fieldList = getFieldList(clazz);
        Map<String, Field> fieldMap = Collections.emptyMap();
        if (CollectionUtils.isNotEmpty(fieldList)) {
            fieldMap = new LinkedHashMap<>();
            for (Field field : fieldList) {
                fieldMap.put(field.getName(), field);
            }
        }
        return fieldMap;
    }

    /**
     * 获取该类的所有属性列表
     *
     * @param clazz 反射类
     * @return
     */
    public static List<Field> getFieldList(Class<?> clazz) {
        if (null == clazz) {
            return null;
        }
        List<Field> fieldList = new LinkedList<>();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            /* 过滤静态属性 */
            if (Modifier.isStatic(field.getModifiers())) {
                continue;
            }
            /* 过滤 transient关键字修饰的属性 */
            if (Modifier.isTransient(field.getModifiers())) {
                continue;
            }
            fieldList.add(field);
        }
        /* 处理父类字段 */
        Class<?> superClass = clazz.getSuperclass();
        if (superClass.equals(Object.class)) {
            return fieldList;
        }
        /* 排除重载属性 */
        return excludeOverrideSuperField(fieldList, getFieldList(superClass));
    }

    /**
     * 排序重置父类属性
     *
     * @param fieldList 子类属性
     * @param superFieldList 父类属性
     */
    public static List<Field> excludeOverrideSuperField(List<Field> fieldList, List<Field> superFieldList) {
        // 子类属性
        Map<String, Field> fieldMap = new HashMap<>();
        for (Field field : fieldList) {
            fieldMap.put(field.getName(), field);
        }
        for (Field superField : superFieldList) {
            if (null == fieldMap.get(superField.getName())) {
                // 加入重置父类属性
                fieldList.add(superField);
            }
        }
        return fieldList;
    }

    public static String getClassName(Type type) {
        if (type == null) {
            return "";
        }
        String className = type.toString();
        if (className.startsWith(TYPE_NAME_PREFIX)) {
            className = className.substring(TYPE_NAME_PREFIX.length());
        }
        return className;
    }

    public static Class<?> getClass(Type type) throws ClassNotFoundException {
        String className = getClassName(type);
        return StrUtil.isBlank(className) ? null : Class.forName(className);
    }

    /**
     * 从XXXMapper中提取其XXXDomain的Class
     *
     * @param mapperClass
     * @return
     */
    public static Class<?> extractModelClass(Class<?> mapperClass) {
        if (mapperClass == BaseMapper.class) {
            log.warn(" Current Class is BaseMapper ");
            return null;
        } else {
            Type[] types = mapperClass.getGenericInterfaces();
            ParameterizedType target = null;
            for (Type type : types) {
                if (type instanceof ParameterizedType && BaseMapper.class.isAssignableFrom(mapperClass)) {
                    target = (ParameterizedType) type;
                    break;
                }
            }
            return target == null ? null : (Class<?>) target.getActualTypeArguments()[0];
        }
    }

    /**
     * 获取指定包下父类为supClazz的所有类的属性的get&set方法,组装成方便使用的Reflector对象
     *
     * @param packagePath 不能为null
     * @param supClazz 父类Class
     * @return
     */
    public static ConcurrentMap<Class<?>, Reflector> getReflectorsFromPackage(List<String> packagePath,
            Class<?> supClazz) {
        if (CollectionUtils.isEmpty(packagePath)) {
            throw new ServiceException("No i18n package is found!");
        }
        ConcurrentHashMap<Class<?>, Reflector> map = new ConcurrentHashMap<>();
        packagePath.forEach(p -> {
            List<Class<?>> classList = PackageScanUtil.getClassFromSuperClass(p, supClazz);
            if (CollectionUtils.isEmpty(classList)) {
                log.warn("PackagePath: {},Can not find specific class which needs to be initialized!", p);
            }
            classList.forEach(clz -> map.put(clz, new Reflector(clz)));
        });
        return map;
    }
}
