package cc.smtweb.system.bpm.util;

import cc.smtweb.framework.core.common.SwMap;
import cc.smtweb.framework.core.exception.SwException;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.FatalBeanException;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class BeanUtil {
    private BeanUtil() {
    }

    public static void mapToBean(SwMap source, Object target) {
        mapToBean(source, target, null);
    }

    private static void mapToBean(SwMap source, Object target, @Nullable Class<?> editable,
                                  @Nullable String... ignoreProperties) throws BeansException {
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        Class<?> actualEditable = target.getClass();
        if (editable != null) {
            if (!editable.isInstance(target)) {
                throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
                        "] not assignable to Editable class [" + editable.getName() + "]");
            }
            actualEditable = editable;
        }
        PropertyDescriptor[] targetPds = BeanUtils.getPropertyDescriptors(actualEditable);
        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()))) {
                Object value = source.get(targetPd.getName());

                if (value == null || ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], value.getClass())) {
                    try {
                        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);
                    }
                }
            }
        }
    }

    public static void beanToMap(Object source, SwMap target, @Nullable String... ignoreProperties) throws BeansException {
        Assert.notNull(source, "Source must not be null");
        Assert.notNull(target, "Target must not be null");

        List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties): null);

        PropertyDescriptor[] sourcePds = BeanUtils.getPropertyDescriptors(source.getClass());

        for (PropertyDescriptor sourcePd : sourcePds) {
            if (ignoreList == null || !ignoreList.contains(sourcePd.getName())) {
//        PropertyDescriptor sourcePd = BeanUtils.getPropertyDescriptor(source.getClass(), targetPd.getKey());
//        if (sourcePd != null) {
                Method readMethod = sourcePd.getReadMethod();
                if (readMethod != null) {
                    try {
                        if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
                            readMethod.setAccessible(true);
                        }
                        Object value = readMethod.invoke(source);
                        target.put(sourcePd.getName(), value);
                    } catch (Throwable ex) {
                        throw new FatalBeanException(
                                "Could not copy property '" + sourcePd.getName() + "' from source to target", ex);
                    }
                }
//        }
            }
        }
    }

    public static List<?> toBeanList(List<SwMap> bodyList, Class<?> clazz) {
        List<Object> beans = new ArrayList<>(bodyList.size());
        try {
            for (SwMap body : bodyList) {
                Object bean = clazz.newInstance();
                BeanUtil.mapToBean(body, bean);
                beans.add(bean);
            }
        } catch (InstantiationException | IllegalAccessException e) {
            throw new SwException(e);
        }
        return beans;
    }

    public static List<SwMap> toMapList(List<?> beans) {
        List<SwMap> bodyList = null;
        if (beans != null) {
            bodyList = new ArrayList<>(beans.size());
            for (Object bean : beans) {
                SwMap body = new SwMap();
                beanToMap(bean, body);
                bodyList.add(body);
            }
        }

        return bodyList;
    }
}
