Zee = window.Zee || {};

/**
 * Thanks to Aaron Quint and his great Sammy.js
 * http://code.quirkey.com/sammy/
 */

/**
 * Request
 *
 * Wrap request properties
 *
 * @author Julien Cabanès <jc@aaz.fr>
 * @version 0.1
 * @class Zee.Request
 */
Zee.Request = function(method, path, params) {
	this.method = method;
	this.path = path;
	this.params = params;
	this.redirect = function(path) {
		window.location.hash = path;
	};
};

/**
 * Dispatcher
 *
 * Match Hash against Routes
 *
 * @author Julien Cabanès <jc@aaz.fr>
 * @version 0.5
 * @class Zee.Dispatcher
 */
Zee.Dispatcher = function(context) {
	this.context = context;
	this.routes = [];
	this.getMatcher = function() {
		return /:([\w\d]+)/g;
	};
	this.getReplacer = function() {
		return '([^\/]+)';
	};
	this.addRoute = function(method, path, callback) {
		var params = [], matches, matcher = this.getMatcher();
		while((matches = matcher.exec(path)) !== null) {
			params.push(matches[1]);
		}
		this.routes.push({
			method: method,
			path: path,
			regexp: new RegExp('^'+path.replace(matcher, this.getReplacer())+'$'),
			params: params,
			isSpecial: path.indexOf('::') !== -1,
			callback: callback
		});
	};

	this.matchRoute = function(route, method, path) {
		if(method === route.method) {
			if(match = route.regexp.exec(path)) {
				return match;
			} else if(route.isSpecial) {
				var index = route.path.indexOf('::');
				if(route.path.substr(0, index) === path.substr(0, index)) {
					return [path, path.substr(index, path.length)];
				}
			}
		}
		return false;
	};

	this.dispatch = function(path, method) {
		var route, match, request, params = {};
		path = path.substr(path.indexOf('#'));
		method = (method || 'get').toLowerCase();
		for(var i in this.routes) {
			route = this.routes[i];
			if(match = this.matchRoute(route, method, path)) {
				// console.log('Zee.Dispatcher > ', route.method, path, route, match);

				// What are you saying?
				for(var j in route.params) {
					if(route.params.hasOwnProperty(j)) {
						params[route.params[j]] = match[parseInt(j, 10)+1];
					}
				}

				// Keep the original request in case of looping otherwise create it
				if(!request) {
					request = new Zee.Request(route.method, path, params);
				}

				// Params are also passed as arguments after request
				var arrParams = [request];
				for(var k in params) {
					if(params.hasOwnProperty(k)) {
						arrParams.push(params[k]);
					}
				}

				// Don't repeat yourself
				request.keepLooping = false;

				// Execution!
				route.callback.apply(this.context, arrParams);

				// So, are we done yet?
				if(!request.keepLooping) {
					this.context.afterDispatch && this.context.afterDispatch(request);
					return true;
				}
			}
		}
		return false;
	};
};

/**
 * PageController
 *
 * Listen hash change and perform actions
 *
 * @author Julien Cabanès <jc@aaz.fr>
 * @version 0.5
 * @class Zee.PageController
 */
Zee.PageController = function(routingSchema, autorun) {
	var me = this;
	this.callbackQueue = [function(request) {
		me.lastHash = request.path;
	}];

	this.afterDispatch = function(request) {
		for(var i in me.callbackQueue) {
			if(me.callbackQueue.hasOwnProperty(i)) {
				me.callbackQueue[i] && me.callbackQueue[i](request);
			}
		}
	};

	this.dispatcher = new Zee.Dispatcher(this);

	this.selector = 'body';
	var i, j;
	// TODO : optimize (DRY)
	for(i in routingSchema) {
		if(!this.selector && i === 'selector') {
			this.selector = routingSchema[i];
		} else if(i.substr(0,1) === '#') {
			if(typeof routingSchema[i] === 'function') {
				this.dispatcher.addRoute('get', i, routingSchema[i]);		// path + callback
			} else {
				for(j in routingSchema[i]) {
					this.dispatcher.addRoute(j, i, routingSchema[i][j]);	// path + method + callback
				}
			}
		} else {
			if(typeof routingSchema[i] === 'function') {
				this.dispatcher.addRoute(i, '#/', routingSchema[i]);		// method + callback
			} else {
				for(j in routingSchema[i]) {
					this.dispatcher.addRoute(i, j, routingSchema[i][j]);	// method + path + callback
				}
			}
		}
	}

	this.run = function(defaultHash) {
		$(window).bind('hashchange', function(e) {
			// Temp hack : hashchange firing twice?
			if(window.location.hash !== me.lastHash) {
				me.dispatcher.dispatch(window.location.hash);
			}
		});

		/*
		window.addEventListener('hashchange', function(e) {
			dispatcher.dispatch(window.location.hash);
		}, false);
		*/

		$(this.selector+' form').live('submit', function(e) {
			if(me.dispatcher.dispatch(this.action, this.method)) {
				e.preventDefault();
			}
		});

		if(window.location.hash === '' && defaultHash) {
			window.location.hash = defaultHash;
		}

		me.dispatcher.dispatch(window.location.hash);
	};

	this.route = function(hash) {
		me.dispatcher.dispatch(hash);
	};

	this.refresh = function() {
		me.dispatcher.dispatch(window.location.hash);
	};

	this.setHash = function(hash) {
		window.location.hash = hash;
	};

	autorun && this.run(autorun);
};


/*
 * jQuery hashchange event - v1.3 - 7/21/2010
 * http://benalman.com/projects/jquery-hashchange-plugin/
 *
 * Copyright (c) 2010 "Cowboy" Ben Alman
 * Dual licensed under the MIT and GPL licenses.
 * http://benalman.com/about/license/
 */
(function(a,q,r){function f(d){d=d||location.href;return"#"+d.replace(/^[^#]*#?(.*)$/,"$1")}var b="hashchange",i=document,l,s=a.event.special,t=i.documentMode,m="on"+b in q&&(t===r||t>7);a.fn[b]=function(d){return d?this.bind(b,d):this.trigger(b)};a.fn[b].delay=50;s[b]=a.extend(s[b],{setup:function(){if(m)return false;a(l.start)},teardown:function(){if(m)return false;a(l.stop)}});l=function(){function d(){var c=f(),e=u(j);if(c!==j){n(j=c,e);a(q).trigger(b)}else if(e!==j)location.href=location.href.replace(/#.*/,
"")+e;g=setTimeout(d,a.fn[b].delay)}var h={},g,j=f(),o=function(c){return c},n=o,u=o;h.start=function(){g||d()};h.stop=function(){g&&clearTimeout(g);g=r};a.browser.msie&&!m&&function(){var c,e;h.start=function(){if(!c){e=(e=a.fn[b].src)&&e+f();c=a('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){e||n(f());d()}).attr("src",e||"javascript:0").insertAfter("body")[0].contentWindow;i.onpropertychange=function(){try{if(event.propertyName==="title")c.document.title=i.title}catch(p){}}}};
h.stop=o;u=function(){return f(c.location.href)};n=function(p,w){var k=c.document,v=a.fn[b].domain;if(p!==w){k.title=i.title;k.open();v&&k.write('<script>document.domain="'+v+'"<\/script>');k.close();c.location.hash=p}}}();return h}()})(jQuery,this);
