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.HashMap;
import java.util.HashSet;
import java.util.Map;
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对象
	 * @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;
		}
	}
}
