text: var query = require( 'querystring' ),
  	http = require( 'http' );
  
  var client = http.createClient( 80, "search.twitter.com" );
  
  var TwitterSearch = function ( refreshInterval ) {
  	this.events = {};
  
  	this.timeoutFn = undefined;
  
  	this.searchPath = "/search.json";
  
  	this.refreshInterval = refreshInterval || 5000;
  
  	this.searchRegistry = {};
  
  	this.tweetRegistry = {};
  
  	this.defaultSearch = {};
  
  	this.nextSearch = undefined;
  };
  
  TwitterSearch.prototype = {
  	search : function ( searchTerm ) {
  		clearTimeout( this.timeoutFn );
  		this.raiseEvent( "search.init", searchTerm );
  		this.defaultSearch = {
  			q : searchTerm,
  			result_type : "recent",
  			rpp : 100
  		};
  		this.nextSearch = undefined;
  		this.searchRegistry = {};
  		this.tweetRegistry = {};
  		this.runSearch();
  	},
  	on : function ( eventName, callback ) {
  		if ( !this.events[ eventName ] ) {
  			this.events[ eventName ] = [];
  		}
  		this.events[ eventName ].push( callback );
  		return function () {
  			this.events[ eventName ] = _.without( this.events[ eventName ], callback );
  		}.bind( this );
  	},
  	raiseEvent : function ( eventName, data ) {
  		if ( this.events[ eventName ] ) {
  			this.events[ eventName ].forEach( function ( callback ) {
  				callback( data );
  			} );
  		}
  	},
  	buildUrl : function () {
  		var srchQry;
  		if ( this.nextSearch ) {
  			srchQry = this.nextSearch;
  		}
  		else {
  			if ( !this.defaultSearch.since_id ) {
  				delete this.defaultSearch.since_id;
  			}
  			srchQry = "?" + query.stringify( this.defaultSearch );
  		}
  		return this.searchPath + srchQry;
  	},
  
  	runSearch : function () {
  		var url = this.buildUrl();
  		if ( !this.searchRegistry[url] ) {
  			this.searchRegistry[url] = true;
  			this.raiseEvent( "search.current", { url : url } );
  			var request = client.request( "GET", url, {"host" : "search.twitter.com"} );
  			request.addListener( "response", function ( response ) {
  				var body = "";
  				response.addListener( "data", function ( data ) {
  					body += data;
  				} );
  
  				response.addListener( "end", function () {
  					this.onSearched( JSON.parse( body ) );
  				}.bind( this ) );
  				this.timeoutFn = setTimeout( this.runSearch.bind( this ), this.refreshInterval );
  			}.bind( this ) );
  			request.end();
  		}
  		else {
  			this.raiseEvent( "search.nodata", { url : url } );
  			this.timeoutFn = setTimeout( this.runSearch.bind( this ), this.refreshInterval );
  		}
  	},
  	onSearched : function ( payload ) {
  		// sift out errors
  		if ( !payload.error ) {
  			if ( payload.results.length > 0 ) {
  				this.processTweets( payload.results );
  			}
  			// do we have a next page option?  If so, let's use it
  			if ( payload.next_page ) {
  				this.nextSearch = payload.next_page;
  			}
  			if ( payload.max_id ) {
  				this.defaultSearch.since_id = payload.max_id;
  			}
  		}
  		else {
  			this.nextSearch = undefined;
  		}
  	},
  	processTweets : function ( tweets ) {
  		var newTweets = tweets.filter( function ( t ) {
  			return !this.tweetRegistry[t];
  		}, this );
  		newTweets.forEach( function ( tweet ) {
  			if ( !this.tweetRegistry[tweet.id] ) {
  				this.tweetRegistry[tweet.id] = true;
  			}
  			// deal with the images that consistently fail from twitter...
  			if ( tweet.profile_image_url === "http://twitter.com/images/default_profile_normal.png" ||
  			     tweet.profile_image_url === "http://static.twitter.com/images/default_profile_normal.png" ) {
  				tweet.profile_image_url = "templates/images/default_profile_1_normal.png";
  			}
  		}, this );
  		this.raiseEvent( "newTweets", newTweets );
  	}
  };
  
  
  module.exports = TwitterSearch;