(function($) {

  $.fn.tweet = function(o){
    var s = $.extend({
      username: ["seaofclouds"],                // [string]   required, unless you want to display our tweets. :) it can be an array, just do ["username1","username2","etc"]
      list: null,                               // [string]   optional name of list belonging to username
      favorites: false,                         // [boolean]  display the user's favorites instead of his tweets
      avatar_size: null,                        // [integer]  height and width of avatar if displayed (48px max)
      count: 3,                                 // [integer]  how many tweets to display?
	  scroll_delay: 0,
      fetch: null,                              // [integer]  how many tweets to fetch via the API (set this higher than 'count' if using the 'filter' option)
      intro_text: null,                         // [string]   do you want text BEFORE your your tweets?
      outro_text: null,                         // [string]   do you want text AFTER your tweets?
      join_text:  null,                         // [string]   optional text in between date and tweet, try setting to "auto"
      auto_join_text_default: "i said,",        // [string]   auto text for non verb: "i said" bullocks
      auto_join_text_ed: "i",                   // [string]   auto text for past tense: "i" surfed
      auto_join_text_ing: "i am",               // [string]   auto tense for present tense: "i was" surfing
      auto_join_text_reply: "i replied to",     // [string]   auto tense for replies: "i replied to" @someone "with"
      auto_join_text_url: "i was looking at",   // [string]   auto tense for urls: "i was looking at" http:...
      loading_text: null,                       // [string]   optional loading text, displayed while tweets load
      query: null,                              // [string]   optional search query
      twitter_url: "twitter.com",               // [string]   custom twitter url, if any (apigee, etc.)
      twitter_api_url: "api.twitter.com",       // [string]   custom twitter api url, if any (apigee, etc.)
      twitter_search_url: "search.twitter.com", // [string]   custom twitter search url, if any (apigee, etc.)
      template: function(info) {                // [function] template used to construct each tweet <li>
        return info["avatar"] + info["time"] + info["join"] + info["text"];
      },
      comparator: function(tweet1, tweet2) {    // [function] comparator used to sort tweets (see Array.sort)
        return tweet2["tweet_time"] - tweet1["tweet_time"];
      },
      filter: function(tweet) {                 // [function] whether or not to include a particular tweet (be sure to also set 'fetch')
        return true;
      }
    }, o);
	
	if(isNaN(new Number(s.count)) || s.count < 1)s.count = 1;
	if(s.count > 100)s.count = 100;
	
	if(isNaN(new Number(s.scroll_delay)) || s.scroll_delay < 0)s.scroll_delay = 0;
	if(s.scroll_delay > 60)s.scroll_delay = 60;
	if(s.scroll_delay > 0 && s.scroll_delay < 1)s.scroll_delay = 1;

    $.fn.extend({
      linkUrl: function() {
        var returning = [];
        // See http://daringfireball.net/2010/07/improved_regex_for_matching_urls
        var regexp = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“”‘’]))/gi;
        this.each(function() {
          returning.push(this.replace(regexp,
                                      function(match) {
                                        var url = (/^[a-z]+:/i).test(match) ? match : "http://"+match;
                                        return "<a href=\""+url+"\">"+match+"</a>";
                                      }));
        });
        return $(returning);
      },
      linkUser: function() {
        var returning = [];
        var regexp = /[\@]+([A-Za-z0-9-_]+)/gi;
        this.each(function() {
          returning.push(this.replace(regexp,"<a href=\"http://"+s.twitter_url+"/$1\">@$1</a>"));        });
        return $(returning);
      },
      linkHash: function() {
        var returning = [];
        var regexp = /(?:^| )[\#]+([A-Za-z0-9-_]+)/gi;
        this.each(function() {
          returning.push(this.replace(regexp, ' <a href="http://'+s.twitter_search_url+'/search?q=&tag=$1&lang=all&from='+s.username.join("%2BOR%2B")+'">#$1</a>'));
        });
        return $(returning);
      },
      capAwesome: function() {
        var returning = [];
        this.each(function() {
          returning.push(this.replace(/\b(awesome)\b/gi, '<span class="awesome">$1</span>'));
        });
        return $(returning);
      },
      capEpic: function() {
        var returning = [];
        this.each(function() {
          returning.push(this.replace(/\b(epic)\b/gi, '<span class="epic">$1</span>'));
        });
        return $(returning);
      },
      makeHeart: function() {
        var returning = [];
        this.each(function() {
          returning.push(this.replace(/(&lt;)+[3]/gi, "<tt class='heart'>&#x2665;</tt>"));
        });
        return $(returning);
      }
    });

    function parse_date(date_str) {
      // The non-search twitter APIs return inconsistently-formatted dates, which Date.parse
      // cannot handle in IE. We therefore perform the following transformation:
      // "Wed Apr 29 08:53:31 +0000 2009" => "Wed, Apr 29 2009 08:53:31 +0000"
      return Date.parse(date_str.replace(/^([a-z]{3})( [a-z]{3} \d\d?)(.*)( \d{4})$/i, '$1,$2$4$3'));
    }

    function relative_time(date) {
      var relative_to = (arguments.length > 1) ? arguments[1] : new Date();
      var delta = parseInt((relative_to.getTime() - date) / 1000, 10);
      var r = '';
      if (delta < 60) {
        r = delta + ' seconds ago';
      } else if(delta < 120) {
        r = 'a minute ago';
      } else if(delta < (45*60)) {
        r = (parseInt(delta / 60, 10)).toString() + ' minutes ago';
      } else if(delta < (2*60*60)) {
        r = 'an hour ago';
      } else if(delta < (24*60*60)) {
        r = '' + (parseInt(delta / 3600, 10)).toString() + ' hours ago';
      } else if(delta < (48*60*60)) {
        r = 'a day ago';
      } else {
        r = (parseInt(delta / 86400, 10)).toString() + ' days ago';
      }
      return 'about ' + r;
    }

    function build_url(page) {
      var proto = ('https:' == document.location.protocol ? 'https:' : 'http:');
      var count = (s.fetch === null) ? s.count : s.fetch;
      if (s.list) {
        return proto+"//"+s.twitter_api_url+"/1/"+s.username[0]+"/lists/"+s.list+"/statuses.json?per_page="+count+"&callback=?";
      } else if (s.favorites) {
        return proto+"//"+s.twitter_api_url+"/favorites/"+s.username[0]+".json?count="+s.count+"&callback=?";
      } else if (s.query === null && s.username.length == 1) {
        return proto+'//'+s.twitter_api_url+'/1/statuses/user_timeline.json?screen_name='+s.username[0]+'&count='+count+'&page='+page+'&include_rts=1&callback=?';
      } else {
        var query = (s.query || 'from:'+s.username.join(' OR from:'));
        return proto+'//'+s.twitter_search_url+'/search.json?&q='+encodeURIComponent(query)+'&rpp='+count+'&page='+page+'&callback=?';
      }
    }

    return this.each(function(i, widget){
      if(typeof(s.username) == "string"){
        s.username = [s.username];
      }
	  
	  var lists = [];
	  var page = 1;
	  var time_interval = 30000;
	  
	  var delay_scroll = function(list, positions){
		var callee = arguments.callee;
		
		if(!list){
			if(lists.length < 1 || callee.scrolling === true)return;
			
			lists[0].css('visibility', 'visible');
			
			var positions = [];
		  	lists[0].children('li').each(function(){
			  	positions.push(-$(this).position().top);
		  	});
		  	positions.push(-lists[0].height());
			
			callee.scrolling = true;
		  	callee(lists[0], positions);
			return;
		}
				  
		list.animate({
			top: positions.shift()
		},{
			duration: 300,
			easing: 'linear', 
			queue: false,
			complete: function(){
				if(positions.length){
					window.setTimeout(function(){
						callee(list, positions);
					}, Math.round(s.scroll_delay*1000));
				}else{
					lists.shift();
					$(this).remove();
					callee.scrolling = false;
					callee();
					if(lists.length < 2) $(widget).trigger("load");
				}
			}
		});
	  };
	  delay_scroll.scrolling = false;
	  
	  var wrap = $(widget);
	  if(!wrap.hasClass('tweet')){
		  wrap.addClass('tweet');
	  }
	  var container = $('<div class="container" style="display:block;position:relative;overflow:hidden;width:' + wrap.width() + 'px;height:' + wrap.height() + 'px;">').appendTo(widget);

      $(widget).bind("load", function(){
		if(lists.length == 2)return;
		
		$.ajax({
			url: build_url(page),
			type: 'GET',
			dataType: 'json',
			error: function(){
				for(var i=0; i<lists.length; i++)lists[i].stop();
				page=1;
				window.setTimeout(function(){
					while(lists.length){
						var lst = lists.shift();
						lst.fadeOut('slow', function(){$(this).remove();});
					}
					$(widget).trigger("load"); 
				}, time_interval);
			},
			success: function(data, status){
				
				if(page < 1000/s.count)page += 1;else page=1;
				
            	var tweets = $.map(data.results || data, function(item){
            		var join_text = s.join_text;

            		if (s.join_text == "auto") {
              			if (item.text.match(/^(@([A-Za-z0-9-_]+)) .*/i)) {
                			join_text = s.auto_join_text_reply;
              			} else if (item.text.match(/(^\w+:\/\/[A-Za-z0-9-_]+\.[A-Za-z0-9-_:%&\?\/.=]+) .*/i)) {
                			join_text = s.auto_join_text_url;
              			} else if (item.text.match(/^((\w+ed)|just) .*/im)) {
                			join_text = s.auto_join_text_ed;
              			} else if (item.text.match(/^(\w*ing) .*/i)) {
                			join_text = s.auto_join_text_ing;
              			} else {
                			join_text = s.auto_join_text_default;
              			}
            		}

            		var screen_name = item.from_user || item.user.screen_name;
           			var source = item.source;
            		var user_url = "http://"+s.twitter_url+"/"+screen_name;
            		var avatar_size = s.avatar_size;
            		var avatar_url = item.profile_image_url || item.user.profile_image_url;
            		var tweet_url = "http://"+s.twitter_url+"/"+screen_name+"/statuses/"+item.id_str;
            		var tweet_time = parse_date(item.created_at);
            		var tweet_relative_time = relative_time(tweet_time);
            		var tweet_raw_text = item.text;
            		var tweet_text = $([tweet_raw_text]).linkUrl().linkUser().linkHash()[0];

            		var user = '<a class="tweet_user" href="'+user_url+'">'+screen_name+'</a>';
            		var join = ((s.join_text) ? ('<span class="tweet_join"> '+join_text+' </span>') : ' ');
            		var avatar = (avatar_size ?
                          ('<a class="tweet_avatar" href="'+user_url+'"><img src="'+avatar_url+
                           '" height="'+avatar_size+'" width="'+avatar_size+
                           '" alt="'+screen_name+'\'s avatar" title="'+screen_name+'\'s avatar" border="0"/></a>') : '');
            		var time = '<span class="tweet_time"><a href="'+tweet_url+'" title="view tweet on twitter">'+tweet_relative_time+'</a></span>';
            		var text = '<span class="tweet_text">'+$([tweet_text]).makeHeart().capAwesome().capEpic()[0]+ '</span>';

            		return { item: item, // For advanced users who want to dig out other info
                     	screen_name: screen_name,
                     	user_url: user_url,
                    	avatar_size: avatar_size,
                     	avatar_url: avatar_url,
                     	source: source,
                     	tweet_url: tweet_url,
                     	tweet_time: tweet_time,
                     	tweet_relative_time: tweet_relative_time,
                     	tweet_raw_text: tweet_raw_text,
                     	tweet_text: tweet_text,
                     	user: user,
                     	join: join,
                     	avatar: avatar,
                     	time: time,
                     	text: text
                   	};
          		});
		  
          		tweets = $.grep(tweets, s.filter).slice(0, s.count);
				
				if(tweets.length==0){
					page = 1;
					window.setTimeout(function(){
						$(widget).trigger("load"); 
					}, time_interval);
					return;
				}
				
		  		var list = $('<ul class="tweet_list" style="display:block;position:absolute;visibility:hidden;width:' + wrap.width() + 'px;">');
          		list.append($.map(tweets.sort(s.comparator), function(t){ 
					return "<li>" + s.template(t) + "</li>"; 
				}).join('')).
              		children('li:first').addClass('tweet_first').end().
              		children('li:odd').addClass('tweet_even').end().
              		children('li:even').addClass('tweet_odd');
			  
		  		list.appendTo(container);
		  		lists.push(list);
				
				var h = list.height() > 0 ? list.height() : container.height();
				var count_min = s.scroll_delay <= 0 ? Math.ceil((0.02 * time_interval - container.height())/h*tweets.length) : Math.ceil(time_interval/s.scroll_delay/1000) ;
				if(count_min < 1)count_min = 1;
				if(count_min > s.count)s.count = count_min;
		  
		  		var top = s.scroll_delay <= 0 && lists.length > 1 ? lists[0].position().top + lists[0].height() : container.height();
				var duration = Math.ceil((top + list.height())/0.02);
				if(duration < time_interval)duration = time_interval;
				
		  		list.css('top', top + 'px');
				
				if(s.scroll_delay <= 0){
					list.css('visibility', 'visible');
					
		  			list.animate({
			  			top: -list.height()
			  		},{
						duration: duration,
						easing: 'linear', 
						queue: false,
						complete: function(){
							lists.shift();
							$(this).remove();
							if(lists.length < 2) $(widget).trigger("load");
			    		}
		  			});
				}else{
					delay_scroll();
				}
		
		 		if(lists.length < 2) $(widget).trigger("load");
        }});
      }).trigger("load");
    });
  };
})(jQuery);

