/**
 * Represents a single Tab in a {@link Ext.tab.Panel TabPanel}. A Tab is simply a slightly
 * customized {@link Ext.button.Button Button}, styled to look like a tab. Tabs are optionally
 * closable, and can also be disabled. 99% of the time you will not need to create Tabs manually
 * as the framework does so automatically when you use a {@link Ext.tab.Panel TabPanel}
 */
Ext.define('Ext.tab.Tab', {
    extend: 'Ext.button.Button',
    alias: 'widget.tab',
 
    /**
     * @property {Boolean} isTab
     * `true` in this class to identify an object as an instantiated Tab, or subclass thereof.
     */
    isTab: true,
 
    baseCls: Ext.baseCSSPrefix + 'tab',
    closeElOverCls: Ext.baseCSSPrefix + 'tab-close-btn-over',
    closeElPressedCls: Ext.baseCSSPrefix + 'tab-close-btn-pressed',
 
    config: {
        /**
         * @cfg {'default'/0/1/2} rotation
         * The rotation of the tab.  Can be one of the following values:
         *
         * - `null` - use the default rotation, depending on the dock position of the tabbar
         * - `0` - no rotation
         * - `1` - rotate 90deg clockwise
         * - `2` - rotate 90deg counter-clockwise
         *
         * The default behavior of this config depends on the dock position of the tabbar:
         *
         * - `'top'` or `'bottom'` - `0`
         * - `'right'` - `1`
         * - `'left'` - `2`
         */
        rotation: 'default',
 
        /**
         * @cfg {'top'/'right'/'bottom'/'left'} tabPosition
         * The tab's position.  Users should not typically need to set this, as it is
         * configured automatically by the tab bar
         */
        tabPosition: 'top'
    },
 
    /**
     * @cfg {Boolean} closable
     * True to make the Tab start closable (the close icon will be visible).
     */
    closable: true,
 
    // The wording is chosen to be less confusing to blind users.
    /**
     * @cfg {String} [closeText="removable"]
     * The accessible text label for the close button link to be announced by screen readers
     * when the tab is focused. This text does not appear visually and is only used when
     * {@link #cfg-closable} is `true`.
     * @locale
     */
    closeText: 'removable',
 
    /**
     * @property {Boolean} active
     * Indicates that this tab is currently active. This is NOT a public configuration.
     * @readonly
     */
    active: false,
 
    /**
     * @property {Boolean} closable
     * True if the tab is currently closable
     */
 
    childEls: [
        'closeEl'
    ],
 
    scale: false,
 
    /**
     * @event activate
     * Fired when the tab is activated.
     * @param {Ext.tab.Tab} this 
     */
 
    /**
     * @event deactivate
     * Fired when the tab is deactivated.
     * @param {Ext.tab.Tab} this 
     */
 
    /**
     * @event beforeclose
     * Fires if the user clicks on the Tab's close button, but before the {@link #close}
     * event is fired. Return false from any listener to stop the close event being fired
     * @param {Ext.tab.Tab} tab The Tab object
     */
 
    /**
     * @event close
     * Fires to indicate that the tab is to be closed, usually because the user has clicked
     * the close button.
     * @param {Ext.tab.Tab} tab The Tab object
     */
 
    ariaRole: 'tab',
    tabIndex: -1,
    
    keyMap: {
        scope: 'this',
        DELETE: 'onDeleteKey'
    },
 
    _btnWrapCls: Ext.baseCSSPrefix + 'tab-wrap',
    _btnCls: Ext.baseCSSPrefix + 'tab-button',
    _baseIconCls: Ext.baseCSSPrefix + 'tab-icon-el',
    _glyphCls: Ext.baseCSSPrefix + 'tab-glyph',
    _innerCls: Ext.baseCSSPrefix + 'tab-inner',
    _textCls: Ext.baseCSSPrefix + 'tab-text',
    _noTextCls: Ext.baseCSSPrefix + 'tab-no-text',
    _hasIconCls: Ext.baseCSSPrefix + 'tab-icon',
 
    _activeCls: Ext.baseCSSPrefix + 'tab-active',
    _closableCls: Ext.baseCSSPrefix + 'tab-closable',
    overCls: Ext.baseCSSPrefix + 'tab-over',
    _pressedCls: Ext.baseCSSPrefix + 'tab-pressed',
    _disabledCls: Ext.baseCSSPrefix + 'tab-disabled',
 
    _rotateClasses: {
        1: Ext.baseCSSPrefix + 'tab-rotate-right',
        2: Ext.baseCSSPrefix + 'tab-rotate-left'
    },
 
    // a mapping of the "ui" positions.  When "rotation" is anything other than 0, a ui
    // position other than the docked side must be used.
    _positions: {
        top: {
            'default': 'top',
            0: 'top',
            1: 'left',
            2: 'right'
        },
        right: {
            'default': 'top',
            0: 'right',
            1: 'top',
            2: 'bottom'
        },
        bottom: {
            'default': 'bottom',
            0: 'bottom',
            1: 'right',
            2: 'left'
        },
        left: {
            'default': 'top',
            0: 'left',
            1: 'bottom',
            2: 'top'
        }
    },
 
    _defaultRotations: {
        top: 0,
        right: 1,
        bottom: 0,
        left: 2
    },
 
    initComponent: function() {
        var me = this;
        
        // Although WAI-ARIA spec has a provision for deleting tab panels,
        // according to accessibility experts at University of Washington
        // closable tab panels can be very confusing to vision impaired users.
        // On top of that there are some technical issues with screen readers
        // not recognizing the changed number of open tabs, so it is better
        // to avoid closable tabs in accessible applications.
        //<debug>
        if (me.closable) {
            Ext.ariaWarn(
                me,
                "Closable tabs can be confusing to users relying on Assistive Technologies " +
                "such as Screen Readers, and are not recommended in accessible applications. " +
                "Please consider setting " + me.title + " tab (" + me.id + ") to closable: false."
            );
        }
        //</debug>
        
        if (me.card) {
            me.setCard(me.card);
        }
 
        me.callParent(arguments);
    },
 
    getActualRotation: function() {
        var rotation = this.getRotation();
 
        return (rotation !== 'default') ? rotation : this._defaultRotations[this.getTabPosition()];
    },
 
    updateRotation: function() {
        this.syncRotationAndPosition();
    },
 
    updateTabPosition: function() {
        this.syncRotationAndPosition();
    },
 
    syncRotationAndPosition: function() {
        var me = this,
            rotateClasses = me._rotateClasses,
            position = me.getTabPosition(),
            rotation = me.getActualRotation(),
            oldRotateCls = me._rotateCls,
            rotateCls = me._rotateCls = rotateClasses[rotation],
            oldPositionCls = me._positionCls,
            positionCls = me._positionCls = me._positions[position][rotation];
 
        if (oldRotateCls !== rotateCls) {
            if (oldRotateCls) {
                me.removeCls(oldRotateCls);
            }
 
            if (rotateCls) {
                me.addCls(rotateCls);
            }
        }
 
        if (oldPositionCls !== positionCls) {
            if (oldPositionCls) {
                me.removeClsWithUI(oldPositionCls);
            }
 
            if (positionCls) {
                me.addClsWithUI(positionCls);
            }
 
            if (me.rendered) {
                me.updateFrame();
            }
        }
 
        if (me.rendered) {
            me.setElOrientation();
        }
    },
 
    onAdded: function(container, pos, instanced) {
        this.callParent([container, pos, instanced]);
        this.syncRotationAndPosition();
    },
 
    getTemplateArgs: function() {
        var me = this,
            result = me.callParent();
 
        result.closable = me.closable;
        result.closeText = me.closeText;
 
        return result;
    },
 
    beforeRender: function() {
        var me = this,
            tabBar = me.up('tabbar'),
            tabPanel = me.up('tabpanel');
 
        me.callParent();
        
        me.ariaRenderAttributes = me.ariaRenderAttributes || {};
 
        if (me.active) {
            me.ariaRenderAttributes['aria-selected'] = true;
            me.addCls(me._activeCls);
        }
        else {
            me.ariaRenderAttributes['aria-selected'] = false;
        }
 
        me.syncClosableCls();
 
        // Propagate minTabWidth and maxTabWidth settings from the owning TabBar then TabPanel
        if (!me.minWidth) {
            me.minWidth = (tabBar) ? tabBar.minTabWidth : me.minWidth;
 
            if (!me.minWidth && tabPanel) {
                me.minWidth = tabPanel.minTabWidth;
            }
 
            if (me.minWidth && me.iconCls) {
                me.minWidth += 25;
            }
        }
 
        if (!me.maxWidth) {
            me.maxWidth = (tabBar) ? tabBar.maxTabWidth : me.maxWidth;
 
            if (!me.maxWidth && tabPanel) {
                me.maxWidth = tabPanel.maxTabWidth;
            }
        }
    },
 
    onRender: function() {
        var me = this;
 
        me.setElOrientation();
 
        me.callParent(arguments);
 
        if (me.closable) {
            me.closeEl.addClsOnOver(me.closeElOverCls);
            me.closeEl.addClsOnClick(me.closeElPressedCls);
        }
    },
 
    setElOrientation: function() {
        var me = this,
            rotation = me.getActualRotation(),
            el = me.el;
 
        if (rotation) {
            el.setVertical(rotation === 1 ? 90 : 270);
        }
        else {
            el.setHorizontal();
        }
    },
 
    enable: function(silent) {
        var me = this;
 
        me.callParent(arguments);
 
        me.removeCls(me._disabledCls);
 
        return me;
    },
 
    disable: function(silent) {
        var me = this;
 
        me.callParent(arguments);
 
        me.addCls(me._disabledCls);
 
        return me;
    },
 
    /**
     * Sets the tab as either closable or not.
     * @param {Boolean} closable Pass false to make the tab not closable. Otherwise the tab
     * will be made closable (eg a close button will appear on the tab)
     */
    setClosable: function(closable) {
        var me = this;
 
        // Closable must be true if no args
        closable = (!arguments.length || !!closable);
 
        if (me.closable !== closable) {
            me.closable = closable;
 
            // set property on the user-facing item ('card'):
            if (me.card) {
                me.card.closable = closable;
            }
 
            me.syncClosableCls();
 
            if (me.rendered) {
                me.syncClosableElements();
 
                // Tab will change width to accommodate close icon
                me.updateLayout();
            }
        }
    },
 
    /**
     * This method ensures that the closeBtn element exists or not based on 'closable'.
     * @private
     */
    syncClosableElements: function() {
        var me = this,
            closeEl = me.closeEl;
 
        if (me.closable) {
            if (!closeEl) {
                closeEl = me.closeEl = me.btnWrap.insertSibling({
                    tag: 'span',
                    id: me.id + '-closeEl',
                    cls: me.baseCls + '-close-btn',
                    html: me.closeText
                }, 'after');
            }
 
            closeEl.addClsOnOver(me.closeElOverCls);
            closeEl.addClsOnClick(me.closeElPressedCls);
        }
        else if (closeEl) {
            closeEl.destroy();
            delete me.closeEl;
        }
    },
 
    /**
     * This method ensures that the closable cls are added or removed based on 'closable'.
     * @private
     */
    syncClosableCls: function() {
        var me = this,
            closableCls = me._closableCls;
 
        if (me.closable) {
            me.addCls(closableCls);
        }
        else {
            me.removeCls(closableCls);
        }
    },
 
    /**
     * Sets this tab's attached card. Usually this is handled automatically by the
     * {@link Ext.tab.Panel} that this Tab belongs to and would not need to be done by the developer
     * @param {Ext.Component} card The card to set
     */
    setCard: function(card) {
        var me = this;
 
        me.card = card;
 
        if (card.iconAlign) {
            me.setIconAlign(card.iconAlign);
        }
 
        if (card.textAlign) {
            me.setTextAlign(card.textAlign);
        }
 
        me.setText(me.title || card.title);
        me.setIconCls(me.iconCls || card.iconCls);
        me.setIcon(me.icon || card.icon);
        me.setGlyph(me.glyph || card.glyph);
    },
 
    /**
     * @private
     * Listener attached to click events on the Tab's close button
     */
    onCloseClick: function() {
        var me = this;
 
        if (me.fireEvent('beforeclose', me) !== false) {
            if (me.tabBar) {
                if (me.tabBar.closeTab(me) === false) {
                    // beforeclose on the panel vetoed the event, stop here
                    return;
                }
            }
            else {
                // if there's no tabbar, fire the close event
                me.fireClose();
            }
        }
    },
 
    /**
     * Fires the close event on the tab.
     * @private
     */
    fireClose: function() {
        this.fireEvent('close', this);
    },
 
    /**
     * @private
     */
    onEnterKey: function(e) {
        var me = this;
 
        if (me.tabBar) {
            me.tabBar.onClick(e, me.el);
            e.stopEvent();
 
            return false;
        }
    },
 
    /**
     * @private
     */
    onDeleteKey: function(e) {
        if (this.closable) {
            this.onCloseClick();
            e.stopEvent();
 
            return false;
        }
    },
 
    /**
     * @private
     */
    beforeClick: function(isCloseClick) {
        if (!isCloseClick) {
            this.focus();
        }
    },
 
    /**
     * @private
     */
    activate: function(supressEvent) {
        var me = this,
            card = me.card,
            ariaDom = me.ariaEl.dom;
 
        me.active = true;
        me.addCls(me._activeCls);
        
        if (ariaDom) {
            ariaDom.setAttribute('aria-selected', true);
        }
        else {
            me.ariaRenderAttributes = me.ariaRenderAttributes || {};
            me.ariaRenderAttributes['aria-selected'] = true;
        }
        
        if (card) {
            if (card.ariaEl.dom) {
                card.ariaEl.dom.setAttribute('aria-expanded', true);
            }
            else {
                card.ariaRenderAttributes = card.ariaRenderAttributes || {};
                card.ariaRenderAttributes['aria-expanded'] = true;
            }
        }
 
        if (supressEvent !== true) {
            me.fireEvent('activate', me);
        }
    },
 
    /**
     * @private
     */
    deactivate: function(supressEvent) {
        var me = this,
            card = me.card,
            ariaDom = me.ariaEl.dom;
 
        me.active = false;
        me.removeCls(me._activeCls);
        
        if (ariaDom) {
            ariaDom.setAttribute('aria-selected', false);
        }
        else {
            me.ariaRenderAttributes = me.ariaRenderAttributes || {};
            me.ariaRenderAttributes['aria-selected'] = false;
        }
        
        if (card) {
            if (card.ariaEl.dom) {
                card.ariaEl.dom.setAttribute('aria-expanded', false);
            }
            else {
                card.ariaRenderAttributes = card.ariaRenderAttributes || {};
                card.ariaRenderAttributes['aria-expanded'] = false;
            }
        }
 
        if (supressEvent !== true) {
            me.fireEvent('deactivate', me);
        }
    },
 
    privates: {
        getFramingInfoCls: function() {
            return this.baseCls + '-' + this.ui + '-' + this._positionCls;
        },
 
        wrapPrimaryEl: function(dom) {
            // Tabs don't need the hacks in Ext.dom.ButtonElement
            Ext.Button.superclass.wrapPrimaryEl.call(this, dom);
        }
    }
});