package com.persagy.proxy.calculation.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.persagy.dmp.basic.dto.RequestData;
import com.persagy.dmp.basic.model.QueryCriteria;
import com.persagy.dmp.common.constant.CommonConstant;
import com.persagy.dmp.common.helper.SpringHelper;
import com.persagy.dmp.common.model.response.CommonResult;
import com.persagy.dmp.digital.client.DigitalObjectFacade;
import com.persagy.dmp.digital.client.DigitalRelationCaclFacade;
import com.persagy.dmp.digital.client.DigitalRelationFacade;
import com.persagy.dmp.digital.entity.ObjectDigital;
import com.persagy.proxy.adm.constant.AdmObjectInfoConstant;
import com.persagy.proxy.adm.constant.AdmObjectType;
import com.persagy.proxy.adm.constant.AdmRelationTypeEnum;
import com.persagy.proxy.adm.model.TwicePointDTO;
import com.persagy.proxy.adm.request.AdmResponse;
import com.persagy.proxy.adm.utils.GeoToolsUtil;
import com.persagy.proxy.calculation.service.AdmRelCalService;
import com.persagy.proxy.common.entity.InstanceUrlParam;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.*;

@Service
@Slf4j
@RequiredArgsConstructor
public class AdmRelCalServiceImpl implements AdmRelCalService {

    /***
     * Description: 计算关系
     * @param context : 请求参数
     * @param admRelationTypeEnums : 关系类型集合
     //* @param objectTypeStr : 参与计算的业务空间(为空表示所有业务空间类型)
     //* @param relationTypeStr : 关系类型
     * return : void
     * @author : lijie
     * date :2021/9/11 20:35
     * Update By lijie 2021/9/11 20:35
     */
    @Override
    public void calcRelation(InstanceUrlParam context, List<AdmRelationTypeEnum> admRelationTypeEnums,String relValue) {
        for (AdmRelationTypeEnum admRelationTypeEnum : admRelationTypeEnums) {
            RequestData requestData = RequestData.builder()
                    .graphCode(admRelationTypeEnum.getGraphCode())
                    .relCode(admRelationTypeEnum.getRelCode())
                    .calBeforeRelFlag(true)
                    .build();
            if (StrUtil.isNotBlank(relValue)){
                requestData.setRelValue(relValue);
            }
            // 调用中台的计算逻辑
            DigitalRelationCaclFacade.calculatingObjRelationPrototype(context.getGroupCode(),context.getProjectId(),
                    context.getAppId(), CommonConstant.DEFAULT_ID, requestData);
        }
    }
    /***
     * Description: 计算空间交通关系
     * @param context : 通用参数
     * @param buildingId : 建筑id
     * @param objectType : 对象类型
     * @param type : 类型
     * @param relationTypeEnum : 关系枚举类
     * @return : void
     * @author : lijie
     * @date :2021/9/30 14:49
     * Update By lijie 2021/9/30 14:49
     */
    @Override
    public AdmResponse calcRelationForArchForArchSpToSp(InstanceUrlParam context, String buildingId, String objectType,
                                                        String type, AdmRelationTypeEnum relationTypeEnum) {
        if (StrUtil.isBlank(buildingId)){
            return AdmResponse.success(new ArrayList<>());
        }
        RequestData requestData = RequestData
                .builder()
                .buildingId(buildingId)
                .objTypes(CollUtil.newHashSet(AdmObjectType.FLOOR.getIndex()))
                .build();
        // 1.根据建筑id查询建筑下的所有楼层
        CommonResult<List<ObjectDigital>> commonResult = DigitalObjectFacade
                .queryObjectListSuperiorIdPrototype(context.getGroupCode(), context.getProjectId(),
                context.getAppId(), context.getUserId(), requestData);
        if (!"success".equals(commonResult.getResult())){
            return AdmResponse.failure(commonResult.getMessage());
        }
        Long count = commonResult.getCount();
        List<ObjectDigital> data = commonResult.getData();
        handleFloorRelationCalc(data,context,objectType,relationTypeEnum);
        if (data.size() == count){
            // 不需要分页查询
            return AdmResponse.success();
        }
        // 有结果集未查完
        long page = (count % CommonConstant.QUERY_DEFAULT_PAGE_SIZE) == 0
                ? (count / CommonConstant.QUERY_DEFAULT_PAGE_SIZE) : (count / CommonConstant.QUERY_DEFAULT_PAGE_SIZE + 1);
        for (long i = 1; i < page; i++) {
            requestData.setPage(i);
            requestData.setSize(CommonConstant.QUERY_DEFAULT_PAGE_SIZE);
            List<ObjectDigital> pageFloors = DigitalObjectFacade
                    .queryObjectListSuperiorId(context.getGroupCode(),
                            context.getProjectId(), context.getAppId(), context.getUserId(), requestData);
            handleFloorRelationCalc(pageFloors,context,objectType,relationTypeEnum);
        }
        return AdmResponse.success();
    }
    /***
     * Description: 处理楼层下的关系计算(BDTP平台已经限制查询数据量)
     * @param data : 楼层数据
     * @param context : 请求参数
     * @param classCode : 对象类型code
     * @param relationTypeEnum: 图类型对象
     * @return : void
     * @author : lijie
     * @date :2021/9/30 16:46
     * Update By lijie 2021/9/30 16:46
     */
    private void handleFloorRelationCalc(List<ObjectDigital> data, InstanceUrlParam context, String classCode,
                                         AdmRelationTypeEnum relationTypeEnum) {
        if (CollUtil.isEmpty(data)){
            return;
        }
        ObjectMapper objectMapper = SpringHelper.getBean(ObjectMapper.class);
        List<ObjectNode> relations = new ArrayList<>();
        // 2.循环所有楼层
        for (ObjectDigital floor : data) {
            if (!floor.getInfos().has(AdmObjectInfoConstant.MODEL_ID)
                    || StrUtil.isBlank(floor.getInfos().get(AdmObjectInfoConstant.MODEL_ID).asText())){
                continue;
            }
            // 2.1 根据楼层的modelId查询所有的门
            List<TwicePointDTO> pointDTOS =
                    queryAllDoorByFloorModelId(floor
                            .getInfos()
                            .get(AdmObjectInfoConstant.MODEL_ID)
                            .asText(),context);
            if (CollUtil.isEmpty(pointDTOS)){
                continue;
            }
            // 2.2 根据楼层id和classCode查询所有的空间
            List<ObjectDigital> spaces = queryAllSpacesByFloorIdAndClassCode(floor.getId(),
                    classCode,floor.getProjectId(),context);
            if (CollUtil.isEmpty(spaces)){
                continue;
            }
            // 2.3 遍历所有的门,得到门拥有的两组x,y坐标
            Map<Integer,List<String>> spaceIndexMap = new HashMap<>();
            for (int i = 0; i < pointDTOS.size(); i++) {
                TwicePointDTO pointDTO = pointDTOS.get(i);
                ObjectNode point1 = objectMapper
                        .createObjectNode()
                        .put("x", pointDTO.getX1())
                        .put("y", pointDTO.getY1())
                        .put("z", 0.00);
                ObjectNode point2 = objectMapper
                        .createObjectNode()
                        .put("x", pointDTO.getX2())
                        .put("y", pointDTO.getY2())
                        .put("z", 0.00);
                // 2.4 遍历坐标组,与空间进行对比,得到所有符合的空间id数组
                for (ObjectDigital space : spaces) {
                    if (null==space.getInfos()){
                        continue;
                    }
                    if (!space.getInfos().has("outline")
                            || !(space.getInfos().get("outline").size()>0)
                            || !(space.getInfos().get("outline").get(0).size()>0)){
                        continue;
                    }
                    // 空间的第一个数组
                    JsonNode jsonNode = space.getInfos().get("outline").get(0).get(0);
                    if (jsonNode==null || !jsonNode.isArray()){
                        continue;
                    }
                    ArrayNode jsonNodes = (ArrayNode) jsonNode;
                    List<ObjectNode> temp = new ArrayList<>();
                    jsonNodes.forEach(node->temp.add((ObjectNode) node));
                    if (GeoToolsUtil.isPointInPoly(point1,temp) || GeoToolsUtil.isPointInPoly(point2,temp)){
                        List<String> spaceIds = spaceIndexMap.getOrDefault(i, new ArrayList<>());
                        spaceIds.add(space.getId());
                        spaceIndexMap.put(i,spaceIds);
                    }
                }
            }
            // 2.5 创建空间服务关系的关系数据
            Set<Map.Entry<Integer, List<String>>> entries = spaceIndexMap.entrySet();

            for (Map.Entry<Integer, List<String>> entry : entries) {
                List<String> list = entry.getValue();
                if(CollUtil.isEmpty(list) || list.size()<2){
                    continue;
                }
                String oneSpaceId = list.get(0);
                for (int i = 1; i < list.size(); i++) {
                    String otherSpaceId = list.get(i);
                    ObjectNode objectNode = objectMapper.createObjectNode()
                            .put("graphCode", relationTypeEnum.getGraphCode())
                            .put("relCode", relationTypeEnum.getRelCode())
                            .put("graphId", StrUtil.format(CommonConstant.DEFAULT_GRAPH_ID_FORMAT, relationTypeEnum.getGraphCode()))
                            .put("projectId", floor.getProjectId())
                            .put("objTo", otherSpaceId)
                            .put("objFrom", oneSpaceId)
                            .put("valid", 1)
                            .put("createApp", context.getAppId())
                            .put("updateApp", context.getAppId())
                            .put("groupCode", context.getGroupCode())
                            .put("creator", context.getUserId())
                            .put("modifier", context.getUserId());
                    if (StrUtil.isNotBlank(classCode)){
                        objectNode.put("relValue",classCode);
                    }
                    relations.add(objectNode);
                }
            }
        }
        if (CollUtil.isNotEmpty(relations)){
            DigitalRelationFacade.create(context.getGroupCode(),context.getProjectId(),context.getAppId(),context.getUserId(),relations);
        }
    }
    /***
     * Description: 查询楼层下的所有空间
     * @param floorId : 楼层id
     * @param classCode : 对象类型code
     * @param projectId : 项目id
     * @param context: 内容上下文
     * @return : java.util.List<com.persagy.dmp.digital.entity.ObjectDigital>
     * @author : lijie
     * @date :2021/10/8 14:47
     * Update By lijie 2021/10/8 14:47
     */
    private List<ObjectDigital> queryAllSpacesByFloorIdAndClassCode(String floorId, String classCode,
                                                                    String projectId, InstanceUrlParam context) {
        RequestData requestData = RequestData
                .builder()
                .projectId(projectId)
                .floorId(floorId)
                .objTypes(CollUtil.newHashSet(AdmObjectType.SPACE.getIndex()))
                .classCodes(CollUtil.newHashSet(classCode))
                .build();
        // 1.查询通用结果集
        CommonResult<List<ObjectDigital>> commonResult = DigitalObjectFacade
                .queryObjectListSuperiorIdPrototype(context.getGroupCode(),
                        context.getProjectId(), context.getAppId(), context.getUserId(), requestData);
        List<ObjectDigital> spaces = commonResult.getData();
        if (CollUtil.isEmpty(spaces)){
            return new ArrayList<>();
        }
        Long count = commonResult.getCount();
        if (spaces.size() == count){
            // 不需要分页查询
            return spaces;
        }
        // 有结果集未查完
        long page = (count % CommonConstant.QUERY_DEFAULT_PAGE_SIZE) == 0
                ? (count / CommonConstant.QUERY_DEFAULT_PAGE_SIZE) : (count / CommonConstant.QUERY_DEFAULT_PAGE_SIZE + 1);
        for (long i = 1; i < page; i++) {
            requestData.setPage(i);
            requestData.setSize(CommonConstant.QUERY_DEFAULT_PAGE_SIZE);
            List<ObjectDigital> pageSpaces = DigitalObjectFacade
                    .queryObjectListSuperiorId(context.getGroupCode(),
                            context.getProjectId(), context.getAppId(), context.getUserId(), requestData);
            if (CollUtil.isEmpty(pageSpaces)){
                continue;
            }
            spaces.addAll(pageSpaces);
        }
        return spaces;
    }

    /***
     * Description: 根据楼层模型id查询所有门数据(BDTP限制了查询条数,需要做分页查询)
     * @param floorModelId : 楼层模型id
     * @param context: 请求参数上下文
     * @return : java.util.List<com.persagy.proxy.adm.dto.TwicePointDTO>
     * @author : lijie
     * @date :2021/10/8 10:02
     * Update By lijie 2021/10/8 10:02
     */
    private List<TwicePointDTO> queryAllDoorByFloorModelId(String floorModelId, InstanceUrlParam context) {

        // 门的classCode:CFBEDR,obj_type为equipment
        QueryCriteria queryCriteria = new QueryCriteria();
        queryCriteria.getCriteria().put("classCode","CFBEDR")
                .put("objType",AdmObjectType.EQUIPMENT.getIndex())
                .put("modelId",floorModelId);
        CommonResult<List<ObjectNode>> commonResult = DigitalObjectFacade
                .queryPrototype(context.getGroupCode(), context.getProjectId(), context.getAppId(),
                        context.getUserId(), queryCriteria);
        List<TwicePointDTO> result = new ArrayList<>();
        List<ObjectNode> objectNodes = commonResult.getData();
        Long count = commonResult.getCount();
        analysisDoorResultData(result,objectNodes);
        if (objectNodes.size()==count){
            // 查询结果集小于BDTP默认分页条数
            return result;
        }
        // 有结果集未查完
        long page = (count % CommonConstant.QUERY_DEFAULT_PAGE_SIZE) == 0
                ? (count / CommonConstant.QUERY_DEFAULT_PAGE_SIZE) : (count / CommonConstant.QUERY_DEFAULT_PAGE_SIZE + 1);
        for (long i = 1; i < page; i++) {
            queryCriteria.setPage(i);
            queryCriteria.setSize(CommonConstant.QUERY_DEFAULT_PAGE_SIZE);
            analysisDoorResultData(result,DigitalObjectFacade
                    .query(context.getGroupCode(), context.getProjectId(), context.getAppId(),
                            context.getUserId(), queryCriteria));
        }
        return result;
    }

    /***
     * Description: 解析门查询结果集
     * @param result : 存储结果集
     * @param objectNodes : 对象结果集
     * @return : void
     * @author : lijie
     * @date :2021/10/8 14:26
     * Update By lijie 2021/10/8 14:26
     */
    private void analysisDoorResultData(List<TwicePointDTO> result, List<ObjectNode> objectNodes) {
        double distance = 100.00;
        if (CollUtil.isEmpty(objectNodes)){
            return;
        }
        for (ObjectNode objectNode : objectNodes) {
            // 1.获得outline
            if (!objectNode.has("outline")
                    || !objectNode.get("outline").isArray()
                    || !(objectNode.get("outline").size()>2)){
                continue;
            }
            if (!objectNode.has("location")
                    || !objectNode.get("location").isObject()
                    || !objectNode.get("location").has("points")
                    || !objectNode.get("location").get("points").isArray()
                    || !(objectNode.get("location").get("points").size()>=1)){
                continue;
            }
            JsonNode point = objectNode.get("location").get("points").get(0);
            ArrayNode points = (ArrayNode) objectNode.get("outline").get(0);
            JsonNode firstPoint = points.get(0);
            JsonNode secondPoint = points.get(1);
            Double k=getPoints(firstPoint.get("x").asDouble(),firstPoint.get("y").asDouble(),
                    secondPoint.get("x").asDouble(),secondPoint.get("y").asDouble());
            if (null==k){
                result.add(TwicePointDTO
                        .builder()
                        .x1(point.get("x").asDouble())
                        .y1(point.get("y").asDouble()+distance)
                        .x2(point.get("x").asDouble())
                        .y2(point.get("y").asDouble()-distance)
                        .build());
                continue;
            }
            result.add(getPointByDistance(point.get("x").asDouble(),point.get("y").asDouble(),k,distance*2));
        }
    }

    /***
     * Description: 计算两点之间的距离(我不太明白这个算法是怎么算的)
     * @param baseX : 坐标点1的x值
     * @param baseY : 坐标点1的y值
     * @param k : 坐标点2的x值
     * @param distance : 坐标点2的y值
     * @return : java.lang.Double
     * @author : lijie
     * @date :2021/10/8 11:31
     * Update By lijie 2021/10/8 11:31
     */
    private TwicePointDTO getPointByDistance(Double baseX,Double baseY,Double k,Double distance){
        if (null==k){
            return TwicePointDTO.builder().x1(baseX).y1(baseY+distance).x2(baseX).y2(baseY-distance).build();
        }
        double vectorX1 = Math.sqrt(distance * distance / (1 + k * k));
        double vectorX2 = -vectorX1;
        double vectorY1 = k*vectorX1;
        double vectorY2 = k*vectorX2;
        return TwicePointDTO.builder().x1(baseX+vectorX1).y1(baseY+vectorY1).x2(baseX+vectorX2).y2(baseY+vectorY2).build();
    }

    /***
     * Description: 计算两点之间的距离(我不太明白这个算法是怎么算的)
     * @param x1 : 坐标点1的x值
     * @param y1 : 坐标点1的y值
     * @param x2 : 坐标点2的x值
     * @param y2 : 坐标点2的y值
     * @return : java.lang.Double  
     * @author : lijie
     * @date :2021/10/8 11:31
     * Update By lijie 2021/10/8 11:31
     */
    private Double getPoints(Double x1,Double y1,Double x2,Double y2){
        if (x1.equals(x2) && y1.equals(y2)){
            return null;
        }
        Double k=null;
        if (!x1.equals(x2)){
            k=(y2-y1)/(x2-x1);
        }
        if (null==k){
            return 0.00;
        }
        if (0.00 == k){
            return null;
        }
        return 1/k;
    }










}
