using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;

namespace EventBus
{

    public sealed class EventBroker : IEventBroker,IDisposable
    {
       private static readonly Lazy<EventBroker> _eventBroker = new Lazy<EventBroker>(() => new EventBroker());
        private EventBroker()
        {

        }
        string GetHash()
        {
            return DateTime.Now.ToLongTimeString();
        }
        public static EventBroker Instance => _eventBroker.Value;
        private readonly Dictionary<Type, List<IBaseEventData>> _list = new Dictionary<Type, List<IBaseEventData>>();
        private bool disposedValue;
        public IEventData<TData, T> GetEvent<TData, T>() 
        {
            lock (_list)
            {
                if (_list.TryGetValue(typeof(EventData<TData, T>), out List<IBaseEventData>? baseevent))
                {
                    baseevent ??= new List<IBaseEventData>();
                    if (baseevent.Count == 0) baseevent.Add(new EventData<TData, T>(GetHash()));
                    return (IEventData<TData, T>)baseevent.First();
                }
                else
                {
                    EventData<TData, T> eventData = new EventData<TData, T>(GetHash());
                    _list.Add(typeof(EventData<TData, T>), new List<IBaseEventData>() { eventData });
                    return eventData;
                }
            }
        }
        public IAnonymousEventData GetEvent(string eventName)
        {
            lock (_list)
            {
                ArgumentNullException.ThrowIfNullOrEmpty(eventName);
                if (_list.TryGetValue(typeof(IAnonymousEventData), out List<IBaseEventData>? baseevent))
                {
                    baseevent ??= new List<IBaseEventData>();
                    if (!baseevent.Where(x => x is AnonymousEventData && x.EventName.Contains(eventName)).Any()) baseevent.Add(new AnonymousEventData(eventName, GetHash()));
                    return (IAnonymousEventData)baseevent.Where(x => x is AnonymousEventData && (x as AnonymousEventData)!.EventName.Contains(eventName)).First();
                }
                else
                {
                    AnonymousEventData eventData = new AnonymousEventData(eventName, GetHash());
                    _list.Add(typeof(IAnonymousEventData), new List<IBaseEventData>() { eventData });
                    return eventData;
                }
            }
        }

        public IAnonymousEventData<T> GetEvent<T>(string eventName)
        {
            lock (_list)
            {
                ArgumentNullException.ThrowIfNullOrEmpty(eventName);
                if (_list.TryGetValue(typeof(IAnonymousEventData), out List<IBaseEventData>? baseevent))
                {
                    baseevent ??= new List<IBaseEventData>();
                    if (!baseevent.Where(x => x is AnonymousEventData<T> && x.EventName.Contains(eventName)).Any()) baseevent.Add(new AnonymousEventData<T>(eventName, GetHash()));
                    return (IAnonymousEventData<T>)baseevent.Where(x => x is AnonymousEventData<T> && (x as AnonymousEventData<T>)!.EventName.Contains(eventName)).First();
                }
                else
                {
                    AnonymousEventData<T> eventData = new AnonymousEventData<T>(eventName, GetHash());
                    _list.Add(typeof(IAnonymousEventData), [eventData]);
                    return eventData;
                }
            }
        }

        public IEventData<TData> GetEvent<TData>()
        {
            lock (_list)
            {
                if (_list.TryGetValue(typeof(EventData<TData>), out List<IBaseEventData>? baseevent))
                {
                    baseevent ??= new List<IBaseEventData>();
                    if (baseevent.Count == 0) baseevent.Add(new EventData<TData>(GetHash()));
                    return (IEventData<TData>)baseevent.First();
                }
                else
                {
                    EventData<TData> eventData = new EventData<TData>(GetHash());
                    _list.Add(typeof(EventData<TData>), new List<IBaseEventData>() { eventData });
                    return eventData;
                }
            }
        }


        internal abstract class BaseValue
        {
            public BaseValue(Properties? properties = null)
            {
                this.Properties = properties ?? Properties.Default;
                this.IntPtr = Guid.NewGuid();
            }
            public DateTime DateTime => DateTime.Now;
            private Properties properties = Properties.Default;
            public Properties Properties { get => properties ?? Properties.Default; private set => properties = value; }
            public abstract bool HasDelegate { get; }
            public Guid IntPtr { get; internal set; }

        }
        internal class ActionValue<TData> :BaseValue
        {
            public ActionValue(Action<object, EventArgs<TData>> action, Properties? properties = null):base(properties)
            {
                this.Action = action;
            }
            internal Action<object, EventArgs<TData>> Action { get; set; }

            public override bool HasDelegate => Action != null;


            public bool Invoke(object sender,TData args,IBaseEventData eventData)
            {
                if (Action == null) return false;
                var eventargs = new EventArgs<TData>(args, Instance, IntPtr, eventData);
                Action.Invoke(sender, eventargs);
                return eventargs.Handle;
            }
        }
        internal abstract class BaseFuncValue<TData,TResult>:BaseValue
        {
            public BaseFuncValue(Properties? properties = null):base(properties)
            {

            }
            public abstract TResult Invoke(object sender, TData data, IBaseEventData eventData);
            public abstract List<TResult> InvokeList(object sender, TData data, IBaseEventData eventData);
            public abstract bool IsListFunc { get; }
        }
        internal class FuncValue<TData,TRestlt> : BaseFuncValue<TData,TRestlt>
        {
            public FuncValue(Func<object, EventArgs<TData>, TRestlt> func, Properties? properties = null):base(properties)
            {
                Func = func;
            }
            internal Func<object, EventArgs<TData>, TRestlt> Func { get; private set; }
            public override bool IsListFunc => false;

            public override bool HasDelegate => Func!=null;

            public override TRestlt Invoke(object sender, TData data, IBaseEventData eventData)
            {
                ArgumentNullException.ThrowIfNull(Func);
                return Func.Invoke(sender, new EventArgs<TData>(data, Instance, IntPtr, eventData, typeof(TRestlt)));
            }

            public override List<TRestlt> InvokeList(object sender, TData data, IBaseEventData eventData)
            {
                throw new NotImplementedException();
            }
        }
        internal class ListFuncValue<TData, TRestlt> : BaseFuncValue<TData,TRestlt>
        {
            public ListFuncValue(Func<object, EventArgs<TData>, List<TRestlt>> func, Properties? properties = null) : base( properties)
            {
                Func = func;
            }

            public Func<object, EventArgs<TData>, List<TRestlt>> Func { get; private set; }
            public override bool IsListFunc => true;

            public override bool HasDelegate => Func != null;

            public override TRestlt Invoke(object sender, TData data, IBaseEventData eventData)
            {
                throw new NotImplementedException();
            }

            public override List<TRestlt> InvokeList(object sender, TData data, IBaseEventData eventData)
            {
                ArgumentNullException.ThrowIfNull(Func);
                return Func.Invoke(sender, new EventArgs<TData>(data, Instance, IntPtr, eventData, typeof(TRestlt)));
            }
        }
        public sealed class EventData<TData> :  IEventData<TData> 
        {
            public string EventName { get; }
            public string Hash { get; }
            internal EventData(string eventName,string hash)
            {

                EventName = eventName;
                Hash = hash;
            }
            internal EventData(string hash) : this(hash, hash)
            {

            }
            ConcurrentDictionary<Guid, ActionValue<TData>> _list = new ConcurrentDictionary<Guid, ActionValue<TData>>();

            public void Publish(object sender, TData data, Properties? properties = null)
            {
                try
                {
                    ArgumentNullException.ThrowIfNull(data);
                    properties ??= Properties.Default;
                    if (_list.IsEmpty) return;
                    var templist = _list.Where(x => x.Value.Properties.FilterRule(properties) && x.Value.HasDelegate).Select(x => x.Value).ToList();
                    if (templist.Count == 0) return;
                    foreach (var val in templist)
                    {
                        if (val.Invoke(sender, data, this)) return;
                    }
                }
                catch
                {

                }
            }
            public void UnSubscrip(Guid guid)
            {
                lock (_list)
                {
                    if (guid == Guid.Empty) return;
                    _list.TryRemove(guid,out _);
                }
            }

            public Guid Subscrip(Action<object, EventArgs<TData>> action, Properties? properties = null)
            {
                lock (_list)
                {
                    ArgumentNullException.ThrowIfNull(action);
                    properties ??= Properties.Default;
                    var temp = new ActionValue<TData>(action, properties);
                    _list[temp.IntPtr] = temp;
                    return temp.IntPtr;
                }

            }
            public void Clear()
            {
                lock (_list)
                {
                    _list.Clear();
                }
            }

            public async Task PublishAsync(object sender, TData data, Properties? properties = null)
            {
                try
                {
                    ArgumentNullException.ThrowIfNull(data);
                    properties ??= Properties.Default;
                    if (_list.IsEmpty) return;
                    var temp = _list.Where(x => x.Value.Properties.FilterRule(properties) && x.Value.HasDelegate).Select(x => x.Value).ToList();
                    if (temp.Count == 0) return;
                    foreach (var val in temp)
                    {
                        if (await Task.Run(() => val.Invoke(sender, data, this))) return;
                    }
                }
                catch
                {

                }
            }
        }
        public sealed class EventData<TData, T> :  IEventData<TData, T>
        {
            public string EventName { get; }
            public string Hash { get; }
            private readonly ConcurrentDictionary<Guid,BaseFuncValue<TData,T>> _list = new ConcurrentDictionary<Guid,BaseFuncValue<TData, T>>();
            internal EventData(string eventName, string hash)
            {
                EventName = eventName;
                Hash = hash;
            }
            internal EventData(string hash) : this(hash, hash)
            {
            }
            public T Publish(object sender, TData data,  Properties? properties = null)
            {
                ArgumentNullException.ThrowIfNull(data);
                if (_list.IsEmpty) return Activator.CreateInstance<T>();
                properties ??= Properties.Default;
                var func = _list.Where(x=>x.Value is FuncValue<TData,T> && x.Value.HasDelegate).Where(x => x.Value.Properties.FilterRule(properties)).Select(x=>x.Value).ToList();
                if (func.Count == 0) return Activator.CreateInstance<T>();
                return func.First().Invoke(sender, data, this);
            }
            public List<T> PublishList(object sender, TData data, Properties? properties = null)
            {
                ArgumentNullException.ThrowIfNull(data);
                if (_list.IsEmpty) return new List<T>();
                properties ??= Properties.Default;
                var func = _list.Where(x => x.Value is ListFuncValue<TData, T> && x.Value.HasDelegate).Where(x => x.Value.Properties.FilterRule(properties)).Select(x => x.Value).ToList();
                if (func.Count == 0) return new List<T>();
                return func.First().InvokeList(sender, data, this);
            }
            public Guid Subscrip(Func<object, EventArgs<TData>, T> func,  Properties? properties = null)
            {
                ArgumentNullException.ThrowIfNull(func);
                properties ??= Properties.Default;
                var temp = new FuncValue<TData, T>(func, properties);
                _list[temp.IntPtr] = temp;
                return temp.IntPtr;
            }

            public Guid SubscripList( Func<object, EventArgs<TData>, List<T>> func,   Properties? properties = null)
            {
                ArgumentNullException.ThrowIfNull(func);
                properties ??= Properties.Default;
                var temp = new ListFuncValue<TData, T>(func, properties);
                _list[temp.IntPtr] = temp;
                return temp.IntPtr;
            }

            public void UnSubscrip(Guid intPtr)
            {
                if (intPtr == Guid.Empty) return;
                _list.TryRemove(intPtr,out var _);
            }

            public void Clear()
            {
                _list.Clear();
            }

            public async Task<T> PublishAsync(object sender, TData data, Properties? properties = null)
            {
                ArgumentNullException.ThrowIfNull(data);
                if (_list.IsEmpty) return Activator.CreateInstance<T>();
                properties ??= Properties.Default;
                var func = _list.Where(x => x.Value is FuncValue<TData, T> && x.Value.HasDelegate).Where(x => x.Value.Properties.FilterRule(properties)).Select(x => x.Value).ToList();
                if (func.Count == 0) return Activator.CreateInstance<T>();
                return await Task.Run(()=> func.First().Invoke(sender, data, this));
            }

            public async Task<List<T>> PublishListAsync(object sender, TData data, Properties? properties = null)
            {
                ArgumentNullException.ThrowIfNull(data);
                if (_list.IsEmpty) return new List<T>();
                properties ??= Properties.Default;
                var func = _list.Where(x => x.Value is ListFuncValue<TData, T> && x.Value.HasDelegate).Where(x => x.Value.Properties.FilterRule(properties)).Select(x => x.Value).ToList();
                if (func.Count == 0) return new List<T>();
                return await Task.Run(() => func.First().InvokeList(sender, data, this));
            }
        }


        public sealed class AnonymousEventData :  IAnonymousEventData
        {
            public string EventName { get; }
            public string Hash { get; }
            internal AnonymousEventData(string eventName, string hash)
            {

                EventName = eventName;
                Hash = hash;
            }
            internal AnonymousEventData(string hash) : this(hash, hash)
            {
            }
            ConcurrentDictionary<Guid, ActionValue<object[]>> _list = new ConcurrentDictionary<Guid, ActionValue<object[]>>();
            public void Publish(object sender, Properties? properties, params object[] data)
            {
                
                properties ??= Properties.Default;
                if (_list.IsEmpty) return;
                var templist = _list.Where(x => x.Value.Properties.FilterRule(properties) && x.Value.HasDelegate).ToList();
                if (templist.Count == 0) return;
                foreach (var val in templist)
                {
                    var args = new EventArgs<object[]>(data, Instance, val.Value.IntPtr,this);
                    val.Value.Action.Invoke(sender, args);
                    if (args.Handle) return;
                }
            }


            public void UnSubscrip(Guid guid)
            {
                if (guid == Guid.Empty) return;
                _list.TryRemove(guid, out _);
            }

            public Guid Subscrip(Action<object, EventArgs<object[]>> action,  Properties? properties=null)
            {
                ArgumentNullException.ThrowIfNull(action);
                properties ??= Properties.Default;
                var act = new ActionValue<object[]>(action, properties);
                _list[act.IntPtr] = act;
                return act.IntPtr;
            }
            public void Clear() => _list.Clear();

            public async Task PublishAsync(object sender, Properties? properties = null, params object[] data)
            {
                properties ??= Properties.Default;
                if (_list.IsEmpty) return;
                var templist = _list.Where(x => x.Value.Properties.FilterRule(properties) && x.Value.HasDelegate).Select(x => x.Value).ToList();
                foreach (var val in templist)
                {
                    var args = new EventArgs<object[]>(data, Instance, val.IntPtr, this);
                    await System.Threading.Tasks.Task.Run(() => val.Action.Invoke(sender, args));
                    if (args.Handle) return;
                }
            }
        }
        public sealed class AnonymousEventData<T> :  IAnonymousEventData<T>
        {
            public string EventName { get; }
            public string Hash { get; }

            ConcurrentDictionary<Guid,BaseFuncValue<object[], T>> _list = new ConcurrentDictionary<Guid, BaseFuncValue<object[], T>>();
            internal AnonymousEventData(string eventName, string hash)
            {
                EventName = eventName;
                Hash = hash;
            }
            internal AnonymousEventData(string hash) : this(hash, hash)
            {

            }
            public T Publish(object sender, Properties? properties, params object[] data)
            {
                if (_list.IsEmpty) return Activator.CreateInstance<T>();
                properties ??= Properties.Default;
                var func = _list.Where(x => x.Value is FuncValue<object[], T> && x.Value.HasDelegate).Where(x => x.Value.Properties.FilterRule(properties)).Select(x=>x.Value).ToList();
                if (func.Count == 0) return Activator.CreateInstance<T>();
                return func.First().Invoke(sender, data, this);
            }
            public List<T> PublishList(object sender, Properties? properties, params object[] data)
            {
                if (_list.IsEmpty) return new List<T>();
                properties ??= Properties.Default;
                var func = _list.Where(x => x.Value is ListFuncValue<object[], T> && x.Value.HasDelegate).Where(x => x.Value.Properties.FilterRule(properties)).Select(x => x.Value).ToList();
                if (func.Count == 0) return new List<T>();
                return func.First().InvokeList(sender, data, this);
            }
            public Guid Subscrip(Func<object, EventArgs<object[]>, T> func, Properties? properties=null)
            {
                ArgumentNullException.ThrowIfNull(func);
                properties ??= Properties.Default;
                var tempfunc = new FuncValue<object[], T>(func, properties);
                _list[tempfunc.IntPtr] = tempfunc;
                return tempfunc.IntPtr;
            }

            public Guid SubscripList(Func<object, EventArgs<object[]>, List<T>> func, Properties? properties=null)
            {
                ArgumentNullException.ThrowIfNull(func);
                properties ??= Properties.Default;
                var tempfunc = new ListFuncValue<object[], T>(func, properties);
                _list[tempfunc.IntPtr] = tempfunc;
                return tempfunc.IntPtr;
            }

            public void UnSubscrip(Guid guid)
            {
                if (guid == Guid.Empty) return;
                _list.TryRemove(guid, out _);
            }

            public void Clear()
            {
                _list.Clear();
            }

            public async Task<T> PublishAsync(object sender, Properties? properties = null, params object[] data)
            {
                if (_list.IsEmpty) return Activator.CreateInstance<T>();
                properties ??= Properties.Default;
                var func = _list.Where(x => x.Value is FuncValue<object[], T> && x.Value.HasDelegate).Where(x => x.Value.Properties.FilterRule(properties)).Select(x=>x.Value).ToList();
                if (func.Count == 0) return Activator.CreateInstance<T>();
                return await Task.Run(()=> func.First().Invoke(sender, data, this));
            }

            public async Task<List<T>> PublishListAsync(object sender, Properties? properties = null, params object[] data)
            {
                if (_list.IsEmpty) return new List<T>();
                properties ??= Properties.Default;
                var func = _list.Where(x => x.Value is ListFuncValue<object[], T> && x.Value.HasDelegate).Where(x => x.Value.Properties.FilterRule(properties)).Select(x => x.Value).ToList();
                if (func.Count == 0) return new List<T>();
                return await Task.Run(()=> func.First().InvokeList(sender, data, this));
            }
        }

        private void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    _list.Values.ToList().ForEach(x => x?.ToList().ForEach(y=>y.Clear()));
                    _list.Clear();
                }

                // TODO: 释放未托管的资源(未托管的对象)并替代终结器
                // TODO: 将大型字段设置为 null
                disposedValue = true;
            }
        }

        // // TODO: 仅当“Dispose(bool disposing)”拥有用于释放未托管资源的代码时才替代终结器
        // ~EventBroker()
        // {
        //     // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
        //     Dispose(disposing: false);
        // }

        public void Dispose()
        {
            // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }
    }
}