using System.Data;
using System.Reflection;

namespace Pilz.Collections.SimpleHistory;

/// <summary>
/// Represent some Object States and Actions.
/// </summary>
public class HistoryPoint
{
    /// <summary>
    /// Represents the Name of this History Point
    /// </summary>
    /// <returns></returns>
    public string Name { get; set; } = string.Empty;

    /// <summary>
    /// A List of Object States and Actions.
    /// </summary>
    /// <returns></returns>
    public List<ObjectBase> Entries { get; } = [];

    /// <summary>
    /// Some data can be refered on this HistoryPoint. Don't know, in some situations this can be helpful.
    /// </summary>
    public readonly object? Tag = null;

    public bool HasEntries<T>() where T : ObjectBase
    {
        return Entries.Where(n => n is T).Any();
    }

    internal void Undo()
    {
        foreach (var s in Entries.OrderBy(n => n.UndoPriority))
        {
            switch (s)
            {
                case ObjectState state:
                    state.Patch();
                    break;
                case ObjectAction action:
                    action.Undo();
                    break;
            }
        }
    }

    internal void Redo()
    {
        foreach (var s in Entries.OrderBy(n => n.RedoPriority))
        {
            switch (s)
            {
                case ObjectState state:
                    state.Patch();
                    break;
                case ObjectAction action:
                    action.Redo();
                    break;
            }
        }
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The objects that should be included.</param>
    /// <param name="whiteList">Specify which members to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] obj, MemberWhiteList whiteList)
    {
        return FromObject([obj], ObjectValueType.None, (object)whiteList, BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The objects that should be included.</param>
    /// <param name="blackList">Specify which members to exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] obj, MemberBlackList blackList)
    {
        return FromObject([obj], ObjectValueType.None, (object)blackList, BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The objects that should be included.</param>
    /// <param name="memberName">The member names to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] obj, params string[] memberName)
    {
        return FromObject(obj, true, memberName);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The objects that should be included.</param>
    /// <param name="isWhiteList">If true, the memberName-Array has member names that should be included.</param>
    /// <param name="memberName">The member names to include/exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] obj, bool isWhiteList, params string[] memberName)
    {
        if (isWhiteList)
            return FromObject([obj], ObjectValueType.None, (object)new MemberWhiteList(memberName), BindingFlags.Default);
        else
            return FromObject([obj], ObjectValueType.None, (object)new MemberBlackList(memberName), BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The objects that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="memberName">The member names to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] obj, ObjectValueType membersToStore, params string[] memberName)
    {
        return FromObject(obj, membersToStore, true, memberName);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The objects that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="isWhiteList">If true, the memberName-Array has member names that should be included.</param>
    /// <param name="memberName">The member names to include/exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] obj, ObjectValueType membersToStore, bool isWhiteList, params string[] memberName)
    {
        if (isWhiteList)
            return FromObject([obj], membersToStore, (object)new MemberWhiteList(memberName), BindingFlags.Default);
        else
            return FromObject([obj], membersToStore, (object)new MemberBlackList(memberName), BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The objects that should be included.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <param name="memberName">The member names to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] obj, BindingFlags flags, params string[] memberName)
    {
        return FromObject(obj, flags, true, memberName);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The objects that should be included.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <param name="isWhiteList">If true, the memberName-Array has member names that should be included.</param>
    /// <param name="memberName">The member names to include/exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] obj, BindingFlags flags, bool isWhiteList, params string[] memberName)
    {
        if (isWhiteList)
            return FromObject([obj], ObjectValueType.None, (object)new MemberWhiteList(memberName), flags);
        else
            return FromObject([obj], ObjectValueType.None, (object)new MemberBlackList(memberName), flags);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The objects that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <param name="memberName">The member names to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] obj, ObjectValueType membersToStore, BindingFlags flags, params string[] memberName)
    {
        return FromObject(obj, flags, true, memberName);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The objects that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <param name="isWhiteList">If true, the memberName-Array has member names that should be included.</param>
    /// <param name="memberName">The member names to include/exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] obj, ObjectValueType membersToStore, BindingFlags flags, bool isWhiteList, params string[] memberName)
    {
        if (isWhiteList)
            return FromObject([obj], membersToStore, (object)new MemberWhiteList(memberName), flags);
        else
            return FromObject([obj], membersToStore, (object)new MemberBlackList(memberName), flags);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="whiteList">Specify which members to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, MemberWhiteList whiteList)
    {
        return FromObject([obj], ObjectValueType.None, (object)whiteList, BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="blackList">Specify which members to exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, MemberBlackList blackList)
    {
        return FromObject([obj], ObjectValueType.None, (object)blackList, BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="memberName">The member names to include/exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, params string[] memberName)
    {
        return FromObject(obj, true, memberName);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="isWhiteList">If true, the memberName-Array has member names that should be included.</param>
    /// <param name="memberName">The member names to include/exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, bool isWhiteList, params string[] memberName)
    {
        if (isWhiteList)
            return FromObject([obj], ObjectValueType.None, (object)new MemberWhiteList(memberName), BindingFlags.Default);
        else
            return FromObject([obj], ObjectValueType.None, (object)new MemberBlackList(memberName), BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="memberName">The member names to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, ObjectValueType membersToStore, params string[] memberName)
    {
        return FromObject(obj, membersToStore, true, memberName);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="isWhiteList">If true, the memberName-Array has member names that should be included.</param>
    /// <param name="memberName">The member names to include/exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, ObjectValueType membersToStore, bool isWhiteList, params string[] memberName)
    {
        if (isWhiteList)
            return FromObject([obj], membersToStore, (object)new MemberWhiteList(memberName), BindingFlags.Default);
        else
            return FromObject([obj], membersToStore, (object)new MemberBlackList(memberName), BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <param name="memberName">The member names to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, BindingFlags flags, params string[] memberName)
    {
        return FromObject(obj, flags, true, memberName);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <param name="isWhiteList">If true, the memberName-Array has member names that should be included.</param>
    /// <param name="memberName">The member names to include/exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, BindingFlags flags, bool isWhiteList, params string[] memberName)
    {
        if (isWhiteList)
            return FromObject([obj], ObjectValueType.None, (object)new MemberWhiteList(memberName), flags);
        else
            return FromObject([obj], ObjectValueType.None, (object)new MemberBlackList(memberName), flags);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <param name="memberName">The member names to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, ObjectValueType membersToStore, BindingFlags flags, params string[] memberName)
    {
        return FromObject(obj, flags, true, memberName);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <param name="isWhiteList">If true, the memberName-Array has member names that should be included.</param>
    /// <param name="memberName">The member names to include/exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, ObjectValueType membersToStore, BindingFlags flags, bool isWhiteList, params string[] memberName)
    {
        if (isWhiteList)
            return FromObject([obj], membersToStore, (object)new MemberWhiteList(memberName), flags);
        else
            return FromObject([obj], membersToStore, (object)new MemberBlackList(memberName), flags);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj)
    {
        return FromObject([obj], ObjectValueType.None, default(object), BindingFlags.Default);
    }
    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, ObjectValueType membersToStore)
    {
        return FromObject([obj], membersToStore, default(object), BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="whiteList">Specify which members to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, ObjectValueType membersToStore, MemberWhiteList whiteList)
    {
        return FromObject([obj], membersToStore, (object)whiteList, BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="blackList">Specify which members to exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, ObjectValueType membersToStore, MemberBlackList blackList)
    {
        return FromObject([obj], membersToStore, (object)blackList, BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, BindingFlags flags)
    {
        return FromObject([obj], ObjectValueType.None, default(object), flags);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, ObjectValueType membersToStore, BindingFlags flags)
    {
        return FromObject([obj], membersToStore, default(object), flags);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="whiteList">Specify which members to include.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, ObjectValueType membersToStore, MemberWhiteList whiteList, BindingFlags flags)
    {
        return FromObject([obj], membersToStore, (object)whiteList, flags);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="obj">The object that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="blackList">Specify which members to exclude.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object obj, ObjectValueType membersToStore, MemberBlackList blackList, BindingFlags flags)
    {
        return FromObject([obj], membersToStore, (object)blackList, flags);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="objs">The objects that should be included.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] objs)
    {
        return FromObject(objs, ObjectValueType.None, default(object), BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="objs">The objects that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] objs, ObjectValueType membersToStore)
    {
        return FromObject(objs, membersToStore, default(object), BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="objs">The objects that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="whiteList">Specify which members to include.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] objs, ObjectValueType membersToStore, MemberWhiteList whiteList)
    {
        return FromObject(objs, membersToStore, (object)whiteList, BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="objs">The objects that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="blackList">Specify which members to exclude.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] objs, ObjectValueType membersToStore, MemberBlackList blackList)
    {
        return FromObject(objs, membersToStore, (object)blackList, BindingFlags.Default);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="objs">The objects that should be included.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] objs, BindingFlags flags)
    {
        return FromObject(objs, ObjectValueType.None, default(object), flags);
    }
    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="objs">The objects that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] objs, ObjectValueType membersToStore, BindingFlags flags)
    {
        return FromObject(objs, membersToStore, default(object), flags);
    }
    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="objs">The objects that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="whiteList">Specify which members to include.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] objs, ObjectValueType membersToStore, MemberWhiteList whiteList, BindingFlags flags)
    {
        return FromObject(objs, membersToStore, (object)whiteList, flags);
    }
    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="objs">The objects that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="blackList">Specify which members to exclude.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <returns>A History Point with Object States.</returns>
    public static HistoryPoint FromObject(object[] objs, ObjectValueType membersToStore, MemberBlackList blackList, BindingFlags flags)
    {
        return FromObject(objs, membersToStore, (object)blackList, flags);
    }

    /// <summary>
    /// Creates an History Point with Object States automaticly from input.
    /// </summary>
    /// <param name="objs">The objects that should be included.</param>
    /// <param name="membersToStore">Specify what member types to include.</param>
    /// <param name="whiteOrBlackList">Specify which members to include.</param>
    /// <param name="flags">The Binding Flags that the members should have.</param>
    /// <returns>A History Point with Object States.</returns>
    private static HistoryPoint FromObject(object[] objs, ObjectValueType membersToStore, object? whiteOrBlackList, BindingFlags flags)
    {
        var hp = new HistoryPoint();

        whiteOrBlackList ??= new MemberBlackList();
        var isWhiteList = whiteOrBlackList is MemberWhiteList;

        if (flags == BindingFlags.Default)
            flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;

        if (membersToStore == ObjectValueType.None)
            membersToStore = ObjectValueType.Field | ObjectValueType.Property;

        foreach (var obj in objs)
        {
            if ((membersToStore & ObjectValueType.Field) == ObjectValueType.Field)
            {
                foreach (var fi in obj.GetType().GetFields(flags))
                {
                    var contains = ((List<string>)whiteOrBlackList).Contains(fi.Name);
                    if (isWhiteList ? contains : !contains)
                    {
                        var os = new ObjectState
                        {
                            Object = obj,
                            MemberName = fi.Name,
                            MemberType = ObjectValueType.Field,
                            MemberFlags = flags,
                            ValueToPatch = fi.GetValue(obj)
                        };
                        hp.Entries.Add(os);
                    }
                }
            }

            if ((membersToStore & ObjectValueType.Property) == ObjectValueType.Property)
            {
                foreach (var pi in obj.GetType().GetProperties(flags))
                {
                    var contains = ((List<string>)whiteOrBlackList).Contains(pi.Name);
                    if (isWhiteList ? contains : !contains)
                    {
                        var os = new ObjectState
                        {
                            Object = obj,
                            MemberName = pi.Name,
                            MemberType = ObjectValueType.Property,
                            MemberFlags = flags,
                            ValueToPatch = pi.GetValue(obj)
                        };
                        hp.Entries.Add(os);
                    }
                }
            }
        }

        return hp;
    }

    /// <summary>
    /// Combines some History Points to one.
    /// </summary>
    /// <param name="hps">An array of History Points to combine.</param>
    /// <returns>One History Point that contains all Data of inputted History Points.</returns>
    public static HistoryPoint Concat(params HistoryPoint[] hps)
    {
        return Concat(null, hps);
    }

    /// <summary>
    /// Combines some History Points to one.
    /// </summary>
    /// <param name="hps">An array of History Points to combine.</param>
    /// <param name="newName">The new name for the History Point after concating.</param>
    /// <returns>One History Point that contains all Data of inputted History Points.</returns>
    public static HistoryPoint Concat(string? newName, params HistoryPoint[] hps)
    {
        var hp = new HistoryPoint();

        foreach (var _hp in hps)
            hp.Entries.AddRange(_hp.Entries);

        if (newName != null)
            hp.Name = newName;
        else
            hp.Name = hps.FirstOrDefault()?.Name ?? string.Empty;

        return hp;
    }

}