// All sorts of things.
// This code is Copyright Jalada & x5315 2009
// You wouldn't want to use it anyway.

// Linked lists
jQuery.noConflict();
	(function($) {
		 // Within this block, $ is a reference to jQuery
	})(jQuery);
function LinkedList() {}
LinkedList.prototype = {
  length: 0,
  first: null,
  last: null
};

LinkedList.Circular = function() {};
LinkedList.Circular.prototype = new LinkedList();

LinkedList.Circular.prototype.append = function(node) {
  if (this.first === null) {
    node.prev = node;
    node.next = node;
    this.first = node;
    this.last = node;
  } else {
    node.prev = this.last;
    node.next = this.first;
    this.first.prev = node;
    this.last.next = node;
    this.last = node;
  }
  this.length++;
};

LinkedList.Circular.prototype.prepend = function(node) {
  if (this.last === null) {
    node.prev = node;
    node.next = node;
    this.first = node;
    this.last = node;
  } else {
    node.prev = this.last;
    node.next = this.first;
    this.last.next = node;
    this.first.prev = node;
    this.first = node;
  }
  this.length++;
}

LinkedList.Circular.prototype.insertAfter = function(node, newNode) {
  newNode.prev = node;
  newNode.next = node.next;
  node.next.prev = newNode;
  node.next = newNode;
  if (newNode.prev == this.last) { this.last = newNode; }
  this.length++;
};

LinkedList.Circular.prototype.remove = function(node) {
  if (this.length > 1) {
    node.prev.next = node.next;
    node.next.prev = node.prev;
    if (node == this.first) { this.first = node.next; }
    if (node == this.last) { this.last = node.prev; }
  } else {
    this.first = null;
    this.last = null;
  }
  node.prev = null;
  node.next = null;
  this.length--;
};
// END OF LINKED LISTS


Array.prototype.inArray = function (value) {
	var i;
	for (i = 0; i < this.length; i++) {
		if (this[i] == value) {
			return true;
		}
	}
	return false;
};

// Array Remove - By John Resig (MIT Licensed)
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);
};

function prependChild(parent, node) {
    parent.insertBefore(node, parent.firstChild);
    return(parent.firstChild);
}

// Preferences, thanks http://phpied.com/json-javascript-cookies/. Updated to JSON2 library.
var prefs = {

  // Data has an array of unescape+escaped (Yeah, maybe tidy that up, but it works.) trends.
  // And some settings.
  data: {},

  load: function () {
    var n = 'twitterfall';
    var nEQ = n + '=';
    var the_cookie = document.cookie.split(';');
    // I don't actually know how this works, I got it off a website (I can't remember what now either).
    for (var i=0; i < the_cookie.length; i++) {
      var c = the_cookie[i];
      while (c.charAt(0)==' ') c = c.substring(1,c.length);
      if (c.indexOf(nEQ) == 0) {
        this.data = JSON.parse(unescape(c.substring(nEQ.length,c.length)));
        return this.data;
      }
    }
    return null;
  },

  save: function () {
    var d = new Date(2009, 12, 31);
    var p = '/';
    document.cookie = 'twitterfall=' + escape(JSON.stringify(this.data))
                      + '; path=' + p
                      + '; expires=' + d.toGMTString();
  }
}


var trend = '', timer = 0, customtimer = 0, popupStatus = 0, guser = '', gpass = '', timelinetimer = 0, map, searchgeocode = false, selectedanimation, animation, queuestatus = 0, gottrends = 3, pausenotification, dmtimer, retweets = true, apitimer, inserttimer, customcounter = 1, timelinefragment = document.createDocumentFragment();

var tweetQueue = new LinkedList.Circular();

// Initialise rolling IDs
var rollingids = new Array(50);

var animations = {
  "fall": function(i) {
    i.css('margin-bottom', 0 - i.height()).css('top', -170)
     .animate({top: "0px", marginBottom: "0px"}, "slow");
  },
  "fade": function(i) {
    i.css('display', 'none').fadeIn('slow');
  },
  "none": function(i) {
    //i.removeClass("post-hidden");
  }
};

prefs.load();

jQuery(document).ready(function() {
			
	setInterval("enableupdatebutton()", 100);						
  // Load initial preferences. If this was hardcoded then we'd have to remember :P
  // This needs to be above Meteor, so that when the trends are pushed (which on first load
  // causes the query string to be processed) the cookie can be updated.
  //loadInitialPrefs();

  // Set up Meteor
  /*Meteor.hostid = uid();
  var possid = [1, 1, 1, 1, 2, 2, 2];
  var serverid = Math.floor(Math.random()*6);
  Meteor.host = "data" + possid[serverid] + ".twitterfall.com";
  // Function to call when data arrives
  Meteor.registerEventCallback("process", newInsert);

  Meteor.joinChannel('control');
  Meteor.joinChannel('twf-trends', 1);
  Meteor.joinChannel('twf-extra', 1);
  Meteor.mode = 'stream';

  // Start streaming
  Meteor.connect();
           
  // Let's set up the map here. Why not? (Tom: Is it in position over 9000?) (Jalada: Yes) (Tom: are you sure?)
  // (Jalada: No)(Tom: Jalada can't code for shit)(Jalada: Yes he can)
  // By putting this here, when working locally, if it fails the rest of the code works.
  // Public
  map = new google.maps.Map2(document.getElementById("map_canvas"), { size: new GSize(200,200) });
  geocoder = new google.maps.ClientGeocoder();
*/
  //////////////////////////////////////
  // Bindings for just about everything!
  //////////////////////////////////////

  // Optimised bindings for icons.
  jQuery("#myTweetResults").click(function(e) {
    var clicked = jQuery(e.target);
	var tag = e.target.tagName.toLowerCase();
    switch (tag){
      case 'a':
        switch (clicked.attr('class')) {
          case 'reply':
		  jQuery("#replysubmit").css("display","block");
	  	  jQuery("#updatesubmit").css("display","none");
		  jQuery("#retweetsubmit").css("display","none");
		  jQuery("#usernamestatus").html('Reply to '+clicked.parents(".post").find(".the-username").text());
		   makeReplyBox(clicked, 'Reply');
            break;
          case 'follow':
            if (prefs.data.oauth_access_token == '') {
              notLoggedIn();
            } else {
              user = clicked.parents(".post").find(".the-username").text();
              doTwitter('follow', {user: user});
            }
            break;
          case 'retweet':
		  jQuery("#retweetsubmit").css("display","block");
		  jQuery("#replysubmit").css("display","none");
	  	  jQuery("#updatesubmit").css("display","none");
		  jQuery("#usernamestatus").html('Retweet '+clicked.parents(".post").find(".the-username").text());
            makeReplyBox(clicked, 'Retweet');
            break;
          case 'dm':
            makeReplyBox(clicked, 'Direct Message to ' + clicked.parents(".post").find(".the-username").text());
            break;
          case 'favtweet':
            if (prefs.data.oauth_access_token == '') {
              notLoggedIn();
            } else {
              id = clicked.parents(".post").attr('id');
              doTwitter('favourite', {id: id});
            }
            break;
        }
        break;
    }
  });
  
  jQuery("#myTweetResults").mouseover(function(e) {
    timelineMouseover(e);
  });
  
  // Trend list
  jQuery("#trendlist > .trend, #extralist > .trend").live("click", function (e) {
    if ((jQuery(e.target).hasClass('hashmts-img')) || (jQuery(e.target).hasClass('hashmts'))) {
      return true;
    } else {
      if (jQuery(this).hasClass('active')) {
        leave(jQuery(this).text(), 'normal');
        makeInactive(jQuery(this));
        redoSaveLink();
        jQuery(this).mouseenter(function() {
          jQuery(this).find('.trend-checkbox').css('visibility', 'visible');
          jQuery(this).find('.hashmts').css('visibility', 'visible');
        }).mouseleave(function() {
          jQuery(this).find('.trend-checkbox').css('visibility', 'hidden');
          jQuery(this).find('.hashmts').css('visibility', 'hidden');
        });
      } else {
        var t = jQuery("#trendlist > .trend:first");
        if (t.hasClass("active")) {
          leave('All Terms', 'normal');
          makeInactive(t);
        }
        join(jQuery(this).text(), 'normal');
        makeActive(jQuery(this));
        redoSaveLink();
      }
    }
  });

  // Custom list
  jQuery(".searchname, .customsearch :checkbox", jQuery("#customSearchPanel")).live("click", function () {
    var table = jQuery(this).parents('table');
    if (table.hasClass('active')) {
      leave(table.find('.searchname').text(), "custom");
      // Reset data too
      table.data("id", 0);
      makeInactive(table);
      redoSaveLink();
    } else {
      var t = jQuery(".customsearch:first", jQuery("#customSearchPanel"));
      if (t.hasClass("active")) {
        leave('All Searches', "custom");
        makeInactive(t);
      }
      join(table.find('.searchname').text(), 'custom');
      table.data("id", 0);
      makeActive(table);
      redoSaveLink();
    }
  });

  jQuery("#center").mouseenter(function() {
    pauseQueue();
  }).mouseleave(function() {
    restartQueue();
  });

  // Speed variable
  jQuery("#speed").change(function() {
    pauseQueue();
    restartQueue();
  });

  // Custom search submission
  jQuery("#customform").submit(function() {
    // Needs MOAR regexp
    if (jQuery("#customsearch").val() != '') {
      //If this was the first custom search, switch off All Terms IF IT'S ON.
      if (jQuery("#customlist > table").length == 1) {
        var t = jQuery("#trendlist > .trend:first");
        if (t.hasClass('active')) {
          leave('All Terms', 'normal');
          makeInactive(t);
        }
      }
      addCustomSearchToList(jQuery("#customsearch").val());
      jQuery("#customform > input").val('');
      redoSaveLink(); 
            
      processCustom();
    }
    return false;
  });

  // Custom search removal
  jQuery(".remove", jQuery("#customlist")).live("click", function() {
    var table = jQuery(this).parents('.customsearch');
    leave(table.find('.searchname').text(), 'custom');
    table.remove();
    redoSaveLink();
    return false;
  });

  // Popup load links
  jQuery(".popupOpen").click(function(){  
    var id = jQuery(this).attr("id");
    var name = id.match(/popup(.+?)Open/);
    centerPopup(name[1]);  
    //load popup  
    loadPopup(name[1]);
  });
  
  jQuery("#popupWidgetsOpen").click(function(){
  		jQuery("#widgetlink").html('<img src="/Twitterfall.wdgt.png" alt="Twitterfall Dashboard Widgets" />');
  		jQuery("#gadgetlink").html('<img src="/Twitterfall.gadget.png" alt="Twitterfall Desktop Gadget" />');
  });
  
  jQuery("#openHelp").click(function() {
    //request data for centering  
    var windowWidth = document.documentElement.clientWidth;  
    var windowHeight = document.documentElement.clientHeight;  
    jQuery("#popupHelp").height(windowHeight - 70).width(windowWidth - 70);
    var popupHeight = jQuery("#popupHelp").height();  
    var popupWidth = jQuery("#popupHelp").width();  
    //centering  
    jQuery("#popupHelp").css({  
      "top": 15,  
      "left": 20
    });  
    //only need force for IE6  
    jQuery("#backgroundPopup").css({  
      "height": windowHeight  
    });
    loadPopup('Help');
    jQuery.get('help.html', function(data) {
      jQuery("#popupHelp").html(data);
    });
  });

  // Close popup links
  jQuery(".popupClose, #backgroundPopup").click(function(){  
    disablePopup();  
  });  

  // Pressing escape to remove popup
  jQuery(document).keypress(function(e){  
    if(e.keyCode==27 && popupStatus==1){  
      disablePopup();
    }  
  });  

  // Hide button
  jQuery("#hidepanels").click(function() {
    var l = jQuery(".left");
    var r = jQuery(".right");
    var u = jQuery("#updates");
    if (jQuery(this).text() == "Hide Panels") {
      l.animate({ left: "-185px"}, 1000, function() { jQuery("#hidepanels").text('Show Panels'); });
      r.animate({ right: "-185px"}, 1000);
      u.animate({ width: "90%" }, 1000);
    } else {
      l.animate({ left: "0px"}, 1000, function() { jQuery("#hidepanels").text('Hide Panels'); });
      r.animate({ right: "0px"}, 1000);
      u.animate({ width: "550px" }, 1000);
    }
  });

  // Submit reply form
  jQuery(".replysubmit").live("click", function() {
					 str = jQuery(this).serialize();
					  var post = jQuery(this).parents(".post");
					   replybox = jQuery(this).parents(".replybox");
										   jQuery.ajax(
                {
                    url: '/twitterapi/twitter.login.check.ajax.php', 
                    cache: false, 
                    type: 'POST',
                    data: str,
                    dataType: 'text',
                    error: function(xhr, ajaxOptions, thrownError){
                        alert('<div style="color:#ff0000"><span>Error loading document</span><br /><span>Error Code:' + xhr.status + '</span></div>');
                    },
                    success: function(data){
					if (data == "") {
      				notLoggedIn();
   				 } else {
    	 
	   	  var status_text = post.find(".message").val();
     	 var user = post.find(".the-username").text();
     	
        var status_id = post.attr("id");
        doTwitter('sendreply', {status_text: status_text, status_id: status_id}, function() {
          replybox.slideUp().removeClass("replybox-active");
        });
        return false;
      
    }
                      
                    }
                }
            );
   
    return false;
  });

	// Submit Update 
	
 jQuery(".updatesubmit").live("click", function() {
					 str = jQuery(this).serialize();
					  var post = jQuery(this).parents(".post");
					   replybox = jQuery(this).parents(".replybox");
					  
				jQuery.ajax(
                {
                    url: '/twitterapi/twitter.login.check.ajax.php', 
                    cache: false, 
                    type: 'POST',
                    data: str,
                    dataType: 'text',
                    error: function(xhr, ajaxOptions, thrownError){
                        alert('Error loading document');
                    },
                    success: function(data){
					if (data == "") {
						
      				notLoggedIn();
   				 } else {
    	 
	   	  var status_text = post.find(".message").val();
     	 var user = post.find(".the-username").text();
     	 
        var status_id = post.attr("id");
		doTwitter('sendupdate', {status_text: status_text, status_id: status_id}, function() {
          replybox.slideUp().removeClass("replybox-active");
        });
        return false;
       
    }
                      
                    }
                }
            );
   
    return false;
  });
  // You know...JQuery needs a 'reset' event
  jQuery(".cancelbutton").live("click", function() {
    jQuery(".replybox").slideUp().removeClass("replybox-active");
    queuestatus = 0;
  });

  // Tweet this button
  jQuery("#tweetthis").click(function() {
    if (prefs.data.oauth_access_token == '') {
      notLoggedIn();
    } else { 
      doTwitter('update', {status_text: "I'm checking out realtime tweets of popular trends on http://twitterfall.com!"});
    }
  });
    
  jQuery(".message").live("keyup", function () {
    // Check if me is necessary
    var me = jQuery(this);
    me.parents(".replybox").find(".count").text(140 - me.val().length);
  });
  
  jQuery(".newMessage").keyup(function() {
    var container = jQuery(this).parents().filter('div');
    jQuery(".newTweetCount", container).text(140 - jQuery(this).val().length)
  })

  jQuery("#newTweet > form, #newTweetBottom > form").submit(function() {
    var container = jQuery(this).parents().filter('div');
    if (oauth_access_token == '') {
      notLoggedIn();
    } else {
      var status_text = jQuery(".newMessage", container).val();
      // Maybe be polite and put an error message
      if (status_text != '') {
        doTwitter('update', {status_text: status_text}, function() {
          if (container.attr('id') == 'newTweet') {
            jQuery("#newTweet").animate({top: "-180px"}, 'slow', function() {
              jQuery(".newMessage", jQuery("#newTweet")).val('');
              jQuery(".newTweetCount", jQuery("#newTweet")).text(140);
            });
          } else {
            jQuery(".newMessage", jQuery("#newTweetBottom")).val('');
            jQuery(".newTweetCount", jQuery("#newTweetBottom")).text(140);
          }
        });
      }
    }
  
    return false;
  });
  
  // Empty Queue button
  jQuery("#emptyqueue").click(function() {
    emptyQueue();
  });
  
  // Change theme function.
  jQuery("#theme").change(function() {
  	var changed = jQuery(this).val();
    var i, a, main;
    for (i=0; (a = document.getElementsByTagName("link")[i]); i++) {
      if (a.getAttribute("title")) {
        a.disabled = true;
        if (a.getAttribute("title") == changed) a.disabled = false;
      }
    }
  });
  
  jQuery("#textsize").change(function () {
  	jQuery('#myTweetResults').css("font-size", jQuery(this).val() + "em");
  });
  
  // Checkbox for 'show my timeline'
  // Seems debatable whether change or click is correct. Swapping to 'click' due to issuez
  jQuery("#showmytimelineval").live("click", function() {
    if (jQuery(this).is(':checked')) {
      getTimeline();
      join('twittertimeline', 'timeline');
      timelinetimer = setInterval("getTimeline()", jQuery("#changetimelineint").val());
      // Add to cookie
      prefs.data.timeline = jQuery("#changetimelineint").val();
      prefs.save();
    } else {
      leave('twittertimeline', 'timeline');
      clearInterval(timelinetimer);
      prefs.data.timeline = '';
      prefs.save();
    }
  });
  
  //Timer for timeline. 
  jQuery("#changetimelineint").live("click", function(){
  	if(jQuery("#showmytimelineval").is(':checked')){
  	  clearInterval(timelinetimer);
  	  timelinetimer = setInterval("getTimeline()", jQuery("#changetimelineint").val());
      prefs.data.timeline = jQuery("#changetimelineint").val();
      prefs.save();
  	  }
  });
  
  // Checkbox for Direct Messages
  jQuery("#showdirectmessagesval").live("click", function() {
    if (jQuery(this).is(':checked')) {
      getDirectMessages();
      join('twitterdirectmessages', 'directmessage');
      dmtimer = setInterval("getDirectMessages()", jQuery("#changedmint").val());
      // Add to cookie
      prefs.data.dm = jQuery("#changedmint").val();
      prefs.save();      
    } else {
      leave('twitterdirectmessages', 'directmessage');
      clearInterval(dmtimer);
      prefs.data.dm = '';
      prefs.save();      
    }  
  });
  
   jQuery("#changedmint").live("click", function(){
  	if(jQuery("#showdirectmessagesval").is(':checked')){
  	  clearInterval(dmtimer);
  	  dmtimer = setInterval("getDirectMessages()", jQuery("#changedmint").val());
      prefs.data.dm = jQuery("#changedmint").val();
      prefs.save();  	  
  	  }
  });
  
  // Geolocation
  jQuery("#geolocform > form").submit(function() {
    var b = jQuery(this).find("button");
    if (b.text() == 'Enable') {
      if (jQuery("#customlist > table").length > 1) {
        showAddress(jQuery("#location").val(), function(coordinates) {
          searchgeocode = {"lat": coordinates.lat, "lon": coordinates.lon, "distance": jQuery("#distance").val(), "unit": jQuery("#unit").val()};
          redoSaveLink();
        });
      } else {
        alert("Geolocation currently only works with custom searches. Please enter at least one custom search.");
      }
    } else {
      jQuery("#map_canvas").fadeOut(function() {
        map.clearOverlays();
        searchgeocode = false;
        b.text('Enable');
        jQuery("#location").val('');
        redoSaveLink();
      });
    }
    return false;
  });

  jQuery("#location").focus(function() {
    if (jQuery(this).val() == 'Location') {
      jQuery(this).val('');
    }
  });

  jQuery("#location").keyup(function() {
    jQuery("#geolocform").find("button").text('Enable');
  });
  
  jQuery("#distance, #unit").change(function() {
    jQuery("#geolocform").find("button").text('Enable');
  });

  jQuery("#location").blur(function() {
    if (jQuery(this).val() == '') {
      jQuery(this).val('Location');
    }
  });
  
  
  // UI2.0
  jQuery(".header").click(function() {
    var indicator = jQuery(this).siblings(".expand").find("a");
    if (indicator.text() == '-') {
      // Hide
      indicator.text('+');      
      jQuery(this).siblings(".contents").slideUp().removeClass("open").addClass("closed");
    } else {
      // Expand
      indicator.text('-');
      jQuery(this).siblings(".contents").slideDown().addClass("open").removeClass("closed");
    }
  }).mouseenter(function() {
    jQuery(this).siblings(".expand").find("a").css({"color": "white"});
  }).mouseleave(function() {
    jQuery(this).siblings(".expand").find("a").css({"color": "DeepSkyBlue"});
  });
  
  jQuery(".expand").click(function() {
    var indicator = jQuery(this).find("a");
    if (indicator.text() == '-') {
      // Hide
      indicator.text('+');      
      jQuery(this).siblings(".contents").slideUp();

    } else {
      // Expand
      indicator.text('-');
      jQuery(this).siblings(".contents").slideDown();
    }
  }).mouseenter(function() {
    jQuery(this).find("a").css({"color": "white"});
  }).mouseleave(function() {
    jQuery(this).find("a").css({"color": "DeepSkyBlue"});
  });
  
  jQuery("#newTweetLink").click(function() {
    // Put the New Tweet dialog in the middle
    centerAbsolute('#newTweet');
    // Override the queue
    queuestatus = 3;
    jQuery(".newTweetCount", jQuery("#newTweet")).text(140);
    jQuery("#newTweet").animate({
      top: "0px"
    }, 'slow');
  });
  
  jQuery("#newTweetClose").click(function() {
    queuestatus = 0;
    restartQueue(); 
    jQuery("#newTweet").animate({top: "-180px"}, 'slow');
  });
  
  jQuery("#newTweetBottomClose").click(function() {
    jQuery("#newTweetBottom").animate({bottom: "-155px"}, 'slow');
  });
  
  jQuery("#makeNewTweetBottom").click(function() {
    centerAbsolute("#newTweetBottom");
    jQuery(".newTweetCount", jQuery("#newTweetBottom")).text(140);
    jQuery("#newTweetClose").click();
    jQuery("#newTweetBottom").animate({bottom: "0px"}, 'slow');   
  });
  
  jQuery("#toggleQueue").click(function() {
    if (jQuery(this).text() == 'Pause Tweets') {
      // Due to the nature of the UI, the queue will ALREADY be paused.
      // If this link ever moves, this will need to be uncommented
      //clearInterval(timer);
      jQuery(this).text('Resume Tweets');
      queuestatus = 3;
    } else {
      // Ditto the above.
      //timer = setInterval("processQueue()", jQuery("#speed").val());
      jQuery(this).text('Pause Tweets');
      queuestatus = 0;
    }
  });
  
  //START OF TWITPICS
  
	jQuery(".twitpic", jQuery("#myTweetResults")).live("mouseover", function() {
  	jQuery("#twitpic").empty().css({'display': 'block',
  	                           'width':   '42px',
  	                           'height':  '42px'});
  	var twitpicurl = this.href;
    twitpicurl = twitpicurl.replace("twitpic.com/","twitpic.com/show/thumb/");
  	twitpicurl = twitpicurl.replace("mobypicture.com","api.mobypicture.com?t=http://mobypicture.com");
    if (twitpicurl.match(/mobypicture\.com/)) {
      twitpicurl = twitpicurl + "&s=small&k=tw1tt3rfa11x5315&format=plain";
    }
  	
   	var img = new Image();
		jQuery(img).load(function(){
   		jQuery("#twitpic").css({'width': 'auto', 'height': 'auto'});
  		// Quick check to see if some other image isn't already there.
  		if (jQuery("#twitpic").html() == "") {
    	  jQuery("#twitpic").append(jQuery(img));
      }
    }).attr('src', twitpicurl);
    jQuery(this).mousemove(function(e){
      jQuery("#twitpic").css({'left': (e.pageX + 5)+"px",
                         'top': (e.pageY + 5)+"px"});
    });
  }).live("mouseout", function() {
    jQuery("#twitpic").css({'display': 'none'}).empty();
    jQuery(this).unbind('mousemove');
  });
      

  //END OF TWITPICS
  
  // Start of tweeterinfo
  
  jQuery(".the-username, .twitter-link", jQuery("#myTweetResults")).live('mouseover', function(e) {
    var username = jQuery(this).attr("href").match(/twitter\.com\/(.*)$/);
    jQuery("#the-username").html('<img src="username-throbber.gif" alt="Loading" />');
  	jQuery.getJSON("https://twitter.com/users/show/" + username[1] + ".json?callback=?",
  	 	function(data){
  	 	  jQuery("#the-username").html('<table id="userhead"><tr><td>Name</td><td>'
  	 		+ data.name
  	 		+ '</td></tr><tr><td>Location</td><td>'
  	 		+ data.location
  	 		+ '</td></tr><tr><td>Followers</td><td>'
  	 		+ data.followers_count
  	 		+ '</td></tr><tr><td>Following</td><td>'
  	 		+ data.friends_count
  	 		+ '</td></tr><tr><td>Updates</td><td>'
  	 		+ data.statuses_count
  	 		+ '</td></tr></table>');	
  	 	});
  	 	
    // Followcost need to support JSONP
  	//jQuery.getJSON("http://followcost.com/" + jQuery(this).text() + ".json",
    //	function(cost){
  	// 		jQuery("#userhead").append('<tr><td><span style="color: black;">Follow</span> <span style="color: #7e0e41;">Cost</span></td><td>'
  	//		+ cost.milliscobles_all_time
  	// 		+ " milliscobles"
  	//		+ "</td></tr>");		
  	// 	});
      jQuery("#the-username").css({'left': (e.pageX + 5)+"px",
             'top': (e.pageY + 5)+"px",
             'display': 'block'});
    }).live("mouseout", function() {
  		jQuery("#the-username").css('display','none').empty();
  		jQuery(this).unbind('mousemove');
  	});
  
  // End of tweeterinfo

  // Clear page
  jQuery("#clearPage").click(function() {
    jQuery("#myTweetResults > .post").fadeOut(function() { jQuery(this).remove(); });
  });

  // Empty the queue on language change
  jQuery("#language").change(function() {
    if (jQuery(this).val() != 'any') {
      jQuery("#myTweetResults").queue("fx", []);
      updateQueueCount();
    }
  });
  
  
  // Short URL binding
  jQuery(".shorturl", jQuery("#myTweetResults")).live('mouseover', function() {
    var post = jQuery(this).parents(".post");
    if (!post.data("original")) {
      post.data("original", post.find(".the-message").text());
    }
    var link = jQuery(this);
    jQuery(this).append('<img src="link-load.gif" alt="Expanding URL..." />').removeClass('shorturl');
    jQuery.post('post.php',
           {f: 'geturl', tinyurl: jQuery(this).text()},
           function(data) {
             link.text(data);
             link.children('img').remove();
           },
           'text');
  });
  
  // Forget my settings
  jQuery("#forget").click(function() {
    document.cookie = 'twitterfall=Expired; path=/; expires=Thu, 2 Aug 2001 00:00:00 UTC;';
    document.location = "/";
  });
  
  // Logout
  jQuery("#logout").live("click", function() {
    clearInterval(timelinetimer);
    clearInterval(dmtimer);
    jQuery("#loginwrap").html('<div id="login"><a id="authorize">Authorize Twitterfall with your Twitter Account</a></div>');
    jQuery(".login", jQuery("#left-wrapper")).text('Login to Twitter');
    prefs.data.oauth_request_token = prefs.data.oauth_request_token_secret =
    prefs.data.oauth_access_token = prefs.data.oauth_access_token_secret = '';
    prefs.save();
    loginBindings();
  });

  // All encompassing storage of select boxes in settings
  jQuery("#settings > select").change(function() {
    var name = jQuery(this).attr("id");
    prefs.data[name] = jQuery(this).val();
    prefs.save();
  });
  
  // Efficiency
  jQuery("#retweets").change(function() {
    if (jQuery(this).val() == 'yes') {
      retweets = true;
    } else {
      retweets = false;
      reQueue();
    }
  });
  
  // Colours
  jQuery(".colour", jQuery("#customlist")).live('change', function() {
    var parent = jQuery(this).parents(".customsearch");
    var customnumber = parent.data("value");
    jQuery(".custom" + customnumber).css('background-color', jQuery(this).val());
    parent.data("colour", jQuery(this).val());
    redoSaveLink();
  })
  
  loginBindings();
 
  //////////////////
  // End of Bindings
  //////////////////
  
  // Put the New Tweet dialog in the middle (admittedly out of sight)
  centerAbsolute('#newTweet');
  centerAbsolute('#newTweetBottom');

  // Process the queue of Tweets
  restartQueue();
  
  // Process the custom trends every 15 seconds
  customtimer = setInterval("processCustom()", 15000);
  
  // Get API Limit no matter what, as there is actually a limit per IP too.
  //getApiLimit();
  //apitimer = setInterval("getApiLimit()", 60000);
  
});

// Stuff to do once we've got the first lot of trends (cookie related really)
function setupAfterTrends() {
  // Load cookie
  prefs.load();
  // Only process the cookie if we have no special URL.
  var regexp = /twitterfall\.com\/(.+)$/;
  var regexp2 = /^\?oauth_token/;
  var match = regexp.exec(window.location.href);
  if (match == null || regexp2.test(match[1])) {
    processListOfTrends(prefs.data.trends);
    if (prefs.data.geoloc) {
      addInGeolocation(prefs.data.geoloc, prefs.data.distance, prefs.data.unit);
    }
    processCustom();
  }
  processSettings();
  if (hadtofixie == 1) {
    jQuery("#theme").val("simple");
  }
  jQuery("#theme").change();
  // Set to logged in if the access token exists.
  if (prefs.data.oauth_access_token != "") {
    // Check if it works
    var query = {f: 'verify', oauth_access_token: prefs.data.oauth_access_token, oauth_access_token_secret: prefs.data.oauth_access_token_secret};
    jQuery.post('post.php',
           query,
           function(data) {
             if (data.statuses_count) {
               loggedInUI();
             } else {
               alert('Twitterfall was unable to log in successfully. You may have revoked access. Please reauthorize Twitterfall on the left if you wish to interact with Twitter');
               prefs.data.oauth_access_token_secret = prefs.data.oauth_access_token = '';
               prefs.save();
             }
           },
           'json'
          );
   // NOW redo the save link
   redoSaveLink();
  }
  
  // Mouse bindings for any new custom searches
  jQuery("#customlist > table").mouseenter(function() {
    jQuery(this).find('.trend-checkbox').css('visibility', 'visible');
  });
  
  jQuery("#customlist > table:not(.active)").mouseleave(function() {
    jQuery(this).find('.trend-checkbox').css('visibility', 'hidden');
  });
  
  jQuery("#trendlist > table, #extralist > table").filter(".active").find('.hashmts').css('visibility', 'visible');
  
  redoSaveLink();
}


//////////////////////////
// Queue-related functions
//////////////////////////

function pauseQueue(){
  // It's more fun without the flag, but less usable.
  if (queuestatus == 1) {
	  clearInterval(timer);
	  clearInterval(pausenotification);
	  queuestatus = 0;
	  paused();
  }
}

function restartQueue(val){
  if (queuestatus == 0) {
  	timer = setInterval("processQueue()", jQuery("#speed").val());
  	queuestatus = 1;
    pausenotification = setTimeout('resumed()', 250);
  }
}

function paused() {
  jQuery("#queuedtweetstext").text('Paused:');
}

function resumed() {
  jQuery("#queuedtweetstext").text('Queued Tweets:');
}

function processQueue() {
  // Well this is easy
  jQuery("#myTweetResults").dequeue();
}

function emptyQueue() {
  // From the end of the queue
  // go through 15

  var initialLength = tweetQueue.length;
  var i = 0;
  var currNode = tweetQueue.last;
  var fallsize = jQuery("#size").val();
  while (i < fallsize) {
    if (currNode.prev === tweetQueue.last) {
      break;
    }
    currNode = currNode.prev;
    i++;
  }
  currNode.prev = tweetQueue.last;
  tweetQueue.first = currNode;
  tweetQueue.length = i + 1;

  reQueue();
  while (jQuery("#myTweetResults").queue("fx").length != 1) {
    processQueue();
  }
}

// My dear lord...
function reQueue() {
  var t = jQuery("#myTweetResults");
  // Empty the queue
  t.queue("fx", []);
  // Fixes bug where requeueing causes first queue item to execute. Guess this is a JQuery 'feature'
  t.queue(function() {});
  // Foreach hidden post, queue up the animation
  var i = 0;
  var currNode = tweetQueue.first;
  while (i < tweetQueue.length) {
    if (retweets == false) {
      var regexp = /(?:RT|via:)/;
      if (regexp.exec(currNode.text) == null) {
        addQueueItem();
      } else {
        // Sod it, get rid of it.
        tweetQueue.remove(currNode);
      }
    } else {
      addQueueItem();
    }
  i++;
  }
  updateQueueCount();
}


//////////////////////////////////
// Custom trends-related functions
//////////////////////////////////

function processCustom() {
    if (jQuery("#customlist > table:first").hasClass('active')) {
      jQuery("#customlist > table:gt(0)").each(function () {
        doCustomFetch(jQuery(this)); 
      });
    } else {
      jQuery("#customlist > table").filter(".active").each(function() {
        doCustomFetch(jQuery(this));      
      });
    }
}

// Note this takes a JQuery object. Not text
function doCustomFetch(trend) {
  // Get language
  var language = jQuery("#language").val();
  if (language == 'any') {
    var langquery = '';
  } else {
    var langquery = '&lang=' + language;
  }
  
  // Get the actual name (colour picker now means we can't just do this automatically
  var text = trend.find('.searchname').text();
  
  // Check this isn't the first time
  if (trend.data("id") != 0) {  
    var url = 'http://search.twitter.com/search.json?q=' + escape(text) + langquery + '&since_id=' + trend.data("id") + '&rpp=100';
  } else if (jQuery("#myTweetResults > .post").length == 0) {
    var url = 'http://search.twitter.com/search.json?q=' + escape(text) + langquery + '&rpp=20';
  } else {
    var url = 'http://search.twitter.com/search.json?q=' + escape(text) + langquery + '&since_id=' + jQuery("#myTweetResults > .post").attr("id") + '&rpp=100';
  }
  
  // Add geocode if necessary
  // Todo, lookup the right way of doing this?
  if (searchgeocode != false) {
    var url = url + '&geocode=' + searchgeocode.lat + ',' + searchgeocode.lon + ',' + searchgeocode.distance + searchgeocode.unit;
  }
    
  // Add callback
  var url = url + '&callback=?';

  jQuery.getJSON(url, function(data) {
    jQuery.each(data.results.reverse(), function(item) {
      // Presuming these are going to be in order (that's what the reverse is for)
      trend.data("id", this.id);
      if (rollingids.inArray(this.id) == false) {

        var data = 'id{' + this.id + '}username{' + this.from_user + '}avatar{' + this.profile_image_url + '}text{' + this.text + '}type{custom custom' + trend.data("value") + '}lang{' + language + '}channel{' + text + '}time{' + this.created_at + '}colour{' + trend.data("colour") + '}';
        newInsert(data);
        addToQueue(this.id);
      }
    });
  });
}


// Get user timeline
function getTimeline() {
  // Get the latest timeline
  var for_data = jQuery("#showmytimeline");
  
  // If it's 0, don't use a since_id, use the timeline
  if (for_data.data("id") != 0) {
    query = {username: guser, password: gpass, f: 'gettimeline', sinceid: for_data.data("id")};
  } else if (jQuery("#myTweetResults > .post").length == 0) {
    query = {username: guser, password: gpass, f: 'gettimeline'};
  } else {
    query = {username: guser, password: gpass, f: 'gettimeline', sinceid: jQuery("#myTweetResults > .post").attr("id")};
  }
  query.oauth_access_token = prefs.data.oauth_access_token;
  query.oauth_access_token_secret = prefs.data.oauth_access_token_secret;
  
  jQuery.post('post.php',
         query,
         function(data) {
           if (data.error) {
             var data = 'id{0}username{error}avatar{error.png}text{There was an error getting your timeline: ' + data.error + '}type{timeline}lang{' + jQuery("#language").val() + '}channel{twittertimeline}';
           } else {
             jQuery.each(data.reverse(), function(item) {
               for_data.data("id", this.id);
               // Add to queue but don't check it - best way round so you don't miss something DEFINITELY in your timeline
               addToQueue(this.id);
               var data = 'id{' + this.id + '}username{' + this.user.screen_name + '}avatar{' + this.user.profile_image_url + '}text{' + this.text + '}type{timeline}lang{' + jQuery("#language").val() + '}channel{twittertimeline}time{' + this.created_at + '}';
               newInsert(data);
             });
           }
           //getApiLimit();
         },
         "json"
        );
}

// Get user directmessages
function getDirectMessages() {
  // Get the latest timeline
  var for_data = jQuery("#showdirectmessages");
  
  // If it's 0, don't use a since_id, use the timeline
  if (for_data.data("id") != 0) {
    query = {username: guser, password: gpass, f: 'getdirectmessages', sinceid: for_data.data("id")};
  } else if (jQuery("#myTweetResults > .post").length == 0) {
    query = {username: guser, password: gpass, f: 'getdirectmessages'};
  } else {
    query = {username: guser, password: gpass, f: 'getdirectmessages', sinceid: jQuery("#myTweetResults > .post").attr("id")};
  }
  query.oauth_access_token = prefs.data.oauth_access_token;
  query.oauth_access_token_secret = prefs.data.oauth_access_token_secret;
  
  jQuery.post('post.php',
         query,
         function(data) {
           if (data.error) {
             var data = 'id{0}username{error}avatar{error.png}text{There was an error getting your Direct Messages: ' + data.error + '}type{directmessage}lang{' + jQuery("#language").val() + '}channel{twitterdirectmessages}';
           } else {
             jQuery.each(data.reverse(), function(item) {
               for_data.data("id", this.id);
               var data = 'id{' + this.id + '}username{' + this.sender.screen_name + '}avatar{' + this.sender.profile_image_url + '}text{' + this.text + '}type{directmessage}lang{' + jQuery("#language").val() + '}channel{twitterdirectmessages}time{' + this.created_at + '}';
               newInsert(data);
             });
           }
           //getApiLimit();
         },
         "json"
        );
}


//////////////////////////////////
// General tweet-related functions
//////////////////////////////////

function parseText(text) {
  // Make URL links
  var text = text.replace(/((?:http\:\/\/)|(www\.))(.+?)(\s|$|,|\. |\.$|\"|\))/g, "<a href=\"http://$2$3\" target=\"_blank\">$1$3<\/a>$4");
  // Give twitpics classes
  var text = text.replace(/<a href=\"http:\/\/(?:www\.)?twitpic\.com/g, "<a class=\"twitpic\" href=\"http:\/\/twitpic.com");
  var text = text.replace(/<a href=\"http:\/\/(?:www\.)?mobypicture\.com/g, "<a class=\"twitpic\" href=\"http:\/\/mobypicture.com");
  // Give shortened URLs classes
  var text = text.replace(/<a href=\"http:\/\/(?:www\.)?(bit\.ly|is\.gd|snipurl|tinyurl|twurl|sn\.im|snipr|snurl|ff\.im|tr\.im)/g, "<a class=\"shorturl\" href=\"http:\/\/$1");
  // Make Twitter @ links
  var text = text.replace(/(\s|^|\()\@([0-9a-zA-Z_]+)/g, "$1<a href=\"http://twitter.com/$2\" class=\"twitter-link\" target=\"_blank\">@$2</a>");
  return text;
}

function newInsert(data, postprocess) {
  var postprocess = postprocess || function(i) { return true; };
  
  // Process the message received from Meteor and put into an array.
  if (data.match(/^{"Trends":/)) {
    updateTrends(eval('(' + data + ')'));
  } else if (data.match(/^{"Extra":/)) {
    updateExtra(eval('(' + data + ')'));
  } else {
    // One day I'll make this JSON.
    // The [\s\S] is because '.' in JavaScript does NOT match new lines. 
    // http://www.regular-expressions.info/dot.html#nodotall
    var regexp = /id{([0-9]+?)}username{(.+?)}avatar{(.+?)}text{([\s\S]+?)}type{(.+?)}lang{(.*?)}channel{(.*?)}(?:time{(.*?)})?(?:colour{(.*?)})?/m;
    var matches = data.match(regexp);
    var time = matches[8] || "";
    var colour = matches[9] || "";

    // Check for RTs first
    if (retweets == false) {
      var regexp = /(?:RT|via:)/;
      if (regexp.exec(matches[4])) {
        return true;
      }
    }
    
    // Check for language
    var messagelang = matches[6];
    if (messagelang == '') {
      // Set it to 'any'
      messagelang = 'any';
    }
    
    if (messagelang == jQuery("#language").val() || jQuery("#language").val() == 'any') {
      // Parse the text
      var text = parseText(matches[4]);
      
      // tweetQueue
      var newNode = {
        id: matches[1],
        user: matches[2],
        avatar: matches[3],
        text: text,
        type: matches[5],
        channel: matches[7],
        time: time,
        colour: colour
      }
      tweetQueue.append(newNode);
      
      addQueueItem();
      updateQueueCount();
    }
  }
}



function addQueueItem() {
  if (jQuery("#myTweetResults").queue("fx").length == "0") {
    // Append an empty queue item to stop it jumping 2 at once
    jQuery("#myTweetResults").queue(function () {});
  }
  jQuery("#myTweetResults").queue(function () {
    updateQueueCount();
    var p = tweetQueue.first;
    
    jQuery("#myTweetResults").prepend(constructUpdate(p.id, p.user, p.avatar, p.text, p.type, p.time));
    var newPost = jQuery("#" + p.id);
    
    if (p.colour != "") {
      // Has been set a colour, find out what custom it is based on the type
      var customid = p.type.split(' ')[1];
      newPost.css('background-color', jQuery("#" + customid).data("colour")); 
    }
    
    newPost.data("time", p.time);

    eval("animations." + jQuery("#animation").val() + "(newPost)");
    
    tweetQueue.remove(p);
    
    // Fade out, then remove, tweets at the bottom of the Twitterfall
    jQuery(".post:gt(" + jQuery("#size").val() + ")", jQuery("#myTweetResults")).fadeOut("slow", function() { jQuery(this).remove() });
  });
}

// Looks nasty, is just all on one line.
function constructUpdate(id, user, avatar, update, type, time) {
  return '<div id="' + id + '" class="post ' + type + '"><div class="postinner"><table cellspacing="0" cellpadding="0"><tr><td class="username"><a href="http://twitter.com/' + user + '" target="_blank"><img width="48" height="48" src="' + avatar + '" alt="' + user + '" /></a></td><td class="status-body"><div class="time">' + time + '</div><p><a class="the-username" href="http://twitter.com/' + user + '" target="_blank">' + user + '</a> <span class="the-message">' + update + '</span></p></td><td><table class="icons"><tbody><tr><td><img src="user__arrow.png" width="16" height="16" alt="Reply" title="Reply" class="reply" /></td><td><img src="user__dm.png" width="16" height="16" alt="Direct Message" title="Direct Message" class="dm" /></td></tr><td><img src="user__retweet.png" width="16" height="16" alt="Retweet This" title="Retweet This" class="retweet" /></td><td><img src="user__plus.png" width="16" height="16" alt="Follow" title="Follow" class="follow" /></td></tr><tr><td><img src="user__love.png" width="16" height="16" alt="Favourite Tweet" title="Favourite Tweet" class="favtweet" /></td><td><a href="http://twitter.com/'+user+'/statuses/'+id+'" target="_blank"><img src="user__view.png" width="16" height="16" alt="View in Twitter" title="View in Twitter" class="viewtweet" /></a></td></tr></tbody></table></td></tr></table></div><div class="replybox"><form><table><tbody><tr><td><div class="count">140</div><span class="box-header"></span></td></tr><tr><td><textarea class="message" cols="80"></textarea></td><td class="button"><button class="replysubmit" type="button">Send</button><br /><button type="reset" class="cancelbutton">Cancel</button></td></tr></tbody></table></form></div></div>';
}


//////////////////////////
// Trend-related functions
//////////////////////////

function updateTrends(data) {
  var oldTrends = new Array();
  jQuery("#trendlist > .trend").filter(".active").each(function() {
    oldTrends.push(jQuery(this).text());
  });
  jQuery.each(data.Trends, function(i, item) {
    // Remove and add one at a time, this stops it flashing away and back
    // which looks awful. Though this method is more costly, as prepend is costly.
    jQuery("#trendlist > .trend:last").remove();
    var a = '<table class="trend"><tbody><tr><td>' + item + '</td><td class="trend-checkbox-outer"><input type="checkbox" class="trend-checkbox" /></td><td ';
    if (item.charAt(0) == '#') {
      a += 'class="hashmts">';
    } else {
      a += 'class="hashmts-off">';
    }
    
    a += '<a href="http://hashmarksthespot.com/' + item.substring(1) + '" target="_blank"><img class="hashmts-img" src="hashmts.png" alt="Information about this hashtag on hashmarksthespot.com" title="Information about this hashtag on hashmarksthespot.com" /></a></td></tr></tbody></table>';    
    
    jQuery("#trendlist").prepend(a);
  });
  jQuery.each(oldTrends, function(i, item) {
    var filter = jQuery("#trendlist > .trend").filter(function() { return jQuery(this).text() == item});
    if (filter.length < 1) {
      findATermAHome(item);
    } else {
      filter.each(function() { makeActive(jQuery(this)) });
    }
  });

  // Add hover bindings (mouseenter/leave cannot be live binded by JQuery yet)
  jQuery("#trendlist > .trend:not(.active)").mouseenter(function() {
    jQuery(this).find('.trend-checkbox').css('visibility', 'visible');
    jQuery(this).find('.hashmts').css('visibility', 'visible');
  }).mouseleave(function() {
    jQuery(this).find('.trend-checkbox').css('visibility', 'hidden');
    jQuery(this).find('.hashmts').css('visibility', 'hidden');
  });
  
  jQuery("#trendlist > .trend").filter(".active").find('.hashmts').css('visibility', 'visible');
  
  
  if (gottrends != 0) {
    gottrends = gottrends - 1;
    if (gottrends == 1) {
      iHazTrendsNao();
    }
  }
  
}

function updateExtra(data) {
  // Just erase and replace, unlike updateTrends()
  var oldExtra = new Array();
  jQuery("#extralist > .trend").filter(".active").each(function() {
    oldExtra.push(jQuery(this).text());
  });
  
  jQuery("#extralist > .trend").remove();

  jQuery.each(data.Extra, function(i, item) {
    var a = '<table class="trend"><tbody><tr><td>' + item + '</td><td class="trend-checkbox-outer"><input type="checkbox" class="trend-checkbox" /></td><td ';
    if (item.charAt(0) == '#') {
      a += 'class="hashmts">';
    } else {
      a += 'class="hashmts-off">';
    }
      
    a += '<a href="http://hashmarksthespot.com/' + item.substring(1) + '" target="_blank"><img class="hashmts-img" src="hashmts.png" alt="Information about this hashtag on hashmarksthespot.com" title="Information about this hashtag on hashmarksthespot.com" /></a></td></tr></tbody></table>';    
      
    jQuery("#extralist").prepend(a);
  });
  
  if (data.Extra.length != 0) {
    jQuery("#specialtrend").show();
  } else {
    jQuery("#specialtrend").hide();
  }
  
  
  jQuery.each(oldExtra, function(i, item) {
    var filter = jQuery("#extralist > .trend").filter(function() { return jQuery(this).text() == item });
    if (filter.length < 1) {
      leave(item, 'normal');
      findATermAHome(item);
    } else {
      filter.each(function() { makeActive(jQuery(this)) });
    }
  });

  // Add hover bindings (mouseenter/leave cannot be live binded by JQuery yet)
  jQuery("#extralist > .trend:not(.active)").mouseenter(function() {
    jQuery(this).find('.trend-checkbox').css('visibility', 'visible');
    jQuery(this).find('.hashmts').css('visibility', 'visible');
  }).mouseleave(function() {
    jQuery(this).find('.trend-checkbox').css('visibility', 'hidden');
    jQuery(this).find('.hashmts').css('visibility', 'hidden');
  });

  if (gottrends != 0) {
    gottrends = gottrends - 1;
    if (gottrends == 1) {
      iHazTrendsNao();
    }
  }
  return true;
}

function findATermAHome(item) {
  // Check to see if a colour is attached
  var match = item.match(/(.+)\!(\#.+)?$/);
  try {
    var colour = match[2];
  } catch(e) {
    var colour = "";
  }
  try {
    var item = match[1];
  } catch(e) {
    var item = item;
  }
  
  
  var filter = jQuery("#extralist > table, #trendlist > table").filter(function() { return jQuery(this).text().toLowerCase() == item.toLowerCase() });
  if (filter.length > 0) {
    join(jQuery(filter.get(0)).text(), 'normal');
    makeActive(jQuery(filter.get(0)));
    return true;
  }
  
  // This little term has no home, so we build it one
  addCustomSearchToList(item);
  jQuery("#customlist > table:last").data("colour", colour);
  try {
    jQuery("#customlist > table:last").find('.colour').val(colour).change();
  } catch(e) {
    // Do nothing
  }
  if (jQuery("#customlist > table:first").hasClass('active')) {
    // Do nothing
  } else {
    makeActive(jQuery("#customlist > table:last"));
    processCustom();
  }

}

function iHazTrendsNao() {
    // Check the URL, if it's a query, we're being given saved data.
    // If it's not, then we're being given something someone's written (or just something simple)
    var url = window.location.href;
    var regexp = /twitterfall\.com\/\?(.+)$/;
    var match = regexp.exec(url);
    if (match) {
      // Check if it's an OAuth response from Twitter.
      if (jQuery.query.has('oauth_token')) {
        prefs.load();
        if (prefs.data.oauth_access_token != jQuery.query.get('oauth_token')) {
          twitterLogin(jQuery.query.get('oauth_token'));
        }
      } else {
        processSavedQuery();
      }
    } else {
      var regexp = /twitterfall\.com\/(.+)$/;
      var match = regexp.exec(url);
      if (match) {
        processSingleQuery(match[1]);
      }
    }
    setupAfterTrends();
    gottrends = 0;
}

// Join a channel for a trend
function join(channel, type) {
  if (type == 'normal') {
    if (channel == 'All Terms') {
      jQuery("#trendlist > .active, #extralist > .active").each(function(i) {
        leave(jQuery(this).text(), 'normal');
        makeInactive(jQuery(this));
      });

    }
    try {
      Meteor.joinChannel('twitterfall-' + escape(channel), 15);
    } catch (err) {
      Meteor.leaveChannel('twitterfall-' + escape(channel));
      Meteor.joinChannel('twitterfall-' + escape(channel));
    }
  } else if (type == 'custom') {
    if (channel == 'All Searches') {
      jQuery("#customlist > .active").each(function(i) {
        leave(jQuery(this).text(), 'custom');
        makeInactive(jQuery(this));
      });
    }
    setTimeout("processCustom()", 2000);
  }
}

// Leave a channel for a trend
function leave(channel, type) {
  if (type == 'normal') {
    Meteor.leaveChannel('twitterfall-' + escape(channel));
  }
  // Epic filter
  var currNode = tweetQueue.first;
  while (1) {
    if (currNode === null) {
      break;
    }
    if (currNode.type.split(' ').inArray(type)) {
      if (channel == 'All Searches') {
        tweetQueue.remove(currNode);
        nextNode = tweetQueue.first;
      } else if (currNode.channel == channel) {
        tweetQueue.remove(currNode);
        nextNode = tweetQueue.first;
      } else {
        nextNode = currNode.next;
      }
    } else {
      nextNode = currNode.next;
    }
    if (currNode == tweetQueue.last) {
      break;
    } else {
      currNode = nextNode;
    }
  }
  // Here's an idea
  // Rather than emptying the animation queue and redoing it all AFTER iterating through everything
  // Why not start this function by emptying the animation queue, then add things to the animation queue
  // when you know you're not deleting them.
  // Genius.
  reQueue();
}

// This needs some tidying up, but it does work.
function changeTrend(newTrend) {
  jQuery("#myTweetResults > div").fadeOut(jQuery("#fade").val(), function() {
    // Make sure to catch any currently hidden updates. And remove the faded out ones.
    jQuery(this).remove();
  });
  // Empty any queued up updates too
  jQuery("#myTweetResults").queue("fx", []);
  updateQueueCount();

  if (newTrend.match(/custom-/)) {
    // Force a custom process
    processCustom();
  }
  Meteor.leaveChannel('twitterfall-' + escape(trend));
  Meteor.joinChannel('twitterfall-' + escape(newTrend), 15);
  
  // If the new trend isn't a custom trend, and the old trend WAS, reconnect
  // (and vice versa)
  if (!(newTrend.match(/custom-/)) && (trend.match(/custom-/))) {
    Meteor.connect();
  } else if ((newTrend.match(/custom-/)) && !(trend.match(/custom-/))) {
    Meteor.disconnect();
  }

  trend = newTrend;
  Meteor.connect();
}


///////////////////////////
// Other assorted functions
///////////////////////////

// Make a unique ID for Meteor
function uid() {
  var newDate = new Date;
  return newDate.getTime();
}

function loadPopup(popup){
  //loads popup only if it is disabled
  if(popupStatus==0){
    // Pause queue
    pauseQueue();
    jQuery("#backgroundPopup").css({
      "opacity": "0.7"
    });
    jQuery("#backgroundPopup").fadeIn("slow");
    jQuery("#popup" + popup).fadeIn("slow");
    popupStatus = 1;
  }
}

function disablePopup(){  
  //disables popup only if it is enabled  
  if(popupStatus==1){  
    restartQueue();
    jQuery("#backgroundPopup").fadeOut("slow");  
    jQuery(".popup").fadeOut("slow");
    popupStatus = 0;  
  }  
}  

//centering popup  
function centerPopup(popup){  
  //request data for centering  
  var windowWidth = document.documentElement.clientWidth;  
  var windowHeight = document.documentElement.clientHeight;  
  var popupHeight = jQuery("#popup" + popup).height();  
  var popupWidth = jQuery("#popup" + popup).width();  
  //centering  
  jQuery("#popup" + popup).css({  
    "top": 100,  
    "left": windowWidth/2-popupWidth/2  
  });  
  //only need force for IE6  
  jQuery("#backgroundPopup").css({  
    "height": windowHeight  
  });  
}

function centerAbsolute(elementId){
  var windowWidth = document.documentElement.clientWidth;  
  var windowHeight = document.documentElement.clientHeight;
  var elementHeight = jQuery(elementId).height();
  var elementWidth = jQuery(elementId).width();
  jQuery(elementId).css({
    "left": (windowWidth/2)-(elementWidth/2)
  });
}

function notLoggedIn() {
	alert('Please login on the right to interact with Twitter!');
  // Need to login, foo
  //jQuery("#twtLoginWrapper").html('<p>Please login on the left to interact with Twitter!</p>');
 // centerPopup('Reply');
  //loadPopup('Reply');
 // setTimeout("disablePopup()", 1500);
}

// Twitter proxy function
function doTwitter(f, data, callback) {
  var query;
  switch (f) {
    case 'sendreply':
      query = { f: 'sendreply', message: data.status_text, reply: data.status_id };
      break;
    case 'follow':
      query = { username: guser, password: gpass, f: 'follow', friendname: data.user };
      break;
    case 'sendupdate':
      query = { f: 'sendupdate', message: data.status_text, reply: data.status_id};
      break;
    case 'favourite':
      query = { username: guser, password: gpass, f: 'createfavourite', favid: data.id};
      break;
    default:
      return false;
  }
 // query.oauth_access_token = prefs.data.oauth_access_token;
  //query.oauth_access_token_secret = prefs.data.oauth_access_token_secret;
  
  pauseQueue();
  // Override
  queuestatus = 3;
    
  // Put Throbber in
  jQuery("#popupReplyInner").html('<img src="throbber.gif" alt="Loading" />');
  centerPopup('Reply');
  loadPopup('Reply');
   jQuery.post('/twitterapi/reply.php',
         query,
         function(data) {
           if (data == 'Invalid OAuth Request') {
             jQuery("#popupReplyInner").html('<p>Twitter returned "Invalid OAuth Request". You may not be logged in correctly.</p>');
             setTimeout("disablePopup()", 1500);
             queuestatus = 0;
             restartQueue();
           } else {
             alert('Sent!');
            jQuery("#replyboxtext").val(jQuery("#frmTweetHiddenvalue").val());
      		jQuery("#count").text(140-jQuery("#frmTweetHiddenvalue").val().length);
			jQuery("#replysubmit").css("display","none");
			jQuery("#retweetsubmit").css("display","none");
	  	  	jQuery("#updatesubmit").css("display","block");
			jQuery("#usernamestatus").html('What are you doing?');
           }
         },
         "text"
        );
}


function showAddress(address, callback) {
  geocoder.getLocations(
    address,
    function(response) {
      if (!response || response.Status.code != 200) {
        alert("\"" + address + "\" not found");
      } else {
        map.clearOverlays();
        jQuery("#geolocform").find("button").text('Disable');
        jQuery("#map_canvas").fadeIn();
        place = response.Placemark[0];
        point = new google.maps.LatLng(place.Point.coordinates[1],
                                       place.Point.coordinates[0]);
        map.setCenter(point, 5);
        var marker = new google.maps.Marker(point);
        map.addOverlay(marker);
        callback({"lat": place.Point.coordinates[1], "lon": place.Point.coordinates[0]});
      }
    }
  );
}

function updateQueueCount() {
  var n = jQuery("#myTweetResults").queue("fx");
  // Hack to get rid of that stupid -1.
  if (n.length == 0) {
    jQuery("#counter").text(n.length);
  } else {
    jQuery("#counter").text(n.length - 1);
  }
}

function addCustomSearchToList(value) {
  jQuery("#customlist").append('<table class="customsearch" id="custom' + customcounter + '"><tbody><tr><td><input class="colour" value="#706a5f" /></td><td class="searchname">' + value + '</td><td class="trend-checkbox-outer"><input class="trend-checkbox" type="checkbox" /></td><td class="remove"><img src="cross.png" alt="remove" /></td></tr></tbody></table>');
  jQuery("#customlist > table:last").find(".colour").colorInput().end()
                               .data("id", 0)
                               .data("value", customcounter)
                               .data("colour", "#494234")
                               .mouseenter(function() {
                                 jQuery(this).find('.trend-checkbox').css('visibility', 'visible');
                               }).mouseleave(function() {
                                 jQuery(this).find('.trend-checkbox').css('visibility', 'hidden');
                               });
  customcounter += 1;
  if (jQuery("#nocustomsearch:checked").val() == null) {
    jQuery.get("/custom-stats/" + value.replace("#", "%23"));
  }
}

// Takes a JQuery object
function makeReplyBox(button, header) {
  var post = button.parents(".post");
  var replybox = post.find(".replybox");
  // Check if the replybox is already open.

    user = post.find(".the-username").text();
	user += " " + jQuery("#frmTweetHiddenvalue").val();
	
    replybox.find(".box-header").text(header + ':');
    // You could calculate the count based off the textarea, but that would probably be slower.
    if (button.hasClass('reply')) {
	  jQuery("#replyboxtext").val('@' + user + ' ');
	  jQuery("#count").text(137 - user.length);
    } else if (button.hasClass('retweet')) {
      message = post.data("original") || post.find(".the-message").text();
      jQuery("#replyboxtext").val('RT @' + user + ': ' + message);
      jQuery("#count").text(134 - user.length - message.length);
    } else if (button.hasClass('dm')) {
      message = '';
      jQuery("#replyboxtext").val('');
      jQuery("#count").text(140);
    }
  jQuery(window).scrollTop(0);
}

function processSingleQuery(input) {
  var input = unescape(input);
  input = input.replace(/(<([^>]+)>)/ig,"");
  var filter = jQuery("#trendlist > .trend").filter(function() {
    return jQuery(this).text().toLowerCase() == input.toLowerCase();
  });
  var extrafilter = jQuery("#extralist > .trend").filter(function() {
    return jQuery(this).text().toLowerCase() == input.toLowerCase();
  });
  if(extrafilter.length == 1) {
    trend = extrafilter.text();
    join(trend, 'normal');
    makeActive(extrafilter);
  } else if (filter.length == 1) {
    trend = filter.text();
    join(trend, 'normal');
    makeActive(filter);  
  } else {
    // No match, add it as a custom trend
    makeInactive(jQuery("#customlist > table"));
    addCustomSearchToList(input);
    makeActive(jQuery("#customlist > table:last"));
    processCustom();
  }
}

// REMEMBER THAT POPULAR TRENDS ACTUALLY CHANGE FOOL
function processSavedQuery() {
  var trends = false, geoloc = false;

  if (jQuery.query.has('trend')) {
    trends = jQuery.query.get('trend');
  }
  if (jQuery.query.has('geoloc')) {
    geoloc = jQuery.query.get('geoloc');
    distance = jQuery.query.get('distance');
    unit = jQuery.query.get('unit');
  }
  
  if (trends != false) {
    if (!(isArray(trends))) {
      var newtrends = new Array();
      newtrends.push(trends);
      trends = newtrends;
    }
  
    processListOfTrends(trends);
  }
  
  if (geoloc != false) {
    addInGeolocation(geoloc, distance, unit)
  } else {
    processCustom();
  }
}

function processListOfTrends(trends) {
  // If there are no trends, then set All Terms on
  if ((trends.length == 0) && (prefs.data.timeline == '') && (prefs.data.dm == '')) {
    join('All Terms', 'normal');
    makeActive(jQuery("#trendlist > .trend:first"));
  } else {
    jQuery.each(trends, function(i, data) {
      data = unescape(data);
      findATermAHome(data);  
    });
  }
}

function redoSaveLink() {
  var trends = new Array;
  
  jQuery("#trendlist > .active, #extralist > .active").each(function() {
    trends.push(escape(jQuery(this).text()));
  });
  
  // Colour picker causes extra spaces, so need to do a different filter:
  jQuery("#customlist > table:gt(0)").each(function() {
    trends.push(escape(jQuery(this).find('.searchname').text() + '!' + jQuery(this).data('colour')));
  });
  
  // Do cookie while we're at it.
  prefs.data.trends = trends;
 
  var querystring = "?trend=" + trends.join("&trend=");
  if (searchgeocode != false) {
    querystring = querystring + '&geoloc=' + escape(jQuery("#location").val())
                              + '&distance=' + jQuery("#distance").val()
                              + '&unit=' + jQuery("#unit").val();
    // Save geolocation in cookie too
    prefs.data.geoloc = jQuery("#location").val();
    prefs.data.distance = jQuery("#distance").val();
    prefs.data.unit = jQuery("#unit").val();
  } else {
    // If the searchgeocode IS false, be sure to get shot of it from the cookie.
    delete prefs.data.geoloc;
    delete prefs.data.distance;
    delete prefs.data.unit;
  }
  if (querystring == '?trend=') {
    querystring = '';
  }
  
  prefs.save();
  jQuery("#savelink").attr("href", 'http://twitterfall.com/' + querystring);
}

function loadInitialPrefs() {
  prefs.data.trends = new Array();
  jQuery("#settings > select").each(function() {
    var name = jQuery(this).attr("id");
    var val = jQuery(this).val();
    // http://www.quirksmode.org/js/associative.html
    prefs.data[name] = val;
  });
  prefs.data.oauth_request_token = prefs.data.oauth_request_token_secret =
  prefs.data.oauth_access_token = prefs.data.oauth_access_token_secret = '';
  
  prefs.data.timeline = prefs.data.dm = '';
}

function twitterLogin(oauth_token) {
  jQuery.post('post.php',
         {f: 'getaccess', oauth_request_token: oauth_token, oauth_request_token_secret: prefs.data.oauth_request_token_secret},
         function(data) {
           if (data.oauth_access_token != "") {
             prefs.data.oauth_access_token = data.oauth_access_token;
             prefs.data.oauth_access_token_secret = data.oauth_access_token_secret;
             prefs.save();
             loggedInUI();
           } else {
             alert("There was an error getting an access token from Twitter. You could still be fine. Try removing any '?oauth_token=' from the address in your browser. We're working on ironing out these oAuth bugs soon.");
           }
         },
         "json"
        );
}

function loggedInUI() {
  jQuery(".login", jQuery("#left-wrapper")).text('Logged In');
  jQuery("#login").fadeOut("slow", function() {
                 // Encapsulated in a div because it's faster to insert.
                 jQuery("#loginwrap").html('<div><form id="showmytimeline">'
                   +	'<label id="showtimelinelabel" for="showmytimeline">Timeline</label><input type="checkbox" id="showmytimelineval"></input>'
                   +	'<label for="changetimelineint">Timeline Intvl.</label><select id="changetimelineint"><option value="60000">1 min</option><option value="75000" selected="yes">1.25 mins</option><option value="120000">2 mins</option><option value="180000">3 mins</option><option value="300000">5 mins</option><option value="600000">10 mins</option></select>'
                   +	'</form><br />'
                   +  '<form id="showdirectmessages">'
                   +	'<label id="showdmlabel" for="showmydirectmessages">Direct Msgs.</label><input type="checkbox" id="showdirectmessagesval"></input>'
                   +	'<label for="directmessageint">DM Interval</label><select id="changedmint"><option value="60000">1 min</option><option value="120000" selected="yes">2 mins</option><option value="180000">3 mins</option><option value="300000">5 mins</option><option value="600000">10 mins</option></select>'
                   +	'</form><br />'
                   + '<p><a id="logout">Logout</a></p>'
                   +  '<br /></div>');
                 jQuery("#showmytimeline").data("id", 0);
                 jQuery("#showdirectmessages").data("id", 0);
                 
                 if (prefs.data.timeline != '') {
                  jQuery("#changetimelineint").val(prefs.data.timeline);
                  jQuery("#showmytimelineval").click();
                 }
                 if (prefs.data.dm != '') {
                  jQuery("#changedmint").val(prefs.data.dm);
                  jQuery("#showdirectmessagesval").click();
                 }
               });
}

function processSettings() {
  jQuery("#settings > select").each(function() {
    var name = jQuery(this).attr("id");
    // http://www.quirksmode.org/js/associative.html
    if (prefs.data[name]) {
      jQuery(this).val(prefs.data[name]);
      // Make handlers do something
      jQuery(this).change();
    }
  });
}

function addInGeolocation(geoloc, distance, unit) {
  // Bit hacky
  jQuery("#geoPanel > .header").click();
  jQuery("#location").val(unescape(geoloc));
  jQuery("#distance").val(distance);
  jQuery("#unit").val(unit);
  showAddress(unescape(geoloc), function(coordinates) {
    searchgeocode = {"lat": coordinates.lat,
                     "lon": coordinates.lon,
                     "distance": jQuery("#distance").val(),
                     "unit": jQuery("#unit").val()};
    processCustom();
    redoSaveLink();
  });
}

function makeActive(trend) {
  trend.addClass('active')
       .find('.trend-checkbox')
         .css('visibility', 'visible')
         .attr('checked', 'checked')
       .end()
       .unbind("mouseleave");
}

function makeInactive(trend) {
  var c = trend.find('.trend-checkbox');
  trend.removeClass('active');
  c.attr('checked', '').css('visibility', 'hidden');

  if (trend.css('background-color') == 'rgb(255, 255, 255)') {
    c.css('visibility', 'visible');
  }  
  // Redo the mouseleave so that the checkbox disappears
  trend.mouseleave(function() {
    jQuery(this).find('.trend-checkbox').css('visibility', 'hidden');
  });
}

function loginBindings() {
  jQuery("#authorize").click(function() {
    jQuery.post('post.php',
             {f: 'gettoken'},
             function(data) {
               if (data.oauth_request_token != "") {
                 prefs.data.oauth_request_token = data.oauth_request_token;
                 prefs.data.oauth_request_token_secret = data.oauth_request_token_secret;
                 prefs.save();
                 document.location = 'https://twitter.com/oauth/authorize?oauth_token=' + data.oauth_request_token;
               } else {
                 alert("There was an error getting a request token from Twitter");
               }
             },
             "json"
            );

  })
}

/*
function getApiLimit() {
  var request;
  if (guser != '') {
    request = function(callback) {
      jQuery.ajax({
        type: "GET",
        url: "http://twitter.com/account/rate_limit_status.json",
        dataType: "jsonp",
        username: guser,
        password: gpass,
        success: function(data) {callback(data)},
        error: function(textStatus) {
          alert(textStatus);
        }
      });
    }  
  } else {
    request = function(callback) {
      jQuery.ajax({
        type: "GET",
        url: "http://twitter.com/account/rate_limit_status.json",
        dataType: "jsonp",
        success: function(data) {callback(data)}
      });      
    }
  }
  
  request(function(data) {
    jQuery("#api").html(data.remaining_hits);
  });
}
*/

function timelineMouseover(e) {
  var hovered = jQuery(e.target);
  var tag = e.target.tagName.toLowerCase();
  
  // More optimal I expect?
  if (hovered.parents('.post').length != 0) {
    var p = hovered.parents('.post');
    jQuery("#myTweetResults").unbind('mouseover');
    pauseQueue();
    p.find('.icons').css('visibility', 'visible').end()
     .find('.time').css('display', 'block').html(makeDateString(p.data("time"))).end()
     .addClass("highlight")
     .mouseleave(function() {
       jQuery("#myTweetResults").mouseover(function(e) {
         timelineMouseover(e);
       });
       restartQueue();
       jQuery(this).unbind('mouseleave')
              .find('.icons').css('visibility', 'hidden').end()
              .find('.time').css('display', 'none').end()
              .removeClass("highlight");
     });
  }
}

function makeDateString(String) {
  if (String == "") {
    return "";
  }
  // Takes: "Fri, 06 Mar 2009 23:04:38 +0000"
  var d = new Date();
  
  var then = Date.parse(String);
  var now = d.getTime();
  var bit;
  if ((now - then) <= 3600000) {
    (((now - then) % 60000) > 29) ? bit = 1 : bit = 0
    var minutes = parseInt((now - then) / 60000) + bit;
    return minutes + " minutes ago";
  } else if ((now - then) <= 86400000) {
    (((now - then) % 3600000) > 29) ? bit = 1 : bit = 0
    var hours = parseInt((now - then) / 3600000) + bit;
    return hours + " hours ago";
  } else {
    (((now - then) % 34560000) > 11) ? bit = 1 : bit = 0
    var days = parseInt((now - then) / 34560000);
    return days + " days ago";
  }
  
}

function addToQueue(value, queue) {
  var q = queue || rollingids;
  q.pop();
  q.unshift(value);
}
function enableupdatebutton() {
	
  if(jQuery("#frmTweetHiddenvalue").val()==jQuery.trim(jQuery("#replyboxtext").val()) || jQuery.trim(jQuery("#replyboxtext").val())=="")
  {
	jQuery("#replysubmit").css("display","none");
	jQuery("#retweetsubmit").css("display","none");
	jQuery("#updatesubmit").css("display","block");
	jQuery("#usernamestatus").html('What are you doing?');  
  }
}
 
function isArray(obj) {
  if (obj.constructor.toString().indexOf("Array") == -1) {
    return false;
  } else {
    return true;
  }
};

