Imports System.Text
Namespace Serie
    Public NotInheritable Class MonitorSlave
        Implements IRecepteur
        ''undone

        Private mState As IRecepteur.eState


#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

        Public Event FrameRececpt(ByVal pSender As IRecepteur, ByVal pFrameReceived As Modbus.Frame.ClsFrameModbusBase, ByRef pFrameSend As Modbus.Frame.ClsFrameModbusBase) Implements IRecepteur.FrameRececpt


#Region "Attributs privés"

#Region "Config Modbus"
        ''' <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 "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 pTimeOut As Integer, ByVal pAscii As Boolean)
            MyClass.New(pComNum, pComSpeed, pComParity, pComBit, pComStop, Net.IPAddress.Parse("0.0.0.0"), 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 pTimeOut As Integer, ByVal pAscii As Boolean)
            mTimeOut = pTimeOut
            mAscii = pAscii

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

            SetState(IRecepteur.eState.Closed)
            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)

            mTimeOut = CInt(ltStr(6))
            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(7))

            SetState(IRecepteur.eState.Closed)
            mThread = Nothing
        End Sub
#End Region

#Region "Membres publiques"
        ''' <summary>
        ''' Retourne le type de connexion
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property ConnexionType() As IAnimCom.EConnexType Implements IRecepteur.ConnexionType
            Get
                Return IAnimCom.EConnexType.COM
            End Get
        End Property
        ''' <summary>
        ''' Ouvre la connexion
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Open() As Boolean Implements IRecepteur.Open
            If mThread Is Nothing OrElse Not mThread.IsRun Then
                SetState(IRecepteur.eState.Listening)
                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 ReadOnly Property StringParam() As String Implements IRecepteur.StringParam
            Get
                Return String.Format("{0},{1},{2},{3},{4},{5},{6},{7}", mComNum, CInt(mComParity), mComBit, mComSpeed, CInt(mComStop), IIF(mAscii, "1", "0"), mTimeOut, mRemoteIP)
            End Get
        End Property
        ''' <summary>
        ''' Retourne l'état actuel de la connexion
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property State() As IRecepteur.eState Implements IRecepteur.State
            Get
                Return mState
            End Get
        End Property

#Region "Fermeture"
        ''' <summary>
        ''' Ferme la connexion
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub Close() Implements IRecepteur.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 Sub Close(ByVal pWait As Boolean)
            If mThread IsNot Nothing Then
                mThread.StopThread(pWait)
                mThread = Nothing
            End If
            SetState(IRecepteur.eState.Closed)
        End Sub
#End Region

#Region "Config Modbus"
        ''' <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 mState <> IRecepteur.eState.Closed 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 mState <> IRecepteur.eState.Closed 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 mState <> IRecepteur.eState.Closed 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 mState <> IRecepteur.eState.Closed 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 mState <> IRecepteur.eState.Closed 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 mState <> IRecepteur.eState.Closed 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 mState <> IRecepteur.eState.Closed 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(IRecepteur.eState.Listening)

            '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)
                Sleep(1000)
            Loop
            If Not (pThread.CanRun) Then Exit Sub

            SetState(IRecepteur.eState.Listening)

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

            SetState(IRecepteur.eState.Listening Or IRecepteur.eState.OneOrMoreClientConnected)

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

            'Tant que l'on peut fonctionner
            Do While pThread.CanRun
                'Tests
                If mPort.BytesToRead > 0 Then
                    'Données en attente

                    Dim lFrameRecept As Modbus.Frame.ClsFrameModbusBase

                    lStart = Environment.TickCount

                    If mAscii Then
                        Dim lStr As String = mPort.ReadExisting
                        lFrameRecept = Frame.GetFrame(lStr, True).Trame
                    Else
                        Dim ltByte As Byte()
                        ReDim ltByte(CInt(mPort.BytesToRead - 1))
                        mPort.BaseStream.Read(ltByte, 0, ltByte.Length)
#If DEBUG Then
                        Debug.Print(MdlCommonNH.Array2String(ltByte))
#End If
                        Dim lFrame As Frame = Frame.GetFrame(ltByte, True)
                        If lFrame IsNot Nothing Then
                            lFrameRecept = lFrame.Trame
                        Else
                            lFrameRecept = Nothing
                        End If
                    End If

                    If lFrameRecept IsNot Nothing Then
                        Dim lToSend As Modbus.Frame.ClsFrameModbusBase = Nothing
                        Try
                            RaiseEvent FrameRececpt(Me, lFrameRecept, lToSend)
                        Catch ex As Exception
                            'Ne rien faire
#If DEBUG Then
                            Stop
#End If
                        End Try
                        If lToSend IsNot Nothing And lStart + mTimeOut >= Environment.TickCount Then
                            Dim ltByte As Byte()
                            If mAscii Then
                                ltByte = System.Text.Encoding.UTF8.GetBytes((New Frame(lToSend)).GetFrameAscii)
                            Else
                                ltByte = (New Frame(lToSend)).GetFrame
                            End If
                            mPort.BaseStream.Write(ltByte, 0, ltByte.Length)
                        End If
                    End If
                Else
                    Sleep(100)
                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(IRecepteur.eState.Closed)
            If mRemoteIP.ToString <> "0.0.0.0" Then
                SetState(IRecepteur.eState.Closed)
            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

        Private Sub SetState(ByVal pState As IRecepteur.eState)
            mState = pState
        End Sub
    End Class
End Namespace