using System;
using System.Collections.Generic;
using System.Text;

namespace BitIntegerTest
{
    public class Packet
    {
        private List<byte> buffer;
        private int bitIndex = 0;
        private int maxBitIndex = 0;
        private int previousBitIndex = -1;

        /// <summary>
        /// Expands the buffer size appending bytes so that the write functions don't overflow.
        /// Records the furthest write in the maxBitIndex
        /// </summary>
        /// <param name="bits">The number of bits to allocate and record.</param>
        private void ExpandBuffer(int bits)
        {
            if (bits < 1) throw new ArgumentOutOfRangeException("bits must be greater than 0");
            while ((bitIndex + bits + 7) / 8 > buffer.Count) buffer.Add(new byte());
            maxBitIndex = Math.Max(maxBitIndex, bitIndex + bits);
        }

        /// <summary>
        /// Constructor.
        /// </summary>
        public Packet()
        {
            buffer = new List<byte>();
        }

        /// <summary>
        /// Fills the buffer with the requested bytes to work with.
        /// </summary>
        /// <param name="buffer">The bytes to load into the buffer</param>
        public Packet(byte[] buffer)
        {
            this.buffer = new List<byte>(buffer);
        }

        /// <summary>
        /// Fills the buffer with the requested bytes to work with.
        /// </summary>
        /// <param name="buffer">The bytes to load into the buffer</param>
        /// <param name="maxBitIndex">The maximum bit index</param>
        public Packet(byte[] buffer, int maxBitIndex)
        {
            this.buffer = new List<byte>(buffer);
            this.maxBitIndex = maxBitIndex;
        }

        /// <summary>
        /// Gets or sets the bit index.
        /// </summary>
        public int BitIndex
        {
            get
            {
                return bitIndex;
            }
            set
            {
                if (value < 0 || (value + 7) / 8 > buffer.Count) throw new ArgumentOutOfRangeException("Unable to set the bit index outside of the buffer size.");
                bitIndex = value;
            }
        }

        /// <summary>
        /// Sets the bit index to the start of the data section.
        /// </summary>
        public void ResetBitIndex()
        {
            if (maxBitIndex < 32) throw new InvalidOperationException("Unable to set the bit index over the max bit index.");
            bitIndex = 32;
        }

        /// <summary>
        /// Gets or Sets the max bit index.
        /// </summary>
        public int MaxBitIndex
        {
            set
            {
                if (value < 0 || (value + 7) / 8 > buffer.Count) throw new ArgumentOutOfRangeException("Unable to set the max bit index outside of the buffer size.");
                maxBitIndex = value;
            }
        }

        public int BufferLength
        {
            get
            {
                return buffer.Count;
            }
        }

        /// <summary>
        /// Returns the buffer as an array of bytes.
        /// </summary>
        public byte[] Buffer
        {
            get
            {
                return buffer.ToArray();
            }
        }

        public int HeaderSize
        {
            get
            {
                return 4;
            }
        }

        /// <summary>
        /// Returns a string of 0s and 1s representing the bits in the buffer. Good for debugging.
        /// Places a space between nibbles and two spaces between bytes.
        /// </summary>
        /// <returns></returns>
        public string Trace()
        {
            string s = string.Empty;
            for (int copyBits = 0; copyBits < buffer.Count * 8; ++copyBits)
            {
                s += ((buffer[copyBits / 8] >> (7 - copyBits % 8)) & 0x1) == 0 ? "0" : "1";
                if ((copyBits + 1) % 4 == 0 && copyBits != 0)
                {
                    s += " ";
                    if ((copyBits + 1) % 8 == 0)
                    {
                        s += " ";
                    }
                }
            }
            return s;
        }

        /// <summary>
        /// Rounds the bitIndex up to a byte.
        /// </summary>
        public void RoundUpToByte()
        {
            bitIndex = (bitIndex + 7) / 8 * 8;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns>true if 32 bits exist in the buffer</returns>
        public bool HasHeader()
        {
            return buffer.Count >= 4;
        }

        /// <summary>
        /// Reads the length in bits stored in the header.
        /// </summary>
        public void ReadHeader()
        {
            int oldBitIndex = bitIndex;
            bitIndex = 0;
            maxBitIndex = 32;
            uint length;
            if (ReadUInt32(out length))
            {
                if (length > int.MaxValue)
                {
                    maxBitIndex = int.MaxValue;
                }
                else
                {
                    maxBitIndex = (int)length;
                }
            }
            bitIndex = oldBitIndex;
        }

        /// <summary>
        /// Writes a begin packet header of 32 bits.
        /// </summary>
        internal void BeginPacket()
        {
            WriteUInt32(0);
        }

        /// <summary>
        /// Writes maxBitIndex to the packet header created with BeginPacket.
        /// </summary>
        internal void EndPacket()
        {
            int oldBitIndex = bitIndex;
            bitIndex = 0;
            WriteUInt32((uint)maxBitIndex);
            bitIndex = oldBitIndex;
        }

        /// <summary>
        /// Appends a byte directly to the end of the buffer.
        /// </summary>
        /// <param name="value"></param>
        public void AppendByte(byte value)
        {
            buffer.Add(value);
            bitIndex += 8;
        }

        /// <summary>
        /// Determines if the packet has read in all the data defined by the maxBitIndex.
        /// </summary>
        /// <returns></returns>
        public bool HasData()
        {
            return bitIndex >= maxBitIndex;
        }

        /// <summary>
        /// Rollback as if the last read didn't occur.
        /// </summary>
        public void RollbackRead()
        {
            if (previousBitIndex == -1) throw new InvalidOperationException("A read method must be called successfully first");
            bitIndex = previousBitIndex;
            previousBitIndex = -1;
        }

        public void ClearBits(int bits)
        {
            if (bitIndex % 8 + bits - 1 < 8)
            {
                byte mask = 0xFF;
                for (int bitItr = bitIndex; bitItr < bitIndex + bits; ++bitItr)
                {
                    mask ^= (byte)(1 << 7 - bitItr % 8);
                }
                buffer[bitIndex / 8] &= mask;
            }
            else if (bitIndex % 8 + bits - 1 < 16)
            {
                int offset = 8 - bitIndex % 8;
                buffer[bitIndex / 8] >>= offset;
                buffer[bitIndex / 8] <<= offset;
                offset = bits - (8 - bitIndex % 8);
                buffer[bitIndex / 8 + 1] <<= offset;
                buffer[bitIndex / 8 + 1] >>= offset;
            }
            else
            {
                int offset = 8 - bitIndex % 8;
                buffer[bitIndex / 8] >>= offset;
                buffer[bitIndex / 8] <<= offset;
                for (int bitItr = bitIndex + 8 - bitIndex % 8; bitItr < (bitIndex + bits - 1) / 8 * 8; bitItr += 8)
                {
                    buffer[bitItr / 8] = 0;
                }
                offset = bitIndex + bits - (bitIndex + bits - 1) / 8 * 8;
                buffer[(bitIndex + bits - 1) / 8] <<= offset;
                buffer[(bitIndex + bits - 1) / 8] >>= offset;
            }
        }

        // Write Methods

        /// <summary>
        /// Writes an Event ID.
        /// </summary>
        /// <param name="value">The event ID normally stored in an enumeration.</param>
        public void WriteEventID(uint value)
        {
            WriteVariableWidthUInt(value, 6);
        }

        /// <summary>
        /// Writes a single bit either 0 or 1 into the buffer.
        /// </summary>
        /// <param name="value">The boolean value to write.</param>
        public void WriteBool(bool value)
        {
            ExpandBuffer(1);
            if (value) buffer[bitIndex / 8] |= (byte)(1 << (7 - bitIndex % 8));
            ++bitIndex;
        }

        /// <summary>
        /// Writes an 8 bit unsigned byte into the buffer.
        /// </summary>
        /// <param name="value">The unsigned byte value to write.</param>
        public void WriteByte(byte value)
        {
            ExpandBuffer(8);
            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> offset);
            if (offset != 0)
            {
                buffer[bitIndex / 8 + 1] |= (byte)(value << 8 - offset);
            }
            bitIndex += 8;
        }

        /// <summary>
        /// Writes an 8 bit signed byte into the buffer.
        /// </summary>
        /// <param name="value">The signed byte value to write.</param>
        public void WriteSByte(sbyte value)
        {
            WriteByte((byte)value);
        }

        /// <summary>
        /// Writes a 16 bit unsigned short into the buffer.
        /// </summary>
        /// <param name="value">The unsigned short value to write.</param>
        public void WriteUInt16(ushort value)
        {
            ExpandBuffer(16);
            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> 8 + offset);
            buffer[bitIndex / 8 + 1] |= (byte)(value >> offset);
            if (offset != 0)
            {
                buffer[bitIndex / 8 + 2] |= (byte)(value << 8 - offset);
            }
            bitIndex += 16;
        }

        /// <summary>
        /// Writes a 16 bit signed short into the buffer.
        /// </summary>
        /// <param name="value">The signed short value to write.</param>
        public void WriteInt16(short value)
        {
            WriteUInt16((ushort)value);
        }

        /// <summary>
        /// Writes a 32 bit unsigned integer into the buffer.
        /// </summary>
        /// <param name="value">The unsigned integer value to write.</param>
        public void WriteUInt32(uint value)
        {
            ExpandBuffer(32);
            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> 24 + offset);
            buffer[bitIndex / 8 + 1] |= (byte)(value >> 16 + offset);
            buffer[bitIndex / 8 + 2] |= (byte)(value >> 8 + offset);
            buffer[bitIndex / 8 + 3] |= (byte)(value >> offset);
            if (offset != 0)
            {
                buffer[bitIndex / 8 + 4] |= (byte)(value << 8 - offset);
            }
            bitIndex += 32;
        }

        /// <summary>
        /// Writes a 32 bit signed integer into the buffer.
        /// </summary>
        /// <param name="value">The signed integer value to write.</param>
        public void WriteInt32(int value)
        {
            WriteUInt32((uint)value);
        }

        /// <summary>
        /// Writes a 64 bit unsigned integer into the buffer.
        /// </summary>
        /// <param name="value">The unsigned integer value to write.</param>
        public void WriteUInt64(ulong value)
        {
            ExpandBuffer(64);
            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> 56 + offset);
            buffer[bitIndex / 8 + 1] |= (byte)(value >> 48 + offset);
            buffer[bitIndex / 8 + 2] |= (byte)(value >> 32 + offset);
            buffer[bitIndex / 8 + 3] |= (byte)(value >> 24 + offset);
            buffer[bitIndex / 8 + 4] |= (byte)(value >> 16 + offset);
            buffer[bitIndex / 8 + 5] |= (byte)(value >> 8 + offset);
            buffer[bitIndex / 8 + 6] |= (byte)(value >> offset);
            if (offset != 0)
            {
                buffer[bitIndex / 8 + 7] |= (byte)(value << 8 - offset);
            }
            bitIndex += 64;
        }

        /// <summary>
        /// Writes a 64 bit signed integer into the buffer.
        /// </summary>
        /// <param name="value">The signed integer value to write.</param>
        public void WriteInt64(long value)
        {
            WriteUInt64((ulong)value);
        }

        /// <summary>
        /// Writes an n bit unsigned integer into the buffer.
        /// </summary>
        /// <param name="value">The unsigned integer value to write.</param>
        /// <param name="bits">The number of bits to use.</param>
        public void WriteUInt(uint value, int bits)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");
            if (bits != 32 && value > (0x1 << bits) - 1) throw new ArgumentOutOfRangeException("Value does not fit into " + bits.ToString() + " bits.");

            ExpandBuffer(bits);

            value <<= 32 - bits;

            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> 24 + offset);
            if (offset + bits > 8)
            {
                buffer[bitIndex / 8 + 1] |= (byte)(value >> 16 + offset);
                if (offset + bits > 16)
                {
                    buffer[bitIndex / 8 + 2] |= (byte)(value >> 8 + offset);
                    if (offset + bits > 24)
                    {
                        buffer[bitIndex / 8 + 3] |= (byte)(value >> offset);
                        if (offset + bits > 32)
                        {
                            buffer[bitIndex / 8 + 4] |= (byte)(value << 8 - offset);
                        }
                    }
                }
            }
            bitIndex += bits;
        }

        /// <summary>
        /// Writes an n bit unsigned integer into the buffer.
        /// </summary>
        /// <param name="value">The unsigned integer value to write.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        public void WriteUInt(uint value, uint min, uint max)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            if (value < min || value > max) throw new ArgumentOutOfRangeException("The value must be on the interval [min, max]");

            uint valueBase = max - min + 1;
            // Log2
            var bits = 32;
            if (valueBase != 0)
            {
                int left = 0;
                int right = 32;
                for (int i = 0; i < 5; ++i)
                {
                    if (valueBase >> (32 - (left + right) / 2) != 0)
                    {
                        // Left
                        right = (left + right) / 2;
                    }
                    else
                    {
                        // Right
                        left = (left + right) / 2;
                    }
                }
                bits = 32 - left;
            }
            WriteUInt(value - min, bits);
        }

        /// <summary>
        /// Writes an n bit unsigned integer into the buffer.
        /// </summary>
        /// <param name="value">The unsigned integer value to write.</param>
        /// <param name="bits">The number of bits to use.</param>
        public void WriteUInt(ulong value, int bits)
        {
            if (bits < 1 || bits > 64) throw new ArgumentOutOfRangeException("bits must be in the range (0, 64].");
            if (bits != 64 && value > ((ulong)0x1 << bits) - 1) throw new ArgumentOutOfRangeException("Value does not fit into " + bits.ToString() + " bits.");

            ExpandBuffer(bits);

            value <<= 64 - bits;

            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(value >> 56 + offset);
            if (offset + bits > 8)
            {
                buffer[bitIndex / 8 + 1] |= (byte)(value >> 48 + offset);
                if (offset + bits > 16)
                {
                    buffer[bitIndex / 8 + 2] |= (byte)(value >> 40 + offset);
                    if (offset + bits > 24)
                    {
                        buffer[bitIndex / 8 + 3] |= (byte)(value >> 32 + offset);
                        if (offset + bits > 32)
                        {
                            buffer[bitIndex / 8 + 4] |= (byte)(value >> 24 + offset);
                            if (offset + bits > 40)
                            {
                                buffer[bitIndex / 8 + 5] |= (byte)(value >> 16 + offset);
                                if (offset + bits > 48)
                                {
                                    buffer[bitIndex / 8 + 6] |= (byte)(value >> 8 + offset);
                                    if (offset + bits > 56)
                                    {
                                        buffer[bitIndex / 8 + 7] |= (byte)(value >> offset);
                                        if (offset + bits > 64)
                                        {
                                            buffer[bitIndex / 8 + 8] |= (byte)(value << 8 - offset);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            bitIndex += bits;
        }

        /// <summary>
        /// Writes an n bit unsigned integer into the buffer.
        /// </summary>
        /// <param name="value">The unsigned integer value to write.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        public void WriteUInt(ulong value, ulong min, ulong max)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            if (value < min || value > max) throw new ArgumentOutOfRangeException("The value must be on the interval [min, max]");

            ulong valueBase = max - min + 1;
            // Log2
            var bits = 64;
            if (valueBase != 0)
            {
                int left = 0;
                int right = 64;
                for (int i = 0; i < 6; ++i)
                {
                    if (valueBase >> (64 - (left + right) / 2) != 0)
                    {
                        // Left
                        right = (left + right) / 2;
                    }
                    else
                    {
                        // Right
                        left = (left + right) / 2;
                    }
                }
                bits = 64 - left;
            }
            WriteUInt(value - min, bits);
        }

        /// <summary>
        /// Writes an n bit signed integer into the buffer.
        /// </summary>
        /// <param name="value">The signed integer value to write.</param>
        /// <param name="bits">The number of bits to use.</param>
        public void WriteInt(int value, int bits)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");
            if (bits != 32 && (value < -(0x1 << (bits - 1)) || value >= 0x1 << (bits - 1))) throw new ArgumentOutOfRangeException("Value does not fit into " + bits.ToString() + " bits.");

            ExpandBuffer(bits);

            value <<= 32 - bits;
            uint uvalue = (uint)value;

            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(uvalue >> 24 + offset);
            if (offset + bits > 8)
            {
                buffer[bitIndex / 8 + 1] |= (byte)(uvalue >> 16 + offset);
                if (offset + bits > 16)
                {
                    buffer[bitIndex / 8 + 2] |= (byte)(uvalue >> 8 + offset);
                    if (offset + bits > 24)
                    {
                        buffer[bitIndex / 8 + 3] |= (byte)(uvalue >> offset);
                        if (offset + bits > 32)
                        {
                            buffer[bitIndex / 8 + 4] |= (byte)(uvalue << 8 - offset);
                        }
                    }
                }
            }
            bitIndex += bits;
        }

        /// <summary>
        /// Writes an n bit signed integer into the buffer.
        /// </summary>
        /// <param name="value">The signed integer value to write.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        public void WriteInt(int value, int min, int max)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            if (value < min || value > max) throw new ArgumentOutOfRangeException("The value must be on the interval [min, max]");

            uint valueBase = (uint)(max - min + 1);
            // Log2
            var bits = 32;
            if (valueBase != 0)
            {
                int left = 0;
                int right = 32;
                for (int i = 0; i < 5; ++i)
                {
                    if (valueBase >> (32 - (left + right) / 2) != 0)
                    {
                        // Left
                        right = (left + right) / 2;
                    }
                    else
                    {
                        // Right
                        left = (left + right) / 2;
                    }
                }
                bits = 32 - left;
            }
            WriteInt(value - min, bits);
        }

        /// <summary>
        /// Writes an n bit signed integer into the buffer.
        /// </summary>
        /// <param name="value">The signed integer value to write.</param>
        /// <param name="bits">The number of bits to use.</param>
        public void WriteInt(long value, int bits)
        {
            if (bits < 1 || bits > 64) throw new ArgumentOutOfRangeException("bits must be in the range (0, 64].");
            if (bits != 64 && (value < -((long)0x1 << (bits - 1)) || value >= (long)0x1 << (bits - 1))) throw new ArgumentOutOfRangeException("Value does not fit into " + bits.ToString() + " bits.");

            ExpandBuffer(bits);

            value <<= 64 - bits;
            ulong uvalue = (ulong)value;

            int offset = bitIndex % 8;
            buffer[bitIndex / 8] |= (byte)(uvalue >> 56 + offset);
            if (offset + bits > 8)
            {
                buffer[bitIndex / 8 + 1] |= (byte)(uvalue >> 48 + offset);
                if (offset + bits > 16)
                {
                    buffer[bitIndex / 8 + 2] |= (byte)(uvalue >> 40 + offset);
                    if (offset + bits > 24)
                    {
                        buffer[bitIndex / 8 + 3] |= (byte)(uvalue >> 32 + offset);
                        if (offset + bits > 32)
                        {
                            buffer[bitIndex / 8 + 4] |= (byte)(uvalue >> 24 + offset);
                            if (offset + bits > 40)
                            {
                                buffer[bitIndex / 8 + 5] |= (byte)(uvalue >> 16 + offset);
                                if (offset + bits > 48)
                                {
                                    buffer[bitIndex / 8 + 6] |= (byte)(uvalue >> 8 + offset);
                                    if (offset + bits > 56)
                                    {
                                        buffer[bitIndex / 8 + 7] |= (byte)(uvalue >> offset);
                                        if (offset + bits > 64)
                                        {
                                            buffer[bitIndex / 8 + 8] |= (byte)(uvalue << 8 - offset);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            bitIndex += bits;
        }

        /// <summary>
        /// Writes an n bit signed integer into the buffer.
        /// </summary>
        /// <param name="value">The signed integer value to write.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        public void WriteInt(long value, long min, long max)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            if (value < min || value > max) throw new ArgumentOutOfRangeException("The value must be on the interval [min, max]");

            ulong valueBase = (ulong)(max - min + 1);
            // Log2
            var bits = 64;
            if (valueBase != 0)
            {
                int left = 0;
                int right = 64;
                for (int i = 0; i < 6; ++i)
                {
                    if (valueBase >> (64 - (left + right) / 2) != 0)
                    {
                        // Left
                        right = (left + right) / 2;
                    }
                    else
                    {
                        // Right
                        left = (left + right) / 2;
                    }
                }
                bits = 64 - left;
            }
            WriteInt(value - min, bits);
        }

        /// <summary>
        /// Writes a 32 bit single into the buffer.
        /// </summary>
        /// <param name="value">The single value to write.</param>
        public void WriteSingle(float value)
        {
            WriteUInt32(BitConverter.ToUInt32(BitConverter.GetBytes(value), 0));
        }

        /// <summary>
        /// Writes a 64 bit double into the buffer.
        /// </summary>
        /// <param name="value">The double value to write.</param>
        public void WriteDouble(double value)
        {
            byte[] bytes = BitConverter.GetBytes(value);
            WriteUInt32(BitConverter.ToUInt32(bytes, 0));
            WriteUInt32(BitConverter.ToUInt32(bytes, 4));
        }

        /// <summary>
        /// Writes an integer using a variable width encoding of bits. Choose a bits value that represents the number of bits to hold the average value.
        /// </summary>
        /// <param name="value">The unsigned integer value to write.</param>
        /// <param name="bits">The number of bits to use for the sequence.</param>
        public void WriteVariableWidthUInt(uint value, int bits = 4)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");
            int shift = bits;
            // Stop when our value can fit inside
            for (; shift < 32 && value >= (0x1 << shift); shift += bits)
            {
                WriteBool(true); // Write a 1 for a continuation bit signifying one more interval is needed
            }
            if (shift < 32)
            {
                WriteBool(false); // Write a 0 for a continuation bit signifying the end
            }
            WriteUInt(value, shift >= 32 ? 32 : shift);
        }

        /// <summary>
        /// Writes an integer using a variable width encoding of bits. Choose a bits value that represents the number of bits to hold the average value.
        /// </summary>
        /// <param name="value">The unsigned integer value to write.</param>
        /// <param name="bits">The number of bits to use for the sequence.</param>
        public void WriteVariableWidthUInt(ulong value, int bits = 4)
        {
            if (bits < 1 || bits > 64) throw new ArgumentOutOfRangeException("bits must be in the range (0, 64].");
            int shift = bits;
            // Stop when our value can fit inside
            for (; shift < 64 && value >= (ulong)(0x1 << shift); shift += bits)
            {
                WriteBool(true); // Write a 1 for a continuation bit signifying one more interval is needed
            }
            if (shift < 64)
            {
                WriteBool(false); // Write a 0 for a continuation bit signifying the end
            }
            WriteUInt(value, shift >= 64 ? 64 : shift);
        }

        /// <summary>
        /// Writes an integer using a variable length of bits. Choose a bits value that represents the number of bits to hold the average value.
        /// </summary>
        /// <param name="value">The signed integer value to write.</param>
        /// <param name="bits">The number of bits to use for the sequence.</param>
        public void WriteVariableWidthInt(int value, int bits = 4)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");
            int shift = bits;
            // Stop when our value can fit inside
            for (; shift < 32 && (value < -(0x1 << (shift - 1)) || value >= 0x1 << (shift - 1)); shift += bits)
            {
                WriteBool(true); // Write a 1 for a continuation bit signifying one more interval is needed
            }
            if (shift < 32)
            {
                WriteBool(false); // Write a 0 for a continuation bit signifying the end
            }
            WriteInt(value, shift >= 32 ? 32 : shift);
        }

        /// <summary>
        /// Writes an integer using a variable length of bits. Choose a bits value that represents the number of bits to hold the average value.
        /// </summary>
        /// <param name="value">The signed integer value to write.</param>
        /// <param name="bits">The number of bits to use for the sequence.</param>
        public void WriteVariableWidthInt(long value, int bits = 4)
        {
            if (bits < 1 || bits > 64) throw new ArgumentOutOfRangeException("bits must be in the range (0, 64].");
            int shift = bits;
            // Stop when our value can fit inside
            for (; shift < 64 && (value < -(0x1 << (shift - 1)) || value >= 0x1 << (shift - 1)); shift += bits)
            {
                WriteBool(true); // Write a 1 for a continuation bit signifying one more interval is needed
            }
            if (shift < 64)
            {
                WriteBool(false); // Write a 0 for a continuation bit signifying the end
            }
            WriteInt(value, shift >= 64 ? 64 : shift);
        }

        /// <summary>
        /// Writes an array of unsigned integers in a given range defining a base.
        /// </summary>
        /// <param name="value">The array of unsigned integers to write.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        public void WriteArrayUInt32(uint[] value, int min, int max)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            ulong valueBase = (ulong)(max - min + 1);
            var sum = new List<ulong>();
            var valueBasePow = new List<ulong>();
            valueBasePow.Add(1);
            for (var i = 0; i < value.Length; ++i)
            {
                // sum += arrayBasePow * array[i];
                ulong multiplyCarry = 0;
                ulong addCarry = 0;
                for (var j = 0; j < valueBasePow.Count || multiplyCarry != 0 || j < sum.Count || addCarry != 0; ++j)
                {
                    if (j >= sum.Count)
                    {
                        sum.Add(0);
                    }

                    ulong temp = 0;
                    ulong product = 0;
                    if (j < valueBasePow.Count)
                    {
                        product = (ulong)(value[i] - min) * valueBasePow[j];
                    }
                    temp += product + multiplyCarry;
                    multiplyCarry = temp >> 32;
                    temp &= 0xFFFFFFFF;

                    sum[j] += temp + addCarry;
                    addCarry = sum[j] >> 32;
                    sum[j] &= 0xFFFFFFFF;
                }
                // arrayBasePow *= arrayBase;
                multiplyCarry = 0;
                for (var j = 0; j < valueBasePow.Count || multiplyCarry != 0; ++j)
                {
                    if (j >= valueBasePow.Count)
                    {
                        valueBasePow.Add(0);
                    }
                    ulong product = 0;
                    if (j < valueBasePow.Count)
                    {
                        product = valueBase * valueBasePow[j];
                    }
                    valueBasePow[j] = product + multiplyCarry;
                    multiplyCarry = valueBasePow[j] >> 32;
                    valueBasePow[j] &= 0xFFFFFFFF;
                }
            }

            for (var i = sum.Count - 1; i > 0 && sum[i] == 0; --i)
            {
                sum.RemoveAt(sum.Count - 1);
            }

            // Log2
            int numberOfBits = 0;
            if (!(valueBasePow.Count == 1 && valueBasePow[0] == 0))
            {
                int left = 0;
                int right = 32;
                for (int i = 0; i < 5; ++i)
                {
                    if (valueBasePow[valueBasePow.Count - 1] >> (32 - (left + right) / 2) != 0)
                    {
                        // Left
                        right = (left + right) / 2;
                    }
                    else
                    {
                        // Right
                        left = (left + right) / 2;
                    }
                }
                numberOfBits = (valueBasePow.Count - 1) * 32 + (32 - left);
            }

            for (int copyBits = 0; copyBits < numberOfBits; copyBits += 32)
            {
                WriteUInt((uint)sum[copyBits / 32], numberOfBits - copyBits > 32 ? 32 : numberOfBits - copyBits);
            }
        }

        /// <summary>
        /// Writes an array of signed integers in a given range defining a base.
        /// </summary>
        /// <param name="value">The array of signed integers to write.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        public void WriteArrayInt32(int[] value, int min, int max)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            var uValue = new uint[value.Length];
            for (var i = 0; i < value.Length; ++i)
            {
                uValue[i] = (uint)(value[i] - min);
            }
            WriteArrayUInt32(uValue, 0, max - min);
        }

        /// <summary>
        /// Writes an array of singles in a given range converted to a given integral base.
        /// Creates a value for the ratio: value / (valueBase - 1) in relationship to value / (max - min).
        /// </summary>
        /// <param name="value">The array of singles to write.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="valueBase">The base to convert the singles to.</param>
        public void WriteArraySingle(float[] value, float min, float max, int valueBase)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            var uValue = new uint[value.Length];
            if (min < 0 && max > 0)
            {
                for (var i = 0; i < value.Length; ++i)
                {
                    uValue[i] = value[i] == 0 ? 0 : (uint)Math.Round((value[i] - min) / (max - min) * (valueBase - 2)) + 1;
                }
            }
            else
            {
                for (var i = 0; i < value.Length; ++i)
                {
                    uValue[i] = (uint)Math.Round((value[i] - min) / (max - min) * (valueBase - 1));
                }
            }
            WriteArrayUInt32(uValue, 0, valueBase - 1);
        }

        /// <summary>
        /// Writes an array of doubles in a given range converted to a given integral base.
        /// Creates a value for the ratio: value / (valueBase - 1) in relationship to value / (max - min).
        /// </summary>
        /// <param name="value">The array of doubles to write.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="valueBase">The base to convert the doubles to.</param>
        public void WriteArrayDouble(double[] value, double min, double max, int valueBase)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            var uValue = new uint[value.Length];
            if (min < 0 && max > 0)
            {
                for (var i = 0; i < value.Length; ++i)
                {
                    uValue[i] = value[i] == 0 ? 0 : (uint)Math.Round((value[i] - min) / (max - min) * (valueBase - 2)) + 1;
                }
            }
            else
            {
                for (var i = 0; i < value.Length; ++i)
                {
                    uValue[i] = (uint)Math.Round((value[i] - min) / (max - min) * (valueBase - 1));
                }
            }
            WriteArrayUInt32(uValue, 0, valueBase - 1);
        }

        /// <summary>
        /// Creates a value for the ratio: value / (2 ^ bitResolution - 1) in relationship to value / (max - min).
        /// This allows a floating point to have a resolution.
        /// </summary>
        /// <param name="value">The floating point value in the range.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="bitResolution">The number of bits to use for the ratio.</param>
        public void WriteCustomResolutionSingle(float value, float min, float max, int bitResolution)
        {
            if (bitResolution < 1 || bitResolution > 31) throw new ArgumentOutOfRangeException("bitResolution must be in the range (0, 32).");
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            if (value < min || value > max) throw new ArgumentOutOfRangeException("The value must be on the interval [min, max]");
            uint uValue;
            if (min < 0 && max > 0)
            {
                uValue = value == 0 ? 0 : (uint)Math.Round((value - min) / (max - min) * (float)((0x1 << bitResolution) - 2)) + 1;
            }
            else
            {
                uValue = (uint)Math.Round((value - min) / (max - min) * (float)((0x1 << bitResolution) - 1));
            }
            WriteUInt(uValue, bitResolution);
        }

        /// <summary>
        /// Creates a value for the ratio: value / (2 ^ bitResolution - 1) in relationship to value / (max - min).
        /// This allows a floating point to have a resolution.
        /// </summary>
        /// <param name="value">The double value in the range.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="bitResolution">The number of bits to use for the ratio.</param>
        public void WriteCustomResolutionDouble(double value, double min, double max, int bitResolution)
        {
            if (bitResolution < 1 || bitResolution > 31) throw new ArgumentOutOfRangeException("bitResolution must be in the range (0, 32).");
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            if (value < min || value > max) throw new ArgumentOutOfRangeException("The value must be on the interval [min, max]");
            uint uValue;
            if (min < 0 && max > 0)
            {
                uValue = value == 0 ? 0 : (uint)Math.Round((value - min) / (max - min) * (double)((0x1 << bitResolution) - 2)) + 1;
            }
            else
            {
                uValue = (uint)Math.Round((value - min) / (max - min) * (double)((0x1 << bitResolution) - 1));
            }
            WriteUInt(uValue, bitResolution);
        }

        /// <summary>
        /// Writes the length of the string using WriteVariableWidthUInt and then writes a boolean, true for unicode, false for ascii. 
        /// ASCII uses a compression step using either 6 or 7 bits per character.
        /// </summary>
        /// <param name="value">The string value to write.</param>
        /// <param name="bits">The number of bits for the length written using an unsigned variable-width integer.</param>
        public void WriteString(string value, int bits = 4)
        {
            if (bits < 1 || bits > 31) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32).");

            var asciiBytes = Encoding.ASCII.GetBytes(value);
            uint size = (uint)asciiBytes.Length;
            WriteVariableWidthUInt(size, bits);
            foreach (byte asciiByte in asciiBytes)
            {
                WriteByte(asciiByte);
            }
        }

        /// <summary>
        /// Appends a binary packet to the buffer.
        /// </summary>
        /// <param name="value">The binary packet to write.</param>
        public void WriteBinaryPacket(Packet value)
        {
            int oldBitIndex = value.bitIndex;
            value.bitIndex = 0;
            int valueMaxBitIndex = value.maxBitIndex;
            WriteVariableWidthUInt((uint)valueMaxBitIndex);
            for (int copyBits = 0; copyBits < valueMaxBitIndex; copyBits += 32)
            {
                uint uValue;
                value.ReadUInt(out uValue, valueMaxBitIndex - copyBits > 32 ? 32 : valueMaxBitIndex - copyBits);
                WriteUInt(uValue, valueMaxBitIndex - copyBits > 32 ? 32 : valueMaxBitIndex - copyBits);
            }
            bitIndex += (int)valueMaxBitIndex;
            value.bitIndex = oldBitIndex;
        }

        //READ METHODS

        /// <summary>
        /// Reads an Event ID.
        /// </summary>
        /// <param name="value">The event ID normally stored in an enumeration.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadEventID(out uint value, bool nestedRead = false)
        {
            return ReadVariableWidthUInt(out value, 6);
        }

        /// <summary>
        /// Reads one bit from the buffer.
        /// </summary>
        /// <param name="value">Boolean.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadBool(out bool value, bool nestedRead = false)
        {
            if (!nestedRead) previousBitIndex = bitIndex;
            value = false;
            if ((bitIndex + 1 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            value = ((buffer[bitIndex / 8] >> (7 - bitIndex % 8)) & 0x1) == 1;
            ++bitIndex;
            return true;
        }

        /// <summary>
        /// Reads an 8 bits unsigned byte from the buffer.
        /// </summary>
        /// <param name="value">Unsigned Byte.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadByte(out byte value, bool nestedRead = false)
        {
            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + 8 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (byte)(buffer[bitIndex / 8] << offset);
            if (offset != 0)
            {
                value |= (byte)(buffer[bitIndex / 8 + 1] >> 8 - offset);
            }
            bitIndex += 8;
            return true;
        }

        /// <summary>
        /// Reads an 8 bits signed byte from the buffer.
        /// </summary>
        /// <param name="value">Signed Byte.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadSByte(out sbyte value, bool nestedRead = false)
        {
            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + 8 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (sbyte)(buffer[bitIndex / 8] << offset);
            if (offset != 0)
            {
                value |= (sbyte)(buffer[bitIndex / 8 + 1] >> 8 - offset);
            }
            bitIndex += 8;
            return true;
        }

        /// <summary>
        /// Reads a 16 bit unsigned short from the buffer.
        /// </summary>
        /// <param name="value">Unsigned Short.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadUInt16(out ushort value, bool nestedRead = false)
        {
            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + 16 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (ushort)(buffer[bitIndex / 8] << 8 + offset);
            value |= (ushort)(buffer[bitIndex / 8 + 1] << offset);
            if (offset != 0)
            {
                value |= (ushort)(buffer[bitIndex / 8 + 2] >> 8 - offset);
            }
            bitIndex += 16;
            return true;
        }

        /// <summary>
        /// Reads a 16 bit signed short from the buffer.
        /// </summary>
        /// <param name="value">Signed Short.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadInt16(out short value, bool nestedRead = false)
        {
            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + 16 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (short)(buffer[bitIndex / 8] << 8 + offset);
            value |= (short)(buffer[bitIndex / 8 + 1] << offset);
            if (offset != 0)
            {
                value |= (short)(buffer[bitIndex / 8 + 2] >> 8 - offset);
            }
            bitIndex += 16;
            return true;
        }

        /// <summary>
        /// Reads a 32 bit unsigned integer from the buffer.
        /// </summary>
        /// <param name="value">Unsigned Integer.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadUInt32(out uint value, bool nestedRead = false)
        {
            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + 32 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (uint)(buffer[bitIndex / 8] << 24 + offset);
            value |= (uint)(buffer[bitIndex / 8 + 1] << 16 + offset);
            value |= (uint)(buffer[bitIndex / 8 + 2] << 8 + offset);
            value |= (uint)(buffer[bitIndex / 8 + 3] << offset);
            if (offset != 0)
            {
                value |= (uint)(buffer[bitIndex / 8 + 4] >> 8 - offset);
            }
            bitIndex += 32;
            return true;
        }

        /// <summary>
        /// Reads a 32 bit signed integer from the buffer.
        /// </summary>
        /// <param name="value">Signed Integer.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadInt32(out int value, bool nestedRead = false)
        {
            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + 32 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (int)(buffer[bitIndex / 8] << 24 + offset);
            value |= (int)(buffer[bitIndex / 8 + 1] << 16 + offset);
            value |= (int)(buffer[bitIndex / 8 + 2] << 8 + offset);
            value |= (int)(buffer[bitIndex / 8 + 3] << offset);
            if (offset != 0)
            {
                value |= (int)(buffer[bitIndex / 8 + 4] >> 8 - offset);
            }
            bitIndex += 32;
            return true;
        }

        /// <summary>
        /// Reads a 64 bit unsigned integer from the buffer.
        /// </summary>
        /// <param name="value">Unsigned Long.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadUInt64(out ulong value, bool nestedRead = false)
        {
            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + 64 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (ulong)(buffer[bitIndex / 8]) << 56 + offset;
            value |= (ulong)(buffer[bitIndex / 8 + 1]) << 48 + offset;
            value |= (ulong)(buffer[bitIndex / 8 + 2]) << 40 + offset;
            value |= (ulong)(buffer[bitIndex / 8 + 3]) << 32 + offset;
            value |= (ulong)(buffer[bitIndex / 8 + 4]) << 24 + offset;
            value |= (ulong)(buffer[bitIndex / 8 + 5]) << 16 + offset;
            value |= (ulong)(buffer[bitIndex / 8 + 6]) << 8 + offset;
            value |= (ulong)(buffer[bitIndex / 8 + 7]) << offset;
            if (offset != 0)
            {
                value |= (ulong)(buffer[bitIndex / 8 + 8]) >> 8 - offset;
            }
            bitIndex += 64;
            return true;
        }

        /// <summary>
        /// Reads a 64 bit signed integer from the buffer.
        /// </summary>
        /// <param name="value">Signed Long.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadInt64(out long value, bool nestedRead = false)
        {
            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + 64 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            int offset = bitIndex % 8;
            value |= (long)(buffer[bitIndex / 8]) << 56 + offset;
            value |= (long)(buffer[bitIndex / 8 + 1]) << 48 + offset;
            value |= (long)(buffer[bitIndex / 8 + 2]) << 40 + offset;
            value |= (long)(buffer[bitIndex / 8 + 3]) << 32 + offset;
            value |= (long)(buffer[bitIndex / 8 + 4]) << 24 + offset;
            value |= (long)(buffer[bitIndex / 8 + 5]) << 16 + offset;
            value |= (long)(buffer[bitIndex / 8 + 6]) << 8 + offset;
            value |= (long)(buffer[bitIndex / 8 + 7]) << offset;
            if (offset != 0)
            {
                value |= (long)(buffer[bitIndex / 8 + 8]) >> 8 - offset;
            }
            bitIndex += 64;
            return true;
        }

        /// <summary>
        /// Reads an n bit unsigned integer from the buffer.
        /// </summary>
        /// <param name="value">Unsigned Integer.</param>
        /// <param name="bits">The number of bits used to write.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadUInt(out uint value, int bits, bool nestedRead = false)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");

            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + bits + 7) / 8 > buffer.Count)
            {
                return false;
            }

            int offset = bitIndex % 8;
            value = (uint)buffer[bitIndex / 8] << 24 + offset;
            if (offset + bits > 8)
            {
                value |= (uint)buffer[bitIndex / 8 + 1] << 16 + offset;
                if (offset + bits > 16)
                {
                    value |= (uint)buffer[bitIndex / 8 + 2] << 8 + offset;
                    if (offset + bits > 24)
                    {
                        value |= (uint)buffer[bitIndex / 8 + 3] << offset;
                        if (offset + bits > 32)
                        {
                            value |= (uint)buffer[bitIndex / 8 + 4] >> 8 - offset;
                        }
                    }
                }
            }

            value >>= 32 - bits;
            bitIndex += bits;
            return true;
        }

        /// <summary>
        /// Reads an n bit unsigned integer from the buffer.
        /// </summary>
        /// <param name="value">Unsigned Integer.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadUInt(out uint value, uint min, uint max, bool nestedRead = false)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");

            if (!nestedRead) previousBitIndex = bitIndex;
            uint valueBase = max - min + 1;
            // Log2
            var bits = 32;
            if (valueBase != 0)
            {
                int left = 0;
                int right = 32;
                for (int i = 0; i < 5; ++i)
                {
                    if (valueBase >> (32 - (left + right) / 2) != 0)
                    {
                        // Left
                        right = (left + right) / 2;
                    }
                    else
                    {
                        // Right
                        left = (left + right) / 2;
                    }
                }
                bits = 32 - left;
            }
            if (!ReadUInt(out value, bits, true)) return false;
            value += min;
            return true;
        }

        /// <summary>
        /// Reads an n bit unsigned integer from the buffer.
        /// </summary>
        /// <param name="value">Unsigned Integer.</param>
        /// <param name="bits">The number of bits used to write.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadUInt(out ulong value, int bits, bool nestedRead = false)
        {
            if (bits < 1 || bits > 64) throw new ArgumentOutOfRangeException("bits must be in the range (0, 64].");

            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + bits + 7) / 8 > buffer.Count)
            {
                return false;
            }

            int offset = bitIndex % 8;
            value = (ulong)buffer[bitIndex / 8] << 56 + offset;
            if (offset + bits > 8)
            {
                value |= (ulong)buffer[bitIndex / 8 + 1] << 48 + offset;
                if (offset + bits > 16)
                {
                    value |= (ulong)buffer[bitIndex / 8 + 2] << 40 + offset;
                    if (offset + bits > 24)
                    {
                        value |= (ulong)buffer[bitIndex / 8 + 3] << 32 + offset;
                        if (offset + bits > 32)
                        {
                            value |= (ulong)buffer[bitIndex / 8 + 4] << 24 + offset;
                            if (offset + bits > 40)
                            {
                                value |= (ulong)buffer[bitIndex / 8 + 5] << 16 + offset;
                                if (offset + bits > 48)
                                {
                                    value |= (ulong)buffer[bitIndex / 8 + 6] << 8 + offset;
                                    if (offset + bits > 56)
                                    {
                                        value |= (ulong)buffer[bitIndex / 8 + 7] << offset;
                                        if (offset + bits > 64)
                                        {
                                            value |= (ulong)buffer[bitIndex / 8 + 8] >> 8 - offset;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            value >>= 64 - bits;
            bitIndex += bits;
            return true;
        }

        /// <summary>
        /// Reads an n bit unsigned integer from the buffer.
        /// </summary>
        /// <param name="value">Unsigned Integer.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadUInt(out ulong value, ulong min, ulong max, bool nestedRead = false)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");

            if (!nestedRead) previousBitIndex = bitIndex;
            ulong valueBase = max - min + 1;
            // Log2
            var bits = 64;
            if (valueBase != 0)
            {
                int left = 0;
                int right = 64;
                for (int i = 0; i < 6; ++i)
                {
                    if (valueBase >> (64 - (left + right) / 2) != 0)
                    {
                        // Left
                        right = (left + right) / 2;
                    }
                    else
                    {
                        // Right
                        left = (left + right) / 2;
                    }
                }
                bits = 64 - left;
            }
            if (!ReadUInt(out value, bits, true)) return false;
            value += min;
            return true;
        }

        /// <summary>
        /// Reads an n bit custom signed integer from the buffer.
        /// </summary>
        /// <param name="value">Signed Integer.</param>
        /// <param name="bits">The number of bits used to write.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadInt(out int value, int bits, bool nestedRead = false)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");

            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + bits + 7) / 8 > buffer.Count)
            {
                return false;
            }

            int offset = bitIndex % 8;
            value = buffer[bitIndex / 8] << 24 + offset;
            if (offset + bits > 8)
            {
                value |= buffer[bitIndex / 8 + 1] << 16 + offset;
                if (offset + bits > 16)
                {
                    value |= buffer[bitIndex / 8 + 2] << 8 + offset;
                    if (offset + bits > 24)
                    {
                        value |= buffer[bitIndex / 8 + 3] << offset;
                        if (offset + bits > 32)
                        {
                            value |= buffer[bitIndex / 8 + 4] >> 8 - offset;
                        }
                    }
                }
            }

            value >>= 32 - bits;
            bitIndex += bits;
            return true;
        }

        /// <summary>
        /// Reads an n bit custom signed integer from the buffer.
        /// </summary>
        /// <param name="value">Signed Integer.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadInt(out int value, int min, int max, bool nestedRead = false)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");

            if (!nestedRead) previousBitIndex = bitIndex;
            uint valueBase = (uint)(max - min + 1);
            // Log2
            var bits = 32;
            if (valueBase != 0)
            {
                int left = 0;
                int right = 32;
                for (int i = 0; i < 5; ++i)
                {
                    if (valueBase >> (32 - (left + right) / 2) != 0)
                    {
                        // Left
                        right = (left + right) / 2;
                    }
                    else
                    {
                        // Right
                        left = (left + right) / 2;
                    }
                }
                bits = 32 - left;
            }
            if (!ReadInt(out value, bits, true)) return false;
            value += min;
            return true;
        }

        /// <summary>
        /// Reads an n bit custom signed integer from the buffer.
        /// </summary>
        /// <param name="value">Signed Integer.</param>
        /// <param name="bits">The number of bits used to write.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadInt(out long value, int bits, bool nestedRead = false)
        {
            if (bits < 1 || bits > 64) throw new ArgumentOutOfRangeException("bits must be in the range (0, 64].");

            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + bits + 7) / 8 > buffer.Count)
            {
                return false;
            }

            int offset = bitIndex % 8;
            value = buffer[bitIndex / 8] << 56 + offset;
            if (offset + bits > 8)
            {
                value |= (long)buffer[bitIndex / 8 + 1] << 48 + offset;
                if (offset + bits > 16)
                {
                    value |= (long)buffer[bitIndex / 8 + 2] << 40 + offset;
                    if (offset + bits > 24)
                    {
                        value |= (long)buffer[bitIndex / 8 + 3] << 32 + offset;
                        if (offset + bits > 32)
                        {
                            value |= (long)buffer[bitIndex / 8 + 4] << 24 + offset;
                            if (offset + bits > 40)
                            {
                                value |= (long)buffer[bitIndex / 8 + 5] << 16 + offset;
                                if (offset + bits > 48)
                                {
                                    value |= (long)buffer[bitIndex / 8 + 6] << 8 + offset;
                                    if (offset + bits > 56)
                                    {
                                        value |= (long)buffer[bitIndex / 8 + 7] << offset;
                                        if (offset + bits > 64)
                                        {
                                            value |= (long)buffer[bitIndex / 8 + 8] >> 8 - offset;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }

            value >>= 64 - bits;
            bitIndex += bits;
            return true;
        }

        /// <summary>
        /// Reads an n bit custom signed integer from the buffer.
        /// </summary>
        /// <param name="value">Signed Integer.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadInt(out long value, long min, long max, bool nestedRead = false)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");

            if (!nestedRead) previousBitIndex = bitIndex;
            ulong valueBase = (ulong)(max - min + 1);
            // Log2
            var bits = 64;
            if (valueBase != 0)
            {
                int left = 0;
                int right = 64;
                for (int i = 0; i < 6; ++i)
                {
                    if (valueBase >> (64 - (left + right) / 2) != 0)
                    {
                        // Left
                        right = (left + right) / 2;
                    }
                    else
                    {
                        // Right
                        left = (left + right) / 2;
                    }
                }
                bits = 64 - left;
            }
            if (!ReadInt(out value, bits, true)) return false;
            value += min;
            return true;
        }

        /// <summary>
        /// Reads a 32 bit single from the buffer.
        /// </summary>
        /// <param name="value">Single.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadSingle(out float value, bool nestedRead = false)
        {
            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + 32 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            uint uValue;
            if (!ReadUInt32(out uValue, true)) return false;
            value = BitConverter.ToSingle(BitConverter.GetBytes(uValue), 0);
            return true;
        }

        /// <summary>
        /// Reads a 64 bit double from the buffer.
        /// </summary>
        /// <param name="value">Double.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadDouble(out double value, bool nestedRead = false)
        {
            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            if ((bitIndex + 64 + 7) / 8 > buffer.Count)
            {
                return false;
            }
            byte[] bytes = new byte[8];
            uint uValue1;
            if (!ReadUInt32(out uValue1, true)) return false;
            byte[] first4bytes = BitConverter.GetBytes(uValue1);
            uint uValue2;
            if (!ReadUInt32(out uValue2, true)) return false;
            byte[] last4bytes = BitConverter.GetBytes(uValue2);

            for (uint copyBytes = 0; copyBytes < 4; ++copyBytes)
            {
                bytes[copyBytes] = first4bytes[copyBytes];
            }
            for (uint copyBytes = 4; copyBytes < 8; ++copyBytes)
            {
                bytes[copyBytes] = last4bytes[copyBytes - 4];
            }
            value = BitConverter.ToDouble(bytes, 0);
            return true;
        }

        /// <summary>
        /// Reads the variable-width unsigned integer.
        /// </summary>
        /// <param name="value">Unsigned Integer.</param>
        /// <param name="bits">The bit size of the sequence used to write.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadVariableWidthUInt(out uint value, int bits = 4, bool nestedRead = false)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");

            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            int valueBitCount = bits;
            bool continuationBitValue = true;
            do
            {
                if (!ReadBool(out continuationBitValue, true)) return false;
                if (continuationBitValue) valueBitCount += bits;
            }
            while (continuationBitValue && valueBitCount < 32);
            return ReadUInt(out value, valueBitCount >= 32 ? 32 : valueBitCount, true);
        }

        /// <summary>
        /// Reads the variable-width unsigned integer.
        /// </summary>
        /// <param name="value">Unsigned Integer.</param>
        /// <param name="bits">The bit size of the sequence used to write.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadVariableWidthUInt(out ulong value, int bits = 4, bool nestedRead = false)
        {
            if (bits < 1 || bits > 64) throw new ArgumentOutOfRangeException("bits must be in the range (0, 64].");

            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            int valueBitCount = bits;
            bool continuationBitValue = true;
            do
            {
                if (!ReadBool(out continuationBitValue, true)) return false;
                if (continuationBitValue) valueBitCount += bits;
            }
            while (continuationBitValue && valueBitCount < 64);
            return ReadUInt(out value, valueBitCount >= 64 ? 64 : valueBitCount, true);
        }

        /// <summary>
        /// Reads the variable-width signed integer.
        /// </summary>
        /// <param name="value">Signed Integer.</param>
        /// <param name="bits">The bit size of the sequence used to write.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadVariableWidthInt(out int value, int bits = 4, bool nestedRead = false)
        {
            if (bits < 1 || bits > 32) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32].");

            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            int valueBitCount = bits;
            bool continuationBitValue = true;
            do
            {
                if (!ReadBool(out continuationBitValue, true)) return false;
                if (continuationBitValue) valueBitCount += bits;
            }
            while (continuationBitValue && valueBitCount < 32);
            return ReadInt(out value, valueBitCount >= 32 ? 32 : valueBitCount, true);
        }

        /// <summary>
        /// Reads the variable-width signed integer.
        /// </summary>
        /// <param name="value">Signed Integer.</param>
        /// <param name="bits">The bit size of the sequence used to write.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadVariableWidthInt(out long value, int bits = 4, bool nestedRead = false)
        {
            if (bits < 1 || bits > 64) throw new ArgumentOutOfRangeException("bits must be in the range (0, 64].");

            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            int valueBitCount = bits;
            bool continuationBitValue = true;
            do
            {
                if (!ReadBool(out continuationBitValue, true)) return false;
                if (continuationBitValue) valueBitCount += bits;
            }
            while (continuationBitValue && valueBitCount < 64);
            return ReadInt(out value, valueBitCount >= 64 ? 64 : valueBitCount, true);
        }

        /// <summary>
        /// Reads an array of unsigned integers in the given range defining a base.
        /// </summary>
        /// <param name="value">Array of Unsigned Integers.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadArrayUInt32(uint[] value, int min, int max, bool nestedRead = false)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            if (!nestedRead) previousBitIndex = bitIndex;
            ulong valueBase = (ulong)(max - min + 1);
            var exponent = value.Length;

            var powResult = new List<ulong>();
            powResult.Add(1);
            var newPowResult = new List<ulong>();

            var valueBaseTemp = new List<ulong>();
            valueBaseTemp.Add(valueBase);
            var newValueBaseTemp = new List<ulong>();

            // Pow(value.size, arrayBase) using exponentiation by squaring
            while (exponent != 0)
            {
                if (exponent % 2 != 0)
                {
                    // powResult *= valueBaseTemp;
                    newPowResult.Clear();
                    for (int i = 0; i < valueBaseTemp.Count; ++i)
                    {
                        ulong carry = 0;
                        for (var j = 0; j < powResult.Count || carry != 0; ++j)
                        {
                            if (i + j >= newPowResult.Count)
                            {
                                newPowResult.Add(0);
                            }
                            ulong product = 0;
                            if (j < powResult.Count)
                            {
                                product = valueBaseTemp[i] * powResult[j];
                            }
                            newPowResult[j + i] += product + carry;
                            carry = newPowResult[j + i] >> 32;
                            newPowResult[j + i] &= 0xFFFFFFFF;
                        }
                    }
                    powResult.Clear();
                    powResult.InsertRange(0, newPowResult);
                    exponent--;
                }
                // valueBaseTemp *= valueBaseTemp;
                newValueBaseTemp.Clear();
                for (int i = 0; i < valueBaseTemp.Count; ++i)
                {
                    ulong carry = 0;
                    for (var j = 0; j < valueBaseTemp.Count || carry != 0; ++j)
                    {
                        if (i + j >= newValueBaseTemp.Count)
                        {
                            newValueBaseTemp.Add(0);
                        }
                        ulong product = 0;
                        if (j < valueBaseTemp.Count)
                        {
                            product = valueBaseTemp[i] * valueBaseTemp[j];
                        }
                        newValueBaseTemp[j + i] += product + carry;
                        carry = newValueBaseTemp[j + i] >> 32;
                        newValueBaseTemp[j + i] &= 0xFFFFFFFF;
                    }
                }
                valueBaseTemp.Clear();
                valueBaseTemp.InsertRange(0, newValueBaseTemp);

                exponent /= 2;
            }

            for (var i = powResult.Count - 1; i > 0 && powResult[i] == 0; --i)
            {
                powResult.RemoveAt(powResult.Count - 1);
            }

            // Log2
            int numberOfBits = 0;
            if (!(powResult.Count == 1 && powResult[0] == 0))
            {
                int left = 0;
                int right = 32;
                for (int i = 0; i < 5; ++i)
                {
                    if (powResult[powResult.Count - 1] >> (32 - (left + right) / 2) != 0)
                    {
                        // Left
                        right = (left + right) / 2;
                    }
                    else
                    {
                        // Right
                        left = (left + right) / 2;
                    }
                }
                numberOfBits = (powResult.Count - 1) * 32 + (32 - left);
            }

            var sum = new List<ulong>((numberOfBits + 31) / 32);
            for (int copyBits = 0; copyBits < numberOfBits; copyBits += 32)
            {
                uint uValue;
                if (!ReadUInt(out uValue, (int)numberOfBits - copyBits > 32 ? 32 : (int)numberOfBits - copyBits, true)) return false;
                sum.Add(uValue);
            }

            for (var i = 0; i < value.Length; ++i)
            {
                ulong remainder = 0;
                for (var j = sum.Count - 1; j >= 0; --j)
                {
                    sum[j] += remainder << 32;
                    remainder = sum[j] % valueBase;
                    sum[j] /= valueBase;
                }
                value[i] = (uint)remainder + (uint)min;
            }
            return true;
        }

        /// <summary>
        /// Reads an array of signed integers in the given range defining a base.
        /// </summary>
        /// <param name="value">Array of Signed Integers.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadArrayInt32(int[] value, int min, int max, bool nestedRead = false)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            if (!nestedRead) previousBitIndex = bitIndex;

            var uValue = new uint[value.Length];
            if (!ReadArrayUInt32(uValue, min, max, true)) return false;

            for (var i = 0; i < value.Length; ++i)
            {
                value[i] = (int)uValue[i] + min;
            }
            return true;
        }

        /// <summary>
        /// Reads an array of singles in the given range converted to the given base.
        /// </summary>
        /// <param name="value">Array of Singles.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="valueBase">The base to convert the singles to.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadArraySingle(float[] value, float min, float max, int valueBase, bool nestedRead = false)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            if (!nestedRead) previousBitIndex = bitIndex;
            var uValue = new uint[value.Length];
            if (!ReadArrayUInt32(uValue, 0, valueBase - 1, true)) return false;
            if (min < 0 && max > 0)
            {
                for (var i = 0; i < value.Length; ++i)
                {
                    value[i] = uValue[i] == 0 ? 0 : (uValue[i] - 1) / (float)(valueBase - 2) * (max - min) + min;
                }
            }
            else
            {
                for (var i = 0; i < value.Length; ++i)
                {
                    value[i] = uValue[i] / (float)(valueBase - 1) * (max - min) + min;
                }
            }
            return true;
        }

        /// <summary>
        /// Reads an array of doubles in the given range converted to the given base.
        /// </summary>
        /// <param name="value">Array of Doubles.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="valueBase">The base to convert the doubles to.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadArrayDouble(double[] value, double min, double max, int valueBase, bool nestedRead = false)
        {
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");
            if (!nestedRead) previousBitIndex = bitIndex;
            var uValue = new uint[value.Length];
            if (!ReadArrayUInt32(uValue, 0, valueBase - 1, true)) return false;
            if (min < 0 && max > 0)
            {
                for (var i = 0; i < value.Length; ++i)
                {
                    value[i] = uValue[i] == 0 ? 0 : (uValue[i] - 1) / (double)(valueBase - 2) * (max - min) + min;
                }
            }
            else
            {
                for (var i = 0; i < value.Length; ++i)
                {
                    value[i] = uValue[i] / (double)(valueBase - 1) * (max - min) + min;
                }
            }
            return true;
        }

        /// <summary>
        /// Reads a custom resolution single.
        /// </summary>
        /// <param name="value">Single.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="bitResolution">The number of bits written for the ratio.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadCustomResolutionSingle(out float value, float min, float max, int bitResolution, bool nestedRead = false)
        {
            if (bitResolution < 1 || bitResolution > 31) throw new ArgumentOutOfRangeException("bitResolution must be in the range (0, 32).");
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");

            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            uint uValue;
            if (!ReadUInt(out uValue, bitResolution, true)) return false;
            if (min < 0 && max > 0)
            {
                value = uValue == 0 ? 0 : (uValue - 1) / (float)((0x1 << bitResolution) - 2) * (max - min) + min;
            }
            else
            {
                value = uValue / (float)((0x1 << bitResolution) - 1) * (max - min) + min;
            }
            return true;
        }

        /// <summary>
        /// Reads a custom resolution double.
        /// </summary>
        /// <param name="value">Double.</param>
        /// <param name="min">The minimum value in the range.</param>
        /// <param name="max">The maximum value in the range.</param>
        /// <param name="bitResolution">The number of bits written for the ratio.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadCustomResolutionDouble(out double value, double min, double max, int bitResolution, bool nestedRead = false)
        {
            if (bitResolution < 1 || bitResolution > 31) throw new ArgumentOutOfRangeException("bitResolution must be in the range (0, 32).");
            if (max <= min) throw new ArgumentOutOfRangeException("max must be greater than min.");

            if (!nestedRead) previousBitIndex = bitIndex;
            value = 0;
            uint uValue;
            if (!ReadUInt(out uValue, bitResolution, true)) return false;
            if (min < 0 && max > 0)
            {
                value = uValue == 0 ? 0 : (uValue - 1) / (double)((0x1 << bitResolution) - 2) * (max - min) + min;
            }
            else
            {
                value = uValue / (double)((0x1 << bitResolution) - 1) * (max - min) + min;
            }
            return true;
        }

        /// <summary>
        /// Reads a string.
        /// </summary>
        /// <param name="value">String.</param>
        /// <param name="bits">The number of bits for the length written using an unsigned variable-width integer.</param>
        /// <param name="limit">The maximum number of characters to read.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadString(out string value, int bits = 4, int limit = 255, bool nestedRead = false)
        {
            if (bits < 1 || bits > 31) throw new ArgumentOutOfRangeException("bits must be in the range (0, 32).");

            if (!nestedRead) previousBitIndex = bitIndex;
            value = string.Empty;
            uint size;
            if (!ReadVariableWidthUInt(out size, bits, true)) return false;
            for (var asciiByteItr = 0; asciiByteItr < size; ++asciiByteItr)
            {
                byte asciiByte;
                if (!ReadByte(out asciiByte, true)) return false;
                value += asciiByte;
                if (value.Length > limit) return false;
            }
            return true;
        }

        /// <summary>
        /// Reads a binary packet that has been written to the buffer.
        /// </summary>
        /// <param name="value">A binary packet.</param>
        /// <param name="nestedRead">If false then the previous bit index is stored so the operation can be rolled back.</param>
        /// <returns>false on error.</returns>
        public bool ReadBinaryPacket(out Packet value, bool nestedRead = false)
        {
            value = new Packet();
            if (!nestedRead) previousBitIndex = bitIndex;
            uint valueMaxBitIndex;
            if (!ReadVariableWidthUInt(out valueMaxBitIndex, nestedRead: true)) return false;
            for (int copyBits = 0; copyBits < valueMaxBitIndex; copyBits += 32)
            {
                uint uValue;
                if (!ReadUInt(out uValue, (int)valueMaxBitIndex - copyBits > 32 ? 32 : (int)valueMaxBitIndex - copyBits, true)) return false;
                value.WriteUInt(uValue, (int)valueMaxBitIndex - copyBits > 32 ? 32 : (int)valueMaxBitIndex - copyBits);
            }
            bitIndex += (int)valueMaxBitIndex;
            value.bitIndex = 0;
            return true;
        }

    }
}