import { Engine } from '../Engine';
const generatorFunctionDeclaration = /^\s*(?:function)?\*/;
/**
 *
 */
function isCoroutineGenerator(x) {
    if (typeof x !== 'function') {
        return false;
    }
    if (generatorFunctionDeclaration.test(Function.prototype.toString.call(x))) {
        return true;
    }
    if (!Object.getPrototypeOf) {
        return false;
    }
    return Object.getPrototypeOf(x) === Object.getPrototypeOf(new Function('return function * () {}')());
}
/**
 *
 */
export function coroutine(...args) {
    let coroutineGenerator;
    let thisArg;
    let options;
    let passedEngine;
    // coroutine(coroutineGenerator: CoroutineGenerator, options?: CoroutineOptions): Promise<void>;
    if (isCoroutineGenerator(args[0])) {
        thisArg = globalThis;
        coroutineGenerator = args[0];
        options = args[1];
    }
    // coroutine(thisArg: any, coroutineGenerator: CoroutineGenerator, options?: CoroutineOptions): Promise<void>;
    if (isCoroutineGenerator(args[1])) {
        thisArg = args[0];
        coroutineGenerator = args[1];
        options = args[2];
    }
    // coroutine(thisArg: any, engine: Engine, coroutineGenerator: CoroutineGenerator, options?: CoroutineOptions): Promise<void>;
    if (args[1] instanceof Engine) {
        thisArg = args[0];
        passedEngine = args[1];
        coroutineGenerator = args[2];
        options = args[3];
    }
    // coroutine(engine: Engine, coroutineGenerator: CoroutineGenerator, options?: CoroutineOptions): Promise<void>;
    if (args[0] instanceof Engine) {
        thisArg = globalThis;
        passedEngine = args[0];
        coroutineGenerator = args[1];
        options = args[2];
    }
    const schedule = options === null || options === void 0 ? void 0 : options.timing;
    let engine;
    try {
        engine = passedEngine !== null && passedEngine !== void 0 ? passedEngine : Engine.useEngine();
    }
    catch (_) {
        throw Error('Cannot run coroutine without engine parameter outside of an excalibur lifecycle method.\n' +
            'Pass an engine parameter to ex.coroutine(engine, function * {...})');
    }
    const generatorFcn = coroutineGenerator.bind(thisArg);
    return new Promise((resolve, reject) => {
        const generator = generatorFcn();
        const loop = (elapsedMs) => {
            try {
                const { done, value } = generator.next(elapsedMs);
                if (done) {
                    resolve();
                }
                if (value instanceof Promise) {
                    value.then(() => {
                        // schedule next loop
                        engine.clock.schedule(loop, 0, schedule);
                    });
                }
                else if (value === undefined || value === void 0) {
                    // schedule next frame
                    engine.clock.schedule(loop, 0, schedule);
                }
                else {
                    // schedule value milliseconds from now
                    engine.clock.schedule(loop, value || 0, schedule);
                }
            }
            catch (e) {
                reject(e);
            }
        };
        loop(engine.clock.elapsed()); // run first frame immediately
    });
}
//# sourceMappingURL=Coroutine.js.map