Imports System.Reflection
Imports System.Collections.Generic
Imports Mercury.Mobile

''' <summary>
''' Describes the comparison options for a Mercury.Mobile.Enumeration object.
''' </summary>
''' <remarks></remarks>
Public Enum EnumerationComparison
    ''' <summary>
    ''' Compares the display name and value for equality.  This is the default comparison.
    ''' </summary>
    ''' <remarks></remarks>
    Full

    ''' <summary>
    ''' Compares only the display name for equality.
    ''' </summary>
    ''' <remarks></remarks>
    Name

    ''' <summary>
    ''' Compares only the value for equality.
    ''' </summary>
    ''' <remarks></remarks>
    Value
End Enum

''' <summary>
''' An object representing an enumerated type value.  To inheritors: 
''' a default constructor is required in order to properly implement 
''' this class.
''' </summary>
''' <remarks></remarks>
Public MustInherit Class Enumeration
    Implements IEqualityComparer(Of Enumeration)

    ''' <summary>
    ''' Initializes a default instance of the <see cref="Mercury.Mobile.Enumeration"/> class.
    ''' </summary>
    ''' <remarks></remarks>
    Protected Sub New()
    End Sub

    ''' <summary>
    ''' Initializes a new instance of the Enumeration class 
    ''' with the specified display name and integer value.
    ''' </summary>
    ''' <param name="displayName">The display name of this item.</param>
    ''' <param name="value">The value for this item.</param>
    ''' <remarks></remarks>
    Protected Sub New(ByVal displayName As String, ByVal value As Integer)
        Me._displayName = displayName
        Me._value = value
    End Sub

    Private Shared ThisType As Type = GetType(Enumeration)
    Private ReadOnly _displayName As String

    ''' <summary>
    ''' Gets the display name of the enumerated type.
    ''' </summary>
    ''' <returns>The display name of the enumerated type.</returns>
    ''' <remarks></remarks>
    Public ReadOnly Property DisplayName() As String
        Get
            Return Me._displayName
        End Get
    End Property

    Private ReadOnly _value As Integer

    ''' <summary>
    ''' Gets the value of the enumerated type.
    ''' </summary>
    ''' <returns>The value of the enumerated type.</returns>
    ''' <remarks></remarks>
    Public ReadOnly Property Value() As Integer
        Get
            Return Me._value
        End Get
    End Property

    ''' <summary>
    ''' Returns the enumeration associated with the specified display name.
    ''' </summary>
    ''' <param name="type">The enumeration type to search.</param>
    ''' <param name="displayName">The display name to search.</param>
    ''' <returns>The enumeration associated with the specified display name or null.</returns>
    ''' <remarks></remarks>
    Public Shared Function FromDisplayName(ByVal type As Type, ByVal displayName As String) As Enumeration
        Dim enumList = Enumeration.GetAll(type)
        If enumList Is Nothing Then _
            Return Nothing
        For Each enm In enumList
            If enm.DisplayName = displayName Then _
                Return enm
        Next
        Return Nothing
    End Function

    ''' <summary>
    ''' Returns the enumeration associated with the specified display name.
    ''' </summary>
    ''' <typeparam name="T">The enumeration type to search.</typeparam>
    ''' <param name="displayName">The display name to search.</param>
    ''' <returns>The enumeration associated with the specified display name or null.</returns>
    ''' <remarks></remarks>
    Public Shared Function FromDisplayName(Of T As {Enumeration, New})(ByVal displayName As String) As T
        Dim enumList = Enumeration.GetAll(Of T)()
        If enumList Is Nothing Then _
            Return Nothing
        For Each enm In enumList
            If enm.DisplayName = displayName Then _
                Return enm
        Next
        Return Nothing
    End Function

    ''' <summary>
    ''' Returns the enumeration associated with the specified value.
    ''' </summary>
    ''' <param name="type">The type of the enumeration to search.</param>
    ''' <param name="value">The value of the enumeration to find.</param>
    ''' <returns>The enumeration associated with the specified value or null.</returns>
    ''' <remarks></remarks>
    Public Shared Function FromValue(ByVal type As Type, ByVal value As Integer) As Enumeration
        Dim enumList = Enumeration.GetAll(type)
        If enumList IsNot Nothing Then
            For Each enm In enumList
                If enm.Value = value Then _
                    Return enm
            Next
        End If
        Return Nothing
    End Function

    ''' <summary>
    ''' Returns the enumeration associated with the specified value.
    ''' </summary>
    ''' <typeparam name="T">The type of the enumeration to search.</typeparam>
    ''' <param name="value">The value of the enumeration to find.</param>
    ''' <returns>The enumeration associated with the specified value or null.</returns>
    ''' <remarks></remarks>
    Public Shared Function FromValue(Of T As {Enumeration, New})(ByVal value As Integer) As T
        Dim enumList = Enumeration.GetAll(Of T)()
        If enumList IsNot Nothing Then
            For Each enm In enumList
                If enm.Value = value Then _
                    Return enm
            Next
        End If
        Return Nothing
    End Function

    ''' <summary>
    ''' Returns the elements of the specified enumeration.
    ''' </summary>
    ''' <typeparam name="T">The type of the enumeration whose elements to find.</typeparam>
    ''' <returns>The elements of the specified enumeration or null.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetAll(Of T As {Enumeration, New})() As IEnumerable(Of T)
        Dim type = GetType(T)
        If type Is Nothing Then _
            Return Nothing
        Dim fields = type.GetFields(BindingFlags.Public Or BindingFlags.Static Or BindingFlags.DeclaredOnly)
        Dim instance = New T()
        Dim enumList = New List(Of T)()
        For Each info In fields
            Dim result = TryCast(info.GetValue(instance), T)
            If result IsNot Nothing Then _
                enumList.Add(result)
        Next
        Return enumList
    End Function

    ''' <summary>
    ''' Returns the elements of the specified enumeration.
    ''' </summary>
    ''' <param name="type">The type of the enumeration whose elements to find.</param>
    ''' <returns>The elements of the specified enumeration or null.</returns>
    ''' <remarks></remarks>
    Public Shared Function GetAll(ByVal type As Type) As IEnumerable(Of Enumeration)
        If type Is Nothing Then _
            Return Nothing
        If Not (ThisType.IsAssignableFrom(type)) Then _
            Return New ArgumentException("The specified type does not derive from Mercury.Mobile.Enumeration.")
        Dim fields = type.GetFields(BindingFlags.Public Or BindingFlags.Static Or BindingFlags.DeclaredOnly)
        Dim instance = Activator2.CreateInstance(type, True)
        Dim enumList = New List(Of Enumeration)()
        For Each info In fields
            Dim result = TryCast(info.GetValue(instance), Enumeration)
            If result IsNot Nothing Then _
                enumList.Add(result)
        Next
        Return enumList
    End Function

    ''' <summary>
    ''' Returns a System.String that represents the current <see cref="Mercury.Mobile.Enumeration"/>.
    ''' </summary>
    ''' <returns>A System.String that represents the current <see cref="Mercury.Mobile.Enumeration"/>.</returns>
    ''' <remarks></remarks>
    Public Overrides Function ToString() As String
        If String.IsNullOrEmpty(Me.DisplayName) Then _
            Return MyBase.ToString()
        Return Me.DisplayName
    End Function

    Public Shared Operator =(ByVal x As Enumeration, ByVal y As Enumeration) As Boolean
        Return Enumeration.Equals(x, y, EnumerationComparison.Full)
    End Operator

    Public Shared Operator <>(ByVal x As Enumeration, ByVal y As Enumeration) As Boolean
        Return Not (x = y)
    End Operator

    Private Function InstanceEquals(ByVal x As Enumeration, ByVal y As Enumeration) As Boolean Implements System.Collections.Generic.IEqualityComparer(Of Enumeration).Equals
        Return Enumeration.Equals(x, y, EnumerationComparison.Full)
    End Function

    ''' <summary>
    ''' Returns a value indicating whether the specified Mercury.Mobile.Enumeration objects are equal.
    ''' </summary>
    ''' <param name="x">The first Mercury.Mobile.Enumeration object to compare.</param>
    ''' <param name="y">The second Mercury.Mobile.Enumeration object to compare.</param>
    ''' <param name="comparison">A value indicating the comparison mode.</param>
    ''' <returns>True if the specified Mercury.Mobile.Enumeration objects are equal; otherwise, false.</returns>
    ''' <remarks></remarks>
    Public Shared Shadows Function Equals(ByVal x As Enumeration, ByVal y As Enumeration, _
                                          ByVal comparison As EnumerationComparison) As Boolean
        If ReferenceEquals(x, Nothing) AndAlso ReferenceEquals(y, Nothing) Then _
            Return True
        If ReferenceEquals(x, Nothing) OrElse ReferenceEquals(y, Nothing) Then _
            Return False
        Dim typeX = x.GetType()
        Dim typeY = y.GetType()
        If Not (typeX.Equals(typeY)) Then _
            Return False

        Select Case comparison
            Case EnumerationComparison.Full
                Return (x.DisplayName = y.DisplayName AndAlso x.Value = y.Value)
            Case EnumerationComparison.Name
                Return (x.DisplayName = y.DisplayName)
            Case EnumerationComparison.Value
                Return (x.Value = y.Value)
        End Select

        Return False
    End Function

    ''' <summary>
    ''' Serves as a hash function for this enumerated type.
    ''' </summary>
    ''' <returns>A hashcode for this Mercury.Mobile.Enumeration object.</returns>
    ''' <remarks></remarks>
    Public Overrides Function GetHashCode() As Integer
        Return Me.GetHashCode(Me)
    End Function

    Private Overloads Function GetHashCode(ByVal obj As Enumeration) As Integer Implements System.Collections.Generic.IEqualityComparer(Of Enumeration).GetHashCode
        Return Enumeration.DeriveHashCode(obj.GetType().Name, obj.DisplayName, obj.Value)
    End Function

    Private Shared Function DeriveHashCode(ByVal enumName As String, ByVal name As String, ByVal value As Integer) As Integer
        Dim hc = name.GetHashCode()
        Dim ehc = enumName.GetHashCode()
        Return CInt(value ^ hc ^ ehc)
    End Function

End Class