/**
 * A Profile represents a range of devices that fall under a common category. For the vast majority
 * of apps that use device profiles, the app defines a Phone profile and a Tablet profile. Doing
 * this enables you to easily customize the experience for the different sized screens offered by
 * those device types.
 *
 * Only one Profile can be active at a time, and each Profile defines a simple {@link #isActive}
 * function that should return either true or false. The first Profile to return true from its
 * isActive function is set as your Application's
 * {@link Ext.app.Application#currentProfile current profile}.
 *
 * A Profile can define any number of {@link #models}{@link #views}{@link #controllers} and
 * {@link #stores} which will be loaded if the Profile is activated. It can also define
 * a {@link #launch} function that will be called after all of its dependencies have been loaded,
 * just before the {@link Ext.app.Application#launch application launch} function is called.
 *
 * ## Sample Usage
 *
 * First you need to tell your Application about your Profile(s):
 *
 *     Ext.application({
 *         name: 'MyApp',
 *         profiles: ['Phone', 'Tablet']
 *     });
 *
 * This will load app/profile/Phone.js and app/profile/Tablet.js. Here's how we might define the
 * Phone profile:
 *
 *     Ext.define('MyApp.profile.Phone', {
 *         extend: 'Ext.app.Profile',
 *
 *         views: ['Main'],
 *
 *         isActive: function() {
 *             return Ext.os.is('Phone');
 *         }
 *     });
 *
 * The isActive function returns true if we detect that we are running on a phone device. If that
 * is the case the Application will set this Profile active and load the 'Main' view specified
 * in the Profile's {@link #views} config.
 *
 * ## Class Specializations
 *
 * Because Profiles are specializations of an application, all of the models, views, controllers
 * and stores defined in a Profile are expected to be namespaced under the name of the Profile.
 * Here's an expanded form of the example above:
 *
 *     Ext.define('MyApp.profile.Phone', {
 *         extend: 'Ext.app.Profile',
 *
 *         views: ['Main'],
 *         controllers: ['Signup'],
 *         models: ['MyApp.model.Group'],
 *
 *         isActive: function() {
 *             return Ext.os.is('Phone');
 *         }
 *     });
 *
 * In this case, the Profile is going to load *app/view/phone/Main.js*,
 * *app/controller/phone/Signup.js* and *app/model/Group.js*. Notice that in each of the first
 * two cases the name of the profile ('phone' in this case) was injected into the class names.
 * In the third case we specified the full Model name (for Group) so the Profile name was not
 * injected.
 *
 * For a fuller understanding of the ideas behind Profiles and how best to use them in your app,
 * we suggest you read the [device profiles guide](/touch/2.4/core_concepts/device_profiles.html).
 * 
 */
Ext.define('Ext.app.Profile', {
    mixins: [
        'Ext.mixin.Observable'
    ],
 
    requires: [
        'Ext.app.Controller'
    ],
 
    /**
     * @property {Boolean} 
     * `true` to identify an object as an instance of `Ext.app.Profile`
     */
    isProfile: true,
 
    /**
     * @cfg {String} [namespace]
     * The namespace that this Profile's classes can be found in. Defaults to the lowercase
     * Profile {@link #name}, for example a Profile called MyApp.profile.Phone will by default
     * have a 'phone' namespace, which means that this Profile's additional models, stores, views
     * and controllers will be loaded from the MyApp.model.phone.*, MyApp.store.phone.*,
     * MyApp.view.phone.* and MyApp.controller.phone.* namespaces respectively.
     * @accessor
     */
 
    /**
     * @cfg {String} [name]
     * The name of this Profile. Defaults to the last section of the class name (e.g. a profile
     * called MyApp.profile.Phone will default the name to 'Phone').
     * @accessor
     */
 
    config: {
        /**
         * @cfg {String} mainView
         */
        mainView: {
            $value: null,
            lazy: true
        },
 
        /**
         * @cfg {Ext.app.Application} application
         * The {@link Ext.app.Application Application} instance to which this Profile is
         * bound. This is set automatically.
         * @accessor
         * @readonly
         */
        application: null,
 
        // @cmd-auto-dependency {aliasPrefix: "controller.", profile: true, blame: "all"}
        /**
         * @cfg {String[]} controllers
         * Any additional {@link Ext.app.Controller Controllers} to load for this profile.
         * Note that each item here will be prepended with the Profile namespace when loaded.
         *
         * Example usage:
         *
         *     controllers: [
         *         'Users',
         *         'MyApp.controller.Products'
         *     ]
         *
         * This will load *MyApp.controller.tablet.Users* and *MyApp.controller.Products*.
         * @accessor
         */
        controllers: [],
 
        // @cmd-auto-dependency {aliasPrefix : "model.", profile: true, blame: "all"}
        /**
         * @cfg {String[]} models
         * Any additional {@link Ext.app.Application#models Models} to load for this profile.
         * Note that each item here will be prepended with the Profile namespace when loaded.
         *
         * Example usage:
         *
         *     models: [
         *         'Group',
         *         'MyApp.model.User'
         *     ]
         *
         * This will load *MyApp.model.tablet.Group* and *MyApp.model.User*.
         * @accessor
         */
        models: [],
 
        /* eslint-disable-next-line max-len */
        // @cmd-auto-dependency { aliasPrefix: "view.", profile: true, isKeyedObject:true, blame: "all" }
        /**
         * @cfg {Object/String[]} views
         * This config allows the active profile to define a set of `xtypes` and map them
         * to desired classes and default configurations. Normally an `xtype` is statically
         * declared by a {@link Ext.Component component} in its class definition. This
         * mechanism allows the active profile to control a set of these types.
         *
         * Example:
         *
         *      views: {
         *          // The "main" xtype maps to MyApp.view.tablet.Main
         *          //
         *          main: 'MyApp.view.tablet.Main',
         *
         *          // The "inbox" xtype maps to a subclass of MyApp.view.Inbox (created
         *          // by this mechanism) that sets the "mode" config to "compact".
         *          //
         *          inbox: {
         *              xclass: 'MyApp.view.Inbox',
         *              mode: 'compact'
         *          }
         *      }
         *
         * Note that class names used in this form must be full class names, unlike the
         * historical usage of `views`. Further, these views cannot be accessed using the
         * `getView` method but rather via their assigned `xtype`.
         *
         * The historical usage of this config is enabled when an array is passed. In this
         * case, these are simply additional {@link Ext.app.Application#views views} to
         * load for this profile. Note that each item here will be prepended with the
         * Profile namespace when loaded.
         *
         * Example usage:
         *
         *     views: [
         *         'Main',
         *         'MyApp.view.Login'
         *     ]
         *
         * This will load *MyApp.view.tablet.Main* and *MyApp.view.Login*. While supported,
         * this usage is discouraged in favor of `xtype` mapping.
         * @accessor
         */
        views: [],
 
        // @cmd-auto-dependency {aliasPrefix: "store.", profile: true, blame: "all"}
        /**
         * @cfg {String[]} stores
         * Any additional {@link Ext.app.Application#stores Stores} to load for this profile.
         * Note that each item here will be prepended with the Profile namespace when loaded.
         *
         * Example usage:
         *
         *     stores: [
         *         'Users',
         *         'MyApp.store.Products'
         *     ]
         *
         * This will load *MyApp.store.tablet.Users* and *MyApp.store.Products*.
         * @accessor
         */
        stores: []
    },
 
    /**
     * Creates a new Profile instance
     */
    constructor: function(config) {
        this.initConfig(config);
 
        this.mixins.observable.constructor.apply(this, arguments);
    },
 
    /**
     * Determines whether or not this Profile is active on the device isActive is executed on.
     * Should return true if this profile is meant to be active on this device, false otherwise.
     * Each Profile should implement this function (the default implementation just returns false).
     * @return {Boolean} True if this Profile should be activated on the device it is running on,
     * false otherwise
     */
    isActive: function() {
        return false;
    },
 
    /**
     * This method is called once the profile is determined to be the active profile. This
     * initialization is performed before controllers are initialized and therefore also
     * before launch.
     * @protected
     * @since 6.0.1
     */
    init: function() {
        var views = this.getViews(),
            xtype;
 
        if (views && !(views instanceof Array)) {
            for (xtype in views) {
                Ext.ClassManager.setXType(views[xtype], xtype);
            }
        }
    },
 
    /**
     * @method
     * The launch function is called by the {@link Ext.app.Application Application} if this
     * Profile's {@link #isActive} function returned true. This is typically the best place to run
     * any profile-specific app launch code. Example usage:
     *
     *     launch: function() {
     *         Ext.create('MyApp.view.tablet.Main');
     *     }
     */
    launch: Ext.emptyFn,
 
    onClassExtended: function(cls, data, hooks) {
        var onBeforeClassCreated = hooks.onBeforeCreated;
 
        hooks.onBeforeCreated = function(cls, data) {
            var Controller = Ext.app.Controller,
                className = cls.$className,
                requires = [],
                proto = cls.prototype,
                views = data.views,
                name, namespace;
 
            // Process name and namespace configs here since we need to use the namespace
            // in the dependency calculation
            name = data.name;
 
            if (name) {
                delete data.name;
            }
            else {
                name = className.split('.');
                name = name[name.length - 1];
            }
 
            cls._name = name;
 
            cls._namespace = name = (data.namespace || name).toLowerCase();
            delete data.namespace;
 
            namespace = Controller.resolveNamespace(cls, data);
 
            Controller.processDependencies(proto, requires, namespace, 'model', data.models, name);
            Controller.processDependencies(proto, requires, namespace, 'store', data.stores, name);
            Controller.processDependencies(proto, requires, namespace, 'controller',
                                           data.controllers, name);
 
            if (views) {
                if (views instanceof Array) {
                    Controller.processDependencies(proto, requires, namespace, 'view', views, name);
                }
                else {
                    Ext.app.Profile.processViews(className, views, requires);
                }
            }
 
            Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
        };
    },
 
    getName: function() {
        // This used to be a Config but is now processed in onClassExtended so we provide
        // the getter for compat.
        return this.self._name;
    },
 
    getNamespace: function() {
        // This used to be a Config but is now processed in onClassExtended so we provide
        // the getter for compat.
        return this.self._namespace;
    },
 
    privates: {
        statics: {
            processViews: function(className, views, requires) {
                var body, cls, s, xtype;
 
                for (xtype in views) {
                    cls = views[xtype];
 
                    if (typeof cls !== 'string') {
                        s = cls.xclass;
 
                        //<debug>
                        if (!s) {
                            Ext.raise('Views must specify an xclass');
                        }
                        //</debug>
 
                        body = Ext.apply({
                            extend: s
                        }, cls);
 
                        delete body.xclass;
 
                        // Class names will be App.profile.Tablet$inbox for example
                        Ext.define(views[xtype] = className + '$' + xtype, body);
                        cls = s;
                    }
 
                    requires.push(cls);
                }
            }
        }
    }
});