namespace DbaMessageRestorer;

/// <summary>Reads an in-memory DEFLATE bit stream LSB-first.</summary>
internal sealed class BitReader
{
    private readonly byte[] _data;
    private long _bitPosition;

    public BitReader(byte[] data)
    {
        _data = data ?? throw new ArgumentNullException(nameof(data));
    }

    public long BitPosition => _bitPosition;
    public long LengthBits => checked((long)_data.Length * 8L);
    public bool IsByteAligned => (_bitPosition & 7L) == 0;
    public int BitsUntilByteBoundary => (int)((8L - (_bitPosition & 7L)) & 7L);

    public uint ReadBits(int bitCount)
    {
        if (bitCount < 0 || bitCount > 32)
        {
            throw new ArgumentOutOfRangeException(nameof(bitCount));
        }

        EnsureBitsAvailable(bitCount);
        uint value = 0;

        for (int i = 0; i < bitCount; i++)
        {
            int byteIndex = checked((int)(_bitPosition >> 3));
            int bitIndex = (int)(_bitPosition & 7L);
            uint bit = (uint)((_data[byteIndex] >> bitIndex) & 1);
            value |= bit << i;
            _bitPosition++;
        }

        return value;
    }

    public byte ReadAlignedByte()
    {
        EnsureAligned();
        EnsureBitsAvailable(8);
        int byteIndex = checked((int)(_bitPosition >> 3));
        byte value = _data[byteIndex];
        _bitPosition += 8;
        return value;
    }

    public ushort ReadUInt16LittleEndian()
    {
        byte low = ReadAlignedByte();
        byte high = ReadAlignedByte();
        return (ushort)(low | (high << 8));
    }

    public void SkipAlignedBytes(int byteCount)
    {
        if (byteCount < 0)
        {
            throw new ArgumentOutOfRangeException(nameof(byteCount));
        }

        EnsureAligned();
        EnsureBitsAvailable(checked(byteCount * 8L));
        _bitPosition += checked(byteCount * 8L);
    }

    private void EnsureAligned()
    {
        if (!IsByteAligned)
        {
            throw new InvalidOperationException(
                "This operation requires a byte-aligned bit stream.");
        }
    }

    private void EnsureBitsAvailable(long bitCount)
    {
        if (bitCount < 0 || _bitPosition > LengthBits - bitCount)
        {
            throw new InvalidDataException(
                "Unexpected end of the raw DEFLATE bit stream.");
        }
    }
}
