/**
 * @private
 */
Ext.define('Ext.event.ListenerStack', {
 
    currentOrder: 'current',
 
    length: 0,
 
    constructor: function() {
        this.listeners = {
            before: [],
            current: [],
            after: []
        };
 
        this.lateBindingMap = {};
 
        return this;
    },
 
    add: function(fn, scope, options, order, observable) {
        var lateBindingMap = this.lateBindingMap,
            listeners = this.getAll(order),
            i = listeners.length,
            isMethodName = typeof fn === 'string',
            bindingMap, listener, id, namedScope;
 
        if (isMethodName && scope && scope.isIdentifiable) {
            id = scope.getId();
 
            bindingMap = lateBindingMap[id];
 
            if (bindingMap) {
                if (bindingMap[fn]) {
                    return false;
                }
                else {
                    bindingMap[fn] = true;
                }
            }
            else {
                lateBindingMap[id] = bindingMap = {};
                bindingMap[fn] = true;
            }
        }
        else {
            if (> 0) {
                while (i--) {
                    listener = listeners[i];
 
                    if (listener.fn === fn && listener.scope === scope) {
                        listener.options = options;
                        return false;
                    }
                }
            }
        }
 
        if (!isMethodName) {
            namedScope = Ext._namedScopes[scope];
 
            if (namedScope && namedScope.isController) {
                // If the user declared the listener fn as a function reference, not a 
                // string, controller scope is invalid 
                Ext.Error.raise("Cannot resolve scope 'controller' for '" + options.type +
                    "' listener declared on Observable: '" + observable.id + "'");
            }
 
            scope = (scope && !namedScope) ? scope : observable;
        }
 
        listener = this.create(fn, scope, options, order, observable);
 
        // Allow for {foo: 'onFoo', scope: 'this/controller'} 
        if (isMethodName && (!scope || scope in Ext._namedScopes)) {
            listener.boundFn = this.bindDynamicScope(observable, fn, scope);
            listener.isLateBinding = false;
        }
 
        if (options && options.prepend) {
            delete options.prepend;
            listeners.unshift(listener);
        }
        else {
            listeners.push(listener);
        }
 
        this.length++;
 
        return true;
    },
 
    bindDynamicScope: function (observable, funcName, passedScope) {
        return function () {
            var scope = observable.resolveListenerScope(passedScope);
            //<debug> 
            if (typeof scope[funcName] !== 'function') {
                Ext.Error.raise('No such method ' + funcName + ' on ' + scope.$className);
            }
            //</debug> 
            return scope[funcName].apply(scope, arguments);
        };
    },
 
    getAt: function (index, order) {
        return this.getAll(order)[index];
    },
 
    getAll: function (order) {
        return this.listeners[order || this.currentOrder];
    },
 
    count: function (order) {
        return this.getAll(order).length;
    },
 
    create: function (fn, scope, options, order, observable) {
        options = options || {};
        return {
            stack: this,
            fn: fn,
            firingFn: false,
            boundFn: false,
            isLateBinding: typeof fn === 'string',
            scope: scope,
            options: options,
            order: order,
            observable: observable,
            type: options.type
        };
    },
 
    remove: function (fn, scope, order) {
        var listeners = this.getAll(order),
            i = listeners.length,
            isRemoved = false,
            lateBindingMap = this.lateBindingMap,
            listener, id;
 
        if (> 0) {
            // Start from the end index, faster than looping from the 
            // beginning for "single" listeners, 
            // which are normally LIFO 
            while (i--) {
                listener = listeners[i];
 
                if (listener.fn === fn && listener.scope === scope) {
                    listeners.splice(i, 1);
                    isRemoved = true;
                    this.length--;
 
                    if (scope && scope.isIdentifiable && typeof fn === 'string') {
                        id = scope.getId();
 
                        if (lateBindingMap[id] && lateBindingMap[id][fn]) {
                            delete lateBindingMap[id][fn];
                        }
                    }
                    break;
                }
            }
        }
 
        return isRemoved;
    }
});