"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "HeatshrinkDecoder", {
    enumerable: true,
    get: function() {
        return HeatshrinkDecoder;
    }
});
var HSState = /*#__PURE__*/ function(HSState) {
    HSState[HSState["TAG_BIT"] = 0] = "TAG_BIT";
    HSState[HSState["YIELD_LITERAL"] = 1] = "YIELD_LITERAL";
    HSState[HSState["BACKREF_INDEX_MSB"] = 2] = "BACKREF_INDEX_MSB";
    HSState[HSState["BACKREF_INDEX_LSB"] = 3] = "BACKREF_INDEX_LSB";
    HSState[HSState["BACKREF_COUNT_MSB"] = 4] = "BACKREF_COUNT_MSB";
    HSState[HSState["BACKREF_COUNT_LSB"] = 5] = "BACKREF_COUNT_LSB";
    HSState[HSState["YIELD_BACKREF"] = 6] = "YIELD_BACKREF";
    return HSState;
}(HSState || {});
class HeatshrinkDecoder {
    windowBits;
    lookaheadBits;
    windowSize;
    window;
    head = 0;
    state = 0;
    outputIndex = 0;
    outputCount = 0;
    inputBuffer;
    inputIndex = 0;
    bitAccumulator = 0;
    bitsAvailable = 0;
    constructor(windowBits, lookaheadBits){
        this.windowBits = windowBits;
        this.lookaheadBits = lookaheadBits;
        this.windowSize = 1 << windowBits;
        this.window = Buffer.alloc(this.windowSize);
        this.inputBuffer = Buffer.alloc(0);
    }
    decompress(input) {
        this.inputBuffer = input;
        this.inputIndex = 0;
        this.bitAccumulator = 0;
        this.bitsAvailable = 0;
        this.head = 0;
        this.state = 0;
        const output = [];
        while(this.inputIndex < this.inputBuffer.length || this.bitsAvailable > 0){
            const result = this.step();
            if (result === null) {
                break;
            }
            if (result >= 0) {
                output.push(result);
            }
        }
        return Buffer.from(output);
    }
    step() {
        switch(this.state){
            case 0:
                return this.handleTagBit();
            case 1:
                return this.handleYieldLiteral();
            case 2:
                return this.handleBackrefIndexMSB();
            case 3:
                return this.handleBackrefIndexLSB();
            case 4:
                return this.handleBackrefCountMSB();
            case 5:
                return this.handleBackrefCountLSB();
            case 6:
                return this.handleYieldBackref();
            default:
                return null;
        }
    }
    readBits(count) {
        while(this.bitsAvailable < count && this.inputIndex < this.inputBuffer.length){
            this.bitAccumulator = this.bitAccumulator << 8 | this.inputBuffer[this.inputIndex++];
            this.bitsAvailable += 8;
        }
        if (this.bitsAvailable < count) {
            return null;
        }
        this.bitsAvailable -= count;
        const result = this.bitAccumulator >> this.bitsAvailable & (1 << count) - 1;
        this.bitAccumulator &= (1 << this.bitsAvailable) - 1;
        return result;
    }
    handleTagBit() {
        const bit = this.readBits(1);
        if (bit === null) return null;
        if (bit === 1) {
            this.state = 1;
        } else {
            if (this.windowBits > 8) {
                this.state = 2;
            } else {
                this.outputIndex = 0;
                this.state = 3;
            }
        }
        return -1;
    }
    handleYieldLiteral() {
        const byte = this.readBits(8);
        if (byte === null) return null;
        this.pushByte(byte);
        this.state = 0;
        return byte;
    }
    handleBackrefIndexMSB() {
        const msb = this.readBits(this.windowBits - 8);
        if (msb === null) return null;
        this.outputIndex = msb << 8;
        this.state = 3;
        return -1;
    }
    handleBackrefIndexLSB() {
        const bits = this.windowBits > 8 ? 8 : this.windowBits;
        const lsb = this.readBits(bits);
        if (lsb === null) return null;
        this.outputIndex |= lsb;
        this.outputCount = 0;
        if (this.lookaheadBits > 8) {
            this.state = 4;
        } else {
            this.state = 5;
        }
        return -1;
    }
    handleBackrefCountMSB() {
        const msb = this.readBits(this.lookaheadBits - 8);
        if (msb === null) return null;
        this.outputCount = msb << 8;
        this.state = 5;
        return -1;
    }
    handleBackrefCountLSB() {
        const bits = this.lookaheadBits > 8 ? 8 : this.lookaheadBits;
        const lsb = this.readBits(bits);
        if (lsb === null) return null;
        this.outputCount |= lsb;
        this.outputCount += 1;
        this.outputIndex += 1;
        this.state = 6;
        return -1;
    }
    handleYieldBackref() {
        if (this.outputCount === 0) {
            this.state = 0;
            return -1;
        }
        const pos = (this.head - this.outputIndex + this.windowSize) % this.windowSize;
        const byte = this.window[pos];
        this.pushByte(byte);
        this.outputCount--;
        if (this.outputCount === 0) {
            this.state = 0;
        }
        return byte;
    }
    pushByte(byte) {
        this.window[this.head] = byte;
        this.head = (this.head + 1) % this.windowSize;
    }
}

//# sourceMappingURL=heatshrink-decoder.js.map