/**
 * @private
 */
Ext.define('Ext.event.publisher.Focus', {
    extend: 'Ext.event.publisher.Dom',
 
    handledEvents: ['focusin', 'focusout'],
 
    syntheticEvents: {
        focusenter: true,
        focusleave: true
    },
 
    eventMap: {
        focus: 'focusenter',
        focusin: 'focusenter',
        blur: 'focusleave',
        focusout: 'focusleave'
    },
 
    handles: function(eventName) {
        return this.syntheticEvents[eventName];
    },
 
    doDelegatedEvent: function(e, invokeAfter) {
        var me = this;
 
        e = me.callParent([e, false]);
 
        if (e) {
            me.processSyntheticEvent(me.eventMap[e.type], e);
    
            if (invokeAfter) {
                me.afterEvent(e);
            }
        }
    },
 
    createSyntheticEvent: function(eventName, origEvent, realTarget) {
        var event = new Ext.event.Event(origEvent.browserEvent);
    
        event.type = eventName;
 
        if (realTarget) {
            event.relatedTarget = event.target;
            event.target = realTarget;
        }
        else {
            event.target = origEvent.target;
            event.relatedTarget = origEvent.relatedTarget || null;
        }
    
        return event;
    },
 
    processSyntheticEvent: function(eventName, e, eventTarget) {
        var me = this,
            subscribers, nodes, node, i, len, targets;
    
        subscribers = me.getSubscribers(eventName);
 
        if (subscribers.$length) {
            nodes   = me.gatherSubscriberNodes(eventName, subscribers);
            targets = [];
 
            for (= 0, len = nodes.length; i < len; i++) {
                node = nodes[i];
    
                if (me.isEventTarget(node, eventName, e, eventTarget)) {
                    targets.push(node);
                }
            }
 
            for (= 0, len = targets.length; i < len; i++) {
                node = targets[i];
    
                if (node) {
                    me.fireNodeEvent(node, eventName, e, eventTarget);
                }
            }
        }
    },
 
    gatherSubscriberNodes: function(eventName, subscribers) {
        var idSubscribers = subscribers.id,
            classNameSubscribers = subscribers.className,
            selectorSubscribers = subscribers.selector,
            hasIdSubscribers = idSubscribers.$length > 0,
            hasClassNameSubscribers = classNameSubscribers.$length > 0,
            hasSelectorSubscribers = selectorSubscribers.$length > 0,
            result = [],
            subscriber, item;
    
        if (hasIdSubscribers) {
            for (item in idSubscribers) {
                if (item === '$length') {
                    continue;
                }
            
                subscriber = document.getElementById(item);
            
                if (subscriber) {
                    result.push(subscriber);
                }
            }
        }
    
        // Blow up for now 
        if (hasClassNameSubscribers) {
            Ext.Error.raise('Class name subscribers in Focus publisher');
        }
    
        // Same thing 
        if (hasSelectorSubscribers) {
            Ext.Error.raise('Selector subscribers in Focus publisher');
        }
    
        return result;
    },
 
    isEventTarget: function(node, eventName, e, realTarget) {
        return eventName === 'focusenter' ? this.isFocusEnterTarget(node, e, realTarget)
             : eventName === 'focusleave' ? this.isFocusLeaveTarget(node, e, realTarget)
             :                              null
        ;
    },
 
    isFocusEnterTarget: function(node, e, eventTarget) {
        var event = e.browserEvent,
            from = eventTarget || event.relatedTarget,
            to = event.target,
            fly;
    
        if (to === window) {
            return false;
        }
    
        fly = Ext.fly(node);
    
        // If the focusin event happened outside of the node, it's definitely 
        // not going to be a focusenter. 
        if (!fly.isAncestor(to)) {
            return false;
        }
    
        // For focusin events originated from the "outside" of the document, 
        // IE will report as if they came from the document body, and WebKit 
        // will set relatedTarget to null. That's an easy catch. 
        if (!from) {
            return true;
        }
    
        // If the node is not a parent of the "from" element, then we have a 
        // legit focusenter 
        if (!fly.isAncestor(from)) {
            return true;
        }
    
        return false;
    },
 
    isFocusLeaveTarget: function(node, e, eventTarget) {
        var event = e.browserEvent,
            from = eventTarget !== undefined ? eventTarget : event.target,
            to = eventTarget !== undefined ? event.target : event.relatedTarget,
            fly;
    
        // In Firefox we have to do really strange things :/ 
        if (from === window || to === window) {
            return false;
        }
    
        fly = Ext.fly(node);
    
        if (!fly.isAncestor(from)) {
            return false;
        }
    
        if (!to) {
            return true;
        }
    
        if (!fly.isAncestor(to)) {
            return true;
        }
    
        return false;
    },
 
    fireNodeEvent: function(subscriber, eventName, origEvent, realTarget) {
        var e = this.createSyntheticEvent(eventName, origEvent, realTarget);
    
        e.setCurrentTarget(subscriber);
    
        this.dispatch('#' + subscriber.id, eventName, [e, subscriber]);
    }
},
 
function() {
    // At this point only Firefox does not support focusin/focusout, see this bug: 
    // https://bugzilla.mozilla.org/show_bug.cgi?id=687787 
    if (!Ext.supports.FocusinFocusoutEvents) {
        // We need to track previously focused element to detect when 
        // a target lost focus 
        document.addEventListener('blur', function(e) {
            if (e.target === window || e.target === document) {
                delete document.previousActiveElement;
            }
            else {
                document.previousActiveElement = e.target;
            }
        }, true);
        
        // When focusin/focusout are not available we capture focus event instead, 
        // and fire both focusenter *and* focusleave in the focus handler. 
        this.override({
            handledEvents: ['focus'],
            
            doDelegatedEvent: function(e, invokeAfter) {
                var me = this,
                    el;
 
                e = me.callSuper([e, false]);
 
                if (e) {
                    el = document.previousActiveElement;
    
                    if (el !== undefined) {
                        me.processSyntheticEvent('focusleave', e, el);
                    }
    
                    me.processSyntheticEvent(me.eventMap[e.type], e, el || null);
 
                    if (invokeAfter) {
                        me.afterEvent(e);
                    }
                }
            }
        });
    }
});