Namespace Serie
    ''' <summary>
    ''' Moniteur de communication pour un maitre sur une communication sérielle
    ''' </summary>
    ''' <remarks></remarks>
    Public NotInheritable Class MonitorMaster
        Inherits ClsBaseAnimCom

#Region "Membres partagés publique"
        ''' <summary>
        ''' Retourne la liste des vitesses utilisables
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Shared Function GetSpeedList() As Integer()
            Return New Integer() {9600, 19200, 38400, 57600, 115200}
        End Function
        ''' <summary>
        ''' Ferme toutes les connexion (Série et TCP)
        ''' </summary>
        ''' <param name="pWait">Spécifie si le programme doit attendre</param>
        ''' <remarks></remarks>
        Public Shared Sub CloseAllConnexions(ByVal pWait As Boolean)
            ClsBaseThread.StopAllThreads(True, pWait)
        End Sub
#End Region

#Region "Attributs privés"
#Region "Config Modbus"
        ''' <summary>
        ''' Délai inter-trame
        ''' </summary>
        ''' <remarks></remarks>
        Private mInterFrame As Integer
        ''' <summary>
        ''' Délai de timeout
        ''' </summary>
        ''' <remarks></remarks>
        Private mTimeOut As Integer
        ''' <summary>
        ''' Précise si la communication de fait par Modbus RTU ou ASCII
        ''' </summary>
        ''' <remarks></remarks>
        Private mAscii As Boolean
#End Region

#Region "Configuration du port"
        ''' <summary>
        ''' Numéro du port COM
        ''' </summary>
        ''' <remarks></remarks>
        Private mComNum As Integer
        ''' <summary>
        ''' Vitesse du port
        ''' </summary>
        ''' <remarks></remarks>
        Private mComSpeed As Integer
        ''' <summary>
        ''' Parité
        ''' </summary>
        ''' <remarks></remarks>
        Private mComParity As IO.Ports.Parity
        ''' <summary>
        ''' Nombre de bits de données
        ''' </summary>
        ''' <remarks></remarks>
        Private mComBit As Integer
        ''' <summary>
        ''' Nombre de bits de stop
        ''' </summary>
        ''' <remarks></remarks>
        Private mComStop As IO.Ports.StopBits
        ''' <summary>
        ''' Mémorise l'adresse IP distante si port COM déporté
        ''' </summary>
        ''' <remarks></remarks>
        Private mRemoteIP As Net.IPAddress
#End Region

#Region "Liste des envois"
        ''' <summary>
        ''' Liste des trames dans le buffer d'envoi
        ''' </summary>
        ''' <remarks></remarks>
        Private mTramePrio As New Queue(Of SendingFrame)
        ''' <summary>
        ''' Liste des trames en attente de génération de gestion par le code client
        ''' </summary>
        ''' <remarks></remarks>
        Private mTrameWait As New Queue(Of SendingFrame)
#End Region

#Region "Gestion interne"
        ''' <summary>
        ''' Signal au thread qu'il est autorisé à sortir
        ''' </summary>
        ''' <remarks></remarks>
        Private mCanGo As Boolean = False
        ''' <summary>
        ''' Mémorise le port série
        ''' </summary>
        ''' <remarks></remarks>
        Private mPort As IO.Ports.SerialPort = Nothing
        ''' <summary>
        ''' Thread de communication
        ''' </summary>
        ''' <remarks></remarks>
        Private mThread As ClsDelegateThread
#End Region
#End Region

#Region "Constructeurs"
        ''' <summary>
        ''' Constructeur de la classe
        ''' </summary>
        ''' <param name="pComNum">Numéro du port à utiliser</param>
        ''' <param name="pComSpeed">Vitesse de communication</param>
        ''' <param name="pComParity">Parité</param>
        ''' <param name="pComBit">Nombre de bits de données</param>
        ''' <param name="pComStop">Nombre de bits de stop</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal pComNum As Integer, ByVal pComSpeed As Integer, ByVal pComParity As IO.Ports.Parity, ByVal pComBit As Integer, ByVal pComStop As IO.Ports.StopBits, ByVal pInterFrame As Integer, ByVal pTimeOut As Integer, ByVal pAscii As Boolean)
            MyClass.New(pComNum, pComSpeed, pComParity, pComBit, pComStop, Net.IPAddress.Parse("0.0.0.0"), pInterFrame, pTimeOut, pAscii)
        End Sub
        ''' <summary>
        ''' Constructeur de la classe
        ''' </summary>
        ''' <param name="pComNum">Numéro du port à utiliser</param>
        ''' <param name="pComSpeed">Vitesse de communication</param>
        ''' <param name="pComParity">Parité</param>
        ''' <param name="pComBit">Nombre de bits de données</param>
        ''' <param name="pComStop">Nombre de bits de stop</param>
        ''' <param name="pRemoteIP">IP distante</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal pComNum As Integer, ByVal pComSpeed As Integer, ByVal pComParity As IO.Ports.Parity, ByVal pComBit As Integer, ByVal pComStop As IO.Ports.StopBits, ByVal pRemoteIP As Net.IPAddress, ByVal pInterFrame As Integer, ByVal pTimeOut As Integer, ByVal pAscii As Boolean)
            mInterFrame = pInterFrame
            mTimeOut = pTimeOut
            mAscii = pAscii

            mComNum = pComNum
            mComSpeed = pComSpeed
            mComParity = pComParity
            mComBit = pComBit
            mComStop = pComStop
            mRemoteIP = pRemoteIP

            SetState(IAnimCom.EState.NotConnectedConnectable)
            mThread = Nothing
        End Sub
        ''' <summary>
        ''' Constructeur de la classe
        ''' </summary>
        ''' <param name="pParams">Paramètres de la connexion</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal pParams As String)
            Dim ltStr As String() = pParams.Split(","c)

            mInterFrame = CInt(ltStr(6))
            mTimeOut = CInt(ltStr(7))
            mAscii = (ltStr(5) = "1")

            mComNum = CInt(ltStr(0))
            mComSpeed = CInt(ltStr(3))
            mComParity = CType(CInt(ltStr(1)), IO.Ports.Parity)
            mComBit = CInt(ltStr(2))
            mComStop = CType(CInt(ltStr(4)), IO.Ports.StopBits)
            mRemoteIP = Net.IPAddress.Parse(ltStr(8))

            SetState(IAnimCom.EState.NotConnectedConnectable)
            mThread = Nothing
        End Sub
#End Region

#Region "Membres publiques"
        ''' <summary>
        ''' Retourne le type de connexion
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overrides ReadOnly Property ConnexionType() As IAnimCom.EConnexType
            Get
                Return IAnimCom.EConnexType.COM
            End Get
        End Property
        ''' <summary>
        ''' Ouvre la connexion
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overrides Function Open() As Boolean
            If mThread Is Nothing OrElse Not mThread.IsRun Then
                MyBase.Open()
                mTramePrio.Clear()
                mTrameWait.Clear()
                mThread = New ClsDelegateThread("Moniteur COM" + CStr(mComNum), New ClsDelegateThread.SubThread(AddressOf ProSubThread), New ClsDelegateThread.ErrThread(AddressOf ProErrThread))
            End If
            Return True
        End Function
        ''' <summary>
        ''' Retourne les paramètres de la connexion
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Overrides ReadOnly Property StringParam() As String
            Get
                Return String.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8}", mComNum, CInt(mComParity), mComBit, mComSpeed, CInt(mComStop), IIF(mAscii, "1", "0"), mInterFrame, mTimeOut, mRemoteIP)
            End Get
        End Property

#Region "Fermeture"
        ''' <summary>
        ''' Ferme la connexion
        ''' </summary>
        ''' <remarks></remarks>
        Public Overrides Sub Close()
            Close(False)
        End Sub
        ''' <summary>
        ''' Femre la connexion
        ''' </summary>
        ''' <param name="pWait">Indique si la connexion doit être fermée avant de treprendre la main</param>
        ''' <remarks></remarks>
        Public Overloads Sub Close(ByVal pWait As Boolean)
            MyBase.Close()
            If mThread IsNot Nothing Then
                mThread.StopThread(pWait)
                mThread = Nothing
            End If
        End Sub
#End Region

#Region "Config Modbus"
        ''' <summary>
        ''' Retourne ou définit le délai inter-trame
        ''' </summary>
        ''' <remarks></remarks>
        Public Property InterFrame() As Integer
            Get
                Return mInterFrame
            End Get
            Set(ByVal value As Integer)
                If value > 50 Then
                    mInterFrame = value
                End If
            End Set
        End Property
        ''' <summary>
        ''' Retourne ou définit le délai de timeout
        ''' </summary>
        ''' <remarks></remarks>
        Public Property TimeOut() As Integer
            Get
                Return mTimeOut
            End Get
            Set(ByVal value As Integer)
                If value > 50 Then
                    mTimeOut = value
                End If
            End Set
        End Property
        ''' <summary>
        ''' Précise si la communication de fait par Modbus RTU ou ASCII
        ''' </summary>
        ''' <remarks></remarks>
        Public Property Ascii() As Boolean
            Get
                Return mAscii
            End Get
            Set(ByVal value As Boolean)
                If State <> IAnimCom.EState.NotConnectedConnectable Then
                    Throw New InvalidOperationException("Opération interdite quand la connexion est établie")
                End If
                mAscii = value
            End Set
        End Property
#End Region

#Region "Configuration du port"
        ''' <summary>
        ''' Retourne ou définit le numéro du port COM
        ''' </summary>
        ''' <remarks></remarks>
        Public Property ComNum() As Integer
            Get
                Return mComNum
            End Get
            Set(ByVal value As Integer)
                If State <> IAnimCom.EState.NotConnectedConnectable Then
                    Throw New InvalidOperationException("Opération interdite quand la connexion est établie")
                End If
                If value < 1 Or value > 255 Then
                    Throw New ArgumentException("La valeur doit être comprise entre 1 et 255 (bornes incluses)")
                End If
                mComNum = value
            End Set
        End Property
        ''' <summary>
        ''' Retourne ou définit la vitesse du port
        ''' </summary>
        ''' <remarks></remarks>
        Public Property ComSpeed() As Integer
            Get
                Return mComSpeed
            End Get
            Set(ByVal value As Integer)
                If State <> IAnimCom.EState.NotConnectedConnectable Then
                    Throw New InvalidOperationException("Opération interdite quand la connexion est établie")
                End If
                If Array.IndexOf(GetSpeedList, value) = -1 Then
                    Throw New ArgumentException("La valeur doit être comprise les vitesses prévues")
                End If
                mComSpeed = value
            End Set
        End Property
        ''' <summary>
        ''' Retourne ou définit la parité
        ''' </summary>
        ''' <remarks></remarks>
        Public Property ComParity() As IO.Ports.Parity
            Get
                Return mComParity
            End Get
            Set(ByVal value As IO.Ports.Parity)
                If State <> IAnimCom.EState.NotConnectedConnectable Then
                    Throw New InvalidOperationException("Opération interdite quand la connexion est établie")
                End If
                mComParity = value
            End Set
        End Property
        ''' <summary>
        ''' Retourne ou définit le nombre de bits de données
        ''' </summary>
        ''' <remarks></remarks>
        Public Property ComBit() As Integer
            Get
                Return mComBit
            End Get
            Set(ByVal value As Integer)
                If State <> IAnimCom.EState.NotConnectedConnectable Then
                    Throw New InvalidOperationException("Opération interdite quand la connexion est établie")
                End If
                If value < 7 Or value > 8 Then
                    Throw New ArgumentException("La valeur doit être 7 ou 8")
                End If
                mComBit = value
            End Set
        End Property
        ''' <summary>
        ''' Retourne ou définit le nombre de bits de stop
        ''' </summary>
        ''' <remarks></remarks>
        Public Property ComStop() As IO.Ports.StopBits
            Get
                Return mComStop
            End Get
            Set(ByVal value As IO.Ports.StopBits)
                If State <> IAnimCom.EState.NotConnectedConnectable Then
                    Throw New InvalidOperationException("Opération interdite quand la connexion est établie")
                End If
                mComStop = value
            End Set
        End Property
        ''' <summary>
        ''' Retourne ou définit l'adresse IP distante si port COM déporté
        ''' </summary>
        ''' <remarks></remarks>
        Public Property RemoteIP() As Net.IPAddress
            Get
                Return mRemoteIP
            End Get
            Set(ByVal value As Net.IPAddress)
                If State <> IAnimCom.EState.NotConnectedConnectable Then
                    Throw New InvalidOperationException("Opération interdite quand la connexion est établie")
                End If
                mRemoteIP = value
            End Set
        End Property
#End Region

#End Region

#Region "Fonction du thread"

        Private Sub ProSubThread(ByVal pThread As ClsDelegateThread)
            SetState(IAnimCom.EState.AutoConnect Or IAnimCom.EState.NotConnectable)

            Dim lLostConnexion As Boolean = False

            'Attente de démarrage
            'TODO : Controle Adresse IP
            Do While pThread.CanRun AndAlso mRemoteIP.ToString <> "0.0.0.0" AndAlso Not My.Computer.Network.Ping(mRemoteIP.ToString, 1000)
                If Not lLostConnexion Then
                    LostConnexion()
                End If
                lLostConnexion = True
                Sleep(1000)
            Loop
            If Not (pThread.CanRun) Then Exit Sub

            If lLostConnexion Then
                RestartedConnexion()
            End If

            SetState(IAnimCom.EState.NotConnectedConnectable Or IAnimCom.EState.AutoConnect)

            'Ouverture du port
            mPort = New IO.Ports.SerialPort("COM" + CStr(mComNum), mComSpeed, mComParity, mComBit, mComStop)
            mPort.Open()

            SetState(IAnimCom.EState.AutoConnect Or IAnimCom.EState.Connected)

            'Init de la variable de sunchro
            Dim lStart As Integer = Environment.TickCount

            'Tant que l'on peut fonctionner
            Do While pThread.CanRun

                'Buffer optimisé
                If mTramePrio.Count < 4 Then
                    'Aucune trame prioritaire en attente
                    Dim lTrameAttente As SendingFrame = GetNextFrame()
                    'Trame en attente ?
                    If lTrameAttente IsNot Nothing Then
                        'Synchronisation des threads
                        SyncLock mTramePrio
                            'Ajoute la trame dans le buffer prioritaire
                            mTramePrio.Enqueue(lTrameAttente)
                        End SyncLock
                    End If
                End If

                'Tests
                If mTrameWait.Count > 0 Then
                    'Trame en attente de traitement par le code client

                    Dim lFrame As SendingFrame 'Trame envoyée (avec sa réponse)
                    Dim lFrame1 As SendingFrame 'Trame a envoyer en priorité

                    'Récupère la trame en attente
                    SyncLock mTrameWait
                        lFrame = mTrameWait.Dequeue
                    End SyncLock

                    'Génère l'evenement de réception
                    lFrame1 = lFrame.RaiseEvt(Me)
                    'Si une trame est a envoyer en priotité
                    If lFrame1 IsNot Nothing Then
                        'Met la trame en attente dans la file d'attente prioritaire
                        SyncLock mTramePrio
                            mTramePrio.Enqueue(lFrame1)
                        End SyncLock
                    End If
                ElseIf mTramePrio.Count = 0 Then
                    'Aucune trame prioritaire en attente
                    Dim lTrameAttente As SendingFrame = GetNextFrame()
                    'Trame en attente ?
                    If lTrameAttente IsNot Nothing Then
                        'Synchronisation des threads
                        SyncLock mTramePrio
                            'Ajoute la trame dans le buffer prioritaire
                            mTramePrio.Enqueue(lTrameAttente)
                        End SyncLock
                    Else
                        'Aucune trame à envoyer, on fait une pause
                        Sleep(100)
                    End If
                Else
                    Dim lFrame As SendingFrame
                    'Synchronisation des threads
                    SyncLock mTramePrio
                        'Récupère la trame
                        lFrame = mTramePrio.Dequeue()
                    End SyncLock

                    'Attente de la fin du délai inter-trame
                    Do While Environment.TickCount < lStart + mInterFrame And Environment.TickCount >= lStart
                        Sleep(10)
                    Loop

                    'Attente de la réponse (+gestion du Timeout)
                    Do While mPort.BytesToRead > 0
                        mPort.ReadExisting()
                        Sleep(10)
                    Loop

                    'TODO : Controle trame de requête

                    'Envoi de la requête
                    If mAscii Then
                        mPort.Write((New Frame(lFrame.FrameSend)).GetFrameAscii)
                    Else
                        Dim ltFrame As Byte() = (New Frame(lFrame.FrameSend)).GetFrame
                        mPort.Write(ltFrame, 0, ltFrame.Length)
                    End If

                    lStart = Environment.TickCount

                    Dim lLen As Integer = 0

                    'Attente de la réponse (+gestion du Timeout)
                    Do While Environment.TickCount < lStart + mTimeOut And Environment.TickCount >= lStart
                        If lLen > 0 AndAlso mPort.BytesToRead = lLen Then
                            Exit Do
                        ElseIf mPort.BytesToRead <> lLen Then
                            lLen = mPort.BytesToRead
                        End If
                        Sleep(100)
                    Loop
                    lStart = Environment.TickCount

                    'Si quelquechose a été reçu
                    If mPort.BytesToRead > 0 Then
                        Dim llFrame As Modbus.Frame.ClsFrameModbusBase = Nothing
                        'Décodage de la trame
                        If mAscii Then
                            Dim lFullFrame As Modbus.Serie.Frame = Frame.GetFrame(mPort.ReadExisting, False)
                            If lFullFrame IsNot Nothing Then
                                llFrame = lFullFrame.Trame
                            End If
                        Else
                            Dim ltData As Byte()
                            ReDim ltData(mPort.BytesToRead - 1)
                            mPort.Read(ltData, 0, ltData.Length)
                            Dim lFullFrame As Modbus.Serie.Frame = Frame.GetFrame(ltData, False)
                            If lFullFrame IsNot Nothing Then
                                llFrame = lFullFrame.Trame
                            End If
                        End If

                        'Si la trame n'a pas pu être décodée
                        If llFrame Is Nothing Then
                            lFrame.DecrementResend()
                            'On réémet ?
                            If lFrame.LeftResend < 0 Then
                                'Non donc, on indique que l'on a rien reçu
                                SyncLock mTrameWait
                                    mTrameWait.Enqueue(lFrame.Clone(Nothing))
                                End SyncLock
                            Else
                                'Oui donc, on renvoi dans le buffer prioritaire
                                SyncLock mTramePrio
                                    mTramePrio.Enqueue(lFrame)
                                End SyncLock
                            End If
                        Else
                            'La trame a eté décodée, donc on signal la réception
                            lFrame = lFrame.Clone(llFrame)
                            SyncLock mTrameWait
                                mTrameWait.Enqueue(lFrame)
                            End SyncLock
                        End If
                    Else
                            'Pas de réponse, donc :
                            lFrame.DecrementResend()
                            'On réémet ?
                            If lFrame.LeftResend < 0 Then
                                'Non donc, on indique que l'on a rien reçu
                                SyncLock mTrameWait
                                    mTrameWait.Enqueue(lFrame.Clone(Nothing))
                                End SyncLock
                            Else
                                'Oui donc, on renvoi dans le buffer prioritaire
                                SyncLock mTramePrio
                                    mTramePrio.Enqueue(lFrame)
                                End SyncLock
                            End If
                    End If
                End If
            Loop

            'On sort du thread, on ferme donc le port COM
            If mPort IsNot Nothing Then
                Try
                    mPort.Close()
                Catch ex As Exception
#If DEBUG Then
                    Stop
#End If
                    'Ne rien faire
                End Try
                mPort = Nothing
            End If

            'On signal l'arrêt du thread
            Close(False)
        End Sub

        Private Function ProErrThread(ByVal pThread As ClsDelegateThread, ByVal pEx As System.Exception) As Boolean
            'Erreur dans le thread
#If DEBUG Then
            Debug.Print(pEx.ToString)
            Stop
#End If

            'Ferme le port COM
            If mPort IsNot Nothing Then
                Try
                    mPort.Close()
                Catch ex As Exception
#If DEBUG Then
                    Stop
#End If
                    'Ne rien faire
                End Try
                mPort = Nothing
            End If

            SetState(IAnimCom.EState.NotConnectedConnectable)
            If mRemoteIP.ToString <> "0.0.0.0" Then
                SetState(State Or IAnimCom.EState.AutoConnect)
            End If

            If mRemoteIP.ToString <> "0.0.0.0" Then
                RestartedConnexion()
            Else
                Close(False)
            End If

            'Pas de redémarrage
            Return mRemoteIP.ToString <> "0.0.0.0"
        End Function
#End Region
    End Class
End Namespace