using System.Reflection;
using Cysharp.Threading.Tasks;

namespace KYFramework
{
    public sealed class EventSystem
    {
        private readonly Dictionary<long, Component> allComponents = new Dictionary<long, Component>();
        private readonly UnOrderMultiMap<Type, Type> types = new UnOrderMultiMap<Type, Type>();
        private readonly Dictionary<Type, List<EventInfo>> allEvents = new Dictionary<Type, List<EventInfo>>();
        private Dictionary<Type, Dictionary<int, object>> allInvokes = new();

        private readonly UnOrderMultiMap<Type, IAwakeSystem> awakeSystems = new UnOrderMultiMap<Type, IAwakeSystem>();

        private readonly UnOrderMultiMap<Type, IStartSystem> startSystems = new UnOrderMultiMap<Type, IStartSystem>();

        private readonly UnOrderMultiMap<Type, IDestroySystem> destroySystems =
            new UnOrderMultiMap<Type, IDestroySystem>();

        private readonly UnOrderMultiMap<Type, ILoadSystem> loadSystems = new UnOrderMultiMap<Type, ILoadSystem>();

        private readonly UnOrderMultiMap<Type, IUpdateSystem>
            updateSystems = new UnOrderMultiMap<Type, IUpdateSystem>();

        private readonly UnOrderMultiMap<Type, ILateUpdateSystem> lateUpdateSystems =
            new UnOrderMultiMap<Type, ILateUpdateSystem>();

        private readonly UnOrderMultiMap<Type, IChangeSystem>
            changeSystems = new UnOrderMultiMap<Type, IChangeSystem>();

        private readonly UnOrderMultiMap<Type, IDeserializeSystem> deserializeSystems =
            new UnOrderMultiMap<Type, IDeserializeSystem>();

        private Queue<long> updates = new Queue<long>();
        private Queue<long> updates2 = new Queue<long>();

        private readonly Queue<long> starts = new Queue<long>();

        private Queue<long> loaders = new Queue<long>();
        private Queue<long> loaders2 = new Queue<long>();

        private Queue<long> lateUpdates = new Queue<long>();
        private Queue<long> lateUpdates2 = new Queue<long>();

        public List<Assembly> assemblies = new List<Assembly>();

        public void Add(Assembly assembly)
        {
            this.assemblies.Add(assembly);
            this.types.Clear();
            foreach (Assembly assembly1 in assemblies)
            {
                foreach (Type type in assembly1.GetTypes())
                {
                    object[] objects = type.GetCustomAttributes(typeof(BaseAttribute), false);
                    if (objects.Length == 0) continue;
                    BaseAttribute baseAttribute = (BaseAttribute)objects[0];
                    this.types.Add(baseAttribute.AttributeType, type);
                }
            }

            this.awakeSystems.Clear();
            this.lateUpdateSystems.Clear();
            this.updateSystems.Clear();
            this.startSystems.Clear();
            this.loadSystems.Clear();
            this.changeSystems.Clear();
            this.destroySystems.Clear();
            this.deserializeSystems.Clear();

            if (types.ContainsKey(typeof(ObjectSystemAttribute)))
            {
                foreach (Type type in types[typeof(ObjectSystemAttribute)])
                {
                    object[] attrs = type.GetCustomAttributes(typeof(ObjectSystemAttribute), false);

                    if (attrs.Length == 0)
                    {
                        continue;
                    }

                    object obj = Activator.CreateInstance(type);
                    switch (obj)
                    {
                        case IAwakeSystem objectSystem:
                            this.awakeSystems.Add(objectSystem.Type(), objectSystem);
                            break;
                        case IUpdateSystem updateSystem:
                            this.updateSystems.Add(updateSystem.Type(), updateSystem);
                            break;
                        case ILateUpdateSystem lateUpdateSystem:
                            this.lateUpdateSystems.Add(lateUpdateSystem.Type(), lateUpdateSystem);
                            break;
                        case IStartSystem startSystem:
                            this.startSystems.Add(startSystem.Type(), startSystem);
                            break;
                        case IDestroySystem destroySystem:
                            this.destroySystems.Add(destroySystem.Type(), destroySystem);
                            break;
                        case ILoadSystem loadSystem:
                            this.loadSystems.Add(loadSystem.Type(), loadSystem);
                            break;
                        case IChangeSystem changeSystem:
                            this.changeSystems.Add(changeSystem.Type(), changeSystem);
                            break;
                        case IDeserializeSystem deserializeSystem:
                            this.deserializeSystems.Add(deserializeSystem.Type(), deserializeSystem);
                            break;
                    }
                }
            }

            this.allEvents.Clear();
            if (types.ContainsKey(typeof(EventAttribute)))
            {
                foreach (Type type in types[typeof(EventAttribute)])
                {
                    IEvent obj = Activator.CreateInstance(type) as IEvent;
                    if (obj == null)
                    {
                        throw new Exception($"type not is AEvent: {type.Name}");
                    }

                    object[] attrs = type.GetCustomAttributes(typeof(EventAttribute), false);
                    foreach (object attr in attrs)
                    {
                        EventAttribute eventAttribute = attr as EventAttribute;

                        Type eventType = obj.GetEventType();

                        EventInfo eventInfo = new(obj);

                        if (!this.allEvents.ContainsKey(eventType))
                        {
                            this.allEvents.Add(eventType, new List<EventInfo>());
                        }

                        this.allEvents[eventType].Add(eventInfo);
                    }
                }
            }

            this.allInvokes = new Dictionary<Type, Dictionary<int, object>>();
            if (types.ContainsKey(typeof(InvokeAttribute)))
            {
                foreach (Type type in types[typeof(InvokeAttribute)])
                {
                    object obj = Activator.CreateInstance(type);
                    IInvoke iInvoke = obj as IInvoke;
                    if (iInvoke == null)
                    {
                        throw new Exception($"type not is callback: {type.Name}");
                    }

                    object[] attrs = type.GetCustomAttributes(typeof(InvokeAttribute), false);
                    foreach (object attr in attrs)
                    {
                        if (!this.allInvokes.TryGetValue(iInvoke.Type, out var dict))
                        {
                            dict = new Dictionary<int, object>();
                            this.allInvokes.Add(iInvoke.Type, dict);
                        }

                        InvokeAttribute invokeAttribute = attr as InvokeAttribute;

                        try
                        {
                            dict.Add(invokeAttribute.Type, obj);
                        }
                        catch (Exception e)
                        {
                            throw new Exception($"action type duplicate: {iInvoke.Type.Name} {invokeAttribute.Type}",
                                e);
                        }
                    }
                }
            }


            this.Load();
        }

        private class EventInfo
        {
            public IEvent IEvent { get; }


            public EventInfo(IEvent iEvent)
            {
                this.IEvent = iEvent;
            }
        }


        public List<Type> GetTypes(Type systemAttributeType)
        {
            if (!this.types.ContainsKey(systemAttributeType))
            {
                return new List<Type>();
            }

            return this.types[systemAttributeType];
        }

        public void Add(Component component)
        {
            this.allComponents.Add(component.InstanceId, component);

            Type type = component.GetType();

            if (this.loadSystems.ContainsKey(type))
            {
                this.loaders.Enqueue(component.InstanceId);
            }

            if (this.updateSystems.ContainsKey(type))
            {
                this.updates.Enqueue(component.InstanceId);
            }

            if (this.startSystems.ContainsKey(type))
            {
                this.starts.Enqueue(component.InstanceId);
            }

            if (this.lateUpdateSystems.ContainsKey(type))
            {
                this.lateUpdates.Enqueue(component.InstanceId);
            }
        }

        public void Remove(long instanceId)
        {
            this.allComponents.Remove(instanceId);
        }

        public Component Get(long instanceId)
        {
            Component component = null;
            this.allComponents.TryGetValue(instanceId, out component);
            return component;
        }

        public void Deserialize(Component component)
        {
            List<IDeserializeSystem> iDeserializeSystems = this.deserializeSystems[component.GetType()];
            if (iDeserializeSystems == null)
            {
                return;
            }

            foreach (IDeserializeSystem deserializeSystem in iDeserializeSystems)
            {
                if (deserializeSystem == null)
                {
                    continue;
                }

                try
                {
                    deserializeSystem.Run(component);
                }
                catch (Exception e)
                {
                    Log.Error(e);
                }
            }
        }

        public void Awake(Component component)
        {
            List<IAwakeSystem> iAwakeSystems = this.awakeSystems[component.GetType()];
            if (iAwakeSystems == null)
            {
                return;
            }

            foreach (IAwakeSystem aAwakeSystem in iAwakeSystems)
            {
                if (aAwakeSystem == null)
                {
                    continue;
                }

                IAwake iAwake = aAwakeSystem as IAwake;
                if (iAwake == null)
                {
                    continue;
                }

                try
                {
                    iAwake.Run(component);
                }
                catch (Exception e)
                {
                    Log.Error(e);
                }
            }
        }

        public void Awake<P1>(Component component, P1 p1)
        {
            List<IAwakeSystem> iAwakeSystems = this.awakeSystems[component.GetType()];
            if (iAwakeSystems == null)
            {
                return;
            }

            foreach (IAwakeSystem aAwakeSystem in iAwakeSystems)
            {
                if (aAwakeSystem == null)
                {
                    continue;
                }

                IAwake<P1> iAwake = aAwakeSystem as IAwake<P1>;
                if (iAwake == null)
                {
                    continue;
                }

                try
                {
                    iAwake.Run(component, p1);
                }
                catch (Exception e)
                {
                    Log.Error(e);
                }
            }
        }

        public void Awake<P1, P2>(Component component, P1 p1, P2 p2)
        {
            List<IAwakeSystem> iAwakeSystems = this.awakeSystems[component.GetType()];
            if (iAwakeSystems == null)
            {
                return;
            }

            foreach (IAwakeSystem aAwakeSystem in iAwakeSystems)
            {
                if (aAwakeSystem == null)
                {
                    continue;
                }

                IAwake<P1, P2> iAwake = aAwakeSystem as IAwake<P1, P2>;
                if (iAwake == null)
                {
                    continue;
                }

                try
                {
                    iAwake.Run(component, p1, p2);
                }
                catch (Exception e)
                {
                    Log.Error(e);
                }
            }
        }

        public void Awake<P1, P2, P3>(Component component, P1 p1, P2 p2, P3 p3)
        {
            List<IAwakeSystem> iAwakeSystems = this.awakeSystems[component.GetType()];
            if (iAwakeSystems == null)
            {
                return;
            }

            foreach (IAwakeSystem aAwakeSystem in iAwakeSystems)
            {
                if (aAwakeSystem == null)
                {
                    continue;
                }

                IAwake<P1, P2, P3> iAwake = aAwakeSystem as IAwake<P1, P2, P3>;
                if (iAwake == null)
                {
                    continue;
                }

                try
                {
                    iAwake.Run(component, p1, p2, p3);
                }
                catch (Exception e)
                {
                    Log.Error(e);
                }
            }
        }

        public void Change(Component component)
        {
            List<IChangeSystem> iChangeSystems = this.changeSystems[component.GetType()];
            if (iChangeSystems == null)
            {
                return;
            }

            foreach (IChangeSystem iChangeSystem in iChangeSystems)
            {
                if (iChangeSystem == null)
                {
                    continue;
                }

                try
                {
                    iChangeSystem.Run(component);
                }
                catch (Exception e)
                {
                    Log.Error(e);
                }
            }
        }

        public void Load()
        {
            while (this.loaders.Count > 0)
            {
                long instanceId = this.loaders.Dequeue();
                Component component;
                if (!this.allComponents.TryGetValue(instanceId, out component))
                {
                    continue;
                }

                if (component.IsDisposed)
                {
                    continue;
                }

                List<ILoadSystem> iLoadSystems = this.loadSystems[component.GetType()];
                if (iLoadSystems == null)
                {
                    continue;
                }

                this.loaders2.Enqueue(instanceId);

                foreach (ILoadSystem iLoadSystem in iLoadSystems)
                {
                    try
                    {
                        iLoadSystem.Run(component);
                    }
                    catch (Exception e)
                    {
                        Log.Error(e);
                    }
                }
            }

            ObjectHelper.Swap(ref this.loaders, ref this.loaders2);
        }

        private void Start()
        {
            while (this.starts.Count > 0)
            {
                long instanceId = this.starts.Dequeue();
                Component component;
                if (!this.allComponents.TryGetValue(instanceId, out component))
                {
                    continue;
                }

                List<IStartSystem> iStartSystems = this.startSystems[component.GetType()];
                if (iStartSystems == null)
                {
                    continue;
                }

                foreach (IStartSystem iStartSystem in iStartSystems)
                {
                    try
                    {
                        iStartSystem.Run(component);
                    }
                    catch (Exception e)
                    {
                        Log.Error(e);
                    }
                }
            }
        }

        public void Destroy(Component component)
        {
            List<IDestroySystem> iDestroySystems = this.destroySystems[component.GetType()];
            if (iDestroySystems == null)
            {
                return;
            }

            foreach (IDestroySystem iDestroySystem in iDestroySystems)
            {
                if (iDestroySystem == null)
                {
                    continue;
                }

                try
                {
                    iDestroySystem.Run(component);
                }
                catch (Exception e)
                {
                    Log.Error(e);
                }
            }
        }

        public void Update()
        {
            this.Start();

            while (this.updates.Count > 0)
            {
                long instanceId = this.updates.Dequeue();
                Component component;
                if (!this.allComponents.TryGetValue(instanceId, out component))
                {
                    continue;
                }

                if (component.IsDisposed)
                {
                    continue;
                }

                List<IUpdateSystem> iUpdateSystems = this.updateSystems[component.GetType()];
                if (iUpdateSystems == null)
                {
                    continue;
                }

                this.updates2.Enqueue(instanceId);

                foreach (IUpdateSystem iUpdateSystem in iUpdateSystems)
                {
                    try
                    {
                        iUpdateSystem.Run(component);
                    }
                    catch (Exception e)
                    {
                        Log.Error(e);
                    }
                }
            }

            ObjectHelper.Swap(ref this.updates, ref this.updates2);
        }

        public void LateUpdate()
        {
            while (this.lateUpdates.Count > 0)
            {
                long instanceId = this.lateUpdates.Dequeue();
                Component component;
                if (!this.allComponents.TryGetValue(instanceId, out component))
                {
                    continue;
                }

                if (component.IsDisposed)
                {
                    continue;
                }

                List<ILateUpdateSystem> iLateUpdateSystems = this.lateUpdateSystems[component.GetType()];
                if (iLateUpdateSystems == null)
                {
                    continue;
                }

                this.lateUpdates2.Enqueue(instanceId);

                foreach (ILateUpdateSystem iLateUpdateSystem in iLateUpdateSystems)
                {
                    try
                    {
                        iLateUpdateSystem.Run(component);
                    }
                    catch (Exception e)
                    {
                        Log.Error(e);
                    }
                }
            }

            ObjectHelper.Swap(ref this.lateUpdates, ref this.lateUpdates2);
        }

        public void Publish<T>(T a) where T : struct
        {
            List<EventInfo> iEvents;
            if (!this.allEvents.TryGetValue(typeof(T), out iEvents))
            {
                return;
            }

            foreach (EventInfo eventInfo in iEvents)
            {
                if (!(eventInfo.IEvent is AEvent<T> aEvent))
                {
                    Log.Error($"event error: {eventInfo.IEvent.GetType().Name}");
                    continue;
                }

                aEvent.Handle(a).Forget();
            }
        }

        // Invoke跟Publish的区别(特别注意)
        // Invoke类似函数,必须有被调用方,否则异常,调用者跟被调用者属于同一模块,比如MoveComponent中的Timer计时器,调用跟被调用的代码均属于移动模块
        // 既然Invoke跟函数一样,那么为什么不使用函数呢? 因为有时候不方便直接调用,比如Config加载,在客户端跟服务端加载方式不一样。比如TimerComponent需要根据Id分发
        // 注意,不要把Invoke当函数使用,这样会造成代码可读性降低,能用函数不要用Invoke
        // publish是事件,抛出去可以没人订阅,调用者跟被调用者属于两个模块,比如任务系统需要知道道具使用的信息,则订阅道具使用事件
        public void Invoke<A>(int type, A args) where A : struct
        {
            if (!this.allInvokes.TryGetValue(typeof(A), out var invokeHandlers))
            {
                throw new Exception($"Invoke error: {typeof(A).Name}");
            }

            if (!invokeHandlers.TryGetValue(type, out var invokeHandler))
            {
                throw new Exception($"Invoke error: {typeof(A).Name} {type}");
            }

            var aInvokeHandler = invokeHandler as AInvokeHandler<A>;
            if (aInvokeHandler == null)
            {
                throw new Exception($"Invoke error, not AInvokeHandler: {typeof(A).Name} {type}");
            }

            aInvokeHandler.Handle(args);
        }

        public T Invoke<A, T>(int type, A args) where A : struct
        {
            if (!this.allInvokes.TryGetValue(typeof(A), out var invokeHandlers))
            {
                throw new Exception($"Invoke error: {typeof(A).Name}");
            }

            if (!invokeHandlers.TryGetValue(type, out var invokeHandler))
            {
                throw new Exception($"Invoke error: {typeof(A).Name} {type}");
            }

            var aInvokeHandler = invokeHandler as AInvokeHandler<A, T>;
            if (aInvokeHandler == null)
            {
                throw new Exception($"Invoke error, not AInvokeHandler: {typeof(T).Name} {type}");
            }

            return aInvokeHandler.Handle(args);
        }

        public void Invoke<A>(A args) where A : struct
        {
            Invoke(0, args);
        }

        public T Invoke<A, T>(A args) where A : struct
        {
            return Invoke<A, T>(0, args);
        }
    }
}