package cn.estsh.i3plus.pojo.aps.common;

import cn.estsh.i3plus.pojo.base.bean.BaseBean;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Predicate;

/**
 * @Description :关联关系工具类
 *
 * @Reference :
 * @Author : jason.niu
 * @CreateDate : 2019-09-17
 * @Modify:
 **/
public class BeanRelation {
    Map<Class<? extends BaseBean>, Map<Long, Map<Enum<?>, List<BaseBean>>>> caches = new HashMap<>();

    private void init() {
        for (Class<? extends BaseBean> cls : BeanInfo.getBeanClasses()) {
            caches.put(cls, new ConcurrentHashMap<>());
        }
    }

    private static Map<Long, BeanRelation> relations = new ConcurrentHashMap<>();
    private static BeanRelation get() {
        Long userId = 0l;
        BeanRelation relation = relations.get(userId);
        if (relation == null) {
            synchronized (BeanRelation.class) {
                relation = relations.get(userId);
                if (relation == null) {
                    relation = new BeanRelation();
                    relation.init();
                    relations.put(userId, relation);
                }
            }
        }

        return relation;
    }

    private static Map<Enum<?>, List<BaseBean>> createRelation(Class<? extends BaseBean> cls) {
        Map<Enum<?>, List<BaseBean>> result = new HashMap<>();
        BeanInfo beanInfo = BeanInfo.getBeanInfo(cls);
        if (beanInfo == null) {
            return result;
        }
        for (Enum<?> holder : beanInfo.getAllHolders()) {
            result.put(holder, new LinkedList<>());
        }
        return result;
    }

    public static Map<Enum<?>, List<BaseBean>> get(BaseBean bean) {
        Map<Enum<?>, List<BaseBean>> temp = get().caches.get(bean.getClass()).get(bean.getId());
        if (temp == null) {
            synchronized (bean.getClass()) {
                if (temp == null) {
                    temp = createRelation(bean.getClass());
                    get().caches.get(bean.getClass()).put(bean.getId(), temp);
                }
            }
        }
        return temp;
    }

    /**
     * 获取关联对象
     * @param bean  实体对象
     * @param holder   关联路径
     * @param <T> 返回关联对象的实体类型
     * @return 若存在多个关联则只取第一个。
     */
    public static <T extends BaseBean> T get(BaseBean bean, Enum<?> holder) {
        List<T> beans = (List<T>)get(bean).get(holder);
        if (beans == null || beans.isEmpty()) {
            return null;
        }
        return beans.get(0);
    }

    /**
     * 获取关联对象
     * @param bean 实体对象
     * @param holder    第一个关联路径
     * @param args   后续关联路径
     * @param <T> 返回关联对象的实体类型
     * @return 若存在多个关联则只取第一个。
     */
    public static <T extends BaseBean> T get(BaseBean bean, Enum<?> holder, Enum<?>... args) {
        return get(bean, null, holder, args);
    }

    /**
     * 根据筛选条件获取关联对象
     * @param bean  实体对象
     * @param pred  Lambda表达式，用来筛选关联对象，返回true则符合筛选条件
     * @param holder 第一个关联路径
     * @param args  后续关联路径
     * @param <T>   返回关联对象的实体类型
     * @return 只找第一个符合筛选条件的关联对象。
     */
    public static <T extends BaseBean> T get(BaseBean bean, Predicate<T> pred, Enum<?> holder, Enum<?>... args) {
        List<BaseBean> nextBeans = list(bean, holder);
        for (BaseBean nextEntity : nextBeans) {
            T result = getImpl(nextEntity, pred, args, 0);
            if (result != null) {
                return result;
            }
        }

        return null;
    }

    private static <T extends BaseBean> T getImpl(BaseBean bean, Predicate<T> pred, Enum<?>[] args, int index) {
        if (index >= args.length) {
            if (pred == null || pred.test((T)bean)) {
                return (T)bean;
            }
        } else {
            List<BaseBean> relaBeans = list(bean, args[index]);
            for (BaseBean relaBean : relaBeans) {
                T temp = getImpl(relaBean, pred, args, index + 1);
                if (temp != null) {
                    return temp;
                }
            }
        }

        return null;
    }

    /**
     * 获取关联对象集合
     * @param bean  实体对象
     * @param holder 关联路径
     * @param <T> 返回关联对象的实体类型
     * @return 返回的List集合非拷贝的，外界不能对其修改。
     */
    public static <T extends BaseBean> List<T> list(BaseBean bean, Enum<?> holder) {
        List<T> beans = (List<T>)get(bean).get(holder);
        if (beans == null) {
            return new ArrayList<>();
        }
        return beans;
    }

    /**
     * 获取关联对象集合
     * @param bean 实体对象
     * @param holder 第一个关联路径
     * @param args 后续关联路径
     * @param <T> 返回关联对象的实体类型
     * @return  返回的List集合为拷贝的，外界可以对其修改。
     */
    public static <T extends BaseBean> List<T> list(BaseBean bean, Enum<?> holder, Enum<?>... args) {
        return list(bean, null, holder, args);
    }

    /**
     * 获取符合筛选条件的关联对象集合
     * @param bean 实体对象
     * @param pred Lambda筛选表达式，返回true为筛选出来的对象。
     * @param holder 第一个关联路径
     * @param args 后续关联路径
     * @param <T> 返回关联对象的实体类型
     * @return 返回的List集合为拷贝的，外界可以对其修改。
     */
    public static <T extends BaseBean> List<T> list(BaseBean bean, Predicate<T> pred, Enum<?> holder, Enum<?>... args) {
        List<T> result = new ArrayList<>();
        List<BaseBean> nextBeans = list(bean, holder);
        for (BaseBean nextBean : nextBeans) {
            listImpl(result, nextBean, pred, args, 0);
        }

        return result;
    }

    public static <T extends BaseBean> List<T> listByClass(BaseBean bean, Class<T> cls, Enum<?> holder, Enum<?>... args) {
        List<T> beans = new ArrayList<>();
        List<BaseBean> nextBeans = list(bean, holder, args);
        for (BaseBean nextBean : nextBeans) {
            if (nextBean.getClass() == cls) {
                beans.add((T)nextBean);
            }
        }
        return beans;
    }

    private static <T extends BaseBean> void listImpl(List<T> result, BaseBean bean, Predicate<T> pred, Enum<?>[] holders, int index) {
        if (index >= holders.length) {
            if (pred == null || pred.test((T)bean)) {
                result.add((T)bean);
            }
        } else {
            List<BaseBean> nextBeans = list(bean, holders[index]);
            for (BaseBean nextBean : nextBeans) {
                listImpl(result, nextBean, pred, holders, index + 1);
            }
        }
    }

    public static <T extends BaseBean> List<T> lastList(BaseBean bean, Enum<?>... holders) {
        List<T> result = new ArrayList<>();
        lastListImpl(result, bean, bean, holders, 0);
        return result;
    }
    private final static <T extends BaseBean> boolean lastListImpl(List<T> result, BaseBean bean, BaseBean self,
                                                                   Enum<?>[] holders, int index) {
        if (index >= holders.length) {
            if (self == bean) {
                return false;
            }
            index = 0;
            self = bean;
        }

        boolean bNotLast = true;
        List<BaseBean> nextBeans = list(bean, holders[index]);
        for (BaseBean nextBean : nextBeans) {
            if (lastListImpl(result, nextBean, self, holders, index + 1)) {
                result.add((T)nextBean);
                bNotLast = false;
            }
        }
        return index == 0 && bNotLast;
    }

    /**
     * 获取关联对象集合，排除重复的对象
     * @param bean 实体对象
     * @param holder 第一个关联路径
     * @param args 后续关联路径
     * @param <T> 返回关联对象的实体类型
     * @return  返回的List集合为拷贝的，外界可以对其修改。
     */
    public static <T extends BaseBean> Set<T> listUnique(BaseBean bean, Enum<?> holder, Enum<?>... args) {
        Set<T> result = new HashSet<>();
        List<BaseBean> nextBeans = list(bean, holder);
        for (BaseBean nextBean : nextBeans) {
            listUniqueImpl(result, nextBean, args, 0);
        }
        result.remove(bean);
        return result;
    }

    private static <T extends BaseBean> void listUniqueImpl(Set<T> result, BaseBean bean, Enum<?>[] holders, int index) {
        if (index >= holders.length) {
            result.add((T)bean);
        } else {
            List<BaseBean> nextBeans = list(bean, holders[index]);
            for (BaseBean nextBean : nextBeans) {
                listUniqueImpl(result, nextBean, holders, index + 1);
            }
        }
    }


    /**
     * 设值两个对象之间的关联
     *
     * @param bean
     * @param holder
     * @param relaBean
     */
    public static void set(BaseBean bean, Enum<?> holder, BaseBean relaBean) {
        if (bean == null || holder == null) {
            return;
        }
        if (relaBean == null) {
            remove(bean, holder);
        } else {
            BeanInfo beanInfo = BeanInfo.getBeanInfo(bean.getClass());
            final Enum<?> reverseHolder = beanInfo.getReverseHolder(holder);
            switch (beanInfo.getRelationType(holder)) {
                case MULTI_TO_MULTI:
                    break;
                case MULTI_TO_ONE:
                    remove(bean, holder);
                    break;
                case INVALID:
                    break;
                case ONE_TO_MULTI:
                    remove(relaBean, reverseHolder);
                    break;
                case ONE_TO_ONE:
                    remove(bean, holder);
                    remove(relaBean, reverseHolder);
                    break;
                default:
                    break;
            }

            setImpl(bean, holder, relaBean, reverseHolder);
        }
    }

    /**
     * 建立对象之间双向引用实现
     *
     * @param bean
     * @param holder
     * @param relaBean
     * @param reverseHolder
     */
    private static void setImpl(BaseBean bean, Enum<?> holder, BaseBean relaBean, Enum<?> reverseHolder) {
        get(bean).get(holder).add(relaBean);
        if (reverseHolder != null) {
            get(relaBean).get(reverseHolder).add(bean);
        }
    }

    /**
     * 移除关联
     *
     * @param bean
     * @param holder
     */
    public static void remove(BaseBean bean, Enum<?> holder) {
        if (bean == null || holder == null) {
            return;
        }
        BeanInfo beanInfo = BeanInfo.getBeanInfo(bean.getClass());
        final Enum<?> reverseHolder = beanInfo.getReverseHolder(holder);
        if (reverseHolder != null) {
            for (BaseBean relaBean : get(bean).get(holder)) {
                if (relaBean == null) {
                    continue;
                }
                get(relaBean).get(reverseHolder).remove(bean);
            }
        }
        get(bean).get(holder).clear();
    }

    /**
     * 移除关联的指定对象
     *
     * @param bean
     * @param holder
     * @param relaBean
     */
    public static void remove(BaseBean bean, Enum<?> holder, BaseBean relaBean) {
        if (bean == null || holder == null) {
            return;
        }
        BeanInfo beanInfo = BeanInfo.getBeanInfo(bean.getClass());
        final Enum<?> reverseHolder = beanInfo.getReverseHolder(holder);
        if (reverseHolder != null) {
            get(relaBean).get(reverseHolder).remove(bean);
        }
        get(bean).get(holder).remove(relaBean);
    }

    /**
     * 按指定路径，递归调用lambda表达式，当lambda表达式返回false或路径存在循环则退出递归。
     * @param bean
     * @param fun
     * @param holders
     * @param <T>
     */
    public static <T extends BaseBean> void recursion(BaseBean bean, Predicate<T> fun, Enum<?>... holders) {
        if (holders.length == 0) {
            return;
        }
        recursionImpl(bean, fun, holders, 0);
    }

    /**
     * 按指定路径，递归调用实现。采用广度优先搜索。
     * @param bean
     * @param fun
     * @param holders
     * @param index
     * @param <T>
     */
    @SuppressWarnings("unchecked")
    private final static <T extends BaseBean> void recursionImpl(BaseBean bean, Predicate<T> fun,
                                                                      Enum<?>[] holders, int index) {
        if (index >= holders.length) {
            if (!fun.test((T) bean)) {
                return;
            }
            index = 0;
        }

        List<BaseBean> relaBeans = list(bean, holders[index]);
        for (BaseBean relaBean : relaBeans) {
            if (relaBean.getId() != bean.getId()) {
                recursionImpl(relaBean, fun, holders, index + 1);
            }
        }
    }

    /**
     * 判断target是否为bean为起点的以holders路径上的点。
     * @param bean
     * @param target
     * @param holders
     * @return
     */
    public static boolean recursionContains(BaseBean bean, BaseBean target, Enum<?>... holders) {
        if (holders.length == 0) {
            return false;
        }

        return recursionContainsImpl(bean, target, holders, 0);
    }

    private final static boolean recursionContainsImpl(BaseBean bean, BaseBean target,
                                                                 Enum<?>[] holders, int index) {
        if (index >= holders.length) {
            if (target == bean) {
                return true;
            }
            index = 0;
        }

        List<BaseBean> relaBeans = list(bean, holders[index]);
        for (BaseBean relaBean : relaBeans) {
            if (recursionContainsImpl(relaBean, target, holders, index + 1)) {
                return true;
            }
        }

        return false;
    }
}
