' *
' * The project site is at: http://sourceforge.jp/projects/fishbornas/
' *
' * First author tiritomato 2012.
' *
' * Distributed under the FishbornArchiveShelf License (See
' *  file "Licenses/License.txt" contained in a project, or the following link.
' *  http://sourceforge.jp/projects/fishbornas/scm/svn/blobs/head/trunk/Licenses/License.txt)
' *
' * 2012.06.07 Initial Revision (tiritomato)
' *

Partial Public Class Logic

    ''' <summary>
    ''' デリゲート<see cref="Echoes.Handler"/>をリスト管理するクラス。
    ''' </summary>
    ''' <typeparam name="TArgs">この型によって<see cref="Echoes.Handler"/>デリゲートを定義します。</typeparam>
    ''' <remarks>
    ''' <para>デリゲートリストを構築します。<br/>マルチキャストデリゲートと似ていますが、以下の点で異なります。
    ''' <list type="bullet">
    ''' <item><description>二重登録が禁止される</description></item>
    ''' <item><description>デリゲートが登録済みか検査できる</description></item>
    ''' <item><description><see cref="Echoes.Handler"/>デリゲートしか登録できない</description></item>
    ''' </list>リスト操作にマルチキャストデリゲートが与えられると、個別に解体されてから評価されます。</para>
    ''' <para>リスト管理はスレッドセーフです。ただし、<see cref="Echoes.Echo"/>内からリスト編集操作をコールするとデッドロックします。</para>
    ''' <para><paramref name="TArgs"/>が<see cref="EventArgs"/>を継承するときは<see cref="UniqueEvents"/>を利用してください。</para>
    ''' </remarks>
    Public Class Echoes(Of TArgs)

        ''' <summary>
        ''' <see cref="Echoes"/>でリスト管理するデリゲートを定義します。この型に適合するデリゲートがリストとして管理されます。
        ''' </summary>
        ''' <param name="e"><paramref name="TArgs"/>を引数にもつデリゲートが定義されます。</param>
        ''' <remarks><see cref="Echoes"/>でリスト管理するデリゲートでは戻り値は返せません。</remarks>
        Public Delegate Sub Handler(ByVal e As TArgs)

        ''' <summary>
        ''' リストをクリアします。
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub Clear()
            SyncLock Payload
                Payload.Clear()
            End SyncLock
        End Sub

        ''' <summary>
        ''' リストに、指定した<see cref="Handler"/>デリゲートが含まれるかを返します。
        ''' </summary>
        ''' <param name="d">検査するデリゲート</param>
        ''' <returns><see cref="Echoes"/>のリスト中に、<paramref name="d"/>が含まれる時に True を返します。それ以外の時に False を返します。</returns>
        ''' <remarks>マルチキャストデリゲートを指定すると解体されて、ひとつでも一致する要素があれば True を返します。</remarks>
        Public Function Contains(ByVal d As Handler) As Boolean
            SyncLock Payload
                Contains = Contains(GetInvocationList(d))
            End SyncLock
        End Function

        ''' <summary>
        ''' リストに<see cref="Handler"/>デリゲートを追加します。
        ''' </summary>
        ''' <param name="d">追加するデリゲート</param>
        ''' <returns>一つ以上デリゲートが追加されると True を返します。それ以外の時に False を返します。</returns>
        ''' <remarks>マルチキャストデリゲートを指定すると追加前に解体され、解体された個別のデリゲートごとに同じデリゲートが既に登録されているかが検査されて、登録されていなかったデリゲートだけが追加登録されます。</remarks>
        Public Function Add(ByVal d As Handler) As Boolean
            Add = False
            SyncLock Payload
                Dim list As System.Collections.Generic.List(Of Handler) = GetInvocationList(d)
                If 0 < list.Count And Contains(list) = False Then
                    Payload.AddRange(list)
                    Add = True
                End If
            End SyncLock
        End Function

        ''' <summary>
        ''' リストから指定した<see cref="Handler"/>デリゲートを削除します。
        ''' </summary>
        ''' <param name="d">削除するデリゲート</param>
        ''' <remarks>マルチキャストデリゲートを指定すると追加前に解体され、解体された個別のデリゲートごとに同じデリゲートが既に登録されているかが検査されて、登録されていたデリゲートが削除されます。</remarks>
        Public Sub Remove(ByVal d As Handler)
            SyncLock Payload
                For Each item As Handler In GetInvocationList(d)
                    If Payload.Contains(item) Then Payload.Remove(item)
                Next
            End SyncLock
        End Sub

        ''' <summary>
        ''' リストされた<see cref="Handler"/>デリゲートをリストの最初から順番に呼び出します。
        ''' </summary>
        ''' <param name="e"><see cref="Handler"/>デリゲートに渡す引数</param>
        ''' <remarks><see cref="Echoes"/>のメソッドはスレッドセーフです。ただしリストされたデリゲートの内部から、この<see cref="Echoes"/>オブジェクトのEchoを再度呼び出したり、<see cref="Clear"/>や<see cref="Add"/>などリストの編集操作を呼び出すとデッドロックします。</remarks>
        Public Sub Echo(ByVal e As TArgs)
            SyncLock Payload
                For Each d As Handler In Payload
                    d(e)
                Next
            End SyncLock
        End Sub

        Private Payload As New System.Collections.Generic.List(Of Handler)

        ''' <summary>
        ''' Echoesのリスト中に、listに列挙されるHandlerデリゲートに一致するアイテムが含まれるかを返します。
        ''' </summary>
        ''' <param name="list">検査するHandlerデリゲートのリスト</param>
        ''' <returns>
        ''' <para><see cref="Echoes"/>のリスト中に、listに列挙されるHandlerデリゲートに一致するアイテムが含まれる時に True を返します。</para>
        ''' <para>それ以外の時に False を返します。</para>
        ''' </returns>
        ''' <remarks></remarks>
        Private Function Contains(ByVal list As System.Collections.Generic.IEnumerable(Of Handler)) As Boolean
            Contains = False
            If list Is Nothing Then Exit Function
            For Each item As Handler In list
                If Payload.Contains(item) Then
                    Contains = True
                    Exit For
                End If
            Next
        End Function

        Private Shared Function GetInvocationList(ByVal d As Handler) As System.Collections.Generic.List(Of Handler)

            Dim ret As New System.Collections.Generic.List(Of Handler)
            GetInvocationList = ret
            If d Is Nothing Then Exit Function

            Dim list As System.Delegate() = d.GetInvocationList
            For Each item As System.Delegate In list
                Dim try_result As Handler = TryCast(item, Handler)
                If try_result IsNot Nothing AndAlso ret.Contains(try_result) = False Then ret.Add(try_result)
            Next

        End Function

    End Class

End Class