$.extend({
    router: {
        routes: {
            list: [],
            current: null,

            add: function (route, level, callback, context) {
                var r, keys, i;
                if (typeof this.list[level] === 'undefined') {
                    this.list[level] = [];
                }
                r = {
                    'route': route,
                    'level': level,
                    'callback': function (params) {
                        if (callback !== undefined) {
                            if (context === undefined) {
                                callback(params);
                            } else {
                                callback.call(context, params);
                            }
                        }
                    },
                },
                    i = this.list[level].length;
                if (this.exist(r) === false) {
                    this.list[level][i] = r;
                }
            },

            exist: function (r) {
                var found = false, i = 0;
                while (i < this.list[r.level].length && found === false) {
                    if (this.list[r.level][i].route === r.route) {
                        found = true;
                    }
                    i += 1;
                }
                return found;
            },

            clean: function (level) {
                this.list = this.list.slice(0, level);
            },

            cleanAll: function () {
                this.list = this.list.slice(0, 0);
            },

            search: function (hash, level) {
                var stop = false,
                    i, j,
                    regex,
                    result,
                    extracted;
                level = level || 0;
                i = this.list.length - 1;
                hash.route = hash.route === "undefined" ? "/" : hash.route;
                while ((stop  === false) && (i >= level)) {
                    j = 0;
                    while ((stop === false) && (j < this.list[i].length)) {
                        extracted = $.router.extractKeys(this.list[i][j].route);
                        regex = new RegExp(extracted.regex);
                        if (regex.test(hash.route)) {
                            result = regex.exec(hash.route);
                            stop = true;
                            result.shift();
                            for (var k = 0; k < result.length; k += 1) {
                                hash[extracted.keys[k]] = result[k];
                            }
                            this.current = this.list[i][j];
                            this.clean(this.list[i][j].level + 1);
                            this.list[i][j].callback(hash);
                        }
                        j += 1;
                    }
                    i -= 1;
                }
            },

            isLastLevel: function () {
                return this.current.level === (this.list.length - 1);
            },

            isCurrent: function (hash) {
                var extracted = $.router.extractKeys(this.current.route),
                    regex = new RegExp('^' + extracted.regex + '$');
                return regex.test(hash);
            }
        },

        start: function (hash, level) {
            var hashInfo = this.parseHash(hash);
            if ($.router.routes.current.route !== hashInfo.route) {
                this.routes.search(hashInfo, level);
            }
        },

        redirect: function (hash) {
            var hashInfo = this.parseHash(hash);
            this.routes.search(hashInfo);
        },

        extractKeys: function (regex) {
            var re_key = new RegExp(/:(\w+)/),
                keys = [],
                result;
            while (re_key.test(regex)) {
                result = re_key.exec(regex);
                keys.push(result[1]);
                regex = regex.replace(result[0], '([^\/]+)');
            }
            return {'regex': regex, 'keys': keys}
        },

        deserialize: function (params) {
            var result = {},
                p,
                params = params.split('&');
            while (params.length) {
                p = params.shift().split('=');
                if (p[0] !== '') {
                    if (p.length === 2) {
                        result[p[0]] = p[1] === 'true' ? true : p[1];
                    } else {
                        result[p[0]] = true;
                    }
                }
            }
            return result;
        },

        serialize: function (obj) {
            return $.param(obj);
        },

        genHash: function (components) {
            return '#/' + components.join('/');
        },

        parseHash: function (hashTag) {
            var re = new RegExp(/(?:^#?([a-zA-Z0-9\/_-]+))(?:\?([A-Za-z0-9\/&=_-]+))?/g),
                groups = re.exec(hashTag),
                r, params = {};
            groups.shift();
            // route
            r = groups[0];
            // params
            if (groups[1] !== undefined) {
                params = this.deserialize(groups[1]);
            }
            return params.length === 0 ? {'route': r} : $.extend({'route': r}, params);
        },

        hashHandler: function () {
            var hashTag = window.location.href.split('#')[1],
                hashInfo = $.router.parseHash(hashTag);
            $.router.routes.search(hashInfo)
        },
    }
});

$(window).bind('hashchange', $.router.hashHandler);
$(window).bind('load', $.router.hashHandler);
