import { deferedSpawn } from "async-spawner";
import Emittery from "emittery";
import { promisify } from "util";

//needed to pass information on wether or not the promise has been aborted witout 'this'
class AbortableEmittery extends Emittery {
	public aborted = false;

	abort() {
		this.aborted = true;
		this.emit("abort");
	}
}

class AbortablePromise<T> extends Promise<T | null> {
	private emitter: AbortableEmittery;

	constructor(
		make_generator: (
			await_or_abort: (
				promise: Promise<any>,
				on_abort: () => void
			) => Promise<void>
		) => AsyncGenerator
	) {
		const emitter = new AbortableEmittery();
		const await_or_abort = (
			promise: Promise<any>,
			on_abort: () => void
		) => {
			return new Promise((resolve, reject) => {
				let resolved = false;
				emitter.on("abort", () => {
					if (!resolved) {
						on_abort();
						resolve(null);
						resolved = true;
					}
				});
				promise
					.then((result) => {
						if (!resolved) {
							resolve(result);
							resolved = true;
						}
					})
					.catch(reject);
			});
		};
		super(async (resolve, reject) => {
			let step_value;
			const generator = make_generator(await_or_abort);
			do {
				if (emitter.aborted) {
					resolve(null);
					return;
				}
				step_value = await generator.next();
			} while (!step_value.done);
		});
		this.emitter = emitter;
	}

	abort() {
		this.emitter.abort();
	}
}

// const sleep = promisify((timeout: number, callback: (...args: any[]) => void) =>
// 	setTimeout(callback, timeout)
// );

// const a = new AbortablePromise(async function* () {
// 	yield await sleep(1000);
// 	console.log("awaited 100");
// 	yield await sleep(2000);
// 	console.log("awaited 200");
// 	yield await sleep(3000);
// 	console.log("awaited 300");
// });

// setTimeout(() => a.abort(), 1500);

const b = new AbortablePromise(async function* (await_or_abort) {
	const ping = await deferedSpawn("ping", ["8.8.8.8"]);
	while (true) {
		console.log(
			await await_or_abort(ping.waitForNextData(), () => {
				ping.kill();
			})
		);
		yield;
	}
});

setTimeout(() => b.abort(), 5000);
