/**
 * @private
 * Handle batch read / write of DOMs, currently used in SizeMonitor + PaintMonitor
 */
Ext.define('Ext.TaskQueue', {
    requires: 'Ext.AnimationQueue',
 
    singleton: true,
 
    pending: false,
 
    mode: true, // true for 'read', false for 'write' 
    
    //<debug> 
    protectedReadQueue: [],
    protectedWriteQueue: [],
    //</debug> 
    
    readQueue: [],
    writeQueue: [],
    readRequestId: 0,
    writeRequestId: 0,
 
    timer: null,
 
    constructor: function() {
        this.run = Ext.Function.bind(this.run, this);
        
        // Some global things like floated wrapper are persistent and will add tasks/ 
        // add timers all the time, spoiling resource checks in our unit test suite. 
        // To work around that we're implementing a parallel queue where only trusted 
        // tasks will go, and fly under the radar of resource checker. 
        //<debug> 
        this.runProtected = Ext.Function.bind(
            this.run, this, [this.protectedReadQueue, this.protectedWriteQueue, 'runProtected']
        );
        this.runProtected.$skipTimerCheck = true;
        //</debug> 
        
        // 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 
        if (Ext.os.is.iOS) {
            //<debug> 
            this.watch.$skipTimerCheck = true;
            //</debug> 
            Ext.interval(this.watch, 500, this);
        }
    },
 
    requestRead: function(fn, scope, args) {
        var request = {
                id: ++this.readRequestId,
                fn: fn,
                scope: scope,
                args: args
            };
 
        //<debug> 
        if (arguments[3] === true) {
            this.protectedReadQueue.push(request);
            this.request(true, 'runProtected');
        }
        else {
        //</debug> 
            this.readQueue.push(request);
            this.request(true);
        //<debug> 
        }
        //</debug> 
        
        
        return request.id;
    },
    
    cancelRead: function(id) {
        this.cancelRequest(this.readQueue, id, true);
    },
 
    requestWrite: function(fn, scope, args) {
        var request = {
                id: ++this.writeRequestId,
                fn: fn,
                scope: scope,
                args: args
            };
        
        //<debug> 
        if (arguments[3] === true) {
            this.protectedWriteQueue.push(request);
            this.request(false, 'runProtected');
        }
        else {
        //</debug> 
            this.writeQueue.push(request);
            this.request(false);
        //<debug> 
        }
        //</debug> 
        
        return request.id;
    },
    
    cancelWrite: function(id) {
        this.cancelRequest(this.writeQueue, id, false);
    },
 
    request: function(mode, method) {
        var me = this;
        
        if (!me.pending) {
            me.pendingTime = Date.now();
            me.pending = true;
            me.mode = mode;
            
            if (mode) {
                me.timer = Ext.defer(me[method] || me.run, 1);
            }
            else {
                me.timer = Ext.Function.requestAnimationFrame(me[method] || me.run);
            }
        }
        
        //<debug> 
        // Last one should win 
        if (me.mode === mode && me.timer) {
            clearTimeout(me.timer);
            
            if (mode) {
                me.timer = Ext.defer(me[method] || me.run, 1);
            }
            else {
                me.timer = Ext.Function.requestAnimationFrame(me[method] || me.run);
            }
        }
        //</debug> 
    },
    
    cancelRequest: function(queue, id, mode) {
        for (var i = 0; i < queue.length; i++) {
            if (queue[i].id === id) {
                queue.splice(i, 1);
                
                break;
            }
        }
        
        if (!queue.length && this.mode === mode && this.timer) {
            clearTimeout(this.timer);
        }
    },
 
    watch: function() {
        if (this.pending && Date.now() - this.pendingTime >= 500) {
            this.run();
        }
    },
 
    run: function(readQueue, writeQueue, method) {
        var me = this,
            mode = null,
            queue, tasks, task, fn, scope, args, i, len;
        
        readQueue = readQueue || me.readQueue;
        writeQueue = writeQueue || me.writeQueue;
 
        me.pending = false;
 
        me.pending = me.timer = false;
        
        if (me.mode) {
            queue = readQueue;
 
            if (writeQueue.length > 0) {
                mode = false;
            }
        }
        else {
            queue = writeQueue;
 
            if (readQueue.length > 0) {
                mode = true;
            }
        }
 
        tasks = queue.slice();
        queue.length = 0;
 
        for (= 0, len = tasks.length; i < len; i++) {
            task = tasks[i];
            
            fn = task.fn;
            scope = task.scope;
            args = task.args;
            
            if (scope && (scope.destroying || scope.destroyed)) {
                continue;
            }
            
            if (typeof fn === 'string') {
                fn = scope[fn];
            }
 
            if (args) {
                fn.apply(scope, args);
            }
            else {
                fn.call(scope);
            }
        }
 
        tasks.length = 0;
 
        if (mode !== null) {
            me.request(mode, method);
        }
    },
    
    clear: function() {
        var me = this,
            timer = me.timer;
 
        me.readQueue.length = me.writeQueue.length = 0;
        me.pending = me.timer = false;
        me.mode = true;
        
        if (timer) {
            clearTimeout(timer);
            Ext.Function.cancelAnimationFrame(timer);
        }
    }
 
    //<debug> 
    ,privates: {
        flush: function() {
            var me = this;
 
            while (me.readQueue.length || me.writeQueue.length) {
                clearTimeout(me.timer);
                Ext.Function.cancelAnimationFrame(me.timer);
                me.run();
            }
            me.mode = true;
        }
    }
    //</debug> 
});