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

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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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 ArrayList<>());
        }
        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;
    }

    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);
    }

    public static <T extends BaseBean> T get(BaseBean bean, Enum<?> holder, Enum<?>... args) {
        return get(bean, null, holder, args);
    }

    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;
    }

    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;
    }

    public static <T extends BaseBean> List<T> list(BaseBean bean, Enum<?> holder, Enum<?>... args) {
        return list(bean, null, holder, args);
    }

    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);
            }
        }
    }

    /**
     * 设值两个对象之间的关联
     *
     * @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
     */
    private 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
     */
    private 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);
    }

    /**
     * 删除对象
     *
     * @param bean
     */
    public static void delete(BaseBean bean) {
        if (bean == null) {
            return;
        }
        BeanInfo beanInfo = BeanInfo.getBeanInfo(bean.getClass());
        List<Enum<?>> ownerSigns = beanInfo.getOwnerHolders();
        for (Enum<?> holder : ownerSigns) {
            List<BaseBean> relaBeans = new ArrayList<>(list(bean, holder));
            for (BaseBean relaBean : relaBeans) {
                //MemoryManager.delete(relaBean);
            }
        }

        List<Enum<?>> normalSigns = beanInfo.getNormalSigns();
        for (Enum<?> holder : normalSigns) {
            Enum<?> reverseHolder = beanInfo.getReverseHolder(holder);
            List<BaseBean> relaBeans = new ArrayList<>(list(bean, holder));
            for (BaseBean relaBean : relaBeans) {
                if (reverseHolder != null) {
                    remove(relaBean, reverseHolder, bean);
                }
            }
        }
    }
}
