export class Runtime {
    private runtimeProcess?: Deno.ChildProcess;

    constructor(private port: number) {}

    async checkRuntimeRev() {
        const currentRev = new TextDecoder().decode(
            await new Deno.Command("git", { args: ["rev-parse", "HEAD"] })
                .output()
                .then((output) => output.stdout),
        ).trim();
        const runtimeRev = (await Deno.readTextFile("../runtime/build/rev"))
            .trim();
        if (runtimeRev !== currentRev) {
            console.error(
                "runtime out-of-date; run 'make' inside runtime/ folder",
            );
            Deno.exit(1);
        }
    }

    async start() {
        await this.checkRuntimeRev();
        this.runtimeProcess = new Deno.Command("../runtime/build/sliger", {
            args: [],
            stdout: "piped",
        }).spawn();
    }

    stop() {
        this.runtimeProcess?.kill();
        this.runtimeProcess = undefined;
    }

    async connect(): Promise<RuntimeConnection> {
        // return await new Promise((resolve) => {
        //     setTimeout(async () => {
        //         resolve(
        //             new RuntimeConnection(
        //                 await Deno.connect({
        //                     port: this.port,
        //                 }),
        //             ),
        //         );
        //     }, 100);
        // });
        return new RuntimeConnection(
            await Deno.connect({
                port: this.port,
            }),
        );
    }
}

export class RuntimeConnection {
    constructor(private connection: Deno.Conn) {}

    async write(text: string): Promise<void> {
        const req = new TextEncoder().encode(text);
        await this.connection.write(req);
    }

    async send<T>(value: T): Promise<void> {
        await this.write(JSON.stringify(value));
    }

    async read(): Promise<string> {
        let result = "";
        while (true) {
            const buf = new Uint8Array(256);
            const readRes = await this.connection.read(buf);
            if (readRes != null) {
                result += new TextDecoder().decode(buf.slice(0, readRes));
            } else {
                break;
            }
        }
        return result;
    }

    async receive<T>(): Promise<T> {
        return JSON.parse(await this.read()) as T;
    }

    close() {
        this.connection.close();
    }
}