// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.

using System.IO;
using System.Text;
using System.Threading;
using Microsoft.TestCommon;
using Moq;

namespace System.Net.Http.Handlers
{
    public class ProgressStreamTest
    {
        [Fact]
        public void Read_ReportsBytesRead()
        {
            // Arrange
            HttpResponseMessage response = CreateResponse();
            Stream innerStream = response.Content.ReadAsStreamAsync().Result;
            long? expectedLength = response.Content.Headers.ContentLength;
            MockProgressEventHandler mockProgressEventHandler;
            ProgressMessageHandler progressMessageHandler = MockProgressEventHandler.CreateProgressMessageHandler(out mockProgressEventHandler, sendProgress: false);
            ProgressStream progressStream = CreateProgressStream(innerStream: innerStream, progressMessageHandler: progressMessageHandler, response: response);

            // Act/Assert
            int totalBytesRead = 0;
            int bytesRead = 0;
            do
            {
                byte[] buffer = new byte[8];
                bytesRead = progressStream.Read(buffer, 0, buffer.Length);
                totalBytesRead += bytesRead;

                Assert.Equal(totalBytesRead, mockProgressEventHandler.EventArgs.BytesTransferred);
                Assert.Equal((100L * totalBytesRead) / expectedLength, mockProgressEventHandler.EventArgs.ProgressPercentage);
            }
            while (bytesRead > 0);

            Assert.Equal(expectedLength, mockProgressEventHandler.EventArgs.TotalBytes);
            Assert.Equal(100, mockProgressEventHandler.EventArgs.ProgressPercentage);
        }

        [Fact]
        public void ReadByte_ReportsBytesRead()
        {
            // Arrange
            HttpResponseMessage response = CreateResponse();
            Stream innerStream = response.Content.ReadAsStreamAsync().Result;
            long? expectedLength = response.Content.Headers.ContentLength;
            MockProgressEventHandler mockProgressEventHandler;
            ProgressMessageHandler progressMessageHandler = MockProgressEventHandler.CreateProgressMessageHandler(out mockProgressEventHandler, sendProgress: false);
            ProgressStream progressStream = CreateProgressStream(innerStream: innerStream, progressMessageHandler: progressMessageHandler, response: response);

            // Act/Assert
            int totalBytesRead = 0;
            while (progressStream.ReadByte() != -1)
            {
                totalBytesRead += 1;

                Assert.Equal(totalBytesRead, mockProgressEventHandler.EventArgs.BytesTransferred);
                Assert.Equal((100L * totalBytesRead) / expectedLength, mockProgressEventHandler.EventArgs.ProgressPercentage);
            }

            Assert.Equal(expectedLength, mockProgressEventHandler.EventArgs.TotalBytes);
            Assert.Equal(100, mockProgressEventHandler.EventArgs.ProgressPercentage);
        }

#if !NETFX_CORE // BeginX and EndX not supported on Streams in portable libraries
        [Fact]
        public void BeginEndRead_ReportsBytesRead()
        {
            // Arrange
            HttpResponseMessage response = CreateResponse();
            Stream innerStream = response.Content.ReadAsStreamAsync().Result;
            long? expectedLength = response.Content.Headers.ContentLength;
            MockProgressEventHandler mockProgressEventHandler;
            ProgressMessageHandler progressMessageHandler = MockProgressEventHandler.CreateProgressMessageHandler(out mockProgressEventHandler, sendProgress: false);
            ProgressStream progressStream = CreateProgressStream(innerStream: innerStream, progressMessageHandler: progressMessageHandler, response: response);
            object userState = new object();

            // Act/Assert
            int totalBytesRead = 0;
            int bytesRead = 0;
            do
            {
                byte[] buffer = new byte[8];
                IAsyncResult result = progressStream.BeginRead(buffer, 0, buffer.Length, null, userState);
                bytesRead = progressStream.EndRead(result);
                totalBytesRead += bytesRead;

                Assert.Same(userState, mockProgressEventHandler.EventArgs.UserState);
                Assert.Equal(totalBytesRead, mockProgressEventHandler.EventArgs.BytesTransferred);
                Assert.Equal((100L * totalBytesRead) / expectedLength, mockProgressEventHandler.EventArgs.ProgressPercentage);
            }
            while (bytesRead > 0);

            Assert.Equal(expectedLength, mockProgressEventHandler.EventArgs.TotalBytes);
            Assert.Equal(100, mockProgressEventHandler.EventArgs.ProgressPercentage);
        }
#endif

        [Fact]
        public void ReadAsync_ReportsBytesRead()
        {
            // Arrange
            HttpResponseMessage response = CreateResponse();
            Stream innerStream = response.Content.ReadAsStreamAsync().Result;
            long? expectedLength = response.Content.Headers.ContentLength;
            MockProgressEventHandler mockProgressEventHandler;
            ProgressMessageHandler progressMessageHandler = MockProgressEventHandler.CreateProgressMessageHandler(out mockProgressEventHandler, sendProgress: false);
            ProgressStream progressStream = CreateProgressStream(innerStream: innerStream, progressMessageHandler: progressMessageHandler, response: response);
            object userState = new object();

            // Act/Assert
            int totalBytesRead = 0;
            int bytesRead = 0;
            do
            {
                byte[] buffer = new byte[8];
                bytesRead = progressStream.ReadAsync(buffer, 0, buffer.Length).Result;
                totalBytesRead += bytesRead;

                Assert.Equal(totalBytesRead, mockProgressEventHandler.EventArgs.BytesTransferred);
                Assert.Equal((100L * totalBytesRead) / expectedLength, mockProgressEventHandler.EventArgs.ProgressPercentage);
            }
            while (bytesRead > 0);

            Assert.Equal(expectedLength, mockProgressEventHandler.EventArgs.TotalBytes);
            Assert.Equal(100, mockProgressEventHandler.EventArgs.ProgressPercentage);
        }

        [Fact]
        public void Write_ReportsBytesWritten()
        {
            // Arrange
            HttpRequestMessage request = CreateRequest();
            Stream innerStream = new MemoryStream();
            byte[] buffer = CreateBufferContent();
            long? expectedLength = request.Content.Headers.ContentLength;
            MockProgressEventHandler mockProgressEventHandler;
            ProgressMessageHandler progressMessageHandler = MockProgressEventHandler.CreateProgressMessageHandler(out mockProgressEventHandler, sendProgress: true);
            ProgressStream progressStream = CreateProgressStream(innerStream: innerStream, progressMessageHandler: progressMessageHandler, request: request);

            // Act/Assert
            int totalBytesWritten = 0;
            int bytesWritten = 0;
            while (totalBytesWritten < expectedLength)
            {
                bytesWritten = Math.Min(8, (int)expectedLength - totalBytesWritten);
                progressStream.Write(buffer, totalBytesWritten, bytesWritten);
                totalBytesWritten += bytesWritten;

                Assert.Equal(totalBytesWritten, mockProgressEventHandler.EventArgs.BytesTransferred);
                Assert.Equal((100L * totalBytesWritten) / expectedLength, mockProgressEventHandler.EventArgs.ProgressPercentage);
            }

            Assert.Equal(expectedLength, mockProgressEventHandler.EventArgs.TotalBytes);
            Assert.Equal(100, mockProgressEventHandler.EventArgs.ProgressPercentage);
        }

        [Fact]
        public void WriteByte_ReportsBytesWritten()
        {
            // Arrange
            HttpRequestMessage request = CreateRequest();
            Stream innerStream = new MemoryStream();
            byte[] buffer = CreateBufferContent();
            long? expectedLength = request.Content.Headers.ContentLength;
            MockProgressEventHandler mockProgressEventHandler;
            ProgressMessageHandler progressMessageHandler = MockProgressEventHandler.CreateProgressMessageHandler(out mockProgressEventHandler, sendProgress: true);
            ProgressStream progressStream = CreateProgressStream(innerStream: innerStream, progressMessageHandler: progressMessageHandler, request: request);

            // Act/Assert
            int totalBytesWritten = 0;
            while (totalBytesWritten < expectedLength)
            {
                progressStream.WriteByte(buffer[totalBytesWritten]);
                totalBytesWritten += 1;

                Assert.Equal(totalBytesWritten, mockProgressEventHandler.EventArgs.BytesTransferred);
                Assert.Equal((100L * totalBytesWritten) / expectedLength, mockProgressEventHandler.EventArgs.ProgressPercentage);
            }

            Assert.Equal(expectedLength, mockProgressEventHandler.EventArgs.TotalBytes);
            Assert.Equal(100, mockProgressEventHandler.EventArgs.ProgressPercentage);
        }

#if !NETFX_CORE // BeginX and EndX not supported on Streams in portable libraries
        [Fact]
        public void BeginEndWrite_ReportsBytesWritten()
        {
            // Arrange
            ManualResetEvent writeComplete = new ManualResetEvent(false);
            HttpRequestMessage request = CreateRequest();
            Stream innerStream = new MemoryStream();
            byte[] buffer = CreateBufferContent();
            long? expectedLength = request.Content.Headers.ContentLength;
            MockProgressEventHandler mockProgressEventHandler;
            ProgressMessageHandler progressMessageHandler = MockProgressEventHandler.CreateProgressMessageHandler(out mockProgressEventHandler, sendProgress: true);
            ProgressStream progressStream = CreateProgressStream(innerStream: innerStream, progressMessageHandler: progressMessageHandler, request: request);
            object userState = new object();

            // Act/Assert
            int totalBytesWritten = 0;
            int bytesWritten = 0;
            while (totalBytesWritten < expectedLength)
            {
                bytesWritten = Math.Min(8, (int)expectedLength - totalBytesWritten);
                IAsyncResult result = progressStream.BeginWrite(buffer, totalBytesWritten, bytesWritten,
                    ia =>
                    {
                        progressStream.EndWrite(ia);
                        writeComplete.Set();
                    },
                    userState);

                writeComplete.WaitOne();
                writeComplete.Reset();
                totalBytesWritten += bytesWritten;

                Assert.Same(userState, mockProgressEventHandler.EventArgs.UserState);
                Assert.Equal(totalBytesWritten, mockProgressEventHandler.EventArgs.BytesTransferred);
                Assert.Equal((100L * totalBytesWritten) / expectedLength, mockProgressEventHandler.EventArgs.ProgressPercentage);
            }

            Assert.Equal(expectedLength, mockProgressEventHandler.EventArgs.TotalBytes);
            Assert.Equal(100, mockProgressEventHandler.EventArgs.ProgressPercentage);
        }
#endif

        [Fact]
        public void WriteAsync_ReportsBytesWritten()
        {
            // Arrange
            ManualResetEvent writeComplete = new ManualResetEvent(false);
            HttpRequestMessage request = CreateRequest();
            Stream innerStream = new MemoryStream();
            byte[] buffer = CreateBufferContent();
            long? expectedLength = request.Content.Headers.ContentLength;
            MockProgressEventHandler mockProgressEventHandler;
            ProgressMessageHandler progressMessageHandler = MockProgressEventHandler.CreateProgressMessageHandler(out mockProgressEventHandler, sendProgress: true);
            ProgressStream progressStream = CreateProgressStream(innerStream: innerStream, progressMessageHandler: progressMessageHandler, request: request);
            object userState = new object();

            // Act/Assert
            int totalBytesWritten = 0;
            int bytesWritten = 0;
            while (totalBytesWritten < expectedLength)
            {
                bytesWritten = Math.Min(8, (int)expectedLength - totalBytesWritten);
                progressStream.WriteAsync(buffer, totalBytesWritten, bytesWritten).Wait();

                totalBytesWritten += bytesWritten;

                Assert.Equal(totalBytesWritten, mockProgressEventHandler.EventArgs.BytesTransferred);
                Assert.Equal((100L * totalBytesWritten) / expectedLength, mockProgressEventHandler.EventArgs.ProgressPercentage);
            }

            Assert.Equal(expectedLength, mockProgressEventHandler.EventArgs.TotalBytes);
            Assert.Equal(100, mockProgressEventHandler.EventArgs.ProgressPercentage);
        }

        internal static ProgressStream CreateProgressStream(
            Stream innerStream = null,
            ProgressMessageHandler progressMessageHandler = null,
            HttpRequestMessage request = null,
            HttpResponseMessage response = null)
        {
            Stream iStream = innerStream ?? new Mock<Stream>().Object;
            ProgressMessageHandler pHandler = progressMessageHandler ?? new ProgressMessageHandler();
            HttpRequestMessage req = request ?? new HttpRequestMessage();
            HttpResponseMessage rsp = response ?? new HttpResponseMessage();
            return new ProgressStream(iStream, pHandler, req, rsp);
        }

        private static HttpRequestMessage CreateRequest()
        {
            return new HttpRequestMessage
            {
                Content = CreateStringContent()
            };
        }

        private static HttpResponseMessage CreateResponse()
        {
            return new HttpResponseMessage
            {
                Content = CreateStringContent()
            };
        }

        private static String CreateContent()
        {
            StringBuilder content = new StringBuilder();
            for (int count = 0; count < 100; count++)
            {
                content.Append("1234567890");
            }
            return content.ToString();
        }

        private static HttpContent CreateStringContent()
        {
            return new StringContent(CreateContent());
        }

        private static byte[] CreateBufferContent()
        {
            return Encoding.UTF8.GetBytes(CreateContent());
        }
    }
}
