package gaf3.core.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.FatalBeanException;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.stream.Stream;

import static org.springframework.beans.BeanUtils.getPropertyDescriptor;

public class DataBeanHelper {
	private final static Logger LOG = LoggerFactory.getLogger(DataBeanHelper.class);
	
	/**
	 * 数据Bean转换为Map对象
	 * @param source 源数据对象，必须为一个标准的Bean对象，只包含数据属性和Getter和Setter方法。
	 * @param destination 目标对象，Map类型，Key-Value形式。
	 * @param keymap 属性名转换映射表。默认情况下Bean中的属性名和Map中的Key相同，如果希望不同，在映射表中指定。
	 * 				 key为Bean中的属性名称,value为Map中的Key值。
	 * @param direction KeyMap的方向。true：bean->map false：map->bean
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void Bean2Map(Object source,Map destination,Properties keymap,boolean direction) {
		if(direction)
			Bean2Map(source,destination,keymap);
		else
		{
			Enumeration en = keymap.propertyNames();
			while(en.hasMoreElements()){
				String name = (String) en.nextElement();
				String newName = keymap.getProperty(name);
				try {
					Object value = getBeanValue(source, newName);
					if (value != null)
						destination.put(name, value);
				} catch (Exception ex) {
					LOG.debug(ex.toString());
				}
			}
		}
	}

	/**
	 * 数据对象转换为Map对象，采用默认的属性名映射方案和影射的方向
	 * @param source
	 * @param destination
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void Bean2Map(Object source,Map destination,Properties keymap) {
		String methodName = null;
		Method[] methods = source.getClass().getMethods();
		try{
			for (Method method : methods) {
				methodName = method.getName();

				if (methodName.length() >= 4 && methodName.substring(0, 3).equals("get")) {
					String key = methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
					if (keymap != null) {
						String newkey = keymap.getProperty(key);
						if (newkey != null)
							key = newkey;
						else
							continue;
					}
					if ("class".equals(key))
						continue;

					Object obj = method.invoke(source);
					if (obj != null) destination.put(key, obj);
				}
			}
		}catch(Exception ex)
		{
			LOG.debug(ex.toString());
			LOG.debug(methodName);
		}
	}
	/**
	 * 数据对象转换为Map对象，采用默认的属性名映射方案
	 * @param source
	 * @param destination
	 */
	@SuppressWarnings("rawtypes")
	public static void Bean2Map(Object source,Map destination) {
		Bean2Map(source,destination,null);
	}

	/**
	 * 数据对象转换为Map对象，采用默认的属性名映射方案和影射的方向
	 * @param source
	 * @param destination
	 */
	public static void Bean2StringMap(Object source,Map<String,String> destination,Properties keymap) {
		String methodName;
		Method[] methods = source.getClass().getMethods();
		try{
			for (Method method : methods) {
				methodName = method.getName();

				if (methodName.length() >= 4 && methodName.substring(0, 3).equals("get")) {
					String key = methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
					if (keymap != null) {
						String newkey = keymap.getProperty(key);
						if (newkey != null)
							key = newkey;
						else
							continue;
					}
					if ("class".equals(key))
						continue;
					Object obj = method.invoke(source);
					if (obj != null)
						destination.put(key,
								(obj instanceof String) ? (String) obj : obj.toString());
					else
						destination.put(key, null);
				}
			}
		}catch(Exception ex)
		{
			ex.printStackTrace();
		}
	}
	/**
	 * 数据对象转换为Map对象，采用默认的属性名映射方案
	 * @param source
	 * @param destination
	 */
	public static void Bean2StringMap(Object source,Map<String,String> destination) {
		Bean2StringMap(source,destination,null);
	}
	/**
	 * Map对象转换为Bean对象
	 * @param source 源数据对象，Map类型，Key-Value的形式保存属性名和值得列表
	 * @param destination 目标对象，必须为一个标准的Bean对象，只包含数据属性和Getter和Setter方法。
	 * @param keymap 属性名转换映射表。默认情况下Bean中的属性名和Map中的Key相同，如果希望不同，在映射表中指定。
	 * 				 key为Bean中的属性名称,value为Map中的Key值。
	 * @param direction KeyMap的方向。true：bean->map false：map->bean
	 * @throws Exception 当调用Setter方法失败是抛出异常
	 */
	@SuppressWarnings("rawtypes")
	public static void Map2Bean(Map source,Object destination,Properties keymap,boolean direction) throws Exception{
		if(direction)
			Map2Bean(source,destination,keymap);
		else
		{
			Enumeration en = keymap.propertyNames();
			while(en.hasMoreElements()){
				String name = (String) en.nextElement();
				String newName = keymap.getProperty(name);
				Object value = source.get(name);
				if(value!=null)
					setBeanValue(destination,value,newName);
			}
		}
	}
	/**
	 * Map对象转换为Bean对象
	 * @param source 源数据对象，Map类型，Key-Value的形式保存属性名和值得列表
	 * @param destination 目标对象，必须为一个标准的Bean对象，只包含数据属性和Getter和Setter方法。
	 * @param keymap 属性名转换映射表。默认情况下Bean中的属性名和Map中的Key相同，如果希望不同，在映射表中指定。
	 * 				 key为Bean中的属性名称,value为Map中的Key值。
	 * @throws Exception 当调用Setter方法失败是抛出异常
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public static void Map2Bean(Map source,Object destination,Properties keymap) throws Exception{
		Method[] methods = destination.getClass().getMethods();
		for (Method method : methods) {
			String methodName = method.getName();
			if (methodName.length() >= 4 && methodName.substring(0, 3).equals("set")) {
				String key = methodName.substring(3, 4).toLowerCase() + methodName.substring(4);
				String attrName = key;
				if (keymap != null) {
					String newkey = keymap.getProperty(key);
					if (newkey != null)
						key = newkey;
					else
						continue;
				}
				Object obj = source.get(key);
				Class cls = method.getParameterTypes()[0];
				if (obj != null) {
					if (cls.isInstance(obj))
						method.invoke(destination, obj);
//					else if(cls == Integer.class){
//						methods[i].invoke(destination,new Object[]{Integer.parseInt(obj.toString())});
//					}else if(cls == Long.class){
//						methods[i].invoke(destination,new Object[]{Long.parseLong(obj.toString())});
					else if (Number.class.isAssignableFrom(cls)) {
						method.invoke(destination, tryParse(obj.toString(), cls));
					} else if ((obj instanceof Map) && !Map.class.isAssignableFrom(cls)) {
						Object dest = cls.getDeclaredConstructor().newInstance();
						Map2Bean((Map) obj, dest);
						method.invoke(destination, dest);
					} else
						method.invoke(destination, obj);
				}

			}
		}
		// 设置嵌套对象
		Stream<Map.Entry> stream = source.entrySet().stream();
		stream.filter(e -> isNestedProperty(e.getKey().toString())).forEach(e -> {
			try {
				setNestedProperty(destination, e.getValue(), e.getKey().toString());
			} catch (Exception exception) {
				LOG.warn("嵌套设置对象属性{}失败：", e.getKey(), exception.getMessage());
			}
		});
	}

	/**
	 * Map对象转换为Bean对象,采用默认的属性名映射方案
	 * @param source
	 * @param destination
	 * @throws Exception 
	 */
	@SuppressWarnings("rawtypes")
	public static void Map2Bean(Map source,Object destination) throws Exception{
		Map2Bean(source,destination,null);
	}
	
	/**
	 * 调用对象中的一个方法
	 * @param obj
	 * @param param
	 * @param methodName
	 * @return
	 * @throws Exception
	 */
	public static Object callMethod(Object obj,Object param,String methodName) throws Exception{
		Method method=null;
		/*试图找到最满足条件的方法*/
		if(param != null){
			try{
				method = obj.getClass().getMethod(methodName, param.getClass());
			}catch(Exception ex){
				LOG.debug(ex.toString());
			}
			if(method != null)
				return method.invoke(obj, param);
		}

		/*找不到请求参数类型跟param的类型完全相同的方法，查找参数类型跟其父类相同的方法*/
		Method[] methods = obj.getClass().getMethods();
		for (Method item : methods) {
			if (methodName.equals(item.getName())) {
				if (item.getParameterTypes().length == 1 && (param == null || item.getParameterTypes()[0].isAssignableFrom(param.getClass()))) {
					method = item;
					break;
				}
			}
		}
		if(method == null){
			String className = obj.getClass().getName();
			String message ="没有找到要调用的方法："+ className +"."+methodName +"(" + ( param==null ? "null" : param.getClass().getName()) + ")";
			LOG.error(message);
			throw new NoSuchMethodException(message);
		}
		return method.invoke(obj, param);
	}
	
	/**
	 * 调用对象中的一个方法，两个参数
	 * @param obj
	 * @param param
	 * @param param2
	 * @param methodName
	 * @return
	 * @throws Exception
	 */
	public static Object callMethod2(Object obj,Object param,Object param2,String methodName) throws Exception{
		Method method=null;
		/*试图找到最满足条件的方法*/
		try{
			method = obj.getClass().getMethod(methodName, param.getClass(),param2.getClass());
		}catch(Exception ex){
			LOG.debug(ex.toString());
		}
		if(method != null)
			return method.invoke(obj, param,param2);

		/*找不到请求参数类型跟param的类型完全相同的方法，查找参数类型跟其父类相同的方法*/
		Method[] methods = obj.getClass().getMethods();
		for (Method item : methods) {
			if (methodName.equals(item.getName())) {
				if (item.getParameterTypes().length == 2 && item.getParameterTypes()[0].isAssignableFrom(param.getClass())
						&& item.getParameterTypes()[1].isAssignableFrom(param2.getClass())) {
					method = item;
					break;
				}
			}
		}
		if(method == null){
			String message ="没有找到要调用的方法："+ obj.getClass().getName()+"."+methodName +"("+param.getClass().getName()+")"; 
			LOG.error(message);
			throw new NoSuchMethodException(message);
		}
		return method.invoke(obj, param,param2);
	}
	
	/**
	 * 设置Bean对象的属性值
	 * @param bean
	 * @param value
	 * @param attrName
	 * @throws Exception
	 */
	public static void setBeanValue(Object bean,Object value,String attrName) throws Exception{
		String methodName;
		if(attrName.length() > 1)
			methodName = "set" + attrName.substring(0,1).toUpperCase() + attrName.substring(1);
		else
			methodName = "set" + attrName.toUpperCase();
		
		callMethod(bean,value,methodName);
		//Method method = bean.getClass().getMethod(methodName,new Class[]{value.getClass()});
		//method.invoke(bean,new Object[]{value});
	}
	/**
	 * 获取Bean对象的属性值
	 * @param bean
	 * @param attrName
	 * @return
	 * @throws Exception
	 */
	public static Object getBeanValue(Object bean,String attrName) throws Exception{
		String methodName;
		if(attrName.length() > 1)
			methodName = "get" + attrName.substring(0,1).toUpperCase() + attrName.substring(1);
		else
			methodName = "get" + attrName.toUpperCase();
		
		Method method = bean.getClass().getMethod(methodName);
		return method.invoke(bean);
	}

	public static void setBeanValueIndicator(Object bean, int indicator, String attrName) throws Exception {
		String methodName;
		if(attrName.length() > 1)
			methodName = "set" + attrName.substring(0,1).toUpperCase() + attrName.substring(1) + "_Indicator";
		else
			methodName = "set" + attrName.toUpperCase() + "_Indicator";
		
		Method method = bean.getClass().getMethod(methodName, int.class);
		method.invoke(bean, indicator);
	}

	public static<T> void Bean2Bean(T source,T destination){
		Bean2Bean(source,destination,true);
	}
	public static<T> void Bean2Bean(T source,T destination,boolean skipNull){
		
		PropertyDescriptor[] pds = BeanUtils.getPropertyDescriptors(source.getClass());
		for(PropertyDescriptor pd : pds){
			Object value;
			try {
				Method read = pd.getReadMethod();
				Method write = pd.getWriteMethod();
				if(read == null || write == null)
					continue;
				if(!Modifier.isPublic(read.getDeclaringClass().getModifiers())
						||!Modifier.isPublic(read.getModifiers())
						||!Modifier.isPublic(write.getModifiers()))
					continue;
				value = read.invoke(source);
				if(skipNull && value == null)
					continue;
				write.invoke(destination, value);
			} catch (Throwable e) {
				e.printStackTrace();
			}
		}
	}
	@SuppressWarnings("unchecked")
	public static<T extends Number> T tryParse(String value,Class<T> cls){
		if(StringUtil.isNullOrEmpty(value))
			return null;
		try{
			Method method = cls.getMethod("valueOf", String.class);
			if(method != null)
				return (T) method.invoke(null, value);
		}catch(Throwable e){
			e.printStackTrace();
		}
		return null;
	}
	public static<T> void Bean2Obj(T source,Object target, @Nullable String... ignoreProperties){
		Bean2Obj(source,target,true, ignoreProperties);
	}
	public static<T> void Bean2Obj(T source,Object target,boolean skipNull, @Nullable String... ignoreProperties){
		PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(target.getClass());
		List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
		for(PropertyDescriptor targetPd : targetPds){
			Method writeMethod = targetPd.getWriteMethod();
			if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
				PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
				if (sourcePd != null) {
					Method readMethod = sourcePd.getReadMethod();
					if (readMethod != null &&
							ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
						try {
							if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
								readMethod.setAccessible(true);
							}
							Object value = readMethod.invoke(source);
							if(value == null && skipNull) continue;

							if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
								writeMethod.setAccessible(true);
							}
							writeMethod.invoke(target, value);
						}
						catch (Throwable ex) {
							throw new FatalBeanException(
									"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
						}
					}
				}
			}
		}
	}

	/**
	 * 设置嵌套对象属性
	 * @param bean
	 * @param value
	 * @param attrName
	 * @throws Exception
	 */
	public static void setNestedProperty(Object bean,Object value,String attrName) throws Exception {
		if(!isNestedProperty(attrName)) {
			setBeanValue(bean, value, attrName);
			return;
		}
		String[] names = attrName.split("\\.", 2);

		// 获得属性信息
		PropertyDescriptor pd = BeanUtils.getPropertyDescriptor(bean.getClass(), names[0]);
		if(pd == null) return;
		Method read = pd.getReadMethod();
		Method write = pd.getWriteMethod();
		if(read == null
				|| !Modifier.isPublic(read.getDeclaringClass().getModifiers())
				|| !Modifier.isPublic(read.getModifiers()))
			return;

		// 处理嵌套对象
		Object nestedObj = read.invoke(bean);
		if (nestedObj == null) {
			if(write == null || !Modifier.isPublic(write.getModifiers()))
				return; // 属性不可写，不进行后续赋值操作
			nestedObj = BeanUtils.instantiateClass(pd.getPropertyType());
		} else {
			write = null; // 嵌套对象已存在值，不进行对象赋值
		}

		// 递归调用
		setNestedProperty(nestedObj, value, names[1]);

		if (write != null) {
			write.invoke(bean, nestedObj);
		}
	}
	public static boolean isNestedProperty(String attrName) {
		return attrName.contains(".");
	}
}
