/**
 * @private
 */
Ext.define('Ext.AnimationQueue', {
    singleton: true,
 
    constructor: function() {
        var me = this;
 
        me.queue = [];
        me.taskQueue = [];
        me.runningQueue = [];
        me.idleQueue = [];
        me.isRunning = false;
        me.isIdle = true;
 
        me.run = me.run.bind(me);
 
        // iOS has a nasty bug which causes pending requestAnimationFrame to not release
        // the callback when the WebView is switched back and forth from / to being background process
        // We use a watchdog timer to workaround this, and restore the pending state correctly if this happens
        // This timer has to be set as an interval from the very beginning and we have to keep it running for
        // as long as the app lives, setting it later doesn't seem to work.
        // The watchdog timer must be accessible for environments to cancel.
        if (Ext.os.is.iOS) {
            //<debug>
            me.watch.$skipTimerCheck = true;
            //</debug>
            me.watchdogTimer = Ext.interval(me.watch, 500, me);
        }
    },
 
    /**
     *
     * @param {Function} fn
     * @param {Object} [scope]
     * @param {Object} [args]
     */
    start: function(fn, scope, args) {
        var me = this;
 
        me.queue.push(arguments);
 
        if (!me.isRunning) {
            if (me.hasOwnProperty('idleTimer')) {
                Ext.undefer(me.idleTimer);
                delete me.idleTimer;
            }
 
            if (me.hasOwnProperty('idleQueueTimer')) {
                Ext.undefer(me.idleQueueTimer);
                delete me.idleQueueTimer;
            }
 
            me.isIdle = false;
            me.isRunning = true;
            //<debug>
            me.startCountTime = Ext.now();
            me.count = 0;
            //</debug>
            me.doStart();
        }
    },
 
    clear: function() {
        var me = this;
 
        Ext.undefer(me.idleTimer);
        Ext.undefer(me.idleQueueTimer);
        Ext.unraf(me.animationFrameId);
 
        me.idleTimer = me.idleQueueTimer = me.animationFrameId = null;
 
        me.queue.length = me.taskQueue.length = me.runningQueue.length = me.idleQueue.length = 0;
        me.isRunning = false;
        me.isIdle = true;
 
        //<debug>
        me.startCountTime = Ext.now();
        me.count = 0;
        //</debug>
    },
 
    watch: function() {
        if (this.isRunning && Ext.now() - this.lastRunTime >= 500) {
            this.run();
        }
    },
 
    run: function() {
        var me = this, item, element;
 
        // When asked to start or iterate, it will now create a new one
        me.animationFrameId = null;
 
        if (!me.isRunning) {
            return;
        }
 
        var queue = me.runningQueue,
            now = Ext.now(),
            i, ln;
 
        me.lastRunTime = now;
        me.frameStartTime = now;
 
        // We are doing cleanup here for any destroyed elements
        // this is temporary until we fix CssTransition to properly
        // inform an element that it is being animated
        // then the element, during destruction, will need to cleanup
        // the animation (see Ext.fx.runner.CssTransition#run)
        i = me.queue.length;
        while (i--) {
            item = me.queue[i];
            element = item[1] && item[1].getElement && item[1].getElement();
            if (element && element.destroyed) {
                me.queue.splice(i, 1);
            }
        }
 
        queue.push.apply(queue, me.queue); // take a snapshot of the current queue and run it
 
        for (i = 0, ln = queue.length; i < ln; i++) {
            me.invoke(queue[i]);
        }
 
        queue.length = 0;
 
        //<debug>
        var elapse = me.frameStartTime - me.startCountTime,
            count = ++me.count;
 
        if (elapse >= 200) {
            me.onFpsChanged(count * 1000 / elapse, count, elapse);
            me.startCountTime = me.frameStartTime;
            me.count = 0;
        }
        //</debug>
 
        if (!me.queue.length) {
            me.stop();
        }
 
        // Could have been stopped while invoking handlers
        if (me.isRunning) {
            me.doIterate();
        }
    },
 
    //<debug>
    onFpsChanged: Ext.emptyFn,
 
    onStop: Ext.emptyFn,
    //</debug>
 
    doStart: function() {
        if (!this.animationFrameId) {
            this.animationFrameId = Ext.raf(this.run);
        }
        this.lastRunTime = Ext.now();
    },
 
    doIterate: function() {
        if (!this.animationFrameId) {
            this.animationFrameId = Ext.raf(this.run);
        }
    },
 
    doStop: function() {
        if (this.animationFrameId) {
            Ext.unraf(this.animationFrameId);
        }
        
        this.animationFrameId = null;
    },
 
    /**
     *
     * @param {Function} fn
     * @param {Object} [scope]
     * @param {Object} [args]
     */
    stop: function(fn, scope, args) {
        var me = this;
 
        if (!me.isRunning) {
            return;
        }
 
        var queue = me.queue,
            ln = queue.length,
            i, item;
 
        for (i = 0; i < ln; i++) {
            item = queue[i];
            if (item[0] === fn && item[1] === scope && item[2] === args) {
                queue.splice(i, 1);
                i--;
                ln--;
            }
        }
 
        if (ln === 0) {
            me.doStop();
            //<debug>
            me.onStop();
            //</debug>
            me.isRunning = false;
 
            if (me.idleQueue.length && !me.idleTimer) {
                me.idleTimer = Ext.defer(me.whenIdle, 100, me);
            }
        }
    },
 
    onIdle: function(fn, scope, args) {
        var me = this,
            listeners = me.idleQueue,
            i, ln, listener;
 
        for (i = 0, ln = listeners.length; i < ln; i++) {
            listener = listeners[i];
            if (fn === listener[0] && scope === listener[1] && args === listener[2]) {
                return;
            }
        }
 
        listeners.push(arguments);
 
        if (me.isIdle) {
            me.processIdleQueue();
        }
        else if (!me.idleTimer) {
            me.idleTimer = Ext.defer(me.whenIdle, 100, me);
        }
    },
 
    unIdle: function(fn, scope, args) {
        var me = this,
            listeners = me.idleQueue,
            i, ln, listener;
 
        for (i = 0, ln = listeners.length; i < ln; i++) {
            listener = listeners[i];
            if (fn === listener[0] && scope === listener[1] && args === listener[2]) {
                listeners.splice(i, 1);
                return true;
            }
        }
        
        if (!listeners.length && me.idleTimer) {
            Ext.undefer(me.idleTimer);
            delete me.idleTimer;
        }
        
        if (!listeners.length && me.idleQueueTimer) {
            Ext.undefer(me.idleQueueTimer);
            delete me.idleQueueTimer;
        }
 
        return false;
    },
 
    queueTask: function(fn, scope, args) {
        this.taskQueue.push(arguments);
        this.processTaskQueue();
    },
 
    dequeueTask: function(fn, scope, args) {
        var listeners = this.taskQueue,
            i, ln, listener;
 
        for (i = 0, ln = listeners.length; i < ln; i++) {
            listener = listeners[i];
            if (fn === listener[0] && scope === listener[1] && args === listener[2]) {
                listeners.splice(i, 1);
                i--;
                ln--;
            }
        }
    },
 
    invoke: function(listener) {
        var fn = listener[0],
            scope = listener[1],
            args = listener[2];
 
        fn = (typeof fn == 'string' ? scope[fn] : fn);
 
        if (Ext.isArray(args)) {
            fn.apply(scope, args);
        }
        else {
            fn.call(scope, args);
        }
    },
 
    whenIdle: function() {
        delete this.idleTimer;
        this.isIdle = true;
        this.processIdleQueue();
    },
 
    processIdleQueue: function() {
        if (!this.hasOwnProperty('idleQueueTimer')) {
            this.idleQueueTimer = Ext.defer(this.processIdleQueueItem, 1, this);
        }
    },
 
    processIdleQueueItem: function() {
        delete this.idleQueueTimer;
 
        if (!this.isIdle) {
            return;
        }
 
        var listeners = this.idleQueue,
            listener;
 
        if (listeners.length > 0) {
            listener = listeners.shift();
            this.invoke(listener);
            this.processIdleQueue();
        }
    },
 
    processTaskQueue: function() {
        if (!this.hasOwnProperty('taskQueueTimer')) {
            this.taskQueueTimer = Ext.defer(this.processTaskQueueItem, 15, this);
        }
    },
 
    processTaskQueueItem: function() {
        delete this.taskQueueTimer;
 
        var listeners = this.taskQueue,
            listener;
 
        if (listeners.length > 0) {
            listener = listeners.shift();
            this.invoke(listener);
            this.processTaskQueue();
        }
    }
    //<debug>
    ,
 
    /**
     *
     * @param {Number} fps Frames per second.
     * @param {Number} count Actual number of frames rendered during interval.
     * @param {Number} interval Interval duration.
     */
    showFps: function () {
        var styleTpl = {
            color: 'white',
            'background-color': 'black',
            'text-align': 'center',
            'font-family': 'sans-serif',
            'font-size': '8px',
            'font-weight': 'normal',
            'font-style': 'normal',
            'line-height': '20px',
            '-webkit-font-smoothing': 'antialiased',
 
            'zIndex': 100000,
            position: 'absolute'
        };
 
        Ext.getBody().append([
            // --- Average ---
            {
                style: Ext.applyIf({
                    bottom: '50px',
                    left: 0,
                    width: '50px',
                    height: '20px'
                }, styleTpl),
                html: 'Average'
            },
            {
                style: Ext.applyIf({
                    'background-color': 'red',
                    'font-size': '18px',
                    'line-height': '50px',
 
                    bottom: 0,
                    left: 0,
                    width: '50px',
                    height: '50px'
                }, styleTpl),
                id: '__averageFps',
                html: '0'
            },
            // --- Min ---
            {
                style: Ext.applyIf({
                    bottom: '50px',
                    left: '50px',
                    width: '50px',
                    height: '20px'
                }, styleTpl),
                html: 'Min (Last 1k)'
            },
            {
                style: Ext.applyIf({
                    'background-color': 'orange',
                    'font-size': '18px',
                    'line-height': '50px',
 
                    bottom: 0,
                    left: '50px',
                    width: '50px',
                    height: '50px'
                }, styleTpl),
                id: '__minFps',
                html: '0'
            },
            // --- Max ---
            {
                style: Ext.applyIf({
                    bottom: '50px',
                    left: '100px',
                    width: '50px',
                    height: '20px'
                }, styleTpl),
                html: 'Max (Last 1k)'
            },
            {
                style: Ext.applyIf({
                    'background-color': 'maroon',
                    'font-size': '18px',
                    'line-height': '50px',
 
                    bottom: 0,
                    left: '100px',
                    width: '50px',
                    height: '50px'
                }, styleTpl),
                id: '__maxFps',
                html: '0'
            },
            // --- Current ---
            {
                style: Ext.applyIf({
                    bottom: '50px',
                    left: '150px',
                    width: '50px',
                    height: '20px'
                }, styleTpl),
                html: 'Current'
            },
            {
                style: Ext.applyIf({
                    'background-color': 'green',
                    'font-size': '18px',
                    'line-height': '50px',
 
                    bottom: 0,
                    left: '150px',
                    width: '50px',
                    height: '50px'
                }, styleTpl),
                id: '__currentFps',
                html: '0'
            }
        ]);
 
        Ext.AnimationQueue.resetFps();
    },
 
    resetFps: function () {
        var currentFps = Ext.get('__currentFps'),
            averageFps = Ext.get('__averageFps'),
            minFps = Ext.get('__minFps'),
            maxFps = Ext.get('__maxFps'),
            min = 1000,
            max = 0,
            count = 0,
            sum = 0;
 
        if (!currentFps) {
            return;
        }
 
        Ext.AnimationQueue.onFpsChanged = function (fps) {
            count++;
 
            if (!(count % 10)) {
                min = 1000;
                max = 0;
            }
 
            sum += fps;
            min = Math.min(min, fps);
            max = Math.max(max, fps);
            currentFps.setHtml(Math.round(fps));
            // All-time average since last reset.
            averageFps.setHtml(Math.round(sum / count));
            minFps.setHtml(Math.round(min));
            maxFps.setHtml(Math.round(max));
        };
    }
 
}, function () {
    /*
        Global FPS indicator. Add ?showfps to use in any application. Note that this REQUIRES true requestAnimationFrame
        to be accurate.
     */
    var paramsString = window.location.search.substr(1),
        paramsArray = paramsString.split("&");
 
    if (Ext.Array.contains(paramsArray, "showfps")) {
        Ext.onReady(this.showFps.bind(this));
    }
//</debug>
});