package com.persagy.adm.server.backstage.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.persagy.adm.server.algorithm.constant.AdmServerConstant;
import com.persagy.adm.server.algorithm.entity.AdmModelFile;
import com.persagy.adm.server.backstage.domain.AdmAnalysisModel;
import com.persagy.adm.server.backstage.domain.Location;
import com.persagy.adm.server.backstage.domain.Parameter;
import com.persagy.adm.server.backstage.domain.Point;
import com.persagy.adm.server.backstage.service.DataHandlerService;
import com.persagy.dmp.common.constant.DigitalObjectType;
import com.persagy.dmp.common.constant.DigitalObjectTypeShort;
import com.persagy.dmp.common.constant.GraphCodeEnum;
import com.persagy.dmp.common.constant.RelCodeEnum;
import com.persagy.dmp.common.helper.SpringHelper;
import com.persagy.dmp.common.model.entity.AuditableEntity;
import com.persagy.dmp.digital.entity.ObjectDigital;
import com.persagy.dmp.digital.entity.ObjectRelation;
import lombok.extern.slf4j.Slf4j;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.stream.Collectors.joining;

@Slf4j
public abstract class AbstractDataHandler implements DataHandlerService {

    private static final String SOURCE_ID="SourceId";
    private static final String ID="Id";
    private static final String LOCATION="Location";
    private static final String PARAMETERS="Parameters";
    private static final String OUTLINE="OutLine";
    private static final String NAME="Name";
    private static final String NAME_VALUE="名称";
    private static final String CODE_VALUE="编号";
    protected static final Map<String,String> ID_MAP = MapUtil
            .of(Pair.of(DigitalObjectType.PROJECT.getIndex(), DigitalObjectTypeShort.PROJECT.getIndex()),
            Pair.of(DigitalObjectType.BUILDING.getIndex(), DigitalObjectTypeShort.BUILDING.getIndex()),
            Pair.of(DigitalObjectType.FLOOR.getIndex(), DigitalObjectTypeShort.FLOOR.getIndex()),
            Pair.of(DigitalObjectType.SPACE.getIndex(), DigitalObjectTypeShort.SPACE.getIndex()),
            Pair.of(DigitalObjectType.SHAFT.getIndex(), DigitalObjectTypeShort.SHAFT.getIndex()),
            Pair.of(DigitalObjectType.SYSTEM.getIndex(), DigitalObjectTypeShort.SYSTEM.getIndex()),
            Pair.of(DigitalObjectType.PARTITION.getIndex(), DigitalObjectTypeShort.PARTITION.getIndex()),
            Pair.of(DigitalObjectType.EQUIPMENT.getIndex(), DigitalObjectTypeShort.EQUIPMENT.getIndex()),
            Pair.of(DigitalObjectType.COMPONENT.getIndex(), DigitalObjectTypeShort.COMPONENT.getIndex()));

    /**
     * 对模型数据进行解析
     * @param objectNodeMap : bimId与对象的映射
     * @param model : 模型数据
     * @param objects : 对象数据
     * @param classCode : 对象类型
     * return : void
     * @author : lijie
     * Update By 2022/1/22 14:10
     */
    @Override
    public void handleData(Map<String, ObjectNode> objectNodeMap, AdmAnalysisModel model, JSONArray objects,String classCode,String planName) {
        if (null==objects || objects.size()<=0){
            return;
        }
        int size = objects.size();
        List<ObjectNode> needAddObjects = model.getNeedAddObjects();
        if (CollUtil.isEmpty(needAddObjects)){
            needAddObjects = CollUtil.newArrayList();
            model.setNeedAddObjects(needAddObjects);
        }
        List<ObjectNode> needUpdateObjects = model.getNeedUpdateObjects();
        if (CollUtil.isEmpty(needUpdateObjects)){
            needUpdateObjects = CollUtil.newArrayList();
            model.setNeedUpdateObjects(needUpdateObjects);
        }
        Set<String> needDeleteObjectIds = model.getNeedDeleteObjectIds();
        if (CollUtil.isEmpty(needDeleteObjectIds)){
            needDeleteObjectIds = CollUtil.newHashSet();
            model.setNeedDeleteObjectIds(needDeleteObjectIds);
        }
        List<ObjectRelation> needAddRelations = model.getNeedAddRelations();
        if (CollUtil.isEmpty(needAddRelations)){
            needAddRelations = CollUtil.newArrayList();
            model.setNeedAddRelations(needAddRelations);
        }
        Set<String> needDeleteRelationObjIds = model.getNeedDeleteRelationObjIds();
        if (CollUtil.isEmpty(needAddRelations)){
            needDeleteRelationObjIds = CollUtil.newHashSet();
            model.setNeedDeleteRelationObjIds(needDeleteRelationObjIds);
        }
        model.setNeedDeleteRelationGraphCodes(CollUtil.newHashSet(GraphCodeEnum.MechInArch.name(),GraphCodeEnum.ArchSubset.name()));
        model.setNeedDeleteRelationRelCodes(CollUtil.newHashSet(RelCodeEnum.Sy2Bd.name(),RelCodeEnum.Sy2Fl.name(),
                RelCodeEnum.Eq2Bd.name(),RelCodeEnum.Eq2Fl.name(),RelCodeEnum.Sp2Bd.name(),RelCodeEnum.Sp2Fl.name()));
        AdmModelFile currentModelFile = model.getCurrentModelFile();
        ObjectMapper objectMapper = SpringHelper.getBean(ObjectMapper.class);
        String relClassCode = getClassCode(classCode);
        String relObjectType = getObjectType(relClassCode);
        // 获得历史所有的对象id
        Set<String> oldObjectIds = getOldObjectIdsByClassCode(objectNodeMap,relClassCode);
        Set<String> updateObjectIds = new HashSet<>();
        for (int i = 0; i < size; i++) {
            JSONObject element = objects.getJSONObject(i);
            if (StrUtil.isBlank(element.getString(SOURCE_ID))){
                continue;
            }
            // 1.特殊字段特殊处理
            ObjectNode objectNode = handleSpecificField(element,objectNodeMap,currentModelFile,objectMapper,relClassCode,relObjectType);
            // 2.移除不需要的字段
            removeUnnecessaryField(element);
            // 3.剩余字段的处理
            Set<String> keySet = element.keySet();
            for (String key : keySet) {
                objectNode.putPOJO(StrUtil.lowerFirst(key),element.get(key));
            }
            // 4.放入数组中
            if (!objectNode.has(AuditableEntity.PROP_ID)){
                String generateId = generateId(relObjectType, currentModelFile.getProjectId());
                objectNode.put(AuditableEntity.PROP_ID,generateId);
                needAddObjects.add(objectNode);
                // 创建关系对象
                createNeedAddRelation(generateId,currentModelFile,needAddRelations,relObjectType,relClassCode);
                continue;
            }
            needUpdateObjects.add(objectNode);
            updateObjectIds.add(objectNode.get(AuditableEntity.PROP_ID).asText());
            needDeleteRelationObjIds.add(objectNode.get(AuditableEntity.PROP_ID).asText());
            // 创建关系对象
            createNeedAddRelation(objectNode.get(AuditableEntity.PROP_ID).asText(),currentModelFile,needAddRelations,relObjectType,relClassCode);
        }
        // 获得需删除的对象id
        oldObjectIds.removeAll(updateObjectIds);
        needDeleteObjectIds.addAll(oldObjectIds);
    }

    /**
     * 获得对象分类
     * @param classCode :对象类型编码
     * @return : java.lang.String
     * @author : lijie
     * Update By 2022/1/22 16:33
     */
    protected abstract String getObjectType(String classCode);
    /**
     * 生成对象id
     * @param objType : 对象类型
     * @param projectId : 项目id
     * @return : java.lang.String
     * @author : lijie
     * Update By 2022/1/22 16:33
     */
    protected String generateId(String objType,String projectId){
        return Stream.of(ID_MAP.get(objType),
                StrUtil.replace(projectId,DigitalObjectTypeShort.PROJECT.getIndex(),StrUtil.EMPTY),
                IdWorker.getIdStr()).filter(ObjectUtil::isNotNull).collect(Collectors.joining(StrUtil.EMPTY));
    }

    /**
     * 获得对象类型
     * @param classCode :对象类型编码
     * @return : java.lang.String
     * @author : lijie
     * Update By 2022/1/22 16:16
     */
    protected String getClassCode(String classCode){
        return classCode;
    }

    /**
     * 生成cADID
     * @param localId : 本地编码
     * @param localName : 本地名称
     * @return : java.lang.String
     * @author : lijie
     * Update By 2022/1/22 15:56
     */
    protected String generateCadId(String localId, String localName){
        return Stream.of(localId,localName).filter(ObjectUtil::isNotNull).collect(joining(StrUtil.COLON));
    }

    /**
     * 获得outline
     * @param srcOutline : 原有的轮廓
     * @return : com.alibaba.fastjson.JSONArray
     * @author : lijie
     * Update By 2022/1/22 15:55
     */
    protected JSONArray generateOutline(JSONArray srcOutline){
        return srcOutline;
    }

    /**
     * 获得bimLocation
     * @param location : 位置信息
     * @return : java.lang.String
     * @author : lijie
     * Update By 2022/1/22 15:54
     */
    protected String generateBimLocation(Location location){
        if (null==location || CollUtil.isEmpty(location.getPoints())){
            return null;
        }
        Point point = location.getPoints().get(0);
        return Stream.of(point.getX(), point.getY(), point.getZ())
                .filter(ObjectUtil::isNotNull)
                .map(ObjectUtil::toString)
                .collect(joining(StrUtil.COMMA));
    }

    /**
     * 生成本地编码
     * @param sourceId : 模型唯一标识
     * @return : java.lang.String
     * @author : lijie
     * Update By 2022/1/22 15:38
     */
    protected String generateLocalId(String sourceId,JsonNode localId){
        if (null!=localId && localId.isTextual()){
            return localId.asText();
        }
        return sourceId;
    }

    /**
     * 生成bimId
     * @param projectId : 项目id
     * @param floorId : 楼层id
     * @param sourceId : 模型文件中的唯一id
     * @return : java.lang.String
     * @author : lijie
     * Update By 2022/1/22 15:20
     */
    protected String generateBimId(String projectId, String floorId, String sourceId){
        return StrUtil.join(StrUtil.COLON,StrUtil.join(StrUtil.EMPTY,projectId,floorId),sourceId);
    }

    /**
     * 根据参数生成本地名称
     * @param parameters : 参数[{"Name":"名称","Value":"KT-17"},{"Name":"编号","Value":"13"}]
     * @param name : 对象名称
     * @return : java.lang.String
     * @author : lijie
     * Update By 2022/1/22 15:14
     */
    protected String generateLocalName(JSONArray parameters, String name, JsonNode localName,String sourceId){
        if (null!=localName && localName.isTextual()){
            return localName.asText();
        }
        if (null==parameters || parameters.size()<=0){
            return Stream.of(name,sourceId).filter(ObjectUtil::isNotNull).collect(joining(StrUtil.DASHED));
        }
        List<Parameter> list = parameters.toJavaList(Parameter.class);
        Optional<Parameter> nameParam = list.stream().filter(parameter -> NAME_VALUE.equals(parameter.getName())).findFirst();
        Optional<Parameter> codeParam = list.stream().filter(parameter -> CODE_VALUE.equals(parameter.getName())).findFirst();
        return StrUtil.join(StrUtil.EMPTY,nameParam.isPresent()?nameParam.get().getValue():name,codeParam.isPresent()?codeParam.get().getValue():StrUtil.EMPTY);
    }
    /**
     * 根据对象类型获取旧对象id数组
     * @param objectNodeMap : 旧对象映射
     * @param classCode : 对象类型
     * @return : java.util.Set<java.lang.String>
     * @author : lijie
     * Update By 2022/1/22 17:17
     */
    private Set<String> getOldObjectIdsByClassCode(Map<String, ObjectNode> objectNodeMap, String classCode){
        Set<String> result = new HashSet<>();
        Set<String> keySet = objectNodeMap.keySet();
        for (String key : keySet) {
            if (!key.endsWith(classCode)){
                continue;
            }
            ObjectNode objectNode = objectNodeMap.get(key);
            if (null==objectNode
                    ||!objectNode.has(AuditableEntity.PROP_ID)
                    ||!objectNode.get(AuditableEntity.PROP_ID).isTextual()){
               continue;
            }
            result.add(objectNode.get(AuditableEntity.PROP_ID).asText());
        }
        return result;
    }
    /**
     * 处理特殊字段
     * @param element : 模型对象
     * @param objectNodeMap : 旧模型对象映射
     * @param currentModelFile : 当前模型文件信息
     * @param objectMapper : ...
     * @return : com.fasterxml.jackson.databind.node.ObjectNode
     * @author : lijie
     * Update By 2022/1/22 18:29
     */
    private ObjectNode handleSpecificField(JSONObject element, Map<String, ObjectNode> objectNodeMap,
                                           AdmModelFile currentModelFile, ObjectMapper objectMapper,
                                           String classCode,String objType){
        String sourceId = element.getString(SOURCE_ID);
        String bimId = generateBimId(currentModelFile.getProjectId(),currentModelFile.getFloorId(),sourceId);
        ObjectNode objectNode = objectNodeMap.getOrDefault(bimId+StrUtil.UNDERLINE+classCode, objectMapper.createObjectNode());
        JSONArray parameters = element.getJSONArray(PARAMETERS);
        Location location = element.getObject(LOCATION, Location.class);
        JSONArray srcOutline = element.getJSONArray(OUTLINE);
        String objName = element.getString(NAME);
        String localName = generateLocalName(parameters,objName,objectNode.get(AdmServerConstant.LOCAL_NAME),sourceId);
        String localId = generateLocalId(sourceId,objectNode.get(AdmServerConstant.LOCAL_ID));
        String bimLocation = generateBimLocation(location);
        JSONArray outline = generateOutline(srcOutline);
        String cadId = generateCadId(localId,localName);
        objectNode.put(AdmServerConstant.BIM_ID,bimId);
        objectNode.put(AdmServerConstant.NAME,objName);
        objectNode.put(AdmServerConstant.LOCAL_ID,localId);
        objectNode.put(AdmServerConstant.LOCAL_NAME,localName);
        objectNode.put(AdmServerConstant.BIM_LOCATION,bimLocation);
        objectNode.putPOJO(AdmServerConstant.OUTLINE,null!=outline?outline.toJSONString():null);
        objectNode.put(AdmServerConstant.CAD_ID,cadId);
        objectNode.put(AdmServerConstant.FLOOR_ID,currentModelFile.getFloorId());
        objectNode.put(AdmServerConstant.BUILDING_ID,currentModelFile.getBuildingId());
        objectNode.put(ObjectDigital.CLASS_CODE_HUM,classCode);
        objectNode.put(ObjectDigital.OBJ_TYPE_HUM,objType);
        return objectNode;
    }
    /**
     * 移除不需要的字段
     * @param element : 模型对象
     * return : void
     * @author : lijie
     * Update By 2022/1/22 18:32
     */
    private void removeUnnecessaryField(JSONObject element){
        element.remove(ID);
        element.remove(PARAMETERS);
        element.remove(LOCATION);
        element.remove(OUTLINE);
    }

    /**
     * 创建建筑下的设备/系统/空间,楼层下的设备/系统/空间关系
     * @param id : 主键id
     * @param currentModelFile : 当前模型文件
     * @param needAddRelations : 需添加的关系集合
     * @param objType : 对象类型编码
     * return : void
     * @author : lijie
     * Update By 2022/1/22 18:19
     */
    protected void createNeedAddRelation(String id, AdmModelFile currentModelFile,
                                         List<ObjectRelation> needAddRelations,
                                         String objType,String classCode){
        if (!CollUtil.newHashSet(DigitalObjectType.SPACE.getIndex(),
                DigitalObjectType.EQUIPMENT.getIndex(),
                DigitalObjectType.SYSTEM.getIndex()).contains(objType)){
            return;
        }
        if (DigitalObjectType.SPACE.getIndex().equals(objType)){
            needAddRelations.add(createObjectRelation(currentModelFile.getBuildingId(),id,
                    GraphCodeEnum.ArchSubset.name(), RelCodeEnum.Bd2Sp.name(),classCode,
                    currentModelFile.getGroupCode(),currentModelFile.getProjectId()));
            needAddRelations.add(createObjectRelation(currentModelFile.getFloorId(),id,
                    GraphCodeEnum.ArchSubset.name(), RelCodeEnum.Fl2Sp.name(),classCode,
                    currentModelFile.getGroupCode(),currentModelFile.getProjectId()));
            return;
        }
        if (DigitalObjectType.SYSTEM.getIndex().equals(objType)){
            needAddRelations.add(createObjectRelation(id,currentModelFile.getBuildingId(),
                    GraphCodeEnum.MechInArch.name(), RelCodeEnum.Sy2Bd.name(),classCode,
                    currentModelFile.getGroupCode(),currentModelFile.getProjectId()));
            needAddRelations.add(createObjectRelation(currentModelFile.getFloorId(),id,
                    GraphCodeEnum.MechInArch.name(), RelCodeEnum.Sy2Fl.name(),classCode,
                    currentModelFile.getGroupCode(),currentModelFile.getProjectId()));
            return;
        }
        if (DigitalObjectType.EQUIPMENT.getIndex().equals(objType)){
            needAddRelations.add(createObjectRelation(id,currentModelFile.getBuildingId(),
                    GraphCodeEnum.MechInArch.name(), RelCodeEnum.Eq2Bd.name(),classCode,
                    currentModelFile.getGroupCode(),currentModelFile.getProjectId()));
            needAddRelations.add(createObjectRelation(id,currentModelFile.getFloorId(),
                    GraphCodeEnum.MechInArch.name(), RelCodeEnum.Eq2Fl.name(),classCode,
                    currentModelFile.getGroupCode(),currentModelFile.getProjectId()));
        }
    }
    /**
     * 创建关系对象
     * @param objFrom : 关系左侧对象id
     * @param objTo : 关系右侧对象id
     * @param graphCode : 集团编码
     * @param relCode : 边关系
     * @param relValue : 边内容
     * @param groupCode : 集团编码
     * @param projectId : 项目id
     * @return : com.persagy.dmp.digital.entity.ObjectRelation
     * @author : lijie
     * Update By 2022/1/22 18:42
     */
    private ObjectRelation createObjectRelation(String objFrom, String objTo, String graphCode,
                                                String relCode, String relValue,String groupCode,String projectId){
        ObjectRelation objectRelation = new ObjectRelation();
        objectRelation.setObjFrom(objFrom);
        objectRelation.setObjTo(objTo);
        objectRelation.setGraphCode(graphCode);
        objectRelation.setGraphId(StrUtil.format("Gt{}001",graphCode));
        objectRelation.setGroupCode(groupCode);
        objectRelation.setProjectId(projectId);
        objectRelation.setRelCode(relCode);
        objectRelation.setRelValue(relValue);
        return objectRelation;
    }

}
