/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
using System;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using System.IO;

using Avro.IO;

namespace Avro.Test
{
    using Decoder = Avro.IO.Decoder;
    using Encoder = Avro.IO.Encoder;
    delegate T Decode<T>(Decoder d);
    delegate void Skip<T>(Decoder d);
    delegate void Encode<T>(Encoder e, T t);

    /// <summary>
    /// Tests the BinaryEncoder and BinaryDecoder. This is pertty general set of test cases and hence
    /// can be used for any encoder and its corresponding decoder.
    /// </summary>
    [TestFixture]
    public class BinaryCodecTests
    {

        /// <summary>
        /// Writes an avro type T with value t into a stream using the encode method e
        /// and reads it back using the decode method d and verifies that
        /// the value read back is the same as the one written in.
        /// </summary>
        /// <typeparam name="T">Avro type to test</typeparam>
        /// <param name="t">Value for the Avro type to test.</param>
        /// <param name="r">The decode method</param>
        /// <param name="w">The encode method</param>
        private void TestRead<T>(T t, Decode<T> r, Encode<T> w, int size)
        {
            MemoryStream iostr = new MemoryStream();
            Encoder e = new BinaryEncoder(iostr);
            w(e, t);
            iostr.Flush();
            Assert.AreEqual(size, iostr.Length);
            iostr.Position = 0;
            Decoder d = new BinaryDecoder(iostr);
            T actual = r(d);
            Assert.AreEqual(t, actual);
            Assert.AreEqual(-1, iostr.ReadByte());
            iostr.Close();
        }

        /// <summary>
        /// Writes an avro type T with value t into a stream using the encode method e
        /// and reads it back using the decode method d and verifies that
        /// the value read back is the same as the one written in.
        /// </summary>
        /// <typeparam name="T">Avro type to test</typeparam>
        /// <param name="t">Value for the Avro type to test.</param>
        /// <param name="r">The skip method</param>
        /// <param name="w">The encode method</param>
        private void TestSkip<T>(T t, Skip<T> s, Encode<T> w, int size)
        {
            MemoryStream iostr = new MemoryStream();
            Encoder e = new BinaryEncoder(iostr);
            w(e, t);
            iostr.Flush();
            Assert.AreEqual(size, iostr.Length);
            iostr.Position = 0;
            Decoder d = new BinaryDecoder(iostr);
            s(d);
            Assert.AreEqual(-1, iostr.ReadByte());
            iostr.Close();
        }


        [TestCase(true)]
        [TestCase(false)]
        public void TestBoolean(bool b)
        {
            TestRead(b, (Decoder d) => d.ReadBoolean(), (Encoder e, bool t) => e.WriteBoolean(t), 1);
            TestSkip(b, (Decoder d) => d.SkipBoolean(), (Encoder e, bool t) => e.WriteBoolean(t), 1);
        }

        [TestCase(0, 1)]
        [TestCase(1, 1)]
        [TestCase(63, 1)]
        [TestCase(64, 2)]
        [TestCase(8191, 2)]
        [TestCase(8192, 3)]
        [TestCase(1048575, 3)]
        [TestCase(1048576, 4)]
        [TestCase(134217727, 4)]
        [TestCase(134217728, 5)]
        [TestCase(2147483647, 5)]
        [TestCase(-1, 1)]
        [TestCase(-64, 1)]
        [TestCase(-65, 2)]
        [TestCase(-8192, 2)]
        [TestCase(-8193, 3)]
        [TestCase(-1048576, 3)]
        [TestCase(-1048577, 4)]
        [TestCase(-134217728, 4)]
        [TestCase(-134217729, 5)]
        [TestCase(-2147483648, 5)]
        public void TestInt(int n, int size)
        {
            TestRead(n, (Decoder d) => d.ReadInt(), (Encoder e, int t) => e.WriteInt(t), size);
            TestSkip(n, (Decoder d) => d.SkipInt(), (Encoder e, int t) => e.WriteInt(t), size);
        }

        [TestCase(0, 1)]
        [TestCase(1, 1)]
        [TestCase(63, 1)]
        [TestCase(64, 2)]
        [TestCase(8191, 2)]
        [TestCase(8192, 3)]
        [TestCase(1048575, 3)]
        [TestCase(1048576, 4)]
        [TestCase(134217727, 4)]
        [TestCase(134217728, 5)]
        [TestCase(17179869183L, 5)]
        [TestCase(17179869184L, 6)]
        [TestCase(2199023255551L, 6)]
        [TestCase(2199023255552L, 7)]
        [TestCase(281474976710655L, 7)]
        [TestCase(281474976710656L, 8)]
        [TestCase(36028797018963967L, 8)]
        [TestCase(36028797018963968L, 9)]
        [TestCase(4611686018427387903L, 9)]
        [TestCase(4611686018427387904L, 10)]
        [TestCase(9223372036854775807L, 10)]
        [TestCase(-1, 1)]
        [TestCase(-64, 1)]
        [TestCase(-65, 2)]
        [TestCase(-8192, 2)]
        [TestCase(-8193, 3)]
        [TestCase(-1048576, 3)]
        [TestCase(-1048577, 4)]
        [TestCase(-134217728, 4)]
        [TestCase(-134217729, 5)]
        [TestCase(-17179869184L, 5)]
        [TestCase(-17179869185L, 6)]
        [TestCase(-2199023255552L, 6)]
        [TestCase(-2199023255553L, 7)]
        [TestCase(-281474976710656L, 7)]
        [TestCase(-281474976710657L, 8)]
        [TestCase(-36028797018963968L, 8)]
        [TestCase(-36028797018963969L, 9)]
        [TestCase(-4611686018427387904L, 9)]
        [TestCase(-4611686018427387905L, 10)]
        [TestCase(-9223372036854775808L, 10)]
        public void TestLong(long n, int size)
        {
            TestRead(n, (Decoder d) => d.ReadLong(), (Encoder e, long t) => e.WriteLong(t), size);
            TestSkip(n, (Decoder d) => d.SkipLong(), (Encoder e, long t) => e.WriteLong(t), size);
        }

        [TestCase(0.0f)]
        [TestCase(Single.MaxValue, Description = "Max value")]
        [TestCase(1.17549435E-38f, Description = "Min 'normal' value")]
        [TestCase(1.4e-45f, Description = "Min value")]
        public void TestFloat(float n)
        {
            TestRead(n, (Decoder d) => d.ReadFloat(), (Encoder e, float t) => e.WriteFloat(t), 4);
            TestSkip(n, (Decoder d) => d.SkipFloat(), (Encoder e, float t) => e.WriteFloat(t), 4);
        }

        [TestCase(0.0)]
        [TestCase(1.7976931348623157e+308, Description = "Max value")]
        [TestCase(2.2250738585072014E-308, Description = "Min 'normal' value")]
        [TestCase(4.9e-324, Description = "Min value")]
        public void TestDouble(double n)
        {
            TestRead(n, (Decoder d) => d.ReadDouble(), (Encoder e, double t) => e.WriteDouble(t), 8);
            TestSkip(n, (Decoder d) => d.SkipDouble(), (Encoder e, double t) => e.WriteDouble(t), 8);
        }


        [TestCase(0, 1)]
        [TestCase(5, 1)]
        [TestCase(63, 1)]
        [TestCase(64, 2)]
        [TestCase(8191, 2)]
        [TestCase(8192, 3)]
        public void TestBytes(int length, int overhead)
        {
            Random r = new Random();
            byte[] b = new byte[length];
            r.NextBytes(b);
            TestRead(b, (Decoder d) => d.ReadBytes(), (Encoder e, byte[] t) => e.WriteBytes(t), overhead + b.Length);
            TestSkip(b, (Decoder d) => d.SkipBytes(), (Encoder e, byte[] t) => e.WriteBytes(t), overhead + b.Length);
        }

        [TestCase("", 1)]
        [TestCase("hello", 1)]
        [TestCase("1234567890123456789012345678901234567890123456789012345678901234", 2)]
        public void TestString(string n, int overhead)
        {
            TestRead(n, (Decoder d) => d.ReadString(), (Encoder e, string t) => e.WriteString(t), overhead + n.Length);
            TestSkip(n, (Decoder d) => d.SkipString(), (Encoder e, string t) => e.WriteString(t), overhead + n.Length);
        }

        [TestCase(0, 1)]
        [TestCase(1, 1)]
        [TestCase(64, 2)]
        public void TestEnum(int n, int size)
        {
            TestRead(n, (Decoder d) => d.ReadEnum(), (Encoder e, int t) => e.WriteEnum(t), size);
            TestSkip(n, (Decoder d) => d.SkipEnum(), (Encoder e, int t) => e.WriteEnum(t), size);
        }

        [TestCase(1, new int[] { })]
        [TestCase(3, new int[] { 0 })]
        [TestCase(4, new int[] { 64 })]
        public void TestArray(int size, int[] entries)
        {
            TestRead(entries, (Decoder d) =>
            {
                int[] t = new int[entries.Length];
                int j = 0;
                for (long n = d.ReadArrayStart(); n != 0; n = d.ReadArrayNext())
                {
                    for (int i = 0; i < n; i++) { t[j++] = d.ReadInt(); }

                }
                return t;
            },
                (Encoder e, int[] t) =>
                {
                    e.WriteArrayStart();
                    e.SetItemCount(t.Length);
                    foreach (int i in t) { e.StartItem(); e.WriteInt(i); } e.WriteArrayEnd();
                }, size);

            TestSkip(entries, (Decoder d) =>
            {
                for (long n = d.ReadArrayStart(); n != 0; n = d.ReadArrayNext())
                {
                    for (int i = 0; i < n; i++) { d.SkipInt(); }

                }
            },
                (Encoder e, int[] t) =>
                {
                    e.WriteArrayStart();
                    e.SetItemCount(t.Length);
                    foreach (int i in t) { e.StartItem(); e.WriteInt(i); } e.WriteArrayEnd();
                }, size);
        }

        [TestCase(1, new string[] { })]
        [TestCase(6, new string[] { "a", "b" })]
        [TestCase(9, new string[] { "a", "b", "c", "" })]
        public void TestMap(int size, string[] entries)
        {
            TestRead(entries, (Decoder d) =>
            {
                string[] t = new string[entries.Length];
                int j = 0;
                for (long n = d.ReadArrayStart(); n != 0; n = d.ReadArrayNext())
                {
                    for (int i = 0; i < n; i++) { t[j++] = d.ReadString(); t[j++] = d.ReadString(); }

                }
                return t;
            },
                (Encoder e, string[] t) =>
                {
                    e.WriteArrayStart();
                    e.SetItemCount(t.Length / 2);
                    for (int i = 0; i < t.Length; i += 2)
                    {
                        e.StartItem(); e.WriteString(t[i]); e.WriteString(t[i + 1]);
                    }
                    e.WriteArrayEnd();
                }, size);

            TestSkip(entries, (Decoder d) =>
            {
                for (long n = d.ReadArrayStart(); n != 0; n = d.ReadArrayNext())
                {
                    for (int i = 0; i < n; i++) { d.SkipString(); d.SkipString(); }

                }
            },
                (Encoder e, string[] t) =>
                {
                    e.WriteArrayStart();
                    e.SetItemCount(t.Length / 2);
                    for (int i = 0; i < t.Length; i += 2)
                    {
                        e.StartItem(); e.WriteString(t[i]); e.WriteString(t[i + 1]);
                    }
                    e.WriteArrayEnd();
                }, size);
        }

        [TestCase(0, 1)]
        [TestCase(1, 1)]
        [TestCase(64, 2)]
        public void TestUnionIndex(int n, int size)
        {
            TestRead(n, (Decoder d) => d.ReadUnionIndex(), (Encoder e, int t) => e.WriteUnionIndex(t), size);
            TestSkip(n, (Decoder d) => d.SkipUnionIndex(), (Encoder e, int t) => e.WriteUnionIndex(t), size);
        }

        [TestCase(0)]
        [TestCase(1)]
        [TestCase(64)]
        public void TestFixed(int size)
        {
            byte[] b = new byte[size];
            new Random().NextBytes(b);
            TestRead(b, (Decoder d) => { byte[] t = new byte[size]; d.ReadFixed(t); return t; },
                (Encoder e, byte[] t) => e.WriteFixed(t), size);
            TestSkip(b, (Decoder d) => d.SkipFixed(size),
                (Encoder e, byte[] t) => e.WriteFixed(t), size);
        }
    }
}
