// here, the extra check for window['Ext'] is needed for use with cmd-test
// code injection.  we need to make that this file will sync up with page global
// scope to avoid duplicate Ext.Boot state.  That check is after the initial Ext check
// to allow the sandboxing template to inject an appropriate Ext var and prevent the
// global detection.
var Ext = Ext || window['Ext'] || {};


//<editor-fold desc="Microloader">
/**
 * @class Ext.Microloader
 * @private
 * @singleton
 */
Ext.Microloader = Ext.Microloader || (function () {
    var Boot = Ext.Boot,
    //<debug>
        _debug = function (message) {
            //console.log(message);
        },
    //</debug>
        _warn = function (message) {
            console.log("[WARN] " + message);
        },
        _privatePrefix = '_ext:' + location.pathname,

        /**
         * @method getStorageKey
         * The Following combination is used to create isolated local storage keys
         * '_ext' is used to scope all the local storage keys that we internally by Ext
         * 'location.pathname' is used to force each assets to cache by an absolute URL (/build/MyApp) (dev vs prod)
         * 'url' is used to force each asset to cache relative to the page (app.json vs resources/app.css)
         * 'profileId' is used to differentiate the builds of an application (neptune vs crisp)
         * 'Microloader.appId' is unique to the application and will differentiate apps on the same host (dev mode running app watch against multiple apps)
         */
        getStorageKey = function(url, profileId) {
            return  _privatePrefix + url + '-' + (profileId ? profileId + '-' : '') + Microloader.appId;
        },
        postProcessor, _storage;

    try {
        _storage = window['localStorage'];
    } catch(ex) {
        // ignore
    }

    var _cache = window['applicationCache'],
        // Local Storage Controller
        LocalStorage = {
            clearAllPrivate: function(manifest) {
                if(_storage) {

                    //Remove the entry for the manifest first
                    _storage.removeItem(manifest.key);

                    var i, key,
                        removeKeys = [],
                        suffix = manifest.profile + '-' + Microloader.appId,
                        ln = _storage.length;
                    for (i = 0; i < ln; i++) {
                        key = _storage.key(i);
                        // If key starts with the private key and the suffix is present we can clear this entry
                        if (key.indexOf(_privatePrefix) === 0 && key.indexOf(suffix) !== -1) {
                            removeKeys.push(key);
                        }
                    }

                    for(i in removeKeys) {
                        //<debug>
                        _debug("Removing "+ removeKeys[i] + " from Local Storage");
                        //</debug>
                        _storage.removeItem(removeKeys[i]);
                    }
                }
            },
            /**
             * @private
             */
            retrieveAsset: function (key) {
                try {
                    return _storage.getItem(key);
                }
                catch (e) {
                    // Private browsing mode
                    return null;
                }
            },

            setAsset: function(key, content) {
                try {
                    if (content === null || content == '') {
                        _storage.removeItem(key);
                    } else {
                        _storage.setItem(key, content);
                    }
                }
                catch (e) {
                    if (_storage && e.code == e.QUOTA_EXCEEDED_ERR) {
                        //<debug>
                        _warn("LocalStorage Quota exceeded, cannot store " + key + " locally");
                        //</debug>
                    }
                }
            }
        };

        var Asset = function (cfg) {
            if (typeof cfg.assetConfig === 'string') {
                this.assetConfig = {
                    path: cfg.assetConfig
                };
            } else {
                this.assetConfig = cfg.assetConfig;
            }

            this.type = cfg.type;
            this.key = getStorageKey(this.assetConfig.path, cfg.manifest.profile);

            if (cfg.loadFromCache) {
                this.loadFromCache();
            }
        };

        Asset.prototype = {
            shouldCache: function() {
                return _storage && this.assetConfig.update && this.assetConfig.hash && !this.assetConfig.remote;
            },

            is: function (asset) {
                return (!!asset && this.assetConfig && asset.assetConfig && (this.assetConfig.hash === asset.assetConfig.hash))
            },

            cache: function(content) {
                if (this.shouldCache()) {
                    LocalStorage.setAsset(this.key, content || this.content);
                }
            },

            uncache: function() {
                LocalStorage.setAsset(this.key, null);
            },

            updateContent: function (content) {
                this.content = content;
            },

            getSize: function () {
                return this.content ? this.content.length : 0;
            },

            loadFromCache: function() {
                if (this.shouldCache()) {
                    this.content = LocalStorage.retrieveAsset(this.key);
                }
            }
        };

        var Manifest = function (cfg) {
            if (typeof cfg.content === "string") {
                this.content = JSON.parse(cfg.content);
            } else {
                this.content = cfg.content;
            }
            this.assetMap = {};

            this.url = cfg.url;
            this.fromCache = !!cfg.cached;
            this.assetCache = !(cfg.assetCache === false);
            this.key = getStorageKey(this.url);

            // Pull out select properties for repetitive use
            this.profile = this.content.profile;
            this.hash = this.content.hash;
            this.loadOrder = this.content.loadOrder;
            this.deltas = this.content.cache ? this.content.cache.deltas : null;
            this.cacheEnabled = this.content.cache ? this.content.cache.enable : false;

            this.loadOrderMap = (this.loadOrder) ? Boot.createLoadOrderMap(this.loadOrder) : null;

            var tags = this.content.tags,
                platformTags = Ext.platformTags;

            if (tags) {
                if (tags instanceof Array) {
                    for (var i = 0; i < tags.length; i++) {
                        platformTags[tags[i]] = true;
                    }
                } else {
                    Boot.apply(platformTags, tags);
                }

                // re-apply the query parameters, so that the params as specified
                // in the url always has highest priority
                Boot.apply(platformTags, Boot.loadPlatformsParam());
            }

            // Convert all assets into Assets
            this.js = this.processAssets(this.content.js, 'js');
            this.css = this.processAssets(this.content.css, 'css');
        };

        Manifest.prototype = {
            processAsset:  function(assetConfig, type) {
                var processedAsset = new Asset({
                    manifest: this,
                    assetConfig: assetConfig,
                    type: type,
                    loadFromCache: this.assetCache
                });
                this.assetMap[assetConfig.path] = processedAsset;
                return processedAsset;
            },

            processAssets: function(assets, type) {
                var results = [],
                    ln = assets.length,
                    i, assetConfig;

                for (i = 0; i < ln; i++) {
                    assetConfig = assets[i];
                    results.push(this.processAsset(assetConfig, type));
                }

                return results;
            },

            useAppCache: function() {
                return true;
            },

            // Concatenate all assets for easy access
            getAssets: function () {
                return this.css.concat(this.js);
            },

            getAsset: function (path) {
                return this.assetMap[path];
            },

            shouldCache: function() {
                return this.hash && this.cacheEnabled;
            },

            cache: function(content) {
                if (this.shouldCache()) {
                    LocalStorage.setAsset(this.key, JSON.stringify(content || this.content));
                }
                //<debug>
                else {
                    _debug("Manifest caching is disabled.");
                }
                //</debug>
            },

            is: function(manifest) {
                //<debug>
                _debug("Testing Manifest: " + this.hash + " VS " +  manifest.hash);
                //</debug>
                return this.hash === manifest.hash;
            },

            // Clear the manifest from local storage
            uncache: function() {
                LocalStorage.setAsset(this.key, null);
            },

            exportContent: function() {
                return Boot.apply({
                    loadOrderMap: this.loadOrderMap
                }, this.content);
            }
        };

        /**
         * Microloader
         * @type {Array}
         * @private
         */
        var _listeners = [],
        _loaded = false,
        Microloader = {
            init: function () {
                Ext.microloaded = true;

                // data-app is in the dev template for an application and is also
                // injected into the app my CMD for production
                // We use this to prefix localStorage cache to prevent collisions
                var microloaderElement = document.getElementById('microloader');
                Microloader.appId = microloaderElement ? microloaderElement.getAttribute('data-app') : '';

                if (Ext.beforeLoad) {
                    postProcessor = Ext.beforeLoad(Ext.platformTags);
                }

                var readyHandler = Ext._beforereadyhandler;

                Ext._beforereadyhandler = function () {
                    if (Ext.Boot !== Boot) {
                        Ext.apply(Ext.Boot, Boot);
                        Ext.Boot = Boot;
                    }
                    if (readyHandler) {
                        readyHandler();
                    }
                };
            },

            applyCacheBuster: function(url) {
                var tstamp = new Date().getTime(),
                    sep = url.indexOf('?') === -1 ? '?' : '&';
                url = url + sep + "_dc=" + tstamp;
                return url;
            },

            run: function() {
                Microloader.init();
                var manifest = Ext.manifest;

                if (typeof manifest === "string") {
                    var extension = ".json",
                        url = manifest.indexOf(extension) === manifest.length - extension.length
                            ? manifest
                            : manifest + ".json",
                        key = getStorageKey(url),
                        content = LocalStorage.retrieveAsset(key);

                    // Manifest found in local storage, use this for immediate boot except in PhantomJS environments for building.
                    if (content) {
                        //<debug>
                            _debug("Manifest file, '" + url + "', was found in Local Storage");
                        //</debug>
                        manifest = new Manifest({
                            url: url,
                            content: content,
                            cached: true
                        });
                        if (postProcessor) {
                            postProcessor(manifest);
                        }
                        Microloader.load(manifest);


                    // Manifest is not in local storage. Fetch it from the server
                    } else {
                        //<debug>
                        _debug("Manifest file was not found in Local Storage, loading: " + url);
                        //</debug>

                        if (location.href.indexOf('file:/') === 0) {
                            Manifest.url = Microloader.applyCacheBuster(url + 'p');
                            Boot.load(Manifest.url);
                        }
                        else {
                            Boot.fetch(Microloader.applyCacheBuster(url), function(result) {
                                Microloader.setManifest(result.content);
                            });
                        }
                    }

                // Embedded Manifest into JS file
                } else {
                    //<debug>
                        _debug("Manifest was embedded into application javascript file");
                    //</debug>
                    manifest = new Manifest({
                        content: manifest
                    });
                    Microloader.load(manifest);
                }
            },

            /**
             *
             * @param cfg
             */
            setManifest: function(cfg) {
                var manifest = new Manifest({
                    url: Manifest.url,
                    content: cfg
                });
                manifest.cache();
                if (postProcessor) {
                    postProcessor(manifest);
                }
                Microloader.load(manifest);
            },

            /**
             * @param {Manifest} manifest
             */
            load: function (manifest) {
                Microloader.urls = [];
                Microloader.manifest = manifest;
                Ext.manifest = Microloader.manifest.exportContent();

                var assets = manifest.getAssets(),
                    cachedAssets = [],
                    asset, i, len, include, entry;

                for (len = assets.length, i = 0; i < len; i++) {
                    asset = assets[i];
                    include = Microloader.filterAsset(asset);
                    if (include) {
                        // Asset is using the localStorage caching system
                        if (manifest.shouldCache() && asset.shouldCache()) {
                            // Asset already has content from localStorage, instantly seed that into boot
                            if (asset.content) {
                                //<debug>
                                    _debug("Asset: " + asset.assetConfig.path + " was found in local storage. No remote load for this file");
                                //</debug>
                                entry = Boot.registerContent(asset.assetConfig.path, asset.type, asset.content);
                                if (entry.evaluated) {
                                    _warn("Asset: " + asset.assetConfig.path + " was evaluated prior to local storage being consulted.");
                                }
                            //load via AJAX and seed content into Boot
                            } else {
                                //<debug>
                                    _debug("Asset: " + asset.assetConfig.path + " was NOT found in local storage. Adding to load queue");
                                //</debug>
                                cachedAssets.push(asset);
                            }
                        }
                        Microloader.urls.push(asset.assetConfig.path);
                        Boot.assetConfig[asset.assetConfig.path] = Boot.apply({type: asset.type}, asset.assetConfig);
                    }
                }

                // If any assets are using the caching system and do not have local versions load them first via AJAX
                if (cachedAssets.length > 0) {
                    Microloader.remainingCachedAssets = cachedAssets.length;
                    while (cachedAssets.length > 0) {
                        asset = cachedAssets.pop();
                        //<debug>
                            _debug("Preloading/Fetching Cached Assets from: " + asset.assetConfig.path);
                        //</debug>
                        Boot.fetch(asset.assetConfig.path, (function(asset) {
                            return function(result) {
                                Microloader.onCachedAssetLoaded(asset, result);
                            }
                        })(asset));
                    }
                } else {
                    Microloader.onCachedAssetsReady();
                }
            },

            // Load the asset and seed its content into Boot to be evaluated in sequence
            onCachedAssetLoaded: function (asset, result) {
                var checksum;
                result = Microloader.parseResult(result);
                Microloader.remainingCachedAssets--;

                if (!result.error) {
                    checksum = Microloader.checksum(result.content, asset.assetConfig.hash);
                    if (!checksum) {
                        _warn("Cached Asset '" + asset.assetConfig.path + "' has failed checksum. This asset will be uncached for future loading");

                        // Un cache this asset so it is loaded next time
                        asset.uncache();
                    }

                    //<debug>
                        _debug("Checksum for Cached Asset: " + asset.assetConfig.path + " is " + checksum);
                    //</debug>
                    Boot.registerContent(asset.assetConfig.path, asset.type, result.content);
                    asset.updateContent(result.content);
                    asset.cache();
                } else {
                    _warn("There was an error pre-loading the asset '" + asset.assetConfig.path + "'. This asset will be uncached for future loading");

                    // Un cache this asset so it is loaded next time
                    asset.uncache();
                }

                if (Microloader.remainingCachedAssets === 0) {
                    Microloader.onCachedAssetsReady();
                }
            },

            onCachedAssetsReady: function(){
                Boot.load({
                    url: Microloader.urls,
                    loadOrder: Microloader.manifest.loadOrder,
                    loadOrderMap: Microloader.manifest.loadOrderMap,
                    sequential: true,
                    success: Microloader.onAllAssetsReady,
                    failure: Microloader.onAllAssetsReady
                });
            },

            onAllAssetsReady: function() {
                _loaded = true;
                Microloader.notify();

                if (navigator.onLine !== false) {
                    //<debug>
                        _debug("Application is online, checking for updates");
                    //</debug>
                    Microloader.checkAllUpdates();
                }
                else {
                    //<debug>
                        _debug("Application is offline, adding online listener to check for updates");
                    //</debug>
                    if(window['addEventListener']) {
                        window.addEventListener('online', Microloader.checkAllUpdates, false);
                    }
                }
            },

            onMicroloaderReady: function (listener) {
                if (_loaded) {
                    listener();
                } else {
                    _listeners.push(listener);
                }
            },

            /**
             * @private
             */
            notify: function () {
                //<debug>
                    _debug("notifying microloader ready listeners.");
                //</debug>
                var listener;
                while((listener = _listeners.shift())) {
                    listener();
                }
            },

            // Delta patches content
            patch: function (content, delta) {
                var output = [],
                    chunk, i, ln;

                if (delta.length === 0) {
                    return content;
                }

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

                    if (typeof chunk === 'number') {
                        output.push(content.substring(chunk, chunk + delta[++i]));
                    }
                    else {
                        output.push(chunk);
                    }
                }

                return output.join('');
            },

            checkAllUpdates: function() {
                //<debug>
                    _debug("Checking for All Updates");
                //</debug>
                if(window['removeEventListener']) {
                    window.removeEventListener('online', Microloader.checkAllUpdates, false);
                }

                if(_cache) {
                    Microloader.checkForAppCacheUpdate();
                }

                // Manifest came from a cached instance, check for updates
                if (Microloader.manifest.fromCache) {
                    Microloader.checkForUpdates();
                }
            },

            checkForAppCacheUpdate: function() {
                //<debug>
                    _debug("Checking App Cache status");
                //</debug>
                if (_cache.status === _cache.UPDATEREADY || _cache.status === _cache.OBSOLETE) {
                    //<debug>
                        _debug("App Cache is already in an updated");
                    //</debug>
                    Microloader.appCacheState = 'updated';
                } else if (_cache.status !== _cache.IDLE && _cache.status !== _cache.UNCACHED) {
                    //<debug>
                        _debug("App Cache is checking or downloading updates, adding listeners");
                    //</debug>
                    Microloader.appCacheState = 'checking';
                    _cache.addEventListener('error', Microloader.onAppCacheError);
                    _cache.addEventListener('noupdate', Microloader.onAppCacheNotUpdated);
                    _cache.addEventListener('cached', Microloader.onAppCacheNotUpdated);
                    _cache.addEventListener('updateready', Microloader.onAppCacheReady);
                    _cache.addEventListener('obsolete', Microloader.onAppCacheObsolete);
                } else {
                    //<debug>
                        _debug("App Cache is current or uncached");
                    //</debug>
                    Microloader.appCacheState = 'current';
                }
            },

            checkForUpdates: function() {
                // Fetch the Latest Manifest from the server
                //<debug>
                    _debug("Checking for updates at: " + Microloader.manifest.url);
                //</debug>
                Boot.fetch(Microloader.applyCacheBuster(Microloader.manifest.url), Microloader.onUpdatedManifestLoaded);
            },

            onAppCacheError: function(e) {
                _warn(e.message);

                Microloader.appCacheState = 'error';
                Microloader.notifyUpdateReady();
            },

            onAppCacheReady: function() {
                _cache.swapCache();
                Microloader.appCacheUpdated();
            },

            onAppCacheObsolete: function() {
                Microloader.appCacheUpdated();
            },

            appCacheUpdated: function() {
                //<debug>
                    _debug("App Cache Updated");
                //</debug>
                Microloader.appCacheState = 'updated';
                Microloader.notifyUpdateReady();
            },

            onAppCacheNotUpdated: function() {
                //<debug>
                    _debug("App Cache Not Updated Callback");
                //</debug>
                Microloader.appCacheState = 'current';
                Microloader.notifyUpdateReady();
            },


            filterAsset: function(asset) {
                var cfg = (asset && asset.assetConfig) || {};
                if(cfg.platform || cfg.exclude) {
                    return Boot.filterPlatform(cfg.platform, cfg.exclude);
                }
                return true;
            },

            onUpdatedManifestLoaded: function (result) {
                result = Microloader.parseResult(result);

                if (!result.error) {
                    var currentAssets, newAssets, currentAsset, newAsset, prop,
                        assets, deltas, deltaPath, include,
                        updatingAssets = [],
                        manifest = new Manifest({
                            url: Microloader.manifest.url,
                            content: result.content,
                            assetCache: false
                        });

                    Microloader.remainingUpdatingAssets = 0;
                    Microloader.updatedAssets = [];
                    Microloader.removedAssets = [];
                    Microloader.updatedManifest = null;
                    Microloader.updatedAssetsReady = false;

                    // If the updated manifest has turned off caching we need to clear out all local storage
                    // and trigger a appupdate as all content is now uncached
                    if (!manifest.shouldCache()) {
                        //<debug>
                        _debug("New Manifest has caching disabled, clearing out any private storage");
                        //</debug>

                        Microloader.updatedManifest = manifest;
                        LocalStorage.clearAllPrivate(manifest);
                        Microloader.onAllUpdatedAssetsReady();
                        return;
                    }

                    // Manifest itself has changed
                    if (!Microloader.manifest.is(manifest)) {
                        Microloader.updatedManifest = manifest;

                        currentAssets = Microloader.manifest.getAssets();
                        newAssets = manifest.getAssets();

                        // Look through new assets for assets that do not exist or assets that have different versions
                        for (prop in newAssets) {
                            newAsset = newAssets[prop];
                            currentAsset = Microloader.manifest.getAsset(newAsset.assetConfig.path);
                            include = Microloader.filterAsset(newAsset);

                            if (include && (!currentAsset || (newAsset.shouldCache() && (!currentAsset.is(newAsset))))) {
                                //<debug>
                                    _debug("New/Updated Version of Asset: " + newAsset.assetConfig.path + " was found in new manifest");
                                //</debug>
                                updatingAssets.push({_new: newAsset, _current: currentAsset});
                            }
                        }

                        // Look through current assets for stale/old assets that have been removed
                        for (prop in currentAssets) {
                            currentAsset = currentAssets[prop];
                            newAsset = manifest.getAsset(currentAsset.assetConfig.path);

                            //New version of this asset has been filtered out
                            include = !Microloader.filterAsset(newAsset);

                            if (!include || !newAsset || (currentAsset.shouldCache() && !newAsset.shouldCache())) {
                                //<debug>
                                    _debug("Asset: " + currentAsset.assetConfig.path + " was not found in new manifest, has been filtered out or has been switched to not cache. Marked for removal");
                                //</debug>
                                Microloader.removedAssets.push(currentAsset);
                            }
                        }

                        // Loop through all assets that need updating
                        if (updatingAssets.length > 0) {
                            Microloader.remainingUpdatingAssets = updatingAssets.length;
                            while (updatingAssets.length > 0) {
                                assets = updatingAssets.pop();
                                newAsset = assets._new;
                                currentAsset = assets._current;

                                // Full Updates will simply download the file and replace its current content
                                if (newAsset.assetConfig.update === "full" || !currentAsset) {

                                    //<debug>
                                    if (newAsset.assetConfig.update === "delta") {
                                        _debug("Delta updated asset found without current asset available: " + newAsset.assetConfig.path + " fetching full file");
                                    } else {
                                        _debug("Full update found for: " + newAsset.assetConfig.path + " fetching");
                                    }
                                    //</debug>

                                    // Load the asset and cache its  its content into Boot to be evaluated in sequence
                                    Boot.fetch(newAsset.assetConfig.path, (function (asset) {
                                            return function (result) {
                                                Microloader.onFullAssetUpdateLoaded(asset, result)
                                            };
                                        }(newAsset))
                                    );

                                    // Delta updates will be given a delta patch
                                } else if (newAsset.assetConfig.update === "delta") {
                                    deltas = manifest.deltas;
                                    deltaPath = deltas + "/" + newAsset.assetConfig.path + "/" + currentAsset.assetConfig.hash + ".json";
                                    // Fetch the Delta Patch and update the contents of the asset
                                    //<debug>
                                        _debug("Delta update found for: " + newAsset.assetConfig.path + " fetching");
                                    //</debug>
                                    Boot.fetch(deltaPath,
                                        (function (asset, oldAsset) {
                                            return function (result) {
                                                Microloader.onDeltaAssetUpdateLoaded(asset, oldAsset, result)
                                            };
                                        }(newAsset, currentAsset))
                                    );
                                }
                            }
                        } else {
                            //<debug>
                                _debug("No Assets needed updating");
                            //</debug>
                            Microloader.onAllUpdatedAssetsReady();
                        }
                    } else {
                        //<debug>
                            _debug("Manifest files have matching hash's");
                        //</debug>
                        Microloader.onAllUpdatedAssetsReady();
                    }
                } else {
                    _warn("Error loading manifest file to check for updates");
                    Microloader.onAllUpdatedAssetsReady();
                }
            },

            onFullAssetUpdateLoaded: function(asset, result) {
                var checksum;
                result = Microloader.parseResult(result);
                Microloader.remainingUpdatingAssets--;

                if (!result.error) {
                    checksum = Microloader.checksum(result.content, asset.assetConfig.hash);
                    //<debug>
                        _debug("Checksum for Full asset: " + asset.assetConfig.path + " is " + checksum);
                    //</debug>
                    if (!checksum) {
                        //<debug>
                            _debug("Full Update Asset: " + asset.assetConfig.path + " has failed checksum. This asset will be uncached for future loading");
                        //</debug>

                        // uncache this asset as there is a new version somewhere that has not been loaded.
                        asset.uncache();
                    } else {
                        asset.updateContent(result.content);
                        Microloader.updatedAssets.push(asset);
                    }
                } else {
                    //<debug>
                        _debug("Error loading file at" + asset.assetConfig.path + ". This asset will be uncached for future loading");
                    //</debug>

                    // uncache this asset as there is a new version somewhere that has not been loaded.
                    asset.uncache();
                }

                if (Microloader.remainingUpdatingAssets === 0) {
                        Microloader.onAllUpdatedAssetsReady();
                }
            },

            onDeltaAssetUpdateLoaded: function(asset, oldAsset, result) {
                var json, checksum, content;
                result = Microloader.parseResult(result);
                Microloader.remainingUpdatingAssets--;

                if (!result.error) {
                    //<debug>
                        _debug("Delta patch loaded successfully, patching content");
                    //</debug>
                    try {
                        json = JSON.parse(result.content);
                        content = Microloader.patch(oldAsset.content, json);
                        checksum = Microloader.checksum(content, asset.assetConfig.hash);
                        //<debug>
                            _debug("Checksum for Delta Patched asset: " + asset.assetConfig.path + " is " + checksum);
                        //</debug>
                        if (!checksum) {
                            //<debug>
                                _debug("Delta Update Asset: " + asset.assetConfig.path + " has failed checksum. This asset will be uncached for future loading");
                            //</debug>

                            // uncache this asset as there is a new version somewhere that has not been loaded.
                            asset.uncache();
                        } else {
                            asset.updateContent(content);
                            Microloader.updatedAssets.push(asset);
                        }
                    } catch (e) {
                        _warn("Error parsing delta patch for " + asset.assetConfig.path + " with hash " + oldAsset.assetConfig.hash + " . This asset will be uncached for future loading");
                        // uncache this asset as there is a new version somewhere that has not been loaded.
                        asset.uncache();
                    }
                } else {
                    _warn("Error loading delta patch for " + asset.assetConfig.path + " with hash " + oldAsset.assetConfig.hash + " . This asset will be uncached for future loading");

                    // uncache this asset as there is a new version somewhere that has not been loaded.
                    asset.uncache();
                }
                if (Microloader.remainingUpdatingAssets === 0) {
                    Microloader.onAllUpdatedAssetsReady();
                }
            },

            //TODO: Make this all transaction based to allow for reverting if quota is exceeded
            onAllUpdatedAssetsReady: function() {
                var asset;
                Microloader.updatedAssetsReady = true;

                if (Microloader.updatedManifest) {
                    while (Microloader.removedAssets.length > 0) {
                        asset = Microloader.removedAssets.pop();
                        //<debug>
                            _debug("Asset: " + asset.assetConfig.path + " was removed, un-caching");
                        //</debug>
                        asset.uncache();
                    }

                    if (Microloader.updatedManifest) {
                        //<debug>
                        _debug("Manifest was updated, re-caching");
                        //</debug>
                        Microloader.updatedManifest.cache();
                    }

                    while (Microloader.updatedAssets.length > 0) {
                        asset = Microloader.updatedAssets.pop();
                        //<debug>
                            _debug("Asset: " + asset.assetConfig.path + " was updated, re-caching");
                        //</debug>
                        asset.cache();
                    }

                }

                Microloader.notifyUpdateReady();
            },

            notifyUpdateReady: function () {
                if (Microloader.appCacheState !== 'checking' && Microloader.updatedAssetsReady) {
                    if (Microloader.appCacheState === 'updated' || Microloader.updatedManifest) {
                        //<debug>
                            _debug("There was an update here you will want to reload the app, trigger an event");
                        //</debug>
                        Microloader.appUpdate = {
                            updated: true,
                            app: Microloader.appCacheState === 'updated',
                            manifest: Microloader.updatedManifest && Microloader.updatedManifest.exportContent()
                        };

                        Microloader.fireAppUpdate();
                    }
                    //<debug>
                    else {
                        _debug("AppCache and LocalStorage Cache are current, no updating needed");
                        Microloader.appUpdate = {};
                    }
                    //</debug>
                }
            },

            fireAppUpdate: function() {
                if (Ext.GlobalEvents) {
                    // We defer dispatching this event slightly in order to let the application finish loading
                    // as we are still very early in the lifecycle
                    Ext.defer(function() {
                        Ext.GlobalEvents.fireEvent('appupdate', Microloader.appUpdate);
                    }, 1000);
                }
            },

            checksum: function(content, hash) {
                if(!content || !hash) {
                    return false;
                }

                var passed = true,
                    hashLn = hash.length,
                    checksumType = content.substring(0, 1);

                if (checksumType == '/') {
                    if (content.substring(2, hashLn + 2) !== hash) {
                        passed = false;
                    }
                } else if (checksumType == 'f') {
                    if (content.substring(10, hashLn + 10) !== hash) {
                        passed = false;
                    }
                } else if (checksumType == '.') {
                    if (content.substring(1, hashLn + 1) !== hash) {
                        passed = false;
                    }
                }
                return passed;
            },
            parseResult: function(result) {
                var rst = {};
                if ((result.exception || result.status === 0) && !Boot.env.phantom) {
                    rst.error = true;
                } else if ((result.status >= 200 && result.status < 300) || result.status === 304
                    || Boot.env.phantom
                    || (result.status === 0 && result.content.length > 0)
                ) {
                    rst.content = result.content;
                } else {
                    rst.error = true;
                }
                return rst;
            }
        };

    return Microloader;
}());

/**
 * @type {String/Object}
 */
Ext.manifest = Ext.manifest || "bootstrap";

Ext.Microloader.run();