package cn.sagacloud.server.algorithm.common.utils

import cn.sagacloud.server.algorithm.backstage.model.datacenter.DCFloor
import cn.sagacloud.server.algorithm.backstage.model.datacenter.DCISpace
import cn.sagacloud.server.algorithm.backstage.model.datacenter.QueryResponse
import cn.sagacloud.server.algorithm.backstage.model.datacenter.UpdateRequest
import cn.sagacloud.server.algorithm.models.entities.Space
import cn.sagacloud.server.algorithm.models.jsonAnalyzer.Point
import cn.sagacloud.server.algorithm.models.modelFile.ModelFile
import cn.sagacloud.server.algorithm.services.BaseDataService.modelFileService
import cn.sagacloud.server.algorithm.services.BaseDataService.spaceService
import cn.sagacloud.server.algorithm.services.datacenter.DataCenterRequest
import com.sybotan.service.SObjectService
import com.sybotan.service.models.SCascadeQuery
import com.sybotan.service.models.requests.SQueryRequest
import com.sybotan.service.models.responses.SQueryResponse
import org.apache.commons.collections4.CollectionUtils
import org.apache.commons.lang3.StringUtils
import org.geotools.geometry.jts.JTSFactoryFinder
import org.locationtech.jts.geom.Coordinate
import org.locationtech.jts.geom.Polygon
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import java.util.*
import java.util.stream.Collectors

/**
 * 空间冲突计算工具
 * 基于python代码改写:relations.src.affected_space.affected_space
 * @author ADM
 * @date 2021-10-23
 */
@Component
class SpaceAffectUtil {

    @Autowired
    private val service: DataCenterRequest? = null

    /**
     * 计算空间冲突
     * @param projectId
     * @param modelId
     */
    fun affectedSpace(projectId: String, modelId: String): Boolean {
        if (StringUtils.isBlank(projectId) || StringUtils.isBlank(modelId)) {
            return true
        }
        // 查楼层
        val spaceList = querySpaceByFloorModel(projectId, modelId)
        if (CollectionUtils.isEmpty(spaceList)) {
            return true
        }
        // 查原模型
        val modelIds = queryModelFile(projectId, modelId)
        if (CollectionUtils.size(modelIds) != 2) {
            return true
        }
        // 查模型关联的元空间
        val iSpaceMap = queryMetaSpaceByModel(modelIds)
        if (CollectionUtils.isEmpty(iSpaceMap[modelIds!![1]])) {
            // 旧模型没有关联的元空间不需要处理
            return true
        }
        // 获取有变化的空间列表
        val spaces = getAffectedSpaceIds(spaceList, iSpaceMap[modelIds[0]], iSpaceMap[modelIds[1]])
        // 更新空间
        updateSpaceList(projectId, spaces)
        return true
    }

    /**
     * 获取有变化的空间ID列表
     * @param spaces
     * @param newMetaSpaces
     * @param oldMetaSpaces
     * @return
     */
    private fun getAffectedSpaceIds(spaces: List<DCISpace?>?, newMetaSpaces: List<Space>?, oldMetaSpaces: List<Space>?): List<DCISpace?>? {
        // 准备修改后的元空间
        val newMetaSpaceMap: MutableMap<String?, Space> = HashMap()
        var i = 0
        val j = CollectionUtils.size(newMetaSpaces)
        while (i < j) {
            val space = newMetaSpaces!![i]
            newMetaSpaceMap[space.id] = space
            i++
        }
        // 截取有变化的元空间
        val affectList: MutableList<Polygon?> = ArrayList()
        for (space in oldMetaSpaces!!) {
            val polygon = createPolygon(space.outline)
            val newSpace = newMetaSpaceMap[space.id]
            if (newSpace == null) {
                affectList.add(polygon)
                continue
            }
            val newPolygon = createPolygon(newSpace.outline)
            // 有交集则加入有变化的元空间列表
            if (isGeoContains(polygon, newPolygon)) {
                affectList.add(polygon)
                continue
            }
        }
        if (CollectionUtils.isEmpty(affectList)) {
            return null
        }
        // 与有变化的元空间有交集的空间
        val rsList: MutableList<DCISpace?> = ArrayList()
        for (space in spaces!!) {
            val polygon = createPolygon(space!!.outline)
            // 有交集则加入有变化的空间列表
            for(affect in affectList) {
                if (isGeoContains(polygon, affect!!)) {
                    // TODO state设置为1
                    rsList.add(space)
                    break;
                }
            }
        }
        return rsList
    }

    /**
     * 点面包含关系判断
     * 用途：判断一个面是否包含一个点，即一个点是否在一个面内
     * @param fromOutLines: 轮廓坐标
     * @param toOutLines: 轮廓坐标
     * @return
     */
    fun isGeoContains(fromPolygon: Polygon, toPolygon: Polygon): Boolean {
        if (fromPolygon == null || toPolygon == null) {
            return false
        }
        //判断面-面包含关系
        return (fromPolygon.contains(toPolygon)
                || fromPolygon.equalsTopo(toPolygon)
                || fromPolygon.overlaps(toPolygon)
                || toPolygon.contains(fromPolygon))
    }

    /**
     * 创建平面对象
     * @param list 轮廓线
     */
    private fun createPolygon(list: ArrayList<ArrayList<Point>>?): Polygon {
        val geometryFactory = JTSFactoryFinder.getGeometryFactory()
        val objects: MutableList<Coordinate> = ArrayList()
        for (outLines in list!!) {
            for(point in outLines!!) {
                objects.add(Coordinate(point.x, point.y, point.z))
            }
        }
        //注意数据的闭合
        val coordinates = objects.toTypedArray()
        val ring = geometryFactory.createLinearRing(coordinates)
        return geometryFactory.createPolygon(ring, null)
    }

    /**
     * 更新空间列表
     * @param projectId
     * @param spaces
     */
    private fun updateSpaceList(projectId: String, spaces: List<DCISpace?>?) {
        val request = UpdateRequest<DCISpace>()
        request.content = spaces as ArrayList<DCISpace>
        service!!.iSpaceUpdate(request, projectId)
    }

    /**
     * 查询楼层关联的空间
     * @param projectId
     * @param modelId
     * @return
     */
    private fun querySpaceByFloorModel(projectId: String, modelId: String): List<DCISpace?>? {
        val request = SQueryRequest()
        request.pageSize = 500
        request.filters = " modelId = '$modelId' "
        val cascade = SCascadeQuery()
        cascade.name = "spaceList"
        val cascades = ArrayList<SCascadeQuery>()
        cascades.add(cascade)
        request.cascade = cascades
        val response: QueryResponse<DCFloor>? = service!!.floorQuery(request, projectId)
        if (response == null || CollectionUtils.isEmpty(response.content)) {
            return null
        }
        val floors = response.content
        val spaceList: MutableList<DCISpace> = ArrayList<DCISpace>()
        for (floor in floors!!) {
            val spaces: List<DCISpace>? = floor!!.spaceList
            if (CollectionUtils.isEmpty(spaces)) {
                continue
            }
            spaceList.addAll(spaces!!)
        }
        return spaceList
    }

    /**
     * 获取模型
     * @param projectId
     * @param modelId
     * @return
     */
    private fun queryModelFile(projectId: String, modelId: String): List<String>? {
        val modelFileService: SObjectService<ModelFile> = modelFileService
        val request = SQueryRequest()
        request.filters = "floorModelId = '$modelId' and removed = false and status = 4 and version is not null"
        request.orders = "version desc"
        val projection = ArrayList<String>()
        projection.add("id")
        request.projection = projection
        val response: SQueryResponse<ModelFile> = modelFileService.pageQuery(request, null)
        if (response == null || CollectionUtils.isEmpty(response.content)) {
            return null
        }
        val floors = response.content
        return floors!!.stream().map<String>(ModelFile::id).collect(Collectors.toList())
    }

    /**
     * 根据模型查询关联的元空间
     * @param modelIds
     * @return
     */
    private fun queryMetaSpaceByModel(modelIds: List<String>?): Map<String, List<Space>?> {
        val spaceMap: MutableMap<String, List<Space>?> = HashMap(2)
        val spaceService: SObjectService<Space> = spaceService
        // 查询新模型的元空间
        var request = SQueryRequest()
        request.filters = "modelId = '" + modelIds!![0] + "' "
        val projection = ArrayList<String>()
        projection.add("sourceId")
        projection.add("outline")
        request.projection = projection
        var response: SQueryResponse<Space> = spaceService.pageQuery(request, null)
        if (CollectionUtils.isNotEmpty(response.content)) {
            spaceMap[modelIds[0]] = response.content
        }
        // 查询旧模型的元空间
        request = SQueryRequest()
        request.filters = "modelId = '" + modelIds[1] + "' "
        response = spaceService.pageQuery(request, null)
        if (CollectionUtils.isNotEmpty(response.content)) {
            spaceMap[modelIds[1]] = response.content
        }
        return spaceMap
    }
}