package com.stylefeng.guns.core.support;

import com.stylefeng.guns.core.support.exception.ToolBoxException;
import com.stylefeng.guns.core.util.Convert;

import java.beans.*;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;

/**
 * Bean工具类
 *
 * @author Looly
 */
public class BeanKit {

    /**
     * 判断是否为Bean对象
     *
     * @param clazz 待测试类
     * @return 是否为Bean对象
     */
    public static boolean isBean(Class<?> clazz) {
        if (ClassKit.isNormalClass(clazz)) {
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                if (method.getParameterTypes().length == 1 && method.getName().startsWith("set")) {
                    //检测包含标准的setXXX方法即视为标准的JavaBean
                    return true;
                }
            }
        }
        return false;
    }

    public static PropertyEditor findEditor(Class<?> type) {
        return PropertyEditorManager.findEditor(type);
    }

    /**
     * 获得Bean字段描述数组
     *
     * @param clazz Bean类
     * @return 字段描述数组
     * @throws IntrospectionException
     */
    public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws IntrospectionException {
        return Introspector.getBeanInfo(clazz).getPropertyDescriptors();
    }

    /**
     * 获得字段名和字段描述Map
     *
     * @param clazz Bean类
     * @return 字段名和字段描述Map
     * @throws IntrospectionException
     */
    public static Map<String, PropertyDescriptor> getFieldNamePropertyDescriptorMap(Class<?> clazz) throws IntrospectionException {
        final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz);
        Map<String, PropertyDescriptor> map = new HashMap<>();
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            map.put(propertyDescriptor.getName(), propertyDescriptor);
        }
        return map;
    }

    /**
     * 获得Bean类属性描述
     *
     * @param clazz     Bean类
     * @param fieldName 字段名
     * @return PropertyDescriptor
     * @throws IntrospectionException
     */
    public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, final String fieldName) throws IntrospectionException {
        PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(clazz);
        for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
            if (ObjectKit.equals(fieldName, propertyDescriptor.getName())) {
                return propertyDescriptor;
            }
        }
        return null;
    }

    /**
     * Map转换为Bean对象
     *
     * @param map       Map
     * @param beanClass Bean Class
     * @return Bean
     */
    public static <T> T mapToBean(Map<?, ?> map, Class<T> beanClass) {
        return fillBeanWithMap(map, ClassKit.newInstance(beanClass));
    }

    /**
     * Map转换为Bean对象<br>
     * 忽略大小写
     *
     * @param map       Map
     * @param beanClass Bean Class
     * @return Bean
     */
    public static <T> T mapToBeanIgnoreCase(Map<?, ?> map, Class<T> beanClass) {
        return fillBeanWithMapIgnoreCase(map, ClassKit.newInstance(beanClass));
    }

    /**
     * 使用Map填充Bean对象
     *
     * @param map  Map
     * @param bean Bean
     * @return Bean
     */
    public static <T> T fillBeanWithMap(final Map<?, ?> map, T bean) {
        return fillBean(bean, new ValueProvider() {
            @Override
            public Object value(String name) {
                return map.get(name);
            }
        });
    }

    /**
     * 使用Map填充Bean对象，可配置将下划线转换为驼峰
     *
     * @param map           Map
     * @param bean          Bean
     * @param isToCamelCase 是否将下划线模式转换为驼峰模式
     * @return Bean
     */
    public static <T> T fillBeanWithMap(Map<?, ?> map, T bean, boolean isToCamelCase) {
        if (isToCamelCase) {
            final Map<Object, Object> map2 = new HashMap<Object, Object>();
            for (Entry<?, ?> entry : map.entrySet()) {
                final Object key = entry.getKey();
                if (null != key && key instanceof String) {
                    final String keyStr = (String) key;
                    map2.put(StrKit.toCamelCase(keyStr), entry.getValue());
                } else {
                    map2.put(key, entry.getValue());
                }
            }
            return fillBeanWithMap(map2, bean);
        }

        return fillBeanWithMap(map, bean);
    }

    /**
     * 使用Map填充Bean对象，忽略大小写
     *
     * @param map  Map
     * @param bean Bean
     * @return Bean
     */
    public static <T> T fillBeanWithMapIgnoreCase(Map<?, ?> map, T bean) {
        final Map<Object, Object> map2 = new HashMap<Object, Object>();
        for (Entry<?, ?> entry : map.entrySet()) {
            final Object key = entry.getKey();
            if (key instanceof String) {
                final String keyStr = (String) key;
                map2.put(keyStr.toLowerCase(), entry.getValue());
            } else {
                map2.put(key, entry.getValue());
            }
        }

        return fillBean(bean, new ValueProvider() {
            @Override
            public Object value(String name) {
                return map2.get(name.toLowerCase());
            }
        });
    }

    /**
     * ServletRequest 参数转Bean
     *
     * @param request   ServletRequest
     * @param beanClass Bean Class
     * @return Bean
     */
    public static <T> T requestParamToBean(javax.servlet.ServletRequest request, Class<T> beanClass) {
        return fillBeanWithRequestParam(request, ClassKit.newInstance(beanClass));
    }

    /**
     * ServletRequest 参数转Bean
     *
     * @param request ServletRequest
     * @param bean    Bean
     * @return Bean
     */
    public static <T> T fillBeanWithRequestParam(final javax.servlet.ServletRequest request, T bean) {
        final String beanName = StrKit.lowerFirst(bean.getClass().getSimpleName());
        return fillBean(bean, new ValueProvider() {
            @Override
            public Object value(String name) {
                String value = request.getParameter(name);
                if (StrKit.isEmpty(value)) {
                    // 使用类名前缀尝试查找值
                    value = request.getParameter(beanName + StrKit.DOT + name);
                    if (StrKit.isEmpty(value)) {
                        // 此处取得的值为空时跳过，包括null和""
                        value = null;
                    }
                }
                return value;
            }
        });
    }

    /**
     * ServletRequest 参数转Bean
     *
     * @param <T>
     * @param beanClass     Bean Class
     * @param valueProvider 值提供者
     * @return Bean
     */
    public static <T> T toBean(Class<T> beanClass, ValueProvider valueProvider) {
        return fillBean(ClassKit.newInstance(beanClass), valueProvider);
    }

    /**
     * 填充Bean
     *
     * @param <T>
     * @param bean          Bean
     * @param valueProvider 值提供者
     * @return Bean
     */
    public static <T> T fillBean(T bean, ValueProvider valueProvider) {
        if (null == valueProvider) {
            return bean;
        }

        Class<?> beanClass = bean.getClass();
        try {
            PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(beanClass);
            String propertyName;
            Object value;
            for (PropertyDescriptor property : propertyDescriptors) {
                propertyName = property.getName();
                value = valueProvider.value(propertyName);
                if (null == value) {
                    continue;
                }
                try {
                    property.getWriteMethod().invoke(bean, Convert.parse(property.getPropertyType(), value));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            throw new ToolBoxException(e);
        }
        return bean;
    }

    /**
     * 对象转Map
     *
     * @param bean bean对象
     * @return Map
     */
    public static <T> Map<String, Object> beanToMap(T bean) {
        return beanToMap(bean, false);
    }

    /**
     * 对象转Map
     *
     * @param bean bean对象
     * @return Map
     */
    public static <T> List<Map<String, Object>> listToMapList(List<T> bean) {
        ArrayList<Map<String, Object>> maps = new ArrayList<>();
        for (T t : bean) {
            maps.add(beanToMap(bean, false));
        }
        return maps;
    }

    /**
     * 对象转Map
     *
     * @param bean              bean对象
     * @param isToUnderlineCase 是否转换为下划线模式
     * @return Map
     */
    public static <T> Map<String, Object> beanToMap(T bean, boolean isToUnderlineCase) {

        if (bean == null) {
            return null;
        }
        Map<String, Object> map = new HashMap<String, Object>();
        try {
            final PropertyDescriptor[] propertyDescriptors = getPropertyDescriptors(bean.getClass());
            for (PropertyDescriptor property : propertyDescriptors) {
                String key = property.getName();
                // 过滤class属性
                if (false == key.equals("class")) {
                    // 得到property对应的getter方法
                    Method getter = property.getReadMethod();
                    Object value = getter.invoke(bean);
                    if (null != value) {
                        map.put(isToUnderlineCase ? StrKit.toUnderlineCase(key) : key, value);
                    }
                }
            }
        } catch (Exception e) {
            throw new ToolBoxException(e);
        }
        return map;
    }

    /**
     * 复制Bean对象属性
     *
     * @param source 源Bean对象
     * @param target 目标Bean对象
     */
    public static void copyProperties(Object source, Object target) {
        copyProperties(source, target, CopyOptions.create());
    }

    /**
     * 复制Bean对象属性<br>
     * 限制类用于限制拷贝的属性，例如一个类我只想复制其父类的一些属性，就可以将editable设置为父类
     *
     * @param source           源Bean对象
     * @param target           目标Bean对象
     * @param ignoreProperties 不拷贝的的属性列表
     */
    public static void copyProperties(Object source, Object target, String... ignoreProperties) {
        copyProperties(source, target, CopyOptions.create().setIgnoreProperties(ignoreProperties));
    }

    /**
     * 复制Bean对象属性<br>
     * 限制类用于限制拷贝的属性，例如一个类我只想复制其父类的一些属性，就可以将editable设置为父类
     *
     * @param source      源Bean对象
     * @param target      目标Bean对象
     * @param copyOptions 拷贝选项，见 {@link CopyOptions}
     */
    public static void copyProperties(Object source, Object target, CopyOptions copyOptions) {
        if (null == copyOptions) {
            copyOptions = new CopyOptions();
        }

        Class<?> actualEditable = target.getClass();
        if (copyOptions.editable != null) {
            //检查限制类是否为target的父类或接口
            if (!copyOptions.editable.isInstance(target)) {
                throw new IllegalArgumentException(StrKit.format("Target class [{}] not assignable to Editable class [{}]", target.getClass().getName(), copyOptions.editable.getName()));
            }
            actualEditable = copyOptions.editable;
        }
        PropertyDescriptor[] targetPds = null;
        Map<String, PropertyDescriptor> sourcePdMap;
        try {
            sourcePdMap = getFieldNamePropertyDescriptorMap(source.getClass());
            targetPds = getPropertyDescriptors(actualEditable);
        } catch (IntrospectionException e) {
            throw new ToolBoxException(e);
        }

        HashSet<String> ignoreSet = copyOptions.ignoreProperties != null ? CollectionKit.newHashSet(copyOptions.ignoreProperties) : null;
        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null && (ignoreSet == null || false == ignoreSet.contains(targetPd.getName()))) {
                PropertyDescriptor sourcePd = sourcePdMap.get(targetPd.getName());
                if (sourcePd != null) {
                    Method readMethod = sourcePd.getReadMethod();
                    // 源对象字段的getter方法返回值必须可转换为目标对象setter方法的第一个参数
                    if (readMethod != null && ClassKit.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
                        try {
                            Object value = ClassKit.setAccessible(readMethod).invoke(source);
                            if (null != value || false == copyOptions.isIgnoreNullValue) {
                                ClassKit.setAccessible(writeMethod).invoke(target, value);
                            }
                        } catch (Throwable ex) {
                            throw new ToolBoxException(ex, "Copy property [{}] to [{}] error: {}", sourcePd.getName(), targetPd.getName(), ex.getMessage());
                        }
                    }
                }
            }
        }
    }

    /**
     * 值提供者，用于提供Bean注入时参数对应值得抽象接口<br>
     * 继承或匿名实例化此接口<br>
     * 在Bean注入过程中，Bean获得字段名，通过外部方式根据这个字段名查找相应的字段值，然后注入Bean<br>
     *
     * @author Looly
     */
    public static interface ValueProvider {
        /**
         * 获取值
         *
         * @param name Bean对象中参数名
         * @return 对应参数名的值
         */
        public Object value(String name);
    }

    /**
     * 属性拷贝选项<br>
     * 包括：<br>
     * 1、限制的类或接口，必须为目标对象的实现接口或父类，用于限制拷贝的属性，例如一个类我只想复制其父类的一些属性，就可以将editable设置为父类<br>
     * 2、是否忽略空值，当源对象的值为null时，true: 忽略而不注入此值，false: 注入null<br>
     * 3、忽略的属性列表，设置一个属性列表，不拷贝这些属性值<br>
     *
     * @author Looly
     */
    public static class CopyOptions {
        /**
         * 限制的类或接口，必须为目标对象的实现接口或父类，用于限制拷贝的属性，例如一个类我只想复制其父类的一些属性，就可以将editable设置为父类
         */
        private Class<?> editable;
        /**
         * 是否忽略空值，当源对象的值为null时，true: 忽略而不注入此值，false: 注入null
         */
        private boolean isIgnoreNullValue;
        /**
         * 忽略的属性列表，设置一个属性列表，不拷贝这些属性值
         */
        private String[] ignoreProperties;

        /**
         * 创建拷贝选项
         *
         * @return 拷贝选项
         */
        public static CopyOptions create() {
            return new CopyOptions();
        }

        /**
         * 创建拷贝选项
         *
         * @param editable          限制的类或接口，必须为目标对象的实现接口或父类，用于限制拷贝的属性
         * @param isIgnoreNullValue 是否忽略空值，当源对象的值为null时，true: 忽略而不注入此值，false: 注入null
         * @param ignoreProperties  忽略的属性列表，设置一个属性列表，不拷贝这些属性值
         * @return 拷贝选项
         */
        public static CopyOptions create(Class<?> editable, boolean isIgnoreNullValue, String... ignoreProperties) {
            return new CopyOptions(editable, isIgnoreNullValue, ignoreProperties);
        }

        /**
         * 构造拷贝选项
         */
        public CopyOptions() {
        }

        /**
         * 构造拷贝选项
         *
         * @param editable          限制的类或接口，必须为目标对象的实现接口或父类，用于限制拷贝的属性
         * @param isIgnoreNullValue 是否忽略空值，当源对象的值为null时，true: 忽略而不注入此值，false: 注入null
         * @param ignoreProperties  忽略的属性列表，设置一个属性列表，不拷贝这些属性值
         */
        public CopyOptions(Class<?> editable, boolean isIgnoreNullValue, String... ignoreProperties) {
            this.editable = editable;
            this.isIgnoreNullValue = isIgnoreNullValue;
            this.ignoreProperties = ignoreProperties;
        }

        /**
         * 设置限制的类或接口，必须为目标对象的实现接口或父类，用于限制拷贝的属性
         *
         * @param editable 限制的类或接口
         * @return CopyOptions
         */
        public CopyOptions setEditable(Class<?> editable) {
            this.editable = editable;
            return this;
        }

        /**
         * 设置是否忽略空值，当源对象的值为null时，true: 忽略而不注入此值，false: 注入null
         *
         * @param isIgnoreNullVall 是否忽略空值，当源对象的值为null时，true: 忽略而不注入此值，false: 注入null
         * @return CopyOptions
         */
        public CopyOptions setIgnoreNullValue(boolean isIgnoreNullVall) {
            this.isIgnoreNullValue = isIgnoreNullVall;
            return this;
        }

        /**
         * 设置忽略的属性列表，设置一个属性列表，不拷贝这些属性值
         *
         * @param ignoreProperties 忽略的属性列表，设置一个属性列表，不拷贝这些属性值
         * @return CopyOptions
         */
        public CopyOptions setIgnoreProperties(String... ignoreProperties) {
            this.ignoreProperties = ignoreProperties;
            return this;
        }
    }
}
