/**
 * 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: {
        observable: "Ext.mixin.Observable"
    },

    config: {
        /**
         * @cfg {String} namespace The namespace that this Profile's classes can be found in. Defaults to the lowercased
         * 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
         */
        namespace: 'auto',

        /**
         * @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
         */
        name: 'auto',

        /**
         * @cfg {String} mainView
         */
        mainView: {
            $value: null,
            lazy: true
        },

        /**
         * @cfg {Ext.app.Application} application The {@link Ext.app.Application Application} instance that this
         * Profile is bound to. This is set automatically.
         * @accessor
         * @readonly
         */
        application: null,

        // @cmd-auto-dependency {aliasPrefix: "controller.", profile: true, blame: "all"}
        /**
         * @cfg {Array} 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 {Array} 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, blame: "all"}
        /**
         * @cfg {Array} views Any 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*.
         * @accessor
         */
        views: [],

        // @cmd-auto-dependency {aliasPrefix: "store.", profile: true, blame: "all"}
        /**
         * @cfg {Array} 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;
    },

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

    /**
     * @private
     */
    applyNamespace: function(name) {
        if (name == 'auto') {
            name = this.getName();
        }

        return name.toLowerCase();
    },

    /**
     * @private
     */
    applyName: function(name) {
        if (name == 'auto') {
            var pieces = this.$className.split('.');
            name = pieces[pieces.length - 1];
        }

        return name;
    },
    onClassExtended: function(cls, data, hooks) {
        var onBeforeClassCreated = hooks.onBeforeCreated;

        hooks.onBeforeCreated = function(cls, data) {
            var Controller = Ext.app.Controller,
                requires = [],
                proto = cls.prototype,
                namespace;

            namespace = Controller.resolveNamespace(cls, data);

            Controller.processDependencies(proto, requires, namespace, 'model', data.models);
            Controller.processDependencies(proto, requires, namespace, 'view', data.views);
            Controller.processDependencies(proto, requires, namespace, 'store', data.stores);
            Controller.processDependencies(proto, requires, namespace, 'controller', data.controllers);

            Ext.require(requires, Ext.Function.pass(onBeforeClassCreated, arguments, this));
        };
    },

    /**
     * @private
     * Computes the full class names of any specified model, view, controller and store dependencies, returns them in
     * an object map for easy loading
     */
    getDependencies: function() {
        var allClasses = [],
            appName = this.getApplication().getName(),
            namespace = this.getNamespace(),
            map = {
                model: this.getModels(),
                view: this.getViews(),
                controller: this.getControllers(),
                store: this.getStores()
            },
            classType, classNames;

        for (classType in map) {
            classNames = [];

            Ext.each(map[classType], function(className) {
                if (Ext.isString(className)) {
                    //we check name === appName to allow MyApp.profile.MyApp to exist
                    if (Ext.isString(className) && (Ext.ClassManager.getPrefix(className) === "" || className === appName)) {
                        className = appName + '.' + classType + '.' + namespace + '.' + className;
                    }

                    classNames.push(className);
                    allClasses.push(className);
                }
            }, this);

            map[classType] = classNames;
        }

        map.all = allClasses;

        return map;
    }
});