// <auto-generated/> (Turns off StyleCop analysis in this file.)

// 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.Buffers;
using System.IO;
using System.Text;
using Thrift.Protocol.Entities;

namespace Thrift.Protocol
{
    // ReSharper disable once InconsistentNaming
    internal abstract class TProtocol : IDisposable
    {
        public const int DefaultRecursionDepth = 64;

        private bool _isDisposed;

        protected TProtocol(int initialCapacity = 8192)
        {
            Transport = new MemoryStream(initialCapacity);
            RecursionLimit = DefaultRecursionDepth;
            RecursionDepth = 0;
        }

        protected MemoryStream Transport { get; }

        protected int RecursionDepth { get; set; }

        protected int RecursionLimit { get; set; }

        public ArraySegment<byte> WrittenData
        {
            get => new ArraySegment<byte>(Transport.GetBuffer(), 0, (int)Transport.Length);
        }

        public int Position
        {
            get => (int)Transport.Position;
            set => Transport.Position = value;
        }

        public int Length => (int)Transport.Length;

        public void Clear(int offset = 0)
        {
            if (offset > Transport.Length)
                throw new ArgumentOutOfRangeException(nameof(offset));

            Transport.Position = offset;
            Transport.SetLength(offset);
        }

        public virtual void Reset()
        {
            RecursionDepth = 0;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void IncrementRecursionDepth()
        {
            if (RecursionDepth >= RecursionLimit)
            {
                throw new TProtocolException(TProtocolException.DEPTH_LIMIT, $"Depth of recursion exceeded the limit: {RecursionLimit}");
            }

            ++RecursionDepth;
        }

        public void DecrementRecursionDepth()
        {
            --RecursionDepth;
        }

        protected virtual void Dispose(bool disposing)
        {
            if (!_isDisposed)
            {
                if (disposing)
                {
                    Transport.Dispose();
                }
            }
            _isDisposed = true;
        }

        public abstract void WriteMessageBegin(TMessage message);

        public abstract void WriteMessageBegin(TMessage message, out int seqIdPosition);

        public abstract void WriteMessageEnd();

        public abstract void WriteStructBegin(TStruct @struct);

        public abstract void WriteStructEnd();

        public abstract void WriteFieldBegin(TField field);

        public abstract void WriteFieldEnd();

        public abstract void WriteFieldStop();

        public abstract void WriteListBegin(TList list);

        public abstract void WriteListBegin(TList list, out int countPosition);

        public abstract void WriteListEnd();

        public abstract void WriteBool(bool b);

        public abstract void WriteByte(sbyte b);

        public abstract void WriteI16(short i16);

        public abstract void WriteI32(int i32);

        public abstract int WriteUI32(uint ui32, Span<byte> buffer);

        public abstract void WriteI64(long i64);

        public abstract void WriteDouble(double d);

        public virtual void WriteString(string s)
        {
#if NETSTANDARD2_1_OR_GREATER
            if (s.Length <= 128)
            {
                Span<byte> buffer = stackalloc byte[256];
                int numberOfBytes = Encoding.UTF8.GetBytes(s, buffer);
                WriteBinary(buffer.Slice(0, numberOfBytes));
                return;
            }
#endif
            var buf = ArrayPool<byte>.Shared.Rent(Encoding.UTF8.GetByteCount(s));
            try
            {
                var numberOfBytes = Encoding.UTF8.GetBytes(s, 0, s.Length, buf, 0);

                WriteBinary(buf, 0, numberOfBytes);
            }
            finally
            {
                ArrayPool<byte>.Shared.Return(buf);
            }
        }

#if NETSTANDARD2_1_OR_GREATER
        public abstract void WriteBinary(ReadOnlySpan<byte> bytes);
#endif

        public abstract void WriteBinary(byte[] bytes, int offset, int count);

        public void WriteRaw(byte[] bytes)
        {
            this.Transport.Write(bytes, 0, bytes.Length);
        }

        public void WriteRaw(byte[] bytes, int offset, int count)
        {
            this.Transport.Write(bytes, offset, count);
        }

        public void WriteRaw(ArraySegment<byte> bytes)
        {
            this.Transport.Write(bytes.Array, bytes.Offset, bytes.Count);
        }
    }
}
