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

namespace FairyGUI
{
    public delegate void TimerCallback(object param);

    /// <summary>
    /// 
    /// </summary>
    public class Timers
    {
        public static int repeat;
        public static float time;

        public static bool catchCallbackExceptions = false;

        Dictionary<TimerCallback, Anymous_T> _items;
        Dictionary<TimerCallback, Anymous_T> _toAdd;
        List<Anymous_T> _toRemove;
        List<Anymous_T> _pool;

        TimersEngine _engine;
        GameObject gameObject;

        private static Timers _inst;

        public static Timers inst
        {
            get
            {
                if (_inst == null)
                    _inst = new Timers();
                return _inst;
            }
        }

        public Timers()
        {
            _inst = this;
            gameObject = new GameObject("[FairyGUI.Timers]");
            gameObject.hideFlags = HideFlags.HideInHierarchy;
            gameObject.SetActive(true);
            if (Application.isPlaying)
            {
                Object.DontDestroyOnLoad(gameObject);
            }

            _engine = gameObject.AddComponent<TimersEngine>();

            _items = new Dictionary<TimerCallback, Anymous_T>();
            _toAdd = new Dictionary<TimerCallback, Anymous_T>();
            _toRemove = new List<Anymous_T>();
            _pool = new List<Anymous_T>(100);
        }

        public void Add(float interval, int repeat, TimerCallback callback)
        {
            Add(interval, repeat, callback, null);
        }

        /**
         * @interval in seconds
         * @repeat 0 indicate loop infinitely, otherwise the run count
         **/
        public void Add(float interval, int repeat, TimerCallback callback, object callbackParam)
        {
            if (callback == null)
            {
                Debug.LogWarning("timer callback is null, " + interval + "," + repeat);
                return;
            }

            Anymous_T t;
            if (_items.TryGetValue(callback, out t))
            {
                t.set(interval, repeat, callback, callbackParam);
                t.elapsed = 0;
                t.deleted = false;
                return;
            }

            if (_toAdd.TryGetValue(callback, out t))
            {
                t.set(interval, repeat, callback, callbackParam);
                return;
            }

            t = GetFromPool();
            t.interval = interval;
            t.repeat = repeat;
            t.callback = callback;
            t.param = callbackParam;
            _toAdd[callback] = t;
        }

        public void CallLater(TimerCallback callback)
        {
            Add(0.001f, 1, callback);
        }

        public void CallLater(TimerCallback callback, object callbackParam)
        {
            Add(0.001f, 1, callback, callbackParam);
        }

        public void AddUpdate(TimerCallback callback)
        {
            Add(0.001f, 0, callback);
        }

        public void AddUpdate(TimerCallback callback, object callbackParam)
        {
            Add(0.001f, 0, callback, callbackParam);
        }

        public void StartCoroutine(IEnumerator routine)
        {
            _engine.StartCoroutine(routine);
        }

        public bool Exists(TimerCallback callback)
        {
            if (_toAdd.ContainsKey(callback))
                return true;

            Anymous_T at;
            if (_items.TryGetValue(callback, out at))
                return !at.deleted;

            return false;
        }

        public void Remove(TimerCallback callback)
        {
            Anymous_T t;
            if (_toAdd.TryGetValue(callback, out t))
            {
                _toAdd.Remove(callback);
                ReturnToPool(t);
            }

            if (_items.TryGetValue(callback, out t))
                t.deleted = true;
        }

        private Anymous_T GetFromPool()
        {
            Anymous_T t;
            int cnt = _pool.Count;
            if (cnt > 0)
            {
                t = _pool[cnt - 1];
                _pool.RemoveAt(cnt - 1);
                t.deleted = false;
                t.elapsed = 0;
            }
            else
                t = new Anymous_T();

            return t;
        }

        private void ReturnToPool(Anymous_T t)
        {
            t.callback = null;
            _pool.Add(t);
        }

        public void Update()
        {
            float dt = Time.unscaledDeltaTime;
            Dictionary<TimerCallback, Anymous_T>.Enumerator iter;

            if (_items.Count > 0)
            {
                iter = _items.GetEnumerator();
                while (iter.MoveNext())
                {
                    Anymous_T i = iter.Current.Value;
                    if (i.deleted)
                    {
                        _toRemove.Add(i);
                        continue;
                    }

                    i.elapsed += dt;
                    if (i.elapsed < i.interval)
                        continue;

                    i.elapsed -= i.interval;
                    if (i.elapsed < 0 || i.elapsed > 0.03f)
                        i.elapsed = 0;

                    if (i.repeat > 0)
                    {
                        i.repeat--;
                        if (i.repeat == 0)
                        {
                            i.deleted = true;
                            _toRemove.Add(i);
                        }
                    }

                    repeat = i.repeat;
                    if (i.callback != null)
                    {
                        if (catchCallbackExceptions)
                        {
                            try
                            {
                                i.callback(i.param);
                            }
                            catch (System.Exception e)
                            {
                                i.deleted = true;
                                Debug.LogWarning("FairyGUI: timer(internal=" + i.interval + ", repeat=" + i.repeat +
                                                 ") callback error > " + e.Message);
                            }
                        }
                        else
                            i.callback(i.param);
                    }
                }

                iter.Dispose();
            }

            int len = _toRemove.Count;
            if (len > 0)
            {
                for (int k = 0; k < len; k++)
                {
                    Anymous_T i = _toRemove[k];
                    if (i.deleted && i.callback != null)
                    {
                        _items.Remove(i.callback);
                        ReturnToPool(i);
                    }
                }

                _toRemove.Clear();
            }

            if (_toAdd.Count > 0)
            {
                iter = _toAdd.GetEnumerator();
                while (iter.MoveNext())
                    _items.Add(iter.Current.Key, iter.Current.Value);
                iter.Dispose();
                _toAdd.Clear();
            }
        }
    }

    class Anymous_T
    {
        public float interval;
        public int repeat;
        public TimerCallback callback;
        public object param;

        public float elapsed;
        public bool deleted;

        public void set(float interval, int repeat, TimerCallback callback, object param)
        {
            this.interval = interval;
            this.repeat = repeat;
            this.callback = callback;
            this.param = param;
        }
    }

    class TimersEngine : MonoBehaviour
    {
        void Update()
        {
            Timers.inst.Update();
        }
    }
}