/*
 * 作者:吴隆健
 * 日期: 2019-04-15
 * 时间: 15:27
 * 
 */
using System;
using System.Collections.Concurrent;
using Server.DB.Redis;
using Server.Data.Module;
using System.Collections.Generic;
using System.Threading;
using MrWu.Debug;
using System.Threading.Tasks;
using GameData;
using Server.Core;
using Server.MQ;
using StackExchange.Redis;

namespace Server
{
    //发包就四种情况


    /*
	 * 1.发所有
	 * 2.发某类模块的所有
	 * 3.发给指定的
	 * 4.发给某类模块的任意一个
	 * */

    //拦截多开与检查断线
    //提供的服务就是判断在不在线,
    /// <summary>
    /// 服务器生命检查
    /// </summary>
    public class ServerHeart : ServerNode
    {

        /// <summary>
        /// 
        /// </summary>
        public static ServerHeart instanece {
            get;
            private set;
        }

        public bool IsSendHeart = false;
        
        /// <summary>
        /// 
        /// </summary>
        protected ServerHeart() { }

        static ServerHeart() {
            instanece = new ServerHeart();
        }

        /// <summary>
        /// 模块类型
        /// </summary>
        public ModuleType myType {
            get;
            private set;
        }

        /// <summary>
        /// 模块名称
        /// </summary>
        public string moduleName {
            get;
            private set;
        }

        /// <summary>
        /// 未收到心跳的最长时间，超过这个事件认为是掉线
        /// </summary>
        private const int MaxNoGetHeart = 40;
        
        /// <summary>
        /// 1分钟不更新认为自己死亡
        /// </summary>
        private readonly TimeSpan heartTime = new TimeSpan(0, 0, MaxNoGetHeart);

        /// <summary>
        /// 1分钟不更新认为死亡 单位秒
        /// </summary>
        private const int dieTime = MaxNoGetHeart * 1000;

        /// <summary>
        /// 所有的服务器->这个数据
        /// </summary>
        private readonly ConcurrentDictionary<int, ClusterInfo> Servers = new ConcurrentDictionary<int, ClusterInfo>();

        private string m_nodeHeartKey;

        /// <summary>
        /// 节点信息 key
        /// </summary>
        /// <returns></returns>
        private string nodeHeartKey {
            get
            {
                if (m_nodeHeartKey == null)
                    m_nodeHeartKey = GetNodeHeartKey(moduleName, nodeNum); // "Game:LockModule:Module." + moduleName + "." + nodeNum;
                return m_nodeHeartKey;
            }
        }

        public string GetNodeHeartKey(string moduleName,int nodeNum)
        {
            return $"Game:LockModule:Module.{moduleName}.{nodeNum}";
        }

        /// <summary>
        /// 定时器 _ 线程定时器
        /// </summary>
        private Timer timer;

        DistributedLock disLock = new DistributedLock(10);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="type"></param>
        /// <param name="nodeNum"></param>
        /// <returns></returns>
        //启动 -> 因为操作同一个库，所以必须分布式锁   -> 从redis中获取到自己这个服务器的编号
        public bool Start(ModuleType type, int nodeNum) {
            this.myType = type;
            this.moduleName = Enum.GetName(type.GetType(), type);
            this.id = (int)type;
            this.nodeNum = nodeNum;
            //检查redis中的数据
            
            bool start = disLock.LockRun(nodeHeartKey,() => {
                if (redisManager.db.KeyExists(nodeHeartKey))
                    return false;
                return Heart();
            });
            
            AddModule(id,moduleName, nodeNum);

            if (start) {
                //10秒一次
                timer = new Timer(OnTime, null, 0, 10000);
            }

            return start;
        }

        /// <summary>
        ///  正常关闭模块
        /// </summary>
        public void Close() {
            timer.Dispose();
            if (heartTimer != null)
                heartTimer.Dispose();
            disLock.LockRun(nodeHeartKey, () => {
                if (redisManager.db.KeyExists(nodeHeartKey))
                    redisManager.db.KeyDelete(nodeHeartKey);
            });
        }

        private List<NodeInfo> tmpdieNode = new List<NodeInfo>();

        /// <summary>
        /// 定时器
        /// </summary>
        private void OnTime(Object obj) {
            //心跳
            HeartAsync();
            
            tmpdieNode.Clear();
            //每10秒检测一次所有服务器是否掉线
            try {
                foreach (var ser in Servers.Values) {
                    foreach (var node in ser.nodes.Values) {
                        if (node.type == id && node.nodeNum == nodeNum) continue;
                        if (node.ExpireTime - TimeInfo.Instance.FrameTime < 15) //距离过期时间剩下15秒内 才去检查 
                        {
                            if (!tmpdieNode.Contains(node)) {
                                tmpdieNode.Add(node);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                Debug.Error("D12E5112-817D-40C0-A8FB-CED9AC969945:" + e.ToString());
            }
            
            _ = CheckDie();
        }

        private async Task CheckDie()
        {
            if (tmpdieNode.Count <= 0)
            {
                return;
            }

            List<NodeInfo> checks = new List<NodeInfo>();
            List<Task<TimeSpan?>> timeSpans = new List<Task<TimeSpan?>>();
            IBatch batch = redisManager.db.CreateBatch();
            for (int i = 0; i < tmpdieNode.Count; i++)
            {
                checks.Add(tmpdieNode[i]);
                timeSpans.Add(batch.KeyTimeToLiveAsync(GetNodeHeartKey(tmpdieNode[i].ModuleName, tmpdieNode[i].nodeNum)));
            }
            batch.Execute();
            await Task.WhenAll(timeSpans);
            
            for (int i = 0; i < checks.Count; i++)
            {
                if (timeSpans[i].Result == null)
                {
                    Debug.ImportantLog($"模块掉线:{checks[i].ModuleName}-{checks[i].nodeNum}");
                    checks[i].isDie = true;
                    RemoveDie(checks[i].type, checks[i].nodeNum);
                    GlobalDispatcher.Instance.DispatchNextFrame(GlobalMsgId.ModuleNodeCrash,checks[i].ModuleName);
                }
                else
                {
                    checks[i].ExpireTime = TimeInfo.Instance.FrameTime + (long)timeSpans[i].Result.Value.TotalMilliseconds;
                    //Debug.Info($"过期时间:{checks[i].ModuleName} {checks[i].nodeNum} {checks[i].ExpireTime}");
                }
            }
        }

        /// <summary>
        /// 添加模块
        /// </summary>
        /// <param name="moduleId"></param>
        /// <param name="nodeNum"></param>
        public void AddModule(int moduleId,string moduleName,int nodeNum)
        {
            try
            {
                ClusterInfo ci = Servers.GetOrAdd(moduleId, new ClusterInfo()
                {
                    type = moduleId,
                    ModuleName = moduleName
                });

                NodeInfo node = ci.nodes.GetOrAdd(nodeNum, new NodeInfo()
                {
                    type = moduleId,
                    ModuleName = moduleName,
                    nodeNum = nodeNum,
                    isDie = false
                });

                node.ExpireTime = TimeInfo.Instance.FrameTime + dieTime;
            }
            catch (Exception e)
            {
                Debug.Error("1AD0CEC8-1ACB-4CEC-AB11-EB86C8A63C75:" + e.ToString());
            }   
        }

        // /// <summary>
        // /// 处理心跳
        // /// </summary>
        // /// <param name="moduleId"></param>
        // /// <param name="nodeNum"></param>
        // public void DoHeart(int moduleId, int nodeNum) {
        //     //心跳
        //     try
        //     {
        //         ClusterInfo ci = Servers.GetOrAdd(moduleId, new ClusterInfo()
        //         {
        //             type = moduleId
        //         });
        //
        //         NodeInfo node = ci.nodes.GetOrAdd(nodeNum, new NodeInfo()
        //         {
        //             type = moduleId,
        //             nodeNum = nodeNum
        //         });
        //
        //         node.LastActivityTime = TimeInfo.Instance.FrameTime;
        //     } catch (Exception e) {
        //         Debug.Error("1AD0CEC8-1ACB-4CEC-AB11-EB86C8A63C75:" + e.ToString());
        //     }
        // }

        // /// <summary>
        // /// 来心跳
        // /// </summary>
        // /// <param name="moduleId"></param>
        // /// <param name="nodeNum"></param>
        // public Task DoHeartAsync(int moduleId, int nodeNum) {
        //     return Task.Run(
        //         () => DoHeart(moduleId, nodeNum)
        //     );
        // }

        /// <summary>
        /// 某个模块是否死亡
        /// </summary>
        public Task<bool> IsDieAsync(int moduleId, int nodeNum) {
            return Task.Run(() => IsDie(moduleId, nodeNum));
        }

        /// <summary>
        /// 检查节点死否死亡
        /// </summary>
        /// <param name="moduleId"></param>
        /// <param name="nodeNum"></param>
        /// <returns></returns>
        public bool IsDie(int moduleId, int nodeNum) {
            bool result = true;
            
            try {
                if (Servers.TryGetValue(moduleId, out ClusterInfo ci))
                {
                    if (ci.nodes.TryGetValue(nodeNum, out NodeInfo node))
                    {
                        result = node.isDie;
                        if (result)
                            RemoveDie(moduleId, nodeNum);
                    }
                }
            } catch (Exception e) {
                Debug.Error("FC703541-D8AB-4641-9618-9CB0F090FFE4:" + e.ToString());
            }
            
            return result;
        }

        /// <summary>
        /// 检查该模块是否没有一个在线
        /// </summary>
        /// <param name="moduleId"></param>
        /// <returns></returns>
        public bool IsDie(int moduleId) {
            bool result = true;
            try {
                if (Servers.TryGetValue(moduleId, out ClusterInfo ci)) {
                    foreach (var node in ci.nodes.Values) {
                        if (!node.isDie) {
                            result = false;
                            break;
                        }
                    }
                }
            } catch (Exception e) {
                Debug.Error("78A9B0FC-9EFF-4E53-B921-F5EF328BCF5F:" + e.ToString());
            }
            return result;
        }

        /// <summary>
        /// 输出日志
        /// </summary>
        public string Log() {
            string str = string.Empty;
            str += id + "," + nodeNum + ":";

            str += Environment.NewLine;
            
            try {
                foreach (var ser in Servers.Values) {
                    foreach (NodeInfo node in ser.nodes.Values) {
                        str += "在线的模块:" + node.type + "," + node.nodeNum +"," + Environment.NewLine;
                    }
                }
            } catch (Exception e) {
                Debug.Error("E4BEB6EB-465A-428B-9C5D-85264A108429:" + e.ToString());
            }

            //str += Environment.NewLine;

            return str;
        }

        /// <summary>
        /// 移除死亡的模块
        /// </summary>
        /// <param name="moduleId"></param>
        /// <param name="nodeNum"></param>
        public Task RemoveDie(int moduleId, int nodeNum, bool force = false) {
            
            //移除死掉的节点
            return Task.Run(() => {
                bool r = false;
                try {
                    ClusterInfo ci = null;
                    if (Servers.TryGetValue(moduleId, out ci)) {
                        NodeInfo node = null;
                        if (ci.nodes.TryGetValue(nodeNum, out node)) {
                            if (node.isDie && (moduleId != this.id || nodeNum != this.nodeNum)) {
                                r = ci.nodes.TryRemove(nodeNum,out _);
                            } else if (force) {
                                r = ci.nodes.TryRemove(nodeNum,out _);
                            }
                        }
                    }
                } catch (Exception e) {
                    Debug.Error("3D910EC1-3682-44B2-A767-4FC8517BFD2D:" + e.ToString());
                }

                if (r) {
                    GlobalDispatcher.Instance.DispatchNextFrame(GlobalMsgId.ModuleNodeDie,new ServerNode(moduleId,nodeNum));
                }
            });
        }

        /// <summary>
        /// 获取所有的在线的节点
        /// </summary>
        /// <returns></returns>
        public List<ServerNode> GetAllOnline() {
            List<ServerNode> nodes = new List<ServerNode>();
            try
            {
                foreach (var ser in Servers.Values)
                {
                    foreach (var node in ser.nodes.Values)
                    {
                        if (!node.isDie)
                            nodes.Add(new ServerNode(node.type, node.nodeNum));
                    }
                }

            }
            catch (Exception e)
            {
                Debug.Error("F95EF643-7315-48A6-8D07-1606E16102D3:" + e.ToString());
            }

            return nodes;
        }
        
        /// <summary>
        /// 获取所有的在线节点
        /// </summary>
        /// <returns></returns>
        public List<ServerNode> GetAllOnline(int moduleId) {
            List<ServerNode> nodes = new List<ServerNode>();
            try {
                ClusterInfo ci = null;
                if (Servers.TryGetValue(moduleId, out ci)) {
                    foreach (var node in ci.nodes.Values) {
                        if (!node.isDie)
                            nodes.Add(new ServerNode(node.type, node.nodeNum));
                    }
                }
            } catch (Exception e) {
                Debug.Error("E1C6A85D-8F0C-47A9-8103-5AC3D7ACDF0B:" + e.ToString());
            }
            return nodes;
        }


        /// <summary>
        /// 心跳
        /// </summary>
        /// <returns></returns>
        private Task<bool> HeartAsync() {
            return redisManager.db.StringSetAsync(nodeHeartKey, "online", heartTime);
        }

        private bool Heart()
        {
            return redisManager.db.StringSet(nodeHeartKey, "online", heartTime);
        }

        #region 心跳包发送处理
        /// <summary>
        /// 发送心跳包定时器
        /// </summary>
        private Timer heartTimer;

        /// <summary>
        /// 12秒钟发送一次心跳包
        /// </summary>
        const int constheartTime = 12000;

        /// <summary>
        /// 心跳包
        /// </summary>
        private string heartPack;

        
        
        /// <summary>
        /// 开始发送
        /// </summary>
        /// <param name="heartPack"></param>
        public void BeginHeart(string heartPack) {
            this.heartPack = heartPack;
            heartTimer = new Timer(SendHeart, null, 0, constheartTime);
        }

        /// <summary>
        /// 更改心跳包
        /// </summary>
        public void ChangeHeart(string heartPack) {
            this.heartPack = heartPack;
        }

        /// <summary>
        /// 发送心跳包
        /// </summary>
        private void SendHeart(object state) {
            if (!IsSendHeart)
            {
                return;
            }
            GlobalMQ.instance.DeliveryAll(GlobalMQ.CreateMessage(heartPack), true);
        }

        #endregion


        /// <summary>
        /// 集群信息
        /// </summary>
        private class ClusterInfo
        {
            /// <summary>
            /// 模块id
            /// </summary>
            public int type;

            /// <summary>
            /// 模块名称
            /// </summary>
            public string ModuleName;
            
            /// <summary>
            /// 节点id
            /// </summary>
            public ConcurrentDictionary<int, NodeInfo> nodes = new ConcurrentDictionary<int, NodeInfo>();
        }

        /// <summary>
        /// 节点信息
        /// </summary>
        private class NodeInfo
        {
            /// <summary>
            /// 类型
            /// </summary>
            public int type;

            /// <summary>
            /// 模块名称
            /// </summary>
            public string ModuleName;
            
            /// <summary>
            /// 节点
            /// </summary>
            public int nodeNum;

            /// <summary>
            /// 过期的时间
            /// </summary>
            public long ExpireTime;
            
            public bool isDie
            {
                get;
                set;
            }
        }
    }

}
