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

import cn.sagacloud.server.algorithm.backstage.model.datacenter.DCEquipment
import cn.sagacloud.server.algorithm.services.datacenter.DataCenterService
import cn.sagacloud.server.algorithm.models.config.MyProperties
import cn.sagacloud.server.algorithm.models.entities.WallColumnLog
import cn.sagacloud.server.algorithm.models.log.SyncLogEntity
import cn.sagacloud.server.algorithm.models.modelFile.FloorModel
import cn.sagacloud.server.algorithm.models.modelFile.ModelFile
import cn.sagacloud.server.algorithm.models.request.BaseGraphRequest
import cn.sagacloud.server.algorithm.services.BaseDataService
import cn.sagacloud.server.algorithm.services.CommonService
import cn.sagacloud.server.algorithm.services.http.HttpService
import cn.sagacloud.server.algorithm.services.endpoint.BaseGraphService
import cn.sagacloud.server.algorithm.services.modelCheck.ModelLogService
import cn.sagacloud.server.algorithm.services.monitor.ModelMonitorService
import com.alibaba.fastjson.JSON
import com.alibaba.fastjson.PropertyNamingStrategy
import com.alibaba.fastjson.serializer.SerializeConfig
import com.alibaba.fastjson.util.TypeUtils
import com.sybotan.database.SFilter
import com.sybotan.service.models.enums.SResponseType
import com.sybotan.service.models.requests.SCreateRequest
import com.sybotan.service.utils.SSpringContextUtil
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import java.io.ByteArrayOutputStream
import java.util.zip.GZIPOutputStream
import kotlin.collections.ArrayList

/**
 *
 *
 * @author jxing
 */
@Component
class SyncUtil {

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

    @Autowired
    lateinit var myProperties: MyProperties

    @Autowired
    lateinit var dataCenterService: DataCenterService

    @Autowired
    lateinit var modelLogService: ModelLogService

    @Autowired
    lateinit var monitorService: ModelMonitorService

    var dataService = BaseDataService

    /**
     * 在楼层绑定了一个或多个数据中心楼层的情况下同步设备/部件/元空间
     * @param model     要被同步数据的模型
     * @param syncFloor 同步楼层的对象
     * @param floor     所属楼层
     */
    fun syncDataWithBindingFloor(syncFloor: SyncFloor, model: ModelFile, floor: FloorModel) {
        val bimIdPrefix = CommonService.generateBIMIDPrefix(model.projectId!!, floor.folderId!!, floor.floorName!!, true)
        val syncEquip = SSpringContextUtil.getBean(SyncEquipment::class.java) as SyncEquipment
        val syncComponent = SSpringContextUtil.getBean(SyncEquipPart::class.java) as SyncEquipPart
        val syncISpace = SSpringContextUtil.getBean(SyncISpace::class.java) as SyncISpace
        val syncModelComponent = SSpringContextUtil.getBean(SyncModelComponent::class.java) as SyncModelComponent

        // 开始同步设备, 部件, 元空间
        monitorService.syncDatacenter(model.id!!, syncFloor, arrayListOf(syncModelComponent, syncEquip, syncComponent, syncISpace), bimIdPrefix)

        // 处理模型中绑定过propertyId的设备和部件
        syncEquip.syncPropertyId()
        syncComponent.syncPropertyId()

        // 上传底图, 并更新楼层的FloorMap
        var baseResult = uploadBaseGraph(model.id!!, zipBaseGraph(model.id!!))
        if (!baseResult)
            throw Exception("底图保存失败")

        // 更新楼层的信息点 modelId 和 FloorMap
        for (dcFloor in syncFloor.dcFloor) {
            dcFloor.modelId = model.floorModelId
            if (dcFloor.infos == null)
                dcFloor.infos = HashMap()
            dcFloor.infos!!["floorMap"] = "${myProperties.fileService.baseGraph}/${model.id}.jsonz"
            syncFloor.syncUpdate(dcFloor)
        }

        monitorService.beginWriteLog(model.id!!)
        try {
            // 记录删除和创建记录
            syncLog(syncEquip.createSyncResultList, syncEquip.deleteList, model.projectId!!, model.id!!)

            // 开始记录柱子, 虚拟墙, 墙的增删日志
            val prevValidModel = modelLogService.getPrevValidModel(model)
            val columnAddDeleteLog = modelLogService.getColumnAddDeleteLog(model.id!!, prevValidModel)
            val wallAddDeleteLog = modelLogService.getWallAddDeleteLog(model.id!!, prevValidModel)
            val virtualWallAddDeleteLog = modelLogService.getVirtualWallAddDeleteLog(model.id!!, prevValidModel)
            val convertToSyncLogEntityList = modelLogService.convertToSyncLogEntityList(model.id!!, columnAddDeleteLog as ArrayList<WallColumnLog>)
            convertToSyncLogEntityList.addAll(modelLogService.convertToSyncLogEntityList(model.id!!, wallAddDeleteLog as ArrayList<WallColumnLog>))
            convertToSyncLogEntityList.addAll(modelLogService.convertToSyncLogEntityList(model.id!!, virtualWallAddDeleteLog as ArrayList<WallColumnLog>))
            val request = SCreateRequest<SyncLogEntity>()
            request.content = convertToSyncLogEntityList
            BaseDataService.syncLogService.delete(SFilter.and(SFilter.eq("modelId", model.id!!), SFilter.inList("type", arrayListOf("Wall", "VirtualWall", "Column"))))
            BaseDataService.syncLogService.createList(request)
        }catch (e : Exception){
            monitorService.endWriteLogExceptionally(model.id!!, "error:${e.message}, ${CommonService.getStackTrace(e)}")
            throw e
        }
        monitorService.endWriteLog(model.id!!)
    }

    /**
     * 记录同步日志
     */
    private fun syncLog(createSyncResultList: ArrayList<DCEquipment>, deleteList: ArrayList<DCEquipment>, projectId: String, modelId: String) {
        // id --> floorName/buildingName
        val floorIdMap = HashMap<String, String?>()
        val buildingIdMap = HashMap<String, String?>()

        getAllFloorBuildingId(createSyncResultList, floorIdMap, buildingIdMap)
        getAllFloorBuildingId(deleteList, floorIdMap, buildingIdMap)
        fulfillFloorName(floorIdMap, projectId)
        fulfillBuildingName(buildingIdMap, projectId)
        addSyncLog(modelId, 1, createSyncResultList, floorIdMap, buildingIdMap, false)
        addSyncLog(modelId, 0, deleteList, floorIdMap, buildingIdMap, false)
    }

    /**
     * 添加同步日志到数据库
     */
    private fun addSyncLog(modelId: String, operation: Int, syncResultList: ArrayList<DCEquipment>, floorIdMap: HashMap<String, String?>, buildingIdMap: HashMap<String, String?>, isDelete : Boolean) {
        val list = ArrayList<SyncLogEntity>()
        syncResultList.forEach {
            val syncLogEntity = SyncLogEntity()
            syncLogEntity.objId = it.id
            syncLogEntity.bimId = it.bimId
            syncLogEntity.modelId = modelId
            syncLogEntity.operation = operation
            syncLogEntity.buildingId = it.buildingId
            if(syncLogEntity.buildingId != null && buildingIdMap.containsKey(syncLogEntity.buildingId!!)) {
                syncLogEntity.buildingName = buildingIdMap[syncLogEntity.buildingId!!]
            }
            syncLogEntity.floorId = it.floorId
            if(syncLogEntity.floorId != null && floorIdMap.containsKey(syncLogEntity.floorId!!)) {
                syncLogEntity.floorName = buildingIdMap[syncLogEntity.floorId!!]
            }
            syncLogEntity.localId = it.localId
            syncLogEntity.localName = it.localName
            syncLogEntity.cadId = it.cadId
            syncLogEntity.type = it.classCode
            list.add(syncLogEntity)
        }
        val sCreateRequest = SCreateRequest<SyncLogEntity>()
        sCreateRequest.content = list
        if(isDelete)
            dataService.syncLogService.delete(SFilter.eq("modelId", modelId))
        dataService.syncLogService.createList(sCreateRequest)
    }

    /**
     * 获取建筑的名称
     */
    private fun fulfillBuildingName(buildingIdMap: HashMap<String, String?>, projectId: String) {
        val result = dataCenterService.getBuilding(buildingIdMap.keys.toMutableList() as ArrayList<String>, projectId)
        result?.forEach {
            if(it.id == null)
                return@forEach
            if(it.localName.isNullOrEmpty()){
                if(!it.name.isNullOrEmpty())
                    buildingIdMap[it.id!!] = it.name
            }else{
                buildingIdMap[it.id!!] = it.localName
            }
        }
    }

    /**
     * 获取楼层的名称
     */
    private fun fulfillFloorName(floorIdMap: HashMap<String, String?>, projectId: String) {
        val result = dataCenterService.getFloor(floorIdMap.keys.toMutableList() as ArrayList<String>, projectId)
        result?.forEach {
            if(it.id == null)
                return@forEach
            if(it.localName.isNullOrEmpty()){
                if(!it.name.isNullOrEmpty())
                    floorIdMap[it.id!!] = it.name
            }else{
                floorIdMap[it.id!!] = it.localName
            }
        }
    }

    /**
     * 获取对象所属楼层id和建筑id
     */
    private fun getAllFloorBuildingId(resultList: ArrayList<DCEquipment>, floorIdMap: HashMap<String, String?>, buildingIdMap: HashMap<String, String?>) {
        resultList.forEach {
            if(it.floorId != null)
                floorIdMap.put(it.floorId!!, null)
            if(it.buildingId != null)
                buildingIdMap.put(it.buildingId!!, null)
        }
    }

    /**
     * 压缩底图
     */
    fun zipBaseGraph(modelId: String) : ByteArray{
        val modelGraph = BaseGraphRequest()
        modelGraph.modelId = modelId
        val baseGraph = BaseGraphService.composeBaseGraph(modelGraph)
        if(baseGraph.result == SResponseType.success) {
            val byteArrOut = ByteArrayOutputStream()
            val gzipOut = GZIPOutputStream(byteArrOut)
            try {
                TypeUtils.compatibleWithJavaBean = true
                val config = SerializeConfig()
                config.propertyNamingStrategy = PropertyNamingStrategy.PascalCase
                val str = JSON.toJSONString(baseGraph, config)
                // 压缩
                gzipOut.write(str.toByteArray())
                gzipOut.close()
                return byteArrOut.toByteArray()
            } catch (e: Exception) {
            }
        }
        throw Exception("底图获取失败")
    }

    /**
     * 发送底图到文件服务器
     */
    fun uploadBaseGraph(modelId : String, data : ByteArray) : Boolean{
        val result = HttpService.sendFileByte("${myProperties.fileService.baseGraphPath}${modelId}.jsonz", data)
        if(result.contains("success"))
            return true
        logger.error(result)
        return false
    }

//    /**
//     * 同步数据中心数据  (如果楼层的当前模型状态为4, 则做同步操作, 否则直接结束)
//     * 1. 检查BIMID是否有"ModelId:"开头的设备/部件/元空间
//     *      1. 如果有
//     *          1. 判断指定楼层是否有outline
//     *              1. 有
//     *                  1. 通过BIMLocation信息点判断是否在outline内
//     *                      1. 在的话就修改floorId为当前楼层id, 并将他们的BIMID改为 FloorId : SourceId
//     *                                   (如果有冲突的BIMID, 则将信息更新给原来在楼层下的对象, 并删除)
//     *                      2. 不在则不做操作
//     *              2. 没有
//     *                  1. 将这些设备/部件/元空间的FloorId改为指定楼层
//     *     2. 如果没有
//     *          1. 如果该楼层有outline, 则同步在outline内的数据
//     *          2. 如果该楼层没有outline, 则同步所有数据到该楼层
//     */
//    fun syncDataToDCFloor(floor: FloorModel, dcFloorId: String) {
//        if(floor.currentModelId.isNullOrBlank())
//            return
//        var modelFile = dataService.modelFileService.select(SFilter.eq("id", floor.currentModelId!!)).entity()
//        if(modelFile == null || modelFile.status != 4){
//            return
//        }
//        // 检查BIMID是否存在以"ModelId:"开头的设备/部件/元空间
//        val dcEquipmentMap = dataCenterService.getDCDataByBimIdPrefix(modelFile.id!!, floor.projectId!!, DataCenterService.DCDataType.equip)
//        val dcISpaceMap = dataCenterService.getDCDataByBimIdPrefix(modelFile.id!!, floor.projectId!!, DataCenterService.DCDataType.iSpace)
//        val dcComponentMap = dataCenterService.getDCDataByBimIdPrefix(modelFile.id!!, floor.projectId!!, DataCenterService.DCDataType.component)
//        if(dcEquipmentMap == null || dcISpaceMap == null || dcComponentMap == null){
//            throw Exception("获取数据中心数据失败")
//        }
//        if(dcEquipmentMap.isEmpty() && dcComponentMap.isEmpty() && dcISpaceMap.isEmpty()){
//            // 数据中心没有项目下的数据, 直接拿数据库中数据同步
//            val syncFloor = SSpringContextUtil.getBean(SyncFloor::class.java) as SyncFloor
//            syncFloor.init(floor).prepareDataCenterData()
//            // 数据中心没有查到该楼层
//            if (syncFloor.dcFloor.isNullOrEmpty()) {
//                throw Exception("要被绑定的数据中心的楼层不存在")
//            }else {
//                // 开始同步设备, 部件, 元空间
//                syncDataWithBindingFloor(syncFloor, modelFile, true)
//            }
//        }else{
//            // 数据中心项目下有设备, 如果绑定的楼层有outline, 则将在轮廓内的data的FloorId信息点设为该楼层id. 如果没有绑定outline, 则全部加入
//            val dcEquipmentMap = dataCenterService.getEquipment(dcFloorId, floor.projectId!!)
//            val dcISpaceMap = dataCenterService.getISpace(dcFloorId, floor.projectId!!)
//            val dcComponentMap = dataCenterService.getComponent(dcFloorId, floor.projectId!!)
//
//        }
//    }

//    /**
//     * 将map的key(BIMID)由 ID : SourceId 改为 SourceId
//     * 将对象的BIMID信息点改为bimIdPrefix : SourceId
//     */
//    fun updateBIMIDKey(map : HashMap<String, JSONObject>, bimIdPrefix : String){
//        var iterator = map.iterator()
//        val tmp = HashMap<String, JSONObject>()
//        while(iterator.hasNext()){
//            val item = iterator.next()
//
//        }
//    }
}