#nullable disable

// Inflate.cs
// ------------------------------------------------------------------
//
// Copyright (c) 2009 Dino Chiesa and Microsoft Corporation.
// All rights reserved.
//
// This code module is part of DotNetZip, a zipfile class library.
//
// ------------------------------------------------------------------
//
// This code is licensed under the Microsoft Public License.
// See the file License.txt for the license details.
// More info on: http://dotnetzip.codeplex.com
//
// ------------------------------------------------------------------
//
// last saved (in emacs):
// Time-stamp: <2010-January-08 18:32:12>
//
// ------------------------------------------------------------------
//
// This module defines classes for decompression. This code is derived
// from the jzlib implementation of zlib, but significantly modified.
// The object model is not the same, and many of the behaviors are
// different.  Nonetheless, in keeping with the license for jzlib, I am
// reproducing the copyright to that code here.
//
// ------------------------------------------------------------------
//
// Copyright (c) 2000,2001,2002,2003 ymnk, JCraft,Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in
// the documentation and/or other materials provided with the distribution.
//
// 3. The names of the authors may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL JCRAFT,
// INC. OR ANY CONTRIBUTORS TO THIS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
// EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------
//
// This program is based on zlib-1.1.3; credit to authors
// Jean-loup Gailly(jloup@gzip.org) and Mark Adler(madler@alumni.caltech.edu)
// and contributors of zlib.
//
// -----------------------------------------------------------------------

using System;
using System.Buffers;
using SharpCompress.Algorithms;
using SharpCompress.Common;

namespace SharpCompress.Compressors.Deflate;

internal sealed class InflateBlocks
{
    private const int MANY = 1440;

    // Table for deflate from PKZIP's appnote.txt.
    internal static readonly int[] border =
    {
        16,
        17,
        18,
        0,
        8,
        7,
        9,
        6,
        10,
        5,
        11,
        4,
        12,
        3,
        13,
        2,
        14,
        1,
        15,
    };

    internal ZlibCodec _codec; // pointer back to this zlib stream
    internal int[] bb = new int[1]; // bit length tree depth

    // mode independent information
    internal int bitb; // bit buffer
    internal int bitk; // bits in bit buffer
    internal int[] blens; // bit lengths of codes
    internal uint check; // check on output
    internal object checkfn; // check function
    internal InflateCodes codes = new(); // if CODES, current state
    internal int end; // one byte after sliding window
    internal int[] hufts; // single malloc for tree space
    internal int index; // index into blens (or border)
    internal InfTree inftree = new();
    internal int last; // true if this block is the last block
    internal int left; // if STORED, bytes left to copy
    private InflateBlockMode mode; // current inflate_block mode
    internal int readAt; // window read pointer
    internal int table; // table lengths (14 bits)
    internal int[] tb = new int[1]; // bit length decoding tree
    internal IMemoryOwner<byte> window; // sliding window
    internal int writeAt; // window write pointer

    internal InflateBlocks(ZlibCodec codec, object checkfn, int w)
    {
        _codec = codec;
        hufts = ArrayPool<int>.Shared.Rent(MANY * 3);
        Array.Clear(hufts, 0, MANY * 3);
        window = MemoryPool<byte>.Shared.Rent(w);
        end = w;
        this.checkfn = checkfn;
        mode = InflateBlockMode.TYPE;
        Reset();
    }

    internal uint Reset()
    {
        var oldCheck = check;
        mode = InflateBlockMode.TYPE;
        bitk = 0;
        bitb = 0;
        readAt = writeAt = 0;

        if (checkfn != null)
        {
            _codec._adler32 = check = 1;
        }
        return oldCheck;
    }

    internal int Process(int r)
    {
        int t; // temporary storage
        int b; // bit buffer
        int k; // bits in bit buffer
        int p; // input data pointer
        int n; // bytes available there
        int q; // output window write pointer
        int m; // bytes to end of window or read pointer

        // copy input/output information to locals (UPDATE macro restores)

        p = _codec.NextIn;
        n = _codec.AvailableBytesIn;
        b = bitb;
        k = bitk;

        q = writeAt;
        m = (q < readAt ? readAt - q - 1 : end - q);

        // process input based on current state
        while (true)
        {
            switch (mode)
            {
                case InflateBlockMode.TYPE:

                    while (k < (3))
                    {
                        if (n != 0)
                        {
                            r = ZlibConstants.Z_OK;
                        }
                        else
                        {
                            bitb = b;
                            bitk = k;
                            _codec.AvailableBytesIn = n;
                            _codec.TotalBytesIn += p - _codec.NextIn;
                            _codec.NextIn = p;
                            writeAt = q;
                            return Flush(r);
                        }

                        n--;
                        b |= (_codec.InputBuffer[p++] & 0xff) << k;
                        k += 8;
                    }
                    t = (b & 7);
                    last = t & 1;

                    switch ((uint)t >> 1)
                    {
                        case 0: // stored
                            b >>= 3;
                            k -= (3);
                            t = k & 7; // go to byte boundary
                            b >>= t;
                            k -= t;
                            mode = InflateBlockMode.LENS; // get length of stored block
                            break;

                        case 1: // fixed
                            var bl = new int[1];
                            var bd = new int[1];
                            var tl = new int[1][];
                            var td = new int[1][];
                            InfTree.inflate_trees_fixed(bl, bd, tl, td, _codec);
                            codes.Init(bl[0], bd[0], tl[0], 0, td[0], 0);
                            b >>= 3;
                            k -= 3;
                            mode = InflateBlockMode.CODES;
                            break;

                        case 2: // dynamic
                            b >>= 3;
                            k -= 3;
                            mode = InflateBlockMode.TABLE;
                            break;

                        case 3: // illegal
                            b >>= 3;
                            k -= 3;
                            mode = InflateBlockMode.BAD;
                            _codec.Message = "invalid block type";
                            r = ZlibConstants.Z_DATA_ERROR;
                            bitb = b;
                            bitk = k;
                            _codec.AvailableBytesIn = n;
                            _codec.TotalBytesIn += p - _codec.NextIn;
                            _codec.NextIn = p;
                            writeAt = q;
                            return Flush(r);
                    }
                    break;

                case InflateBlockMode.LENS:

                    while (k < (32))
                    {
                        if (n != 0)
                        {
                            r = ZlibConstants.Z_OK;
                        }
                        else
                        {
                            bitb = b;
                            bitk = k;
                            _codec.AvailableBytesIn = n;
                            _codec.TotalBytesIn += p - _codec.NextIn;
                            _codec.NextIn = p;
                            writeAt = q;
                            return Flush(r);
                        }
                        ;
                        n--;
                        b |= (_codec.InputBuffer[p++] & 0xff) << k;
                        k += 8;
                    }

                    if ((((~b) >> 16) & 0xffff) != (b & 0xffff))
                    {
                        mode = InflateBlockMode.BAD;
                        _codec.Message = "invalid stored block lengths";
                        r = ZlibConstants.Z_DATA_ERROR;

                        bitb = b;
                        bitk = k;
                        _codec.AvailableBytesIn = n;
                        _codec.TotalBytesIn += p - _codec.NextIn;
                        _codec.NextIn = p;
                        writeAt = q;
                        return Flush(r);
                    }
                    left = (b & 0xffff);
                    b = k = 0; // dump bits
                    mode =
                        left != 0
                            ? InflateBlockMode.STORED
                            : (last != 0 ? InflateBlockMode.DRY : InflateBlockMode.TYPE);
                    break;

                case InflateBlockMode.STORED:
                    if (n == 0)
                    {
                        bitb = b;
                        bitk = k;
                        _codec.AvailableBytesIn = n;
                        _codec.TotalBytesIn += p - _codec.NextIn;
                        _codec.NextIn = p;
                        writeAt = q;
                        return Flush(r);
                    }

                    if (m == 0)
                    {
                        if (q == end && readAt != 0)
                        {
                            q = 0;
                            m = (q < readAt ? readAt - q - 1 : end - q);
                        }
                        if (m == 0)
                        {
                            writeAt = q;
                            r = Flush(r);
                            q = writeAt;
                            m = (q < readAt ? readAt - q - 1 : end - q);
                            if (q == end && readAt != 0)
                            {
                                q = 0;
                                m = (q < readAt ? readAt - q - 1 : end - q);
                            }
                            if (m == 0)
                            {
                                bitb = b;
                                bitk = k;
                                _codec.AvailableBytesIn = n;
                                _codec.TotalBytesIn += p - _codec.NextIn;
                                _codec.NextIn = p;
                                writeAt = q;
                                return Flush(r);
                            }
                        }
                    }
                    r = ZlibConstants.Z_OK;

                    t = left;
                    if (t > n)
                    {
                        t = n;
                    }
                    if (t > m)
                    {
                        t = m;
                    }
                    _codec.InputBuffer.AsSpan(p, t).CopyTo(window.Memory.Span.Slice(q));
                    p += t;
                    n -= t;
                    q += t;
                    m -= t;
                    if ((left -= t) != 0)
                    {
                        break;
                    }
                    mode = last != 0 ? InflateBlockMode.DRY : InflateBlockMode.TYPE;
                    break;

                case InflateBlockMode.TABLE:

                    while (k < (14))
                    {
                        if (n != 0)
                        {
                            r = ZlibConstants.Z_OK;
                        }
                        else
                        {
                            bitb = b;
                            bitk = k;
                            _codec.AvailableBytesIn = n;
                            _codec.TotalBytesIn += p - _codec.NextIn;
                            _codec.NextIn = p;
                            writeAt = q;
                            return Flush(r);
                        }

                        n--;
                        b |= (_codec.InputBuffer[p++] & 0xff) << k;
                        k += 8;
                    }

                    table = t = (b & 0x3fff);
                    if ((t & 0x1f) > 29 || ((t >> 5) & 0x1f) > 29)
                    {
                        mode = InflateBlockMode.BAD;
                        _codec.Message = "too many length or distance symbols";
                        r = ZlibConstants.Z_DATA_ERROR;

                        bitb = b;
                        bitk = k;
                        _codec.AvailableBytesIn = n;
                        _codec.TotalBytesIn += p - _codec.NextIn;
                        _codec.NextIn = p;
                        writeAt = q;
                        return Flush(r);
                    }
                    t = 258 + (t & 0x1f) + ((t >> 5) & 0x1f);
                    if (blens is null || blens.Length < t)
                    {
                        blens = new int[t];
                    }
                    else
                    {
                        Array.Clear(blens, 0, t);

                        // for (int i = 0; i < t; i++)
                        // {
                        //     blens[i] = 0;
                        // }
                    }

                    b >>= 14;
                    k -= 14;

                    index = 0;
                    mode = InflateBlockMode.BTREE;
                    goto case InflateBlockMode.BTREE;

                case InflateBlockMode.BTREE:
                    while (index < 4 + (table >> 10))
                    {
                        while (k < (3))
                        {
                            if (n != 0)
                            {
                                r = ZlibConstants.Z_OK;
                            }
                            else
                            {
                                bitb = b;
                                bitk = k;
                                _codec.AvailableBytesIn = n;
                                _codec.TotalBytesIn += p - _codec.NextIn;
                                _codec.NextIn = p;
                                writeAt = q;
                                return Flush(r);
                            }

                            n--;
                            b |= (_codec.InputBuffer[p++] & 0xff) << k;
                            k += 8;
                        }

                        blens[border[index++]] = b & 7;

                        b >>= 3;
                        k -= 3;
                    }

                    while (index < 19)
                    {
                        blens[border[index++]] = 0;
                    }

                    bb[0] = 7;
                    t = inftree.inflate_trees_bits(blens, bb, tb, hufts, _codec);
                    if (t != ZlibConstants.Z_OK)
                    {
                        r = t;
                        if (r == ZlibConstants.Z_DATA_ERROR)
                        {
                            blens = null;
                            mode = InflateBlockMode.BAD;
                        }

                        bitb = b;
                        bitk = k;
                        _codec.AvailableBytesIn = n;
                        _codec.TotalBytesIn += p - _codec.NextIn;
                        _codec.NextIn = p;
                        writeAt = q;
                        return Flush(r);
                    }

                    index = 0;
                    mode = InflateBlockMode.DTREE;
                    goto case InflateBlockMode.DTREE;

                case InflateBlockMode.DTREE:
                    while (true)
                    {
                        t = table;
                        if (!(index < 258 + (t & 0x1f) + ((t >> 5) & 0x1f)))
                        {
                            break;
                        }

                        int i,
                            j,
                            c;

                        t = bb[0];

                        while (k < t)
                        {
                            if (n != 0)
                            {
                                r = ZlibConstants.Z_OK;
                            }
                            else
                            {
                                bitb = b;
                                bitk = k;
                                _codec.AvailableBytesIn = n;
                                _codec.TotalBytesIn += p - _codec.NextIn;
                                _codec.NextIn = p;
                                writeAt = q;
                                return Flush(r);
                            }

                            n--;
                            b |= (_codec.InputBuffer[p++] & 0xff) << k;
                            k += 8;
                        }

                        t = hufts[
                            ((tb[0] + (b & InternalInflateConstants.InflateMask[t])) * 3) + 1
                        ];
                        c = hufts[
                            ((tb[0] + (b & InternalInflateConstants.InflateMask[t])) * 3) + 2
                        ];

                        if (c < 16)
                        {
                            b >>= t;
                            k -= t;
                            blens[index++] = c;
                        }
                        else
                        {
                            // c == 16..18
                            i = c == 18 ? 7 : c - 14;
                            j = c == 18 ? 11 : 3;

                            while (k < (t + i))
                            {
                                if (n != 0)
                                {
                                    r = ZlibConstants.Z_OK;
                                }
                                else
                                {
                                    bitb = b;
                                    bitk = k;
                                    _codec.AvailableBytesIn = n;
                                    _codec.TotalBytesIn += p - _codec.NextIn;
                                    _codec.NextIn = p;
                                    writeAt = q;
                                    return Flush(r);
                                }

                                n--;
                                b |= (_codec.InputBuffer[p++] & 0xff) << k;
                                k += 8;
                            }

                            b >>= t;
                            k -= t;

                            j += (b & InternalInflateConstants.InflateMask[i]);

                            b >>= i;
                            k -= i;

                            i = index;
                            t = table;
                            if (i + j > 258 + (t & 0x1f) + ((t >> 5) & 0x1f) || (c == 16 && i < 1))
                            {
                                blens = null;
                                mode = InflateBlockMode.BAD;
                                _codec.Message = "invalid bit length repeat";
                                r = ZlibConstants.Z_DATA_ERROR;

                                bitb = b;
                                bitk = k;
                                _codec.AvailableBytesIn = n;
                                _codec.TotalBytesIn += p - _codec.NextIn;
                                _codec.NextIn = p;
                                writeAt = q;
                                return Flush(r);
                            }

                            c = (c == 16) ? blens[i - 1] : 0;
                            do
                            {
                                blens[i++] = c;
                            } while (--j != 0);
                            index = i;
                        }
                    }

                    tb[0] = -1;

                    {
                        var bl = new[] { 9 }; // must be <= 9 for lookahead assumptions
                        var bd = new[] { 6 }; // must be <= 9 for lookahead assumptions
                        var tl = new int[1];
                        var td = new int[1];

                        t = table;
                        t = inftree.inflate_trees_dynamic(
                            257 + (t & 0x1f),
                            1 + ((t >> 5) & 0x1f),
                            blens,
                            bl,
                            bd,
                            tl,
                            td,
                            hufts,
                            _codec
                        );

                        if (t != ZlibConstants.Z_OK)
                        {
                            if (t == ZlibConstants.Z_DATA_ERROR)
                            {
                                blens = null;
                                mode = InflateBlockMode.BAD;
                            }
                            r = t;

                            bitb = b;
                            bitk = k;
                            _codec.AvailableBytesIn = n;
                            _codec.TotalBytesIn += p - _codec.NextIn;
                            _codec.NextIn = p;
                            writeAt = q;
                            return Flush(r);
                        }
                        codes.Init(bl[0], bd[0], hufts, tl[0], hufts, td[0]);
                    }
                    mode = InflateBlockMode.CODES;
                    goto case InflateBlockMode.CODES;

                case InflateBlockMode.CODES:
                    bitb = b;
                    bitk = k;
                    _codec.AvailableBytesIn = n;
                    _codec.TotalBytesIn += p - _codec.NextIn;
                    _codec.NextIn = p;
                    writeAt = q;

                    r = codes.Process(this, r);
                    if (r != ZlibConstants.Z_STREAM_END)
                    {
                        return Flush(r);
                    }

                    r = ZlibConstants.Z_OK;
                    p = _codec.NextIn;
                    n = _codec.AvailableBytesIn;
                    b = bitb;
                    k = bitk;
                    q = writeAt;
                    m = (q < readAt ? readAt - q - 1 : end - q);

                    if (last == 0)
                    {
                        mode = InflateBlockMode.TYPE;
                        break;
                    }
                    mode = InflateBlockMode.DRY;
                    goto case InflateBlockMode.DRY;

                case InflateBlockMode.DRY:
                    writeAt = q;
                    r = Flush(r);
                    q = writeAt;
                    m = (q < readAt ? readAt - q - 1 : end - q);
                    if (readAt != writeAt)
                    {
                        bitb = b;
                        bitk = k;
                        _codec.AvailableBytesIn = n;
                        _codec.TotalBytesIn += p - _codec.NextIn;
                        _codec.NextIn = p;
                        writeAt = q;
                        return Flush(r);
                    }
                    mode = InflateBlockMode.DONE;
                    goto case InflateBlockMode.DONE;

                case InflateBlockMode.DONE:
                    r = ZlibConstants.Z_STREAM_END;
                    bitb = b;
                    bitk = k;
                    _codec.AvailableBytesIn = n;
                    _codec.TotalBytesIn += p - _codec.NextIn;
                    _codec.NextIn = p;
                    writeAt = q;
                    return Flush(r);

                case InflateBlockMode.BAD:
                    r = ZlibConstants.Z_DATA_ERROR;

                    bitb = b;
                    bitk = k;
                    _codec.AvailableBytesIn = n;
                    _codec.TotalBytesIn += p - _codec.NextIn;
                    _codec.NextIn = p;
                    writeAt = q;
                    return Flush(r);

                default:
                    r = ZlibConstants.Z_STREAM_ERROR;

                    bitb = b;
                    bitk = k;
                    _codec.AvailableBytesIn = n;
                    _codec.TotalBytesIn += p - _codec.NextIn;
                    _codec.NextIn = p;
                    writeAt = q;
                    return Flush(r);
            }
        }
    }

    internal void Free()
    {
        Reset();
        window?.Dispose();
        window = null;
        if (hufts is not null)
        {
            ArrayPool<int>.Shared.Return(hufts, clearArray: true);
            hufts = null;
        }
    }

    internal void SetDictionary(byte[] d, int start, int n)
    {
        d.AsSpan(start, n).CopyTo(window.Memory.Span.Slice(0, n));
        readAt = writeAt = n;
    }

    // Returns true if inflate is currently at the end of a block generated
    // by Z_SYNC_FLUSH or Z_FULL_FLUSH.
    internal int SyncPoint() => mode == InflateBlockMode.LENS ? 1 : 0;

    // copy as much as possible from the sliding window to the output area
    internal int Flush(int r)
    {
        int nBytes;

        for (var pass = 0; pass < 2; pass++)
        {
            if (pass == 0)
            {
                // compute number of bytes to copy as far as end of window
                nBytes = ((readAt <= writeAt ? writeAt : end) - readAt);
            }
            else
            {
                // compute bytes to copy
                nBytes = writeAt - readAt;
            }

            // workitem 8870
            if (nBytes == 0)
            {
                if (r == ZlibConstants.Z_BUF_ERROR)
                {
                    r = ZlibConstants.Z_OK;
                }
                return r;
            }

            if (nBytes > _codec.AvailableBytesOut)
            {
                nBytes = _codec.AvailableBytesOut;
            }

            if (nBytes != 0 && r == ZlibConstants.Z_BUF_ERROR)
            {
                r = ZlibConstants.Z_OK;
            }

            // update counters
            _codec.AvailableBytesOut -= nBytes;
            _codec.TotalBytesOut += nBytes;

            // update check information
            if (checkfn != null)
            {
                _codec._adler32 = check = Adler32.Calculate(
                    check,
                    window.Memory.Span.Slice(readAt, nBytes)
                );
            }

            // copy as far as end of window
            window
                .Memory.Span.Slice(readAt, nBytes)
                .CopyTo(_codec.OutputBuffer.AsSpan(_codec.NextOut));
            _codec.NextOut += nBytes;
            readAt += nBytes;

            // see if more to copy at beginning of window
            if (readAt == end && pass == 0)
            {
                // wrap pointers
                readAt = 0;
                if (writeAt == end)
                {
                    writeAt = 0;
                }
            }
            else
            {
                pass++;
            }
        }

        // done
        return r;
    }

    #region Nested type: InflateBlockMode

    private enum InflateBlockMode
    {
        TYPE = 0, // get type bits (3, including end bit)
        LENS = 1, // get lengths for stored
        STORED = 2, // processing stored block
        TABLE = 3, // get table lengths
        BTREE = 4, // get bit lengths tree for a dynamic block
        DTREE = 5, // get length, distance trees for a dynamic block
        CODES = 6, // processing fixed or dynamic block
        DRY = 7, // output remaining window bytes
        DONE = 8, // finished last block, done
        BAD = 9, // ot a data error--stuck here
    }

    #endregion
}

internal static class InternalInflateConstants
{
    // And'ing with mask[n] masks the lower n bits
    internal static readonly int[] InflateMask =
    {
        0x00000000,
        0x00000001,
        0x00000003,
        0x00000007,
        0x0000000f,
        0x0000001f,
        0x0000003f,
        0x0000007f,
        0x000000ff,
        0x000001ff,
        0x000003ff,
        0x000007ff,
        0x00000fff,
        0x00001fff,
        0x00003fff,
        0x00007fff,
        0x0000ffff,
    };
}

internal sealed class InflateCodes
{
    // waiting for "i:"=input,
    //             "o:"=output,
    //             "x:"=nothing
    private const int START = 0; // x: set up for LEN
    private const int LEN = 1; // i: get length/literal/eob next
    private const int LENEXT = 2; // i: getting length extra (have base)
    private const int DIST = 3; // i: get distance next
    private const int DISTEXT = 4; // i: getting distance extra
    private const int COPY = 5; // o: copying bytes in window, waiting for space
    private const int LIT = 6; // o: got literal, waiting for output space
    private const int WASH = 7; // o: got eob, possibly still output waiting
    private const int END = 8; // x: got eob and all data flushed
    private const int BADCODE = 9; // x: got error

    // if EXT or COPY, where and how much
    internal int bitsToGet; // bits to get for extra
    internal byte dbits; // dtree bits decoder per branch
    internal int dist; // distance back to copy from
    internal int[] dtree; // distance tree
    internal int dtree_index; // distance tree

    internal byte lbits; // ltree bits decoded per branch
    internal int len;
    internal int lit;
    internal int[] ltree; // literal/length/eob tree
    internal int ltree_index; // literal/length/eob tree
    internal int mode; // current inflate_codes mode
    internal int need; // bits needed
    internal int[] tree; // pointer into tree
    internal int tree_index;

    internal void Init(int bl, int bd, int[] tl, int tl_index, int[] td, int td_index)
    {
        mode = START;
        lbits = (byte)bl;
        dbits = (byte)bd;
        ltree = tl;
        ltree_index = tl_index;
        dtree = td;
        dtree_index = td_index;
        tree = null;
    }

    internal int Process(InflateBlocks blocks, int r)
    {
        int j; // temporary storage
        int tindex; // temporary pointer
        int e; // extra bits or operation
        var b = 0; // bit buffer
        var k = 0; // bits in bit buffer
        var p = 0; // input data pointer
        int n; // bytes available there
        int q; // output window write pointer
        int m; // bytes to end of window or read pointer
        int f; // pointer to copy strings from

        var z = blocks._codec;

        // copy input/output information to locals (UPDATE macro restores)
        p = z.NextIn;
        n = z.AvailableBytesIn;
        b = blocks.bitb;
        k = blocks.bitk;
        q = blocks.writeAt;
        m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;

        // process input and output based on current state
        while (true)
        {
            switch (mode)
            {
                // waiting for "i:"=input, "o:"=output, "x:"=nothing
                case START: // x: set up for LEN
                    if (m >= 258 && n >= 10)
                    {
                        blocks.bitb = b;
                        blocks.bitk = k;
                        z.AvailableBytesIn = n;
                        z.TotalBytesIn += p - z.NextIn;
                        z.NextIn = p;
                        blocks.writeAt = q;
                        r = InflateFast(
                            lbits,
                            dbits,
                            ltree,
                            ltree_index,
                            dtree,
                            dtree_index,
                            blocks,
                            z
                        );

                        p = z.NextIn;
                        n = z.AvailableBytesIn;
                        b = blocks.bitb;
                        k = blocks.bitk;
                        q = blocks.writeAt;
                        m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;

                        if (r != ZlibConstants.Z_OK)
                        {
                            mode = (r == ZlibConstants.Z_STREAM_END) ? WASH : BADCODE;
                            break;
                        }
                    }
                    need = lbits;
                    tree = ltree;
                    tree_index = ltree_index;

                    mode = LEN;
                    goto case LEN;

                case LEN: // i: get length/literal/eob next
                    j = need;

                    while (k < j)
                    {
                        if (n != 0)
                        {
                            r = ZlibConstants.Z_OK;
                        }
                        else
                        {
                            // Handling missing trailing bit(s)
                            var tmp_tindex =
                                (tree_index + (b & InternalInflateConstants.InflateMask[k])) * 3;
                            if (k >= tree[tmp_tindex + 1])
                            {
                                break;
                            }

                            blocks.bitb = b;
                            blocks.bitk = k;
                            z.AvailableBytesIn = n;
                            z.TotalBytesIn += p - z.NextIn;
                            z.NextIn = p;
                            blocks.writeAt = q;
                            return blocks.Flush(r);
                        }
                        n--;
                        b |= (z.InputBuffer[p++] & 0xff) << k;
                        k += 8;
                    }

                    tindex = (tree_index + (b & InternalInflateConstants.InflateMask[j])) * 3;

                    b >>= (tree[tindex + 1]);
                    k -= (tree[tindex + 1]);

                    e = tree[tindex];

                    if (e == 0)
                    {
                        // literal
                        lit = tree[tindex + 2];
                        mode = LIT;
                        break;
                    }
                    if ((e & 16) != 0)
                    {
                        // length
                        bitsToGet = e & 15;
                        len = tree[tindex + 2];
                        mode = LENEXT;
                        break;
                    }
                    if ((e & 64) == 0)
                    {
                        // next table
                        need = e;
                        tree_index = (tindex / 3) + tree[tindex + 2];
                        break;
                    }
                    if ((e & 32) != 0)
                    {
                        // end of block
                        mode = WASH;
                        break;
                    }
                    mode = BADCODE; // invalid code
                    z.Message = "invalid literal/length code";
                    r = ZlibConstants.Z_DATA_ERROR;

                    blocks.bitb = b;
                    blocks.bitk = k;
                    z.AvailableBytesIn = n;
                    z.TotalBytesIn += p - z.NextIn;
                    z.NextIn = p;
                    blocks.writeAt = q;
                    return blocks.Flush(r);

                case LENEXT: // i: getting length extra (have base)
                    j = bitsToGet;

                    while (k < j)
                    {
                        if (n != 0)
                        {
                            r = ZlibConstants.Z_OK;
                        }
                        else
                        {
                            blocks.bitb = b;
                            blocks.bitk = k;
                            z.AvailableBytesIn = n;
                            z.TotalBytesIn += p - z.NextIn;
                            z.NextIn = p;
                            blocks.writeAt = q;
                            return blocks.Flush(r);
                        }
                        n--;
                        b |= (z.InputBuffer[p++] & 0xff) << k;
                        k += 8;
                    }

                    len += (b & InternalInflateConstants.InflateMask[j]);

                    b >>= j;
                    k -= j;

                    need = dbits;
                    tree = dtree;
                    tree_index = dtree_index;
                    mode = DIST;
                    goto case DIST;

                case DIST: // i: get distance next
                    j = need;

                    while (k < j)
                    {
                        if (n != 0)
                        {
                            r = ZlibConstants.Z_OK;
                        }
                        else
                        {
                            // Handling missing trailing bit(s)
                            var tmp_tindex =
                                (tree_index + (b & InternalInflateConstants.InflateMask[k])) * 3;
                            if (k >= tree[tmp_tindex + 1])
                            {
                                break;
                            }

                            blocks.bitb = b;
                            blocks.bitk = k;
                            z.AvailableBytesIn = n;
                            z.TotalBytesIn += p - z.NextIn;
                            z.NextIn = p;
                            blocks.writeAt = q;
                            return blocks.Flush(r);
                        }
                        n--;
                        b |= (z.InputBuffer[p++] & 0xff) << k;
                        k += 8;
                    }

                    tindex = (tree_index + (b & InternalInflateConstants.InflateMask[j])) * 3;

                    b >>= tree[tindex + 1];
                    k -= tree[tindex + 1];

                    e = (tree[tindex]);
                    if ((e & 0x10) != 0)
                    {
                        // distance
                        bitsToGet = e & 15;
                        dist = tree[tindex + 2];
                        mode = DISTEXT;
                        break;
                    }
                    if ((e & 64) == 0)
                    {
                        // next table
                        need = e;
                        tree_index = (tindex / 3) + tree[tindex + 2];
                        break;
                    }
                    mode = BADCODE; // invalid code
                    z.Message = "invalid distance code";
                    r = ZlibConstants.Z_DATA_ERROR;

                    blocks.bitb = b;
                    blocks.bitk = k;
                    z.AvailableBytesIn = n;
                    z.TotalBytesIn += p - z.NextIn;
                    z.NextIn = p;
                    blocks.writeAt = q;
                    return blocks.Flush(r);

                case DISTEXT: // i: getting distance extra
                    j = bitsToGet;

                    while (k < j)
                    {
                        if (n != 0)
                        {
                            r = ZlibConstants.Z_OK;
                        }
                        else
                        {
                            blocks.bitb = b;
                            blocks.bitk = k;
                            z.AvailableBytesIn = n;
                            z.TotalBytesIn += p - z.NextIn;
                            z.NextIn = p;
                            blocks.writeAt = q;
                            return blocks.Flush(r);
                        }
                        n--;
                        b |= (z.InputBuffer[p++] & 0xff) << k;
                        k += 8;
                    }

                    dist += (b & InternalInflateConstants.InflateMask[j]);

                    b >>= j;
                    k -= j;

                    mode = COPY;
                    goto case COPY;

                case COPY: // o: copying bytes in window, waiting for space
                    f = q - dist;
                    while (f < 0)
                    {
                        // modulo window size-"while" instead
                        f += blocks.end; // of "if" handles invalid distances
                    }
                    while (len != 0)
                    {
                        if (m == 0)
                        {
                            if (q == blocks.end && blocks.readAt != 0)
                            {
                                q = 0;
                                m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
                            }
                            if (m == 0)
                            {
                                blocks.writeAt = q;
                                r = blocks.Flush(r);
                                q = blocks.writeAt;
                                m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;

                                if (q == blocks.end && blocks.readAt != 0)
                                {
                                    q = 0;
                                    m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
                                }

                                if (m == 0)
                                {
                                    blocks.bitb = b;
                                    blocks.bitk = k;
                                    z.AvailableBytesIn = n;
                                    z.TotalBytesIn += p - z.NextIn;
                                    z.NextIn = p;
                                    blocks.writeAt = q;
                                    return blocks.Flush(r);
                                }
                            }
                        }

                        blocks.window.Memory.Span[q++] = blocks.window.Memory.Span[f++];
                        m--;

                        if (f == blocks.end)
                        {
                            f = 0;
                        }
                        len--;
                    }
                    mode = START;
                    break;

                case LIT: // o: got literal, waiting for output space
                    if (m == 0)
                    {
                        if (q == blocks.end && blocks.readAt != 0)
                        {
                            q = 0;
                            m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
                        }
                        if (m == 0)
                        {
                            blocks.writeAt = q;
                            r = blocks.Flush(r);
                            q = blocks.writeAt;
                            m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;

                            if (q == blocks.end && blocks.readAt != 0)
                            {
                                q = 0;
                                m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;
                            }
                            if (m == 0)
                            {
                                blocks.bitb = b;
                                blocks.bitk = k;
                                z.AvailableBytesIn = n;
                                z.TotalBytesIn += p - z.NextIn;
                                z.NextIn = p;
                                blocks.writeAt = q;
                                return blocks.Flush(r);
                            }
                        }
                    }
                    r = ZlibConstants.Z_OK;

                    blocks.window.Memory.Span[q++] = (byte)lit;
                    m--;

                    mode = START;
                    break;

                case WASH: // o: got eob, possibly more output
                    if (k > 7)
                    {
                        // return unused byte, if any
                        k -= 8;
                        n++;
                        p--; // can always return one
                    }

                    blocks.writeAt = q;
                    r = blocks.Flush(r);
                    q = blocks.writeAt;
                    m = q < blocks.readAt ? blocks.readAt - q - 1 : blocks.end - q;

                    if (blocks.readAt != blocks.writeAt)
                    {
                        blocks.bitb = b;
                        blocks.bitk = k;
                        z.AvailableBytesIn = n;
                        z.TotalBytesIn += p - z.NextIn;
                        z.NextIn = p;
                        blocks.writeAt = q;
                        return blocks.Flush(r);
                    }
                    mode = END;
                    goto case END;

                case END:
                    r = ZlibConstants.Z_STREAM_END;
                    blocks.bitb = b;
                    blocks.bitk = k;
                    z.AvailableBytesIn = n;
                    z.TotalBytesIn += p - z.NextIn;
                    z.NextIn = p;
                    blocks.writeAt = q;
                    return blocks.Flush(r);

                case BADCODE: // x: got error

                    r = ZlibConstants.Z_DATA_ERROR;

                    blocks.bitb = b;
                    blocks.bitk = k;
                    z.AvailableBytesIn = n;
                    z.TotalBytesIn += p - z.NextIn;
                    z.NextIn = p;
                    blocks.writeAt = q;
                    return blocks.Flush(r);

                default:
                    r = ZlibConstants.Z_STREAM_ERROR;

                    blocks.bitb = b;
                    blocks.bitk = k;
                    z.AvailableBytesIn = n;
                    z.TotalBytesIn += p - z.NextIn;
                    z.NextIn = p;
                    blocks.writeAt = q;
                    return blocks.Flush(r);
            }
        }
    }

    // Called with number of bytes left to write in window at least 258
    // (the maximum string length) and number of input bytes available
    // at least ten.  The ten bytes are six bytes for the longest length/
    // distance pair plus four bytes for overloading the bit buffer.

    internal int InflateFast(
        int bl,
        int bd,
        int[] tl,
        int tl_index,
        int[] td,
        int td_index,
        InflateBlocks s,
        ZlibCodec z
    )
    {
        int t; // temporary pointer
        int[] tp; // temporary pointer
        int tp_index; // temporary pointer
        int e; // extra bits or operation
        int b; // bit buffer
        int k; // bits in bit buffer
        int p; // input data pointer
        int n; // bytes available there
        int q; // output window write pointer
        int m; // bytes to end of window or read pointer
        int ml; // mask for literal/length tree
        int md; // mask for distance tree
        int c; // bytes to copy
        int d; // distance back to copy from
        int r; // copy source pointer

        int tp_index_t_3; // (tp_index+t)*3

        // load input, output, bit values
        p = z.NextIn;
        n = z.AvailableBytesIn;
        b = s.bitb;
        k = s.bitk;
        q = s.writeAt;
        m = q < s.readAt ? s.readAt - q - 1 : s.end - q;

        // initialize masks
        ml = InternalInflateConstants.InflateMask[bl];
        md = InternalInflateConstants.InflateMask[bd];

        // do until not enough input or output space for fast loop
        do
        {
            // assume called with m >= 258 && n >= 10
            // get literal/length code
            while (k < (20))
            {
                // max bits for literal/length code
                n--;
                b |= (z.InputBuffer[p++] & 0xff) << k;
                k += 8;
            }

            t = b & ml;
            tp = tl;
            tp_index = tl_index;
            tp_index_t_3 = (tp_index + t) * 3;
            if ((e = tp[tp_index_t_3]) == 0)
            {
                b >>= (tp[tp_index_t_3 + 1]);
                k -= (tp[tp_index_t_3 + 1]);

                s.window.Memory.Span[q++] = (byte)tp[tp_index_t_3 + 2];
                m--;
                continue;
            }
            do
            {
                b >>= (tp[tp_index_t_3 + 1]);
                k -= (tp[tp_index_t_3 + 1]);

                if ((e & 16) != 0)
                {
                    e &= 15;
                    c = tp[tp_index_t_3 + 2] + (b & InternalInflateConstants.InflateMask[e]);

                    b >>= e;
                    k -= e;

                    // decode distance base of block to copy
                    while (k < 15)
                    {
                        // max bits for distance code
                        n--;
                        b |= (z.InputBuffer[p++] & 0xff) << k;
                        k += 8;
                    }

                    t = b & md;
                    tp = td;
                    tp_index = td_index;
                    tp_index_t_3 = (tp_index + t) * 3;
                    e = tp[tp_index_t_3];

                    do
                    {
                        b >>= (tp[tp_index_t_3 + 1]);
                        k -= (tp[tp_index_t_3 + 1]);

                        if ((e & 16) != 0)
                        {
                            // get extra bits to add to distance base
                            e &= 15;
                            while (k < e)
                            {
                                // get extra bits (up to 13)
                                n--;
                                b |= (z.InputBuffer[p++] & 0xff) << k;
                                k += 8;
                            }

                            d =
                                tp[tp_index_t_3 + 2]
                                + (b & InternalInflateConstants.InflateMask[e]);

                            b >>= e;
                            k -= e;

                            // do the copy
                            m -= c;
                            if (q >= d)
                            {
                                // offset before dest
                                //  just copy
                                r = q - d;
                                if (q - r > 0 && 2 > (q - r))
                                {
                                    s.window.Memory.Span[q++] = s.window.Memory.Span[r++]; // minimum count is three,
                                    s.window.Memory.Span[q++] = s.window.Memory.Span[r++]; // so unroll loop a little
                                    c -= 2;
                                }
                                else
                                {
                                    s.window.Memory.Span.Slice(r, 2)
                                        .CopyTo(s.window.Memory.Span.Slice(q));
                                    q += 2;
                                    r += 2;
                                    c -= 2;
                                }
                            }
                            else
                            {
                                // else offset after destination
                                r = q - d;
                                do
                                {
                                    r += s.end; // force pointer in window
                                } while (r < 0); // covers invalid distances
                                e = s.end - r;
                                if (c > e)
                                {
                                    // if source crosses,
                                    c -= e; // wrapped copy
                                    if (q - r > 0 && e > (q - r))
                                    {
                                        do
                                        {
                                            s.window.Memory.Span[q++] = s.window.Memory.Span[r++];
                                        } while (--e != 0);
                                    }
                                    else
                                    {
                                        s.window.Memory.Span.Slice(r, e)
                                            .CopyTo(s.window.Memory.Span.Slice(q));
                                        q += e;
                                        r += e;
                                        e = 0;
                                    }
                                    r = 0; // copy rest from start of window
                                }
                            }

                            // copy all or what's left
                            if (q - r > 0 && c > (q - r))
                            {
                                do
                                {
                                    s.window.Memory.Span[q++] = s.window.Memory.Span[r++];
                                } while (--c != 0);
                            }
                            else
                            {
                                s.window.Memory.Span.Slice(r, c)
                                    .CopyTo(s.window.Memory.Span.Slice(q));
                                q += c;
                                r += c;
                                c = 0;
                            }
                            break;
                        }
                        if ((e & 64) == 0)
                        {
                            t += tp[tp_index_t_3 + 2];
                            t += (b & InternalInflateConstants.InflateMask[e]);
                            tp_index_t_3 = (tp_index + t) * 3;
                            e = tp[tp_index_t_3];
                        }
                        else
                        {
                            z.Message = "invalid distance code";

                            c = z.AvailableBytesIn - n;
                            c = (k >> 3) < c ? k >> 3 : c;
                            n += c;
                            p -= c;
                            k -= (c << 3);

                            s.bitb = b;
                            s.bitk = k;
                            z.AvailableBytesIn = n;
                            z.TotalBytesIn += p - z.NextIn;
                            z.NextIn = p;
                            s.writeAt = q;

                            return ZlibConstants.Z_DATA_ERROR;
                        }
                    } while (true);
                    break;
                }

                if ((e & 64) == 0)
                {
                    t += tp[tp_index_t_3 + 2];
                    t += (b & InternalInflateConstants.InflateMask[e]);
                    tp_index_t_3 = (tp_index + t) * 3;
                    if ((e = tp[tp_index_t_3]) == 0)
                    {
                        b >>= (tp[tp_index_t_3 + 1]);
                        k -= (tp[tp_index_t_3 + 1]);
                        s.window.Memory.Span[q++] = (byte)tp[tp_index_t_3 + 2];
                        m--;
                        break;
                    }
                }
                else if ((e & 32) != 0)
                {
                    c = z.AvailableBytesIn - n;
                    c = (k >> 3) < c ? k >> 3 : c;
                    n += c;
                    p -= c;
                    k -= (c << 3);

                    s.bitb = b;
                    s.bitk = k;
                    z.AvailableBytesIn = n;
                    z.TotalBytesIn += p - z.NextIn;
                    z.NextIn = p;
                    s.writeAt = q;

                    return ZlibConstants.Z_STREAM_END;
                }
                else
                {
                    z.Message = "invalid literal/length code";

                    c = z.AvailableBytesIn - n;
                    c = (k >> 3) < c ? k >> 3 : c;
                    n += c;
                    p -= c;
                    k -= (c << 3);

                    s.bitb = b;
                    s.bitk = k;
                    z.AvailableBytesIn = n;
                    z.TotalBytesIn += p - z.NextIn;
                    z.NextIn = p;
                    s.writeAt = q;

                    return ZlibConstants.Z_DATA_ERROR;
                }
            } while (true);
        } while (m >= 258 && n >= 10);

        // not enough input or output--restore pointers and return
        c = z.AvailableBytesIn - n;
        c = (k >> 3) < c ? k >> 3 : c;
        n += c;
        p -= c;
        k -= (c << 3);

        s.bitb = b;
        s.bitk = k;
        z.AvailableBytesIn = n;
        z.TotalBytesIn += p - z.NextIn;
        z.NextIn = p;
        s.writeAt = q;

        return ZlibConstants.Z_OK;
    }
}

internal sealed class InflateManager
{
    // preset dictionary flag in zlib header
    private const int PRESET_DICT = 0x20;

    private const int Z_DEFLATED = 8;
    private static readonly byte[] mark = { 0, 0, 0xff, 0xff };

    internal ZlibCodec _codec; // pointer back to this zlib stream
    internal InflateBlocks blocks; // current inflate_blocks state

    // mode dependent information

    // if CHECK, check values to compare
    internal uint computedCheck; // computed check value
    internal uint expectedCheck; // stream check value

    // if BAD, inflateSync's marker bytes count
    internal int marker;
    internal int method; // if FLAGS, method byte
    private InflateManagerMode mode; // current inflate mode

    // mode independent information
    //internal int nowrap; // flag for no wrapper

    internal int wbits; // log2(window size)  (8..15, defaults to 15)

    public InflateManager() { }

    public InflateManager(bool expectRfc1950HeaderBytes) =>
        HandleRfc1950HeaderBytes = expectRfc1950HeaderBytes;

    internal bool HandleRfc1950HeaderBytes { get; set; } = true;

    internal int Reset()
    {
        _codec.TotalBytesIn = _codec.TotalBytesOut = 0;
        _codec.Message = null;
        mode = HandleRfc1950HeaderBytes ? InflateManagerMode.METHOD : InflateManagerMode.BLOCKS;
        blocks.Reset();
        return ZlibConstants.Z_OK;
    }

    internal int End()
    {
        blocks?.Free();
        blocks = null;
        return ZlibConstants.Z_OK;
    }

    internal int Initialize(ZlibCodec codec, int w)
    {
        _codec = codec;
        _codec.Message = null;
        blocks = null;

        // handle undocumented nowrap option (no zlib header or check)
        //nowrap = 0;
        //if (w < 0)
        //{
        //    w = - w;
        //    nowrap = 1;
        //}

        // set window size
        if (w < 8 || w > 15)
        {
            End();
            throw new ZlibException("Bad window size.");

            //return ZlibConstants.Z_STREAM_ERROR;
        }
        wbits = w;

        blocks = new InflateBlocks(codec, HandleRfc1950HeaderBytes ? this : null, 1 << w);

        // reset state
        Reset();
        return ZlibConstants.Z_OK;
    }

    internal int Inflate(FlushType flush)
    {
        int b;

        if (_codec.InputBuffer is null)
        {
            throw new ZlibException("InputBuffer is null. ");
        }

        //             int f = (flush == FlushType.Finish)
        //                 ? ZlibConstants.Z_BUF_ERROR
        //                 : ZlibConstants.Z_OK;

        // workitem 8870
        var f = ZlibConstants.Z_OK;
        var r = ZlibConstants.Z_BUF_ERROR;

        while (true)
        {
            switch (mode)
            {
                case InflateManagerMode.METHOD:
                    if (_codec.AvailableBytesIn == 0)
                    {
                        return r;
                    }
                    r = f;
                    _codec.AvailableBytesIn--;
                    _codec.TotalBytesIn++;
                    if (((method = _codec.InputBuffer[_codec.NextIn++]) & 0xf) != Z_DEFLATED)
                    {
                        mode = InflateManagerMode.BAD;
                        _codec.Message = string.Format(
                            Constants.DefaultCultureInfo,
                            "unknown compression method (0x{0:X2})",
                            method
                        );
                        marker = 5; // can't try inflateSync
                        break;
                    }
                    if ((method >> 4) + 8 > wbits)
                    {
                        mode = InflateManagerMode.BAD;
                        _codec.Message = string.Format(
                            Constants.DefaultCultureInfo,
                            "invalid window size ({0})",
                            (method >> 4) + 8
                        );
                        marker = 5; // can't try inflateSync
                        break;
                    }
                    mode = InflateManagerMode.FLAG;
                    break;

                case InflateManagerMode.FLAG:
                    if (_codec.AvailableBytesIn == 0)
                    {
                        return r;
                    }
                    r = f;
                    _codec.AvailableBytesIn--;
                    _codec.TotalBytesIn++;
                    b = (_codec.InputBuffer[_codec.NextIn++]) & 0xff;

                    if ((((method << 8) + b) % 31) != 0)
                    {
                        mode = InflateManagerMode.BAD;
                        _codec.Message = "incorrect header check";
                        marker = 5; // can't try inflateSync
                        break;
                    }

                    mode =
                        ((b & PRESET_DICT) == 0)
                            ? InflateManagerMode.BLOCKS
                            : InflateManagerMode.DICT4;
                    break;

                case InflateManagerMode.DICT4:
                    if (_codec.AvailableBytesIn == 0)
                    {
                        return r;
                    }
                    r = f;
                    _codec.AvailableBytesIn--;
                    _codec.TotalBytesIn++;
                    expectedCheck = (uint)(
                        (_codec.InputBuffer[_codec.NextIn++] << 24) & 0xff000000
                    );
                    mode = InflateManagerMode.DICT3;
                    break;

                case InflateManagerMode.DICT3:
                    if (_codec.AvailableBytesIn == 0)
                    {
                        return r;
                    }
                    r = f;
                    _codec.AvailableBytesIn--;
                    _codec.TotalBytesIn++;
                    expectedCheck += (uint)(
                        (_codec.InputBuffer[_codec.NextIn++] << 16) & 0x00ff0000
                    );
                    mode = InflateManagerMode.DICT2;
                    break;

                case InflateManagerMode.DICT2:

                    if (_codec.AvailableBytesIn == 0)
                    {
                        return r;
                    }
                    r = f;
                    _codec.AvailableBytesIn--;
                    _codec.TotalBytesIn++;
                    expectedCheck += (uint)(
                        (_codec.InputBuffer[_codec.NextIn++] << 8) & 0x0000ff00
                    );
                    mode = InflateManagerMode.DICT1;
                    break;

                case InflateManagerMode.DICT1:
                    if (_codec.AvailableBytesIn == 0)
                    {
                        return r;
                    }
                    r = f;
                    _codec.AvailableBytesIn--;
                    _codec.TotalBytesIn++;
                    expectedCheck += (uint)(_codec.InputBuffer[_codec.NextIn++] & 0x000000ff);
                    _codec._adler32 = expectedCheck;
                    mode = InflateManagerMode.DICT0;
                    return ZlibConstants.Z_NEED_DICT;

                case InflateManagerMode.DICT0:
                    mode = InflateManagerMode.BAD;
                    _codec.Message = "need dictionary";
                    marker = 0; // can try inflateSync
                    return ZlibConstants.Z_STREAM_ERROR;

                case InflateManagerMode.BLOCKS:
                    r = blocks.Process(r);
                    if (r == ZlibConstants.Z_DATA_ERROR)
                    {
                        mode = InflateManagerMode.BAD;
                        marker = 0; // can try inflateSync
                        break;
                    }

                    if (r == ZlibConstants.Z_OK)
                    {
                        r = f;
                    }

                    if (r != ZlibConstants.Z_STREAM_END)
                    {
                        return r;
                    }

                    r = f;
                    computedCheck = blocks.Reset();
                    if (!HandleRfc1950HeaderBytes)
                    {
                        mode = InflateManagerMode.DONE;
                        return ZlibConstants.Z_STREAM_END;
                    }
                    mode = InflateManagerMode.CHECK4;
                    break;

                case InflateManagerMode.CHECK4:
                    if (_codec.AvailableBytesIn == 0)
                    {
                        return r;
                    }
                    r = f;
                    _codec.AvailableBytesIn--;
                    _codec.TotalBytesIn++;
                    expectedCheck = (uint)(
                        (_codec.InputBuffer[_codec.NextIn++] << 24) & 0xff000000
                    );
                    mode = InflateManagerMode.CHECK3;
                    break;

                case InflateManagerMode.CHECK3:
                    if (_codec.AvailableBytesIn == 0)
                    {
                        return r;
                    }
                    r = f;
                    _codec.AvailableBytesIn--;
                    _codec.TotalBytesIn++;
                    expectedCheck += (uint)(
                        (_codec.InputBuffer[_codec.NextIn++] << 16) & 0x00ff0000
                    );
                    mode = InflateManagerMode.CHECK2;
                    break;

                case InflateManagerMode.CHECK2:
                    if (_codec.AvailableBytesIn == 0)
                    {
                        return r;
                    }
                    r = f;
                    _codec.AvailableBytesIn--;
                    _codec.TotalBytesIn++;
                    expectedCheck += (uint)(
                        (_codec.InputBuffer[_codec.NextIn++] << 8) & 0x0000ff00
                    );
                    mode = InflateManagerMode.CHECK1;
                    break;

                case InflateManagerMode.CHECK1:
                    if (_codec.AvailableBytesIn == 0)
                    {
                        return r;
                    }
                    r = f;
                    _codec.AvailableBytesIn--;
                    _codec.TotalBytesIn++;
                    expectedCheck += (uint)(_codec.InputBuffer[_codec.NextIn++] & 0x000000ff);
                    if (computedCheck != expectedCheck)
                    {
                        mode = InflateManagerMode.BAD;
                        _codec.Message = "incorrect data check";
                        marker = 5; // can't try inflateSync
                        break;
                    }
                    mode = InflateManagerMode.DONE;
                    return ZlibConstants.Z_STREAM_END;

                case InflateManagerMode.DONE:
                    return ZlibConstants.Z_STREAM_END;

                case InflateManagerMode.BAD:
                    throw new ZlibException(
                        string.Format(
                            Constants.DefaultCultureInfo,
                            "Bad state ({0})",
                            _codec.Message
                        )
                    );

                default:
                    throw new ZlibException("Stream error.");
            }
        }
    }

    internal int SetDictionary(byte[] dictionary)
    {
        var index = 0;
        var length = dictionary.Length;
        if (mode != InflateManagerMode.DICT0)
        {
            throw new ZlibException("Stream error.");
        }

        if (Adler32.Calculate(1, dictionary) != _codec._adler32)
        {
            return ZlibConstants.Z_DATA_ERROR;
        }

        _codec._adler32 = 1;

        if (length >= (1 << wbits))
        {
            length = (1 << wbits) - 1;
            index = dictionary.Length - length;
        }
        blocks.SetDictionary(dictionary, index, length);
        mode = InflateManagerMode.BLOCKS;
        return ZlibConstants.Z_OK;
    }

    internal int Sync()
    {
        int n; // number of bytes to look at
        int p; // pointer to bytes
        int m; // number of marker bytes found in a row
        long r,
            w; // temporaries to save total_in and total_out

        // set up
        if (mode != InflateManagerMode.BAD)
        {
            mode = InflateManagerMode.BAD;
            marker = 0;
        }
        if ((n = _codec.AvailableBytesIn) == 0)
        {
            return ZlibConstants.Z_BUF_ERROR;
        }
        p = _codec.NextIn;
        m = marker;

        // search
        while (n != 0 && m < 4)
        {
            if (_codec.InputBuffer[p] == mark[m])
            {
                m++;
            }
            else if (_codec.InputBuffer[p] != 0)
            {
                m = 0;
            }
            else
            {
                m = 4 - m;
            }
            p++;
            n--;
        }

        // restore
        _codec.TotalBytesIn += p - _codec.NextIn;
        _codec.NextIn = p;
        _codec.AvailableBytesIn = n;
        marker = m;

        // return no joy or set up to restart on a new block
        if (m != 4)
        {
            return ZlibConstants.Z_DATA_ERROR;
        }
        r = _codec.TotalBytesIn;
        w = _codec.TotalBytesOut;
        Reset();
        _codec.TotalBytesIn = r;
        _codec.TotalBytesOut = w;
        mode = InflateManagerMode.BLOCKS;
        return ZlibConstants.Z_OK;
    }

    // Returns true if inflate is currently at the end of a block generated
    // by Z_SYNC_FLUSH or Z_FULL_FLUSH. This function is used by one PPP
    // implementation to provide an additional safety check. PPP uses Z_SYNC_FLUSH
    // but removes the length bytes of the resulting empty stored block. When
    // decompressing, PPP checks that at the end of input packet, inflate is
    // waiting for these length bytes.
    internal int SyncPoint(ZlibCodec z) => blocks.SyncPoint();

    #region Nested type: InflateManagerMode

    private enum InflateManagerMode
    {
        METHOD = 0, // waiting for method byte
        FLAG = 1, // waiting for flag byte
        DICT4 = 2, // four dictionary check bytes to go
        DICT3 = 3, // three dictionary check bytes to go
        DICT2 = 4, // two dictionary check bytes to go
        DICT1 = 5, // one dictionary check byte to go
        DICT0 = 6, // waiting for inflateSetDictionary
        BLOCKS = 7, // decompressing blocks
        CHECK4 = 8, // four check bytes to go
        CHECK3 = 9, // three check bytes to go
        CHECK2 = 10, // two check bytes to go
        CHECK1 = 11, // one check byte to go
        DONE = 12, // finished check, done
        BAD = 13, // got an error--stay here
    }

    #endregion
}
