// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
import { assert } from "../_util/asserts.ts";
import { copy } from "../bytes/mod.ts";
const MAX_SIZE = 2 ** 32 - 2;
const DEFAULT_CHUNK_SIZE = 16_640;
/** A variable-sized buffer of bytes with `read()` and `write()` methods.
 *
 * Buffer is almost always used with some I/O like files and sockets. It allows
 * one to buffer up a download from a socket. Buffer grows and shrinks as
 * necessary.
 *
 * Buffer is NOT the same thing as Node's Buffer. Node's Buffer was created in
 * 2009 before JavaScript had the concept of ArrayBuffers. It's simply a
 * non-standard ArrayBuffer.
 *
 * ArrayBuffer is a fixed memory allocation. Buffer is implemented on top of
 * ArrayBuffer.
 *
 * Based on [Go Buffer](https://golang.org/pkg/bytes/#Buffer). */ export class Buffer {
    #buf;
    #off = 0;
    #readable = new ReadableStream({
        type: "bytes",
        pull: (controller)=>{
            const view = new Uint8Array(controller.byobRequest.view.buffer);
            if (this.empty()) {
                // Buffer is empty, reset to recover space.
                this.reset();
                controller.close();
                controller.byobRequest.respond(0);
                return;
            }
            const nread = copy(this.#buf.subarray(this.#off), view);
            this.#off += nread;
            controller.byobRequest.respond(nread);
        },
        autoAllocateChunkSize: DEFAULT_CHUNK_SIZE
    });
    get readable() {
        return this.#readable;
    }
    #writable = new WritableStream({
        write: (chunk)=>{
            const m = this.#grow(chunk.byteLength);
            copy(chunk, this.#buf, m);
        }
    });
    get writable() {
        return this.#writable;
    }
    constructor(ab){
        this.#buf = ab === undefined ? new Uint8Array(0) : new Uint8Array(ab);
    }
    /** Returns a slice holding the unread portion of the buffer.
   *
   * The slice is valid for use only until the next buffer modification (that
   * is, only until the next call to a method like `read()`, `write()`,
   * `reset()`, or `truncate()`). If `options.copy` is false the slice aliases the buffer content at
   * least until the next buffer modification, so immediate changes to the
   * slice will affect the result of future reads.
   * @param options Defaults to `{ copy: true }`
   */ bytes(options = {
        copy: true
    }) {
        if (options.copy === false) return this.#buf.subarray(this.#off);
        return this.#buf.slice(this.#off);
    }
    /** Returns whether the unread portion of the buffer is empty. */ empty() {
        return this.#buf.byteLength <= this.#off;
    }
    /** A read only number of bytes of the unread portion of the buffer. */ get length() {
        return this.#buf.byteLength - this.#off;
    }
    /** The read only capacity of the buffer's underlying byte slice, that is,
   * the total space allocated for the buffer's data. */ get capacity() {
        return this.#buf.buffer.byteLength;
    }
    /** Discards all but the first `n` unread bytes from the buffer but
   * continues to use the same allocated storage. It throws if `n` is
   * negative or greater than the length of the buffer. */ truncate(n) {
        if (n === 0) {
            this.reset();
            return;
        }
        if (n < 0 || n > this.length) {
            throw Error("bytes.Buffer: truncation out of range");
        }
        this.#reslice(this.#off + n);
    }
    reset() {
        this.#reslice(0);
        this.#off = 0;
    }
    #tryGrowByReslice(n) {
        const l = this.#buf.byteLength;
        if (n <= this.capacity - l) {
            this.#reslice(l + n);
            return l;
        }
        return -1;
    }
    #reslice(len) {
        assert(len <= this.#buf.buffer.byteLength);
        this.#buf = new Uint8Array(this.#buf.buffer, 0, len);
    }
    #grow(n1) {
        const m = this.length;
        // If buffer is empty, reset to recover space.
        if (m === 0 && this.#off !== 0) {
            this.reset();
        }
        // Fast: Try to grow by means of a reslice.
        const i = this.#tryGrowByReslice(n1);
        if (i >= 0) {
            return i;
        }
        const c = this.capacity;
        if (n1 <= Math.floor(c / 2) - m) {
            // We can slide things down instead of allocating a new
            // ArrayBuffer. We only need m+n <= c to slide, but
            // we instead let capacity get twice as large so we
            // don't spend all our time copying.
            copy(this.#buf.subarray(this.#off), this.#buf);
        } else if (c + n1 > MAX_SIZE) {
            throw new Error("The buffer cannot be grown beyond the maximum size.");
        } else {
            // Not enough space anywhere, we need to allocate.
            const buf = new Uint8Array(Math.min(2 * c + n1, MAX_SIZE));
            copy(this.#buf.subarray(this.#off), buf);
            this.#buf = buf;
        }
        // Restore this.#off and len(this.#buf).
        this.#off = 0;
        this.#reslice(Math.min(m + n1, MAX_SIZE));
        return m;
    }
    /** Grows the buffer's capacity, if necessary, to guarantee space for
   * another `n` bytes. After `.grow(n)`, at least `n` bytes can be written to
   * the buffer without another allocation. If `n` is negative, `.grow()` will
   * throw. If the buffer can't grow it will throw an error.
   *
   * Based on Go Lang's
   * [Buffer.Grow](https://golang.org/pkg/bytes/#Buffer.Grow). */ grow(n) {
        if (n < 0) {
            throw Error("Buffer.grow: negative count");
        }
        const m = this.#grow(n);
        this.#reslice(m);
    }
}
/** A TransformStream that will only read & enqueue `size` amount of bytes.
 * This operation is chunk based and not BYOB based,
 * and as such will read more than needed.
 *
 * if options.error is set, then instead of terminating the stream,
 * an error will be thrown.
 *
 * ```ts
 * import { LimitedBytesTransformStream } from "https://deno.land/std@$STD_VERSION/streams/buffer.ts";
 * const res = await fetch("https://example.com");
 * const parts = res.body!
 *   .pipeThrough(new LimitedBytesTransformStream(512 * 1024));
 * ```
 */ export class LimitedBytesTransformStream extends TransformStream {
    #read = 0;
    constructor(size, options = {}){
        super({
            transform: (chunk, controller)=>{
                if (this.#read + chunk.byteLength > size) {
                    if (options.error) {
                        throw new RangeError(`Exceeded byte size limit of '${size}'`);
                    } else {
                        controller.terminate();
                    }
                } else {
                    this.#read += chunk.byteLength;
                    controller.enqueue(chunk);
                }
            }
        });
    }
}
/** A TransformStream that will only read & enqueue `size` amount of chunks.
 *
 * if options.error is set, then instead of terminating the stream,
 * an error will be thrown.
 *
 * ```ts
 * import { LimitedTransformStream } from "https://deno.land/std@$STD_VERSION/streams/buffer.ts";
 * const res = await fetch("https://example.com");
 * const parts = res.body!.pipeThrough(new LimitedTransformStream(50));
 * ```
 */ export class LimitedTransformStream extends TransformStream {
    #read = 0;
    constructor(size, options = {}){
        super({
            transform: (chunk, controller)=>{
                if (this.#read + 1 > size) {
                    if (options.error) {
                        throw new RangeError(`Exceeded chunk limit of '${size}'`);
                    } else {
                        controller.terminate();
                    }
                } else {
                    this.#read++;
                    controller.enqueue(chunk);
                }
            }
        });
    }
}
/**
 * A transform stream that only transforms from the zero-indexed `start` and `end` bytes (both inclusive).
 *
 * @example
 * ```ts
 * import { ByteSliceStream } from "https://deno.land/std@$STD_VERSION/streams/buffer.ts";
 * const response = await fetch("https://example.com");
 * const rangedStream = response.body!
 *   .pipeThrough(new ByteSliceStream(3, 8));
 * ```
 */ export class ByteSliceStream extends TransformStream {
    #offsetStart = 0;
    #offsetEnd = 0;
    constructor(start = 0, end = Infinity){
        super({
            start: ()=>{
                assert(start >= 0, "`start` must be greater than 0");
                end += 1;
            },
            transform: (chunk, controller)=>{
                this.#offsetStart = this.#offsetEnd;
                this.#offsetEnd += chunk.byteLength;
                if (this.#offsetEnd > start) {
                    if (this.#offsetStart < start) {
                        chunk = chunk.slice(start - this.#offsetStart);
                    }
                    if (this.#offsetEnd >= end) {
                        chunk = chunk.slice(0, chunk.byteLength - this.#offsetEnd + end);
                        controller.enqueue(chunk);
                        controller.terminate();
                    } else {
                        controller.enqueue(chunk);
                    }
                }
            }
        });
    }
}
//# sourceMappingURL=data:application/json;base64,[encoded data, 18668 characters]