using System;
using System.Collections.Generic;

namespace CorgiLabs.Utility
{
    public static class EventManager
    {
        /// <summary>
        /// Internal Events Dictionary, used to store delegates and trigger callbacks.
        /// </summary>
        private static Dictionary<EventID, Delegate> m_Events = new Dictionary<EventID, Delegate>();


        #region Subscription methods

        /// <summary>
        /// Subscribes a no argument callback handler to an event.
        /// </summary>
        /// <param name="inEvent">Event to listen to.</param>
        /// <param name="inHandler">Listener handler.</param>
        public static void StartListening(EventID inEvent, Callback inHandler)
        {
            // If the event is already in Dictionary (= there should be other listeners already), check for duplication;
            // else add the entry
            if (m_Events.ContainsKey(inEvent))
            {
                // Get invocation list
                Delegate[] invocationList = (m_Events[inEvent] as Callback)?.GetInvocationList();

                // Cycle invocation list and check for a match; if found, abort since the listener is already subscribed
                for (int i = 0; i < invocationList.Length; i++)
                {
                    if (inHandler as Delegate == invocationList[i])
                        return;
                }
            }
            else
                m_Events.Add(inEvent, null);

            // Add the handler to the invocation list
            m_Events[inEvent] = m_Events[inEvent] as Callback + inHandler;
        }


        /// <summary>
        /// Subscribes a one argument callback handler to an event.
        /// </summary>
        /// <param name="inEvent">Event to listen to.</param>
        /// <param name="inHandler">Listener handler.</param>
        public static void StartListening<T>(EventID inEvent, Callback<T> inHandler)
        {
            // If the event is already in Dictionary (= there should be other listeners already), check for duplication;
            // else add the entry
            if (m_Events.ContainsKey(inEvent))
            {
                // Get invocation list
                Delegate[] invocationList = (m_Events[inEvent] as Callback<T>)?.GetInvocationList();

                // Cycle invocation list and check for a match; if found, abort since the listener is already subscribed
                for (int i = 0; i < invocationList.Length; i++)
                {
                    if (inHandler as Delegate == invocationList[i])
                        return;
                }
            }
            else
                m_Events.Add(inEvent, null);

            // Add the handler to the invocation list
            m_Events[inEvent] = m_Events[inEvent] as Callback<T> + inHandler;
        }


        /// <summary>
        /// Subscribes a two argument callback handler to an event.
        /// </summary>
        /// <param name="inEvent">Event to listen to.</param>
        /// <param name="inHandler">Listener handler.</param>
        public static void StartListening<T, K>(EventID inEvent, Callback<T, K> inHandler)
        {
            // If the event is already in Dictionary (= there should be other listeners already), check for duplication;
            // else add the entry
            if (m_Events.ContainsKey(inEvent))
            {
                // Get invocation list
                Delegate[] invocationList = (m_Events[inEvent] as Callback<T,K>)?.GetInvocationList();

                // Cycle invocation list and check for a match; if found, abort since the listener is already subscribed
                for (int i = 0; i < invocationList.Length; i++)
                {
                    if (inHandler as Delegate == invocationList[i])
                        return;
                }
            }
            else
                m_Events.Add(inEvent, null);

            // Add the handler to the invocation list
            m_Events[inEvent] = m_Events[inEvent] as Callback<T, K> + inHandler;
        }

        #endregion

        #region Unsubscription methods

        /// <summary>
        /// Unsubscribes a no argument handler from an event.
        /// </summary>
        /// <param name="inEvent">Event to stop listening to.</param>
        /// <param name="inHandler">Listener handler.</param>
        public static void StopListening(EventID inEvent, Callback inHandler)
        {
            // If the Dictionary contains the specified Event's entry
            if (m_Events.ContainsKey(inEvent))
            {
                // Remove the listener from the invocation list
                m_Events[inEvent] = m_Events[inEvent] as Callback - inHandler;

                // Then, if there are no more listeners, remove the event itself
                if (m_Events[inEvent] == null) m_Events.Remove(inEvent);
            }
        }


        /// <summary>
        /// Unsubscribes a no argument handler from an event.
        /// </summary>
        /// <param name="inEvent">Event to stop listening to.</param>
        /// <param name="inHandler">Listener handler.</param>
        public static void StopListening<T>(EventID inEvent, Callback<T> inHandler)
        {
            // If the Dictionary contains the specified Event's entry
            if (m_Events.ContainsKey(inEvent))
            {
                // Remove the listener from the invocation list
                m_Events[inEvent] = m_Events[inEvent] as Callback<T> - inHandler;

                // Then, if there are no more listeners, remove the event itself
                if (m_Events[inEvent] == null) m_Events.Remove(inEvent);
            }
        }


        /// <summary>
        /// Unsubscribes a no argument handler from an event.
        /// </summary>
        /// <param name="inEvent">Event to stop listening to.</param>
        /// <param name="inHandler">Listener handler.</param>
        public static void StopListening<T,K>(EventID inEvent, Callback<T,K> inHandler)
        {
            // If the Dictionary contains the specified Event's entry
            if (m_Events.ContainsKey(inEvent))
            {
                // Remove the listener from the invocation list
                m_Events[inEvent] = m_Events[inEvent] as Callback<T,K> - inHandler;

                // Then, if there are no more listeners, remove the event itself
                if (m_Events[inEvent] == null) m_Events.Remove(inEvent);
            }
        }

        #endregion

        #region Triggering methods

        /// <summary>
        /// Triggers a no argument event.
        /// </summary>
        /// <param name="inEvent">Event to trigger.</param>
        public static void TriggerEvent(EventID inEvent)
        {
            if (m_Events.ContainsKey(inEvent)) (m_Events[inEvent] as Callback)?.Invoke();
        }

        /// <summary>
        /// Triggers a one-argument event.
        /// </summary>
        /// <typeparam name="T">Argument type.</typeparam>
        /// <param name="inEvent">Event to trigger.</param>
        /// <param name="arg">Invocation argument.</param>
        public static void TriggerEvent<T>(EventID inEvent, T arg)
        {
            if (m_Events.ContainsKey(inEvent)) (m_Events[inEvent] as Callback<T>)?.Invoke(arg);
        }

        /// <summary>
        /// Triggers a two-arguments event.
        /// </summary>
        /// <typeparam name="T">First argument type.</typeparam>
        /// <typeparam name="K">Second argument type.</typeparam>
        /// <param name="inEvent">Event to trigger.</param>
        /// <param name="arg1">First invocation argument.</param>
        /// <param name="arg2">Second invocation argument.</param>
        public static void TriggerEvent<T,K>(EventID inEvent, T arg1, K arg2)
        {
            if (m_Events.ContainsKey(inEvent)) (m_Events[inEvent] as Callback<T,K>)?.Invoke(arg1, arg2);
        }

        #endregion
    }
}
