using System;
using System.Globalization;
using Xunit;

namespace StackExchange.Redis.Tests
{
    public class KeysAndValues
    {
        [Fact]
        public void TestValues()
        {
            RedisValue @default = default(RedisValue);
            CheckNull(@default);

            RedisValue nullString = (string)null;
            CheckNull(nullString);

            RedisValue nullBlob = (byte[])null;
            CheckNull(nullBlob);

            RedisValue emptyString = "";
            CheckNotNull(emptyString);

            RedisValue emptyBlob = new byte[0];
            CheckNotNull(emptyBlob);

            RedisValue a0 = new string('a', 1);
            CheckNotNull(a0);
            RedisValue a1 = new string('a', 1);
            CheckNotNull(a1);
            RedisValue b0 = new[] { (byte)'b' };
            CheckNotNull(b0);
            RedisValue b1 = new[] { (byte)'b' };
            CheckNotNull(b1);

            RedisValue i4 = 1;
            CheckNotNull(i4);
            RedisValue i8 = 1L;
            CheckNotNull(i8);

            RedisValue bool1 = true;
            CheckNotNull(bool1);
            RedisValue bool2 = false;
            CheckNotNull(bool2);
            RedisValue bool3 = true;
            CheckNotNull(bool3);

            CheckSame(a0, a0);
            CheckSame(a1, a1);
            CheckSame(a0, a1);

            CheckSame(b0, b0);
            CheckSame(b1, b1);
            CheckSame(b0, b1);

            CheckSame(i4, i4);
            CheckSame(i8, i8);
            CheckSame(i4, i8);

            CheckSame(bool1, bool3);
            CheckNotSame(bool1, bool2);
        }

        private void CheckSame(RedisValue x, RedisValue y)
        {
            Assert.True(Equals(x, y));
            Assert.True(x.Equals(y));
            Assert.True(y.Equals(x));
            Assert.True(x.GetHashCode() == y.GetHashCode());
        }

        private void CheckNotSame(RedisValue x, RedisValue y)
        {
            Assert.False(Equals(x, y));
            Assert.False(x.Equals(y));
            Assert.False(y.Equals(x));
            Assert.False(x.GetHashCode() == y.GetHashCode()); // well, very unlikely
        }

        private void CheckNotNull(RedisValue value)
        {
            Assert.False(value.IsNull);
            Assert.NotNull((byte[])value);
            Assert.NotNull((string)value);
            Assert.NotEqual(-1, value.GetHashCode());

            Assert.NotNull((string)value);
            Assert.NotNull((byte[])value);

            CheckSame(value, value);
            CheckNotSame(value, default(RedisValue));
            CheckNotSame(value, (string)null);
            CheckNotSame(value, (byte[])null);
        }

        private void CheckNull(RedisValue value)
        {
            Assert.True(value.IsNull);
            Assert.True(value.IsNullOrEmpty);
            Assert.False(value.IsInteger);
            Assert.Equal(-1, value.GetHashCode());

            Assert.Null((string)value);
            Assert.Null((byte[])value);

            Assert.Equal(0, (int)value);
            Assert.Equal(0L, (long)value);

            CheckSame(value, value);
            CheckSame(value, default(RedisValue));
            CheckSame(value, (string)null);
            CheckSame(value, (byte[])null);
        }

        [Fact]
        public void ValuesAreConvertible()
        {
            RedisValue val = 123;
            object o = val;
            byte[] blob = (byte[])Convert.ChangeType(o, typeof(byte[]));

            Assert.Equal(3, blob.Length);
            Assert.Equal((byte)'1', blob[0]);
            Assert.Equal((byte)'2', blob[1]);
            Assert.Equal((byte)'3', blob[2]);

            Assert.Equal((double)123, Convert.ToDouble(o));

            IConvertible c = (IConvertible)o;
            Assert.Equal((short)123, c.ToInt16(CultureInfo.InvariantCulture));
            Assert.Equal((int)123, c.ToInt32(CultureInfo.InvariantCulture));
            Assert.Equal((long)123, c.ToInt64(CultureInfo.InvariantCulture));
            Assert.Equal((float)123, c.ToSingle(CultureInfo.InvariantCulture));
            Assert.Equal("123", c.ToString(CultureInfo.InvariantCulture));
            Assert.Equal((double)123, c.ToDouble(CultureInfo.InvariantCulture));
            Assert.Equal((decimal)123, c.ToDecimal(CultureInfo.InvariantCulture));
            Assert.Equal((ushort)123, c.ToUInt16(CultureInfo.InvariantCulture));
            Assert.Equal((uint)123, c.ToUInt32(CultureInfo.InvariantCulture));
            Assert.Equal((ulong)123, c.ToUInt64(CultureInfo.InvariantCulture));

            blob = (byte[])c.ToType(typeof(byte[]), CultureInfo.InvariantCulture);
            Assert.Equal(3, blob.Length);
            Assert.Equal((byte)'1', blob[0]);
            Assert.Equal((byte)'2', blob[1]);
            Assert.Equal((byte)'3', blob[2]);
        }

        [Fact]
        public void CanBeDynamic()
        {
            RedisValue val = "abc";
            object o = val;
            dynamic d = o;
            byte[] blob = (byte[])d; // could be in a try/catch
            Assert.Equal(3, blob.Length);
            Assert.Equal((byte)'a', blob[0]);
            Assert.Equal((byte)'b', blob[1]);
            Assert.Equal((byte)'c', blob[2]);
        }

        [Fact]
        public void TryParse()
        {
            {
                RedisValue val = "1";
                Assert.True(val.TryParse(out int i));
                Assert.Equal(1, i);
                Assert.True(val.TryParse(out long l));
                Assert.Equal(1L, l);
                Assert.True(val.TryParse(out double d));
                Assert.Equal(1.0, l);
            }

            {
                RedisValue val = "8675309";
                Assert.True(val.TryParse(out int i));
                Assert.Equal(8675309, i);
                Assert.True(val.TryParse(out long l));
                Assert.Equal(8675309L, l);
                Assert.True(val.TryParse(out double d));
                Assert.Equal(8675309.0, l);
            }

            {
                RedisValue val = "3.14159";
                Assert.True(val.TryParse(out double d));
                Assert.Equal(3.14159, d);
            }

            {
                RedisValue val = "not a real number";
                Assert.False(val.TryParse(out int i));
                Assert.False(val.TryParse(out long l));
                Assert.False(val.TryParse(out double d));
            }
        }
    }
}
