// <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.Collections.Generic;
using System.Diagnostics;
using Thrift.Protocol.Entities;

namespace Thrift.Protocol
{
    // ReSharper disable once InconsistentNaming
    internal sealed class TCompactProtocol : TProtocol
    {
        private const byte ProtocolId = 0x82;
        private const byte Version = 1;
        private const byte VersionMask = 0x1f; // 0001 1111
        private const byte TypeMask = 0xE0; // 1110 0000
        private const byte TypeBits = 0x07; // 0000 0111
        private const int TypeShiftAmount = 5;
        private static readonly TStruct AnonymousStruct = new TStruct(string.Empty);
        private static readonly TField StopField = new TField(string.Empty, TType.Stop, 0);

        private const byte NoTypeOverride = 0xFF;

        // ReSharper disable once InconsistentNaming
        private static readonly byte[] TTypeToCompactType = new byte[16];
        private static readonly TType[] CompactTypeToTType = new TType[13];

        /// <summary>
        ///     Used to keep track of the last field for the current and previous structs, so we can do the delta stuff.
        /// </summary>
        private readonly Stack<short> _lastField = new Stack<short>(15);

        /// <summary>
        ///     If we encounter a boolean field begin, save the TField here so it can have the value incorporated.
        /// </summary>
        private TField? _booleanField;

        private short _lastFieldId;

        // 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 struct VarInt
        {
            public byte[] bytes;
            public int count;
        }

        // minimize memory allocations by means of an preallocated VarInt buffer
        private VarInt PreAllocatedVarInt = new VarInt()
        {
            bytes = new byte[10], // see Int64ToVarInt()
            count = 0
        };

        private readonly byte[] EmptyUInt32Buffer = new byte[5];

        public TCompactProtocol(int initialCapacity = 8192)
            : base(initialCapacity)
        {
            TTypeToCompactType[(int)TType.Stop] = Types.Stop;
            TTypeToCompactType[(int)TType.Bool] = Types.BooleanTrue;
            TTypeToCompactType[(int)TType.Byte] = Types.Byte;
            TTypeToCompactType[(int)TType.I16] = Types.I16;
            TTypeToCompactType[(int)TType.I32] = Types.I32;
            TTypeToCompactType[(int)TType.I64] = Types.I64;
            TTypeToCompactType[(int)TType.Double] = Types.Double;
            TTypeToCompactType[(int)TType.String] = Types.Binary;
            TTypeToCompactType[(int)TType.List] = Types.List;
            TTypeToCompactType[(int)TType.Set] = Types.Set;
            TTypeToCompactType[(int)TType.Map] = Types.Map;
            TTypeToCompactType[(int)TType.Struct] = Types.Struct;

            CompactTypeToTType[Types.Stop] = TType.Stop;
            CompactTypeToTType[Types.BooleanTrue] = TType.Bool;
            CompactTypeToTType[Types.BooleanFalse] = TType.Bool;
            CompactTypeToTType[Types.Byte] = TType.Byte;
            CompactTypeToTType[Types.I16] = TType.I16;
            CompactTypeToTType[Types.I32] = TType.I32;
            CompactTypeToTType[Types.I64] = TType.I64;
            CompactTypeToTType[Types.Double] = TType.Double;
            CompactTypeToTType[Types.Binary] = TType.String;
            CompactTypeToTType[Types.List] = TType.List;
            CompactTypeToTType[Types.Set] = TType.Set;
            CompactTypeToTType[Types.Map] = TType.Map;
            CompactTypeToTType[Types.Struct] = TType.Struct;
        }

        public override void Reset()
        {
            _lastField.Clear();
            _lastFieldId = 0;

            base.Reset();
        }

        public override void WriteMessageBegin(TMessage message)
        {
            PreAllocatedBuffer[0] = ProtocolId;
            PreAllocatedBuffer[1] = (byte)((Version & VersionMask) | (((uint)message.Type << TypeShiftAmount) & TypeMask));
            Transport.Write(PreAllocatedBuffer, 0, 2);

            Int32ToVarInt((uint)message.SeqID, ref PreAllocatedVarInt);
            Transport.Write(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count);

            WriteString(message.Name);
        }

        public override void WriteMessageBegin(TMessage message, out int seqIdPosition)
        {
            PreAllocatedBuffer[0] = ProtocolId;
            PreAllocatedBuffer[1] = (byte)((Version & VersionMask) | (((uint)message.Type << TypeShiftAmount) & TypeMask));
            Transport.Write(PreAllocatedBuffer, 0, 2);

            seqIdPosition = (int)Transport.Position;
            // Write empty bytes to reserve the space for the seqId to be written later on.
            Transport.Write(EmptyUInt32Buffer, 0, 5);

            WriteString(message.Name);
        }

        public override void WriteMessageEnd()
        {
        }

        /// <summary>
        ///     Write a struct begin. This doesn't actually put anything on the wire. We
        ///     use it as an opportunity to put special placeholder markers on the field
        ///     stack so we can get the field id deltas correct.
        /// </summary>
        public override void WriteStructBegin(TStruct @struct)
        {
            _lastField.Push(_lastFieldId);
            _lastFieldId = 0;
        }

        public override void WriteStructEnd()
        {
            _lastFieldId = _lastField.Pop();
        }

        private void WriteFieldBeginInternal(TField field, byte fieldType)
        {
            // if there's a exType override passed in, use that. Otherwise ask GetCompactType().
            if (fieldType == NoTypeOverride)
                fieldType = GetCompactType(field.Type);

            // check if we can use delta encoding for the field id
            if (field.ID > _lastFieldId)
            {
                var delta = field.ID - _lastFieldId;
                if (delta <= 15)
                {
                    // Write them together
                    PreAllocatedBuffer[0] = (byte)((delta << 4) | fieldType);
                    Transport.Write(PreAllocatedBuffer, 0, 1);
                    _lastFieldId = field.ID;
                    return;
                }
            }

            // Write them separate
            PreAllocatedBuffer[0] = fieldType;
            Transport.Write(PreAllocatedBuffer, 0, 1);
            WriteI16(field.ID);
            _lastFieldId = field.ID;
        }

        public override void WriteFieldBegin(TField field)
        {
            if (field.Type == TType.Bool)
            {
                _booleanField = field;
            }
            else
            {
                WriteFieldBeginInternal(field, NoTypeOverride);
            }
        }

        public override void WriteFieldEnd()
        {
        }

        public override void WriteFieldStop()
        {
            PreAllocatedBuffer[0] = Types.Stop;
            Transport.Write(PreAllocatedBuffer, 0, 1);
        }

        public override void WriteListBegin(TList list)
        {
            if (list.Count <= 14)
            {
                PreAllocatedBuffer[0] = (byte)((list.Count << 4) | GetCompactType(list.ElementType));
                Transport.Write(PreAllocatedBuffer, 0, 1);
            }
            else
            {
                PreAllocatedBuffer[0] = (byte)(0xf0 | GetCompactType(list.ElementType));
                Transport.Write(PreAllocatedBuffer, 0, 1);

                Int32ToVarInt((uint)list.Count, ref PreAllocatedVarInt);
                Transport.Write(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count);
            }
        }

        public override void WriteListBegin(TList list, out int countPosition)
        {
            /*
            Note: Compact sizing disabled because size might not be known initially.
            if (list.Count <= 14)
            {
                PreAllocatedBuffer[0] = (byte)((list.Count << 4) | GetCompactType(list.ElementType));
                Transport.Write(PreAllocatedBuffer, 0, 1);
            }
            else*/
            {
                PreAllocatedBuffer[0] = (byte)(0xf0 | GetCompactType(list.ElementType));
                Transport.Write(PreAllocatedBuffer, 0, 1);

                countPosition = (int)Transport.Position;
                // Write empty bytes to reserve the space for the count to be written later on.
                Transport.Write(EmptyUInt32Buffer, 0, 5);
            }
        }

        public override void WriteListEnd()
        {
        }

        public override void WriteBool(bool b)
        {
            /*
            Write a boolean value. Potentially, this could be a boolean field, in
            which case the field header info isn't written yet. If so, decide what the
            right exType header is for the value and then Write the field header.
            Otherwise, Write a single byte.
            */

            if (_booleanField != null)
            {
                // we haven't written the field header yet
                var type = b ? Types.BooleanTrue : Types.BooleanFalse;
                WriteFieldBeginInternal(_booleanField.Value, type);
                _booleanField = null;
            }
            else
            {
                // we're not part of a field, so just write the value.
                PreAllocatedBuffer[0] = b ? Types.BooleanTrue : Types.BooleanFalse;
                Transport.Write(PreAllocatedBuffer, 0, 1);
            }
        }

        public override void WriteByte(sbyte b)
        {
            PreAllocatedBuffer[0] = (byte)b;
            Transport.Write(PreAllocatedBuffer, 0, 1);
        }

        public override void WriteI16(short i16)
        {
            Int32ToVarInt(IntToZigzag(i16), ref PreAllocatedVarInt);
            Transport.Write(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count);
        }

        private static void Int32ToVarInt(uint n, ref VarInt varint)
        {
            // Write an i32 as a varint. Results in 1 - 5 bytes on the wire.
            varint.count = 0;
            Debug.Assert(varint.bytes.Length >= 5);

            while (true)
            {
                if ((n & ~0x7F) == 0)
                {
                    varint.bytes[varint.count++] = (byte)n;
                    break;
                }

                varint.bytes[varint.count++] = (byte)((n & 0x7F) | 0x80);
                n >>= 7;
            }
        }

        public override void WriteI32(int i32)
        {
            Int32ToVarInt(IntToZigzag(i32), ref PreAllocatedVarInt);
            Transport.Write(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count);
        }

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

            buffer[0] = (byte)(0x80 | (ui32 & 0x7F));
            ui32 >>= 7;
            buffer[1] = (byte)(0x80 | (ui32 & 0x7F));
            ui32 >>= 7;
            buffer[2] = (byte)(0x80 | (ui32 & 0x7F));
            ui32 >>= 7;
            buffer[3] = (byte)(0x80 | (ui32 & 0x7F));
            ui32 >>= 7;
            buffer[4] = (byte)ui32;

            return 5;
        }

        static private void Int64ToVarInt(ulong n, ref VarInt varint)
        {
            // Write an i64 as a varint. Results in 1-10 bytes on the wire.
            varint.count = 0;
            Debug.Assert(varint.bytes.Length >= 10);

            while (true)
            {
                if ((n & ~(ulong)0x7FL) == 0)
                {
                    varint.bytes[varint.count++] = (byte)n;
                    break;
                }
                varint.bytes[varint.count++] = (byte)((n & 0x7F) | 0x80);
                n >>= 7;
            }
        }

        public override void WriteI64(long i64)
        {
            Int64ToVarInt(LongToZigzag(i64), ref PreAllocatedVarInt);
            Transport.Write(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count);
        }

        public override void WriteDouble(double d)
        {
            FixedLongToBytes(BitConverter.DoubleToInt64Bits(d), PreAllocatedBuffer, 0);
            Transport.Write(PreAllocatedBuffer, 0, 8);
        }

#if NETSTANDARD2_1_OR_GREATER
        public override void WriteBinary(ReadOnlySpan<byte> bytes)
        {
            Int32ToVarInt((uint)bytes.Length, ref PreAllocatedVarInt);
            Transport.Write(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count);
            Transport.Write(bytes);
        }
#endif

        public override void WriteBinary(byte[] bytes, int offset, int count)
        {
            Int32ToVarInt((uint)count, ref PreAllocatedVarInt);
            Transport.Write(PreAllocatedVarInt.bytes, 0, PreAllocatedVarInt.count);
            Transport.Write(bytes, offset, count);
        }

        private static byte GetCompactType(TType ttype)
        {
            // Given a TType value, find the appropriate TCompactProtocol.Types constant.
            return TTypeToCompactType[(int)ttype];
        }

        private static ulong LongToZigzag(long n)
        {
            // Convert l into a zigzag long. This allows negative numbers to be represented compactly as a varint
            return (ulong)(n << 1) ^ (ulong)(n >> 63);
        }

        private static uint IntToZigzag(int n)
        {
            // Convert n into a zigzag int. This allows negative numbers to be represented compactly as a varint
            return (uint)(n << 1) ^ (uint)(n >> 31);
        }

        private static void FixedLongToBytes(long n, byte[] buf, int off)
        {
            // Convert a long into little-endian bytes in buf starting at off and going until off+7.
            buf[off + 0] = (byte)(n & 0xff);
            buf[off + 1] = (byte)((n >> 8) & 0xff);
            buf[off + 2] = (byte)((n >> 16) & 0xff);
            buf[off + 3] = (byte)((n >> 24) & 0xff);
            buf[off + 4] = (byte)((n >> 32) & 0xff);
            buf[off + 5] = (byte)((n >> 40) & 0xff);
            buf[off + 6] = (byte)((n >> 48) & 0xff);
            buf[off + 7] = (byte)((n >> 56) & 0xff);
        }

        public sealed class Factory : TProtocolFactory
        {
            public override TProtocol GetProtocol(int initialCapacity = 8192)
            {
                return new TCompactProtocol(initialCapacity);
            }
        }

        /// <summary>
        ///     All of the on-wire exType codes.
        /// </summary>
        private static class Types
        {
            public const byte Stop = 0x00;
            public const byte BooleanTrue = 0x01;
            public const byte BooleanFalse = 0x02;
            public const byte Byte = 0x03;
            public const byte I16 = 0x04;
            public const byte I32 = 0x05;
            public const byte I64 = 0x06;
            public const byte Double = 0x07;
            public const byte Binary = 0x08;
            public const byte List = 0x09;
            public const byte Set = 0x0A;
            public const byte Map = 0x0B;
            public const byte Struct = 0x0C;
        }
    }
}
