var PostStream = function(options) {
	// allow creating without options - can call init(options) later
	if (options) this.init(options);
};

PostStream.prototype = {
	defaults: {
		// update callback, called whenever new posts are received
		update_callback: function(){},
		
		// error callback, called if there is an ajax error
		error_callback: function(){},
		
		ajaxOptions: {
			url: '/ajax/?method=search_proxy.get', //?method=tiles.get',
			type: 'GET',
			dataType: 'json',
			cache: false,
			data: {}
		},

		// call load() immediately? default yes
		autoload: true,

		limit: 20,
		filter_data: null,
		extras: null,
		hours: 24,
		to: null,
		view: 'stream',
		
		// if an array of posts are provided, we'll use those instead of ajax
		posts: null,
		
		add_tile_ids: false,
		add_since_time: true,
		refresh_interval: 30000 // check every 30 seconds
	},
	
	guid: 0,
	
	// store reusable regular expressions here, for speed
	regx: {
		linkdomain: /^https?:\/\/([^\/]+)\//i,
		tags: /<[^>]+>/g
	},
	
	init: function(options){
		// if this is an active instance, destroy first
		if (this.instance) {
			this.destroy();
		}
		
		this.options = $.extend(true, {}, this.defaults, options || {});
		
		// intialize the tile_ids object
		this.tile_ids = {};
		
		// initialize time of last update
		this.last_timestamp = 0;
		
		this.refresh_interval = this.options.refresh_interval;
		
		// increment the unique instance number, and only process xhr callbacks if the instance number is the same as when it started
		this.instance = ++PostStream.prototype.guid;
		
		if (this.options.autoload) {
			this.load();
		}
	},

	// stop updating, clear any data, abort any current requests
	destroy: function(){
		// no longer an active instance (don't process callbacks for xhr)
		this.instance = this.options = false;

		this.stop_update();
	},

	load: function(ajaxOptions, localOptions, callbackData){
		// if posts were provided, use those instead of ajax
		if (this.load_static_posts()) {
			return;
		}

		// unschedule any pending update, will be rescheduled when we finish
		this.stop_update();
		
		var self = this,
			o = $.extend(true, {}, this.options, localOptions || {}),
			instance = this.instance, // remember the instance number, only process callbacks if it matches
			ajaxOpt = $.extend
				(
				 	true, 
				 	{
						// default complete handler
						complete: function(){
							if (self.instance === instance) self.update_complete();
						},
				
						// default success handler
						success: function(posts){
							if (self.instance === instance) self.update_success(posts, callbackData);
						},
				
						error: function(){
							if (self.instance === instance) self.update_error(callbackData);
						},
				
						// add default data, computing the tile_ids and since time, if necessary
						data: {
							tile_ids: o.add_tile_ids && this.get_tile_ids() || '',
							since: o.add_since_time && this.last_timestamp || '',
							filter_data: o.filter_data && JSON.stringify(o.filter_data) || '',
							extras: o.extras || '',
							limit: o.limit || '',
							hours: o.hours || '',
							argument_id: o.argument_id || '',
							view: o.view || '',
							first_available_shard: o.first_available_shard || 0
						}
					},
					o.ajaxOptions, 
					ajaxOptions || {}
				); // make a copy of the ajaxOptions template
				
		// iff this is an active instance, run the ajax call
		if (instance) $.ajax(ajaxOpt);
	},
	
	load_static_posts: function(){
		// static_posts will be a clone of the posts option, if provided
		this.static_posts = this.static_posts || $.merge([], this.options.posts || []);
		
		if (!this.static_posts.length) {
			return false; // no, we didn't have any static posts
		}
		
		// use the limit to determine how many to give at a time
		var posts = this.static_posts.splice(0, this.options.limit);
		
		// update
		this.update_success(posts);
		
		return true; // yes, we had & used static posts
	},
	
	update_complete: function(){
		this.schedule_update();
	},
	
	update_success: function(posts, callbackData) {
		// restore interval to normal, in case it was extended due to errors
		this.refresh_interval = this.options.refresh_interval;
		
		var actual_posts = [];
		
		for (var i in posts) {
			var post = this.add_post(posts[i]);
			
			// don't include filtered posts in the actual post array
			if (post) {
				actual_posts[actual_posts.length] = post;
			}
		}

		// we call the callback, where the posts may be added to the page, reordered, etc.
		this.options.update_callback(actual_posts, callbackData);
	},
	
	update_error: function(callbackData){
		// double the refresh interval every time an error happens, until a success
		this.refresh_interval *= 2; 
		
		// call the error callback
		this.options.error_callback(callbackData);
	},

	add_post: function(post) {
	
		if(!post)
			return null;
			
		if (post.type === 'info') {
			if(post.timestamp > this.last_timestamp)
				this.last_timestamp = post.timestamp;
				
			return false; // don't include 'info' posts

		// detect blog results - they need massaging
		} else if (post.source != 'twitter' && post.linkback && !post.user) {
			// we'll set the "id" (like username) to be the domain of the link
			var link = this.regx.linkdomain.exec(post.linkback);
			
			post.user = {
				id: link[1],
				avatar: 'http://' + link[1] + '/favicon.ico',
				display: link[1]
			};
			
			// strip tags out of post - probably should happen on server or before adding to database
			post.post = post.post.replace(this.regx.tags, '');
			
			// give a fmsgid and original_id - the id
			post.fmsgid = post.original_id = post.id;
		} else {
			if(post.time && post.time > this.last_timestamp)
				this.last_timestamp = post.time;
			
			// keep track of the (unique) post fmsgid's, for future ajax calls
			this.tile_ids[post.fmsgid] = true;

			// normalize some data (old post data to new post data format)
			post.user = post.user || {
				id: post.uid || post.id || post.from,
				avatar: post.sm_colour_photo,
				bio: post.bio,
				extid: post.extid,
				followers: post.followers,
				following: post.following,
				gender: post.gender,
				name: post.username
			};
			
			post.user.display = (post.source === 'twitter' ? '@'+post.user.id : post.name);
			post.date = post.date || post.post_date || post.post_time;
			post.time = post.post_time || Math.round(new Date(post.date).getTime()/1000);
			post.original_id = post.original_id || post.fmsgid;
			post.uid = post.user.id;
		}
		return post;
	},
	
	// schedule an update in the future
	schedule_update: function(){
		var self = this;

		// don't schedule if refresh_interval is 0/false
		if (this.refresh_interval) {
			this.timeout = setTimeout(function(){
				self.load();
			}, this.refresh_interval);
		}
	},
	
	// clear the timeout for the scheduled update
	stop_update: function(){
		clearTimeout(this.timeout);
		this.timeout = null;
	},
	
	get_tile_ids: function(){
		 var ids = [];
		 for (var k in this.tile_ids) {
		 	ids[ids.length] = k;
		 }
		 return ids.length && ids.join(',');
	}
};

