import { MethodNotImplementedError, DecompressionBombError } from '../errors';
import Stream from './Stream';
/*
 * Copyright 2012 Mozilla Foundation
 *
 * The DecodeStream class contained in this file is a TypeScript port of the
 * JavaScript DecodeStream class in Mozilla's pdf.js project, made available
 * under the Apache 2.0 open source license.
 */
// Lots of DecodeStreams are created whose buffers are never used.  For these
// we share a single empty buffer. This is (a) space-efficient and (b) avoids
// having special cases that would be required if we used |null| for an empty
// buffer.
const emptyBuffer = new Uint8Array(0);
/**
 * Maximum allowed decoded buffer size (100 MB).
 * Prevents decompression bomb attacks from consuming unbounded memory.
 */
const MAX_DECODED_SIZE = 100 * 1024 * 1024;
/**
 * Super class for the decoding streams
 */
class DecodeStream {
    constructor(maybeMinBufferLength) {
        Object.defineProperty(this, "bufferLength", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "buffer", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "eof", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "pos", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        Object.defineProperty(this, "minBufferLength", {
            enumerable: true,
            configurable: true,
            writable: true,
            value: void 0
        });
        this.pos = 0;
        this.bufferLength = 0;
        this.eof = false;
        this.buffer = emptyBuffer;
        this.minBufferLength = 512;
        if (maybeMinBufferLength) {
            // Compute the first power of two that is as big as maybeMinBufferLength.
            while (this.minBufferLength < maybeMinBufferLength) {
                this.minBufferLength *= 2;
            }
            if (this.minBufferLength > MAX_DECODED_SIZE) {
                this.minBufferLength = MAX_DECODED_SIZE;
            }
        }
    }
    get isEmpty() {
        while (!this.eof && this.bufferLength === 0) {
            this.readBlock();
        }
        return this.bufferLength === 0;
    }
    getByte() {
        const pos = this.pos;
        while (this.bufferLength <= pos) {
            if (this.eof) {
                return -1;
            }
            this.readBlock();
        }
        return this.buffer[this.pos++];
    }
    getUint16() {
        const b0 = this.getByte();
        const b1 = this.getByte();
        if (b0 === -1 || b1 === -1) {
            return -1;
        }
        return (b0 << 8) + b1;
    }
    getInt32() {
        const b0 = this.getByte();
        const b1 = this.getByte();
        const b2 = this.getByte();
        const b3 = this.getByte();
        return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
    }
    getBytes(length, forceClamped = false) {
        let end;
        const pos = this.pos;
        if (length) {
            this.ensureBuffer(pos + length);
            end = pos + length;
            while (!this.eof && this.bufferLength < end) {
                this.readBlock();
            }
            const bufEnd = this.bufferLength;
            if (end > bufEnd) {
                end = bufEnd;
            }
        }
        else {
            while (!this.eof) {
                this.readBlock();
            }
            end = this.bufferLength;
        }
        this.pos = end;
        const subarray = this.buffer.subarray(pos, end);
        // `this.buffer` is either a `Uint8Array` or `Uint8ClampedArray` here.
        return forceClamped && !(subarray instanceof Uint8ClampedArray)
            ? new Uint8ClampedArray(subarray)
            : subarray;
    }
    peekByte() {
        const peekedByte = this.getByte();
        this.pos--;
        return peekedByte;
    }
    peekBytes(length, forceClamped = false) {
        const bytes = this.getBytes(length, forceClamped);
        this.pos -= bytes.length;
        return bytes;
    }
    skip(n) {
        if (!n) {
            n = 1;
        }
        this.pos += n;
    }
    reset() {
        this.pos = 0;
    }
    makeSubStream(start, length /* dict */) {
        const end = start + length;
        while (this.bufferLength <= end && !this.eof) {
            this.readBlock();
        }
        return new Stream(this.buffer, start, length /* dict */);
    }
    decode() {
        while (!this.eof)
            this.readBlock();
        return this.buffer.subarray(0, this.bufferLength);
    }
    readBlock() {
        throw new MethodNotImplementedError(this.constructor.name, 'readBlock');
    }
    ensureBuffer(requested) {
        const buffer = this.buffer;
        if (requested <= buffer.byteLength) {
            return buffer;
        }
        if (requested > MAX_DECODED_SIZE) {
            throw new DecompressionBombError(requested, MAX_DECODED_SIZE);
        }
        let size = this.minBufferLength;
        while (size < requested) {
            size *= 2;
        }
        if (size > MAX_DECODED_SIZE) {
            size = MAX_DECODED_SIZE;
            if (size < requested) {
                throw new DecompressionBombError(requested, MAX_DECODED_SIZE);
            }
        }
        const buffer2 = new Uint8Array(size);
        buffer2.set(buffer);
        return (this.buffer = buffer2);
    }
}
export default DecodeStream;
//# sourceMappingURL=DecodeStream.js.map