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 __());
    };
})();
(function (factory) {
    if (typeof module === "object" && typeof module.exports === "object") {
        var v = factory(require, exports);
        if (v !== undefined) module.exports = v;
    }
    else if (typeof define === "function" && define.amd) {
        define(["require", "exports", "./Test", "@dojo/core/async/Task", "@dojo/core/lang", "lodash", "platform", "benchmark"], factory);
    }
})(function (require, exports) {
    "use strict";
    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");
    // Explicitly require benchmark dependencies and attach Benchmark to them to improve WebPack compatibility
    var _ = require("lodash");
    var platform = require("platform");
    var Benchmark = require("benchmark");
    // TODO: remove the <any> cast when benchmark typings are updated to include runInContext
    Benchmark.runInContext({ _: _, platform: platform });
    /**
     * A wrapper around a Benchmark.js Benchmark that maps its API to that used by Test.
     */
    var BenchmarkTest = (function (_super) {
        __extends(BenchmarkTest, _super);
        function BenchmarkTest(descriptor) {
            var _this = this;
            // Call the superclass constructor with the set of descriptor keys not specific to BenchmarkTest
            var args = {};
            Object.keys(descriptor).forEach(function (key) {
                switch (key) {
                    case 'options':
                        break;
                    default:
                        args[key] = descriptor[key];
                }
            });
            args.test = args.test || function () { };
            _this = _super.call(this, args) || this;
            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 (deferred) {
                        // deferred is optional for compat with BenchmarkTestFunction, but it will always be defined here
                        var dfd = createDeferred(this.benchmark, deferred, options.numCallsUntilResolution);
                        testFunction.call(this, dfd);
                    };
                })(_this.test);
            }
            _this.benchmark = new Benchmark(descriptor.name, options.defer ? 'this.benchmark.internTest.test(deferred);' : 'this.internTest.test();', 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) {
                // ignore
            },
            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 () {
                // Stop listening for benchmark events once the test is finished
                benchmark.off();
            })
                .then(function () {
                _this._hasPassed = true;
            }, function (error) {
                _this.error = error;
                throw error;
            })
                .finally(function () { return _this.executor.emit('testEnd', _this); });
        };
        BenchmarkTest.prototype.toJSON = function () {
            var json = _super.prototype.toJSON.call(this);
            var benchmark = this.benchmark;
            json.benchmark = {
                hz: benchmark.hz,
                times: benchmark.times,
                stats: benchmark.stats
            };
            return json;
        };
        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;
    function isBenchmarkTest(value) {
        return value && value.benchmark != null && Test_1.isTest(value);
    }
    exports.isBenchmarkTest = isBenchmarkTest;
    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) {
        var remainingCalls = numCallsUntilResolution || 1;
        return {
            resolve: function () {
                --remainingCalls;
                if (remainingCalls === 0) {
                    deferred.resolve();
                }
                else if (remainingCalls < 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