using System;
using UnityEngine;
using System.Collections.Generic;

namespace BingoBrain.Core
{
    public abstract class BaseDispatcher<T, Msg, Param> : IDisposable where T : class, new() where Param : class
    {
        private static T m_instance;

        public static T Instance
        {
            get
            {
                if (m_instance == null)
                {
                    m_instance = new T();
                }

                return m_instance;
            }
        }

        private Dictionary<Msg, List<Action<Param>>> m_msgPriorityDict = new();
        private Dictionary<Msg, List<Action<Param>>> m_msgDict = new();
        private Dictionary<Msg, List<Action<Param>>> m_msgFinallyDict = new();
        private Dictionary<Msg, List<Action<Param>>> m_msgOnceDict = new();

        public void AddPriorityListener(Msg msgId, Action<Param> listener)
        {
            if (m_msgPriorityDict.TryGetValue(msgId, out var value))
            {
                value.Add(listener);
            }
            else
            {
                var list = Uncc<Action<Param>>.Get();
                list.Add(listener);
                m_msgPriorityDict.Add(msgId, list);
            }
        }

        public void AddListener(Msg msgId, Action<Param> listener)
        {
            if (m_msgDict.TryGetValue(msgId, out var value))
            {
                value.Add(listener);
            }
            else
            {
                var list = Uncc<Action<Param>>.Get();
                list.Add(listener);
                m_msgDict.Add(msgId, list);
            }
        }

        public void AddFinallyListener(Msg msgId, Action<Param> listener)
        {
            if (m_msgFinallyDict.TryGetValue(msgId, out var value))
            {
                value.Add(listener);
            }
            else
            {
                var list = Uncc<Action<Param>>.Get();
                list.Add(listener);
                m_msgFinallyDict.Add(msgId, list);
            }
        }

        public void AddOnceListener(Msg msgId, Action<Param> listener)
        {
            if (m_msgOnceDict.TryGetValue(msgId, out var value))
            {
                value.Add(listener);
            }
            else
            {
                var list = Uncc<Action<Param>>.Get();
                list.Add(listener);
                m_msgOnceDict.Add(msgId, list);
            }
        }

        private bool ContainsListener(Msg msgId, Action<Param> listener, Dictionary<Msg, List<Action<Param>>> msgDict)
        {
            return msgDict.TryGetValue(msgId, out var list) && list.Contains(listener);
        }

        private bool ContainsPriorityListener(Msg msgId, Action<Param> listener)
        {
            return ContainsListener(msgId, listener, m_msgPriorityDict);
        }

        private bool ContainsListener(Msg msgId, Action<Param> listener)
        {
            return ContainsListener(msgId, listener, m_msgDict);
        }

        private bool ContainsFinallyListener(Msg msgId, Action<Param> listener)
        {
            return ContainsListener(msgId, listener, m_msgFinallyDict);
        }

        private bool ContainsOnceListener(Msg msgId, Action<Param> listener)
        {
            return ContainsListener(msgId, listener, m_msgOnceDict);
        }

        public void RemovePriorityListener(Msg msgId, Action<Param> listener)
        {
            if (m_msgPriorityDict.TryGetValue(msgId, out var list))
            {
                if (list.Contains(listener))
                {
                    list.Remove(listener);
                    if (list.Count == 0)
                    {
                        Uncc<Action<Param>>.Release(list);
                        m_msgPriorityDict.Remove(msgId);
                    }
                }
            }
        }

        public void RemoveListener(Msg msgId, Action<Param> listener)
        {
            if (m_msgDict.TryGetValue(msgId, out var list))
            {
                if (list.Contains(listener))
                {
                    list.Remove(listener);
                    if (list.Count == 0)
                    {
                        Uncc<Action<Param>>.Release(list);
                        m_msgDict.Remove(msgId);
                    }
                }
            }
        }

        public void RemoveFinallyListener(Msg msgId, Action<Param> listener)
        {
            if (m_msgFinallyDict.TryGetValue(msgId, out var list))
            {
                if (list.Contains(listener))
                {
                    list.Remove(listener);
                    if (list.Count == 0)
                    {
                        Uncc<Action<Param>>.Release(list);
                        m_msgFinallyDict.Remove(msgId);
                    }
                }
            }
        }

        public void RemoveOnceListener(Msg msgId, Action<Param> listener)
        {
            if (m_msgOnceDict.ContainsKey(msgId))
            {
                List<Action<Param>> list = m_msgOnceDict[msgId];
                if (list.Contains(listener))
                {
                    list.Remove(listener);
                    if (list.Count == 0)
                    {
                        Uncc<Action<Param>>.Release(list);
                        m_msgOnceDict.Remove(msgId);
                    }
                }
            }
        }

        public void Dispatch(Msg msgId, Param param = null)
        {
            InvokeMethods(m_msgPriorityDict, msgId, param);
            InvokeMethods(m_msgDict, msgId, param);
            InvokeMethods(m_msgFinallyDict, msgId, param);
            InvokeMethods(m_msgOnceDict, msgId, param);

            if (m_msgOnceDict.ContainsKey(msgId))
            {
                Uncc<Action<Param>>.Release(m_msgOnceDict[msgId]);
                m_msgOnceDict.Remove(msgId);
            }
        }

        private void InvokeMethods(Dictionary<Msg, List<Action<Param>>> msgDict, Msg msgId, Param param)
        {
            if (!msgDict.ContainsKey(msgId)) return;

            var rawList = msgDict[msgId];
            int funcCount = rawList.Count;
            if (funcCount == 1)
            {
                Action<Param> onEvent = rawList[0];
                onEvent(param);
                return;
            }

            var invokeFuncs = Uncc<Action<Param>>.Get();
            invokeFuncs.AddRange(rawList);
            for (var i = 0; i < funcCount; i++)
            {
                try
                {
                    var onEvent = invokeFuncs[i];
                    onEvent(param);
                }
                catch (Exception e)
                {
                    Debug.LogError(e);
                }
            }

            Uncc<Action<Param>>.Release(invokeFuncs);
        }

        public void Clear()
        {
            m_msgPriorityDict.Clear();
            m_msgDict.Clear();
            m_msgFinallyDict.Clear();
            m_msgOnceDict.Clear();
        }

        public void Dispose()
        {
            m_instance = null;

            m_msgPriorityDict.Clear();
            m_msgDict.Clear();
            m_msgFinallyDict.Clear();
            m_msgOnceDict.Clear();
            m_msgPriorityDict = null;
            m_msgDict = null;
            m_msgFinallyDict = null;
            m_msgOnceDict = null;
        }
    }
}