/**
 * Garbage collector for Ext.dom.Element instances.  Automatically cleans up Elements
 * that are no longer in the dom, but were not properly destroyed using
 * {@link Ext.dom.Element#destroy destroy()}.  Recommended practice is for Components to
 * clean up their own elements, but the GarbageCollector runs on regularly scheduled
 * intervals to attempt to clean up orphaned Elements that may have slipped through the cracks.
 * @private
 */
Ext.define('Ext.dom.GarbageCollector', {
    singleton: true,
 
    /**
     * @property {Number} 
     * The interval at which to run Element garbage collection. Set this property directly
     * to tune the interval.
     *
     *     Ext.dom.GarbageCollector.interval = 60000; // run garbage collection every one minute
     */
    interval: 30000,
 
    constructor: function() {
        var me = this;
 
        me.lastTime = Ext.now();
        me.onTick = me.onTick.bind(me);
 
        //<debug>
        me.onTick.$skipTimerCheck = true;
        //</debug>
 
        me.resume();
    },
 
    /**
     * Collects orphaned Ext.dom.Elements by removing their listeners and evicting them
     * from the cache.  Runs on a regularly scheduled {@link #interval} but can be called
     * directly to force garbage collection.
     * @return {String[]} An array containing the IDs of the elements that were garbage
     * collected, prefixed by their tag names.  Only applies in dev mode.  Returns nothing
     * in a production build.
     */
    collect: function() {
        var me = this,
            cache = Ext.cache,
            eid, dom, el, t, isGarbage, tagName;
 
        //<debug>
        var collectedIds = []; // eslint-disable-line vars-on-top, one-var
        //</debug>
 
        for (eid in cache) {
            if (!cache.hasOwnProperty(eid)) {
                continue;
            }
 
            el = cache[eid];
 
            if (el.skipGarbageCollection) {
                continue;
            }
 
            dom = el.dom;
 
            //<debug>
            // Should always have a DOM node
            if (!dom) {
                Ext.raise('Missing DOM node in element garbage collection: ' + eid);
            }
            //</debug>
 
            try {
                // In IE, accessing any properties of the window object of an orphaned iframe
                // can result in a "Permission Denied" error.  The same error also occurs
                // when accessing any properties of orphaned documentElement or body inside
                // of an iframe (documentElement and body become orphaned when the iframe
                // contentWindow is unloaded)
                isGarbage = Ext.isGarbage(dom);
            }
            catch (e) {
                // if an error was thrown in isGarbage it is most likely because we are
                // dealing with an inaccessible window or documentElement inside an orphaned
                // iframe in IE.  In this case we can't do anything except remove the
                // cache entry.
                delete cache[eid];
                //<debug>
                collectedIds.push('#' + el.id);
                //</debug>
                continue;
            }
 
            if (isGarbage) {
                isGarbage = false;
 
                if (el && el.dom) {
                    //<debug>
                    tagName = el.dom.tagName;
                    //</debug>
 
                    el.collect();
 
                    //<debug>
                    collectedIds.push((tagName ? tagName : '') + '#' + el.id);
                    //</debug>
                }
            }
        }
 
        //<feature legacyBrowser>
        // Cleanup IE Object leaks
        if (Ext.isIE9m) {
            t = {};
 
            for (eid in cache) {
                if (cache.hasOwnProperty(eid)) {
                    t[eid] = cache[eid];
                }
            }
 
            Ext.cache = Ext.dom.Element.cache = t;
        }
        //</feature>
 
        me.lastTime = Ext.now();
 
        //<debug>
        return collectedIds;
        //</debug>
    },
 
    onTick: function() {
        this.timerId = null;
 
        if (Ext.enableGarbageCollector) {
            this.collect();
        }
 
        this.resume();
    },
 
    /**
     * Pauses the timer and stops garbage collection
     */
    pause: function() {
        var timerId = this.timerId;
 
        if (timerId) {
            this.timerId = null;
            Ext.undefer(timerId);
        }
    },
 
    /**
     * Resumes garbage collection at the specified {@link #interval}
     */
    resume: function() {
        var me = this,
            lastTime = me.lastTime;
 
        if (Ext.enableGarbageCollector && (Ext.now() - lastTime) > me.interval) {
            me.collect();
        }
 
        if (!me.timerId) {
            me.timerId = Ext.defer(me.onTick, me.interval);
        }
    }
});