/**
 * Provides a common registry of all menus on a page.
 * @singleton
 */
Ext.define('Ext.menu.Manager', {
    singleton: true,
    requires: [
        'Ext.util.MixedCollection',
        'Ext.util.KeyMap'
    ],
    alternateClassName: 'Ext.menu.MenuMgr',
 
    uses: ['Ext.menu.Menu'],
    
    menuSelector: '.' + Ext.baseCSSPrefix + 'menu',
 
    menus: {},
    groups: {},
    attached: false,
    lastShow: new Date(),
 
    constructor: function() {
        this.active = new Ext.util.MixedCollection();
    },
 
    init: function() {
        var me = this;
 
        // Only want to call this once 
        if (!me.initialized) {
            // If document is ready, this will be called immediately, otherwise 
            // wait until we are 
            Ext.onReady(function() {
                Ext.getDoc().addKeyListener(27, me.hideAll, me);
            });
        }
        me.initialized = true;
    },
 
    /**
     * Hides all menus that are currently visible
     * @return {Boolean} success True if any active menus were hidden.
     */
    hideAll: function() {
        var active = this.active,
            menus, m, mLen;
 
        if (active.length > 0) {
            menus = Ext.Array.slice(active.items);
            mLen  = menus.length;
 
            for (= 0; m < mLen; m++) {
                menus[m].hide();
            }
 
            return true;
        }
        return false;
    },
 
    onHide: function(m) {
        var me = this,
            active = me.active;
            
        active.remove(m);
        if (active.length < 1) {
            Ext.un('mousedown', me.onMouseDown, me);
            me.attached = false;
        }
    },
 
    onShow: function(m) {
        var me = this,
            active   = me.active,
            attached = me.attached;
 
        me.lastShow = new Date();
        active.add(m);
        if (!attached) {
            Ext.on('mousedown', me.onMouseDown, me, {
                // On IE we have issues with the menu stealing focus at certain points 
                // during the head, so give it a short buffer 
                buffer: Ext.isIE9m ? 10 : undefined
            });
            me.attached = true;
        }
        m.toFront();
    },
 
    onBeforeHide: function(m) {
        if (m.activeChild) {
            m.activeChild.hide();
        }
        if (m.autoHideTimer) {
            clearTimeout(m.autoHideTimer);
            delete m.autoHideTimer;
        }
    },
 
    onBeforeShow: function(m) {
        var active = this.active,
            parentMenu = m.parentMenu;
            
        active.remove(m);
        if (!parentMenu && !m.allowOtherMenus) {
            this.hideAll();
        }
        else if (parentMenu && parentMenu.activeChild && m != parentMenu.activeChild) {
            parentMenu.activeChild.hide();
        }
    },
 
    // @private 
    onMouseDown: function(e) {
        var me = this,
            active = me.active,
            activeMenuCount = active.length,
            lastShow = me.lastShow,
            i;
 
        if (Ext.Date.getElapsed(lastShow) > 50 && activeMenuCount) {
            // Because we use a buffer in IE, the target may have been removed from the 
            // DOM by the time we get here, so the selector will never find the menu. In this 
            // case, it's safer to not hide than menus than to do so 
            if (Ext.isIE9m && !Ext.getDoc().contains(e.target)) {
                return;
            }
            else {
                // If any active menus are an ancestor of the target element, we don't hide 
                for (= 0; i < activeMenuCount; i++) {
                    if (active.items[i].owns(e.target)) {
                        return;
                    }
                }
            }
            me.hideAll();
        }
    },
 
    // @private 
    register: function(menu) {
        var me = this;
 
        me.init();
 
        if (menu.floating) {
            me.menus[menu.id] = menu;
            menu.on({
                beforehide: me.onBeforeHide,
                hide: me.onHide,
                beforeshow: me.onBeforeShow,
                show: me.onShow,
                scope: me
            });
        }
    },
 
    /**
     * Returns a {@link Ext.menu.Menu} object
     * @param {String/Object} menu The string menu id, an existing menu object reference, or a Menu config that will
     * be used to generate and return a new Menu this.
     * @param {Object} [config] A configuration to use when creating the menu.
     * @return {Ext.menu.Menu} The specified menu, or null if none are found
     */
    get: function(menu, config) {
        var menus = this.menus;
        
        if (typeof menu == 'string') { // menu id 
            if (!menus) {  // not initialized, no menus to return 
                menu = null;
            } else {
                menu = menus[menu];
            }
        } else if (Ext.isArray(menu)) { // array of menu items 
            config = Ext.apply({items:menu}, config);
            menu = new Ext.menu.Menu(config);
        } else if (!menu.isComponent) { // otherwise, must be a config 
            config = Ext.apply({}, menu, config);
            menu = Ext.ComponentManager.create(config, 'menu');
        }
        return menu;
    },
 
    // @private 
    unregister: function(menu) {
        var me = this,
            menus = me.menus,
            active = me.active;
 
        delete menus[menu.id];
        active.remove(menu);
        menu.un({
            beforehide: me.onBeforeHide,
            hide: me.onHide,
            beforeshow: me.onBeforeShow,
            show: me.onShow,
            scope: me
        });
    },
 
    // @private 
    registerCheckable: function(menuItem) {
        var groups  = this.groups,
            groupId = menuItem.group;
 
        if (groupId) {
            if (!groups[groupId]) {
                groups[groupId] = [];
            }
 
            groups[groupId].push(menuItem);
        }
    },
 
    // @private 
    unregisterCheckable: function(menuItem) {
        var groups  = this.groups,
            groupId = menuItem.group;
 
        if (groupId) {
            Ext.Array.remove(groups[groupId], menuItem);
        }
    },
 
    onCheckChange: function(menuItem, state) {
        var groups  = this.groups,
            groupId = menuItem.group,
            i       = 0,
            group, ln, curr;
 
        if (groupId && state) {
            group = groups[groupId];
            ln = group.length;
            for (; i < ln; i++) {
                curr = group[i];
                if (curr != menuItem) {
                    curr.setChecked(false);
                }
            }
        }
    }
});