Imports System.IO.Ports
Imports System.Text
Imports System.Threading

Imports UTS_Core.UTSModule.Test.StatusMonitor
Imports UTS_Core.UTSModule.Test.StatusMonitor.TestCommandStatusMonitor

Namespace UTSModule.Test
    Public Class ControllerComPortTask
        Private _stateMachineTaskStatus As StateMachineStatus

        ''' <summary>
        ''' 当前串口对象
        ''' </summary>
        Public ReadOnly Conn As ControllerComPort

        ''' <summary>
        ''' UTS串口波特率,默认115200
        ''' </summary>
        Public Property ComportBaudRate As Integer


        ''' <summary>
        ''' 接收等待最长毫秒数,默认100ms
        ''' </summary>
        Public Property NormalWaitReplyMaxTime As Integer


        ''' <summary>
        ''' 串口通讯写入失败计数,通讯成功置零，通讯失败则加1
        ''' </summary>
        Public Property ComportWriteFailCount As Integer


        ''' <summary>
        ''' 串口通讯写入失败最大上限，默认5次
        ''' </summary>
        Public Property ComportWriteMaxFailCount As Integer

        ''' <summary>
        ''' 状态机执行间隔，默认10ms
        ''' </summary>
        Public Property StateMachineInterval As Integer

        ''' <summary>
        ''' 发送同步命令的间隔,默认1000ms
        ''' </summary>
        Public SendSyncInterval As Integer


        ''' <summary>
        ''' 测试状态是否发生变化
        ''' </summary>
        Public TestStatusChanged As Boolean

        ''' <summary>
        ''' 是否收到按键主动数据，需要回复ACK
        ''' </summary>
        Public WaitReplyKeyStatusAck As Boolean

        ''' <summary>
        ''' 发送同步命令的时间
        ''' </summary>
        Public SendSyncTime As DateTime

        ''' <summary>
        ''' 已发送通讯数据,等待回复的时间
        ''' </summary>
        Public WaitReplyStartTime As DateTime

        ''' <summary>
        ''' 当前可以访问的串口名集合
        ''' </summary>
        Private Shared ReadOnly ComportNames As New Queue(Of String)

        ''' <summary>按键串口控制器句柄,全局唯一</summary>
        Private Shared _controller As ControllerComPortTask

        ''' <summary>初始化测试器线程锁</summary>
        Private Shared ReadOnly InitLock As New Object()

        ''' <summary>
        ''' 创建按键串口控制器,若按键串口控制器已经创建则返回句柄
        ''' </summary>
        ''' <returns></returns>
        Public Shared Function CreateControllerPortTask() As ControllerComPortTask
            If _controller Is Nothing Then
                SyncLock InitLock
                    Thread.MemoryBarrier()
                    If _controller Is Nothing Then
                        _controller = New ControllerComPortTask
                    End If
                End SyncLock
            End If
            Return _controller
        End Function


        Private Sub New()
            Conn = New ControllerComPort()

            ComportBaudRate = 115200
            NormalWaitReplyMaxTime = 100
            ComportWriteFailCount = 0
            ComportWriteMaxFailCount = 5

            StateMachineInterval = 10

            SendSyncInterval = 1000

        End Sub


        ''' <summary>
        ''' 开启任务
        ''' </summary>
        Public Sub StartTask()
            'todo：当前版本无控制器，取消开启控制器状态机
            Return

            '避免重复开启状态机
            If _stateMachineTaskStatus <> StateMachineStatus.Quit Then Return

            _stateMachineTaskStatus = StateMachineStatus.Idle

            '状态机执行
            While _stateMachineTaskStatus <> StateMachineStatus.Quit
                StateMachine()
                Thread.Sleep(StateMachineInterval)
            End While

            '状态机退出
            Conn.CloseSerialPort()
        End Sub

        Public Sub EndTask()
            _stateMachineTaskStatus = StateMachineStatus.Quit
        End Sub

#Region "控制模块"

        ''' <summary>
        ''' 期望回复的测试命令。发送测试命令后，判断串口接收是否为指定命令的回复数据
        ''' </summary>
        Public ExpectReplyTestCommand As String = ""


        ''' <summary>
        ''' 需要发送的测试命令
        ''' </summary>
        Public TestSendData As ControllerComPortData


        ''' <summary>
        ''' 接收测试命令
        ''' </summary>
        Public TestReceiveData As ControllerComPortData


        ''' <summary>
        ''' 测试命令接收等待最长时间
        ''' </summary>
        Public TestWaitReplyMaxTime As Integer = 100
#End Region



#Region "状态机执行细节"
        ''' <summary>
        ''' J_Controller状态机
        ''' </summary>
        Private Sub StateMachine()
            Pretreatment()

            Select Case _stateMachineTaskStatus
                Case StateMachineStatus.Quit
                    '退出状态机
                Case StateMachineStatus.Idle
                    IdleFunction()
                Case StateMachineStatus.GetComport
                    Thread.Sleep(500) '避免串口打开操作过于频繁
                    GetComPortFunction()
                Case StateMachineStatus.SendHearBeat
                    SendHearBeat()
                Case StateMachineStatus.WaitReply
                    WaitReply()
                Case StateMachineStatus.SendCommand
                    SendCommand()
                Case StateMachineStatus.ReplyKeyStatusAck
                    ReplyKeyStatusAck()
                Case StateMachineStatus.ComportConnected
                    ComportConnected()
                Case StateMachineStatus.ComportDisconnected
                    ComportDisconnected()
                Case Else
                    Throw New Exception($"Unknown ControllerStatus {_stateMachineTaskStatus}")
            End Select
        End Sub

        Private Sub Pretreatment()
            '自动上报的命令处理
            If Conn.CacheData.Count = 0 Then Return

            For i As Integer = 0 To Conn.CacheData.Count - 1
                Dim receivedData As ControllerComPortData = Conn.CacheData.Peek()

                '处理自发上报通讯数据
                If String.Compare(receivedData.Command, "KEY_EVENT", True) = 0 Then '按键按下
                    '避免状态机卡顿
                    ThreadPool.QueueUserWorkItem(AddressOf UpdateKeyValue, receivedData.Params(0))
                    WaitReplyKeyStatusAck = True '需要回复键值包ACK
                    Conn.CacheData.Dequeue() '移除头部
                ElseIf String.Compare(receivedData.Command, "INPUT_CHANGE_CH", True) = 0 Then '电平变化按下
                    'todo:具体处理通道
                Else
                    If _stateMachineTaskStatus = UtsComportTask.ComportStatusEnum.WaitReply Then
                        Continue For
                    Else
                        Conn.CacheData.Dequeue() '移除头部
                    End If
                End If
            Next
        End Sub


        Private Sub UpdateKeyValue(obj As Object)
            Dim key As String = obj.ToString()
            Dim tmp As UtsKeyValueMonitor.UtsKeyValueEnum = UtsKeyValueMonitor.UtsKeyValueEnum.None
            If [Enum].TryParse(key, tmp) Then
                UtsKeyValueMonitor.UtsKeyValue = tmp
            End If
        End Sub

        Private Sub IdleFunction()
            If ControllerComPortStatusMonitor.ComportStatus = ComportStatusMonitor.ComPortConnectStatusEnum.Connected Then
                '有测试命令需要下发
                If ControllerCommandStatus = TestCommandStatusEnum.WaitingToSend Then
                    _stateMachineTaskStatus = StateMachineStatus.SendCommand
                    Return
                End If

                '收到按键状态命令，需要回复ACK
                If WaitReplyKeyStatusAck = True Then
                    WaitReplyKeyStatusAck = False
                    _stateMachineTaskStatus = StateMachineStatus.ReplyKeyStatusAck
                    Return
                End If

                '测试状态变化，需要发送状态变化指令
                If TestStatusChanged = True Then
                    TestStatusChanged = False
                    _stateMachineTaskStatus = StateMachineStatus.SendHearBeat
                    Return
                End If

                '定期发送Sync命令
                Static span As TimeSpan
                span = Now - SendSyncTime
                If span.TotalMilliseconds >= SendSyncInterval Then
                    _stateMachineTaskStatus = StateMachineStatus.SendHearBeat '发送同步命令
                    Return
                End If
            Else
                _stateMachineTaskStatus = StateMachineStatus.GetComport '获取设备
            End If
        End Sub


        Private Sub GetComPortFunction()
            ControllerComPortStatusMonitor.ComportStatus = ComportStatusMonitor.ComPortConnectStatusEnum.Connecting

            If ComportNames.Count > 0 Then
                Dim comportName As String = ComportNames.Dequeue()

                '避免重复打开测试设备的串口号
                If ComportStatusMonitor.ComportStatus = ComportStatusMonitor.ComPortConnectStatusEnum.Connected Then
                    If String.Compare(UtsComportTask.Conn.ComportName, comportName) = 0 Then
                        _stateMachineTaskStatus = StateMachineStatus.Idle
                        Return
                    End If
                End If

                If Conn.OpenSerialPort(comportName, ComportBaudRate) = False Then
                    _stateMachineTaskStatus = StateMachineStatus.Idle
                    Return
                End If

                '发送询问包
                Dim sendData As New ControllerComPortData("*IDN?")

                If Conn.SendTextAppendCrLf(sendData) Then
                    ComportWriteFailCount = 0
                    WaitReplyStartTime = Now
                    _stateMachineTaskStatus = StateMachineStatus.WaitReply
                Else
                    _stateMachineTaskStatus = StateMachineStatus.Idle
                End If
            Else
                For Each comportName As String In UtsComPort.GetPortNames()
                    ComportNames.Enqueue(comportName) '添加可用串口列表
                Next
                _stateMachineTaskStatus = StateMachineStatus.Idle
            End If
        End Sub

        Public Sub SendHearBeat()
            Dim str As String = Choose(TestStatusMonitor.TestStatus + 1, "IDEL", "TESTING", "PASS", "FAIL").ToString()
            Dim sendData As New ControllerComPortData("TEST_STATUS", str)

            If Conn.SendTextAppendCrLf(sendData) Then
                ComportWriteFailCount = 0
                SendSyncTime = Now '有数据通讯,发送同步命令时间更新

                WaitReplyStartTime = Now
                _stateMachineTaskStatus = StateMachineStatus.WaitReply
            Else
                ComportWriteFailCount += 1
                If ComportWriteFailCount >= ComportWriteMaxFailCount Then
                    ComportWriteFailCount = 0
                    _stateMachineTaskStatus = StateMachineStatus.ComportDisconnected
                Else
                    _stateMachineTaskStatus = StateMachineStatus.Idle
                End If
            End If
        End Sub

        Public Sub WaitReply()
            Dim timeout As Integer
            If ControllerCommandStatus = TestCommandStatusEnum.WaitingToReceive Then
                timeout = TestWaitReplyMaxTime
            Else
                timeout = NormalWaitReplyMaxTime
            End If

            Dim span As TimeSpan = Now - WaitReplyStartTime
            If span.TotalMilliseconds > timeout Then '超时
                If ControllerCommandStatus = TestCommandStatusEnum.WaitingToReceive Then
                    ControllerCommandStatus = TestCommandStatusEnum.ReceiveTimeout '测试命令接收超时
                    _stateMachineTaskStatus = StateMachineStatus.Idle
                Else
                    If ControllerComPortStatusMonitor.ComportStatus = ComportStatusMonitor.ComPortConnectStatusEnum.Connected Then
                        ComportWriteFailCount += 1
                        If ComportWriteFailCount >= ComportWriteMaxFailCount Then '超过写入失败上限
                            ComportWriteFailCount = 0
                            _stateMachineTaskStatus = StateMachineStatus.ComportDisconnected
                        Else
                            _stateMachineTaskStatus = StateMachineStatus.Idle
                        End If
                    Else
                        _stateMachineTaskStatus = StateMachineStatus.Idle
                    End If
                End If
            Else
                If Conn.CacheData.Count > 0 Then
                    '等待回复的期望值检测
                    Dim receivedData As ControllerComPortData = Conn.CacheData.Dequeue() '接收数据

                    Select Case receivedData.Command
                        Case "AUTS J_Controller V1.0" '回复查询串口包
                            _stateMachineTaskStatus = StateMachineStatus.ComportConnected
                        Case "TRUE" '回复心跳包或其他命令
                            ControllerCommandStatus = TestCommandStatusEnum.ReceiveCompleted
                            TestReceiveData = receivedData

                            ComportWriteFailCount = 0
                            _stateMachineTaskStatus = StateMachineStatus.Idle
                    End Select
                End If
            End If
        End Sub


        Public Sub SendCommand()
            Dim sendData As ControllerComPortData = TestSendData
            If sendData Is Nothing Then
                _stateMachineTaskStatus = StateMachineStatus.Idle
                Return
            End If

            'todo:发送命令仍有问题，还需添加重发机制
            ExpectReplyTestCommand = sendData.Command   '期望回复的命令

            If Conn.SendTextAppendCrLf(sendData) Then
                ComportWriteFailCount = 0
                WaitReplyStartTime = Now '更新等待接收的起始时间
                TestCommandStatus = TestCommandStatusEnum.WaitingToReceive '等待接收
                _stateMachineTaskStatus = StateMachineStatus.WaitReply
            Else
                TestCommandStatus = TestCommandStatusEnum.SendingFailure '发送失败

                ComportWriteFailCount += 1
                If ComportWriteFailCount >= ComportWriteMaxFailCount Then
                    ComportWriteFailCount = 0
                    _stateMachineTaskStatus = StateMachineStatus.ComportDisconnected
                Else
                    _stateMachineTaskStatus = StateMachineStatus.Idle
                End If
            End If
        End Sub

        Public Sub ReplyKeyStatusAck()
            Dim sendData As New ControllerComPortData("TRUE")

            If Conn.SendTextAppendCrLf(sendData) Then
                ComportWriteFailCount = 0
                _stateMachineTaskStatus = StateMachineStatus.Idle
            Else
                ComportWriteFailCount += 1
                If ComportWriteFailCount >= ComportWriteMaxFailCount Then
                    ComportWriteFailCount = 0
                    _stateMachineTaskStatus = StateMachineStatus.ComportDisconnected
                Else
                    _stateMachineTaskStatus = StateMachineStatus.Idle
                End If
            End If
        End Sub

        Public Sub ComportConnected()
            ControllerComPortStatusMonitor.ComportStatus = ComportStatusMonitor.ComPortConnectStatusEnum.Connected
            _stateMachineTaskStatus = StateMachineStatus.Idle
        End Sub

        Public Sub ComportDisconnected()
            ControllerComPortStatusMonitor.ComportStatus = ComportStatusMonitor.ComPortConnectStatusEnum.UnConnected
            _stateMachineTaskStatus = StateMachineStatus.Idle
        End Sub


        Enum StateMachineStatus
            ''' <summary>
            ''' 退出状态机
            ''' </summary>
            Quit

            ''' <summary>
            ''' 入口
            ''' </summary>
            Idle

            ''' <summary>
            ''' 获取串口设备
            ''' </summary>
            GetComport

            ''' <summary>
            ''' 发送心跳包
            ''' </summary>
            SendHearBeat

            ''' <summary>
            ''' 等待回复
            ''' </summary>
            WaitReply

            ''' <summary>
            ''' 发送控制通道命令
            ''' </summary>
            SendCommand

            ''' <summary>
            ''' 键值回复
            ''' </summary>
            ReplyKeyStatusAck

            ''' <summary>
            ''' 连接成功
            ''' </summary>
            ComportConnected

            ''' <summary>
            ''' 连接失败
            ''' </summary>
            ComportDisconnected
        End Enum
#End Region



        Public Class ControllerComPort
            ''' <summary>通讯串口</summary>
            Private ReadOnly Property Comport As SerialPort

            ''' <summary>接收到的字符串</summary>
            Private ReadOnly _receiveString As New StringBuilder

            ''' <summary>接收到的经过处理后的合法数据队列</summary>
            Public CacheData As New Queue(Of ControllerComPortData)

            ''' <summary>接收起始时间</summary>
            Private _receiveStartTime As DateTime

            Sub New()
                Comport = New SerialPort()

                AddHandler Comport.DataReceived, AddressOf ComPort_DataReceived
            End Sub

            ''' <summary>
            ''' 打开串口
            ''' </summary>
            ''' <param name="portName"></param>
            ''' <param name="baudRate"></param>
            Public Function OpenSerialPort(portName As String, baudRate As Integer) As Boolean

                Try
                    If Comport.IsOpen Then Comport.Close()
                    With Comport
                        .PortName = portName '串口名
                        .BaudRate = baudRate '波特率
                        .DataBits = 8 '数据位
                        .StopBits = StopBits.One '停止位
                        .Parity = Parity.None  '偶校验
                        .RtsEnable = True
                        .Open()
                    End With
                Catch ex As Exception
                    Console.WriteLine($"打开控制串口失败!原因:{ex.Message}。")
                    Return False
                End Try
                Return True
            End Function

            Public Function CloseSerialPort() As Boolean
                If Comport Is Nothing Then Return True
                Try
                    Comport.Close()
                Catch ex As Exception
                    Return False
                End Try

                Return True
            End Function


            Private Function DealReceivedData(receiveBuffer() As Byte) As Boolean
                If receiveBuffer.Length <= 0 Then Return True
                If receiveBuffer(receiveBuffer.Length - 1) = 10 Then '接收到换行符，接收完成

                    _receiveString.Append(Encoding.UTF8.GetString(receiveBuffer, 0, receiveBuffer.Length))

                    Dim tmpString() As String = _receiveString.ToString().Replace(ControlChars.Cr, String.Empty).Split(New Char() {ControlChars.Lf}, StringSplitOptions.RemoveEmptyEntries)

                    For Each str As String In tmpString
                        Try
                            CacheData.Enqueue(ControllerComPortData.ConvertFromString(str))

                            If CacheData.Count > 64 Then CacheData.Dequeue() '缓存数据队列过多，移除头部缓存

                        Catch ex As Exception
                            Console.WriteLine($"ControllerPort ConvertString Error,String:{str} Error:{ex.Message}")
                        End Try
                    Next

                    _receiveString.Clear() '处理完成清空缓存
                Else
                    If _receiveString.Length = 0 Then '单包未接收完全，记录起始时间
                        _receiveStartTime = Now
                    End If

                    _receiveString.Append(Encoding.UTF8.GetString(receiveBuffer, 0, receiveBuffer.Length))
                End If

                Return True
            End Function

            Private Function ReceiveData() As Byte()
                Static bytes As Integer = 0
                Try
                    bytes = Comport.BytesToRead

                Catch ex As Exception
                    Console.WriteLine($"Controler BytesToRead Error:{ex.Message}")
                    Return New Byte() {}
                End Try

                Dim receiveBuffer(bytes - 1) As Byte
                Try
                    Comport.Read(receiveBuffer, 0, bytes)
                Catch ex As Exception
                    Console.WriteLine($"Controler ReceiveData Error:{ex.Message}")
                    Return New Byte() {}
                End Try

                Return receiveBuffer
            End Function

            Private Sub ComPort_DataReceived(sender As Object, e As SerialDataReceivedEventArgs)
                Dim receiveBuffer() As Byte = ReceiveData()

                DealReceivedData(receiveBuffer)
            End Sub


            Public Function SendText(sendString As String) As Boolean
                Try
                    Comport.Write(sendString)
                Catch ex As Exception
                    Console.WriteLine($"Controller SendString Error,原因：{ex.Message}")
                    Return False
                End Try
                Return True
            End Function

            Public Function SendTextAppendCrLf(sendData As ControllerComPortData) As Boolean
                If sendData Is Nothing Then Throw New Exception($"Controller SendTest：SendData Is Nothing")

                Return SendText($"{sendData}{vbCrLf}")
            End Function

            Public Function SendTextAppendCrLf(command As String, params As List(Of String)) As Boolean
                Return SendText($"{ControllerComPortData.ConvertToString(command, params)}{vbCrLf}")
            End Function
        End Class


        Public Class ControllerComPortData
            ''' <summary>通讯字符串的分隔符</summary>
            Private Shared _separator As Char = ":"c

            ''' <summary>
            ''' UTS通讯命令
            ''' </summary>
            ''' <returns></returns>
            Property Command() As String
            ''' <summary>
            ''' UTS命令参数
            ''' </summary>
            ''' <returns></returns>
            Property Params() As List(Of String)


            Sub New()
                Command = String.Empty
                Params = New List(Of String)
            End Sub

            Sub New(cmd As String)
                Command = cmd
                Params = New List(Of String)
            End Sub

            Sub New(cmd As String, param As String)
                Command = cmd
                Params = New List(Of String) From {param}
            End Sub

            Public Shared Function ConvertToString(cmd As String, params As List(Of String)) As String
                Dim strTemp As New StringBuilder
                strTemp.Append(cmd)

                For Each param As String In params
                    strTemp.Append(_separator)
                    strTemp.Append(param)
                Next

                Return strTemp.ToString()
            End Function

            Public Shared Function ConvertFromString(str As String) As ControllerComPortData
                If String.IsNullOrWhiteSpace(str) Then Throw New Exception($"ControllerComPort ConvertFromString Invalid String!")

                Dim strings() As String = str.Split(_separator)
                Dim result As New ControllerComPortData
                result.Command = strings(0)

                result.Params.Clear()
                For i As Integer = 1 To strings.Length - 1
                    result.Params.Add(strings(i))
                Next

                Return result
            End Function

            ''' <summary>
            ''' 将参数字符串处理成参数列表
            ''' </summary>
            ''' <returns></returns>
            Public Shared Function StringToParamList(paramString As String) As List(Of String)
                Dim strings() As String = paramString.Split(_separator)
                Return New List(Of String)(strings)
            End Function

            Public Overrides Function ToString() As String
                Return ConvertToString(Command, Params)
            End Function
        End Class

    End Class
End Namespace