/**
 * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
 *
 * @author David Sehnal <david.sehnal@gmail.com>
 */

import { RuntimeContext } from './execution/runtime-context'
import { Progress } from './execution/progress'
import { ExecuteObservable, ExecuteObservableChild, ExecuteInContext } from './execution/observable';
import { SyncRuntimeContext } from './execution/synchronous';
import { idFactory } from 'mol-util/id-factory';

/** A "named function wrapper" with built in "computation tree progress tracking". */
interface Task<T> {
    /** run the task without observation */
    run(): Promise<T>,
    /** run the task with the specified observer, default updateRate is 250ms */
    run(observer: Progress.Observer, updateRateMs?: number): Promise<T>,

    /**
     * Run a child task that adds a new node to the progress tree. Allows to passing the progress so
     * that the progress tree can be kept in a "good state" without having to separately call update.
     */
    runAsChild(ctx: RuntimeContext, progress?: string | Partial<RuntimeContext.ProgressUpdate>): Promise<T>

    /** Run the task on the specified context. */
    runInContext(ctx: RuntimeContext): Promise<T>

    readonly id: number,
    readonly name: string
}

namespace Task {
    class Impl<T> implements Task<T> {
        readonly id: number;

        run(observer?: Progress.Observer, updateRateMs?: number): Promise<T> {
            if (observer) return ExecuteObservable(this, observer, updateRateMs as number || 250);
            return this.f(SyncRuntimeContext);
        }

        runAsChild(ctx: RuntimeContext, progress?: string | Partial<RuntimeContext.ProgressUpdate>): Promise<T> {
            if (ctx.isSynchronous) return this.f(SyncRuntimeContext);
            return ExecuteObservableChild(ctx, this, progress as string | Partial<RuntimeContext.ProgressUpdate>);
        }

        runInContext(ctx: RuntimeContext): Promise<T> {
            if (ctx.isSynchronous) return this.f(SyncRuntimeContext);
            return ExecuteInContext(ctx, this);
        }

        constructor(public name: string, public f: (ctx: RuntimeContext) => Promise<T>, public onAbort?: () => void) {
            this.id = getNextId();
        }
    }

    export interface Aborted { isAborted: true, reason: string }
    export function isAbort(e: any): e is Aborted { return !!e && !!e.isAborted; }
    export function Aborted(reason: string): Aborted { return { isAborted: true, reason }; }

    export function create<T>(name: string, f: (ctx: RuntimeContext) => Promise<T>, onAbort?: () => void): Task<T> {
        return new Impl(name, f, onAbort);
    }

    export function constant<T>(name: string, value: T): Task<T> { return create(name, async ctx => value); }
    export function fail(name: string, reason: string): Task<any> { return create(name, async ctx => { throw new Error(reason); }); }

    export interface Progress {
        taskId: number,
        taskName: string,
        startedTime: number,
        message: string,
        canAbort: boolean,
        isIndeterminate: boolean,
        current: number,
        max: number
    }

    const getNextId = idFactory(0, 0x3fffffff)
}

export { Task }