;(function() {
	
	/**
	 * The main router that is responsible for routing hash changes.
	 * 
	 * @var Jive
	 */
	function Jive() {
		return this;
	};
	
	Jive.prototype = {
		/**
		 * The interval binding.
		 * 
		 * @var Interval
		 */
	    binding: null,
		
		/**
		 * The last recorded hash.
		 * 
		 * @var String
		 */
		hash: null,
		
		/**
		 * The collection of routes applied to the router.
		 * 
		 * @var Object
		 */
		routes: [],
		
		/**
		 * Creates a new Jive instance.
		 * 
		 * @return Jive
		 */
		create: function() {
			return new Jive;
		},
		
		/**
		 * Binds the hash change monitor.
		 * 
		 * @param Integer interval The interval which is set to check for hash changes.
		 * 
		 * @return Jive
		 */
		bind: function(interval) {
			var self = this;
			this.binding = setInterval(function() { self.checkAndDispatch() }, interval || 100);
			return this;
		},
		
		/**
		 * Unbinds the hash change monitor.
		 * 
		 * @return Jive
		 */
		unbind: function() {
			clearInterval(this.binding);
			return this;
		},
		
		/**
		 * Sets a route.
		 * 
		 * @param String   name     The name of the route.
		 * @param RegExp   regex    The regular expression to match the request to.
		 * @param Function callback The callback to execute if the route matches the request.
		 * 
		 * @return Jive
		 */
		set: function(name, regex, callback) {
			// Handle different types of arguments.
			// If only one argument is given, it is assumed a catch-all route and will be executed if nothing else is
			// matched.
			// If two arguments are given, it is assumed a regular expression and a callback. The name will be
			// automatically generated from the length of the routes.
			if (typeof name === 'function') {
				callback = name;
				regex = /.*/;
				name = this.routes.length;
			} else if (name instanceof RegExp) {
				callback = regex;
				regex = name;
				name = this.routes.length;
			}
			this.routes[this.routes.length] = new Route(name, regex, callback);
			return this;
		},
		
		/**
		 * Returns the specified route.
		 * 
		 * @param String name The route name.
		 * 
		 * @throws Exception if the route does not exist.
		 * 
		 * @return Route
		 */
		get: function(name) {
			for (var i in this.routes) {
				if (name === this.routes[i].name) {
					return this.routes[i];
				}
			}
			throw 'Route "' + name.toString() + '" does not exist.';
		},
		
		/**
		 * Returns whether or not the specified route exists.
		 * 
		 * @param String name The name of the route.
		 * 
		 * @return bool
		 */
		has: function(name) {
			for (var i in this.routes) {
				if (name === this.routes[i].name) {
					return true;
				}
			}
			return false;
		},
		
		/**
		 * Removes the specified route.
		 * 
		 * @param String name The name of the route.
		 * 
		 * @return Jive
		 */
		remove: function(name) {
			var temp = [];
			for (var i in this.routes) {
				if (name !== this.routes[i].name) {
					temp[i] = this.routes[i];
				}
			}
			this.routes = temp;
			return this;
		},
		
		/**
		 * Checks the hash for any changes. Returns if it has changed.
		 * 
		 * @return bool
		 */
		check: function() {
			if (this.hash !== window.location.hash) {
				return true;
			}
			return false;
		},
		
		/**
		 * Updates the hash.
		 * 
		 * @return Jive
		 */
		update: function() {
			this.hash = window.location.hash;
			return this;
		},
		
		/**
		 * Dispatches the call.
		 * 
		 * @return Jive
		 */
		dispatch: function(request) {
			var hash = this.hash.substring(1);
			for (var i in this.routes) {
				if (this.routes[i].execute(hash)) {
					return true;
				}
			}
			return false;
		},
		
		/**
		 * Checks for changes. If the hash has changed, it is updated and dispatched to. Returns whether or not the
		 * dispatch matched a route and executed it.
		 * 
		 * @return bool
		 */
		checkAndDispatch: function() {
			if (this.check()) {
				this.update();
				return this.dispatch();
			}
			return false;
		}
	};
	
	/**
	 * Instantiates the route.
	 * 
	 * @param String   name     The name of the route.
	 * @param RegExp   regex    The regular expression to match against the request.
	 * @param Function callback The callback to execute if the route matches.
	 * 
	 * @return Route
	 */
	function Route(name, regex, callback) {
		this.name = name;
		this.regex = regex;
		this.callback = callback;
	}
	
	Route.prototype = {
		/**
		 * The name of the route. Can be pretty much anything, but more manageable if it's a string.
		 * 
		 * @var mixed
		 */
	    name: null,

		/**
		 * The regex to match against the request.
		 * 
		 * @var RegExp
		 */
		regex: /.*/,

		/**
		 * The callback to execute if the request is matched.
		 * 
		 * @var Function
		 */
		callback: function(){},

		/**
		 * Returns whether or not the specified request matches the set route.
		 * 
		 * @param String request The request to match.
		 * 
		 * @return bool
		 */
		match: function(request) {
			return request.match(this.regex);
		},

		/**
		 * If the specified request matches the route, then the callback is executed. If it matches it returns true, if
		 * not, then it returns false.
		 * 
		 * @param String request The request to match.
		 * 
		 * @return bool
		 */
		execute: function(request) {
			var params = this.match(request);
			if (params === null) {
				return false;				
			}
			params.unshift(request);
			this.callback.apply(this, params);
			return true;
		}
	}
	
	// Allows easy access to an already set up instance
	window.jive = new Jive;
	
})();

