/**
 * An `{@link Ext.mixin.Observable Observable}` through which Ext fires global events.
 * 
 * Ext.on() and Ext.un() are shorthand for {@link #addListener} and {@link #removeListener}
 * on this Observable.  For example, to listen for the idle event:
 * 
 *     Ext.on('idle', function() {
 *         // do something
 *     });
 */
Ext.define('Ext.GlobalEvents', {
    extend: 'Ext.mixin.Observable',
    alternateClassName: 'Ext.globalEvents', // for compat with Ext JS 4.2 and earlier 
    
    requires: [
        'Ext.dom.Element'
    ],
 
    observableType: 'global',
 
    singleton: true,
 
    /**
     * @private
     */
    resizeBuffer: 100,
 
    /**
     * @event added
     * Fires when a Component is added to a Container.
     * @param {Ext.Component} component
     */
 
    /**
     * @event beforeresponsiveupdate
     * Fires before {@link Ext.mixin.Responsive} perform any updates in response to
     * dynamic changes. This is prior to refreshing `responsiveFormulas`.
     * @param {Object} context The context object used by `responsiveConfig` expressions.
     * @since 5.0.1
     */
 
    /**
     * @event beginresponsiveupdate
     * Fires when {@link Ext.mixin.Responsive} is about to perform updates in response to
     * dynamic changes. At this point all `responsiveFormulas` have been refreshed.
     * @param {Object} context The context object used by `responsiveConfig` expressions.
     * @since 5.0.1
     */
 
    /**
     * @event responsiveupdate
     * Fires after {@link Ext.mixin.Responsive} has performed updates in response to
     * dynamic changes.
     * @param {Object} context The context object used by `responsiveConfig` expressions.
     * @since 5.0.1
     */
 
    /**
     * @event collapse
     * Fires when a Component is collapsed (e.g., a panel).
     * @param {Ext.Component} component
     */
 
    /**
     * @event expand
     * Fires when a Component is expanded (e.g., a panel).
     * @param {Ext.Component} component
     */
 
    /**
     * @event hide
     * Fires when a Component is hidden.
     * @param {Ext.Component} component
     */
 
    /**
     * @event idle
     * Fires when an event handler finishes its run, just before returning to
     * browser control.
     * 
     * This includes DOM event handlers, Ajax (including JSONP) event handlers,
     * and {@link Ext.util.TaskRunner TaskRunners}
     * 
     * When called at the tail of a DOM event, the event object is passed as the
     * sole parameter.
     * 
     * This can be useful for performing cleanup, or update tasks which need to
     * happen only after all code in an event handler has been run, but which
     * should not be executed in a timer due to the intervening browser
     * reflow/repaint which would take place.
     */
 
    /**
     * @event onlinechange
     * Fires when the online status of the page changes. See {@link Ext#method-isOnline}
     * @param {Boolean} online `true` if in an online state.
     *
     * @since 6.2.1
     */
 
    /**
     * @event removed
     * Fires when a Component is removed from a Container.
     * @param {Ext.Component} component
     */
 
    /**
     * @event resize
     * Fires when the browser window is resized.  To avoid running resize handlers
     * too often resulting in sluggish window resizing, the resize event uses a buffer
     * of 100 milliseconds in the Classic toolkit, and fires on animation frame
     * in the Modern toolkit.
     * @param {Number} width The new width
     * @param {Number} height The new height
     */
 
    /**
     * @event show
     * Fires when a Component is shown.
     * @param {Ext.Component} component
     */
    
    /**
     * @event beforebindnotify
     * Fires before a scheduled set of bindings are fired. This allows interested parties
     * to react and do any required work.
     * @param {Ext.util.Scheduler} scheduler The scheduler triggering the bindings.
     * 
     * @private
     * @since 5.1.0
     */
    
    /**
     * @event mousedown
     * A mousedown listener on the document that is immune to stopPropagation()
     * used in cases where we need to know if a mousedown event occurred on the
     * document regardless of whether some other handler tried to stop it.  An
     * example where this is useful is a menu that needs to be hidden whenever
     * there is a mousedown event on the document.
     * @param {Ext.event.Event} e The event object
     */
 
    /**
     * @property {Object} idleEventMask 
     * This object holds event names for events that should not trigger an `idle` event
     * following their dispatch.
     * @private
     * @since 5.0.0
     */
    idleEventMask: {
        mousemove: 1,
        touchmove: 1,
        pointermove: 1,
        MSPointerMove: 1,
        unload: 1
    },
 
    constructor: function() {
        var me = this;
 
        me.callParent();
 
        Ext.onInternalReady(function() {
            // using a closure here instead of attaching the event directly to the 
            // attachListeners method gives us a chance to override the method 
            me.attachListeners();
        });
    },
 
    attachListeners: function() {
        var me = this,
            win = Ext.getWin(),
            doc = Ext.getDoc(),
            docElement, bufferedFn,
            winListeners = {
                scope: me,
                resize: {
                    fn: 'fireResize'
                }
            };
 
        me.onlineState = Ext.isOnline();
        
        // Capture width/height to compare later in fireResize 
        me.curHeight = curHeight = Ext.Element.getViewportHeight();
        me.curWidth = curWidth = Ext.Element.getViewportWidth();
 
        win.on({
            scope: me,
            online: 'handleOnlineChange',
            offline: 'handleOnlineChange'
        });
        
        // This function is not entirely harmless but since it is not directly related 
        // to any component, element, or other destructible entity and effectively cannot 
        // be cleaned up, we have to pretend it's OK for this timer to go unnoticed. 
        //<debug> 
        me.fireResize.$skipTimerCheck = true;
        //</debug> 
        
        // In IE9- when using legacy onresize event via attachEvent or onresize property, 
        // the event may fire for *content size changes* as well as actual document view 
        // size changes. See this: https://msdn.microsoft.com/en-us/library/ms536959(v=vs.85).aspx 
        // and this: http://stackoverflow.com/questions/1852751/window-resize-event-firing-in-internet-explorer 
        // The amount of these events firing all at once can be entirely staggering, and they 
        // often happen during layouts so we have to be über careful to execute as few JavaScript 
        // statements as possible to improve overall framework performance. 
        if (Ext.isIE8) {
            bufferedFn = Ext.Function.createBuffered(me.fireResize, me.resizeBuffer, me);
            docElement = doc.dom.documentElement;
            
            win.dom.attachEvent('onresize', function(e) {
                var w = docElement.clientWidth,
                    h = docElement.clientHeight;
                
                // Ext.GlobalEvents is never destroyed so closing over it is no big deal 
                if (!== me.curWidth || h !== me.curHeight) {
                    bufferedFn();
                }
            });
        }
        else {
            // CSS layouts only require buffering to the next anmation frame 
            if (Ext.isModern) {
                winListeners.resize.onFrame = true;
            }
            else {
                winListeners.resize.buffer = me.resizeBuffer;
            };
            
            win.on(winListeners);
        }
        
        doc.on('mousedown', 'fireMouseDown', me);
    },
 
    fireMouseDown: function(e) {
        this.fireEvent('mousedown', e);
    },
 
    fireResize: function() {
        var me = this,
            Element = Ext.Element,
            w = Element.getViewportWidth(),
            h = Element.getViewportHeight();
        
        // In IE the resize event will sometimes fire even though the w/h are the same. 
        if (me.curHeight !== h || me.curWidth !== w) {
            me.curHeight = h;
            me.curWidth = w;
            
            if (me.hasListeners.resize) {
                me.fireEvent('resize', w, h);
            }
        }
    },
 
    handleOnlineChange: function() {
        var online = Ext.isOnline();
        if (online !== this.onlineState) {
            this.onlineState = online;
            this.fireEvent('onlinechange', online);
        }
    }
 
}, function(GlobalEvents) {
    /**
     * @member Ext
     * @method on
     * Shorthand for {@link Ext.GlobalEvents#addListener}.
     * @inheritdoc Ext.mixin.Observable#addListener
     */
    Ext.on = function() {
        return GlobalEvents.addListener.apply(GlobalEvents, arguments);
    };
 
    /**
     * @member Ext
     * @method un
     * Shorthand for {@link Ext.GlobalEvents#removeListener}.
     * @inheritdoc Ext.mixin.Observable#removeListener
     */
    Ext.un = function() {
        return GlobalEvents.removeListener.apply(GlobalEvents, arguments);
    };
 
    /**
     * @member Ext
     * @method fireEvent
     * Shorthand for {@link Ext.GlobalEvents#fireEvent}.
     * @inheritdoc Ext.mixin.Observable#fireEvent
     *
     * @since 6.2.0
     */
    Ext.fireEvent = function() {
        return GlobalEvents.fireEvent.apply(GlobalEvents, arguments);
    };
});