#nullable disable

using System.IO;

namespace SharpCompress.Compressors.LZMA.RangeCoder;

internal class Encoder
{
    public const uint K_TOP_VALUE = (1 << 24);

    private Stream _stream;

    public ulong _low;
    public uint _range;
    private uint _cacheSize;
    private byte _cache;

    //long StartPosition;

    public void SetStream(Stream stream) => _stream = stream;

    public void ReleaseStream() => _stream = null;

    public void Init()
    {
        //StartPosition = Stream.Position;

        _low = 0;
        _range = 0xFFFFFFFF;
        _cacheSize = 1;
        _cache = 0;
    }

    public void FlushData()
    {
        for (var i = 0; i < 5; i++)
        {
            ShiftLow();
        }
    }

    public void FlushStream() => _stream.Flush();

    public void CloseStream() => _stream.Dispose();

    public void Encode(uint start, uint size, uint total)
    {
        _low += start * (_range /= total);
        _range *= size;
        while (_range < K_TOP_VALUE)
        {
            _range <<= 8;
            ShiftLow();
        }
    }

    public void ShiftLow()
    {
        if ((uint)_low < 0xFF000000 || (uint)(_low >> 32) == 1)
        {
            var temp = _cache;
            do
            {
                _stream.WriteByte((byte)(temp + (_low >> 32)));
                temp = 0xFF;
            } while (--_cacheSize != 0);
            _cache = (byte)(((uint)_low) >> 24);
        }
        _cacheSize++;
        _low = ((uint)_low) << 8;
    }

    public void EncodeDirectBits(uint v, int numTotalBits)
    {
        for (var i = numTotalBits - 1; i >= 0; i--)
        {
            _range >>= 1;
            if (((v >> i) & 1) == 1)
            {
                _low += _range;
            }
            if (_range < K_TOP_VALUE)
            {
                _range <<= 8;
                ShiftLow();
            }
        }
    }

    public void EncodeBit(uint size0, int numTotalBits, uint symbol)
    {
        var newBound = (_range >> numTotalBits) * size0;
        if (symbol == 0)
        {
            _range = newBound;
        }
        else
        {
            _low += newBound;
            _range -= newBound;
        }
        while (_range < K_TOP_VALUE)
        {
            _range <<= 8;
            ShiftLow();
        }
    }

    public long GetProcessedSizeAdd() => -1;

    //return _cacheSize + Stream.Position - StartPosition + 4;
    // (long)Stream.GetProcessedSize();
}

internal class Decoder
{
    public const uint K_TOP_VALUE = (1 << 24);
    public uint _range;
    public uint _code;

    // public Buffer.InBuffer Stream = new Buffer.InBuffer(1 << 16);
    public Stream _stream;
    public long _total;

    public void Init(Stream stream)
    {
        // Stream.Init(stream);
        _stream = stream;

        _code = 0;
        _range = 0xFFFFFFFF;
        for (var i = 0; i < 5; i++)
        {
            _code = (_code << 8) | (byte)_stream.ReadByte();
        }
        _total = 5;
    }

    public void ReleaseStream() =>
        // Stream.ReleaseStream();
        _stream = null;

    public void CloseStream() => _stream.Dispose();

    public void Normalize()
    {
        while (_range < K_TOP_VALUE)
        {
            _code = (_code << 8) | (byte)_stream.ReadByte();
            _range <<= 8;
            _total++;
        }
    }

    public void Normalize2()
    {
        if (_range < K_TOP_VALUE)
        {
            _code = (_code << 8) | (byte)_stream.ReadByte();
            _range <<= 8;
            _total++;
        }
    }

    public uint GetThreshold(uint total) => _code / (_range /= total);

    public void Decode(uint start, uint size)
    {
        _code -= start * _range;
        _range *= size;
        Normalize();
    }

    public uint DecodeDirectBits(int numTotalBits)
    {
        var range = _range;
        var code = _code;
        uint result = 0;
        for (var i = numTotalBits; i > 0; i--)
        {
            range >>= 1;
            /*
            result <<= 1;
            if (code >= range)
            {
                code -= range;
                result |= 1;
            }
            */
            var t = (code - range) >> 31;
            code -= range & (t - 1);
            result = (result << 1) | (1 - t);

            if (range < K_TOP_VALUE)
            {
                code = (code << 8) | (byte)_stream.ReadByte();
                range <<= 8;
                _total++;
            }
        }
        _range = range;
        _code = code;
        return result;
    }

    public uint DecodeBit(uint size0, int numTotalBits)
    {
        var newBound = (_range >> numTotalBits) * size0;
        uint symbol;
        if (_code < newBound)
        {
            symbol = 0;
            _range = newBound;
        }
        else
        {
            symbol = 1;
            _code -= newBound;
            _range -= newBound;
        }
        Normalize();
        return symbol;
    }

    public bool IsFinished => _code == 0;

    // ulong GetProcessedSize() {return Stream.GetProcessedSize(); }
}
