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

import cn.estsh.i3plus.pojo.aps.validator.IMainKeyValidatorImpl;
import cn.estsh.i3plus.pojo.base.bean.BaseBean;
import cn.estsh.i3plus.pojo.base.enumutil.ApsEnumUtil;

import java.sql.Timestamp;
import java.util.*;
import java.util.function.Consumer;

/**
 * @Description :APS 实体对象的元信息
 * 包含实体对象之间的关联关系信息。
 * @Reference :
 * @Author : jason.niu
 * @CreateDate : 2019-09-17
 * @Modify:
 **/
public class BeanInfo {
    private Class<? extends BaseBean> cls;
    private BeanInfo superBeanInfo;
    private List<BeanInfo> childsBeanInfos = new ArrayList<>();
    private Map<Enum<?>, RelationInfo> relations = new HashMap<>();

    public BeanInfo(Class<? extends BaseBean> cls) {
        this.cls = cls;
    }

    public void initHolder(Class<Enum<? extends Enum<?>>> holderCls) {
        Enum<? extends Enum<?>>[] ens = holderCls.getEnumConstants();
        for (Enum<? extends Enum<?>> en : ens) {
            RelationInfo info = new RelationInfo();
            info.setHolder(en);
            info.setBeanInfo(this);
            relations.put(en, info);
        }
    }

    public BeanInfo getSuperBeanInfo() { return superBeanInfo; }

    public boolean validHolder(Enum<?> holder) {
        return this.relations.get(holder) != null;
    }

    public Enum<?> getHolder(String name) {
        for (Enum<?> holder : relations.keySet()) {
            if (holder.name().equalsIgnoreCase(name)) {
                return holder;
            }
        }
        if (superBeanInfo != null) {
            return  superBeanInfo.getHolder(name);
        }

        return null;
    }

    public Enum<?> getReverseHolder(Enum<?> holder) {
        RelationInfo relaInfo = getRelationInfo(holder);
        if (relaInfo != null) {
            return relaInfo.getReverseHolder();
        }
        return null;
    }

    public Enum<?> getReverseHolder(String name) {
        RelationInfo relaInfo = getRelationInfo(name);
        if (relaInfo != null) {
            return relaInfo.getReverseHolder();
        }
        return null;
    }

    public RelationInfo getRelationInfo(Enum<?> holder) {
        if (validHolder(holder)) {
            return relations.get(holder);
        }
        if (superBeanInfo != null) {
            return  superBeanInfo.getRelationInfo(holder);
        }
        return null;
    }

    public RelationInfo getRelationInfo(String name) {
        Enum<?> holder = getHolder(name);
        if (holder != null) {
            return getRelationInfo(holder);
        }
        return null;
    }

    public void addRelationInfo(Enum<?> holder, RelationInfo info) {
        this.relations.put(holder, info);
    }

    public <T extends BaseBean> Class<T> getBeanClass() {
        return (Class<T>)cls;
    }

    public BeanInfo getRelationBeanInfo(Enum<?> holder) {
        RelationInfo relaInfo = getRelationInfo(holder);
        if (relaInfo != null) {
            return  relaInfo.getBeanInfo();
        }
        return null;
    }

    public BeanInfo getRelationBeanInfo(String name) {
        RelationInfo relaInfo = getRelationInfo(name);
        if (relaInfo != null) {
            return  relaInfo.getBeanInfo();
        }

        return null;
    }

    public Class<? extends BaseBean> getRelationClass(Enum<?> holder) {
        RelationInfo relaInfo = getRelationInfo(holder);
        if (relaInfo != null) {
            return relaInfo.getBeanInfo().getBeanClass();
        }
        return null;
    }

    public RELATION_TYPE getRelationType(Enum<?> holder) {
        RelationInfo relaInfo = getRelationInfo(holder);
        if (relaInfo != null) {
            return relaInfo.getType();
        }
        return RELATION_TYPE.INVALID;
    }

    public RELATION_TYPE getRelationType(String name) {
        RelationInfo relaInfo = getRelationInfo(name);
        if (relaInfo != null) {
            return relaInfo.getType();
        }

        return RELATION_TYPE.INVALID;
    }

    private void foreach(Consumer<RelationInfo> fun) {
        for (Map.Entry<Enum<?>, RelationInfo> entry : relations.entrySet()) {
            fun.accept(entry.getValue());
        }
        if (this.superBeanInfo != null) {
            this.superBeanInfo.foreach(fun);
        }
    }

    public List<Enum<?>> getAllHolders() {
        List<Enum<?>> holders = new ArrayList<>();
        foreach((RelationInfo info)->{
            holders.add(info.getHolder());
        });
        return holders;
    }

    public List<Enum<?>> getOwnerHolders() {
        List<Enum<?>> owners = new ArrayList<>();
        foreach((RelationInfo info)->{
            if (info.isOwner()) {
                owners.add(info.getHolder());
            }
        });
        return owners;
    }

    public List<Enum<?>> getNormalSigns() {
        List<Enum<?>> holders = new ArrayList<>();
        foreach((RelationInfo info)->{
            if (!info.isOwner()) {
                holders.add(info.getHolder());
            }
        });
        return holders;
    }

    private static Map<Class<? extends BaseBean>, BeanInfo> beanInfos = new HashMap<>();
    private static Map<String, BeanInfo> nameMapBeanInfos = new HashMap<>();
    private static IMainKeyValidatorImpl mainKeyValidator;

    static {
        BeanInfo beanInfo = new BeanInfo(BaseBean.class);
        beanInfos.put(BaseBean.class, beanInfo);
        nameMapBeanInfos.put(BaseBean.class.getSimpleName(), beanInfo);
    }

    public static void register(Class<? extends BaseBean> cls) {
        if (beanInfos.containsKey(cls)) {
            return;
        }
        BeanInfo beanInfo = new BeanInfo(cls);
        beanInfos.put(cls, beanInfo);
        nameMapBeanInfos.put(cls.getSimpleName(), beanInfo);
        register((Class<? extends BaseBean>)cls.getSuperclass());
    }

    public static void init() {
        for (Map.Entry<Class<? extends BaseBean>, BeanInfo> entry : beanInfos.entrySet()) {
            Class<? extends BaseBean> superClass = (Class<? extends BaseBean>)entry.getKey().getSuperclass();
            BeanInfo superBeanInfo = beanInfos.get(superClass);
            if (superBeanInfo != null) {
                entry.getValue().superBeanInfo = superBeanInfo;
                superBeanInfo.childsBeanInfos.add(entry.getValue());
            }
        }

        BeanRelationUtil.initData("cn.estsh.i3plus.pojo.aps.holders");
        BeanRelationUtil.loadConfig("relations");
    }

    public static Set<Class<? extends BaseBean>> getBeanClasses() {
        Set<Class<? extends BaseBean>> beanClasses = new HashSet<>();
        for (Map.Entry<Class<? extends BaseBean>, BeanInfo> entry : beanInfos.entrySet()) {
            beanClasses.add(entry.getKey());
        }

        return beanClasses;
    }

    public static BeanInfo getBeanInfo(Class<? extends BaseBean> cls) {
        BeanInfo info = beanInfos.get(cls);
        return info;
    }

    public static BeanInfo getBeanInfo(String name) {
        BeanInfo info = nameMapBeanInfos.get(name);
        return info;
    }

    public static Class<? extends BaseBean> getSuperClass(Class<? extends BaseBean> cls) {
        BeanInfo superBeanInfo = getBeanInfo(cls).superBeanInfo;
        if (superBeanInfo != null) {
            return superBeanInfo.getBeanClass();
        }
        return null;
    }

    public static List<Class<? extends BaseBean>> getChildClasses(Class<? extends BaseBean> cls) {
        List<Class<? extends BaseBean>> beanClasses = new ArrayList<>();
        List<BeanInfo> childBeanInfos = getBeanInfo(cls).childsBeanInfos;
        for (BeanInfo childBeanInfo : childBeanInfos) {
            beanClasses.add(childBeanInfo.getBeanClass());
        }
        return beanClasses;
    }

    public static ApsEnumUtil.FIELD_TYPE getFieldType(Class<?> cls) {
        ApsEnumUtil.FIELD_TYPE type = null;
        if (cls == Boolean.class || cls == boolean.class) {
            type = ApsEnumUtil.FIELD_TYPE.BOOLEAN;
        } else if (cls == Character.class) {
            type = ApsEnumUtil.FIELD_TYPE.CHAR;
        } else if (cls == Short.class || cls == short.class) {
            type = ApsEnumUtil.FIELD_TYPE.SHORT;
        } else if (cls == Integer.class || cls == int.class) {
            type = ApsEnumUtil.FIELD_TYPE.INTEGER;
        } else if (cls == Long.class || cls == long.class) {
            type = ApsEnumUtil.FIELD_TYPE.LONG;
        } else if (cls == Double.class || cls == Float.class ||
                   cls == double.class || cls == float.class) {
            type = ApsEnumUtil.FIELD_TYPE.DOUBLE;
        } else if (cls == String.class) {
            type = ApsEnumUtil.FIELD_TYPE.STRING;
        } else if(cls == Date.class || cls == java.sql.Date.class || cls == Timestamp.class) {
            type = ApsEnumUtil.FIELD_TYPE.DATE_TIME;
        } else if (cls == DateDuration.class) {
            type = ApsEnumUtil.FIELD_TYPE.DURATION;
        } else if (Enum.class.isAssignableFrom(cls)) {
            type = ApsEnumUtil.FIELD_TYPE.ENUM;
        } else if (BaseBean.class.isAssignableFrom(cls)) {
            type = ApsEnumUtil.FIELD_TYPE.OBJECT;
        }
        return type;
    }

    public static Class<?> getFieldClass(ApsEnumUtil.FIELD_TYPE type) {
        Class<?> cls = null;
        switch (type) {
            case BOOLEAN:
                cls = Boolean.class;
                break;
            case CHAR:
                cls = Character.class;
                break;
            case SHORT:
                cls = Short.class;
                break;
            case INTEGER:
                cls = Integer.class;
                break;
            case LONG:
                cls = Long.class;
                break;
            case DOUBLE:
                cls = Double.class;
                break;
            case DATE:
            case TIME:
            case DATE_TIME:
                cls = Date.class;
                break;
            case STRING:
                cls = String.class;
                break;
            case DURATION:
                cls = DateDuration.class;
                break;
            default:
                break;
        }

        return cls;
    }

    public static IMainKeyValidatorImpl getMainKeyValidator() { return mainKeyValidator; }

    public static void setMainKeyValidator(IMainKeyValidatorImpl impl) { mainKeyValidator = impl; }
}
