// copy/paste into console or add as a snippet in the dev tools
var jsperf = (function () {
    var before, beforeEach, after, afterEach, tests;

    var perfNow = (function () {
        var perf = window.performance || {};
        perf.now = perf.now || perf.mozNow || perf.msNow || perf.oNow || perf.webkitNow || Date.now || function () {
            return new Date().getTime();
        };

        return function () {
            return perf.now();
        };
    })();

    function out (results) {
        if (console.table) {
            console.table(results);
        } else {
            results.forEach(function (r, i) {
                console.log(i + '\t' + r.iterations);
            });
        }
    };

    function init () {
        before = beforeEach = after = afterEach = null;
        tests = [];
    }

    function setBefore (fn) { before = fn; }
    function setBeforeEach (fn) { beforeEach = fn; }
    function setAfter (fn) { after = fn; }
    function setAfterEach (fn) { afterEach = fn; }
    function addTest (name, fn) {
        if (!fn) {
            fn = name;
            name = 'Test #' + (tests.length + 1);
        }
        tests.push({
            name: name,
            fn: fn
        });
    }

    function metrics (results) {
        var max = Math.max.apply(Math, results.map(function (r) { return r.Iterations; }));
        results.forEach(function (r) {
            var diff = Math.round(((max - r.Iterations) / max) * 100, 2);
            r.Compare = diff === 0 ? 'Fastest' : diff + '% Slower';
        });
    }

    function run (duration) {
        var start, startEach, endEach, test, result,
            i = 0,
            l = tests.length,
            results = [],
            duration = duration || 10*1000;

        if (!l) {
            console.error('No tests provided');
            return;
        }

        start = perfNow();
        if (before) before();
        while (i++ < l) {
            test = tests.shift();
            result = {
                'Test Name': test.name,
                Iterations: 0,
                Compare: '',
                Duration: 0
            };

            console.log('Running', test.name);
            if (beforeEach) beforeEach();
            
            startEach = perfNow();
            do {
                test.fn();
                endEach = perfNow();
                result.Iterations++;
            } while (startEach + duration > endEach)
            
            if (afterEach) afterEach();
            
            result.Duration = endEach - startEach;
            results.push(result);
        }
        if (after) after();

        metrics(results);
        out(results);
        init();
    }

    init();

    return {
        before: setBefore,
        beforeEach: setBeforeEach,
        after: setAfter,
        afterEach: setAfterEach,
        test: addTest,
        run: run
    };
})();