using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;


namespace SFI.COMM.LIB.Redis
{
    public class Redis : RedisClient
    {
         
        public Redis(string host, int port, string password) : base(host, port, password)
        {
        }

        public Redis(string host, string password) : this(host, 6379, password)
        {
        }

        public Redis(string password) : this("localhost", 6379, password)
        {
        }

        #region Connection command additions
        int db;
        public int Db
        {
            get
            {
                return db;
            }

            set
            {
                db = value;
                SendExpectSuccess("SELECT", db);
            }
        }
        #endregion

        #region String commands
        public void Set(string key, string value)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                if (value == null)
                    throw new ArgumentNullException("value");

                Set(key, Encoding.UTF8.GetBytes(value));
            }
            catch (Exception ex)
            {

            }
            
        }

        public void Set(string key, byte[] value)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                if (value == null)
                    throw new ArgumentNullException("value");

                if (value.Length > 1073741824)
                    throw new ArgumentException("value exceeds 1G", "value");

                if (!SendDataCommand(value, "SET", key))
                    throw new Exception("Unable to connect");
                ExpectSuccess();
            }
            catch (Exception ex)
            {

            }            
        }



        public void HSet(string key, string value)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                if (value == null)
                    throw new ArgumentNullException("value");

                HSet(key, Encoding.UTF8.GetBytes(value));
            }
            catch (Exception ex)
            {

            }

        }

        public void HSet(string key, byte[] value)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                if (value == null)
                    throw new ArgumentNullException("value");

                if (value.Length > 1073741824)
                    throw new ArgumentException("value exceeds 1G", "value");

                if (!SendDataCommand(value, "HSET", key))
                    throw new Exception("Unable to connect");
                ExpectSuccess();
            }
            catch (Exception ex)
            {

            }
        }


        public bool SetNX(string key, string value)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                if (value == null)
                    throw new ArgumentNullException("value");

                return SetNX(key, Encoding.UTF8.GetBytes(value));
            }
            catch (Exception ex)
            {
                return false;
            }

            
        }

        public bool SetNX(string key, byte[] value)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                if (value == null)
                    throw new ArgumentNullException("value");

                if (value.Length > 1073741824)
                    throw new ArgumentException("value exceeds 1G", "value");

                return SendDataExpectInt(value, "SETNX", key) > 0 ? true : false;
            }
            catch (Exception ex)
            {
                return false;
            }

            
        }

        public void MSet(string[] keys, byte[][] values)
        {
            try
            {
                SendTuplesCommand(keys, values, "MSET");
                ExpectSuccess();
            }
            catch (Exception ex)
            {

            }
            
        }

        public byte[] Get(string key)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                return SendExpectData("GET", key);
            }
            catch (Exception ex)
            {
                return null;
            }

            
        }

        public string GetString(string key)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                return Encoding.UTF8.GetString(Get(key));
            }
            catch (Exception ex)
            {
                return string.Empty;
            }           
        }

        public byte[] GetSet(string key, byte[] value)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                if (value == null)
                    throw new ArgumentNullException("value");

                if (value.Length > 1073741824)
                    throw new ArgumentException("value exceeds 1G", "value");

                if (!SendDataCommand(value, "GETSET", key))
                    throw new Exception("Unable to connect");

                return ReadData();
            }
            catch (Exception ex)
            {
                return null;
            }

            
        }

        public string GetSet(string key, string value)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                if (value == null)
                    throw new ArgumentNullException("value");
                return Encoding.UTF8.GetString(GetSet(key, Encoding.UTF8.GetBytes(value)));
            }
            catch (Exception ex)
            {
                return string.Empty;
            }

            
        }

        public byte[][] MGet(params string[] keys)
        {
            try
            {
                if (keys == null)
                    throw new ArgumentNullException("keys");
                if (keys.Length == 0)
                    throw new ArgumentException("keys");

                return SendExpectDataArray("MGET", keys);
            }
            catch (Exception ex)
            {
                return null;
            }

           
        }
        #endregion

        #region String command additions
        public string this[string key]
        {
            get { return GetString(key); }
            set { Set(key, value); }
        }

        public void Set(IDictionary<string, string> dict)
        {
            try
            {
                if (dict == null)
                    throw new ArgumentNullException("dict");

                Set(dict.ToDictionary(k => k.Key, v => Encoding.UTF8.GetBytes(v.Value)));
            }
            catch (Exception ex)
            {

            }

           
        }

        public void Set(IDictionary<string, byte[]> dict)
        {
            try
            {
                if (dict == null)
                    throw new ArgumentNullException("dict");

                MSet(dict.Keys.ToArray(), dict.Values.ToArray());
            }
            catch (Exception ex)
            {

            }

           
        }

        public int Increment(string key)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                return SendExpectInt("INCR", key);
            }
            catch (Exception ex)
            {
                return 0;
            }

            
        }

        public int Increment(string key, int count)
        { 
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                return SendExpectInt("INCRBY", key, count);
            }
            catch (Exception ex)
            {
                return 0;
            }
           
        }

        public int Decrement(string key)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                return SendExpectInt("DECR", key);
            }
            catch (Exception ex)
            {
                return 0;
            }            
        }

        public int Decrement(string key, int count)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                return SendExpectInt("DECRBY", key, count);
            }
            catch (Exception ex)
            {
                return 0;
            }

            
        }
        #endregion

        #region Key commands
        public string[] Scan(ref int cursor, params object[] args)
        {
            SendCommand("SCAN", PrependArgs(args, cursor));
            object[] result = ReadMixedArray();
            cursor = int.Parse(ToString(result[0] as byte[]));
            object[] dataArray = result[1] as object[];

            string[] keys = new string[dataArray.Length];
            for (int i = 0; i < keys.Length; i++)
                keys[i] = ToString(dataArray[i] as byte[]);
            return keys;
        }

        public byte[][] Sort(string key, params object[] options)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");

                SendCommand("SORT", PrependArgs(options, key));
                return ReadDataArray();
            }
            catch (Exception ex)
            {
                return null;
            }

            
        }

        public int SortStore(string destination, string key, params object[] options)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                if (destination == null)
                    throw new ArgumentNullException("destination");

                SendCommand("SORT", PrependArgs(options, key, "STORE", destination));
                return ReadInt();
            }
            catch (Exception ex)
            {
                return 0;
            }            
        }

        public string RandomKey()
        {
            return SendExpectString("RANDOMKEY");
        }

        public bool Rename(string oldKeyname, string newKeyname)
        {
            try
            {
                if (oldKeyname == null)
                    throw new ArgumentNullException("oldKeyname");
                if (newKeyname == null)
                    throw new ArgumentNullException("newKeyname");
                return SendGetString("RENAME", oldKeyname, newKeyname)[0] == '+';
            }
            catch (Exception ex)
            {
                return false;
            }

            
        }

        public bool Expire(string key, int seconds)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                return SendExpectInt("EXPIRE", key, seconds) == 1;
            }
            catch (Exception ex)
            {
                return false;
            }

          
        }

        public bool ExpireAt(string key, int time)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                return SendExpectInt("EXPIREAT", key, time) == 1;
            }
            catch (Exception ex)
            {
                return false;
            }

           
        }
        #endregion

        #region Key command additions
        public string[] Keys
        {
            get
            {
                return GetKeys("*");
            }
        }

        public string[] GetKeys(string pattern)
        {
            try
            {
                if (pattern == null)
                    throw new ArgumentNullException("pattern");

                return SendExpectStringArray("KEYS", pattern);
            }
            catch (Exception ex)
            {
                return null;
            }

           
        }

        public byte[][] Sort(SortOptions options)
        {
            if (options.StoreInKey != null)
            {
                int n = SortStore(options.StoreInKey, options.Key, options.ToArgs());
                return new byte[n][];
            }
            else
            {
                return Sort(options.Key, options.ToArgs());
            }
        }

        public bool ContainsKey(string key)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                return SendExpectInt("EXISTS", key) == 1;
            }
            catch (Exception ex)
            {
                return false;
            }

            
        }

        public bool Remove(string key)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                return SendExpectInt("DEL", key) == 1;
            }
            catch (Exception ex)
            {
                return false;
            }

            
        }

        public int Remove(params string[] args)
        {
            try
            {
                if (args == null)
                    throw new ArgumentNullException("args");
                return SendExpectInt("DEL", args);
            }
            catch (Exception ex)
            {
                return 0;
            }

            
        }

        public KeyType TypeOf(string key)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                switch (SendExpectString("TYPE", key))
                {
                    case "none":
                        return KeyType.None;
                    case "string":
                        return KeyType.String;
                    case "set":
                        return KeyType.Set;
                    case "list":
                        return KeyType.List;
                }
                throw new ResponseException("Invalid value");
            }
            catch (Exception ex)
            {
                return KeyType.None;
            }

           
        }

        public int TimeToLive(string key)
        {
            try
            {
                if (key == null)
                    throw new ArgumentNullException("key");
                return SendExpectInt("TTL", key);
            }
            catch (Exception ex)
            {
                return 0;
            }

            
        }
        #endregion

        #region Server commands
        public int DbSize
        {
            get
            {
                return SendExpectInt("DBSIZE");
            }
        }

        public void Save()
        {
            SendExpectSuccess("SAVE");
        }

        public void FlushAll()
        {
            SendExpectSuccess("FLUSHALL");
        }

        public void FlushDb()
        {
            SendExpectSuccess("FLUSHDB");
        }

        const long UnixEpoch = 621355968000000000L;

        public DateTime LastSave
        {
            get
            {
                int t = SendExpectInt("LASTSAVE");

                return new DateTime(UnixEpoch) + TimeSpan.FromSeconds(t);
            }
        }
        #endregion

        #region Server command additions
        public void BackgroundSave()
        {
            SendExpectSuccess("BGSAVE");
        }

        public Dictionary<string, string> GetInfo()
        {
            byte[] r = SendExpectData("INFO");
            var dict = new Dictionary<string, string>();

            foreach (var line in Encoding.UTF8.GetString(r).Split('\n'))
            {
                int p = line.IndexOf(':');
                if (p == -1)
                    continue;
                dict.Add(line.Substring(0, p), line.Substring(p + 1));
            }
            return dict;
        }
        #endregion

        #region List command additions
        public byte[][] ListRange(string key, int start, int end)
        {
            return SendExpectDataArray("LRANGE", key, start, end);
        }

        public void LeftPush(string key, string value)
        {
            LeftPush(key, Encoding.UTF8.GetBytes(value));
        }

        public void LeftPush(string key, byte[] value)
        {
            SendDataCommand(value, "LPUSH", key);
            ExpectSuccess();
        }

        public void RightPush(string key, string value)
        {
            RightPush(key, Encoding.UTF8.GetBytes(value));
        }

        public void RightPush(string key, byte[] value)
        {
            SendDataCommand(value, "RPUSH", key);
            ExpectSuccess();
        }

        public int ListLength(string key)
        {
            return SendExpectInt("LLEN", key);
        }

        public byte[] ListIndex(string key, int index)
        {
            SendCommand("LINDEX", key, index);
            return ReadData();
        }

        public byte[] LeftPop(string key)
        {
            SendCommand("LPOP", key);
            return ReadData();
        }

        public byte[] RightPop(string key)
        {
            SendCommand("RPOP", key);
            return ReadData();
        }
        #endregion

        #region Set command additions
        public bool AddToSet(string key, byte[] member)
        {
            return SendDataExpectInt(member, "SADD", key) > 0;
        }

        public bool AddToSet(string key, string member)
        {
            return AddToSet(key, Encoding.UTF8.GetBytes(member));
        }

        public int CardinalityOfSet(string key)
        {
            return SendExpectInt("SCARD", key);
        }

        public bool IsMemberOfSet(string key, byte[] member)
        {
            return SendDataExpectInt(member, "SISMEMBER", key) > 0;
        }

        public bool IsMemberOfSet(string key, string member)
        {
            return IsMemberOfSet(key, Encoding.UTF8.GetBytes(member));
        }

        public byte[][] GetMembersOfSet(string key)
        {
            return SendExpectDataArray("SMEMBERS", key);
        }

        public byte[] GetRandomMemberOfSet(string key)
        {
            return SendExpectData("SRANDMEMBER", key);
        }

        public byte[] PopRandomMemberOfSet(string key)
        {
            return SendExpectData("SPOP", key);
        }

        public bool RemoveFromSet(string key, byte[] member)
        {
            try
            {
                return SendDataExpectInt(member, "SREM", key) > 0;
            }
            catch (Exception ex)
            {
                return false;
            }
            
        }

        public bool RemoveFromSet(string key, string member)
        {
            try
            {
                return RemoveFromSet(key, Encoding.UTF8.GetBytes(member));
            }
            catch (Exception ex)
            {
                return false;
            }            
        }

        public byte[][] GetUnionOfSets(params string[] keys)
        {
            try
            {
                if (keys == null)
                    throw new ArgumentNullException();

                return SendExpectDataArray("SUNION", keys);
            }
            catch (Exception ex)
            {
                return null;
            }            
        }

        void StoreSetCommands(string cmd, params string[] keys)
        {
            try
            {
                if (string.IsNullOrEmpty(cmd))
                    throw new ArgumentNullException("cmd");

                if (keys == null)
                    throw new ArgumentNullException("keys");

                SendExpectSuccess(cmd, keys);
            }
            catch (Exception ex)
            {
                
            }            
        }

        public void StoreUnionOfSets(params string[] keys)
        {
            StoreSetCommands("SUNIONSTORE", keys);
        }

        public byte[][] GetIntersectionOfSets(params string[] keys)
        {
            try
            {
                if (keys == null)
                    throw new ArgumentNullException();

                return SendExpectDataArray("SINTER", keys);
            }
            catch (Exception ex)
            {
                return null;
            }           
        }

        public void StoreIntersectionOfSets(params string[] keys)
        {
            StoreSetCommands("SINTERSTORE", keys);
        }

        public byte[][] GetDifferenceOfSets(params string[] keys)
        {
            try
            {
                if (keys == null)
                    throw new ArgumentNullException();

                return SendExpectDataArray("SDIFF", keys);
            }
            catch (Exception ex)
            {
                return null;
            }
            
        }

        public void StoreDifferenceOfSets(params string[] keys)
        {
            StoreSetCommands("SDIFFSTORE", keys);
        }

        public bool MoveMemberToSet(string srcKey, string destKey, byte[] member)
        {
            return SendDataExpectInt(member, "SMOVE", srcKey, destKey) > 0;
        }
        #endregion

        #region Pub commands
        public int Publish(string channel, string message)
        {
            return Publish(channel, Encoding.UTF8.GetBytes(message));
        }

        public int Publish(string channel, byte[] message)
        {
            try
            {
                if (channel == null)
                    throw new ArgumentNullException("channel");
                if (message == null)
                    throw new ArgumentNullException("message");

                return SendDataExpectInt(message, "PUBLISH", channel);
            }
            catch (Exception ex)
            {
                return 0;
            }            
        }
        #endregion

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                SendCommand("QUIT");
                ExpectSuccess();
            }
            base.Dispose(disposing);
        }
    }

    public class SortOptions
    {
        public string Key { get; set; }
        public bool Descending { get; set; }
        public bool Lexographically { get; set; }
        public int LowerLimit { get; set; }
        public int UpperLimit { get; set; }
        public string By { get; set; }
        public string StoreInKey { get; set; }
        public string Get { get; set; }

        public object[] ToArgs()
        {
            System.Collections.ArrayList args = new System.Collections.ArrayList();

            if (LowerLimit != 0 || UpperLimit != 0)
            {
                args.Add("LIMIT");
                args.Add(LowerLimit);
                args.Add(UpperLimit);
            }
            if (Lexographically)
                args.Add("ALPHA");
            if (!string.IsNullOrEmpty(By))
            {
                args.Add("BY");
                args.Add(By);
            }
            if (!string.IsNullOrEmpty(Get))
            {
                args.Add("GET");
                args.Add(Get);
            }
            return args.ToArray();
        }
    }
}
