Imports System

Imports System.Collections

Imports System.Collections.Generic

Imports System.Reflection

Imports System.Reflection.Emit

Imports System.Text.RegularExpressions

Imports System.Runtime.CompilerServices


Namespace com.bodurov

    Public Module DataSourceCreator


        Private ReadOnly PropertNameRegex As New Regex("^[A-Za-z]+[A-Za-z0-9_]*$", RegexOptions.Singleline)


        <Extension()> _

        Public Function ToDataSource(ByVal list As IEnumerable(Of IDictionary)) As IEnumerable

            Dim firstDict As IDictionary = Nothing

            Dim hasData As Boolean = False

            For Each currentDict As IDictionary In list

                hasData = True

                firstDict = currentDict

                Exit For


            If Not hasData Then

                Return New Object() {}

            End If

            If firstDict Is Nothing Then

                Throw New ArgumentException("IDictionary entry cannot be null")

            End If


            Dim objectType As Type = Nothing


            Dim tb As TypeBuilder = GetTypeBuilder(list.GetHashCode())


            Dim constructor As ConstructorBuilder = tb.DefineDefaultConstructor(MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.RTSpecialName)


            For Each pair As DictionaryEntry In firstDict

                If PropertNameRegex.IsMatch(Convert.ToString(pair.Key), 0) Then

                    Dim type As Type

                    If pair.Value Is Nothing Then

                        type = GetType(Object)


                        type = pair.Value.[GetType]()()

                    End If

                    CreateProperty(tb, Convert.ToString(pair.Key), type)


                    Throw New ArgumentException("Each key of IDictionary must be alphanumeric and start with character.")

                End If


            objectType = tb.CreateType()


            Return GenerateEnumerable(objectType, list, firstDict)

        End Function


        Private Function GenerateEnumerable(ByVal objectType As Type, ByVal list As IEnumerable(Of IDictionary), ByVal firstDict As IDictionary) As IEnumerable

            Dim listType As Type = GetType(List(Of )).MakeGenericType(New Type() {objectType})

            Dim listOfCustom As IList = Activator.CreateInstance(listType)


            For Each currentDict As IDictionary In list

                If currentDict Is Nothing Then

                    Throw New ArgumentException("IDictionary entry cannot be null")

                End If

                Dim row As Object = Activator.CreateInstance(objectType)

                For Each pair As DictionaryEntry In firstDict

                    If currentDict.Contains(pair.Key) Then

                        Dim [property] As PropertyInfo = objectType.GetProperty(Convert.ToString(pair.Key))

                        [property].SetValue(row, Convert.ChangeType(currentDict(pair.Key), [property].PropertyType, Nothing), Nothing)

                    End If


                listType.GetMethod("Add").Invoke(listOfCustom, New Object() {row})


            Return TryCast(listOfCustom, IEnumerable)

        End Function


        Private Function GetTypeBuilder(ByVal code As Integer) As TypeBuilder

            Dim an As New AssemblyName("TempAssembly" & code.ToString())

            Dim assemblyBuilder As AssemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run)

            Dim moduleBuilder As ModuleBuilder = assemblyBuilder.DefineDynamicModule("MainModule")


            Dim tb As TypeBuilder = moduleBuilder.DefineType("TempType" & code.ToString(), TypeAttributes.[Public] Or TypeAttributes.[Class] Or TypeAttributes.AutoClass Or TypeAttributes.AnsiClass Or TypeAttributes.BeforeFieldInit Or TypeAttributes.AutoLayout, GetType(Object))

            Return tb

        End Function


        Private Sub CreateProperty(ByVal tb As TypeBuilder, ByVal propertyName As String, ByVal propertyType As Type)

            Dim fieldBuilder As FieldBuilder = tb.DefineField("_" & propertyName, propertyType, FieldAttributes.[Private])



            Dim propertyBuilder As PropertyBuilder = tb.DefineProperty(propertyName, PropertyAttributes.HasDefault, propertyType, Nothing)

            Dim getPropMthdBldr As MethodBuilder = tb.DefineMethod("get_" & propertyName, MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, propertyType, Type.EmptyTypes)


            Dim getIL As ILGenerator = getPropMthdBldr.GetILGenerator()



            getIL.Emit(OpCodes.Ldfld, fieldBuilder)



            Dim setPropMthdBldr As MethodBuilder = tb.DefineMethod("set_" & propertyName, MethodAttributes.[Public] Or MethodAttributes.SpecialName Or MethodAttributes.HideBySig, Nothing, New Type() {propertyType})


            Dim setIL As ILGenerator = setPropMthdBldr.GetILGenerator()




            setIL.Emit(OpCodes.Stfld, fieldBuilder)





        End Sub

    End Module

End Namespace