/**
 * A base class for all menu items that require menu-related functionality such as click handling,
 * sub-menus, icons, etc.
 *
 *     @example
 *     var mainPanel = Ext.create('Ext.Panel', {
 *         fullscreen: true,
 *
 *         items: {
 *             xtype: 'menu',
 *             floated: false,
 *             docked: 'left',
 *             items: [{
 *                 text: 'regular item 1'
 *             },{
 *                 text: 'regular item 2'
 *             },{
 *                 text: 'regular item 3'
 *             }]
 *         }
 *     });
 */
Ext.define('Ext.menu.Item', {
    extend: 'Ext.Component',
    alias: 'widget.menuitem',
    alternateClassName: 'Ext.menu.TextItem',
 
    /**
     * @property {Boolean} isMenuItem
     * `true` in this class to identify an object as an instantiated Menu Item, or subclass thereof.
     */
    isMenuItem: true,
 
    /**
     * @cfg {Number} menuExpandDelay
     * The delay in milliseconds before this item's sub-menu expands after this item is moused over.
     */
    menuExpandDelay: 200,
 
    /**
     * @cfg {Number} menuHideDelay
     * The delay in milliseconds before this item's sub-menu hides after this item is moused out.
     */
    menuHideDelay: 200,
 
    /**
     * @cfg {Object} scope
     * The scope (`this` refeence) in which the configured {@link #handler} will be executed,
     * unless the scope is a ViewController method nmame.
     * @accessor
     */
    scope: null,
 
    /**
     * @cfg {Boolean} destroyMenu
     * Whether or not to destroy any associated sub-menu when this item is destroyed.
     */
    destroyMenu: true,
 
    /**
    * @cfg {Number} clickHideDelay
    * The delay in milliseconds to wait before hiding the menu after clicking the menu item.
    * This only has an effect when `hideOnClick: true`.
    */
    clickHideDelay: 0,
 
    /**
     * @cfg {Boolean} [hideOnClick=true]
     * Whether to not to hide the owning menu when this item is clicked.
     */
    hideOnClick: true,
 
    config: {
        /**
         * @cfg {String} [href='#']
         * The href attribute to use for the underlying anchor link.
         */
        href: null,
 
        /**
         * @cfg {String} target
         * The target attribute to use for the underlying anchor link.
         */
        target: null,
 
        /**
         * @cfg {Function/String} handler
         * A function called when the menu item is clicked (can be used instead 
         * of {@link #click} event).
         * @cfg {Ext.menu.Item} handler.item The item that was clicked
         * @cfg {Ext.event.Event} handler.e The underlying {@link Ext.event.Event}.
         * @controllable
         */
        handler: null,
 
        /**
         * @cfg {String} [text]
         * The text to display in the menu item.
         */
        text: null,
 
        /**
         * @cfg {Ext.menu.Menu/Object} menu
         * Either an instance of {@link Ext.menu.Menu} or a config object for 
         * an {@link Ext.menu.Menu} which will act as a sub-menu to this item.
         */
        menu: {
            lazy: true,
            $value: null
        },
 
        /**
         * @cfg {String} menuAlign
         * The default 
         * {@link Ext.util.Positionable#getAlignToXY Ext.util.Positionable.getAlignToXY} anchor
         * position value for this item's sub-menu relative to this item's position.
         */
        menuAlign: 'tl-tr?',
 
        /**
         * @cfg {String} [icon]
         * The url of an icon to display as the background image of the icon.
         */
        icon: null,
 
        /**
         * @cfg {String} [iconCls]
         * The CSS class to apply to the icon.
         */
        iconCls: null,
 
        /**
         * @cfg {'left'/'right'}
         * The position of the icon relative to the text
         */
        iconAlign: 'left',
 
        /**
         * @cfg {Boolean} [indented=true]
         * By default menu items reserve space at their start for an icon, depending on their
         * containing menu's {@link Ext.menu.Menu#indented} value.
         *
         * This option allows the indented behavior to be overridden for an individual menu item.
         */
        indented: null,
 
        /**
         * @cfg {Boolean} [separator=false]
         * If `true`, this item places an {@link Ext.menu.Separator} above 
         * itself unless it is the first visible item.
         */
        separator: null
    },
 
    inheritUi: true,
 
    ariaRole: 'menuitem',
 
    focusable: true,
 
    classCls: Ext.baseCSSPrefix + 'menuitem',
 
    activeCls: Ext.baseCSSPrefix + 'active',
    hasLeftIconCls: Ext.baseCSSPrefix + 'has-left-icon',
    hasRightIconCls: Ext.baseCSSPrefix + 'has-right-icon',
    hasArrowCls: Ext.baseCSSPrefix + 'has-arrow',
    hasHrefCls: Ext.baseCSSPrefix + 'has-href',
 
    isMenuOwner: true,
 
 
    template: [{
        reference: 'bodyElement',
        tag: 'a',
        href: '#',
        cls: Ext.baseCSSPrefix + 'body-el ' +
            Ext.baseCSSPrefix + 'unselectable',
        children: [{
            reference: 'leftIconWrapElement',
            cls: Ext.baseCSSPrefix + 'left-icon-wrap-el ' +
            Ext.baseCSSPrefix + 'icon-wrap-el',
            children: [{
                reference: 'leftIconElement',
                cls: Ext.baseCSSPrefix + 'left-icon-el ' +
                Ext.baseCSSPrefix + 'icon-el ' +
                Ext.baseCSSPrefix + 'font-icon'
            }]
        }, {
            html: '\u00a0',
            reference: 'textElement',
            cls: Ext.baseCSSPrefix + 'text-el'
        }, {
            reference: 'rightIconWrapElement',
            cls: Ext.baseCSSPrefix + 'right-icon-wrap-el ' +
            Ext.baseCSSPrefix + 'icon-wrap-el',
            children: [{
                reference: 'rightIconElement',
                cls: Ext.baseCSSPrefix + 'right-icon-el ' +
                Ext.baseCSSPrefix + 'icon-el ' +
                Ext.baseCSSPrefix + 'font-icon'
            }]
        }, {
            reference: 'arrowElement',
            cls: Ext.baseCSSPrefix + 'arrow-el ' +
            Ext.baseCSSPrefix + 'font-icon'
        }]
    }],
 
    ariaEl: 'bodyElement',
 
    focusEl: 'bodyElement',
 
    initialize: function() {
        this.callParent();
        this.syncHasIconCls();
        
        if (Ext.supports.Touch) {
            this.handleTouch();
        }
    },
 
    getFocusClsEl: function() {
        return this.el;
    },
 
    /**
     * Expand this item's menu.
     * @param {Ext.event.Event} event Optional. Menus auto focus when invoked by key events.
     */
    expandMenu: function(event) {
        var me = this,
            menu = me.getMenu();
 
        // An item can be focused (active), but disabled.
        // Disabled items must not action on click (or left/right arrow)
        // http://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#menu
        // "Disabled menu items receive focus but have no action when Enter or Left Arrow/Right 
        // Arrow is pressed."
        if (!me.getDisabled() && menu) {
 
            // Needs an upward link
            menu.parentMenu = me.parentMenu;
 
            // hideOnClick makes no sense when there's a child menu
            me.hideOnClick = false;
 
            if (menu.isVisible()) {
                // Keyboard events should focus the first menu item even if it was already expanded
                if (event && event.type === 'keydown') {
                    menu.focus();
                }
            }
            else {
                // Pointer-invoked menus do not auto focus, key invoked ones do.
                menu.autoFocus = !event || !event.pointerType;
                menu.showBy(me, me.getMenuAlign(), {
                    axisLock: true  // Flips left/right when constrained
                    // instead of covering the menu.
                });
            }
        }
    },
 
    getRefItems: function(deep) {
    // This is not a Container, so needs a special implementation to
    // return its subtree.
        var menu = this.getMenu(),
            items;
 
        if (menu) {
            items = menu.getRefItems(deep);
            items.unshift(menu);
        }
 
        return items || [];
    },
 
    onFocusEnter: function(e) {
        var me = this;
 
        me.callParent([e]);
 
        // We do not refuse activation if the Item is disabled.
        // http://www.w3.org/TR/2013/WD-wai-aria-practices-20130307/#menu
        // "Disabled menu items receive focus but have no action when Enter or Left Arrow/Right 
        // Arrow is pressed."
        me.addCls(me.activeCls);
 
        me.activated = true;
 
        if (me.hasListeners.activate) {
            me.fireEvent('activate', me);
        }
 
        if (me.parentMenu) {
            me.parentMenu.setActiveItem(me);
        }
    },
 
    onFocusLeave: function(e) {
        var me = this,
            // Do not call the menu into existence.
            // This property is set by updateMenu.
            menu = me.menu;
 
        me.callParent([e]);
        me.removeCls(me.activeCls);
 
        if (menu) {
            menu.hide();
        }
 
        me.activated = false;
 
        if (me.hasListeners.deactivate) {
            me.fireEvent('deactivate', me);
        }
 
        if (me.parentMenu) {
            me.parentMenu.setActiveItem(null);
        }
    },
 
    onRemoved: function(destroying) {
        this.callParent([destroying]);
        this.parentMenu = null;
    },
 
    doDestroy: function() {
        var me = this;
 
        me.separatorElement = Ext.destroy(me.separatorElement);
        me.setMenu(null);
    
        me.linkClickListener = Ext.destroy(me.linkClickListener);
            
        me.callParent();
    },
 
    updateText: function(text) {
        if (text == null || text === '') {
            text = '\u00a0';
        }
 
        this.textElement.dom.firstChild.data = text;
    },
 
    applyMenu: function(menu) {
        var me = this,
            ariaDom = me.ariaEl.dom;
 
        if (menu) {
            if (menu.isMenu) {
                menu.setConstrainAlign(Ext.getBody());
                menu.ownerCmp = me;
            }
            else {
                menu = Ext.menu.Menu.create(menu, {
                    ownerCmp: me,
                    $initParent: me,
                    constrainAlign: Ext.getBody()
                });
            }
 
            ariaDom.setAttribute('aria-haspopup', true);
            ariaDom.setAttribute('aria-owns', menu.id);
        }
        else {
            ariaDom.removeAttribute('aria-haspopup');
            ariaDom.removeAttribute('aria-owns');
        }
 
        me.toggleCls(me.hasArrowCls, !!menu);
 
        return menu;
    },
 
    updateMenu: function(menu, oldMenu) {
        if (oldMenu) {
            if (this.destroyMenu) {
                Ext.destroy(oldMenu);
            }
            else {
                oldMenu.parentMenu = null;
            }
        }
 
        // A property which will only exist when the Menu has been instantiated.
        this.menu = menu;
    },
 
    updateHref: function(href) {
        this.bodyElement.dom.href = href;
        this.toggleCls(this.hasHrefCls, !!href);
    },
 
    updateTarget: function(target) {
        this.bodyElement.dom.target = target;
    },
 
    updateIcon: function(icon) {
        var me = this,
            iconElement = (me.getIconAlign() === 'left')
                ? this.leftIconElement
                : this.rightIconElement;
 
        if (icon) {
            iconElement.setStyle('background-image', 'url(' + icon + ')');
        }
        else {
            iconElement.setStyle('background-image', '');
        }
 
        if (!me.isConfiguring) {
            me.syncHasIconCls();
        }
    },
 
    updateIconCls: function(iconCls, oldIconCls) {
        var me = this,
            iconElement = (me.getIconAlign() === 'left')
                ? this.leftIconElement
                : this.rightIconElement;
 
        if (iconCls) {
            iconElement.replaceCls(oldIconCls, iconCls);
        }
        else {
            iconElement.removeCls(oldIconCls);
        }
 
        if (!me.isConfiguring) {
            me.syncHasIconCls();
        }
    },
 
    updateIconAlign: function(iconAlign) {
        if (!this.isConfiguring) {
            this.syncHasIconCls();
        }
    },
 
    updateSeparator: function(separator) {
        var me = this,
            separatorElement = me.separatorElement;
 
        if (separator) {
            if (separatorElement) {
                separatorElement.show();
            }
            else {
                me.separatorElement = separatorElement = Ext.Element.create({
                    cls: Ext.baseCSSPrefix + 'menuseparator'
                });
                me.el.dom.insertBefore(separatorElement.dom, me.el.dom.firstChild);
            }
        }
        else if (separatorElement) {
            separatorElement.hide();
        }
    },
 
    privates: {
 
        /**
        * Function to add click listener for touch devices. 
        */
        handleTouch: function() {
            var me = this,
                linkEl = me.bodyElement;
          
            me.linkClickListener = linkEl.on({
                click: me.onClick,
                capture: true,
                translate: false,
                scope: me,
                destroyable: true
            });
 
        },
 
        onSpace: function(e) {
            return this.onClick(e);
        },
 
        onClick: function(e) {
            var me = this,
                href = me.getHref(),
                clickHideDelay = me.clickHideDelay,
                browserEvent = e.browserEvent,
                handler = me.getHandler(),
                isTouchEvent = e.pointerType === 'touch',
                clickResult;
 
            if (me.linkClickListener && !isTouchEvent && e.parentEvent) {
                // If this is a touch device and event is not a touch event
                // And is wrapped with parent event then ignore it
                // as we already have another click listener for this
                e.stopEvent();
 
                return;
            }
 
            // Stop clicks on the anchor if there's no href, or we're disabled
            if ((!href || me.getDisabled()) && me.bodyElement.dom === e.getTarget('a')) {
                e.stopEvent();
 
                if (me.getDisabled()) {
                    return false;
                }
            }
 
            if (me.getDisabled() || me.handlingClick) {
                return;
            }
 
            if (me.hideOnClick && !me.getMenu()) {
                if (!clickHideDelay) {
                    me.hideParentMenus();
                }
                else {
                    me.hideParentMenusTimer = Ext.defer(me.hideParentMenus, clickHideDelay, me);
                }
            }
 
            // Click event may have destroyed the menu, don't do anything further
            clickResult = me.fireEvent('click', me, e);
 
            // Click listener could have destroyed the menu and/or item.
            if (me.destroyed) {
                return;
            }
 
            if (clickResult !== false && handler) {
                Ext.callback(handler, me.scope, [me, e], 0, me);
            }
 
            // And the handler could have done the same. We check this twice
            // because if the menu was destroyed in the click listener, the handler
            // should not have been called.
            if (me.destroyed) {
                return;
            }
 
            // We only manually need to trigger the click event if it's come from a key event 
            // and the event has not had preventDefault called.
            if (href && e.type !== 'click' && !browserEvent.defaultPrevented) {
                me.handlingClick = true;
                me.bodyElement.dom.click();
                me.handlingClick = false;
            }
 
            return clickResult;
        },
 
        /**
         * @private
         * Hides the entire floating menu tree that we are within.
         * Walks up the refOwner axis hiding each Menu instance it find until it hits
         * a non-floating ancestor.
         */
        hideParentMenus: function() {
            var menu;
 
            for (menu = this.getRefOwner();
                menu && ((menu.isMenu && menu.getFloated()) || menu.isMenuItem);
                menu = menu.getRefOwner()
            ) {
                if (menu.isMenu) {
                    menu.hide();
                }
            }
        },
 
        hasIcon: function() {
            return !!(this.getIconCls() || this.getIcon());
        },
 
        syncHasIconCls: function() {
            var me = this,
                rightCls = me.hasRightIconCls,
                leftCls = me.hasLeftIconCls,
                iconAlign = me.getIconAlign();
 
            if (me.hasIcon()) {
                if (iconAlign === 'left') {
                    me.replaceCls(rightCls, leftCls);
                }
                else if (iconAlign === 'right') {
                    me.replaceCls(leftCls, rightCls);
                }
            }
            else {
                me.removeCls([leftCls, rightCls]);
            }
        }
    }
});