(function($){
	var settings = {
		channel: null, // Required
		url: null, // Required: e.g. "/chat/{cmd}/1"
		mainChannel: null, // Optional
		channelUrl: null, // Optional
		channelPrefix: null, // Optional
		anonymousUser: null, // Optional
		analytics: [], // Optional (_gaq-object)
		isAdmin: false,
		feedSelector: '.feed',
		questionsSelector: '.questions',
		pollsSelector: '.polls',
		adminSelector: '.admin',
		feedRefreshRate: 20,
		pollsRefreshRate: 20,
		messageCount: 20,
		maxLength: 140,
		defaultText: {
			textStyle: {
				color: '#999',
				fontStyle: 'italic',
				width: 'auto'
			}
		},
		strings: {
			noComments: 'No comments yet',
			noMoreComments: 'No more comments available',
			sendError: 'Error sending message. Maybe because of repeating?',
			reply: 'reply',
			cancelReply: 'cancel',
			nicknameInput: 'Insert your nickname',
			msgPlaceholder: 'Write new message',
			msgMaxLengthReached: 'Maximum message length reached',
			admin: {
				removeMessage: 'Remove message',
				confirmRemoveTweet: 'Delete this message permanently?',
				confirmRemoveMessage: 'Remove this message?',
				cannotRemoveTweet: 'Cannot remove this message from twitter. If not separately removed, it will show up again.',
				createPoll: 'Create new poll',
				pollQuestion: 'Question',
				pollChoices: 'Choices',
				pollAddChoice: 'Add choice',
				pollRemoveChoice: 'remove',
				pollCreated: 'Poll created',
				removePoll: 'Remove this poll',
				confirmRemovePoll: 'Delete this poll and all the voting results permanently?'
			}
		}
	};
	
	var usernamePattern = /^\s*\[([^\]]*)\]\s*/;
	var mentionPattern = /^\s*@(\S*)\s*/;
	var linkPattern = /(http(?:s)?:\/\/(?:.+?))(\. |, | |\)|\()/ig;
	var linkAtEndPattern = /(http(?:s)?:\/\/(?:\S+?))(\.?)$/ig;
	var questionPattern = /^q:\s*(.*)$/i;
	var hashtagPattern = /#([\wäö]+)/ig;
	var twitterUserPattern = /@(\S*)/g;
	
	var methods = {
		settings: function(options){
			$.extend(true,settings,options);
		},
		init: function(options){
			return this.each(function(){
				var chat = $(this);
				
				if( options ) { 
					$.extend(true,settings,options);
				}
				
				// #channel #mainChannel [username] message
				var maxLength = settings.maxLength - settings.channel.length - 2;
				if( settings.mainChannel != null && settings.mainChannel.length > 0 ) {
					maxLength -= settings.mainChannel.length - 2;
				}
				
				chat.data('tweetChat',{
					maxLength: maxLength
				});
				
				$('input.message',chat).live('keypress',function(event){
					// Enter-key sends the message
					if( event.keyCode == '13' ) {
						event.preventDefault();
						chat.tweetChat('sendMessage');
					}
				}).live('keyup',function(event){
					// Check for maximum length
					var maxLength = chat.data('tweetChat').maxLength;
					var mentionLength = 0;
					var parentTweet = $(this).closest('p.tweet');
					if( parentTweet.length > 0 ) {
						mentionLength = parentTweet.children('.sender-account').text().length + 2;
						if( parentTweet.children('.sender-channel').text() != settings.channel ) {
							mentionLength += parentTweet.children('.sender-channel').text().length + 2;
						}
					}
					var messageLength = $(this).val().length;
					var usernameLength = $('.my_username',chat).text().length;
					if( messageLength + usernameLength + mentionLength > maxLength  ) {
						event.preventDefault();
						$(this).val($(this).val().substr(0,maxLength-usernameLength-mentionLength));
						var info = $(this).closest('.chat').find('.msginfo'); 
						info.text(settings.strings.msgMaxLengthReached)
							.stop(true).fadeTo(1,1,function(){info.fadeTo(3000,0.9,function(){info.fadeTo(1500,0);});});
					}
				}).defaultText(settings.defaultText);
				
				$('input.send',chat).live('click',function(){
					chat.tweetChat('sendMessage');
					return false;
				});
				
				// Clicking 'reply' moves message input under the tweet that user replies to
				$('.tweet a.reply',chat).live('click',function(){
					if( $(this).text() == settings.strings.reply ) {
						$('.tweet a.reply',chat).text(settings.strings.reply).removeClass('cancel-reply');
						if( $('.msg-placeholder',chat).length > 0 ) {
							$('.msg',chat).remove().appendTo($(this).closest('.tweet'));
						} else {
							$('.msg',chat)
								.replaceWith($('<p class="msg-placeholder ui-corner-all">'+settings.strings.msgPlaceholder+'</p>'))
								.appendTo($(this).closest('.tweet'));
						}
						$('.msg .message',chat).defaultText(settings.defaultText).focus();
						$(this).text(settings.strings.cancelReply).addClass('cancel-reply');
					} else {
						$('.msg-placeholder').replaceWith($('.msg'));
						$(this).text(settings.strings.reply).removeClass('cancel-reply');
						$('.msg .message').defaultText(settings.defaultText).focus();
					}
					return false;
				});
				
				// Clicking placeholder (original message input) cancels reply
				$('.msg-placeholder',chat).live('click',function(){
					$('a.cancel-reply',chat).click();
					$('.msg input',chat).focus();
					return false;
				});
				
				// "Read more" -link loads older messages
				$('.readmore',chat).attr('rel',settings.messageCount).live('click',function(){
					var offset = parseInt($(this).attr('rel'));
					chat.tweetChat('updateFeed',offset);
					offset += settings.messageCount;
					$(this).attr('rel',offset);
					return false;
				});
				
				// Clicking poll gives user's vote to that choise
				$('.poll .choice',chat).live('click',function(){
					var poll = $(this).closest('.poll');
					var pollKey = poll.attr('id').substr(5);
					var choice = $(this).attr('rel');
					poll.append('<div class="loader"></div>');
					$.post(settings.url.replace('{cmd}','votePoll'),{
						poll: pollKey,
						vote: choice,
						time: new Date()
					},function(data,status){
						$('.loader',poll).remove();
						chat.tweetChat('updatePoll',poll,data.poll);
						// Track analytics
						settings.analytics.push(['_trackEvent','TweetChat','PollVoted']);
					});
					return false;
				});
				
				// Admin can remove messages
				$('.admin-delete-message').live('click',function(){
					var statusElement = $(this).closest('.tweet');
					var statusId = statusElement.attr('id').substr(8);
					var teksti = statusElement.children('.tweet-content').text();
					var viesti = '"'+teksti+'"\n\n'+settings.strings.admin.confirmRemoveTweet;
					if( !statusElement.hasClass('anonymous') ) {
						viesti = '"'+teksti+'"\n\n'+settings.strings.admin.confirmRemoveMessage
							+'\n'+settings.strings.admin.cannotRemoveTweet;
					}
					if( !confirm(viesti) ) {
						return false;
					}
					statusElement.append('<div class="loader"></div>');
					$.post(settings.url.replace('{cmd}','admin'),{
						cmd: 'deleteMessage',
						statusId: statusId,
						time: new Date()
					},function(data){
						if( data.error ) {
							$('.loader',statusElement).remove();
							alert(data.error);
						} else {
							statusElement.remove();
							settings.analytics.push(['_trackEvent','TweetChat','AdminMessageRemoved']);
						}
					});
					return false;
				});
				
				// Admin can create polls
				$('.admin-create-poll').live('click',function(){
					var dialog = $('<form class="create-poll"><div class="title">'+settings.strings.admin.pollQuestion+': <input type="text" name="title" size="30" /></div></form>');
					var choices = $('<div class="choices">'+settings.strings.admin.pollChoices+':</div>');
					choices.append($('<div class="choice"><input type="text" name="choices[]" size="33" /></div>'));
					choices.append($('<div class="choice"><input type="text" name="choices[]" size="33" /></div>'));
					dialog.append(choices);
					dialog.append('<a href="#" class="add-choice">'+settings.strings.admin.pollAddChoice+'</a>');
					dialog.append('<input type="hidden" name="cmd" value="createPoll" />');
					var addChoice = function(){
						choices.append($('<div class="choice"><input type="text" name="choices[]" size="33" /> <a class="remove-choice" href="#">'+settings.strings.admin.pollRemoveChoice+'</a></div>'));
						$('.remove-choice',choices).click(function(){
							$(this).closest('.choice').remove();
							return false;
						});
					};
					$('.add-choice',dialog).click(function(){
						addChoice();
						return false;
					});
					dialog.dialog({
						autoOpen: true,
						modal: true,
						resizable: true,
						title: settings.strings.admin.createPoll,
						buttons: {
							'Ok': function(){
								$.post(settings.url.replace('{cmd}','admin'),dialog.serialize(),function(data){
									if( data.error ) {
										alert(data.error);
									} else {
										var pollElement = chat.tweetChat('insertPoll',data.pollKey,data.poll);
										$(settings.pollsSelector,chat).append(pollElement);
										chat.tweetChat('updatePoll',pollElement,data.poll);
										// Track analytics
										settings.analytics.push(['_trackEvent','TweetChat','AdminPollCreated']);
									}
								});
								dialog.dialog('close').remove();
							},
							'Cancel': function(){
								dialog.dialog('close').remove();
							}
						}
					});
					return false;
				});
				
				// Admin can remove polls
				$('.poll').live('hover',function(e){
					if( settings.isAdmin ) {
						if( e.type == 'mouseenter' ) {
							$(this).prepend('<a class="admin-remove-poll ui-corner-all" title="'+settings.strings.admin.removePoll+'" href="#">X</a>');
						} else if( e.type == 'mouseleave' ) {
							$('.admin-remove-poll',this).remove();
						}
					}
				});
				$('.admin-remove-poll').live('click',function(){
					var pollElement = $(this).closest('.poll');
					var poll = pollElement.attr('rel');
					var room = $(this).closest('.polls').attr('rel');
					if( !confirm(settings.strings.admin.confirmRemovePoll) ) {
						return false;
					}
					pollElement.append('<div class="loader"></div>');
					$.post(settings.url.replace('{cmd}','admin'),{
						cmd: 'removePoll',
						room: room,
						poll: poll,
						time: new Date()
					},function(data){
						pollElement.remove();
						// Track analytics
						settings.analytics.push(['_trackEvent','TweetChat','AdminPollRemoved']);
					});
					return false;
				});
				
				// If username is not set, prompt for username
				if( $('.my_username',chat).text() == '' ) {
					chat.tweetChat('changeUsername');
				}
				$('.change-username',chat).live('click',function(){
					chat.tweetChat('changeUsername');
					return false;
				});
				
				// Updaters for chat and polls
				chat.tweetChat('updateFeed');
				chat.data('tweetChat').feedUpdater = setInterval(
					function(){chat.tweetChat('updateFeed');},
					settings.feedRefreshRate*1000
				);
				chat.tweetChat('updatePolls');
				chat.data('tweetChat').pollUpdater = setInterval(
					function(){chat.tweetChat('updatePolls');},
					settings.pollsRefreshRate*1000
				);
				
			});
		},
		insertMessage: function(tweet){
			var chat = this;
			
			if( tweet == null ) {
				return this;
			}
			if( $('#message_'+tweet.id_str,chat).length > 0 ) {
				// The message already exists
				return this;
			}
			var feed = $(settings.feedSelector,chat);
			
			var newMessage = $('<p class="tweet" id="message_'+tweet.id_str+'"></p>');
			var message = tweet.text;
			
			// If tweet is from search results (could not fetch from twitter)
			if( tweet.user == null ) {
				tweet.user = {
					screen_name: tweet.from_user
				}; 
			}
			
			if( tweet.user.screen_name == settings.anonymousUser ) {
				// First remove the main channel hashtag from the beginning
				if( settings.mainChannel != null ) {
					message = message.replace(new RegExp('^\\s*#'+settings.mainChannel,'i'),'');
				}
				// If the #channel is in (the beginning of) msg, remove it
				message = message.replace(new RegExp('^\\s*#'+settings.channel,'i'),'');
			}
			
			// Replace channel-hashtag with a link to conversation-page
			var channelRegexp = new RegExp('\\s*#('+settings.channelPrefix+'([\\wäö]+))','ig');
			var singleChannelRegexp = new RegExp('\\s*#('+settings.channelPrefix+'([\\wäö]+))','i');
			var channelMatches = message.match(channelRegexp);
			var channelLink = [];
			var senderChannel = null;
			if( channelMatches != null ) for( var i=0; i < channelMatches.length; i++ ) {
				channelMatch = singleChannelRegexp.exec(channelMatches[i]);
				var singleChannel = channelMatch[1].toLowerCase();
				if( singleChannel != settings.mainChannel.toLowerCase() && singleChannel != settings.channel.toLowerCase() ) {
					if( senderChannel == null ) {
						senderChannel = channelMatch[2];
					}
					if( tweet.user.screen_name == settings.anonymousUser ) {
						message = message.replace(channelMatches[i],'');
					}
					channelLink.push('(<a class="channel-link" href="'+settings.channelUrl+channelMatch[2]+'">'+channelMatch[2]+'</a>)');
				}
			}
			
			// Search for place to append
			var parent = null;
			// If the message is reply to existing message, append to that
			if( tweet.in_reply_to_status_id_str ) {
				message = message.replace(mentionPattern,'');
				var parentMessage = $('#message_'+tweet.in_reply_to_status_id_str);
				if( parentMessage.length > 0 ) {
					// Replied message is in the feed
					parent = parentMessage;
				} else {
					// Replied message is not in the feed (outside the channel)
					newMessage.addClass('parent ui-corner-all');
				}
			} else if( settings.mainChannel == settings.channel ) {
				message = message.replace(mentionPattern,'');
				newMessage.addClass('parent ui-corner-all');
			} else {
				// Message is not a reply
				newMessage.addClass('parent ui-corner-all');
			}
			
			var username = '';
			if( tweet.user.screen_name == settings.anonymousUser ) {
				// Message is sent by anonymous user (no twitter account),
				// parse screen name from the message
				newMessage.addClass('anonymous');
				var usernameMatch = usernamePattern.exec(message);
				if( usernameMatch != null ) {
					username = usernameMatch[1];
					message = message.replace(usernamePattern,'');
				} else {
					username = null;
				}
			} else {
				// Message from a twitter account, prepend username to msg
				username = tweet.user.screen_name;
			}
			
			// Append channel link to message
			if( channelLink.length > 0 ) {
				message = message+' '+channelLink.join(' ');
			}
			
			// If the message is a question, add it to questions-box
			questionMatch = questionPattern.exec(message);
			if( questionMatch != null ) {
				if( $('#question_'+tweet.id_str).length == 0 ) {
					$('<a href="#message_'+tweet.id_str+'"></a>').text(questionMatch[1])
						.appendTo('.questions')
						.wrap('<p id="question_'+tweet.id_str+'" class="question"></p>');
				}
				newMessage.addClass('question');
				message = questionMatch[1];
			}
			
			// Create links
			message = message.replace(linkPattern,'<a target="_blank" href="$1" rel="nofollow">$1</a>$2');
			message = message.replace(linkAtEndPattern,'<a target="_blank" href="$1" rel="nofollow">$1</a>$2');
			// Link hashtags to twitter
			message = message.replace(hashtagPattern,'<a target="_blank" href="http://twitter.com/search?q=%23$1">#$1</a>');
			// Link twitter usenames
			message = message.replace(twitterUserPattern,'<a target="_blank" href="http://twitter.com/$1">@$1</a>');
			
			if( username != null ) {
				if( tweet.user.screen_name == settings.anonymousUser ) {
					message = '<span class="sender username">'+username+'</span>: <span class="tweet-content">'+message+'</span>';
				} else {
					message = '<a target="_blank" href="http://www.twitter.com/'+username+'" class="sender username">@'+username+'</a>: <span class="tweet-content">'+message+'</span>';
				}
			} else {
				message = '<span class="tweet-content">'+message+'</span>';
				username = '(no name)';
			}
			if( parent == null ) {
				message = '<img src="'+tweet.user.profile_image_url+'" alt="'+tweet.user.name+'" />'+message;
			}
			// Append reply-button
			message += ' <a href="#" class="reply command">'+settings.strings.reply+'</a>';
			// Append send time to message
			var time = $.timeago(new Date(tweet.timestamp*1000));
			message += '<span class="time" rel="'+tweet.timestamp+'"> - <abbr class="timeago" title="'+tweet.isotime+'">'+time+'</abbr></span>';
			// Add sender account for replies to work
			message += '<span class="sender-account">'+tweet.user.screen_name+'</span>';
			// Add sender channel to show replies in not-main channel too
			if( senderChannel != null ) {
				message += '<span class="sender-channel">'+settings.channelPrefix+senderChannel+'</span>';
			}

			message = message.replace(/\s*(.*)\s*/,'$1');
			newMessage.html(message);
			if( settings.isAdmin ) {
				newMessage.prepend('<a class="admin-delete-message ui-corner-all" title="'+settings.strings.admin.removeMessage+'" href="#">X</a>');
			}
			
			// Insert message to the right place according to timestamp
			if( parent == null ) {
				parent = feed;
			}
			var nextMessage = $('.tweet:first',parent);
			var nextTime = parseInt($('.time',nextMessage).attr('rel'));
			while( nextMessage.length > 0 && tweet.timestamp < nextTime ) {
				nextMessage = nextMessage.next('.tweet');
				var nextTime = parseInt($('.time',nextMessage).attr('rel'));
			}
			if( nextMessage.length > 0 ) {
				newMessage.insertBefore(nextMessage);
			} else {
				parent.append(newMessage);
			}
		},
		updateFeed: function(offset){
			var chat = this;
			return this.each(function(){
				var feed = $(settings.feedSelector,this);
				
				if( feed.children().length == 0 || offset > 0 ) {
					feed.append('<div class="loader"/></div>');
				}
				
				$.getJSON(settings.url.replace('{cmd}','getFeed'),{
					maxcount: settings.messageCount,
					offset: offset
				},function(data){
					$('.loader',feed).remove();
					if( data.length == 0 ) {
						if( feed.children().length == 0 ) {
							feed.prepend('<p class="no-comments">'+settings.strings.noComments+'</p>');
						}
						// There's no more messages to read
						$('.readmore',chat).replaceWith(settings.strings.noMoreComments);
					} else {
						$('.no-comments',feed).remove();
					}
					for( var i in data ) {
						chat.tweetChat('insertMessage',data[i]);
					}
					$('abbr.timeago').timeago();
				});
			});
		},
		sendMessage: function(){
			return this.each(function(){
				var chat = $(this);
				var prefix = '';
				if( $('.msg input.is_question',chat).is(':checked') ) {
					prefix = 'q: ';
				}
				var message = prefix + $('.msg input.message',this).val();
				var username = $('.my_username',this).text();
				var parent = $('.msg',this).closest('p.tweet');
				var replyTo = '';
				var replyAccount = '';
				var replyChannel = '';
				if( parent.length > 0 ) {
					replyTo = parent.attr('id').substr(8);
					replyAccount = parent.children('.sender-account').text();
					replyChannel = parent.children('.sender-channel').text();
				}
				if( message == prefix ) {
					return chat; // Do not send empty messages
				}
				if( message.length + username.length > chat.data('tweetChat').maxLength ) {
					return chat; // Do not send too long messages
				}
				$('.msg input',chat).attr('disabled','true');
				// Show loading animation
				$('.msg',this).append('<div class="loader"></div>');
				// Send message
				$.post(settings.url.replace('{cmd}','sendMessage'),{
					message: message,
					reply_to: replyTo,
					reply_account: replyAccount,
					reply_channel: replyChannel,
					username: username,
					time: new Date()
				},function(data,status){
					// Stop loading animation
					$('.msg .loader',chat).remove();
					if( data.error ) {
						var info = chat.find('.msginfo',chat);
						info.text(settings.strings.sendError)
							.stop(true).fadeTo(1,1,function(){info.fadeTo(3000,0.9,function(){info.fadeTo(1500,0);});});
					} else {
						$('.msg input.message',chat).val('');
						$('.msg input.is_question',chat).removeAttr('checked');
						chat.tweetChat('insertMessage',data);
					}
					$('.msg input',chat).removeAttr('disabled').focus();
					$('.tweet a.reply',chat).text(settings.strings.reply).removeClass('cancel-reply');
					$('.msg-placeholder',chat).replaceWith($('.msg',chat));
					
					// Track analytics
					settings.analytics.push(['_trackEvent','TweetChat','MessageSent']);
				});
			});
		},
		changeUsername: function(){
			var chat = this;
			var dialog = $('<div><input type="text" name="username_input" size="15" /></div>');
			
			var closeDialog = function(){
				var username = $.trim($('input',this).val());
				if( username.length == 0 ) {
					// Do not close if the nickname is empty
				} else {
					var oldUsername = $('.my_username',chat).text();
					$('.my_username',chat).text(username);
					chat.tweetChat('registerUser');
					$(this).dialog('close');
				}
			}
			dialog.dialog({
				autoOpen: true,
				modal: true,
				resizable: false,
				title: settings.strings.nicknameInput,
				buttons: {
					'Ok': closeDialog
				},
				open: function(){
					$('input',this).val($('.my_username',chat).text()).live('keypress',function(event){
						if( event.keyCode == '13' ) {
							event.preventDefault();
							closeDialog.apply(dialog);
						}
					});
				}
			});
		},
		insertPoll: function(pollKey,poll){
			var pollElement = $('<div class="ui-widget ui-corner-all poll" id="poll_'+pollKey+'" rel="'+pollKey+'"></div>');
			var head = $('<div class="ui-widget-header ui-corner-top"></div>');
			head.text(poll.title);
			pollElement.append(head);
			var content = $('<div class="ui-widget-content ui-corner-bottom"></div>');
			for(var choiceKey in poll.choices) {
				var choice = $('<div class="choice ui-corner-all" id="poll_'+pollKey+'_choice_'+choiceKey+'" rel="'+choiceKey+'"></div>');
				choice.text(poll.choices[choiceKey]);
				choice.prepend($('<span class="result">'+poll.votes[choiceKey]+'</span> '));
				choice.append($('<div class="resultBar"></div>'));
				content.append(choice);
			}
			pollElement.append(content);
			return pollElement;
		},
		updatePolls: function(){
			$(this).each(function(){
				var chat = $(this);
				var polls = $(settings.pollsSelector,chat);
				$.getJSON(settings.url.replace('{cmd}','getPolls'),{},function(data){
					pollsToRemove = $('.poll',polls);
					for(var pollKey in data.polls) {
						var poll = data.polls[pollKey];
						var pollElement = $('#poll_'+pollKey);
						if( pollElement.length == 0 ) {
							// Create poll element
							pollElement = chat.tweetChat('insertPoll',pollKey,poll);
							polls.append(pollElement);
						} else {
							// Poll exists, do not remove
							pollsToRemove = pollsToRemove.not(pollElement);
						}
						// Update poll results
						chat.tweetChat('updatePoll',pollElement,poll);
					}
					pollsToRemove.remove();
				});
			});
		},
		updatePoll: function(pollElement,poll){
			var pollKey = pollElement.attr('id').substr(5);
			var maxResult = 1;
			for(var choiceKey in poll.choices) {
				var result = $('#poll_'+pollKey+'_choice_'+choiceKey+' .result',pollElement);
				result.text(poll.votes[choiceKey]);
				if( poll.votes[choiceKey] > maxResult ) {
					maxResult = poll.votes[choiceKey];
				}
			}
			$('.choice',pollElement).each(function(){
				var percent = (parseInt($('.result',this).text())*100) / (maxResult);
				$('.resultBar',this).css({
					width: percent+'%'
				});
			});
			// Check the current vote of the user
			$('#poll_'+pollKey+'_choice_'+poll.myvote,pollElement).addClass('selected')
				.siblings('.choice').removeClass('selected');
		},
		registerUser: function(oldUsername){
			var username = $('.my_username',this).text();
			$.post(settings.url.replace('{cmd}','registerUser'),{
				username: username,
				oldusername: oldUsername,
				time: new Date()
			},function(data,status){
				// Only registering user, no need to do anything
				// Track analytics
				settings.analytics.push(['_trackEvent','TweetChat','NicknameChanged']);
			});
		}
	};

	$.fn.tweetChat = function(method){
		if( methods[method] ) {
			return methods[method].apply(this,Array.prototype.slice.call(arguments,1));
		} else if( typeof method === 'object' || !method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error('Method ' +  method + ' does not exist on jQuery.tweetChat');
		}
	};
})(jQuery);

