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

import cn.sagacloud.server.algorithm.backstage.model.datacenter.DCFloor
import cn.sagacloud.server.algorithm.backstage.model.datacenter.DCSpace
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.alibaba.fastjson.JSON
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.GeometryFactory
import org.locationtech.jts.geom.LinearRing
import org.locationtech.jts.geom.Polygon
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import java.util.*
import java.util.stream.Collectors
import kotlin.collections.ArrayList

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

    companion object {
        // 日志
        private val logger = LoggerFactory.getLogger(SpaceAffectUtil::class.java)
    } // Companion object

    @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
        }
        // 查楼层下的空间.根据楼层id查询
        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<DCSpace?>?, newMetaSpaces: List<Space>?, oldMetaSpaces: List<Space>?): List<DCSpace?>? {
        // 准备修改后的元空间
        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!!) {
            for (outLines in space.outline!!) {
                val polygon = createPolygonV2(outLines)
                val newSpace = newMetaSpaceMap[space.id]
                if (newSpace == null) {
                    affectList.add(polygon)
                    continue
                }
                for (newOutLines in newSpace.outline!!) {
                    val newPolygon = createPolygonV2(newOutLines)
                    // 有交集则加入有变化的元空间列表
                    if (isGeoContains(polygon, newPolygon)) {
                        affectList.add(polygon)
                        break
                    }
                }
            }
        }
        if (CollectionUtils.isEmpty(affectList)) {
            return null
        }
        // 与有变化的元空间有交集的空间
        val rsList: MutableList<DCSpace?> = ArrayList()
        for (space in spaces!!) {
            if (space!!.outline.isNullOrEmpty()){
                continue
            }
            out@
            for (outLines in space!!.outline!!) {
                for (subOutLine in outLines!!){
                    val polygon = createPolygonV2(subOutLine)
                    // 有交集则加入有变化的空间列表
                    for(affect in affectList) {
                        if (isGeoContains(polygon, affect!!)) {
                            // state设置为1
                            space.state = 1
                            rsList.add(space)
                            break@out;
                        }
                    }
                }
            }

        }
        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 createPolygonV2(list: ArrayList<Point>?): Polygon? {
        val geometryFactory = JTSFactoryFinder.getGeometryFactory()
        var ring: LinearRing? =null
        try {
            try {
                ring = createRing(geometryFactory,list)
            }catch (ingroed:IllegalArgumentException){
                list!!.add(list[0])
                ring = createRing(geometryFactory,list)
            }
        }catch (e:Exception){}
        if (null==ring){
            return null
        }
        return geometryFactory.createPolygon(ring, null)
    }
    /**
     * 创建平面对象
     * @param list 轮廓线
     */
    private fun createRing(geometryFactory: GeometryFactory, list: ArrayList<Point>?): LinearRing {
        val objects: MutableList<Coordinate> = ArrayList()
        for (point in list!!) {
            objects.add(Coordinate(point.x, point.y, point.z))
        }
        //注意数据的闭合
        val coordinates = objects.toTypedArray()
        return geometryFactory.createLinearRing(coordinates)
    }

    /**
     * 更新空间列表
     * @param projectId
     * @param spaces
     */
    private fun updateSpaceList(projectId: String, spaces: List<DCSpace?>?) {
        val request = UpdateRequest<DCSpace>()
        request.content = spaces as ArrayList<DCSpace>
        val spaceUpdate = service!!.spaceUpdate(request, projectId)
        if (!"success".equals(spaceUpdate?.result!!)){
            logger.error("#######更新空间失败:"+JSON.toJSONString(spaceUpdate))
        }
    }

    /**
     * 查询楼层关联的空间
     * @param projectId
     * @param modelId
     * @return
     */
    private fun querySpaceByFloorModel(projectId: String, modelId: String): List<DCSpace?>? {
        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<DCSpace> = ArrayList()
        for (floor in floors!!) {
            val spaces: List<DCSpace>? = 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 not version isnull"
        request.orders = "version desc"
        request.pageSize=2
        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
    }
}