/**
 * @author Ed Spencer
 *
 * Ext.app.Application defines the set of {@link Ext.data.Model Models}, {@link Ext.app.Controller Controllers},
 * {@link Ext.app.Profile Profiles}, {@link Ext.data.Store Stores} and {@link Ext.Component Views} that an application
 * consists of. It automatically loads all of those dependencies and can optionally specify a {@link #launch} function
 * that will be called when everything is ready.
 *
 * Sample usage:
 *
 *     Ext.application({
 *         name: 'MyApp',
 *
 *         models: ['User', 'Group'],
 *         stores: ['Users'],
 *         controllers: ['Users'],
 *         views: ['Main', 'ShowUser'],
 *
 *         launch: function() {
 *             Ext.create('MyApp.view.Main');
 *         }
 *     });
 *
 * Creating an Application instance is the only time in Sencha Touch that we don't use Ext.create to create the new
 * instance. Instead, the {@link Ext#application} function instantiates an Ext.app.Application internally,
 * automatically loading the Ext.app.Application class if it is not present on the page already and hooking in to
 * {@link Ext#onReady} before creating the instance itself. An alternative is to use Ext.create inside an Ext.onReady
 * callback, but Ext.application is preferred.
 *
 * ## Dependencies
 *
 * Application follows a simple convention when it comes to specifying the controllers, views, models, stores and
 * profiles it requires. By default it expects each of them to be found inside the *app/controller*, *app/view*,
 * *app/model*, *app/store* and *app/profile* directories in your app - if you follow this convention you can just
 * specify the last part of each class name and Application will figure out the rest for you:
 *
 *     Ext.application({
 *         name: 'MyApp',
 *
 *         controllers: ['Users'],
 *         models: ['User', 'Group'],
 *         stores: ['Users'],
 *         views: ['Main', 'ShowUser']
 *     });
 *
 * The example above will load 6 files:
 *
 * - app/model/User.js
 * - app/model/Group.js
 * - app/store/Users.js
 * - app/controller/Users.js
 * - app/view/Main.js
 * - app/view/ShowUser.js
 *
 * ### Nested Dependencies
 *
 * For larger apps it's common to split the models, views and controllers into subfolders to keep the project
 * organized. This is especially true of views - it's not unheard of for large apps to have over a hundred separate
 * view classes so organizing them into folders can make maintenance much simpler.
 *
 * To specify dependencies in subfolders just use a period (".") to specify the folder:
 *
 *     Ext.application({
 *         name: 'MyApp',
 *
 *         controllers: ['Users', 'nested.MyController'],
 *         views: ['products.Show', 'products.Edit', 'user.Login']
 *     });
 *
 * In this case these 5 files will be loaded:
 *
 * - app/controller/Users.js
 * - app/controller/nested/MyController.js
 * - app/view/products/Show.js
 * - app/view/products/Edit.js
 * - app/view/user/Login.js
 *
 * Note that we can mix and match within each configuration here - for each model, view, controller, profile or store
 * you can specify either just the final part of the class name (if you follow the directory conventions), or the full
 * class name.
 *
 * ### External Dependencies
 *
 * Finally, we can specify application dependencies from outside our application by fully-qualifying the classes we
 * want to load. A common use case for this is sharing authentication logic between multiple applications. Perhaps you
 * have several apps that login via a common user database and you want to share that code between them. An easy way to
 * do this is to create a folder alongside your app folder and then add its contents as dependencies for your app.
 *
 * For example, let's say our shared login code contains a login controller, a user model and a login form view. We
 * want to use all of these in our application:
 *
 *     Ext.Loader.setPath({
 *         'Auth': 'Auth'
 *     });
 *
 *     Ext.application({
 *         views: ['Auth.view.LoginForm', 'Welcome'],
 *         controllers: ['Auth.controller.Sessions', 'Main'],
 *         models: ['Auth.model.User']
 *     });
 *
 * This will load the following files:
 *
 * - Auth/view/LoginForm.js
 * - Auth/controller/Sessions.js
 * - Auth/model/User.js
 * - app/view/Welcome.js
 * - app/controller/Main.js
 *
 * The first three were loaded from outside our application, the last two from the application itself. Note how we can
 * still mix and match application files and external dependency files.
 *
 * Note that to enable the loading of external dependencies we just have to tell the Loader where to find those files,
 * which is what we do with the Ext.Loader.setPath call above. In this case we're telling the Loader to find any class
 * starting with the 'Auth' namespace inside our 'Auth' folder. This means we can drop our common Auth code into our
 * application alongside the app folder and the framework will be able to figure out how to load everything.
 *
 * ## Launching
 *
 * Each Application can define a {@link Ext.app.Application#launch launch} function, which is called as soon as all of
 * your app's classes have been loaded and the app is ready to be launched. This is usually the best place to put any
 * application startup logic, typically creating the main view structure for your app.
 *
 * In addition to the Application launch function, there are two other places you can put app startup logic. Firstly,
 * each Controller is able to define an {@link Ext.app.Controller#init init} function, which is called before the
 * Application launch function. Secondly, if you are using Device Profiles, each Profile can define a
 * {@link Ext.app.Profile#launch launch} function, which is called after the Controller init functions but before the
 * Application launch function.
 *
 * Note that only the active Profile has its launch function called - for example if you define profiles for Phone and
 * Tablet and then launch the app on a tablet, only the Tablet Profile's launch function is called.
 *
 * 1. Controller#init functions called
 * 2. Profile#launch function called
 * 3. Application#launch function called
 * 4. Controller#launch functions called
 *
 * When using Profiles it is common to place most of the bootup logic inside the Profile launch function because each
 * Profile has a different set of views that need to be constructed at startup.
 *
 * ## Adding to Home Screen
 *
 * iOS devices allow your users to add your app to their home screen for easy access. iOS allows you to customize
 * several aspects of this, including the icon that will appear on the home screen and the startup image. These can be
 * specified in the Ext.application setup block:
 *
 *     Ext.application({
 *         name: 'MyApp',
 *
 *         {@link #icon}: 'resources/img/icon.png',
 *         {@link #isIconPrecomposed}: false,
 *         {@link #startupImage}: {
 *             '320x460': 'resources/startup/320x460.jpg',
 *             '640x920': 'resources/startup/640x920.png',
 *             '640x1096': 'resources/startup/640x1096.png',
 *             '768x1004': 'resources/startup/768x1004.png',
 *             '748x1024': 'resources/startup/748x1024.png',
 *             '1536x2008': 'resources/startup/1536x2008.png',
 *             '1496x2048': 'resources/startup/1496x2048.png'
 *         }
 *     });
 *
 * When the user adds your app to the home screen, your resources/img/icon.png file will be used as the application
 * {@link #icon}. We also used the {@link #isIconPrecomposed} configuration to turn off the gloss effect that is automatically added
 * to icons in iOS. Finally we used the {@link #startupImage} configuration to provide the images that will be displayed
 * while your application is starting up. See also {@link #statusBarStyle}.
 *
 * ## Find out more
 *
 * If you are not already familiar with writing applications with Sencha Touch we recommend reading the
 * [intro to applications guide](../../../getting_started/building_your_first_app.html), which
 * lays out the core principles of writing apps with Sencha Touch.
 */
Ext.define('Ext.app.Application', {
    extend: 'Ext.app.Controller',

    requires: [
        'Ext.app.History',
        'Ext.app.Profile',
        'Ext.app.Router',
        'Ext.app.Action'
    ],

    config: {
        /**
         * @cfg {String/Object} icon
         * Specifies a set of URLs to the application icon for different device form factors. This icon is displayed
         * when the application is added to the device's Home Screen.
         *
         *     Ext.setup({
         *         icon: {
         *             57: 'resources/icons/Icon.png',
         *             72: 'resources/icons/Icon~ipad.png',
         *             114: 'resources/icons/Icon@2x.png',
         *             144: 'resources/icons/Icon~ipad@2x.png'
         *         },
         *         onReady: function() {
         *             // ...
         *         }
         *     });
         *
         * Each key represents the dimension of the icon as a square shape. For example: '57' is the key for a 57 x 57
         * icon image. Here is the breakdown of each dimension and its device target:
         *
         * - 57: Non-retina iPhone, iPod touch, and all Android devices
         * - 72: Retina iPhone and iPod touch
         * - 114: Non-retina iPad (first and second generation)
         * - 144: Retina iPad (third generation)
         *
         * Note that the dimensions of the icon images must be exactly 57x57, 72x72, 114x114 and 144x144 respectively.
         *
         * It is highly recommended that you provide all these different sizes to accommodate a full range of
         * devices currently available. However if you only have one icon in one size, make it 57x57 in size and
         * specify it as a string value. This same icon will be used on all supported devices.
         *
         *     Ext.application({
         *         icon: 'resources/icons/Icon.png',
         *         launch: function() {
         *             // ...
         *         }
         *     });
         */

        /**
         * @cfg {Object} startupImage
         * Specifies a set of URLs to the application startup images for different device form factors. This image is
         * displayed when the application is being launched from the Home Screen icon. Note that this currently only applies
         * to iOS devices.
         *
         *     Ext.application({
         *         startupImage: {
         *             '320x460': 'resources/startup/320x460.jpg',
         *             '640x920': 'resources/startup/640x920.png',
         *             '640x1096': 'resources/startup/640x1096.png',
         *             '768x1004': 'resources/startup/768x1004.png',
         *             '748x1024': 'resources/startup/748x1024.png',
         *             '1536x2008': 'resources/startup/1536x2008.png',
         *             '1496x2048': 'resources/startup/1496x2048.png'
         *         },
         *         launch: function() {
         *             // ...
         *         }
         *     });
         *
         * Each key represents the dimension of the image. For example: '320x460' is the key for a 320px x 460px image.
         * Here is the breakdown of each dimension and its device target:
         *
         * - 320x460: Non-retina iPhone, iPod touch, and all Android devices
         * - 640x920: Retina iPhone and iPod touch
         * - 640x1096: iPhone 5 and iPod touch (fifth generation)
         * - 768x1004: Non-retina iPad (first and second generation) in portrait orientation
         * - 748x1024: Non-retina iPad (first and second generation) in landscape orientation
         * - 1536x2008: Retina iPad (third generation) in portrait orientation
         * - 1496x2048: Retina iPad (third generation) in landscape orientation
         *
         * Please note that there's no automatic fallback mechanism for the startup images. In other words, if you don't specify
         * a valid image for a certain device, nothing will be displayed while the application is being launched on that device.
         */

        /**
         * @cfg {Boolean} isIconPrecomposed
         * `true` to not having a glossy effect added to the icon by the OS, which will preserve its exact look. This currently
         * only applies to iOS devices.
         */

        /**
         * @cfg {String} [statusBarStyle='black'] Allows you to set the style of the status bar when your app is added to the
         * home screen on iOS devices. Alternative is to set to 'black-translucent', which turns
         * the status bar semi-transparent and overlaps the app content. This is usually not a good option for web apps
         */

        /**
         * @cfg {String} tabletIcon Path to the _.png_ image file to use when your app is added to the home screen on an
         * iOS **tablet** device (iPad).
         * @deprecated 2.0.0 Please use the {@link #icon} configuration instead.
         */

        /**
         * @cfg {String} phoneIcon Path to the _.png_ image file to use when your app is added to the home screen on an
         * iOS **phone** device (iPhone or iPod).
         * @deprecated 2.0.0 Please use the {@link #icon} configuration instead.
         */

        /**
         * @cfg {Boolean} glossOnIcon If set to `false`, the 'gloss' effect added to home screen {@link #icon icons} on
         * iOS devices will be removed.
         * @deprecated 2.0.0 Please use the {@link #isIconPrecomposed} configuration instead.
         */

        /**
         * @cfg {String} phoneStartupScreen Path to the _.png_ image file that will be displayed while the app is
         * starting up once it has been added to the home screen of an iOS phone device (iPhone or iPod). This _.png_
         * file should be 320px wide and 460px high.
         * @deprecated 2.0.0 Please use the {@link #startupImage} configuration instead.
         */

        /**
         * @cfg {String} tabletStartupScreen Path to the _.png_ image file that will be displayed while the app is
         * starting up once it has been added to the home screen of an iOS tablet device (iPad). This _.png_ file should
         * be 768px wide and 1004px high.
         * @deprecated 2.0.0 Please use the {@link #startupImage} configuration instead.
         */

        /**
         * @cfg {Array} profiles The set of profiles to load for this Application. Each profile is expected to
         * exist inside the *app/profile* directory and define a class following the convention
         * AppName.profile.ProfileName. For example, in the code below, the classes *AppName.profile.Phone*
         * and *AppName.profile.Tablet* will be loaded. Note that we are able to specify
         * either the full class name (as with *AppName.profile.Tablet*) or just the final part of the class name
         * and leave Application to automatically prepend *AppName.profile.'* to each:
         *
         *     profiles: [
         *         'Phone',
         *         'AppName.profile.Tablet',
         *         'SomeCustomNamespace.profile.Desktop'
         *     ]
         * @accessor
         */
        profiles: [],

        /**
         * @cfg {Array} controllers The set of controllers to load for this Application. Each controller is expected to
         * exist inside the *app/controller* directory and define a class following the convention
         * AppName.controller.ControllerName. For example, in the code below, the classes *AppName.controller.Users*,
         * *AppName.controller.Groups* and *AppName.controller.Products* will be loaded. Note that we are able to specify
         * either the full class name (as with *AppName.controller.Products*) or just the final part of the class name
         * and leave Application to automatically prepend *AppName.controller.'* to each:
         *
         *     controllers: [
         *         'Users',
         *         'Groups',
         *         'AppName.controller.Products',
         *         'SomeCustomNamespace.controller.Orders'
         *     ]
         * @accessor
         */
        controllers: [],

        /**
         * @cfg {Ext.app.History} history The global {@link Ext.app.History History} instance attached to this
         * Application. For more information, see
         * [Routing, Deep Linking, and the Back Button](../../../core_concepts/history_support.html).
         * @accessor
         * @readonly
         */
        history: {},

        /**
         * @cfg {String} name The name of the Application. This should be a single word without spaces or periods
         * because it is used as the Application's global namespace. All classes in your application should be
         * namespaced under the Application's name - for example if your application name is 'MyApp', your classes
         * should be named 'MyApp.model.User', 'MyApp.controller.Users', 'MyApp.view.Main' etc
         * @accessor
         */
        name: null,

        /**
         * @cfg {String} appFolder The path to the directory which contains all application's classes.
         * This path will be registered via {@link Ext.Loader#setPath} for the namespace specified in the {@link #name name} config.
         * @accessor
         */
        appFolder : 'app',

        /**
         * @cfg {Ext.app.Router} router The global {@link Ext.app.Router Router} instance attached to this Application.
         * @accessor
         * @readonly
         */
        router: {},

        /**
         * @cfg {Array} controllerInstances Used internally as the collection of instantiated controllers. Use {@link #getController} instead.
         * @private
         * @accessor
         */
        controllerInstances: [],

        /**
         * @cfg {Array} profileInstances Used internally as the collection of instantiated profiles.
         * @private
         * @accessor
         */
        profileInstances: [],

        /**
         * @cfg {Ext.app.Profile} currentProfile The {@link Ext.app.Profile Profile} that is currently active for the
         * Application. This is set once, automatically by the Application before launch.
         * @accessor
         * @readonly
         */
        currentProfile: null,

        /**
         * @cfg {Function} launch An optional function that will be called when the Application is ready to be
         * launched. This is normally used to render any initial UI required by your application
         * @accessor
         */
        launch: Ext.emptyFn,

        /**
         * @private
         * @cfg {Boolean} enableLoader Private config to disable loading of Profiles at application construct time.
         * This is used by Sencha's unit test suite to test _Application.js_ in isolation and is likely to be removed
         * in favor of a more pleasing solution by the time you use it.
         * @accessor
         */
        enableLoader: true,

        /**
         * @cfg {String[]} requires An array of extra dependencies, to be required after this application's {@link #name} config
         * has been processed properly, but before anything else to ensure overrides get executed first.
         * @accessor
         */
        requires: [],

        /**
         * @cfg {String} themeVariationPrefix Used only with {@link themeVariation} this prefix will be added before the variation as a class on the HTML
         * tag of your application.
         */
        themeVariationPrefix: Ext.baseCSSPrefix + 'theme-variation-',

        /**
         * @cfg {String} themeVariationTransitionCls This is only used with {@link themeVariation}. The Class provided will be added to the HTML tag
         * then removed once the transition is complete. The duration of this delayed removal is parsed from the class itself, for example if the class
         * has the property 'transition: color 4s, background 6s, background-color 1s' the delay will be 6s (the largest time used in that class.
         *
         * @accessor
        */
        themeVariationTransitionCls: null,

        /**
         * @cfg {String/Function} themeVariation A string to determine the variation on the current theme being used. This string will be prefixed by
         * {@link themeVariationPrefix} and the resulting string will be added to the HTML tag of your application. If a function is provided that function
         * must return a string.
         *
         *  //This will result in 'x-theme-variation-dark' being added as a class to the html tag of your application
         *  MyApp.app.setThemeVariation("dark");
         *
         * @accessor
         */
        themeVariation: null
    },

    /**
     * Constructs a new Application instance.
     */
    constructor: function(config) {
        config = config || {};

        Ext.applyIf(config, {
            application: this
        });

        this.initConfig(config);

        //it's common to pass in functions to an application but because they are not predictable config names they
        //aren't ordinarily placed onto this so we need to do it manually
        for (var key in config) {
            this[key] = config[key];
        }

        // <deprecated product=touch since=2.0>
        if (config.autoCreateViewport) {
            Ext.Logger.deprecate(
                '[Ext.app.Application] autoCreateViewport has been deprecated in Sencha Touch 2. Please implement a ' +
                'launch function on your Application instead and use Ext.create("MyApp.view.Main") to create your initial UI.'
            );
        }
        // </deprecated>

        //<debug>
        Ext.Loader.setConfig({ enabled: true });
        //</debug>

        Ext.require(this.getRequires(), function() {
            if (this.getEnableLoader() !== false) {
                Ext.require(this.getProfiles(), this.onProfilesLoaded, this);
            }
        }, this);
    },

    /**
     * Dispatches a given {@link Ext.app.Action} to the relevant Controller instance. This is not usually called
     * directly by the developer, instead Sencha Touch's History support picks up on changes to the browser's url
     * and calls dispatch automatically.
     * @param {Ext.app.Action} action The action to dispatch.
     * @param {Boolean} [addToHistory=true] Sets the browser's url to the action's url.
     */
    dispatch: function(action, addToHistory) {
        action = action || {};
        Ext.applyIf(action, {
            application: this
        });

        action = Ext.factory(action, Ext.app.Action);

        if (action) {
            var profile    = this.getCurrentProfile(),
                profileNS  = profile ? profile.getNamespace() : undefined,
                controller = this.getController(action.getController(), profileNS);

            if (controller) {
                if (addToHistory !== false) {
                    this.getHistory().add(action, true);
                }

                controller.execute(action);
            }
        }
    },

    /**
     * Redirects the browser to the given url. This only affects the url after the '#'. You can pass in either a String
     * or a Model instance - if a Model instance is defined its {@link Ext.data.Model#toUrl toUrl} function is called,
     * which returns a string representing the url for that model. Internally, this uses your application's
     * {@link Ext.app.Router Router} to decode the url into a matching controller action and then calls
     * {@link #dispatch}.
     * @param {String/Ext.data.Model} url The String url to redirect to.
     */
    redirectTo: function(url) {
        if (Ext.data && Ext.data.Model && url instanceof Ext.data.Model) {
            var record = url;

            url = record.toUrl();
        }

        var decoded = this.getRouter().recognize(url);

        if (decoded) {
            decoded.url = url;
            if (record) {
                decoded.data = {};
                decoded.data.record = record;
            }
            return this.dispatch(decoded);
        }
    },

    /**
     * @private
     * (documented on Controller's control config)
     */
    control: function(selectors, controller) {
        //if the controller is not defined, use this instead (the application instance)
        controller = controller || this;

        var dispatcher = this.getEventDispatcher(),
            refs = (controller) ? controller.getRefs() : {},
            selector, eventName, listener, listeners, ref;

        for (selector in selectors) {
            if (selectors.hasOwnProperty(selector)) {
                listeners = selectors[selector];
                ref = refs[selector];

                //refs can be used in place of selectors
                if (ref) {
                    selector = ref.selector || ref;
                }
                for (eventName in listeners) {
                    listener = listeners[eventName];

                    if (Ext.isString(listener)) {
                        listener = controller[listener];
                    }

                    dispatcher.addListener('component', selector, eventName, listener, controller);
                }
            }
        }
    },

    /**
     * Returns the Controller instance for the given controller name.
     * @param {String} name The name of the Controller.
     * @param {String} [profileName] Optional profile name. If passed, this is the same as calling
     * `getController('profileName.controllerName')`.
     * @return {Ext.app.Controller} controller instance or undefined.
     */
    getController: function(name, profileName) {
        var instances = this.getControllerInstances(),
            appName   = this.getName(),
            format    = Ext.String.format,
            topLevelName;

        if (name instanceof Ext.app.Controller) {
            return name;
        }

        if (instances[name]) {
            return instances[name];
        } else {
            topLevelName = format("{0}.controller.{1}", appName, name);
            profileName  = format("{0}.controller.{1}.{2}", appName, profileName, name);

            return instances[profileName] || instances[topLevelName];
        }
    },

    /**
     * @private
     * Callback that is invoked when all of the configured Profiles have been loaded. Detects the current profile and
     * gathers any additional dependencies from that profile, then loads all of those dependencies.
     */
    onProfilesLoaded: function() {
        var profiles  = this.getProfiles(),
            length    = profiles.length,
            instances = [],
            requires  = this.gatherDependencies(),
            current, i, profileDeps;

        for (i = 0; i < length; i++) {
            instances[i] = Ext.create(profiles[i], {
                application: this
            });

            /*
             * Note that we actually require all of the dependencies for all Profiles - this is so that we can produce
             * a single build file that will work on all defined Profiles. Although the other classes will be loaded,
             * the correct Profile will still be identified and the other classes ignored. While this feels somewhat
             * inefficient, the majority of the bulk of an application is likely to be the framework itself. The bigger
             * the app though, the bigger the effect of this inefficiency so ideally we will create a way to create and
             * load Profile-specific builds in a future release.
             */
            profileDeps = instances[i].getDependencies();
            requires = requires.concat(profileDeps.all);

            if (instances[i].isActive() && !current) {
                current = instances[i];

                this.setCurrentProfile(current);

                this.setControllers(this.getControllers().concat(profileDeps.controller));
                this.setModels(this.getModels().concat(profileDeps.model));
                this.setViews(this.getViews().concat(profileDeps.view));
                this.setStores(this.getStores().concat(profileDeps.store));
            }
        }

        this.setProfileInstances(instances);
        Ext.require(requires, this.loadControllerDependencies, this);
    },

    /**
     * @private
     * Controllers can also specify dependencies, so we grab them all here and require them.
     */
    loadControllerDependencies: function() {
        this.instantiateControllers();

        var controllers = this.getControllerInstances(),
            classes = [],
            stores = [],
            i, controller, controllerStores, name;

        for (name in controllers) {
            controller = controllers[name];
            controllerStores = controller.getStores();
            stores = stores.concat(controllerStores);

            classes = classes.concat(controller.getModels().concat(controller.getViews()).concat(controllerStores));
        }

        this.setStores(this.getStores().concat(stores));

        Ext.require(classes, this.onDependenciesLoaded, this);
    },

    /**
     * @private
     * Callback that is invoked when all of the Application, Controller and Profile dependencies have been loaded.
     * Launches the controllers, then the profile and application.
     */
    onDependenciesLoaded: function() {
        var me = this,
            profile = this.getCurrentProfile(),
            launcher = this.getLaunch(),
            controllers, name;

        this.instantiateStores();

        //<deprecated product=touch since=2.0>
        Ext.app.Application.appInstance = this;

        if (Ext.Router) {
            Ext.Router.setAppInstance(this);
        }
        //</deprecated>

        controllers = this.getControllerInstances();

        for (name in controllers) {
            controllers[name].init(this);
        }

        if (profile) {
            profile.launch();
        }

        launcher.call(me);

        for (name in controllers) {
            //<debug warn>
            if (controllers[name] && !(controllers[name] instanceof Ext.app.Controller)) {
                Ext.Logger.warn("The controller '" + name + "' doesn't have a launch method. Are you sure it extends from Ext.app.Controller?");
            } else {
            //</debug>
                controllers[name].launch(this);
            //<debug warn>
            }
            //</debug>
        }

        me.redirectTo(window.location.hash.substr(1));
    },

    /**
     * @private
     * Gathers up all of the previously computed MVCS dependencies into a single array that we can pass to {@link Ext#require}.
     */
    gatherDependencies: function() {
        var classes = this.getModels().concat(this.getViews()).concat(this.getControllers());

        Ext.each(this.getStores(), function(storeName) {
            if (Ext.isString(storeName)) {
                classes.push(storeName);
            }
        }, this);

        return classes;
    },

    /**
     * @private
     * Should be called after dependencies are loaded, instantiates all of the Stores specified in the {@link #stores}
     * config. For each item in the stores array we make sure the Store is instantiated. When strings are specified,
     * the corresponding _app/store/StoreName.js_ was loaded so we now instantiate a `MyApp.store.StoreName`, giving it the
     * id `StoreName`.
     */
    instantiateStores: function() {
        var stores  = this.getStores(),
            length  = stores.length,
            store, storeClass, storeName, splits, i;

        for (i = 0; i < length; i++) {
            store = stores[i];

            if (Ext.data && Ext.data.Store && !(store instanceof Ext.data.Store)) {
                if (Ext.isString(store)) {
                    storeName = store;
                    storeClass = Ext.ClassManager.classes[store];

                    store = {
                        xclass: store
                    };

                    //we don't want to wipe out a configured storeId in the app's Store subclass so need
                    //to check for this first
                    if (storeClass.prototype.defaultConfig.storeId === undefined) {
                        splits = storeName.split('.');
                        store.id = splits[splits.length - 1];
                    }
                }

                stores[i] = Ext.factory(store, Ext.data.Store);
            }
        }

        this.setStores(stores);
    },

    /**
     * @private
     * Called once all of our controllers have been loaded
     */
    instantiateControllers: function() {
        var controllerNames = this.getControllers(),
            instances = {},
            length = controllerNames.length,
            name, i;

        for (i = 0; i < length; i++) {
            name = controllerNames[i];

            instances[name] = Ext.create(name, {
                application: this
            });
        }

        return this.setControllerInstances(instances);
    },

    /**
     * @private
     * As a convenience developers can locally qualify controller names (e.g. 'MyController' vs
     * 'MyApp.controller.MyController'). This just makes sure everything ends up fully qualified
     */
    applyControllers: function(controllers) {
        return this.getFullyQualified(controllers, 'controller');
    },

    /**
     * @private
     * As a convenience developers can locally qualify profile names (e.g. 'MyProfile' vs
     * 'MyApp.profile.MyProfile'). This just makes sure everything ends up fully qualified
     */
    applyProfiles: function(profiles) {
        return this.getFullyQualified(profiles, 'profile');
    },

    /**
     * @private
     * Checks that the name configuration has any whitespace, and trims them if found.
     */
    applyName: function(name) {
        var oldName;
        if (name && name.match(/ /g)) {
            oldName = name;
            name = name.replace(/ /g, "");

            // <debug>
            Ext.Logger.warn('Attempting to create an application with a name which contains whitespace ("' + oldName + '"). Renamed to "' + name + '".');
            // </debug>
        }

        return name;
    },

    /**
     * @private
     * Makes sure the app namespace exists, sets the `app` property of the namespace to this application and sets its
     * loading path (checks to make sure the path hadn't already been set via Ext.Loader.setPath)
     */
    updateName: function(newName) {
        Ext.ClassManager.setNamespace(newName + '.app', this);

        if (!Ext.Loader.config.paths[newName]) {
            Ext.Loader.setPath(newName, this.getAppFolder());
        }
    },

    /**
     * @private
     */
    applyRouter: function(config) {
        return Ext.factory(config, Ext.app.Router, this.getRouter());
    },

    /**
     * @private
     */
    applyHistory: function(config) {
        var history = Ext.factory(config, Ext.app.History, this.getHistory());

        history.on('change', this.onHistoryChange, this);

        return history;
    },

    /**
     * @private
     */
    onHistoryChange: function(url) {
        this.dispatch(this.getRouter().recognize(url), false);
    },

    updateThemeVariation: function(newVariation, oldVariation) {
        var html = Ext.getBody().getParent(),
            themeVariationPrefix = this.getThemeVariationPrefix() || "",
            transitionCls = this.getThemeVariationTransitionCls();

        if (Ext.isFunction(newVariation)) {
            newVariation = newVariation.call(this);
        }

        if(!Ext.isString(newVariation)) {
            Ext.Error.raise("Theme variation must be a String.'");
        }

        if(transitionCls) {
            var css = "", duration = 0,
                rules = document.styleSheets[0].cssRules,
                i, rule, times, time;

            html.addCls(transitionCls);
            for(i in rules) {
                rule = rules[i];
                if(rule.selectorText && rule.selectorText.indexOf("." + transitionCls) >=1) {
                    css += rule.cssText;
                }
            }

            times = css.match(/[0-9]+s/g);
            for(i in times) {
                time = parseInt(times[i]);
                if(time > duration) {
                    duration = time;
                }
            }

            if(this.$themeVariationChangeTimeout) {
                clearTimeout(this.$themeVariationChangeTimeout);
                this.$themeVariationChangeTimeout = null;
            }

            this.$themeVariationChangeTimeout = Ext.defer(function() {
                html.removeCls(transitionCls);
            }, time * 1000);
        }

        html.removeCls(themeVariationPrefix + oldVariation);
        html.addCls(themeVariationPrefix + newVariation);
    }
}, function() {
    // <deprecated product=touch since=2.0>
    Ext.regApplication = function(config) {
        Ext.Logger.deprecate(
            '[Ext.app.Application] Ext.regApplication() is deprecated, please replace it with Ext.application()'
        );

        var appName = config.name,
            format  = Ext.String.format;

        Ext.ns(
            appName,
            format("{0}.controllers", appName),
            format("{0}.models", appName),
            format("{0}.views", appName)
        );

        Ext.application(config);
    };

    Ext.define('Ext.data.ProxyMgr', {
        singleton: true,

        registerType: function(name, cls) {
            Ext.Logger.deprecate(
                'Ext.data.ProxyMgr no longer exists - instead of calling Ext.data.ProxyMgr.registerType just update ' +
                'your custom Proxy class to set alias: "proxy.' + name + '"'
            );

            Ext.ClassManager.setAlias(cls, "proxy." + name);
        }
    });

    Ext.reg = function(alias, cls) {
        Ext.Logger.deprecate(
            'Ext.reg is deprecated, please set xtype: "' + alias + '" directly in your subclass instead'
        );

        Ext.ClassManager.setAlias(cls, alias);
    };

    Ext.redirect = function() {
        var app = Ext.app.Application.appInstance;

        Ext.Logger.deprecate('[Ext.app.Application] Ext.redirect is deprecated, please use YourApp.redirectTo instead');

        if (app) {
            app.redirectTo.apply(app, arguments);
        }
    };

    Ext.dispatch = function() {
        var app = Ext.app.Application.appInstance;

        Ext.Logger.deprecate('[Ext.app.Application] Ext.dispatch is deprecated, please use YourApp.dispatch instead');

        if (app) {
            app.dispatch.apply(app, arguments);
        }
    };

    // </deprecated>
});


/**
 * A Container has all of the abilities of {@link Ext.Component Component}, but lets you nest other Components inside
 * it. Applications are made up of lots of components, usually nested inside one another. Containers allow you to
 * render and arrange child Components inside them. Most apps have a single top-level Container called a Viewport,
 * which takes up the entire screen. Inside of this are child components, for example in a mail app the Viewport
 * Container's two children might be a message List and an email preview pane.
 *
 * Containers give the following extra functionality:
 *
 * - Adding child Components at instantiation and run time
 * - Removing child Components
 * - Specifying a [Layout](../../../core_concepts/layouts.html)
 *
 * Layouts determine how the child Components should be laid out on the screen. In our mail app example we'd use an
 * HBox layout so that we can pin the email list to the left hand edge of the screen and allow the preview pane to
 * occupy the rest. There are several layouts in Sencha Touch 2, each of which help you achieve your desired
 * application structure, further explained in the [Layout guide](../../../core_concepts/layouts.html).
 *
 * ## Adding Components to Containers
 *
 * As we mentioned above, Containers are special Components that can have child Components arranged by a Layout. One of
 * the code samples above showed how to create a Panel with 2 child Panels already defined inside it but it's easy to
 * do this at run time too:
 *
 *     @example miniphone
 *     //this is the Panel we'll be adding below
 *     var aboutPanel = Ext.create('Ext.Panel', {
 *         html: 'About this app'
 *     });
 *
 *     //this is the Panel we'll be adding to
 *     var mainPanel = Ext.create('Ext.Panel', {
 *         fullscreen: true,
 *
 *         layout: 'hbox',
 *         defaults: {
 *             flex: 1
 *         },
 *
 *         items: {
 *             html: 'First Panel',
 *             style: 'background-color: #5E99CC;'
 *         }
 *     });
 *
 *     //now we add the first panel inside the second
 *     mainPanel.add(aboutPanel);
 *
 * Here we created three Panels in total. First we made the aboutPanel, which we might use to tell the user a little
 * about the app. Then we create one called mainPanel, which already contains a third Panel in its
 * {@link Ext.Container#cfg-items items} configuration, with some dummy text ("First Panel"). Finally, we add the first
 * panel to the second by calling the {@link Ext.Container#method-add add} method on `mainPanel`.
 *
 * In this case we gave our mainPanel another hbox layout, but we also introduced some
 * {@link Ext.Container#defaults defaults}. These are applied to every item in the Panel, so in this case every child
 * inside `mainPanel` will be given a `flex: 1` configuration. The effect of this is that when we first render the screen
 * only a single child is present inside `mainPanel`, so that child takes up the full width available to it. Once the
 * `mainPanel.add` line is called though, the `aboutPanel` is rendered inside of it and also given a `flex` of 1, which will
 * cause it and the first panel to both receive half the full width of the `mainPanel`.
 *
 * Likewise, it's easy to remove items from a Container:
 *
 *     mainPanel.remove(aboutPanel);
 *
 * After this line is run everything is back to how it was, with the first child panel once again taking up the full
 * width inside `mainPanel`.
 *
 * ## Further Reading
 *
 * See the [Component & Container Guide](../../../core_concepts/components.html) for more information, and check out the
 * {@link Ext.Container} class docs also.
 */
Ext.define('Ext.Container', {
    extend: 'Ext.Component',

    alternateClassName: 'Ext.lib.Container',

    requires: [
        'Ext.layout.*',
        'Ext.ItemCollection',
        'Ext.behavior.Scrollable',
        'Ext.Mask'
    ],

    xtype: 'container',

    /**
     * @event add
     * Fires whenever item added to the Container.
     * @param {Ext.Container} this The Container instance.
     * @param {Object} item The item added to the Container.
     * @param {Number} index The index of the item within the Container.
     */

    /**
     * @event remove
     * Fires whenever item removed from the Container.
     * @param {Ext.Container} this The Container instance.
     * @param {Object} item The item removed from the Container.
     * @param {Number} index The index of the item that was removed.
     */

    /**
     * @event move
     * Fires whenever item moved within the Container.
     * @param {Ext.Container} this The Container instance.
     * @param {Object} item The item moved within the Container.
     * @param {Number} toIndex The new index of the item.
     * @param {Number} fromIndex The old index of the item.
     */

    /**
     * @private
     * @event renderedchange
     * Fires whenever an item is rendered into a container or derendered
     * from a Container.
     * @param {Ext.Container} this The Container instance.
     * @param {Object} item The item in the Container.
     * @param {Boolean} rendered The current rendered status of the item.
     */

    /**
     * @event activate
     * Fires whenever item within the Container is activated.
     * @param {Object} newActiveItem The new active item within the container.
     * @param {Ext.Container} this The Container instance.
     * @param {Object} oldActiveItem The old active item within the container.
     */

    /**
     * @event deactivate
     * Fires whenever item within the Container is deactivated.
     * @param {Object} oldActiveItem The old active item within the container.
     * @param {Ext.Container} this The Container instance.
     * @param {Object} newActiveItem The new active item within the container.
     */

    eventedConfig: {
        /**
         * @cfg {Object/String/Number} activeItem The item from the {@link #cfg-items} collection that will be active first. This is
         * usually only meaningful in a {@link Ext.layout.Card card layout}, where only one item can be active at a
         * time. If passes a string, it will be assumed to be a {@link Ext.ComponentQuery} selector.
         * @accessor
         * @evented
         */
        activeItem: 0,

        /**
         * @cfg {Boolean/String/Object} scrollable
         * Configuration options to make this Container scrollable. Acceptable values are:
         *
         * - `'horizontal'`, `'vertical'`, `'both'` to enabling scrolling for that direction.
         * - `true`/`false` to explicitly enable/disable scrolling.
         *
         * Alternatively, you can give it an object which is then passed to the scroller instance:
         *
         *     scrollable: {
         *         direction: 'vertical',
         *         directionLock: true
         *     }
         *
         * Please look at the {@link Ext.scroll.Scroller} documentation for more example on how to use this.
         * @return {Ext.scroll.View} The scroll view.
         * @accessor
         * @evented
         */
        scrollable: null
    },

    config: {
        /**
         * @cfg {String/Object/Boolean} cardSwitchAnimation
         * Animation to be used during transitions of cards.
         * @removed 2.0.0 Please use {@link Ext.layout.Card#animation} instead
         */

        /**
         * @cfg {Object/String} layout Configuration for this Container's layout. Example:
         *
         *     Ext.create('Ext.Container', {
         *         layout: {
         *             type: 'hbox',
         *             align: 'middle'
         *         },
         *         items: [
         *             {
         *                 xtype: 'panel',
         *                 flex: 1,
         *                 style: 'background-color: red;'
         *             },
         *             {
         *                 xtype: 'panel',
         *                 flex: 2,
         *                 style: 'background-color: green'
         *             }
         *         ]
         *     });
         *
         * See the [Layouts Guide](../../../core_concepts/layouts.html) for more information.
         *
         * @accessor
         */
        layout: null,

        /**
         * @cfg {Object} control Enables you to easily control Components inside this Container by listening to their
         * events and taking some action. For example, if we had a container with a nested Disable button, and we
         * wanted to hide the Container when the Disable button is tapped, we could do this:
         *
         *     Ext.create('Ext.Container', {
         *         control: {
         *            'button[text=Disable]': {
         *                tap: 'hideMe'
         *            }
         *         },
         *
         *         hideMe: function () {
         *             this.hide();
         *         }
         *     });
         *
         * We used a {@link Ext.ComponentQuery} selector to listen to the {@link Ext.Button#tap tap} event on any
         * {@link Ext.Button button} anywhere inside the Container that has the {@link Ext.Button#text text} 'Disable'.
         * Whenever a Component matching that selector fires the `tap` event our `hideMe` function is called. `hideMe` is
         * called with scope: `this` (e.g. `this` is the Container instance).
         *
         */
        control: {},

        /**
         * @cfg {Object} defaults A set of default configurations to apply to all child Components in this Container.
         * It's often useful to specify defaults when creating more than one items with similar configurations. For
         * example here we can specify that each child is a panel and avoid repeating the xtype declaration for each
         * one:
         *
         *     Ext.create('Ext.Container', {
         *         defaults: {
         *             xtype: 'panel'
         *         },
         *         items: [
         *             {
         *                 html: 'Panel 1'
         *             },
         *             {
         *                 html: 'Panel 2'
         *             }
         *         ]
         *     });
         *
         * @accessor
         */
        defaults: null,

        /**
         * @cfg {Array/Object} items The child items to add to this Container. This is usually an array of Component
         * configurations or instances, for example:
         *
         *     Ext.create('Ext.Container', {
         *         items: [
         *             {
         *                 xtype: 'panel',
         *                 html: 'This is an item'
         *             }
         *         ]
         *     });
         * @accessor
         */
        items: null,

        /**
         * @cfg {Boolean} autoDestroy If `true`, child items will be destroyed as soon as they are {@link #method-remove removed}
         * from this container.
         * @accessor
         */
        autoDestroy: true,

        /** @cfg {String} defaultType
         * The default {@link Ext.Component xtype} of child Components to create in this Container when a child item
         * is specified as a raw configuration object, rather than as an instantiated Component.
         * @accessor
         */
        defaultType: null,

        //@private
        useBodyElement: null,

        /**
         * @cfg {Boolean/Object/Ext.Mask/Ext.LoadMask} masked
         * A configuration to allow you to mask this container.
         * You can optionally pass an object block with and xtype of `loadmask`, and an optional `message` value to
         * display a loading mask. Please refer to the {@link Ext.LoadMask} component to see other configurations.
         *
         *     masked: {
         *         xtype: 'loadmask',
         *         message: 'My message'
         *     }
         *
         * Alternatively, you can just call the setter at any time with `true`/`false` to show/hide the mask:
         *
         *     setMasked(true); //show the mask
         *     setMasked(false); //hides the mask
         *
         * There are also two convenient methods, {@link #method-mask} and {@link #unmask}, to allow you to mask and unmask
         * this container at any time.
         *
         * Remember, the {@link Ext.Viewport} is always a container, so if you want to mask your whole application at anytime,
         * can call:
         *
         *     Ext.Viewport.setMasked({
         *         xtype: 'loadmask',
         *         message: 'Hello'
         *     });
         *
         * @accessor
         */
        masked: null,

        /**
         * @cfg {Boolean} modal `true` to make this Container modal. This will create a mask underneath the Container
         * that covers its parent and does not allow the user to interact with any other Components until this
         * Container is dismissed.
         * @accessor
         */
        modal: null,

        /**
         * @cfg {Boolean} hideOnMaskTap When using a {@link #modal} Component, setting this to `true` will hide the modal
         * mask and the Container when the mask is tapped on.
         * @accessor
         */
        hideOnMaskTap: null
    },

    isContainer: true,

    constructor: function(config) {
        var me = this;

        me._items = me.items = new Ext.ItemCollection();
        me.innerItems = [];

        me.onItemAdd = me.onFirstItemAdd;

        me.callParent(arguments);
    },

    getElementConfig: function() {
        return {
            reference: 'element',
            classList: ['x-container', 'x-unsized'],
            children: [{
                reference: 'innerElement',
                className: 'x-inner'
            }]
        };
    },

    /**
     * Changes the {@link #masked} configuration when its setter is called, which will convert the value
     * into a proper object/instance of {@link Ext.Mask}/{@link Ext.LoadMask}. If a mask already exists,
     * it will use that instead.
     * @param {Boolean/Object/Ext.Mask/Ext.LoadMask} masked
     * @return {Object}
     */
    applyMasked: function(masked) {
        var isVisible = true,
            currentMask;

        if (masked === false) {
            masked = true;
            isVisible = false;
        }

        currentMask = Ext.factory(masked, Ext.Mask, this.getMasked());

        if (currentMask) {
            this.add(currentMask);
            currentMask.setHidden(!isVisible);
        }

        return currentMask;
    },

    /**
     * Convenience method which calls {@link #setMasked} with a value of `true` (to show the mask). For additional
     * functionality, call the {@link #setMasked} function direction (See the {@link #masked} configuration documentation
     * for more information).
     */
    mask: function(mask) {
        this.setMasked(mask || true);
    },

    /**
     * Convenience method which calls {@link #setMasked} with a value of false (to hide the mask). For additional
     * functionality, call the {@link #setMasked} function direction (See the {@link #masked} configuration documentation
     * for more information).
     */
    unmask: function() {
        this.setMasked(false);
    },

    setParent: function(container) {
        this.callSuper(arguments);

        if (container) {
            var modal = this.getModal();

            if (modal) {
                container.insertBefore(modal, this);
                modal.setZIndex(this.getZIndex() - 1);
            }
        }
    },

    applyModal: function(modal, currentModal) {
        var isVisible = true;

        if (modal === false) {
            modal = true;
            isVisible = false;
        }

        currentModal = Ext.factory(modal, Ext.Mask, currentModal);

        if (currentModal) {
            currentModal.setVisibility(isVisible);
        }

        return currentModal;
    },

    updateModal: function(modal) {
        var container = this.getParent();

        if (container) {
            if (modal) {
                container.insertBefore(modal, this);
                modal.setZIndex(this.getZIndex() - 1);
            }
            else {
                container.remove(modal);
            }
        }
    },

    updateHideOnMaskTap : function(hide) {
        var mask = this.getModal();

        if (mask) {
            mask[hide ? 'on' : 'un'].call(mask, 'tap', 'hide', this);
        }
    },

    updateZIndex: function(zIndex) {
        var modal = this.getModal();

        this.callParent(arguments);

        if (modal) {
            modal.setZIndex(zIndex - 1);
        }
    },

    updateBaseCls: function(newBaseCls, oldBaseCls) {
        var me = this,
            ui = me.getUi();

        if (oldBaseCls) {
            this.element.removeCls(oldBaseCls);
            this.innerElement.removeCls(newBaseCls, null, 'inner');

            if (ui) {
                this.element.removeCls(this.currentUi);
            }
        }

        if (newBaseCls) {
            this.element.addCls(newBaseCls);
            this.innerElement.addCls(newBaseCls, null, 'inner');

            if (ui) {
                this.element.addCls(newBaseCls, null, ui);
                this.currentUi = newBaseCls + '-' + ui;
            }
        }
    },

    updateUseBodyElement: function(useBodyElement) {
        if (useBodyElement) {
            this.link('bodyElement', this.innerElement.wrap({
                cls: 'x-body'
            }));
        }
    },

    applyItems: function(items, collection) {
        if (items) {
            var me = this;

            me.getDefaultType();
            me.getDefaults();

            if (me.initialized && collection.length > 0) {
                me.removeAll();
            }

            me.add(items);

            //Don't need to call setActiveItem when Container is first initialized
            if (me.initialized) {
                var activeItem = me.initialConfig.activeItem || me.config.activeItem || 0;

                me.setActiveItem(activeItem);
            }
        }
    },

    /**
     * @private
     */
     applyControl: function(selectors) {
         var selector, key, listener, listeners;

         for (selector in selectors) {
             listeners = selectors[selector];

             for (key in listeners) {
                 listener = listeners[key];

                 if (Ext.isObject(listener)) {
                     listener.delegate = selector;
                 }
             }

             listeners.delegate = selector;

             this.addListener(listeners);
         }

         return selectors;
     },

    /**
     * Initialize layout and event listeners the very first time an item is added
     * @private
     */
    onFirstItemAdd: function() {
        delete this.onItemAdd;

        if (this.innerHtmlElement && !this.getHtml()) {
            this.innerHtmlElement.destroy();
            delete this.innerHtmlElement;
        }

        this.on('innerstatechange', 'onItemInnerStateChange', this, {
            delegate: '> component'
        });

        return this.onItemAdd.apply(this, arguments);
    },

    //<debug error>
    updateLayout: function(newLayout, oldLayout) {
        if (oldLayout && oldLayout.isLayout) {
            Ext.Logger.error('Replacing a layout after one has already been initialized is not currently supported.');
        }
    },
    //</debug>

    getLayout: function() {
        var layout = this.layout;
        if (!layout) {
            layout = this.link('_layout', this.link('layout', Ext.factory(this._layout || 'default', Ext.layout.Default, null, 'layout')));
            layout.setContainer(this);
        }

        return layout;
    },

    updateDefaultType: function(defaultType) {
        // Cache the direct reference to the default item class here for performance
        this.defaultItemClass = Ext.ClassManager.getByAlias('widget.' + defaultType);

        //<debug error>
        if (!this.defaultItemClass) {
            Ext.Logger.error("Invalid defaultType of: '" + defaultType + "', must be a valid component xtype");
        }
        //</debug>
    },

    applyDefaults: function(defaults) {
        if (defaults) {
            this.factoryItem = this.factoryItemWithDefaults;
            return defaults;
        }
    },

    factoryItem: function(item) {
        //<debug error>
        if (!item) {
            Ext.Logger.error("Invalid item given: " + item + ", must be either the config object to factory a new item, " +
                "or an existing component instance");
        }
        //</debug>

        return Ext.factory(item, this.defaultItemClass);
    },

    factoryItemWithDefaults: function(item) {
        //<debug error>
        if (!item) {
            Ext.Logger.error("Invalid item given: " + item + ", must be either the config object to factory a new item, " +
                "or an existing component instance");
        }
        //</debug>

        var me = this,
            defaults = me.getDefaults(),
            instance;

        if (!defaults) {
            return Ext.factory(item, me.defaultItemClass);
        }

        // Existing instance
        if (item.isComponent) {
            instance = item;

            // Apply defaults only if this is not already an item of this container
            if (defaults && item.isInnerItem() && !me.has(instance)) {
                instance.setConfig(defaults, true);
            }
        }
        // Config object
        else {
            if (defaults && !item.ignoreDefaults) {
                // Note:
                // - defaults is only applied to inner items
                // - we merge the given config together with defaults into a new object so that the original object stays intact
                if (!(
                        item.hasOwnProperty('left') &&
                        item.hasOwnProperty('right') &&
                        item.hasOwnProperty('top') &&
                        item.hasOwnProperty('bottom') &&
                        item.hasOwnProperty('docked') &&
                        item.hasOwnProperty('centered')
                    )) {
                    item = Ext.mergeIf({}, item, defaults);
                }
            }

            instance = Ext.factory(item, me.defaultItemClass);
        }

        return instance;
    },

    /**
     * Adds one or more Components to this Container. Example:
     *
     *     var myPanel = Ext.create('Ext.Panel', {
     *         html: 'This will be added to a Container'
     *     });
     *
     *     myContainer.add([myPanel]);
     *
     * @param {Object/Object[]/Ext.Component/Ext.Component[]} newItems The new items to add to the Container.
     * @return {Ext.Component} The last item added to the Container from the `newItems` array.
     */
    add: function(newItems) {
        var me = this,
            i, ln, item, newActiveItem;

        if (Ext.isArray(newItems)) {
            for (i = 0, ln = newItems.length; i < ln; i++) {
                item = me.factoryItem(newItems[i]);
                this.doAdd(item);
                if (!newActiveItem && !this.getActiveItem() && this.innerItems.length > 0 && item.isInnerItem()) {
                    newActiveItem = item;
                }
            }
        } else {
            item = me.factoryItem(newItems);
            this.doAdd(item);
            if (!newActiveItem && !this.getActiveItem() && this.innerItems.length > 0 && item.isInnerItem()) {
                newActiveItem = item;
            }
        }

        if (newActiveItem) {
            this.setActiveItem(newActiveItem);
        }

        return item;
    },

    /**
     * @private
     * @param {Ext.Component} item
     */
    doAdd: function(item) {
        var me = this,
            items = me.getItems(),
            index;

        if (!items.has(item)) {
            index = items.length;
            items.add(item);

            if (item.isInnerItem()) {
                me.insertInner(item);
            }

            item.setParent(me);

            me.onItemAdd(item, index);
        }
    },

    /**
     * Removes an item from this Container, optionally destroying it.
     * @param {Object} item The item to remove.
     * @param {Boolean} [destroy] Calls the Component's {@link Ext.Component#method-destroy destroy}
     * method if `true`.
     * @return {Ext.Component} this
     */
    remove: function(item, destroy) {
        var me = this,
            index = me.indexOf(item),
            innerItems = me.getInnerItems();

        if (destroy === undefined) {
            destroy = me.getAutoDestroy();
        }

        if (index !== -1) {
            if (!me.removingAll && innerItems.length > 1 && item === me.getActiveItem()) {
                me.on({
                    activeitemchange: 'doRemove',
                    scope: me,
                    single: true,
                    order: 'after',
                    args: [item, index, destroy]
                });

                me.doResetActiveItem(innerItems.indexOf(item));
            }
            else {
                me.doRemove(item, index, destroy);
                if (innerItems.length === 0) {
                    me.setActiveItem(null);
                }
            }
        }

        return me;
    },

    doResetActiveItem: function(innerIndex) {
        if (innerIndex === 0) {
            this.setActiveItem(1);
        }
        else {
            this.setActiveItem(0);
        }
    },

    doRemove: function(item, index, destroy) {
        var me = this;

        me.items.remove(item);

        if (item.isInnerItem()) {
            me.removeInner(item);
        }

        me.onItemRemove(item, index, destroy);

        item.setParent(null);

        if (destroy) {
            item.destroy();
        }
    },

    /**
     * Removes all items currently in the Container, optionally destroying them all.
     * @param {Boolean} destroy If `true`, {@link Ext.Component#method-destroy destroys}
     * each removed Component.
     * @param {Boolean} everything If `true`, completely remove all items including
     * docked / centered and floating items.
     * @return {Ext.Component} this
     */
    removeAll: function(destroy, everything) {
        var items = this.items,
            ln = items.length,
            i = 0,
            item;

        if (typeof destroy != 'boolean') {
            destroy = this.getAutoDestroy();
        }

        everything = Boolean(everything);

        // removingAll flag is used so we don't unnecessarily change activeItem while removing all items.
        this.removingAll = true;

        for (; i < ln; i++) {
            item = items.getAt(i);

            if (item && (everything || item.isInnerItem())) {
                this.doRemove(item, i, destroy);
                i--;
                ln--;
            }
        }
        this.setActiveItem(null);

        this.removingAll = false;

        return this;
    },

    /**
     * Returns the Component for a given index in the Container's {@link #property-items}.
     * @param {Number} index The index of the Component to return.
     * @return {Ext.Component} The item at the specified `index`, if found.
     */
    getAt: function(index) {
        return this.items.getAt(index);
    },

    getInnerAt: function(index) {
        return this.innerItems[index];
    },

    /**
     * Removes the Component at the specified index:
     *
     *     myContainer.removeAt(0); // removes the first item
     *
     * @param {Number} index The index of the Component to remove.
     */
    removeAt: function(index) {
        var item = this.getAt(index);

        if (item) {
            this.remove(item);
        }

        return this;
    },

    /**
     * Removes an inner Component at the specified index:
     *
     *     myContainer.removeInnerAt(0); // removes the first item of the innerItems property
     *
     * @param {Number} index The index of the Component to remove.
     */
    removeInnerAt: function(index) {
        var item = this.getInnerItems()[index];

        if (item) {
            this.remove(item);
        }

        return this;
    },

    /**
     * @private
     */
    has: function(item) {
        return this.getItems().indexOf(item) != -1;
    },

    /**
     * @private
     */
    hasInnerItem: function(item) {
        return this.innerItems.indexOf(item) != -1;
    },

    /**
     * @private
     */
    indexOf: function(item) {
        return this.getItems().indexOf(item);
    },

    innerIndexOf: function(item) {
        return this.innerItems.indexOf(item);
    },

    /**
     * @private
     * @param {Ext.Component} item
     * @param {Number} index
     */
    insertInner: function(item, index) {
        var items = this.getItems().items,
            innerItems = this.innerItems,
            currentInnerIndex = innerItems.indexOf(item),
            newInnerIndex = -1,
            nextSibling;

        if (currentInnerIndex !== -1) {
            innerItems.splice(currentInnerIndex, 1);
        }

        if (typeof index == 'number') {
            do {
                nextSibling = items[++index];
            } while (nextSibling && !nextSibling.isInnerItem());

            if (nextSibling) {
                newInnerIndex = innerItems.indexOf(nextSibling);
                innerItems.splice(newInnerIndex, 0, item);
            }
        }

        if (newInnerIndex === -1) {
            innerItems.push(item);
            newInnerIndex = innerItems.length - 1;
        }

        if (currentInnerIndex !== -1) {
            this.onInnerItemMove(item, newInnerIndex, currentInnerIndex);
        }

        return this;
    },

    onInnerItemMove: Ext.emptyFn,

    /**
     * @private
     * @param {Ext.Component} item
     */
    removeInner: function(item) {
        Ext.Array.remove(this.innerItems, item);

        return this;
    },

    /**
     * Adds a child Component at the given index. For example, here's how we can add a new item, making it the first
     * child Component of this Container:
     *
     *     myContainer.insert(0, {xtype: 'panel', html: 'new item'});
     *
     * @param {Number} index The index to insert the Component at.
     * @param {Object} item The Component to insert.
     */
    insert: function(index, item) {
        var me = this,
            i;

        //<debug error>
        if (typeof index != 'number') {
            Ext.Logger.error("Invalid index of '" + index + "', must be a valid number");
        }
        //</debug>

        if (Ext.isArray(item)) {
            for (i = item.length - 1; i >= 0; i--) {
                me.insert(index, item[i]);
            }

            return me;
        }

        item = this.factoryItem(item);

        this.doInsert(index, item);

        return item;
    },

    /**
     * @private
     * @param {Number} index
     * @param {Ext.Component} item
     */
    doInsert: function(index, item) {
        var me = this,
            items = me.items,
            itemsLength = items.length,
            currentIndex, isInnerItem;

        isInnerItem = item.isInnerItem();

        if (index > itemsLength) {
            index = itemsLength;
        }

        if (items[index - 1] === item) {
            return me;
        }

        currentIndex = me.indexOf(item);

        if (currentIndex !== -1) {
            if (currentIndex < index) {
                index -= 1;
            }

            items.removeAt(currentIndex);
        }

        items.insert(index, item);

        if (currentIndex === -1) {
            item.setParent(me);
        }

        if (isInnerItem) {
            me.insertInner(item, index);
        }

        if (currentIndex !== -1) {
            me.onItemMove(item, index, currentIndex);
        }
        else {
            me.onItemAdd(item, index);
        }
    },

    /**
     * @private
     */
    insertFirst: function(item) {
        return this.insert(0, item);
    },

    /**
     * @private
     */
    insertLast: function(item) {
        return this.insert(this.getItems().length, item);
    },

    /**
     * @private
     */
    insertBefore: function(item, relativeToItem) {
        var index = this.indexOf(relativeToItem);

        if (index !== -1) {
            this.insert(index, item);
        }
        return this;
    },

    /**
     * @private
     */
    insertAfter: function(item, relativeToItem) {
        var index = this.indexOf(relativeToItem);

        if (index !== -1) {
            this.insert(index + 1, item);
        }
        return this;
    },

    /**
     * @private
     */
    onItemAdd: function(item, index) {
        this.doItemLayoutAdd(item, index);

        if (this.initialized) {
            this.fireEvent('add', this, item, index);
        }
    },

    doItemLayoutAdd: function(item, index) {
        var layout = this.getLayout();

        if (this.isRendered() && item.setRendered(true)) {
            item.fireAction('renderedchange', [this, item, true], 'onItemAdd', layout, { args: [item, index] });
        }
        else {
            layout.onItemAdd(item, index);
        }
    },

    /**
     * @private
     */
    onItemRemove: function(item, index, destroying) {
        this.doItemLayoutRemove(item, index, destroying);

        this.fireEvent('remove', this, item, index);
    },

    doItemLayoutRemove: function(item, index, destroying) {
        var layout = this.getLayout();

        if (this.isRendered() && item.setRendered(false)) {
            item.fireAction('renderedchange', [this, item, false], 'onItemRemove', layout, { args: [item, index, destroying] });
        }
        else {
            layout.onItemRemove(item, index, destroying);
        }
    },

    /**
     * @private
     */
    onItemMove: function(item, toIndex, fromIndex) {
        if (item.isDocked()) {
            item.setDocked(null);
        }

        this.doItemLayoutMove(item, toIndex, fromIndex);

        this.fireEvent('move', this, item, toIndex, fromIndex);
    },

    doItemLayoutMove: function(item, toIndex, fromIndex) {
        this.getLayout().onItemMove(item, toIndex, fromIndex);
    },

    onItemInnerStateChange: function(item, isInner) {
        var layout = this.getLayout();

        if (isInner) {
            this.insertInner(item, this.items.indexOf(item));
        }
        else {
            this.removeInner(item);
        }

        layout.onItemInnerStateChange.apply(layout, arguments);
    },

    /**
     * Returns all inner {@link #property-items} of this container. `inner` means that the item is not `docked` or
     * `floating`.
     * @return {Array} The inner items of this container.
     */
    getInnerItems: function() {
        return this.innerItems;
    },

    /**
     * Returns all the {@link Ext.Component#docked} items in this container.
     * @return {Array} The docked items of this container.
     */
    getDockedItems: function() {
        var items = this.getItems().items,
            dockedItems = [],
            ln = items.length,
            item, i;

        for (i = 0; i < ln; i++) {
            item = items[i];
            if (item.isDocked()) {
                dockedItems.push(item);
            }
        }

        return dockedItems;
    },

    /**
     * @private
     */
    applyActiveItem: function(activeItem, currentActiveItem) {
        var innerItems = this.getInnerItems();

        // Make sure the items are already initialized
        this.getItems();

        // No items left to be active, reset back to 0 on falsy changes
        if (!activeItem && innerItems.length === 0) {
            return 0;
        }
        else if (typeof activeItem == 'number') {
            activeItem = Math.max(0, Math.min(activeItem, innerItems.length - 1));
            activeItem = innerItems[activeItem];

            if (activeItem) {
                return activeItem;
            }
            else if (currentActiveItem) {
                return null;
            }
        }
        else if (activeItem) {
            var item;

            //ComponentQuery selector?
            if (typeof activeItem == 'string') {
                item = this.child(activeItem);

                activeItem = {
                    xtype : activeItem
                };
            }

            if (!item || !item.isComponent) {
                item = this.factoryItem(activeItem);
            }
            this.pendingActiveItem = item;

            //<debug error>
            if (!item.isInnerItem()) {
                Ext.Logger.error("Setting activeItem to be a non-inner item");
            }
            //</debug>

            if (!this.has(item)) {
                this.add(item);
            }

            return item;
        }
    },

    /**
     * Animates to the supplied `activeItem` with a specified animation. Currently this only works
     * with a Card layout.  This passed animation will override any default animations on the
     * container, for a single card switch. The animation will be destroyed when complete.
     * @param {Object/Number} activeItem The item or item index to make active.
     * @param {Object/Ext.fx.layout.Card} animation Card animation configuration or instance.
     */
    animateActiveItem: function(activeItem, animation) {
        var layout = this.getLayout(),
            defaultAnimation;

        if (this.activeItemAnimation) {
            this.activeItemAnimation.destroy();
        }
        this.activeItemAnimation = animation = new Ext.fx.layout.Card(animation);
        if (animation && layout.isCard) {
            animation.setLayout(layout);
            defaultAnimation = layout.getAnimation();
            if (defaultAnimation) {
                defaultAnimation.disable();
            }
            animation.on('animationend', function() {
                if (defaultAnimation) {
                    defaultAnimation.enable();
                }
                animation.destroy();
            }, this);
        }
        return this.setActiveItem(activeItem);
    },

    /**
     * @private
     */
    doSetActiveItem: function(newActiveItem, oldActiveItem) {
        delete this.pendingActiveItem;
        if (oldActiveItem) {
            oldActiveItem.fireEvent('deactivate', oldActiveItem, this, newActiveItem);
        }

        if (newActiveItem) {
            newActiveItem.fireEvent('activate', newActiveItem, this, oldActiveItem);
        }
    },

    show:function(){
        this.callParent(arguments);

        var modal = this.getModal();

        if (modal) {
            modal.setHidden(false);
        }

        return this;
    },

    hide:function(){
        this.callParent(arguments);

        var modal = this.getModal();

        if (modal) {
            modal.setHidden(true);
        }

        return this;
    },

    doSetHidden: function(hidden) {
        var modal = this.getModal();

        if (modal && (modal.getHidden() !== hidden)) {
            modal.setHidden(hidden);
        }

        this.callSuper(arguments);
    },

    /**
     * @private
     */
    setRendered: function(rendered) {
        if (this.callParent(arguments)) {
            var items = this.items.items,
                i, ln;

            for (i = 0,ln = items.length; i < ln; i++) {
                items[i].setRendered(rendered);
            }

            return true;
        }

        return false;
    },

    /**
     * @private
     */
    getScrollableBehavior: function() {
        var behavior = this.scrollableBehavior;

        if (!behavior) {
            behavior = this.scrollableBehavior = new Ext.behavior.Scrollable(this);
        }

        return behavior;
    },

    /**
     * @private
     */
    applyScrollable: function(config) {
        if (typeof config === 'boolean') {
            //<debug warn>
            if (config === false && !(this.getHeight() !== null || this.heightLayoutSized || (this.getTop() !== null && this.getBottom() !== null))) {
                Ext.Logger.warn("This container is set to scrollable: false but has no specified height. " +
                    "You may need to set the container to scrollable: null or provide a height.", this);
            }
            //</debug>
            this.getScrollableBehavior().setConfig({disabled: !config});
        } else if (config && !config.isObservable) {
            this.getScrollableBehavior().setConfig(config);
        }
        return config;
    },

    doSetScrollable: function() {
        // Used for plugins when they need to reinitialize scroller listeners
    },

    /**
     * Returns an the scrollable instance for this container, which is a {@link Ext.scroll.View} class.
     *
     * Please checkout the documentation for {@link Ext.scroll.View}, {@link Ext.scroll.View#getScroller}
     * and {@link Ext.scroll.Scroller} for more information.
     * @return {Ext.scroll.View} The scroll view.
     */
    getScrollable: function() {
        return this.getScrollableBehavior().getScrollView();
    },

    // Used by ComponentQuery to retrieve all of the items
    // which can potentially be considered a child of this Container.
    // This should be overridden by components which have child items
    // that are not contained in items. For example `dockedItems`, `menu`, etc
    // @private
    getRefItems: function(deep) {
        var items = this.getItems().items.slice(),
            ln = items.length,
            i, item;

        if (deep) {
            for (i = 0; i < ln; i++) {
                item = items[i];

                if (item.getRefItems) {
                    items = items.concat(item.getRefItems(true));
                }
            }
        }

        return items;
    },

    /**
     * Examines this container's `{@link #property-items}` property
     * and gets a direct child component of this container.
     * @param {String/Number} component This parameter may be any of the following:
     *
     * - {String} : representing the `itemId`
     * or `{@link Ext.Component#getId id}` of the child component.
     * - {Number} : representing the position of the child component
     * within the `{@link #property-items}` property.
     *
     * For additional information see {@link Ext.util.MixedCollection#get}.
     * @return {Ext.Component} The component (if found).
     */
    getComponent: function(component) {
        if (Ext.isObject(component)) {
            component = component.getItemId();
        }

        return this.getItems().get(component);
    },

    /**
     * Finds a docked item of this container using a reference, `id `or an `index` of its location
     * in {@link #getDockedItems}.
     * @param {String/Number} component The `id` or `index` of the component to find.
     * @return {Ext.Component/Boolean} The docked component, if found.
     */
    getDockedComponent: function(component) {
        if (Ext.isObject(component)) {
            component = component.getItemId();
        }

        var dockedItems = this.getDockedItems(),
            ln = dockedItems.length,
            item, i;

        if (Ext.isNumber(component)) {
            return dockedItems[component];
        }

        for (i = 0; i < ln; i++) {
            item = dockedItems[i];
            if (item.id == component) {
                return item;
            }
        }

        return false;
    },

    /**
     * Retrieves all descendant components which match the passed selector.
     * Executes an Ext.ComponentQuery.query using this container as its root.
     * @param {String} selector Selector complying to an Ext.ComponentQuery selector.
     * @return {Array} Ext.Component's which matched the selector.
     */
    query: function(selector) {
        return Ext.ComponentQuery.query(selector, this);
    },

    /**
     * Retrieves the first direct child of this container which matches the passed selector.
     * The passed in selector must comply with an {@link Ext.ComponentQuery} selector.
     * @param {String} selector An {@link Ext.ComponentQuery} selector.
     * @return {Ext.Component}
     */
    child: function(selector) {
        return this.query('> ' + selector)[0] || null;
    },

    /**
     * Retrieves the first descendant of this container which matches the passed selector.
     * The passed in selector must comply with an {@link Ext.ComponentQuery} selector.
     * @param {String} selector An {@link Ext.ComponentQuery} selector.
     * @return {Ext.Component}
     */
    down: function(selector) {
        return this.query(selector)[0] || null;
    },

    //<deprecated product=touch since=2.0>
    onClassExtended: function(Class, members) {
        if ('onAdd' in members || 'onRemove' in members) {
            throw new Error("["+Class.$className+"] 'onAdd()' and 'onRemove()' methods " +
                            "no longer exist in Ext.Container, please use 'onItemAdd()' " +
                            "and 'onItemRemove()' instead }");
        }
    },
    //</deprecated>

    destroy: function() {
        var me = this,
            modal = me.getModal();

        if (modal) {
            modal.destroy();
        }

        me.removeAll(true, true);
        me.unlink('_scrollable');
        Ext.destroy(me.items);

        me.callSuper();
    }

}, function() {
    this.addMember('defaultItemClass', this);

    //<deprecated product=touch since=2.0>
    /**
     * @method addAll
     * Adds an array of Components to this Container.
     * @deprecated 2.0.0 Please use {@link #method-add} instead.
     * @param {Array} items The array of items to add to this container.
     * @return {Array} The array of items after they have been added.
     */
    Ext.deprecateClassMethod(this, 'addAll', 'add');

    /**
     * @method removeDocked
     * Removes a docked item from this Container.
     * @deprecated 2.0.0 Please use {@link #method-remove} instead.
     * @param {Object} item The item to remove.
     * @param {Boolean} destroy Calls the Component's {@link Ext.Component#method-destroy destroy}
     * method if `true`.
     * @return {Ext.Component} this
     */
    Ext.deprecateClassMethod(this, 'removeDocked', 'remove');

    /**
     * @member Ext.Container
     * @property items
     * @type Ext.util.MixedCollection
     * The set of all items in this Container.
     * @deprecated 2.0.0 Please use {@link #getItems} method instead.
     */
    this.override({
        constructor: function(config) {
            config = config || {};

            var dockedItems = config.dockedItems,
                i, ln, item;

            /**
             * @cfg {Boolean/String/Object} scroll
             * @inheritdoc Ext.Container#scrollable
             * @deprecated 2.0.0 Please use the {@link #scrollable} configuration.
             */
            if (config.scroll) {
                //<debug warn>
                Ext.Logger.deprecate("'scroll' config is deprecated, please use 'scrollable' instead.", this);
                //</debug>

                config.scrollable = config.scroll;
                delete config.scroll;
            }

            this.callOverridden(arguments);

            if (dockedItems) {
                //<debug warn>
                Ext.Logger.deprecate("'dockedItems' config is deprecated, please add all docked items inside the 'items' config with a 'docked' property indicating the docking position instead, i.e { /*...*/ docked: 'top' /*...*/ }");
                //</debug>

                dockedItems = Ext.Array.from(dockedItems);

                for (i = 0,ln = dockedItems.length; i < ln; i++) {
                    item = dockedItems[i];
                    if ('dock' in item) {
                        //<debug warn>
                        Ext.Logger.deprecate("'dock' config for docked items is deprecated, please use 'docked' instead");
                        //</debug>
                        item.docked = item.dock;
                    }
                }

                this.add(dockedItems);
            }
        },

        add: function() {
            var args = arguments;

            if (args.length > 1) {
                if (typeof args[0] == 'number') {
                    //<debug warn>
                    Ext.Logger.deprecate("add(index, item) method signature is deprecated, please use insert(index, item) instead");
                    //</debug>
                    return this.insert(args[0], args[1]);
                }
                //<debug warn>
                Ext.Logger.deprecate("Passing items as multiple arguments is deprecated, please use one single array of items instead");
                //</debug>
                args = [Array.prototype.slice.call(args)];
            }

            return this.callOverridden(args);
        },

        doAdd: function(item) {
            var docked = item.getDocked(),
                overlay = item.overlay,
                position;

            if (overlay && docked) {
                //<debug>
                Ext.Logger.deprecate("'overlay' config is deprecated on docked items, please set the top/left/right/bottom configurations instead.", this);
                //</debug>

                if (docked == "top") {
                    position = {
                        top: 0,
                        bottom: 'auto',
                        left: 0,
                        right: 0
                    };
                } else if (docked == "bottom") {
                    position = {
                        top: null,
                        bottom: 0,
                        left: 0,
                        right: 0
                    };
                }

                if (position) {
                    item.setDocked(false);

                    item.setTop(position.top);
                    item.setBottom(position.bottom);
                    item.setLeft(position.left);
                    item.setRight(position.right);
                }
            }

            return this.callOverridden(arguments);
        },

        applyDefaults: function(defaults) {
            if (typeof defaults == 'function') {
                //<debug warn>
                Ext.Logger.deprecate("Passing a function as 'defaults' is deprecated. To add custom logics when " +
                    "'defaults' is applied to each item, have your own factoryItem() method in your sub-class instead");
                //</debug>
            }

            return this.callOverridden(arguments);
        },

        factoryItemWithDefaults: function(item) {
            var defaults = this.getDefaults(),
            customDefaults, ret;

            // Defaults is a function (must return a string, object, or class instance)
            if (typeof defaults == 'function') {
                customDefaults = defaults.call(this, item);
            }

            // String (must be the id of an existent component)
            if (typeof item == 'string') {
                //<debug warn>
                Ext.Logger.deprecate("Passing a string id of item ('"+item+"') is deprecated, please pass a reference to that item instead");
                //</debug>

                item = Ext.getCmp(item);
            }

            if (customDefaults) {
                this._defaults = customDefaults;
            }

            ret = this.callParent([item]);

            if (customDefaults) {
                this._defaults = defaults;
            }

            return ret;
        },

        applyMasked: function(masked) {
            if (Ext.isObject(masked) && !masked.isInstance && 'message' in masked && !('xtype' in masked) && !('xclass' in masked)) {
                masked.xtype = 'loadmask';

                //<debug warn>
                Ext.Logger.deprecate("Using a 'message' config without specify an 'xtype' or 'xclass' will no longer implicitly set 'xtype' to 'loadmask'. Please set that explicitly.");
                //</debug>
            }

            return this.callOverridden(arguments);
        }
    });

    Ext.deprecateClassMethod(this, 'setMask', 'setMasked');
    //</deprecated>
});


"use strict";

var base = require('./base');
var code = require('./code');

function exportEnumMsgID(lstreq, reqid, lstres, resid) {
    var str = '// 消息ID定义 \r\n';
    str += 'enum MSGID {\r\n\r\n';

    str += '\t//--------------------------------------------------------------------------- \r\n';
    str += '\t// 客户端发送的消息 \r\n';
    var arr = [[], [], []];
    for (var i = 0; i < lstreq.length; ++i) {
        arr[0].push(base.buildEnumReqMemberName(lstreq[i].name));
        arr[1].push('= ' + reqid + ';');
        arr[2].push('// ' + lstreq[i].comment);
        reqid++;
    }

    str += code.alignCode(arr, '\t');

    str += '\r\n';

    str += '\t//--------------------------------------------------------------------------- \r\n';
    str += '\t// 服务器发送的消息 \r\n';
    arr = [[], [], []];
    for (var i = 0; i < lstres.length; ++i) {
        arr[0].push(base.buildEnumResMemberName(lstres[i].name));
        arr[1].push('= ' + resid + ';');
        arr[2].push('// ' + lstres[i].comment);
        resid++;
    }

    str += code.alignCode(arr, '\t');

    str += '\r\n';

    str += '}\r\n\r\n';

    return str;
}

function getPBRealType(str, root) {
    if (str == 'MsgID') {
        return 'MSGID';
    }

    if (base.isBaseType(str)) {
        if (str == 'int') {
            return 'int32';
        }
        else if (str == 'time') {
            return 'int32';
        }

        return str;
    }

    for (var i = 0; i < root.length; ++i) {
        if (root[i].name == str) {
            if (root[i].type == 'enum') {
                return root[i].name;
            }

            if (root[i].type == 'struct' || root[i].type == 'message' || root[i].type == 'static') {
                return root[i].name;
            }

            if (root[i].type == 'type') {
                return getPBRealType(root[i].val);
            }
        }
    }

    return '[ERR]';
}

function getFirstType(member) {
    if (member.hasOwnProperty('type2')) {
        if (member.type2 == 'repeated') {
            return 'repeated';
        }
    }

    if (member.hasOwnProperty('val') && member.val.val == 'NULL') {
        return 'optional';
    }

    return 'required';
}

function exportMember(obj, lstexport, root) {
    base.forEachStruct(obj.name, obj, root, function (structname, cobj, root) {
        if (cobj.name.name.indexOf('_') != 0) {
            if (base.isEnum(cobj.type, root)) {
                if (lstexport.indexOf(cobj.type) < 0) {
                    lstexport.push(cobj.type);
                }
            }
            else if (base.isStruct(cobj.type, root) || base.isStatic(cobj.type, root)) {

                if (lstexport.indexOf(cobj.type) < 0) {
                    lstexport = exportMember(base.getGlobalObj(cobj.type, root), lstexport, root);
                }

                if (lstexport.indexOf(cobj.type) < 0) {
                    lstexport.push(cobj.type);
                }
            }
        }
    }, true);

    return lstexport;
}

function exportEnum(obj, callback, root) {
    var str = '// ' + obj.comment + ' \r\n';
    str += 'enum ' + obj.name + ' {\r\n';

    var arr = [[], [], []];
    for (var i = 0; i < obj.val.length; ++i) {
        arr[0].push(obj.val[i].name);
        arr[1].push('= ' + obj.val[i].val.val + ';');
        arr[2].push('// ' + obj.val[i].comment);
    }

    str += code.alignCode(arr, '\t');

    str += '}\r\n'

    return str;
}

function exportMsg(msg, callback, root) {
    var str = '// ' + msg.comment + ' \r\n';
    str += 'message ' + base.getNoUnderscoreName(msg.name) + ' {\r\n';

    var beginid = 1;

    var arr = [[], [], []];
    base.forEachStruct(msg.name, msg, root, function (structname, obj, root) {
        if (obj.name.name.indexOf('_') != 0) {
            arr[0].push(getFirstType(obj) + ' ' + base.getNoUnderscoreName(getPBRealType(obj.type, root)) + ' ' + obj.name.name);
            arr[1].push('= ' + beginid + ';');
            arr[2].push('// ' + obj.comment);

            beginid++;
        }
    }, true);

    str += code.alignCode(arr, '\t');

    str += '}\r\n'

    return str;
}

// callback(isok, err)
function exportProtobuf(pkgname, obj, callback) {
    var lstexport = [];
    var lstreq = [];
    var lstres = [];

    if (Array.isArray(obj)) {
        var str = 'package ' + pkgname + ';\r\n\r\n';

        for (var i = 0; i < obj.length; ++i) {
            if (obj[i].type == 'message') {
                if (base.isResMsg(obj[i].name)) {
                    lstres.push({name: obj[i].name, comment: obj[i].comment});
                }
                else if (base.isReqMsg(obj[i].name)) {
                    lstreq.push({name: obj[i].name, comment: obj[i].comment});
                }
            }
        }

        str += exportEnumMsgID(lstreq, base.getGlobalObj('REQ_MSGID_BEGIN', obj).val.val, lstres, base.getGlobalObj('RES_MSGID_BEGIN', obj).val.val);

        for (var i = 0; i < obj.length; ++i) {
            if (obj[i].type == 'message') {
                lstexport = exportMember(obj[i], lstexport, obj);
            }
        }

        for (var i = 0; i < lstexport.length; ++i) {
            var co = base.getGlobalObj(lstexport[i], obj);
            if (co.type == 'enum') {
                var cs = exportEnum(co, callback, obj);
                if (cs == undefined) {
                    return ;
                }

                str += cs + '\r\n';
            }
            else {
                var cs = exportMsg(co, callback, obj);
                if (cs == undefined) {
                    return ;
                }

                str += cs + '\r\n';
            }
        }

        for (var i = 0; i < obj.length; ++i) {
            if (obj[i].type == 'message') {
                var cs = exportMsg(obj[i], callback, obj);
                if (cs == undefined) {
                    return ;
                }

                str += cs + '\r\n';
            }
        }

        return str;
    }

    return undefined;
}

exports.exportProtobuf = exportProtobuf;


/**
 * @private
 * Windows Phone version of Viewport.
 */
Ext.define('Ext.viewport.WindowsPhone', {
    requires: [],

    alternateClassName: 'Ext.viewport.WP',

    extend: 'Ext.viewport.Default',

    // so one pixel line is displayed on the right side of the screen. Setting width more than 100% fix the issue
//    config: {
//        width: '100.2%',
//        height: '100.2%'
//    },

    config: {
        translatable: {
            translationMethod: 'csstransform'
        }
    },

    initialize: function () {
        // There is -ms-user-select CSS property for IE10, but it seems it works only in desktop browser. So we need to prevent selection event.
        var preventSelection = function(e) {
            var srcElement = e.srcElement.nodeName.toUpperCase(),
                selectableElements = ['INPUT', 'TEXTAREA'];

            if (selectableElements.indexOf(srcElement) == -1) {
                return false;
            }
        };

        document.body.addEventListener('onselectstart', preventSelection);

        this.callParent(arguments);
    }
});