Imports System.Reflection
Imports System.Runtime.InteropServices

Namespace SimpleHistory

    Public Class ObjectAction
        Inherits ObjectBase

        Public Property [Object] As Object = Nothing
        Public ReadOnly Property ParametersUndo As New List(Of Object)
        Public ReadOnly Property ParametersRedo As New List(Of Object)
        Public Property MethodUndo As MethodInfo = Nothing
        Public Property MethodRedo As MethodInfo = Nothing
        Public Property AutogenerateObject As Boolean = True

        ''' <summary>
        ''' Creates a new Instance of Object Action.
        ''' </summary>
        Public Sub New()
        End Sub

        Private Function GetMethodInfo(obj As Object, name As String, flags As BindingFlags)
            Return obj.GetType.GetMethod(name, flags)
        End Function

        ''' <summary>
        ''' Creates a new Instance of Object Action.
        ''' </summary>
        ''' <param name="obj">The Objects that contains the methodes to call.</param>
        ''' <param name="methodNameUndo">The name of the methode to call on Undo.</param>
        ''' <param name="methodNameRedo">The name of the methode to call on Redo.</param>
        Public Sub New(obj As Object, methodNameUndo As String, methodNameRedo As String)
            Me.New(obj, methodNameUndo, methodNameRedo, {}, {}, BindingFlags.Default, BindingFlags.Default)
        End Sub
        ''' <summary>
        ''' Creates a new Instance of Object Action.
        ''' </summary>
        ''' <param name="obj">The Objects that contains the methodes to call.</param>
        ''' <param name="methodNameUndo">The name of the methode to call on Undo.</param>
        ''' <param name="methodNameRedo">The name of the methode to call on Redo.</param>
        ''' <param name="paramsUndo">The parameters for calling the methode on Undo.</param>
        ''' <param name="paramsRedo">The parameters for calling the methode on Redo.</param>
        Public Sub New(obj As Object, methodNameUndo As String, methodNameRedo As String, paramsUndo As Object(), paramsRedo As Object())
            Me.New(obj, methodNameUndo, methodNameRedo, paramsUndo, paramsRedo, BindingFlags.Default, BindingFlags.Default)
        End Sub
        ''' <summary>
        ''' Creates a new Instance of Object Action.
        ''' </summary>
        ''' <param name="obj">The Objects that contains the methodes to call.</param>
        ''' <param name="methodNameUndo">The name of the methode to call on Undo.</param>
        ''' <param name="methodNameRedo">The name of the methode to call on Redo.</param>
        ''' <param name="paramsUndo">The parameters for calling the methode on Undo.</param>
        ''' <param name="paramsRedo">The parameters for calling the methode on Redo.</param>
        ''' <param name="methodFlagsUndo">The Binding Flags of Methode on Undo.</param>
        ''' <param name="methodFlagsRedo">The Binding Flags of Methode on Redo.</param>
        Public Sub New(obj As Object, methodNameUndo As String, methodNameRedo As String, paramsUndo As Object(), paramsRedo As Object(), methodFlagsUndo As BindingFlags, methodFlagsRedo As BindingFlags)
            [Object] = obj
            ParametersUndo.AddRange(paramsUndo)
            ParametersRedo.AddRange(paramsRedo)
            MethodUndo = GetMethodInfo(obj, methodNameUndo, GetFlags(methodFlagsUndo))
            MethodRedo = GetMethodInfo(obj, methodNameRedo, GetFlags(methodFlagsRedo))
        End Sub
        ''' <summary>
        ''' Creates a new Instance of Object Action.
        ''' </summary>
        ''' <param name="obj">The Objects that contains the methodes to call.</param>
        ''' <param name="methodNameUndo">The name of the methode to call on Undo.</param>
        ''' <param name="methodNameRedo">The name of the methode to call on Redo.</param>
        ''' <param name="methodFlagsUndo">The Binding Flags of Methode on Undo.</param>
        ''' <param name="methodFlagsRedo">The Binding Flags of Methode on Redo.</param>
        Public Sub New(obj As Object, methodNameUndo As String, methodNameRedo As String, methodFlagsUndo As BindingFlags, methodFlagsRedo As BindingFlags)
            Me.New(obj, methodNameUndo, methodNameRedo, {}, {}, methodFlagsUndo, methodFlagsRedo)
        End Sub

        ''' <summary>
        ''' Creates a new Instance of Object Action.
        ''' </summary>
        ''' <param name="obj">The Objects that contains the methodes to call.</param>
        ''' <param name="methodUndo">The MethodInfo of the methode to call on Undo.</param>
        ''' <param name="methodRedo">The MethodInfo of the methode to call on Redo.</param>
        Public Sub New(obj As Object, methodUndo As MethodInfo, methodRedo As MethodInfo)
            [Object] = obj
            Me.MethodUndo = methodUndo
            Me.MethodRedo = methodRedo
        End Sub
        ''' <summary>
        ''' Creates a new Instance of Object Action.
        ''' </summary>
        ''' <param name="obj">The Objects that contains the methodes to call.</param>
        ''' <param name="methodUndo">The MethodInfo of the methode to call on Undo.</param>
        ''' <param name="methodRedo">The MethodInfo of the methode to call on Redo.</param>
        ''' <param name="paramsUndo">The parameters for calling the methode on Undo.</param>
        ''' <param name="paramsRedo">The parameters for calling the methode on Redo.</param>
        Public Sub New(obj As Object, methodUndo As MethodInfo, methodRedo As MethodInfo, paramsUndo As Object(), paramsRedo As Object())
            Me.New(obj, methodUndo, methodRedo)
            ParametersUndo.AddRange(paramsUndo)
            ParametersRedo.AddRange(paramsRedo)
        End Sub
        ''' <summary>
        ''' Creates a new Instance of Object Action.
        ''' </summary>
        ''' <param name="obj">The Objects that contains the methodes to call.</param>
        ''' <param name="methodUndo">The Delegate of the methode to call on Undo.</param>
        ''' <param name="methodRedo">The Delegate of the methode to call on Redo.</param>
        Public Sub New(obj As Object, methodUndo As [Delegate], methodRedo As [Delegate])
            [Object] = obj
            Me.MethodUndo = methodUndo.Method
            Me.MethodRedo = methodRedo.Method
        End Sub
        ''' <summary>
        ''' Creates a new Instance of Object Action.
        ''' </summary>
        ''' <param name="obj">The Objects that contains the methodes to call.</param>
        ''' <param name="methodUndo">The Delegate of the methode to call on Undo.</param>
        ''' <param name="methodRedo">The Delegate of the methode to call on Redo.</param>
        ''' <param name="paramsUndo">The parameters for calling the methode on Undo.</param>
        ''' <param name="paramsRedo">The parameters for calling the methode on Redo.</param>
        Public Sub New(obj As Object, methodUndo As [Delegate], methodRedo As [Delegate], paramsUndo As Object(), paramsRedo As Object())
            Me.New(obj, methodUndo, methodRedo)
            ParametersUndo.AddRange(paramsUndo)
            ParametersRedo.AddRange(paramsRedo)
        End Sub
        ''' <summary>
        ''' Creates a new Instance of Object Action.
        ''' </summary>
        ''' <param name="obj">The Objects that contains the methodes to call.</param>
        ''' <param name="methodUndo">The Action of the methode to call on Undo.</param>
        ''' <param name="methodRedo">The Action of the methode to call on Redo.</param>
        Public Sub New(obj As Object, methodUndo As Action, methodRedo As Action)
            [Object] = obj
            Me.MethodUndo = methodUndo.Method
            Me.MethodRedo = methodRedo.Method
        End Sub

        Private Function GetFlags(flags As BindingFlags) As BindingFlags
            If flags = BindingFlags.Default Then
                flags = BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic
            End If
            Return flags
        End Function

        Friend Sub Undo()
            CheckIfObjIsNothing(MethodUndo)
            MethodUndo?.Invoke([Object], ParametersUndo.ToArray)
        End Sub

        Friend Sub Redo()
            CheckIfObjIsNothing(MethodRedo)
            MethodRedo?.Invoke([Object], ParametersRedo.ToArray)
        End Sub

        Private Sub CheckIfObjIsNothing(mi As MethodInfo)
            If mi IsNot Nothing AndAlso Not mi.IsStatic AndAlso AutogenerateObject Then
                If (Me.Object Is Nothing OrElse Me.Object.GetType <> mi.ReflectedType) AndAlso Not mi.IsStatic Then
                    Dim constructor As ConstructorInfo = mi.ReflectedType.GetConstructor(Type.EmptyTypes)
                    Dim classObject As Object = constructor.Invoke({})
                    Me.Object = classObject
                End If
            End If
        End Sub

    End Class


End Namespace