package cn.sagacloud.server.algorithm.backstage.sync

import cn.sagacloud.server.algorithm.backstage.model.datacenter.DCEquipment
import cn.sagacloud.server.algorithm.backstage.model.datacenter.DCFloor
import cn.sagacloud.server.algorithm.models.config.MyProperties
import cn.sagacloud.server.algorithm.models.entities.*
import cn.sagacloud.server.algorithm.models.jsonAnalyzer.Location
import cn.sagacloud.server.algorithm.models.jsonAnalyzer.Point
import cn.sagacloud.server.algorithm.models.modelFile.FloorModel
import cn.sagacloud.server.algorithm.services.BaseDataService
import cn.sagacloud.server.algorithm.services.CommonService
import cn.sagacloud.server.algorithm.services.datacenter.DataCenterService
import cn.sagacloud.server.datacenter.models.entities.graphtype.MLocation
import cn.sagacloud.server.datacenter.models.entities.graphtype.MPoint
import com.alibaba.fastjson.JSONObject
import com.sybotan.database.SFilter
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Scope
import org.springframework.stereotype.Component


/**
 * 同步设备
 *
 * @author jxing
 */
@Suppress("UNCHECKED_CAST")
@Component
@Scope("prototype")
class SyncEquipment : SyncObject {
    var modelId: String? = null
    var floor: FloorModel? = null
    var dcFloors : ArrayList<DCFloor>? = null
    var bimIdPrefix : String? = null
    val bimMap = HashMap<String, Equipment>()

    @Autowired
    lateinit var dataCenterService: DataCenterService
    @Autowired
    lateinit var properties: MyProperties

    val dataService = BaseDataService

    /**
     * 初始化
     */
    override fun init(modelId: String, obj: FloorModel, dcFloors: ArrayList<DCFloor>, bimIdPrefix: String): SyncObject {
        this.modelId = modelId
        this.floor = obj
        this.dcFloors = dcFloors
        this.bimIdPrefix = bimIdPrefix
        return this
    }

    // 模型内设备
    var modelArr = ArrayList<Equipment>()
    // 数据中心设备
    var dataCenter = HashMap<String, DCEquipment>()
    var cadIdDataCenter = HashMap<String, DCEquipment>()
    // 交集(BIMID)
//    val intersection = HashSet<String>()

    var createList = ArrayList<DCEquipment>()
    var createSyncResultList = ArrayList<DCEquipment>()

    var deleteList = ArrayList<DCEquipment>()

    var updateList = ArrayList<DCEquipment>()

    val propertyMap = HashMap<String, String>()   // propertyId --> EquipId
    private val bimIdPropertyMap = HashMap<String, String>()      // bimid --> propertyId

    /**
     * 准备模型数据
     */
    override fun prepareModelData() : SyncObject{
        modelArr.addAll(dataService.equipmentService.select(SFilter.eq("modelId", modelId!!)).exec())
        // 加上墙,门,窗,柱设备的同步
        // 墙
        val wallArr:ArrayList<Wall> = dataService.wallService.select(SFilter.eq("modelId", modelId!!)).exec()
        // 门
        val doorArr:ArrayList<Door> = dataService.doorService.select(SFilter.eq("modelId", modelId!!)).exec()
        // 窗
        val windowArr:ArrayList<Window> = dataService.windowService.select(SFilter.eq("modelId", modelId!!)).exec()
        // 柱
        val columnArr:ArrayList<Column> = dataService.columnService.select(SFilter.eq("modelId", modelId!!)).exec()
        // 转换为设备对象
        transferWallToEquipment(wallArr)
        transferDoorToEquipment(doorArr)
        transferWindowToEquipment(windowArr)
        transferColumnToEquipment(columnArr)
        return this
    }
    /**
     * 将柱对象转换为设备对象
     * @param arr : 柱对象列表
     * @author : lijie
     * Update By 2022/2/12 18:34
     */
    private fun transferColumnToEquipment(arr: List<Column>) {
        if (arr.isNullOrEmpty()){
            return
        }
        if (properties.datacenter.equipColumn.isNullOrEmpty()){
            return
        }
        arr.forEach {
            val columnEquip = Equipment()
            columnEquip.localId=it.sourceId
            columnEquip.localName=it.name
            columnEquip.modelId=it.modelId
            columnEquip.location=it.location
            columnEquip.outline=it.outline
            columnEquip.sourceId=it.sourceId
            columnEquip.name=it.name
            columnEquip.family=getFamilyFromFamilyName(it.familyName,properties.datacenter.equipColumn,false)
            if (columnEquip.family.isNullOrEmpty()){
                return
            }
            if (it.familyName.isNullOrBlank()){
                columnEquip.familyName=columnEquip.family+"-"+columnEquip.name
            }
            columnEquip.familySymbol=it.familySymbol
            modelArr.add(columnEquip)
        }
    }

    /**
     * 将窗数据转换为设备
     * @param arr : 窗数据
     * @author : lijie
     * Update By 2022/2/12 18:28
     */
    private fun transferWindowToEquipment(arr: List<Window>) {
        if (arr.isNullOrEmpty()){
            return
        }
        if (properties.datacenter.equipWindow.isNullOrEmpty()){
            return
        }
        arr.forEach {
            val windowEquip = Equipment()
            windowEquip.localId=it.sourceId
            windowEquip.localName=it.name
            windowEquip.modelId=it.modelId
            windowEquip.location=transferMLocationToLocation(it.location)
            windowEquip.outline=transferMoutlineToOutline(it.outline)
            windowEquip.sourceId=it.sourceId
            windowEquip.name=it.name
            windowEquip.family=getFamilyFromFamilyName(it.familyName,properties.datacenter.equipWindow,false)
            if (windowEquip.family.isNullOrEmpty()){
                return
            }
            if (it.familyName.isNullOrBlank()){
                windowEquip.familyName=windowEquip.family+"-"+windowEquip.name
            }
            windowEquip.familySymbol=it.familySymbol
            modelArr.add(windowEquip)
        }
    }
    /**
     * 转换outline
     * @author : lijie
     * Update By 2022/2/12 18:49
     */
    private fun transferMoutlineToOutline(outline: List<List<MPoint>>?): ArrayList<ArrayList<Point>>? {
        if (null==outline){
            return null
        }
        val points=ArrayList<ArrayList<Point>>()
        outline.forEach {
            run {
                val subPoints = ArrayList<Point>()
                it.forEach {
                    subPoints.add(JSONObject.parseObject(JSONObject.toJSONString(it), Point::class.java))
                }
                points.add(subPoints)
            }
        }
        return points
    }
    /**
     * 转换location
     * @author : lijie
     * Update By 2022/2/12 18:49
     */
    private fun transferMLocationToLocation(location: MLocation?): Location? {
        if (null==location){
            return null
        }
        return JSONObject.parseObject(JSONObject.toJSONString(location),Location::class.java)
    }

    /**
     * 将门数据转换为设备
     * @param arr : 门的数据
     * @author : lijie
     * Update By 2022/2/12 17:17
     */
    private fun transferDoorToEquipment(arr: List<Door>) {
        if (arr.isNullOrEmpty()){
            return
        }
        if (properties.datacenter.equipDoor.isNullOrEmpty()){
            return
        }
        arr.forEach {
            val doorEquip = Equipment()
            doorEquip.localId=it.sourceId
            doorEquip.localName=it.name
            doorEquip.modelId=it.modelId
            doorEquip.location=transferMLocationToLocation(it.location)
            doorEquip.outline=transferMoutlineToOutline(it.outline)
            doorEquip.sourceId=it.sourceId
            doorEquip.name=it.name
            doorEquip.family=getFamilyFromFamilyName(it.familyName,properties.datacenter.equipDoor,true)
            if (doorEquip.family.isNullOrEmpty()){
                return
            }
            if (it.familyName.isNullOrBlank()){
                doorEquip.familyName=doorEquip.family+"-"+doorEquip.name
            }
            doorEquip.familySymbol=it.familySymbol
            modelArr.add(doorEquip)
        }
    }

    /**
     * 将墙数据转换为设备数据
     * @param arr : 墙的数据
     * @author : lijie
     * Update By 2022/2/12 10:28
     */
    private fun transferWallToEquipment(arr: List<Wall>) {
        if (arr.isNullOrEmpty()){
            return
        }
        if(properties.datacenter.equipWall.isNullOrEmpty()){
            return
        }
        arr.forEach {
            val wallEquip = Equipment()
            val infos = HashMap<String,String>()
            if (null!=it.width){
                infos[WIDTH_INFO_KEY] = it.width.toString()
            }
            wallEquip.infos=infos
            wallEquip.localId=it.sourceId
            wallEquip.localName=it.name
            wallEquip.modelId=it.modelId
            wallEquip.location=it.location
            wallEquip.outline=it.outline
            wallEquip.sourceId=it.sourceId
            wallEquip.name=it.name
            wallEquip.family=getFamilyFromName(it.name,properties.datacenter.equipWall)
            if (wallEquip.family.isNullOrEmpty()){
                return
            }
            wallEquip.familyName=wallEquip.family+"-"+wallEquip.name
            modelArr.add(wallEquip)
        }
    }
    /**
     * 获取设备的镞编码
     * @param name : 名称
     * @param classCodes : 设备类编码
     * @author : lijie
     * Update By 2022/2/12 11:39
     */
    private fun getFamilyFromName(name: String?, classCodes: List<String>): String? {
        if (classCodes.isNullOrEmpty()){
            return null
        }
        if (!name.isNullOrEmpty()) {
            val index = name.indexOf("-")
            if(index != -1){
                val family = name.substring(0, index)
                classCodes.forEach { if (it.endsWith(family)){return it} }
            }
        }
        return classCodes[0]
    }
    /**
     * 获取设备的镞编码
     * @param familyName : 名称
     * @param classCodes : 设备类编码
     * @author : lijie
     * Update By 2022/2/12 11:39
     */
    private fun getFamilyFromFamilyName(familyName: String?, classCodes: List<String>,doorFlag:Boolean): String? {
        if (classCodes.isNullOrEmpty()){
            return null
        }
        if (!familyName.isNullOrEmpty()) {
            val index = familyName.indexOf("-")
            if(index != -1){
                val family = familyName.substring(0, index)
                classCodes.forEach { if (it.endsWith(family)){return it} }
            }
            if (doorFlag){
                // 从名称中获取,如果族名称含有防火门关键字则返回防火门编码
                if (familyName.contains("防火门") && classCodes.size>1){
                    return classCodes[1]
                }
            }
        }
        return classCodes[0]
    }

    /**
     * 准备数据中心数据
     */
    override fun prepareDataCenterData(): SyncObject {
        if(floor == null || floor!!.projectId == null) {
            return this
        }
        val result = dataCenterService.getEquipmentByBIMIDPrefix(bimIdPrefix!!, floor!!.projectId!!)
        val cadIdResult = dataCenterService.getEquipmentByCADID(floor!!.projectId!!)
        if(result == null || cadIdResult == null) {
            throw Exception("从数据中心获取设备失败!")
        }
        if(result.isNotEmpty()) {
            dataCenter = result
        }
        if(cadIdResult.isNotEmpty()){
            cadIdDataCenter = cadIdResult
        }
        return this
    }

    /**
     * 构建bimidMap
     */
    override fun buildBIMIDMap(): SyncObject {
        val sb = StringBuilder()
        modelArr.forEach {
            if(it.sourceId != null) {
                sb.clear()
                sb.append(bimIdPrefix).append(it.sourceId)
                bimMap[sb.toString()] = it
            }
        }
        return this
    }

    /**
     * 开始匹配并分类
     */
    override fun matchClassify(): SyncObject {
        val sb = StringBuilder()
        dataCenter.forEach { (_, dcEquip) ->
            if(dcEquip.bimId != null && bimMap.contains(dcEquip.bimId!!)){
                val equip = bimMap[dcEquip.bimId!!]
                if(!dcEquip.classCode!!.endsWith(equip!!.family!!)){
//                if(!equip!!.family.equals(dcEquip.classCode)){
                    // 需要先删除, 后添加
                    deleteList.add(dcEquip)
                    return@forEach
                }
                // 交集, 更新
                bimMap.remove(dcEquip.bimId!!)
                // 绑定数据
                addToUpdate(equip, sb, dcEquip)
            }else{
                // 需要被删除的
                deleteList.add(dcEquip)
            }
        }
        // 继续用CADID匹配
        // 逻辑 --> 获取数据中心所有CADID不为空, BIMID为null的设备, 如果用CADID识别到唯一的对象, 则认为是同一对象, 执行update操作
        val cadIdMap = buildCADIDMap(cadIdDataCenter)
        val tobeDelete = HashSet<String>()
        bimMap.forEach{
            val cadId = CommonService.generateCADIDStr(it.value.localId, it.value.localName)
            if(cadId.isNullOrBlank())
                return@forEach
            // CADID找到匹配, 更新该对象
            if(cadIdMap.containsKey(cadId)){
                val dcEquip = cadIdMap[cadId]!!
                addToUpdate(it.value, sb, dcEquip)
                dcEquip.bimId ="${bimIdPrefix}${it.value.sourceId}"
                tobeDelete.add(it.key)
            }
        }
        for(id in tobeDelete)
            bimMap.remove(id)


        // 最后剩余在map里的就是需要创建的
        bimMap.forEach {
            val dcEquip = DCEquipment()
            dcEquip.classCode = it.value.family
            dcEquip.localName = it.value.localName
            dcEquip.localId = it.value.localId
            dcEquip.bimId = it.key
            //dcEquip.floorId = dcFloor!!.id
            dcEquip.familyName = it.value.familyName
            dcEquip.familySymbol = it.value.familySymbol
            dcEquip.modelId = it.value.modelId
            dcEquip.bimTypeID = it.value.bimTypeID
            dcEquip.infos = it.value.infos
            generateCADID(dcEquip)
            val point = it.value.location?.points?.get(0)
            addLocationAndFloorId(point, dcEquip, sb)
            createList.add(dcEquip)
            if(it.value.propertyId != null)
                bimIdPropertyMap[dcEquip.bimId!!] = it.value.propertyId!!
        }
        return this
    }

    /**
     * 获取一个以CADID为key的DCEquipment Map
     */
    private fun buildCADIDMap(cadIdDataCenter: HashMap<String, DCEquipment>): HashMap<String, DCEquipment> {
        val map = HashMap<String, DCEquipment>()
        val duplicatedCADID = HashSet<String>()
        for(id in cadIdDataCenter.keys){
            val equipment = cadIdDataCenter[id]
            if(!equipment!!.cadId.isNullOrBlank()){
                if(duplicatedCADID.contains(equipment.cadId))
                    continue
                if(map.containsKey(equipment.cadId)){
                    duplicatedCADID.add(equipment.cadId!!)
                    map.remove(equipment.cadId!!)
                    continue
                }
                map[equipment.cadId!!] = equipment
            }
        }
        return map
    }

    /**
     * 将需要更新的对象添加到updateList里 (localId, localName为空才更新)
     */
    private fun addToUpdate(equip: Equipment, sb: StringBuilder, dcEquip: DCEquipment) {
        val point = equip.location?.points?.get(0)
        addLocationAndFloorId(point, dcEquip, sb)
        if(dcEquip.localId.isNullOrBlank())
            dcEquip.localId = equip.localId
        if(dcEquip.localName.isNullOrBlank())
            dcEquip.localName = equip.localName
        dcEquip.familyName = equip.familyName
        dcEquip.familySymbol = equip.familySymbol
        dcEquip.modelId = equip.modelId
        dcEquip.bimTypeID = equip.bimTypeID
        updateList.add(dcEquip)
        if(equip.propertyId != null && dcEquip.id != null)
            propertyMap[equip.propertyId!!] = dcEquip.id!!
    }

    /**
     * 给DCEquipment对象添加BIMLocation和FloorId关系
     */
    private fun addLocationAndFloorId(point: Point?, dcEquip: DCEquipment, sb: StringBuilder) {
        if (point != null) {
            if (dcFloors!!.isNotEmpty()) {
                var defaultDCFloor: DCFloor? = null
                // 找到一个没有划分过的楼层作为默认所属楼层(没有outline)
                for (dcFloor in dcFloors!!) {
                    if(dcFloor.outline == null) {
                        defaultDCFloor = dcFloor
                        break
                    }
                }
                for (dcFloor in dcFloors!!) {
                    if (dcFloor.outline != null) {
                        // 如果数据中心的楼层设置了outline, 判断是否在outline内
                        if (CommonService.isPointInPolygonCheckWithJdkGeneralPath(point, dcFloor.outline!!)) {
                            dcEquip.floorId = dcFloor.id
                            dcEquip.buildingId = dcFloor.buildingId
                            break
                        }
                    }
                }
                // 如果没找到所属楼层, 则使用默认楼层作为所属楼层
                if(dcEquip.buildingId == null && defaultDCFloor != null){
                    dcEquip.floorId = defaultDCFloor.id
                    dcEquip.buildingId = defaultDCFloor.buildingId
                }
            }
            sb.clear()
            sb.append(point.x).append(",").append(point.y).append(",").append(point.z)
            dcEquip.bimLocation = sb.toString()
        }
    }

    /**
     * 同步创建到数据中心
     */
    override fun syncCreate(): SyncObject {
        if(createList.isNotEmpty()) {
            dataCenterService.sync(createList as ArrayList<Any>, createSyncResultList as ArrayList<Any>, floor!!.projectId!!, DataCenterService.DataCenterOperator.equipCreate)
        }
        if(!bimIdPropertyMap.isEmpty()){
            createSyncResultList.forEach {
                if(it.bimId!= null && bimIdPropertyMap.containsKey(it.bimId!!)){
                    val propertyId = bimIdPropertyMap[it.bimId!!]
                    propertyMap[propertyId!!] = it.id!!
                }
            }
        }
        return this
    }

    /**
     * 同步在数据中心删除
     */
    override fun syncDelete(): SyncObject {
        if(deleteList.isNotEmpty())
            dataCenterService.sync(deleteList as ArrayList<Any>, null, floor!!.projectId!!, DataCenterService.DataCenterOperator.equipDelete)
        return this
    }

    /**
     * 同步在数据中心更新
     */
    override fun syncUpdate(): SyncObject {
        if(updateList.isNotEmpty()) {
//            updateList.forEach {
//                it.localId = null
//                it.cadId = null
//                it.localName = null
//            }
            dataCenterService.sync(updateList as ArrayList<Any>, null, floor!!.projectId!!, DataCenterService.DataCenterOperator.equipUpdate)
        }
        return this
    }

    fun syncPropertyId() {
        val propertyList = dataCenterService.getPropertyById(propertyMap.keys.toMutableList() as ArrayList<String>, floor!!.projectId!!)
        if(propertyList == null)
            return
        propertyList.forEach {
            if(propertyMap.containsKey(it.id)){
                it.equipId = propertyMap[it.id]
            }
        }
        dataCenterService.propertyUpdate(propertyList, floor!!.projectId!!)
    }

    companion object {
        fun generateCADID(dcEquip: DCEquipment) {
            if (dcEquip.localId.isNullOrBlank() || dcEquip.localName.isNullOrBlank()) {
                if (dcEquip.localId.isNullOrBlank() && dcEquip.localName.isNullOrBlank()) {
                    dcEquip.cadId = null
                } else
                    dcEquip.cadId = "${if(dcEquip.localId == null)"" else dcEquip.localId}${if(dcEquip.localName == null)"" else dcEquip.localName}"
            } else {
                dcEquip.cadId = "${dcEquip.localId}:${dcEquip.localName}"
            }
        }

        const val WIDTH_INFO_KEY = "width"
    }
}