<script lang="ts">
    import { onMount } from "svelte";
    import { dmxData } from "../dmx/store";

    export let port: SerialPort;
    export let fps: number = 50;

    const FRAME_TIME = 1000 / fps; // milliseconds

    let lines = [];
    let linesResolve: (value: string | PromiseLike<string>) => void = null;

    function readLine(): Promise<string> {
        if (lines.length > 0) {
            return Promise.resolve(lines.shift());
        } else {
            // lines is empty, register callbacks
            return new Promise((resolve) => {
                linesResolve = resolve;
            });
        }
    }

    const handleLine = (line: string) => {
        if (linesResolve !== null) {
            linesResolve(line);
            linesResolve = null;
        } else {
            lines.push(line);
        }
    };

    let running = true;
    async function readLoop() {
        console.log("runner started");
        await port.open({ baudRate: 500_000 });
        let reader = port.readable.getReader();
        let data = "";
        try {
            while (running) {
                const { value, done } = await reader.read();
                if (done) break;

                const chunk = new TextDecoder().decode(value);
                data += chunk;

                const lines = data.split("\r\n");
                data = lines.pop();
                for (let line of lines) {
                    handleLine(line);
                }
            }
        } finally {
            reader.releaseLock();
        }
        await port.close();
        console.log("runner stopped");
    }
    let readLoopHandle: Promise<void> = null;

    async function sync() {
        while (running) {
            const line = await readLine();
            if (line === "Sync.") {
                return;
            }
        }
    }

    const sleep = (ms: number) =>
        new Promise((resolve) => setTimeout(resolve, ms));

    let lastLoopTime: number = null;

    async function writeLoop() {
        await sync();
        while (running) {
            const loopStart = performance.now();

            // write out payload
            let writer = port.writable.getWriter();
            try {
                await writer.write($dmxData);
            } finally {
                writer.releaseLock();
            }

            // read response
            const response = await readLine();
            if (response !== "Ack.") {
                console.log(`received bad response: "${response}"`);
                lastLoopTime = null;
                await sync();
                continue;
            }

            const loopTime = performance.now() - loopStart;
            if (loopTime < FRAME_TIME) {
                await sleep(FRAME_TIME - loopTime);
            } else {
                console.warn(`loop took too long (+${(loopTime - FRAME_TIME).toFixed(2)} ms)`);
            }
            lastLoopTime = loopTime;
        }
    }
    let writeLoopHandle: Promise<void> = null;

    onMount(() => {
        console.log("mount");
        readLoopHandle = readLoop();
        writeLoopHandle = writeLoop();

        return () => {
            console.log("unmount");
            running = false;
            if (linesResolve !== null) {
                linesResolve("");
            }
        };
    });
</script>

<p>
    {#if lastLoopTime !== null}
        {lastLoopTime.toFixed(2).padStart(5)}
    {:else}
        syncing
    {/if}
</p>
