package com.copy.trade.util;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.*;
import org.springframework.cglib.beans.BeanCopier;
import org.springframework.util.Assert;

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

/**
 * 简单bean 拷贝实现类
 *
 * @author quzhimin
 * @date 2018/7/19
 */
public class BeanCopior extends BeanUtils {

    private static final Logger log = LoggerFactory.getLogger(BeanCopior.class);

    public static <T> T map(Object source, Class<T> destinationClass) {
        if (source == null) {
            return null;
        }
        BeanCopier copier = BeanCopier.create(source.getClass(), destinationClass, false);
        T target = null;
        try {
            target = destinationClass.newInstance();
            copier.copy(source, target, null);
        } catch (InstantiationException | IllegalAccessException e) {
            log.error(e.getMessage(), e);
            WebAssert.throwException("sys.bean.map.fail");
        }
        return target;
    }

    public static <T> List<T> mapList(Collection sourceList, Class<T> destinationClass) {
        ArrayList destinationList = new ArrayList();
        listCopy(destinationClass, destinationList, sourceList);
        return destinationList;
    }

//    public static <T> PageInfo<T> mapPageInfo(Collection sourceList, Class<T> destinationClass) {
//        Page destinationPage = new Page();
//        copy(sourceList, destinationPage);
//        destinationPage.clear();
//        listCopy(destinationClass, destinationPage, sourceList);
//        return new PageInfo(destinationPage);
//    }

    public static void copy(Object source, Object destinationObject) {
        copy(source, destinationObject, true);
    }

    public static void copy(Object source, Object destinationObject, boolean excludeNull) {
        if (excludeNull) {
            copyProperties(source, destinationObject, getNullPropertyNames(source));
        } else {
            copyProperties(source, destinationObject);
        }
    }


    /**
     * 带字段映射拷贝
     * 未映射的字段，则不转换，直接拷贝
     *
     * @param source 源对象
     * @param target 拷贝对象
     * @param map    {
     *               target.beanName : source.beanName
     *               }
     * @throws BeansException
     */
    public static void copy(Object source, Object target, Map<String, String> map) throws BeansException {

        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        Class<?> actualEditable = target.getClass();
        PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);

        for (PropertyDescriptor targetPd : targetPds) {
            Method writeMethod = targetPd.getWriteMethod();
            if (writeMethod != null) {
                String mapedName = map.get(targetPd.getName());
                String readName = mapedName == null ? targetPd.getName() : mapedName;
                PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), readName);
                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 (!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);
                        }
                    }
                }
            }
        }
    }

    private static <T> void listCopy(Class<T> destinationClass, ArrayList destinationList, Collection sourceList) {
        BeanCopier copier = null;
        Iterator it = sourceList.iterator();
        while (it.hasNext()) {
            Object sourceObject = it.next();
            if (copier == null) {
                copier = BeanCopier.create(sourceObject.getClass(), destinationClass, false);
            }
            Object destinationObject;
            try {
                destinationObject = destinationClass.newInstance();
                copier.copy(sourceObject, destinationObject, null);
                destinationList.add(destinationObject);
            } catch (InstantiationException | IllegalAccessException e) {
                log.error(e.getMessage(), e);
                WebAssert.throwException("sys.bean.copy.fail");
            }
        }
    }

    private static String[] getNullPropertyNames(Object source) {
        final BeanWrapper src = new BeanWrapperImpl(source);
        PropertyDescriptor[] pds = src.getPropertyDescriptors();

        Set<String> emptyNames = new HashSet<>();
        for (PropertyDescriptor pd : pds) {
            Object srcValue = src.getPropertyValue(pd.getName());
            if (srcValue == null) {
                emptyNames.add(pd.getName());
            }
        }

        String[] result = new String[emptyNames.size()];
        return emptyNames.toArray(result);
    }
}
