package com.waytous.anticollision.tcp

import com.blankj.utilcode.util.DeviceUtils
import com.waytous.anticollision.config.DeviceConfig
import com.waytous.anticollision.config.Settings
import com.waytous.anticollision.listener.SessionListener
import com.waytous.anticollision.utils.*
import io.github.toggery.jt808.codec.*
import io.github.toggery.jt808.messagebody.*
import io.netty.buffer.ByteBuf
import io.netty.buffer.UnpooledByteBufAllocator
import io.netty.util.DefaultAttributeMap
import java.util.concurrent.Executors
import java.util.concurrent.ScheduledFuture
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.locks.ReentrantLock

/**
 * 最大尝试链接次数
 * */
const val CONNECT_MAX_RETRY = 3

/**
 * 设备状态
 * @author male
 * */
internal enum class DeviceStatus {
    /**
     * 未注册
     * */
    Unregistered,

    /**
     * 注册中
     * */
    Registering,

    /**
     * 已册中
     * */
    Registered,

    /**
     * 未鉴权
     * */
    UnAuthenticated,

    /**
     * 正在鉴权
     * */
    Authenticating,

    /**
     * 已鉴权
     * */
    Authenticated
}

internal class Schedule {

    companion object {
        const val tcpReadIntervalSecs: Long = 2  // 2 seconds
        const val tcpConnectTimeoutSecs = 40       // 40 seconds
        const val signInTimeoutSecs: Long = 60       // 60 seconds
        const val heartbeatInterval = 120   // 120 seconds
    }
}

/**
 * jt808网络会话管理
 * @author male
 * */
internal class Session : ConnectListener, SyncMessageListener<AbstractToStringJoiner> {

    /**
     * 消息内容解析器
     * */
    private val mParser by lazy {
        SyncParser(this)
    }

    /**
     * 开启新线程，将接收到的字节流交给消息内容解析器解析
     * */
    private val parseExecutor by lazy {
        Executors.newSingleThreadExecutor(NamedThreadFactory("Parser"))
    }

    /**
     * 设备状态
     * */
    private val deviceStatus by lazy {
        AtomicReference(DeviceStatus.UnAuthenticated)
    }

    /**
     * tcp链接管理者
     * */
    private val tcpManager by lazy {
        TcpManager(this, 2)
    }

    private val buffs by lazy {
        mutableListOf<ByteBuf>()
    }

    private val listeners by lazy {
        mutableSetOf<SessionListener>()
    }

    private val attributeMap by lazy {
        DefaultAttributeMap()
    }

    private val scheduler by lazy {
        Executors.newScheduledThreadPool(1, NamedThreadFactory("Heartbeat"))
    }

    private var scheduleFutureTask: ScheduledFuture<*>? = null

    private val mOperateLock by lazy {
        ReentrantLock()
    }

    private val mOperateCondition by lazy {
        mOperateLock.newCondition()
    }

    override fun onDataReceived(buf: ByteBuf) {
        parseExecutor.execute { mParser.parse(buf) }
    }

    override fun onConnect() {
        if (deviceStatus.get() == DeviceStatus.UnAuthenticated) {
            tcpManager.disconnect()
            return
        }
        tryLogin(::doDeviceSignUp)
    }

    /**
     * 设备尝试登录
     * */
    private fun tryLogin(block: () -> Boolean) {
        var retryTimes = 0
        var success: Boolean
        do {
            success = block()
            retryTimes++
        } while (retryTimes < CONNECT_MAX_RETRY && !success)
        if (!success) {
            handleDisconnect(Error.JT808EncodeError)
        }
    }

    /**
     * 错误处理逻辑
     * */
    private fun handleDisconnect(error: Error) {
        when (error) {
            Error.IOError, Error.StreamClosed, Error.Timeout, Error.NotConnected -> {

            }
            else -> {}
        }
    }

    /**
     * 设备登录
     * */
    fun login(): SignInStatus {
        mOperateLock.lock()
        try {
            deviceStatus.set(DeviceStatus.Registering)
            tcpManager.connect(DeviceConfig.HostConfig.host, DeviceConfig.HostConfig.port, Schedule.tcpConnectTimeoutSecs)
            return if (mOperateCondition.await(Schedule.signInTimeoutSecs, TimeUnit.SECONDS)) {
                when (deviceStatus.get()) {
                    DeviceStatus.Authenticated -> {
                        SignInStatus.SignIn
                    }
                    else -> {
                        SignInStatus.SignOut
                    }
                }
            } else {
                SignInStatus.SignOut
            }
        } catch (e: InterruptedException) {
            loge("登录线程被中断，被中断线程：${Thread.currentThread().name},中断线程：${e.printStackTrace()}")
            return SignInStatus.SignOut
        } finally {
            mOperateLock.unlock()
        }
    }

    /**
     * 添加监听器
     * */
    fun addSessionListener(listener: SessionListener) {
        synchronized(listeners) {
            listeners.add(listener)
        }
    }

    /**
     * 删除监听器
     * */
    fun removeSessionListener(listener: SessionListener) {
        synchronized(listeners) {
            listeners.remove(listener)
        }
    }

    private fun notifyConnectStatus(status: ConnectStatus) {
        if (deviceStatus.get() != DeviceStatus.Authenticated) {
            return
        }
        synchronized(listeners) {
            listeners.forEach {
                it.onConnectStatusChanged(status)
            }
        }
    }

    private fun notifySignInStatus(status: SignInStatus, error: Error) {
        synchronized(listeners) {
            listeners.forEach {
                if (status == SignInStatus.SignIn) {
                    it.onUserSignIn()
                } else {
                    it.onUserSignOut(error)
                }
            }
        }
    }

    /**
     * 设备鉴权
     * */
    private fun doAuthenticate(): Boolean {
        val b0102 = B0102().apply {
            token = Settings.token
        }
        val request = Message.of(0x0102, b0102).apply {
            simNo = DeviceConfig.phoneNumber
        }
        Message.encode(
            request, MessageMetadata.inbounds(), attributeMap,
            UnpooledByteBufAllocator.DEFAULT::buffer, buffs::add
        )
        deviceStatus.set(DeviceStatus.Authenticating)
        return doSendMessage("设备鉴权")
    }

    /**
     * 发送设备注册消息
     * */
    private fun doDeviceSignUp(): Boolean {
        Settings.isRegistered = false
        Settings.deviceId = ""
        val b0100 = B0100().apply {
            province = DeviceConfig.province
            city = DeviceConfig.city
            model = DeviceUtils.getModel()
            plateColor = B0100.PLATE_COLOR_YELLOW
            maker = DeviceUtils.getManufacturer()
            id = DeviceUtils.getUniqueDeviceId()
        }
        val request = Message.of(0x0100, b0100).apply {
            simNo = DeviceConfig.phoneNumber
        }
        Message.encode(
            request, MessageMetadata.inbounds(), attributeMap,
            UnpooledByteBufAllocator.DEFAULT::buffer, buffs::add
        )
        return doSendMessage("设备注册")
    }

    /**
     * 开始心跳
     * */
    private fun startHeartbeat() {
        scheduleFutureTask = scheduler.scheduleAtFixedRate(
            ::sendPing,
            0,
            Schedule.tcpReadIntervalSecs,
            TimeUnit.SECONDS
        )
    }

    /**
     * 结束心跳
     * */
    private fun stopHeartbeat() {
        if (scheduleFutureTask?.isDone == false) {
            scheduleFutureTask?.cancel(true)
        }
    }

    /**
     * 发送心跳
     * */
    private fun sendPing() {
        val request = Message.of(0x0002).apply {
            simNo = DeviceConfig.phoneNumber
        }
        Message.encode(
            request, MessageMetadata.inbounds(), attributeMap,
            UnpooledByteBufAllocator.DEFAULT::buffer, buffs::add
        )
        doSendMessage("发送心跳")
    }

    /**
     * 发送心跳
     * */
    private fun doSendMessage(name: String) = if (buffs.isNotEmpty()) {
        val byteArray = ByteArray(buffs[0].readableBytes())
        buffs[0].readBytes(byteArray)
        logd("【$name】:${HexUtil.dump(byteArray)}")
        tcpManager.send(byteArray)
        synchronized(buffs) {
            buffs.removeAt(0)
        }
        true
    } else {
        loge("【$name】:buffs is empty,Message encode failed!")
        false
    }

    override fun onDisconnect(error: Error) {
        stopHeartbeat()
        logd(error.reason)
    }

    override fun onSignUp(payload: AbstractToStringJoiner) {
        Settings.deviceId = DeviceUtils.getUniqueDeviceId()
        Settings.isRegistered = true
        Settings.token = (payload as B8100).token
        deviceStatus.set(DeviceStatus.Registered)
        tryLogin(::doAuthenticate)
    }

    override fun onCommonResponse(payload: AbstractToStringJoiner) {
        val codec = (payload as B8001)
        when (codec.replyId) {
            0x0100 -> {
                when (codec.result) {
                    B8001.RESULT_SUCCESSFUL -> {
                        deviceStatus.set(DeviceStatus.Registered)
                        loge("设备注册成功！")
                    }
                    B8001.RESULT_FAILED -> {
                        deviceStatus.set(DeviceStatus.Unregistered)
                        loge("设备注册失败！")
                    }
                    B8001.RESULT_WRONG -> {
                        deviceStatus.set(DeviceStatus.Unregistered)
                        loge("设备注册消息有误！")
                    }
                }
            }
            0x0102 -> {
                when (codec.result) {
                    B8001.RESULT_SUCCESSFUL -> {
                        if (deviceStatus.get() == DeviceStatus.Authenticating) {
                            deviceStatus.set(DeviceStatus.Authenticated)
                            startHeartbeat()
                            loge("设备鉴权成功！")
                        } else {
                            deviceStatus.set(DeviceStatus.UnAuthenticated)
                            loge("设备鉴权失败！")
                        }
                    }
                    B8001.RESULT_FAILED -> {
                        deviceStatus.set(DeviceStatus.UnAuthenticated)
                        loge("设备鉴权失败！")
                    }
                    B8001.RESULT_WRONG -> {
                        deviceStatus.set(DeviceStatus.UnAuthenticated)
                        loge("设备鉴权消息有误！")
                    }
                }
            }
            0x0002 -> {
                when (codec.result) {
                    B8001.RESULT_SUCCESSFUL -> {
                        logd("心跳响应成功")
                    }
                    B8001.RESULT_FAILED -> {
                        loge("心跳响应失败！")
                    }
                    B8001.RESULT_WRONG -> {
                        loge("心跳消息有误！")
                    }
                }
            }
        }
    }

}