#nullable disable

using System;
using System.IO;
using System.Text;
using SharpCompress.Compressors.Rar;

namespace SharpCompress.Compressors.PPMd.H
{
    internal class RangeCoder
    {
        internal const int TOP = 1 << 24;
        internal const int BOT = 1 << 15;
        internal const long UINT_MASK = 0xFFFFffffL;

        // uint low, code, range;
        private long _low, _code, _range;
        private readonly IRarUnpack _unpackRead;
        private readonly Stream _stream;

        internal RangeCoder(IRarUnpack unpackRead)
        {
            _unpackRead = unpackRead;
            Init();
        }

        internal RangeCoder(Stream stream)
        {
            _stream = stream;
            Init();
        }

        private void Init()
        {
            SubRange = new SubRange();

            _low = _code = 0L;
            _range = 0xFFFFffffL;
            for (int i = 0; i < 4; i++)
            {
                _code = ((_code << 8) | Char) & UINT_MASK;
            }
        }

        internal int CurrentCount
        {
            get
            {
                _range = (_range / SubRange.Scale) & UINT_MASK;
                return (int)((_code - _low) / (_range));
            }
        }

        private long Char
        {
            get
            {
                if (_unpackRead != null)
                {
                    return (_unpackRead.Char);
                }
                if (_stream != null)
                {
                    return _stream.ReadByte();
                }
                return -1;
            }
        }

        internal SubRange SubRange { get; private set; }

        internal long GetCurrentShiftCount(int shift)
        {
            _range = Utility.URShift(_range, shift);
            return ((_code - _low) / (_range)) & UINT_MASK;
        }

        internal void Decode()
        {
            _low = (_low + (_range * SubRange.LowCount)) & UINT_MASK;
            _range = (_range * (SubRange.HighCount - SubRange.LowCount)) & UINT_MASK;
        }

        internal void AriDecNormalize()
        {
            //		while ((low ^ (low + range)) < TOP || range < BOT && ((range = -low & (BOT - 1)) != 0 ? true : true))
            //		{
            //			code = ((code << 8) | unpackRead.getChar()&0xff)&uintMask;
            //			range = (range << 8)&uintMask;
            //			low = (low << 8)&uintMask;
            //		}

            // Rewrote for clarity
            bool c2 = false;
            while ((_low ^ (_low + _range)) < TOP || (c2 = _range < BOT))
            {
                if (c2)
                {
                    _range = (-_low & (BOT - 1)) & UINT_MASK;
                    c2 = false;
                }
                _code = ((_code << 8) | Char) & UINT_MASK;
                _range = (_range << 8) & UINT_MASK;
                _low = (_low << 8) & UINT_MASK;
            }
        }

        // Debug
        public override String ToString()
        {
            StringBuilder buffer = new StringBuilder();
            buffer.Append("RangeCoder[");
            buffer.Append("\n  low=");
            buffer.Append(_low);
            buffer.Append("\n  code=");
            buffer.Append(_code);
            buffer.Append("\n  range=");
            buffer.Append(_range);
            buffer.Append("\n  subrange=");
            buffer.Append(SubRange);
            buffer.Append(']');
            return buffer.ToString();
        }
    }

    internal class SubRange
    {
        // uint LowCount, HighCount, scale;
        private long _lowCount, _highCount, _scale;

        internal void IncScale(int dScale)
        {
            Scale = Scale + dScale;
        }

        internal long HighCount { get => _highCount; set => _highCount = value & RangeCoder.UINT_MASK; }

        internal long LowCount { get => _lowCount & RangeCoder.UINT_MASK; set => _lowCount = value & RangeCoder.UINT_MASK; }

        internal long Scale { get => _scale; set => _scale = value & RangeCoder.UINT_MASK; }

        // Debug
        public override String ToString()
        {
            StringBuilder buffer = new StringBuilder();
            buffer.Append("SubRange[");
            buffer.Append("\n  lowCount=");
            buffer.Append(_lowCount);
            buffer.Append("\n  highCount=");
            buffer.Append(_highCount);
            buffer.Append("\n  scale=");
            buffer.Append(_scale);
            buffer.Append(']');
            return buffer.ToString();
        }
    }
}
