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


const DeflaterConstants = require("./DeflaterConstants");
const DeflateStrategy = require("./DeflateStrategy");
const DeflaterHuffman = require("./DeflaterHuffman");
const Adler32 = require("./Adler32");

// Low level compression engine for deflate algorithm which uses a 32K sliding window
// with secondary compression from Huffman/Shannon-Fano codes.
class DeflaterEngine extends DeflaterConstants {
    
    constructor(_pending) {
        super();
        this.ins_h = 0;
        this.head = null;
        this.prev = null;
        this.matchStart = 0;
        this.matchLen = 0;
        this.prevAvailable = false;
        this.blockStart = 0;
        this.strstart = 0;
        this.lookahead = 0;
        this.window = null;
        this.m_Strategy = DeflateStrategy.DEFAULT;
        this.max_chain = 0;
        this.max_lazy = 0;
        this.niceLength = 0;
        this.goodLength = 0;
        this.compressionFunction = 0;
        this.inputBuf = null;
        this.m_TotalIn = 0;
        this.inputOff = 0;
        this.inputEnd = 0;
        this.pending = null;
        this.huffman = null;
        this.m_Adler = null;
        this.pending = _pending;
        this.huffman = new DeflaterHuffman(_pending);
        this.m_Adler = new Adler32();
        this.window = new Uint8Array(2 * DeflaterConstants.WSIZE);
        this.head = new Int16Array(DeflaterConstants.HASH_SIZE);
        this.prev = new Int16Array(DeflaterConstants.WSIZE);
        this.blockStart = (this.strstart = 1);
    }
    
    deflate(flush, finish) {
        let progress = false;
        while (true) {
            this.fillWindow();
            let canFlush = flush && (this.inputOff === this.inputEnd);
            switch (this.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 Error("unknown compressionFunction");
            }
            }
            if (this.pending.isFlushed && progress) {
            }
            else 
                break;
        }
        return progress;
    }
    
    setInput(buffer, offset, count) {
        if (buffer === null) 
            throw new Error("buffer");
        if (offset < 0) 
            throw new Error("offset");
        if (count < 0) 
            throw new Error("count");
        if (this.inputOff < this.inputEnd) 
            throw new Error("Old input was not completely processed");
        let end = offset + count;
        if ((offset > end) || (end > buffer.length)) 
            throw new Error("count");
        this.inputBuf = buffer;
        this.inputOff = offset;
        this.inputEnd = end;
    }
    
    needsInput() {
        return this.inputEnd === this.inputOff;
    }
    
    setDictionary(buffer, offset, length) {
        this.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(let iii = 0; iii < (length); iii++) this.window[(this.strstart) + iii] = buffer[(offset) + iii];
        this.updateHash();
        --length;
        while ((--length) > 0) {
            this.insertString();
            this.strstart++;
        }
        this.strstart += 2;
        this.blockStart = this.strstart;
    }
    
    reset() {
        this.huffman.reset();
        this.m_Adler.reset();
        this.blockStart = (this.strstart = 1);
        this.lookahead = 0;
        this.m_TotalIn = 0;
        this.prevAvailable = false;
        this.matchLen = DeflaterConstants.MIN_MATCH - 1;
        for (let i = 0; i < DeflaterConstants.HASH_SIZE; i++) {
            this.head[i] = 0;
        }
        for (let i = 0; i < DeflaterConstants.WSIZE; i++) {
            this.prev[i] = 0;
        }
    }
    
    resetAdler() {
        this.m_Adler.reset();
    }
    
    get adler() {
        return this.m_Adler.value;
    }
    
    get totalIn() {
        return this.m_TotalIn;
    }
    
    get strategy() {
        return this.m_Strategy;
    }
    set strategy(value) {
        this.m_Strategy = value;
        return value;
    }
    
    setLevel(level) {
        if (((level < 0)) || (level > 9)) 
            throw new Error("level");
        this.goodLength = DeflaterConstants.GOOD_LENGTH[level];
        this.max_lazy = DeflaterConstants.MAX_LAZY[level];
        this.niceLength = DeflaterConstants.NICE_LENGTH[level];
        this.max_chain = DeflaterConstants.MAX_CHAIN[level];
        if (DeflaterConstants.COMPR_FUNC[level] !== this.compressionFunction) {
            switch (this.compressionFunction) { 
            case DeflaterConstants.DEFLATE_STORED: {
                if (this.strstart > this.blockStart) {
                    this.huffman.flushStoredBlock(this.window, this.blockStart, this.strstart - this.blockStart, false);
                    this.blockStart = this.strstart;
                }
                this.updateHash();
                break;
            }
            case DeflaterConstants.DEFLATE_FAST: {
                if (this.strstart > this.blockStart) {
                    this.huffman.flushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false);
                    this.blockStart = this.strstart;
                }
                break;
            }
            case DeflaterConstants.DEFLATE_SLOW: {
                if (this.prevAvailable) 
                    this.huffman.tallyLit((this.window[this.strstart - 1]) & 0xff);
                if (this.strstart > this.blockStart) {
                    this.huffman.flushBlock(this.window, this.blockStart, this.strstart - this.blockStart, false);
                    this.blockStart = this.strstart;
                }
                this.prevAvailable = false;
                this.matchLen = DeflaterConstants.MIN_MATCH - 1;
                break;
            }
            }
            this.compressionFunction = DeflaterConstants.COMPR_FUNC[level];
        }
    }
    
    fillWindow() {
        if (this.strstart >= (DeflaterConstants.WSIZE + DeflaterConstants.MAX_DIST)) 
            this.slideWindow();
        while ((this.lookahead < DeflaterConstants.MIN_LOOKAHEAD) && (this.inputOff < this.inputEnd)) {
            let more = (2 * DeflaterConstants.WSIZE) - this.lookahead - this.strstart;
            if (more > (this.inputEnd - this.inputOff)) 
                more = this.inputEnd - this.inputOff;
            for(let iii = 0; iii < (more); iii++) this.window[(this.strstart + this.lookahead) + iii] = this.inputBuf[(this.inputOff) + iii];
            this.m_Adler.updateByBufEx(this.inputBuf, this.inputOff, more);
            this.inputOff += more;
            this.m_TotalIn += more;
            this.lookahead += more;
        }
        if (this.lookahead >= DeflaterConstants.MIN_MATCH) 
            this.updateHash();
    }
    
    updateHash() {
        this.ins_h = ((this.window[this.strstart]) << DeflaterConstants.HASH_SHIFT) ^ (this.window[this.strstart + 1]);
    }
    
    insertString() {
        let match = 0;
        let hash = (((this.ins_h << DeflaterConstants.HASH_SHIFT) ^ (this.window[this.strstart + ((DeflaterConstants.MIN_MATCH - 1))]))) & DeflaterConstants.HASH_MASK;
        this.prev[this.strstart & DeflaterConstants.WMASK] = (match = this.head[hash]);
        this.head[hash] = this.strstart;
        this.ins_h = hash;
        return (match) & 0xffff;
    }
    
    slideWindow() {
        for(let iii = 0; iii < (DeflaterConstants.WSIZE); iii++) this.window[(0) + iii] = this.window[(DeflaterConstants.WSIZE) + iii];
        this.matchStart -= DeflaterConstants.WSIZE;
        this.strstart -= DeflaterConstants.WSIZE;
        this.blockStart -= DeflaterConstants.WSIZE;
        for (let i = 0; i < DeflaterConstants.HASH_SIZE; ++i) {
            let m = (this.head[i]) & 0xffff;
            this.head[i] = (m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0);
        }
        for (let i = 0; i < DeflaterConstants.WSIZE; i++) {
            let m = (this.prev[i]) & 0xffff;
            this.prev[i] = (m >= DeflaterConstants.WSIZE ? (m - DeflaterConstants.WSIZE) : 0);
        }
    }
    
    findLongestMatch(curMatch) {
        let chainLength = this.max_chain;
        let _niceLength = this.niceLength;
        let _prev = this.prev;
        let scan = this.strstart;
        let match = 0;
        let best_end = this.strstart + this.matchLen;
        let best_len = Math.max(this.matchLen, DeflaterConstants.MIN_MATCH - 1);
        let limit = Math.max(this.strstart - DeflaterConstants.MAX_DIST, 0);
        let strend = (this.strstart + DeflaterConstants.MAX_MATCH) - 1;
        let scan_end1 = this.window[best_end - 1];
        let scan_end = this.window[best_end];
        if (best_len >= this.goodLength) 
            chainLength >>= 2;
        if (_niceLength > this.lookahead) 
            _niceLength = this.lookahead;
        while (true) {
            if ((this.window[curMatch + best_len] !== scan_end || this.window[(curMatch + best_len) - 1] !== scan_end1 || this.window[curMatch] !== this.window[scan]) || this.window[curMatch + 1] !== this.window[scan + 1]) {
                curMatch = ((_prev[curMatch & DeflaterConstants.WMASK]) & 0xffff);
                chainLength--;
                if (curMatch > limit && chainLength !== 0) {
                }
                else 
                    break;
                continue;
            }
            match = curMatch + 2;
            scan += 2;
            while ((((this.window[++scan] === this.window[++match] && this.window[++scan] === this.window[++match] && this.window[++scan] === this.window[++match]) && this.window[++scan] === this.window[++match] && this.window[++scan] === this.window[++match]) && this.window[++scan] === this.window[++match] && this.window[++scan] === this.window[++match]) && this.window[++scan] === this.window[++match] && ((scan < strend))) {
            }
            if (scan > best_end) {
                this.matchStart = curMatch;
                best_end = scan;
                best_len = scan - this.strstart;
                if (best_len >= _niceLength) 
                    break;
                scan_end1 = this.window[best_end - 1];
                scan_end = this.window[best_end];
            }
            scan = this.strstart;
            curMatch = ((_prev[curMatch & DeflaterConstants.WMASK]) & 0xffff);
            chainLength--;
            if (curMatch > limit && chainLength !== 0) {
            }
            else 
                break;
        }
        this.matchLen = Math.min(best_len, this.lookahead);
        return this.matchLen >= DeflaterConstants.MIN_MATCH;
    }
    
    deflateStored(flush, finish) {
        if (!flush && (this.lookahead === 0)) 
            return false;
        this.strstart += this.lookahead;
        this.lookahead = 0;
        let storedLength = this.strstart - this.blockStart;
        if ((storedLength >= DeflaterConstants.MAX_BLOCK_SIZE) || (((this.blockStart < DeflaterConstants.WSIZE) && storedLength >= DeflaterConstants.MAX_DIST)) || flush) {
            let lastBlock = finish;
            if (storedLength > DeflaterConstants.MAX_BLOCK_SIZE) {
                storedLength = DeflaterConstants.MAX_BLOCK_SIZE;
                lastBlock = false;
            }
            this.huffman.flushStoredBlock(this.window, this.blockStart, storedLength, lastBlock);
            this.blockStart += storedLength;
            return !lastBlock;
        }
        return true;
    }
    
    deflateFast(flush, finish) {
        if ((this.lookahead < DeflaterConstants.MIN_LOOKAHEAD) && !flush) 
            return false;
        while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) {
            if (this.lookahead === 0) {
                this.huffman.flushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish);
                this.blockStart = this.strstart;
                return false;
            }
            if (this.strstart > ((2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD)) 
                this.slideWindow();
            let hashHead = 0;
            if ((this.lookahead >= DeflaterConstants.MIN_MATCH && (((hashHead = this.insertString()))) !== 0 && this.m_Strategy !== DeflateStrategy.HUFFMANONLY) && (this.strstart - hashHead) <= DeflaterConstants.MAX_DIST && this.findLongestMatch(hashHead)) {
                let full = this.huffman.tallyDist(this.strstart - this.matchStart, this.matchLen);
                this.lookahead -= this.matchLen;
                if (this.matchLen <= this.max_lazy && this.lookahead >= DeflaterConstants.MIN_MATCH) {
                    while ((--this.matchLen) > 0) {
                        ++this.strstart;
                        this.insertString();
                    }
                    ++this.strstart;
                }
                else {
                    this.strstart += this.matchLen;
                    if (this.lookahead >= (DeflaterConstants.MIN_MATCH - 1)) 
                        this.updateHash();
                }
                this.matchLen = DeflaterConstants.MIN_MATCH - 1;
                if (!full) 
                    continue;
            }
            else {
                this.huffman.tallyLit((this.window[this.strstart]) & 0xff);
                ++this.strstart;
                --this.lookahead;
            }
            if (this.huffman.isFull()) {
                let lastBlock = finish && (this.lookahead === 0);
                this.huffman.flushBlock(this.window, this.blockStart, this.strstart - this.blockStart, lastBlock);
                this.blockStart = this.strstart;
                return !lastBlock;
            }
        }
        return true;
    }
    
    deflateSlow(flush, finish) {
        if ((this.lookahead < DeflaterConstants.MIN_LOOKAHEAD) && !flush) 
            return false;
        while (this.lookahead >= DeflaterConstants.MIN_LOOKAHEAD || flush) {
            if (this.lookahead === 0) {
                if (this.prevAvailable) 
                    this.huffman.tallyLit((this.window[this.strstart - 1]) & 0xff);
                this.prevAvailable = false;
                this.huffman.flushBlock(this.window, this.blockStart, this.strstart - this.blockStart, finish);
                this.blockStart = this.strstart;
                return false;
            }
            if (this.strstart >= ((2 * DeflaterConstants.WSIZE) - DeflaterConstants.MIN_LOOKAHEAD)) 
                this.slideWindow();
            let prevMatch = this.matchStart;
            let prevLen = this.matchLen;
            if (this.lookahead >= DeflaterConstants.MIN_MATCH) {
                let hashHead = this.insertString();
                if ((this.m_Strategy !== DeflateStrategy.HUFFMANONLY && hashHead !== 0 && (this.strstart - hashHead) <= DeflaterConstants.MAX_DIST) && this.findLongestMatch(hashHead)) {
                    if (this.matchLen <= 5 && ((this.m_Strategy === DeflateStrategy.FILTERED || ((this.matchLen === DeflaterConstants.MIN_MATCH && (this.strstart - this.matchStart) > DeflaterEngine.tooFar))))) 
                        this.matchLen = DeflaterConstants.MIN_MATCH - 1;
                }
            }
            if ((prevLen >= DeflaterConstants.MIN_MATCH) && (this.matchLen <= prevLen)) {
                this.huffman.tallyDist(this.strstart - 1 - prevMatch, prevLen);
                prevLen -= 2;
                do{
                    this.strstart++;
                    this.lookahead--;
                    if (this.lookahead >= DeflaterConstants.MIN_MATCH) 
                        this.insertString();
                    --prevLen;
                } while (prevLen > 0); 
                this.strstart++;
                this.lookahead--;
                this.prevAvailable = false;
                this.matchLen = DeflaterConstants.MIN_MATCH - 1;
            }
            else {
                if (this.prevAvailable) 
                    this.huffman.tallyLit((this.window[this.strstart - 1]) & 0xff);
                this.prevAvailable = true;
                this.strstart++;
                this.lookahead--;
            }
            if (this.huffman.isFull()) {
                let len = this.strstart - this.blockStart;
                if (this.prevAvailable) 
                    len--;
                let lastBlock = (finish && (this.lookahead === 0) && !this.prevAvailable);
                this.huffman.flushBlock(this.window, this.blockStart, len, lastBlock);
                this.blockStart += len;
                return !lastBlock;
            }
        }
        return true;
    }
    
    static static_constructor() {
        DeflaterEngine.tooFar = 4096;
    }
}


DeflaterEngine.static_constructor();

module.exports = DeflaterEngine