using System;
using System.Collections.Generic;
using UnityEngine.ResourceManagement.Util;

internal class DelegateList<T>
{
    Func<Action<T>, LinkedListNode<Action<T>>> m_acquireFunc;
    Action<LinkedListNode<Action<T>>> m_releaseFunc;
    LinkedList<Action<T>> m_callbacks;
    bool m_invoking = false;

    public DelegateList(Func<Action<T>, LinkedListNode<Action<T>>> acquireFunc, Action<LinkedListNode<Action<T>>> releaseFunc)
    {
        if (acquireFunc == null)
            throw new ArgumentNullException("acquireFunc");
        if (releaseFunc == null)
            throw new ArgumentNullException("releaseFunc");
        m_acquireFunc = acquireFunc;
        m_releaseFunc = releaseFunc;
    }

    public int Count
    {
        get { return m_callbacks == null ? 0 : m_callbacks.Count; }
    }

    public void Add(Action<T> action)
    {
        var node = m_acquireFunc(action);
        if (m_callbacks == null)
            m_callbacks = new LinkedList<Action<T>>();
        m_callbacks.AddLast(node);
    }

    public void Remove(Action<T> action)
    {
        if (m_callbacks == null)
            return;

        var node = m_callbacks.First;
        while (node != null)
        {
            if (node.Value == action)
            {
                if (m_invoking)
                {
                    node.Value = null;
                }
                else
                {
                    m_callbacks.Remove(node);
                    m_releaseFunc(node);
                }

                return;
            }

            node = node.Next;
        }
    }

    public void Invoke(T res)
    {
        if (m_callbacks == null)
            return;

        m_invoking = true;
        var node = m_callbacks.First;
        while (node != null)
        {
            if (node.Value != null)
            {
                try
                {
                    node.Value(res);
                }
                catch (Exception ex)
                {
                    UnityEngine.Debug.LogException(ex);
                }
            }

            node = node.Next;
        }

        m_invoking = false;
        var r = m_callbacks.First;
        while (r != null)
        {
            var next = r.Next;
            if (r.Value == null)
            {
                m_callbacks.Remove(r);
                m_releaseFunc(r);
            }

            r = next;
        }
    }

    public void Clear()
    {
        if (m_callbacks == null)
            return;
        var node = m_callbacks.First;
        while (node != null)
        {
            var next = node.Next;
            m_callbacks.Remove(node);
            m_releaseFunc(node);
            node = next;
        }
    }

    public static DelegateList<T> CreateWithGlobalCache()
    {
        if (!GlobalLinkedListNodeCache<Action<T>>.CacheExists)
            GlobalLinkedListNodeCache<Action<T>>.SetCacheSize(32);
        return new DelegateList<T>(GlobalLinkedListNodeCache<Action<T>>.Acquire, GlobalLinkedListNodeCache<Action<T>>.Release);
    }
}