/********************************
 *
 * 作者:吴隆健
 * 创建时间: 2019/7/5 11:23:27
 *
 ********************************/

using System;
using System.Threading;
using System.Threading.Tasks;
using GameData;
using Server.Core;
using Server.Data.Module;
using Server.DB.Redis;
using Server.MQ;
using Server.Pack;
using StackExchange.Redis;
using Debug = MrWu.Debug.Debug;

namespace Server.Data
{
    /// <summary>
    /// 玩家锁定信息
    /// </summary>
    public class PlayerLockInfo
    {
        /// <summary>
        /// 被锁的模块信息
        /// </summary>
        public ModuleUtile util;

        /// <summary>
        /// 玩家的serid
        /// </summary>
        public int gameid;

        /// <summary>
        /// 玩家的客户端id
        /// </summary>
        public int clientid;

        /// <summary>
        /// 被锁的在的游戏名称
        /// </summary>
        public string name;

        /// <summary>
        /// 是否是朋友房
        /// </summary>
        public bool isFriend;
    }

    /// <summary>
    /// 玩家操作
    /// </summary>
    public class PlayerFun
    {
        /*
         * 玩家数据 在登录游戏/仓库是 加锁  在退出游戏/仓库 时解锁
         * 中途可能出现宕,  在玩家登录其他游戏时，检查锁定信息，若锁定的游戏确认宕机则解锁玩家
         *
         * */

        /// <summary>
        /// 被锁的玩家列表
        /// </summary>
        private const string LockKey = "Game:LockPlayer:";

        /// <summary>
        /// 获取玩家锁定的key
        /// </summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        public static string GetKey(int userid)
        {
            return LockKey + userid;
        }

        /// <summary>
        /// 锁定玩家
        /// </summary>
        /// <param name="userid"></param>
        /// <param name="util"></param>
        /// <param name="gameid"></param>
        /// <param name="clientid"></param>
        /// <param name="name"></param>
        /// <param name="isFriend"></param>
        /// <returns></returns>
        public static bool LockPlayer(int userid, ModuleUtile util, int gameid, int clientid, string name,
            bool isFriend)
        {
            string key = GetKey(userid);
            PlayerLockInfo lockInfo = new PlayerLockInfo()
            {
                util = util,
                gameid = gameid,
                clientid = clientid,
                name = name,
                isFriend = isFriend
            };

            return redisManager.db.StringSet(key, JsonPack.GetJson(lockInfo), null, When.NotExists);
        }

        /// <summary>
        /// 修改锁信息
        /// </summary>
        /// <param name="userid"></param>
        /// <param name="lockInfo"></param>
        /// <returns></returns>
        public static bool EditorLockPlayer(int userid, PlayerLockInfo lockInfo)
        {
            string key = GetKey(userid);
            return redisManager.db.StringSet(key, JsonPack.GetJson(lockInfo), null, When.Exists);
        }

        /// <summary>
        /// 获取玩家锁定信息
        /// </summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        public static Task<PlayerLockInfo> GetPlayerLockInfoAsync(int userid)
        {
            return Task.Run(() => GetPlayerLockInfo(userid));
        }

        /// <summary>
        /// 获取玩家锁定信息
        /// </summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        public static PlayerLockInfo GetPlayerLockInfo(int userid)
        {
            string key = GetKey(userid);
            string value = redisManager.db.StringGet(key);

            return GetPlayerLockInfo(value);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static PlayerLockInfo GetPlayerLockInfo(string value)
        {
            if (string.IsNullOrEmpty(value))
                return null;
            return JsonPack.GetData<PlayerLockInfo>(value);
        }

        /// <summary>
        /// 解锁玩家 当玩家是朋友房加锁的 只能有本模块去解锁,不然要使用 force 
        /// </summary>
        /// <param name="userid">解锁的玩家userid</param>
        /// <param name="myUtil"></param>
        /// <param name="force"></param>
        /// <returns></returns>
        public static bool UnLockPlayer(int userid, ModuleUtile myUtil, bool force = false)
        {
#if DEBUG
            Debug.Info($"UnLockPlayer {userid}");
#endif

            string key = GetKey(userid);
            string value = redisManager.db.StringGet(key);

            var lockInfo = GetPlayerLockInfo(value);

            if (lockInfo == null)
                return true; //没被锁

            var db = redisManager.GetDataBase();

            bool r = !lockInfo.isFriend || force;

            if (!r)
            {
                //只能本模块可以解锁
                r = myUtil.Equals(lockInfo.util);
            }

            if (r)
            {
                //解锁玩家
                var tran = db.CreateTransaction();
                tran.AddCondition(Condition.StringEqual(key, value));
                tran.KeyDeleteAsync(key);
                r = tran.Execute();
            }

            return r;
        }

        /// <summary>
        /// 异步获取玩家数据
        /// </summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        public static Task<playerData> GetPlayerDataAsync(int userid)
        {
            return Task<playerData>.Factory.StartNew(
                () => GetPlayerData(userid)
            );
        }

        /// <summary>
        /// 写玩家数据
        /// </summary>
        /// <param name="pdc"></param>
        //public static async Task WritePlayerData(Game.Data.PlayerDataChange pdc) {
        public static void WritePlayerDataOnDataCenter(GameData.PlayerDataChange pdc)
        {
            PackHead head = PackHead.Create();
            head.userid = pdc.userid;
            head.packlx = ServerPackAgreement.WritePlayerData;
            GlobalMQ.instance.DeliveryEveryOne((int)ModuleType.DataCenter, GlobalMQ.CreateMessage(head, pdc), true,
                true);
            head.Dispose();
        }

        /// <summary>
        /// 保存玩家数据
        /// </summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        public static void SavePlayerDataOnDataCenter(int userid)
        {
            Thread thread = new Thread(
                () =>
                {
                    //保存玩家数据
                    PackHead head = PackHead.Create();
                    head.userid = userid;
                    head.packlx = ServerPackAgreement.SavePlayerData;

                    GlobalMQ.instance.Call_EveryOne((int)ModuleType.DataCenter, head, null);
                    head.Dispose();
                }
            );
            thread.Start();
        }

        /// <summary>
        /// 把变化的数据添加到玩家数据上
        /// </summary>
        /// <param name="pl"></param>
        /// <param name="pdc"></param>
        public static void PlayerAddChangeData(playerData pl, GameData.PlayerDataChange pdc)
        {
            pl.money += pdc.moneyChange;
            pl.coupon += pdc.couponChange;
            pl.exp += pdc.expChange;
            pl.fangka += pdc.fangkaChange;
            pl.gold += pdc.goldChange;
            pl.MatchRoll += pdc.MatchRollChange;
        }

        /// <summary>
        /// 获取玩家数据
        /// </summary>
        /// <returns></returns>
        public static playerData GetPlayerData(int userid)
        {
            //第一次读取
            var result = ReadPlayerData(userid);
            if (!result.Item1)
            {
                //不成功，尝试第二次读取
                result = ReadPlayerData(userid);
                if (result.Item1)
                {
                    //成功
                    result.Item2.isUpper = 1;
                }
                else
                {
                    //还是不成功，发到 DataCenter 请求读取
                    Debug.Warning($"PlayerFun GetPlayerData {userid} 第二次读取还未成功！");
                    //return GetPlayerDataOnDataCenter(userid);
                    return result.Item2;
                }
            }

            return result.Item2;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        public static Tuple<bool, playerData> ReadPlayerData(int userid)
        {
            playerData pl = new playerData();
            try
            {
                //这是RPC方式
                //3种情况 
                //1.redis 有 且与 sql版本一致 直接返回 redis中的数据
                //2.redis 有 而与 sql版本不一致 sql数据+redis增量返回 -> 异步更新 redis 原始数据+版本
                //3.redis 无 直接取 sql数据 -> 异步更新 redis原始数据 + 版本
                pl.userid = userid;

                //拿到redis 数据 ,没拿到 ver 就是-1 拿到则有信息
                GameData.PlayerDataChange pdc;
                int ver = PlayerDataManager.LoadRedisPlayerData(pl, out pdc);

                //如果 ver 与数据库版本不一致 则会从新加载 并更新redis
                if (!PlayerDataManager.LoadSqlPlayerData(pl, ver, pdc))
                {
                    Debug.Warning("加载玩家失败!" + ver + ",没有这个玩家:" + pl.userid);
                    pl = null;
                }
                ReferencePool.Recycle(pdc);
            }
            catch (Exception e)
            {
                Debug.Error("读取玩家数据时出错!" + userid + "," + e.ToString());
                pl = null;
            }

            return new Tuple<bool, playerData>(true, pl);
        }


        /// <summary>
        /// 保存玩家数据
        /// </summary>
        /// <param name="userid"></param>
        /// <returns></returns>
        public static Thread SavePlayerData(int userid)
        {
            Thread thread = new Thread(
                () =>
                {
                    var succ = PlayerDataManager.SaveToSql(userid);
                    if (!succ)
                    {
                        Debug.Warning($"PlayerFun SavePlayerData {userid} 第二次读取还未成功！");
                        //发送到 DataCenter 请求保存操作
                        SavePlayerDataOnDataCenter(userid);
                    }
                }
            );
            thread.Start();
            return thread;
        }

        /// <summary>
        /// 写入玩家数据
        /// </summary>
        /// <param name="pdc"></param>
        /// <returns></returns>
        public static bool WritePlayerDataInternal(GameData.PlayerDataChange pdc)
        {
            bool isOk = true;
            Debug.ImportantLog("写库:" + pdc.ToString());

            //写入成功了
            if (PlayerDataManager.WriteToRedis(pdc))
            {
                //LastWriteList.Add(pdc.userid);
                Debug.ImportantLog("写库完成!");
            }
            else
            {
                //写入失败,先冲sql中加载数据 //再写
                playerData pl = new playerData()
                {
                    userid = pdc.userid
                };

                if (!PlayerDataManager.LoadSqlPlayerData(pl, -1, null))
                {
                    Debug.Error("没有这个玩家，无法写库:" + pl.userid);
                    return isOk;
                }
                else
                {
                    if (!PlayerDataManager.WriteToRedis(pdc))
                    {
                        Debug.Fatal($"还是写库失败!!! 把包退回去! {pdc.ToString()}");
                        isOk = false;
                        //if (msg != null)
                        //    msg.NAck(false, true);
                    }
                    else
                    {
                        //LastWriteList.Add(pdc.userid);
                    }
                }
            }

            return isOk;
        }

        private static void WritePlayerData(PlayerDataChange pdc)
        {
            if (pdc.userid < 0) return;
            bool isOk = false;
            try
            {
                isOk = WritePlayerDataInternal(pdc);
            }
            catch (Exception ex)
            {
                Debug.Error("WritePlayerData: " + ex);
            }

            if (!isOk)
            {
                Debug.Warning($"PlayerFun WritePlayerData {pdc.userid} 未成功！");
                try
                {
                    //发送到 DataCenter 请求写入操作
                    WritePlayerDataOnDataCenter(pdc);
                }
                catch (Exception ex)
                {
                    Debug.Error("WritePlayerDataOnDataCenter: " + ex);
                }
            }

            ReferencePool.Recycle(pdc);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="pdc"></param>
        public static Task WritePlayerDataAsync(PlayerDataChange pdc)
        {
            if (pdc.userid > 0)
                return Task.Run(() => WritePlayerData(pdc));
            else
            {
                ReferencePool.Recycle(pdc);
                return Task.CompletedTask;
            }
        }
    }
}