/**
 * This mixin implements focus trap for widgets that do not want to allow the user
 * to tab out, circling focus among the child items instead. The widget should be
 * derived from Panel since it relies on the tab guards feature of the Panel.
 *
 * The best example of such widget is a Window, or a dialog as per WAI-ARIA 1.0:
 * http://www.w3.org/TR/wai-aria-practices/#dialog_modal
 * http://www.w3.org/TR/wai-aria-practices/#dialog_nonmodal
 *
 * @private
 */
Ext.define('Ext.util.FocusTrap', {
    extend: 'Ext.Mixin',
    
    mixinConfig: {
        id: 'focustrap',
        
        after: {
            afterRender: 'initTabGuards',
            addTool: 'initTabGuards',
            add: 'initTabGuards',
            remove: 'initTabGuards',
            addDocked: 'initTabGuards',
            removeDocked: 'initTabGuards',
            onShow: 'initTabGuards',
            afterHide: 'initTabGuards'
        }
    },
    
    config: {
        tabGuard: true,
    
        tabGuardTpl:
            '<div id="{id}-{tabGuardEl}" data-ref="{tabGuardEl}" role="button" ' +
                'data-tabguardposition="{tabGuard}" aria-busy="true" style="height:0"' +
                'class="' + Ext.baseCSSPrefix + 'hidden-clip">' +
            '</div>',
    
        tabGuardIndex: 0
    },
    
    tabGuardPositionAttribute: 'data-tabguardposition',
    
    privates: {
        initTabGuards: function() {
            var me = this,
                posAttr = me.tabGuardPositionAttribute,
                beforeGuard = me.tabGuardBeforeEl,
                afterGuard = me.tabGuardAfterEl,
                tabIndex = me.tabGuardIndex,
                nodes;
            
            if (!me.rendered || !me.tabGuard) {
                return;
            }
            
            nodes = me.el.findTabbableElements({
                skipSelf: true
            });
            
            // Both tab guards may be in the list, disregard them
            if (nodes[0] && nodes[0].hasAttribute(posAttr)) {
                nodes.shift();
            }
            
            if (nodes.length && nodes[nodes.length - 1].hasAttribute(posAttr)) {
                nodes.pop();
            }
            
            if (nodes.length) {
                beforeGuard.dom.setAttribute('tabIndex', tabIndex);
                beforeGuard.on('focusenter', me.onTabGuardFocusEnter, me);
                
                afterGuard.dom.setAttribute('tabIndex', tabIndex);
                afterGuard.on('focusenter',  me.onTabGuardFocusEnter, me);
            }
            else {
                beforeGuard.dom.removeAttribute('tabIndex');
                beforeGuard.un('focusenter', me.onTabGuardFocusEnter, me);
                
                afterGuard.dom.removeAttribute('tabIndex');
                afterGuard.un('focusenter',  me.onTabGuardFocusEnter, me);
            }
        },
        
        onTabGuardFocusEnter: function(e, target) {
            var me = this,
                el = me.el,
                posAttr = me.tabGuardPositionAttribute,
                position = target.getAttribute(posAttr),
                from = e.relatedTarget,
                nodes, forward, nextFocus;
            
            // Focus was within the parent widget and is trying to escape;
            // for topmost guard we need to bounce focus back to the last tabbable
            // element in the parent widget, and vice versa for the bottom trap.
            if (!from.hasAttribute(posAttr) && from !== el.dom && el.contains(from)) {
                forward = position === 'before' ? false : true;
            }
            
            // It is entirely possible that focus was outside the widget and
            // the user tabbed into the widget, or widget main el was focused
            // and the user pressed the Tab key. In that case we forward the focus
            // to the next available element in the natural tab order, i.e. the element
            // after the topmost guard, or the element before the bottom guard.
            else {
                forward = position === 'before' ? true : false;
            }
            
            nodes = el.findTabbableElements({
                skipSelf: true
            });
            
            // Tabbables will include two tab guards, so remove them
            nodes.shift();
            nodes.pop();
            
            nextFocus = forward ? nodes[0] : nodes[nodes.length - 1];
            
            if (nextFocus) {
                nextFocus.focus();
            }
        }
    }
});
