/**
 * The Router is an ordered set of {@link Ext.app.route.Route} definitions that decode a
 * url into a controller function to execute. Each `route` defines a type of url to match,
 * along with the controller function to call if it is matched. The Router uses the
 * {@link Ext.util.History} singleton to find out when the browser's url has changed.
 *
 * Routes are almost always defined inside a {@link Ext.app.Controller Controller}, as
 * opposed to on the Router itself. End-developers should not usually need to interact
 * directly with the Router as the Controllers manage everything automatically. See the
 * {@link Ext.app.Controller Controller documentation} for more information on specifying
 * routes.
 *
 * @private
 */
Ext.define('Ext.app.route.Router', {
    singleton : true,

    requires : [
        'Ext.app.route.Queue',
        'Ext.app.route.Route',
        'Ext.util.History'
    ],

    /**
     * @property {String} [multipleToken=|] The token to split the routes to support multiple routes.
     */
    multipleToken: '|',

    /**
     * @property {Boolean} queueRoutes True to queue routes to be executed one after the
     * other, false to execute routes immediately.
     */
    queueRoutes: true,

    /**
     * @property {Ext.app.route.Route[]} routes The connected {@link Ext.app.route.Route}
     * instances.
     */

    constructor : function () {
        var History = Ext.util.History;

        if (!History.ready) {
            History.init();
        }

        History.on('change', this.onStateChange, this);
        this.clear();
    },

    /**
     * React to a token
     *
     * @private
     * @param {String} token The token to react to.
     */
    onStateChange : function (token) {
        var me          = this,
            app         = me.application,
            routes      = me.routes,
            len         = routes.length,
            queueRoutes = me.queueRoutes,
            tokens      = token.split(me.multipleToken),
            t           = 0,
            length      = tokens.length,
            i, queue, route, args, matched;

        for (; t < length; t++) {
            token = tokens[t];
            matched = false;

            if (queueRoutes) {
                //create a queue
                queue = new Ext.app.route.Queue({
                    token : token
                });
            }

            for (i = 0; i < len; i++) {
                route = routes[i];
                args  = route.recognize(token);

                if (args) {
                    matched = true;
                    if (queueRoutes) {
                        queue.queueAction(route, args);
                    } else {
                        route.execute(token, args);
                    }
                }
            }

            if (queueRoutes) {
                //run the queue
                queue.runQueue();
            }
            
            if (!matched && app) {
                app.fireEvent('unmatchedroute', token);
            }
        }
    },

    /**
     * Create the {@link Ext.app.route.Route} instance and connect to the
     * {@link Ext.app.route.Router} singleton.
     *
     * @param {String} url The url to recognize.
     * @param {String} action The action on the controller to execute when the url is
     * matched.
     * @param {Ext.app.Controller} controller The controller associated with the
     * {@link Ext.app.route.Route}
     */
    connect : function (url, action, controller) {
        var config = {
                url        : url,
                action     : action,
                controller : controller
            };

        if (Ext.isObject(action)) {
            Ext.merge(config, action);
        }
        this.routes.push(new Ext.app.route.Route(config));
    },
    
    /**
     * Disconnects all routes for a controller.
     * @param {Ext.app.Controller} controller The controller to disconnect routes from.
     */
    disconnectAll: function(controller) {
        var routes = this.routes,
            len = routes.length,
            newRoutes = [],
            i, route;
    
        for (i = 0; i < len; ++i) {
            route = routes[i];
            if (route.controller !== controller) {
                newRoutes.push(route);
            }
        }
        this.routes = newRoutes;
    },

    /**
     * Recognizes a url string connected to the Router, return the controller/action pair
     * plus any additional config associated with it.
     *
     * @param {String} url The url to recognize.
     * @return {Object/Boolean} If the url was recognized, the controller and action to
     * call, else `false`.
     */
    recognize : function(url) {
        var routes = this.routes || [],
            i      = 0,
            len    = routes.length,
            route, args;

        for (; i < len; i++) {
            route = routes[i];
            args  = route.recognize(url);

            if (args) {
                //route is recognized, return it and the arguments recognized if any
                return {
                    route : route,
                    args  : args
                };
            }
        }

        return false;
    },

    /**
     * Convenience method which just calls the supplied function with the
     * {@link Ext.app.route.Router} singleton. Example usage:
     *
     *     Ext.app.route.Router.draw(function(map) {
     *         map.connect('activate/:token', {controller: 'users', action: 'activate'});
     *         map.connect('home',            {controller: 'index', action: 'home'});
     *     });
     *
     * @param {Function} fn The function to call
     */
    draw : function(fn) {
        fn.call(this, this);
    },

    /**
     * Clear all the recognized routes.
     */
    clear : function() {
        this.routes = [];
    }
});
