/**
 * A class that manages a group of {@link Ext.Component#floating} Components and provides z-order management,
 * and Component activation behavior, including masking below the active (topmost) Component.
 *
 * {@link Ext.Component#floating Floating} Components which are rendered directly into the document (such as
 * {@link Ext.window.Window Window}s) which are {@link Ext.Component#method-show show}n are managed by a
 * {@link Ext.WindowManager global instance}.
 *
 * {@link Ext.Component#floating Floating} Components which are descendants of {@link Ext.Component#floating floating}
 * *Containers* (for example a {@link Ext.view.BoundList BoundList} within an {@link Ext.window.Window Window},
 * or a {@link Ext.menu.Menu Menu}), are managed by a ZIndexManager owned by that floating Container. Therefore
 * ComboBox dropdowns within Windows will have managed z-indices guaranteed to be correct, relative to the Window.
 */
Ext.define('Ext.ZIndexManager', {
    alternateClassName: 'Ext.WindowGroup',

    statics: {
        zBase : 9000
    },

    constructor: function(container) {
        var me = this;

        me.list = {};
        me.zIndexStack = [];
        me.front = null;

        if (container) {

            // This is the ZIndexManager for an Ext.container.Container, base its zseed on the zIndex of the Container's element
            if (container.isContainer) {
                container.on('resize', me._onContainerResize, me);
                me.zseed = Ext.Number.from(me.rendered ? container.getEl().getStyle('zIndex') : undefined, me.getNextZSeed());
                // The containing element we will be dealing with (eg masking) is the content target
                me.targetEl = container.getTargetEl();
                me.container = container;
            }
            // This is the ZIndexManager for a DOM element
            else {
                Ext.EventManager.onWindowResize(me._onContainerResize, me);
                me.zseed = me.getNextZSeed();
                me.targetEl = Ext.get(container);
            }
        }
        // No container passed means we are the global WindowManager. Our target is the doc body.
        // DOM must be ready to collect that ref.
        else {
            Ext.EventManager.onWindowResize(me._onContainerResize, me);
            me.zseed = me.getNextZSeed();
            Ext.onDocumentReady(function() {
                me.targetEl = Ext.getBody();
            });
        }
    },

    getNextZSeed: function() {
        return (Ext.ZIndexManager.zBase += 10000);
    },

    setBase: function(baseZIndex) {
        this.zseed = baseZIndex;
        var result = this.assignZIndices();
        this._activateLast();
        return result;
    },

    // @private
    assignZIndices: function() {
        var a = this.zIndexStack,
            len = a.length,
            i = 0,
            zIndex = this.zseed,
            comp,
            topModal;

        for (; i < len; i++) {
            comp = a[i];
            if (comp && !comp.hidden) {

                // Setting the zIndex of a Component returns the topmost zIndex consumed by
                // that Component.
                // If it's just a plain floating Component such as a BoundList, then the
                // return value is the passed value plus 10, ready for the next item.
                // If a floating *Container* has its zIndex set, it re-orders its managed
                // floating children, starting from that new base, and returns a value 10000 above
                // the highest zIndex which it allocates.
                zIndex = comp.setZIndex(zIndex);
                if (comp.modal) {
                    topModal = comp;
                }
            }
        }
        
        // If we encountered a modal in our reassigment, ensure our modal mask is just below it.
        if (topModal) {
            this._showModalMask(topModal)
        }
        return zIndex;
    },

    // @private
    _setActiveChild: function(comp, oldFront) {
        var front = this.front,
            oldPreventFocus = comp.preventFocusOnActivate;

        if (comp !== front) {

            if (front && !front.destroying) {
                front.setActive(false, comp);
            }
            this.front = comp;
            if (comp && comp != oldFront) {

                // If the previously active comp did not take focus, then do not disturb focus state by focusing the new front
                comp.preventFocusOnActivate = comp.preventFocusOnActivate || oldFront && (oldFront.preventFocusOnActivate || !oldFront.focusOnToFront);

                comp.setActive(true);
                
                // If the modal mask was utilized by the outgoing front component, reposition it.
                if (comp.modal) {
                    this._showModalMask(comp);
                }

                // Restore the new front's focusing flag
                comp.preventFocusOnActivate = oldPreventFocus;
            }
        }
    },

    onComponentHide: function(comp){
        this._activateLast();
    },

    // @private
    _activateLast: function() {
        var me = this,
            stack = me.zIndexStack,
            i = stack.length - 1,
            comp;

        // Go down through the z-index stack.
        // Activate the next visible one down.
        // If that was modal, then we're done
        for (; i >= 0 && stack[i].hidden; --i);

        // The loop found a visible floater to activate
        if ((comp = stack[i])) {
            me._setActiveChild(comp, me.front);
            if (comp.modal) {
                return;
            }
        }
        // No other floater to activate, just deactivate the current one
        else {
            if (me.front && !me.front.destroying) {
                me.front.setActive(false);
            }
            me.front = null;
        }

        // If the new top one was not modal, keep going down to find the next visible
        // modal one to shift the modal mask down under
        for (; i >= 0; --i) {
            comp = stack[i];
            // If we find a visible modal further down the zIndex stack, move the mask to just under it.
            if (comp.isVisible() && comp.modal) {
                me._showModalMask(comp);
                return;
            }
        }

        // No visible modal Component was found in the run down the stack.
        // So hide the modal mask
        me._hideModalMask();
    },

    _showModalMask: function(comp) {
        var me = this,
            zIndex = comp.el.getStyle('zIndex') - 4,
            maskTarget = comp.floatParent ? comp.floatParent.getTargetEl() : comp.container,
            mask = me.mask,
            shim = me.maskShim,
            viewSize;

        if (!mask) {
            if (Ext.isIE6) {
                shim = me.maskShim = Ext.getBody().createChild({
                    tag: 'iframe',
                    cls : Ext.baseCSSPrefix + 'shim ' + Ext.baseCSSPrefix + 'mask-shim'
                });
                shim.setVisibilityMode(Ext.Element.DISPLAY);
            }

            // Create the mask at zero size so that it does not affect upcoming target measurements.
            mask = me.mask = Ext.getBody().createChild({
                cls: Ext.baseCSSPrefix + 'mask',
                style: 'height:0;width:0'
            });
            mask.setVisibilityMode(Ext.Element.DISPLAY);
            mask.on('click', me._onMaskClick, me);
        }
        
        mask.maskTarget = maskTarget;
        viewSize = me.getMaskBox();

        if (shim) {
            shim.setStyle('zIndex', zIndex);
            shim.show();
            shim.setBox(viewSize);
        }
        mask.setStyle('zIndex', zIndex);

        // setting mask box before showing it in an IE7 strict iframe within a quirks page
        // can cause body scrolling [EXTJSIV-6219]
        mask.show();
        mask.setBox(viewSize);
    },

    _hideModalMask: function() {
        var mask = this.mask,
            maskShim = this.maskShim;

        if (mask && mask.isVisible()) {
            mask.maskTarget = undefined;
            mask.hide();
            if (maskShim) {
                maskShim.hide();
            }
        }
    },

    _onMaskClick: function() {
        if (this.front) {
            this.front.focus();
        }
    },
    
    getMaskBox: function(){
        var maskTarget = this.mask.maskTarget;
        if (maskTarget.dom === document.body) {
            return {
                height: Math.max(document.body.scrollHeight, Ext.dom.Element.getDocumentHeight()),
                width: Math.max(document.body.scrollWidth, document.documentElement.clientWidth),
                x: 0,
                y: 0
            };
        } else {
            return maskTarget.getBox();
        } 
    },

    _onContainerResize: function() {
        var me = this,
            mask = me.mask,
            maskShim = me.maskShim,
            viewSize;

        if (mask && mask.isVisible()) {

            // At the new container size, the mask might be *causing* the scrollbar, so to find the valid
            // client size to mask, we must temporarily unmask the parent node.
            mask.hide();
            if (maskShim) {
                maskShim.hide();
            }

            viewSize = me.getMaskBox();
            if (maskShim) {
                maskShim.setSize(viewSize);
                maskShim.show();
            }
            mask.setSize(viewSize);
            mask.show();
        }
    },

    /**
     * Registers a floating {@link Ext.Component} with this ZIndexManager. This should not
     * need to be called under normal circumstances. Floating Components (such as Windows,
     * BoundLists and Menus) are automatically registered with a
     * {@link Ext.Component#zIndexManager zIndexManager} at render time.
     *
     * Where this may be useful is moving Windows between two ZIndexManagers. For example,
     * to bring the Ext.MessageBox dialog under the same manager as the Desktop's
     * ZIndexManager in the desktop sample app:
     *
     *     MyDesktop.getDesktop().getManager().register(Ext.MessageBox);
     *
     * @param {Ext.Component} comp The Component to register.
     */
    register : function(comp) {
        var me = this,
            compAfterHide = comp.afterHide;
        
        if (comp.zIndexManager) {
            comp.zIndexManager.unregister(comp);
        }
        comp.zIndexManager = me;

        me.list[comp.id] = comp;
        me.zIndexStack.push(comp);
        
        // Hook into Component's afterHide processing
        comp.afterHide = function() {
            compAfterHide.apply(comp, arguments);
            me.onComponentHide(comp);
        };
    },

    /**
     * Unregisters a {@link Ext.Component} from this ZIndexManager. This should not
     * need to be called. Components are automatically unregistered upon destruction.
     * See {@link #register}.
     * @param {Ext.Component} comp The Component to unregister.
     */
    unregister : function(comp) {
        var me = this,
            list = me.list;
        
        delete comp.zIndexManager;
        if (list && list[comp.id]) {
            delete list[comp.id];
            
            // Relinquish control of Component's afterHide processing
            delete comp.afterHide;
            Ext.Array.remove(me.zIndexStack, comp);

            // Destruction requires that the topmost visible floater be activated. Same as hiding.
            me._activateLast();
        }
    },

    /**
     * Gets a registered Component by id.
     * @param {String/Object} id The id of the Component or a {@link Ext.Component} instance
     * @return {Ext.Component}
     */
    get : function(id) {
        return id.isComponent ? id : this.list[id];
    },

   /**
     * Brings the specified Component to the front of any other active Components in this ZIndexManager.
     * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
     * @return {Boolean} True if the dialog was brought to the front, else false
     * if it was already in front
     */
    bringToFront : function(comp, preventFocus) {
        var me = this,
            result = false,
            zIndexStack = me.zIndexStack;
        
        comp = me.get(comp);
        if (comp !== me.front) {
            Ext.Array.remove(zIndexStack, comp);
            if (comp.preventBringToFront) {
                // this takes care of cases where a load mask should be displayed under a floated component
                zIndexStack.unshift(comp);
            } else {
                // the default behavior is to push onto the stack
                zIndexStack.push(comp);
            }

            me.assignZIndices();

            // Activate new topmost
            if (!preventFocus) {
                me._activateLast();
            }
            result = true;
            me.front = comp;
            
            // If new topmost is modal, ensure the mask is there
            if (comp.modal) {
                me._showModalMask(comp);
            }
        }
        return result;
    },

    /**
     * Sends the specified Component to the back of other active Components in this ZIndexManager.
     * @param {String/Object} comp The id of the Component or a {@link Ext.Component} instance
     * @return {Ext.Component} The Component
     */
    sendToBack : function(comp) {
        var me = this;
        
        comp = me.get(comp);
        Ext.Array.remove(me.zIndexStack, comp);
        me.zIndexStack.unshift(comp);
        me.assignZIndices();
        this._activateLast();
        return comp;
    },

    /**
     * Hides all Components managed by this ZIndexManager.
     */
    hideAll : function() {
        var list = this.list,
            item,
            id;
            
        for (id in list) {
            if (list.hasOwnProperty(id)) {
                item = list[id];
                if (item.isComponent && item.isVisible()) {
                    item.hide();
                }
            }
        }
    },

    /**
     * @private
     * Temporarily hides all currently visible managed Components. This is for when
     * dragging a Window which may manage a set of floating descendants in its ZIndexManager;
     * they should all be hidden just for the duration of the drag.
     */
    hide: function() {
        var i = 0,
            stack = this.zIndexStack,
            len = stack.length,
            comp;

        this.tempHidden = [];
        for (; i < len; i++) {
            comp = stack[i];
            if (comp.isVisible()) {
                this.tempHidden.push(comp);
                comp.el.hide();
                comp.hidden = true;
            }
        }
    },

    /**
     * @private
     * Restores temporarily hidden managed Components to visibility.
     */
    show: function() {
        var i = 0,
            tempHidden = this.tempHidden,
            len = tempHidden ? tempHidden.length : 0,
            comp;

        for (; i < len; i++) {
            comp = tempHidden[i];
            comp.el.show();
            comp.hidden = false;
            comp.setPosition(comp.x, comp.y);
        }
        delete this.tempHidden;
    },

    /**
     * Gets the currently-active Component in this ZIndexManager.
     * @return {Ext.Component} The active Component
     */
    getActive : function() {
        return this.front;
    },

    /**
     * Returns zero or more Components in this ZIndexManager using the custom search function passed to this method.
     * The function should accept a single {@link Ext.Component} reference as its only argument and should
     * return true if the Component matches the search criteria, otherwise it should return false.
     * @param {Function} fn The search function
     * @param {Object} [scope] The scope (this reference) in which the function is executed.
     * Defaults to the Component being tested. That gets passed to the function if not specified.
     * @return {Array} An array of zero or more matching windows
     */
    getBy : function(fn, scope) {
        var r = [],
            i = 0,
            stack = this.zIndexStack,
            len = stack.length,
            comp;

        for (; i < len; i++) {
            comp = stack[i];
            if (fn.call(scope||comp, comp) !== false) {
                r.push(comp);
            }
        }
        return r;
    },

    /**
     * Executes the specified function once for every Component in this ZIndexManager, passing each
     * Component as the only parameter. Returning false from the function will stop the iteration.
     * @param {Function} fn The function to execute for each item
     * @param {Object} [scope] The scope (this reference) in which the function
     * is executed. Defaults to the current Component in the iteration.
     */
    each : function(fn, scope) {
        var list = this.list,
            id,
            comp;
            
        for (id in list) {
            if (list.hasOwnProperty(id)) {
                comp = list[id];
                if (comp.isComponent && fn.call(scope || comp, comp) === false) {
                    return;
                }
            }
        }
    },

    /**
     * Executes the specified function once for every Component in this ZIndexManager, passing each
     * Component as the only parameter. Returning false from the function will stop the iteration.
     * The components are passed to the function starting at the bottom and proceeding to the top.
     * @param {Function} fn The function to execute for each item
     * @param {Object} scope (optional) The scope (this reference) in which the function
     * is executed. Defaults to the current Component in the iteration.
     */
    eachBottomUp: function (fn, scope) {
        var stack = this.zIndexStack,
            i = 0,
            len = stack.length,
            comp;

        for (; i < len; i++) {
            comp = stack[i];
            if (comp.isComponent && fn.call(scope || comp, comp) === false) {
                return;
            }
        }
    },

    /**
     * Executes the specified function once for every Component in this ZIndexManager, passing each
     * Component as the only parameter. Returning false from the function will stop the iteration.
     * The components are passed to the function starting at the top and proceeding to the bottom.
     * @param {Function} fn The function to execute for each item
     * @param {Object} [scope] The scope (this reference) in which the function
     * is executed. Defaults to the current Component in the iteration.
     */
    eachTopDown: function (fn, scope) {
        var stack = this.zIndexStack,
            i = stack.length,
            comp;

        for (; i-- > 0; ) {
            comp = stack[i];
            if (comp.isComponent && fn.call(scope || comp, comp) === false) {
                return;
            }
        }
    },

    destroy: function() {
        var me   = this,
            list = me.list,
            comp,
            id;

        for (id in list) {
            if (list.hasOwnProperty(id)) {
                comp = list[id];

                if (comp.isComponent) {
                    comp.destroy();
                }
            }
        }

        delete me.zIndexStack;
        delete me.list;
        delete me.container;
        delete me.targetEl;
    }
}, function() {
    /**
     * @class Ext.WindowManager
     * @extends Ext.ZIndexManager
     *
     * The default global floating Component group that is available automatically.
     *
     * This manages instances of floating Components which were rendered programatically without
     * being added to a {@link Ext.container.Container Container}, and for floating Components
     * which were added into non-floating Containers.
     * 
     * *Floating* Containers create their own instance of ZIndexManager, and floating Components
     * added at any depth below there are managed by that ZIndexManager.
     *
     * @singleton
     */
    Ext.WindowManager = Ext.WindowMgr = new this();
});