/**
 * A mixin that gives Ext.Component and Ext.Widget the ability to process the "delegate"
 * event option.
 * @private
 */
Ext.define('Ext.mixin.ComponentDelegation', {
    extend: 'Ext.Mixin',
    mixinConfig: {
        id: 'componentDelegation'
    },

    privates: {
        /**
         * @private
         * Adds a listeners with the "delegate" event option.  Users should not invoke this
         * method directly.  Use the "delegate" event option of {@link #addListener} instead.
         */
        addDelegatedListener: function(eventName, fn, scope, options, order, caller, manager) {
            var me = this,
                delegatedEvents, event, priority;

            // The following processing of the "order" option is typically done by the
            // doAddListener method of Ext.mixin.Observable, but that method does not
            // get called when adding a delegated listener, so we must do the conversion
            // of order to priority here.
            order = order || options.order;

            if (order) {
                priority = (options && options.priority);

                if (!priority) { // priority option takes precedence over order
                    // do not mutate the user's options
                    options = options ? Ext.Object.chain(options) : {};
                    options.priority = me.$orderToPriority[order];
                }
            }

            //<debug>
            if (options.target) {
                Ext.raise("Cannot add '" + eventName + "' listener to component: '"
                + me.id + "' - 'delegate' and 'target' event options are incompatible.");
            }
            //</debug>

            // Delegated events are tracked in a map keyed by event name, where the values
            // are instances of Ext.util.Event that track all of the delegate listeners
            // for the given event name.
            delegatedEvents = me.$delegatedEvents || (me.$delegatedEvents = {});
            event = delegatedEvents[eventName] ||
                    (delegatedEvents[eventName] = new Ext.util.Event(me, eventName));

            if (event.addListener(fn, scope, options, caller, manager)) {
                me.$hasDelegatedListeners._incr_(eventName);
            }
        },

        /**
         * @private
         * Clears all listeners that were attached using the "delegate" event option.
         * Users should not invoke this method directly.  It is called automatically as
         * part of normal {@link #clearListeners} processing.
         */
        clearDelegatedListeners: function() {
            var me = this,
                delegatedEvents = me.$delegatedEvents,
                eventName, event, listenerCount;

            if (delegatedEvents) {
                for (eventName in delegatedEvents) {
                    event = delegatedEvents[eventName];
                    listenerCount = event.listeners.length;
                    event.clearListeners();
                    me.$hasDelegatedListeners._decr_(eventName, listenerCount);
                    delete delegatedEvents[eventName];
                }
            }
        },

        /**
         * @private
         * Fires a delegated event.  Users should not invoke this method directly.  It
         * is called automatically by the framework as needed (see the "delegate" event
         * option of {@link #addListener} for more details.
         */
        doFireDelegatedEvent: function(eventName, args) {
            var me = this,
                ret = true,
                owner, delegatedEvents, event;

            // NOTE: $hasDelegatedListeners exists on the prototype of this mixin
            // which means it is inherited by both Ext.Component and Ext.Widget
            // This means that if any Component in the universe is listening for
            // the given eventName in a delegated manner, we need to traverse up the
            // hierarchy to see if that Component is in fact our ancestor, and if so
            // we need to fire the event on the ancestor.
            if (me.$hasDelegatedListeners[eventName]) {
                owner = me.getRefOwner();

                while (owner) {
                    delegatedEvents = owner.$delegatedEvents;
                    if (delegatedEvents ) {
                        event = delegatedEvents[eventName];

                        if (event) {
                            ret = event.fireDelegated(me, args);

                            if (ret === false) {
                                break;
                            }
                        }
                    }

                    owner = owner.getRefOwner();
                }
            }

            return ret;
        },

        /**
         * @private
         * Removes delegated listeners for a given eventName, function, and scope.
         * Users should not invoke this method directly.  It is called automatically by
         * the framework as part of {@link #removeListener} processing.
         */
        removeDelegatedListener: function(eventName, fn, scope) {
            var me = this,
                delegatedEvents = me.$delegatedEvents,
                event;

            if (delegatedEvents ) {
                event = delegatedEvents[eventName];
                if (event && event.removeListener(fn, scope)) {
                    me.$hasDelegatedListeners._decr_(eventName);

                    if (event.listeners.length === 0) {
                        delete delegatedEvents[eventName];
                    }
                }
            }
        }
    },

    onClassMixedIn: function(T) {
        // When a Component listener is attached with the "delegate" option, it means
        // All components anywhere in the hierarchy MUST now fire the event just in case
        // the Component with the delegate listener is an ancestor of the component that
        // fired the event (otherwise the ancestor will not have a chance to intercept
        // and process the event - see doFireDelegatedEvent).  To ensure that this happens
        // we chain the class-level hasListeners object of Ext.Component and Ext.Widget
        // to the single $hasDelegatedListeners object (see class-creation callback
        // of this class for more info)
        function HasListeners() {}
        T.prototype.HasListeners = T.HasListeners = HasListeners;
        HasListeners.prototype = T.hasListeners = new Ext.mixin.ComponentDelegation.HasDelegatedListeners();
    }
}, function(ComponentDelegation) {
    // Here We set up a HasListeners instance ($hasDelegatedListeners) that will be incremented
    // and decremented any time a Component or Widget adds or removes a listener using the
    // "delegate" event option.  This HasListeners instance is stored on the prototype
    // of the ComponentDelegation mixin, and therefore will be applied to the prototype
    // of both Ext.Component and Ext.Widget.  This means that Ext.Widget and Ext.Component
    // (intentionally) share the same $hasDelegatedListeners instance.  To understand the
    // reason for this common instance one must first understand how delegated events are
    // fired:
    //
    // When any component or widget fires an event of any kind, it must call doFireDelegatedEvent
    // to process possible delegated listeners.  The implementation of doFireDelegatedEvent
    // traverses up the component hierarchy searching for any ancestors that may be listening
    // for the event in a delegated manner; however, this traversal of the hierarchy can
    // be skipped if there are no known Components with delegated listeners for the given event.
    // The $hasDelegatedListeners instance is used to track whether or not there are any
    // delegated listeners for the given event name for this purpose.  Since Ext.Widgets
    // and Ext.Components can be part of the same hierarchy they must share the same
    // $hasDelegatesListeners instance.
    function HasDelegatedListeners() {}

    ComponentDelegation.HasDelegatedListeners = HasDelegatedListeners;

    HasDelegatedListeners.prototype = ComponentDelegation.prototype.$hasDelegatedListeners =
            new Ext.mixin.Observable.HasListeners();
});