package com.sagacloud.utils.relationUtils;

import com.google.common.base.Strings;
import com.sagacloud.broadcast.MessageHandler;
import com.sagacloud.dict.EqStaticInfo;
import com.alibaba.fastjson.*;
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.CommonQueryCriteria;
import com.sagacloud.utils.jsonHelper.SubsetEntity;
import com.sagacloud.utils.tables.HBaseTable;
import com.sagacloud.utils.tables.ObjectTable;
import com.sagacloud.utils.tables.RelTable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * Created by Xiaoyu on 2018/3/28
 */
//{
//  "graph_type": "Subset",
//  "graph_name": "从属关系",
//  "Content": [
//      {
//        "rel_name": "建筑下的楼层",
//        "directional": true,
//        "target_objs": "",
//        "rel_type": "1",
//        "description": ""
//      },
//      {
//        "rel_name": "建筑下的空间",
//        "directional": true,
//        "target_objs": "",
//        "rel_type": "2",
//        "description": ""
//      },
//      {
//        "rel_name": "建筑下的系统设备部件",
//        "directional": true,
//        "target_objs": "",
//        "rel_type": "3",
//        "description": ""
//      },
//      {
//        "rel_name": "楼层下的空间",
//        "directional": true,
//        "target_objs": "",
//        "rel_type": "4",
//        "description": ""
//      },
//      {
//        "rel_name": "设备下的部件",
//        "directional": true,
//        "target_objs": "",
//        "rel_type": "5",
//        "description": ""
//      },
//      {
//        "rel_name": "建筑下的资产",
//        "directional": true,
//        "target_objs": "",
//        "rel_type": "6",
//        "description": ""
//      },
//      {
//        "rel_name": "楼层下的资产",
//        "directional": true,
//        "target_objs": "",
//        "rel_type": "7",
//        "description": ""
//      },
//      {
//        "rel_name": "建筑下的元空间",
//        "directional": true,
//        "target_objs": "",
//        "rel_type": "8",
//        "description": ""
//      },
//      {
//        "rel_name": "楼层下的元空间",
//        "directional": true,
//        "target_objs": "",
//        "rel_type": "9",
//        "description": ""
//      },
//  ]
//}
public class SubsetUtil {
    private static final Logger log = LoggerFactory.getLogger(SubsetUtil.class);
    private final static String graphType = "Subset";

    public static ArrayList<String> getCtime(String pjId, ArrayList<String> idList) {
        Hashtable<String, String> ctimeMap = new Hashtable<>();
        if (pjId.startsWith(Const.OBJ_TYPE_PROJECT))
            pjId.substring(2);
        ArrayList<String> result = new ArrayList<>();
        JSONArray criterias = new JSONArray();
        try {
            for (String id : idList) {
                JSONObject criteria = new JSONObject();
                criteria.put("id", id);
                CommonUtil.rewriteObjectTableCriteria(pjId, criteria, "id");
                criterias.add(criteria);
            }
            if (criterias.size() == 0) {
                return result;
            }
            JSONObject re = CommonUtil.combine_query(HBaseTable.objects, criterias);
            JSONArray content = (JSONArray) re.get(Const.JSON_FIELD_Content);
            for (int i = 0; i < content.size(); ++i) {
                JSONObject obj = (JSONObject) content.get(i);
                String id = CommonUtil.combineId(pjId, obj.get(ObjectTable.KEY_OBJ_TYPE).toString(), obj.get(ObjectTable.KEY_OBJ_ID).toString());
                ctimeMap.put(id, obj.get(ObjectTable.KEY_CREATE_TIME).toString());
            }
            for (String id : idList) {
                result.add(ctimeMap.get(id));
            }
        } catch (Exception e) {
            e.printStackTrace();
            return result;
        }
        return result;
    }

    public static ArrayList<String> getSuperSet(String pjId, String id, CommonQueryCriteria cqc) {
        Set<String> idList = new HashSet<>();
        idList.add(id);
        Hashtable<String, ArrayList<String>> tempResult = getSuperSet(pjId, idList, cqc);
        return tempResult.get(id);
    }

    public static Hashtable<String, ArrayList<String>> getSuperSet(String pjId, Set<String> idList, CommonQueryCriteria cqc) {
        //2019.11.13更改，系统可以属于多个建筑
        HashMap<String, List<String>> fatherMap = new HashMap<>();//child->father
        Hashtable<String, ArrayList<String>> result = new Hashtable<>();
        Hashtable<String, JSONObject> objMap = new Hashtable<>();
        if (pjId.startsWith(Const.OBJ_TYPE_PROJECT)) {
            pjId = pjId.substring(2);
        }
        String graphId = null;
        try {
            graphId = RelationUtil.getGraphInstance(pjId, "Subset", CommonUtil.getTime(), false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (graphId == null || idList.size() == 0) {//获取不到图实例
            return new Hashtable<>();
        }
        try {
            //1.根据cqc过滤id
            JSONArray criterias = new JSONArray();
            for (String id : idList) {
                JSONObject criteria = new JSONObject();
                criteria.put("id", id);
                CommonUtil.rewriteObjectTableCriteria(pjId, criteria, "id");
                criterias.add(criteria);
            }
            JSONObject re = CommonUtil.combine_query(HBaseTable.objects, criterias);
            JSONArray content = (JSONArray) re.get(Const.JSON_FIELD_Content);
            for (int i = 0; i < content.size(); ++i) {
                JSONObject obj = (JSONObject) content.get(i);
                String id = CommonUtil.combineId(pjId, obj.get(ObjectTable.KEY_OBJ_TYPE).toString(), obj.get(ObjectTable.KEY_OBJ_ID).toString());
                objMap.put(id, obj);
            }
            Set<String> tempIdList = new HashSet<>(idList);
            for (String id : tempIdList) {
                if (!filterCqc(id, objMap, cqc)) {
                    idList.remove(id);
                }
            }
            //1.end
            JSONArray criterias1 = genCriteriasForSurperset(pjId, graphId, idList);
            if (criterias1.size() == 0) {
                return new Hashtable<>();
            }
            re = CommonUtil.combine_query(HBaseTable.rel_btw_objs, criterias1);
            JSONArray content1 = (JSONArray) re.get(Const.JSON_FIELD_Content);
            Set<String> furtherQueryId = new HashSet<>();
            for (int i = 0; i < content1.size(); ++i) {
                JSONObject obj = (JSONObject) content1.get(i);
                String fromId = CommonUtil.combineId(pjId, obj.get(RelTable.KEY_FROM_OBJ_TYPE).toString(), obj.get(RelTable.KEY_FROM_OBJ_ID).toString());
                String toId = CommonUtil.combineId(pjId, obj.get(RelTable.KEY_TO_OBJ_TYPE).toString(), obj.get(RelTable.KEY_TO_OBJ_ID).toString());
                //2019.11.13更改，系统可以属于多个建筑
                if(!fatherMap.keySet().contains(toId)){
                    fatherMap.put(toId, new ArrayList<>());
                }
                fatherMap.get(toId).add( fromId);
                //如果查询出来的所属位置不是建筑，进行下一步查询
                if (!fromId.startsWith(Const.OBJ_TYPE_BUILDING)) {
                    furtherQueryId.add(fromId);
                }
            }
            //2.继续查询楼层所属建筑和设备所属建筑
            JSONArray criterias2 = genCriteriasForSurperset(pjId, graphId, furtherQueryId);
            if (criterias2.size() > 0) {
                re = CommonUtil.combine_query(HBaseTable.rel_btw_objs, criterias2);
                JSONArray content2 = (JSONArray) re.get(Const.JSON_FIELD_Content);
                for (int i = 0; i < content2.size(); ++i) {
                    JSONObject obj = (JSONObject) content2.get(i);
                    String fromId = CommonUtil.combineId(pjId, obj.get(RelTable.KEY_FROM_OBJ_TYPE).toString(), obj.get(RelTable.KEY_FROM_OBJ_ID).toString());
                    String toId = CommonUtil.combineId(pjId, obj.get(RelTable.KEY_TO_OBJ_TYPE).toString(), obj.get(RelTable.KEY_TO_OBJ_ID).toString());
                    //2019.11.13更改，系统可以属于多个建筑
                    if(!fatherMap.keySet().contains(toId)){
                        fatherMap.put(toId, new ArrayList<>());
                    }
                    fatherMap.get(toId).add( fromId);
                }
            }
            //2.end
            //整理结果
            for (String id : idList) {
                ArrayList<String> fatherList = new ArrayList<>();
                result.put(id, fatherList);
                List<String> fathers = fatherMap.get(id);
                //2019.11.13更改，系统可以属于多个建筑
                if (fathers != null) {
                    fatherList.addAll(fathers);
                    fathers.forEach(f -> {
                        if(fatherMap.keySet().contains(f)){
                            fatherList.addAll(fatherMap.get(f));
                        }
                    });
                }
            }
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return new Hashtable<>();
        }
    }

    private static JSONArray genCriteriasForSurperset(String pjId, String graphId, Set<String> idList) {
        JSONArray criterias = new JSONArray();
        for (String id : idList) {
            if (id.startsWith(Const.OBJ_TYPE_SPACE)) {
                criterias.add(genSubsetNode(pjId, graphId, "4", null, id));//楼层下空间
                criterias.add(genSubsetNode(pjId, graphId, "2", null, id));//建筑下空间
            } else if (id.startsWith(Const.OBJ_TYPE_ISPACE)) {
                criterias.add(genSubsetNode(pjId, graphId, "9", null, id));//楼层下元空间
                criterias.add(genSubsetNode(pjId, graphId, "8", null, id));//建筑下元空间
            } else if (id.startsWith(Const.OBJ_TYPE_FLOOR)) {
                criterias.add(genSubsetNode(pjId, graphId, "1", null, id));//建筑下楼层
            } else if (id.startsWith(Const.OBJ_TYPE_EQUIPMENT)) {
                criterias.add(genSubsetNode(pjId, graphId, "3", null, id));//建筑下设备
            } else if (id.startsWith(Const.OBJ_TYPE_SYSTEM)) {
                criterias.add(genSubsetNode(pjId, graphId, "3", null, id));//建筑下系统
            } else if (id.startsWith(Const.OBJ_TYPE_EQUIPMENT_COMPONENT)) {
                criterias.add(genSubsetNode(pjId, graphId, "3", null, id));//建筑下部件
                criterias.add(genSubsetNode(pjId, graphId, "5", null, id));//设备下部件
            }
        }
        return criterias;
    }

    /**
     * @param pjId
     * @param entities entity.fromId == null, 表示只和上级进行解绑，不对下级进行操作。entity.formId != null && en.toId != null，进行修改或添加关系操作
     * @return true成功，false失败
     */
    public static boolean modify(String pjId, List<SubsetEntity> entities) {
        if (pjId.startsWith(Const.OBJ_TYPE_PROJECT)) {
            pjId = pjId.substring(2);
        }
        try {
            String time = CommonUtil.getTime();
            String graphId = RelationUtil.getGraphInstance(pjId, graphType, time, true);
            JSONArray inserts = new JSONArray();
            JSONArray deletes = new JSONArray();
            for (SubsetEntity en : entities) {
                classify(pjId, en, inserts, deletes, graphId);
            }
            CommonUtil.delete(true, HBaseTable.rel_btw_objs, deletes);
            CommonUtil.insert(true, HBaseTable.rel_btw_objs, inserts);
            MessageHandler.publish(String.format("{\"type\":8,\"projectId\":\"%s\",\"name\":\"relationChange\",\"description\":\"\"}", CommonMethods.getProject(pjId, true)));
            MessageHandler.publish(String.format("{\"type\":14,\"projectId\":\"%s\",\"graphId\":\"%s\",\"description\":\"\"}", CommonMethods.getProject(pjId, true), graphId));
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

    }

    /**
     * 对象创建时调用此方法增加对象间关系。bd-->eq, bd-->fl, bd-->sp, ...
     */
    public static boolean addRel(String pjId, String fromId, String toId) {
        List<SubsetEntity> entities = new ArrayList<>(1);
        entities.add(new SubsetEntity(fromId, toId));
        return modify(pjId, entities);
    }

    /**
     * @param pjId    不包含Pj开头
     * @param en      en.fromId==null, 表示只和上级解绑
     * @param inserts
     * @param deletes
     * @param graphId
     */
    private static void classify(String pjId, SubsetEntity en, JSONArray inserts, JSONArray deletes, String graphId) {
        if (en.fromId == null && en.toId == null) {
            return;
        } else if (en.toId.startsWith(Const.OBJ_TYPE_FLOOR)) {//建筑->楼层
            JSONObject deleteNode = genSubsetNode(pjId, graphId, "1", null, en.toId);//删除楼层所属的建筑
            deletes.add(deleteNode);
            if (en.fromId != null && en.fromId.startsWith(Const.OBJ_TYPE_BUILDING)) {//添加新的所属建筑
                JSONObject insertNode = genSubsetNode(pjId, graphId, "1", en.fromId, en.toId);
                inserts.add(insertNode);
            }
        } else if (en.toId.startsWith(Const.OBJ_TYPE_SPACE)) {//楼层->空间 or 建筑->空间
            JSONObject deleteNode = genSubsetNode(pjId, graphId, "2", null, en.toId);//删除空间所属建筑
            deletes.add(deleteNode);
            deleteNode = genSubsetNode(pjId, graphId, "4", null, en.toId);//删除空间所属楼层
            deletes.add(deleteNode);
            if (en.fromId != null && en.fromId.startsWith(Const.OBJ_TYPE_BUILDING)) {//添加空间所属建筑
                JSONObject insertNode = genSubsetNode(pjId, graphId, "2", en.fromId, en.toId);
                inserts.add(insertNode);
            } else if (en.fromId != null && en.fromId.startsWith(Const.OBJ_TYPE_FLOOR)) {//添加空间所属楼层
                JSONObject insertNode = genSubsetNode(pjId, graphId, "4", en.fromId, en.toId);
                inserts.add(insertNode);
            }
        } else if (en.toId.startsWith(Const.OBJ_TYPE_ISPACE)) {//楼层->元空间 or 建筑->元空间
            JSONObject deleteNode = genSubsetNode(pjId, graphId, "8", null, en.toId);//删除元空间所属建筑
            deletes.add(deleteNode);
            deleteNode = genSubsetNode(pjId, graphId, "9", null, en.toId);//删除元空间所属楼层
            deletes.add(deleteNode);
            if (en.fromId != null && en.fromId.startsWith(Const.OBJ_TYPE_BUILDING)) {//添加元空间所属建筑
                JSONObject insertNode = genSubsetNode(pjId, graphId, "8", en.fromId, en.toId);
                inserts.add(insertNode);
            } else if (en.fromId != null && en.fromId.startsWith(Const.OBJ_TYPE_FLOOR)) {//添加元空间所属楼层
                JSONObject insertNode = genSubsetNode(pjId, graphId, "9", en.fromId, en.toId);
                inserts.add(insertNode);
            }
        } else if (en.toId.startsWith(Const.OBJ_TYPE_SYSTEM) || en.toId.startsWith(Const.OBJ_TYPE_EQUIPMENT)) {//建筑->系统，设备
            JSONObject deleteNode = genSubsetNode(pjId, graphId, "3", null, en.toId);//删除建筑下的系统或设备
            deletes.add(deleteNode);
            if (en.fromId != null && en.fromId.startsWith(Const.OBJ_TYPE_BUILDING)) {//添加系统或设备的所属建筑
                JSONObject insertNode = genSubsetNode(pjId, graphId, "3", en.fromId, en.toId);
                inserts.add(insertNode);
            }
        } else if (en.toId.startsWith(Const.OBJ_TYPE_EQUIPMENT_COMPONENT)) {//设备->部件 或者 建筑->部件
            JSONObject deleteNode = genSubsetNode(pjId, graphId, "3", null, en.toId);//删除建筑下的部件
            deletes.add(deleteNode);
            deleteNode = genSubsetNode(pjId, graphId, "5", null, en.toId);//删除设备下的部件
            deletes.add(deleteNode);
            if (en.fromId != null && en.fromId.startsWith(Const.OBJ_TYPE_BUILDING)) {//添加建筑下的部件
                JSONObject insertNode = genSubsetNode(pjId, graphId, "3", en.fromId, en.toId);
                inserts.add(insertNode);
            } else if (en.fromId != null && en.fromId.startsWith(Const.OBJ_TYPE_EQUIPMENT)) {//添加设备下的部件
                JSONObject insertNode = genSubsetNode(pjId, graphId, "5", en.fromId, en.toId);
                inserts.add(insertNode);
            }
        }
    }

    private static JSONObject genSubsetNode(String pjId, String graphId, String relType, String fromId, String toId) {
        JSONObject json = new JSONObject();
        json.put(RelTable.KEY_PJ_ID, pjId);
        json.put(RelTable.KEY_GRAPH_ID, graphId);
        json.put(RelTable.KEY_REL_TYPE, relType);
        if (fromId != null) {
            json.put(RelTable.KEY_FROM_OBJ_TYPE, fromId.substring(0, 2));
            json.put(RelTable.KEY_FROM_OBJ_ID, fromId.substring(12));
        }
        if (toId != null) {
            json.put(RelTable.KEY_TO_OBJ_TYPE, toId.substring(0, 2));
            json.put(RelTable.KEY_TO_OBJ_ID, toId.substring(12));
        }
        return json;
    }

    /**
     * @param pjId
     * @param fatherId Bd*** 返回 [Eq,Sp,Fl,Sy,Si]; Eq*** 返回 [Ec]; Fl*** 返回 [Sp,Si]
     * @return
     */
    public static Set<String> findChildrenType(String pjId, String fatherId) {
        Set<String> result = new HashSet<>();
        if (pjId.startsWith(Const.OBJ_TYPE_PROJECT)) {
            pjId = pjId.substring(2);
        }
        try {
            //如果fatherId是项目id
            if (fatherId.startsWith(Const.OBJ_TYPE_PROJECT)) {
                JSONObject criteria = new JSONObject();
                criteria.put(ObjectTable.KEY_PJ_ID, pjId);
                criteria.put(ObjectTable.COL_VALID, true);
                Long count = CommonUtil.getCount(HBaseTable.objects, criteria);
                if (count > 0) {
                    result.add("项目下有可用物理对象");
                }
                return result;
            }
            //如果fatherId不是项目id
            String graphId = RelationUtil.getGraphInstance(pjId, "Subset", CommonUtil.getTime(), false);
            if (graphId == null)
                return result;
            //1.从关系表查询出fatherId下的所有子节点
            JSONObject relQuery = genSubsetNode(pjId, graphId, null, fatherId, null);
            JSONObject relResult = CommonUtil.query(false, HBaseTable.rel_btw_objs, relQuery);
            JSONArray relContent = (JSONArray) relResult.get(Const.JSON_FIELD_Content);
            //1.end
            //2.查询obj表，验证所有对象valid是否为true
            if (relContent.size() > 0) {
                JSONArray objQuery = new JSONArray();
                //构建查询条件
                for (int i = 0; i < relContent.size(); ++i) {
                    JSONObject relNode = (JSONObject) relContent.get(i);
                    if (relNode.get(RelTable.KEY_TO_OBJ_TYPE).toString().startsWith(Const.OBJ_TYPE_PROPERTY))//不考虑资产
                        continue;
                    JSONObject objQueryNode = new JSONObject();
                    objQueryNode.put(ObjectTable.KEY_PJ_ID, pjId);
                    objQueryNode.put(ObjectTable.KEY_OBJ_TYPE, relNode.get(RelTable.KEY_TO_OBJ_TYPE));
                    objQueryNode.put(ObjectTable.KEY_OBJ_ID, relNode.get(RelTable.KEY_TO_OBJ_ID));
                    objQuery.add(objQueryNode);
                }
                //开始查询
                JSONObject objResult = CommonUtil.combine_query(HBaseTable.objects, objQuery);
                JSONArray objContent = (JSONArray) objResult.get(Const.JSON_FIELD_Content);
                //筛选valid为true的，并把类型放到result里
                for (int i = 0; i < objContent.size(); ++i) {
                    JSONObject obj = (JSONObject) objContent.get(i);
                    Boolean valid = (Boolean) obj.get(ObjectTable.COL_VALID);
                    if (valid != null && valid) {
                        result.add(obj.get(ObjectTable.KEY_OBJ_TYPE).toString());
                    }
                }
            }
            //2.end
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return new HashSet<>();
        }
    }

    /**
     * @param fromId 建筑id，楼层id，设备id；可查询 1.建筑下的[楼层，空间，元空间，系统，设备，部件，资产] 2.楼层下的[空间，元空间，资产] 3.设备下的[部件]
     * @param types  Fl，Sp，Sy，Eq，Ec，Si，Pe，两位系统编码，三位种族编码，四位设备编码，六位部件编码。如果types长度为0或者null，查询所有类型
     * @param cqc    可以为null，参考CommonQueryCriteria，只用到ctime和valid
     * @return e.g. ["SpId1","SpId2"]
     */
    public static ArrayList<String> getSubset(String pjId, String fromId, Set<String> types, CommonQueryCriteria cqc, EqStaticInfo eqStaticInfo) {
        ArrayList<String> result = new ArrayList<>();
        if (pjId.startsWith(Const.OBJ_TYPE_PROJECT)) {
            pjId = pjId.substring(2);
        }
        String graphId = null;
        try {
            graphId = RelationUtil.getGraphInstance(pjId, "Subset", CommonUtil.getTime(), false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (graphId == null) {
            return new ArrayList<>();
        }
        Hashtable<String, Hashtable<String, Set<String>>> subsetMap = new Hashtable<>();//父对象id -> type(Sp,Eq等) -> Set<子对象id>
        Set<String> bdSubtype = new HashSet<>();//查询建筑下的类型
        Set<String> flSubtype = new HashSet<>();//查询楼层下的类型
        Set<String> eqSubtype = new HashSet<>();//查询设备下的类型
        Set<String> rawTargetType = new HashSet<>();//真正要查询的粗略类型
        fillRawTypes(pjId, fromId, types, bdSubtype, flSubtype, eqSubtype, rawTargetType);
        if (fromId.startsWith(Const.OBJ_TYPE_BUILDING)) {//建筑可能需要查询两次
            Set<String> fromIds = new HashSet<>();
            fromIds.add(fromId);
            getBdSubSet(fromIds, bdSubtype, subsetMap, pjId, graphId);
            fromIds.clear();
            if (subsetMap.get(fromId) != null) {
                //查询空间时，再次查询楼层下空间
                if (rawTargetType.contains(Const.OBJ_TYPE_SPACE) && subsetMap.get(fromId).get(Const.OBJ_TYPE_FLOOR) != null) {
                    fromIds.addAll(subsetMap.get(fromId).get(Const.OBJ_TYPE_FLOOR));
                }
                //查询元空间时，再次查询楼层下元空间
                if (rawTargetType.contains(Const.OBJ_TYPE_ISPACE) && subsetMap.get(fromId).get(Const.OBJ_TYPE_FLOOR) != null) {
                    fromIds.addAll(subsetMap.get(fromId).get(Const.OBJ_TYPE_FLOOR));
                }
                //查询部件时，再次查询设备下的部件
                if (rawTargetType.contains(Const.OBJ_TYPE_EQUIPMENT_COMPONENT) && subsetMap.get(fromId).get(Const.OBJ_TYPE_EQUIPMENT) != null) {
                    fromIds.addAll(subsetMap.get(fromId).get(Const.OBJ_TYPE_EQUIPMENT));
                }
                //查询资产时，再次查询楼层下的资产
                if (rawTargetType.contains(Const.OBJ_TYPE_PROPERTY) && subsetMap.get(fromId).get(Const.OBJ_TYPE_FLOOR) != null) {
                    fromIds.addAll(subsetMap.get(fromId).get(Const.OBJ_TYPE_FLOOR));
                }
                getFlEqSubSet(fromIds, flSubtype, eqSubtype, subsetMap, pjId, graphId);
            }
        } else {
            Set<String> fromIds = new HashSet<>();
            fromIds.add(fromId);
            getFlEqSubSet(fromIds, flSubtype, eqSubtype, subsetMap, pjId, graphId);
        }
        //2.根据粗略类型，将所有id添加到result中
        Hashtable<String, Set<String>> rootMap = subsetMap.get(fromId);
        if (rootMap != null) {
            for (String rawType : rawTargetType) {
                //添加直接挂在fromId下的对象
                if (rootMap.get(rawType) != null) {
                    result.addAll(rootMap.get(rawType));
                }
                if (rawType.equals(Const.OBJ_TYPE_SPACE) || rawType.equals(Const.OBJ_TYPE_ISPACE)) {//查询空间和元空间
                    //如果fromId是建筑，先找到建筑下的楼层，再添加楼层下的空间
                    if (fromId.startsWith(Const.OBJ_TYPE_BUILDING) && rootMap.containsKey(Const.OBJ_TYPE_FLOOR)) {
                        for (String floorId : rootMap.get(Const.OBJ_TYPE_FLOOR)) {
                            if (subsetMap.get(floorId) != null && subsetMap.get(floorId).get(rawType) != null)
                                result.addAll(subsetMap.get(floorId).get(rawType));
                        }
                    }
                } else if (rawType.equals(Const.OBJ_TYPE_EQUIPMENT_COMPONENT)) {//查询部件
                    //如果fromId是建筑，先找到建筑下的设备，再添加设备下的部件
                    if (fromId.startsWith(Const.OBJ_TYPE_BUILDING) && rootMap.containsKey(Const.OBJ_TYPE_EQUIPMENT)) {
                        for (String eqId : rootMap.get(Const.OBJ_TYPE_EQUIPMENT)) {
                            if (subsetMap.get(eqId) != null && subsetMap.get(eqId).get(rawType) != null)
                                result.addAll(subsetMap.get(eqId).get(rawType));
                        }
                    }
                } else if (rawType.equals(Const.OBJ_TYPE_PROPERTY)) {
                    //如果fromId是建筑，先找到建筑下的资产，再添加楼层下的资产
                    if (fromId.startsWith(Const.OBJ_TYPE_BUILDING) && rootMap.containsKey(Const.OBJ_TYPE_FLOOR)) {
                        for (String floorId : rootMap.get(Const.OBJ_TYPE_FLOOR)) {
                            if (subsetMap.get(floorId) != null && subsetMap.get(floorId).get(rawType) != null)
                                result.addAll(subsetMap.get(floorId).get(rawType));
                        }
                    }
                }
            }
        }
        //2.end
        filter(pjId, result, types, cqc, eqStaticInfo);
        return result;
    }

    private static void filter(String pjId, ArrayList<String> idList, Set<String> types, CommonQueryCriteria cqc, EqStaticInfo eqStaticInfo) {
        Hashtable<String, String> typeMap = new Hashtable();//对象id -> 对象具体类型（type + subType）
        Hashtable<String, JSONObject> objMap = new Hashtable<>();
        JSONArray criterias = new JSONArray();
        try {
            for (String id : idList) {
                JSONObject criteria = new JSONObject();
                criteria.put("id", id);
                CommonUtil.rewriteObjectTableCriteria(pjId, criteria, "id");
                criterias.add(criteria);
            }
            if (criterias.size() == 0) {
                return;
            }
            JSONObject re = CommonUtil.combine_query(HBaseTable.objects, criterias);
            JSONArray content = (JSONArray) re.get(Const.JSON_FIELD_Content);
            for (int i = 0; i < content.size(); ++i) {
                JSONObject obj = (JSONObject) content.get(i);
                String id = CommonUtil.combineId(pjId, obj.get(ObjectTable.KEY_OBJ_TYPE).toString(), obj.get(ObjectTable.KEY_OBJ_ID).toString());
                String type = obj.get(ObjectTable.KEY_OBJ_TYPE).toString();
                String subType = Strings.nullToEmpty((String) obj.get(ObjectTable.COL_SUBTYPE));
                typeMap.put(id, type + subType);
                objMap.put(id, obj);
            }

            for (int i = idList.size() - 1; i >= 0; --i) {
                boolean valid = filterCqc(idList.get(i), objMap, cqc) && filterType(pjId, idList.get(i), typeMap, types, eqStaticInfo);
                if (!valid) {
                    idList.remove(i);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static boolean filterType(String pjId, String id, Hashtable<String, String> typeMap, Set<String> types, EqStaticInfo eqStaticInfo) {
        if (types == null || types.size() == 0)
            return true;
        String objType = typeMap.get(id);
        boolean valid = false;
        for (String targetType : types) {
            if (eqStaticInfo.getSysSet().contains(targetType)) {//具体系统类型,objType=Sy+2位系统编码
                valid = valid || objType.endsWith(targetType);
            } else if (eqStaticInfo.getEqSet().contains(targetType)) {//具体设备类型，objType=Eq+4位设备编码
                valid = valid || objType.endsWith(targetType);
            } else if (eqStaticInfo.getEcSet().contains(targetType)) {//具体部件编码，objType=Ec+6位部件编码
                valid = valid || objType.endsWith(targetType);
            } else if (eqStaticInfo.getFamilySet().contains(targetType)) {//具体资产编码，objType = Pe+3位种族编码
                valid = valid || objType.endsWith(targetType);
            }else if(PhysicalObject.bussinessSpaceTypeMap.containsKey(targetType)){
                if(targetType.equals("GeneralZone")){
                    //通用业务空间老数据subtype=null,新数据为GeneralZone，支持新老数据
                    valid = valid || objType.equals(Const.OBJ_TYPE_SPACE) || objType.endsWith(targetType);
                }else
                    valid = valid || objType.endsWith(targetType);
            } else {
                valid = valid || objType.startsWith(targetType);
            }
            if(valid)
                return true;
        }
        return false;
    }

    private static boolean filterCqc(String id, Hashtable<String, JSONObject> objMap, CommonQueryCriteria cqc) {
        if (cqc == null) return true;
        JSONObject obj = objMap.get(id);
        if (obj == null) return false;
        Boolean objValid = (Boolean) obj.get(ObjectTable.COL_VALID);
        String objCtime = (String) obj.get(ObjectTable.KEY_CREATE_TIME);
        boolean isValid = true;
        boolean isCtimeValid = true;
        if (cqc.getValid() != null && !cqc.getValid().equals(objValid)) {
            isValid = false;
        }
        if (cqc.getCtime() != null) {
            String timeRangeLow = (String) cqc.getCtime().get("$gte");
            if (timeRangeLow != null && (objCtime.compareTo(timeRangeLow) < 0))//objCtime小于timeRangeLow
                isCtimeValid = false;
            timeRangeLow = (String) cqc.getCtime().get("$gt");
            if (timeRangeLow != null && (objCtime.compareTo(timeRangeLow) <= 0))//objCtime小于等于timeRangeLow
                isCtimeValid = false;
            String timeRangeHigh = (String) cqc.getCtime().get("$lte");
            if (timeRangeHigh != null && (objCtime.compareTo(timeRangeHigh) > 0))//objCtime大于timeRangeHigh
                isCtimeValid = false;
            timeRangeHigh = (String) cqc.getCtime().get("$lt");
            if (timeRangeHigh != null && (objCtime.compareTo(timeRangeHigh) >= 0))//objCtime大于等于timeRangeHigh
                isCtimeValid = false;
        }
        return isValid && isCtimeValid;
    }

    /**
     * 将具体的设备、系统、部件类型变成Eq、Sy、Ec进行查询
     *
     * @param fromId
     * @param types      具体的类型，包括设备四位编码，系统两位编码，部件六位编码，种族三位编码等
     * @param bdSubtype  查询建筑下的类型，Eq，Sy等
     * @param flSubtype  查询楼层下的类型，Sp，Si等
     * @param eqSubtype  查询设备下的类型，Ec等
     * @param targetType 真正要查询的粗略类型
     */
    private static void fillRawTypes(String pjId, String fromId, Set<String> types, Set<String> bdSubtype, Set<String> flSubtype, Set<String> eqSubtype, Set<String> targetType) {
        //1.获取查询中要用到的类型。如果fromId是建筑，要进行两次查询，第一次用到bdSubtype，第二次用到flSubtype和eqSubtype；
        //如果fromId是楼层，只查询一次，用到flSubtype；如果formId是设备，只查询一次，用到eqSubtype。
        if (types == null || types.size() == 0) {//types中未指定类型，查询所有的
            if (fromId.startsWith(Const.OBJ_TYPE_BUILDING)) {
                bdSubtype.add(Const.OBJ_TYPE_FLOOR);
                bdSubtype.add(Const.OBJ_TYPE_SPACE);
                bdSubtype.add(Const.OBJ_TYPE_ISPACE);
                bdSubtype.add(Const.OBJ_TYPE_SYSTEM);
                bdSubtype.add(Const.OBJ_TYPE_EQUIPMENT);
                bdSubtype.add(Const.OBJ_TYPE_EQUIPMENT_COMPONENT);
                bdSubtype.add(Const.OBJ_TYPE_PROPERTY);
                flSubtype.add(Const.OBJ_TYPE_SPACE);
                flSubtype.add(Const.OBJ_TYPE_ISPACE);
                flSubtype.add(Const.OBJ_TYPE_PROPERTY);
                eqSubtype.add(Const.OBJ_TYPE_EQUIPMENT_COMPONENT);
                targetType.addAll(bdSubtype);
                targetType.addAll(flSubtype);
                targetType.addAll(eqSubtype);
            } else if (fromId.startsWith(Const.OBJ_TYPE_FLOOR)) {
                flSubtype.add(Const.OBJ_TYPE_SPACE);
                flSubtype.add(Const.OBJ_TYPE_ISPACE);
                flSubtype.add(Const.OBJ_TYPE_PROPERTY);
                targetType.addAll(flSubtype);
            } else if (fromId.startsWith(Const.OBJ_TYPE_EQUIPMENT)) {
                eqSubtype.add(Const.OBJ_TYPE_EQUIPMENT_COMPONENT);
                targetType.addAll(eqSubtype);
            }
        } else {
            EqStaticInfo eqStaticInfo = new EqStaticInfo(pjId);
            //types中指定了类型
            for (String type : types) {
                if (type.equals(Const.OBJ_TYPE_FLOOR)) {
                    bdSubtype.add(type);
                    targetType.add(type);
                } else if (type.equals(Const.OBJ_TYPE_SPACE) || PhysicalObject.bussinessSpaceTypeMap.containsKey(type)) {
                    if (fromId.startsWith(Const.OBJ_TYPE_BUILDING)) {
                        bdSubtype.add(Const.OBJ_TYPE_SPACE);
                    }
                    flSubtype.add(Const.OBJ_TYPE_SPACE);
                    targetType.add(Const.OBJ_TYPE_SPACE);
                } else if(type.equals(Const.OBJ_TYPE_ISPACE)){
                    if (fromId.startsWith(Const.OBJ_TYPE_BUILDING)) {
                        bdSubtype.add(type);
                    }
                    flSubtype.add(type);
                    targetType.add(type);
                }else if (type.equals(Const.OBJ_TYPE_SYSTEM) || eqStaticInfo.getSysSet().contains(type)) {
                    bdSubtype.add(Const.OBJ_TYPE_SYSTEM);
                    targetType.add(Const.OBJ_TYPE_SYSTEM);
                } else if (type.equals(Const.OBJ_TYPE_EQUIPMENT) || eqStaticInfo.getEqSet().contains(type)) {
                    bdSubtype.add(Const.OBJ_TYPE_EQUIPMENT);
                    targetType.add(Const.OBJ_TYPE_EQUIPMENT);
                } else if (type.equals(Const.OBJ_TYPE_EQUIPMENT_COMPONENT) || eqStaticInfo.getEcSet().contains(type)) {
                    if (fromId.startsWith(Const.OBJ_TYPE_BUILDING)) {
                        bdSubtype.add(Const.OBJ_TYPE_EQUIPMENT);
                    }
                    eqSubtype.add(Const.OBJ_TYPE_EQUIPMENT_COMPONENT);
                    targetType.add(Const.OBJ_TYPE_EQUIPMENT_COMPONENT);
                } else if (type.equals(Const.OBJ_TYPE_PROPERTY) || eqStaticInfo.getFamilySet().contains(type)) {
                    if (fromId.startsWith(Const.OBJ_TYPE_BUILDING)) {
                        bdSubtype.add(Const.OBJ_TYPE_PROPERTY);
                    }
                    flSubtype.add(Const.OBJ_TYPE_PROPERTY);
                    targetType.add(Const.OBJ_TYPE_PROPERTY);
                }
            }
        }
        //1.end
    }

    /**
     * 查询直接与建筑相关的物理对象
     *
     * @param fromIds   BdID
     * @param rawTypes  bdSubtype
     * @param subsetMap BdID->type->ObjID(e.g. BdID->Fl->FlID)
     * @param pjId      项目id，不带前缀Pj
     * @param graphId   图id
     */
    private static void getBdSubSet(Set<String> fromIds, Set<String> rawTypes, Hashtable<String, Hashtable<String, Set<String>>> subsetMap, String pjId, String graphId) {
        //1.构建查询条件
        JSONArray criterias = new JSONArray();
        for (String fromId : fromIds) {
            for (String type : rawTypes) {
                switch (type) {
                    case Const.OBJ_TYPE_FLOOR:
                        criterias.add(genSubsetNode(pjId, graphId, "1", fromId, null));
                        break;
                    case Const.OBJ_TYPE_SPACE:
                        criterias.add(genSubsetNode(pjId, graphId, "1", fromId, null));
                        criterias.add(genSubsetNode(pjId, graphId, "2", fromId, null));
                        break;
                    case Const.OBJ_TYPE_ISPACE:
                        criterias.add(genSubsetNode(pjId, graphId, "1", fromId, null));
                        criterias.add(genSubsetNode(pjId, graphId, "8", fromId, null));
                        break;
                    case Const.OBJ_TYPE_SYSTEM:
                        criterias.add(genSubsetNode(pjId, graphId, "3", fromId, null));
                        break;
                    case Const.OBJ_TYPE_EQUIPMENT:
                        criterias.add(genSubsetNode(pjId, graphId, "3", fromId, null));
                        break;
                    case Const.OBJ_TYPE_EQUIPMENT_COMPONENT://部件、设备与建筑是同一种关系，会先查出建筑下的设备
                        criterias.add(genSubsetNode(pjId, graphId, "3", fromId, null));
                        break;
                    case Const.OBJ_TYPE_PROPERTY:
                        criterias.add(genSubsetNode(pjId, graphId, "1", fromId, null));
                        criterias.add(genSubsetNode(pjId, graphId, "6", fromId, null));
                        break;
                    default:
                }
            }
        }
        //1.end
        try {
            if (criterias.size() == 0) {
                return;
            }
            JSONObject re = CommonUtil.combine_query(HBaseTable.rel_btw_objs, criterias);
            JSONArray content = (JSONArray) re.get(Const.JSON_FIELD_Content);
            for (int i = 0; i < content.size(); ++i) {
                JSONObject obj = (JSONObject) content.get(i);
                String fromId = CommonUtil.combineId(pjId, obj.get(RelTable.KEY_FROM_OBJ_TYPE).toString(), obj.get(RelTable.KEY_FROM_OBJ_ID).toString());
                String toId = CommonUtil.combineId(pjId, obj.get(RelTable.KEY_TO_OBJ_TYPE).toString(), obj.get(RelTable.KEY_TO_OBJ_ID).toString());
                String childType = obj.get(RelTable.KEY_TO_OBJ_TYPE).toString();
                if (!subsetMap.containsKey(fromId)) {
                    subsetMap.put(fromId, new Hashtable<>());
                }
                Hashtable<String, Set<String>> mapById = subsetMap.get(fromId);
                if (!mapById.containsKey(childType)) {
                    mapById.put(childType, new HashSet<>());
                }
                Set<String> childrenSet = mapById.get(childType);
                childrenSet.add(toId);
            }
        } catch (Exception e) {
            log.error("SubsetUtil.getBdSubSet:" + e.getMessage());
        }
    }

    private static void getFlEqSubSet(Set<String> fromIds, Set<String> flSubtypes, Set<String> eqSubtypes, Hashtable<String, Hashtable<String, Set<String>>> subsetMap, String pjId, String graphId) {
        JSONArray criterias = new JSONArray();
        for (String fromId : fromIds) {
            if (fromId.startsWith(Const.OBJ_TYPE_FLOOR)) {
                for (String type : flSubtypes) {
                    switch (type) {
                        case Const.OBJ_TYPE_SPACE:
                            criterias.add(genSubsetNode(pjId, graphId, "4", fromId, null));
                            break;
                        case Const.OBJ_TYPE_ISPACE:
                            criterias.add(genSubsetNode(pjId, graphId, "9", fromId, null));
                            break;
                        case Const.OBJ_TYPE_PROPERTY:
                            criterias.add(genSubsetNode(pjId, graphId, "7", fromId, null));
                            break;
                        default:
                    }
                }
            } else if (fromId.startsWith(Const.OBJ_TYPE_EQUIPMENT)) {
                for (String type : eqSubtypes) {
                    switch (type) {
                        case Const.OBJ_TYPE_EQUIPMENT_COMPONENT:
                            criterias.add(genSubsetNode(pjId, graphId, "5", fromId, null));
                            break;
                        default:
                    }
                }
            }
        }
        try {
            if (criterias.size() == 0) {
                return;
            }
            JSONObject re = CommonUtil.combine_query(HBaseTable.rel_btw_objs, criterias);
            JSONArray content = (JSONArray) re.get(Const.JSON_FIELD_Content);
            for (int i = 0; i < content.size(); ++i) {
                JSONObject obj = (JSONObject) content.get(i);
                String fromId = CommonUtil.combineId(pjId, obj.get(RelTable.KEY_FROM_OBJ_TYPE).toString(), obj.get(RelTable.KEY_FROM_OBJ_ID).toString());
                String toId = CommonUtil.combineId(pjId, obj.get(RelTable.KEY_TO_OBJ_TYPE).toString(), obj.get(RelTable.KEY_TO_OBJ_ID).toString());
                String childType = obj.get(RelTable.KEY_TO_OBJ_TYPE).toString();
                if (!subsetMap.containsKey(fromId)) {
                    subsetMap.put(fromId, new Hashtable<>());
                }
                Hashtable<String, Set<String>> mapById = subsetMap.get(fromId);
                if (!mapById.containsKey(childType)) {
                    mapById.put(childType, new HashSet<>());
                }
                Set<String> childrenSet = mapById.get(childType);
                childrenSet.add(toId);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
