namespace DbaMessageInserter;

/// <summary>
/// Writes DEFLATE bits least-significant bit first, as required by RFC 1951.
/// </summary>
internal sealed class BitWriter
{
    private readonly Stream _stream;
    private byte _currentByte;
    private int _usedBits;
    private long _completedBytes;

    public BitWriter(Stream stream)
    {
        _stream = stream ?? throw new ArgumentNullException(nameof(stream));
    }

    public long BitPosition => checked(_completedBytes * 8L + _usedBits);
    public bool IsByteAligned => _usedBits == 0;

    /// <summary>Number of bits needed to reach the next byte boundary (0..7).</summary>
    public int BitsUntilByteBoundary => (8 - _usedBits) & 7;

    public void WriteBits(uint value, int bitCount)
    {
        if (bitCount < 0 || bitCount > 32)
        {
            throw new ArgumentOutOfRangeException(nameof(bitCount));
        }

        for (int i = 0; i < bitCount; i++)
        {
            uint bit = (value >> i) & 1u;
            _currentByte |= (byte)(bit << _usedBits);
            _usedBits++;

            if (_usedBits == 8)
            {
                FlushCurrentByte();
            }
        }
    }

    /// <summary>
    /// Completes the current byte with zero padding. Normal Stored blocks use
    /// this method. NPNCB blocks use explicit DBA bits instead.
    /// </summary>
    public void AlignToByte()
    {
        if (_usedBits != 0)
        {
            FlushCurrentByte();
        }
    }

    public void WriteAlignedByte(byte value)
    {
        EnsureAligned();
        _stream.WriteByte(value);
        _completedBytes++;
    }

    public void WriteAlignedBytes(ReadOnlySpan<byte> bytes)
    {
        EnsureAligned();
        _stream.Write(bytes);
        _completedBytes += bytes.Length;
    }

    public void WriteUInt16LittleEndian(ushort value)
    {
        EnsureAligned();
        WriteAlignedByte((byte)value);
        WriteAlignedByte((byte)(value >> 8));
    }

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

    private void FlushCurrentByte()
    {
        _stream.WriteByte(_currentByte);
        _completedBytes++;
        _currentByte = 0;
        _usedBits = 0;
    }
}
