/*
 * 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 Stream = require("./../../../unisharp/Stream");

const InflaterHuffmanTree = require("./InflaterHuffmanTree");
const DeflaterConstants = require("./DeflaterConstants");
const Adler32 = require("./Adler32");
const InflaterDynHeader = require("./InflaterDynHeader");
const StreamManipulator = require("./StreamManipulator");
const OutputWindow = require("./OutputWindow");
const Deflater = require("./Deflater");

// 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.
class Inflater {
    
    constructor(_noHeader = false) {
        this.mode = 0;
        this.readAdler = 0;
        this.neededBits = 0;
        this.repLength = 0;
        this.repDist = 0;
        this.uncomprLen = 0;
        this.isLastBlock = false;
        this.m_TotalOut = 0;
        this.m_TotalIn = 0;
        this.noHeader = false;
        this.input = null;
        this.outputWindow = null;
        this.dynHeader = null;
        this.litlenTree = null;
        this.distTree = null;
        this.m_Adler = null;
        this.noHeader = _noHeader;
        this.m_Adler = new Adler32();
        this.input = new StreamManipulator();
        this.outputWindow = new OutputWindow();
        this.mode = (_noHeader ? Inflater.dECODE_BLOCKS : Inflater.dECODE_HEADER);
    }
    
    reset() {
        this.mode = (this.noHeader ? Inflater.dECODE_BLOCKS : Inflater.dECODE_HEADER);
        this.m_TotalIn = 0;
        this.m_TotalOut = 0;
        this.input.reset();
        this.outputWindow.reset();
        this.dynHeader = null;
        this.litlenTree = null;
        this.distTree = null;
        this.isLastBlock = false;
        this.m_Adler.reset();
    }
    
    decodeHeaderEx() {
        let header = this.input.peekBits(16);
        if (header < 0) 
            return false;
        this.input.dropBits(16);
        header = (((header << 8) | ((header >> 8)))) & 0xffff;
        if ((header % 31) !== 0) 
            throw new Error("Header checksum illegal");
        if (((header & 0x0f00)) !== (Deflater.DEFLATED << 8)) 
            throw new Error("Compression Method unknown");
        if (((header & 0x0020)) === 0) 
            this.mode = Inflater.dECODE_BLOCKS;
        else {
            this.mode = Inflater.dECODE_DICT;
            this.neededBits = 32;
        }
        return true;
    }
    
    decodeDictEx() {
        while (this.neededBits > 0) {
            let dictByte = this.input.peekBits(8);
            if (dictByte < 0) 
                return false;
            this.input.dropBits(8);
            this.readAdler = (this.readAdler << 8) | dictByte;
            this.neededBits -= 8;
        }
        return false;
    }
    
    decodeHuffmanEx() {
        let free = this.outputWindow.getFreeSpace();
        let mode0 = this.mode;
        while (free >= 258) {
            let symbol = 0;
            switch (mode0) { 
            case Inflater.dECODE_HUFFMAN: {
                while (true) {
                    symbol = this.litlenTree.getSymbol(this.input);
                    if (((symbol & (~0xff))) !== 0) 
                        break;
                    this.outputWindow.write(symbol);
                    if ((--free) < 258) 
                        return true;
                }
                if (symbol < 257) {
                    if (symbol < 0) 
                        return false;
                    else {
                        this.distTree = null;
                        this.litlenTree = null;
                        this.mode = (mode0 = Inflater.dECODE_BLOCKS);
                        return true;
                    }
                }
                try {
                    this.repLength = Inflater.cPLENS[symbol - 257];
                    this.neededBits = Inflater.cPLEXT[symbol - 257];
                } catch (ex557) {
                    throw new Error("Illegal rep length code");
                }
                mode0 = Inflater.dECODE_HUFFMAN_LENBITS;
                break;
            }
            case Inflater.dECODE_HUFFMAN_LENBITS: {
                if (this.neededBits > 0) {
                    this.mode = (mode0 = Inflater.dECODE_HUFFMAN_LENBITS);
                    let i = this.input.peekBits(this.neededBits);
                    if (i < 0) 
                        return false;
                    this.input.dropBits(this.neededBits);
                    this.repLength += i;
                }
                this.mode = (mode0 = Inflater.dECODE_HUFFMAN_DIST);
                break;
            }
            case Inflater.dECODE_HUFFMAN_DIST: {
                symbol = this.distTree.getSymbol(this.input);
                if (symbol < 0) 
                    return false;
                try {
                    this.repDist = Inflater.cPDIST[symbol];
                    this.neededBits = Inflater.cPDEXT[symbol];
                } catch (ex558) {
                    throw new Error("Illegal rep dist code");
                }
                mode0 = Inflater.dECODE_HUFFMAN_DISTBITS;
                break;
            }
            case Inflater.dECODE_HUFFMAN_DISTBITS: {
                if (this.neededBits > 0) {
                    this.mode = (mode0 = Inflater.dECODE_HUFFMAN_DISTBITS);
                    let i = this.input.peekBits(this.neededBits);
                    if (i < 0) 
                        return false;
                    this.input.dropBits(this.neededBits);
                    this.repDist += i;
                }
                this.outputWindow.repeat(this.repLength, this.repDist);
                free -= this.repLength;
                this.mode = (mode0 = Inflater.dECODE_HUFFMAN);
                break;
            }
            default: {
                throw new Error("Inflater unknown mode");
            }
            }
        }
        return true;
    }
    
    decodeChksumEx() {
        while (this.neededBits > 0) {
            let chkByte = this.input.peekBits(8);
            if (chkByte < 0) 
                return false;
            this.input.dropBits(8);
            this.readAdler = (this.readAdler << 8) | chkByte;
            this.neededBits -= 8;
        }
        if ((this.m_Adler.value) !== this.readAdler) 
            throw new Error(("Adler chksum doesn't match: " + (String(this.m_Adler.value)) + " vs. ") + (String(this.readAdler)));
        this.mode = Inflater.fINISHED;
        return false;
    }
    
    decode() {
        while (true) {
            switch (this.mode) { 
            case Inflater.dECODE_HEADER: {
                return this.decodeHeaderEx();
            }
            case Inflater.dECODE_DICT: {
                return this.decodeDictEx();
            }
            case Inflater.dECODE_CHKSUM: {
                return this.decodeChksumEx();
            }
            case Inflater.dECODE_BLOCKS: {
                if (this.isLastBlock) {
                    if (this.noHeader) {
                        this.mode = Inflater.fINISHED;
                        return false;
                    }
                    else {
                        this.input.skipToByteBoundary();
                        this.neededBits = 32;
                        this.mode = Inflater.dECODE_CHKSUM;
                        return true;
                    }
                }
                let type = this.input.peekBits(3);
                if (type < 0) 
                    return false;
                this.input.dropBits(3);
                if (((type & 1)) !== 0) 
                    this.isLastBlock = true;
                switch (type >> 1) { 
                case DeflaterConstants.STORED_BLOCK: {
                    this.input.skipToByteBoundary();
                    this.mode = Inflater.dECODE_STORED_LEN1;
                    break;
                }
                case DeflaterConstants.STATIC_TREES: {
                    this.litlenTree = InflaterHuffmanTree.DEF_LIT_LEN_TREE;
                    this.distTree = InflaterHuffmanTree.DEF_DIST_TREE;
                    this.mode = Inflater.dECODE_HUFFMAN;
                    break;
                }
                case DeflaterConstants.DYN_TREES: {
                    this.dynHeader = new InflaterDynHeader();
                    this.mode = Inflater.dECODE_DYN_HEADER;
                    break;
                }
                default: {
                    throw new Error("Unknown block type " + (String(type)));
                }
                }
                return true;
            }
            case Inflater.dECODE_STORED_LEN1: {{
                        if ((((this.uncomprLen = this.input.peekBits(16)))) < 0) 
                            return false;
                        this.input.dropBits(16);
                        this.mode = Inflater.dECODE_STORED_LEN2;
                    }
                break;
            }
            case Inflater.dECODE_STORED_LEN2: {{
                        let nlen = this.input.peekBits(16);
                        if (nlen < 0) 
                            return false;
                        this.input.dropBits(16);
                        if (nlen !== ((this.uncomprLen ^ 0xffff))) 
                            throw new Error("broken uncompressed block");
                        this.mode = Inflater.dECODE_STORED;
                    }
                break;
            }
            case Inflater.dECODE_STORED: {{
                        let more = this.outputWindow.copyStored(this.input, this.uncomprLen);
                        this.uncomprLen -= more;
                        if (this.uncomprLen === 0) {
                            this.mode = Inflater.dECODE_BLOCKS;
                            return true;
                        }
                        return !this.input.isNeedingInput;
                    }
            }
            case Inflater.dECODE_DYN_HEADER: {
                if (!this.dynHeader.decode(this.input)) 
                    return false;
                this.litlenTree = this.dynHeader.buildLitLenTree();
                this.distTree = this.dynHeader.buildDistTree();
                this.mode = Inflater.dECODE_HUFFMAN;
                break;
            }
            case Inflater.dECODE_HUFFMAN: 
            case Inflater.dECODE_HUFFMAN_LENBITS: 
            case Inflater.dECODE_HUFFMAN_DIST: 
            case Inflater.dECODE_HUFFMAN_DISTBITS: {
                return this.decodeHuffmanEx();
            }
            case Inflater.fINISHED: {
                return false;
            }
            default: {
                throw new Error("Inflater.Decode unknown mode");
            }
            }
        }
    }
    
    setDictionary(buffer) {
        this.setDictionaryEx(buffer, 0, buffer.length);
    }
    
    setDictionaryEx(buffer, index, count) {
        if (buffer === null) 
            throw new Error("buffer");
        if (index < 0) 
            throw new Error("index");
        if (count < 0) 
            throw new Error("count");
        if (!this.isNeedingDictionary) 
            throw new Error("Dictionary is not needed");
        this.m_Adler.updateByBufEx(buffer, index, count);
        if ((this.m_Adler.value) !== this.readAdler) 
            throw new Error("Wrong adler checksum");
        this.m_Adler.reset();
        this.outputWindow.copyDict(buffer, index, count);
        this.mode = Inflater.dECODE_BLOCKS;
    }
    
    setInput(buffer) {
        this.setInputEx(buffer, 0, buffer.length);
    }
    
    setInputEx(buffer, index, count) {
        this.input.setInput(buffer, index, count);
        this.m_TotalIn += count;
    }
    
    inflate(buffer) {
        if (buffer === null) 
            throw new Error("buffer");
        return this.inflateEx(buffer, 0, buffer.length);
    }
    
    inflateEx(buffer, offset, count) {
        if (buffer === null) 
            throw new Error("buffer");
        if (count < 0) 
            throw new Error("count", "count cannot be negative");
        if (offset < 0) 
            throw new Error("offset", "offset cannot be negative");
        if ((offset + count) > buffer.length) 
            throw new Error("count exceeds buffer bounds");
        if (count === 0) {
            if (!this.isFinished) 
                this.decode();
            return 0;
        }
        let bytesCopied = 0;
        do{
            if (this.mode !== Inflater.dECODE_CHKSUM) {
                let more = this.outputWindow.copyOutput(buffer, offset, count);
                if (more > 0) {
                    this.m_Adler.updateByBufEx(buffer, offset, more);
                    offset += more;
                    bytesCopied += more;
                    this.m_TotalOut += more;
                    count -= more;
                    if (count === 0) 
                        return bytesCopied;
                }
            }
        } while (this.decode() || (((this.outputWindow.getAvailable() > 0) && (this.mode !== Inflater.dECODE_CHKSUM)))); 
        return bytesCopied;
    }
    
    get isNeedingInput() {
        return this.input.isNeedingInput;
    }
    
    get isNeedingDictionary() {
        return this.mode === Inflater.dECODE_DICT && this.neededBits === 0;
    }
    
    get isFinished() {
        return this.mode === Inflater.fINISHED && this.outputWindow.getAvailable() === 0;
    }
    
    get adler() {
        return (this.isNeedingDictionary ? this.readAdler : this.m_Adler.value);
    }
    
    get totalOut() {
        return this.m_TotalOut;
    }
    
    get totalIn() {
        return this.m_TotalIn - this.remainingInput;
    }
    
    get remainingInput() {
        return this.input.availableBytes;
    }
    
    static static_constructor() {
        Inflater.cPLENS = new Int32Array([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]);
        Inflater.cPLEXT = new Int32Array([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]);
        Inflater.cPDIST = new Int32Array([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]);
        Inflater.cPDEXT = new Int32Array([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]);
        Inflater.dECODE_HEADER = 0;
        Inflater.dECODE_DICT = 1;
        Inflater.dECODE_BLOCKS = 2;
        Inflater.dECODE_STORED_LEN1 = 3;
        Inflater.dECODE_STORED_LEN2 = 4;
        Inflater.dECODE_STORED = 5;
        Inflater.dECODE_DYN_HEADER = 6;
        Inflater.dECODE_HUFFMAN = 7;
        Inflater.dECODE_HUFFMAN_LENBITS = 8;
        Inflater.dECODE_HUFFMAN_DIST = 9;
        Inflater.dECODE_HUFFMAN_DISTBITS = 10;
        Inflater.dECODE_CHKSUM = 11;
        Inflater.fINISHED = 12;
    }
}


Inflater.static_constructor();

module.exports = Inflater