/**
 * 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'
    ],

    /**
     * @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: [],

        // @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);
                }
            }
        }
    }
});
