/**
 * @class Ext.Loader
 * @singleton
 * @author Jacky Nguyen <jacky@sencha.com>
 * @docauthor Jacky Nguyen <jacky@sencha.com>
 *
 * Ext.Loader is the heart of the new dynamic dependency loading capability in Ext JS 4+. It is most commonly used
 * via the {@link Ext#require} shorthand. Ext.Loader supports both asynchronous and synchronous loading
 * approaches, and leverage their advantages for the best development flow. We'll discuss about the pros and cons
 * of each approach:
 *
 * # Asynchronous Loading
 *
 * - *Advantages:*
 *       + Cross-domain
 *       + No web server needed: you can run the application via the file system protocol
 *     (i.e: `file://path/to/your/index.html`)
 *       + Best possible debugging experience: error messages come with the exact file name and line number
 *
 * - *Disadvantages:*
 *       + Dependencies need to be specified before-hand
 *
 * ### Method 1: Explicitly include what you need:
 *
 *     // Syntax
 *     Ext.require({String/Array} expressions);
 *
 *     // Example: Single alias
 *     Ext.require('widget.window');
 *
 *     // Example: Single class name
 *     Ext.require('Ext.window.Window');
 *
 *     // Example: Multiple aliases / class names mix
 *     Ext.require(['widget.window', 'layout.border', 'Ext.data.Connection']);
 *
 *     // Wildcards
 *     Ext.require(['widget.*', 'layout.*', 'Ext.data.*']);
 *
 * ### Method 2: Explicitly exclude what you don't need:
 *
 *     // Syntax: Note that it must be in this chaining format.
 *     Ext.exclude({String/Array} expressions)
 *        .require({String/Array} expressions);
 *
 *     // Include everything except Ext.data.*
 *     Ext.exclude('Ext.data.*').require('*');
 *
 *     // Include all widgets except widget.checkbox*,
 *     // which will match widget.checkbox, widget.checkboxfield, widget.checkboxgroup, etc.
 *     Ext.exclude('widget.checkbox*').require('widget.*');
 *
 * # Synchronous Loading on Demand
 *
 * - *Advantages:*
 *       + There's no need to specify dependencies before-hand, which is always the convenience of including
 *     ext-all.js before
 *
 * - *Disadvantages:*
 *       + Not as good debugging experience since file name won't be shown (except in Firebug at the moment)
 *       + Must be from the same domain due to XHR restriction
 *       + Need a web server, same reason as above
 *
 * There's one simple rule to follow: Instantiate everything with Ext.create instead of the `new` keyword
 *
 *     Ext.create('widget.window', { ... }); // Instead of new Ext.window.Window({...});
 *
 *     Ext.create('Ext.window.Window', {}); // Same as above, using full class name instead of alias
 *
 *     Ext.widget('window', {}); // Same as above, all you need is the traditional `xtype`
 *
 * Behind the scene, {@link Ext.ClassManager} will automatically check whether the given class name / alias has already
 * existed on the page. If it's not, Ext.Loader will immediately switch itself to synchronous mode and automatic load
 * the given class and all its dependencies.
 *
 * # Hybrid Loading - The Best of Both Worlds
 *
 * It has all the advantages combined from asynchronous and synchronous loading. The development flow is simple:
 *
 * ### Step 1: Start writing your application using synchronous approach.
 *
 * Ext.Loader will automatically fetch all dependencies on demand as they're needed during run-time. For example:
 *
 *     Ext.onReady(function(){
 *         var window = Ext.createWidget('window', {
 *             width: 500,
 *             height: 300,
 *             layout: {
 *                 type: 'border',
 *                 padding: 5
 *             },
 *             title: 'Hello Dialog',
 *             items: [{
 *                 title: 'Navigation',
 *                 collapsible: true,
 *                 region: 'west',
 *                 width: 200,
 *                 html: 'Hello',
 *                 split: true
 *             }, {
 *                 title: 'TabPanel',
 *                 region: 'center'
 *             }]
 *         });
 *
 *         window.show();
 *     })
 *
 * ### Step 2: Along the way, when you need better debugging ability, watch the console for warnings like these:
 *
 *     [Ext.Loader] Synchronously loading 'Ext.window.Window'; consider adding Ext.require('Ext.window.Window') before your application's code ClassManager.js:432
 *     [Ext.Loader] Synchronously loading 'Ext.layout.container.Border'; consider adding Ext.require('Ext.layout.container.Border') before your application's code
 *
 * Simply copy and paste the suggested code above `Ext.onReady`, e.g.:
 *
 *     Ext.require('Ext.window.Window');
 *     Ext.require('Ext.layout.container.Border');
 *
 *     Ext.onReady(...);
 *
 * Everything should now load via asynchronous mode.
 *
 * # Deployment
 *
 * It's important to note that dynamic loading should only be used during development on your local machines.
 * During production, all dependencies should be combined into one single JavaScript file. Ext.Loader makes
 * the whole process of transitioning from / to between development / maintenance and production as easy as
 * possible. Internally {@link Ext.Loader#history Ext.Loader.history} maintains the list of all dependencies
 * your application needs in the exact loading sequence. It's as simple as concatenating all files in this
 * array into one, then include it on top of your application.
 *
 * This process will be automated with Sencha Command, to be released and documented towards Ext JS 4 Final.
 */

(function(Manager, Class, flexSetter, alias) {

   
var
       
//<if nonBrowser>
        isNonBrowser
= typeof window === 'undefined',
        isNodeJS
= isNonBrowser && (typeof require === 'function'),
        isPhantomJS
= (typeof phantom !== 'undefined' && phantom.fs),
       
//</if>
        dependencyProperties
= ['extend', 'mixins', 'requires'],
       
Loader;

   
Loader = Ext.Loader = {
        /**
         * @private
         */

        documentHead
: typeof document !== 'undefined' && (document.head || document.getElementsByTagName('head')[0]),

        /**
         * Flag indicating whether there are still files being loaded
         * @private
         */

        isLoading
: false,

        /**
         * Maintain the queue for all dependencies. Each item in the array is an object of the format:
         * {
         *      requires: [...], // The required classes for this queue item
         *      callback: function() { ... } // The function to execute when all classes specified in requires exist
         * }
         * @private
         */

        queue
: [],

        /**
         * Maintain the list of files that have already been handled so that they never get double-loaded
         * @private
         */

        isFileLoaded
: {},

        /**
         * Maintain the list of listeners to execute when all required scripts are fully loaded
         * @private
         */

        readyListeners
: [],

        /**
         * Contains optional dependencies to be loaded last
         * @private
         */

        optionalRequires
: [],

        /**
         * Map of fully qualified class names to an array of dependent classes.
         * @private
         */

        requiresMap
: {},

        /**
         * @private
         */

        numPendingFiles
: 0,

        /**
         * @private
         */

        numLoadedFiles
: 0,

        /** @private */
        hasFileLoadError: false,

        /**
         * @private
         */

        classNameToFilePathMap
: {},

        /**
         * @property {[String]} history
         * An array of class names to keep track of the dependency loading order.
         * This is not guaranteed to be the same everytime due to the asynchronous nature of the Loader.
         */

        history
: [],

        /**
         * Configuration
         * @private
         */

        config
: {
            /**
             * @cfg {Boolean} enabled
             * Whether or not to enable the dynamic dependency loading feature Defaults to false
             */

            enabled
: false,

            /**
             * @cfg {Boolean} disableCaching
             * Appends current timestamp to script files to prevent caching Defaults to true
             */

            disableCaching
: true,

            /**
             * @cfg {String} disableCachingParam
             * The get parameter name for the cache buster's timestamp. Defaults to '_dc'
             */

            disableCachingParam
: '_dc',

            /**
             * @cfg {Object} paths
             * The mapping from namespaces to file paths
             *
             *     {
             *         'Ext': '.', // This is set by default, Ext.layout.container.Container will be
             *                     // loaded from ./layout/Container.js
             *
             *         'My': './src/my_own_folder' // My.layout.Container will be loaded from
             *                                     // ./src/my_own_folder/layout/Container.js
             *     }
             *
             * Note that all relative paths are relative to the current HTML document.
             * If not being specified, for example, `Other.awesome.Class`
             * will simply be loaded from `./Other/awesome/Class.js`
             */

            paths
: {
               
'Ext': '.'
           
}
       
},

        /**
         * Set the configuration for the loader. This should be called right after ext-core.js
         * (or ext-core-debug.js) is included in the page, e.g.:
         *
         *     <script type="text/javascript" src="ext-core-debug.js"></script>
         *     <script type="text/javascript">
         *       Ext.Loader.setConfig({
         *           enabled: true,
         *           paths: {
         *               'My': 'my_own_path'
         *           }
         *       });
         *     <script>
         *     <script type="text/javascript">
         *       Ext.require(...);
         *
         *       Ext.onReady(function() {
         *           // application code here
         *       });
         *     </script>
         *
         * Refer to config options of {@link Ext.Loader} for the list of possible properties.
         *
         * @param {String/Object} name  Name of the value to override, or a config object to override multiple values.
         * @param {Object} value  (optional) The new value to set, needed if first parameter is String.
         * @return {Ext.Loader} this
         */

        setConfig
: function(name, value) {
           
if (Ext.isObject(name) && arguments.length === 1) {
               
Ext.Object.merge(this.config, name);
           
}
           
else {
               
this.config[name] = (Ext.isObject(value)) ? Ext.Object.merge(this.config[name], value) : value;
           
}

           
return this;
       
},

        /**
         * Get the config value corresponding to the specified name.
         * If no name is given, will return the config object.
         * @param {String} name The config property name
         * @return {Object/Mixed}
         */

        getConfig
: function(name) {
           
if (name) {
               
return this.config[name];
           
}

           
return this.config;
       
},

        /**
         * Sets the path of a namespace. For Example:
         *
         *     Ext.Loader.setPath('Ext', '.');
         *
         * @param {String/Object} name See {@link Ext.Function#flexSetter flexSetter}
         * @param {String} path See {@link Ext.Function#flexSetter flexSetter}
         * @return {Ext.Loader} this
         * @method
         */

        setPath
: flexSetter(function(name, path) {
           
//<if nonBrowser>
           
if (isNonBrowser) {
               
if (isNodeJS) {
                    path
= require('fs').realpathSync(path);
               
}
           
}
           
//</if>
           
this.config.paths[name] = path;

           
return this;
       
}),

        /**
         * Translates a className to a file path by adding the the proper prefix and converting the .'s to /'s.
         * For example:
         *
         *     Ext.Loader.setPath('My', '/path/to/My');
         *
         *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/path/to/My/awesome/Class.js'
         *
         * Note that the deeper namespace levels, if explicitly set, are always resolved first. For example:
         *
         *     Ext.Loader.setPath({
         *         'My': '/path/to/lib',
         *         'My.awesome': '/other/path/for/awesome/stuff',
         *         'My.awesome.more': '/more/awesome/path'
         *     });
         *
         *     alert(Ext.Loader.getPath('My.awesome.Class')); // alerts '/other/path/for/awesome/stuff/Class.js'
         *
         *     alert(Ext.Loader.getPath('My.awesome.more.Class')); // alerts '/more/awesome/path/Class.js'
         *
         *     alert(Ext.Loader.getPath('My.cool.Class')); // alerts '/path/to/lib/cool/Class.js'
         *
         *     alert(Ext.Loader.getPath('Unknown.strange.Stuff')); // alerts 'Unknown/strange/Stuff.js'
         *
         * @param {String} className
         * @return {String} path
         */

        getPath
: function(className) {
           
var path = '',
                paths
= this.config.paths,
                prefix
= this.getPrefix(className);

           
if (prefix.length > 0) {
               
if (prefix === className) {
                   
return paths[prefix];
               
}

                path
= paths[prefix];
                className
= className.substring(prefix.length + 1);
           
}

           
if (path.length > 0) {
                path
+= '/';
           
}

           
return path.replace(/\/\.\//g, '/') + className.replace(/\./g, "/") + '.js';
       
},

        /**
         * @private
         * @param {String} className
         */

        getPrefix
: function(className) {
           
var paths = this.config.paths,
                prefix
, deepestPrefix = '';

           
if (paths.hasOwnProperty(className)) {
               
return className;
           
}

           
for (prefix in paths) {
               
if (paths.hasOwnProperty(prefix) && prefix + '.' === className.substring(0, prefix.length + 1)) {
                   
if (prefix.length > deepestPrefix.length) {
                        deepestPrefix
= prefix;
                   
}
               
}
           
}

           
return deepestPrefix;
       
},

        /**
         * Refresh all items in the queue. If all dependencies for an item exist during looping,
         * it will execute the callback and call refreshQueue again. Triggers onReady when the queue is
         * empty
         * @private
         */

        refreshQueue
: function() {
           
var ln = this.queue.length,
                i
, item, j, requires;

           
if (ln === 0) {
               
this.triggerReady();
               
return;
           
}

           
for (i = 0; i < ln; i++) {
                item
= this.queue[i];

               
if (item) {
                    requires
= item.requires;

                   
// Don't bother checking when the number of files loaded
                   
// is still less than the array length
                   
if (requires.length > this.numLoadedFiles) {
                       
continue;
                   
}

                    j
= 0;

                   
do {
                       
if (Manager.isCreated(requires[j])) {
                           
// Take out from the queue
                           
Ext.Array.erase(requires, j, 1);
                       
}
                       
else {
                            j
++;
                       
}
                   
} while (j < requires.length);

                   
if (item.requires.length === 0) {
                       
Ext.Array.erase(this.queue, i, 1);
                        item
.callback.call(item.scope);
                       
this.refreshQueue();
                       
break;
                   
}
               
}
           
}

           
return this;
       
},

        /**
         * Inject a script element to document's head, call onLoad and onError accordingly
         * @private
         */

        injectScriptElement
: function(url, onLoad, onError, scope) {
           
var script = document.createElement('script'),
                me
= this,
                onLoadFn
= function() {
                    me
.cleanupScriptElement(script);
                    onLoad
.call(scope);
               
},
                onErrorFn
= function() {
                    me
.cleanupScriptElement(script);
                    onError
.call(scope);
               
};

            script
.type = 'text/javascript';
            script
.src = url;
            script
.onload = onLoadFn;
            script
.onerror = onErrorFn;
            script
.onreadystatechange = function() {
               
if (this.readyState === 'loaded' || this.readyState === 'complete') {
                    onLoadFn
();
               
}
           
};

           
this.documentHead.appendChild(script);

           
return script;
       
},

        /**
         * @private
         */

        cleanupScriptElement
: function(script) {
            script
.onload = null;
            script
.onreadystatechange = null;
            script
.onerror = null;

           
return this;
       
},

        /**
         * Load a script file, supports both asynchronous and synchronous approaches
         *
         * @param {String} url
         * @param {Function} onLoad
         * @param {Scope} scope
         * @param {Boolean} synchronous
         * @private
         */

        loadScriptFile
: function(url, onLoad, onError, scope, synchronous) {
           
var me = this,
                noCacheUrl
= url + (this.getConfig('disableCaching') ? ('?' + this.getConfig('disableCachingParam') + '=' + Ext.Date.now()) : ''),
                fileName
= url.split('/').pop(),
                isCrossOriginRestricted
= false,
                xhr
, status, onScriptError;

            scope
= scope || this;

           
this.isLoading = true;

           
if (!synchronous) {
                onScriptError
= function() {
                    onError
.call(scope, "Failed loading '" + url + "', please verify that the file exists", synchronous);
               
};

               
if (!Ext.isReady && Ext.onDocumentReady) {
                   
Ext.onDocumentReady(function() {
                        me
.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
                   
});
               
}
               
else {
                   
this.injectScriptElement(noCacheUrl, onLoad, onScriptError, scope);
               
}
           
}
           
else {
               
if (typeof XMLHttpRequest !== 'undefined') {
                    xhr
= new XMLHttpRequest();
               
} else {
                    xhr
= new ActiveXObject('Microsoft.XMLHTTP');
               
}

               
try {
                    xhr
.open('GET', noCacheUrl, false);
                    xhr
.send(null);
               
} catch (e) {
                    isCrossOriginRestricted
= true;
               
}

                status
= (xhr.status === 1223) ? 204 : xhr.status;

               
if (!isCrossOriginRestricted) {
                    isCrossOriginRestricted
= (status === 0);
               
}

               
if (isCrossOriginRestricted
               
//<if isNonBrowser>
               
&& !isPhantomJS
               
//</if>
               
) {
                    onError
.call(this, "Failed loading synchronously via XHR: '" + url + "'; It's likely that the file is either " +
                                       
"being loaded from a different domain or from the local file system whereby cross origin " +
                                       
"requests are not allowed due to security reasons. Use asynchronous loading with " +
                                       
"Ext.require instead.", synchronous);
               
}
               
else if (status >= 200 && status < 300
               
//<if isNonBrowser>
               
|| isPhantomJS
               
//</if>
               
) {
                   
// Firebug friendly, file names are still shown even though they're eval'ed code
                   
new Function(xhr.responseText + "\n//@ sourceURL=" + fileName)();

                    onLoad
.call(scope);
               
}
               
else {
                    onError
.call(this, "Failed loading synchronously via XHR: '" + url + "'; please " +
                                       
"verify that the file exists. " +
                                       
"XHR status code: " + status, synchronous);
               
}

               
// Prevent potential IE memory leak
                xhr
= null;
           
}
       
},

        /**
         * Explicitly exclude files from being loaded. Useful when used in conjunction with a broad include expression.
         * Can be chained with more `require` and `exclude` methods, e.g.:
         *
         *     Ext.exclude('Ext.data.*').require('*');
         *
         *     Ext.exclude('widget.button*').require('widget.*');
         *
         * {@link Ext#exclude Ext.exclude} is alias for {@link Ext.Loader#exclude Ext.Loader.exclude} for convenience.
         *
         * @param {String/[String]} excludes
         * @return {Object} object contains `require` method for chaining
         */

        exclude
: function(excludes) {
           
var me = this;

           
return {
                require
: function(expressions, fn, scope) {
                   
return me.require(expressions, fn, scope, excludes);
               
},

                syncRequire
: function(expressions, fn, scope) {
                   
return me.syncRequire(expressions, fn, scope, excludes);
               
}
           
};
       
},

        /**
         * Synchronously loads all classes by the given names and all their direct dependencies;
         * optionally executes the given callback function when finishes, within the optional scope.
         *
         * {@link Ext#syncRequire Ext.syncRequire} is alias for {@link Ext.Loader#syncRequire Ext.Loader.syncRequire} for convenience.
         *
         * @param {String/[String]} expressions Can either be a string or an array of string
         * @param {Function} fn (Optional) The callback function
         * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
         * @param {String/[String]} excludes (Optional) Classes to be excluded, useful when being used with expressions
         */

        syncRequire
: function() {
           
this.syncModeEnabled = true;
           
this.require.apply(this, arguments);
           
this.refreshQueue();
           
this.syncModeEnabled = false;
       
},

        /**
         * Loads all classes by the given names and all their direct dependencies;
         * optionally executes the given callback function when finishes, within the optional scope.
         *
         * {@link Ext#require Ext.require} is alias for {@link Ext.Loader#require Ext.Loader.require} for convenience.
         *
         * @param {String/[String]} expressions Can either be a string or an array of string
         * @param {Function} fn (Optional) The callback function
         * @param {Object} scope (Optional) The execution scope (`this`) of the callback function
         * @param {String/[String]} excludes (Optional) Classes to be excluded, useful when being used with expressions
         */

        require
: function(expressions, fn, scope, excludes) {
           
var filePath, expression, exclude, className, excluded = {},
                excludedClassNames
= [],
                possibleClassNames
= [],
                possibleClassName
, classNames = [],
                i
, j, ln, subLn;

            expressions
= Ext.Array.from(expressions);
            excludes
= Ext.Array.from(excludes);

            fn
= fn || Ext.emptyFn;

            scope
= scope || Ext.global;

           
for (i = 0, ln = excludes.length; i < ln; i++) {
                exclude
= excludes[i];

               
if (typeof exclude === 'string' && exclude.length > 0) {
                    excludedClassNames
= Manager.getNamesByExpression(exclude);

                   
for (j = 0, subLn = excludedClassNames.length; j < subLn; j++) {
                        excluded
[excludedClassNames[j]] = true;
                   
}
               
}
           
}

           
for (i = 0, ln = expressions.length; i < ln; i++) {
                expression
= expressions[i];

               
if (typeof expression === 'string' && expression.length > 0) {
                    possibleClassNames
= Manager.getNamesByExpression(expression);

                   
for (j = 0, subLn = possibleClassNames.length; j < subLn; j++) {
                        possibleClassName
= possibleClassNames[j];

                       
if (!excluded.hasOwnProperty(possibleClassName) && !Manager.isCreated(possibleClassName)) {
                           
Ext.Array.include(classNames, possibleClassName);
                       
}
                   
}
               
}
           
}

           
// If the dynamic dependency feature is not being used, throw an error
           
// if the dependencies are not defined
           
if (!this.config.enabled) {
               
if (classNames.length > 0) {
                   
Ext.Error.raise({
                        sourceClass
: "Ext.Loader",
                        sourceMethod
: "require",
                        msg
: "Ext.Loader is not enabled, so dependencies cannot be resolved dynamically. " +
                             
"Missing required class" + ((classNames.length > 1) ? "es" : "") + ": " + classNames.join(', ')
                   
});
               
}
           
}

           
if (classNames.length === 0) {
                fn
.call(scope);
               
return this;
           
}

           
this.queue.push({
                requires
: classNames,
                callback
: fn,
                scope
: scope
           
});

            classNames
= classNames.slice();

           
for (i = 0, ln = classNames.length; i < ln; i++) {
                className
= classNames[i];

               
if (!this.isFileLoaded.hasOwnProperty(className)) {
                   
this.isFileLoaded[className] = false;

                    filePath
= this.getPath(className);

                   
this.classNameToFilePathMap[className] = filePath;

                   
this.numPendingFiles++;

                   
//<if nonBrowser>
                   
if (isNonBrowser) {
                       
if (isNodeJS) {
                            require
(filePath);
                       
}
                       
// Temporary support for hammerjs
                       
else {
                           
var f = fs.open(filePath),
                                content
= '',
                                line
;

                           
while (true) {
                                line
= f.readLine();
                               
if (line.length === 0) {
                                   
break;
                               
}
                                content
+= line;
                           
}

                            f
.close();
                           
eval(content);
                       
}

                       
this.onFileLoaded(className, filePath);

                       
if (ln === 1) {
                           
return Manager.get(className);
                       
}

                       
continue;
                   
}
                   
//</if>
                   
this.loadScriptFile(
                        filePath
,
                       
Ext.Function.pass(this.onFileLoaded, [className, filePath], this),
                       
Ext.Function.pass(this.onFileLoadError, [className, filePath]),
                       
this,
                       
this.syncModeEnabled
                   
);
               
}
           
}

           
return this;
       
},

        /**
         * @private
         * @param {String} className
         * @param {String} filePath
         */

        onFileLoaded
: function(className, filePath) {
           
this.numLoadedFiles++;

           
this.isFileLoaded[className] = true;

           
this.numPendingFiles--;

           
if (this.numPendingFiles === 0) {
               
this.refreshQueue();
           
}

           
//<debug>
           
if (this.numPendingFiles <= 1) {
                window
.status = "Finished loading all dependencies, onReady fired!";
           
}
           
else {
                window
.status = "Loading dependencies, " + this.numPendingFiles + " files left...";
           
}
           
//</debug>

           
//<debug>
           
if (!this.syncModeEnabled && this.numPendingFiles === 0 && this.isLoading && !this.hasFileLoadError) {
               
var queue = this.queue,
                    requires
,
                    i
, ln, j, subLn, missingClasses = [], missingPaths = [];

               
for (i = 0, ln = queue.length; i < ln; i++) {
                    requires
= queue[i].requires;

                   
for (j = 0, subLn = requires.length; j < ln; j++) {
                       
if (this.isFileLoaded[requires[j]]) {
                            missingClasses
.push(requires[j]);
                       
}
                   
}
               
}

               
if (missingClasses.length < 1) {
                   
return;
               
}

                missingClasses
= Ext.Array.filter(missingClasses, function(item) {
                   
return !this.requiresMap.hasOwnProperty(item);
               
}, this);

               
for (i = 0,ln = missingClasses.length; i < ln; i++) {
                    missingPaths
.push(this.classNameToFilePathMap[missingClasses[i]]);
               
}

               
Ext.Error.raise({
                    sourceClass
: "Ext.Loader",
                    sourceMethod
: "onFileLoaded",
                    msg
: "The following classes are not declared even if their files have been " +
                           
"loaded: '" + missingClasses.join("', '") + "'. Please check the source code of their " +
                           
"corresponding files for possible typos: '" + missingPaths.join("', '") + "'"
               
});
           
}
           
//</debug>
       
},

        /**
         * @private
         */

        onFileLoadError
: function(className, filePath, errorMessage, isSynchronous) {
           
this.numPendingFiles--;
           
this.hasFileLoadError = true;

           
//<debug error>
           
Ext.Error.raise({
                sourceClass
: "Ext.Loader",
                classToLoad
: className,
                loadPath
: filePath,
                loadingType
: isSynchronous ? 'synchronous' : 'async',
                msg
: errorMessage
           
});
           
//</debug>
       
},

        /**
         * @private
         */

        addOptionalRequires
: function(requires) {
           
var optionalRequires = this.optionalRequires,
                i
, ln, require;

            requires
= Ext.Array.from(requires);

           
for (i = 0, ln = requires.length; i < ln; i++) {
                require
= requires[i];

               
Ext.Array.include(optionalRequires, require);
           
}

           
return this;
       
},

        /**
         * @private
         */

        triggerReady
: function(force) {
           
var readyListeners = this.readyListeners,
                optionalRequires
, listener;

           
if (this.isLoading || force) {
               
this.isLoading = false;

               
if (this.optionalRequires.length) {
                   
// Clone then empty the array to eliminate potential recursive loop issue
                    optionalRequires
= Ext.Array.clone(this.optionalRequires);

                   
// Empty the original array
                   
this.optionalRequires.length = 0;

                   
this.require(optionalRequires, Ext.Function.pass(this.triggerReady, [true], this), this);
                   
return this;
               
}

               
while (readyListeners.length) {
                    listener
= readyListeners.shift();
                    listener
.fn.call(listener.scope);

                   
if (this.isLoading) {
                       
return this;
                   
}
               
}
           
}

           
return this;
       
},

        /**
         * Adds new listener to be executed when all required scripts are fully loaded.
         *
         * @param {Function} fn The function callback to be executed
         * @param {Object} scope The execution scope (`this`) of the callback function
         * @param {Boolean} withDomReady Whether or not to wait for document dom ready as well
         */

        onReady
: function(fn, scope, withDomReady, options) {
           
var oldFn;

           
if (withDomReady !== false && Ext.onDocumentReady) {
                oldFn
= fn;

                fn
= function() {
                   
Ext.onDocumentReady(oldFn, scope, options);
               
};
           
}

           
if (!this.isLoading) {
                fn
.call(scope);
           
}
           
else {
               
this.readyListeners.push({
                    fn
: fn,
                    scope
: scope
               
});
           
}
       
},

        /**
         * @private
         * @param {String} className
         */

        historyPush
: function(className) {
           
if (className && this.isFileLoaded.hasOwnProperty(className)) {
               
Ext.Array.include(this.history, className);
           
}

           
return this;
       
}
   
};

    /**
     * @member Ext
     * @method require
     * @alias Ext.Loader#require
     */

   
Ext.require = alias(Loader, 'require');

    /**
     * @member Ext
     * @method syncRequire
     * @alias Ext.Loader#syncRequire
     */

   
Ext.syncRequire = alias(Loader, 'syncRequire');

    /**
     * @member Ext
     * @method exclude
     * @alias Ext.Loader#exclude
     */

   
Ext.exclude = alias(Loader, 'exclude');

    /**
     * @member Ext
     * @method onReady
     * @alias Ext.Loader#onReady
     */

   
Ext.onReady = function(fn, scope, options) {
       
Loader.onReady(fn, scope, true, options);
   
};

    /**
     * @cfg {[String]} requires
     * @member Ext.Class
     * List of classes that have to be loaded before instanciating this class.
     * For example:
     *
     *     Ext.define('Mother', {
     *         requires: ['Child'],
     *         giveBirth: function() {
     *             // we can be sure that child class is available.
     *             return new Child();
     *         }
     *     });
     */

   
Class.registerPreprocessor('loader', function(cls, data, continueFn) {
       
var me = this,
            dependencies
= [],
            className
= Manager.getName(cls),
            i
, j, ln, subLn, value, propertyName, propertyValue;

       
/*
        Basically loop through the dependencyProperties, look for string class names and push
        them into a stack, regardless of whether the property's value is a string, array or object. For example:
        {
              extend: 'Ext.MyClass',
              requires: ['Ext.some.OtherClass'],
              mixins: {
                  observable: 'Ext.util.Observable';
              }
        }
        which will later be transformed into:
        {
              extend: Ext.MyClass,
              requires: [Ext.some.OtherClass],
              mixins: {
                  observable: Ext.util.Observable;
              }
        }
        */


       
for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
            propertyName
= dependencyProperties[i];

           
if (data.hasOwnProperty(propertyName)) {
                propertyValue
= data[propertyName];

               
if (typeof propertyValue === 'string') {
                    dependencies
.push(propertyValue);
               
}
               
else if (propertyValue instanceof Array) {
                   
for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
                        value
= propertyValue[j];

                       
if (typeof value === 'string') {
                            dependencies
.push(value);
                       
}
                   
}
               
}
               
else {
                   
for (j in propertyValue) {
                       
if (propertyValue.hasOwnProperty(j)) {
                            value
= propertyValue[j];

                           
if (typeof value === 'string') {
                                dependencies
.push(value);
                           
}
                       
}
                   
}
               
}
           
}
       
}

       
if (dependencies.length === 0) {
//            Loader.historyPush(className);
           
return;
       
}

       
//<debug error>
       
var deadlockPath = [],
            requiresMap
= Loader.requiresMap,
            detectDeadlock
;

       
/*
        Automatically detect deadlocks before-hand,
        will throw an error with detailed path for ease of debugging. Examples of deadlock cases:

        - A extends B, then B extends A
        - A requires B, B requires C, then C requires A

        The detectDeadlock function will recursively transverse till the leaf, hence it can detect deadlocks
        no matter how deep the path is.
        */


       
if (className) {
            requiresMap
[className] = dependencies;

            detectDeadlock
= function(cls) {
                deadlockPath
.push(cls);

               
if (requiresMap[cls]) {
                   
if (Ext.Array.contains(requiresMap[cls], className)) {
                       
Ext.Error.raise({
                            sourceClass
: "Ext.Loader",
                            msg
: "Deadlock detected while loading dependencies! '" + className + "' and '" +
                                deadlockPath
[1] + "' " + "mutually require each other. Path: " +
                                deadlockPath
.join(' -> ') + " -> " + deadlockPath[0]
                       
});
                   
}

                   
for (i = 0, ln = requiresMap[cls].length; i < ln; i++) {
                        detectDeadlock
(requiresMap[cls][i]);
                   
}
               
}
           
};

            detectDeadlock
(className);
       
}

       
//</debug>

       
Loader.require(dependencies, function() {
           
for (i = 0, ln = dependencyProperties.length; i < ln; i++) {
                propertyName
= dependencyProperties[i];

               
if (data.hasOwnProperty(propertyName)) {
                    propertyValue
= data[propertyName];

                   
if (typeof propertyValue === 'string') {
                        data
[propertyName] = Manager.get(propertyValue);
                   
}
                   
else if (propertyValue instanceof Array) {
                       
for (j = 0, subLn = propertyValue.length; j < subLn; j++) {
                            value
= propertyValue[j];

                           
if (typeof value === 'string') {
                                data
[propertyName][j] = Manager.get(value);
                           
}
                       
}
                   
}
                   
else {
                       
for (var k in propertyValue) {
                           
if (propertyValue.hasOwnProperty(k)) {
                                value
= propertyValue[k];

                               
if (typeof value === 'string') {
                                    data
[propertyName][k] = Manager.get(value);
                               
}
                           
}
                       
}
                   
}
               
}
           
}

            continueFn
.call(me, cls, data);
       
});

       
return false;
   
}, true);

   
Class.setDefaultPreprocessorPosition('loader', 'after', 'className');

    /**
     * @cfg {[String]} uses
     * @member Ext.Class
     * List of classes to load together with this class.  These aren't neccessarily loaded before
     * this class is instanciated. For example:
     *
     *     Ext.define('Mother', {
     *         uses: ['Child'],
     *         giveBirth: function() {
     *             // This code might, or might not work:
     *             // return new Child();
     *
     *             // Instead use Ext.create() to load the class at the spot if not loaded already:
     *             return Ext.create('Child');
     *         }
     *     });
     */

   
Manager.registerPostprocessor('uses', function(name, cls, data) {
       
var uses = Ext.Array.from(data.uses),
            items
= [],
            i
, ln, item;

       
for (i = 0, ln = uses.length; i < ln; i++) {
            item
= uses[i];

           
if (typeof item === 'string') {
                items
.push(item);
           
}
       
}

       
Loader.addOptionalRequires(items);
   
});

   
Manager.setDefaultPostprocessorPosition('uses', 'last');

})(Ext.ClassManager, Ext.Class, Ext.Function.flexSetter, Ext.Function.alias);