Imports System.Reflection
Imports System.ComponentModel

Namespace Data

    ''' <summary>
    ''' A set of reflected properties.
    ''' </summary>
    ''' <typeparam name="T">The type of the object on which to generate property descriptors.</typeparam>
    ''' <remarks></remarks>
    Friend Class ReflectedProperties(Of T)

        Friend Sub New()
            Me.InitializeCollection()
        End Sub

        Friend Sub New(ByVal isReadOnly As Boolean)
            Me.New(Nothing, Nothing, isReadOnly)
        End Sub

        Friend Sub New(ByVal overrideGet As Func(Of String, Object, Object), ByVal overrideSet As Action(Of String, Object, Object), ByVal isReadOnly As Boolean)
            Me._getValueHandler = overrideGet
            Me._setValueHandler = overrideSet
            Me._isReadOnly = isReadOnly
            Me.InitializeCollection()
        End Sub

        Private _reflectedProperties As IDictionary(Of String, PropertyInfo) = New Dictionary(Of String, PropertyInfo)()
        Private _descriptors As PropertyDescriptorCollection
        Private _getValueHandler As Func(Of String, Object, Object)
        Private _setValueHandler As Action(Of String, Object, Object)
        Private _isReadOnly As Boolean

        Friend ReadOnly Property Descriptors() As PropertyDescriptorCollection
            Get
                Return Me._descriptors
            End Get
        End Property

        Friend ReadOnly Property IsReadOnly() As Boolean
            Get
                Return Me._isReadOnly
            End Get
        End Property

        Friend ReadOnly Property GetValueHandler() As Func(Of String, Object, Object)
            Get
                Return Me._getValueHandler
            End Get
        End Property

        Friend ReadOnly Property SetValueHandler() As Action(Of String, Object, Object)
            Get
                Return Me._setValueHandler
            End Get
        End Property

        Private Sub InitializeCollection()
            Dim currentType = GetType(T)
            Dim pArray = currentType.GetProperties()
            If pArray IsNot Nothing AndAlso pArray.Length > 0 Then
                Dim descArr = New DefaultPropertyDescriptor(pArray.Length - 1) {}
                Dim current As PropertyInfo = Nothing
                Dim descrip As DefaultPropertyDescriptor = Nothing
                For i As Integer = 0 To pArray.Length - 1
                    current = pArray(i)
                    Me._reflectedProperties.Add(current.Name, current)
                    If Not (current.CanWrite) OrElse Me.IsReadOnly Then
                        descrip = New DefaultPropertyDescriptor(currentType, current.Name, current.PropertyType, AddressOf _InternalGetValue)
                    Else
                        descrip = New DefaultPropertyDescriptor(currentType, current.Name, current.PropertyType, AddressOf _InternalGetValue, AddressOf _InternalSetValue)
                    End If
                    descArr.SetValue(descrip, i)
                Next

                Me._descriptors = New PropertyDescriptorCollection(descArr)
            End If
        End Sub

        Private Function _InternalGetValue(ByVal propertyName As String, ByVal component As Object) As Object
            If Me.GetValueHandler IsNot Nothing Then _
                Return Me.GetValueHandler.Invoke(propertyName, component)
            Return Me.GetReflectedPropertyValue(propertyName, component)
        End Function

        Private Sub _InternalSetValue(ByVal propertyName As String, ByVal component As Object, ByVal value As Object)
            If Me.SetValueHandler IsNot Nothing Then _
                Me.SetValueHandler.Invoke(propertyName, component, value)
            Me.SetReflectedPropertyValue(propertyName, component, value)
        End Sub

        Friend Function GetReflectedPropertyValue(ByVal propertyName As String, ByVal component As Object) As Object
            Dim pInfo As PropertyInfo = Nothing
            If component IsNot Nothing AndAlso Me._reflectedProperties.TryGetValue(propertyName, pInfo) Then
                Return pInfo.GetValue(component, Nothing)
            End If
            Return Nothing
        End Function

        Friend Sub SetReflectedPropertyValue(ByVal propertyName As String, ByVal component As Object, ByVal value As Object)
            Dim pInfo As PropertyInfo = Nothing
            If component IsNot Nothing AndAlso Me._reflectedProperties.TryGetValue(propertyName, pInfo) Then
                pInfo.SetValue(component, value, Nothing)
            End If
        End Sub

    End Class

End Namespace