/*/ Tweet Blender /*/
(function($){$.toJSON=function(o)
{if(typeof(JSON)=='object'&&JSON.stringify)
return JSON.stringify(o);var type=typeof(o);if(o===null)
return"null";if(type=="undefined")
return undefined;if(type=="number"||type=="boolean")
return o+"";if(type=="string")
return $.quoteString(o);if(type=='object')
{if(typeof o.toJSON=="function")
return $.toJSON(o.toJSON());if(o.constructor===Date)
{var month=o.getUTCMonth()+1;if(month<10)month='0'+month;var day=o.getUTCDate();if(day<10)day='0'+day;var year=o.getUTCFullYear();var hours=o.getUTCHours();if(hours<10)hours='0'+hours;var minutes=o.getUTCMinutes();if(minutes<10)minutes='0'+minutes;var seconds=o.getUTCSeconds();if(seconds<10)seconds='0'+seconds;var milli=o.getUTCMilliseconds();if(milli<100)milli='0'+milli;if(milli<10)milli='0'+milli;return'"'+year+'-'+month+'-'+day+'T'+
hours+':'+minutes+':'+seconds+'.'+milli+'Z"';}
if(o.constructor===Array)
{var ret=[];for(var i=0;i<o.length;i++)
ret.push($.toJSON(o[i])||"null");return"["+ret.join(",")+"]";}
var pairs=[];for(var k in o){var name;var type=typeof k;if(type=="number")
name='"'+k+'"';else if(type=="string")
name=$.quoteString(k);else
continue;if(typeof o[k]=="function")
continue;var val=$.toJSON(o[k]);pairs.push(name+":"+val);}
return"{"+pairs.join(", ")+"}";}};$.evalJSON=function(src)
{if(typeof(JSON)=='object'&&JSON.parse)
return JSON.parse(src);return eval("("+src+")");};$.secureEvalJSON=function(src)
{if(typeof(JSON)=='object'&&JSON.parse)
return JSON.parse(src);var filtered=src;filtered=filtered.replace(/\\["\\\/bfnrtu]/g,'@');filtered=filtered.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,']');filtered=filtered.replace(/(?:^|:|,)(?:\s*\[)+/g,'');if(/^[\],:{}\s]*$/.test(filtered))
return eval("("+src+")");else
throw new SyntaxError("Error parsing JSON, source is not valid.");};$.quoteString=function(string)
{if(string.match(_escapeable))
{return'"'+string.replace(_escapeable,function(a)
{var c=_meta[a];if(typeof c==='string')return c;c=a.charCodeAt();return'\\u00'+Math.floor(c/16).toString(16)+(c%16).toString(16);})+'"';}
return'"'+string+'"';};var _escapeable=/["\\\x00-\x1f\x7f-\x9f]/g;var _meta={'\b':'\\b','\t':'\\t','\n':'\\n','\f':'\\f','\r':'\\r','"':'\\"','\\':'\\\\'};})(jQuery);

// jquery.jsonp 2.1.4 (c)2010 Julian Aubourg | MIT License
// http://code.google.com/p/jquery-jsonp/
(function(e,b){function d(){}function t(C){c=[C]}function m(C){f.insertBefore(C,f.firstChild)}function l(E,C,D){return E&&E.apply(C.context||C,D)}function k(C){return/\?/.test(C)?"&":"?"}var n="async",s="charset",q="",A="error",r="_jqjsp",w="on",o=w+"click",p=w+A,a=w+"load",i=w+"readystatechange",z="removeChild",g="<script/>",v="success",y="timeout",x=e.browser,f=e("head")[0]||document.documentElement,u={},j=0,c,h={callback:r,url:location.href};function B(C){C=e.extend({},h,C);var Q=C.complete,E=C.dataFilter,M=C.callbackParameter,R=C.callback,G=C.cache,J=C.pageCache,I=C.charset,D=C.url,L=C.data,P=C.timeout,O,K=0,H=d;C.abort=function(){!K++&&H()};if(l(C.beforeSend,C,[C])===false||K){return C}D=D||q;L=L?((typeof L)=="string"?L:e.param(L,C.traditional)):q;D+=L?(k(D)+L):q;M&&(D+=k(D)+encodeURIComponent(M)+"=?");!G&&!J&&(D+=k(D)+"_"+(new Date()).getTime()+"=");D=D.replace(/=\?(&|$)/,"="+R+"$1");function N(S){!K++&&b(function(){H();J&&(u[D]={s:[S]});E&&(S=E.apply(C,[S]));l(C.success,C,[S,v]);l(Q,C,[C,v])},0)}function F(S){!K++&&b(function(){H();J&&S!=y&&(u[D]=S);l(C.error,C,[C,S]);l(Q,C,[C,S])},0)}J&&(O=u[D])?(O.s?N(O.s[0]):F(O)):b(function(T,S,U){if(!K){U=P>0&&b(function(){F(y)},P);H=function(){U&&clearTimeout(U);T[i]=T[o]=T[a]=T[p]=null;f[z](T);S&&f[z](S)};window[R]=t;T=e(g)[0];T.id=r+j++;if(I){T[s]=I}function V(W){(T[o]||d)();W=c;c=undefined;W?N(W[0]):F(A)}if(x.msie){T.event=o;T.htmlFor=T.id;T[i]=function(){/loaded|complete/.test(T.readyState)&&V()}}else{T[p]=T[a]=V;x.opera?((S=e(g)[0]).text="jQuery('#"+T.id+"')[0]."+p+"()"):T[n]=n}T.src=D;m(T);S&&m(S)}},0);return C}B.setup=function(C){e.extend(h,C)};e.jsonp=B})(jQuery,setTimeout);

/*
 * Library of shared JS functions and data structures
 */

// Fallback for sites not supporting wp_localize_script
if (typeof(TB_labels) == 'undefined') {
	var TB_labels = {
		no_config: "No configuration settings found",
		twitter_logo: "Twitter Logo",
		kino: "Development by Kirill Novitchenko",
		refresh: "Refresh",
		no_sources: "Twitter sources to blend are not defined",
		no_global_config: "Cannot retrieve Tweet Blender configuration options",
		version_msg: "Powered by Tweet Blender plugin v{0} blending {1}",
		limit_msg: "You reached Twitter API connection limit",
		no_tweets_msg: "No tweets found for {0}",
		loading_msg: "Loading tweets...",
		time_past: "{0} {1} ago",
		time_future: "in {0} {1}",
		second: "second",
		seconds: "seconds",
		minute: "minute",
		minutes: "minutes",
		hour: "hour",
		hours: "hours",
		day: "day",
		days: "days",
		week: "week",
		weeks: "weeks",
		month: "month",
		months: "months",
		year: "year",
		years: "years",
		check_fail: "Check failed",
		limit_num: "Max is {0}/hour",
		limit_left: "You have {0} left",
		from: "from",
		reply: "reply",
		follow: "follow",
		limit_reset: "Next reset"
	};
}

var TB_monthNumber = {'Jan':1,'Feb':2,'Mar':3,'Apr':4,'May':5,'Jun':6,'Jul':7,'Aug':8,'Sep':9,'Oct':10,'Nov':11,'Dec':12},
TB_timePeriods = new Array(TB_labels.second, TB_labels.minute, TB_labels.hour, TB_labels.day, TB_labels.week, TB_labels.month, TB_labels.year),
TB_timePeriodsPlural = new Array(TB_labels.seconds, TB_labels.minutes, TB_labels.hours, TB_labels.days, TB_labels.weeks, TB_labels.months, TB_labels.years),
TB_timePeriodLengths = new Array("60","60","24","7","4.35","12");

//search: Wed, 27 May 2009 15:52:40 +0000
//user feed: Thu May 21 00:09:16 +0000 2009
function TB_str2date(dateString) {
	
	var dateObj = new Date(),
	dateData = dateString.split(/[\s\:]/);
	
	// if it's a search format
	if (dateString.indexOf(',') >= 0) {
		// $wday,$mday, $mon, $year, $hour,$min,$sec,$offset
		dateObj.setUTCFullYear(dateData[3],TB_monthNumber[""+dateData[2]]-1,dateData[1]);
		dateObj.setUTCHours(dateData[4],dateData[5],dateData[6],0);
	}
	// if it's a user feed format
	else {
		// $wday,$mon,$mday,$hour,$min,$sec,$offset,$year
		dateObj.setUTCFullYear(dateData[7],TB_monthNumber[""+dateData[1]]-1,dateData[2]);
		dateObj.setUTCHours(dateData[3],dateData[4],dateData[5],0);
	}

	return dateObj;
}

function TB_verbalTime(dateObj) {

 var j,
	now = new Date(),
	difference,
	verbalTime,
	template;
	
	if (now.getTime() > dateObj.getTime()) {
		difference = Math.round((now.getTime() - dateObj.getTime()) / 1000);
		template = TB_labels.time_past;
	}
	else {
		difference = Math.round((dateObj.getTime() - now.getTime()) / 1000);
		template = TB_labels.time_future;
	}
		

 for(j = 0; difference >= TB_timePeriodLengths[j] && j < TB_timePeriodLengths.length; j++) {
     difference = difference / TB_timePeriodLengths[j];
 }
 difference = Math.round(difference);

 verbalTime = TB_timePeriods[j];
 if (difference != 1) {
     verbalTime = TB_timePeriodsPlural[j];;
 }

 return template.format(difference,verbalTime);
}

function TB_addLoadEvent(func) { 
	var oldonload = window.onload; 
	if (typeof window.onload != 'function') { 
	    window.onload = func; 
	} else { 
	    window.onload = function() { 
	      oldonload(); 
	      func(); 
	    }
	} 
}

//function to get the size of an object
function TB_getObjectSize(obj) {
	var size = 0, key;
	for (key in obj) {
	     if (obj.hasOwnProperty(key)) size++;
	}
	return size;
}

//function to dedupe array
function TB_getUniqueElements(arr) {
	 var uniques = [], i, val;
	 for(i=arr.length;i--;){
	     val = arr[i];  
	     if(jQuery.inArray( val, uniques )===-1){
	         uniques.unshift(val);
	     }
	 }
	 return uniques;
}

//Add format function to enable templates
String.prototype.format = function() {
	var s = this, i = arguments.length;

	 while (i--) {
	     s = s.replace(new RegExp('\\{' + i + '\\}', 'gm'), arguments[i]);
	 }
	 return s;
};

/**
 * @author http://kirill-novitchenko.com
 */

var TB_version = '3.3.9',	// Plugin version 
TB_rateLimitData,
TB_tmp,
TB_mode = 'widget',
TB_started = false,
TB_tweetsToCache = new Object(),
TB_allSources = new Array(),
jQnc = jQuery,
TB_sourceCounts = new Array(),
TB_sourceNames = new Array(),
TB_seenTweets = new Array();

// initialize each widget
function TB_start() {

	// prevent initializing twice
	if (TB_started) {
		return;
	}
	else {
		TB_started = true;
	}
	
	// check to make sure config is included
	if (typeof(TB_config) == 'undefined') {
		TB_showMessage(null,'noconf',TB_labels.no_config,true);
		return;
	}
		
	// process widget configuration
	TB_config.widgets = new Object();
	jQuery.each(jQuery('form.tb-widget-configuration'),function(i,obj){

		var widgetConfId = obj.id,
		widgetId,
		widgetHTML,
		tweetHTML,
		needWidgetHTML = false;
		
		// if there is widget HTML div following the form we don't need to build HTML
		if (jQuery('#'+obj.id).next().length > 0) { 
			if (jQuery('#' + obj.id).next().attr('id') != '') {
				if (jQuery('#' + obj.id).next().attr('id').indexOf('-mc') > 0) {
					widgetId = widgetConfId.substr(0, widgetConfId.length - 2);
				}
				else {
					needWidgetHTML = true;
					widgetId = widgetConfId;
				}
			}
			else {
				needWidgetHTML = true;
				widgetId = widgetConfId;
			}
		}
		// if it's just a form -> assume that's post/page body content
		else {
			needWidgetHTML = true;
			widgetId = widgetConfId;
		}
		
		TB_config.widgets[widgetId] = new Object;
		
		// set all properties
		jQuery.each(jQuery('#'+widgetConfId).find('input'),function(j,property) {
			TB_config.widgets[widgetId][property.name] = property.value;
		});
		
		if (typeof(TB_config.widgets[widgetId].sources) != 'undefined') {
			TB_allSources = TB_allSources.concat(TB_config.widgets[widgetId].sources.split(','));
		}
		
		if (needWidgetHTML) {
			// add widget HTML
			widgetHTML = '<div id="' + widgetId + '-mc"><div class="tb_header">' +
//				'<img class="tb_twitterlogo" src="' + TB_pluginPath + '/img/twitter-logo.png" alt="' + TB_labels.twitter_logo + '" />' +
				'<div class="tb_tools" style="background-image:url(' + TB_pluginPath + '/img/bg_sm.png)">' +
//				'<a class="tb_infolink" href="http://kirill-novitchenko.com" title="' + TB_labels.kino + '" style="background-image:url(' + TB_pluginPath + '/img/info-kino.png)"> </a>' +
				'<a class="tb_refreshlink" href="javascript:TB_blend(\'' + widgetId + '\');" title="' + TB_labels.refresh + '"><img src="' + TB_pluginPath + '/img/ajax-refresh-icon.gif" alt="' + TB_labels.refresh + '" /></a></div></div>';
			if (TB_config.general_seo_tweets_googleoff) {
				widgetHTML += '<!--googleoff: index--><div class="tb_tweetlist"></div><!--googleon: index-->';
			}
			else {
				widgetHTML += '<div class="tb_tweetlist"></div>';
			}
			widgetHTML += '<div class="tb_footer">';
			if (!TB_config.archive_is_disabled) {
				if (TB_config.widgets[widgetId].viewMoreUrl) {
					widgetHTML += '<a class="tb_archivelink" href="' + TB_config.widgets[widgetId].viewMoreUrl + '">view more &raquo;</a>';
				}
				else if (TB_config.default_view_more_url) {
					widgetHTML += '<a class="tb_archivelink" href="' + TB_config.default_view_more_url + '">view more &raquo;</a>';
				}
			}
			widgetHTML += '</div></div>';
			jQuery('#'+obj.id).after(widgetHTML);
		}
	});

	// if there are no widgets on the page - no need to continue
	if (TB_getObjectSize(TB_config.widgets) < 1) {
		return;
	}
	
	// de-dupe list of all sources
	TB_allSources = TB_getUniqueElements(TB_allSources);
	
	jQuery('div.tb_tools').css('background-image','url(' + TB_pluginPath + '/img/bg.png)').width(56);
	jQuery('a.tb_infolink').css('display','inline').css('margin-right','11px');
	
	// make sure plugins are available
	if (typeof(jQuery.toJSON) == 'undefined' && typeof(jQnc.toJSON) == 'function') {
		jQuery.toJSON = jQnc.toJSON;
	}
	if (typeof(jQuery.jsonp) == 'undefined' && typeof(jQnc.jsonp) == 'function') {
		jQuery.jsonp = jQnc.jsonp;
	}

	// if there is no archive page, hide view more links
	if (!TB_config.default_view_more_url) {
		jQuery('a.defaultUrl').hide();
	}
	
	// get config options and blend
	if (typeof(TB_config) != 'undefined') {
		
		// if admin turned on re-route
		if (TB_config['advanced_reroute_on']) {
			TB_config['rate_limit_url'] = {
				'url': TB_pluginPath + '/ws.php?action=rate_limit_status',
				'dtype': 'json'
			};
		}
		// else check limit for the user's PC
		else {
			TB_config['rate_limit_url'] = {
				'url': 'http://twitter.com/account/rate_limit_status.json',
				'dtype': 'jsonp'
			};
		}
		
		// for each widget on the page
		for (widgetId in TB_config.widgets) {
			
			if (typeof(TB_config.widgets[widgetId].sources) == 'undefined' || TB_config.widgets[widgetId].sources == '') {
				TB_showMessage(widgetId,'nosrc',TB_labels.no_sources, true);
			}
			else {
	
				// create info box shown when Twitter logo is clicked
				TB_initInfoBox(widgetId);
				
				// create all the urls for refresh calls
				TB_makeAjaxURLs(widgetId);			
	
				// update values to reflect cache use if there are divs with tweets already
				TB_config.widgets[widgetId]['minTweetId'] = 0;
				TB_config.widgets[widgetId]['maxTweetId'] = 0;
				if (jQuery('#'+widgetId + '-mc > div.tb_tweetlist > div.tb_tweet').size() > 0) {
					if (TB_tmp = jQuery('#'+widgetId + '-mc > div.tb_tweetlist > div:last').attr('id')) {
						TB_config.widgets[widgetId]['minTweetId'] = TB_tmp;
					}
					if (TB_tmp = jQuery('#'+widgetId + '-mc > div.tb_tweetlist > div:first').attr('id')) {
						TB_config.widgets[widgetId]['maxTweetId'] = TB_tmp;
					}
				}
				TB_config.widgets[widgetId]['tweetsShown'] = jQuery('#'+widgetId + '-mc > div.tb_tweetlist').children('div').size();
				
				// wire mouse overs to existing tweets
				jQuery.each(jQuery('#' + widgetId + '-mc > div.tb_tweetlist').children('div'),function(i,obj){ TB_wireMouseOver(obj.id); });

					// wire target="_blank" on links
					jQuery('a.tb_photo, .tb_author a, .tb_msg a, .tweet-tools a, .tb_infolink').click(function(){
						this.target = "_blank";
					});
		
				// add automatic refresh
				if (parseInt(TB_config.widgets[widgetId].refreshRate) > 1) {
					setInterval('TB_blend(\''+widgetId+'\');',parseInt(TB_config.widgets[widgetId].refreshRate) * 1000);
				}
				
				// if we need to refresh once or 
				// if there are no tweets shown from cache
				// or if there are less tweets then needed
				// then blend right away
				if (parseInt(TB_config.widgets[widgetId].refreshRate) == 1 || TB_config.widgets[widgetId].tweetsShown < TB_config.widgets[widgetId].tweetsNum) {
					TB_blend(widgetId);
				}
			}
		}
	}
	else {
		TB_showMessage(null,'noconf',TB_labels.no_global_config,true);
	
		// disable refresh
		jQuery('a.tb_refreshlink').remove();
		jQuery('div.tb_tools').css('background-image','url(' + TB_pluginPath + '/img/bg_sm.png)').width(28);
	}
}

// form Twitter API queries
function TB_makeAjaxURLs(widgetId) {
	var TB_searchTerms = new Array(),
	TB_screenNameQueries = new Array(),
	TB_screenNames = new Array(),
	screenName = '',
	modifier = '',
	colonPos,
	pipePos;
	
	TB_config.widgets[widgetId]['ajaxURLs'] = new Array();

	jQuery.each(TB_config.widgets[widgetId].sources.split(','),function(i,src) {

		// remove spaces
		src = jQuery.trim(src);
		
		// if it's a private screen name
		if (src.charAt(0) == '!') {

			// if there is an alias
			if ((colonPos = src.indexOf(':')) > 0) {
				// split into screen name and optional string name/title
				screenName = src.substr(2, colonPos - 1);
				TB_sourceNames[screenName.toLowerCase()] = src.substr(colonPos + 1);
				src = src.substr(1, colonPos - 1);
			}
			else {
				screenName = src.substr(2);
			}

			// if we are serving only favorites
			if (TB_config.widgets[widgetId].favoritesOnly) {
				TB_addAjaxUrl(widgetId,'favorites',{'screen_name':screenName},src,1,null);
			}
			// if we are not using Search API
			else if (TB_config.advanced_no_search_api) {
				TB_addAjaxUrl(widgetId,'user_timeline',{'screen_name':screenName},src,1,null);
			}
			else {
				TB_addAjaxUrl(widgetId,'search',{'from':screenName},src,1,null);
			}
		}
		// if it's a public screen name
		else if (src.charAt(0) == '@' && src.indexOf('/') == -1) {
			
			// if we are serving only favorites
			if (TB_config.widgets[widgetId].favoritesOnly) {
				// if there is an alias
				if ((colonPos = src.indexOf(':')) > 0) {
					// split into screen name and optional string name/title
					screenName = src.substr(1, colonPos - 1);
					TB_sourceNames[screenName.toLowerCase()] = src.substr(colonPos + 1);
					src = src.substr(0, colonPos);
				}
				else {
					screenName = src.substr(1);
				}

				TB_addAjaxUrl(widgetId,'favorites',{'screen_name':screenName},src,0,null);
			}
			// if it includes modifiers, use a one-off URL
			else if ((pipePos = src.indexOf('|')) > 1) {
				// if we had an alias for that name
				if ((colonPos = src.indexOf(':')) > 0) {
					// split into screen name and optional string name/title
					screenName = src.substr(1,pipePos-1);
					modifier = src.substr(pipePos+1,(colonPos - pipePos - 1));
					TB_sourceNames[screenName.toLowerCase()] = src.substr(colonPos + 1);
					src = src.substr(0, colonPos);
				}
				else {
					screenName = src.substr(1,pipePos-1);
					modifier = src.substr(pipePos+1);
				}
				
				// if modifier is a hashtag
				if (modifier.charAt(0) == '#') {
					TB_addAjaxUrl(widgetId,'search',{'from':screenName,'tag':modifier.substr(1)},src,0,modifier);
				}
				// else modifier is just a keyword
				else {
					TB_addAjaxUrl(widgetId,'search',{'from':screenName,'ors':modifier},src,0,modifier);
				}
			}
			else {

				// if we had an alias for that name
				if ((colonPos = src.indexOf(':')) > 0) {
					// split into screen name and optional string name/title
					screenName = src.substr(1,colonPos-1);
					TB_sourceNames[screenName.toLowerCase()] = src.substr(colonPos + 1);
					src = src.substr(0, colonPos);
				}
				else {
					screenName = src.substr(1);
				}
				
				// if we are not using Search API
				if (TB_config.advanced_no_search_api) {
					TB_addAjaxUrl(widgetId,'user_timeline',{'screen_name':screenName},src,0,null);
				}
				// else, group with other screen names
				else {
					// check to make sure we are not over the query length limit
					if (escape(TB_screenNameQueries.join(' OR ')).length + src.length > 140) {
						TB_addAjaxUrl(widgetId,'search',{'q':TB_screenNameQueries.join(' OR ')},escape('@'+TB_screenNames.join(',@')),0,null);
						TB_screenNames = new Array();
						TB_screenNameQueries = new Array();
					}
					TB_screenNames.push(screenName);
					if (TB_config.filter_hide_mentions) {
						TB_screenNameQueries.push('from:' + screenName);
					}
					else {
						TB_screenNameQueries.push(src + ' OR from:' + screenName);
					}
				}
			}
		}
		// if it's a list
		else if (src.charAt(0) == '@' && src.indexOf('/') > 1) {
			
			// if we have a modifier
			if ((pipePos = src.indexOf('|')) > 1) {
				
				TB_addAjaxUrl(widgetId, 'list_timeline', {
					'user': src.substr(1, src.indexOf('/') - 1),
					'list': src.substr(src.indexOf('/') + 1, pipePos - src.indexOf('/') - 1)
				}, src, 0, src.substr(pipePos+1));
			}
			// if it's just a regular list
			else {
				TB_addAjaxUrl(widgetId, 'list_timeline', {
					'user': src.substr(1, src.indexOf('/') - 1),
					'list': src.substr(src.indexOf('/') + 1)
				}, src, 0, null);
			}
		}
		// else it's a hash or keyword 
		else if (src != '') {

 			// if it's a multi-word keyword give it a dedicated ajax call
			if (src.indexOf(' ') > 0) {
				// if it's not in quotes already then add them
				if (src.charAt(0) != '"') {
					src = '"' + src + '"';
				}
			}

			// check to make sure we are not over the query length limit
			if (TB_searchTerms.join(' OR ').length + src.length > 140) {
				TB_addAjaxUrl(widgetId,'search',{'q':TB_searchTerms.join(' OR ')},TB_searchTerms.join(','),0,null);
				TB_searchTerms = new Array();
			}
			TB_searchTerms.push(src);
		}
	});
	
	// if there are terms that are not part of a query - add another query
	if (TB_searchTerms.length > 0) {
		TB_addAjaxUrl(widgetId,'search',{'q':TB_searchTerms.join(' OR ')},TB_searchTerms.join(','),0,null);
	}
	
	// if there are screenNames - join them into a single query
	if (TB_screenNames.length > 0) {
		TB_addAjaxUrl(widgetId,'search',{'q':TB_screenNameQueries.join(' OR ')},encodeURI('@'+TB_screenNames.join(',@')),0,null);
	}
}

function TB_addAjaxUrl(widgetId,actionType,queryData,src,isPrivateSrc,modifier) {
	
	var url;
	
	// check language filter	
	if (typeof(TB_config['filter_lang']) != 'undefined' && TB_config.filter_lang.length == 2) {
		queryData.lang = TB_config.filter_lang;
	}
	else {
		queryData.lang = 'all';
	}
			
	// check private
	if (isPrivateSrc) {
		queryData.private = 1;
	}

	if (actionType == 'search' && (TB_config.advanced_reroute_on || TB_config.reached_api_limit || isPrivateSrc)) {
		queryData.action = actionType;
		TB_config.widgets[widgetId]['ajaxURLs'].push({
			'url':TB_pluginPath + '/ws.php',
			'data':queryData,
			'source':src,
			'privateSrc':isPrivateSrc,
			'dtype':'json',
			'modifier':modifier
		});
	}
	else if (actionType == 'search') {
		queryData.rpp = TB_config.widgets[widgetId]['tweetsNum'] * 2;
		TB_config.widgets[widgetId]['ajaxURLs'].push({
			'url': 'http://search.twitter.com/search.json',
			'data':queryData,
			'source':src,
			'privateSrc':0,
			'dtype':'jsonp',
			'modifier':modifier
		});
	}
	else if (actionType == 'list_timeline' && (TB_config.advanced_reroute_on || TB_config.reached_api_limit)) {
		queryData.action = actionType;

		// remove unneeded params
		delete queryData.user;
		delete queryData.list;
		delete queryData.nots;
		delete queryData.lang;

		TB_config.widgets[widgetId]['ajaxURLs'].push({
			'url':TB_pluginPath + '/ws.php',
			'data':queryData,
			'source':src,
			'privateSrc':0,
			'dtype':'json',
			'modifier':modifier
		});
	}
	else if (actionType == 'list_timeline'){
		url = 'http://api.twitter.com/1/' + queryData.user + '/lists/' + queryData.list + '/statuses.json';

		// remove unneeded params
		delete queryData.user;
		delete queryData.list;
		delete queryData.nots;
		delete queryData.lang;
		
		queryData.per_page = TB_config.widgets[widgetId]['tweetsNum']  * 2;
		TB_config.widgets[widgetId]['ajaxURLs'].push({
			'url':url,
			'data':queryData,
			'source':src,
			'privateSrc':0,
			'dtype':'jsonp',
			'modifier':modifier
		});
	}
	else if (actionType == 'user_timeline' && (TB_config.advanced_reroute_on || TB_config.reached_api_limit || isPrivateSrc)) {
		queryData.action = actionType;
		TB_config.widgets[widgetId]['ajaxURLs'].push({
			'url':TB_pluginPath + '/ws.php',
			'data':queryData,
			'source':src,
			'privateSrc':0,
			'dtype':'json',
			'modifier':modifier
		});
	}
	else if (actionType == 'user_timeline') {
		TB_config.widgets[widgetId]['ajaxURLs'].push({
			'data':queryData,
			'url': 'http://api.twitter.com/1/statuses/user_timeline.json',
			'source':src,
			'private':0,
			'dtype':'jsonp',
			'modifier':modifier
		});
	}
	else if (actionType == 'favorites' && (TB_config.advanced_reroute_on || TB_config.reached_api_limit || isPrivateSrc)) {
		queryData.action = actionType;
		TB_config.widgets[widgetId]['ajaxURLs'].push({
			'data':queryData,
			'url':TB_pluginPath + '/ws.php',
			'source':src,
			'privateSrc':0,
			'dtype':'json',
			'modifier':modifier
		});
	}
	else if (actionType == 'favorites') {
		TB_config.widgets[widgetId]['ajaxURLs'].push({
			'data':{},	// favorites does not support any params
			'url': 'http://api.twitter.com/1/favorites/' + queryData.screen_name + '.json',
			'source':src,
			'private':0,
			'dtype':'jsonp',
			'modifier':modifier
		});
	}
}

function TB_initInfoBox(widgetId) {
	// create HTML for sources
	TB_config.widgets[widgetId].sourcesHTML = '';
	TB_config.widgets[widgetId].sourcesCount = 0;
	jQuery.each(TB_config.widgets[widgetId].sources.split(','),function(i,src) {
		// if there is a private source mark - strip it
		if (src.charAt(0) == '!') {
		 	src = src.substr(1);
		}
		// if there is an alias - strip it
		if ((colonPos = src.indexOf(':')) > 0) {
			src = src.substr(0, colonPos);
		}
		// if there is a modifier - strip it
		if ((pipePos = src.indexOf('|')) > 0) {
			src = src.substr(0, pipePos);
		}
		
		
		TB_config.widgets[widgetId].sourcesHTML += '<a href="';
		if (src.charAt(0) == '@') {
		 	TB_config.widgets[widgetId].sourcesHTML += 'http://twitter.com/' + src.substr(1);
		}
		else {
		 	TB_config.widgets[widgetId].sourcesHTML += 'http://search.twitter.com/search?q=' + escape(src);
		}
		TB_config.widgets[widgetId].sourcesHTML += '">' + src + '</a> ';
		TB_config.widgets[widgetId].sourcesCount++;
	});		
	
	// add action to twitter logo
	jQuery('#' + widgetId + '-mc').children('div.tb_header').children('img.tb_twitterlogo').click(function(){
		TB_showMessage(widgetId,'info',TB_labels.version_msg.format(TB_version,TB_config.widgets[widgetId].sourcesHTML),false);
	});	
}

function TB_blend(widgetId) {

	// show loading indicator
	TB_showLoader(widgetId);

	// if not using cache/server then check limit for user viwing the page
	if (!TB_config.advanced_reroute_on && !TB_config.reached_api_limit) {
		jQuery.ajax({
			url: TB_config.rate_limit_url.url,
			dataType: TB_config.rate_limit_url.dtype,
			success: function(json){
				// if can't get the limit or reached it
				if (json.error || json.remaining_hits < TB_config.widgets[widgetId].ajaxURLs.length) {

					TB_config['reached_api_limit'] = true;
					
					// if cache is not disabled, reroute traffic through server
					if (!TB_config.advanced_disable_cache) {
						// switch back to normal mode once limit has been reset
						var wait = 1000 * 60 * 5,	// by default, try again in 5 minutes
						now = new Date(),
						dateObj;
						// if we have actual reset time, use it
						if (json.reset_time) {
							dateObj = TB_str2date(json.reset_time);
							wait = Math.round(dateObj.getTime() - now.getTime());
						}
						setTimeout("TB_config.reached_api_limit=false;TB_makeAjaxURLs('"+widgetId+"');TB_blend('"+widgetId+"');",wait);

						// regen URLs so they go to server and get tweets
						TB_makeAjaxURLs(widgetId);
						TB_getTweets(widgetId);
					}
					// if we reached limit, don't have cache turned on, and need to tell user - show message
					else if (TB_config.advanced_show_limit_msg) {
						TB_showMessage(widgetId,'limit',TB_labels.limit_msg + '. ' + TB_labels.limit_reset.format(TB_verbalTime(TB_str2date(json.reset_time))), false);
					}
				}
				// else, get new feeds
				else {
					TB_getTweets(widgetId);
				}
			},
			error: function(){
				TB_getTweets(widgetId);
			}
		});
	}
	else {
		TB_getTweets(widgetId);
	}
}

function TB_checkComplete(widgetId) {
	
	if (TB_config.widgets[widgetId].urlsDone == TB_config.widgets[widgetId].ajaxURLs.length) {

		// hide loading message
		TB_hideLoader(widgetId);

		// if nothing added after we are through all sources let user know
		if(jQuery('#' + widgetId + '-mc > div.tb_tweetlist').children('div').size() == 0) {
			// show no tweets message
			
			/* FUTURE: include location in message
			if (typeof(TB_config['filter_location_name']) != 'undefined' && TB_config.filter_location_name.length > 0) {
				TB_showMessage(widgetId, 'notweets', 'No tweets found for ' + TB_config.widgets[widgetId].sourcesHTML + '(within ' + TB_config.filter_location_dist + TB_config.filter_location_dist_units + ' of ' + TB_config.filter_location_name + ')', true);
			}
			else {
			*/
				TB_showMessage(widgetId, 'notweets', TB_labels.no_tweets_msg.format(TB_config.widgets[widgetId].sourcesHTML), true);
		}
		else {
			TB_hideMessage(widgetId,'notweets');
			
			// store cache
			if((typeof(TB_config.advanced_disable_cache) != 'undefined' && !TB_config.advanced_disable_cache)) {
				TB_cacheNewTweets();	
			}
		}
	}
}
	
function TB_getTweets(widgetId) {
	
	TB_config.widgets[widgetId]['urlsDone'] = 0;
	
	// iterate over AJAX URLs
	jQuery.each(TB_config.widgets[widgetId].ajaxURLs,function(i,urlInfo) {
		
		// special jsonp use case to ensure proper error handling
		if (urlInfo.dtype == 'jsonp' && typeof(jQuery.jsonp) != 'undefined') {
			jQuery.jsonp({
				data:urlInfo.data,
				callbackParameter: "callback",
				url: urlInfo.url,
				timeout: 2000,
				success: function (json) {
					// if we had valid JSON but with error
					if (json.error) {
						// if we reached the API limit
						if (json.error.indexOf('Rate limit exceeded') == 0) {
							TB_config['reached_api_limit'] = true;
						}
						TB_config.widgets[widgetId].urlsDone++;
						TB_checkComplete(widgetId);
					}
					else {
						TB_addTweets(widgetId,json,urlInfo);
					}
				},
				error: function(jqXHR, textStatus, errorThrown) {
					TB_config.widgets[widgetId].urlsDone++;
					TB_checkComplete(widgetId);
				}
			});
		}
		else {
			jQuery.ajax({
				data:urlInfo.data,
				dataType: urlInfo.dtype,
				url: urlInfo.url,
				timeout: 2000,
				success: function (json) {
					// if we had valid JSON but with error
					if (json.error) {
						// if we reached the API limit
						if (json.error.indexOf('Rate limit exceeded') == 0) {
							TB_config['reached_api_limit'] = true;
						}
						TB_config.widgets[widgetId].urlsDone++;
						TB_checkComplete(widgetId);
					}
					else {
						TB_addTweets(widgetId,json,urlInfo);
					}
				},
				error: function(jqXHR, textStatus, errorThrown) {
					TB_config.widgets[widgetId].urlsDone++;
					TB_checkComplete(widgetId);
				}
			});
		}
	});
}

function TB_cacheNewTweets() {

	if (TB_getObjectSize(TB_tweetsToCache) > 0) {

		if (typeof(jQuery.toJSON) == 'undefined' && typeof(jQnc.toJSON) == 'function') {
			jQuery.toJSON = jQnc.toJSON;
		}
		
		jQuery.ajax({
			url: 		TB_pluginPath + '/ws.php?action=cache_data',
			type:		'POST',
			dataType: 	'json',
			data: ({
				tweets: jQuery.toJSON(TB_tweetsToCache)
			}),
			success: function(json){
				if (!json.error) {
					TB_tweetsToCache = new Object();
				}
			}
		});
	}
}

function TB_addTweets(widgetId,jsonData,urlInfo) {

	var i,
	tweets = jsonData,
	originalTweet,
	isNewTweet = false,
	tweetUser,
	badStrings,
	tb_tweet,
	tweetExists;
	
	if (typeof(jsonData.results) != 'undefined') {
		tweets = jsonData.results;
	}

	jQuery.each(tweets,function(i,tweetJson) {

		tb_tweet = new TB_tweet(tweetJson);
		tb_tweet.modifier = urlInfo.modifier;
		
		// make sure it's OK to show
		if (!tb_tweet.isOKtoShow())	{
			return true;
		}

		tb_tweet.selectRelevantSources(urlInfo.source);
				
		isNewTweet = false;

		// if this tweet already in the set, skip it
		tweetExists = false;
		jQuery('div.tb_tweet').each(function() {
			if (tb_tweet.isSameId(this.id)) {
				tweetExists = true;
			}
		});
		if (tweetExists) {
			return true;
		}
		
		
		// if this is the first tweet, just add it and set it to be both min and max
		if (TB_config.widgets[widgetId].tweetsShown == 0) {
			TB_config.widgets[widgetId].tweetsShown++;
			TB_config.widgets[widgetId].minTweetId = tb_tweet.divId;
			TB_config.widgets[widgetId].maxTweetId = tb_tweet.divId;			

			// add at the end
			jQuery('#'+widgetId+'-mc > div.tb_tweetlist').append(tb_tweet.getHTML());
			
			isNewTweet = true;
		}
		// if tweet older than the oldest
		else if (tb_tweet.isOlderThan(TB_config.widgets[widgetId].minTweetId)) {
			// if we are at max already, no need to work through the rest of this set as the rest will be older
			if (TB_config.widgets[widgetId].tweetsShown >= TB_config.widgets[widgetId].tweetsNum) {
				return false;
			}
			else {
				TB_config.widgets[widgetId].tweetsShown++;

				// add at the end
				jQuery('#'+widgetId+'-mc > div.tb_tweetlist').append(tb_tweet.getHTML());

				// make it the oldest
				TB_config.widgets[widgetId].minTweetId = tb_tweet.divId;
				
				// if we have only one tweet then make it the newest as well
				if (TB_config.widgets[widgetId].tweetsNum == 1) {
					TB_config.widgets[widgetId].maxTweetId = tb_tweet.divId;
				}
				
				isNewTweet = true;
			}
		}
		// if tweet is newer than the newest
		else if (tb_tweet.isNewerThan(TB_config.widgets[widgetId].maxTweetId)) {
			// if we are at max already, remove bottom tweet
			TB_enforceLimit(widgetId);
			
			// add in the beginning
			jQuery('#'+widgetId+'-mc > div.tb_tweetlist').prepend(tb_tweet.getHTML());
			TB_config.widgets[widgetId].tweetsShown++;

			// make it the newest
			TB_config.widgets[widgetId].maxTweetId = tb_tweet.divId;
			
			// if we have only one tweet then make it the oldest as well
			if (TB_config.widgets[widgetId].tweetsNum == 1) {
				TB_config.widgets[widgetId].minTweetId = tb_tweet.divId;
			}

			isNewTweet = true;
		}
		// if tweet is in the middle
		else {
			// if we are at max already, remove bottom tweet
			TB_enforceLimit(widgetId);

			// traverse currently shown tweets and insert in the appropriate spot
			var prevTweetId = TB_config.widgets[widgetId].maxTweetId,
			nextTweetId;
			jQuery('#'+widgetId+'-mc > div.tb_tweetlist > div.tb_tweet').each(function(i,nextTweet){
				nextTweetId = nextTweet.id;
				if (tb_tweet.isOlderThan(prevTweetId) && tb_tweet.isNewerThan(nextTweetId)) {
					jQuery('#'+prevTweetId).after(tb_tweet.getHTML());
					TB_config.widgets[widgetId].tweetsShown++;
					return false;
				}
				prevTweetId = nextTweetId;
			});
			
			// if got to here and tweet still not there, make it the last
			if (jQuery('#'+tb_tweet.divId).length <= 0) {
					jQuery('#'+TB_config.widgets[widgetId].minTweetId).after(tb_tweet.getHTML());
					TB_config.widgets[widgetId].minTweetId = tb_tweet.divId;
					// if we have only one tweet then make it the newest as well
					if (TB_config.widgets[widgetId].tweetsNum == 1) {
						TB_config.widgets[widgetId].maxTweetId = tb_tweet.divId;
					}
					TB_config.widgets[widgetId].tweetsShown++;
			}
			
			isNewTweet = true;
		}

		// if new tweet and cache is on, queue it for caching
		if (isNewTweet && (typeof(TB_config.advanced_disable_cache) != 'undefined' && !TB_config.advanced_disable_cache)) {
			TB_tweetsToCache[tb_tweet.divId] = {
				"s" :	tb_tweet.sources,
				"p" :	urlInfo.privateSrc,
				"t" :	tb_tweet.jsonCode
			};
		}
		
		// wire mouseover action items
        TB_wireMouseOver(tb_tweet.divId);
		
		// if filtering out same tweets - add text to seen tweets
		if (TB_config.filter_hide_same_text) {
			TB_seenTweets.push(tb_tweet.jsonCode.text);
		}
	});
	
	TB_config.widgets[widgetId].urlsDone++;
	
	// wire target="_blank" on links
	jQuery('a.tb_photo, .tb_author a, .tb_msg a, .tweet-tools a, .tb_infolink').click(function(){
		this.target = "_blank";
	});
	
	TB_checkComplete(widgetId);
}

function TB_wireMouseOver(tweetId) {
	// wire mouseover action items
    if(TB_config[TB_mode + '_show_reply_link'] || TB_config[TB_mode + '_show_follow_link']) {
		jQuery('#'+tweetId).hover(
		      function () {
				jQuery(this).find("div:last").slideDown()
		      }, 
		      function () {
		        jQuery(this).find("div:last").slideUp();
		      }
		);
	}		
}

function TB_enforceLimit(widgetId) {
	
	if (TB_config.widgets[widgetId].tweetsShown == TB_config.widgets[widgetId].tweetsNum) {
		var lastTweet = jQuery('#' + TB_config.widgets[widgetId].minTweetId),
		nextToLastTweet = lastTweet.prev('div.tb_tweet');
		
		// remove last tweet
		lastTweet.remove();
		TB_config.widgets[widgetId].tweetsShown--;
		
		// remove from cache queue as well if we planned to cache it
		delete TB_tweetsToCache[TB_config.widgets[widgetId].minTweetId];
		
		// if no tweets left, reset min and max and finish
		if (TB_config.widgets[widgetId].tweetsShown == 0) {
			TB_config.widgets[widgetId].minTweetId = 0;
			TB_config.widgets[widgetId].maxTweetId = 0;
			return;
		}
		else {
			// make next to last to be last now
			if(nextToLastTweet.length > 0) {
				TB_config.widgets[widgetId].minTweetId = nextToLastTweet.attr('id');
			}
		}
	}
}

function TB_showLoader(widgetId) {
	// if there are not tweets, show loading message
	if(TB_config.widgets[widgetId].tweetsShown == 0) {
		TB_showMessage(widgetId,'loading',TB_labels.loading_msg,true);
	}
	// show animated icon
	jQuery('#' + widgetId + '-mc > div.tb_header > div.tb_tools > a.tb_refreshlink > img').attr('src',TB_pluginPath + '/img/ajax-refresh.gif');
	jQuery('#' + widgetId + '-mc > div.tb_header > div.tb_tools > a.tb_refreshlink').addClass('loading');
}

function TB_hideLoader(widgetId) {
	// hide loading message
	TB_hideMessage(widgetId,'loading');

	// show static icon
	jQuery('#' + widgetId + '-mc > div.tb_header > div.tb_tools > a.tb_refreshlink > img').attr('src',TB_pluginPath + '/img/ajax-refresh-icon.gif');
	jQuery('#' + widgetId + '-mc > div.tb_header > div.tb_tools > a.tb_refreshlink').removeClass('loading');
}

function TB_showMessage(widgetId, messageId, msg, keepOnScreen){

	// if no widgetId is given -> show message in all widgets and ignore keepOnScreen
	if(!widgetId) {
		jQuery('div.tb_tweetlist').before('<div id="msg_' + messageId + '" class="tb_msg" style="display:none;">' + msg + '</div>');
		return;
	}
	
	// if it doesn't exist
	if (!jQuery('#' + widgetId + '-mc').children('#msg_' + messageId).length) {
		jQuery('#' + widgetId + '-mc').children('div.tb_tweetlist').before('<div id="msg_' + messageId + '" class="tb_msg" style="display:none;">' + msg + '</div>');
		jQuery('#' + widgetId + '-mc').children('#msg_' + messageId).slideDown();
		if (!keepOnScreen) {
			setTimeout('TB_hideMessage("' + widgetId + '","' + messageId + '")', 8000);
		}
	}
	// else if it's hidden
	else if (jQuery('#' + widgetId + '-mc').children('#msg_' + messageId).is(':hidden')) {
		jQuery('#' + widgetId + '-mc').children('#msg_' + messageId).slideDown();
	}
}

function TB_hideAllMessages() {
	jQuery('div.tb_msg').slideUp(1000,function(){jQuery('div.tb_msg').remove()});
}

function TB_hideMessage(widgetId,messageId) {
	jQuery('#' + widgetId + '-mc').children('#msg_' + messageId).slideUp(1000,function(){jQuery('#' + widgetId + '-mc').children('#msg_' + messageId).remove()});
}

// TB Class for tweets
function TB_tweet(tweetJson) {

	// raw JSON for the tweet
	this.jsonCode  = tweetJson;
		
	// tweetDate property
	this.tweetDate = TB_str2date(tweetJson.created_at);
	
	// author screen name property
	if (typeof(tweetJson.from_user) != 'undefined') {
		this.screenName = tweetJson.from_user;
	}
	else if (typeof(tweetJson.user.screen_name) != 'undefined') {
		this.screenName = tweetJson.user.screen_name;
	}
	
	// id of the tweet
	this.id = tweetJson.id_str;
		
	// sources relevant to this tweet
	this.sources = '';
	
	// process sources and select the ones relevant to this tweet
	this.selectRelevantSources = function(urlSources) {
		var allSources = new Array(),
		sourceScreenName = '@'+this.screenName,
		jsonCode = this.jsonCode;
		jQuery.each(urlSources.split(','),function(i,src) {
			if (sourceScreenName == src || jsonCode.text.indexOf(src) > 0){
				allSources.push(src);
			}
		});
		//  set property with relevant sources
		if (allSources.length > 0) {
			this.sources = allSources.join(',');
		}
		// just in case to make sure we don't have empty source
		else {
			this.sources = urlSources;
		}
	}
	
	/* setup template
	 * the following placeholders will be used
	 * {0} - tweet div id
	 * {1} - screen name
	 * {2} - profile image url
	 * {3} - tweet message
	 * {4} - tweet id as string
	 * {5} - date
	 * {6} - source as link
	 */
	// if user supplied a custom format - use it
	var template;
	if (TB_config.custom_template) {
		template = TB_config.custom_template;
	}
	// if not, use default format
	else {
		template = '<div id="{0}" class="tb_tweet">';
		if (TB_config['widget_show_photos']) {
			template += '<a class="tb_photo" rel="nofollow" href="http://twitter.com/{1}"><img src="{2}" alt="{1}"></a>';
		}
		if (TB_config['widget_show_user']) {
			template += '<span class="tb_author"><a rel="nofollow" href="http://twitter.com/{1}">{1}</a>: </span> ';
		}
		template += '<span class="tb_msg">{3}</span><br />';
		// start tweet footer with info
		if (!TB_config.general_seo_tweets_googleoff && TB_config.general_seo_footer_googleoff) {
			template += '<!--googleoff: index-->';
		}
		template += ' <span class="tb_tweet-info">';
		// show timestamp
		template += '<a rel="nofollow" href="http://twitter.com/{1}/statuses/{4}">{5}</a>';		
		// show source if requested
		if (TB_config['widget_show_source'] && tweetJson.source) {
			template += ' ' + TB_labels.from + ' {6}';
		}
		// end tweet footer
		template += '</span>';
		if (!TB_config.general_seo_tweets_googleoff && TB_config.general_seo_footer_googleoff) {
			template += '<!--googleon: index-->';
		}
		// add tweet tools
	   if (TB_config.widget_show_follow_link || TB_config.widget_show_reply_link) {
			template += '<div class="tweet-tools" style="display:none;">';
	        if (TB_config.widget_show_reply_link) {
	        	template += '<a rel="nofollow" href="http://twitter.com/home?status=@{1}%20&in_reply_to_status_id={4}&in_reply_to={1}">' + TB_labels.reply + '</a>';
	        }
	        if (TB_config.widget_show_follow_link && TB_config.widget_show_reply_link) {
	        	template += ' | ';
	        }
	        if (TB_config.widget_show_follow_link) {
	        	template += '<a rel="nofollow" href="http://twitter.com/{1}">' + TB_labels.follow + ' {1}</a>';
	        }
	        template += '</div>'; 
		}
		// end tweet	
		template += "</div>\n";
	}
	
	// creates unique div ID for this tweet
	getDivId = function(tweetDate,screenName,strId) {
		return 't-' + tweetDate.getTime() + '-' + screenName + '-' + strId;	
	}

	// div id of the tweet
	this.divId = getDivId(this.tweetDate,this.screenName,this.id);
	
	// makes HTML for each tweet
	this.getHTML = function() {
		/* the following placeholders will be used in the template
		 * {0} - tweet div id
		 * {1} - screen name
		 * {2} - profile image url
		 * {3} - tweet message
		 * {4} - tweet id as string
		 * {5} - date
		 * {6} - source as link
		 */
		var textHtml = this.jsonCode.text,
		nameHtml,
		imageUrl = '',
		dateHtml = '',
		sourceHtml = '';

		if (typeof(TB_sourceNames[this.screenName.toLowerCase()]) != 'undefined') {
			nameHtml = TB_sourceNames[this.screenName.toLowerCase()];
		}
		else {
			nameHtml =  this.screenName;
		}
				
		// link URLs
		if (TB_config.general_link_urls) {
			textHtml = textHtml.replace(/(https?:\/\/\S+)/gi, '<a rel="nofollow" href="$1">$1</a>');
		}
		// link screen names
		if (TB_config.general_link_screen_names) {
			textHtml = textHtml.replace(/\@([\w]+)/gi,'<a rel="nofollow" href="http://twitter.com/$1">@$1</a>'); 
		}
		// link hash tags
		if (TB_config.general_link_hash_tags) {
			textHtml = textHtml.replace(/\#(\S+)/gi,'<a rel="nofollow" href="http://search.twitter.com/search?q=%23$1">#$1</a>'); 
		}
		if (tweetJson.profile_image_url) {
			imageUrl = tweetJson.profile_image_url;
		}
		else {
			imageUrl = tweetJson.user.profile_image_url;
		}
		// make date
		if (TB_config.general_timestamp_format) {
			if (typeof(jQuery.PHPDate) != 'undefined') {
				dateHtml += jQuery.PHPDate(TB_config.general_timestamp_format,this.tweetDate);
			}
			else if (typeof(jQnc.PHPDate) != 'undefined') {
				dateHtml += jQnc.PHPDate(TB_config.general_timestamp_format,this.tweetDate);
			}
		}
		else {
			dateHtml += TB_verbalTime(this.tweetDate);
		} 
		// if source is url encoded -> decode
		if (tweetJson.source.indexOf('&lt;') >= 0) {
			sourceHtml += jQuery('<textarea/>').html(tweetJson.source).val();
		}
		// else use as is
		else {
			sourceHtml += tweetJson.source;
		}

		// return formatted string
		return template.format(this.divId,nameHtml,imageUrl,textHtml,tweetJson.id_str,dateHtml,sourceHtml);
	}
	
	this.isNewerThan = function(TB_tweetId) {
		var tweetIdParts,
		ourTimeStamp,
		otherTweetTimeStamp,
		otherTweetScreenName;
		
		// if other tweet's ID is not defined - assume we are newer
		if (typeof(TB_tweetId) == 'undefined') {
			return true;
		}
		// if it's some weird format - assume we are newer
		else if (TB_tweetId.indexOf('-') <= 0) {
			return true;
		}
		// else, prepare for real comparisons
		else {
			tweetIdParts = TB_tweetId.split('-');
			otherTweetTimeStamp = tweetIdParts[1];
			otherTweetScreenName = tweetIdParts[2];
			ourTimeStamp = this.tweetDate.getTime();
		}

		// if our timestamp is later
		if (ourTimeStamp > otherTweetTimeStamp) {
			return true;
		}
		// if timestamp is older
		else if (ourTimeStamp < otherTweetTimeStamp) {
			return false;
		}
		// if timestamps are same but users are different
		else if (this.screenName != otherTweetScreenName){
			return true;
		}
		// if timestamps are same and users same
		else {
			return false;
		}
	}
	
	this.isOlderThan = function(TB_tweetId) {
		return !this.isNewerThan(TB_tweetId);
	}
	
	
	this.isSameId = function(TB_tweetId) {

		var parts1, parts2;

		// if other tweet's ID is not defined - assume we are not the same
		if (typeof(TB_tweetId) == 'undefined') {
			return false;
		}
		// if it's some weird format - assume we are not the same
		else if (TB_tweetId.indexOf('-') <= 0) {
			return flase;
		}
		// else, do the real comparisons
		else {
			
			parts1 = TB_tweetId.split('-');
			parts2 = this.divId.split('-');
			
			// if one of the IDs is in old format - compare only the first two parts
			if (parts1.length == 3 || parts2.length == 3) {
				return (parts1[1] == parts2[1] && parts1[2] == parts2[2]);
			}
			// for new formats - compare all 3 parts
			else {
				return (parts1[1] == parts2[1] && parts1[2] == parts2[2] && parts1[3] == parts2[3]);
			}
		}
	}
	
	/* returns true if this tweet doesn't contain any words that are supposed to be filtered out 
	 * and if it's not supposed to be hidden due to other criteria
	 */
	this.isOKtoShow = function() {

		// if we have a modifier on that source and text contains it
		if (this.modifier) {
			if (this.modifier.length > 0 && this.jsonCode.text.indexOf(this.modifier) < 0) {
				return false;
			}
		}
		
		// if we don't show tweets with same content
		if (TB_config.filter_hide_same_text) {
			if (jQuery.inArray(this.jsonCode.text,TB_seenTweets) > 0) {
				return false;
			}
		}

		// if this is a reply
		if (this.jsonCode.in_reply_to_user_id || this.jsonCode.to_user_id) {
			// if we don't show replies
			if (TB_config.filter_hide_replies) {
				return false;
			}
		}
		// else, if it's not a reply but we are showing only replies
		else if (TB_config.filter_hide_not_replies) {
			return false;
		}

		// if there are filtered words and the tweet text matches any of them - skip this tweet
		if (typeof(TB_config['filter_bad_strings']) != 'undefined' && TB_config.filter_bad_strings.length > 0) {
			badStrings = TB_config.filter_bad_strings.split(',');
			for (i = 0; i < badStrings.length; i++) {
				if (this.jsonCode.text.indexOf(badStrings[i]) >= 0 || this.screenName.indexOf(badStrings[i]) >= 0) {
					return false;
				}
			}
		}
		
		// if throttling is ON and we are at max for this user, skip it
		if (TB_config.filter_limit_per_source > 0) {
			if (typeof(TB_sourceCounts[this.screenName]) != 'undefined' || TB_sourceCounts[this.screenName] == 0) {
			
				if (TB_sourceCounts[this.screenName] >= TB_config.filter_limit_per_source) {
					return false;
				}
				else {
					TB_sourceCounts[this.screenName]++;
				}
			}
			else {
				TB_sourceCounts[this.screenName] = 1;
				if (TB_config.widget_limit_per_source_time > 0) {
					setTimeout('TB_sourceCounts["' + this.screenName + '"]=0',TB_config.widget_limit_per_source_time * 1000);
				}
			}
		}
		
		return true;
	}
	
}

// initialize
TB_addLoadEvent(TB_start); jQuery(document).ready(TB_start);


