"use strict";
var __extends = (this && this.__extends) || (function () {
    var extendStatics = Object.setPrototypeOf ||
        ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
        function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
    return function (d, b) {
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
var Test_1 = require("./Test");
var Task_1 = require("@dojo/core/async/Task");
var lang_1 = require("@dojo/core/lang");
var Benchmark = require("benchmark");
var BenchmarkTest = (function (_super) {
    __extends(BenchmarkTest, _super);
    function BenchmarkTest(descriptor) {
        var _this = this;
        var args = {};
        Object.keys(descriptor).forEach(function (key) {
            switch (key) {
                case 'test':
                case 'options':
                    break;
                default:
                    args[key] = descriptor[key];
            }
        });
        _this = _super.call(this, args) || this;
        _this.test = (descriptor && descriptor.test) || function () { };
        var options = lang_1.mixin({}, _this.test.options, {
            async: true,
            setup: createLifecycle(true),
            teardown: createLifecycle(false)
        });
        if (options.defer) {
            _this.test = (function (testFunction) { return function (test, deferred) {
                var dfd = createDeferred(test.benchmark, deferred, options.numCallsUntilResolution);
                testFunction.call(_this, _this, dfd);
            }; })(_this.test);
        }
        _this.benchmark = new Benchmark(descriptor.name, options.defer ?
            'this.benchmark.internTest.test(this.benchmark.internTest, deferred);' :
            'this.internTest.test(this.internTest);', options);
        Object.defineProperty(_this.benchmark, 'name', {
            get: function () {
                return _this.name;
            },
            set: function (name) {
                _this.name = name;
            }
        });
        _this.benchmark.internTest = _this;
        return _this;
    }
    Object.defineProperty(BenchmarkTest.prototype, "timeElapsed", {
        get: function () {
            if (this.benchmark && this.benchmark.times) {
                return this.benchmark.times.elapsed;
            }
            return 0;
        },
        set: function (_value) {
        },
        enumerable: true,
        configurable: true
    });
    BenchmarkTest.prototype.async = function (_timeout, _numCallsUntilResolution) {
        throw new Error('Benchmark tests must be marked as asynchronous and use the deferred ' +
            'passed to them rather than call `this.async()`.');
    };
    BenchmarkTest.prototype.run = function () {
        var _this = this;
        this.hasPassed = false;
        this._usesRemote = false;
        var benchmark = this.benchmark;
        return new Task_1.default(function (resolve, reject) {
            benchmark.on('abort', function () {
                reject(benchmark.error);
            });
            benchmark.on('error', function () {
                if (benchmark.error === Test_1.SKIP) {
                    resolve();
                }
                else {
                    reject(benchmark.error);
                }
            });
            benchmark.on('complete', function () {
                resolve();
            });
            _this.executor.emit('testStart', _this).then(function () {
                benchmark.run();
            });
        }, function () {
            benchmark.abort();
        }).finally(function () {
            benchmark.off();
        })
            .then(function () {
            _this.hasPassed = true;
        }, function (error) {
            _this.error = error;
            throw error;
        })
            .finally(function () { return _this.executor.emit('testEnd', _this); });
    };
    BenchmarkTest.async = function (testFunction, numCallsUntilResolution) {
        testFunction.options = lang_1.mixin({}, testFunction.options, {
            defer: true,
            numCallsUntilResolution: numCallsUntilResolution
        });
        return testFunction;
    };
    return BenchmarkTest;
}(Test_1.default));
exports.default = BenchmarkTest;
var createLifecycle = function (before) {
    var queueName = before ? 'Before' : 'After';
    var queueMethod = before ? 'push' : 'unshift';
    var methodName = before ? 'before' : 'after';
    return [
        "(function (benchmark) {",
        "\tvar queue = benchmark.intern" + queueName + "EachLoopQueue;",
        "\tvar suite;",
        "\tif (!queue) {",
        "\t\tsuite = benchmark.internTest;",
        "\t\tbenchmark.intern" + queueName + "EachLoopQueue = queue = [];",
        "\t\twhile ((suite = suite.parent)) {",
        "\t\t\tif (suite." + methodName + "EachLoop) {",
        "\t\t\t\tqueue." + queueMethod + "(suite);",
        "\t\t\t}",
        "\t\t}",
        "\t}",
        "\tvar i = queue.length;",
        "\twhile((suite = queue[--i])) {",
        "\t\tsuite." + methodName + "EachLoop();",
        "\t}",
        "})(this.benchmark || this);\n"
    ].join('\n');
};
function createDeferred(benchmark, deferred, numCallsUntilResolution) {
    if (!numCallsUntilResolution) {
        numCallsUntilResolution = 1;
    }
    return {
        resolve: function () {
            --numCallsUntilResolution;
            if (numCallsUntilResolution === 0) {
                deferred.resolve();
            }
            else if (numCallsUntilResolution < 0) {
                throw new Error('resolve called too many times');
            }
        },
        reject: function (error) {
            benchmark.error = error;
            benchmark.abort();
            deferred.resolve();
        },
        rejectOnError: function (callback) {
            var self = this;
            return function () {
                try {
                    return callback.apply(this, arguments);
                }
                catch (error) {
                    self.reject(error);
                }
            };
        },
        callback: function (callback) {
            var self = this;
            return this.rejectOnError(function () {
                var returnValue = callback.apply(this, arguments);
                self.resolve();
                return returnValue;
            });
        }
    };
}
//# sourceMappingURL=BenchmarkTest.js.map