package com.bocloud.ims.service.common;

import java.util.ArrayList;
import java.util.List;

import com.bocloud.common.model.RequestContext;

/**
 * @param <Model>  driver返回的model数据类型
 * @param <Entity> db中对应的entity数据类型
 */
public abstract class AbstractSync<Model, Entity> {

    /**
     * 按业务上父级资源id查询该资源列表
     *
     * @param parentId 父级资源id
     */
    protected abstract List<Entity> listByParent(long parentId);

    /**
     * 保存entity
     *
     * @param entity  entity
     * @param context 当前用户id
     */
    protected abstract void save(Entity entity, RequestContext context);

    /**
     * 更新entity
     *
     * @param entity  entity
     * @param context 当前用户id
     */
    protected abstract void update(Entity entity, RequestContext context);

    /**
     * 删除entity，级联删除子资源，删的彻底
     *
     * @param entity  entity
     * @param context 当前用户id
     */
    public abstract void remove(Entity entity, RequestContext context);


    /**
     * 将model转换成entity
     *
     * @param model    driver返回的model数据
     * @param entity   db中对应的entity数据
     * @param parentId 该数据类型业务上的父数据的id
     */
    protected abstract void modelToEntity(Model model, Entity entity, long parentId);

    /**
     * 在同一个父级资源下，判断一个model和一个entity在业务上是否为同一个数据
     *
     * @param model  driver返回的model数据类型
     * @param entity db中对应的entity数据
     * @return true 是 false 否
     */
    protected abstract boolean modelEqualEntity(Model model, Entity entity);

    /**
     * 根据model插入或更新一个entity
     *
     * @param modelClass  driver返回的model数据类型
     * @param entityClass db中对应的entity数据类型
     * @param parentId    该数据类型业务上的父数据的id
     * @param model       driver返回的model数据
     * @param context     当前用户id
     * @return 返回创建或更新后的entity
     */
    public Entity syncOne(Class<Model> modelClass, Class<Entity> entityClass, long parentId, Model model, RequestContext context) {
        List<Entity> entities = listByParent(parentId);
        return syncOne(modelClass, entityClass, parentId, entities, model, context);
    }

    public Entity syncOne(Class<Model> modelClass, Class<Entity> entityClass, long parentId, List<Entity> entities, Model model, RequestContext context) {
        Entity entityFromDB = null;
        for (Entity entity : entities) {
            if (modelEqualEntity(model, entity)) {
                entityFromDB = entity;
                break;
            }
        }
        if (entityFromDB == null) {
            try {
                entityFromDB = entityClass.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                throw new RuntimeException("No constructor on " + entityClass.getName());
            }
            modelToEntity(model, entityFromDB, parentId);
            save(entityFromDB, context);
        } else {
            modelToEntity(model, entityFromDB, parentId);
            update(entityFromDB, context);
        }
        return entityFromDB;
    }

    /**
     * 根据model对entity进行全面更新
     *
     * @param modelClass  driver返回的model数据类型
     * @param entityClass db中对应的entity数据类型
     * @param parentId    该数据类型业务上的父数据的id
     * @param models      driver返回的model数据集合
     * @param context     当前用户id
     * @return 返回创建或更新后的entity集合
     */
    public List<Entity> syncAll(Class<Model> modelClass, Class<Entity> entityClass, long parentId, List<Model> models, RequestContext context) {
        if (models == null) {
            return null;
        }

        List<Entity> result = new ArrayList<>();

        List<Entity> allEntities = listByParent(parentId);
        List<Entity> existsEntities = new ArrayList<>();
        for (Model model : models) {
            result.add(syncOne(modelClass, entityClass, parentId, allEntities, model, context));
            for (Entity entity : allEntities) {
                if (modelEqualEntity(model, entity)) {
                    existsEntities.add(entity);
                    break;
                }
            }
        }
        allEntities.removeAll(existsEntities);
        for (Entity entity : allEntities) {
            remove(entity, context);
        }
        return result;
    }

}