"use strict";
var task_resolve_1 = require("./task.resolve");
var Rx = require('rx');
var task_return_values_1 = require("./task.return.values");
var debug = require('debug')('cb:task.runner');
var _ = require('../lodash.custom');
var once = require('once');
var domain = require('domain');
(function (TaskReportType) {
    TaskReportType[TaskReportType["start"] = "start"] = "start";
    TaskReportType[TaskReportType["end"] = "end"] = "end";
    TaskReportType[TaskReportType["error"] = "error"] = "error";
})(exports.TaskReportType || (exports.TaskReportType = {}));
var TaskReportType = exports.TaskReportType;
(function (TaskSkipReasons) {
    TaskSkipReasons[TaskSkipReasons["SkipFlag"] = "SkipFlag"] = "SkipFlag";
    TaskSkipReasons[TaskSkipReasons["IfChanged"] = "IfChanged"] = "IfChanged";
})(exports.TaskSkipReasons || (exports.TaskSkipReasons = {}));
var TaskSkipReasons = exports.TaskSkipReasons;
/**
 * This creates a wrapper around the actual function that will be run.
 * This done to allow the before/after reporting to work as expected for consumers
 */
function time(scheduler) {
    return scheduler ? scheduler.now() : new Date().getTime();
}
exports.time = time;
function createObservableFromSequenceItem(item, trigger, ctx) {
    return Rx.Observable.create(function (observer) {
        var startTime = time(trigger.config.scheduler);
        /**
         * Complete immediately if this item was marked
         * as 'skipped'
         */
        if (!trigger.config.force && item.task.skipped) {
            var additionalStats = {
                skipped: true,
                skippedReason: TaskSkipReasons.SkipFlag
            };
            var stats_1 = getStartStats(startTime, additionalStats);
            observer.onNext(getTaskReport(TaskReportType.start, item, stats_1));
            observer.onNext(getTaskReport(TaskReportType.end, item, getEndStats(stats_1, startTime, additionalStats)));
            observer.onCompleted();
            return;
        }
        /**
         * Complete immediately if this item was marked
         * with an 'ifChanged' predicate
         */
        if (!trigger.config.force && item.task.ifChanged.length && ctx.hasIn(['ifChanged'])) {
            var hasChanges = ctx
                .get('ifChanged')
                .filter(function (x) {
                return item.task.ifChanged.indexOf(x.get('userInput')) !== -1;
            })
                .some(function (x) { return x.get('changed'); });
            if (!hasChanges) {
                var additionalStats = {
                    skipped: true,
                    skippedReason: TaskSkipReasons.IfChanged
                };
                var stats_2 = getStartStats(startTime, additionalStats);
                observer.onNext(getTaskReport(TaskReportType.start, item, stats_2));
                observer.onNext(getTaskReport(TaskReportType.end, item, getEndStats(stats_2, startTime, additionalStats)));
                observer.onCompleted();
                return;
            }
        }
        /**
         * Timestamp when this task starts
         * @type {TaskStats}
         */
        var stats = getStartStats(startTime, { skipped: false });
        debug("> seqUID " + item.seqUID + " started");
        /**
         * Task started
         */
        observer.onNext(getTaskReport(TaskReportType.start, item, stats));
        /**
         * Exit after 1 second if we're in a 'dry run'
         */
        if (trigger.config.dryRun) {
            return Rx.Observable
                .just('dryRun')
                .delay(trigger.config.dryRunDuration, trigger.config.scheduler)
                .do(function (_) {
                observer.onNext(getTaskReport(TaskReportType.end, item, getEndStats(stats, time(trigger.config.scheduler))));
                observer.onCompleted();
            }).subscribe();
        }
        if (item.task.type === task_resolve_1.TaskTypes.InlineFunction
            || item.task.type === task_resolve_1.TaskTypes.ExternalTask
            || item.task.type === task_resolve_1.TaskTypes.Adaptor) {
            var argCount = item.factory.length;
            var cb_1 = once(function (err) {
                if (err) {
                    observer.onError(err);
                    return;
                }
                observer.onNext(getTaskReport(TaskReportType.end, item, getEndStats(stats, time(trigger.config.scheduler))));
                observer.onCompleted();
            });
            var d = domain.create();
            d.once('error', function (err) {
                cb_1(err);
            });
            var domainBoundFn = d.bind(item.factory.bind(null, item.options, trigger));
            var done = function (err) {
                d.removeListener('error', function (err) {
                    cb_1(err);
                });
                d.exit();
                return cb_1.apply(null, arguments);
            };
            var result = domainBoundFn(done);
            if (result) {
                var returns = task_return_values_1.default(result, done);
                /**
                 * If the return value does not need to be consumed,
                 * but it is IS a function, assume it's the tear-down logic
                 * for this task - which also means it MUST signify completion
                 * via the callback
                 */
                if (!returns && typeof result === 'function') {
                    if (argCount >= 3) {
                        return result;
                    }
                    else {
                        done(new Error('You returned tear-down logic, but you never asked for the completion callback'));
                        return;
                    }
                }
            }
            else {
                /**
                 * Assume sync function if nothing returned
                 * and 3rd argument was not asked for
                 */
                if (argCount < 3) {
                    done();
                    return;
                }
            }
        }
    }).catch(function (error) {
        /**
         * **--**--MAGIC--**--**
         * If a task throws an error of any kind, we want that error to propagate as normal,
         * but we want tp prepend an error report so that the error report can be observed
         * before the sequence ends.
         */
        return Rx.Observable.concat(Rx.Observable.just(getTaskErrorReport(item, getErrorStats(error, time(trigger.config.scheduler)))), Rx.Observable.throw(error));
    });
}
exports.createObservableFromSequenceItem = createObservableFromSequenceItem;
/**
 * Factory for TaskReports
 */
function getTaskReport(type, item, stats) {
    return { type: type, item: item, stats: stats };
}
/**
 * Create a new stats object with startTime
 */
function getStartStats(startTime, additional) {
    return _.assign({}, additional, {
        startTime: startTime,
        started: true,
        endTime: 0,
        duration: 0,
        completed: false,
        errors: []
    });
}
exports.getStartStats = getStartStats;
/**
 * Create a new stats object with completed/duration flags etc
 */
function getEndStats(stats, endTime, additional) {
    return _.assign({}, stats, additional, {
        endTime: endTime,
        duration: endTime - stats.startTime,
        completed: true
    });
}
/**
 * Factory for TaskReports that errored
 */
function getTaskErrorReport(item, stats) {
    return { type: TaskReportType.error, item: item, stats: stats };
}
/**
 * Get basic stats for a task error
 */
function getErrorStats(error, endTime) {
    if (error._cbError) {
        return {
            endTime: endTime,
            completed: false,
            errors: [error],
            cbError: true,
            cbExitCode: error._cbExitCode
        };
    }
    return {
        endTime: endTime,
        completed: false,
        errors: [error]
    };
}
//# sourceMappingURL=task.runner.js.map