using Thousandto.Code.Center;
using Thousandto.Core.Base;
using System;
using System.Collections.Generic;

namespace Thousandto.Code.Logic
{
    //计时器事件系统
    public class TimerEventSystem : BaseSystem
    {
        #region//常量
        //每小时的秒数
        public const float HOUR_SEC_COUNT = 60 * 60;
        //每天的秒数
        public const float DAY_SEC_COUNT = HOUR_SEC_COUNT * 24;
        //每周的秒数
        public const float WEEK_SEC_COUNT = DAY_SEC_COUNT * 7;
        #endregion

        #region//私有变量
        //id计数器
        private int _idCounter = 1;
        //计时器列表
        private List<TimerEventInfo> _timeEventList = new List<TimerEventInfo>();
        //记录心跳系统是否已经接收到心跳信息
        private bool _receiveHeartMsg = false;
        //下一次跨天时间戳
        private int _crossDayTime = -1;
        #endregion

        #region//初始化,反初始化
        public void Initialize()
        {
            _receiveHeartMsg = false;
            _idCounter = 1;
            _crossDayTime = -1;
        }
        public void Uninitialize()
        {
            _receiveHeartMsg = false;
            _idCounter = 1;
            _timeEventList.Clear();
            _crossDayTime = -1;
        }
        #endregion

        #region//公有函数
        /// <summary>
        /// 增加倒计时事件
        /// </summary>
        /// <param name="countDownTime 倒计时时间,单位秒"></param>
        /// <param loop="是否循环,不循环就只会执行1次,循环就会循环倒计时"></param>
        /// <param callBack="时间到时的回调函数,参数为计时器id"></param>
        /// <returns>返回计时器ID,如果小于0表示添加失败</returns>
        public int AddCountDownEvent(float countDownTime, bool loop, object param, Action<int, double, object> callBack)
        {
            if (countDownTime <= 0f)
                return -1;

            if (callBack == null)
                return -1;
            return AddTimerEvent(countDownTime, 0f, TimerEventType.CountDown, loop, param, callBack);
        }

        /// <summary>
        /// 增加小时时间戳计时器,每个小时到达指定时间时通知
        /// </summary>
        /// <param time="时间值,单位秒,每小时达到这个时间的时候通知,本时间必须小于1小时"></param>
        /// <param loop="是否循环,不循环就只会执行1次,循环就会循环计时"></param>
        /// <param callBack="时间到时的回调函数,参数为计时器id"></param>
        /// <returns>返回计时器ID,如果小于0表示添加失败</returns>
        public int AddTimeStampHourEvent(float time, float validTime, bool loop, object param, Action<int, double, object> callBack)
        {
            if (time <= 0f || time >= HOUR_SEC_COUNT)
                return -1;
            if (callBack == null)
                return -1;
            return AddTimerEvent(time, validTime, TimerEventType.TimeStampHour, loop, param, callBack);
        }

        /// <summary>
        /// 增加天时间戳计时器,每天到达指定时间时通知
        /// </summary>
        /// <param time="时间值,单位秒,每天达到这个时间的时候通知,本时间必须小于1天"></param>
        /// <param loop="是否循环,不循环就只会执行1次,循环就会循环计时"></param>
        /// <param callBack="时间到时的回调函数,参数为计时器id"></param>
        /// <returns>返回计时器ID,如果小于0表示添加失败</returns>
        public int AddTimeStampDayEvent(float time, float validTime, bool loop, object param, Action<int, double, object> callBack)
        {
            if (time <= 0f || time >= DAY_SEC_COUNT)
                return -1;
            if (callBack == null)
                return -1;
            return AddTimerEvent(time, validTime, TimerEventType.TimeStampDay, loop, param, callBack);
        }
        /// <summary>
        /// 增加周时间戳计时器,每周到达指定时间时通知
        /// </summary>
        /// <param time="时间值,单位秒,每周达到这个时间的时候通知,本时间必须小于1周"></param>
        /// <param loop="是否循环,不循环就只会执行1次,循环就会循环计时"></param>
        /// <param callBack="时间到时的回调函数,参数为计时器id"></param>
        /// <returns>返回计时器ID,如果小于0表示添加失败</returns>
        public int AddTimeStampWeekEvent(float time, float validTime, bool loop, object param, Action<int, double, object> callBack)
        {
            if (time <= 0f || time >= WEEK_SEC_COUNT)
            {
                UnityEngine.Debug.LogError("周活动时间有误:" + time + (time <= 0) + (time >= WEEK_SEC_COUNT));
                return -1;
            }
            if (callBack == null)
            {
                UnityEngine.Debug.LogError("周活动callBack为空");
                return -1;
            }

            return AddTimerEvent(time, validTime, TimerEventType.TimeStampWeek, loop, param, callBack);
        }
        /// <summary>
        /// 删除计时器
        /// </summary>
        /// <param name="id 计时器id"></param>
        public void RemoveTimerEvent(int id)
        {
            for (int i = _timeEventList.Count - 1; i >= 0; --i)
            {
                if (_timeEventList[i].ID == id)
                {
                    _timeEventList.RemoveAt(i);
                    break;
                }
            }
        }
        #endregion

        #region//私有函数
        protected override bool OnUpdate(float deltaTime)
        {
            var curHeart = GameCenter.HeartSystem.IsReceiveHeart;
            if (curHeart && !_receiveHeartMsg)
            {
                //判断如果第一次收到心跳消息,重新计算计时器
                _receiveHeartMsg = true;
                for (int i = _timeEventList.Count - 1; i >= 0; --i)
                {
                    CalculateExecuteTime(_timeEventList[i]);
                }
                _timeEventList.Sort(TimerSort);
                _crossDayTime = GetCrossDayTime((int)GameCenter.HeartSystem.ServerTime);
            }
            if (curHeart)
            {
                var serverTime = GameCenter.HeartSystem.ServerTime + GameCenter.HeartSystem.ServerZoneOffset;
                bool reSort = false;
                for (int i = _timeEventList.Count - 1; i >= 0; --i)
                {
                    if(i >= _timeEventList.Count)
                    {
                        continue;
                    }
                    var timer = _timeEventList[i];
                    if (CheckTimer(timer, serverTime))
                    {
                        if (timer.Loop)
                        {
                            //循环的计时器,需要重新排序
                            reSort = true;
                        }
                        else
                        {
                            _timeEventList.Remove(timer);
                        }
                    }
                    else
                    {
                        break;
                    }
                }
                if (reSort)
                {
                    _timeEventList.Sort(TimerSort);
                }
                if (GameCenter.HeartSystem.ServerTime >= _crossDayTime)
                {
                    _crossDayTime = GetCrossDayTime((int)GameCenter.HeartSystem.ServerTime);
                    GameCenter.PushFixEvent(Global.LogicEventDefine.EID_EVENT_CROSSDAY);
                }
            }
            return base.OnUpdate(deltaTime);
        }
        private bool CheckTimer(TimerEventInfo timer, double serverTime)
        {
            if (serverTime >= timer.NextExecuteTime)
            {
                //已经超过了执行的时间,判断是否还在有效期内
                var outTime = serverTime - timer.NextExecuteTime;
                if (outTime < timer.ValidTimeValue || timer.Type == TimerEventType.CountDown)
                {
                    //还在有效期,执行事件
                    if (!timer.IsExecute)
                    {
                        timer.CallBack(timer.ID, timer.ValidTimeValue - outTime, timer.Param);
                    }
                    if (!timer.Loop)
                    {
                        timer.IsExecute = true;
                    }
                }
                if (timer.Loop)
                {
                    timer.IsExecute = false;
                    switch (timer.Type)
                    {
                        case TimerEventType.CountDown:
                            //下一次处理时间
                            timer.NextExecuteTime = serverTime + timer.TimeValue;
                            break;
                        case TimerEventType.TimeStampHour:
                            {
                                //下一次处理时间
                                DateTime curDateTime = new DateTime(1970, 1, 1, 0, 0, 0);
                                long lTime = long.Parse(((ulong)serverTime).ToString() + "0000000");
                                TimeSpan toNow = new TimeSpan(lTime);
                                curDateTime = curDateTime.Add(toNow);
                                var hourStartTime = serverTime - curDateTime.Minute * 60 - curDateTime.Second;
                                timer.NextExecuteTime = hourStartTime + HOUR_SEC_COUNT + timer.TimeValue;
                            }
                            break;
                        case TimerEventType.TimeStampDay:
                            {
                                //下一次处理时间
                                DateTime curDateTime = new DateTime(1970, 1, 1, 0, 0, 0);
                                long lTime = long.Parse(((ulong)serverTime).ToString() + "0000000");
                                TimeSpan toNow = new TimeSpan(lTime);
                                curDateTime = curDateTime.Add(toNow);
                                var dayStartSec = serverTime - curDateTime.Hour * HOUR_SEC_COUNT - curDateTime.Minute * 60 - curDateTime.Second;
                                timer.NextExecuteTime = dayStartSec + DAY_SEC_COUNT + timer.TimeValue;
                            }
                            break;
                        case TimerEventType.TimeStampWeek:
                            {
                                //下一次处理时间
                                DateTime curDateTime = new DateTime(1970, 1, 1, 0, 0, 0);
                                long lTime = long.Parse(((ulong)serverTime).ToString() + "0000000");
                                TimeSpan toNow = new TimeSpan(lTime);
                                curDateTime = curDateTime.Add(toNow);
                                int dayOfWeek = (int)curDateTime.DayOfWeek;
                                if (dayOfWeek == 0)
                                {
                                    dayOfWeek = 6;
                                }
                                else
                                {
                                    dayOfWeek -= 1;
                                }
                                var weekStartSec = serverTime - dayOfWeek * DAY_SEC_COUNT - curDateTime.Hour * HOUR_SEC_COUNT - curDateTime.Minute * 60 - curDateTime.Second;
                                timer.NextExecuteTime = weekStartSec + WEEK_SEC_COUNT + timer.TimeValue;
                            }
                            break;
                    }
                }
                return true;
            }
            return false;
        }
        //增加计时器统一函数
        private int AddTimerEvent(float time, float validTime, TimerEventType type, bool loop, object param, Action<int, double, object> callBack)
        {
            var timerInfo = new TimerEventInfo();
            timerInfo.ID = ++_idCounter;
            timerInfo.Type = type;
            timerInfo.Loop = loop;
            timerInfo.TimeValue = time;
            timerInfo.ValidTimeValue = validTime;
            timerInfo.Param = param;
            timerInfo.AddedTime = GameCenter.HeartSystem.ServerTime + GameCenter.HeartSystem.ServerZoneOffset;
            timerInfo.CallBack = callBack;
            timerInfo.IsExecute = false;
            _timeEventList.Add(timerInfo);

            if (GameCenter.HeartSystem.IsReceiveHeart)
            {
                CalculateExecuteTime(timerInfo);
                _timeEventList.Sort(TimerSort);
            }
            return timerInfo.ID;
        }
        //计算下一次执行的时间
        private void CalculateExecuteTime(TimerEventInfo timerInfo)
        {
            var serverTime = GameCenter.HeartSystem.ServerTime + GameCenter.HeartSystem.ServerZoneOffset;
            DateTime curDateTime = new DateTime(1970, 1, 1, 0, 0, 0);
            long lTime = long.Parse(((ulong)serverTime).ToString() + "0000000");
            TimeSpan toNow = new TimeSpan(lTime);
            curDateTime = curDateTime.Add(toNow);

            switch (timerInfo.Type)
            {
                case TimerEventType.CountDown:
                    timerInfo.NextExecuteTime = serverTime + timerInfo.TimeValue;
                    break;
                case TimerEventType.TimeStampHour:
                    {
                        var hourStartTime = serverTime - curDateTime.Minute * 60 - curDateTime.Second;
                        timerInfo.NextExecuteTime = hourStartTime + timerInfo.TimeValue;
                        while ((timerInfo.NextExecuteTime + timerInfo.ValidTimeValue) < serverTime)
                        {
                            hourStartTime += HOUR_SEC_COUNT;
                            timerInfo.NextExecuteTime = hourStartTime + timerInfo.TimeValue;
                        }
                    }
                    break;
                case TimerEventType.TimeStampDay:
                    {
                        var dayStartSec = serverTime - curDateTime.Hour * HOUR_SEC_COUNT - curDateTime.Minute * 60 - curDateTime.Second;
                        timerInfo.NextExecuteTime = dayStartSec + timerInfo.TimeValue;
                        while ((timerInfo.NextExecuteTime + timerInfo.ValidTimeValue) < serverTime)
                        {
                            dayStartSec += DAY_SEC_COUNT;
                            timerInfo.NextExecuteTime = dayStartSec + timerInfo.TimeValue;
                        }
                    }
                    break;
                case TimerEventType.TimeStampWeek:
                    {
                        int dayOfWeek = (int)curDateTime.DayOfWeek;
                        if (dayOfWeek == 0)
                        {
                            dayOfWeek = 6;
                        }
                        else
                        {
                            dayOfWeek -= 1;
                        }
                        var weekStartSec = serverTime - dayOfWeek * DAY_SEC_COUNT - curDateTime.Hour * HOUR_SEC_COUNT - curDateTime.Minute * 60 - curDateTime.Second;
                        timerInfo.NextExecuteTime = weekStartSec + timerInfo.TimeValue;

                        while ((timerInfo.NextExecuteTime + timerInfo.ValidTimeValue) < serverTime)
                        {
                            //已超时,换到下一周
                            weekStartSec += WEEK_SEC_COUNT;
                            timerInfo.NextExecuteTime = weekStartSec + timerInfo.TimeValue;
                        }
                    }
                    break;
            }
        }
        //排序函数
        private int TimerSort(TimerEventInfo left, TimerEventInfo right)
        {
            //从后往前排
            return right.NextExecuteTime.CompareTo(left.NextExecuteTime);
        }

        //获取跨天时间戳
        private int GetCrossDayTime(int time)
        {
            var dataTime = new DateTime((long)time * 10000000L + 621356256000000000L);
            return (int)((new DateTime(dataTime.Year, dataTime.Month, dataTime.Day).Ticks - 621356256000000000L) / 10000000L) + 86400;
        }
        #endregion
    }
}