//
// -- Moby Namespace
//

(function($) {
	$.moby = function(options) {
		var opts = $.extend({}, $.moby.defaults, options);
	};
	
	$.moby.version = '0.5';
	
	$.moby.defaults = {};

})(jQuery);


//
// -- Moby tools
//

(function($) {
	
	$.moby.tools = function(options) {
		var opts = $.extend({}, $.moby.defaults, options);
	};
	
	// -- generates unique id's
	var uniqueIdCounter = 0;
	$.moby.tools.uniqueId = function() {
		uniqueIdCounter++;
		return uniqueIdCounter;
	};
	
	
	// -- preloads images
	$.moby.tools.preload = function() {
		for(var i = 0; i<arguments.length; i++) {
			$("<img>").attr("src", arguments[i]);
		}
	};
	
	// autolink urls in text
	$.moby.tools.autolink = function(s) {   
		s = " " + s + " ";
		s = s.replace(/ www./g, "http://www.");
		s = s.replace(/http:/g, " http:");
		s = s.replace(/  /g, " ");
		
		var hlink = /\s(ht|f)tp:\/\/([^ \,\;\:\!\)\(\"\'\<\>\f\n\r\t\v])+/g;
		return (s.replace(hlink, function ($0,$1,$2) { 
			s = $0.substring(1,$0.length); 
			// remove trailing dots, if any
			while (s.length>0 && s.charAt(s.length-1)=='.') 
				s=s.substring(0,s.length-1);
			// add hlink
			return " " + s.link(s); 
		}));
	};
	
	// -- find @username and #hashtag and style them
	$.moby.tools.autotag = function(text) {
		// initiate tag en user archive
		tags = new Array();
		users = new Array();
		
		// handle some exceptions
		text = text.replace(")", " ) ").replace("(", " ( ").replace(".", " . ").replace(",", " , ").replace(":", " : ").replace("!", " ! ");
		
		// split words to loop text
		words = text.split(" ");
		
		$.each(words, function(i, word){
			firstletter = word.substr(0,1);
			
			if (firstletter == "#")
				tags[tags.length] = word;
			else if (firstletter == "@")
				users[users.length] = word;
		});
		
		text = " " + text + " ";
		
		// replace each tag
		$.each(tags, function(i, tag){
			text = text.replace(" " + tag + " ", ' <a href="http://www.mobypicture.com/group/' + tag.replace("#", "") + '" target="_blank" class="hashTag">' + tag + "</a> ")
		});
		
		// replace each username
		$.each(users, function(i, user){
			text = text.replace(" " + user + " ", ' <a href="http://www.twitter.com/' + user.replace("@", "") + '" target="_blank" class="twitterUser">' + user + '</a> ')
		});
		
		// reset the exceptions
		text = text.replace(" ) ", ")").replace(" ( ", "(").replace(" . ", ".").replace(" , ", ",").replace(/&amp;/gi, "&").replace(" : ", ":").replace(" ! ", "!");
		text = text.trim();
		
		// return the text
		return text;
	};
	
	// -- checks if image is loaded
	$.moby.tools.imageLoaded = function(img) {
		if (!img.complete)
			return false;

		if (typeof img.naturalWidth != "undefined" && img.naturalWidth == 0)
			return false;
	
		return true;
	};
	
	// -- calculates the time difference with the server in seconds
	$.moby.tools.timesync = function(){
		timeLoader = $.moby.loader.create('timesync');
		$.get("/ajax.php?action=gmtTime", function(data){
			try {
				timesince = $.moby.tools.timeGMT();
					
				fTime = new Date(data);
				sTime = new Date(timesince);
				
				$.moby.tools.timesync.correction  = Math.floor( (fTime.getTime() - sTime.getTime()) / 1000);
			} catch (err) { /* */ }
			$.moby.loader.destroy(timeLoader);
		});
	};
	$.moby.tools.timesync.correction = 0;
	
	// -- get the current time in GMT
	$.moby.tools.timeGMT = function (){
		tempTime = new Date();
		
		if ($.moby.tools.timesync.correction  != 0)
			tempTime.setTime(tempTime.getTime() + ($.moby.tools.timesync.correction  * 1000));
				
		gmtMs = tempTime.toGMTString();
		
		return gmtMs;
	};
	
	// get a user-friendly time format (timesince is optional)
	$.moby.tools.niceTime = function(time, timesince){
		if (time === undefined)
			return " ";
		
		if (timesince === undefined)
			timesince = $.moby.tools.timeGMT();
		
		fTime = new Date(time);
		sTime = new Date(timesince);
		
		timeDif = Math.floor( (sTime.getTime() - fTime.getTime()) / 1000);
		
		if (timeDif < 60)
			return timeDif + ' seconds ago';
		else if (timeDif < 120){
			minutecount = Math.floor(timeDif / 60);
			if (minutecount == 1)
				return '1 minute ago';
			else
				return minutecount + ' minutes ago';
		}
		else if (timeDif < 3600)
			return Math.floor(timeDif/60) + ' minutes ago';
		else if (timeDif < 86400){
			hourcount = Math.round(timeDif / 3600);
			if (hourcount == 1)
				return '1 hour ago';
			else
				return hourcount + ' hours ago';
		} 
		else if (timeDif < 259200)
		{
			dayCount = Math.floor(timeDif / 86400);
			if (dayCount == 1)
				return '1 day ago';
			else
				return dayCount + ' days ago';
		}
		
		return time;
	};
})(jQuery);

//
// -- Moby loader, enables to check if task is running and attach callbacks
//

(function($) {
	var mLoaders = new Array();
	var lCounter = -1;
	var startCallbacks = new Array();
	var stopCallbacks = new Array();
	
	// -- Namespace
	$.moby.loader = function() {return true;};
	
	// -- Create a new loader
	$.moby.loader.create = function(name){
		doCallback = false;
		if (!$.moby.loader.isBussy(name))
			doCallback = true;
		
		lCounter++;
		dataPos = lCounter;
		
		newData = new Object;
		newData.id = dataPos;
		newData.name = name;
		newData.startTime = $.moby.tools.timeGMT;
		
		mLoaders[dataPos] = newData;
		
		// if loader name is nog bussy anymore
		if (doCallback){
			$.each(startCallbacks, function(i, item){
				if (item.name == name)
					item.callback(name, newData.id);
			});
		}
		
		return newData.id;
	};
	
	// -- Create a new loader
	$.moby.loader.destroy = function(id){
		iCounter = -1;
		rempos = -1;
		$(mLoaders).each(function(){
			iCounter++;
			if (this.id == id)
				rempos = iCounter;
		});
		
		if (rempos == -1)
			return false;
		
		name = mLoaders[rempos].name;	
		mLoaders.remove(rempos);
		
		// if loader name is nog bussy anymore
		if (!$.moby.loader.isBussy(name)){
			$.each(stopCallbacks, function(){
				if (this.name == name)
					this.callback(name, id);
			});
		}
		
		return true;
	};
	
	// check if a loader is bussy
	$.moby.loader.isBussy = function(name){
		response = false;
		
		$.each(mLoaders, function(){
			if (this.name == name)
				response = true;
		});
		
		return response;
	};
	
	// -- add a callback, callback is fired when new loader object is found
	$.moby.loader.addStartCallback = function(name, callback){
		newData = new Object;
		newData.name = name;
		newData.callback = callback;
		
		startCallbacks[startCallbacks.length] = newData;
	};
	
	// -- add a callback, callback is fired when no mLoaders under this name is known
	$.moby.loader.addStopCallback = function(name, callback){
		newData = new Object;
		newData.name = name;
		newData.callback = callback;
		
		stopCallbacks[stopCallbacks.length] = newData;
	};

})(jQuery);

//
// -- Moby Favicon service
// fetches favicons found in texts
//

(function($) {
	var faviUrls = new Array(); 		// all fetched favicons and corresponding urls (cache)
	var fetchFaviBussy = false; 		// is the favicon service bussy ?
	
	$.moby.favicon = function(options) {
		var opts = $.extend({}, $.moby.defaults, options);
	};
	
	// -- Moby strips an url and returns middle domain name+ tld
	function stripDomain(url){
		stripedUrl = url.replace("http://", "");
		stripedUrl = stripedUrl.replace("www.", "");
		slashPos = stripedUrl.search("/");

		if (slashPos != -1) {
			stripedUrl = stripedUrl.substring(0, slashPos);
		}
		
		return stripedUrl;
	};
	
	// -- get the position for a url in the favicon cache
	function getFaviconID(url){
		stripedUrl = stripDomain(url);
	
		faviID = -1;
		$.each(faviUrls, function(i, item){
			if (item[0] == stripedUrl) {
				faviID = i;
				return faviID;
			}
		});
		
		return faviID;
	};
	
	// -- favicon Fetch Service, downloads favicons and places it in cache
	$.moby.favicon.fetchService = function(url) {		
		// only one fetch at the time, or it will becoume favicon soup
		if (fetchFaviBussy){
			// TODO: add code to deny double service requests
			setTimeout('$.moby.favicon.fetchService("' + url + '")', 600);
		} else {
			fetchFaviBussy = true;
			if (getFaviconID(url) != -1) {
				// faviIcon has already been found
				fetchFaviBussy = false;
			} else {
				$.get("/ajax?action=favicon&callback=?", {request_url: url} , function(data){
					if (data.favicon != ""){
						// Save the new favicon
						faviID = faviUrls.length;
						faviUrls[faviID] = new Array();
						faviUrls[faviID][0] = stripDomain(url); // Domain shortcode
						faviUrls[faviID][1] = data.favicon; // Domain favicon
						
						// preload favicon
						$.moby.tools.preload(data.favicon);
					};
					
					// release favicon service
					fetchFaviBussy = false;
				}, 'json');
			}
		}
	};
	
	// -- get a favicon location from the local cache
	$.moby.favicon.getFavicon = function(url){
		faviID = getFaviconID(url);
		
		if (faviID == -1)
			return false;
		else
			return faviUrls[faviID][1];
	};
	
	// -- strip urls from text and fetch favicons
	$.moby.favicon.preload = function(text){
		// detect links
		linkedtext = "<div> " + $.moby.tools.autolink(text) + " </div>";
		
		// find the links and fetch the favicons
		$(linkedtext).find("a").each(function(i,item){
			url = $(this).attr("href");
			
			// check if icon is already present in cache
			faviID = getFaviconID(url);
			
			// if favicon isnt know, qeue it for download
			if (faviID == -1)
				$.moby.favicon.fetchService(url);
		});
	};
	
	// -- links all urls found in givven text and matches links to favicon database
	$.moby.favicon.autolink = function (text){
		linkedtext = $("<div>" + $.moby.tools.autolink(text) + "</div>");
		
		linkedtext.find("a").each(function(i,item){
			url = $(this).attr("href");
			
			$(this).attr("target", "_blank");
			favIcon = $.moby.favicon.getFavicon($(this).attr("href"));
				
			if (favIcon !== false)
				$(this).html($.moby.favicon.textFormat($(this).attr("href"), favIcon));
		});
		
		return linkedtext.html();	
	};
	
	// -- how text should be formated in the autolink function
	$.moby.favicon.textFormat = function (url, favicon){
		return '<img src="' + favicon + '" alt="" /> [link]';
	};
})(jQuery);

//
// -- Moby Database(db): namespace & tools
//

(function($) {
	$.moby.db = function() { return true; };
})(jQuery);

//
// -- Moby Database(db): text streams
//

(function($) {
	// -- namespace
	$.moby.db.text = function() { 
		this.sources = new Array(); 		// mediaStreams configuration
		this.textData = new Array(); 		// Moby db
		this.lastUpdate = 0; 				// Time last recieved tweet
		
		this.getLast = function(){
			if (this.length() == 0)
				return false;
			else
				return this.textData[this.length() - 1];
		}
		
		this.getFirst = function(){
			if (this.length() == 0)
				return false;
			else
				return this.textData[0];
		}
		
		this.delLast = function(){
			if (this.length() == 0)
				return false;
			
			this.textData.remove(this.length()-1);
			return true;
		}
		
		this.length = function(){
			return this.textData.length;
		}
		
		// adds a source to the database
		this.addsource = function(source, data){
			confPos = this.sources.length;
			
			if (data == 'undefined') 
				data == {};
			
			data = $.extend({}, $.moby.db.text.sources.defaults, source.defaults, data);
			data.instance = confPos;
			
			this.sources[confPos] = new Object;
			this.sources[confPos].source = source;
			this.sources[confPos].data = data;
			
			// if this source updates on its own, execute inmediatly
			if (data.standalone){
				var _self = this;
				this.sources[confPos].source(_self, data);
			}
				
			return data.instance;
		};
		
		
		// -- fetch all the data from the configured sources
		this.fetchdata = function(){
			var _self = this;
			
			// fetch all configured sources
			$.each(this.sources, function(i, item){
				if (item.data.standalone == false)
					item.data = item.source(_self, item.data);
			});
			
			// pause a while
			setTimeout(function(){_self.fetchdata();}, this.pausetime());
		};
		
		// -- returns the pausetime in miliseconds in wich the script should wait until recheck for new data
		this.pausetime = function(){
			// Recheck after a few seconds depending on the tweets queued
			if (this.textData.length < 4) {
				// check text feeds again after 30 seconds
				return 30000;
			} else {
				// check text feeds again after 50 seconds
				return 50000;
			}	
		};
		
		// prepare data for stream and add it to the queue
		this.add = function(id, text, date, user, avatarUrl){
			// check if id isn't banned
			if (this.filter(id, text, date, user, avatarUrl)) {
				// Update last update time
				currentTime = new Date();
				this.lastUpdate = currentTime.getTime();
				
				// preload the favicons in the text
				$.moby.favicon.preload(text);
				
				// preload Avatar
				$.moby.tools.preload(avatarUrl);
				
				// Decide the position in the stream (show older tweets before new tweets)
				dataPos = -1;
				$.each(this.textData, function(i,item){
					if (dataPos == -1){
						if (id > item.id) {
							dataPos = i;
						}
					}
				});
				
				// if no position is found, add it to the end of the queue
				if (dataPos == -1)
					dataPos = this.textData.length;
				
				newData = new Object;
				newData.id = id;
				newData.text = text;
				newData.date = date;
				newData.user = user;
				newData.avatarUrl = avatarUrl;
				
				this.textData.splice(dataPos,0,newData);
			}
		};
		
		this.filter = function(){ return true; };
		
	};
	
	
	// -- Namespace for the sources
	$.moby.db.text.sources = function(){ return true; }
	
	// -- Default sources settings
	$.moby.db.text.sources.defaults = { standalone: false, instance: 0 };
	
	// -- get new tweets from the twittersearch
	$.moby.db.text.sources.twittersearch = function(textDb, data){
		textUrl = "http://search.twitter.com/search.json?callback=?";
		
		if (data.query != '')
			extra = data.query + "&";
		else
			extra = '';
		
		if (data.lastID != 0)
			textUrl = "http://search.twitter.com/search.json?since_id=" + data.lastID + "&" + extra + "callback=?";
		else
			textUrl = "http://search.twitter.com/search.json?" + extra + "callback=?";
		
		$.getJSON(textUrl, {q: data.keywords}, function(respons){
			if (data.lastID != respons.max_id && respons.max_id != '-1'){
				data.lastID = respons.max_id;
				
				respons.results = respons.results.reverse();
				
				$.each(respons.results, function(i, item){
					textDb.add(item.id, item.text, item.created_at, item.from_user, item.profile_image_url);
				});
			}
			
		});
		
		return data;
	};
	
	// -- twittersearch source default settings
	$.moby.db.text.sources.twittersearch.defaults = {
		lastID: 0,
		keywords: '',
		query: ''
	};
	
	// -- get data from a local stream resource
	$.moby.db.text.sources.localfeed = function(textDb, data){
		$.getJSON(data.url, {max_id: data.lastID}, function(respons){
			updateTime = -1;
			
			//try {
				if (data.lastID != respons.max_id && respons.max_id != '-1'){
					data.lastID = respons.max_id;
					
					// -- make sure the newest items are added first so the favicon service will cache these first
					respons.results = respons.results.reverse();
					
					$.each(respons.results, function(i, item){
						textDb.add(item.id, item.text, item.created_at, item.from_user, item.profile_image_url);
					});
				}
				
				timesince = $.moby.tools.timeGMT();
				
				fTime = new Date(respons.next_update);
				sTime = new Date(timesince);
				
				updateTime = Math.floor(fTime.getTime() - sTime.getTime());
			//} catch(err) { /**/ }
				
			setTimeout(function(){$.moby.db.text.sources.localfeed(textDb, data);}, $.moby.db.text.sources.localfeed.updatetime(updateTime));
		});
		
		return data;
	};
	
	// -- local stream default settings
	$.moby.db.text.sources.localfeed.defaults = {
		lastID: 0,
		url: '',
		standalone: true
	};
	
	// -- local streal default updatetime calculator
	$.moby.db.text.sources.localfeed.updatetime = function(updateTime){
		if (updateTime < 2000)
			updateTime = 2000;
		else if (updateTime > 120000)	
			updateTime = 120000;
			
		return updateTime;
	};
})(jQuery);


//
// -- Moby Database(db): media streams
//

(function($) {
	// -- namespace
	$.moby.db.media = function() {
		this.sources = new Array(); 		// mediaStreams configuration
		this.mobyData = new Array(); 		// Moby db
		indexCounter = -1;
		
		var mobyDefaultObject = {
			id			: 0,
			title 		: '',
			link 		: '',
			description : '',
			geo_latlong : '',
			pubDate 	: '',
			username 	: '',
			avatar 		: '',
			fullImage 	: '',
			viewImage 	: '',
			thumbImage 	: '',
			squareImage : '',
			pausetime 	: '',
			lastupdate 	: 0,
			isStatic 	: false
		};
		
		this.getLast = function(){
			if (this.length() == 0)
				return false;
			else
				return this.mobyData[this.length() - 1];
		}
		
		this.getFirst = function(){
			if (this.length() == 0)
				return false;
			else
				return this.mobyData[0];
		}
		
		this.delLast = function(){
			if (this.length() == 0)
				return false;
			
			this.mobyData.remove(this.length()-1);
			return true;
		}
		
		this.getByIndex = function(index){
			var goodIndex = false;
			for(var i = 0; i< this.length(); i++) {
				if (this.mobyData[i].index == index)
					goodIndex = this.mobyData[i];
			}
			
			return goodIndex;
		};
		
		this.length = function(){
			return this.mobyData.length;
		}
		
		this.addsource = function(source, data){
			confPos = this.sources.length;
			
			if (data == 'undefined') 
				data == {};
			
			data = $.extend({}, $.moby.db.media.sources.defaults, source.defaults, data);
			data.instance = confPos;
			
			this.sources[confPos] = new Object;
			this.sources[confPos].source = source;
			this.sources[confPos].data = data;
			
			// if this source updates on its own, execute inmediatly
			if (data.standalone) {
				var _self = this;
				this.sources[confPos].source(_self, data);
			}
				
			return data.instance;
		};
		
		// -- fetch all the data from the configured sources
		this.fetchdata = function(){
			var _self = this;
			
			// fetch all configured sources (if the source is not standalone and there are cycles left)
			$.each(this.sources, function(i, item){
				if (item.data.standalone == false && item.data.cycles != 0){
					item.data = item.source(_self, item.data);
					item.data.cycles--;
				}
			});
			
			// pause a while
			setTimeout(function(){_self.fetchdata();}, this.pausetime());
		};
		
		// -- returns the pausetime in miliseconds in wich the script should wait until recheck for new data
		this.pausetime = function(){
			// Recheck after a few seconds depending on the tweets queued
			if (this.mobyData.length < 4) {
				// check text feeds again after 30 seconds
				return 20000;
			} else {
				// check text feeds again after 50 seconds
				return 40000;
			}	
		};
		
		
		// add a new picture by json object 
		this.add = function(mobyObject){
			mobyObject = data = $.extend({}, mobyDefaultObject, mobyObject);
			
			if (mobyObject.mediaType == 'audio')
				return;
			
			mobyObject = mobyPreloadObject(mobyObject);
			mobyObject.isStatic = false;
			
			indexCounter++;
			mobyObject.index = indexCounter;
			
			
			dataPos = -1;
			$.each(this.mobyData, function(i,item){
				if (dataPos == -1){
					if (mobyObject.id > item.id) {
						dataPos = i;
					}
				}
			});
			
			// if no position is found, add it to the end of the queue
			if (dataPos == -1)
				dataPos = this.mobyData.length;
			
			this.mobyData.splice(dataPos,0,mobyObject);
			
			this.addCallback(mobyObject);
		};
		
		this.addCallback = function(mobyObject){return};
		this.addFinishCallback = function(){return};
	};
	
	// -- Namespace for the sources
	$.moby.db.media.sources = function(){ return true; }
	
	// -- Default sources settings
	$.moby.db.media.sources.defaults = { standalone: false, instance: 0, cycles: -1 };
	
	$.moby.db.media.sources.mobyfeed = function(mobyDb, data){
		searchUrl = data.url + "?amount=" + data.maxResults + "&jsoncallback=?";
		
		$.getJSON(searchUrl, function(respons){
			currentTime = new Date();
			
			doCycle = false;
			
			try {
				if (data.lastID != respons.items[0].id) {
					mobyResetID = data.lastID;
					data.lastID = respons.items[0].id;
					doCycle = true;
				}
			} catch(err) { /* */ }
			
			if (doCycle) {
				var addCount = 0;
				
				respons.items = respons.items.reverse();
				
				$.each(respons.items, function(i, item){
					if (mobyResetID < item.id){
						mobyDb.add(item);
						addCount++;
					}
				});
				
				if (addCount != 0)
					mobyDb.addFinishCallback();
			}
		});
		
		return data;
	};
	
	// -- Default mobyfeed settings
	$.moby.db.media.sources.mobyfeed.defaults = {
		lastID: 0,
		url: '',
		maxResults: 20
	};
	
	// -- the moby preloader Object
	function mobyPreloadObject(mobyObject){
		$.moby.tools.preload(mobyObject.avatar);
		return mobyObject;
	}
})(jQuery);


//
// -- Moby Backchannel(bc)
//

(function($) {
	var mediaData = new $.moby.db.media();
	
	var textData = new $.moby.db.text();
	
	var textIDs = new Array(); 			// The textIDs currently on the screen
	var textIDcounter = 0; 				// number of tweets on the screen
	
	// -- sets the namespace
	$.moby.bc = function() { return true; };
	
	// -- starts the backchannel
	$.moby.bc.start = function(){
		
		// start the moby backchannel after the timesync is complete
		$.moby.loader.addStopCallback('timesync', function(){
			if (!$.moby.bc.started){
				$.moby.bc.started = true;
				
				$.moby.bc.text.start();
				$.moby.bc.media.start();
			}
		});
		
		$.moby.tools.timesync();
	};
	
	$.moby.bc.started = false;
	
	// --- default backchannel settings
	$.moby.bc.defaults = {};
	
	// -- sets the namespace
	$.moby.bc.media = function() { return true; };
	
	$.moby.bc.media.addCallback = function(item){return;};
	
	// -- global function to add sources (you can write plugins for this one!)
	$.moby.bc.media.addsource = function(source, data) {
		mediaData.addsource(source, data);
	};
	
	// -- add a twitter-source to the backchannel
	$.moby.bc.addMobyFeed = function(feedUrl) {
		$.moby.bc.media.addsource($.moby.db.media.sources.mobyfeed, {url: feedUrl});
	};
	
	// -- push a new message to the pushcallback and remove it from the queue
	$.moby.bc.media.pushMessage = function(){
		// Check if textData is present
		if (mediaData.length() == 0) 
			setTimeout('$.moby.bc.media.pushMessage()', 1500);
		else
		{	
			// get the next text item			
			mediaItem = mediaData.getLast();
			
			// make a call to the pushCallback
			$.moby.bc.media.pushCallback(mediaItem);
			
			// cleanup
			mediaData.delLast();
			
			// set the timeout for the textscroller depending on the number of tweets in the qeue
			setTimeout('$.moby.bc.media.pushMessage()', $.moby.bc.media.pushTimer());
		}
	};
	
	// -- returns the time in miliseconds until the next push
	$.moby.bc.media.pushTimer = function(){
		if (mediaData.length() > 10) {
			return 7000;
		} else if (mediaData.length() > 5) {
			return 9000;
		} else {
			return 12000;
		}
	};
	
	// -- callback for text items, the backchannel pushes text items to be displayed here
	$.moby.bc.media.pushCallback = function(mediaItem){return;};
	
	// -- function to start the backchannel
	$.moby.bc.media.start = function(){
		mediaData.addCallback = $.moby.bc.media.addCallback;
		mediaData.fetchdata();
		$.moby.bc.media.pushMessage();
	};
	
	// -- sets the namespace
	$.moby.bc.text = function() { return true; };
	
	$.moby.bc.text.sourceCount = 0;
	
	// -- global function to add sources (you can write plugins for this one!)
	$.moby.bc.text.addsource = function(source, data) {
		$.moby.bc.text.sourceCount++;
		textData.addsource(source, data);
	};
	
	// -- add a twitter-source to the backchannel
	$.moby.bc.addTwitterSearch = function(searchKeywords) {
		$.moby.bc.text.addsource($.moby.db.text.sources.twittersearch, {keywords: searchKeywords});
	};
	
	// -- add a timed feed to the concussion 
	$.moby.bc.addTimedFeed = function(feedUrl){
		$.moby.bc.text.addsource($.moby.db.text.sources.localfeed, {url: feedUrl});
	}
	
	// -- push a new message to the pushcallback and remove it from the queue
	$.moby.bc.text.pushMessage = function(){
		// Check if textData is present
		if (textData.length() == 0) 
			setTimeout('$.moby.bc.text.pushMessage()', 1500);
		else
		{	
			// get new textID position
			textIDcounter++;
			
			// store the textID
			textIDs[textIDs.length] = textIDcounter; 
			
			// get the next text item			
			textItem = textData.getLast();
			
			textItem.text = textItem.text + " ";
			
			if (textItem.date === undefined)
				textItem.date = new Date();
			
			// make a call to the pushCallback
			$.moby.bc.text.pushCallback(textItem);
			
			// cleanup
			textData.delLast();
			
			// set the timeout for the textscroller depending on the number of tweets in the qeue
			setTimeout('$.moby.bc.text.pushMessage()', $.moby.bc.text.pushTimer());
		}
	};
	
	// -- returns the time in miliseconds until the next push
	$.moby.bc.text.pushTimer = function(){
		if (textData.length() > 70) {
			return 500;
		} else if (textData.length() > 50) {
			return 1000;
		} else if (textData.length() > 35) {
			return 1500;
		} else if (textData.length() > 20) {
			return 2500;
		} else if (textData.length() > 10) {
			return 3500;
		} else if (textData.length() > 5) {
			return 6500;
		} else {
			return 9500;
		}
	};
	
	// -- callback for text items, the backchannel pushes text items to be displayed here
	$.moby.bc.text.pushCallback = function(textItem){return;};
	
	// -- function to start the backchannel
	$.moby.bc.text.start = function(){
		textData.fetchdata();
		$.moby.bc.text.pushMessage();
	};
})(jQuery);

//
// -- Raw Tools
// 

// simple array removal
if (!Array.prototype.remove){
	Array.prototype.remove = function(from, to) {
		var rest = this.slice((to || from) + 1 || this.length);
		this.length = from < 0 ? this.length + from : from;
		return this.push.apply(this, rest);
	};
}

// trim function for strings
if (!String.prototype.trim){
	String.prototype.trim = function() {
		return this.replace(/^\s+|\s+$/g,"");
	};
}

var icon_group, icon_group_medium, icon_group_large, icon_audio, icon_video, icon_photo;
(function($) {
	$.moby.map = function(element, options) {
		if (options == 'undefined')
			options = {};
		
		var opts = $.extend({}, $.moby.map.defaults, options);
		
		if (opts.mapTypeId == 'undefined')
			opts.mapTypeId = google.maps.MapTypeId.SATELLITE;
		
		var target = element;
		var _self = this;
		this.gmap = '';
		
		var markers = new Array();
		var markerCounter = -1;
		
		// make sure icons are (pre)loaded
		$.moby.map.loadPresets();
		
		this.marker = function(iconType, options){
			return new google.maps.Marker($.extend({map: this.gmap}, iconType, options));
		};
		
		this.loaded = function(){return true;};
		
		// kickstart the map if document is ready loading
		$(document).ready(function(){
			// check if position is known
			if (opts.center == 'undefinded');
				opts.center = new google.maps.LatLng(parseFloat(opts.lat), parseFloat(opts.lon));
			
			// initialize the map
			_self.gmap = new google.maps.Map(document.getElementById(target), opts);
			
			// when the window resizes, trigger the google maps resize event
			$(window).resize(function(){
				google.maps.event.trigger(_self.gmap, 'resize');
			});
			
			// trigger the loaded callback
			_self.loaded();
		});
	};
	
	$.moby.map.defaults = {
		navigation: true,
		mapTypeId: 'undefined',
		lat: 52.43759500093111,
		lon: 5.756621360778809,
		zoom: 16,
		disableDefaultUI: true,
		mapTypeControl: true,
		scaleControl: true,
		navigationControl: true,
		scrollwheel: true,
		preCacheRatio: 0.6,
		postCacheRatio: 1
	};


	var presetsLoaded = false;
	$.moby.map.loadPresets = function(){
		//execute only once
		if (presetsLoaded)
			return;
			
		presetsLoaded = true;
		
		icon_group = {
			icon: new google.maps.MarkerImage(
				'http://www.mobypicture.com/images/layout/map_marker_group.png',
				new google.maps.Size(22, 22),
				new google.maps.Point(0,0),
				new google.maps.Point(12, 12)
			),
			shadow: new google.maps.MarkerImage(
				'http://www.mobypicture.com/images/layout/map_marker_shadow.png',
				new google.maps.Size(50, 22)
			),
			shape: {
		  		coord: [ 12,0,  22,12,  12,22,  0,12 ],
				type: 'poly'
  			}
	    };
	    
	    icon_group_medium = {
			icon: new google.maps.MarkerImage(
				'http://www.mobypicture.com/images/layout/map_marker_group_medium.png',
				new google.maps.Size(38, 38),
				new google.maps.Point(0,0),
				new google.maps.Point(19, 19)
			),
			shadow: '',
			shape: {
		  		coord: [ 19,8,  29,19,  19,29,  8,19 ],
				type: 'poly'
  			}
	    };
		
	   icon_group_large = {
			icon: new google.maps.MarkerImage(
				'http://www.mobypicture.com/images/layout/map_marker_group_large.png',
				new google.maps.Size(38, 38),
				new google.maps.Point(0,0),
				new google.maps.Point(19, 7)
			),
			shadow: '',
			shape: {
		  		coord: [ 19,8,  29,19,  19,29,  8,19 ],
				type: 'poly'
  			}
	    };
	    
	    icon_audio = {
			icon: new google.maps.MarkerImage(
				'http://www.mobypicture.com/images/layout/map_marker_audio.png',
				new google.maps.Size(22, 22),
				new google.maps.Point(0,0),
				new google.maps.Point(12, 12)
			),
			shadow: new google.maps.MarkerImage(
				'http://www.mobypicture.com/images/layout/map_marker_shadow.png',
				new google.maps.Size(50, 22)
			),
			shape: {
		  		coord: [ 12,0,  22,12,  12,22,  0,12 ],
				type: 'poly'
  			}
	    };
	    
	    icon_photo = {
			icon: new google.maps.MarkerImage(
				'http://www.mobypicture.com/images/layout/map_marker_photo.png',
				new google.maps.Size(22, 22),
				new google.maps.Point(0,0),
				new google.maps.Point(12, 12)
			),
			shadow: new google.maps.MarkerImage(
				'http://www.mobypicture.com/images/layout/map_marker_shadow.png',
				new google.maps.Size(50, 22)
			),
			shape: {
		  		coord: [ 12,0,  22,12,  12,22,  0,12 ],
				type: 'poly'
  			}
	    };
	    
	    icon_video = {
			icon: new google.maps.MarkerImage(
				'http://www.mobypicture.com/images/layout/map_marker_video.png',
				new google.maps.Size(22, 22),
				new google.maps.Point(0,0),
				new google.maps.Point(12, 12)
			),
			shadow: new google.maps.MarkerImage(
				'http://www.mobypicture.com/images/layout/map_marker_shadow.png',
				new google.maps.Size(50, 22)
			),
			shape: {
		  		coord: [ 12,0,  22,12,  12,22,  0,12 ],
				type: 'poly'
  			}

	    };
	};
})(jQuery);
