Imports System.IO
Imports System.Reflection
Imports Pilz.Reflection.PluginSystem.Attributes

Public Class Plugin

    ''' <summary>
    ''' A collection of Methods that contains PluginFunctions that will be called automatically when loading the Plugin, as long as the property AutoCallMainFunctions is set to True.
    ''' </summary>
    ''' <returns></returns>
    Public ReadOnly Property MainFunctions As IReadOnlyList(Of PluginFunction)
    ''' <summary>
    ''' A collection of Methods with a FunctionCode (excluding all PluginFunctions from MainFunctions).
    ''' </summary>
    ''' <returns></returns>
    Public ReadOnly Property PluginFunctions As IReadOnlyList(Of PluginFunction)
    ''' <summary>
    ''' Gets the assembly that contains the PluginFunctions of this Plugin
    ''' </summary>
    ''' <returns></returns>
    Public ReadOnly Property Assembly As Assembly
    ''' <summary>
    ''' Gets the main module that contains the PluginFunctions of this Plugin
    ''' </summary>
    ''' <returns></returns>
    Public ReadOnly Property MainModule As Type

    ''' <summary>
    ''' Load a new Plugin and its PluginFunctions.
    ''' </summary>
    ''' <param name="filePath"></param>
    ''' <param name="autoCallMainFunction">If true, all MainMethods of a Plugin will be called as soon as a Plugin is loaded.</param>
    ''' <param name="entryTypeName">The name of the type where to search for Methods when loading a new Plugin.</param>
    Public Sub New(filePath As String, autoCallMainFunction As Boolean, entryTypeName As String)
        Assembly = Assembly.LoadFile(filePath)
        MainModule = Assembly.GetType(entryTypeName)

        If MainModule Is Nothing Then
            Throw New PluginLoadException("Plugin Modul not found!")
        End If

        'Define the attribute types to observe
        Dim entryMethodType As Type = GetType(LoadMethodAttribute)
        Dim implementMethodType As Type = GetType(PluginFunctionAttribute)

        'Create the lists
        Dim mainMethods As New List(Of PluginFunction)
        Dim implementMethods As New List(Of PluginFunction)

        'Search for PluginFunctions
        For Each mi As MethodInfo In MainModule.GetMethods
            Dim found As Boolean = False

            'Check if the method has one of the defined attributes
            For Each attr As Attribute In Attribute.GetCustomAttributes(mi)
                If Not found Then
                    Dim t As Type = attr.GetType

                    Select Case t
                        Case entryMethodType
                            mainMethods.Add(New PluginFunction(mi, Me))

                        Case implementMethodType
                            With CType(attr, PluginFunctionAttribute)
                                implementMethods.Add(New PluginFunction(mi, Me, .Params, .FunctionCode))
                            End With

                    End Select

                    found = True
                End If
            Next
        Next

        'Set the collections
        Me.MainFunctions = mainMethods
        Me.PluginFunctions = implementMethods

        'Call all PluginFunctions in MainFunctions
        If autoCallMainFunction Then
            For Each func As PluginFunction In mainMethods
                Dim params As ParameterInfo() = func.Method.GetParameters
                If params.Length = 1 Then
                    Dim startupExe As String = Assembly.GetEntryAssembly.Location
                    Dim args As String() = {startupExe, filePath}
                    func.Invoke({args})
                ElseIf Not params.Any Then
                    func.Invoke()
                End If
            Next
        End If
    End Sub

    ''' <summary>
    ''' Get all PluginFunctions that have one of the given function codes.
    ''' </summary>
    ''' <param name="funcCodes"></param>
    ''' <returns></returns>
    Public Function GetFunctions(ParamArray funcCodes As String()) As IEnumerable(Of PluginFunction)
        Dim funcs As New List(Of PluginFunction)

        For Each func As PluginFunction In PluginFunctions
            If funcCodes.Contains(func.FunctionCode) Then
                funcs.Add(func)
            End If
        Next

        Return funcs
    End Function

    ''' <summary>
    ''' Get the first PluginFunction that have the one of the given function codes.
    ''' </summary>
    ''' <returns></returns>
    Public Function GetFunction(ParamArray funcCodes As String()) As PluginFunction
        Dim f As PluginFunction = Nothing

        For Each func As PluginFunction In PluginFunctions
            If f Is Nothing AndAlso funcCodes.Contains(func.FunctionCode) Then
                f = func
            End If
        Next

        Return f
    End Function

End Class
