
/**
 * Date Time processor for parsing dates in the form of:
 * Mon, 25 Feb 2008 03:09:22 GMT
 *
 * By default, if a timestamp is within a month, time will be displayed as
 * x days, x months ago. If the timestamp flag is used, it will be either
 * today, tomorrow or a timestamp
 *
 * Possible classes:
 * timestamp  	-  show a timestamp, regardless of when the time was
 * abbr       	-  abbreviates time unit names
 * descr		  	-  append either "ago" or "remaining"
 * notime    	-  ommits time from the display, supported with timestamp
 * timeonly  	-  shows only the time, only works with timestamp
 * printable 	-  adds a span.print version of the time for printable formats
 */
(function() {

	// the units we are using to calculate time differences
	var units = {
		year: 60 * 60 * 24 * 365,
		month: 60 * 60 * 24 * 30,
		week: 60 * 60 * 24 * 7,
		day: 60 * 60 * 24,
		hour: 60 * 60,
		minute: 60,
		second: 1
	};

	// define some abbreviations
	var abbrs = {
		year:'yr', month:'mth', hour:'hr',
		minute:'min', second:'sec'
	};

	// finds the biggest unit possible in the seconds, returns a tuple with
	// the unit, the quantity and the number of seconds to decrement by
	var findBiggestUnit = function(seconds) {
		for (var key in units) {
			var qty = Math.floor(seconds / units[key]);
			if(qty != 0) return [key,qty,units[key] * qty];
		}
	}

	// calculates the time difference between two times
	var timeDifference = function(time1, time2) {
		var elapsed = Math.abs((time2.getTime() - time1.getTime()) / 1000);
		var tokens = [];

		// break into chunks of time, in descending order
		while(!isNaN(elapsed) && (chunk = findBiggestUnit(elapsed))) {
			suffix = chunk[1] > 1 ? 's' : '';
			tokens.push(chunk[1] + ' ' + chunk[0] + suffix);
			elapsed -= chunk[2];
		}

		return tokens;
	}

	// returns true if an element has a particular class
	var hasClass = function(element, className) {
		return (
			element.className &&
			element.className.match( new RegExp( "\\b"+className+"\\b" ) )
			) ? true : false;
	}

	// gets the first text node that is a direct child of the span
	var findSpanTextNode = function(element) {
		for(var i=0; i<element.childNodes.length; i++) {
			if(element.childNodes[i].nodeType == 3) {
				return element.childNodes[i];
			}
		}
	}

	// calculates the difference in two dates in days
	var getDifferenceInDays = function(date1, date2) {
		var date2 = !date2 ? new Date() : date2;
		return (date1.getTime()-date2.getTime()) / (1000 * 60 * 60 * 24);
	}

	// formats the timestamp, optionally uses the dateformat library
	var formatTimestamp = function(time,config) {

		var now = new Date();
		var today = new Date(now.getFullYear(), now.getMonth(),
			now.getDate());
		var yesterday = new Date(now.getFullYear(), now.getMonth(),
			now.getDate()-1);
		var tomorrow = new Date(today.getTime() + 24*3600000);

		// calculate time
		var timetext = ', ' + time.getHours() + ':' +
			((time.getMinutes()>10) ? time.getMinutes() : '0' + time.getMinutes());

		// display a short time if we can
		if(time.getTime() >= (tomorrow.getTime()+24*3600000)) {
			// do nothing for future times
		} else if(time.getTime() >= tomorrow.getTime()) {
			return 'tomorrow' + (config.notime ? '' : timetext);
		} else if(time.getTime() >= today.getTime()) {
			return 'today' + (config.notime ? '' : timetext);
		} else if(time.getTime() >= yesterday.getTime()) {
			return 'yesterday' + (config.notime ? '' : timetext);
		}

		// fall back to a date string
		if(typeof(dateFormat) == 'function') {
			if(config.notime) {
				var monthMask = config.abbreviate ? 'mmm' : 'mmmm';
				var mask = monthMask + ' d, yyyy';
			} else if(config.timeonly) {
				var mask = 'h:MM tt'
			} else {
				var monthMask = config.abbreviate ? 'mmm' : 'mmmm';
				var mask = monthMask + ' d, yyyy h:MM tt';
			}
			return dateFormat(time, mask);
		} else {
			return time.toLocaleString();
		}
	}

	// gets a date object parsed from the span
	var getSpanDate = function(span) {
		var node = findSpanTextNode(span);
		if(!node) return false;
		return span.gmttime ? span.gmttime : new Date(Date.parse(node.nodeValue));
	}

	// process a span with a class of gmttime
	var formatSpan = function(span) {
		var dateString = '';
		var date = getSpanDate(span);
		if(!date) return false;

		var dayDiff = getDifferenceInDays(date);
		var since = dayDiff < 0;

		var config = {
			relative: !hasClass(span, 'timestamp'),
			abbreviate: !hasClass(span, 'timestamp') && hasClass(span,'abbr'),
			timeonly: hasClass(span, 'timeonly'),
			describe: hasClass(span, 'descr') || hasClass(span, 'desc'),
			notime: hasClass(span, 'notime'),
			print: hasClass(span, 'printable')
		}

		// override relative if the date is more than 28 days old
		if(Math.abs(dayDiff) > 28) config.relative = false;

		if(!config.relative) {
			dateString = formatTimestamp(date, config);
		} else if(config.relative && since) {
			dateString = timeDifference(date, new Date())
				.slice(0,2).join(', ');
		} else {
			dateString = timeDifference(date, new Date())
				.slice(0,2).join(', ');
		}

		if(!dateString) {
			dateString = formatTimestamp(date, config);
		} else if(config.describe && config.relative) {
			dateString += since ? ' ago' : ' remaining';
		}

		// optionally use abbreviations
		if(config.abbreviate) {
			for(var a in abbrs) dateString = dateString.replace(a, abbrs[a]);
		}

		// set the title
		span.title = date.toLocaleString();
		span.gmttime = date;

		// replace the value with the formatted time
		var textNode = findSpanTextNode(span);

		screenSpan = document.createElement('span');
		screenSpan.className = 'screen';
		screenSpan.appendChild(document.createTextNode(dateString));
		span.replaceChild(screenSpan,textNode);

		// optionally, add a print only version
		if(config.print) {
			printSpan = document.createElement('span');
			printSpan.className = 'print';
			printSpan.appendChild(document.createTextNode(date.toLocaleString()));
			span.insertBefore(printSpan,screenSpan);
		}
	}

	// function that sets up a span
	var initializeSpan = function(span) {
		// bind some functions to the span
		span.getDate = function() { return getSpanDate(this); }
		span.format = function() { return formatSpan(this); }

		// process the span
		span.format();
	}

	// process span.gmttime
	if(window.jQuery) {

		// add a jQuery plugin
		jQuery.fn.dateTime = function() {
			return this.each(function(){
				initializeSpan(this);
			});
		};

		// process each span
		jQuery('span.gmttime').dateTime();

	// otherwise, do it the old fashioned way
	} else {
		var spans = document.getElementsByTagName('span');
		for(i=0; i<spans.length; i++) {
			if(hasClass(spans[i], 'gmttime')) initializeSpan(spans[i]);
		}
	}

}());

/**
 * Client for the User service, requires a session
 */

var Service = Service || {};
Service.User = {};

(function() {

	var S = Service.User;
	var ServiceException = function(content) {
		this.message = content.errormessage;
		this.errorcode = content.errorcode;
	}

	/**
	 * Gets the url for a method call
	 */
	S.urlFor = function(method) {
		return '/api/v1/rest?service=user&format=json&method='+method;
	}

	/**
	 * Make a call to the service
	 */
	S.serviceGet = function(method, params) {

		// parse json response
		var responseText = jQuery.ajax({
				url: S.urlFor(method),
				async: false
		}).responseText;
		var json = eval("("+responseText+")");

		// handle failure
		if(json.status == 'fail') {
			throw new ServiceException(json)
		}

		return json.content;
	}

	/**
	 * Gets the active user
	 */
	S.getActiveUser = function() {
		var response = S.serviceGet('getActiveUser');
		return {
			'userId': response.userId,
			'displayName': response.displayName,
			'avatarSrc': response.avatarSrc,
			'privateMessages': response.privateMessages,
			'userLinks': response.userLinks
		}
	}

}());
(function (jQuery) {

	var Carousel = function (list, delay, autorotate) {
		this.list = jQuery(list);
		this.listItems = this.list.find('li').get();
		this.itemWidth = jQuery(this.listItems).width();
		this.animating = false;
		this.autorotate = autorotate == undefined ? true : autorotate;
		this.delay = delay == undefined ? 6000 : delay;
		this.position = 0;

		var carousel = this;
		jQuery(this.listItems).each(function (index, element) {
			jQuery(element).css({
				left: index*carousel.itemWidth,
				display: 'block'
			});
		});

		if (this.autorotate)
		{
			this.autoRotater = setInterval(function () {
				carousel.slideLeft();
			}, this.delay);
		}

		this.list.bind('next', function () {
			carousel.slideLeft();
		});

		this.list.bind('previous', function () {
			carousel.slideRight();
		});
	};

	Carousel.prototype = {
		slideLeft : function () {
			if (!this.animating)
			{
				var carousel = this;
				if (this.autorotate)
				{
					clearInterval(this.autoRotater);
					this.autoRotater = setInterval(function () {
						carousel.slideLeft();
					}, this.delay);
				}
				this.animating = true;
				var position = this.list.position();
				var newPosition = position.left - this.itemWidth;

				var nextItem = jQuery(this.getNextItem());
				var currentItem = jQuery(this.listItems[this.position]);
				nextItem.css('left', currentItem.position().left + this.itemWidth);

				this.list.animate({
					left: newPosition
				}, 1500, 'swing', function () {
					carousel.animating=false;
				});
				this.position = this.getNextPosition();
			}
		},

		slideRight : function () {
			if (!this.animating)
			{
				var carousel = this;
				if (this.autorotate)
				{
					clearInterval(this.autoRotater);
					this.autoRotater = setInterval(function () {
						carousel.slideLeft();
					}, this.delay);
				}
				this.animating = true;
				var position = this.list.position();
				var newPosition = position.left + this.itemWidth;

				var previousItem = jQuery(this.getPreviousItem());
				var currentItem = jQuery(this.listItems[this.position]);
				previousItem.css('left', currentItem.position().left - this.itemWidth);

				this.list.animate({
					left: newPosition
				}, 1500, 'swing', function () {
					carousel.animating=false;
				});
				this.position = this.getPreviousPosition();
			}
		},

		getNextItem : function () {
			return this.listItems[this.getNextPosition()];
		},

		getPreviousItem : function () {
			return this.listItems[this.getPreviousPosition()];
		},

		getNextPosition : function () {
			var nextPosition = this.position + 1;
			return nextPosition % this.listItems.length;
		},

		getPreviousPosition : function () {
			var previousPosition = this.position - 1;
			if (previousPosition < 0)
			{
				previousPosition = this.listItems.length - 1;
			}
			return previousPosition;
		}
	};

	var VerticalCarousel = function (list, delay, autorotate) {
		this.list = jQuery(list);
		this.listItems = this.list.find('li').get();
		this.itemHeight = jQuery(this.listItems).height();
		this.animating = false;
		this.autorotate = autorotate == undefined ? true : autorotate;
		this.delay = delay == undefined ? 6000 : delay;

		var carousel = this;
		jQuery(this.listItems).each(function (index, element) {
			jQuery(element).css({
				top: index*carousel.itemHeight,
				display: 'block'
			});
		});
		this.listItems.unshift(this.listItems.pop());

		if (this.autorotate)
		{
			this.autoRotater = setInterval(function () {
				carousel.slideUp();
			}, this.delay);
		}

		this.list.bind('next', function () {
			carousel.slideUp();
		});

		this.list.bind('previous', function () {
			carousel.slideDown();
		});
	};

	VerticalCarousel.prototype = {
		slideUp : function () {
			if (!this.animating)
			{
				clearInterval(this.autoRotater);
				var carousel = this;
				this.autoRotater = setInterval(function () {
					carousel.slideUp();
				}, this.delay);
				this.animating = true;
				var position = this.list.position();
				var newPosition = position.top - this.itemHeight -1;
				var carousel = this;

				var item = this.listItems.shift();
				jQuery(item).css('top', jQuery(this.listItems[this.listItems.length-1]).position().top + this.itemHeight);
				this.listItems.push(item);

				this.list.animate({
					top: newPosition
				}, 1500, 'swing', function () {
					carousel.animating=false;
				});
			}
		},

		slideDown : function () {
			if (!this.animating)
			{
				clearInterval(this.autoRotater);
				var carousel = this;
				this.autoRotater = setInterval(function () {
					carousel.slideDown();
				}, this.delay);
				this.animating = true;
				var position = this.list.position();
				var newPosition = position.top + this.itemHeight -1;
				var carousel = this;

				var item = this.listItems.pop();
				jQuery(item).css('top', jQuery(this.listItems[0]).position().top - this.itemHeight);
				this.listItems.unshift(item);

				this.list.animate({
					top: newPosition
				}, 1500, 'swing', function () {
					carousel.animating=false;
				});
			}
		}
	};

	jQuery.fn.extend({
		carousel : function (delay, autorotate)
		{
			var carousel = new Carousel(this, delay, autorotate);
			return carousel;
		},
		verticalCarousel : function (delay, autorotate)
		{
			var carousel = new VerticalCarousel(this, delay, autorotate);
			return carousel;
		}
	});
}(jQuery));
/**
 * Behaviour for the front page, adds the login box and animates the designs
 */
(function() {

	var buildLink = function(linkData)
	{
		var c = '';
		if (linkData['class'])
			var c = ' class="'+linkData['class']+'"';

		return '<a'+c+' title="'+linkData['title']+'" href="'+linkData['href']+'">'+linkData['text']+'</a>';
	};

	var showAccountBox = function() {
		var user = Service.User.getActiveUser();

		// build login box html
		var html =
		'<li class="profile">'+
		'<a title="User Profile for '+user.displayName+'" ' +
		'class="displayname" href="/users/'+user.userId+'">'+
		'<img src="'+user.avatarSrc+'" title="'+user.displayName+'" alt="Avatar for '+user.displayName+'" /> '+
		'<span class="name">'+user.displayName+'</span></a></strong></li>';

		// add extra links
		for (var i = 0; i < user.userLinks.length; i++)
		{
			html += '<li>' + buildLink(user.userLinks[i]) + '</li>';
		}

		jQuery('#nav-account').html(html);
	};

	var cookieExists = function(name) {
		return document.cookie.match(new RegExp('\\b'+name+'=')) != null;
	};

	jQuery(document).ready(function(){

		var loginCheck = jQuery(document.createElement('li')).append('<em>Checking Login...</em>');
		jQuery('#nav-account').prepend(loginCheck);

		// potential login cookies
		if (cookieExists('PHPSESSID') || cookieExists('usertokens')) {
			try { showAccountBox(); } catch(e) { }
		}

		loginCheck.remove();
	});

}());
/**
 * Handles the display of the twittermonial
 */
(function () {
	var feature = jQuery('div.feature ul.slideshow');

	var featureCarousel = feature.carousel(8000, false);

	var timeoutId = 0;

	var featureAutoRotate = function (direction) {
		clearTimeout(timeoutId);
		feature.trigger(direction);
		var interval = 8000;
		if (direction == 'next' && featureCarousel.getNextPosition() == 0)
		{
			interval = 12000;
		}
		timeoutId = setTimeout(function () { featureAutoRotate('next') ;},interval);
	};

	timeoutId = setTimeout(function () { featureAutoRotate('next'); }, 8000);


	jQuery('div.feature ul.controls .previous').click(function (event) {
		event.preventDefault();
		featureAutoRotate('previous');
	});

	jQuery('div.feature ul.controls .next').click(function (event) {
		event.preventDefault();
		featureAutoRotate('next');
	});

	if (jQuery('ul.twittermonial li').length > 2)
	{
		jQuery('div#ticker ul.controls').css('display', 'block');
		var twittermonial = jQuery('ul.twittermonial');

		twittermonial.verticalCarousel(30000);

		jQuery('ul.twittermonial li').each(function(index, element) {
			tweet = jQuery(element).find('q a');
			tweet.attr({
				'href' : tweet.attr('data-tweeturl'),
				'target' : '_blank'
			});
			author = jQuery(element).find('span.author a');
			author.attr({
				'href' : 'http://twitter.com/'+author.attr('data-author'),
				'target' : '_blank'
			});
		});
	}
}());
