package tmall.mapper.ORM;

import org.apache.commons.lang3.StringUtils;
import org.mybatis.spring.SqlSessionTemplate;
import tmall.pojo.base.BaseExample;
import tmall.annotation.ORMAnnotation.Enumerated;
import tmall.annotation.ORMAnnotation.JoinColumn;
import tmall.annotation.ORMAnnotation.ManyToOne;
import tmall.annotation.ORMAnnotation.OneToMany;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 *  通用 Mapper | 核心,处理一对多,多对一的插入
 */

@SuppressWarnings("unchecked")
public class Mapper4ORM {
    Object mapper;

    private Class mapperInterface;

    private SqlSessionTemplate sqlSessionTemplate;

    void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }

    void setMybatisMapper(Class mapperInterface) throws Exception {
        this.mapperInterface = mapperInterface;
        mapper = getMapper(mapperInterface);
    }

    public Object getMapper(Class mapperInterface) throws Exception {
        return sqlSessionTemplate.getMapper(mapperInterface);
    }

    public BaseExample getExample(Class mapperInterface) throws Exception {
        ParameterizedType t = (ParameterizedType) mapperInterface.getGenericInterfaces()[0];
        Class exampleClass = (Class) t.getActualTypeArguments()[1];
        return (BaseExample) exampleClass.newInstance();
    }

    public Class getMapperInterfaceByPOJO(Class POJOClass) throws Exception {
        ParameterizedType t = (ParameterizedType) POJOClass.getGenericInterfaces()[0];
        return (Class) t.getActualTypeArguments()[0];
    }

    /**
     * 获取一个类里的,有指定annotation的,所有 Filed
     *
     * @param objectClass     一个类
     * @param annotationClass 指定的 annotation
     * @return 所有的 Filed
     */
    List<Field> getFieldsEquals(Class objectClass, Class annotationClass) {
        if (objectClass == null) {
            return null;
        }
        List<Field> fields = new ArrayList<>();
        for (Class temp = objectClass; temp != Object.class; temp = temp.getSuperclass()) {
            fields.addAll(Arrays.asList(temp.getDeclaredFields()));
        }
        List<Field> result = new ArrayList<>();
        for (Field field : fields) {
            if (field.getAnnotation(annotationClass) != null)
                result.add(field);
        }
        return result;
    }

    /**
     * 读取时,处理所有 多对一 的填充
     *
     * @param object 被填充的对象
     * @param depth  当前深度
     * @throws Exception 反射异常
     */
    public void fillManyToOneOnReading(Object object, int depth) throws Exception {
        if (object == null) {
            return;
        }
        Class clazz = object.getClass();
        // 获取所有 ManyToOne注解的Filed
        List<Field> result = getFieldsEquals(clazz, ManyToOne.class);
        for (Field field : result) {
            //获取外键的表名
            String joinColumn = field.getAnnotation(JoinColumn.class).name();

            //获取要填充对象的mapper
            Class targetMapperClass = getMapperInterfaceByPOJO(field.getType());
            Object targetMapper = getMapper(targetMapperClass);
            //获取外键值
            Integer joinColumnValue = (Integer) clazz.
                    getMethod("get" + StringUtils.capitalize(joinColumn)).invoke(object);
            if (joinColumnValue == null) {
                continue;
            }
            //配置查询器example
            BaseExample example = getExample(targetMapperClass);
            Object criteria = example.createCriteria();
            // 配置criteria
            criteria.getClass().getMethod("andIdEqualTo", Integer.class).invoke(criteria, joinColumnValue);
            //查询,获取结果列表
            List targetResults = (List) targetMapper.getClass().getMethod("selectByExample", example.getClass()).
                    invoke(targetMapper, example);

            //判断是否为空 ,不为空插入 filed
            if (targetResults.size() > 0) {
                Object targetResult = targetResults.get(0);
                fillOnReading(targetResult, depth - 1);
                clazz.getMethod("set" + StringUtils.capitalize(field.getName()), targetResult.getClass())
                        .invoke(object, targetResult);
            }
        }
    }

    /**
     * 读取时,处理所有 一对多 的填充
     *
     * @param object 被填充的对象
     * @param depth  当前深度
     * @throws Exception 反射异常
     */
    public void fillOneToManyOnReading(Object object, int depth) throws Exception {
        if (object == null) {
            return;
        }
        Class clazz = object.getClass();
        // 获取所有 ManyToOne注解的Filed
        List<Field> result = getFieldsEquals(clazz, OneToMany.class);
        for (Field field : result) {
            //获取外键的表名
            String joinColumn = field.getAnnotation(JoinColumn.class).name();
            //得到其Generic的类型
            Type genericType = field.getGenericType();
            ParameterizedType pt = (ParameterizedType) genericType;
            //得到List泛型里的目标类型对象
            Class targetClass = (Class) pt.getActualTypeArguments()[0];
            //获取要填充对象的mapper
            Class targetMapperClass = getMapperInterfaceByPOJO(targetClass);
            Object targetMapper = getMapper(targetMapperClass);
            //获取外键值
            Integer joinColumnValue = (Integer) clazz.
                    getMethod("getId").invoke(object);
            //配置查询器example
            BaseExample example = getExample(targetMapperClass);
            Object criteria = example.createCriteria();
            // 配置criteria
            criteria.getClass().getMethod("and" + StringUtils.capitalize(joinColumn) + "EqualTo", Integer.class).invoke(criteria, joinColumnValue);
            //查询,获取结果列表
            List targetResults = (List) targetMapper.getClass().getMethod("selectByExample", example.getClass()).
                    invoke(targetMapper, example);
            for (int i = 0; i < targetResults.size(); i++) {
                Object item = targetResults.get(i);
                fillOnReading(item, depth - 1);
                targetResults.set(i, item);
            }
            //插入 filed
            clazz.getMethod("set" + StringUtils.capitalize(field.getName()), List.class)
                    .invoke(object, targetResults);

        }
    }

    /**
     * 读取时,处理所有 Enum 的填充
     *
     * @param object 被填充的对象
     * @throws Exception 反射异常
     */
    public void fillEnumOnReading(Object object) throws Exception {
        if (object == null) {
            return;
        }
        Class clazz = object.getClass();
        // 获取所有 ManyToOne注解的Filed
        List<Field> result = getFieldsEquals(clazz, Enumerated.class);
        for (Field field : result) {
            //获取Enum对应的,String类型的变量名
            String varName = field.getAnnotation(Enumerated.class).var();

            //获取值
            String enumString = (String) clazz.
                    getMethod("get" + StringUtils.capitalize(varName)).invoke(object);

            // 转成Enum,插回 filed
            Enum resultObj = Enum.valueOf((Class<Enum>) field.getType(), enumString);
            clazz.getMethod("set" + StringUtils.capitalize(field.getName()), resultObj.getClass())
                    .invoke(object, resultObj);

        }
    }

    /**
     * 写入时,处理所有 Enum 的填充
     *
     * @param object 被填充的对象
     * @throws Exception 反射异常
     */
    public void fillEnumOnWriting(Object object) throws Exception {
        if (object == null) {
            return;
        }
        Class clazz = object.getClass();
        // 获取所有 ManyToOne注解的Filed
        List<Field> result = getFieldsEquals(clazz, Enumerated.class);
        for (Field field : result) {
            //获取Enum对应的,String类型的变量名
            String varName = field.getAnnotation(Enumerated.class).var();

            //获取 Enum
            Enum enumObj = (Enum) clazz.
                    getMethod("get" + StringUtils.capitalize(field.getName())).invoke(object);

            // 转成 String,插回 varName
            String enumString = enumObj.name();
            clazz.getMethod("set" + StringUtils.capitalize(varName), String.class)
                    .invoke(object, enumString);
        }
    }

    /**
     * 写入时,处理所有 ManyToOne 的填充
     *
     * @param object 被填充的对象
     * @throws Exception 反射异常
     */
    public void fillManyToOneOnWriting(Object object) throws Exception {
        if (object == null) {
            return;
        }
        Class clazz = object.getClass();
        // 获取所有 ManyToOne注解的Filed
        List<Field> result = getFieldsEquals(clazz, ManyToOne.class);
        for (Field field : result) {
            //获取One端的变量名
            String columnName = field.getAnnotation(JoinColumn.class).name();

            //获取One的对象
            Object targetObj = clazz
                    .getMethod("get" + StringUtils.capitalize(field.getName()))
                    .invoke(object);
            if (targetObj == null) {
                continue;
            }
            //获取 获取 id 值
            int id = (int) targetObj.getClass().
                    getMethod("getId").invoke(targetObj);

            // 插回 columnName

            clazz.getMethod("set" + StringUtils.capitalize(columnName), Integer.class)
                    .invoke(object, id);
        }
    }

    /**
     * 读取时填充数据,递归调用上面的方法
     * @param object 对象
     * @param depth 当前递归深度
     * @throws Exception 反射异常
     */
    public void fillOnReading(Object object, int depth) throws Exception {
        if (object == null) {
            return;
        }
        if (depth <= 0) {
            return;
        }

        // 处理 ManyToOne
        fillManyToOneOnReading(object, depth);
        // 处理 OneToMany
        fillOneToManyOnReading(object, depth);
        // 处理 Enumerated
        fillEnumOnReading(object);

    }

    /**
     * 写入时填充数据,递归调用上面的方法
     * @param object 对象
     * @throws Exception 反射异常
     */
    public void fillOnWriting(Object object) throws Exception {
        if (object == null) {
            return;
        }
        // 处理 Enumerated
        fillEnumOnWriting(object);
        // 处理 ManyToOne
        fillManyToOneOnWriting(object);
    }
}