using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Unity.Burst;
using Unity.Collections.LowLevel.Unsafe;
using UnityEngine.Scripting.APIUpdating;
namespace Unity.Collections
{
    /// 
    /// Writes data in an endian format to serialize data.
    /// 
    /// 
    /// Data streams can be used to serialize data (e.g. over the network). The
    /// DataStreamWriter and  classes work together
    /// to serialize data for sending and then to deserialize when receiving.
    ///
    /// DataStreamWriter writes data in the endian format native to the current machine architecture.
    /// For network byte order use the so named methods.
    /// 
    /// The reader can be used to deserialize the data from a NativeArray<byte>, writing data
    /// to a NativeArray<byte> and reading it back can be done like this:
    /// 
    /// using (var data = new NativeArray<byte>(16, Allocator.Persistent))
    /// {
    ///     var dataWriter = new DataStreamWriter(data);
    ///     dataWriter.WriteInt(42);
    ///     dataWriter.WriteInt(1234);
    ///     // Length is the actual amount of data inside the writer,
    ///     // Capacity is the total amount.
    ///     var dataReader = new DataStreamReader(nativeArrayOfBytes.GetSubArray(0, dataWriter.Length));
    ///     var myFirstInt = dataReader.ReadInt();
    ///     var mySecondInt = dataReader.ReadInt();
    /// }
    /// 
    ///
    /// There are a number of functions for various data types. If a copy of the writer
    /// is stored it can be used to overwrite the data later on. This is particularly useful when
    /// the size of the data is written at the start and you want to write it at
    /// the end when you know the value.
    /// 
    ///
    /// 
    /// using (var data = new NativeArray<byte>(16, Allocator.Persistent))
    /// {
    ///     var dataWriter = new DataStreamWriter(data);
    ///     // My header data
    ///     var headerSizeMark = dataWriter;
    ///     dataWriter.WriteUShort((ushort)0);
    ///     var payloadSizeMark = dataWriter;
    ///     dataWriter.WriteUShort((ushort)0);
    ///     dataWriter.WriteInt(42);
    ///     dataWriter.WriteInt(1234);
    ///     var headerSize = data.Length;
    ///     // Update header size to correct value
    ///     headerSizeMark.WriteUShort((ushort)headerSize);
    ///     // My payload data
    ///     byte[] someBytes = Encoding.ASCII.GetBytes("some string");
    ///     dataWriter.Write(someBytes, someBytes.Length);
    ///     // Update payload size to correct value
    ///     payloadSizeMark.WriteUShort((ushort)(dataWriter.Length - headerSize));
    /// }
    /// 
    /// 
    [MovedFrom(true, "Unity.Networking.Transport", "Unity.Networking.Transport")]
    [StructLayout(LayoutKind.Sequential)]
    [GenerateTestsForBurstCompatibility]
    public unsafe struct DataStreamWriter
    {
        /// 
        /// Show the byte order in which the current computer architecture stores data.
        /// 
        /// 
        /// Different computer architectures store data using different byte orders.
        /// 
        /// - Big-endian: the most significant byte is at the left end of a word.///
- Little-endian: means the most significant byte is at the right end of a word.///
/// 
        public static bool IsLittleEndian
        {
            get
            {
                uint test = 1;
                byte* testPtr = (byte*)&test;
                return testPtr[0] == 1;
            }
        }
        struct StreamData
        {
            public byte* buffer;
            public int length;
            public int capacity;
            public ulong bitBuffer;
            public int bitIndex;
            public int failedWrites;
        }
        [NativeDisableUnsafePtrRestriction] StreamData m_Data;
        /// 
        /// Used for sending data asynchronously.
        /// 
        public IntPtr m_SendHandleData;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
        AtomicSafetyHandle m_Safety;
#endif
        /// 
        /// Initializes a new instance of the DataStreamWriter struct.
        /// 
        /// The number of bytes available in the buffer.
        /// The  used to allocate the memory.
        public DataStreamWriter(int length, AllocatorManager.AllocatorHandle allocator)
        {
            CheckAllocator(allocator);
            Initialize(out this, CollectionHelper.CreateNativeArray(length, allocator));
        }
        /// 
        /// Initializes a new instance of the DataStreamWriter struct with a NativeArray<byte>
        /// 
        /// The buffer to attach to the DataStreamWriter.
        public DataStreamWriter(NativeArray data)
        {
            Initialize(out this, data);
        }
        /// 
        /// Initializes a new instance of the DataStreamWriter struct with a memory we don't own
        /// 
        /// Pointer to the data
        /// Length of the data
        public DataStreamWriter(byte* data, int length)
        {
            var na = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(data, length, Allocator.Invalid);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
            NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref na, AtomicSafetyHandle.GetTempMemoryHandle());
#endif
            Initialize(out this, na);
        }
        /// 
        /// Convert internal data buffer to NativeArray for use in entities APIs.
        /// 
        /// NativeArray representation of internal buffer.
        public NativeArray AsNativeArray()
        {
            var na = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(m_Data.buffer, Length, Allocator.Invalid);
#if ENABLE_UNITY_COLLECTIONS_CHECKS
            NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref na, m_Safety);
#endif
            return na;
        }
        static void Initialize(out DataStreamWriter self, NativeArray data)
        {
            self.m_SendHandleData = IntPtr.Zero;
            self.m_Data.capacity = data.Length;
            self.m_Data.length = 0;
            self.m_Data.buffer = (byte*)data.GetUnsafePtr();
            self.m_Data.bitBuffer = 0;
            self.m_Data.bitIndex = 0;
            self.m_Data.failedWrites = 0;
#if ENABLE_UNITY_COLLECTIONS_CHECKS
            self.m_Safety = NativeArrayUnsafeUtility.GetAtomicSafetyHandle(data);
#endif
        }
        static short ByteSwap(short val)
        {
            return (short)(((val & 0xff) << 8) | ((val >> 8) & 0xff));
        }
        static int ByteSwap(int val)
        {
            return (int)(((val & 0xff) << 24) | ((val & 0xff00) << 8) | ((val >> 8) & 0xff00) | ((val >> 24) & 0xff));
        }
        /// 
        /// True if there is a valid data buffer present. This would be false
        /// if the writer was created with no arguments.
        /// 
        public readonly bool IsCreated
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get { return m_Data.buffer != null; }
        }
        /// 
        /// If there is a write failure this returns true.
        /// A failure might happen if an attempt is made to write more than there is capacity for.
        /// 
        public readonly bool HasFailedWrites
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get => m_Data.failedWrites > 0;
        }
        /// 
        /// The total size of the data buffer, see  for
        /// the size of space used in the buffer.
        /// 
        public readonly int Capacity
        {
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            get
            {
                CheckRead();
                return m_Data.capacity;
            }
        }
        /// 
        /// The size of the buffer used. See  for the total size.
        /// 
        public int Length
        {
            get
            {
                CheckRead();
                SyncBitData();
                return m_Data.length + ((m_Data.bitIndex + 7) >> 3);
            }
        }
        /// 
        /// The size of the buffer used in bits. See  for the length in bytes.
        /// 
        public int LengthInBits
        {
            get
            {
                CheckRead();
                SyncBitData();
                return m_Data.length * 8 + m_Data.bitIndex;
            }
        }
        void SyncBitData()
        {
            var bitIndex = m_Data.bitIndex;
            if (bitIndex <= 0)
                return;
            CheckWrite();
            var bitBuffer = m_Data.bitBuffer;
            int offset = 0;
            while (bitIndex > 0)
            {
                m_Data.buffer[m_Data.length + offset] = (byte)bitBuffer;
                bitIndex -= 8;
                bitBuffer >>= 8;
                ++offset;
            }
        }
        /// 
        /// Causes any buffered bits to be written to the data buffer.
        /// Note this needs to be invoked after using methods that writes directly to the bit buffer.
        /// 
        public void Flush()
        {
            while (m_Data.bitIndex > 0)
            {
                m_Data.buffer[m_Data.length++] = (byte)m_Data.bitBuffer;
                m_Data.bitIndex -= 8;
                m_Data.bitBuffer >>= 8;
            }
            m_Data.bitIndex = 0;
        }
        bool WriteBytesInternal(byte* data, int bytes)
        {
            CheckWrite();
            if (m_Data.length + ((m_Data.bitIndex + 7) >> 3) + bytes > m_Data.capacity)
            {
                ++m_Data.failedWrites;
                return false;
            }
            Flush();
            UnsafeUtility.MemCpy(m_Data.buffer + m_Data.length, data, bytes);
            m_Data.length += bytes;
            return true;
        }
        /// 
        /// Writes an unsigned byte to the current stream and advances the stream position by one byte.
        /// 
        /// The unsigned byte to write.
        /// Whether the write was successful
        public bool WriteByte(byte value)
        {
            return WriteBytesInternal((byte*)&value, sizeof(byte));
        }
        /// 
        /// Copy NativeArray of bytes into the writer's data buffer.
        /// 
        /// Source byte array
        /// Whether the write was successful
        public bool WriteBytes(NativeArray value)
        {
            return WriteBytesInternal((byte*)value.GetUnsafeReadOnlyPtr(), value.Length);
        }
        /// 
        /// Copy Span of bytes into the writer's data buffer.
        /// 
        /// Source byte span
        /// Whether the write was successful
        public bool WriteBytes(Span value)
        {
            fixed (byte* data = value)
            {
                return WriteBytesInternal(data, value.Length);
            }
        }
        /// 
        /// Writes a 2-byte signed short to the current stream and advances the stream position by two bytes.
        /// 
        /// The 2-byte signed short to write.
        /// Whether the write was successful
        public bool WriteShort(short value)
        {
            return WriteBytesInternal((byte*)&value, sizeof(short));
        }
        /// 
        /// Writes a 2-byte unsigned short to the current stream and advances the stream position by two bytes.
        /// 
        /// The 2-byte unsigned short to write.
        /// Whether the write was successful
        public bool WriteUShort(ushort value)
        {
            return WriteBytesInternal((byte*)&value, sizeof(ushort));
        }
        /// 
        /// Writes a 4-byte signed integer from the current stream and advances the current position of the stream by four bytes.
        /// 
        /// The 4-byte signed integer to write.
        /// Whether the write was successful
        public bool WriteInt(int value)
        {
            return WriteBytesInternal((byte*)&value, sizeof(int));
        }
        /// 
        /// Reads a 4-byte unsigned integer from the current stream and advances the current position of the stream by four bytes.
        /// 
        /// The 4-byte unsigned integer to write.
        /// Whether the write was successful
        public bool WriteUInt(uint value)
        {
            return WriteBytesInternal((byte*)&value, sizeof(uint));
        }
        /// 
        /// Writes an 8-byte signed long from the stream and advances the current position of the stream by eight bytes.
        /// 
        /// The 8-byte signed long to write.
        /// Whether the write was successful
        public bool WriteLong(long value)
        {
            return WriteBytesInternal((byte*)&value, sizeof(long));
        }
        /// 
        /// Reads an 8-byte unsigned long from the stream and advances the current position of the stream by eight bytes.
        /// 
        /// The 8-byte unsigned long to write.
        /// Whether the write was successful
        public bool WriteULong(ulong value)
        {
            return WriteBytesInternal((byte*)&value, sizeof(ulong));
        }
        /// 
        /// Writes a 2-byte signed short to the current stream using Big-endian byte order and advances the stream position by two bytes.
        /// If the stream is in little-endian order, the byte order will be swapped.
        /// 
        /// The 2-byte signed short to write.
        /// Whether the write was successful
        public bool WriteShortNetworkByteOrder(short value)
        {
            short netValue = IsLittleEndian ? ByteSwap(value) : value;
            return WriteBytesInternal((byte*)&netValue, sizeof(short));
        }
        /// 
        /// Writes a 2-byte unsigned short to the current stream using Big-endian byte order and advances the stream position by two bytes.
        /// If the stream is in little-endian order, the byte order will be swapped.
        /// 
        /// The 2-byte unsigned short to write.
        /// Whether the write was successful
        public bool WriteUShortNetworkByteOrder(ushort value)
        {
            return WriteShortNetworkByteOrder((short)value);
        }
        /// 
        /// Writes a 4-byte signed integer from the current stream using Big-endian byte order and advances the current position of the stream by four bytes.
        /// If the current machine is in little-endian order, the byte order will be swapped.
        /// 
        /// The 4-byte signed integer to write.
        /// Whether the write was successful
        public bool WriteIntNetworkByteOrder(int value)
        {
            int netValue = IsLittleEndian ? ByteSwap(value) : value;
            return WriteBytesInternal((byte*)&netValue, sizeof(int));
        }
        /// 
        /// Writes a 4-byte unsigned integer from the current stream using Big-endian byte order and advances the current position of the stream by four bytes.
        /// If the stream is in little-endian order, the byte order will be swapped.
        /// 
        /// The 4-byte unsigned integer to write.
        /// Whether the write was successful
        public bool WriteUIntNetworkByteOrder(uint value)
        {
            return WriteIntNetworkByteOrder((int)value);
        }
        /// 
        /// Writes a 4-byte floating point value to the data stream.
        /// 
        /// The 4-byte floating point value to write.
        /// Whether the write was successful
        public bool WriteFloat(float value)
        {
            UIntFloat uf = new UIntFloat();
            uf.floatValue = value;
            return WriteInt((int)uf.intValue);
        }
        /// 
        /// Writes a 8-byte floating point value to the data stream.
        /// 
        /// The 8-byte floating point value to write.
        /// Whether the write was successful
        public bool WriteDouble(double value)
        {
            UIntFloat uf = new UIntFloat();
            uf.doubleValue = value;
            return WriteLong((long)uf.longValue);
        }
        void FlushBits()
        {
            while (m_Data.bitIndex >= 8)
            {
                m_Data.buffer[m_Data.length++] = (byte)m_Data.bitBuffer;
                m_Data.bitIndex -= 8;
                m_Data.bitBuffer >>= 8;
            }
        }
        void WriteRawBitsInternal(uint value, int numbits)
        {
            CheckBits(value, numbits);
            m_Data.bitBuffer |= ((ulong)value << m_Data.bitIndex);
            m_Data.bitIndex += numbits;
        }
        /// 
        /// Appends a specified number of bits to the data stream.
        /// 
        /// The bits to write.
        /// A positive number of bytes to write.
        /// Whether the write was successful
        public bool WriteRawBits(uint value, int numbits)
        {
            CheckWrite();
            if (m_Data.length + ((m_Data.bitIndex + numbits + 7) >> 3) > m_Data.capacity)
            {
                ++m_Data.failedWrites;
                return false;
            }
            WriteRawBitsInternal(value, numbits);
            FlushBits();
            return true;
        }
        /// 
        /// Writes a 4-byte unsigned integer value to the data stream using a .
        /// 
        /// The 4-byte unsigned integer to write.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public bool WritePackedUInt(uint value, in StreamCompressionModel model)
        {
            CheckWrite();
            int bucket = model.CalculateBucket(value);
            uint offset = model.bucketOffsets[bucket];
            int bits = model.bucketSizes[bucket];
            ushort encodeEntry = model.encodeTable[bucket];
            if (m_Data.length + ((m_Data.bitIndex + (encodeEntry & 0xff) + bits + 7) >> 3) > m_Data.capacity)
            {
                ++m_Data.failedWrites;
                return false;
            }
            WriteRawBitsInternal((uint)(encodeEntry >> 8), encodeEntry & 0xFF);
            WriteRawBitsInternal(value - offset, bits);
            FlushBits();
            return true;
        }
        /// 
        /// Writes an 8-byte unsigned long value to the data stream using a .
        /// 
        /// The 8-byte unsigned long to write.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public bool WritePackedULong(ulong value, in StreamCompressionModel model)
        {
            var data = (uint*)&value;
            return WritePackedUInt(data[0], model) &
                   WritePackedUInt(data[1], model);
        }
        /// 
        /// Writes a 4-byte signed integer value to the data stream using a .
        /// Negative values are interleaved between positive values, i.e. (0, -1, 1, -2, 2)
        /// 
        /// The 4-byte signed integer to write.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public bool WritePackedInt(int value, in StreamCompressionModel model)
        {
            uint interleaved = (uint)((value >> 31) ^ (value << 1));      // interleave negative values between positive values: 0, -1, 1, -2, 2
            return WritePackedUInt(interleaved, model);
        }
        /// 
        /// Writes a 8-byte signed long value to the data stream using a .
        /// 
        /// The 8-byte signed long to write.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public bool WritePackedLong(long value, in StreamCompressionModel model)
        {
            ulong interleaved = (ulong)((value >> 63) ^ (value << 1));      // interleave negative values between positive values: 0, -1, 1, -2, 2
            return WritePackedULong(interleaved, model);
        }
        /// 
        /// Writes a 4-byte floating point value to the data stream using a .
        /// 
        /// The 4-byte floating point value to write.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public bool WritePackedFloat(float value, in StreamCompressionModel model)
        {
            return WritePackedFloatDelta(value, 0, model);
        }
        /// 
        /// Writes a 8-byte floating point value to the data stream using a .
        /// 
        /// The 8-byte floating point value to write.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public bool WritePackedDouble(double value, in StreamCompressionModel model)
        {
            return WritePackedDoubleDelta(value, 0, model);
        }
        /// 
        /// Writes a delta 4-byte unsigned integer value to the data stream using a .
        /// Note that the Uint values are cast to an Int after computing the diff.
        /// 
        /// The current 4-byte unsigned integer value.
        /// The previous 4-byte unsigned integer value, used to compute the diff.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public bool WritePackedUIntDelta(uint value, uint baseline, in StreamCompressionModel model)
        {
            int diff = (int)(baseline - value);
            return WritePackedInt(diff, model);
        }
        /// 
        /// Writes a delta 4-byte signed integer value to the data stream using a .
        /// 
        /// The current 4-byte signed integer value.
        /// The previous 4-byte signed integer value, used to compute the diff.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public bool WritePackedIntDelta(int value, int baseline, in StreamCompressionModel model)
        {
            int diff = (int)(baseline - value);
            return WritePackedInt(diff, model);
        }
        /// 
        /// Writes a delta 8-byte signed long value to the data stream using a .
        /// 
        /// The current 8-byte signed long value.
        /// The previous 8-byte signed long value, used to compute the diff.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public bool WritePackedLongDelta(long value, long baseline, in StreamCompressionModel model)
        {
            long diff = (long)(baseline - value);
            return WritePackedLong(diff, model);
        }
        /// 
        /// Writes a delta 8-byte unsigned long value to the data stream using a .
        /// Note that the unsigned long values are cast to a signed long after computing the diff.
        /// 
        /// The current 8-byte unsigned long value.
        /// The previous 8-byte unsigned long, used to compute the diff.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public bool WritePackedULongDelta(ulong value, ulong baseline, in StreamCompressionModel model)
        {
            long diff = (long)(baseline - value);
            return WritePackedLong(diff, model);
        }
        /// 
        /// Writes a 4-byte floating point value to the data stream.
        ///
        /// If the data did not change a zero bit is prepended, otherwise a 1 bit is prepended.
        /// When reading back the data, the first bit is then checked for whether the data was changed or not.
        /// 
        /// The current 4-byte floating point value.
        /// The previous 4-byte floating value, used to compute the diff.
        /// Not currently used.
        /// Whether the write was successful
        public bool WritePackedFloatDelta(float value, float baseline, in StreamCompressionModel model)
        {
            CheckWrite();
            var bits = 0;
            if (value != baseline)
                bits = 32;
            if (m_Data.length + ((m_Data.bitIndex + 1 + bits + 7) >> 3) > m_Data.capacity)
            {
                ++m_Data.failedWrites;
                return false;
            }
            if (bits == 0)
                WriteRawBitsInternal(0, 1);
            else
            {
                WriteRawBitsInternal(1, 1);
                UIntFloat uf = new UIntFloat();
                uf.floatValue = value;
                WriteRawBitsInternal(uf.intValue, bits);
            }
            FlushBits();
            return true;
        }
        /// 
        /// Writes a 8-byte floating point value to the data stream.
        ///
        /// If the data did not change a zero bit is prepended, otherwise a 1 bit is prepended.
        /// When reading back the data, the first bit is then checked for whether the data was changed or not.
        /// 
        /// The current 8-byte floating point value.
        /// The previous 8-byte floating value, used to compute the diff.
        /// Not currently used.
        /// Whether the write was successful
        public bool WritePackedDoubleDelta(double value, double baseline, in StreamCompressionModel model)
        {
            CheckWrite();
            var bits = 0;
            if (value != baseline)
                bits = 64;
            if (m_Data.length + ((m_Data.bitIndex + 1 + bits + 7) >> 3) > m_Data.capacity)
            {
                ++m_Data.failedWrites;
                return false;
            }
            if (bits == 0)
                WriteRawBitsInternal(0, 1);
            else
            {
                WriteRawBitsInternal(1, 1);
                UIntFloat uf = new UIntFloat();
                uf.doubleValue = value;
                var data = (uint*)&uf.longValue;
                WriteRawBitsInternal(data[0], 32);
                FlushBits();
                WriteRawBitsInternal(data[1], 32);
            }
            FlushBits();
            return true;
        }
        /// 
        /// Writes a FixedString32Bytes value to the data stream.
        /// 
        /// The FixedString32Bytes to write.
        /// Whether the write was successful
        public unsafe bool WriteFixedString32(FixedString32Bytes str)
        {
            int length = (int)*((ushort*)&str) + 2;
            byte* data = ((byte*)&str);
            return WriteBytesInternal(data, length);
        }
        /// 
        /// Writes a FixedString64Bytes value to the data stream.
        /// 
        /// The FixedString64Bytes to write.
        /// Whether the write was successful
        public unsafe bool WriteFixedString64(FixedString64Bytes str)
        {
            int length = (int)*((ushort*)&str) + 2;
            byte* data = ((byte*)&str);
            return WriteBytesInternal(data, length);
        }
        /// 
        /// Writes a FixedString128Bytes value to the data stream.
        /// 
        /// The FixedString128Bytes to write.
        /// Whether the write was successful
        public unsafe bool WriteFixedString128(FixedString128Bytes str)
        {
            int length = (int)*((ushort*)&str) + 2;
            byte* data = ((byte*)&str);
            return WriteBytesInternal(data, length);
        }
        /// 
        /// Writes a FixedString512Bytes value to the data stream.
        /// 
        /// The FixedString512Bytes to write.
        /// Whether the write was successful
        public unsafe bool WriteFixedString512(FixedString512Bytes str)
        {
            int length = (int)*((ushort*)&str) + 2;
            byte* data = ((byte*)&str);
            return WriteBytesInternal(data, length);
        }
        /// 
        /// Writes a FixedString4096Bytes value to the data stream.
        /// 
        /// The FixedString4096Bytes to write.
        /// Whether the write was successful
        public unsafe bool WriteFixedString4096(FixedString4096Bytes str)
        {
            int length = (int)*((ushort*)&str) + 2;
            byte* data = ((byte*)&str);
            return WriteBytesInternal(data, length);
        }
        /// 
        /// Writes a FixedString32Bytes delta value to the data stream using a .
        /// 
        /// The current FixedString32Bytes value.
        /// The previous FixedString32Bytes value, used to compute the diff.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public unsafe bool WritePackedFixedString32Delta(FixedString32Bytes str, FixedString32Bytes baseline, in StreamCompressionModel model)
        {
            ushort length = *((ushort*)&str);
            byte* data = ((byte*)&str) + 2;
            return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
        }
        /// 
        /// Writes a delta FixedString64Bytes value to the data stream using a .
        /// 
        /// The current FixedString64Bytes value.
        /// The previous FixedString64Bytes value, used to compute the diff.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public unsafe bool WritePackedFixedString64Delta(FixedString64Bytes str, FixedString64Bytes baseline, in StreamCompressionModel model)
        {
            ushort length = *((ushort*)&str);
            byte* data = ((byte*)&str) + 2;
            return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
        }
        /// 
        /// Writes a delta FixedString128Bytes value to the data stream using a .
        /// 
        /// The current FixedString128Bytes value.
        /// The previous FixedString128Bytes value, used to compute the diff.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public unsafe bool WritePackedFixedString128Delta(FixedString128Bytes str, FixedString128Bytes baseline, in StreamCompressionModel model)
        {
            ushort length = *((ushort*)&str);
            byte* data = ((byte*)&str) + 2;
            return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
        }
        /// 
        /// Writes a delta FixedString512Bytes value to the data stream using a .
        /// 
        /// The current FixedString512Bytes value.
        /// The previous FixedString512Bytes value, used to compute the diff.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public unsafe bool WritePackedFixedString512Delta(FixedString512Bytes str, FixedString512Bytes baseline, in StreamCompressionModel model)
        {
            ushort length = *((ushort*)&str);
            byte* data = ((byte*)&str) + 2;
            return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
        }
        /// 
        /// Writes a delta FixedString4096Bytes value to the data stream using a .
        /// 
        /// The current FixedString4096Bytes value.
        /// The previous FixedString4096Bytes value, used to compute the diff.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        public unsafe bool WritePackedFixedString4096Delta(FixedString4096Bytes str, FixedString4096Bytes baseline, in StreamCompressionModel model)
        {
            ushort length = *((ushort*)&str);
            byte* data = ((byte*)&str) + 2;
            return WritePackedFixedStringDelta(data, length, ((byte*)&baseline) + 2, *((ushort*)&baseline), model);
        }
        /// 
        /// Writes a delta FixedString value to the data stream using a .
        ///
        /// If the value cannot be written  will return true. This state can be cleared by
        /// calling .
        /// 
        /// Pointer to a packed fixed string.
        /// The length of the new value.
        /// The previous value, used to compute the diff.
        /// The length of the previous value.
        ///  model for writing value in a packed manner.
        /// Whether the write was successful
        unsafe bool WritePackedFixedStringDelta(byte* data, uint length, byte* baseData, uint baseLength, in StreamCompressionModel model)
        {
            var oldData = m_Data;
            if (!WritePackedUIntDelta(length, baseLength, model))
                return false;
            bool didFailWrite = false;
            if (length <= baseLength)
            {
                for (uint i = 0; i < length; ++i)
                    didFailWrite |= !WritePackedUIntDelta(data[i], baseData[i], model);
            }
            else
            {
                for (uint i = 0; i < baseLength; ++i)
                    didFailWrite |= !WritePackedUIntDelta(data[i], baseData[i], model);
                for (uint i = baseLength; i < length; ++i)
                    didFailWrite |= !WritePackedUInt(data[i], model);
            }
            // If anything was not written, rewind to the previous position
            if (didFailWrite)
            {
                m_Data = oldData;
                ++m_Data.failedWrites;
            }
            return !didFailWrite;
        }
        /// 
        /// Moves the write position to the start of the data buffer used.
        /// 
        public void Clear()
        {
            m_Data.length = 0;
            m_Data.bitIndex = 0;
            m_Data.bitBuffer = 0;
            m_Data.failedWrites = 0;
        }
        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        readonly void CheckRead()
        {
#if ENABLE_UNITY_COLLECTIONS_CHECKS
            AtomicSafetyHandle.CheckReadAndThrow(m_Safety);
#endif
        }
        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS")]
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        void CheckWrite()
        {
#if ENABLE_UNITY_COLLECTIONS_CHECKS
            AtomicSafetyHandle.CheckWriteAndThrow(m_Safety);
#endif
        }
        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
        static void CheckAllocator(AllocatorManager.AllocatorHandle allocator)
        {
            if (allocator.ToAllocator != Allocator.Temp)
                throw new InvalidOperationException("DataStreamWriters can only be created with temp memory");
        }
        [Conditional("ENABLE_UNITY_COLLECTIONS_CHECKS"), Conditional("UNITY_DOTS_DEBUG")]
        static void CheckBits(uint value, int numBits)
        {
            if (numBits < 0 || numBits > 32)
                throw new ArgumentOutOfRangeException($"Invalid number of bits specified: {numBits}! Valid range is (0, 32) inclusive.");
            var errValue = (1UL << numBits);
            if (value >= errValue)
                throw new ArgumentOutOfRangeException($"Value {value} does not fit in the specified number of bits: {numBits}! Range (inclusive) is (0, {errValue-1})!");
        }
    }
}