/**
 * @author Ed Spencer
 *
 * @aside guide controllers
 * @aside guide apps_intro
 * @aside guide history_support
 * @aside video mvc-part-1
 * @aside video mvc-part-2
 *
 * Controllers are responsible for responding to events that occur within your app. If your app contains a Logout
 * {@link Ext.Button button} that your user can tap on, a Controller would listen to the Button's tap event and take
 * the appropriate action. It allows the View classes to handle the display of data and the Model classes to handle the
 * loading and saving of data - the Controller is the glue that binds them together.
 *
 * ## Relation to Ext.app.Application
 *
 * Controllers exist within the context of an {@link Ext.app.Application Application}. An Application usually consists
 * of a number of Controllers, each of which handle a specific part of the app. For example, an Application that
 * handles the orders for an online shopping site might have controllers for Orders, Customers and Products.
 *
 * All of the Controllers that an Application uses are specified in the Application's
 * {@link Ext.app.Application#controllers} config. The Application automatically instantiates each Controller and keeps
 * references to each, so it is unusual to need to instantiate Controllers directly. By convention each Controller is
 * named after the thing (usually the Model) that it deals with primarily, usually in the plural - for example if your
 * app is called 'MyApp' and you have a Controller that manages Products, convention is to create a
 * MyApp.controller.Products class in the file app/controller/Products.js.
 *
 * ## Refs and Control
 *
 * The centerpiece of Controllers is the twin configurations {@link #refs} and {@link #cfg-control}. These are used to
 * easily gain references to Components inside your app and to take action on them based on events that they fire.
 * Let's look at {@link #refs} first:
 *
 * ### Refs
 *
 * Refs leverage the powerful {@link Ext.ComponentQuery ComponentQuery} syntax to easily locate Components on your
 * page. We can define as many refs as we like for each Controller, for example here we define a ref called 'nav' that
 * finds a Component on the page with the ID 'mainNav'. We then use that ref in the addLogoutButton beneath it:
 *
 *     Ext.define('MyApp.controller.Main', {
 *         extend: 'Ext.app.Controller',
 *
 *         config: {
 *             refs: {
 *                 nav: '#mainNav'
 *             }
 *         },
 *
 *         addLogoutButton: function() {
 *             this.getNav().add({
 *                 text: 'Logout'
 *             });
 *         }
 *     });
 *
 * Usually, a ref is just a key/value pair - the key ('nav' in this case) is the name of the reference that will be
 * generated, the value ('#mainNav' in this case) is the {@link Ext.ComponentQuery ComponentQuery} selector that will
 * be used to find the Component.
 *
 * Underneath that, we have created a simple function called addLogoutButton which uses this ref via its generated
 * 'getNav' function. These getter functions are generated based on the refs you define and always follow the same
 * format - 'get' followed by the capitalized ref name. In this case we're treating the nav reference as though it's a
 * {@link Ext.Toolbar Toolbar}, and adding a Logout button to it when our function is called. This ref would recognize
 * a Toolbar like this:
 *
 *     Ext.create('Ext.Toolbar', {
 *         id: 'mainNav',
 *
 *         items: [
 *             {
 *                 text: 'Some Button'
 *             }
 *         ]
 *     });
 *
 * Assuming this Toolbar has already been created by the time we run our 'addLogoutButton' function (we'll see how that
 * is invoked later), it will get the second button added to it.
 *
 * ### Advanced Refs
 *
 * Refs can also be passed a couple of additional options, beyond name and selector. These are autoCreate and xtype,
 * which are almost always used together:
 *
 *     Ext.define('MyApp.controller.Main', {
 *         extend: 'Ext.app.Controller',
 *
 *         config: {
 *             refs: {
 *                 nav: '#mainNav',
 *
 *                 infoPanel: {
 *                     selector: 'tabpanel panel[name=fish] infopanel',
 *                     xtype: 'infopanel',
 *                     autoCreate: true
 *                 }
 *             }
 *         }
 *     });
 *
 * We've added a second ref to our Controller. Again the name is the key, 'infoPanel' in this case, but this time we've
 * passed an object as the value instead. This time we've used a slightly more complex selector query - in this example
 * imagine that your app contains a {@link Ext.tab.Panel tab panel} and that one of the items in the tab panel has been
 * given the name 'fish'. Our selector matches any Component with the xtype 'infopanel' inside that tab panel item.
 *
 * The difference here is that if that infopanel does not exist already inside the 'fish' panel, it will be
 * automatically created when you call this.getInfoPanel inside your Controller. The Controller is able to do this
 * because we provided the xtype to instantiate with in the event that the selector did not return anything.
 *
 * ### Control
 *
 * The sister config to {@link #refs} is {@link #cfg-control}. {@link #cfg-control Control} is the means by which your listen
 * to events fired by Components and have your Controller react in some way. Control accepts both ComponentQuery
 * selectors and refs as its keys, and listener objects as values - for example:
 *
 *     Ext.define('MyApp.controller.Main', {
 *         extend: 'Ext.app.Controller',
 *
 *         config: {
 *             control: {
 *                 loginButton: {
 *                     tap: 'doLogin'
 *                 },
 *                 'button[action=logout]': {
 *                     tap: 'doLogout'
 *                 }
 *             },
 *
 *             refs: {
 *                 loginButton: 'button[action=login]'
 *             }
 *         },
 *
 *         doLogin: function() {
 *             //called whenever the Login button is tapped
 *         },
 *
 *         doLogout: function() {
 *             //called whenever any Button with action=logout is tapped
 *         }
 *     });
 *
 * Here we have set up two control declarations - one for our loginButton ref and the other for any Button on the page
 * that has been given the action 'logout'. For each declaration we passed in a single event handler - in each case
 * listening for the 'tap' event, specifying the action that should be called when that Button fires the tap event.
 * Note that we specified the 'doLogin' and 'doLogout' methods as strings inside the control block - this is important.
 *
 * You can listen to as many events as you like in each control declaration, and mix and match ComponentQuery selectors
 * and refs as the keys.
 *
 * ## Routes
 *
 * As of Sencha Touch 2, Controllers can now directly specify which routes they are interested in. This enables us to
 * provide history support within our app, as well as the ability to deeply link to any part of the application that we
 * provide a route for.
 *
 * For example, let's say we have a Controller responsible for logging in and viewing user profiles, and want to make
 * those screens accessible via urls. We could achieve that like this:
 *
 *     Ext.define('MyApp.controller.Users', {
 *         extend: 'Ext.app.Controller',
 *
 *         config: {
 *             routes: {
 *                 'login': 'showLogin',
 *                 'user/:id': 'showUserById'
 *             },
 *
 *             refs: {
 *                 main: '#mainTabPanel'
 *             }
 *         },
 *
 *         //uses our 'main' ref above to add a loginpanel to our main TabPanel (note that
 *         //'loginpanel' is a custom xtype created for this application)
 *         showLogin: function() {
 *             this.getMain().add({
 *                 xtype: 'loginpanel'
 *             });
 *         },
 *
 *         //Loads the User then adds a 'userprofile' view to the main TabPanel
 *         showUserById: function(id) {
 *             MyApp.model.User.load(id, {
 *                 scope: this,
 *                 success: function(user) {
 *                     this.getMain().add({
 *                         xtype: 'userprofile',
 *                         user: user
 *                     });
 *                 }
 *             });
 *         }
 *     });
 *
 * The routes we specified above simply map the contents of the browser address bar to a Controller function to call
 * when that route is matched. The routes can be simple text like the login route, which matches against
 * http://myapp.com/#login, or contain wildcards like the 'user/:id' route, which matches urls like
 * http://myapp.com/#user/123. Whenever the address changes the Controller automatically calls the function specified.
 *
 * Note that in the showUserById function we had to first load the User instance. Whenever you use a route, the
 * function that is called by that route is completely responsible for loading its data and restoring state. This is
 * because your user could either send that url to another person or simply refresh the page, which we wipe clear any
 * cached data you had already loaded. There is a more thorough discussion of restoring state with routes in the
 * application architecture guides.
 *
 * ## Advanced Usage
 *
 * See [the Controllers guide](#!/guide/controllers) for advanced Controller usage including before filters
 * and customizing for different devices.
 */
Ext.define('Ext.app.Controller', {
    mixins: {
        observable: "Ext.mixin.Observable"
    },

    config: {
        /**
         * @cfg {Object} refs A collection of named {@link Ext.ComponentQuery ComponentQuery} selectors that makes it
         * easy to get references to key Components on your page. Example usage:
         *
         *     refs: {
         *         main: '#mainTabPanel',
         *         loginButton: '#loginWindow button[action=login]',
         *
         *         infoPanel: {
         *             selector: 'infopanel',
         *             xtype: 'infopanel',
         *             autoCreate: true
         *         }
         *     }
         *
         * The first two are simple ComponentQuery selectors, the third (infoPanel) also passes in the autoCreate and
         * xtype options, which will first run the ComponentQuery to see if a Component matching that selector exists
         * on the page. If not, it will automatically create one using the xtype provided:
         *
         *     someControllerFunction: function() {
         *         //if the info panel didn't exist before, calling its getter will instantiate
         *         //it automatically and return the new instance
         *         this.getInfoPanel().show();
         *     }
         *
         * @accessor
         */
        refs: {},

        /**
         * @cfg {Object} routes Provides a mapping of urls to Controller actions. Whenever the specified url is matched
         * in the address bar, the specified Controller action is called. Example usage:
         *
         *     routes: {
         *         'login': 'showLogin',
         *         'users/:id': 'showUserById'
         *     }
         *
         * The first route will match against http://myapp.com/#login and call the Controller's showLogin function. The
         * second route contains a wildcard (':id') and will match all urls like http://myapp.com/#users/123, calling
         * the showUserById function with the matched ID as the first argument.
         *
         * @accessor
         */
        routes: {},

        /**
         * @cfg {Object} control Provides a mapping of Controller functions that should be called whenever certain
         * Component events are fired. The Components can be specified using {@link Ext.ComponentQuery ComponentQuery}
         * selectors or {@link #refs}. Example usage:
         *
         *     control: {
         *         'button[action=logout]': {
         *             tap: 'doLogout'
         *         },
         *         main: {
         *             activeitemchange: 'doUpdate'
         *         }
         *     }
         *
         * The first item uses a ComponentQuery selector to run the Controller's doLogout function whenever any Button
         * with action=logout is tapped on. The second calls the Controller's doUpdate function whenever the
         * activeitemchange event is fired by the Component referenced by our 'main' ref. In this case main is a tab
         * panel (see {@link #refs} for how to set that reference up).
         *
         * @accessor
         */
        control: {},

        /**
         * @cfg {Object} before Provides a mapping of Controller functions to filter functions that are run before them
         * when dispatched to from a route. These are usually used to run pre-processing functions like authentication
         * before a certain function is executed. They are only called when dispatching from a route. Example usage:
         *
         *     Ext.define('MyApp.controller.Products', {
         *         config: {
         *             before: {
         *                 editProduct: 'authenticate'
         *             },
         *
         *             routes: {
         *                 'product/edit/:id': 'editProduct'
         *             }
         *         },
         *
         *         //this is not directly because our before filter is called first
         *         editProduct: function() {
         *             //... performs the product editing logic
         *         },
         *
         *         //this is run before editProduct
         *         authenticate: function(action) {
         *             MyApp.authenticate({
         *                 success: function() {
         *                     action.resume();
         *                 },
         *                 failure: function() {
         *                     Ext.Msg.alert('Not Logged In', "You can't do that, you're not logged in");
         *                 }
         *             });
         *         }
         *     });
         *
         * @accessor
         */
        before: {},

        /**
         * @cfg {Ext.app.Application} application The Application instance this Controller is attached to. This is
         * automatically provided when using the MVC architecture so should rarely need to be set directly.
         * @accessor
         */
        application: {},

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

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

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

    /**
     * Constructs a new Controller instance
     */
    constructor: function(config) {
        this.initConfig(config);

        this.mixins.observable.constructor.call(this, config);
    },

    /**
     * @cfg
     * Called by the Controller's {@link #application} to initialize the Controller. This is always called before the
     * {@link Ext.app.Application Application} launches, giving the Controller a chance to run any pre-launch logic.
     * See also {@link #launch}, which is called after the {@link Ext.app.Application#launch Application's launch function}
     */
    init: Ext.emptyFn,

    /**
     * @cfg
     * Called by the Controller's {@link #application} immediately after the Application's own
     * {@link Ext.app.Application#launch launch function} has been called. This is usually a good place to run any
     * logic that has to run after the app UI is initialized. See also {@link #init}, which is called before the
     * {@link Ext.app.Application#launch Application's launch function}.
     */
    launch: Ext.emptyFn,

    /**
     * Convenient way to redirect to a new url. See {@link Ext.app.Application#redirectTo} for full usage information.
     * @return {Object}
     */
    redirectTo: function(place) {
        return this.getApplication().redirectTo(place);
    },

    /**
     * @private
     * Executes an Ext.app.Action by giving it the correct before filters and kicking off execution
     */
    execute: function(action, skipFilters) {
        action.setBeforeFilters(this.getBefore()[action.getAction()]);
        action.execute();
    },

    /**
     * @private
     * Massages the before filters into an array of function references for each controller action
     */
    applyBefore: function(before) {
        var filters, name, length, i;

        for (name in before) {
            filters = Ext.Array.from(before[name]);
            length  = filters.length;

            for (i = 0; i < length; i++) {
                filters[i] = this[filters[i]];
            }

            before[name] = filters;
        }

        return before;
    },

    /**
     * @private
     */
    applyControl: function(config) {
        this.control(config, this);

        return config;
    },

    /**
     * @private
     */
    applyRefs: function(refs) {
        //<debug>
        if (Ext.isArray(refs)) {
            Ext.Logger.deprecate("In Sencha Touch 2 the refs config accepts an object but you have passed it an array.");
        }
        //</debug>

        this.ref(refs);

        return refs;
    },

    /**
     * @private
     * Adds any routes specified in this Controller to the global Application router
     */
    applyRoutes: function(routes) {
        var app    = this instanceof Ext.app.Application ? this : this.getApplication(),
            router = app.getRouter(),
            route, url, config;

        for (url in routes) {
            route = routes[url];

            config = {
                controller: this.$className
            };

            if (Ext.isString(route)) {
                config.action = route;
            } else {
                Ext.apply(config, route);
            }

            router.connect(url, config);
        }

        return routes;
    },

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

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

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

    /**
     * @private
     * Returns the fully qualified name for any class name variant. This is used to find the FQ name for the model,
     * view, controller, store and profiles listed in a Controller or Application.
     * @param {String[]} items The array of strings to get the FQ name for
     * @param {String} namespace If the name happens to be an application class, add it to this namespace
     * @return {String} The fully-qualified name of the class
     */
    getFullyQualified: function(items, namespace) {
        var length  = items.length,
            appName = this.getApplication().getName(),
            name, i;

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

            //we check name === appName to allow MyApp.profile.MyApp to exist
            if (Ext.isString(name) && (Ext.Loader.getPrefix(name) === "" || name === appName)) {
                items[i] = appName + '.' + namespace + '.' + name;
            }
        }

        return items;
    },

    /**
     * @private
     */
    control: function(selectors) {
        this.getApplication().control(selectors, this);
    },

    /**
     * @private
     * 1.x-inspired ref implementation
     */
    ref: function(refs) {
        var me = this,
            refName, getterName, selector, info;

        for (refName in refs) {
            selector = refs[refName];
            getterName = "get" + Ext.String.capitalize(refName);

            if (!this[getterName]) {
                if (Ext.isString(refs[refName])) {
                    info = {
                        ref: refName,
                        selector: selector
                    };
                } else {
                    info = refs[refName];
                }

                this[getterName] = function(refName, info) {
                    var args = [refName, info];
                    return function() {
                        return me.getRef.apply(me, args.concat.apply(args, arguments));
                    };
                }(refName, info);
            }

            this.references = this.references || [];
            this.references.push(refName.toLowerCase());
        }
    },

    /**
     * @private
     */
    getRef: function(ref, info, config) {
        this.refCache = this.refCache || {};
        info = info || {};
        config = config || {};

        Ext.apply(info, config);

        if (info.forceCreate) {
            return Ext.ComponentManager.create(info, 'component');
        }

        var me = this,
            cached = me.refCache[ref];

        if (!cached) {
            me.refCache[ref] = cached = Ext.ComponentQuery.query(info.selector)[0];
            if (!cached && info.autoCreate) {
                me.refCache[ref] = cached = Ext.ComponentManager.create(info, 'component');
            }
            if (cached) {
                cached.on('destroy', function() {
                    me.refCache[ref] = null;
                });
            }
        }

        return cached;
    },

    /**
     * @private
     */
    hasRef: function(ref) {
        return this.references && this.references.indexOf(ref.toLowerCase()) !== -1;
    }

    // <deprecated product=touch since=2.0>
    ,onClassExtended: function(cls, members) {
        var prototype = this.prototype,
            defaultConfig = prototype.config,
            config = members.config || {},
            arrayRefs = members.refs,
            objectRefs = {},
            stores = members.stores,
            views = members.views,
            format = Ext.String.format,
            refItem, key, length, i, functionName;

        // Convert deprecated properties in application into a config object
        for (key in defaultConfig) {
            if (key in members && key != "control") {
                if (key == "refs") {
                    //we need to convert refs from the 1.x array-style to 2.x object-style
                    for (i = 0; i < arrayRefs.length; i++) {
                        refItem = arrayRefs[i];

                        objectRefs[refItem.ref] = refItem;
                    }

                    config.refs = objectRefs;
                } else {
                    config[key] = members[key];
                }

                delete members[key];
                // <debug warn>
                Ext.Logger.deprecate(key + ' is deprecated as a property directly on the ' + this.$className + ' prototype. Please put it inside the config object.');
                // </debug>
            }
        }

        if (stores) {
            length = stores.length;
            config.stores = stores;
            for (i = 0; i < length; i++) {
                functionName = format("get{0}Store", Ext.String.capitalize(stores[i]));

                prototype[functionName] = function(name) {
                    return function() {
                        return Ext.StoreManager.lookup(name);
                    };
                }(stores[i]);
            }
        }

        if (views) {
            length = views.length;
            config.views = views;
            for (i = 0; i < length; i++) {
                functionName = format("get{0}View", views[i]);

                prototype[functionName] = function(name) {
                    return function() {
                        return Ext.ClassManager.classes[format("{0}.view.{1}", this.getApplication().getName(), name)];
                    };
                }(views[i]);
            }
        }

        members.config = config;
    },

    /**
     * Returns a reference to a Model.
     * @param {String} modelName
     * @return {Object}
     * @deprecated 2.0.0 Considered bad practice - please just use the Model name instead
     * (e.g. `MyApp.model.User` vs `this.getModel('User')`).
     */
    getModel: function(modelName) {
        //<debug warn>
        Ext.Logger.deprecate("getModel() is deprecated and considered bad practice - please just use the Model " +
            "name instead (e.g. MyApp.model.User vs this.getModel('User'))");
        //</debug>

        var appName = this.getApplication().getName(),
            classes = Ext.ClassManager.classes;

        return classes[appName + '.model.' + modelName];
    },

    /**
     * Returns a reference to another Controller.
     * @param {String} controllerName
     * @param {String} profile
     * @return {Object}
     * @deprecated 2.0.0 Considered bad practice - if you need to do this
     * please use this.getApplication().getController() instead
     */
    getController: function(controllerName, profile) {
        //<debug warn>
        Ext.Logger.deprecate("Ext.app.Controller#getController is deprecated and considered bad practice - " +
            "please use this.getApplication().getController('someController') instead");
        //</debug>

        return this.getApplication().getController(controllerName, profile);
    }
    // </deprecated>
}, function() {
    // <deprecated product=touch since=2.0>
    Ext.regController = function(name, config) {
        Ext.apply(config, {
            extend: 'Ext.app.Controller'
        });

        Ext.Logger.deprecate(
            '[Ext.app.Controller] Ext.regController is deprecated, please use Ext.define to define a Controller as ' +
            'with any other class. For more information see the Touch 1.x -> 2.x migration guide'
        );
        Ext.define('controller.' + name, config);
    };
    // </deprecated>
});
