using System.Reflection;

namespace Pilz.Collections.SimpleHistory;

public class ObjectAction : ObjectBase
{
    public object? Object { get; set; } = null;
    public List<object> ParametersUndo { get; } = [];
    public List<object> ParametersRedo { get; } = [];
    public MethodInfo? MethodUndo { get; set; } = null;
    public MethodInfo? MethodRedo { get; set; } = null;
    public bool AutogenerateObject { get; set; } = true;

    /// <summary>
    /// Creates a new Instance of Object Action.
    /// </summary>
    public ObjectAction()
    {
    }

    /// <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 ObjectAction(object obj, string methodNameUndo, string methodNameRedo) : this(obj, methodNameUndo, methodNameRedo, [], [], BindingFlags.Default, BindingFlags.Default)
    {
    }

    /// <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 ObjectAction(object obj, string methodNameUndo, string methodNameRedo, object[] paramsUndo, object[] paramsRedo) : this(obj, methodNameUndo, methodNameRedo, paramsUndo, paramsRedo, BindingFlags.Default, BindingFlags.Default)
    {
    }

    /// <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 ObjectAction(object obj, string methodNameUndo, string methodNameRedo, object[] paramsUndo, object[] paramsRedo, BindingFlags methodFlagsUndo, BindingFlags methodFlagsRedo)
    {
        Object = obj;
        ParametersUndo.AddRange(paramsUndo);
        ParametersRedo.AddRange(paramsRedo);
        MethodUndo = GetMethodInfo(obj, methodNameUndo, GetFlags(methodFlagsUndo));
        MethodRedo = GetMethodInfo(obj, methodNameRedo, GetFlags(methodFlagsRedo));
    }

    /// <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 ObjectAction(object obj, string methodNameUndo, string methodNameRedo, BindingFlags methodFlagsUndo, BindingFlags methodFlagsRedo) : this(obj, methodNameUndo, methodNameRedo, [], [], methodFlagsUndo, methodFlagsRedo)
    {
    }

    /// <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 ObjectAction(object obj, MethodInfo methodUndo, MethodInfo methodRedo)
    {
        Object = obj;
        MethodUndo = methodUndo;
        MethodRedo = methodRedo;
    }
    /// <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 ObjectAction(object obj, MethodInfo methodUndo, MethodInfo methodRedo, object[] paramsUndo, object[] paramsRedo) : this(obj, methodUndo, methodRedo)
    {
        ParametersUndo.AddRange(paramsUndo);
        ParametersRedo.AddRange(paramsRedo);
    }

    /// <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 ObjectAction(object obj, Delegate methodUndo, Delegate methodRedo)
    {
        Object = obj;
        MethodUndo = methodUndo.Method;
        MethodRedo = methodRedo.Method;
    }

    /// <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 ObjectAction(object obj, Delegate methodUndo, Delegate methodRedo, object[] paramsUndo, object[] paramsRedo) : this(obj, methodUndo, methodRedo)
    {
        ParametersUndo.AddRange(paramsUndo);
        ParametersRedo.AddRange(paramsRedo);
    }

    /// <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 ObjectAction(object obj, Action methodUndo, Action methodRedo)
    {
        Object = obj;
        MethodUndo = methodUndo.Method;
        MethodRedo = methodRedo.Method;
    }

    private static BindingFlags GetFlags(BindingFlags flags)
    {
        if (flags == BindingFlags.Default)
            flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
        return flags;
    }

    private static MethodInfo? GetMethodInfo(object obj, string name, BindingFlags flags)
    {
        return obj.GetType().GetMethod(name, flags);
    }

    internal void Undo()
    {
        CheckIfObjIsNothing(MethodUndo);
        MethodUndo?.Invoke(Object, ParametersUndo.ToArray());
    }

    internal void Redo()
    {
        CheckIfObjIsNothing(MethodRedo);
        MethodRedo?.Invoke(Object, ParametersRedo.ToArray());
    }

    private void CheckIfObjIsNothing(MethodInfo? mi)
    {
        if (mi is not null && !mi.IsStatic && AutogenerateObject)
        {
            if ((Object is null || Object.GetType() != mi.ReflectedType) && !mi.IsStatic)
            {
                var constructor = mi.ReflectedType?.GetConstructor(Type.EmptyTypes);
                var classObject = constructor?.Invoke([]);
                Object = classObject;
            }
        }
    }
}