// <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 Thrift.Protocol.Entities;

namespace Thrift.Protocol
{
    // ReSharper disable once InconsistentNaming
    internal sealed class TBinaryProtocol : TProtocol
    {
        private const uint VersionMask = 0xffff0000;
        private const uint Version1 = 0x80010000;

        private readonly bool StrictRead;
        private readonly bool StrictWrite;

        // minimize memory allocations by means of an preallocated bytes buffer
        // The value of 128 is arbitrarily chosen, the required minimum size must be sizeof(long)
        private readonly byte[] PreAllocatedBuffer = new byte[128];

        private static readonly TStruct AnonymousStruct = new TStruct(string.Empty);
        private static readonly TField StopField = new TField() { Type = TType.Stop };

        public TBinaryProtocol(int initialCapacity = 8192)
            : this(false, true, initialCapacity)
        {
        }

        public TBinaryProtocol(bool strictRead, bool strictWrite, int initialCapacity = 8192)
            : base(initialCapacity)
        {
            StrictRead = strictRead;
            StrictWrite = strictWrite;
        }

        public override void WriteMessageBegin(TMessage message)
        {
            if (StrictWrite)
            {
                var version = Version1 | (uint)message.Type;
                WriteI32((int)version);
                WriteString(message.Name);
                WriteI32(message.SeqID);
            }
            else
            {
                WriteString(message.Name);
                WriteByte((sbyte)message.Type);
                WriteI32(message.SeqID);
            }
        }

        public override void WriteMessageBegin(TMessage message, out int seqIdPosition)
        {
            if (StrictWrite)
            {
                var version = Version1 | (uint)message.Type;
                WriteI32((int)version);
                WriteString(message.Name);
                seqIdPosition = (int)Transport.Position;
                WriteI32(message.SeqID);
            }
            else
            {
                WriteString(message.Name);
                WriteByte((sbyte)message.Type);
                seqIdPosition = (int)Transport.Position;
                WriteI32(message.SeqID);
            }
        }

        public override void WriteMessageEnd()
        {
        }

        public override void WriteStructBegin(TStruct @struct)
        {
        }

        public override void WriteStructEnd()
        {
        }

        public override void WriteFieldBegin(TField field)
        {
            WriteByte((sbyte)field.Type);
            WriteI16(field.ID);
        }

        public override void WriteFieldEnd()
        {
        }

        public override void WriteFieldStop()
        {
            WriteByte((sbyte)TType.Stop);
        }

        public override void WriteListBegin(TList list)
        {
            WriteByte((sbyte)list.ElementType);
            WriteI32(list.Count);
        }

        public override void WriteListBegin(TList list, out int countPosition)
        {
            WriteByte((sbyte)list.ElementType);
            countPosition = (int)Transport.Position;
            WriteI32(list.Count);
        }

        public override void WriteListEnd()
        {
        }

        public override void WriteBool(bool b)
        {
            WriteByte(b ? (sbyte)1 : (sbyte)0);
        }

        public override void WriteByte(sbyte b)
        {
            PreAllocatedBuffer[0] = (byte)b;

            Transport.Write(PreAllocatedBuffer, 0, 1);
        }

        public override void WriteI16(short i16)
        {
            PreAllocatedBuffer[0] = (byte)(0xff & (i16 >> 8));
            PreAllocatedBuffer[1] = (byte)(0xff & i16);

            Transport.Write(PreAllocatedBuffer, 0, 2);
        }

        public override int WriteUI32(uint ui32, Span<byte> buffer)
        {
            if (buffer.Length < 4)
                return 0;

            buffer[0] = (byte)(0xff & (ui32 >> 24));
            buffer[1] = (byte)(0xff & (ui32 >> 16));
            buffer[2] = (byte)(0xff & (ui32 >> 8));
            buffer[3] = (byte)(0xff & ui32);

            return 4;
        }

        public override void WriteI32(int i32)
        {
            PreAllocatedBuffer[0] = (byte)(0xff & (i32 >> 24));
            PreAllocatedBuffer[1] = (byte)(0xff & (i32 >> 16));
            PreAllocatedBuffer[2] = (byte)(0xff & (i32 >> 8));
            PreAllocatedBuffer[3] = (byte)(0xff & i32);

            Transport.Write(PreAllocatedBuffer, 0, 4);
        }

        public override void WriteI64(long i64)
        {
            PreAllocatedBuffer[0] = (byte)(0xff & (i64 >> 56));
            PreAllocatedBuffer[1] = (byte)(0xff & (i64 >> 48));
            PreAllocatedBuffer[2] = (byte)(0xff & (i64 >> 40));
            PreAllocatedBuffer[3] = (byte)(0xff & (i64 >> 32));
            PreAllocatedBuffer[4] = (byte)(0xff & (i64 >> 24));
            PreAllocatedBuffer[5] = (byte)(0xff & (i64 >> 16));
            PreAllocatedBuffer[6] = (byte)(0xff & (i64 >> 8));
            PreAllocatedBuffer[7] = (byte)(0xff & i64);

            Transport.Write(PreAllocatedBuffer, 0, 8);
        }

        public override void WriteDouble(double d)
        {
            WriteI64(BitConverter.DoubleToInt64Bits(d));
        }

#if NETSTANDARD2_1_OR_GREATER
        public override void WriteBinary(ReadOnlySpan<byte> bytes)
        {
            WriteI32(bytes.Length);
            Transport.Write(bytes);
        }
#endif

        public override void WriteBinary(byte[] bytes, int offset, int count)
        {
            WriteI32(count);
            Transport.Write(bytes, offset, count);
        }

        public sealed class Factory : TProtocolFactory
        {
            private readonly bool StrictRead;
            private readonly bool StrictWrite;

            public Factory()
                : this(false, true)
            {
            }

            public Factory(bool strictRead, bool strictWrite)
            {
                StrictRead = strictRead;
                StrictWrite = strictWrite;
            }

            public override TProtocol GetProtocol(int initialCapacity = 8192)
            {
                return new TBinaryProtocol(StrictRead, StrictWrite, initialCapacity);
            }
        }
    }
}
