using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Fort23.UTool;
using UnityEngine.Profiling;
using Utility;

namespace Fort23.Core
{
    using OneTypeSystems = UnOrderMultiMap<Type, object>;

    public sealed class EventSystem : IDisposable
    {
        private static EventSystem instance;

        public static EventSystem Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new EventSystem();
                }

                return instance;
            }
        }

        // private readonly Dictionary<long, TypeData> _allEntities = new Dictionary<long, TypeData>();

        private readonly Dictionary<string, Assembly> _assemblies = new Dictionary<string, Assembly>();

        private readonly UnOrderMultiMapSet<Type, Type> _mapSet = new UnOrderMultiMapSet<Type, Type>();

        private Queue<Entity> _updates = new Queue<Entity>();
        private Queue<Entity> _updates2 = new Queue<Entity>();
        private Queue<Entity> _lateUpdates = new Queue<Entity>();
        private Queue<Entity> _lateUpdates2 = new Queue<Entity>();

        private readonly Map<Entity, Map<CustomMethodType, List<MethodInfo>>> currEntity =
            new Map<Entity, Map<CustomMethodType, List<MethodInfo>>>();

        private readonly Map<Type, Map<CustomMethodType, List<MethodInfo>>> allEntityMethodName =
            new Map<Type, Map<CustomMethodType, List<MethodInfo>>>();


        private EventSystem()
        {
            // TODO 手动添加所包含的程序集
            string[] assemblyNames =
            {
                "Fort23.Core.dll","Fort23.Mono.dll", 
                "Fort23.GameData.dll"
            };
            foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                string assemblyName = $"{assembly.GetName().Name}.dll";
                if (!((IList)assemblyNames).Contains(assemblyName))
                {
                    continue;
                }

                Add(assembly);
            }
        }

        public Assembly GetAssembly(string key)
        {
            if (_assemblies.ContainsKey(key))
            {
                return _assemblies[key];
            }

            return null;
        }

        public void Add(Assembly assembly)
        {
            this._assemblies[$"{assembly.GetName().Name}.dll"] = assembly;
        }


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

            return this._mapSet[systemAttributeType];
        }

        public List<Type> GetTypes()
        {
            List<Type> allTypes = new List<Type>();
            foreach (Assembly assembly in this._assemblies.Values)
            {
                allTypes.AddRange(assembly.GetTypes());
            }

            return allTypes;
        }


        public Map<CustomMethodType, List<MethodInfo>> GetCustomMethod(Entity component)
        {
            Type type = component.GetType();
            Map<CustomMethodType, List<MethodInfo>> methodName = null;
            if (allEntityMethodName.TryGetValue(type, out methodName))
            {
                return methodName;
            }

            methodName = new Map<CustomMethodType, List<MethodInfo>>();
            var methodInfos = type.GetMethods();

            for (int i = 0; i < methodInfos.Length; i++)
            {
                CustomMethod customMethod = methodInfos[i].GetCustomAttribute<CustomMethod>();
                if (customMethod != null)
                {
                    if (!methodName.TryGetValue(customMethod.CustomMethodType,
                            out List<MethodInfo> allEntityMethodName))
                    {
                        allEntityMethodName = new List<MethodInfo>();
                        methodName.Add(customMethod.CustomMethodType, allEntityMethodName);
                    }

                    allEntityMethodName.Add(methodInfos[i]);
                }
            }

            allEntityMethodName.Add(type, methodName);
            return methodName;
        }

        public void RegisterSystem(Entity component, bool isRegister = true)
        {
            if (!isRegister)
            {
                this.Remove(component);
                return;
            }

            if (currEntity.ContainsKey(component))
            {
                return;
                // LogTool.Error("纯在相同的key" + component.InstanceID + "____" + component.GetType());
            }

            Map<CustomMethodType, List<MethodInfo>> allName = GetCustomMethod(component);

            currEntity.Add(component, allName);
            for (allName.Begin(); allName.Next();)
            {
                if (allName.Key == CustomMethodType.Update)
                {
                    _updates.Enqueue(component);
                }

                if (allName.Key == CustomMethodType.LateUpdate)
                {
                    _lateUpdates.Enqueue(component);
                }
            }
        }

        public void Remove(Entity instanceId)
        {
            this.currEntity.Remove(instanceId);
        }

        public Map<CustomMethodType, List<MethodInfo>> Get(Entity instanceId)
        {
            Map<CustomMethodType, List<MethodInfo>> component = null;
            this.currEntity.TryGetValue(instanceId, out component);
            return component;
        }

        public bool IsRegister(Entity instanceId)
        {
            return this.currEntity.ContainsKey(instanceId);
        }


        public void Awake(Entity component, object[] data)
        {
            try
            {
                Dispatch(component, CustomMethodType.Awake, data);
            }
            catch (Exception e)
            {
                LogTool.Exception(e);
            }
        }

        public void Dispatch(Entity component, CustomMethodType customMethodType, object[] data)
        {
            Map<CustomMethodType, List<MethodInfo>> typeData = Get(component);
            if (typeData == null)
            {
                return;
            }

            if (typeData.TryGetValue(customMethodType, out List<MethodInfo> w))
            {
                for (int i = 0; i < w.Count; i++)
                {
                    w[i].Invoke(component, data);
                }
            }
        }

        public void Destroy(Entity component)
        {
            try
            {
                Dispatch(component, CustomMethodType.Destroy, null);
            }
            catch (Exception e)
            {
                LogTool.Exception(e);
            }
        }

        public void Update()
        {
            while (this._updates.Count > 0)
            {
                Entity instanceId = this._updates.Dequeue();


                if (instanceId.IsDisposed)
                {
                    continue;
                }


                this._updates2.Enqueue(instanceId);
                try
                {
                    if (instanceId.IsUpdate())
                    {
                        Dispatch(instanceId, CustomMethodType.Update, null);
                    }
                }
                catch (Exception e)
                {
                    LogTool.Error(e);
                    // throw new Exception(e.Message);
                }
            }

            ObjectHelper.Swap(ref this._updates, ref this._updates2);
        }

        public void LateUpdate()
        {
            while (this._lateUpdates.Count > 0)
            {
                Entity instanceId = this._lateUpdates.Dequeue();

                if (instanceId.IsDisposed)
                {
                    continue;
                }


                this._lateUpdates2.Enqueue(instanceId);


                try
                {
                    if (instanceId.IsUpdate())
                    {
                        Dispatch(instanceId, CustomMethodType.LateUpdate, null);
                    }
                }
                catch (Exception e)
                {
                    LogTool.Exception(e);
                }
            }

            ObjectHelper.Swap(ref this._lateUpdates, ref this._lateUpdates2);
        }

        public void Dispose()
        {
            instance = null;
        }
    }
}