﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using StackExchange.Redis;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;

namespace Wicture.DbRESTFul.Redis
{
    public class RedisContext
    {
        private static ConnectionMultiplexer instance;
        private static object locker = new object();
        
        private static IDatabase GetDatabase(int dbIndex = 0)
        {
            if (instance == null || !instance.IsConnected || !instance.GetDatabase(RedisConnectionManager.DbIndex).IsConnected(default(RedisKey)))
            {
                try
                {
                    lock (locker)
                    {
                        if (instance != null && (!instance.IsConnected || !instance.GetDatabase(RedisConnectionManager.DbIndex).IsConnected(default(RedisKey))))
                        {
                            instance.Dispose();
                            instance = null;
                        }

                        if (instance == null)
                        {
                            instance = ConnectionMultiplexer.Connect(RedisConnectionManager.Options);
                        }
                    }
                }
                catch (Exception ex)
                {
                    instance?.Dispose();
                    instance = null;
                    LoggerManager.Logger.LogError(ex, "连接Redis报错");
                    throw;
                }
            }

            return instance.GetDatabase(RedisConnectionManager.DbIndex != dbIndex ? RedisConnectionManager.DbIndex : dbIndex);
        }

        public object StringGet(CRIModel model)
        {
            var data = GetDatabase(model.dbIndex).StringGet(model.key);

            if (model.resultType == ResultType.Object) return JObject.Parse(data.ToString());
            else if (model.resultType == ResultType.String) return data.ToString();

            return data;
        }

        public object StringSet(CRIModel model)
        {
            var val = (model.param is string || !model.param.GetType().IsByRef) ? model.param : JsonConvert.SerializeObject(model.param);

            return new { success = GetDatabase(model.dbIndex).StringSet(model.key, val.ToString()) };
        }

        public object ListPop(CRIModel model)
        {
            var data = GetDatabase(model.dbIndex).ListLeftPop(model.key);

            if (model.resultType == ResultType.Object) return JToken.Parse(data.ToString());
            else if (model.resultType == ResultType.String) return data.ToString();

            return data;
        }

        public object ListRange(CRIModel model)
        {
            var data = GetDatabase(model.dbIndex).ListRange(model.key);

            if (model.resultType == ResultType.Object) return data.Select(v => JToken.Parse(v.ToString()));
            else if (model.resultType == ResultType.String) return data.Select(v => v.ToString());

            return data;
        }

        public object ListPush(CRIModel model)
        {
            var val = (model.param is string || !model.param.GetType().IsByRef) ? model.param : JsonConvert.SerializeObject(model.param);
            var result = GetDatabase(model.dbIndex).ListRightPush(model.key, val.ToString());

            return new { length = result };
        }

        public object KeyDelete(CRIModel model)
        {
            return new { success = GetDatabase(model.dbIndex).KeyDelete(model.key) };
        }

        public object SetAdd(CRIModel model)
        {
            var val = (model.param is string || !model.param.GetType().IsByRef) ? model.param : JsonConvert.SerializeObject(model.param);

            return new { success = GetDatabase(model.dbIndex).SetAdd(model.key, val.ToString()) }; 
        }

        public bool Exists(string key)
        {
            try
            {
                return GetDatabase().KeyExists(key); 
            }
            catch (Exception ex)
            {
                LoggerManager.Logger.LogError(ex, "KeyExists failed.");
                throw;
            }
        }

        public T GetDataOrCache<TP, T>(string key, TP parameter, Func<TP, T> action, long expiration = 5 * 60)
        {
            T data = default(T);

            var db = GetDatabase();
            if (!db.KeyExists(key))
            {
                data = action(parameter);
                if (!IsNullOrEmpty<T>(data))
                {
                    db.StringSet(key, JsonConvert.SerializeObject(data), TimeSpan.FromSeconds(expiration));
                }
            }
            else
            {
                data = JsonConvert.DeserializeObject<T>(db.StringGet(key));
                if (IsNullOrEmpty<T>(data))
                {
                    data = action(parameter);
                    db.StringSet(key, JsonConvert.SerializeObject(data), TimeSpan.FromSeconds(expiration));
                }
            }

            return data;
        }

        private bool IsNullOrEmpty<T>(T obj)
        {
            return obj == null || obj.Equals(default(T)) || (obj is IEnumerable && (obj as IEnumerable).OfType<object>().Count() == 0);
        }

        /// <summary>
        /// 设置缓存
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expiration"></param>
        public bool Set(string key, object value, long expiration = 5 * 60)
        {
            return GetDatabase().StringSet(key, JsonConvert.SerializeObject(value), TimeSpan.FromSeconds(expiration));
        }

        #region 锁
        /// <summary>
        /// 加锁
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <param name="expiration"></param>
        /// <returns></returns>
        public bool LockTake(string key, string value, long expiration = 5 * 60)
        {
            return GetDatabase().LockTake(key, value, TimeSpan.FromSeconds(expiration));
        }
        /// <summary>
        /// 释放锁
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool LockRelease(string key, string value)
        {
            return GetDatabase().LockRelease(key, value);
        }
        #endregion 
    }
}