/**
 * @class Ext.Function
 *
 * A collection of useful static methods to deal with function callbacks.
 * @singleton
 */
 
Ext.Function = (function() {
// @define Ext.lang.Function 
// @define Ext.Function 
// @require Ext 
// @require Ext.lang.Array 
    var lastTime = 0,
        animFrameId,
        animFrameHandlers = [],
        animFrameNoArgs = [],
        idSource = 0,
        animFrameMap = {},
        slice = Array.prototype.slice,
        win = window,
        global = Ext.global,
        hasImmediate = !!(global.setImmediate && global.clearImmediate),
        requestAnimFrame = win.requestAnimationFrame || win.webkitRequestAnimationFrame ||
            win.mozRequestAnimationFrame || win.oRequestAnimationFrame ||
            function(callback) {
                var currTime = Ext.now(),
                    timeToCall = Math.max(0, 16 - (currTime - lastTime)),
                    timerFn = function() {
                        callback(currTime + timeToCall);
                    },
                    id;
 
                //<debug> 
                timerFn.$origFn = callback.$origFn || callback;
                timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck;
                //</debug> 
                
                id = win.setTimeout(timerFn, timeToCall);
                
                lastTime = currTime + timeToCall;
                
                return id;
            },
        fireHandlers = function() {
            var len = animFrameHandlers.length,
                id, i, handler;
 
            animFrameId = null;
 
            //<debug> 
            var timer;
            //</debug> 
 
            // Fire all animation frame handlers in one go 
            for (= 0; i < len; i++) {
                handler = animFrameHandlers[i];
                id = handler[3];
                
                // Check if this timer has been canceled; its map entry is going to be removed 
                if (animFrameMap[id]) {
                    delete animFrameMap[id];
 
                    //<debug> 
                    timer = Ext.Timer.get(id, 'raf');
                    if (timer) {
                        timer.tick();
                    }
                    //</debug> 
 
                    handler[0].apply(handler[1] || global, handler[2] || animFrameNoArgs);
 
                    //<debug> 
                    if (timer) {
                        timer.tock();
                    }
                    //</debug> 
                }
            }
 
            // Clear all fired animation frame handlers, don't forget that new handlers 
            // could have been created in user handler functions called in the loop above 
            animFrameHandlers = animFrameHandlers.slice(len);
        },
        fireElevatedHandlers = function() {
            Ext.elevate(fireHandlers);
        },
 
    ExtFunction = {
        /**
         * A very commonly used method throughout the framework. It acts as a wrapper around another method
         * which originally accepts 2 arguments for `name` and `value`.
         * The wrapped function then allows "flexible" value setting of either:
         *
         * - `name` and `value` as 2 arguments
         * - one single object argument with multiple key - value pairs
         *
         * For example:
         *
         *     var setValue = Ext.Function.flexSetter(function(name, value) {
         *         this[name] = value;
         *     });
         *
         *     // Afterwards
         *     // Setting a single name - value
         *     setValue('name1', 'value1');
         *
         *     // Settings multiple name - value pairs
         *     setValue({
         *         name1: 'value1',
         *         name2: 'value2',
         *         name3: 'value3'
         *     });
         *
         * @param {Function} setter The single value setter method.
         * @param {String} setter.name The name of the value being set.
         * @param {Object} setter.value The value being set.
         * @return {Function}
         */
        flexSetter: function(setter) {
            return function(name, value) {
                var k, i;
 
                if (name !== null) {
                    if (typeof name !== 'string') {
                        for (in name) {
                            if (name.hasOwnProperty(k)) {
                                setter.call(this, k, name[k]);
                            }
                        }
 
                        if (Ext.enumerables) {
                            for (= Ext.enumerables.length; i--;) {
                                k = Ext.enumerables[i];
                                if (name.hasOwnProperty(k)) {
                                    setter.call(this, k, name[k]);
                                }
                            }
                        }
                    } else {
                        setter.call(this, name, value);
                    }
                }
 
                return this;
            };
        },
 
        /**
         * Create a new function from the provided `fn`, change `this` to the provided scope,
         * optionally overrides arguments for the call. Defaults to the arguments passed by
         * the caller.
         *
         * {@link Ext#bind Ext.bind} is alias for {@link Ext.Function#bind Ext.Function.bind}
         * 
         * **NOTE:** This method is similar to the native `bind()` method. The major difference
         * is in the way the parameters are passed. This method expects an array of parameters,
         * and if supplied, it does not automatically pass forward parameters from the bound
         * function:
         * 
         *      function foo (a, b, c) {
         *          console.log(a, b, c);
         *      }
         *      
         *      var nativeFn = foo.bind(this, 1, 2);
         *      var extFn = Ext.Function.bind(foo, this, [1, 2]);
         *
         *      nativeFn(3); // 1, 2, 3
         *      extFn(3); // 1, 2, undefined
         *
         * This method is unavailable natively on IE8 and IE/Quirks but Ext JS provides a
         * "polyfill" to emulate the important features of the standard `bind` method. In
         * particular, the polyfill only provides binding of "this" and optional arguments.
         * 
         * @param {Function} fn The function to delegate.
         * @param {Object} [scope] The scope (`this` reference) in which the function
         * is executed.
         * **If omitted, defaults to the global environment object (usually the browser `window`).**
         * @param {Array} [args] Overrides arguments for the call. (Defaults to
         * the arguments passed by the caller).
         * @param {Boolean/Number} [appendArgs] if `true` the `args` are appended to the
         * arguments passed to the returned wrapper (by default these arguments are ignored).
         * If a number then the `args` are inserted at the specified position.
         * @return {Function} The bound wrapper function.
         */
        bind: function(fn, scope, args, appendArgs) {
            // Function.prototype.bind is polyfilled in IE8, otherwise native 
            if (arguments.length <= 2) {
                return fn.bind(scope);
            }
 
            var method = fn;
 
            return function() {
                var callArgs = args || arguments;
 
                if (appendArgs === true) {
                    callArgs = slice.call(arguments, 0);
                    callArgs = callArgs.concat(args);
                }
                else if (typeof appendArgs === 'number') {
                    callArgs = slice.call(arguments, 0); // copy arguments first 
                    Ext.Array.insert(callArgs, appendArgs, args);
                }
 
                return method.apply(scope || global, callArgs);
            };
        },
 
        /**
         * Captures the given parameters for a later call to `Ext.callback`. This binding is
         * most useful for resolving scopes for example to an `Ext.app.ViewController`.
         *
         * The arguments match that of `Ext.callback` except for the `args` which, if provided
         * to this method, are prepended to any arguments supplied by the eventual caller of
         * the returned function.
         *
         * @return {Function} A function that, when called, uses `Ext.callback` to call the
         * captured `callback`.
         * @since 5.0.0
         */
        bindCallback: function (callback, scope, args, delay, caller) {
            return function () {
                var a = slice.call(arguments);
                return Ext.callback(callback, scope, args ? args.concat(a) : a, delay, caller);
            };
        },
 
        /**
         * Create a new function from the provided `fn`, the arguments of which are pre-set to `args`.
         * New arguments passed to the newly created callback when it's invoked are appended after the pre-set ones.
         * This is especially useful when creating callbacks.
         *
         * For example:
         *
         *     var originalFunction = function(){
         *         alert(Ext.Array.from(arguments).join(' '));
         *     };
         *
         *     var callback = Ext.Function.pass(originalFunction, ['Hello', 'World']);
         *
         *     callback(); // alerts 'Hello World'
         *     callback('by Me'); // alerts 'Hello World by Me'
         *
         * {@link Ext#pass Ext.pass} is alias for {@link Ext.Function#pass Ext.Function.pass}
         *
         * @param {Function} fn The original function.
         * @param {Array} args The arguments to pass to new callback.
         * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
         * @return {Function} The new callback function.
         */
        pass: function(fn, args, scope) {
            if (!Ext.isArray(args)) {
                if (Ext.isIterable(args)) {
                    args = Ext.Array.clone(args);
                } else {
                    args = args !== undefined ? [args] : [];
                }
            }
 
            return function() {
                var fnArgs = args.slice();
                fnArgs.push.apply(fnArgs, arguments);
                return fn.apply(scope || this, fnArgs);
            };
        },
 
        /**
         * Create an alias to the provided method property with name `methodName` of `object`.
         * Note that the execution scope will still be bound to the provided `object` itself.
         *
         * @param {Object/Function} object
         * @param {String} methodName 
         * @return {Function} aliasFn 
         */
        alias: function(object, methodName) {
            return function() {
                return object[methodName].apply(object, arguments);
            };
        },
 
        /**
         * Create a "clone" of the provided method. The returned method will call the given
         * method passing along all arguments and the "this" pointer and return its result.
         *
         * @param {Function} method 
         * @return {Function} cloneFn 
         */
        clone: function(method) {
            var newMethod, prop;
            
            newMethod = function() {
                return method.apply(this, arguments);
            };
            
            for (prop in method) {
                if (method.hasOwnProperty(prop)) {
                    newMethod[prop] = method[prop];
                }
            }
            
            return newMethod;
        },
 
        /**
         * Creates an interceptor function. The passed function is called before the original one. If it returns false,
         * the original one is not called. The resulting function returns the results of the original function.
         * The passed function is called with the parameters of the original function. Example usage:
         *
         *     var sayHi = function(name){
         *         alert('Hi, ' + name);
         *     };
         *
         *     sayHi('Fred'); // alerts "Hi, Fred"
         *
         *     // create a new function that validates input without
         *     // directly modifying the original function:
         *     var sayHiToFriend = Ext.Function.createInterceptor(sayHi, function(name){
         *         return name === 'Brian';
         *     });
         *
         *     sayHiToFriend('Fred');  // no alert
         *     sayHiToFriend('Brian'); // alerts "Hi, Brian"
         *
         * @param {Function} origFn The original function.
         * @param {Function} newFn The function to call before the original.
         * @param {Object} [scope] The scope (`this` reference) in which the passed function is executed.
         * **If omitted, defaults to the scope in which the original function is called or the browser window.**
         * @param {Object} [returnValue=null] The value to return if the passed function return `false`.
         * @return {Function} The new function.
         */
        createInterceptor: function(origFn, newFn, scope, returnValue) {
            if (!Ext.isFunction(newFn)) {
                return origFn;
            } else {
                returnValue = Ext.isDefined(returnValue) ? returnValue : null;
                
                return function() {
                    var me = this,
                        args = arguments;
 
                    return (newFn.apply(scope || me || global, args) !== false) ?
                                origFn.apply(me || global, args) : returnValue;
                };
            }
        },
 
        /**
         * Creates a delegate (callback) which, when called, executes after a specific delay.
         *
         * @param {Function} fn The function which will be called on a delay when the returned function is called.
         * Optionally, a replacement (or additional) argument list may be specified.
         * @param {Number} delay The number of milliseconds to defer execution by whenever called.
         * @param {Object} scope (optional) The scope (`this` reference) used by the function at execution time.
         * @param {Array} args (optional) Override arguments for the call. (Defaults to the arguments passed by the caller)
         * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
         * if a number the args are inserted at the specified position.
         * @return {Function} A function which, when called, executes the original function after the specified delay.
         */
        createDelayed: function(fn, delay, scope, args, appendArgs) {
            var boundFn = fn;
            
            if (scope || args) {
                boundFn = Ext.Function.bind(fn, scope, args, appendArgs);
            }
 
            return function() {
                var me = this,
                    args = slice.call(arguments),
                    timerFn, timerId;
                
                //<debug> 
                var timer;
                //</debug> 
 
                timerFn = function() {
                    Ext.elevate(boundFn, me, args
                        //<debug> 
                        , timer
                        //</debug> 
                    );
                };
 
                timerId = setTimeout(timerFn, delay);
 
                //<debug> 
                timerFn.$origFn = fn.$origFn || fn;
                timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck;
 
                timer = Ext.Timer.created('timeout', timerId, {
                    type: 'createDelayed',
                    fn: fn,
                    timerFn: timerFn
                });
                //</debug> 
            };
        },
 
        /**
         * Calls function `fn` after the number of milliseconds specified, optionally with
         * a specific `scope` (`this` pointer).
         *
         * Example usage:
         *
         *     var sayHi = function (name) {
         *         alert('Hi, ' + name);
         *     }
         *
         *     // executes immediately:
         *     sayHi('Fred');
         *
         *     // executes after 2 seconds:
         *     Ext.defer(sayHi, 2000, this, ['Fred']);
         *
         * The following syntax is useful for scheduling anonymous functions:
         *
         *     Ext.defer(function () {
         *         alert('Anonymous');
         *     }, 100);
         *
         * NOTE: The `Ext.Function.defer()` method is an alias for `Ext.defer()`.
         *
         * @param {Function} fn The function to defer.
         * @param {Number} millis The number of milliseconds for the `setTimeout` call
         * (if less than or equal to 0 the function is executed immediately).
         * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
         * **If omitted, defaults to the browser window.**
         * @param {Array} [args] Overrides arguments for the call. Defaults to the arguments passed by the caller.
         * @param {Boolean/Number} [appendArgs=false] If `true` args are appended to call args instead of overriding,
         * or, if a number, then the args are inserted at the specified position.
         * @return {Number} The timeout id that can be used with `Ext.undefer`.
         */
        defer: function(fn, millis, scope, args, appendArgs) {
            var timerId = 0,
                timerFn, boundFn;
            
            //<debug> 
            var timer;
            //</debug> 
            
            if (!scope && !args && !appendArgs) {
                boundFn = fn;
            }
            else {
                boundFn = Ext.Function.bind(fn, scope, args, appendArgs);
            }
            
            if (millis > 0) {
                timerFn = function() {
                    Ext.elevate(boundFn
                        //<debug> 
                        , null, null, timer
                        //</debug> 
                    );
                };
 
                timerId = setTimeout(timerFn, millis);
 
                //<debug> 
                timerFn.$origFn = fn.$origFn || fn;
                timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck;
 
                timer = Ext.Timer.created('timeout', timerId, {
                    type: 'defer',
                    fn: fn,
                    timerFn: timerFn
                });
                //</debug> 
            }
            else {
                boundFn();
            }
            
            return timerId;
        },
 
        /**
         * Calls the function `fn` repeatedly at a given interval, optionally with a
         * specific `scope` (`this` pointer).
         *
         *     var sayHi = function (name) {
         *         console.log('Hi, ' + name);
         *     }
         *
         *     // executes every 2 seconds:
         *     var timerId = Ext.interval(sayHi, 2000, this, ['Fred']);
         *
         * The timer is stopped by:
         *
         *     Ext.uninterval(timerId);
         *
         * NOTE: The `Ext.Function.interval()` method is an alias for `Ext.interval()`.
         *
         * @param {Function} fn The function to defer.
         * @param {Number} millis The number of milliseconds for the `setInterval` call
         * @param {Object} scope (optional) The scope (`this` reference) in which the function is executed.
         * **If omitted, defaults to the browser window.**
         * @param {Array} [args] Overrides arguments for the call. Defaults to the arguments passed by the caller.
         * @param {Boolean/Number} [appendArgs=false] If `true` args are appended to call args instead of overriding,
         * or, if a number, then the args are inserted at the specified position.
         * @return {Number} The interval id that can be used with `Ext.uninterval`.
         */
        interval: function(fn, millis, scope, args, appendArgs) {
            var timerFn, timerId, boundFn;
            
            //<debug> 
            var timer;
            //</debug> 
            
            boundFn = Ext.Function.bind(fn, scope, args, appendArgs);
            
            timerFn = function() {
                Ext.elevate(boundFn
                    //<debug> 
                    , null, null, timer
                    //</debug> 
                );
            };
            
            timerId = setInterval(timerFn, millis);
 
            //<debug> 
            timerFn.$origFn = boundFn.$origFn || fn;
            timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck;
 
            timer = Ext.Timer.created('interval', timerId, {
                type: 'interval',
                fn: fn,
                timerFn: timerFn
            });
            //</debug> 
 
            return timerId;
        },
 
        /**
         * Create a combined function call sequence of the original function + the passed function.
         * The resulting function returns the results of the original function.
         * The passed function is called with the parameters of the original function. Example usage:
         *
         *     var sayHi = function(name){
         *         alert('Hi, ' + name);
         *     };
         *
         *     sayHi('Fred'); // alerts "Hi, Fred"
         *
         *     var sayGoodbye = Ext.Function.createSequence(sayHi, function(name){
         *         alert('Bye, ' + name);
         *     });
         *
         *     sayGoodbye('Fred'); // both alerts show
         *
         * @param {Function} originalFn The original function.
         * @param {Function} newFn The function to sequence.
         * @param {Object} [scope] The scope (`this` reference) in which the passed function is executed.
         * If omitted, defaults to the scope in which the original function is called or the
         * default global environment object (usually the browser window).
         * @return {Function} The new function.
         */
        createSequence: function(originalFn, newFn, scope) {
            if (!newFn) {
                return originalFn;
            }
            else {
                return function() {
                    var result = originalFn.apply(this, arguments);
                    newFn.apply(scope || this, arguments);
                    return result;
                };
            }
        },
 
        /**
         * Creates a delegate function, optionally with a bound scope which, when called, buffers
         * the execution of the passed function for the configured number of milliseconds.
         * If called again within that period, the impending invocation will be canceled, and the
         * timeout period will begin again.
         *
         * @param {Function} fn The function to invoke on a buffered timer.
         * @param {Number} buffer The number of milliseconds by which to buffer the invocation of the
         * function.
         * @param {Object} [scope] The scope (`this` reference) in which.
         * the passed function is executed. If omitted, defaults to the scope specified by the caller.
         * @param {Array} [args] Override arguments for the call. Defaults to the arguments
         * passed by the caller.
         * @return {Function} A function which invokes the passed function after buffering for the specified time.
         */
        createBuffered: function(fn, buffer, scope, args) {
            var timerId,
                result = function() {
                    var callArgs = args || slice.call(arguments, 0),
                        me = scope || this,
                        timerFn;
                    
                    //<debug> 
                    var timer;
                    //</debug> 
 
                    if (timerId) {
                        Ext.undefer(timerId);
                    }
 
                    timerFn = function() {
                        Ext.elevate(fn, me, callArgs
                            //<debug> 
                            , timer
                            //</debug> 
                        );
                    };
 
                    result.timer = timerId = setTimeout(timerFn, buffer);
 
                    //<debug> 
                    timerFn.$origFn = fn.$origFn || fn;
                    timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck;
 
                    timer = Ext.Timer.created('timeout', timerId, {
                        type: 'createBuffered',
                        fn: fn,
                        timerFn: timerFn
                    });
                    //</debug> 
                };
 
            return result;
        },
 
        /**
        * Creates a wrapped function that, when invoked, defers execution until the next
        * animation frame
         * @private
         * @param {Function} fn The function to call.
         * @param {Object} [scope] The scope (`this` reference) in which the function is executed. Defaults to the window object.
         * @param {Array} [args] The argument list to pass to the function.
         * @param {Number} [queueStrategy=3] A bit flag that indicates how multiple calls to
         * the returned function within the same animation frame should be handled.
         *
         * - 1: All calls will be queued - FIFO order
         * - 2: Only the first call will be queued
         * - 3: The last call will replace all previous calls
         *
         * @return {Function}
         */
        createAnimationFrame: function(fn, scope, args, queueStrategy) {
            var boundFn, timerId;
 
            queueStrategy = queueStrategy || 3;
 
            boundFn = function() {
                var timerFn,
                    callArgs = args || slice.call(arguments, 0);
 
                scope = scope || this;
 
                if (queueStrategy === 3 && timerId) {
                    ExtFunction.cancelAnimationFrame(timerId);
                }
 
                if ((queueStrategy & 1) || !timerId) {
                    timerFn = function() {
                        timerId = boundFn.timerId = null;
                        fn.apply(scope, callArgs);
                    };
                    
                    //<debug> 
                    timerFn.$origFn = fn.$origFn || fn;
                    timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck;
                    //</debug> 
                    
                    timerId = boundFn.timerId = ExtFunction.requestAnimationFrame(timerFn);
                }
            };
            
            return boundFn;
        },
 
        /**
         * @private
         * Schedules the passed function to be called on the next animation frame.
         * @param {Function} fn The function to call.
         * @param {Object} [scope] The scope (`this` reference) in which the function is executed. Defaults to the window object.
         * @param {Mixed[]} [args] The argument list to pass to the function.
         *
         * @return {Number} Timer id for the new animation frame to use when canceling it.
         */
        requestAnimationFrame: function(fn, scope, args) {
            var id = ++idSource,  // Ids start at 1 
                handler = slice.call(arguments, 0);
 
            handler[3] = id;
            animFrameMap[id] = 1; // A flag to indicate that the timer exists 
 
            //<debug> 
            Ext.Timer.created('raf', id, {
                type: 'raf',
                fn: fn
            });
            //</debug> 
 
            // We might be in fireHandlers at this moment but this new entry will not 
            // be executed until the next frame 
            animFrameHandlers.push(handler);
 
            if (!animFrameId) {
                animFrameId = requestAnimFrame(fireElevatedHandlers);
            }
 
            return id;
        },
 
        cancelAnimationFrame: function (id) {
            // Don't remove any handlers from animFrameHandlers array, because 
            // the might be in use at the moment (when cancelAnimationFrame is called). 
            // Just remove the handler id from the map so it will not be executed 
            delete animFrameMap[id];
 
            //<debug> 
            Ext.Timer.cancel('raf', id);
            //</debug> 
        },
 
        /**
         * Creates a throttled version of the passed function which, when called repeatedly and
         * rapidly, invokes the passed function only after a certain interval has elapsed since the
         * previous invocation.
         *
         * This is useful for wrapping functions which may be called repeatedly, such as
         * a handler of a mouse move event when the processing is expensive.
         *
         * @param {Function} fn The function to execute at a regular time interval.
         * @param {Number} interval The interval in milliseconds on which the passed function is executed.
         * @param {Object} [scope] The scope (`this` reference) in which
         * the passed function is executed. If omitted, defaults to the scope specified by the caller.
         * @return {Function} A function which invokes the passed function at the specified interval.
         */
        createThrottled: function(fn, interval, scope) {
            var lastCallTime = 0,
                elapsed,
                lastArgs,
                timerId,
                execute = function() {
                    fn.apply(scope, lastArgs);
 
                    lastCallTime = Ext.now();
                    lastArgs = timerId = null;
                };
            
            //<debug> 
            execute.$origFn = fn.$origFn || fn;
            execute.$skipTimerCheck = execute.$origFn.$skipTimerCheck;
            //</debug> 
 
            return function() {
                // Use scope of last call unless the creator specified a scope 
                if (!scope) {
                    scope = this;
                }
                elapsed = Ext.now() - lastCallTime;
                lastArgs = Ext.Array.slice(arguments);
 
                // If this is the first invocation, or the throttle interval has been reached, clear any 
                // pending invocation, and call the target function now. 
                if (elapsed >= interval) {
                    Ext.undefer(timerId);
                    execute();
                }
                // Throttle interval has not yet been reached. Only set the timer to fire if not already set. 
                else if (!timerId) {
                    timerId = Ext.defer(execute, interval - elapsed);
                }
            };
        },
 
        /**
         * Wraps the passed function in a barrier function which will call the passed function after the passed number of invocations.
         * @param {Number} count The number of invocations which will result in the calling of the passed function.
         * @param {Function} fn The function to call after the required number of invocations.
         * @param {Object} scope The scope (`this` reference) in which the function will be called.
         */    
        createBarrier: function(count, fn, scope) {
            var barrierFn = function() {
                if (!--count) {
                    fn.apply(scope, arguments);
                }
            };
            
            //<debug> 
            barrierFn.$origFn = fn.$origFn || fn;
            barrierFn.$skipTimerCheck = barrierFn.$origFn.$skipTimerCheck;
            //</debug> 
            
            return barrierFn;
        },
 
        /**
         * Adds behavior to an existing method that is executed before the
         * original behavior of the function.  For example:
         * 
         *     var soup = {
         *         contents: [],
         *         add: function(ingredient) {
         *             this.contents.push(ingredient);
         *         }
         *     };
         *     Ext.Function.interceptBefore(soup, "add", function(ingredient){
         *         if (!this.contents.length && ingredient !== "water") {
         *             // Always add water to start with
         *             this.contents.push("water");
         *         }
         *     });
         *     soup.add("onions");
         *     soup.add("salt");
         *     soup.contents; // will contain: water, onions, salt
         * 
         * @param {Object} object The target object
         * @param {String} methodName Name of the method to override
         * @param {Function} fn Function with the new behavior.  It will
         * be called with the same arguments as the original method.  The
         * return value of this function will be the return value of the
         * new method.
         * @param {Object} [scope] The scope to execute the interceptor function. Defaults to the object.
         * @return {Function} The new function just created.
         */
        interceptBefore: function(object, methodName, fn, scope) {
            var method = object[methodName] || Ext.emptyFn;
 
            return (object[methodName] = function() {
                var ret = fn.apply(scope || this, arguments);
                method.apply(this, arguments);
 
                return ret;
            });
        },
 
        /**
         * Adds behavior to an existing method that is executed after the
         * original behavior of the function.  For example:
         * 
         *     var soup = {
         *         contents: [],
         *         add: function(ingredient) {
         *             this.contents.push(ingredient);
         *         }
         *     };
         *     Ext.Function.interceptAfter(soup, "add", function(ingredient){
         *         // Always add a bit of extra salt
         *         this.contents.push("salt");
         *     });
         *     soup.add("water");
         *     soup.add("onions");
         *     soup.contents; // will contain: water, salt, onions, salt
         * 
         * @param {Object} object The target object
         * @param {String} methodName Name of the method to override
         * @param {Function} fn Function with the new behavior.  It will
         * be called with the same arguments as the original method.  The
         * return value of this function will be the return value of the
         * new method.
         * @param {Object} [scope] The scope to execute the interceptor function. Defaults to the object.
         * @return {Function} The new function just created.
         */
        interceptAfter: function(object, methodName, fn, scope) {
            var method = object[methodName] || Ext.emptyFn;
 
            return (object[methodName] = function() {
                method.apply(this, arguments);
                return fn.apply(scope || this, arguments);
            });
        },
        
        interceptAfterOnce: function(object, methodName, fn, scope) {
            var origMethod = object[methodName],
                newMethod;
            
            newMethod = function() {
                var ret;
                
                if (origMethod) {
                    origMethod.apply(this, arguments);
                }
                
                ret = fn.apply(scope || this, arguments);
                
                object[methodName] = origMethod;
                object = methodName = fn = scope = origMethod = newMethod = null;
                
                return ret;
            };
            
            object[methodName] = newMethod;
            
            return newMethod;
        },
 
        makeCallback: function (callback, scope) {
            //<debug> 
            if (!scope[callback]) {
                if (scope.$className) {
                    Ext.raise('No method "' + callback + '" on ' + scope.$className);
                }
                Ext.raise('No method "' + callback + '"');
            }
            //</debug> 
 
            return function () {
                return scope[callback].apply(scope, arguments);
            };
        },
 
        /**
         * Returns a wrapper function that caches the return value for previously
         * processed function argument(s).
         *
         * For example:
         *
         *      function factorial (value) {
         *          var ret = value;
         *
         *          while (--value > 1) {
         *              ret *= value;
         *          }
         *
         *          return ret;
         *      }
         *
         * Each call to `factorial` will loop and multiply to produce the answer. Using
         * this function we can wrap the above and cache its answers:
         *
         *      factorial = Ext.Function.memoize(factorial);
         *
         * The returned function operates in the same manner as before, but results are
         * stored in a cache to avoid calling the wrapped function when given the same
         * arguments.
         *
         *      var x = factorial(20);  // first time; call real factorial()
         *      var y = factorial(20);  // second time; return value from first call
         *
         * To support multi-argument methods, you will need to provide a `hashFn`.
         *
         *      function permutation (n, k) {
         *          return factorial(n) / factorial(n - k);
         *      }
         *
         *      permutation = Ext.Function.memoize(permutation, null, function (n, k) {
         *          n + '-' + k;
         *      });
         *
         * In this case, the `memoize` of `factorial` is sufficient optimization, but the
         * example is simply to illustrate how to generate a unique key for an expensive,
         * multi-argument method.
         *
         * **IMPORTANT**: This cache is unbounded so be cautious of memory leaks if the
         * `memoize`d function is kept indefinitely or is given an unbounded set of
         * possible arguments.
         *
         * @param {Function} fn Function to wrap.
         * @param {Object} scope Optional scope in which to execute the wrapped function.
         * @param {Function} hashFn Optional function used to compute a hash key for
         * storing the result, based on the arguments to the original function.
         * @return {Function} The caching wrapper function.
         * @since 6.0.0
         */
        memoize: function(fn, scope, hashFn) {
            var memo = {},
                isFunc = hashFn && Ext.isFunction(hashFn);
 
            return function (value) {
                var key = isFunc ? hashFn.apply(scope, arguments) : value;
 
                if (!(key in memo)) {
                    memo[key] = fn.apply(scope, arguments);
                }
 
                return memo[key];
            };
        },
 
        //<debug> 
        _stripCommentRe: /(\/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\/)|(\/\/.*)/g,
        //</debug> 
 
        toCode: function (fn) {
            var s = fn ? fn.toString() : '';
 
            //<debug> 
            s = s.replace(ExtFunction._stripCommentRe, '');
            //</debug> 
 
            return s;
        }
    }; // ExtFunction 
 
    /**
     * @member Ext
     * @method asap
     * Schedules the specified callback function to be executed on the next turn of the
     * event loop. Where available, this method uses the browser's `setImmediate` API. If
     * not available, this method substitutes `setTimeout(0)`. Though not a perfect
     * replacement for `setImmediate` it is sufficient for many use cases.
     *
     * For more details see [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window/setImmediate).
     *
     * @param {Function} fn Callback function.
     * @param {Object} [scope] The scope for the callback (`this` pointer).
     * @param {Mixed[]} [parameters] Additional parameters to pass to `fn`.
     * @return {Number} A cancellation id for `{@link Ext#unasap}`.
     */
    Ext.asap = hasImmediate ?
        function (fn, scope, parameters) {
            var boundFn = fn,
                timerFn, timerId;
            
            //<debug> 
            var timer;
            //</debug> 
            
            if (scope != null || parameters != null) {
                boundFn = ExtFunction.bind(fn, scope, parameters);
            }
            
            timerFn = function() {
                Ext.elevate(boundFn
                    //<debug> 
                    , null, null, timer
                    //</debug> 
                );
            };
 
            timerId = setImmediate(timerFn);
            
            //<debug> 
            timerFn.$origFn = fn.$origFn || fn;
            timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck;
 
            timer = Ext.Timer.created('asap', timerId, {
                type: 'asap',
                fn: fn,
                timerFn: timerFn
            });
            //</debug> 
            
            return timerId;
        } :
        function (fn, scope, parameters) {
            var boundFn = fn,
                timerFn, timerId;
            
            //<debug> 
            var timer;
            //</debug> 
                
            if (scope != null || parameters != null) {
                boundFn = ExtFunction.bind(fn, scope, parameters);
            }
            
            timerFn = function() {
                Ext.elevate(boundFn
                    //<debug> 
                    , null, null, timer
                    //</debug> 
                );
            };
 
            timerId = setTimeout(timerFn, 0, true);
 
            //<debug> 
            timerFn.$origFn = fn.$origFn || fn;
            timerFn.$skipTimerCheck = timerFn.$origFn.$skipTimerCheck;
 
            timer = Ext.Timer.created('timeout', timerId, {
                type: 'asap',
                fn: fn,
                timerFn: timerFn
            });
            //</debug> 
            
            return timerId;
        };
 
    /**
     * @member Ext
     * @method unasap
     * Cancels a previously scheduled call to `{@link Ext#asap}`.
     *
     *      var timerId = Ext.asap(me.method, me);
     *      ...
     *
     *      if (nevermind) {
     *          Ext.unasap(timerId);
     *      }
     *
     * This method always returns `null` to enable simple cleanup:
     *
     *      timerId = Ext.unasap(timerId);  // safe even if !timerId
     *
     * @param {Number} id The id returned by `{@link Ext#asap}`.
     * @return {Object} Always returns `null`.
     */
    Ext.unasap = hasImmediate ?
        function (id) {
            if (id) {
                clearImmediate(id);
                //<debug> 
                Ext.Timer.cancel('asap', id);
                //</debug> 
            }
 
            return null;
        } : function (id) {
            return Ext.undefer(id);
        };
 
    /**
     * @member Ext
     * @method asapCancel
     * Cancels a previously scheduled call to `{@link Ext#asap}`.
     * @param {Number} id The id returned by `{@link Ext#asap}`.
     * @deprecated 6.5.1 Use `Ext.unasap` instead.
     */
    Ext.asapCancel = function (id) {
        return Ext.unasap(id);
    };
 
    /**
     * @method defer
     * @member Ext
     * @inheritdoc Ext.Function#defer
     */
    Ext.defer = ExtFunction.defer;
 
    /**
     * @member Ext
     * @method undefer
     * Cancels a previously scheduled call to `{@link Ext#defer}`.
     *
     *      var timerId = Ext.defer(me.method, me);
     *      ...
     *
     *      if (nevermind) {
     *          Ext.undefer(timerId);
     *      }
     *
     * This method always returns `null` to enable simple cleanup:
     *
     *      timerId = Ext.undefer(timerId);  // safe even if !timerId
     *
     * @param {Number} id The id returned by `{@link Ext#defer}`.
     */
    Ext.undefer = function (id) {
        if (id) {
            clearTimeout(id);
 
            //<debug> 
            Ext.Timer.cancel('timeout', id);
            //</debug> 
        }
 
        return null;
    };
 
    /**
     * @method interval
     * @member Ext
     * @inheritdoc Ext.Function#interval
     */
    Ext.interval = ExtFunction.interval;
 
    /**
     * @member Ext
     * @method uninterval
     * Cancels a previously scheduled call to `{@link Ext#interval}`.
     *
     *      var timerId = Ext.interval(me.method, me);
     *      ...
     *
     *      if (nevermind) {
     *          Ext.uninterval(timerId);
     *      }
     *
     * This method always returns `null` to enable simple cleanup:
     *
     *      timerId = Ext.uninterval(timerId);  // safe even if !timerId
     *
     * @param {Number} id The id returned by `{@link Ext#interval}`.
     */
    Ext.uninterval = function (id) {
        if (id) {
            clearInterval(id);
 
            //<debug> 
            Ext.Timer.cancel('interval', id);
            //</debug> 
        }
 
        return null;
    };
 
    /**
     * @method pass
     * @member Ext
     * @inheritdoc Ext.Function#pass
     */
    Ext.pass = ExtFunction.pass;
 
    /**
     * @method bind
     * @member Ext
     * @inheritdoc Ext.Function#bind
     */
    Ext.bind = ExtFunction.bind;
 
    Ext.raf = function () {
        return ExtFunction.requestAnimationFrame.apply(ExtFunction, arguments);
    };
 
    Ext.unraf = function (id) {
        ExtFunction.cancelAnimationFrame(id);
    };
 
    return ExtFunction;
})();