import { useStateStore } from '@/store/StateStore'
import { useVehicleStore } from '@/store/VehicleStore.js'
import { useMapStore } from '@/store/MapStore.js'
import writeLog from '@/js/writeLog.js'
import router from '../../router'
import { storeToRefs } from 'pinia'
import { Message } from 'view-ui-plus'

const pingIntervalSec = 5000 // 心跳链接时间
const NETWORK_COUNT = 3
const TRUCK_MONITOR_EXIT = 15 * 60
const TOPIC_CLASS = {
    vehicle: ['/vehicle/positioninfo'],
    map: ['/map/obstacles', '/map/lanenode', '/map/lane', '/map/runablearea', '/map/centerline', '/map/diggingworkarea', '/map/dumparea', '/map/barricade', '/map/stationarea', '/map/parkspot', '/map/staticobjs', '/map/electronicFence', 'mapVersionAck'],
    path: ['/vehicle/currPathinfo', '/vehicle/nextPathinfo', '/singleTrack/morepoint', '/singleTrack/morePath', '/vehicle/basicinfo', '/vehicle/dynamicPathinfo'], // 车辆循迹路径信息，当前路径；车辆循迹路径信息，下一段路径；单机循迹预览多路径；单机循迹预览单路径; 车辆动态路权信息
    hmi: [
        '/vehicle/debuginfo', // 车辆调试信息
        '/vehicle/statusinfo', // 车辆状态信息
        '/vehicle/errorCode', // 车辆故障码信息
        '/vehicle/pairinfo', // 车辆配对信息
        '/info/commandInfo', // 车辆提示信息（左下角）
        '/info/tipInfo', // 车辆弹框提示信息
        '/vehicle/paraminfo', // 车辆全局参数信息
        '/command/debugack', // 
        '/pattern/ack', // 车辆模式切换按钮-发送模式切换回复
        '/task/retranTimer', // 车辆任务重传定时器消息
        '/setting/singleTrackAck', // 设置单机循迹
        '/product', 
        '/info/popup', // 弹框确认信息
        '/setting/basicinfo', // 基本设置数据信息
        '/setting/light', // 灯光
        '/setting/safe', // 安全
        '/setting/controlInfo', // 启动参数
        '/setting/ack', // 设置信息结果返回
        '/setting/terminal', // 终端对应关系列表
        '/setting/terminalAck',
        '/setting/ignoreObstaclesAck', // 忽略障碍物回复消息
    ],
    mapHmi: [
        '/setting/map', // 地图管理图层
        '/setting/mapControlAck', // 地图设置回复
        '/setting/roughnessFileAck', // 地图不平整度文件回复
    ],
    login: [
        '/login/ack', // 登录信息
        '/btn/quitApp', // 退出
        '/truckmonitor/start', // 车端登录成功，有连结进来 车端就会发这个消息
        '/truckmonitor/exit', // 车端程序退出，不关闭websocket
    ],
    heart: ['heartbeat']
}

const SETTING_TYPE = {
    1: '基本信息',
    2: '灯光信息',
    3: '安全信息',
    4: '启动控制信息',
    5: '地图管理信息',
    6: '单机循迹信息',
    // 7: '终端打印信息'
}

export default class Socket {
    constructor(root) {
        this.ws = new WebSocket(`ws://${document.location.serviceIP}`)
        this.stateStore = useStateStore()
        this.vehicleStore = useVehicleStore()
        this.mapStore = useMapStore()
        this.heartTimer = null // 心跳定时器
        this.serverTimer = null // 服务器超时定时器
        this.sendFixHeartTimer = null // 定时发送心跳
        this.timeoutTimer = null // 超时定时器
        this.lockReconnect = false // 是否真正建立连接
        this.reconnectTimer = null // 重连倒计时
        this.isSendSync = false // 是否要重连成功之后，发送同步消息
        this.heartCount = 0 // 重连之后，如果没有收到车端登录的消息，需要发送同步消息
        this.root = root
    }

    getConnection() {
        const self = this
        return new Promise((resolve, reject) => {
            self.ws = new WebSocket(`ws://${document.location.serviceIP}`)
            self.ws.onopen = function () {
                // 开启心跳
                self.start()
                self.sendFixHeart()
                self.resetTimeout()
                self.count = 0
                resolve(self.ws)
            }

            self.ws.onerror = function (err) {
                console.log(err)
                self.isSendSync = true
                self.reconnect()
            }
    
            self.ws.onclose = function () {
                !self.isClosed && self.reconnect()
                console.log('closed', new Date())
            }

            self.ws.onmessage = function (msg) {
                self.dealData(msg.data)
                // 收到服务器信息，心跳重置
                self.reset()
            }
        })
    }

    start() {
        this.heartTimer && clearTimeout(this.heartTimer)
        this.serverTimer && clearTimeout(this.serverTimer)
        
        this.heartTimer = setTimeout(() => {
            this.send({
                type: 'heartbeat',
                msg: 'heart beat'
            })

            // 超时关闭
            this.serverTimer = setTimeout(() => {
                this.ws.close()
            }, 15000)
        }, pingIntervalSec)
    }

    // 15s定时发送心跳
    sendFixHeart () {
        this.sendFixHeartTimer && clearInterval(this.sendFixHeartTimer)
        this.sendFixHeartTimer = setInterval(() => {
            this.send({
                type: 'heartbeat',
                msg: 'heart beat'
            })
        }, 15000)
    }

    resetTimeout () {
        const self = this
        if (this.timeoutTimer) {
            this.timeoutCount = 0
            this.stateStore.dealNetworkTip({type: false})
            clearInterval(this.timeoutTimer)
        }
        this.timeoutTimer = setInterval(() => {
            if (this.timeoutCount > NETWORK_COUNT) {
                this.stateStore.dealNetworkTip({type: true, count: this.timeoutCount})
            }
            this.timeoutCount ++
            // 超时一定时间，认为车端程序未启动，跳转登录页面
            if (self.timeoutCount > TRUCK_MONITOR_EXIT) {
                this.dealTruckMointor({
                    visible: true,
                    msg: '车端程序未运行',
                    type: 1
                })
                router.push({
                    name: 'login'
                })
            }
        }, 1000)
    }

    reset () {
        this.start()
    }

    reconnect () {
        if (this.lockReconnect) return
        const self = this
        // this.resetTimeout()
        writeLog('WARNING',"网络异常")
        console.log('reconnect-----')
        if (this.count > 5) {
            Message.error('当前网络不稳定')
            this.count = 0
            // return
        }
        this.count ++

        this.lockReconnect = true
        this.reconnectTimer && clearTimeout(this.reconnectTimer)
        this.reconnectTimer = setTimeout(() => {
            this.getConnection().then(sock => {
                self.root.sock = sock
            })
            this.lockReconnect = false
        }, 4000)
    }

    send (msg) {
        msg = JSON.stringify(msg)
        console.log(`发送消息${msg}`, new Date())
        this.ws && this.ws.send(msg)
    }

    sendLoginMsg() {
        let loginForm = this.stateStore.loginForm || localStorage.getItem(`${location.host}loginForm`)
        if (typeof loginForm === 'string') {
            loginForm = JSON.parse(loginForm)
        }
        this.send({
            type: '/login/req',
            msg: loginForm
        })
    }

    judgeCurrentPath() {
        let currentPath = router.currentRoute.value.path
        if (currentPath.includes('basic-message')) {
            this.sendLoginMsg()
        }
    }

    dealTruckMointor (msg) {
        this.stateStore.setTruckMonitor(msg)
    }

    // 初始化indexDB里的地图图层数据
    initMapLayer(layerName, version) {
        let db = this.root.indexDB
        db.getStoreData(layerName, version).then(res => {
            if (res) {
                this.mapStore.setData(layerName, res.msg)
            } else {
                let data = {
                    type: 'syncMapData',
                    msg: [layerName]
                }
                this.send(data)
            }
        }).catch(error => {
            console.warn(error)
        })
    }

    sendVehicleSyncMsg() {
        let syncVehicleData = {
            type: 'syncVehicleData',
            msg: {}
        }
        this.send(syncVehicleData)
    }

    async dealMapVersion(msg) {
        console.log('msg', msg)
        let storeLayers = window.localStorage.getItem('layers')
        let syncMapLayers = [], initLayers = []
        if (storeLayers) {
            let layers = JSON.parse(storeLayers)
            for (let i = 0; i < msg.length; i++) {
                let item = msg[i]
                let {layerName, layerVersion} = item
                let oldLayer = layers.find(item => item.layerName === layerName)
                if (oldLayer) {
                    let {layerVersion: oldLayerVersion} = oldLayer
                    if (oldLayerVersion !== layerVersion) {
                        syncMapLayers.push(layerName)
                    } else {
                        initLayers.push({layerName, layerVersion})
                    }
                } else {
                    syncMapLayers.push(layerName)
                }
            }
        } else {
            syncMapLayers = msg.map(item => item.layerName)
        }

        if (syncMapLayers.length) {
            await this.root.indexDB.initStore(syncMapLayers)

            if (initLayers.length) {
                await this.root.indexDB.compareTable(initLayers)
                initLayers.forEach(({layerName, layerVersion}) => {
                    this.initMapLayer(layerName, layerVersion)
                })
            }

            let data = {
                type: 'syncMapData',
                msg: syncMapLayers
            }
            this.send(data)
        } else {
            if (initLayers.length) {
                await this.root.indexDB.compareTable(initLayers)
                initLayers.forEach(({layerName, layerVersion}) => {
                    this.initMapLayer(layerName, layerVersion)
                })
            }
            this.sendVehicleSyncMsg()
        }

        localStorage.setItem('layers', JSON.stringify(msg))
    }

    dealheartData() {
        if (this.isSendSync && this.heartCount > 2) {
            // 没有收到车端退出的消息时，收到3次心跳，就发送同步消息
            if (this.heartCount > 2) {
                this.judgeCurrentPath()
                this.isSendSync = false
                this.heartCount = 0
            } else {
                this.heartCount ++
            }
        }
    }

    dealmapData(result, key) {
        const self = this
        const {type, msg, version} = result
        let worker = this.root.worker.worker[key]
        switch(type) {
            case 'mapVersionAck':
                this.dealMapVersion(msg)
                break
            default:
                let strData = window.atob(msg);
                worker.postMessage({type, msg: strData, version})
                break
        }
        worker.onmessage = ({ data }) => {
            const {mapName, result, mapVersion} = data
            self.mapStore.setData(mapName, result)
            self.root.indexDB.storeData(mapName, result, mapVersion)
            self.sendVehicleSyncMsg()
        }
    }

    dealpathData(result, key) {
        const {type, msg} = result
        let [, , name] = type.split('/')
        const worker = this.root.worker.worker[key]
        switch(type) {
            case '/vehicle/basicinfo':
                const {avoidancePath, obstacle} = msg
                avoidancePath && worker.postMessage({name: 'avoidancePath', msg: avoidancePath})
                obstacle && worker.postMessage({name: 'obstacle', msg: obstacle})
                this.vehicleStore.setBasicInfo(msg)
                break
            case '/vehicle/nextPathinfo':
                let { available, path } = msg
                this.mapStore.setData('nextPathAvailable', available)
                path && worker.postMessage({name, msg: path})
                break
            default:
                msg && worker.postMessage({name, msg})
                break
        }

        worker.onmessage = ({data}) => {
            const {name, dealedData} = data
            this.mapStore.setData(name === 'obstacle' ? 'obstacleData' : name, dealedData)
        }
    }

    dealvehicleData(result, key) {
        const {type, msg} = result
        const vehicleStoreToRefs = storeToRefs(this.vehicleStore)
        const {paraminfo, positioninfo} = vehicleStoreToRefs
        const {deviceId} = paraminfo.value
        const worker = this.root.worker.worker[key]
        const prePositionInfo = JSON.parse(JSON.stringify(positioninfo.value))
        worker.postMessage({type, msg, deviceId, pre: prePositionInfo})

        const diffWorker = this.root.worker.worker['vehicleDiff']
        diffWorker.postMessage({pre: prePositionInfo, cur: msg})

        worker.onmessage = ({data}) => {
            const {vehicles, safeObj, trajObj, gps, currentBasic} = data
            this.stateStore.setData('gps', gps)
            currentBasic && this.vehicleStore.setData('currentBasic', currentBasic)
            this.vehicleStore.$patch({
                vehiclesPositioninfo: vehicles,
                safeObj: safeObj,
                trajObj: trajObj
            })
        }

        diffWorker.onmessage = ({data}) => {
            this.vehicleStore.setData('deleteVehicles', data)
        }

        this.vehicleStore.setData('positioninfo', msg)
    }

    handleSettingAck(msg) {
        let {type, code} = msg
        if (!SETTING_TYPE[type]) return
        let ackType = code === 0 ? 'success' : 'error'
        let text = SETTING_TYPE[type];
        if (type == 4) {
            text = '指令'
        }
        Message[ackType](`${text}设置${code === 0 ? '成功' : '失败'}`)
        if (type == 6 && code === 0) {
            let data = {
                type: '/setting/req',
                msg: {
                    type: 6
                }
            }
            this.send(data)
        }
    }

    dealObstaclesAck(msg) {
        let {code} = msg
        let type = code === 0 ? 'success' : 'error'
        let tip = code === 0 ? '成功' : '失败'
        Message[type](`发送车端${tip}`)
    }

    dealhmiData(result) {
        const {type, msg} = result
        let [, , name] = type.split('/')
        // console.log(name, msg)
        switch(type) {
            // 车辆状态信息
            case '/vehicle/statusinfo':
                const {pattern, connStatus} = msg
                this.stateStore.setData('wifi', connStatus)
                this.vehicleStore.setData('pattern', pattern)
                this.vehicleStore.setData(name, msg)
                break
            // 车辆故障码信息
            case '/vehicle/errorCode':
                this.vehicleStore.dealErrorCode(msg)
                break
            // 车辆模式切换按钮-发送模式切换回复
            case '/pattern/ack':
                this.vehicleStore.setData('patternAck', msg)
                break
            // 弹框确认信息
            case '/info/popup':
                this.vehicleStore.dealInfoPop(msg)
                break
            // 设置信息结果返回
            case '/setting/ack':
                this.handleSettingAck(msg)
                break
            case '/setting/terminalAck':
                this.vehicleStore.dealTerminalAck(msg)
                break
            case '/setting/ignoreObstaclesAck':
                this.dealObstaclesAck(msg)
                break
            default: 
                this.vehicleStore.setData(name, msg)
                break
        }
    }

    dealmapHmiData(result) {
        const {type, msg} = result
        let [, , name] = type.split('/')
        switch(type) {
            // 地图不平整度文件回复
            case '/setting/roughnessFileAck':
                const {type, fileName} = msg
                if (type === 1) {
                    this.mapStore.setData(name, fileName)
                }
                break
            default:
                this.mapStore.setData(name, msg)
                break
        }
    }

    startTime() {
        const self = this
        const worker = this.root.worker.worker.time
        worker.postMessage('start')

        worker.onmessage = ({data}) => {
            self.stateStore.setData('currentTime', data)
        }
    }

    postParentMessage(data) {
        if (data) {
            let loginInfo = data
            if (window.top && loginInfo && data.code ===0) {
                window.top.postMessage({
                    type: 'token',
                    data: {
                        ip: window.location.host,
                        loginInfo,
                    }
                }, '*')
            }

            try {
                localStorage.setItem(window.location.host, JSON.stringify(loginInfo))
            } catch (error) {
                this.isTraceless = true
                console.log(error)
                Message.error('请退出无痕模式！')
            }
        }
    }

    dealloginData(result) {
        const {type, msg} = result
        switch(type) {
            // 车端登录成功，有连结进来 车端就会发这个消息
            case '/truckmonitor/start':
                // 收到车端登录成功的消息，则不需要判断收到心跳的次数
                // 需要判断是否再登录页面，如果再登录页面，不需要处理，如果不在登录页面，则需要发送同步信息
                this.isSendSync = false
                this.judgeCurrentPath()
                this.dealTruckMointor({
                    visible: false,
                    type: 1
                })
                break
            case '/truckmonitor/exit':
                router.push({
                    name: 'login'
                })
                this.dealTruckMointor({
                    visible: true,
                    msg: '车端程序未运行',
                    type: 1
                })
                this.stateStore.setData('loginAck', msg)
                break
            // 登录
            case '/login/ack':
                console.log('收到登录信息', new Date())
                this.stateStore.setData('loginAck', msg)
                this.postParentMessage(msg)
                // this.startTime()
                break
            // 退出
            case '/btn/quitApp':
                if (window.state === 'logout') {
                    window.state = null
                    localStorage.removeItem(window.location.host)
                    this.close()
                    router.push({
                        name: 'login'
                    })
                }
        }
    }

    async dealData(data) {
        this.resetTimeout()
        let result = await (new Response(data)).text()

        if (typeof result === 'string' && result !== 'heart beat') {
            try {
                result = JSON.parse(result)
            } catch (err) {
                console.log('err----', result)
            }
        }

        const {type, msg} = result
        // console.log(`接收消息${type}`, new Date().format('yyyy-MM-dd hh:mm:ss'))
        if (!type) return

        // console.log(type)

        let keys = Object.keys(TOPIC_CLASS)
        for (let i = 0; i < keys.length; i++) {
            let key = keys[i]
            if (TOPIC_CLASS[key].includes(type)) {
                this[`deal${key}Data`](result, key)
                break
            }
        }
    }

    close () {
        this.isClosed = true
        this.heartTimer && clearTimeout(this.heartTimer)
        this.serverTimer && clearTimeout(this.serverTimer)
        this.reconnectTimer && clearTimeout(this.reconnectTimer)
        this.sendFixHeartTimer && clearInterval(this.sendFixHeartTimer)
        this.timeoutTimer && clearInterval(this.timeoutTimer)
        this.heartTimer = null
        this.serverTimer = null
        this.reconnectTimer = null
        this.sendFixHeartTimer = null
        this.ws.close()
        this.root.sock = null
        this.root.worker = null
        this.root.indexDB = null
    }
}