/**
 * @protected
 * @class Ext.app.BaseController
 * Base class for Controllers.
 * 
 */
Ext.define('Ext.app.BaseController', {
    requires: [
        'Ext.app.EventBus',
        'Ext.app.domain.Global'
    ],
    
    uses: [
        'Ext.app.domain.Controller'
    ],

    mixins: ['Ext.mixin.Observable'],

    isController: true,

    config : {
        /**
         * @cfg {String} id The id of this controller. You can use this id when dispatching.
         * 
         * For an example of dispatching, see the examples under the 
         * {@link Ext.app.Controller#cfg-listen listen} config.
         *
         * If an id is not explicitly set, it will default to the controller's full classname.
         * 
         * @accessor
         */
        id: null,
       
        /**
         * @cfg {Object} control
         * @accessor
         *
         * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
         * object containing component paths mapped to a hash of listener functions.  
         * The function value may also be a string matching the name of a method on the 
         * controller.
         *
         * In the following example the `updateUser` function is mapped to to the `click`
         * event on a button component, which is a child of the `useredit` component.
         *
         *      Ext.define('MyApp.controller.Users', {
         *          extend: 'Ext.app.Controller',
         *
         *          control: {
         *              'useredit button[action=save]': {
         *                  click: 'updateUser'
         *              }
         *          },
         *
         *          updateUser: function(button) {
         *              console.log('clicked the Save button');
         *          }
         *      });
         *
         * The method you pass to the listener will automatically be resolved on the controller.
         * In this case, the `updateUser` method that will get executed on the `button` `click`
         * event will resolve to the `updateUser` method on the controller,
         *
         * See {@link Ext.ComponentQuery} for more information on component selectors.
         */

        control: null,

        /**
         * @cfg {Object} listen
         * @accessor
         *
         * Adds listeners to different event sources (also called "event domains"). The
         * primary event domain is that of components, but there are also other event domains:
         * {@link Ext.app.domain.Global Global} domain that intercepts events fired from
         * {@link Ext.GlobalEvents} Observable instance, 
         * {@link Ext.app.domain.Controller Controller} domain can be used to listen to events 
         * fired by other Controllers, {@link Ext.app.domain.Store Store} domain gives access to 
         * Store events, and {@link Ext.app.domain.Direct Direct} domain can be used with 
         * Ext.Direct Providers to listen to their events.
         *
         * To listen to "bar" events fired by a controller with id="foo":
         *
         *      Ext.define('AM.controller.Users', {
         *          extend: 'Ext.app.Controller',
         *
         *          listen: {
         *              controller: {
         *                  '#foo': {
         *                      bar: 'onFooBar'
         *                  }
         *              }
         *          }
         *      });
         *
         * To listen to "bar" events fired by any controller, and "baz" events
         * fired by Store with storeId="baz":
         *
         *      Ext.define('AM.controller.Users', {
         *          extend: 'Ext.app.Controller',
         *
         *          listen: {
         *              controller: {
         *                  '*': {
         *                      bar: 'onAnyControllerBar'
         *                  }
         *              },
         *              store: {
         *                  '#baz': {
         *                      baz: 'onStoreBaz'
         *                  }
         *              }
         *          }
         *      });
         *
         * To listen to "idle" events fired by {@link Ext.GlobalEvents} when other event
         * processing is complete and Ext JS is about to return control to the browser:
         *
         *      Ext.define('AM.controller.Users', {
         *          extend: 'Ext.app.Controller',
         *
         *          listen: {
         *              global: {            // Global events are always fired
         *                  idle: 'onIdle'   // from the same object, so there
         *              }                    // are no selectors
         *          }
         *      });
         *
         * As this relates to components, the following example:
         *
         *      Ext.define('AM.controller.Users', {
         *          extend: 'Ext.app.Controller',
         *
         *          listen: {
         *              component: {
         *                  'useredit button[action=save]': {
         *                      click: 'updateUser'
         *                  }
         *              }
         *          }
         *      });
         *
         * Is equivalent to:
         *
         *      Ext.define('AM.controller.Users', {
         *          extend: 'Ext.app.Controller',
         *
         *          control: {
         *              'useredit button[action=save]': {
         *                  click: 'updateUser'
         *              }
         *          }
         *      });
         *
         * Of course, these can all be combined in a single call and used instead of
         * `control`, like so:
         *
         *      Ext.define('AM.controller.Users', {
         *          extend: 'Ext.app.Controller',
         *
         *          listen: {
         *              global: {
         *                  idle: 'onIdle'
         *              },
         *              controller: {
         *                  '*': {
         *                      foobar: 'onAnyFooBar'
         *                  },
         *                  '#foo': {
         *                      bar: 'onFooBar'
         *                  }
         *              },
         *              component: {
         *                  'useredit button[action=save]': {
         *                      click: 'updateUser'
         *                  }
         *              },
         *              store: {
         *                  '#qux': {
         *                      load: 'onQuxLoad'
         *                  }
         *              }
         *          }
         *      });
         */
        listen: null,

        /**
         * @cfg {Object} routes
         * @accessor
         *
         * An object of routes to handle hash changes. A route can be defined in a simple way:
         *
         *     routes : {
         *         'foo/bar'  : 'handleFoo',
         *         'user/:id' : 'showUser'
         *     }
         *
         * Where the property is the hash (which can accept a parameter defined by a colon) and the value
         * is the method on the controller to execute. The parameters will get sent in the action method.
         *
         * At the application level, you can define a event that will be executed when no matching
         * routes are found.
         *
         *     Ext.application({
         *         name: 'MyApp',
         *         listen: {
         *             controller: {
         *                 '#': {
         *                     unmatchedroute: 'onUnmatchedRoute'
         *                 }
         *             }
         *         },
         *
         *         onUnmatchedRoute: function(hash) {
         *             console.log('Unmatched', hash);
         *             // Do something...
         *         }
         *     });
         *
         * There is also a complex means of defining a route where you can use a before action and even
         * specify your own RegEx for the parameter:
         *
         *     routes : {
         *         'foo/bar'  : {
         *             action  : 'handleFoo',
         *             before  : 'beforeHandleFoo'
         *         },
         *         'user/:id' : {
         *             action     : 'showUser',
         *             before     : 'beforeShowUser',
         *             conditions : {
         *                 ':id' : '([0-9]+)'
         *             }
         *         }
         *     }
         *
         * This will only match if the `id` parameter is a number.
         *
         * The before action allows you to cancel an action. Every before action will get passed an `action` argument with
         * a `resume` and `stop` methods as the last argument of the method and you *MUST* execute either method:
         *
         *     beforeHandleFoo : function(action) {
         *         //some logic here
         *
         *         //this will allow the handleFoo action to be executed
         *         action.resume();
         *     },
         *     handleFoo : function() {
         *         //will get executed due to true being passed in callback in beforeHandleFoo
         *     },
         *     beforeShowUser : function(id, action) {
         *         //allows for async process like an Ajax
         *         Ext.Ajax.request({
         *             url     : 'foo.php',
         *             success : function() {
         *                 //will not allow the showUser method to be executed but will continue other queued actions.
         *                 action.stop();
         *             },
         *             failure : function() {
         *                 //will not allow the showUser method to be executed and will not allow other queued actions to be executed.
         *                 action.stop(true);
         *             }
         *         });
         *     },
         *     showUser : function(id) {
         *         //will not get executed due to false being passed in callback in beforeShowUser
         *     }
         *
         * You *MUST* execute the `resume` or `stop` method on the `action` argument. Executing `action.resume();` will continue
         * the action, `action.stop();` will not allow the action to resume but will allow other queued actions to resume,
         * `action.stop(true);` will not allow the action and any other queued actions to resume.
         *
         * The default RegEx that will be used is `([%a-zA-Z0-9\\-\\_\\s,]+)` but you can specify any
         * that may suit what you need to accomplish. An example of an advanced condition may be to make
         * a parameter optional and case-insensitive:
         *
         *     routes : {
         *         'user:id' : {
         *             action     : 'showUser',
         *             before     : 'beforeShowUser',
         *             conditions : {
         *                 ':id' : '(?:(?:\/){1}([%a-z0-9_,\s\-]+))?'
         *             }
         *         }
         *     }
         */
        routes : null,
        before : null
    },

    /**
     * Creates new Controller.
     *
     * @param {Object} [config] Configuration object.
     */
    constructor: function(config) {
        var me = this;

        // In versions prior to 5.1, this constructor used to call the Ext.util.Observable
        // constructor (which applied the config properties directly to the instance)
        // AND it used to call initConfig as well.  Since the constructor of
        // Ext.mixin.Observable calls initConfig, but does not apply the properties to
        // the instance, we do that here for backward compatibility.
        Ext.apply(me, config);
        // The control and listen properties are also methods so we need to delete them
        // from the instance after applying the config object.
        delete me.control;
        delete me.listen;

        me.eventbus = Ext.app.EventBus;

        //need to have eventbus property set before we initialize the config
        me.mixins.observable.constructor.call(me, config);
        // Assuming we haven't set this in updateControl or updateListen, force it here
        me.ensureId();
    },

    applyListen: function(listen) {
        if (Ext.isObject(listen)) {
            listen = Ext.clone(listen);
        }

        return listen;
    },

    applyControl: function(control) {
        if (Ext.isObject(control)) {
            control = Ext.clone(control);
        }

        return control;
    },

    /**
     * @param {Object} control The object to pass to the {@link #method-control} method
     * @private
     */
    updateControl: function(control) {
        this.ensureId();
        if (control) {
            this.control(control);
        }
    },

    /**
     * @param {Object} listen The object to pass to the {@link #method-listen} method
     * @private
     */
    updateListen: function(listen) {
        this.ensureId();
        if (listen) {
            this.listen(listen);
        }
    },

    /**
     * @param {Object} routes The routes to connect to the {@link Ext.app.route.Router}
     * @private
     */
    updateRoutes : function(routes) {
        if (routes) {
            var me = this,
                befores = me.getBefore() || {},
                Router = Ext.app.route.Router,
                url, config, method;

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

                if (Ext.isString(config)) {
                    config = {
                        action : config
                    };
                }

                method = config.action;

                if (!config.before) {
                    config.before = befores[method];
                }
                //<debug>
                else if (befores[method]) {
                    Ext.log.warn('You have a before method configured on a route ("' + url + '") and in the before object property also in the "' +
                        me.self.getName() + '" controller. Will use the before method in the route and disregard the one in the before property.');
                }
                //</debug>

                //connect the route config to the Router
                Router.connect(url, config, me);
            }
        }
    },

    isActive: function() {
        return true;
    },

    /**
     * Adds listeners to components selected via {@link Ext.ComponentQuery}. Accepts an
     * object containing component paths mapped to a hash of listener functions.
     *
     * In the following example the `updateUser` function is mapped to to the `click`
     * event on a button component, which is a child of the `useredit` component.
     *
     *      Ext.define('AM.controller.Users', {
     *          init: function() {
     *              this.control({
     *                  'useredit button[action=save]': {
     *                      click: this.updateUser
     *                  }
     *              });
     *          },
     *          
     *          updateUser: function(button) {
     *              console.log('clicked the Save button');
     *          }
     *      });
     *
     * Or alternatively one call `control` with two arguments:
     *
     *      this.control('useredit button[action=save]', {
     *          click: this.updateUser
     *      });
     *
     * See {@link Ext.ComponentQuery} for more information on component selectors.
     *
     * @param {String/Object} selectors If a String, the second argument is used as the
     * listeners, otherwise an object of selectors -> listeners is assumed
     * @param {Object} [listeners] Config for listeners.
     */
    control: function(selectors, listeners, controller) {
        var me = this,
            ctrl = controller,
            obj;

        if (Ext.isString(selectors)) {
            obj = {};
            obj[selectors] = listeners;
        }
        else {
            obj = selectors;
            ctrl = listeners;
        }

        me.eventbus.control(obj, ctrl || me);
    },

    /**
     * Adds listeners to different event sources (also called "event domains"). The
     * primary event domain is that of components, but there are also other event domains:
     * {@link Ext.app.domain.Global Global} domain that intercepts events fired from
     * {@link Ext.GlobalEvents} Observable instance, {@link Ext.app.domain.Controller Controller}
     * domain can be used to listen to events fired by other Controllers,
     * {@link Ext.app.domain.Store Store} domain gives access to Store events, and
     * {@link Ext.app.domain.Direct Direct} domain can be used with Ext.Direct Providers
     * to listen to their events.
     * 
     * To listen to "bar" events fired by a controller with id="foo":
     *
     *      Ext.define('AM.controller.Users', {
     *          init: function() {
     *              this.listen({
     *                  controller: {
     *                      '#foo': {
     *                         bar: this.onFooBar
     *                      }
     *                  }
     *              });
     *          },
     *          ...
     *      });
     * 
     * To listen to "bar" events fired by any controller, and "baz" events
     * fired by Store with storeId="baz":
     *
     *      Ext.define('AM.controller.Users', {
     *          init: function() {
     *              this.listen({
     *                  controller: {
     *                      '*': {
     *                         bar: this.onAnyControllerBar
     *                      }
     *                  },
     *                  store: {
     *                      '#baz': {
     *                          baz: this.onStoreBaz
     *                      }
     *                  }
     *              });
     *          },
     *          ...
     *      });
     *
     * To listen to "idle" events fired by {@link Ext.GlobalEvents} when other event
     * processing is complete and Ext JS is about to return control to the browser:
     *
     *      Ext.define('AM.controller.Users', {
     *          init: function() {
     *              this.listen({
     *                  global: {               // Global events are always fired
     *                      idle: this.onIdle   // from the same object, so there
     *                  }                       // are no selectors
     *              });
     *          }
     *      });
     * 
     * As this relates to components, the following example:
     *
     *      Ext.define('AM.controller.Users', {
     *          init: function() {
     *              this.listen({
     *                  component: {
     *                      'useredit button[action=save]': {
     *                         click: this.updateUser
     *                      }
     *                  }
     *              });
     *          },
     *          ...
     *      });
     * 
     * Is equivalent to:
     *
     *      Ext.define('AM.controller.Users', {
     *          init: function() {
     *              this.control({
     *                  'useredit button[action=save]': {
     *                     click: this.updateUser
     *                  }
     *              });
     *          },
     *          ...
     *      });
     *
     * Of course, these can all be combined in a single call and used instead of
     * `control`, like so:
     *
     *      Ext.define('AM.controller.Users', {
     *          init: function() {
     *              this.listen({
     *                  global: {
     *                      idle: this.onIdle
     *                  },
     *                  controller: {
     *                      '*': {
     *                         foobar: this.onAnyFooBar
     *                      },
     *                      '#foo': {
     *                         bar: this.onFooBar
     *                      }
     *                  },
     *                  component: {
     *                      'useredit button[action=save]': {
     *                         click: this.updateUser
     *                      }
     *                  },
     *                  store: {
     *                      '#qux': {
     *                          load: this.onQuxLoad
     *                      }
     *                  }
     *              });
     *          },
     *          ...
     *      });
     *
     * @param {Object} to Config object containing domains, selectors and listeners.
     * @param {Ext.app.Controller} [controller] The controller to add the listeners to. Defaults to the current controller.
     */
    listen: function (to, controller) {
        this.eventbus.listen(to, controller || this);
    },
    
    destroy: function() {
        var me = this,
            bus = me.eventbus;

        Ext.app.route.Router.disconnectAll(me);

        if (bus) {
            bus.unlisten(me);
            me.eventbus = null;
        }
        me.clearListeners();
        me.callParent();
    },

    /**
     * Update the hash. By default, it will not execute the routes if the current token and the
     * token passed are the same.
     * 
     * @param {String/Ext.data.Model} token The token to redirect to.  Can be either a String
     * or a {@link Ext.data.Model Model} instance - if a Model instance is passed it will
     * internally be converted into a String token by calling the Model's
     * {@link Ext.data.Model#toUrl toUrl} function.
     *
     * @param {Boolean} force Force the update of the hash regardless of the current token.
     * 
     * @return {Boolean} Will return `true` if the token was updated.
     */
    redirectTo: function(token, force) {
        if (token.isModel) {
            token = token.toUrl();
        }
        if (!force) {
            var currentToken = Ext.util.History.getToken();

            if (currentToken === token) {
                return false;
            }
        } else {
            Ext.app.route.Router.onStateChange(token);
        }

        Ext.util.History.add(token);

        return true;
    }
});
