/**
 * Twitter utilities and jQuery plugin
 */

var maxtweets = 4;

$(function(){
	$(window).load(function(){
		$('#tweets').twitter({ 
			updateInterval: 60, 
			timelineUrl: "http://search.twitter.com/search.json",
			timelineData: { q: 'from:amplifying' },
			onupdate: function() {},
			onstatuses: function(statuses) {
				$('#tweets').find(".tweet:gt(" + (maxtweets - 1) + ")").remove();
				$('#tweets li.tweet:last').addClass('last');
			}
		});
	});
});

(function($) {
	$.fn.twitter = function(settings) {
		if (typeof settings == 'string') {
			var m = this.data("TwitterMonitor");
			if (settings == "stop") {
				if (m != null) {
					m.stopMonitoring();
				}
			}
		} else {
			if (settings == null)
				settings = {};
			if (settings.template == null)
				settings.template = "statustemplate";
			if (settings.timelineUrl == null)
				settings.timelineUrl = "http://api.twitter.com/1/statuses/public_timeline.json";
			if (settings.timeline == null)
				settings.timeline = new TwitterTimeline(settings.timelineUrl, settings.timelineData);
			if (settings.updateInterval == null)
				settings.updateInterval = 60 * 5;
			
			if (settings.where == null) {
				settings.where = this;
			}
			
			var m = new TwitterMonitor(settings.timeline, settings.where, settings.template);
			if (typeof settings.onupdate == "function") {
				m.onupdate = settings.onupdate;
			}
			if (typeof settings.onstatuses == "function") {
				m.onstatuses = settings.onstatuses;
			}
			m.startMonitoring(settings.updateInterval);
			this.data("TwitterMonitor", m);
		}
		
		return this;
	};
})(jQuery);

var TwitterDateUtils = function() {

	var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
	
	function pad(num, padding) {
		var str = "" + num;
		while (str.length < padding) {
			str = "0" + str;
		}
		return str;
	}
	
	function formatDate(d) {
		return d.getHours() + ":" + pad(d.getMinutes(), 2) + " " + 
			d.getDate() + " " + months[d.getMonth()] + " " + d.getFullYear();
	}
	
	return {
		pad: pad,
		formatDate: formatDate
	};
}();

var TwitterTimeline = function(url, data) {
	/* Last status id to send to Twitter when reloading this timeline */
	var _lastStatusId = 0;
	
	/* Listeners to notify when we receive new statuses in this timeline */
	var _listeners = [];
	
	function fireStatusUpdate(statuses) {
		for (var i = 0; i < _listeners.length; i++) {
			_listeners[i](this, statuses);
		}
	}
	
	function handleStatuses(statuses) {
		fireStatusUpdate(statuses);
	}
	
	function log(str) {
		if (typeof console !== "undefined" && typeof console.log === "function") {
			console.log(str);
		}
	}
	
	this.updateStatuses = function() {
		if (data == null) data = {};
		data.suppress_response_codes = true;
		if (_lastStatusId != 0) {
			data.since_id = _lastStatusId;
		}
		$.ajax({
			url: url,
			data: data,
			dataType: "jsonp",
			success: function(data, textStatus) {
				if (data.error != null) {
					log("TwitterTimeline error: " + data.error);
				} else {
					if (data.results != null)
						data = data.results;
					handleStatuses(data);
					if (data.length > 0)
						_lastStatusId = data[0].id;
				}
			},
			error: function (req, textStatus, errorThrown) {
				log("TwitterTimeline error: " + textStatus);
			}
		});
	};
	
	this.subscribe = function(f) {
		_listeners.push(f);
	};
};

var TwitterMonitorUID = 1;

var TwitterMonitor = function(timeline, where, statusTemplate) {
	
	var updateCounter = 0;
	var uid = TwitterMonitorUID++;
	
	var statusUpdateInterval;
	
	var me = this;
	
	timeline.subscribe(function(t, statuses) {
		normalizeStatuses(statuses);
		showStatuses(statuses, "tweet");
		me.onupdate();
	});
	
	function formatTweetText(text) {
		/* Make URLs into links */
		text = text.replace(/(^|\s+)(http:\/\/\S+)/g, "$1<a href=\"$2\" target=\"_blank\">$2</a>");
		/* Make @names into links */
		text = text.replace(/(^|\s+)@([a-zA-Z0-9_]+)/g, "$1<a href=\"http://twitter.com/$2\">@$2</a>");
		/* Make www. into links */
		text = text.replace(/(^|\s+)(www\.\S+)/g, "$1<a href=\"http://$2\" target=\"_blank\">$2</a>");
		return text;
	}
	
	function showStatuses(statuses, type, oldestStatus) {
		if (statuses.length == 0)
			return;
		
		var updateId = "update-" + updateCounter++;
		
		var html = "";
		for (var i = 0; i < statuses.length; i++) {
			var status = statuses[i];
			if (oldestStatus != null && status.date.getTime() < oldestStatus.getTime())
				continue;
			
			var id = "status-" + uid + "-" + status.id;
			
			/* Check for and avoid creating duplicates. But note that we still iterate over them below
			 * so tweets that are in the friends list and a reply will be treated twice!
			 */
			if ($("#" + id).length == 0) {
				var result = tmpl(statusTemplate, {
					status:status,
					id:id,
					statusTextFormatted:formatTweetText(status.text),
					statusDateFormatted:TwitterDateUtils.formatDate(status.date),
					statusDateISO8601:status.date.toISO8601String(),
					updateId: updateId
				});
				html += result;
			}
		}
		
		$(where).find(".loading").hide();
		$(where).prepend(html);
		
		var nodes = [];
		for (var i = 0; i < statuses.length; i++) {
			var status = statuses[i];
			var id = "status-" + uid + "-" + status.id;
			var statusNode = $("#" + id);
			statusNode.data("twitter.id", status.id);
			statusNode.data("twitter.date", status.date);
			statusNode.data("twitter.type", type);
			
			statusNode.addClass(type);
			nodes.push(statusNode);
		}
		
		me.onstatuses(nodes);
		
		updateTimeAgo();
	}
	
	function normalizeStatuses(statuses) {
		/* Create custom properties */
		for (var i = 0; i < statuses.length; i++) {
			var status = statuses[i];
			
			/* Normalize our statuses to always have a user property even if they're a DM */
			if (!status.user) {
				if (status.sender) {
					/* DM */
					status.user = status.sender;
				} else if (status.from_user_id != null) {
					/* Search */
					status.user = {
						id: status.from_user_id,
						screen_name: status.from_user,
						profile_image_url: status.profile_image_url
					};
				}
			}
				
				
			/* Date property */
			status.date = new Date(status.created_at);
		}
	}
	
	function updateTimeAgo() {
		$('abbr[class*=timeago]').timeago();
	}
	
	function doUpdateStatuses() {
		timeline.updateStatuses();
	}
	
	function startUpdateInterval(updateInterval) {
		clearInterval(statusUpdateInterval);
		
		statusUpdateInterval = setInterval(doUpdateStatuses, updateInterval * 1000);
		setTimeout(doUpdateStatuses, 100);
	}
	
	function stopUpdateInterval() {
		clearInterval(statusUpdateInterval);
	}
	
	this.startMonitoring = function(updateInterval) {
		startUpdateInterval(updateInterval);
	};
	
	this.stopMonitoring = function() {
		stopUpdateInterval();
	};
	
	this.onupdate = function() {
		
	};
	
	this.onstatuses = function(status) {
		
	};
};

//Simple JavaScript Templating
//John Resig - http://ejohn.org/ - MIT Licensed
(function(){
var cache = {};

this.tmpl = function tmpl(str, data){
 // Figure out if we're getting a template, or if we need to
 // load the template - and be sure to cache the result.
 var fn = !/\W/.test(str) ?
   cache[str] = cache[str] ||
     tmpl(document.getElementById(str).innerHTML) :
   
   // Generate a reusable function that will serve as a template
   // generator (and which will be cached).
   new Function("obj",
     "var p=[],print=function(){p.push.apply(p,arguments);};" +
     
     // Introduce the data as local variables using with(){}
     "with(obj){p.push('" +
     
     // Convert the template into pure JavaScript
     str.replace(/[\r\t\n]/g, " ")
			.replace(/'(?=[^#]*#>)/g,"\t")
			.split("'").join("\\'")
			.split("\t").join("'")
			.replace(/<#=(.+?)#>/g, "',$1,'")
			.split("<#").join("');")
			.split("#>").join("p.push('")
			+ "');}return p.join('');");
 
 // Provide some basic currying to the user
 return data ? fn( data ) : fn;
};
})();

Date.prototype.setISO8601 = function (string) {
 var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" +
     "(T([0-9]{2}):([0-9]{2})(:([0-9]{2})(\.([0-9]+))?)?" +
     "(Z|(([-+])([0-9]{2}):([0-9]{2})))?)?)?)?";
 var d = string.match(new RegExp(regexp));

 var offset = 0;
 var date = new Date(d[1], 0, 1);

 if (d[3]) { date.setMonth(d[3] - 1); }
 if (d[5]) { date.setDate(d[5]); }
 if (d[7]) { date.setHours(d[7]); }
 if (d[8]) { date.setMinutes(d[8]); }
 if (d[10]) { date.setSeconds(d[10]); }
 if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); }
 if (d[14]) {
     offset = (Number(d[16]) * 60) + Number(d[17]);
     offset *= ((d[15] == '-') ? 1 : -1);
 }

 offset -= date.getTimezoneOffset();
 time = (Number(date) + (offset * 60 * 1000));
 this.setTime(Number(time));
};

Date.prototype.toISO8601String = function (format, offset) {
 /* accepted values for the format [1-6]:
  1 Year:
    YYYY (eg 1997)
  2 Year and month:
    YYYY-MM (eg 1997-07)
  3 Complete date:
    YYYY-MM-DD (eg 1997-07-16)
  4 Complete date plus hours and minutes:
    YYYY-MM-DDThh:mmTZD (eg 1997-07-16T19:20+01:00)
  5 Complete date plus hours, minutes and seconds:
    YYYY-MM-DDThh:mm:ssTZD (eg 1997-07-16T19:20:30+01:00)
  6 Complete date plus hours, minutes, seconds and a decimal
    fraction of a second
    YYYY-MM-DDThh:mm:ss.sTZD (eg 1997-07-16T19:20:30.45+01:00)
 */
 if (!format) { var format = 6; }
 if (!offset) {
     var offset = 'Z';
     var date = this;
 } else {
     var d = offset.match(/([-+])([0-9]{2}):([0-9]{2})/);
     var offsetnum = (Number(d[2]) * 60) + Number(d[3]);
     offsetnum *= ((d[1] == '-') ? -1 : 1);
     var date = new Date(Number(Number(this) + (offsetnum * 60000)));
 }

 var zeropad = function (num) { return ((num < 10) ? '0' : '') + num; };

 var str = "";
 str += date.getUTCFullYear();
 if (format > 1) { str += "-" + zeropad(date.getUTCMonth() + 1); }
 if (format > 2) { str += "-" + zeropad(date.getUTCDate()); }
 if (format > 3) {
     str += "T" + zeropad(date.getUTCHours()) +
            ":" + zeropad(date.getUTCMinutes());
 }
 if (format > 5) {
     var secs = Number(date.getUTCSeconds() + "." +
                ((date.getUTCMilliseconds() < 100) ? '0' : '') +
                zeropad(date.getUTCMilliseconds()));
     str += ":" + zeropad(secs);
 } else if (format > 4) { str += ":" + zeropad(date.getUTCSeconds()); }

 if (format > 3) { str += offset; }
 return str;
};

