// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { BytesList } from "../bytes/bytes_list.ts";
const CR = "\r".charCodeAt(0);
const LF = "\n".charCodeAt(0);
/** @deprecated Use TextLineStream instead, as it can handle empty lines.
 *
 * Transform a stream into a stream where each chunk is divided by a newline,
 * be it `\n` or `\r\n`.
 *
 * ```ts
 * import { LineStream } from "./delimiter.ts";
 * const res = await fetch("https://example.com");
 * const lines = res.body!.pipeThrough(new LineStream());
 * ```
 */ export class LineStream extends TransformStream {
    #bufs = new BytesList();
    #prevHadCR = false;
    constructor(){
        super({
            transform: (chunk, controller)=>{
                this.#handle(chunk, controller);
            },
            flush: (controller)=>{
                controller.enqueue(this.#mergeBufs(false));
            }
        });
    }
    #handle(chunk, controller) {
        const lfIndex = chunk.indexOf(LF);
        if (this.#prevHadCR) {
            this.#prevHadCR = false;
            if (lfIndex === 0) {
                controller.enqueue(this.#mergeBufs(true));
                this.#handle(chunk.subarray(1), controller);
                return;
            }
        }
        if (lfIndex === -1) {
            if (chunk.at(-1) === CR) {
                this.#prevHadCR = true;
            }
            this.#bufs.add(chunk);
        } else {
            let crOrLfIndex = lfIndex;
            if (chunk[lfIndex - 1] === CR) {
                crOrLfIndex--;
            }
            this.#bufs.add(chunk.subarray(0, crOrLfIndex));
            controller.enqueue(this.#mergeBufs(false));
            this.#handle(chunk.subarray(lfIndex + 1), controller);
        }
    }
    #mergeBufs(prevHadCR) {
        const mergeBuf = this.#bufs.concat();
        this.#bufs = new BytesList();
        if (prevHadCR) {
            return mergeBuf.subarray(0, -1);
        } else {
            return mergeBuf;
        }
    }
}
/** Transform a stream into a stream where each chunk is divided by a newline,
 * be it `\n` or `\r\n`. `\r` can be enabled via the `allowCR` option.
 *
 * ```ts
 * import { TextLineStream } from "./delimiter.ts";
 * const res = await fetch("https://example.com");
 * const lines = res.body!
 *   .pipeThrough(new TextDecoderStream())
 *   .pipeThrough(new TextLineStream());
 * ```
 */ export class TextLineStream extends TransformStream {
    #allowCR;
    #buf = "";
    constructor(options){
        super({
            transform: (chunk, controller)=>this.#handle(chunk, controller),
            flush: (controller)=>this.#handle("\r\n", controller)
        });
        this.#allowCR = options?.allowCR ?? false;
    }
    #handle(chunk1, controller1) {
        chunk1 = this.#buf + chunk1;
        for(;;){
            const lfIndex1 = chunk1.indexOf("\n");
            if (this.#allowCR) {
                const crIndex = chunk1.indexOf("\r");
                if (crIndex !== -1 && crIndex !== chunk1.length - 1 && (lfIndex1 === -1 || lfIndex1 - 1 > crIndex)) {
                    controller1.enqueue(chunk1.slice(0, crIndex));
                    chunk1 = chunk1.slice(crIndex + 1);
                    continue;
                }
            }
            if (lfIndex1 !== -1) {
                let crOrLfIndex1 = lfIndex1;
                if (chunk1[lfIndex1 - 1] === "\r") {
                    crOrLfIndex1--;
                }
                controller1.enqueue(chunk1.slice(0, crOrLfIndex1));
                chunk1 = chunk1.slice(lfIndex1 + 1);
                continue;
            }
            break;
        }
        this.#buf = chunk1;
    }
}
/** Transform a stream into a stream where each chunk is divided by a given delimiter.
 *
 * ```ts
 * import { DelimiterStream } from "./delimiter.ts";
 * const res = await fetch("https://example.com");
 * const parts = res.body!
 *   .pipeThrough(new DelimiterStream(new TextEncoder().encode("foo")))
 *   .pipeThrough(new TextDecoderStream());
 * ```
 */ export class DelimiterStream extends TransformStream {
    #bufs = new BytesList();
    #delimiter;
    #inspectIndex = 0;
    #matchIndex = 0;
    #delimLen;
    #delimLPS;
    constructor(delimiter){
        super({
            transform: (chunk, controller)=>{
                this.#handle(chunk, controller);
            },
            flush: (controller)=>{
                controller.enqueue(this.#bufs.concat());
            }
        });
        this.#delimiter = delimiter;
        this.#delimLen = delimiter.length;
        this.#delimLPS = createLPS(delimiter);
    }
    #handle(chunk2, controller2) {
        this.#bufs.add(chunk2);
        let localIndex = 0;
        while(this.#inspectIndex < this.#bufs.size()){
            if (chunk2[localIndex] === this.#delimiter[this.#matchIndex]) {
                this.#inspectIndex++;
                localIndex++;
                this.#matchIndex++;
                if (this.#matchIndex === this.#delimLen) {
                    // Full match
                    const matchEnd = this.#inspectIndex - this.#delimLen;
                    const readyBytes = this.#bufs.slice(0, matchEnd);
                    controller2.enqueue(readyBytes);
                    // Reset match, different from KMP.
                    this.#bufs.shift(this.#inspectIndex);
                    this.#inspectIndex = 0;
                    this.#matchIndex = 0;
                }
            } else {
                if (this.#matchIndex === 0) {
                    this.#inspectIndex++;
                    localIndex++;
                } else {
                    this.#matchIndex = this.#delimLPS[this.#matchIndex - 1];
                }
            }
        }
    }
}
/** Transform a stream into a stream where each chunk is divided by a given delimiter.
 *
 * ```ts
 * import { TextDelimiterStream } from "./delimiter.ts";
 * const res = await fetch("https://example.com");
 * const parts = res.body!
 *   .pipeThrough(new TextDecoderStream())
 *   .pipeThrough(new TextDelimiterStream("foo"));
 * ```
 */ export class TextDelimiterStream extends TransformStream {
    #buf = "";
    #delimiter;
    #inspectIndex = 0;
    #matchIndex = 0;
    #delimLPS;
    constructor(delimiter){
        super({
            transform: (chunk, controller)=>{
                this.#handle(chunk, controller);
            },
            flush: (controller)=>{
                controller.enqueue(this.#buf);
            }
        });
        this.#delimiter = delimiter;
        this.#delimLPS = createLPS(new TextEncoder().encode(delimiter));
    }
    #handle(chunk3, controller3) {
        this.#buf += chunk3;
        let localIndex1 = 0;
        while(this.#inspectIndex < this.#buf.length){
            if (chunk3[localIndex1] === this.#delimiter[this.#matchIndex]) {
                this.#inspectIndex++;
                localIndex1++;
                this.#matchIndex++;
                if (this.#matchIndex === this.#delimiter.length) {
                    // Full match
                    const matchEnd1 = this.#inspectIndex - this.#delimiter.length;
                    const readyString = this.#buf.slice(0, matchEnd1);
                    controller3.enqueue(readyString);
                    // Reset match, different from KMP.
                    this.#buf = this.#buf.slice(this.#inspectIndex);
                    this.#inspectIndex = 0;
                    this.#matchIndex = 0;
                }
            } else {
                if (this.#matchIndex === 0) {
                    this.#inspectIndex++;
                    localIndex1++;
                } else {
                    this.#matchIndex = this.#delimLPS[this.#matchIndex - 1];
                }
            }
        }
    }
}
/** Generate longest proper prefix which is also suffix array. */ function createLPS(pat) {
    const lps = new Uint8Array(pat.length);
    lps[0] = 0;
    let prefixEnd = 0;
    let i = 1;
    while(i < lps.length){
        if (pat[i] == pat[prefixEnd]) {
            prefixEnd++;
            lps[i] = prefixEnd;
            i++;
        } else if (prefixEnd === 0) {
            lps[i] = 0;
            i++;
        } else {
            prefixEnd = lps[prefixEnd - 1];
        }
    }
    return lps;
}
//# sourceMappingURL=data:application/json;base64,[encoded data, 20824 characters]