package com.engine.salary.util;


import lombok.extern.slf4j.Slf4j;
import sun.reflect.ConstructorAccessor;
import sun.reflect.FieldAccessor;
import sun.reflect.MethodAccessor;
import sun.reflect.ReflectionFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@Slf4j
public class EnumUtil {

    /**
     * 扩展枚举（把新枚举的值加入旧枚举里，从旧枚举里删除不要的枚举值）
     * @param oldEnumClass 旧枚举类
     * @param newEnumClass 需要扩展的枚举类
     * @param removeOldEnums 需要删除的旧枚举值
     * @param <O>
     * @param <N>
     * @throws Exception
     */
    public static <O extends Enum,N extends Enum> void extendEnum(Class<O> oldEnumClass,Class<N> newEnumClass,O... removeOldEnums) throws Exception {
        boolean needToRemoveOldEnum = removeOldEnums!=null&&removeOldEnums.length>0;
        if(needToRemoveOldEnum){
            removeEnum(oldEnumClass,removeOldEnums);
        }
        List newEnums = values(newEnumClass);
        Field[] fields = getFields(newEnumClass);
        for (Object newEnumObject : newEnums) {
            Enum newEnum = Enum.class.cast(newEnumObject);

            List<Class> fieldTypeList = new ArrayList<>();
            //枚举名称的类型：String.class
            fieldTypeList.add(String.class);
            for (Field field : fields) {
                fieldTypeList.add(field.getType());
            }
            List<Object> fieldValueList = new ArrayList<>();
            //枚举名称
            fieldValueList.add(newEnum.name());
            for (Field field : fields) {
                Object value = field.get(newEnum);
                fieldValueList.add(value);
            }

            Class[] fieldTypes = fieldTypeList.toArray(new Class[]{});
            String[] fieldValues = fieldValueList.toArray(new String[]{});
            addEnum(oldEnumClass,fieldTypes,fieldValues);
        }
    }

    /**
     * 新增枚举值
     * @param enumClass      枚举类型
     * @param fieldTypes 字段的类型，第一个是枚举名类型String
     * @param fieldValues 字段的值，第一个是枚举名称
     * @throws Exception
     */
    public static <T extends Enum<T>> T addEnum(Class<T> enumClass, Class[] fieldTypes, Object[] fieldValues) throws Exception {
        if(fieldTypes==null||fieldTypes.length==0){
            throw new RuntimeException("参数fieldTypes为空");
        }
        if(fieldValues==null||fieldValues.length==0){
            throw new RuntimeException("参数fieldValues为空");
        }
        if(fieldTypes[0]!=String.class){
            throw new RuntimeException("参数fieldTypes[0]不是String.class");
        }
        if(!(fieldValues[0] instanceof String)){
            throw new RuntimeException("参数fieldValues[0]不是字符串");
        }
        if(fieldTypes.length!=fieldValues.length){
            throw new RuntimeException("参数fieldTypes和参数fieldValues的长度不一致");
        }
        ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
        String enumName = fieldValues[0].toString();
        synchronized (enumClass){
            //判断name是否已经添加过了
            if(hasEnumName(enumClass,enumName)){
                log.info("枚举类{}中已存在该枚举名：{}",enumClass.getSimpleName(),enumName);
                return getEnum(enumClass,enumName);
            }
            //name,ordinal,其他自定义字段
            List<Class> allFieldClass = new ArrayList<>(fieldTypes.length + 1);
            allFieldClass.add(String.class);
            allFieldClass.add(int.class);
            for (int i = 1; i < fieldTypes.length; i++) {
                allFieldClass.add(fieldTypes[i]);
            }
            Class[] classes = allFieldClass.toArray(new Class[]{});
            Constructor<T> constructor = enumClass.getDeclaredConstructor(classes);
            ConstructorAccessor constructorAccessor = reflectionFactory.newConstructorAccessor(constructor);
            List<Object> allFields = new ArrayList<>(fieldValues.length + 1);
            allFields.add(enumName);
            int maxOrdinal = getMaxOrdinal(enumClass);
            allFields.add(maxOrdinal+1);
            for (int i = 1; i < fieldValues.length; i++) {
                allFields.add(fieldValues[i]);
            }
            //调用枚举的构造方法，创建新的枚举值
            T newEnum = (T) constructorAccessor.newInstance(allFields.toArray());
            log.info("新增枚举：{}" , newEnum);
            Field valuesField = enumClass.getDeclaredField("$VALUES");
            valuesField.setAccessible(true);

            //解除values属性的final限制
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            int modifiers = modifiersField.getInt(valuesField);
            modifiers &= ~Modifier.FINAL;
            modifiersField.setInt(valuesField, modifiers);

            //将新增的枚举值加入values属性里
            FieldAccessor fieldAccessor = reflectionFactory.newFieldAccessor(valuesField, false);
            T[] ts = (T[]) fieldAccessor.get(enumClass);
            List<T> list = new ArrayList<>(Arrays.asList(ts));
            list.add(newEnum);
            fieldAccessor.set(enumClass, list.toArray(ts));

            //将Class对象的enumConstants和enumConstantDirectory清空（Enum.valueOf()方法会给它们赋值）
            /**
             * Enum.valueOf()逻辑：
             * 1.取enumConstantDirectory
             *      1.1如果有值则直接返回
             *      1.2如果没值，则取enumConstants，并拷贝到enumConstantDirectory，下次可直接返回
             *          1.2.1如果enumConstants有值，则返回
             *          1.2.2如果enumConstants没值，则取枚举的values属性，并拷贝到enumConstants，下次可直接返回
             * 2.enumConstantDirectory.get(name)返回
             */
            Field enumConstantDirectoryField = enumClass.getClass().getDeclaredField("enumConstantDirectory");
            enumConstantDirectoryField.setAccessible(true);
            FieldAccessor enumConstantDirectoryFieldAccessor = reflectionFactory.newFieldAccessor(enumConstantDirectoryField, false);
            enumConstantDirectoryFieldAccessor.set(enumClass,null);
            Field enumConstantsField = enumClass.getClass().getDeclaredField("enumConstants");
            enumConstantsField.setAccessible(true);
            FieldAccessor enumConstantsFieldAccessor = reflectionFactory.newFieldAccessor(enumConstantsField, false);
            enumConstantsFieldAccessor.set(enumClass,null);
            return newEnum;
        }
    }

    /**
     * 获取枚举类当前最大序号
     * @param enumClass 枚举类型
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T extends Enum<T>> int getMaxOrdinal(Class<T> enumClass) throws Exception {
        List<T> values = values(enumClass);
        if(values==null||values.size()==0){
            return 0;
        }
        int maxOrdinal = 0;
        for (T value : values) {
            if(maxOrdinal<value.ordinal()){
                maxOrdinal = value.ordinal();
            }
        }
        return maxOrdinal;
    }

    /**
     * 删除枚举值
     * @param enumClass 枚举类型
     * @param removeOldEnums 需要删除的枚举值
     * @param <T>
     */
    public static <T extends Enum<T>> void removeEnum(Class<T> enumClass, Enum<T>... removeOldEnums) throws Exception {
        if(removeOldEnums==null||removeOldEnums.length==0){
            log.warn("removeOldEnums为空");
            return;
        }
        ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
        synchronized (enumClass){
            Field valuesField = enumClass.getDeclaredField("$VALUES");
            valuesField.setAccessible(true);

            //解除values属性的final限制
            Field modifiersField = Field.class.getDeclaredField("modifiers");
            modifiersField.setAccessible(true);
            int modifiers = modifiersField.getInt(valuesField);
            modifiers &= ~Modifier.FINAL;
            modifiersField.setInt(valuesField, modifiers);

            FieldAccessor fieldAccessor = reflectionFactory.newFieldAccessor(valuesField, false);
            T[] oldEnumArray = (T[]) fieldAccessor.get(enumClass);
            List<T> enumList = new ArrayList<>(Arrays.asList(oldEnumArray));
            for (Enum removeOldEnum : removeOldEnums) {
                //将指定的枚举值从values属性里删除
                enumList.remove(removeOldEnum);
                log.info("删除枚举值：{}",removeOldEnum);
            }
            //把List转成数组
            T[] newEnumArray = (T[]) Arrays.copyOf(enumList.toArray(), enumList.size(), oldEnumArray.getClass());
            fieldAccessor.set(enumClass, newEnumArray);
        }
    }
    /**
     * 修改枚举属性
     * @param enumClass 枚举类型
     * @param enumName 枚举名称
     * @param attributeName 属性名
     * @param attributeValue 属性值
     * @param <T>
     * @throws Exception
     */
    public static <T extends Enum<T>> void setAttribute(Class<T> enumClass,String enumName,String attributeName,Object attributeValue) throws Exception {
        List<T> values = values(enumClass);
        T target = null;
        for (T t:values){
            if(t.name().equals(enumName)){
                target = t;
                break;
            }
        }
        if(target==null){
            throw new RuntimeException("该枚举类没有枚举名："+enumName);
        }
        Field declaredField = target.getClass().getDeclaredField(attributeName);
        declaredField.setAccessible(true);
        declaredField.set(target,attributeValue);
    }

    /**
     * 修改枚举属性
     * @param targetEnum 枚举值
     * @param attributeName 属性名
     * @param attributeValue 属性值
     * @param <T>
     * @throws Exception
     */
    public static <T extends Enum<T>> void setAttribute(T targetEnum,String attributeName,Object attributeValue) throws Exception {
        Field declaredField = targetEnum.getClass().getDeclaredField(attributeName);
        declaredField.setAccessible(true);
        declaredField.set(targetEnum,attributeValue);
    }

    /**
     * 判断枚举类是否包含了指定枚举名
     * @param clazz
     * @param enumName
     * @param <T>
     * @return
     */
    public static <T extends Enum<T>> boolean hasEnumName(Class<T> clazz, String enumName) throws Exception {
//        不要用valueOf方法，因为它会初始化enumConstantDirectory使得后续调用valueOf方法后拿不到后面加入的枚举值
//        try{
//            T t = Enum.valueOf(clazz, enumName);
//            if(t!=null){
//                return true;
//            }
//        }catch (Exception e){
//            e.printStackTrace();
//            System.err.println(e.getMessage());
//        }
        List<T> values = values(clazz);
        for (T t:values){
            if(t.name().equals(enumName)){
                return true;
            }
        }
        return false;
    }

    /**
     * 根据枚举类型和枚举名获取枚举值
     * @param clazz 枚举类型
     * @param enumName 枚举名称
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T extends Enum<T>> T getEnum(Class<T> clazz, String enumName) throws Exception {
        List<T> values = values(clazz);
        for (T t:values){
            if(t.name().equals(enumName)){
                return t;
            }
        }
        return null;
    }

    /**
     * 获取枚举类的所有枚举值
     * @param clazz 枚举类型
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T extends Enum<T>> List<T> values(Class<T> clazz) throws Exception {
        ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
        Field valuesField = clazz.getDeclaredField("$VALUES");
        valuesField.setAccessible(true);
        FieldAccessor fieldAccessor = reflectionFactory.newFieldAccessor(valuesField, false);
        T[] ts = (T[]) fieldAccessor.get(clazz);
        List<T> list = new ArrayList<>(Arrays.asList(ts));
        return list;
    }

    /**
     * 获取枚举类的所有枚举值
     * @param clazz 枚举类型
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T extends Enum<T>> List<T> values2(Class<T> clazz) throws Exception {
        ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
        Method valuesMethod = clazz.getDeclaredMethod("values");
        valuesMethod.setAccessible(true);
        MethodAccessor methodAccessor = reflectionFactory.newMethodAccessor(valuesMethod);
        T[] ts = (T[]) methodAccessor.invoke(clazz, null);
        List<T> list = new ArrayList<>(Arrays.asList(ts));
        return list;
    }

    /**
     * 获取枚举的字段（排除数组类型、枚举类型）
     * @param enumClass
     * @return
     */
    public static Field[] getFields(Class<? extends Enum> enumClass){
        List<Field> result = new ArrayList<>();
        Field[] declaredFields = enumClass.getDeclaredFields();
        for (Field field : declaredFields) {
            Class<?> type = field.getType();
            //排除数组类型(values)、枚举类型(枚举值)
            if(type!=enumClass && !type.isArray()){
                field.setAccessible(true);
                result.add(field);
            }
        }
        return result.toArray(new Field[]{});
    }

    /**
     * 打印枚举值
     * @param enumClass 枚举类型
     * @throws Exception
     */
    public static void printEnum(Class<? extends Enum> enumClass) throws Exception {
        System.out.println("+++++++++++++++++++++++");
        List values = values(enumClass);
        values.stream().forEach(System.out::println);
        System.out.println("+++++++++++++++++++++++");
    }

}
