// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.SignalR.Internal;
using Xunit;

namespace Microsoft.AspNetCore.SignalR.Common.Tests.Internal.Formatters
{
    public partial class BinaryMessageFormatterTests
    {
        [Fact]
        public void WriteMultipleMessages()
        {
            var expectedEncoding = new byte[]
            {
                /* length: */ 0x00,
                    /* body: <empty> */
                /* length: */ 0x0E,
                    /* body: */ 0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x2C, 0x0D, 0x0A, 0x57, 0x6F, 0x72, 0x6C, 0x64, 0x21,
            };

            var messages = new[]
            {
                new byte[0],
                Encoding.UTF8.GetBytes("Hello,\r\nWorld!")
            };

            var writer = MemoryBufferWriter.Get(); // Use small chunks to test Advance/Enlarge and partial payload writing
            try
            {
                foreach (var message in messages)
                {
                    BinaryMessageFormatter.WriteLengthPrefix(message.Length, writer);
                    writer.Write(message);
                }

                Assert.Equal(expectedEncoding, writer.ToArray());
            }
            finally
            {
                MemoryBufferWriter.Return(writer);
            }
        }

        [Theory]
        [InlineData(new byte[] { 0x00 }, new byte[0])]
        [InlineData(new byte[]
            {
                0x80, 0x01, // Size - 128
                0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
                0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
                0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
                0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
                0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
                0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
                0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
                0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
            },
            new byte[]
            {
                0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
                0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
                0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
                0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
                0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f,
                0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f,
                0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f,
                0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f
            })]

        [InlineData(new byte[] { 0x04, 0xAB, 0xCD, 0xEF, 0x12 }, new byte[] { 0xAB, 0xCD, 0xEF, 0x12 })]
        public void WriteBinaryMessage(byte[] encoded, byte[] payload)
        {
            var writer = MemoryBufferWriter.Get();
            try
            {

                BinaryMessageFormatter.WriteLengthPrefix(payload.Length, writer);
                writer.Write(payload);

                Assert.Equal(encoded, writer.ToArray());
            }
            finally
            {
                MemoryBufferWriter.Return(writer);
            }
        }

        [Theory]
        [InlineData(new byte[] { 0x00 }, "")]
        [InlineData(new byte[] { 0x03, 0x41, 0x42, 0x43 }, "ABC")]
        [InlineData(new byte[] { 0x0B, 0x41, 0x0A, 0x52, 0x0D, 0x43, 0x0D, 0x0A, 0x3B, 0x44, 0x45, 0x46 }, "A\nR\rC\r\n;DEF")]
        public void WriteTextMessage(byte[] encoded, string payload)
        {
            var message = Encoding.UTF8.GetBytes(payload);
            var writer = MemoryBufferWriter.Get();
            try
            {

                BinaryMessageFormatter.WriteLengthPrefix(message.Length, writer);
                writer.Write(message);

                Assert.Equal(encoded, writer.ToArray());
            }
            finally
            {
                MemoryBufferWriter.Return(writer);
            }
        }

        [Theory]
        [MemberData(nameof(RandomPayloads))]
        public void RoundTrippingTest(byte[] payload)
        {
            var writer = MemoryBufferWriter.Get();
            try
            {
                BinaryMessageFormatter.WriteLengthPrefix(payload.Length, writer);
                writer.Write(payload);
                var buffer = new ReadOnlySequence<byte>(writer.ToArray());
                Assert.True(BinaryMessageParser.TryParseMessage(ref buffer, out var roundtripped));
                Assert.Equal(payload, roundtripped.ToArray());
            }
            finally
            {
                MemoryBufferWriter.Return(writer);
            }
        }

        public static IEnumerable<object[]> RandomPayloads()
        {
            // boundaries
            yield return new[] { CreatePayload(0) };
            yield return new[] { CreatePayload(1) };
            yield return new[] { CreatePayload(0x7f) };
            yield return new[] { CreatePayload(0x80) };
            yield return new[] { CreatePayload(0x3fff) };
            yield return new[] { CreatePayload(0x4000) };

            // random
            yield return new[] { CreatePayload(0xc0de) };
        }


        private static byte[] CreatePayload(int size) =>
            Enumerable.Range(0, size).Select(n => (byte)(n & 0xff)).ToArray();
    }
}
