#region Using

using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.IO;

#endregion

namespace SharpCompress.Compressors.PPMd.I1;

/// <summary>
/// A simple range coder.
/// </summary>
/// <remarks>
/// Note that in most cases fields are used rather than properties for performance reasons (for example,
/// <see cref="_scale"/> is a field rather than a property).
/// </remarks>
internal class Coder
{
    private const uint RANGE_TOP = 1 << 24;
    private const uint RANGE_BOTTOM = 1 << 15;
    private uint _low;
    private uint _code;
    private uint _range;

    public uint _lowCount;
    public uint _highCount;
    public uint _scale;

    public void RangeEncoderInitialize()
    {
        _low = 0;
        _range = uint.MaxValue;
    }

    public void RangeEncoderNormalize(Stream stream)
    {
        while (
            (_low ^ (_low + _range)) < RANGE_TOP
            || _range < RANGE_BOTTOM && ((_range = (uint)-_low & (RANGE_BOTTOM - 1)) != 0 || true)
        )
        {
            stream.WriteByte((byte)(_low >> 24));
            _range <<= 8;
            _low <<= 8;
        }
    }

    public async ValueTask RangeEncoderNormalizeAsync(
        Stream stream,
        CancellationToken cancellationToken = default
    )
    {
        while (
            (_low ^ (_low + _range)) < RANGE_TOP
            || _range < RANGE_BOTTOM && ((_range = (uint)-_low & (RANGE_BOTTOM - 1)) != 0 || true)
        )
        {
            await stream
                .WriteAsync(new[] { (byte)(_low >> 24) }, 0, 1, cancellationToken)
                .ConfigureAwait(false);
            _range <<= 8;
            _low <<= 8;
        }
    }

    public void RangeEncodeSymbol()
    {
        _low += _lowCount * (_range /= _scale);
        _range *= _highCount - _lowCount;
    }

    public void RangeShiftEncodeSymbol(int rangeShift)
    {
        _low += _lowCount * (_range >>= rangeShift);
        _range *= _highCount - _lowCount;
    }

    public void RangeEncoderFlush(Stream stream)
    {
        for (uint index = 0; index < 4; index++)
        {
            stream.WriteByte((byte)(_low >> 24));
            _low <<= 8;
        }
    }

    public async ValueTask RangeEncoderFlushAsync(
        Stream stream,
        CancellationToken cancellationToken = default
    )
    {
        var buffer = new byte[4];
        for (var index = 0; index < buffer.Length; index++)
        {
            buffer[index] = (byte)(_low >> 24);
            _low <<= 8;
        }

        await stream.WriteAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
    }

    public void RangeDecoderInitialize(Stream stream)
    {
        _low = 0;
        _code = 0;
        _range = uint.MaxValue;
        for (uint index = 0; index < 4; index++)
        {
            _code = (_code << 8) | (byte)stream.ReadByte();
        }
    }

    public async ValueTask RangeDecoderInitializeAsync(
        Stream stream,
        CancellationToken cancellationToken = default
    )
    {
        _low = 0;
        _code = 0;
        _range = uint.MaxValue;

        byte[] buffer = new byte[4];
        await stream.ReadFullyAsync(buffer, 0, 4, cancellationToken).ConfigureAwait(false);

        for (uint index = 0; index < 4; index++)
        {
            _code = (_code << 8) | buffer[index];
        }
    }

    public void RangeDecoderNormalize(Stream stream)
    {
        while (
            (_low ^ (_low + _range)) < RANGE_TOP
            || _range < RANGE_BOTTOM && ((_range = (uint)-_low & (RANGE_BOTTOM - 1)) != 0 || true)
        )
        {
            _code = (_code << 8) | (byte)stream.ReadByte();
            _range <<= 8;
            _low <<= 8;
        }
    }

    public async ValueTask RangeDecoderNormalizeAsync(
        Stream stream,
        CancellationToken cancellationToken = default
    )
    {
        while (
            (_low ^ (_low + _range)) < RANGE_TOP
            || _range < RANGE_BOTTOM && ((_range = (uint)-_low & (RANGE_BOTTOM - 1)) != 0 || true)
        )
        {
            byte[] buffer = new byte[1];
            await stream.ReadFullyAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
            _code = (_code << 8) | buffer[0];
            _range <<= 8;
            _low <<= 8;
        }
    }

    public uint RangeGetCurrentCount() => (_code - _low) / (_range /= _scale);

    public uint RangeGetCurrentShiftCount(int rangeShift) =>
        (_code - _low) / (_range >>= rangeShift);

    public void RangeRemoveSubrange()
    {
        _low += _range * _lowCount;
        _range *= _highCount - _lowCount;
    }
}
