    Ext.define('Ext.event.Controller', {

        isFiring: false,

        listenerStack: null,

        constructor: function(info) {
            this.firingListeners = [];
            this.firingArguments = [];

            this.setInfo(info);

            return this;
        },

        setInfo: function(info) {
            this.info = info;
        },

        getInfo: function() {
            return this.info;
        },

        setListenerStacks: function(listenerStacks) {
            this.listenerStacks = listenerStacks;
        },

        fire: function(args, action) {
            var listenerStacks = this.listenerStacks,
                firingListeners = this.firingListeners,
                firingArguments = this.firingArguments,
                push = firingListeners.push,
                ln = listenerStacks.length,
                listeners, beforeListeners, currentListeners, afterListeners,
                isActionBefore = false,
                isActionAfter = false,
                i;

            firingListeners.length = 0;

            if (action) {
                if (action.order !== 'after') {
                    isActionBefore = true;
                }
                else {
                    isActionAfter = true;
                }
            }

            if (ln === 1) {
                listeners = listenerStacks[0].listeners;
                beforeListeners = listeners.before;
                currentListeners = listeners.current;
                afterListeners = listeners.after;

                if (beforeListeners.length > 0) {
                    push.apply(firingListeners, beforeListeners);
                }

                if (isActionBefore) {
                    push.call(firingListeners, action);
                }

                if (currentListeners.length > 0) {
                    push.apply(firingListeners, currentListeners);
                }

                if (isActionAfter) {
                    push.call(firingListeners, action);
                }

                if (afterListeners.length > 0) {
                    push.apply(firingListeners, afterListeners);
                }
            }
            else {
                for (i = 0; i < ln; i++) {
                    beforeListeners = listenerStacks[i].listeners.before;
                    if (beforeListeners.length > 0) {
                        push.apply(firingListeners, beforeListeners);
                    }
                }

                if (isActionBefore) {
                    push.call(firingListeners, action);
                }

                for (i = 0; i < ln; i++) {
                    currentListeners = listenerStacks[i].listeners.current;
                    if (currentListeners.length > 0) {
                        push.apply(firingListeners, currentListeners);
                    }
                }

                if (isActionAfter) {
                    push.call(firingListeners, action);
                }

                for (i = 0; i < ln; i++) {
                    afterListeners = listenerStacks[i].listeners.after;
                    if (afterListeners.length > 0) {
                        push.apply(firingListeners, afterListeners);
                    }
                }
            }

            if (firingListeners.length === 0) {
                return this;
            }

            if (!args) {
                args = [];
            }

            firingArguments.length = 0;
            firingArguments.push.apply(firingArguments, args);

            // Backwards compatibility
            firingArguments.push(null, this);

            this.doFire();

            return this;
        },

        doFire: function() {
            var firingListeners = this.firingListeners,
                firingArguments = this.firingArguments,
                optionsArgumentIndex = firingArguments.length - 2,
                i, ln, listener, options, fn, firingFn,
                boundFn, isLateBinding, scope, args, result;

            this.isPausing = false;
            this.isPaused = false;
            this.isStopped = false;
            this.isFiring = true;

            for (i = 0,ln = firingListeners.length; i < ln; i++) {
                listener = firingListeners[i];
                options = listener.options;
                fn = listener.fn;
                firingFn = listener.firingFn;
                boundFn = listener.boundFn;
                isLateBinding = listener.isLateBinding;
                scope = listener.scope;

                // Re-bind the callback if it has changed since the last time it's bound (overridden)
                if (isLateBinding && boundFn && boundFn !== scope[fn]) {
                    boundFn = false;
                    firingFn = false;
                }

                if (!boundFn) {
                    if (isLateBinding) {
                        boundFn = scope[fn];

                        if (!boundFn) {
                            continue;
                        }
                    }
                    else {
                        boundFn = fn;
                    }

                    listener.boundFn = boundFn;
                }

                if (!firingFn) {
                    firingFn = boundFn;

                    if (options.buffer) {
                        firingFn = Ext.Function.createBuffered(firingFn, options.buffer, scope);
                    }

                    if (options.delay) {
                        firingFn = Ext.Function.createDelayed(firingFn, options.delay, scope);
                    }

                    listener.firingFn = firingFn;
                }

                firingArguments[optionsArgumentIndex] = options;

                args = firingArguments;

                if (options.args) {
                    args = options.args.concat(args);
                }

                if (options.single === true) {
                    listener.stack.remove(fn, scope, listener.order);
                }

                result = firingFn.apply(scope, args);

                if (result === false) {
                    this.stop();
                }

                if (this.isStopped) {
                    break;
                }

                if (this.isPausing) {
                    this.isPaused = true;
                    firingListeners.splice(0, i + 1);
                    return;
                }
            }

            this.isFiring = false;
            this.listenerStacks = null;
            firingListeners.length = 0;
            firingArguments.length = 0;
            this.connectingController = null;
        },

        connect: function(controller) {
            this.connectingController = controller;
        },

        resume: function() {
            var connectingController = this.connectingController;

            this.isPausing = false;

            if (this.isPaused && this.firingListeners.length > 0) {
                this.isPaused = false;
                this.doFire();
            }

            if (connectingController) {
                connectingController.resume();
            }

            return this;
        },

        isInterrupted: function() {
            return this.isStopped || this.isPaused;
        },

        stop: function() {
            var connectingController = this.connectingController;

            this.isStopped = true;

            if (connectingController) {
                this.connectingController = null;
                connectingController.stop();
            }

            this.isFiring = false;

            this.listenerStacks = null;

            return this;
        },

        pause: function() {
            var connectingController = this.connectingController;

            this.isPausing = true;

            if (connectingController) {
                connectingController.pause();
            }

            return this;
        }
    });