''' <summary>
''' Classe de l'animateur ModBus (ordenancement des trames à envoyer)
''' </summary>
''' <remarks></remarks>
Public NotInheritable Class AnimateurModbus

    Implements IAnimateur

    ''' <summary>
    ''' Classe de gestion d'un esclave
    ''' </summary>
    ''' <remarks></remarks>
    Private NotInheritable Class ClsSlave

#Region "Attributs"
        ''' <summary>
        ''' Mémorise le verrouillage utilisateur des trames cycliques
        ''' </summary>
        ''' <remarks></remarks>
        Private mUserLockCyclic As Boolean
        ''' <summary>
        ''' Mémorise le verrouillage utilisateur des trames non cycliques
        ''' </summary>
        ''' <remarks></remarks>
        Private mUserLockNonCyclic As Boolean
        ''' <summary>
        ''' Nombre de vérrous des trames cycliques
        ''' </summary>
        ''' <remarks></remarks>
        Private mLockCyclic As Integer
        ''' <summary>
        ''' Nombre de vérrous des trames non cycliques
        ''' </summary>
        ''' <remarks></remarks>
        Private mLockNonCyclic As Integer
        ''' <summary>
        ''' Liste des trames cycliques en attente
        ''' </summary>
        ''' <remarks></remarks>
        Private mCyclic As New Queue(Of SendingFrame)
        ''' <summary>
        ''' Liste des trames non cycliques en attente
        ''' </summary>
        ''' <remarks></remarks>
        Private mNonCyclic As New List(Of Queue(Of SendingFrame))
#End Region

        ''' <summary>
        ''' Constructeur de la classe
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub New()
            mUserLockCyclic = False
            mUserLockNonCyclic = False
            mLockCyclic = 0
            mLockNonCyclic = 0
        End Sub

#Region "Méthodes publiques"
        ''' <summary>
        ''' Ajoute une trame cyclique
        ''' </summary>
        ''' <param name="pFrame">Trame à ajouter</param>
        ''' <remarks></remarks>
        Public Sub AddCyclic(ByVal pFrame As SendingFrame)
            SyncLock mCyclic
                mCyclic.Enqueue(pFrame)
                pFrame.IsCyclic = True
            End SyncLock
        End Sub
        ''' <summary>
        ''' Ajoute une trame non cyclique
        ''' </summary>
        ''' <param name="pFrame">Trame à ajouter</param>
        ''' <param name="pPrio">Priorité</param>
        ''' <remarks></remarks>
        Public Sub AddNonCyclic(ByVal pFrame As SendingFrame, ByVal pPrio As Integer)
            If pPrio < 0 Or pPrio > 255 Then
                Throw New ArgumentException("pPrio doit être compri entre 0 et 255", "pPrio")
            End If
            SyncLock mNonCyclic
                Do While mNonCyclic.Count <= pPrio
                    mNonCyclic.Add(New Queue(Of SendingFrame))
                Loop
                mNonCyclic(pPrio).Enqueue(pFrame)
                pFrame.IsCyclic = False
            End SyncLock
        End Sub
        ''' <summary>
        ''' Ajoute les verrous
        ''' </summary>
        ''' <param name="pFrame">Trame source</param>
        ''' <remarks></remarks>
        Public Sub AddLock(ByVal pFrame As SendingFrame)
            If (pFrame.Lock And SendingFrame.ESlaveLock.LockCyclic) = SendingFrame.ESlaveLock.LockCyclic Then
                mLockCyclic += 1
            End If
            If (pFrame.Lock And SendingFrame.ESlaveLock.LockNonCyclic) = SendingFrame.ESlaveLock.LockNonCyclic Then
                mLockNonCyclic += 1
            End If
        End Sub
        ''' <summary>
        ''' Retire les verrous
        ''' </summary>
        ''' <param name="pFrame">Trame source</param>
        ''' <remarks></remarks>
        Public Sub RmvLock(ByVal pFrame As SendingFrame)
            SyncLock Me
                If (pFrame.Lock And SendingFrame.ESlaveLock.LockCyclic) = SendingFrame.ESlaveLock.LockCyclic Then
                    mLockCyclic -= 1
                End If
                If (pFrame.Lock And SendingFrame.ESlaveLock.LockNonCyclic) = SendingFrame.ESlaveLock.LockNonCyclic Then
                    mLockNonCyclic -= 1
                End If
            End SyncLock
        End Sub
        ''' <summary>
        ''' Retourne les prochaines trames à envoyer
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function GetNextFrame() As SendingFrame()
            SyncLock Me
                Dim lLst As New List(Of SendingFrame)

                'Controle de l'intégriter des verrous
                If mLockNonCyclic < 0 Or mLockCyclic < 0 Then
#If DEBUG Then
                    Stop
#End If
                    If mLockNonCyclic < 0 Then mLockNonCyclic = 0
                    If mLockCyclic < 0 Then mLockCyclic = 0
                End If

                'Ajout de la trame non cyclique
                If mLockNonCyclic = 0 Then
                    SyncLock mNonCyclic
                        For i As Integer = 0 To mNonCyclic.Count - 1
                            If mNonCyclic(i).Count > 0 Then
                                AddLock(mNonCyclic(i).Peek)
                                lLst.Add(mNonCyclic(i).Dequeue)
                                Exit For
                            End If
                        Next
                    End SyncLock
                End If

                'Ajout de la trame cyclique
                If mLockCyclic = 0 Then
                    SyncLock mCyclic
                        If mCyclic.Count > 0 Then
                            AddLock(mCyclic.Peek)
                            lLst.Add(mCyclic.Dequeue)
                        End If
                    End SyncLock
                End If

                'Retour du tableau
                Return lLst.ToArray
            End SyncLock
        End Function
        ''' <summary>
        ''' Vide le buffer d'attente des trames cycliques
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub ClearCyclic()
            SyncLock mCyclic
                mCyclic.Clear()
            End SyncLock
        End Sub
        ''' <summary>
        ''' Vide le buffer d'attente des trames non cycliques
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub ClearNonCyclic()
            SyncLock mNonCyclic
                mNonCyclic.Clear()
            End SyncLock
        End Sub
#End Region

#Region "Propriétés publiques"
        ''' <summary>
        ''' Retourne ou définit le verrou utilisateur
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property UserLockCyclic() As Boolean
            Get
                Return mUserLockCyclic
            End Get
            Set(ByVal value As Boolean)
                If mUserLockCyclic = value Then
                    Return
                End If
                SyncLock Me
                    If value Then
                        mLockCyclic += 1
                    Else
                        mLockCyclic -= 1
                    End If
                    mUserLockCyclic = value
                End SyncLock
            End Set
        End Property
        ''' <summary>
        ''' Retourne ou définit le verrou utilisateur
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Property UserLockNonCyclic() As Boolean
            Get
                Return mUserLockNonCyclic
            End Get
            Set(ByVal value As Boolean)
                If mUserLockNonCyclic = value Then
                    Return
                End If
                SyncLock Me
                    If value Then
                        mLockNonCyclic += 1
                    Else
                        mLockNonCyclic -= 1
                    End If
                    mUserLockNonCyclic = value
                End SyncLock
            End Set
        End Property
        ''' <summary>
        ''' Retourne le nombre de trame cyclique en attente d'envois
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property FrameCyclicCount() As Integer
            Get
                Return mCyclic.Count
            End Get
        End Property
        ''' <summary>
        ''' Retourne le nombre de trame non cyclique en attente d'envois
        ''' </summary>
        ''' <value></value>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public ReadOnly Property FrameNonCyclicCount() As Integer
            Get
                Dim lCount As Integer
                SyncLock mNonCyclic
                    For i As Integer = 0 To mNonCyclic.Count - 1
                        lCount += mNonCyclic(i).Count
                    Next
                End SyncLock
                Return lCount
            End Get
        End Property
#End Region
    End Class

#Region "Attributs privés"
    ''' <summary>
    ''' Mémorise la liste des esclaves
    ''' </summary>
    ''' <remarks></remarks>
    Private mSlaves As New Dictionary(Of Integer, ClsSlave)
    ''' <summary>
    ''' Mémorise les trames en attente
    ''' </summary>
    ''' <remarks></remarks>
    Private mWaiting As New Queue(Of SendingFrame)
#End Region

#Region "Constructeurs"
    ''' <summary>
    ''' Constructeur de la classe
    ''' </summary>
    ''' <remarks></remarks>
    Public Sub New()
        'Rien de prévu
    End Sub
#End Region

#Region "Membres privés"
    ''' <summary>
    ''' Retourne l'esclave
    ''' </summary>
    ''' <param name="pID">ID de l'esclave</param>
    ''' <param name="pCreate">Indique si en cas de non trouvé, on le créé</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Private Function GetSlave(ByVal pID As Integer, ByVal pCreate As Boolean) As ClsSlave
        If pID < 0 Or pID > 255 Then
            Throw New ArgumentException("Numéro d'esclave incorrect (0<=N°<=255)")
        End If
        Dim lSlave As ClsSlave = Nothing
        SyncLock mSlaves
            If Not mSlaves.TryGetValue(pID, lSlave) And pCreate = True Then
                lSlave = New ClsSlave
                mSlaves.Add(pID, lSlave)
            End If
            Return lSlave
        End SyncLock
    End Function
#End Region

#Region "Membres publiques"

#Region "Liste des envois"

#Region "Trames cycliques"
    ''' <summary>
    ''' Retourne ou définit si les trames cycliques d'un escalve sont verrouillés
    ''' </summary>
    ''' <remarks></remarks>
    Public Property UserBlockedCyclics(ByVal pNumSlave As Integer) As Boolean Implements IAnimateur.UserBlockedCyclics
        Get
            Dim lSlave As ClsSlave = GetSlave(pNumSlave, False)
            If lSlave Is Nothing Then
                Return False
            Else
                Return lSlave.UserLockCyclic
            End If
        End Get
        Set(ByVal pValue As Boolean)
            Dim lSlave As ClsSlave = GetSlave(pNumSlave, True)
            lSlave.UserLockCyclic = pValue
        End Set
    End Property
    ''' <summary>
    ''' Retourn la liste des verrous utilisateurs des trames cycliques
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property UserBlockedCyclics() As Integer() Implements IAnimateur.UserBlockedCyclics
        Get
            Dim ltLock As New List(Of Integer)
            SyncLock mSlaves
                For Each lSlave As KeyValuePair(Of Integer, ClsSlave) In mSlaves
                    If lSlave.Value.UserLockCyclic Then
                        ltLock.Add(lSlave.Key)
                    End If
                Next
            End SyncLock
            Return ltLock.ToArray
        End Get
    End Property
    ''' <summary>
    ''' Ajoute une trame clyclique
    ''' </summary>
    ''' <param name="pFrame">Trames</param>
    ''' <exception cref="ArgumentException">Exception générée si une des trames n'est pas une requête</exception>
    ''' <remarks></remarks>
    Public Sub CyclicAdd(ByVal pFrame() As SendingFrame) Implements IAnimateur.CyclicAdd
        For Each lFrame As SendingFrame In pFrame
            If lFrame.FrameSend.FrameType <> Modbus.Frame.ClsFrameModbusBase.EFrameType.Request Then
                Throw New ArgumentException("une des trames n'est pas une requête", "pFrame")
            End If
        Next

        For Each lFrame As SendingFrame In pFrame
            CyclicAdd(lFrame)
        Next
    End Sub
    ''' <summary>
    ''' Ajoute une trame clyclique
    ''' </summary>
    ''' <param name="pFrame">Trame</param>
    ''' <exception cref="ArgumentException">Exception générée si la trame n'est pas une requête</exception>
    ''' <remarks></remarks>
    Public Sub CyclicAdd(ByVal pFrame As SendingFrame) Implements IAnimateur.CyclicAdd
        'Controle du type de la trame
        If pFrame.FrameSend.FrameType <> Modbus.Frame.ClsFrameModbusBase.EFrameType.Request Then
            Throw New ArgumentException("pFrame doit être une requête", "pFrame")
        End If

        'Ajout dans la file d'attente
        GetSlave(pFrame.FrameSend.SlaveId, True).AddCyclic(pFrame)
    End Sub
    ''' <summary>
    ''' Retourne le nombre de trames cycliques dans le premier buffer
    ''' </summary>
    ''' <param name="pSlave">Numéro d'esclave</param>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property GetFrameCyclicCount(ByVal pSlave As Integer) As Integer Implements IAnimateur.GetFrameCyclicCount
        Get
            Return GetSlave(pSlave, True).FrameCyclicCount
        End Get
    End Property
    ''' <summary>
    ''' Retourne le nombre de trames cycliques dans le premier buffer
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property GetFrameCyclicCount() As Integer(,) Implements IAnimateur.GetFrameCyclicCount
        Get
            Dim ltCount(,) As Integer
            Dim i As Integer = 0

            SyncLock mSlaves
                ReDim ltCount(mSlaves.Count - 1, 1)
                For Each lSlave As KeyValuePair(Of Integer, ClsSlave) In mSlaves
                    ltCount(i, 0) = lSlave.Key
                    ltCount(i, 1) = lSlave.Value.FrameCyclicCount
                    i += 1
                Next
            End SyncLock
            Return ltCount
        End Get
    End Property
    ''' <summary>
    ''' Supprime toutes les trames cycliques du premier buffer
    ''' </summary>
    ''' <param name="pSlave">Numéro d'esclave</param>
    ''' <remarks></remarks>
    Public Sub ClearCyclic(ByVal pSlave As Integer) Implements IAnimateur.ClearCyclic
        GetSlave(pSlave, True).ClearCyclic()
    End Sub
#End Region

#Region "Trames non cycliques"
    ''' <summary>
    ''' Retourne ou définit si les trames non cycliques d'un escalve sont verrouillés
    ''' </summary>
    ''' <remarks></remarks>
    Public Property UserBlockedNonCyclics(ByVal pNumSlave As Integer) As Boolean Implements IAnimateur.UserBlockedNonCyclics
        Get
            Return GetSlave(pNumSlave, True).UserLockNonCyclic
        End Get
        Set(ByVal pValue As Boolean)
            GetSlave(pNumSlave, True).UserLockNonCyclic = pValue
        End Set
    End Property
    ''' <summary>
    ''' Retourn la liste des verrous utilisateurs des trames non cycliques
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property UserBlockedNonCyclics() As Integer() Implements IAnimateur.UserBlockedNonCyclics
        Get
            Dim ltLock As New List(Of Integer)
            SyncLock mSlaves
                For Each lSlave As KeyValuePair(Of Integer, ClsSlave) In mSlaves
                    If lSlave.Value.UserLockNonCyclic Then
                        ltLock.Add(lSlave.Key)
                    End If
                Next
            End SyncLock
            Return ltLock.ToArray
        End Get
    End Property
    ''' <summary>
    ''' Ajoute des trames non clyclique
    ''' </summary>
    ''' <param name="pFrame">Trames</param>
    ''' <param name="pPriority">Priorité</param>
    ''' <exception cref="ArgumentException">Exception générée si une des trames n'est pas une requête</exception>
    ''' <remarks></remarks>
    Public Sub NonCyclicAdd(ByVal pFrame As SendingFrame(), ByVal pPriority As Integer) Implements IAnimateur.NonCyclicAdd
        For Each lFrame As SendingFrame In pFrame
            If lFrame.FrameSend.FrameType <> Modbus.Frame.ClsFrameModbusBase.EFrameType.Request Then
                Throw New ArgumentException("une des trames n'est pas une requête", "pFrame")
            End If
        Next

        For Each lFrame As SendingFrame In pFrame
            NonCyclicAdd(lFrame, pPriority)
        Next
    End Sub
    ''' <summary>
    ''' Ajoute une trame non clyclique
    ''' </summary>
    ''' <param name="pFrame">Trame</param>
    ''' <param name="pPriority">Priorité</param>
    ''' <exception cref="ArgumentException">Exception générée si la trame n'est pas une requête</exception>
    ''' <remarks></remarks>
    Public Sub NonCyclicAdd(ByVal pFrame As SendingFrame, ByVal pPriority As Integer) Implements IAnimateur.NonCyclicAdd
        'Correction de la valeur pPriority
        If pPriority < 0 Then pPriority = 0
        If pPriority >= 255 Then pPriority = 255

        'Controle du type de la trame
        If pFrame.FrameSend.FrameType <> Modbus.Frame.ClsFrameModbusBase.EFrameType.Request Then
            Throw New ArgumentException("pFrame doit être une requête", "pFrame")
        End If

        'Ajout dans la file d'attente
        GetSlave(pFrame.FrameSend.SlaveId, True).AddNonCyclic(pFrame, pPriority)
    End Sub
    ''' <summary>
    ''' Retourne le nombre de trames non cycliques dans le premier buffer
    ''' </summary>
    ''' <param name="pSlave">Numéro d'esclave</param>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property GetFrameNonCyclicCount(ByVal pSlave As Integer) As Integer Implements IAnimateur.GetFrameNonCyclicCount
        Get
            Return GetSlave(pSlave, True).FrameNonCyclicCount
        End Get
    End Property
    ''' <summary>
    ''' Retourne le nombre de trames non cycliques dans le premier buffer
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public ReadOnly Property GetFrameNonCyclicCount() As Integer(,) Implements IAnimateur.GetFrameNonCyclicCount
        Get
            Dim ltCount(,) As Integer
            Dim i As Integer = 0

            SyncLock mSlaves
                ReDim ltCount(mSlaves.Count - 1, 1)
                For Each lSlave As KeyValuePair(Of Integer, ClsSlave) In mSlaves
                    ltCount(i, 0) = lSlave.Key
                    ltCount(i, 1) = lSlave.Value.FrameNonCyclicCount
                    i += 1
                Next
            End SyncLock
            Return ltCount
        End Get
    End Property
    ''' <summary>
    ''' Supprime toutes les trames non cycliques du premier buffer
    ''' </summary>
    ''' <param name="pSlave">Numéro d'esclave</param>
    ''' <remarks></remarks>
    Public Sub ClearNonCyclic(ByVal pSlave As Integer) Implements IAnimateur.ClearNonCyclic
        GetSlave(pSlave, True).ClearNonCyclic()
    End Sub
#End Region
#End Region

#Region "Gestion de verrous"
    ''' <summary>
    ''' Retire des verrous
    ''' </summary>
    ''' <param name="pFrame">Trame à traiter</param>
    ''' <remarks></remarks>
    Public Sub CheckLockRmv(ByVal pFrame As SendingFrame) Implements IAnimateur.CheckLockRmv
        GetSlave(pFrame.FrameSend.SlaveId, True).RmvLock(pFrame)
    End Sub
    ''' <summary>
    ''' Ajoute des verrous
    ''' </summary>
    ''' <param name="pFrame">Trame à traiter</param>
    ''' <remarks></remarks>
    Public Sub CheckLockAdd(ByVal pFrame As SendingFrame) Implements IAnimateur.CheckLockAdd
        GetSlave(pFrame.FrameSend.SlaveId, True).AddLock(pFrame)
    End Sub
#End Region

#Region "Récupération des trames en attente"
    ''' <summary>
    ''' Retourne la trame suivante (Nothing si plus rien)
    ''' </summary>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Function GetNextFrame() As SendingFrame Implements IAnimateur.GetNextFrame
        SyncLock mWaiting
            If mWaiting.Count > 0 Then
                'Trame en attente de traitement par le code client
                Return mWaiting.Dequeue
            Else
                'Aucune trame dans le buffer d'envoi, on tente de le remplir
                SyncLock mSlaves
                    For Each lSlave As KeyValuePair(Of Integer, ClsSlave) In mSlaves
                        For Each lFrame As SendingFrame In lSlave.Value.GetNextFrame
                            mWaiting.Enqueue(lFrame)
                        Next
                    Next
                End SyncLock

                'Retourne une trame
                If mWaiting.Count > 0 Then
                    Return mWaiting.Dequeue
                Else
                    Return Nothing
                End If
            End If
        End SyncLock
    End Function
#End Region
#End Region
End Class
