/*
 * SDK Pullenti Unitext, version 4.33, fabruary 2026. Copyright (c) 2013-2026, Pullenti. All rights reserved. 
 * Non-Commercial Freeware and Commercial Software. 
 * This class is generated using the converter Unisharping (www.unisharping.ru) from Pullenti C# project. 
 * The latest version of the code is available on the site www.pullenti.ru
 */

package com.pullenti.unitext.internal.zip;

// Inflater is used to decompress data that has been compressed according
// to the "deflate" standard described in rfc1951.
// By default Zlib (rfc1950) headers and footers are expected in the input.
// You can use constructor <code> public Inflater(bool noHeader)</code> passing true
// if there is no Zlib header information
// The usage is as following.  First you have to set some input with
// <code>SetInput()</code>, then Inflate() it.  If inflate doesn't
// inflate any bytes there may be three reasons:
// <ul>
// <li>IsNeedingInput() returns true because the input buffer is empty.
// You have to provide more input with <code>SetInput()</code>.
// NOTE: IsNeedingInput() also returns true when, the stream is finished.
// </li>
// <li>IsNeedingDictionary() returns true, you have to provide a preset
// dictionary with <code>SetDictionary()</code>.</li>
// <li>IsFinished returns true, the inflater has finished.</li>
// </ul>
// Once the first output byte is produced, a dictionary will not be
// needed at a later stage.
public class Inflater {

    private static int[] cPLENS;

    private static int[] cPLEXT;

    private static int[] cPDIST;

    private static int[] cPDEXT;

    private static final int dECODE_HEADER = 0;

    private static final int dECODE_DICT = 1;

    private static final int dECODE_BLOCKS = 2;

    private static final int dECODE_STORED_LEN1 = 3;

    private static final int dECODE_STORED_LEN2 = 4;

    private static final int dECODE_STORED = 5;

    private static final int dECODE_DYN_HEADER = 6;

    private static final int dECODE_HUFFMAN = 7;

    private static final int dECODE_HUFFMAN_LENBITS = 8;

    private static final int dECODE_HUFFMAN_DIST = 9;

    private static final int dECODE_HUFFMAN_DISTBITS = 10;

    private static final int dECODE_CHKSUM = 11;

    private static final int fINISHED = 12;

    private int mode;

    private int readAdler;

    private int neededBits;

    private int repLength;

    private int repDist;

    private int uncomprLen;

    private boolean isLastBlock;

    private int m_TotalOut;

    private int m_TotalIn;

    private boolean noHeader;

    private StreamManipulator input;

    private OutputWindow outputWindow;

    private InflaterDynHeader dynHeader;

    private InflaterHuffmanTree litlenTree;

    private InflaterHuffmanTree distTree;

    private Adler32 m_Adler;

    public Inflater(boolean _noHeader) {
        this.noHeader = _noHeader;
        this.m_Adler = new Adler32();
        input = new StreamManipulator();
        outputWindow = new OutputWindow();
        mode = (_noHeader ? dECODE_BLOCKS : dECODE_HEADER);
    }

    public void reset() {
        mode = (noHeader ? dECODE_BLOCKS : dECODE_HEADER);
        m_TotalIn = 0;
        m_TotalOut = 0;
        input.reset();
        outputWindow.reset();
        dynHeader = null;
        litlenTree = null;
        distTree = null;
        isLastBlock = false;
        m_Adler.reset();
    }

    private boolean decodeHeaderEx() throws Exception {
        int header = input.peekBits(16);
        if (header < 0) 
            return false;
        input.dropBits(16);
        header = (((header << 8) | ((header >> 8)))) & 0xffff;
        if ((header % 31) != 0) 
            throw new Exception("Header checksum illegal");
        if (((header & 0x0f00)) != (Deflater.DEFLATED << 8)) 
            throw new Exception("Compression Method unknown");
        if (((header & 0x0020)) == 0) 
            mode = dECODE_BLOCKS;
        else {
            mode = dECODE_DICT;
            neededBits = 32;
        }
        return true;
    }

    private boolean decodeDictEx() {
        while (neededBits > 0) {
            int dictByte = input.peekBits(8);
            if (dictByte < 0) 
                return false;
            input.dropBits(8);
            readAdler = (readAdler << 8) | dictByte;
            neededBits -= 8;
        }
        return false;
    }

    private boolean decodeHuffmanEx() throws Exception, UnsupportedOperationException {
        int free = outputWindow.getFreeSpace();
        int mode0 = mode;
        while (free >= 258) {
            int symbol;
            switch (mode0) { 
            case dECODE_HUFFMAN:
                while (true) {
                    symbol = litlenTree.getSymbol(input);
                    if (((symbol & (~0xff))) != 0) 
                        break;
                    outputWindow.write(symbol);
                    if ((--free) < 258) 
                        return true;
                }
                if (symbol < 257) {
                    if (symbol < 0) 
                        return false;
                    else {
                        distTree = null;
                        litlenTree = null;
                        mode = (mode0 = dECODE_BLOCKS);
                        return true;
                    }
                }
                try {
                    repLength = cPLENS[symbol - 257];
                    neededBits = cPLEXT[symbol - 257];
                } catch (Exception ex558) {
                    throw new Exception("Illegal rep length code");
                }
                mode0 = dECODE_HUFFMAN_LENBITS;
                break;
            case dECODE_HUFFMAN_LENBITS:
                if (neededBits > 0) {
                    mode = (mode0 = dECODE_HUFFMAN_LENBITS);
                    int i = input.peekBits(neededBits);
                    if (i < 0) 
                        return false;
                    input.dropBits(neededBits);
                    repLength += i;
                }
                mode = (mode0 = dECODE_HUFFMAN_DIST);
                break;
            case dECODE_HUFFMAN_DIST:
                symbol = distTree.getSymbol(input);
                if (symbol < 0) 
                    return false;
                try {
                    repDist = cPDIST[symbol];
                    neededBits = cPDEXT[symbol];
                } catch (Exception ex559) {
                    throw new Exception("Illegal rep dist code");
                }
                mode0 = dECODE_HUFFMAN_DISTBITS;
                break;
            case dECODE_HUFFMAN_DISTBITS:
                if (neededBits > 0) {
                    mode = (mode0 = dECODE_HUFFMAN_DISTBITS);
                    int i = input.peekBits(neededBits);
                    if (i < 0) 
                        return false;
                    input.dropBits(neededBits);
                    repDist += i;
                }
                outputWindow.repeat(repLength, repDist);
                free -= repLength;
                mode = (mode0 = dECODE_HUFFMAN);
                break;
            default: 
                throw new Exception("Inflater unknown mode");
        }
        }
        return true;
    }

    private boolean decodeChksumEx() throws Exception {
        while (neededBits > 0) {
            int chkByte = input.peekBits(8);
            if (chkByte < 0) 
                return false;
            input.dropBits(8);
            readAdler = (readAdler << 8) | chkByte;
            neededBits -= 8;
        }
        if (((int)m_Adler.getValue()) != readAdler) 
            throw new Exception(("Adler chksum doesn't match: " + (((Integer)((int)m_Adler.getValue())).toString()) + " vs. ") + (((Integer)readAdler).toString()));
        mode = fINISHED;
        return false;
    }

    private boolean decode() throws Exception, IndexOutOfBoundsException, UnsupportedOperationException {
        while (true) {
            switch (mode) { 
            case dECODE_HEADER:
                return this.decodeHeaderEx();
            case dECODE_DICT:
                return this.decodeDictEx();
            case dECODE_CHKSUM:
                return this.decodeChksumEx();
            case dECODE_BLOCKS:
                if (isLastBlock) {
                    if (noHeader) {
                        mode = fINISHED;
                        return false;
                    }
                    else {
                        input.skipToByteBoundary();
                        neededBits = 32;
                        mode = dECODE_CHKSUM;
                        return true;
                    }
                }
                int type = input.peekBits(3);
                if (type < 0) 
                    return false;
                input.dropBits(3);
                if (((type & 1)) != 0) 
                    isLastBlock = true;
                switch (type >> 1) { 
                case DeflaterConstants.STORED_BLOCK:
                    input.skipToByteBoundary();
                    mode = dECODE_STORED_LEN1;
                    break;
                case DeflaterConstants.STATIC_TREES:
                    litlenTree = InflaterHuffmanTree.DEFLITLENTREE;
                    distTree = InflaterHuffmanTree.DEFDISTTREE;
                    mode = dECODE_HUFFMAN;
                    break;
                case DeflaterConstants.DYN_TREES:
                    dynHeader = new InflaterDynHeader();
                    mode = dECODE_DYN_HEADER;
                    break;
                default: 
                    throw new Exception("Unknown block type " + (((Integer)type).toString()));
            }
            return true;
            case dECODE_STORED_LEN1:{
                        if ((((uncomprLen = input.peekBits(16)))) < 0) 
                            return false;
                        input.dropBits(16);
                        mode = dECODE_STORED_LEN2;
                    }
                break;
            case dECODE_STORED_LEN2:{
                        int nlen = input.peekBits(16);
                        if (nlen < 0) 
                            return false;
                        input.dropBits(16);
                        if (nlen != ((uncomprLen ^ 0xffff))) 
                            throw new Exception("broken uncompressed block");
                        mode = dECODE_STORED;
                    }
                break;
            case dECODE_STORED:{
                        int more = outputWindow.copyStored(input, uncomprLen);
                        uncomprLen -= more;
                        if (uncomprLen == 0) {
                            mode = dECODE_BLOCKS;
                            return true;
                        }
                        return !input.isNeedingInput();
                    }
            case dECODE_DYN_HEADER:
                if (!dynHeader.decode(input)) 
                    return false;
                litlenTree = dynHeader.buildLitLenTree();
                distTree = dynHeader.buildDistTree();
                mode = dECODE_HUFFMAN;
                break;
            case dECODE_HUFFMAN:
            case dECODE_HUFFMAN_LENBITS:
            case dECODE_HUFFMAN_DIST:
            case dECODE_HUFFMAN_DISTBITS:
                return this.decodeHuffmanEx();
            case fINISHED:
                return false;
            default: 
                throw new Exception("Inflater.Decode unknown mode");
        }
        }
    }

    public void setDictionary(byte[] buffer) throws IllegalArgumentException, IndexOutOfBoundsException, UnsupportedOperationException, Exception {
        this.setDictionaryEx(buffer, 0, buffer.length);
    }

    public void setDictionaryEx(byte[] buffer, int index, int count) throws IllegalArgumentException, IndexOutOfBoundsException, UnsupportedOperationException, Exception {
        if (buffer == null) 
            throw new IllegalArgumentException("buffer");
        if (index < 0) 
            throw new IndexOutOfBoundsException("index");
        if (count < 0) 
            throw new IndexOutOfBoundsException("count");
        if (!isNeedingDictionary()) 
            throw new UnsupportedOperationException("Dictionary is not needed");
        m_Adler.updateByBufEx(buffer, index, count);
        if (((int)m_Adler.getValue()) != readAdler) 
            throw new Exception("Wrong adler checksum");
        m_Adler.reset();
        outputWindow.copyDict(buffer, index, count);
        mode = dECODE_BLOCKS;
    }

    public void setInput(byte[] buffer) throws IllegalArgumentException, IndexOutOfBoundsException, UnsupportedOperationException {
        this.setInputEx(buffer, 0, buffer.length);
    }

    public void setInputEx(byte[] buffer, int index, int count) throws IllegalArgumentException, IndexOutOfBoundsException, UnsupportedOperationException {
        input.setInput(buffer, index, count);
        m_TotalIn += count;
    }

    public int inflate(byte[] buffer) throws IllegalArgumentException, IndexOutOfBoundsException, IllegalArgumentException, UnsupportedOperationException, Exception {
        if (buffer == null) 
            throw new IllegalArgumentException("buffer");
        return this.inflateEx(buffer, 0, buffer.length);
    }

    public int inflateEx(byte[] buffer, int offset, int count) throws IllegalArgumentException, IndexOutOfBoundsException, IllegalArgumentException, UnsupportedOperationException, Exception {
        if (buffer == null) 
            throw new IllegalArgumentException("buffer");
        if (count < 0) 
            throw new IndexOutOfBoundsException("count" + " " + "count cannot be negative");
        if (offset < 0) 
            throw new IndexOutOfBoundsException("offset" + " " + "offset cannot be negative");
        if ((offset + count) > buffer.length) 
            throw new IllegalArgumentException("count exceeds buffer bounds");
        if (count == 0) {
            if (!isFinished()) 
                this.decode();
            return 0;
        }
        int bytesCopied = 0;
        do{
            if (mode != dECODE_CHKSUM) {
                int more = outputWindow.copyOutput(buffer, offset, count);
                if (more > 0) {
                    m_Adler.updateByBufEx(buffer, offset, more);
                    offset += more;
                    bytesCopied += more;
                    m_TotalOut += more;
                    count -= more;
                    if (count == 0) 
                        return bytesCopied;
                }
            }
        } while (this.decode() || (((outputWindow.getAvailable() > 0) && (mode != dECODE_CHKSUM)))); 
        return bytesCopied;
    }

    public boolean isNeedingInput() {
        return input.isNeedingInput();
    }


    public boolean isNeedingDictionary() {
        return mode == dECODE_DICT && neededBits == 0;
    }


    public boolean isFinished() {
        return mode == fINISHED && outputWindow.getAvailable() == 0;
    }


    public int getAdler() {
        return (isNeedingDictionary() ? readAdler : (int)m_Adler.getValue());
    }


    public int getTotalOut() {
        return m_TotalOut;
    }


    public int getTotalIn() {
        return m_TotalIn - getRemainingInput();
    }


    public int getRemainingInput() {
        return input.getAvailableBytes();
    }


    public Inflater() {
        this(false);
    }
    
    static {
        cPLENS = new int[] {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
        cPLEXT = new int[] {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
        cPDIST = new int[] {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577};
        cPDEXT = new int[] {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
    }
}
