(function() {
    var idSeed = 0,
        timerFns = {}, // [id] = { scope: {}, fn: function() {} }
        timerWorker,
        bind = function (fn, scope) {
            return function () {
                return fn.apply(scope, arguments);
            };
        },
        clear = function (nativeFn, type, id) {
            if (id) {
                if (timerWorker) {
                    timerWorker.postMessage({
                        type: type,
                        id: id
                    });
                    delete timerFns[id];
                } else {
                    nativeFn(id);
                }
            }
        },
        set = function (nativeFn, type, fn, scope, delay) {
            var id;
 
            if (typeof scope === 'number') {
                delay = scope;
                scope = null;
            }
 
            if (timerWorker) {
                id = ++idSeed;
                if (id === Number.MAX_VALUE) {
                    id = idSeed = 1;
                }
 
                timerFns[id] = {
                    fn: fn,
                    scope: scope
                };
 
                timerWorker.postMessage({
                    type: type,
                    delay: delay,
                    id: id
                });
            } else {
                if (scope) {
                    fn = fn.bind ? fn.bind(scope) : bind(fn, scope);
                }
 
                id = nativeFn(fn, delay);
            }
 
            return id;
        };
 
    if (window.Worker) {
        try {
            if (window.require) {
                timerWorker = new Worker(require('event/timer-worker')); // eslint-disable-line
            } else {
                // "timer-worker.js" can't be referenced directly, so creating it inline from inserted script
                var blob = new Blob([
                        document.querySelector('#_sttimerworker').textContent
                    ], {
                        type: "text/javascript"
                    });
                
                timerWorker = new Worker(window.URL.createObjectURL(blob));
            }
 
            timerWorker.onmessage = function(e) {
                var message = e.data,
                    id = message.id,
                    entry = timerFns[id];
 
                if (entry) {
                    // timer may have already been cancelled while we were waiting to receive
                    // a message from the worker thread
                    if (entry.scope) {
                        entry.fn.call(entry.scope);
                    } else {
                        entry.fn();
                    }
 
                    if (message.type === 'setTimeout') {
                        delete timerFns[id];
                    }
                }
            };
        } catch (e) {
// if exception Timer.js will revert to setTimeout functions instead of Worker.
        }
    }
 
    /**
     * Similar to `setTimeout` and `Ext.defer` this method schedules a call to the given
     * method after the given `delay` and returns an id that can be used to cancel the
     * request.
     *
     * To cancel a timeout, call {@link ST#deferCancel}. Do not pass these id's to the
     * DOM.
     *
     * ### Note
     *
     * In some modern browsers (currently FF and Chrome timeouts are clamped to >= 1000ms for
     * inactive tabs.
     * https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setTimeout#Timeouts_in_inactive_tabs_clamped_to_>1000ms
     *
     * This can result in much larger delays than expected when playing back recorded events
     * since the Event Player is not guaranteed to be run in an active tab/window.
     *
     * This class leverages Web Workers to emulate the browser's setTimeout and setInterval
     * functionality without the undesirable clamping in incative tabs.
     *
     * Falls back to window.setTimeout/setInterval where Web Workers are not available.
     * @param {Function} fn The function to call after `delay` milliseconds.
     * @param {Object} [scope] The `this` pointer to use for calling `fn`.
     * @param {Number} delay The number of milliseconds to delay before calling `fn`.
     * @method defer
     * @member ST
     */
    ST.defer = function (fn, scope, delay) {
        return set(setTimeout, 'setTimeout', fn, scope, delay);
    };
 
    /**
     * Cancels an operation requested using {@link ST#defer}.
     * @param id
     * @method deferCancel
     * @member ST
     */
    ST.deferCancel = function (id) {
        clear(clearTimeout, 'clearTimeout', id);
    };
 
    ST.interval = function (fn, scope, delay) {
        return set(setInterval, 'setInterval', fn, scope, delay);
    };
 
    ST.intervalCancel = function (id) {
        clear(clearInterval, 'clearInterval', id);
    };
})();