import { IEnumeratorV2, IEnumeratorV2Started } from "../IEnumeratorV2";
import { CoroutineExecutor } from "./CoroutineExecutor";

export abstract class BaseEnumerator implements IEnumeratorV2 {
    public nextEnumerator: BaseEnumerator;

    abstract next(value?: any): IteratorResult<any>;
    
    Start(target?: any): IEnumeratorV2Started {
        let executor = LazyLoad.EnumeratorExecutor(this, target);
        CoroutineExecutor.instance.StartCoroutine(executor);
        return executor;
    }

    Then(iterator: Iterator<any>): IEnumeratorV2 {
        if (!iterator) return this;

        if (iterator instanceof BaseEnumerator) {
            BaseEnumerator.getLastEnumerator(this).nextEnumerator = iterator;
            return this;
        } else {
            let enumerator = LazyLoad.SingleEnumerator(iterator);
            BaseEnumerator.getLastEnumerator(this).nextEnumerator = enumerator;
            return this;
        }
    }
    
    ThenSerial(...iterators: Iterator<any>[]): IEnumeratorV2 {
        let last = BaseEnumerator.getLastEnumerator(this);
        for (let iterator of iterators) {
            if (iterator instanceof BaseEnumerator) {
                last.nextEnumerator = iterator;
            } else {
                let enumerator = LazyLoad.SingleEnumerator(iterator);
                last.nextEnumerator = enumerator;
            }
            last = last.nextEnumerator;
        }
        return this;
    }

    ThenParallel(...iterators: Iterator<any>[]): IEnumeratorV2 {
        return this.Then(LazyLoad.ParallelEnumerator(...iterators));
    }

    ThenAction(action: Function, delaySeconds?:number): IEnumeratorV2 {
        if (delaySeconds > 0) {
            return this.ThenSerial(LazyLoad.WaitTimeEnumerator(delaySeconds), LazyLoad.ActionEnumerator(action));
        } else {
            return this.Then(LazyLoad.ActionEnumerator(action));
        }
    }

    ThenWaitTime(seconds: number): IEnumeratorV2 {
        return this.Then(LazyLoad.WaitTimeEnumerator(seconds));
    }

    static getLastEnumerator(enumerator: BaseEnumerator): BaseEnumerator {
        let next = enumerator;
        while (next.nextEnumerator) {
            next = next.nextEnumerator;
        }
        return next;
    }
}

module LazyLoad {
    export function EnumeratorExecutor(enumerator: BaseEnumerator, target: any) {
        return new (require("./EnumeratorExecutor") as typeof import("./EnumeratorExecutor")).EnumeratorExecutor(enumerator, target);
    }

    export function SingleEnumerator(iterator: Iterator<any>) {
        return new (require("./SingleEnumerator") as typeof import("./SingleEnumerator")).SingleEnumerator(iterator);
    }

    export function ParallelEnumerator(...iterators: Iterator<any>[]) {
        return new (require("./ParallelEnumerator") as typeof import("./ParallelEnumerator")).ParallelEnumerator(iterators);
    }

    export function WaitTimeEnumerator(seconds: number) {
        return new (require("./WaitTimeEnumerator") as typeof import("./WaitTimeEnumerator")).WaitTimeEnumerator(seconds);
    }

    export function ActionEnumerator(action: Function) {
        return new (require("./ActionEnumerator") as typeof import("./ActionEnumerator")).ActionEnumerator(action);
    }
}