#region Using
using System;
using System.IO;
#endregion
// This is a port of Dmitry Shkarin's PPMd Variant I Revision 1.
// Ported by Michael Bone (mjbone03@yahoo.com.au).
namespace SharpCompress.Compressors.PPMd.I1
{
    /// <summary>
    /// The model.
    /// </summary>
    internal partial class Model
    {
        public const uint SIGNATURE = 0x84acaf8fU;
        public const char VARIANT = 'I';
        public const int MAXIMUM_ORDER = 16; // maximum allowed model order
        private const byte UPPER_FREQUENCY = 5;
        private const byte INTERVAL_BIT_COUNT = 7;
        private const byte PERIOD_BIT_COUNT = 7;
        private const byte TOTAL_BIT_COUNT = INTERVAL_BIT_COUNT + PERIOD_BIT_COUNT;
        private const uint INTERVAL = 1 << INTERVAL_BIT_COUNT;
        private const uint BINARY_SCALE = 1 << TOTAL_BIT_COUNT;
        private const uint MAXIMUM_FREQUENCY = 124;
        private const uint ORDER_BOUND = 9;
        private readonly See2Context[,] _see2Contexts;
        private readonly See2Context _emptySee2Context;
        private PpmContext _maximumContext;
        private readonly ushort[,] _binarySummary = new ushort[25, 64]; // binary SEE-contexts
        private readonly byte[] _numberStatisticsToBinarySummaryIndex = new byte[256];
        private readonly byte[] _probabilities = new byte[260];
        private readonly byte[] _characterMask = new byte[256];
        private byte _escapeCount;
        private int _modelOrder;
        private int _orderFall;
        private int _initialEscape;
        private int _initialRunLength;
        private int _runLength;
        private byte _previousSuccess;
        private byte _numberMasked;
        private ModelRestorationMethod _method;
        private PpmState _foundState; // found next state transition
        private Allocator _allocator;
        private Coder _coder;
        private PpmContext _minimumContext;
        private byte _numberStatistics;
        private readonly PpmState[] _decodeStates = new PpmState[256];
        private static readonly ushort[] INITIAL_BINARY_ESCAPES =
        {
            0x3CDD, 0x1F3F, 0x59BF, 0x48F3, 0x64A1, 0x5ABC, 0x6632,
            0x6051
        };
        private static readonly byte[] EXPONENTIAL_ESCAPES = {25, 14, 9, 7, 5, 5, 4, 4, 4, 3, 3, 3, 2, 2, 2, 2};
        #region Public Methods
        public Model()
        {
            // Construct the conversion table for number statistics.  Initially it will contain the following values.
            //
            // 0 2 4 4 4 4 4 4 4 4 4 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
            // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
            // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
            // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
            // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
            // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
            // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
            // 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6
            _numberStatisticsToBinarySummaryIndex[0] = 2 * 0;
            _numberStatisticsToBinarySummaryIndex[1] = 2 * 1;
            for (int index = 2; index < 11; index++)
            {
                _numberStatisticsToBinarySummaryIndex[index] = 2 * 2;
            }
            for (int index = 11; index < 256; index++)
            {
                _numberStatisticsToBinarySummaryIndex[index] = 2 * 3;
            }
            // Construct the probability table.  Initially it will contain the following values (depending on the value of
            // the upper frequency).
            //
            // 00 01 02 03 04 05 06 06 07 07 07 08 08 08 08 09 09 09 09 09 10 10 10 10 10 10 11 11 11 11 11 11
            // 11 12 12 12 12 12 12 12 12 13 13 13 13 13 13 13 13 13 14 14 14 14 14 14 14 14 14 14 15 15 15 15
            // 15 15 15 15 15 15 15 16 16 16 16 16 16 16 16 16 16 16 16 17 17 17 17 17 17 17 17 17 17 17 17 17
            // 18 18 18 18 18 18 18 18 18 18 18 18 18 18 19 19 19 19 19 19 19 19 19 19 19 19 19 19 19 20 20 20
            // 20 20 20 20 20 20 20 20 20 20 20 20 20 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 22 22
            // 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 22 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23 23
            // 23 23 23 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 24 25 25 25 25 25 25 25 25 25
            // 25 25 25 25 25 25 25 25 25 25 25 25 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26 26
            // 26 26 27 27
            uint count = 1;
            uint step = 1;
            uint probability = UPPER_FREQUENCY;
            for (int index = 0; index < UPPER_FREQUENCY; index++)
            {
                _probabilities[index] = (byte)index;
            }
            for (int index = UPPER_FREQUENCY; index < 260; index++)
            {
                _probabilities[index] = (byte)probability;
                count--;
                if (count == 0)
                {
                    step++;
                    count = step;
                    probability++;
                }
            }
            // Create the context array.
            _see2Contexts = new See2Context[24, 32];
            for (int index1 = 0; index1 < 24; index1++)
            {
                for (int index2 = 0; index2 < 32; index2++)
                {
                    _see2Contexts[index1, index2] = new See2Context();
                }
            }
            // Set the signature (identifying the algorithm).
            _emptySee2Context = new See2Context();
            _emptySee2Context._summary = (ushort)(SIGNATURE & 0x0000ffff);
            _emptySee2Context._shift = (byte)((SIGNATURE >> 16) & 0x000000ff);
            _emptySee2Context._count = (byte)(SIGNATURE >> 24);
        }
        /// <summary>
        /// Encode (ie. compress) a given source stream, writing the encoded result to the target stream.
        /// </summary>
        public void Encode(Stream target, Stream source, PpmdProperties properties)
        {
            if (target == null)
            {
                throw new ArgumentNullException(nameof(target));
            }
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            EncodeStart(properties);
            EncodeBlock(target, source, true);
        }
        internal Coder EncodeStart(PpmdProperties properties)
        {
            _allocator = properties._allocator;
            _coder = new Coder();
            _coder.RangeEncoderInitialize();
            StartModel(properties.ModelOrder, properties.RestorationMethod);
            return _coder;
        }
        internal void EncodeBlock(Stream target, Stream source, bool final)
        {
            while (true)
            {
                _minimumContext = _maximumContext;
                _numberStatistics = _minimumContext.NumberStatistics;
                int c = source.ReadByte();
                if (c < 0 && !final)
                {
                    return;
                }
                if (_numberStatistics != 0)
                {
                    EncodeSymbol1(c, _minimumContext);
                    _coder.RangeEncodeSymbol();
                }
                else
                {
                    EncodeBinarySymbol(c, _minimumContext);
                    _coder.RangeShiftEncodeSymbol(TOTAL_BIT_COUNT);
                }
                while (_foundState == PpmState.ZERO)
                {
                    _coder.RangeEncoderNormalize(target);
                    do
                    {
                        _orderFall++;
                        _minimumContext = _minimumContext.Suffix;
                        if (_minimumContext == PpmContext.ZERO)
                        {
                            goto StopEncoding;
                        }
                    }
                    while (_minimumContext.NumberStatistics == _numberMasked);
                    EncodeSymbol2(c, _minimumContext);
                    _coder.RangeEncodeSymbol();
                }
                if (_orderFall == 0 && (Pointer)_foundState.Successor >= _allocator._baseUnit)
                {
                    _maximumContext = _foundState.Successor;
                }
                else
                {
                    UpdateModel(_minimumContext);
                    if (_escapeCount == 0)
                    {
                        ClearMask();
                    }
                }
                _coder.RangeEncoderNormalize(target);
            }
            StopEncoding:
            _coder.RangeEncoderFlush(target);
        }
        /// <summary>
        /// Dencode (ie. decompress) a given source stream, writing the decoded result to the target stream.
        /// </summary>
        public void Decode(Stream target, Stream source, PpmdProperties properties)
        {
            if (target == null)
            {
                throw new ArgumentNullException(nameof(target));
            }
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            DecodeStart(source, properties);
            byte[] buffer = new byte[65536];
            int read;
            while ((read = DecodeBlock(source, buffer, 0, buffer.Length)) != 0)
            {
                target.Write(buffer, 0, read);
            }
        }
        internal Coder DecodeStart(Stream source, PpmdProperties properties)
        {
            _allocator = properties._allocator;
            _coder = new Coder();
            _coder.RangeDecoderInitialize(source);
            StartModel(properties.ModelOrder, properties.RestorationMethod);
            _minimumContext = _maximumContext;
            _numberStatistics = _minimumContext.NumberStatistics;
            return _coder;
        }
        internal int DecodeBlock(Stream source, byte[] buffer, int offset, int count)
        {
            if (_minimumContext == PpmContext.ZERO)
            {
                return 0;
            }
            int total = 0;
            while (total < count)
            {
                if (_numberStatistics != 0)
                {
                    DecodeSymbol1(_minimumContext);
                }
                else
                {
                    DecodeBinarySymbol(_minimumContext);
                }
                _coder.RangeRemoveSubrange();
                while (_foundState == PpmState.ZERO)
                {
                    _coder.RangeDecoderNormalize(source);
                    do
                    {
                        _orderFall++;
                        _minimumContext = _minimumContext.Suffix;
                        if (_minimumContext == PpmContext.ZERO)
                        {
                            goto StopDecoding;
                        }
                    }
                    while (_minimumContext.NumberStatistics == _numberMasked);
                    DecodeSymbol2(_minimumContext);
                    _coder.RangeRemoveSubrange();
                }
                buffer[offset] = _foundState.Symbol;
                offset++;
                total++;
                if (_orderFall == 0 && (Pointer)_foundState.Successor >= _allocator._baseUnit)
                {
                    _maximumContext = _foundState.Successor;
                }
                else
                {
                    UpdateModel(_minimumContext);
                    if (_escapeCount == 0)
                    {
                        ClearMask();
                    }
                }
                _minimumContext = _maximumContext;
                _numberStatistics = _minimumContext.NumberStatistics;
                _coder.RangeDecoderNormalize(source);
            }
            StopDecoding:
            return total;
        }
        #endregion
        #region Private Methods
        /// <summary>
        /// Initialise the model (unless the model order is set to 1 in which case the model should be cleared so that
        /// the statistics are carried over, allowing "solid" mode compression).
        /// </summary>
        private void StartModel(int modelOrder, ModelRestorationMethod modelRestorationMethod)
        {
            Array.Clear(_characterMask, 0, _characterMask.Length);
            _escapeCount = 1;
            // Compress in "solid" mode if the model order value is set to 1 (this will examine the current PPM context
            // structures to determine the value of orderFall).
            if (modelOrder < 2)
            {
                _orderFall = _modelOrder;
                for (PpmContext context = _maximumContext; context.Suffix != PpmContext.ZERO; context = context.Suffix)
                {
                    _orderFall--;
                }
                return;
            }
            _modelOrder = modelOrder;
            _orderFall = modelOrder;
            _method = modelRestorationMethod;
            _allocator.Initialize();
            _initialRunLength = -((modelOrder < 12) ? modelOrder : 12) - 1;
            _runLength = _initialRunLength;
            // Allocate the context structure.
            _maximumContext = _allocator.AllocateContext();
            _maximumContext.Suffix = PpmContext.ZERO;
            _maximumContext.NumberStatistics = 255;
            _maximumContext.SummaryFrequency = (ushort)(_maximumContext.NumberStatistics + 2);
            _maximumContext.Statistics = _allocator.AllocateUnits(256 / 2);
            // allocates enough space for 256 PPM states (each is 6 bytes)
            _previousSuccess = 0;
            for (int index = 0; index < 256; index++)
            {
                PpmState state = _maximumContext.Statistics[index];
                state.Symbol = (byte)index;
                state.Frequency = 1;
                state.Successor = PpmContext.ZERO;
            }
            uint probability = 0;
            for (int index1 = 0; probability < 25; probability++)
            {
                while (_probabilities[index1] == probability)
                {
                    index1++;
                }
                for (int index2 = 0; index2 < 8; index2++)
                {
                    _binarySummary[probability, index2] =
                        (ushort)(BINARY_SCALE - INITIAL_BINARY_ESCAPES[index2] / (index1 + 1));
                }
                for (int index2 = 8; index2 < 64; index2 += 8)
                {
                    for (int index3 = 0; index3 < 8; index3++)
                    {
                        _binarySummary[probability, index2 + index3] = _binarySummary[probability, index3];
                    }
                }
            }
            probability = 0;
            for (uint index1 = 0; probability < 24; probability++)
            {
                while (_probabilities[index1 + 3] == probability + 3)
                {
                    index1++;
                }
                for (int index2 = 0; index2 < 32; index2++)
                {
                    _see2Contexts[probability, index2].Initialize(2 * index1 + 5);
                }
            }
        }
        private void UpdateModel(PpmContext minimumContext)
        {
            PpmState state = PpmState.ZERO;
            PpmContext successor;
            PpmContext currentContext = _maximumContext;
            uint numberStatistics;
            uint ns1;
            uint cf;
            uint sf;
            uint s0;
            uint foundStateFrequency = _foundState.Frequency;
            byte foundStateSymbol = _foundState.Symbol;
            byte symbol;
            byte flag;
            PpmContext foundStateSuccessor = _foundState.Successor;
            PpmContext context = minimumContext.Suffix;
            if ((foundStateFrequency < MAXIMUM_FREQUENCY / 4) && (context != PpmContext.ZERO))
            {
                if (context.NumberStatistics != 0)
                {
                    state = context.Statistics;
                    if (state.Symbol != foundStateSymbol)
                    {
                        do
                        {
                            symbol = state[1].Symbol;
                            state++;
                        }
                        while (symbol != foundStateSymbol);
                        if (state[0].Frequency >= state[-1].Frequency)
                        {
                            Swap(state[0], state[-1]);
                            state--;
                        }
                    }
                    cf = (uint)((state.Frequency < MAXIMUM_FREQUENCY - 9) ? 2 : 0);
                    state.Frequency += (byte)cf;
                    context.SummaryFrequency += (byte)cf;
                }
                else
                {
                    state = context.FirstState;
                    state.Frequency += (byte)((state.Frequency < 32) ? 1 : 0);
                }
            }
            if (_orderFall == 0 && foundStateSuccessor != PpmContext.ZERO)
            {
                _foundState.Successor = CreateSuccessors(true, state, minimumContext);
                if (_foundState.Successor == PpmContext.ZERO)
                {
                    goto RestartModel;
                }
                _maximumContext = _foundState.Successor;
                return;
            }
            _allocator._text[0] = foundStateSymbol;
            _allocator._text++;
            successor = _allocator._text;
            if (_allocator._text >= _allocator._baseUnit)
            {
                goto RestartModel;
            }
            if (foundStateSuccessor != PpmContext.ZERO)
            {
                if (foundStateSuccessor < _allocator._baseUnit)
                {
                    foundStateSuccessor = CreateSuccessors(false, state, minimumContext);
                }
            }
            else
            {
                foundStateSuccessor = ReduceOrder(state, minimumContext);
            }
            if (foundStateSuccessor == PpmContext.ZERO)
            {
                goto RestartModel;
            }
            if (--_orderFall == 0)
            {
                successor = foundStateSuccessor;
                _allocator._text -= (_maximumContext != minimumContext) ? 1 : 0;
            }
            else if (_method > ModelRestorationMethod.Freeze)
            {
                successor = foundStateSuccessor;
                _allocator._text = _allocator._heap;
                _orderFall = 0;
            }
            numberStatistics = minimumContext.NumberStatistics;
            s0 = minimumContext.SummaryFrequency - numberStatistics - foundStateFrequency;
            flag = (byte)((foundStateSymbol >= 0x40) ? 0x08 : 0x00);
            for (; currentContext != minimumContext; currentContext = currentContext.Suffix)
            {
                ns1 = currentContext.NumberStatistics;
                if (ns1 != 0)
                {
                    if ((ns1 & 1) != 0)
                    {
                        state = _allocator.ExpandUnits(currentContext.Statistics, (ns1 + 1) >> 1);
                        if (state == PpmState.ZERO)
                        {
                            goto RestartModel;
                        }
                        currentContext.Statistics = state;
                    }
                    currentContext.SummaryFrequency += (ushort)((3 * ns1 + 1 < numberStatistics) ? 1 : 0);
                }
                else
                {
                    state = _allocator.AllocateUnits(1);
                    if (state == PpmState.ZERO)
                    {
                        goto RestartModel;
                    }
                    Copy(state, currentContext.FirstState);
                    currentContext.Statistics = state;
                    if (state.Frequency < MAXIMUM_FREQUENCY / 4 - 1)
                    {
                        state.Frequency += state.Frequency;
                    }
                    else
                    {
                        state.Frequency = (byte)(MAXIMUM_FREQUENCY - 4);
                    }
                    currentContext.SummaryFrequency =
                        (ushort)(state.Frequency + _initialEscape + ((numberStatistics > 2) ? 1 : 0));
                }
                cf = (uint)(2 * foundStateFrequency * (currentContext.SummaryFrequency + 6));
                sf = s0 + currentContext.SummaryFrequency;
                if (cf < 6 * sf)
                {
                    cf = (uint)(1 + ((cf > sf) ? 1 : 0) + ((cf >= 4 * sf) ? 1 : 0));
                    currentContext.SummaryFrequency += 4;
                }
                else
                {
                    cf = (uint)(4 + ((cf > 9 * sf) ? 1 : 0) + ((cf > 12 * sf) ? 1 : 0) + ((cf > 15 * sf) ? 1 : 0));
                    currentContext.SummaryFrequency += (ushort)cf;
                }
                state = currentContext.Statistics + (++currentContext.NumberStatistics);
                state.Successor = successor;
                state.Symbol = foundStateSymbol;
                state.Frequency = (byte)cf;
                currentContext.Flags |= flag;
            }
            _maximumContext = foundStateSuccessor;
            return;
            RestartModel:
            RestoreModel(currentContext, minimumContext, foundStateSuccessor);
        }
        private PpmContext CreateSuccessors(bool skip, PpmState state, PpmContext context)
        {
            PpmContext upBranch = _foundState.Successor;
            PpmState[] states = new PpmState[MAXIMUM_ORDER];
            uint stateIndex = 0;
            byte symbol = _foundState.Symbol;
            if (!skip)
            {
                states[stateIndex++] = _foundState;
                if (context.Suffix == PpmContext.ZERO)
                {
                    goto NoLoop;
                }
            }
            bool gotoLoopEntry = false;


Answer:             if (state != PpmState.ZERO)