// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Buffers;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

namespace Microsoft.AspNetCore.SignalR.Internal
{
    internal sealed class Utf8BufferTextReader : TextReader
    {
        private readonly Decoder _decoder;
        private ReadOnlySequence<byte> _utf8Buffer;

        [ThreadStatic]
        private static Utf8BufferTextReader? _cachedInstance;

#if DEBUG
        private bool _inUse;
#endif

        public Utf8BufferTextReader()
        {
            _decoder = Encoding.UTF8.GetDecoder();
        }

        public static Utf8BufferTextReader Get(in ReadOnlySequence<byte> utf8Buffer)
        {
            var reader = _cachedInstance;
            if (reader == null)
            {
                reader = new Utf8BufferTextReader();
            }

            // Taken off the thread static
            _cachedInstance = null;
#if DEBUG
            if (reader._inUse)
            {
                throw new InvalidOperationException("The reader wasn't returned!");
            }

            reader._inUse = true;
#endif
            reader.SetBuffer(utf8Buffer);
            return reader;
        }

        public static void Return(Utf8BufferTextReader reader)
        {
            _cachedInstance = reader;
#if DEBUG
            reader._inUse = false;
#endif
        }

        public void SetBuffer(in ReadOnlySequence<byte> utf8Buffer)
        {
            _utf8Buffer = utf8Buffer;
            _decoder.Reset();
        }

        public override int Read(char[] buffer, int index, int count)
        {
            if (_utf8Buffer.IsEmpty)
            {
                return 0;
            }

            var source = _utf8Buffer.First.Span;
            var bytesUsed = 0;
            var charsUsed = 0;
#if NETCOREAPP
            var destination = new Span<char>(buffer, index, count);
            _decoder.Convert(source, destination, false, out bytesUsed, out charsUsed, out var completed);
#else
            unsafe
            {
                fixed (char* destinationChars = &buffer[index])
                fixed (byte* sourceBytes = &MemoryMarshal.GetReference(source))
                {
                    _decoder.Convert(sourceBytes, source.Length, destinationChars, count, false, out bytesUsed, out charsUsed, out var completed);
                }
            }
#endif
            _utf8Buffer = _utf8Buffer.Slice(bytesUsed);

            return charsUsed;
        }
    }
}
