// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license.
import { unitTest, assert, assertEquals } from "./test_util.ts";
import { assertThrowsAsync } from "../../../std/testing/asserts.ts";

unitTest(function streamPipeLocks() {
    const rs = new ReadableStream();
    const ws = new WritableStream();

    assertEquals(rs.locked, false);
    assertEquals(ws.locked, false);

    rs.pipeTo(ws);

    assert(rs.locked);
    assert(ws.locked);
});

unitTest(async function streamPipeFinishUnlocks() {
    const rs = new ReadableStream({
        start(controller: ReadableStreamDefaultController): void {
            controller.close();
        },
    });
    const ws = new WritableStream();

    await rs.pipeTo(ws);
    assertEquals(rs.locked, false);
    assertEquals(ws.locked, false);
});

unitTest(async function streamPipeReadableStreamLocked() {
    const rs = new ReadableStream();
    const ws = new WritableStream();

    rs.getReader();

    await assertThrowsAsync(async () => {
        await rs.pipeTo(ws);
    }, TypeError);
});

unitTest(async function streamPipeReadableStreamLocked() {
    const rs = new ReadableStream();
    const ws = new WritableStream();

    ws.getWriter();

    await assertThrowsAsync(async () => {
        await rs.pipeTo(ws);
    }, TypeError);
});

unitTest(async function streamPipeLotsOfChunks() {
    const CHUNKS = 10;

    const rs = new ReadableStream<number>({
        start(c: ReadableStreamDefaultController): void {
            for (let i = 0; i < CHUNKS; ++i) {
                c.enqueue(i);
            }
            c.close();
        },
    });

    const written: Array<string | number> = [];
    const ws = new WritableStream(
        {
            write(chunk: number): void {
                written.push(chunk);
            },
            close(): void {
                written.push("closed");
            },
        },
        new CountQueuingStrategy({ highWaterMark: CHUNKS })
    );

    await rs.pipeTo(ws);
    const targetValues = [];
    for (let i = 0; i < CHUNKS; ++i) {
        targetValues.push(i);
    }
    targetValues.push("closed");

    assertEquals(written, targetValues, "the correct values must be written");

    // Ensure both readable and writable are closed by the time the pipe finishes.
    await Promise.all([rs.getReader().closed, ws.getWriter().closed]);
});

for (const preventAbort of [true, false]) {
    unitTest(function undefinedRejectionFromPull() {
        const rs = new ReadableStream({
            pull(): Promise<void> {
                return Promise.reject(undefined);
            },
        });

        return rs.pipeTo(new WritableStream(), { preventAbort }).then(
            () => {
                throw new Error("pipeTo promise should be rejected");
            },
            (value) =>
                assertEquals(
                    value,
                    undefined,
                    "rejection value should be undefined"
                )
        );
    });
}

for (const preventCancel of [true, false]) {
    unitTest(function undefinedRejectionWithPreventCancel() {
        const rs = new ReadableStream({
            pull(controller: ReadableStreamDefaultController<number>): void {
                controller.enqueue(0);
            },
        });

        const ws = new WritableStream({
            write(): Promise<void> {
                return Promise.reject(undefined);
            },
        });

        return rs.pipeTo(ws, { preventCancel }).then(
            () => {
                throw new Error("pipeTo promise should be rejected");
            },
            (value) =>
                assertEquals(
                    value,
                    undefined,
                    "rejection value should be undefined"
                )
        );
    });
}
