/*
 * 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;

// Low level compression engine for deflate algorithm which uses a 32K sliding window
// with secondary compression from Huffman/Shannon-Fano codes.
public class DeflaterEngine extends DeflaterConstants {

    private static final int tooFar = 4096;

    public DeflaterEngine(DeflaterPending _pending) {
        super();
        this.pending = _pending;
        huffman = new DeflaterHuffman(_pending);
        m_Adler = new Adler32();
        window = new byte[2 * DeflaterConstants.WSIZE];
        head = new short[DeflaterConstants.HASH_SIZE];
        prev = new short[DeflaterConstants.WSIZE];
        blockStart = (strstart = 1);
    }

    public boolean deflate(boolean flush, boolean finish) throws UnsupportedOperationException, Exception {
        boolean progress;
        while (true) {
            this.fillWindow();
            boolean canFlush = flush && (inputOff == inputEnd);
            switch (compressionFunction) { 
            case DeflaterConstants.DEFLATE_STORED:
                progress = this.deflateStored(canFlush, finish);
                break;
            case DeflaterConstants.DEFLATE_FAST:
                progress = this.deflateFast(canFlush, finish);
                break;
            case DeflaterConstants.DEFLATE_SLOW:
                progress = this.deflateSlow(canFlush, finish);
                break;
            default: 
                throw new UnsupportedOperationException("unknown compressionFunction");
        }
        if (pending.isFlushed() && progress) {
        }
        else 
            break;
        }
        return progress;
    }

    public void setInput(byte[] buffer, int offset, int count) throws IllegalArgumentException, IndexOutOfBoundsException, UnsupportedOperationException {
        if (buffer == null) 
            throw new IllegalArgumentException("buffer");
        if (offset < 0) 
            throw new IndexOutOfBoundsException("offset");
        if (count < 0) 
            throw new IndexOutOfBoundsException("count");
        if (inputOff < inputEnd) 
            throw new UnsupportedOperationException("Old input was not completely processed");
        int end = offset + count;
        if ((offset > end) || (end > buffer.length)) 
            throw new IndexOutOfBoundsException("count");
        inputBuf = buffer;
        inputOff = offset;
        inputEnd = end;
    }

    public boolean needsInput() {
        return inputEnd == inputOff;
    }

    public void setDictionary(byte[] buffer, int offset, int length) {
        m_Adler.updateByBufEx(buffer, offset, length);
        if (length < DeflaterConstants.MIN_MATCH) 
            return;
        if (length > DeflaterConstants.MAX_DIST) {
            offset += (length - DeflaterConstants.MAX_DIST);
            length = DeflaterConstants.MAX_DIST;
        }
        for(int iii = 0; iii < (length); iii++) window[(strstart) + iii] = buffer[(offset) + iii];
        this.updateHash();
        --length;
        while ((--length) > 0) {
            this.insertString();
            strstart++;
        }
        strstart += 2;
        blockStart = strstart;
    }

    public void reset() {
        huffman.reset();
        m_Adler.reset();
        blockStart = (strstart = 1);
        lookahead = 0;
        m_TotalIn = 0;
        prevAvailable = false;
        matchLen = DeflaterConstants.MIN_MATCH - 1;
        for (int i = 0; i < DeflaterConstants.HASH_SIZE; i++) {
            head[i] = (short)0;
        }
        for (int i = 0; i < DeflaterConstants.WSIZE; i++) {
            prev[i] = (short)0;
        }
    }

    public void resetAdler() {
        m_Adler.reset();
    }

    public int getAdler() {
        return (int)m_Adler.getValue();
    }


    public int getTotalIn() {
        return m_TotalIn;
    }


    public DeflateStrategy getStrategy() {
        return m_Strategy;
    }

    public DeflateStrategy setStrategy(DeflateStrategy value) {
        m_Strategy = value;
        return value;
    }


    public void setLevel(int level) throws IndexOutOfBoundsException, Exception {
        if (((level < 0)) || (level > 9)) 
            throw new IndexOutOfBoundsException("level");
        goodLength = DeflaterConstants.GOOD_LENGTH[level];
        max_lazy = DeflaterConstants.MAX_LAZY[level];
        niceLength = DeflaterConstants.NICE_LENGTH[level];
        max_chain = DeflaterConstants.MAX_CHAIN[level];
        if (DeflaterConstants.COMPR_FUNC[level] != compressionFunction) {
            switch (compressionFunction) { 
            case DeflaterConstants.DEFLATE_STORED:
                if (strstart > blockStart) {
                    huffman.flushStoredBlock(window, blockStart, strstart - blockStart, false);
                    blockStart = strstart;
                }
                this.updateHash();
                break;
            case DeflaterConstants.DEFLATE_FAST:
                if (strstart > blockStart) {
                    huffman.flushBlock(window, blockStart, strstart - blockStart, false);
                    blockStart = strstart;
                }
                break;
            case DeflaterConstants.DEFLATE_SLOW:
                if (prevAvailable) 
                    huffman.tallyLit((Byte.toUnsignedInt(window[strstart - 1])) & 0xff);
                if (strstart > blockStart) {
                    huffman.flushBlock(window, blockStart, strstart - blockStart, false);
                    blockStart = strstart;
                }
                prevAvailable = false;
                matchLen = DeflaterConstants.MIN_MATCH - 1;
                break;
        }
        compressionFunction = DeflaterConstants.COMPR_FUNC[level];
        }
    }

    public void fillWindow() {
        if (strstart >= (DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST)) 
            this.slideWindow();
        while ((lookahead < DeflaterConstants.MIN_LOOKAHEAD) && (inputOff < inputEnd)) {
            int more = (2 * DeflaterConstants.WSIZE) - lookahead - strstart;
            if (more > (inputEnd - inputOff)) 
                more = inputEnd - inputOff;
            for(int iii = 0; iii < (more); iii++) window[(strstart + lookahead) + iii] = inputBuf[(inputOff) + iii];
            m_Adler.updateByBufEx(inputBuf, inputOff, more);
            inputOff += more;
            m_TotalIn += more;
            lookahead += more;
        }
        if (lookahead >= DeflaterConstants.MIN_MATCH) 
            this.updateHash();
    }

    private void updateHash() {
        ins_h = ((Byte.toUnsignedInt(window[strstart])) << DeflaterConstants.HASH_SHIFT) ^ (Byte.toUnsignedInt(window[strstart + 1]));
    }

    private int insertString() {
        short match;
        int hash = (((ins_h << DeflaterConstants.HASH_SHIFT) ^ (Byte.toUnsignedInt(window[strstart + ((DeflaterConstants.MIN_MATCH - 1))])))) & DeflaterConstants.HASH_MASK;
        prev[strstart & DeflaterConstants.WMASK] = (match = head[hash]);
        head[hash] = (short)strstart;
        ins_h = hash;
        return ((int)match) & 0xffff;
    }

    private void slideWindow() {
        for(int iii = 0; iii < (DeflaterConstants.WSIZE); iii++) window[(0) + iii] = window[(DeflaterConstants.WSIZE) + iii];
        matchStart -= DeflaterConstants.WSIZE;
        strstart -= DeflaterConstants.WSIZE;
        blockStart -= DeflaterConstants.WSIZE;
        for (int i = 0; i < DeflaterConstants.HASH_SIZE; ++i) {
            int m = ((int)head[i]) & 0xffff;
            head[i] = (short)((m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0));
        }
        for (int i = 0; i < DeflaterConstants.WSIZE; i++) {
            int m = ((int)prev[i]) & 0xffff;
            prev[i] = (short)((m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0));
        }
    }

    private boolean findLongestMatch(int curMatch) {
        int chainLength = this.max_chain;
        int _niceLength = this.niceLength;
        short[] _prev = this.prev;
        int scan = this.strstart;
        int match;
        int best_end = this.strstart + matchLen;
        int best_len = Math.max(matchLen, DeflaterConstants.MIN_MATCH - 1);
        int limit = Math.max(strstart - DeflaterConstants.MAX_DIST, 0);
        int strend = (strstart + DeflaterConstants.MAX_MATCH) - 1;
        byte scan_end1 = window[best_end - 1];
        byte scan_end = window[best_end];
        if (best_len >= this.goodLength) 
            chainLength >>= 2;
        if (_niceLength > lookahead) 
            _niceLength = lookahead;
        while (true) {
            if ((window[curMatch + best_len] != scan_end || window[(curMatch + best_len) - 1] != scan_end1 || window[curMatch] != window[scan]) || window[curMatch + 1] != window[scan + 1]) {
                curMatch = (((int)_prev[curMatch & DeflaterConstants.WMASK]) & 0xffff);
                chainLength--;
                if (curMatch > limit && chainLength != 0) {
                }
                else 
                    break;
                continue;
            }
            match = curMatch + 2;
            scan += 2;
            while ((((window[++scan] == window[++match] && window[++scan] == window[++match] && window[++scan] == window[++match]) && window[++scan] == window[++match] && window[++scan] == window[++match]) && window[++scan] == window[++match] && window[++scan] == window[++match]) && window[++scan] == window[++match] && ((scan < strend))) {
            }
            if (scan > best_end) {
                matchStart = curMatch;
                best_end = scan;
                best_len = scan - strstart;
                if (best_len >= _niceLength) 
                    break;
                scan_end1 = window[best_end - 1];
                scan_end = window[best_end];
            }
            scan = strstart;
            curMatch = (((int)_prev[curMatch & DeflaterConstants.WMASK]) & 0xffff);
            chainLength--;
            if (curMatch > limit && chainLength != 0) {
            }
            else 
                break;
        }
        matchLen = Math.min(best_len, lookahead);
        return matchLen >= DeflaterConstants.MIN_MATCH;
    }

    private boolean deflateStored(boolean flush, boolean finish) {
        if (!flush && (lookahead == 0)) 
            return false;
        strstart += lookahead;
        lookahead = 0;
        int storedLength = strstart - blockStart;
        if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || (((blockStart < DeflaterConstants.WSIZE) && storedLength >= DeflaterConstants.MAX_DIST)) || flush) {
            boolean lastBlock = finish;
            if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE) {
                storedLength = DeflaterConstants.MAX_BLOCK_SIZE;
                lastBlock = false;
            }
            huffman.flushStoredBlock(window, blockStart, storedLength, lastBlock);
            blockStart += storedLength;
            return !lastBlock;
        }
        return true;
    }

    private boolean deflateFast(boolean flush, boolean finish) throws Exception {
        if ((lookahead < DeflaterConstants.MIN_LOOKAHEAD) && !flush) 
            return false;
        while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) {
            if (lookahead == 0) {
                huffman.flushBlock(window, blockStart, strstart - blockStart, finish);
                blockStart = strstart;
                return false;
            }
            if (strstart > ((2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD)) 
                this.slideWindow();
            int hashHead;
            if ((lookahead >= DeflaterConstants.MIN_MATCH && (((hashHead = this.insertString()))) != 0 && m_Strategy != DeflateStrategy.HUFFMANONLY) && (strstart - hashHead) <= DeflaterConstants.MAX_DIST && this.findLongestMatch(hashHead)) {
                boolean full = huffman.tallyDist(strstart - matchStart, matchLen);
                lookahead -= matchLen;
                if (matchLen <= max_lazy && lookahead >= DeflaterConstants.MIN_MATCH) {
                    while ((--matchLen) > 0) {
                        ++strstart;
                        this.insertString();
                    }
                    ++strstart;
                }
                else {
                    strstart += matchLen;
                    if (lookahead >= (DeflaterConstants.MIN_MATCH - 1)) 
                        this.updateHash();
                }
                matchLen = DeflaterConstants.MIN_MATCH - 1;
                if (!full) 
                    continue;
            }
            else {
                huffman.tallyLit((Byte.toUnsignedInt(window[strstart])) & 0xff);
                ++strstart;
                --lookahead;
            }
            if (huffman.isFull()) {
                boolean lastBlock = finish && (lookahead == 0);
                huffman.flushBlock(window, blockStart, strstart - blockStart, lastBlock);
                blockStart = strstart;
                return !lastBlock;
            }
        }
        return true;
    }

    private boolean deflateSlow(boolean flush, boolean finish) throws Exception {
        if ((lookahead < DeflaterConstants.MIN_LOOKAHEAD) && !flush) 
            return false;
        while (lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) {
            if (lookahead == 0) {
                if (prevAvailable) 
                    huffman.tallyLit((Byte.toUnsignedInt(window[strstart - 1])) & 0xff);
                prevAvailable = false;
                huffman.flushBlock(window, blockStart, strstart - blockStart, finish);
                blockStart = strstart;
                return false;
            }
            if (strstart >= ((2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD)) 
                this.slideWindow();
            int prevMatch = matchStart;
            int prevLen = matchLen;
            if (lookahead >= DeflaterConstants.MIN_MATCH) {
                int hashHead = this.insertString();
                if ((m_Strategy != DeflateStrategy.HUFFMANONLY && hashHead != 0 && (strstart - hashHead) <= DeflaterConstants.MAX_DIST) && this.findLongestMatch(hashHead)) {
                    if (matchLen <= 5 && ((m_Strategy == DeflateStrategy.FILTERED || ((matchLen == DeflaterConstants.MIN_MATCH && (strstart - matchStart) > tooFar))))) 
                        matchLen = DeflaterConstants.MIN_MATCH - 1;
                }
            }
            if ((prevLen >= DeflaterConstants.MIN_MATCH) && (matchLen <= prevLen)) {
                huffman.tallyDist(strstart - 1 - prevMatch, prevLen);
                prevLen -= 2;
                do{
                    strstart++;
                    lookahead--;
                    if (lookahead >= DeflaterConstants.MIN_MATCH) 
                        this.insertString();
                    --prevLen;
                } while (prevLen > 0); 
                strstart++;
                lookahead--;
                prevAvailable = false;
                matchLen = DeflaterConstants.MIN_MATCH - 1;
            }
            else {
                if (prevAvailable) 
                    huffman.tallyLit((Byte.toUnsignedInt(window[strstart - 1])) & 0xff);
                prevAvailable = true;
                strstart++;
                lookahead--;
            }
            if (huffman.isFull()) {
                int len = strstart - blockStart;
                if (prevAvailable) 
                    len--;
                boolean lastBlock = (finish && (lookahead == 0) && !prevAvailable);
                huffman.flushBlock(window, blockStart, len, lastBlock);
                blockStart += len;
                return !lastBlock;
            }
        }
        return true;
    }

    private int ins_h;

    private short[] head;

    private short[] prev;

    private int matchStart;

    private int matchLen;

    private boolean prevAvailable;

    private int blockStart;

    private int strstart;

    private int lookahead;

    private byte[] window;

    private DeflateStrategy m_Strategy = DeflateStrategy.DEFAULT;

    private int max_chain;

    private int max_lazy;

    private int niceLength;

    private int goodLength;

    private int compressionFunction;

    private byte[] inputBuf;

    private int m_TotalIn;

    private int inputOff;

    private int inputEnd;

    private DeflaterPending pending;

    private DeflaterHuffman huffman;

    private Adler32 m_Adler;
    public DeflaterEngine() {
        super();
    }
}
