import { IEnumeratorV2Started } from "../IEnumeratorV2";
import { BaseEnumerator } from "./BaseEnumerator";
import { SingleEnumerator } from "./SingleEnumerator";

export class EnumeratorExecutor implements IEnumeratorV2Started {
    public Current: any;

    public target: any;
    public pauseFlag: boolean;
    public doneFlag: boolean;
    public childFlag: boolean;
    public asyncFlag: boolean;
    public error: any;

    private _executor: EnumeratorExecutor;
    private _enumerator: BaseEnumerator;

    constructor(enumerator: BaseEnumerator, target: any) {
        this.target = target;
        this._enumerator = enumerator;
    }

    next(delta?: any): IteratorResult<any> {
        if (this._executor && this._executor.doneFlag) {
            this._executor = null;
        }

        if (this.doneFlag || (!this._enumerator && !this._executor)) {
            this.doneFlag = true;
            return { done: true, value: undefined };
        }

        if (this.asyncFlag || this.pauseFlag) return { done: false, value: undefined };

        let result: IteratorResult<any>;

        if (this._executor) {
            result = this._executor.next(delta);
            this.Current = this._executor.Current;
            if (this._executor.doneFlag) {
                this._executor = null;
            } else {
                result.done = false;
                return result;
            }
        }

        if (!this._enumerator) {
            this.doneFlag = true;
            return { done: true, value: undefined };
        }

        try {
            result = this._enumerator.next(delta);
            let value = result.value;
            let done = result.done;

            if (value) {
                // Iterator
                if (typeof value[Symbol.iterator] === 'function') {
                    value = new SingleEnumerator(<Iterator<any>>value);
                }

                if (value instanceof BaseEnumerator) {
                    if (!done) {
                        BaseEnumerator.getLastEnumerator(value).nextEnumerator = this._enumerator;
                    }
                    this._enumerator = value;
                    result = this._enumerator.next(delta);
                    value = result.value;
                    done = result.done;

                    if (value) {
                        // Iterator again
                        if (typeof value[Symbol.iterator] === 'function') {
                            value = new SingleEnumerator(<Iterator<any>>value);
                        }

                        if (value instanceof BaseEnumerator) {
                            if (!done) {
                                BaseEnumerator.getLastEnumerator(value).nextEnumerator = this._enumerator;
                            }
                            this._enumerator = value;
                            result.done = false;
                            done = false;
                        }
                    }
                }

                if (value instanceof EnumeratorExecutor) {
                    if (done) {
                        this._enumerator = this._enumerator.nextEnumerator;
                    }
                    value.childFlag = true;
                    result.done = false;
                    done = false;
                    this._executor = value;
                } else if (Promise.resolve(value) === value) {
                    this.asyncFlag = true;
                    result.done = false;
                    done = false;
                    (<Promise<any>>value)
                        .then(v => {
                            this.asyncFlag = false;
                            this.Current = v;
                            if (done) {
                                this._enumerator = this._enumerator.nextEnumerator;
                            }
                        })
                        .catch(e => {
                            this.asyncFlag = false;
                            this.doneFlag = true;
                            this._enumerator = null;
                            this.error = e;
                            if (e instanceof Error) {
                                console.error(e.stack);
                            } else {
                                console.error(`Error: ${JSON.stringify(e)}`);
                            }
                        });
                }

                this.Current = value;
            }

            if (done) {
                this._enumerator = this._enumerator.nextEnumerator;
                if (this._enumerator) {
                    result.done = false;
                }
            }
        }
        catch (e) {
            this.doneFlag = true;
            this.error = e;
            if (e instanceof Error) {
                console.error(e.stack);
            } else {
                console.error(`Error: ${JSON.stringify(e)}`);
            }
            result = { done: true, value: e };
        }

        return result;
    }

    Stop(): void {
        this.doneFlag = true;
        if (this._executor) {
            this._executor.Stop();
        }
    }

    Pause(): void {
        this.pauseFlag = true;
        if (this._executor) {
            this._executor.Pause();
        }
    }

    Resume(): void {
        this.pauseFlag = false;
        if (this._executor) {
            this._executor.Resume();
        }
    }

}
