package com.sagacloud.cache.cachedObject;
/*
 * Author: Jxing
 * Create Time: 2018/8/2
 */

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.ehcache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.sagacloud.cache.EhCaches;
import com.sagacloud.cache.cachedObject.pojo.EquipToPropertyMap;
import com.sagacloud.cache.cachedObject.pojo.FinalJSONObjectMap;
import com.sagacloud.cache.cachedObject.pojo.InfoTableObjectMap;
import com.sagacloud.cache.cachedObject.pojo.ObjectTableObjectMap;
import com.sagacloud.cache.utils.CacheUtil;
import com.sagacloud.endpoints.Property;
import com.sagacloud.endpoints.item.PhysicalObject;
import com.sagacloud.utils.CommonMethods;
import com.sagacloud.utils.CommonUtil;
import com.sagacloud.utils.Const;
import com.sagacloud.utils.objects.ObjectType;
import com.sagacloud.utils.tables.ObjectTable;

import de.jkeylockmanager.manager.KeyLockManager;
import de.jkeylockmanager.manager.KeyLockManagers;

public class QueryObjects {
    private static final Logger log = LoggerFactory.getLogger(QueryObjects.class);
    static private final KeyLockManager lockManager = KeyLockManagers.newLock();
//    private static final String OBJECT_INFO_CACHE = "ObjectInfoTableCache";
//    private static final String PROPERTY_INFO_CACHE = "PropertyInfoTableCache";
    private static final String OBJECT_JSONOBJECT_CACHE = "ObjectJSONObjectCache";
    private static final String EQUIP_PROPERTY_CACHE = "EquipToPropertyCache";

    private Map<String, FinalJSONObjectMap> obj2jsonObj = new HashMap<>();
    private Map<String, EquipToPropertyMap> eq2property = new HashMap<>();

//    private CacheManager cacheManager;
    //前头不带Pj
    private String pjId;
    public QueryObjects(String pjId) {
        this.pjId = pjId;

                /*cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
                .withCache(ALL_OBJS_UNDER_PROJECT, CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, AllObjs.class,
                        ResourcePoolsBuilder.newResourcePoolsBuilder().heap(20, MemoryUnit.MB))
                        .build())
                .build(true);*/

        /*cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
                // key 取值是 ObjectType枚举类型
                .withCache(OBJECT_JSONOBJECT_CACHE, CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, FinalJSONObjectMap.class,
                        ResourcePoolsBuilder.heap(100)).build())
//                        ResourcePoolsBuilder.newResourcePoolsBuilder().heap(60, MemoryUnit.MB)).withSizeOfMaxObjectGraph(2000000)
//                .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(Duration.ofDays(1))).build())
                // key 是单一值:link
                .withCache(EQUIP_PROPERTY_CACHE, CacheConfigurationBuilder.newCacheConfigurationBuilder(String.class, EquipToPropertyMap.class,
                        ResourcePoolsBuilder.heap(30)).build())
//                        ResourcePoolsBuilder.newResourcePoolsBuilder().heap(15, MemoryUnit.MB)).withSizeOfMaxObjectGraph(1000000).build())
                .build(true);*/
    }

    /*private <K, V> Cache<K, V> getCache(String cacheName, Class<K> var2, Class<V> var3){
        return cacheManager.getCache(cacheName, var2, var3);
    }*/
    public static QueryObjects get(String pjId){
        Cache<String, QueryObjects> allObjsCache = EhCaches.getCache(EhCaches.CACHE_FOR_QUERY, String.class, QueryObjects.class);
        //double-check模式
        if (allObjsCache.get(pjId) == null) {
            lockManager.executeLocked(pjId, ()->{
                if (allObjsCache.get(pjId) == null){
                    QueryObjects allObjs = new QueryObjects(pjId);
                    allObjsCache.put(pjId, allObjs);
                }
            });
        }
        return allObjsCache.get(pjId);
    }

    /**
     * 获取对象表Map
     * @param pjId 项目Id
     * @return Map
     */
    public static ObjectTableObjectMap getObjectTableMap(String pjId) {
        Cache<String, ObjectTableObjectMap> objectMap = EhCaches.getCache(EhCaches.OBJECT_TABLE_CACHE, String.class, ObjectTableObjectMap.class);
        if (objectMap.get(pjId) == null) {
            lockManager.executeLocked(pjId, ()->{
                if (objectMap.get(pjId) == null){
                    ObjectTableObjectMap objTableObjectMap = new ObjectTableObjectMap();
                    objTableObjectMap.setObjectTableMap(CacheUtil.getObjectMap(pjId));
                    objectMap.put(pjId, objTableObjectMap);
                }
            });
        }
        return objectMap.get(pjId);
    }

    /**
     * 获取最终返回的JSONObject对象的缓存
     * @param typeList 类型数组
     * @return 指定类型的JSONObject数组
     */
    public Map<String, FinalJSONObjectMap> getFinalJSONObjectMap(ObjectType... typeList){
        Map<String, FinalJSONObjectMap> map = new HashMap<>();
//        Cache<String, FinalJSONObjectMap> jsonObjectCache = getCache(OBJECT_JSONOBJECT_CACHE, String.class, FinalJSONObjectMap.class);
        if(typeList == null)
            return map;
        JSONArray arr = new JSONArray();
        boolean isQueryProperty = false;
        for(int i = 0; i < typeList.length; ++i){
            if(typeList[i] == null)
                continue;
            if(typeList[i].equals(ObjectType.property)){
                isQueryProperty = true;
            }
            if(obj2jsonObj.get(typeList[i].name()) != null && ! map.containsKey(typeList[i].name())) {
                map.put(typeList[i].name(), obj2jsonObj.get(typeList[i].name()));
                if(typeList[i].equals(ObjectType.property))
                    isQueryProperty = false;
            }else if(obj2jsonObj.get(typeList[i].name()) == null && !typeList[i].equals(ObjectType.property))
                arr.add(typeList[i].name());
        }
        return getStringFinalJSONObjectMap(map, obj2jsonObj, arr, isQueryProperty);
    }

    public Map<String, FinalJSONObjectMap> getStringFinalJSONObjectMap(Map<String, FinalJSONObjectMap> map, Map<String, FinalJSONObjectMap>jsonObjectCache, JSONArray arr, boolean isQueryProperty) {
        if(arr.size() > 0) {
            JSONObject criteria = generateSubsetQueryCriteria(arr);
            PhysicalObject po = new PhysicalObject();
            String result = po.subsetQuery(CommonMethods.getProject(pjId, true), criteria);
            // 将结果按照cache的key分类
            Map<String, FinalJSONObjectMap> data = classifyData(result);
            // 保存进cache
            for(String type : data.keySet()){
                if(jsonObjectCache.get(type) == null) {
                    lockManager.executeLocked(type, () -> {
                        if (jsonObjectCache.get(type) == null) {
                            jsonObjectCache.put(type, data.get(type));
                        }
                    });
                }
                map.put(type, data.get(type));
            }
        }
        if(isQueryProperty){
            Property propertyEnd = new Property();
            JSONObject criteria = generatePropertyQueryCriteria();
            String result = propertyEnd.relationQuery(CommonMethods.getProject(pjId, true), JSON.toJSONString(criteria, SerializerFeature.WriteMapNullValue, SerializerFeature.DisableCircularReferenceDetect));
            Map<String, FinalJSONObjectMap> propertyData = classifyData(result);
            if(propertyData.size() != 0) {
                for (String type : propertyData.keySet()) {
                    if (jsonObjectCache.get(type) == null) {
                        lockManager.executeLocked(type, () -> {
                            if (jsonObjectCache.get(type) == null) {
                                jsonObjectCache.put(type, propertyData.get(type));
                            }
                        });
                    }
                    map.put(type, propertyData.get(type));
                }
            }else{
                lockManager.executeLocked(ObjectType.property.name(), () -> {
                    if (jsonObjectCache.get(ObjectType.property.name()) == null) {
                        jsonObjectCache.put(ObjectType.property.name(), new FinalJSONObjectMap());
                    }
                });
            }
        }
        return map;
    }

    /**
     * 获取最终返回的JSONObject对象的缓存
     * @param typeList 类型数组
     * @return 指定类型的JSONObject数组
     */
    public Map<String, FinalJSONObjectMap> getFinalJSONObjectMap(String[] typeList){
        Map<String, FinalJSONObjectMap> map = new HashMap<>();
//        Cache<String, FinalJSONObjectMap> obj2jsonObj = getCache(OBJECT_JSONOBJECT_CACHE, String.class, FinalJSONObjectMap.class);
        if(typeList == null)
            return map;
        JSONArray arr = new JSONArray();
        boolean isQueryProperty = false;
        for(int i = 0; i < typeList.length; ++i){
            if(typeList[i] == null)
                continue;
            if(typeList[i].equals(ObjectType.property.name()) && obj2jsonObj.get(ObjectType.property.name()) == null){
                isQueryProperty = true;
            }
            if(obj2jsonObj.get(typeList[i]) != null && ! map.containsKey(typeList[i])) {
                map.put(typeList[i], obj2jsonObj.get(typeList[i]));
                if(typeList[i].equals(ObjectType.property.name()))
                    isQueryProperty = false;
            }else if(obj2jsonObj.get(typeList[i]) == null && !typeList[i].equals(ObjectType.property.name()))
                arr.add(typeList[i]);
        }
        return getStringFinalJSONObjectMap(map, obj2jsonObj, arr, isQueryProperty);
    }
    private JSONObject generatePropertyQueryCriteria() {
        JSONObject criteria = new JSONObject();
        criteria.put("customInfo", true);
        criteria.put("valid", null);
        criteria.put("linkInfos", false);
        criteria.put("venderInfo", true);
        criteria.put("historyInfos", true);
        JSONObject obj = new JSONObject();
        obj.put("family", new JSONArray());
        criteria.put("criteria", obj);
        return criteria;
    }

    /**
     * 获取当前项目内岗位和资产的关联Map
     * @return 岗位和资产的关联Map
     */
    public Map<String, String> getEquipToPropertyLink(){
//        Cache<String, EquipToPropertyMap> cache = getCache(EQUIP_PROPERTY_CACHE, String.class, EquipToPropertyMap.class);
        InfoTableObjectMap map;
        String str = "link";
        //double-check模式
        if (eq2property.get(str) == null) {
            lockManager.executeLocked(str, () -> {
                if (eq2property.get(str) == null) {
                    Map<String, FinalJSONObjectMap> objectMap = getFinalJSONObjectMap(ObjectType.property);
                    Map<String, String> retMap = new HashMap<>();
                    if (objectMap.containsKey(ObjectType.property.name()))
                    {
                        FinalJSONObjectMap propertyMap = objectMap.get(ObjectType.property.name());
                        for (String id : propertyMap.getIdToJSONObject().keySet()) {
                            JSONObject property = propertyMap.getIdToJSONObject().get(id);
                            JSONObject relation = property.getJSONObject("relation");
                            if (relation == null)
                                continue;
                            String link = relation.getString("link");
                            if (link == null)
                                continue;
                            if (!retMap.containsKey(link)) {
                                retMap.put(link, id);
                            }
                        }
                    }
                    EquipToPropertyMap epmap = new EquipToPropertyMap();
                    epmap.setEpMap(retMap);
                    eq2property.put("link", epmap);
                }
            });
        }
        return eq2property.get("link").getEpMap();
    }

    private Map<String, FinalJSONObjectMap> classifyData(String result) {
        Map<String, FinalJSONObjectMap> data = new HashMap<>();
        try{
            JSONObject obj = JSONObject.parseObject(result);
            JSONArray arr = obj.getJSONArray("Content");
            for(int i = 0; i < arr.size(); ++i){
                JSONObject jsonObject = arr.getJSONObject(i);
                String id = jsonObject.getString("id");
                ObjectType ot = CacheUtil.getObjectTypeByIdPrefix(this.pjId, id.startsWith("VO")? id.substring(0, 4) : id.substring(0, 2));
                if(ot == null)
                    continue;
                FinalJSONObjectMap jsonObjectMap = null;
                if(data.get(ot.name()) == null){
                    jsonObjectMap = new FinalJSONObjectMap();
                    //Map<String, JSONObject> idToJSONObject = new HashMap<>();
                    Map<String, JSONObject> idToJSONObject = new ConcurrentHashMap<>();
                    jsonObjectMap.setIdToJSONObject(idToJSONObject);
                    data.put(ot.name(), jsonObjectMap);
                }else
                    jsonObjectMap = data.get(ot.name());
                if(!jsonObjectMap.getIdToJSONObject().containsKey(id)){
                    jsonObjectMap.getIdToJSONObject().put(id, jsonObject);
                }
            }
        }catch (Exception ignore){
        }
        return data;
    }

    private JSONObject generateSubsetQueryCriteria(JSONArray arr) {
        JSONObject criteria = new JSONObject();
        criteria.put("customInfo", true);
        criteria.put("valid", null);
        criteria.put("linkInfos", false);
        criteria.put("venderInfo", true);
        criteria.put("historyInfos", true);
        JSONObject obj = new JSONObject();
        JSONArray typeArr = new JSONArray();
        for(int i = 0; i < arr.size(); ++i){
            typeArr.add(CacheUtil.getIdPrefixByObjectType((String)arr.get(i)));
        }
        obj.put("type", typeArr);
        criteria.put("criteria", obj);
        return criteria;
    }

    /**
     * 清除QueryObjects内的缓存
     * @param msg 从activeMQ读取的消息(详见数据平台文档)
     */
    public static void clearCache(String msg) {
        try {
            JSONObject jsonMsg = JSONObject.parseObject(msg);
            int type = jsonMsg.getInteger("type");
            String projectId = jsonMsg.getString("projectId");
            String id = jsonMsg.getString("id");
            if(projectId == null && (id == null || id.startsWith("Pe")))
                return;
            else if(type != 7){
                JSONObject obj = new JSONObject();
                obj.put("id", id);
                CommonUtil.rewriteObjectTableCriteria(null, obj, "id");
                if(projectId == null) {
                    if(obj.get(ObjectTable.KEY_PJ_ID) == null)
                        return;
                    projectId = (String) obj.get(ObjectTable.KEY_PJ_ID);
                }
            }
            switch (type) {
                case 1:
                case 2:
                case 9:
                case 10:
                    clearCacheByName(EhCaches.OBJECT_TABLE_CACHE, CommonMethods.getProject(projectId, false), id);
                    clearCacheByName(OBJECT_JSONOBJECT_CACHE, CommonMethods.getProject(projectId, false), id);
                    if(id != null && (id.startsWith(Const.OBJ_TYPE_PROPERTY)|| id.startsWith(Const.OBJ_TYPE_EQUIPMENT)))
                        clearCacheByName(EQUIP_PROPERTY_CACHE, CommonMethods.getProject(projectId, false), id);
                    break;
                case 7:
                    JSONArray infos = jsonMsg.getJSONArray("infos");
                    if(infos == null || infos.size() == 0)
                        return;
                    boolean isContainProperty = false;
                    boolean isContainEquip = false;
                    Set<String> typeSet = new HashSet<>();
                    for(int i = 0; i < infos.size(); ++i){
                        JSONObject single = infos.getJSONObject(i);
                        String singleId = single.getString("id");
                        if(singleId != null && singleId.length() > 0){
                            ObjectType objType = CacheUtil.getObjectTypeByIdPrefix(projectId, singleId.startsWith("VO")? singleId.substring(0, 4) : singleId.substring(0, 2));
                            if(objType == null)
                                continue;
                            if(objType.equals(ObjectType.property))
                            {
                                isContainProperty = true;
                                isContainEquip = true;
                            }else if(objType.equals(ObjectType.equipment)){
                                isContainEquip = true;
                                isContainProperty = true;
                            }
                            if(!typeSet.contains(objType.name())){
                                typeSet.add(objType.name());
                            }
                        }
                    }
                    if(isContainEquip || isContainProperty){
                        if(!typeSet.contains(ObjectType.equipment.name()))
                            typeSet.add(ObjectType.equipment.name());
                        if(!typeSet.contains(ObjectType.property.name()))
                            typeSet.add(ObjectType.property.name());
                    }
                    for(String typeKey : typeSet){
                        clearCacheByName(OBJECT_JSONOBJECT_CACHE, CommonMethods.getProject(projectId, false), CacheUtil.getIdPrefixByObjectType(typeKey));
                    }
                    break;
                case 8:
                    //"Eq", "Sy", "Pj", "Bd", "Fl", "Sp","Si", "Ec", "Sh", "VOBd", "VOEi", "VOCu", "VOTn", "VOZn", "VOGp", "VOCt", "VOBc", "VOSe
                    clearCacheByName(OBJECT_JSONOBJECT_CACHE, CommonMethods.getProject(projectId, false),  "Eq");
                    clearCacheByName(OBJECT_JSONOBJECT_CACHE, CommonMethods.getProject(projectId, false),  "Ec");
                    clearCacheByName(OBJECT_JSONOBJECT_CACHE, CommonMethods.getProject(projectId, false),  "Pe");
                    clearCacheByName(EQUIP_PROPERTY_CACHE, CommonMethods.getProject(projectId, false), null);
//                    for(String typeName: PhysicalObject.allTypes){
//                        clearCacheByName(OBJECT_JSONOBJECT_CACHE, CommonMethods.getProject(projectId, false), typeName);
//                    }
                    break;
                case 11:
                    clearCacheByName(OBJECT_JSONOBJECT_CACHE, CommonMethods.getProject(projectId, false), id);
                    break;
            }
        }catch (Exception ex){
            log.error("清除缓存QueryObject失败:" + ex.getMessage() + msg);
        }
    }
    /**
     * 清除项目缓存
     * @param pjId 项目id：Pj1101010001
     */
    public static void clearCacheByProject(String pjId) {
		for(String typeName : PhysicalObject.allTypes){
			clearCacheByName(OBJECT_JSONOBJECT_CACHE, CommonMethods.getProject(pjId, false), typeName);
		}
		clearCacheByName(EQUIP_PROPERTY_CACHE, CommonMethods.getProject(pjId, false), null);
    }
    private static void clearCacheByName(String cacheName, String pjId, String idOrType){
        switch (cacheName){
            case OBJECT_JSONOBJECT_CACHE: // 3
                QueryObjects qo1 = get(pjId);
                if(qo1 == null)
                    break;
//                Cache<String, FinalJSONObjectMap> cache2 = qo1.getCache(cacheName, String.class, FinalJSONObjectMap.class);
                Map<String, FinalJSONObjectMap> cache2 = qo1.obj2jsonObj;
                if(cache2 == null)
                    break;
                if(idOrType != null && idOrType.length() > 0){
                    ObjectType ot = CacheUtil.getObjectTypeByIdPrefix(pjId, idOrType.startsWith("VO")? idOrType.substring(0, 4) : idOrType.substring(0, 2));
                    if(ot != null){
                        if(cache2.containsKey(ot.name()))
                            cache2.remove(ot.name());
                    }else
                        cache2.clear();
                }else
                    cache2.clear();
                break;
            case EQUIP_PROPERTY_CACHE:// 4
                QueryObjects qo3 = get(pjId);
                if(qo3 == null)
                    break;
//                Cache<String, EquipToPropertyMap> cache3 = qo3.getCache(cacheName, String.class, EquipToPropertyMap.class);
                Map<String, EquipToPropertyMap> cache3 = qo3.eq2property;
                if(cache3 == null)
                    break;
                if(cache3.containsKey("link"))
                    cache3.remove("link");
                break;
            case EhCaches.OBJECT_TABLE_CACHE: // 1
                Cache<String, ObjectTableObjectMap> cache4 = EhCaches.getCache(cacheName, String.class, ObjectTableObjectMap.class);
                if(cache4 == null)
                    break;
                if(cache4.containsKey(pjId))
                    cache4.remove(pjId);
                break;
        }
    }

}
