// sizeof(T).
const SIZE_INT32 = 4;
const SIZE_UINT32 = 4;
const SIZE_INT64 = 8;
const SIZE_UINT64 = 8;
const SIZE_FLOAT = 4;
const SIZE_DOUBLE = 8;
// The allocation granularity of the payload.
const PAYLOAD_UNIT = 64;
// Largest JS number.
const CAPACITY_READ_ONLY = 9007199254740992;
// Aligns 'i' by rounding it up to the next multiple of 'alignment'.
const alignInt = function (i, alignment) {
    return i + ((alignment - (i % alignment)) % alignment);
};
// PickleIterator reads data from a Pickle. The Pickle object must remain valid
// while the PickleIterator object is in use.
class PickleIterator {
    payload;
    payloadOffset;
    readIndex;
    endIndex;
    constructor(pickle) {
        this.payload = pickle.getHeader();
        this.payloadOffset = pickle.getHeaderSize();
        this.readIndex = 0;
        this.endIndex = pickle.getPayloadSize();
    }
    readBool() {
        return this.readInt() !== 0;
    }
    readInt() {
        return this.readBytes(SIZE_INT32, Buffer.prototype.readInt32LE);
    }
    readUInt32() {
        return this.readBytes(SIZE_UINT32, Buffer.prototype.readUInt32LE);
    }
    readInt64() {
        return this.readBytes(SIZE_INT64, Buffer.prototype.readBigInt64LE);
    }
    readUInt64() {
        return this.readBytes(SIZE_UINT64, Buffer.prototype.readBigUInt64LE);
    }
    readFloat() {
        return this.readBytes(SIZE_FLOAT, Buffer.prototype.readFloatLE);
    }
    readDouble() {
        return this.readBytes(SIZE_DOUBLE, Buffer.prototype.readDoubleLE);
    }
    readString() {
        return this.readBytes(this.readInt()).toString();
    }
    readBytes(length, method) {
        const readPayloadOffset = this.getReadPayloadOffsetAndAdvance(length);
        if (method != null) {
            return method.call(this.payload, readPayloadOffset, length);
        }
        else {
            return this.payload.slice(readPayloadOffset, readPayloadOffset + length);
        }
    }
    getReadPayloadOffsetAndAdvance(length) {
        if (length > this.endIndex - this.readIndex) {
            this.readIndex = this.endIndex;
            throw new Error('Failed to read data with length of ' + length);
        }
        const readPayloadOffset = this.payloadOffset + this.readIndex;
        this.advance(length);
        return readPayloadOffset;
    }
    advance(size) {
        const alignedSize = alignInt(size, SIZE_UINT32);
        if (this.endIndex - this.readIndex < alignedSize) {
            this.readIndex = this.endIndex;
        }
        else {
            this.readIndex += alignedSize;
        }
    }
}
// This class provides facilities for basic binary value packing and unpacking.
//
// The Pickle class supports appending primitive values (ints, strings, etc.)
// to a pickle instance.  The Pickle instance grows its internal memory buffer
// dynamically to hold the sequence of primitive values.   The internal memory
// buffer is exposed as the "data" of the Pickle.  This "data" can be passed
// to a Pickle object to initialize it for reading.
//
// When reading from a Pickle object, it is important for the consumer to know
// what value types to read and in what order to read them as the Pickle does
// not keep track of the type of data written to it.
//
// The Pickle's data has a header which contains the size of the Pickle's
// payload.  It can optionally support additional space in the header.  That
// space is controlled by the header_size parameter passed to the Pickle
// constructor.
export class Pickle {
    header;
    headerSize;
    capacityAfterHeader;
    writeOffset;
    constructor(buffer) {
        if (buffer) {
            this.header = buffer;
            this.headerSize = buffer.length - this.getPayloadSize();
            this.capacityAfterHeader = CAPACITY_READ_ONLY;
            this.writeOffset = 0;
            if (this.headerSize > buffer.length) {
                this.headerSize = 0;
            }
            if (this.headerSize !== alignInt(this.headerSize, SIZE_UINT32)) {
                this.headerSize = 0;
            }
            if (this.headerSize === 0) {
                this.header = Buffer.alloc(0);
            }
        }
        else {
            this.header = Buffer.alloc(0);
            this.headerSize = SIZE_UINT32;
            this.capacityAfterHeader = 0;
            this.writeOffset = 0;
            this.resize(PAYLOAD_UNIT);
            this.setPayloadSize(0);
        }
    }
    static createEmpty() {
        return new Pickle();
    }
    static createFromBuffer(buffer) {
        return new Pickle(buffer);
    }
    getHeader() {
        return this.header;
    }
    getHeaderSize() {
        return this.headerSize;
    }
    createIterator() {
        return new PickleIterator(this);
    }
    toBuffer() {
        return this.header.slice(0, this.headerSize + this.getPayloadSize());
    }
    writeBool(value) {
        return this.writeInt(value ? 1 : 0);
    }
    writeInt(value) {
        return this.writeBytes(value, SIZE_INT32, Buffer.prototype.writeInt32LE);
    }
    writeUInt32(value) {
        return this.writeBytes(value, SIZE_UINT32, Buffer.prototype.writeUInt32LE);
    }
    writeInt64(value) {
        return this.writeBytes(BigInt(value), SIZE_INT64, Buffer.prototype.writeBigInt64LE);
    }
    writeUInt64(value) {
        return this.writeBytes(BigInt(value), SIZE_UINT64, Buffer.prototype.writeBigUInt64LE);
    }
    writeFloat(value) {
        return this.writeBytes(value, SIZE_FLOAT, Buffer.prototype.writeFloatLE);
    }
    writeDouble(value) {
        return this.writeBytes(value, SIZE_DOUBLE, Buffer.prototype.writeDoubleLE);
    }
    writeString(value) {
        const length = Buffer.byteLength(value, 'utf8');
        if (!this.writeInt(length)) {
            return false;
        }
        return this.writeBytes(value, length);
    }
    setPayloadSize(payloadSize) {
        return this.header.writeUInt32LE(payloadSize, 0);
    }
    getPayloadSize() {
        return this.header.readUInt32LE(0);
    }
    writeBytes(data, length, method) {
        const dataLength = alignInt(length, SIZE_UINT32);
        const newSize = this.writeOffset + dataLength;
        if (newSize > this.capacityAfterHeader) {
            this.resize(Math.max(this.capacityAfterHeader * 2, newSize));
        }
        if (method) {
            method.call(this.header, data, this.headerSize + this.writeOffset);
        }
        else {
            this.header.write(data, this.headerSize + this.writeOffset, length);
        }
        const endOffset = this.headerSize + this.writeOffset + length;
        this.header.fill(0, endOffset, endOffset + dataLength - length);
        this.setPayloadSize(newSize);
        this.writeOffset = newSize;
        return true;
    }
    resize(newCapacity) {
        newCapacity = alignInt(newCapacity, PAYLOAD_UNIT);
        const newHeader = Buffer.alloc(this.headerSize + newCapacity);
        this.header.copy(newHeader, 0, 0, this.headerSize + this.writeOffset);
        this.header = newHeader;
        this.capacityAfterHeader = newCapacity;
    }
}
//# sourceMappingURL=pickle.js.map