/*
 * @package AJAX_Chat
 * @author Sebastian Tschan
 * @copyright (c) Sebastian Tschan
 * @license GNU Affero General Public License
 * @link https://blueimp.net/ajax/
 * 
 * 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 = {

	settingsInitiated: null,
	styleInitiated: null,
	initializeFunction: null,
	finalizeFunction: null,
	loginChannelID: null,
	loginChannelName: null,
	timerRate: null,
	timer: null,
	ajaxURL: null,
	baseURL: null,
	regExpMediaUrl: null,
	dirs: null,
	startChatOnLoad: null,
	chatStarted: null,
	domIDs: null,
	dom: null,
	settings: null,
	nonPersistentSettings: null,
	unusedSettings: null,
	bbCodeTags: null,
	colorCodes: null,
	emoticonCodes: null,
	emoticonFiles: null,
	soundFiles: null,
	sounds: null,
	soundTransform: null,
	sessionName: null,
	cookieExpiration: null,
	cookiePath: null,
	cookieDomain: null,
	cookieSecure: null,
	chatBotName: null,
	chatBotID: null,
	allowUserMessageDelete: null,
	inactiveTimeout: null,
	privateChannelDiff: null,
	privateMessageDiff: null,
	showChannelMessages: null,
	messageTextMaxLength: null,
	socketServerEnabled: null,
	socketServerHost: null,
	socketServerPort: null,
	socketServerChatID: null,
	socket: null,
	socketIsConnected: null,
	socketTimerRate: null,
	socketReconnectTimer: null,
	socketRegistrationID: null,
	userID: null,
	userName: null,
	userRole: null,
	channelID: null,
	channelName: null,
	channelSwitch: null,
	usersList: null,
	userNamesList: null,
	userMenuCounter: null,
	encodedUserName: null,
	userNodeString: null,
	ignoredUserNames: null,
	lastID: null,
	localID: null,
	lang: null,
	langCode: null,
	baseDirection: null,
	originalDocumentTitle: null,
	blinkInterval: null,
	httpRequest: null,
	
	init: function(config, lang, initSettings, initStyle, initialize, initializeFunction, finalizeFunction) {	
		this.httpRequest		= new Object();
		this.usersList			= new Array();
		this.userNamesList		= new Array();
		this.userMenuCounter	= 0;
		this.lastID				= 0;
		this.localID			= 0;
		this.lang				= lang;		
		this.initConfig(config);
		this.initDirectories();		
		if(initSettings) {
			this.initSettings();
		}
		if(initStyle) {
			this.initStyle();
		}
		this.initializeFunction = initializeFunction;
		this.finalizeFunction = finalizeFunction;
		if(initialize) {
			this.setLoadHandler();
		}
	},
	
	initConfig: function(config) {
		this.loginChannelID			= config['loginChannelID'];
		this.loginChannelName		= config['loginChannelName'];
		this.timerRate				= config['timerRate'];
		this.ajaxURL				= config['ajaxURL'];
		this.baseURL				= config['baseURL'];
		this.regExpMediaUrl			= config['regExpMediaUrl'];
		this.startChatOnLoad		= config['startChatOnLoad'];
		this.domIDs					= config['domIDs'];
		this.settings				= config['settings'];
		this.nonPersistentSettings	= config['nonPersistentSettings'];
		this.bbCodeTags				= config['bbCodeTags'];
		this.colorCodes				= config['colorCodes'];
		this.emoticonCodes			= config['emoticonCodes'];
		this.emoticonFiles			= config['emoticonFiles'];
		this.soundFiles				= config['soundFiles'];
		this.sessionName			= config['sessionName'];
		this.cookieExpiration		= config['cookieExpiration'];
		this.cookiePath				= config['cookiePath'];
		this.cookieDomain			= config['cookieDomain'];
		this.cookieSecure			= config['cookieSecure'];
		this.chatBotName			= config['chatBotName'];
		this.chatBotID				= config['chatBotID'];
		this.allowUserMessageDelete	= config['allowUserMessageDelete'];
		this.inactiveTimeout		= config['inactiveTimeout'];
		this.privateChannelDiff		= config['privateChannelDiff'];
		this.privateMessageDiff		= config['privateMessageDiff'];
		this.showChannelMessages	= config['showChannelMessages'];
		this.messageTextMaxLength	= config['messageTextMaxLength'];
		this.socketServerEnabled	= config['socketServerEnabled'];
		this.socketServerHost		= config['socketServerHost'];
		this.socketServerPort		= config['socketServerPort'];
		this.socketServerChatID		= config['socketServerChatID'];
	},

	initDirectories: function() {
		this.dirs = new Object();
		this.dirs['emoticons'] 	= this.baseURL+'img/emoticons/';
		this.dirs['sounds']		= this.baseURL+'sounds/';
		this.dirs['flash']		= this.baseURL+'flash/';
	},
	
	initSettings: function() {
		this.settingsInitiated = true;
		this.unusedSettings = new Object();
		var cookie = this.readCookie(this.sessionName + '_settings');
		if(cookie) {
			var settingsArray = cookie.split('&');
			var setting,key,value,number;
			for(var i=0; i<settingsArray.length; i++) {
				setting = settingsArray[i].split('=');
				if(setting.length == 2) {
					key = setting[0];
					value = this.decodeText(setting[1]);
					switch(value) {
						case 'true':
							value = true;
							break;
						case 'false':
							value = false;
							break;
						case 'null':
							value = null;
							break;
						default:
							number = parseFloat(value);
							if(!isNaN(number)) {
								if(parseInt(number) == number) {
									value = parseInt(number);
								} else {
									value = number;
								}
							}
					}
					if(this.inArray(this.nonPersistentSettings, key)) {
						// The setting is not used, store it for the persistSettings method:
						this.unusedSettings[key] = value;
					} else {
						this.settings[key] = value;
					}
				}
			}
		}
	},

	persistSettings: function() {
		if(this.settingsInitiated) {
			var settingsArray = new Array();
			for(var property in this.settings) {
				if(this.inArray(this.nonPersistentSettings, property)) {
					if(this.unusedSettings && this.unusedSettings[property]) {
						// Store the unusedSetting previously stored:
						this.settings[property] = this.unusedSettings[property];	
					} else {
						continue;
					}
				}
				settingsArray.push(property + '=' + this.encodeText(this.settings[property]));
			}
			this.createCookie(this.sessionName + '_settings', settingsArray.join('&'), this.cookieExpiration);	
		}
	},
	
	getSettings: function() {
		return this.settings;
	},
	
	getSetting: function(key) {
		// Only return null if setting is null or undefined, not if it is false:
		for(var property in this.settings) {
			if(property == key) {
				return this.settings[key];
			}
		}
		return null;
	},
	
	setSetting: function(key, value) {
		this.settings[key] = value;
	},
	
	initializeSettings: function() {
		if(this.settings['persistFontColor'] && this.settings['fontColor']) {
			// Set the inputField font color to the font color:
			if(this.dom['inputField']) {
				this.dom['inputField'].style.color = this.settings['fontColor'];
			}
		}
	},
	
	initialize: function() {	
		this.setUnloadHandler();
		this.initializeDocumentNodes();
		this.loadPageAttributes();
		this.initEmoticons();
		this.initColorCodes();
		this.initializeSettings();		
		this.setSelectedStyle();
		this.customInitialize();
		if(typeof this.initializeFunction == 'function') {
			this.initializeFunction();
		}
		if(!this.isCookieEnabled()) {
			this.addChatBotMessageToChatList('/error CookiesRequired');
		} else {
			if(this.startChatOnLoad) {
				this.startChat();
			} else {
				this.setStartChatHandler();
				this.requestTeaserContent();
			}
		}
	},

	requestTeaserContent: function() {
		var params = '&view=teaser';
		params += '&getInfos=' + this.encodeText('userID,userName,userRole');
		if(!isNaN(parseInt(this.loginChannelID))) {
			params += '&channelID='+this.loginChannelID;
		} else if(this.loginChannelName !== null) {
			params += '&channelName='+this.encodeText(this.loginChannelName);
		}
		this.updateChat(params);
	},
	
	setStartChatHandler: function() {
		if(this.dom['inputField']) {
			this.dom['inputField'].onfocus = function() {
				ajaxChat.startChat();
				// Reset the onfocus event on first call:
				ajaxChat.dom['inputField'].onfocus = '';
			}			
		}
	},
	
	startChat: function() {
		this.chatStarted = true;
		if(this.dom['inputField'] && this.settings['autoFocus']) {
			this.dom['inputField'].focus();
		}
		this.loadFlashInterface();
		this.startChatUpdate();
	},

	loadPageAttributes: function() {
		var htmlTag			= document.getElementsByTagName('html')[0];
		this.langCode		= htmlTag.getAttribute('lang')	? htmlTag.getAttribute('lang')	: 'en';
		this.baseDirection	= htmlTag.getAttribute('dir')	? htmlTag.getAttribute('dir')	: 'ltr';		
	},

	setLoadHandler: function() {
		// Make sure initialize() is called on page load:
  		var onload = window.onload;
		if(typeof onload != 'function') {
			window.onload = function() {
				ajaxChat.initialize();
			}
		} else {
			window.onload = function() {
				onload();
				ajaxChat.initialize();
			}
		}		
	},
	
	setUnloadHandler: function() {
		// Make sure finalize() is called on page unload:
  		var onunload = window.onunload;
		if(typeof onunload != 'function') {
			window.onunload = function() {
				ajaxChat.finalize();
			}
		} else {
			window.onunload = function() {
				ajaxChat.finalize();
				onunload();
			}
		}
	},

	updateDOM: function(id, str, prepend, overwrite) {
		var domNode = this.dom[id] ? this.dom[id] : document.getElementById(id);
		if(!domNode) {
			return;
		}
		try {
			// Test for validity before adding the string to the DOM:
			domNode.cloneNode(false).innerHTML = str;
			if(overwrite) {
				domNode.innerHTML = str;
			} else if(prepend) {
				domNode.innerHTML = str + domNode.innerHTML;
			} else {
				domNode.innerHTML += str;
			}
		} catch(e) {
			this.addChatBotMessageToChatList('/error DOMSyntax '+id);
			this.updateChatlistView();
		}
	},
	
	initializeDocumentNodes: function() {
		this.dom = new Object();
		for(var key in this.domIDs) {
			this.dom[key] = document.getElementById(this.domIDs[key]);
		}
	},

	initEmoticons: function() {
		for(var i=0; i<this.emoticonCodes.length; i++) {
			// Replace specials characters in emoticon codes:
			this.emoticonCodes[i] = this.encodeSpecialChars(this.emoticonCodes[i]);
			if(this.dom['emoticonsContainer']) {
					this.updateDOM(
						'emoticonsContainer',
						'<a href="javascript:ajaxChat.insertText(\''
						+ this.scriptLinkEncode(this.emoticonCodes[i])
						+ '\');"><img src="'
						+ this.dirs['emoticons']
						+ this.emoticonFiles[i]
						+ '" alt="'
						+ this.emoticonCodes[i]
						+ '" title="'
						+ this.emoticonCodes[i]
						+ '"/></a>'
					);
			}
		}
	},
	
	initColorCodes: function() {
		if(this.dom['colorCodesContainer']) {
			for(var i=0; i<this.colorCodes.length; i++) {
				this.updateDOM(
					'colorCodesContainer',
					'<a href="javascript:ajaxChat.setFontColor(\''
					+ this.colorCodes[i]
					+ '\');" style="background-color:'
					+ this.colorCodes[i]
					+ ';" title="'
					+ this.colorCodes[i]
					+ '"></a>'
					+ "\n"
				);
			}			
		}
	},

	startChatUpdate: function() {
		// Start the chat update and retrieve current user and channel info and set the login channel:
		var infos = 'userID,userName,userRole,channelID,channelName';
		if(this.socketServerEnabled) {
			infos += ',socketRegistrationID';
		}
		var params = '&getInfos=' + this.encodeText(infos);
		if(!isNaN(parseInt(this.loginChannelID))) {
			params += '&channelID='+this.loginChannelID;
		} else if(this.loginChannelName !== null) {
			params += '&channelName='+this.encodeText(this.loginChannelName);
		}
		this.updateChat(params);
	},
	
	updateChat: function(paramString) {
		var requestUrl = this.ajaxURL
						+ '&lastID='
						+ this.lastID;
		if(paramString) {
			requestUrl += paramString;
		}
		this.makeRequest(requestUrl,'GET',null);
	},
	
	loadFlashInterface: function() {
		if(this.dom['flashInterfaceContainer']) {
			this.updateDOM(
				'flashInterfaceContainer',
				'<object id="ajaxChatFlashInterface" style="position:absolute; left:-100px;" '
				+'classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" '
				+'codebase="'
				+ window.location.protocol
				+'//download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,0,0" '
				+'height="1" width="1">'
				+'<param name="flashvars" value="bridgeName=ajaxChat"/>'
				+'<param name="src" value="'+this.dirs['flash']+'FABridge.swf"/>'
				+'<embed name="ajaxChatFlashInterface" pluginspage="'
				+ window.location.protocol
				+'//www.macromedia.com/go/getflashplayer" '
				+'src="'+this.dirs['flash']+'FABridge.swf" height="1" width="1" flashvars="bridgeName=ajaxChat"/>'
				+'</object>'
			);
			FABridge.addInitializationCallback('ajaxChat', this.flashInterfaceLoadCompleteHandler);
		}
	},
	
	flashInterfaceLoadCompleteHandler: function() {
		ajaxChat.initializeFlashInterface();
	},

	initializeFlashInterface: function() {
		if(this.socketServerEnabled) {
			this.socketTimerRate = (this.inactiveTimeout-1)*60*1000;
			this.socketConnect();
		}
		this.loadSounds();
		this.initializeCustomFlashInterface();
	},

	socketConnect: function() {
		if(!this.socketIsConnected) {
			try {
				if(!this.socket && FABridge.ajaxChat) {
					this.socket = FABridge.ajaxChat.create('flash.net.XMLSocket');
					this.socket.addEventListener('connect', this.socketConnectHandler);
					this.socket.addEventListener('close', this.socketCloseHandler);
					this.socket.addEventListener('data', this.socketDataHandler);
					this.socket.addEventListener('ioError', this.socketIOErrorHandler);
					this.socket.addEventListener('securityError', this.socketSecurityErrorHandler);
				}
				this.socket.connect(this.socketServerHost, this.socketServerPort);
			} catch(e) {
				//alert(e);
			}
		}
		clearTimeout(this.socketReconnectTimer);
		this.socketReconnectTimer = null;
	},
	
	socketConnectHandler: function(event) {
		ajaxChat.socketIsConnected = true;
		// setTimeout is needed to avoid calling the flash interface recursively:
		setTimeout('ajaxChat.socketRegister()', 0);
	},

	socketCloseHandler: function(event) {
		ajaxChat.socketIsConnected = false;
		if(ajaxChat.socket) {
			clearTimeout(ajaxChat.timer);
			ajaxChat.updateChat(null);
		}
	},
	
	socketDataHandler: function(event) {
		ajaxChat.socketUpdate(event.getData());
	},

	socketIOErrorHandler: function(event) {
		// setTimeout is needed to avoid calling the flash interface recursively (e.g. sound on new messages):
		setTimeout('ajaxChat.addChatBotMessageToChatList(\'/error SocketIO\')', 0);
		setTimeout('ajaxChat.updateChatlistView()', 1);
	},

	socketSecurityErrorHandler: function(event) {
		// setTimeout is needed to avoid calling the flash interface recursively (e.g. sound on new messages):
		setTimeout('ajaxChat.addChatBotMessageToChatList(\'/error SocketSecurity\')', 0);
		setTimeout('ajaxChat.updateChatlistView()', 1);
	},

	socketRegister: function() {
		if(this.socket && this.socketIsConnected) {
			try {
				this.socket.send(
					'<register chatID="'
					+this.socketServerChatID
					+'" userID="'
					+this.userID
					+'" regID="'
					+this.socketRegistrationID
					+'"/>'
				);
			} catch(e) {
				//alert(e);
			}
		}
	},
	
	loadXML: function(str) {
		if(!arguments.callee.parser) {
			try {
				// DOMParser native implementation (Mozilla, Opera):
				arguments.callee.parser = new DOMParser();
			} catch(e) {
				var customDOMParser = function() {}
				if(navigator.appName == 'Microsoft Internet Explorer') {
					// IE implementation:
					customDOMParser.prototype.parseFromString = function(str, contentType) {
						if(!arguments.callee.XMLDOM) {
							arguments.callee.XMLDOM = new ActiveXObject('Microsoft.XMLDOM');
						}
						arguments.callee.XMLDOM.loadXML(str);
						return arguments.callee.XMLDOM;	
					}
				} else {
					// Safari, Konqueror:
					customDOMParser.prototype.parseFromString = function(str, contentType) {
						if(!arguments.callee.httpRequest) {
							arguments.callee.httpRequest = new XMLHttpRequest();
						}
						arguments.callee.httpRequest.open(
							'GET',
							'data:text/xml;charset=utf-8,'+encodeURIComponent(str),
							false
						);
						arguments.callee.httpRequest.send(null);
						return arguments.callee.httpRequest.responseXML;
					}
				}
				arguments.callee.parser = new customDOMParser();
			}
		}
		return arguments.callee.parser.parseFromString(str, 'text/xml');
	},
	
	socketUpdate: function(data) {
		var xmlDoc = this.loadXML(data);
		if(xmlDoc) {
			this.handleOnlineUsers(xmlDoc.getElementsByTagName('user'));
			// If the root node has the attribute "mode" set to "1" it is a channel message:
			if((this.showChannelMessages || xmlDoc.firstChild.getAttribute('mode') != '1') && !this.channelSwitch) {
				var channelID = xmlDoc.firstChild.getAttribute('channelID');
				if(channelID == this.channelID ||
					parseInt(channelID) == parseInt(this.userID)+this.privateMessageDiff
					) {
					this.handleChatMessages(xmlDoc.getElementsByTagName('message'));
				}
			}
		}
	},

	setAudioVolume: function(volume) {
		volume = parseFloat(volume);
		if(!isNaN(volume)) {
			if(volume < 0) {
				volume = 0.0;
			} else if(volume > 1) {
				volume = 1.0;
			}
			this.settings['audioVolume'] = volume;
			try {
				if(!this.soundTransform) {
					this.soundTransform = FABridge.ajaxChat.create('flash.media.SoundTransform');					
				}
				this.soundTransform.setVolume(volume);
			} catch(e) {
				//alert(e);
			}
		}
	},
	
	loadSounds: function() {
		try {
			this.setAudioVolume(this.settings['audioVolume']);
			this.sounds = new Object();
			var sound,urlRequest;
			for(var key in this.soundFiles) {
				sound = FABridge.ajaxChat.create('flash.media.Sound');
				sound.addEventListener('complete', this.soundLoadCompleteHandler);
				sound.addEventListener('ioError', this.soundIOErrorHandler);
				urlRequest = FABridge.ajaxChat.create('flash.net.URLRequest');
				urlRequest.setUrl(this.dirs['sounds']+this.soundFiles[key]);
				sound.load(urlRequest);
			}
		} catch(e) {
			//alert(e);
		}
	},
	
	soundLoadCompleteHandler: function(event) {
		var sound = event.getTarget();
		for(var key in ajaxChat.soundFiles) {
			// Get the sound key by matching the sound URL with the sound filename:
			if((new RegExp(ajaxChat.soundFiles[key])).test(sound.getUrl())) {
				// Add the loaded sound to the sounds list:
				ajaxChat.sounds[key] = sound;
			}
		}
	},

	soundIOErrorHandler: function(event) {
		// setTimeout is needed to avoid calling the flash interface recursively (e.g. sound on new messages):
		setTimeout('ajaxChat.addChatBotMessageToChatList(\'/error SoundIO\')', 0);
		setTimeout('ajaxChat.updateChatlistView()', 1);
	},
	
	soundPlayCompleteHandler: function(event) {
		// soundChannel event 'soundComplete'
	},

	playSound: function(soundID) {
		if(this.sounds && this.sounds[soundID]) {
			try {
				// play() parameters are
				// startTime:Number (default = 0),
				// loops:int (default = 0) and
				// sndTransform:SoundTransform  (default = null)
				return this.sounds[soundID].play(0, 0, this.soundTransform);
			} catch(e) {
				//alert(e);
			}
		}
		return null;
	},
	
	playSoundOnNewMessage: function(dateObject, userID, userName, userRole, messageID, messageText, channelID, ip) {
		if(this.settings['audio'] && this.sounds && this.lastID && !this.channelSwitch) {
			switch(userID) {
				case this.chatBotID:
					var messageParts = messageText.split(' ', 1);
					switch(messageParts[0]) {
						case '/login':
						case '/channelEnter':
							this.playSound(this.settings['soundEnter']);
							break;
						case '/logout':
						case '/channelLeave':
						case '/kick':
							this.playSound(this.settings['soundLeave']);
							break;
						case '/error':
							this.playSound(this.settings['soundError']);
							break;
						default:
							this.playSound(this.settings['soundChatBot']);
					}
					break;
				case this.userID:
					this.playSound(this.settings['soundSend']);
					break;
				default:
					this.playSound(this.settings['soundReceive']);
					break;
			}
		}
	},

	fillSoundSelection: function(selectionID, selectedSound) {
		var selection = document.getElementById(selectionID);
		// Skip the first, empty selection:
		var i = 1;
		for(var key in this.soundFiles) {
			selection.options[i] = new Option(key, key);
			if(key == selectedSound){
				selection.options[i].selected = true;
			}
			i++;
		}
	},
	
	getHttpRequest: function(identifier) {
		if(!this.httpRequest[identifier]) {
			if (window.XMLHttpRequest) {
				this.httpRequest[identifier] = new XMLHttpRequest();
	
