function WebLab_TwitterAPI_Instance( screen_name, container, interval )
{
	// Check if the neccesary parameters are given.
	if( screen_name == null || container == null )
	{
		throw 'Username or container not defined';
		return null;
	}

	// Create private members.
	var _private =
	{
		screen_name: null,				// The Twitter screenware.
		
		container: null,				// The container for the tweets.
		tweetTemplate: null,			// The template for a tweet.
                clickable_url: true,                    // Have clickable url's in tweets

		tweetCount: 10,					// The number of tweets pulled from Twitter, max 200
		
		dateFormat: 'd/m/y h:M:s',		// Format for the date.
										// Posibilities:
										// d	Day of month
										// m	Month
										// y	Year
										// h	Hour
										// M	Minutes
										// s	Seconds
										// l	Local time format
										// D	Day of the week
		
		interval: 60000, 				// The interval in which we check again for tweets. Default: 1 minute
		intervalId: 0,					// The id of the currently running interval
		
		// Information about the callback created
		callbackInfo: {	callbackId: null, scriptTag: null },				

                make_url_clickable: function( str )
                {
                    var regexp = /\b((http|ftp|https):\/\/|www\.)\S+\.\S+\b/ig;
                    var match = regexp.exec( str );
                    while( match != null )
                    {
                        var url = match[0].match( /^(http|ftp|https):\/\// ) ? match[0] : 'http://' + match[0];
                        url = '<a href="' + url + '">' + match[0] + '</a>';
                        str = str.replace( match[0], url );
                        regexp.lastIndex = regexp.lastIndex + ( url.length - match[0].length );
                        
                        match = regexp.exec( str );
                    }
                    return str;
                },

        formatChar: {
				d: function(d){ return d.getDate(); },
				m: function(d){ return d.getMonth()+1; },
				y: function(d){ return d.getFullYear(); },
				h: function(d){ return d.getHours(); },
				M: function(d){ return d.getMinutes(); },
				s: function(d){ return d.getSeconds(); },
				l: function(d){ return d.toLocaleString(); },
				D: function(d){ return d.getDay()+1; }
			},
		formatDate: function( date )	// Format the date into the format the user want.
			{
				// Get format for manipulation.
				dateFormat = _private.dateFormat;
				
				// For each possibility check if it is present.
				for( letter in _private.formatChar )
				{
					dateFormat = dateFormat.replace( letter, _private.formatChar[letter](date) );
				}
				
				return dateFormat;
			},
			
		parseValue: {
				month: {
					Jan: 1,
					Feb: 2,
					Mar: 3,
					Apr: 4,
					May: 5,
					Jun: 6,
					Jul: 7,
					Aug: 8,
					Sep: 9,
					Oct: 10,
					Nov: 11,
					Dec: 12
				}
			},
			
		parseDate: function( date )
			{
				var regexp = /\S+\s(\S+)\s(\d{2})\s(\d{2}):(\d{2}):(\d{2})\s\+\d{4}\s(\d{4})/,
					parts = regexp.exec( date ),
					date = new Date(),
					month = _private.parseValue.month[parts[1]]-1;
				
				date.setFullYear(parts[6], month, parts[2]);
				date.setHours( parts[3], parts[4], parts[5], 0);
				
				return date;
			},
		
		requestHandler: function(){ return _private.callbackInfo; },		// Makes a request.
		makeRequest: function()				// Called to make a request to the Twitter API
		{
			// Call the requestHandler giving it a reference to this object.
			// This function returns the handlerId, pass it on to the caller.
			return _private.requestHandler( _public );
		},

		// Get elements with a specific class
		getElementsByClassName: function( className, parent )
		{
			// If parent isn't given we want all of the elements to be checked
			if( parent == null )
				parent = _private.container == null ? document : _private.container;
			
			var allNodes = parent.getElementsByTagName("*");
			var nodes = new Array();
			var regexp = new RegExp('\\b' + className + '\\b');
			
			for( i=0; i<allNodes.length; i++)
				{
					if(regexp.test( allNodes[i].className ) )
					{
						// If the node has the class add it to the list to be returned
						nodes.push( allNodes[i] );
					}
				}
			
			return nodes;
		}
	};

	// Create public members.
	var _public = 
	{
		getScreenName: function()
			{
				return _private.screen_name;
			},

		setScreenName: function( screen_name )
			{
				_private.screen_name = screen_name;
				_public.update();
			},

		getInterval: function()
			{
				return _private.interval;
			},

		setInterval: function( interval )
			{
				if( interval == null )
				{
					return;
				}
				window.clearInterval( _private.intervalId );
				_private.interval = interval;
				_private.intervalId = window.setInterval( function(){ _public.update(); }, _private.interval );
			},

		getTweetCount: function()
			{
				return _private.tweetCount;
			},

		setTweetCount: function( tweetCount )
			{
				_private.tweetCount = tweetCount;
				_public.update();
			},

		setRequestHandler: function( handler )
			{
				_private.requestHandler = handler;

				// Only let the object that creates the instance set this.
				// By deleting this method we ensure this.
				_public.setRequestHandler = null;
			},
			
		setDateFormat: function( format )
			{
				format = format.replace(/^\s+/,''); 
	  			format = format.replace(/\s+$/,'');
	  			
				if( format.length > 0 )
				{
					_private.dateFormat = format;	
				}
				
			},
			
		getDateFormat: function()
			{
				return _private.dateFormat;	
			},

		getCallbackInfo: function()
			{
				return _private.callbackInfo;
			},

                setClickableUrls: function( clickable )
                        {
                            _private.clickable_url = clickable === true ? true : false;
                        },

                getClickableUrls: function()
                        {
                            return _private.clickable_url;
                        },

		resetCallbackInfo: function()
			{
				_private.callbackInfo = { callbackId: null, scriptTag: null };
			},

		update: function( tweets )
			{
				// If no Twitter data is supplied we make a request and exit.
				if( tweets == null )
				{ 
					// Store the callback information for later use.
					_private.callbackInfo = _private.makeRequest();
					return;
				}

				// For filling a lot of HTML nodes with the same content.
				function setInnerHTML( nodes, html )
				{
					for( i in nodes )
					{
						nodes[i].innerHTML = html;
					}
				}

				// Remove old nodes before inserting the new ones.
				var oldNodes = _private.getElementsByClassName( 'tweet', _private.container );
				for( i in oldNodes )
				{
					_private.container.removeChild( oldNodes[ i ] );
				}

				// If we pulled tweets from the server, we can read the user property en set user information
				if( tweets.length > 0 )
				{
					var user = tweets[0].user;

					// Place the screenname in the template
					var name = _private.getElementsByClassName( 'name', _private.container );
					setInnerHTML( name, user.screen_name );

					// Place the image in the template.
					var image = _private.getElementsByClassName( 'image', _private.container );
					for( i in image )
					{
						image[i].src = user.profile_image_url;
					}

					var location = _private.getElementsByClassName( 'location', _private.container );
					setInnerHTML( location, user.location );
				}
				
				// Create a tweet from the template and insert it into the container
				for( i in tweets )
				{
					var tweet = tweets[i];
					var template = _private.tweetTemplate.cloneNode( true );

                    // Make urls clickable
                    if( _private.clickable_url )
                        tweet.text = _private.make_url_clickable( tweet.text );

					var text = _private.getElementsByClassName( 'text', template );
					setInnerHTML( text, tweet.text );

					var date = _private.getElementsByClassName( 'date', template );
					var tweetDate = _private.parseDate( tweet.created_at );
					setInnerHTML( date, _private.formatDate( tweetDate ) );

					_private.container.appendChild( template );
				}
			}
	}

	// Constructor
	_public.setInterval( interval );
	_private.screen_name = screen_name;
	
	_private.container = document.getElementById( container );

	// Copy the .tweet into a local variable.
	_private.tweetTemplate = _private.getElementsByClassName( 'tweet' )[0];
	if( _private.tweetTemplate == null )
		_private.tweetTemplate = document.createElement('div');
	
	// Remove it from the DOM
	_private.tweetTemplate.parentNode.removeChild( _private.tweetTemplate );

	// Get format from tag.
	var date = _private.getElementsByClassName( 'image' );
	if( date.length > 0 )
	{
		_public.setDateFormat( date[0].innerHTML );
	}
	
	return _public;
}

// Handler, maintainer.
function WebLab_TwitterAPI_Helper()
{
	var _private =
	{
		makeRequest: function( forInstance )
		{
			// Find empty spot in the callback array
			var callbackId;
			for( i=0; i <= _public.callbacks.length; i++ )
			{
				if( _public.callbacks[i] == null )
				{
					callbackId = i;
					break;
				}
			}

			// Get information of a possible previous request.
			requestInfo = forInstance.getCallbackInfo();
			
			// If there is already a query to Twitter in progress, make sure nothing happens with the data.
			if( _public.callbacks[ requestInfo.callbackId ] != null )
			{
				_public.callbacks[ requestInfo.callbackId ] = function( tweets )
				{
					// Do nothing with data.
					
					// Reset callback
					forInstance.resetCallbackInfo();

					// Remove Script tag.
					document.getElementsByTagName('head')[0].removeChild( requestInfo.scriptTag  );

					// Remove request
					_public.callbacks[ requestInfo.callbackId ] = null;
				}
			}

			// Create the script tag
			// Script tag is used because Twitter is on a different domain.
			// XMLHttpRequest can't operate cross-site.
			var tag = document.createElement( 'script' );
			tag.type = 'text/javascript';
			tag.src = 'http://twitter.com/statuses/user_timeline/' +
				forInstance.getScreenName() + '.json?count=' +
				forInstance.getTweetCount() + '&callback=WebLab_TwitterAPI' +
				'.callbacks[' + callbackId + ']';

			// Declare callback and assign it.						
			_public.callbacks[ callbackId ] = function( data )
				{
					// Call the update of the feed forInstance.
					forInstance.update( data );
					
					// Reset callback
					forInstance.resetCallbackInfo();

					// Remove Script tag.
					document.getElementsByTagName('head')[0].removeChild( tag );

					// Remove request
					_public.callbacks[ callbackId ] = null;
				}

			// Append script tag to head, script starts loading.
			document.getElementsByTagName('head')[0].appendChild( tag );

			// Return the requestId to the instance so it can cancel this when a new one is made.
			return { callbackId: callbackId, scriptTag: tag };
		}
	}

	var _public =
	{
		callbacks: new Array(),

		// Creation of an instance
		create: function( screen_name, container, interval )
		{
			instance = new WebLab_TwitterAPI_Instance( screen_name, container, interval );
			instance.setRequestHandler( function( forInstance ){ return _private.makeRequest( forInstance ) } );

			return instance;
		}
	}

	return _public;
}

var WebLab_TwitterAPI = new WebLab_TwitterAPI_Helper();

