//------------------------------------------------------------
// Author: 亦亦
// Mail: 379338943@qq.com
// Data: 2023年2月12日
//------------------------------------------------------------

using System.Collections.Generic;
using Cysharp.Threading.Tasks;
using UnityEngine;

namespace YIUIFramework
{
    /// <summary>
    /// 倒计时管理器
    /// 区别于Times
    ///                            Times                   CountDown
    /// 回调频率                     不可改                     可改                        (虽然中途改频率这个事情很少)
    /// 如果暂停                中间丢失的时间就没了      中途丢失的时间会快速倒计时             (万一有需求 中间的各种计算就丢掉了)
    /// 添加时可立即调用一次      否(还需要自己调一次)          可传参数控制                     (很多时候倒计时都需要第一时间刷新一次的)
    /// 一对多                        否                         是                         (因为用Callback做K 就没办法在同一个Callback下 被别人倒计时)
    /// 可提前结束                     否                         是                         (针对于 比如 匿名函数 等特殊情况)
    /// 回调参数            obj 但是麻烦 而且不可变           已过去时间/总时间                   (更适合于UI上的一些数字倒计时)
    /// 可循环                        否                          是                         (虽然0 都可以无限 但是万一要的是不是0的情况下循环呢 就得递归调自己吗)
    /// 多重载                        否                          是 (满足各种需求)
    /// 匿名函数                      否                          是                          (匿名函数也可以被暂停 移除等操作)
    /// ......
    /// </summary>
    public partial class CountDownMgr : MgrSingleton<CountDownMgr>
    {
        /// <summary>
        /// 回调方法
        /// </summary>
        /// <param name="residueTime">剩余时间</param>
        /// <param name="elapseTime">已过去时间</param>
        /// <param name="totalTime">总时间</param>
        public delegate void TimerCallback(double residueTime, double elapseTime, double totalTime);

        /// <summary>
        /// 所有需要被倒计时的目标
        /// 这个可以一对多
        /// </summary>
        private Dictionary<int, CountDownData> m_AllCountDown = new Dictionary<int, CountDownData>();

        /// <summary>
        /// 临时存储
        /// 下一帧添加的倒计时
        /// </summary>
        private Dictionary<int, CountDownData> m_ToAddCountDown = new Dictionary<int, CountDownData>();

        /// <summary>
        /// 所有需要被倒计时的目标
        /// 这个只能一对一
        /// </summary>
        private Dictionary<TimerCallback, int> m_CallbackGuidDic = new Dictionary<TimerCallback, int>();

        /// <summary>
        /// 临时存储
        /// 下一帧移除的倒计时
        /// </summary>
        private List<int> m_RemoveGuid = new List<int>();

        /// <summary>
        /// 可容纳的最大倒计时
        /// </summary>
        private int m_MaxCount = 1000;

        /// <summary>
        /// 当然已经存在的倒计时数量
        /// </summary>
        private int m_AtCount = 0;

        protected override UniTask<bool> MgrAsyncInit()
        {
            Initialize();
            return base.MgrAsyncInit();
        }

        //初始化
        private void Initialize()
        {
            m_AllCountDown.Clear();
            m_RemoveGuid.Clear();
            m_ToAddCountDown.Clear();
            m_CallbackGuidDic.Clear();
            UpdateAsync().Forget();
        }

        //摧毁
        protected override void OnDispose()
        {
        }
        
        //统一所有取时间都用这个 且方便修改
        private static float GetTime()
        {
            //这是一个倒计时时间不受暂停影响的
            return Time.realtimeSinceStartup;
        }

        //更新频率毫秒
        private int m_UpdataAsyncDelay = (int)(1000f / 30f);

        //使用异步控制频率更新 并没有使用调度器
        //调度器是mono update 不需要这么高的频率
        private async UniTaskVoid UpdateAsync()
        {
            while (true)
            {
                if (Disposed) return;
                ManagerUpdate();
                await UniTask.Delay(m_UpdataAsyncDelay);
            }
        }

        //为了不受mono暂停影响 所以使用异步调用
        public void ManagerUpdate()
        {
            var time = GetTime();

            //需要被添加的
            if (m_ToAddCountDown.Count >= 1)
            {
                foreach (var countDown in m_ToAddCountDown.Values)
                {
                    ToAddCountDownTimer(countDown);
                }

                m_ToAddCountDown.Clear();
            }

            //需要被移除的
            if (m_RemoveGuid.Count >= 1)
            {
                foreach (var guid in m_RemoveGuid)
                {
                    RemoveCountDownTimer(guid);
                }

                m_RemoveGuid.Clear();
            }

            //倒计时
            foreach (var data in m_AllCountDown.Values)
            {
                data.ElapseTime = time - data.StartTime;

                //已经用完时间
                if (data.TotalTime != 0 && data.ElapseTime >= data.TotalTime)
                {
                    data.ElapseTime = data.TotalTime;

                    Callback(data);

                    if (data.Forver)
                    {
                        Restart(data);
                    }
                    else
                    {
                        m_RemoveGuid.Add(data.Guid);
                    }
                }
                else if (time - data.LastCallBackTime >= data.Interval)
                {
                    //这样才可以确保如果卡了很久  该间隔回调的还是会回 不会遗漏
                    //但是如果一瞬间就到最后了 中途的还是会没有  因为这个倒计时类的需求就是这样的
                    data.LastCallBackTime += data.Interval;
                    Callback(data);
                }
            }
        }

        #region 私有

        /// <summary>
        /// 是否可添加倒计时   有同时倒计时上限限制
        /// </summary>
        /// <returns></returns>
        private bool TryAdd()
        {
            return !(m_AtCount >= m_MaxCount);
        }

        /// <summary>
        /// 获取当前索引
        /// </summary>
        /// <returns></returns>
        private int GetGuid()
        {
            return IDHelper.GetGuid();
        }

        //从缓存列表添加到运行列表
        private void ToAddCountDownTimer(CountDownData countDownData)
        {
            if (m_AllCountDown.ContainsKey(countDownData.Guid))
            {
                Logger.LogError($"<color=red> 添加的这个已经存在 :{countDownData.Guid}</color>");
                return;
            }

            m_AllCountDown.Add(countDownData.Guid, countDownData);
            m_AtCount++;
        }

        /// <summary>
        /// 添加一个到字典中
        /// </summary>
        private int AddCountDownTimer(CountDownData countDownData)
        {
            int guid = GetGuid();
            countDownData.Guid = guid;
            m_ToAddCountDown.Add(guid, countDownData);
            return guid;
        }

        private bool RemoveCountDownTimer(int guid)
        {
            if (guid == 0)
            {
                return true;
            }

            if (!m_AllCountDown.ContainsKey(guid))
            {
                return false;
            }

            var data = m_AllCountDown[guid];
            m_AllCountDown.Remove(guid);
            RefPool.Put(data);
            m_AtCount--;
            
            return true;
        }

        /// <summary>
        /// 移除一个回调
        /// 根据需求也可使用 Remove(ref int guid)
        /// </summary>
        public bool Remove(int guid)
        {
            if (guid == 0)
            {
                return true;
            }

            //如果对方还在添加列表中 在移除列表中添加
            //在下一次update的时候 添加列表还是会先添加到all中
            //然后就会被移除 所以并不会被执行
            //最终所有移除的都是从all中移除的
            if (m_ToAddCountDown.ContainsKey(guid))
            {
                m_RemoveGuid.Add(guid);
                return true;
            }

            //如果正在执行中 则添加到移除中
            //下一次循环移除
            if (m_AllCountDown.ContainsKey(guid))
            {
                m_RemoveGuid.Add(guid);
                return true;
            }

            return false;
        }

        /// <summary>
        /// 让倒计时重新开始
        /// </summary>
        private bool Restart(CountDownData data)
        {
            data.ElapseTime       = 0;
            data.LastCallBackTime = GetTime();
            data.StartTime        = data.LastCallBackTime;
            data.EndTime          = data.LastCallBackTime + data.TotalTime;
            return true;
        }

        private void Callback(int guid)
        {
            var exist = m_AllCountDown.TryGetValue(guid, out CountDownData data);
            if (exist)
                Callback(data);
        }

        private void Callback(CountDownData data)
        {
            data.TimerCallback?.Invoke(data.TotalTime - data.ElapseTime, data.ElapseTime, data.TotalTime);
        }

        private void Callback(CountDownData data, double elapseTime)
        {
            data.TimerCallback?.Invoke(data.TotalTime - elapseTime, elapseTime, data.TotalTime);
        }

        #endregion
    }
}