/*
 * Date:	17.09.2007
 *
 * File:	js/chat.js
 * Project:	AJAX_CHAT
 *
 * Author:	Sebastian Tschan
 * Website:	https://blueimp.net
 *
 * License:	GPL
 * 
 * The SELFHTML documentation has been used throughout this project:
 * http://selfhtml.org
 * 
 * Stylesheet and cookie methods have been inspired by Paul Sowden (A List Apart):
 * http://www.alistapart.com/stories/alternate/
 * 
 */

// Ajax Chat client side logic:
var ajaxChat = {
	
	httpRequest: null,
	url: null,
	documentIDs: null,
	chatList: null,
	onlineList: null,
	inputField: null,
	channelSelection: null,
	styleSelection: null,
	emoticonsContainer: null,
	colorCodesContainer: null,
	sessionName: null,
	chatBotName: null,
	timer: null,
	bbCode: null,
	hyperLinks: null,
	lineBreaks: null,
	emoticons: null,
	emoticonPath: null,
	autoScroll: null,
	timerRate: null,
	maxMessages: null,
	wordWrap: null,
	maxWordLength: null,
	breakString: null,
	dateFormat: null,
	bbCodeTags: null,
	colorCodes: null,
	emoticonCodes: null,
	emoticonNames: null,
	httpRequest: null,
	lastID: null,
	lang: null,
	
	init: function(config, lang, initialize) {
		this.url = config['url'];
		this.documentIDs = new Object();
		this.documentIDs['chatListID']				= config['chatListID'];
		this.documentIDs['onlineListID']			= config['onlineListID'];
		this.documentIDs['inputFieldID']			= config['inputFieldID'];
		this.documentIDs['channelSelectionID']		= config['channelSelectionID'];
		this.documentIDs['styleSelectionID']		= config['styleSelectionID'];
		this.documentIDs['emoticonsContainerID']	= config['emoticonsContainerID'];
		this.documentIDs['colorCodesContainerID']	= config['colorCodesContainerID'];	
		this.sessionName = config['sessionName'];
		this.chatBotName = config['chatBotName'];
		this.bbCode = config['bbCode'];
		this.hyperLinks = config['hyperLinks'];
		this.lineBreaks = config['lineBreaks'];
		this.emoticons = config['emoticons'];
		this.emoticonPath = config['emoticonPath'];
		this.autoScroll = config['autoScroll'];
		this.timerRate = config['timerRate'];
		this.maxMessages = config['maxMessages'];
		this.wordWrap = config['wordWrap'];
		this.maxWordLength = config['maxWordLength'];
		this.breakString = config['breakString'];
		this.dateFormat = config['dateFormat'];
		this.bbCodeTags = config['bbCodeTags'];
		this.colorCodes = config['colorCodes'];
		this.emoticonCodes = config['emoticonCodes'];
		this.emoticonFiles = config['emoticonFiles'];
		this.lastID = 0;
		this.lang = lang;
		
		if(initialize)
			this.initialize();
	},
	
	initialize: function() {
		this.chatList				= document.getElementById(this.documentIDs['chatListID']);
		this.onlineList				= document.getElementById(this.documentIDs['onlineListID']);
		this.inputField				= document.getElementById(this.documentIDs['inputFieldID']);
		this.channelSelection		= document.getElementById(this.documentIDs['channelSelectionID']);
		this.styleSelection			= document.getElementById(this.documentIDs['styleSelectionID']);
		this.emoticonsContainer		= document.getElementById(this.documentIDs['emoticonsContainerID']);
		this.colorCodesContainer	= document.getElementById(this.documentIDs['colorCodesContainerID']);
		
		this.initEmoticons();
		this.initColorCodes();
		
		if(this.styleSelection)
			this.setSelectedStyle();
		
		this.inputField.focus();
		
		this.httpRequest = new Object();		
		this.updateChat();
	},
	
	replaceSpecialChars: function(text) {
		if (!arguments.callee.regExp) {
			arguments.callee.regExp = new RegExp('[&<>\'"]', 'g');
		}
		
		return text.replace(
			arguments.callee.regExp,
			this.replaceSpecialCharsCallback
		);
	},
	
	replaceSpecialCharsCallback: function(str) {
		switch(str) {
			case '&':
				return '&amp;';
			case '<':
				return '&lt;';
			case '>':
				return '&gt;';
			case '\'':
				// As &apos; is not supported by IE, we use &#39; as replacement for ('):
				return '&#39;';
			case '"':
				return '&quot;';
			default:
				return str;
		}
	},
	
	initEmoticons: function() {
		for(var i in this.emoticonCodes) {
			// Replace specials characters in emoticon codes:
			this.emoticonCodes[i] = this.replaceSpecialChars(this.emoticonCodes[i]);
			if(this.emoticonsContainer) {
				this.emoticonsContainer.innerHTML 	+= '<a href="javascript:ajaxChat.insertText(\''
													+ this.emoticonCodes[i]
													+ '\');"><img src="'
													+ this.emoticonPath
													+ this.emoticonFiles[i]
													+ '" alt="'
													+ this.emoticonCodes[i]
													+ '"/></a>';				
			}
		}
	},
	
	initColorCodes: function() {
		if(this.colorCodesContainer) {
			for(var i in this.colorCodes) {
				this.colorCodesContainer.innerHTML 	+= '<a href="javascript:ajaxChat.insert(\'[color='
													+ this.colorCodes[i]
													+ ']\', \'[/color]\');" style="background-color:'
													+ this.colorCodes[i]
													+ ';"></a>'
													+ "\n";
			}			
		}
	},
	
	getHttpRequest: function(identifier) {
		if(!this.httpRequest[identifier]) {
			if (window.XMLHttpRequest) {
				this.httpRequest[identifier] = new XMLHttpRequest();
				if (this.httpRequest[identifier].overrideMimeType) {
					this.httpRequest[identifier].overrideMimeType('text/xml');
				}
			} else if (window.ActiveXObject) {
				try {
					this.httpRequest[identifier] = new ActiveXObject("Msxml2.XMLHTTP");
				} catch (e) {
					try {
						this.httpRequest[identifier] = new ActiveXObject("Microsoft.XMLHTTP");
					} catch (e) {
					}
				}
			}
		}
		return this.httpRequest[identifier];
	},
	
	makeRequest: function(url, method, data) {
		try {
			var identifier = method;
			
			this.getHttpRequest(identifier).open(method, url, true);
	
			this.getHttpRequest(identifier).onreadystatechange = function() {
				try {
					ajaxChat.handleResponse(identifier);
				} catch(e) {
					try {
						clearTimeout(ajaxChat.timer);
					} catch(e) {
						//alert(e);
					}
					
					try {
						if(data) {
							// Add error message to the list:
							ajaxChat.addMessageToChatList(
								new Date(),
								ajaxChat.getRoleClass('4'),
								ajaxChat.chatBotName,
								ajaxChat.replaceCommands('/error ConnectionTimeout')
							);
							ajaxChat.updateChatlistView();
						}
					} catch(e) {
						//alert(e);
					}
					
					// Try to update:
					try {				
						ajaxChat.timer = setTimeout('ajaxChat.updateChat();', ajaxChat.timerRate);
					} catch(e) {
						//alert(e);
					}
				}
			};
		
			if(method == 'POST') {
				this.getHttpRequest(identifier).setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
			}
			
			this.getHttpRequest(identifier).send(data);
		} catch(e) {
			clearTimeout(this.timer);
			
			if(data) {
				// Add error message to the list:
				this.addMessageToChatList(
					new Date(),
					this.getRoleClass('4'),
					this.chatBotName,
					this.replaceCommands('/error ConnectionTimeout')
				);
				this.updateChatlistView();
			}
				
			// Try to update:
			this.timer = setTimeout('ajaxChat.updateChat();', this.timerRate);
		}
	},
		
	handleResponse: function(identifier) {
		if (this.getHttpRequest(identifier).readyState == 4) {
			if (this.getHttpRequest(identifier).status == 200) {
				var xmlDoc = this.getHttpRequest(identifier).responseXML;
			} else {
				// Add error message to the list:
				this.addMessageToChatList(
					new Date(),
					this.getRoleClass('4'),
					this.chatBotName,
					this.replaceCommands('/error ConnectionStatus ' + this.getHttpRequest(identifier).status)
				);
				this.updateChatlistView();				
				return false;
			}
		}
		if(!xmlDoc)
			return false;
		
		this.handleXML(xmlDoc);	
		return true;
	},
	
	handleXML: function(xmlDoc) {
		var infos = xmlDoc.getElementsByTagName('info');
		for (i=0; i<infos.length; i++) {
			var infoType = infos[i].getAttribute('type');
			var infoData = infos[i].firstChild ? infos[i].firstChild.nodeValue : '';
			switch(infoType) {
				case 'logout':
					this.handleLogout(infoData);
					return;
				case 'channelSwitch':
					// Clear the chat messages list:
					while(this.chatList.hasChildNodes()) {
						this.chatList.removeChild(this.chatList.firstChild);
					}
					
					if(this.channelSelection) {
						for(var j = 0; j < this.channelSelection.options.length; j++) {
							if(this.channelSelection.options[j].value == infoData) {
								this.channelSelection.options[j].selected = true;
								break;
							}
						}
					}
					break;
			}
		}
			
		// Clear the online users list:
		if(this.onlineList) {
			while(this.onlineList.hasChildNodes()) {
				this.onlineList.removeChild(this.onlineList.firstChild);
			}
		}

		var userNode,userName,userRoleClass,textNode,messageText,rowClass;
		
		var users = xmlDoc.getElementsByTagName('user');
		for (i=0; i<users.length; i++) {
			userName = users[i].firstChild ? users[i].firstChild.nodeValue : '';
			userRoleClass = this.getRoleClass(users[i].getAttribute('role'));
			this.addUserToOnlineList(userName, userRoleClass);
		}
		
		var messages = xmlDoc.getElementsByTagName('message');
		for (i=0; i<messages.length; i++) {
			userNode = messages[i].getElementsByTagName('username')[0];
			userName = userNode.firstChild ? userNode.firstChild.nodeValue : '';
			userRoleClass = this.getRoleClass(messages[i].getAttribute('userRole'));
			textNode = messages[i].getElementsByTagName('text')[0];
			messageText = textNode.firstChild ? textNode.firstChild.nodeValue : '';
			this.addMessageToChatList(new Date(messages[i].getAttribute('dateTime')), userRoleClass, userName, messageText);
		}
		
		if(messages.length != 0) {
			this.updateChatlistView();
			
			this.lastID = messages[messages.length-1].getAttribute('id');
		}
		
		this.timer = setTimeout('ajaxChat.updateChat();', this.timerRate);
	},
	
	addUserToOnlineList: function(userName, userRoleClass) {
		if(this.onlineList) {
			var rowClass = (this.onlineList.childNodes && (this.onlineList.childNodes.length % 2 != 0)) ? 'rowOdd' : 'rowEven';
			this.onlineList.innerHTML	+= '<div class="'
										+ rowClass
										+ '"><a href="javascript:ajaxChat.privateMessage(\''
										// Escaping apostrophs to ensure a valid JavaScript expression:
										+ userName.replace(/&#39;/g, "\\&#39;")
										+ '\');" title="'
										+ this.lang['sendPrivateMessage'].replace(/%s/, userName)
										+ '" class="'
										+ userRoleClass
										+ '">'
										+ userName
										+ '</a></div>';	
		}
	},
	
	addMessageToChatList: function(dateObject, userRoleClass, userName, messageText) {
		var rowClass = (this.chatList.childNodes && (this.chatList.childNodes.length % 2 != 0)) ? 'rowOdd' : 'rowEven';
		this.chatList.innerHTML += '<div class="'
								+ rowClass
								+ '">'
								+ '<span class="dateTime">('
								+ this.formatDate(this.dateFormat, dateObject)
								+ ')</span> <span class="'
								+ userRoleClass
								+ '">'
								+ userName
								+ ':</span> '
								+ this.replaceText(messageText)
								+ '</div>';
	},
	
	updateChatlistView: function() {
		if(this.chatList.childNodes && this.chatList.childNodes.length > this.maxMessages)
			this.chatList.removeChild(this.chatList.firstChild);
			
		if(this.autoScroll)
			this.chatList.scrollTop = this.chatList.scrollHeight;
	},
	
	encodeText: function(text) {
		return encodeURIComponent(text);
	},
	
	inArray: function(haystack, needle) {
		var i = haystack.length;
		while(i--)
			if(haystack[i] === needle)
				return true;
		return false;
	},

	arraySearch: function(needle, haystack) {
	    for(var i in haystack){
	        if(haystack[i] == needle)
	        	return i;
	    }
	    return false;
	},

	stripTags: function(str) {
		if (!arguments.callee.regExp) {
			arguments.callee.regExp = new RegExp('<\\/?[^>]+?>', 'g');
		}
		
		return str.replace(arguments.callee.regExp, '');
	},

	stripBBCodeTags: function(str) {
		if (!arguments.callee.regExp) {
			arguments.callee.regExp = new RegExp('\\[\\/?[^\\]]+?\\]', 'g');
		}
		
		return str.replace(arguments.callee.regExp, '');
	},	

	escapeRegExp: function(text) {
		if (!arguments.callee.regExp) {
			var specials = new Array(
				'^', '$', '*', '+', '?', '.', '|', '/',
				'(', ')', '[', ']', '{', '}', '\\'
			);
			
			// arguments.callee inside a function always refers to the function itself,
			// so we can store our static regular expression as property of this function:
			arguments.callee.regExp = new RegExp(
				'(\\' + specials.join('|\\') + ')', 'g'
			);
		}
		return text.replace(arguments.callee.regExp, '\\$1');
	},

	formatDate: function(format, date) {
		date = (date == null) ? new date() : date;
		
		return format
		.replace(/%Y/g, date.getFullYear())
		.replace(/%m/g, this.addLeadingZero(date.getMonth()+1))
		.replace(/%d/g, this.addLeadingZero(date.getDate()))
		.replace(/%H/g, this.addLeadingZero(date.getHours()))
		.replace(/%i/g, this.addLeadingZero(date.getMinutes()))
		.replace(/%s/g, this.addLeadingZero(date.getSeconds()))		
		;
	},
	
	addLeadingZero: function(number) {
		number = number.toString();
		if(number.length < 2)
			number = '0'+number;
		return number;
	},
	
	getRoleClass: function(roleID) {
		switch(roleID) {
			case '0':
				return 'guest';
			case '1':
				return 'user';
			case '2':
				return 'moderator';
			case '3':
				return 'admin';
			case '4':
				return 'chatBot';
			default:
				return 'default';
		}
	},
	
	updateChat: function() {
		var requestUrl = this.url
						+ '&lastID='
						+ this.lastID;		
		this.makeRequest(requestUrl,'GET',null);
	},
	
	sendMessage: function() {
		if(this.inputField.value == '')
			return;
		clearTimeout(this.timer);
		var message = 	'lastID='
						+ this.lastID
						+ '&text='
						+ this.encodeText(this.inputField.value);				
		this.makeRequest(this.url,'POST',message);
		this.inputField.value = '';
		this.inputField.focus();
	},
		
	switchChannel: function(channel) {
		clearTimeout(this.timer);	
		this.inputField.focus();
		var message = 	'lastID='
						+ this.lastID
						+ '&text='
						+ this.encodeText('/join ' + channel);		
		this.makeRequest(this.url,'POST',message);
		this.inputField.focus();
	},
	
	logout: function() {
		clearTimeout(this.timer);
		var message = 'text=' + this.encodeText('/quit');		
		this.makeRequest(this.url,'POST',message);
	},
	
	handleLogout: function(url) {
		window.location = url;
	},

	privateMessage: function(userName) {
		this.inputField.value = '/msg ' + userName + ' ';
		this.inputField.focus();
	},

	setAutoScroll: function(bool) {
		this.autoScroll = bool;
	},
	
	showHide: function(id, styleDisplay) {
		if(styleDisplay)
			document.getElementById(id).style.display = styleDisplay;
		else {
			if(document.getElementById(id).style.display == 'none') 
				document.getElementById(id).style.display = 'block'; 
			else
				document.getElementById(id).style.display = 'none';
		}
	},
	
	insertText: function(text) {
		this.insert(text, '');
	},
	
	insertBBCode: function(bbCode) {
		switch(bbCode) {			
			case 'url':
				var url = prompt(this.lang['urlDialog'], 'http://');
				if(url)
					this.insert('[url=' + url + ']', '[/url]');
				else
					this.insert('[url]', '[/url]');
				break;
			default:
				this.insert('[' + bbCode + ']', '[/' + bbCode + ']');		
		}
	},

	insert: function(startTag, endTag) {
		this.inputField.focus();
		// Internet Explorer:
		if(typeof document.selection != 'undefined') {
			// Insert the tags:
			var range = document.selection.createRange();
			var insText = range.text;
			range.text = startTag + insText + endTag;
			// Adjust the cursor position:
			range = document.selection.createRange();
			if (insText.length == 0) {
				range.move('character', -endTag.length);
			} else {
				range.moveStart('character', startTag.length + insText.length + endTag.length);			
			}
			range.select();
		}
		// Firefox, etc. (Gecko based browsers):
		else if(typeof this.inputField.selectionStart != 'undefined') {
			// Insert the tags:
			var start = this.inputField.selectionStart;
			var end = this.inputField.selectionEnd;
			var insText = this.inputField.value.substring(start, end);
			this.inputField.value = this.inputField.value.substr(0, start) + startTag + insText + endTag + this.inputField.value.substr(end);
			// Adjust the cursor position:
			var pos;
			if (insText.length == 0) {
				pos = start + startTag.length;
			} else {
				pos = start + startTag.length + insText.length + endTag.length;
			}
			this.inputField.selectionStart = pos;
			this.inputField.selectionEnd = pos;
		}
		// Other browsers:
		else {
			var pos = this.inputField.value.length;
			this.inputField.value = this.inputField.value.substr(0, pos) + startTag + endTag + this.inputField.value.substr(pos);
		}
	},
	
	replaceText: function(text) {
		try{
			text = this.replaceLineBreaks(text);
			// Replace commands and Chat Bot infos:
			if(text.charAt(0) == '/')
				text = this.replaceCommands(text);
			else {
				text = this.replaceBBCode(text);
				text = this.replaceHyperLinks(text);
				text = this.replaceEmoticons(text);
			}
			text = this.breakLongWords(text);
		} catch(e){
			//alert(e);
		}
		return text;
	},
		
	replaceCommands: function(text) {
		try {
			if(text.charAt(0) != '/')
				return text;
			
			textParts = text.split(' ');
				
			switch(textParts[0]) {
				case '/login':
					return	'<span class="chatBotMessage">'
							+ this.lang['login'].replace(/%s/, textParts[1])
							+ '</span>';
				case '/logout':
					var type = '';
					if(textParts.length == 3)
						type = textParts[2];
					return	'<span class="chatBotMessage">'
							+ this.lang['logout' + type].replace(/%s/, textParts[1])
							+ '</span>';
				case '/channelEnter':
					return	'<span class="chatBotMessage">'
							+ this.lang['channelEnter'].replace(/%s/, textParts[1])
							+ '</span>';
				case '/channelLeave':
					return	'<span class="chatBotMessage">'
							+ this.lang['channelLeave'].replace(/%s/, textParts[1])
							+ '</span>';
				case '/privmsg':
					var privMsgText = textParts.slice(1).join(' ');
					privMsgText = this.replaceBBCode(privMsgText);
					privMsgText = this.replaceHyperLinks(privMsgText);
					privMsgText = this.replaceEmoticons(privMsgText);
					return	'<span class="privmsg">'
							+ this.lang['privmsg'] + ' '
							+ '</span>'
							+ privMsgText;
				case '/privmsgto':
					var privMsgText = textParts.slice(2).join(' ');
					privMsgText = this.replaceBBCode(privMsgText);
					privMsgText = this.replaceHyperLinks(privMsgText);
					privMsgText = this.replaceEmoticons(privMsgText);
					return	'<span class="privmsg">'
							+ this.lang['privmsgto'].replace(/%s/, textParts[1]) + ' '
							+ '</span>'
							+ privMsgText;
				case '/queryOpen':
					return	'<span class="chatBotMessage">'
							+ this.lang['queryOpen'].replace(/%s/, textParts[1])
							+ '</span>';
				case '/queryClose':
					return	'<span class="chatBotMessage">'
							+ this.lang['queryClose'].replace(/%s/, textParts[1])
							+ '</span>';
				case '/ignoreAdded':
					return	'<span class="chatBotMessage">'
							+ this.lang['ignoreAdded'].replace(/%s/, textParts[1])
							+ '</span>';					
				case '/ignoreRemoved':
					return	'<span class="chatBotMessage">'
							+ this.lang['ignoreRemoved'].replace(/%s/, textParts[1])
							+ '</span>';					
				case '/ignoreList':
					return	'<span class="chatBotMessage">'
							+ this.lang['ignoreList'] + ' '
							+ textParts.slice(1).join(', ')
							+ '</span>';					
				case '/ignoreListEmpty':
					return	'<span class="chatBotMessage">'
							+ this.lang['ignoreListEmpty']
							+ '</span>';					
				case '/kick':
					return	'<span class="chatBotMessage">'
							+ this.lang['logoutKicked'].replace(/%s/, textParts[1])
							+ '</span>';
				case '/who':
					return	'<span class="chatBotMessage">'
							+ this.lang['who'] + ' '
							+ textParts.slice(1).join(', ')
							+ '</span>';
				case '/whoEmpty':
					return	'<span class="chatBotMessage">'
							+ this.lang['whoEmpty']
							+ '</span>';
				case '/list':
					return	'<span class="chatBotMessage">'
							+ this.lang['list'] + ' '
							+ textParts.slice(1).join(', ')
							+ '</span>';
				case '/bans':
					return	'<span class="chatBotMessage">'
							+ this.lang['bans'] + ' '
							+ textParts.slice(1).join(', ')
							+ '</span>';
				case '/bansEmpty':
					return	'<span class="chatBotMessage">'
							+ this.lang['bansEmpty']
							+ '</span>';
				case '/unban':
					return	'<span class="chatBotMessage">'
							+ this.lang['unban'].replace(/%s/, textParts[1])
							+ '</span>';
				case '/whois':
					return	'<span class="chatBotMessage">'
							+ this.lang['whois'].replace(/%s/, textParts[1]) + ' '
							+ textParts[2]
							+ '</span>';
				case '/roll':
					var rollText = this.lang['roll'].replace(/%s/, textParts[1]);
					rollText = rollText.replace(/%s/, textParts[2]);
					rollText = rollText.replace(/%s/, textParts[3]);
					return	'<span class="chatBotMessage">'
							+ rollText
							+ '</span>';
				case '/error':
					var errorMessage;
					if(textParts.length > 2)
						errorMessage = this.lang['error' + textParts[1]].replace(/%s/, textParts.slice(2).join(' ')) + ' ';
					else
						errorMessage = this.lang['error' + textParts[1]];
					return	'<span class="chatBotErrorMessage">'
							+ errorMessage
							+ '</span>';
				default:
					return this.replaceCustomCommands(text, textParts);
			}
		} catch(e) {
			//alert(e);
		}
		return text;
	},

	containsUnclosedTags: function(str) {
		if (!arguments.callee.regExpOpenTags || !arguments.callee.regExpCloseTags) {
			arguments.callee.regExpOpenTags		= new RegExp('<[^>\\/]+?>', 'gm');
			arguments.callee.regExpCloseTags	= new RegExp('<\\/[^>]+?>', 'gm');
		}	
		var openTags	= str.match(arguments.callee.regExpOpenTags);
		var closeTags	= str.match(arguments.callee.regExpCloseTags);
		// Return true if the number of tags doesn't match:
		if((!openTags && closeTags) || (openTags && !closeTags) || (openTags && closeTags && (openTags.length != closeTags.length)))
			return true;
		return false;
	},
		
	breakLongWords: function(text) {
		var newText = '';
		var charCounter = 0;
		var currentChar, withinTag, withinEntity;
		
		for(var i=0; i<text.length; i++) {
			currentChar = text.charAt(i);
			
			// Check if we are within a tag or entity:
			if(currentChar == '<') {
				withinTag = true;
				// Reset the charCounter after newline tags (<br/>):
				if(i>5 && text.substr(i-5,4) == '<br/')
					charCounter = 0;				
			} else if(withinTag && i>0 && text.charAt(i-1) == '>') {
				withinTag = false;
				// Reset the charCounter after newline tags (<br/>):
				if(i>4 && text.substr(i-5,4) == '<br/')
					charCounter = 0;
			} else if(currentChar == '&') {
				withinEntity = true;
			} else if(withinEntity && i>0 && text.charAt(i-1) == ';') {
				withinEntity = false;
				// We only increase the charCounter once for the whole entiy:
				charCounter++;
			}
				
			if(!withinTag && !withinEntity) {
				// Reset the charCounter if we encounter a word boundary:
				if(currentChar == ' ' || currentChar == '\n' || currentChar == '\t') {
					charCounter = 0;
				} else {
					// We are not within a tag or entity, increase the charCounter:
					charCounter++;
				}
				if(charCounter > this.maxWordLength) {
					// maxWordLength has been reached, break here and reset the charCounter:
					newText += this.breakString;
					charCounter = 0;
				}
			}		
			// Add the current char to the text:
			newText += currentChar;
		}
		
		return newText;
	},
	
	replaceBBCode: function(text) {
		if(!this.bbCode)
			return text;

		if (!arguments.callee.regExp) {
			arguments.callee.regExp = new RegExp(
				'\\[(\\w+)(?:=([^<>]*?))?\\](.+?)\\[\\/\\1\\]',
				'gm'
			);
		}
			
		return text.replace(
			arguments.callee.regExp,
			this.replaceBBCodeCallback
		);
	},
	
	replaceBBCodeCallback: function(str, p1, p2, p3) {
		
		// Only replace predefined BBCode tags:
		if(!ajaxChat.inArray(ajaxChat.bbCodeTags, p1))
			return str;
		
		// Avoid invalid XHTML (unclosed tags):
		if(ajaxChat.containsUnclosedTags(p3))
			return str;
			
		switch(p1) {			

			case 'url':
				var url;
				if(p2)
					url = p2.replace(/\s/gm, ajaxChat.encodeText(' '));
				else
					url = ajaxChat.stripBBCodeTags(p3.replace(/\s/gm, ajaxChat.encodeText(' ')));
				if (!arguments.callee.regExpUrl) {
					arguments.callee.regExpUrl = new RegExp(
						'^((http)|(https)|(ftp)|(irc)):\\/\\/',
						''
					);
				}
				if(!url || !url.match(arguments.callee.regExpUrl))
					return str;
				return '<a href="' + url + '" onclick="window.open(this.href); return false;">' + ajaxChat.replaceBBCode(p3) + '</a>';

			case 'color':
				if(!p2)
					return str;					
				// Only allow predefined color codes:
				if(!ajaxChat.inArray(ajaxChat.colorCodes, p2))
					return str;				
				return '<span style="color:' + p2 + ';">' +	ajaxChat.replaceBBCode(p3) + '</span>';

			case 'quote':
				if(p2)
					return '<span class="quote"><cite>' + ajaxChat.lang['cite'].replace(/%s/, p2) + '</cite><q>' + ajaxChat.replaceBBCode(p3) + '</q></span>';
				return '<span class="quote"><q>' + ajaxChat.replaceBBCode(p3) + '</q></span>';

			case 'code':
				// Replace vertical tabs and multiple spaces with two non-breaking space characters:
				return '<code>' + ajaxChat.replaceBBCode(p3.replace(/\t|(?:  )/gm, '&#160;&#160;')) + '</code>';		
									
			case 'u':
				return '<span style="text-decoration:underline;">' + ajaxChat.replaceBBCode(p3) + '</span>';

			default:
				return '<' + p1 + '>' + ajaxChat.replaceBBCode(p3) + '</' + p1 + '>';

		}	
	},
	
	replaceHyperLinks: function(text) {
		if(!this.hyperLinks)
			return text;
		
		if (!arguments.callee.regExp) {
			arguments.callee.regExp = new RegExp(
				'(^|\\s|>)(((http)|(https)|(ftp)|(irc)):\\/\\/[^\\s<>]+)(?!<\\/a>)',
				'gm'
			);
		}
			
		return text.replace(
			arguments.callee.regExp,
			// Specifying an anonymous function as second parameter:
			function(str, p1, p2) {
				return p1 + '<a href="' + p2 + '" onclick="window.open(this.href); return false;">' + p2 + '</a>';
			}
		);
	},

	replaceLineBreaks: function(text) {
		if (!arguments.callee.regExp) {
			arguments.callee.regExp = new RegExp('\\n',	'g');
		}
		if(!this.lineBreaks)
			return text.replace(arguments.callee.regExp, ' ');
		else
			return text.replace(arguments.callee.regExp, '<br/>');
	},

	replaceEmoticons: function(text) {
		if(!this.emoticons)
			return text;
		
		if (!arguments.callee.regExp) {
			var regExpStr = '^(.*)(';
			for(var i=0; i<this.emoticonCodes.length; i++) {
				if(i!=0)
					regExpStr += '|';
				regExpStr += '(?:' + this.escapeRegExp(this.emoticonCodes[i]) + ')';
			}
			regExpStr += ')(.*)$';

			// arguments.callee inside a function always refers to the function itself,
			// so we can store our static regular expression as property of this function:
			arguments.callee.regExp = new RegExp(regExpStr, 'gm');
		}
		
		return text.replace(
			arguments.callee.regExp,			
			this.replaceEmoticonsCallback
		);
	},
	
	replaceEmoticonsCallback: function(str, p1, p2, p3) {

		if (!arguments.callee.regExp) {
			arguments.callee.regExp = new RegExp('(="[^"]*$)|(&[^;]*$)', '');
		}
		
		// Avoid replacing emoticons in tag attributes or XHTML entities:
		if(p1.match(arguments.callee.regExp))
			return str;
			
		if(p2) {
			// Get the index of the found emoticon:
			var index = ajaxChat.arraySearch(p2, ajaxChat.emoticonCodes);
							
			return 	ajaxChat.replaceEmoticons(p1)
				+	'<img src="'
				+	ajaxChat.emoticonPath
				+	ajaxChat.emoticonFiles[index]
				+	'" alt="'
				+	p2
				+	'" />'
				+ 	ajaxChat.replaceEmoticons(p3);
		}
		
		// No emoticon found, just return:
		return str;
	},

	getActiveStyle: function() {
		var cookie = this.readCookie(this.sessionName + '_style');
		var style = cookie ? cookie : this.getPreferredStyleSheet();
		return style;		
	},

	initStyle: function() {
		this.setActiveStyleSheet(this.getActiveStyle());
	},
	
	persistStyle: function() {
		this.createCookie(this.sessionName + '_style', this.getActiveStyleSheet(), 365);
	},
	
	setSelectedStyle: function() {
		var style = this.getActiveStyle();
		var styleOptions = this.styleSelection.getElementsByTagName('option');
		for(var i=0; i<styleOptions.length; i++) {
			if(styleOptions[i].value == style) {
				styleOptions[i].selected = true;
				break;
			}
		}				
	},
	
	getSelectedStyle: function() {
		var styleOptions = this.styleSelection.getElementsByTagName('option');
		if(this.styleSelection.selectedIndex == -1)
			return styleOptions[0].value;
		else
			return styleOptions[this.styleSelection.selectedIndex].value;
	},
	
	setActiveStyleSheet: function(title) {
		var i, a, main;
		var titleFound = false;
		for(i=0; (a = document.getElementsByTagName('link')[i]); i++) {
			if(a.getAttribute('rel').indexOf('style') != -1 && a.getAttribute('title')) {
				a.disabled = true;
				if(a.getAttribute('title') == title) {
	                a.disabled = false;
	                titleFound = true;
				}
			}
		}
		if(!titleFound && title != null)
		   this.setActiveStyleSheet(this.getPreferredStyleSheet());
	},
	
	getActiveStyleSheet: function() {
		var i, a;
		for(i=0; (a = document.getElementsByTagName('link')[i]); i++) {
			if(a.getAttribute('rel').indexOf('style') != -1 && a.getAttribute('title') && !a.disabled) return a.getAttribute('title');
		}
		return null;
	},
	
	getPreferredStyleSheet: function() {
		var i, a;
		for(i=0; (a = document.getElementsByTagName('link')[i]); i++) {
			if(a.getAttribute('rel').indexOf('style') != -1
				&& a.getAttribute('rel').indexOf('alt') == -1
				&& a.getAttribute('title')
				) return a.getAttribute('title');
		}
		return null;
	},
	
	createCookie: function(name,value,days) {
		if (days) {
			var date = new Date();
			date.setTime(date.getTime()+(days*24*60*60*1000));
			var expires = '; expires='+date.toGMTString();
		}
		else expires = '';
		document.cookie = name+'='+value+expires+'; path=/';
	},
	
	readCookie: function(name) {
		if(!document.cookie)
		   return null;
		var nameEQ = name + '=';
		var ca = document.cookie.split(';');
		for(var i=0;i < ca.length;i++) {
			var c = ca[i];
			while (c.charAt(0)==' ') c = c.substring(1,c.length);
			if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
		}
		return null;
	},

	finalize: function() {
		this.persistStyle();
	},
	
	// Override to replace custom commands:
	// Return replaced text for custom commands
	// text contains the whole message, textParts the message split up as words array
	replaceCustomCommands: function(text, textParts) {
		return text;
	}

}