//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//

using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Utility;

namespace Microsoft.SqlTools.Hosting.Protocol
{
    public class MessageWriter
    {
        private const string ContentLength = "Content-Length: ";
        private const string ContentType = "Content-Type: application/json";
        private const string HeaderSeparator = "\r\n";
        private const string HeaderEnd = "\r\n\r\n";

        private readonly Stream outputStream;
        private readonly AsyncLock writeLock = new AsyncLock();

        public MessageWriter(Stream outputStream)
        {
            Validate.IsNotNull("streamWriter", outputStream);

            this.outputStream = outputStream;
        }
        
        public virtual async Task WriteMessage(Message messageToWrite)
        {
            Validate.IsNotNull("messageToWrite", messageToWrite);

            // Log the JSON representation of the message
            string logMessage = string.Format("Sending message of type[{0}] and method[{1}]",
                messageToWrite.MessageType, messageToWrite.Method);
            Logger.Write(TraceEventType.Verbose, logMessage);

            string serializedMessage = messageToWrite.Serialize();
            // TODO: Allow encoding to be passed in
            byte[] messageBytes = Encoding.UTF8.GetBytes(serializedMessage);

            string headers = ContentLength + messageBytes.Length + HeaderSeparator
                             + ContentType + HeaderEnd;
            byte[] headerBytes = Encoding.ASCII.GetBytes(headers);

            // Make sure only one call is writing at a time.  You might be thinking
            // "Why not use a normal lock?"  We use an AsyncLock here so that the
            // message loop doesn't get blocked while waiting for I/O to complete.
            using (await writeLock.LockAsync())
            {
                // Send the message
                await outputStream.WriteAsync(headerBytes, 0, headerBytes.Length);
                await outputStream.WriteAsync(messageBytes, 0, messageBytes.Length);
                await outputStream.FlushAsync();
            }
        }
    }
}
