/************************************************/
/* IPB3 Javascript								*/
/* -------------------------------------------- */
/* ips.ipb.chat.js - Chat javascript				*/
/* (c) IPS, Inc 2008							*/
/* -------------------------------------------- */
/* Author: Brandon Farber						*/
/************************************************/

/*
 * We have to override this code from ipb.js because we need 'g' flag
 */
Object.extend( RegExp, { 
	escape: function(text)
	{
		if (!arguments.callee.sRE)
		{
		   	var specials = [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^' ];
		   	arguments.callee.sRE = new RegExp( '(\\' + specials.join('|\\') + ')', 'g' );
		}
		return text.replace(arguments.callee.sRE, '\\$1');
	}
});

Object.extend( String, { 
	reverse: function(text)
	{
		return text.split('').reverse().join('');
	}
});

var _chat = window.IPBoard;

_chat.prototype.chat = {
	/*
	 * Number of seconds between requests
	 */
	polling:		3,
	
	/*
	 * Timestamp we last polled server
	 */
	lastpolled:		0,
	
	/*
	 * The last message ID received
	 */
	lastMsgId:		0,
	
	/*
	 * The last time we saw
	 */
	lastTime:		'',

	/*
	 * Only show times at 5 minute intervals instead of 1 minute
	 */
	condenseTime:	0,
	
	/*
	 * Maximum number of li items within the chat window
	 */
	maxMessages:	0,
	
	/*
	 * Flag if we are moderator
	 */
	moderator:		0,

	/*
	 * Flag if we are allowed to do private chats
	 */
	private:		0,
	
	/*
	 * Kicked flag (stop calling to server)
	 */
	kicked:			false,

	/*
	 * Number of seconds between AJAX requests to update "who's chatting"
	 */
	chattingUpdate:	60,
	
	/*
	 * Bypass userid check internally for adding messages
	 */
	bypassFlag:		false,

	/*
	 * Templates for the messages, setup in the skin
	 */
	templates:		[],
	
	/*
	 * Emoticons hash
	 */
	emoticons:		{},
	
	/*
	 * Sound on or off
	 */
	soundEnabled:	0,
	
	/*
	 * Flag to indicate if sound object is ready
	 */
	soundReady:		false,
	
	/*
	 * Sound enabled image
	 */
	soundOnImg:		'',
	
	/*
	 * Sound disabled image
	 */
	soundOffImg:	'',
	
	/*
	 * Name formatting hash
	 */
	nameFormatting:	$H(),
	
	/*
	 * Group formatting info
	 */
	groups:			$H(),
	
	/*
	 * Does window have focus?
	 */
	windowHasFocus:	true,

	/*
	 * Active chat tab
	 */
	activeTab:		'chatroom',

	/*
	 * Map forum id to chat user id
	 */
	forumIdMap:		$H(),
	
	/*
	 * Forum user ids to ignore private chats from
	 */
	ignoreChats:	$H(),

	/*
	 * References for "send private chat" popup
	 */
	privateChats:	$H(),

	/*
	 * Stored typed private chat text for when tabs are switched
	 */
	typedChats:		$H(),

	/*
	 * Unread counts for tabs
	 */
	unreadTabs:		$H(),

	/*
	 * Number of unread items since last lost focus
	 */
	unreadCount:	0,
	
	/*
	 * Bad words hash
	 */
	badwords:		$H(),
	
	/*
	 * Don't show buffer upon entering room
	 */
	noBuffer:		0,
	
	/*
	 * Hide the user has entered/left the room
	 */
	hideEnterExit:	0,

	/*
	 * Minutes of inactivity to kick user after
	 */
	inactiveKick:	0,
	
	/*
	 * Last action performed (timestamp)
	 */
	lastAction:		0,
	
	/*
	 * Initialization function
	 */
	init: function()
	{
		Debug.write("Initializing ips.chat.js");
		
		/*
		 * Wait for the DOM to finish loading...
		 */
		document.observe("dom:loaded", function()
		{
			/*
			 * Set board_url if not set (i.e. popup wrapper)
			 */
			if( !ipb.vars['board_url'] && ipb.vars['base_url'] )
			{
				ipb.vars['board_url']	= ipb.vars['base_url'].replace( /index\.php\?.*?$/, "" );
				
				Debug.write( "Set board_url to " + ipb.vars['board_url'] );
			}

			/*
			 * Update last activity timestamp
			 */
			ipb.chat.lastAction	= parseInt( new Date().getTime().toString().substring( 0, 10 ) );
		
			/*
			 * Create the appropriate iframes
			 */
			ipb.chat.drawIframes();
			
			/*
			 * Set observer for onMessage event
			 */
			Event.observe( window, 'message', function(e)
			{
				ipb.chat.handleMessageFromChild( e.data );
			});

			/*
			 * Set an interval to poll ipb.chat.getMessages(), which initiates the AJAX request.
			 */
			new PeriodicalExecuter( ipb.chat.getMessages, ipb.chat.polling );

			/*
			 * When we submit the chat text, initiate the AJAX request to post it to the server
			 */
			$('chat-submit').observe( 'click', ipb.chat.sendChat );
			
			/*
			 * Watch for "enter" key submissions
			 */
			Event.observe( 'message_textarea', 'keypress', ipb.chat.checkForSendChat );
			
			/*
			 * Make sure we stay "online"
			 */
			ipb.chat.ping();
			new PeriodicalExecuter( ipb.chat.ping, ipb.chat.chattingUpdate );
			
			/*
			 * Sound notifications toggler
			 */
			$('sound_toggle').observe( 'click', function(e) {
				if( ipb.chat.soundEnabled )
				{
					ipb.chat.soundEnabled	= 0;
					ipb.Cookie.set( 'chat_sounds', 'off' );
					Debug.write( "Chat sounds disabled" );
					
					$('sound_toggle_img').writeAttribute( 'src', ipb.chat.soundOffImg );
				}
				else
				{
					ipb.chat.soundEnabled	= 1;
					ipb.Cookie.set( 'chat_sounds', 'on' );
					Debug.write( "Chat sounds enabled" );
					
					$('sound_toggle_img').writeAttribute( 'src', ipb.chat.soundOnImg );
				}
				
				Event.stop(e);
				return false;
			});
			
			/*
			 * Kick user links
			 */
			ipb.chat.initKickLinks();
			
			/*
			 * Enable sounds when sound object is ready
			 */
			soundManager.onready( function(oStatus) {
				if( oStatus.success )
				{
					ipb.chat.soundReady	= true;
				}
			});
			
			/*
			 * Monitor window focus...
			 */
			if( Prototype.Browser.IE )
			{
				document.onfocusin = function() { ipb.chat.cleanTitle(); ipb.chat.windowHasFocus = true; };
				document.onfocusout = function() { ipb.chat.unreadCount = 0; ipb.chat.windowHasFocus = false; };
			}
			else
			{
				Event.observe( window, 'focus', function() { ipb.chat.cleanTitle(); ipb.chat.windowHasFocus = true; } );
				Event.observe( window, 'blur', function() { ipb.chat.unreadCount = 0; ipb.chat.windowHasFocus = false; } );
			}
			
			/*
			 * Handle tabs
			 */
			$$('#chat-tab-bar a').each( function(elem) {
				id = elem.identify();

				if( $( id ) )
				{
					$( id ).observe( "click", ipb.chat.switchTab );
				}
			});
			
			/*
			 * Manually setup menus so we can add our callback
			 */
			$$('.chatmodmenu').each( function(menu){
				id = menu.identify();
				if( $( id + "_menucontent" ) )
				{
					new ipb.Menu( menu, $( id + "_menucontent" ), {}, { afterOpen: ipb.chat.repositionModMenu } );
				}
			});
			
			/*
			 * Open in new window link
			 */
			if( $('chat_new_window') )
			{
				$('chat_new_window').observe( 'click', ipb.chat.openInNewWindow );
			}
		});
	},
	
	/*
	 * Open chat in a new minimalized window
	 */
	openInNewWindow: function(e)
	{
		var _url	= document.location.toString();
		
		if( _url.charAt( _url.length - 1 ) == '#' )
		{
			_url	= _url.substr( 0, _url.length - 1 );
		}

		if( _url.indexOf('?') != '-1' )
		{
			_url	+= '&_popup=1';
		}
		else
		{
			_url	+= '?_popup=1';
		}
		
		window.open( _url, "chatpopout", "status=0,toolbar=0,location=1,menubar=0,directories=0,resizable=1,scrollbars=1,height=550,width=750" );
		
		Event.stop(e);
		return false;
	},
	
	/*
	 * Swap out the tab content
	 */
	switchTab: function( e )
	{
		Event.stop(e);
		
		elem	= Event.findElement(e);
		id		= elem.identify();

		if( id.match( /close\-chat\-tab\-(.+?)/g ) )
		{
			ipb.chat.closePrivateTab( id );
			return false;
		}

		rel		= $( id ).rel;
		
		/*
		 * Already on the clicked tab
		 */
		if( rel == ipb.chat.activeTab )
		{
			Debug.write( "Clicked on active tab" );
			return false;
		}

		/*
		 * Swap out the active window with the storage window we need
		 */
		ipb.chat.switchToTab( rel );
		
		return false;
	},
	
	/*
	 * Actually switch to another tab
	 */
	switchToTab: function( rel )
	{
		if( $( 'storage_' + rel ) )
		{
			/*
			 * Store current message we were typing
			 */
			if( $F('message_textarea').strip() )
			{
				ipb.chat.typedChats.set( ipb.chat.activeTab, $F('message_textarea') );

				$('message_textarea').clear();
			}

			$( 'storage_container_' + ipb.chat.activeTab ).update( $('messages-display').innerHTML );
			$( 'messages-display' ).update( $( 'storage_container_' + rel ).innerHTML ).addClassName('messages-list');
			$( 'storage_container_' + rel ).update();
			
			$( 'tab-' + ipb.chat.activeTab ).removeClassName('active');
			$( 'tab-' + rel ).addClassName('active');

			ipb.chat.activeTab	= rel;

			ipb.chat.updateTabUnreadCount();
			
			/*
			 * Scroll to the bottom
			 */
			$('messages-display').scrollTop	= $('messages-display').scrollHeight + 500;

			/*
			 * Restore textarea if we have stored text for this tab
			 */
			if( ipb.chat.typedChats.get( ipb.chat.activeTab ) )
			{
				$('message_textarea').value	= ipb.chat.typedChats.get( ipb.chat.activeTab );
				
				ipb.chat.typedChats.unset( ipb.chat.activeTab );
			}

			/*
			 * Focus textarea
			 */			
			$('message_textarea').focus();
			
			/*
			 * Update last activity timestamp
			 */
			ipb.chat.lastAction	= parseInt( new Date().getTime().toString().substring( 0, 10 ) );
		}
	},
	
	/*
	 * Build a new tab
	 */
	buildTab: function( id )
	{
		/*
		 * Has this user left?
		 */
		if( !ipb.chat.nameFormatting.get( id ) )
		{
			return false;
		}
		
		/*
		 * Tab exists?
		 */
		if( $("tab-" + id ) )
		{
			if( !$("storage_" + id ) )
			{
				var newOuter	= new Element( 'div', { id: "storage_container_" + id } ).addClassName('storage-container');
				var newStorage	= new Element( 'ul', { id: "storage_" + id } );
				$('iframeContainer').insert( newOuter.insert( newStorage ) );
				
				ipb.chat.typedChats.unset( id );
			}
			
			return true;
		}

		/*
		 * Get name
		 */
		var name	= ipb.chat.nameFormatting.get( id )[2].stripTags();

		/*
		 * First - let's see if we have another tab with this name.  Because if so, that means user timed out/left, and then returned,
		 * so we don't want two tabs from the same user.  We can just update the tab id in that case.
		 */
		var _existed	= false;
		
		$('chat-tab-holder').childElements().each( function(elem){
			/*
			 * This should be an <li>
			 */
			var _thisId	= $(elem).readAttribute('id').replace( /tab-/, '' );
			var _name	= $(elem).innerHTML.stripTags().strip().replace( /&nbsp;/g, '' );
			
			/*
			 * This is the same name as the new tab - just update existing tab
			 */
			if( name == _name )
			{
				$(elem).writeAttribute( 'id', 'tab-' + id );
				$( "storage_container_" + _thisId ).writeAttribute( 'id', "storage_container_" + id );
				$( "storage_" + _thisId ).writeAttribute( 'id', "storage_" + id );
				
				if( ipb.chat.activeTab == _thisId )
				{
					ipb.chat.activeTab	= id;
				}
				
				_existed	= true;
			}
		});

		/*
		 * We did not, so build new tab
		 */
		if( !_existed )
		{
			var newTab	= new Element( 'li', { id: "tab-" + id } );
			newTab.update( ipb.chat.templates['new-tab'].evaluate( { id: id, name: name } ) ).addClassName('left');//.addClassName('active');
			$('chat-tab-holder').insert( { bottom: newTab } );
		}

		/*
		 * Reset "active" tab
		 * @see 	http://community.invisionpower.com/tracker/issue-21639-buffer-clearing/
		 * We build tab and then switchToTab().  If we change active status of tab now, when we
		 * do the switch it gets confused because it thinks the new tab is already active and clears old tab buffer
		 */
		//$( 'tab-' + ipb.chat.activeTab ).removeClassName('active');
		//ipb.chat.activeTab	= id;
		
		/*
		 * Handle click handlers
		 */
		$$('#chat-tab-bar a').each( function(elem) {

			elem.stopObserving();
			
			_id = elem.identify();

			if( $( _id ) )
			{
				$( _id ).observe( "click", ipb.chat.switchTab );
			}
		});

		/*
		 * Create storage container
		 */
		if( !$( "storage_" + id ) )
		{
			var newOuter	= new Element( 'div', { id: "storage_container_" + id } ).addClassName('storage-container');
			var newStorage	= new Element( 'ul', { id: "storage_" + id } );
			$('iframeContainer').insert( newOuter.insert( newStorage ) );
		}
		
		return true;
	},
	
	/*
	 * Close a chat tab
	 */
	closePrivateTab: function( id )
	{
		_rel	= id.replace( /close-chat-tab-/, '' );

		if( !_rel )
		{
			return false;
		}

		/*
		 * If we are on this tab, switch to chatroom
		 */
		if( _rel == ipb.chat.activeTab )
		{
			ipb.chat.switchToTab( 'chatroom' );
		}
		
		/*
		 * Remove storage
		 */
		$( "storage_container_" + _rel ).remove();
		$( "tab-" + _rel ).remove();
		ipb.chat.typedChats.unset( _rel );
		
		/*
		 * Update last activity timestamp
		 */
		ipb.chat.lastAction	= parseInt( new Date().getTime().toString().substring( 0, 10 ) );
		
		return false;
	},
	
	/*
	 * Click handler for sending a new private chat
	 */
	sendPrivateChat: function( id )
	{
		/*
		 * Make sure we have something to send
		 */
		var message	= $F( "priv_chat_text_" + id ).strip();
		
		if( message == '' )
		{
			return false;
		}
		
		ipb.chat.privateChats.get( 'privchatwindow_' + id ).hide();
		$( "priv_chat_text_" + id ).clear();
		
		/*
		 * First, do we already have a tab for this user?
		 */
		if( $( 'storage_' + id ) )
		{
			/*
			 * Switch to tab
			 */
			ipb.chat.switchToTab( id );

			/*
			 * We do, so we need to restore this "room" and switch tab
			 */
			$( 'storage_container_' + ipb.chat.activeTab ).update( $('messages-display').innerHTML );
			$( 'messages-display' ).update( $( 'storage_container_' + id ).innerHTML ).addClassName('messages-list');
			$( 'storage_container_' + id ).update();
			
			$( 'tab-' + ipb.chat.activeTab ).removeClassName('active');
			$( 'tab-' + id ).addClassName('active');

			ipb.chat.activeTab	= id;
		}
		
		/*
		 * No tab yet.  Build, then insert and send message.
		 */
		 
		else
		{
			/*
			 * Backup current tab
			 */
			$( 'storage_container_' + ipb.chat.activeTab ).update( $('messages-display').innerHTML );
			
			/*
			 * Build tab
			 */
			ipb.chat.buildTab( id );
			
			/*
			 * Switch to tab
			 */
			ipb.chat.switchToTab( id );
			
			/*
			 * Switch to new tab
			 */
			$( 'messages-display' ).update( $( 'storage_container_' + id ).innerHTML ).addClassName('messages-list');
			$( 'storage_container_' + id ).update();
		}

		/*
		 * Clean the post for our use
		 */
		message	= ipb.chat.cleanMessage( message );

		/*
		 * Send it to the server
		 */
		if( ipb.chat.sendMessageToChild( "server=" + serverHost + "&path=" + serverPath + "&room=" + roomId + "&user=" + userId + "&access_key=" + accessKey + "&lmsg=" + ipb.chat.lastMsgId + "&action=private&message=" + message + '&toUser=' + id + '&charset=' + ipb.vars['charset'] ) )
		{
			/*
			 * Scroll to the bottom
			 */
			$('messages-display').scrollTop	= $('messages-display').scrollHeight + 500;
		
			message	= message.escapeHTML();
			
			ipb.chat.bypassFlag	= true;
			ipb.chat.updateMessages( "1,0~~||~~" + Math.round(new Date().getTime() / 1000) + ",1," + userName.replace( ',', '~~#~~' ) + "," + message + ",private=" + id + "," + userId + "~~||~~" );
			ipb.chat.bypassFlag	= false;
		}

		/*
		 * Update last activity timestamp
		 */
		ipb.chat.lastAction	= parseInt( new Date().getTime().toString().substring( 0, 10 ) );
			
		Debug.write( "Sending private chat to " + name );
		
		return false;
	},

	/*
	 * Draw the appropriate iframes
	 */
	drawIframes: function()
	{
		/*
		 * We use one iframe to postMessage for window.postMessage browsers
		 */
		if( window.postMessage )
		{
			var iframe = new Element( 'iframe', { 'id': 'chatProxy' } );
			
			iframe.writeAttribute( 'src', 'http://' + serverHost + serverPath + 'web/postMessage.php?parent=' + ourUrl );
			iframe.setStyle( 'position', 'absolute' );
			iframe.setStyle( 'left', '-150px' );
			iframe.setStyle( 'top', '0px' );
			iframe.setStyle( 'display', 'none' );

			$('iframeContainer').insert( iframe );
		}
		/*
		 * Otherwise we use a 3-way iframe system
		 */
		else
		{
			var iframe = new Element( 'iframe', { 'id': 'chatProxy' } );

			iframe.writeAttribute( 'src', 'http://' + serverHost + serverPath + 'web/iframeProxy.php?parent=' + ourUrl );
			iframe.setStyle( 'position', 'absolute' );
			iframe.setStyle( 'left', '-150px' );
			iframe.setStyle( 'top', '0px' );
			iframe.setStyle( 'display', 'none' );

			$('iframeContainer').insert( iframe );
		}
	},
	
	/*
	 * Clean the title tag upon refocusing
	 */
	cleanTitle: function()
	{
		if( document.title.match( /\(\d+\) (.+?)/gi ) )
		{
			document.title = document.title.replace( /\(\d+\) (.+?)/gi, "$1" );
		}
		
		ipb.chat.unreadCount	= 0;
	},

	/*
	 * New message sound
	 */
	triggerMessageSound: function()
	{
		/*
		 * Don't play sound if it comes from our own chat message
		 */
		if( ipb.chat.soundEnabled && ipb.chat.soundReady && !ipb.chat.bypassFlag )
		{
			soundManager.play( 'message', ipb.vars['board_url'] + '/public/sounds/message.mp3' );
			
			/*
			 * Originally we used scriptaculous for sound (with .wav files) however scriptaculous
			 * embeds a player into the page to play the sounds, and this steals focus from the
			 * browser window entirely every time a sound plays (even if you are looking at another tab).
			 * As such we switched to .mp3 sounds via a flash interface.
			 */
			//Sound.play( ipb.vars['board_url'] + '/public/sounds/new_message.wav' );
		}
	},
	
	/*
	 * Enter room sound
	 */
	triggerEnterSound: function()
	{
		if( ipb.chat.soundEnabled && ipb.chat.soundReady )
		{
			soundManager.play( 'enter', ipb.vars['board_url'] + '/public/sounds/enter.mp3' );
			//Sound.play( ipb.vars['board_url'] + '/public/sounds/enter.wav' );
		}
	},
	
	/*
	 * Leave room sound
	 */
	triggerLeaveSound: function()
	{
		if( ipb.chat.soundEnabled && ipb.chat.soundReady )
		{
			soundManager.play( 'leave', ipb.vars['board_url'] + '/public/sounds/leave.mp3' );
			//Sound.play( ipb.vars['board_url'] + '/public/sounds/leave.wav' );
		}
	},

	/*
	 * Did the user just hit 'enter' key in a private chat textarea?
	 */
	checkForSendPrivateChat: function(e)
	{
		/*
		 * Submit chat if we hit the enter key
		 */
		if( e.keyCode == Event.KEY_RETURN )
		{
			Event.stop(e);
			
			elem	= Event.findElement(e);
			id		= elem.identify();
			_id		= id.replace( /priv_chat_text_/, '' );

			ipb.chat.sendPrivateChat( _id );
			
			return false;
		}
		
		return true;
	},
	
	/*
	 * Did the user just hit 'enter' key in the chat textarea?
	 */
	checkForSendChat: function(e)
	{
		/*
		 * Submit chat if we hit the enter key
		 */
		if( e.keyCode == Event.KEY_RETURN )
		{
			ipb.chat.sendChat( e );
		}
	},
	
	/*
	 * Send a chat message to the server
	 */
	sendChat: function( e )
	{
		/*
		 * Don't *really* submit the form..
		 */
		Event.stop(e);
		
		/*
		 * Get form field value and strip whitespace
		 */
		var chatPost	= $F('message_textarea').strip();

		/*
		 * Make sure we have something to send
		 */
		if( chatPost == '' )
		{
			return false;
		}

		/*
		 * Clean the post for our use
		 */
		chatPost	= ipb.chat.cleanMessage( chatPost );

		/*
		 * Private chat?
		 */
		if( ipb.chat.activeTab != 'chatroom' )
		{
			var _thisMessage	= "&action=private&toUser=" + ipb.chat.activeTab;
			var _thisExtra		= "private=" + ipb.chat.activeTab;
		}
		else
		{
			var _thisMessage	= "&action=post";
			var _thisExtra		= '0';
		}

		/*
		 * Send it to the server
		 */
		if( ipb.chat.sendMessageToChild( "server=" + serverHost + "&path=" + serverPath + "&room=" + roomId + "&user=" + userId + "&access_key=" + accessKey + "&lmsg=" + ipb.chat.lastMsgId + _thisMessage + "&message=" + chatPost + '&charset=' + ipb.vars['charset'] ) )
		{
			/*
			 * Scroll to the bottom
			 */
			$('messages-display').scrollTop	= $('messages-display').scrollHeight + 500;
		
			chatPost	= chatPost.escapeHTML();
			
			ipb.chat.bypassFlag	= true;
			ipb.chat.updateMessages( "1,0~~||~~" + Math.round(new Date().getTime() / 1000) + ",1," + userName.replace( ',', '~~#~~' ) + "," + chatPost + "," + _thisExtra + "," + userId + "~~||~~" );
			ipb.chat.bypassFlag	= false;
		}
		
		/*
		 * Clear the textarea
		 */
		$('message_textarea').clear();
		
		/*
		 * Scroll to the bottom
		 */
		$('messages-display').scrollTop	= $('messages-display').scrollHeight + 500;

		/*
		 * Update last activity timestamp
		 */
		ipb.chat.lastAction	= parseInt( new Date().getTime().toString().substring( 0, 10 ) );

		/*
		 * Return false to make sure form doesn't submit
		 */
		return false;
	},
	
	/*
	 * Sends a message to the remote server by changing the document.location
	 * of an iframe for the other location.  We also change the width of the iframe
	 * to trigger it's check for a changed location.  It's more resource efficient
	 * to have an event handler monitor size change, than to manually poll the location
	 * every x seconds
	 */
	sendMessageToChild: function( message )
	{
		/*
		 * Don't do anything if we've been kicked
		 */
		if( ipb.chat.kicked )
		{
			return false;
		}
		
		/*
		 * Post message to remote iframe
		 */
		var elem = $('chatProxy').contentWindow;

		if( window.postMessage )
		{
			Debug.write( "Using window.postMessage method" );
			elem.postMessage( message, 'http://' + serverHost );
		}
		else
		{
			Debug.write( "Using iframe hash method" );
			elem.location = 'http://' + serverHost + serverPath + '/web/iframeProxy.php?parent=' + ourUrl + '#' + message + '&timestamp=' + Math.round(new Date().getTime() / 1000);
		}
		
		return true;
	},
	
	/*
	 * Deal with the response from the remote server
	 */
	handleMessageFromChild: function( message )
	{
		/*
		 * Don't do anything if we've been kicked
		 */
		if( ipb.chat.kicked )
		{
			return false;
		}

		/*
		 * Ignore height notifications
		 */
		if( message.substr( 0, 7 ) == 'height=' )
		{
			return false;
		}

		/*
		 * Only if we have an actual response
		 */
		if ( message.length > 0 )
		{
			/*
			 * If this is a response to a new submission or mod action, just get the messages
			 */
			if( message.substr( 0, 8 ) == '__post__' || message.substr( 0, 12 ) == '__moderate__' )
			{
				ipb.chat.getMessages();
			}
			
			/*
			 * Otherwise we need to update the displayed messages
			 */
			else
			{
				ipb.chat.updateMessages( message );
			}
		}
	},

	/*
	 * Get messages from remote server
	 */
	getMessages: function( pe )
	{
		/*
		 * Generate a current timestamp
		 */
		curtime	= parseInt( new Date().getTime().toString().substring( 0, 10 ) );

		/*
		 * See if we have been inactive too long first
		 */
		ipb.chat.checkInactivity( curtime );

		/*
		 * If kicked, shut off executer
		 */
		if( ipb.chat.kicked )
		{
			pe.stop();
			return false;
		}
		
		/*
		 * If current time - last polled time is less than the polling timeperiod,
		 * skip this iteration (prevent DOS when user submits many messages quickly)
		 */
		if( curtime - ipb.chat.lastpolled < ipb.chat.polling )
		{
			return false;
		}

		/*
		 * Set last polled timestamp
		 */
		ipb.chat.lastpolled	= curtime;
		
		/*
		 * Send the request to the remote server
		 */
		ipb.chat.sendMessageToChild( "server=" + serverHost + "&path=" + serverPath + "&room=" + roomId + "&user=" + userId + "&access_key=" + accessKey + "&msg=" + ipb.chat.lastMsgId + "&charset=" + ipb.vars['charset'] );
	},

	/*
	 * Update the displayed messages on the page
	 */
	updateMessages: function( text )
	{
		/*
		 * Do we have anything?
		 */
		if( !text )
		{
			return true;
		}

		/*
		 * Messages are imploded on ~~||~~
		 */
		var messages = text.split( "~~||~~" );

		/*
		 * Determine if we need to scroll down when the messages are re-inserted.  We have to do this
		 * now so that we can determine if the scrollbar is already scrolled down all the way.  That way
		 * if a user scrolls up, we don't scroll down on them every 3 seconds - but if they are already
		 * scrolled down all the way, we keep moving down as new messages are inserted.
		 */
		var scrollBottom	= false;
		
		/*
		 * Everything vs IE, as per normal :P
		 */
		try
		{
			var _borderTop		= getComputedStyle( $('messages-display'), '' ).getPropertyValue('border-top-width').replace( 'px', '' );
			var _borderBottom	= getComputedStyle( $('messages-display'), '' ).getPropertyValue('border-bottom-width').replace( 'px', '' );
			var _padTop			= getComputedStyle( $('messages-display'), '' ).getPropertyValue('padding-top').replace( 'px', '' );
			var _padBottom		= getComputedStyle( $('messages-display'), '' ).getPropertyValue('padding-bottom').replace( 'px', '' );
		}
		catch( e )
		{
			var _borderTop		= $('messages-display').currentStyle.borderWidth.replace( 'px', '' );
			var _borderBottom	= $('messages-display').currentStyle.borderWidth.replace( 'px', '' );
			var _padTop			= $('messages-display').currentStyle.paddingTop.replace( 'px', '' );
			var _padBottom		= $('messages-display').currentStyle.paddingBottom.replace( 'px', '' );
		}

		var _totalOffset	= parseInt(_borderTop) + parseInt(_borderBottom) + parseInt(_padTop) + parseInt(_padBottom);
		
		/*
		 * Commented out just to save resources in production
		 */
		//Debug.write( "Scrolltop: " + $('messages-display').scrollTop );			// 60
		//Debug.write( "Scrollheight: " + $('messages-display').scrollHeight );		// 360
		//Debug.write( "Offsetheight: " + $('messages-display').getHeight() );		// 306
		//Debug.write( "Style offsets: " + _totalOffset );							// 6 (3px border top + bottom)

		/*
		 * See above example comments after Debug.write lines
		 */
		if( ( $('messages-display').getHeight() - _totalOffset + $('messages-display').scrollTop ) >= ( $('messages-display').scrollHeight - 10 ) )
		{
			scrollBottom	= true;
		}

		/*
		 * Set some variables
		 */
		var _updatedCount	= 0;
		var _showOwn		= false;
		var _initialEmpty	= false;
		var _startLMI		= ipb.chat.lastMsgId;
		
		/*
		 * Loop over the messages
		 */
		for( var i=0; i < messages.length; i++ )
		{
			/*
			 * Split message at comma
			 */
			messages[ i ]	= messages[ i ].replace( /%20/gi, ' ' );
			messages[ i ]	= messages[ i ].replace( /%23/gi, '#' );
			messages[ i ]	= messages[ i ].replace( /%22/gi, '&quot;' );
			messages[ i ]	= messages[ i ].replace( /%3C/gi, '&lt;' );
			messages[ i ]	= messages[ i ].replace( /%3E/gi, '&gt;' );
			messages[ i ]	= messages[ i ].replace( /\</gi, '&lt;' );
			messages[ i ]	= messages[ i ].replace( /\>/gi, '&gt;' );
			messages[ i ]	= ipb.chat.manualDecode( messages[ i ] );

			var msgDetails	= messages[ i ].split( ',' );
			
			/*
			 * First iteration is meta data: code,lastMessageId
			 */
			if( i == 0 )
			{
				/*
				 * Request failed
				 */
				if( msgDetails[0] != 1 )
				{
					/*
					 * Show message in each window
					 */
					$$('.storage-container').each( function(elem) {
						var _id		= $(elem).id.replace( /storage_container_/, '' );
						var ulList	= ipb.chat.getStorageContainer( _id );
						
						ulList.insert( { bottom: ipb.chat.templates['msg-K'].evaluate() } );
						
						if( _id != ipb.chat.activeTab )
						{
							$( "storage_container_" + _id ).update( ulList );
						}
						else
						{
							$('messages-display').update( ulList ).addClassName('messages-list');
						}
					});

					/*
					 * And prevent future calls to server
					 */
					ipb.chat.kicked	= true;

					/*
					 * Jump to bottom of messages
					 */
					$('messages-display').scrollTop	= $('messages-display').scrollHeight + 500;
					
					return true;
				}
				else
				{
					/*
					 * No new messages (only if not insta-posting)
					 */
					if( !ipb.chat.bypassFlag && msgDetails[1] == ipb.chat.lastMsgId )
					{
						if( ipb.chat.lastMsgId == 0 )
						{
							ipb.chat.bypassFlag	= true;
							ipb.chat.lastMsgId	= 1;
							_initialEmpty		= true;

							msgDetails[1]		= 2;
							msgDetails[2]		= userName;
							msgDetails[3]		= '';
							msgDetails[4]		= '1_' + userId;
							msgDetails[5]		= userId;
						}
						else
						{
							return true;
						}
					}
					
					/*
					 * Store last message ID for next request
					 */
					if( !ipb.chat.bypassFlag )
					{
						/*
						 * If we refreshed the page, and all chat messages are being pulled,
						 * we don't want to skip our own messages
						 */
						if( ipb.chat.lastMsgId == 0 )
						{
							_showOwn	= true;
						}

						ipb.chat.lastMsgId	= msgDetails[1];
					}
					
					if( !_initialEmpty )
					{
						continue;
					}
				}
			}
			
			if( !_startLMI && ipb.chat.noBuffer )
			{
				continue;
			}

			/*
			 * We have a message!
			 */
			if( msgDetails[1] )
			{
				/*
				 * Don't add our own messages since we do that automatically upon submit
				 */
				if( !ipb.chat.bypassFlag && msgDetails[5] == userId && ( msgDetails[1] == 1 || msgDetails[1] == 3 ) && !_showOwn )
				{
					continue;
				}
				
				/*
				 * Grab ulList handle
				 */
				var _thisRowId	= 'chatroom';
				
				if( msgDetails[1] == 1 )
				{
					/*
					 * Is this a private chat?
					 */
					if( msgDetails[4] )
					{
						var userDetails = msgDetails[4].split( '=' );
						
						if( userDetails[1] )
						{
							var _user	= msgDetails[5] == userId ? userDetails[1] : msgDetails[5];
							
							if( !$( 'storage_' + _user ) )
							{
								var _forumUser	= ipb.chat.forumIdMap.get( _user );
								
								var _blocked	= 0;
								
								if( _forumUser )
								{
									_blocked	= ipb.chat.ignoreChats.get( _forumUser[0] );
								}
								
								if( _blocked )
								{
									continue;
								}

								if( !ipb.chat.buildTab( _user ) )
								{
									continue;
								}
								
								ipb.chat.switchToTab( _user );
							}
							
							_thisRowId	= _user;
						}
					}
				}

				var ulList	= ipb.chat.getStorageContainer( _thisRowId );
				var alsoTo	= null;

				/*
				 * Format date from timestamp
				 */
				var _itemDate	= new Date();
				var _secs		= parseInt(msgDetails[0]) ? parseInt(msgDetails[0]) : parseInt(new Date().getTime());
				
				_itemDate.setTime( _secs * 1000 );
				
				var _hours		= _itemDate.getHours();
				var _mins		= _itemDate.getMinutes() + "";
				
				/*
				 * This makes sure we have leading 0
				 */
				if( _mins.length == 1 )
				{
					_mins	= "0" + _mins;
				}
				
				/*
				 * Showing times only at the "5's"
				 */
				if( ipb.chat.condenseTime )
				{
					var _minsLastChar	= _mins.charAt( _mins.length - 1 );
					
					if( _minsLastChar == 1 || _minsLastChar == 2 || _minsLastChar == 3 || _minsLastChar == 4 )
					{
						_mins	= _mins.charAt( 0 ) + "" + 0;
					}
					else if(  _minsLastChar == 6 || _minsLastChar == 7 || _minsLastChar == 8 || _minsLastChar == 9 )
					{
						_mins	= _mins.charAt( 0 ) + "" + 5;
					}
				}
				
				/*
				 * Normalize dates for AM/PM instead of 24 hour
				 */
				if( _hours > 12 )
				{
					_itemDate		= ( _hours - 12 ) + ':' + _mins + ' ' + ipb.lang['time_pm'];
				}
				else if( _hours == 0 )
				{
					_itemDate		= 12 + ':' + _mins + ' ' + ipb.lang['time_am'];
				}
				else if( _hours == 12 )
				{
					_itemDate		= _hours + ':' + _mins + ' ' + ipb.lang['time_pm'];
				}
				else
				{
					_itemDate		= _hours + ':' + _mins + ' ' + ipb.lang['time_am'];
				}

				/*
				 * If this is a new time, insert it
				 */
				if( _itemDate != ipb.chat.lastTime && !ipb.chat.bypassFlag )
				{
					var dateItem	= new Element( 'li' );
					dateItem.update( "<label>&nbsp;</label> <div>" + _itemDate + "</div>" ).addClassName('chat-time');
					ulList.insert( { bottom: dateItem } );
				}
				
				/*
				 * Create new list item
				 */
				var listItemVal	= '';

				/*
				 * Re-format newlines/commas from cleaning in sendChat()
				 */
				msgDetails[2]	= msgDetails[2].replace( '~~#~~', "," );
				msgDetails[2]	= ipb.chat.unCleanMessage( msgDetails[2] );
				
				msgDetails[4]	= msgDetails[4].replace( '~~#~~', "," );
				
				msgDetails[3]	= msgDetails[3].replace( '~~#~~', "," );
				msgDetails[3]	= ipb.chat.unCleanMessage( msgDetails[3] );
				msgDetails[3]	= ipb.chat.parseEmoticonsAndBbcode( msgDetails[3] );

				/*
				 * Is this a /me command?
				 */
				if( msgDetails[1] == 1 )
				{
					if( msgDetails[3].match( /^\/me /gi ) )
					{
						msgDetails[1]	= 3;
						msgDetails[3]	= msgDetails[3].replace( /^\/me /gi, '' );
					}
				}
				
				/*
				 * This is a normal chat
				 */
				if( msgDetails[1] == 1 )
				{
					/*
					 * Truncate username
					 */
					msgDetails[2]	= ipb.chat.formatName( msgDetails[5], msgDetails[2].truncate( 14 ) );

					/*
					 * This is here so we can color our own chats properly
					 */
					if( msgDetails[5] == userId )
					{
						listItemVal	= ipb.chat.templates['msg-1'].evaluate( { date: _itemDate, username: msgDetails[2], message: msgDetails[3], ownclass: 'chat-myown' } );
					}
					else
					{
						listItemVal	= ipb.chat.templates['msg-1'].evaluate( { date: _itemDate, username: msgDetails[2], message: msgDetails[3], ownclass: '' } );
					}
					
					/*
					 * Increment counter
					 */
					_updatedCount++;
					ipb.chat.unreadCount++;
					
					if( _thisRowId != ipb.chat.activeTab )
					{
						if( ipb.chat.unreadTabs.get( _thisRowId ) )
						{
							ipb.chat.unreadTabs.set( _thisRowId, ipb.chat.unreadTabs.get( _thisRowId ) + 1 );
						}
						else
						{
							ipb.chat.unreadTabs.set( _thisRowId, 1 );
						}
					}
				}
				
				/*
				 * This is a "{user} has (entered|left) the room" message
				 */
				else if( msgDetails[1] == 2 )
				{
					/*
					 * Ignoring these messages?
					 */
					if( !ipb.chat.bypassFlag && ipb.chat.hideEnterExit )
					{
						continue;
					}

					/*
					 * Details will be (1|2)_(userid).  1 means entered, 2 means left
					 */
					var details = msgDetails[4].split( '_' );

					/*
					 * Check if we've entered or left
					 */
					if( details[0] == 1 )
					{
						var _action	= ipb.lang['entered_room'];
						
						/*
						 * We don't want to add/remove on initial load - the chatters cache should be accurate anyways
						 */
						if( _startLMI > 0 )
						{
							ipb.chat.addUserToList( details[1], msgDetails[2], details[2] );
						}
						
						msgDetails[2]	= msgDetails[2].truncate( 14 );

						var _details	= ipb.chat.groups.get( details[3] );

						if( _details )
						{
							msgDetails[2]	= _details[0].replace( /__DBQ__/g, '"' ) + msgDetails[2] + _details[1].replace( /__DBQ__/g, '"' );
						}
						
						/*
						 * If user left and then returned, add message to private chat tab
						 */
						$('chat-tab-holder').childElements().each( function(elem){
							/*
							 * This should be an <li>
							 */
							var _thisId	= $(elem).readAttribute('id').replace( /tab-/, '' );
							var _name	= $(elem).innerHTML.stripTags().strip().replace( /&nbsp;/g, '' );
							
							/*
							 * This is the same name as the new tab - just update existing tab
							 */
							if( msgDetails[2] == _name )
							{
								$(elem).writeAttribute( 'id', 'tab-' + details[1] );
								$( "storage_container_" + _thisId ).writeAttribute( 'id', "storage_container_" + details[1] );
								$( "storage_" + _thisId ).writeAttribute( 'id', "storage_" + details[1] );
								
								if( ipb.chat.activeTab == _thisId )
								{
									ipb.chat.activeTab	= details[1];
								}
								
								alsoTo	= details[1];
							}
						});
					}
					else if( details[0] == 2 )
					{
						var _action	= ipb.lang['left_room'];
						
						/*
						 * We don't want to add/remove on initial load - the chatters cache should be accurate anyways
						 */
						if( _startLMI > 0 )
						{
							ipb.chat.removeUserFromList( details[1], msgDetails[2] );
						}
						
						msgDetails[2]	= ipb.chat.formatName( details[1], msgDetails[2].truncate( 14 ) );
						
						/*
						 * We also want to show the message on the private chat tab
						 */
						if( $('storage_container_' + details[1] ) )
						{
							alsoTo	= details[1];
						}
					}
					
					/*
					 * Truncate username
					 */
					//msgDetails[2]	= ipb.chat.formatName( details[1], msgDetails[2].truncate( 14 ) );

					listItemVal	= ipb.chat.templates['msg-2'].evaluate( { date: _itemDate, username: msgDetails[2], action: _action } );
				}
				
				/*
				 * This is a /me style command
				 */
				else if( msgDetails[1] == 3 )
				{
					/*
					 * Truncate username
					 */
					msgDetails[2]	= ipb.chat.formatName( msgDetails[5], msgDetails[2].truncate( 14 ) );
					
					listItemVal	= ipb.chat.templates['msg-3'].evaluate( { username: msgDetails[2], message: msgDetails[3] } );
					
					/*
					 * Increment counter
					 */
					_updatedCount++;
					ipb.chat.unreadCount++;
					
					if( _thisRowId != ipb.chat.activeTab )
					{
						if( ipb.chat.unreadTabs.get( _thisRowId ) )
						{
							ipb.chat.unreadTabs.set( _thisRowId, parseInt( ipb.chat.unreadTabs.get( _thisRowId ) ) + 1 );
						}
						else
						{
							ipb.chat.unreadTabs.set( _thisRowId, 1 );
						}
					}
				}
				
				/*
				 * System message
				 */
				else if( msgDetails[1] == 4 )
				{
					listItemVal	= ipb.chat.templates['msg-4'].evaluate( { message: msgDetails[3] } );

					/*
					 * Show system messages in each window
					 */
					$$('.storage-container').each( function(elem) {
						var _id		= $(elem).id.replace( /storage_/, '' );
						
						if( _id != 'chatroom' )
						{
							var _ulList	= ipb.chat.getStorageContainer( _id );

							_ulList.insert( { bottom: ipb.chat.templates['msg-K'].evaluate() } );
							$(elem).update( _ulList );
						}
					});
					
					$('messages-display').update( ipb.chat.getStorageContainer( ipb.chat.activeTab ) ).addClassName('messages-list');
					
					/*
					 * Increment counter
					 */
					_updatedCount++;
					ipb.chat.unreadCount++;
					
					if( _thisRowId != ipb.chat.activeTab )
					{
						if( ipb.chat.unreadTabs.get( _thisRowId ) )
						{
							ipb.chat.unreadTabs.set( _thisRowId, parseInt( ipb.chat.unreadTabs.get( _thisRowId ) ) + 1 );
						}
						else
						{
							ipb.chat.unreadTabs.set( _thisRowId, 1 );
						}
					}
				}
				
				/*
				 * Mod action (currently only "kick")
				 */
				else if( msgDetails[1] == 5 )
				{
					/*
					 * Remove kicked user from chat list
					 */
					ipb.chat.removeUserFromList( msgDetails[4], msgDetails[3] );
					
					/*
					 * Truncate username
					 */
					msgDetails[2]	= ipb.chat.formatName( msgDetails[5], msgDetails[2].truncate( 14 ) );
					msgDetails[3]	= ipb.chat.formatName( msgDetails[4], msgDetails[3] );
					
					listItemVal	= ipb.chat.templates['msg-5'].evaluate( { username: msgDetails[2], date: _itemDate, extra: msgDetails[3] } );
				}

				/*
				 * Insert list item into list
				 */
				ulList.insert( { bottom: listItemVal } );

				/*
				 * Make sure new list has less li items than max allows
				 */
				if( ipb.chat.maxMessages > 0 )
				{
					/*
					 * Got more than we're allowed?
					 */
					if( ulList.childElements().length > ipb.chat.maxMessages )
					{
						/*
						 * Number to remove
						 */
						var _toRemove	= ulList.childElements().length - ipb.chat.maxMessages;
						
						/*
						 * Remove _toRemove elements from BEGINNING of the list (oldest messages)
						 */
						for( var tr=0; tr < _toRemove; tr++ )
						{
							ulList.firstDescendant().remove();
						}
					}
				}
		
				/*
				 * Insert the ulList into the storage container
				 */
				if( ipb.chat.activeTab != _thisRowId )
				{
					$('storage_container_' + _thisRowId ).update( ulList );
				}
				else
				{
					$('messages-display').update( ulList ).addClassName('messages-list');
				}
				
				/*
				 * Need to insert anywhere else?
				 */
				if( alsoTo )
				{
					var otherUlList	= ipb.chat.getStorageContainer( alsoTo );
					otherUlList.insert( { bottom: listItemVal } );
					
					if( ipb.chat.activeTab != alsoTo )
					{
						$('storage_container_' + alsoTo ).update( otherUlList );
					}
					else
					{
						$('messages-display').update( otherUlList ).addClassName('messages-list');
					}
				}
				
				/*
				 * Update the last time
				 */
				if( !ipb.chat.bypassFlag )
				{
					ipb.chat.lastTime	= _itemDate;
				}
			}
		}

		/*
		 * Update the messages-display div with the new list
		 */ 
		//$('messages-display').update( ipb.chat.getStorageContainer( ipb.chat.activeTab ).addClassName('messages-list') );

		/*
		 * Handle unread counts
		 */
		if( !ipb.chat.windowHasFocus && ipb.chat.unreadCount > 0 )
		{
			if( document.title.match( /\(\d+\) (.+?)/gi ) )
			{
				document.title = document.title.replace( /\(\d+\) (.+?)/gi, "(" + ipb.chat.unreadCount + ") $1" );
			}
			else
			{
				document.title = "(" + ipb.chat.unreadCount + ") " + document.title;
			}
		}
		
		/*
		 * Update unread counts on tabs
		 */
		ipb.chat.updateTabUnreadCount();
		
		/*
		 * Trigger sound if we have messages (not enter/leave room or mod kick notifications though)
		 */
		if( _updatedCount )
		{
			ipb.chat.triggerMessageSound();
		}
		
		/*
		 * Reset bypass flag if this was initial pull override
		 */
		if( _initialEmpty )
		{
			ipb.chat.bypassFlag	= false;
		}
		
		/*
		 * Make sure that if the only downloaded messages are private chats that we build the "chatroom" list properly too
		 */
		if( ipb.chat.getStorageContainer( 'chatroom' ).empty() )
		{
			var _whateverItWas	= ipb.chat.bypassFlag;
			ipb.chat.bypassFlag = true;
			ipb.chat.updateMessages( "1," + ipb.chat.lastMsgId + "~~||~~" + Math.round(new Date().getTime() / 1000) + ",2," + userName.replace( ',', '~~#~~' ) + ",,1_" + userId + "," + userId + "~~||~~" );
			ipb.chat.bypassFlag = _whateverItWas;
		}

		/*
		 * If we were at the bottom, let's make sure we're scrolled all the way down in case new items have
		 * been inserted, making the scrollable div taller
		 */
		if( scrollBottom )
		{
			Debug.write( "Scrolling down to: " + $('messages-display').scrollHeight );
			$('messages-display').scrollTop	= $('messages-display').scrollHeight + 500;
		}
		
		return true;
	},
	
	/*
	 * Check if we have been inactive too long
	 */
	checkInactivity: function( timestamp )
	{
		if( !timestamp || !ipb.chat.inactiveKick )
		{
			return;
		}
		
		var _inactiveSeconds	= ipb.chat.inactiveKick * 60;
		
		if( timestamp - ipb.chat.lastAction > _inactiveSeconds )
		{
			var _whateverItWas	= ipb.chat.bypassFlag;
			ipb.chat.bypassFlag = true;
			ipb.chat.updateMessages( "0," + ipb.chat.lastMsgId + "~~||~~" + Math.round(new Date().getTime() / 1000) + ",5," + userName.replace( ',', '~~#~~' ) + ",,1_" + userId + "," + userId + "~~||~~" );
			ipb.chat.bypassFlag = _whateverItWas;
			
			ipb.chat.kicked	= true;
		}
		
		return;
	},

	/*
	 * Update the unread count displayed on each tab
	 */
	updateTabUnreadCount: function()
	{
		ipb.chat.unreadTabs.set( ipb.chat.activeTab, 0 );
		
		ipb.chat.unreadTabs.each( function( pair )
		{
			if( pair.key == ipb.chat.activeTab )
			{
				var _thisHtml	= $('tab-' + pair.key ).innerHTML;

				if( _thisHtml.match( /(.+?) \(\d+\)\<\/a\>/gi ) )
				{
					_thisHtml = _thisHtml.replace( /(.+?) \(\d+\)\<\/a\>/gi, "$1</a>" );
				}
				
				$('tab-' + pair.key ).update( _thisHtml );
			
				return;
			}
			
			if( !$('tab-' + pair.key ) || !pair.value )
			{
				return;
			}
			
			var _thisHtml	= $('tab-' + pair.key ).innerHTML;

			if( _thisHtml.match( /(.+?) \(\d+\)\<\/a\>/gi ) )
			{
				_thisHtml = _thisHtml.replace( /(.+?) \(\d+\)\<\/a\>/gi, "$1 (" + pair.value + ")</a>" );
			}
			else
			{
				_thisHtml = _thisHtml.replace( /\<\/a\>/i, '' ) + " (" + pair.value + ")</a>";
			}
			
			$('tab-' + pair.key ).update( _thisHtml );
		});
		
		/*
		 * Reset click handlers
		 */
		$$('#chat-tab-bar a').each( function(elem) {
			id = elem.identify();

			if( $( id ) )
			{
				$( id ).stopObserving('click');
				$( id ).observe( "click", ipb.chat.switchTab );
			}
		});
	},
	
	/*
	 * Get the appropriate storage container
	 */
	getStorageContainer: function( tab )
	{
		if( !$( 'storage_' + tab ) )
		{
			return new Element( 'ul', { id: 'storage_' + tab } );
		}
		else
		{
			return new Element( 'ul', { id: 'storage_' + tab } ).update( $( 'storage_' + tab ).innerHTML );
		}
	},
	
	/*
	 * Add a user to the "Online chat users" list dynamically
	 */
	addUserToList: function( user_id, username, forum_user_id )
	{
		/*
		 * Only add if it doesn't exist
		 */
		if( !$('user_' + user_id) && !$('link_' + forum_user_id) )
		{
			var htmlFragment	= new Element( 'li', { id: 'user_' + user_id } );
			
			/*
			 * Initialize basic additions
			 */
			new Ajax.Request( 
								ipb.vars['base_url'] + "&app=ipchat&module=ajax&section=adduser&md5check=" + ipb.vars['secure_hash'] + "&id=" + forum_user_id + "&user=" + user_id, 
								{ 
									method: 'get',
									onSuccess: function(t)
									{
										/*
										 * Insert the username into the li
										 */
										if( Object.isUndefined( t.responseJSON ) )
										{
											// Well, this is bad.
											Debug.error("Invalid response returned from the server");
											return;
										}
										
										htmlFragment.update( t.responseJSON['html'] );

										ipb.chat.nameFormatting.set( user_id, [ t.responseJSON['prefix'], t.responseJSON['suffix'], t.responseJSON['name'] ] );
										ipb.chat.forumIdMap.set( user_id, [ forum_user_id, t.responseJSON['_canBeIgnored'] ] );

										$('online-chat-count').innerHTML	= parseInt( $('online-chat-count').innerHTML ) + 1;

										/*
										 * Set up private chat, block user, and mod links
										 */
										var modMenu	= new Element( 'ul', { 'class': 'kickmenu', id: 'mod_link_' + user_id + '_menucontent' } );
										
										if( user_id != userId )
										{
											if( ipb.chat.moderator )
											{
												var modML	= new Element( 'li' );
												var modLink	= new Element( 'a', { id: 'kick_user_' + user_id, 'class': 'kick_user', href: '#', title: ipb.lang['chat_kick_user'] } );
												modMenu		= modMenu.update( modML.update( modLink.update( "<img src='" + ipb.vars['img_url'] + "/user_delete.png' /> " + ipb.lang['chat_kick_user'] ) ) );
												
												var modML	= new Element( 'li' );
												var modLink	= new Element( 'a', { id: 'ban_user_' + user_id + '_' + forum_user_id, 'class': 'ban_user', href: '#', title: ipb.lang['chat_ban_user'] } );
												modMenu		= modMenu.insert( { bottom: modML.update( modLink.update( "<img src='" + ipb.vars['img_url'] + "/user_delete.png' /> " + ipb.lang['chat_ban_user'] ) ) } );
											}
											
											if( ipb.chat.private )
											{
												var modML	= new Element( 'li' );
												var modLink	= new Element( 'a', { id: 'priv_user_' + user_id + '_' + forum_user_id, 'class': 'priv_user', href: '#', title: ipb.lang['chat_priv_user'] } );
												modMenu		= modMenu.insert( { bottom: modML.update( modLink.update( "<img src='" + ipb.vars['img_url'] + "/user_comment.png' /> " + ipb.lang['chat_priv_user'] ) ) } );
											}
											
											if( ipb.chat.ignoreChats.get( forum_user_id ) )
											{
												var modML	= new Element( 'li' );
												var modLink	= new Element( 'a', { id: 'unblock_user_' + user_id + '_' + forum_user_id, 'class': 'block_user', href: '#', title: ipb.lang['unblock_priv_user'] } );
												modMenu		= modMenu.insert( { bottom: modML.update( modLink.update( "<img src='" + ipb.vars['img_url'] + "/comments_ignore.png' /> " + ipb.lang['unblock_priv_user'] ) ) } );
											}
											else
											{
												var modML	= new Element( 'li' );
												var modLink	= new Element( 'a', { id: 'block_user_' + user_id + '_' + forum_user_id, 'class': 'block_user', href: '#', title: ipb.lang['block_priv_user'] } );
												modMenu		= modMenu.insert( { bottom: modML.update( modLink.update( "<img src='" + ipb.vars['img_url'] + "/comments_ignore.png' /> " + ipb.lang['block_priv_user'] ) ) } );												
											}
										}
										else
										{
											var modML	= new Element( 'li' );
											modMenu		= modMenu.update( modML.update( '<em>' + ipb.lang['cant_kick_self'] + '</em>' ) );
										}
										
										$('mod-menu-container').insert( { bottom: modMenu } );
							
										/*
										 * Insert the new list item into the online users list, alphabetically
										 */
										var _hasBeenInserted	= false;
										
										$$('#chatters-online > li').each( function(elem) {
											if( !_hasBeenInserted )
											{
												if( ipb.chat.moderator )
												{
													var _name	= $(elem).firstDescendant().next().innerHTML.stripTags().replace( /\<\!--.+?--\>/ig, "" );

													if( username.toLowerCase() < _name.toLowerCase() )
													{
														$(elem).insert( { before: htmlFragment } );
														_hasBeenInserted	= true;
													}
												}
												else
												{
													var _name	= $(elem).innerHTML.stripTags().replace( /\<\!--.+?--\>/ig, "" );

													if( username.toLowerCase() < _name.toLowerCase() )
													{
														$(elem).insert( { before: htmlFragment } );
														_hasBeenInserted	= true;
													}
												}
											}
										});
										
										/*
										 * If hasn't been inserted, insert it at end of list
										 */
										if( !_hasBeenInserted )
										{
											$('chatters-online').insert( { bottom: htmlFragment } );
										}

										/*
										 * If we are a moderator, initialize the menu and then re-initialize
										 * the kick user links
										 */
										new ipb.Menu( $('mod_link_' + user_id ), $( 'mod_link_' + user_id + '_menucontent' ), {}, { afterOpen: ipb.chat.repositionModMenu } );
										
										ipb.chat.initKickLinks();
										
										/*
										 * Trigger sound
										 */
										ipb.chat.triggerEnterSound();
										
										/*
										 * Update count on tab
										 */
										if( $('chat-tab-count') )
										{
											var _curCount	= parseInt( $('chat-tab-count').innerHTML );
											var _newCount	= _curCount + 1;
											
											$('chat-tab-count').update( _newCount );
											$('chat-tab-count').writeAttribute( 'title', ipb.chat.templates['count-title'].evaluate({ count: _newCount }) );
										}
									}
								}
							);
		}
	},
	
	/*
	 * Remove a user from the "Online chat users" list dynamically
	 */
	removeUserFromList: function( user_id, username )
	{
		if( $('user_' + user_id) )
		{
			$('user_' + user_id).remove();
			
			if( $('mod_link_' + user_id + '_menucontent' ) )
			{
				$('mod_link_' + user_id + '_menucontent' ).remove();
			}
			
			$('online-chat-count').innerHTML	= parseInt( $('online-chat-count').innerHTML ) - 1;

			/*
			 * Trigger sound
			 */
			ipb.chat.triggerLeaveSound();

			/*
			 * Update count on tab
			 */
			if( $('chat-tab-count') )
			{
				var _curCount	= parseInt( $('chat-tab-count').innerHTML );
				var _newCount	= _curCount - 1;
				
				$('chat-tab-count').update( _newCount );
				$('chat-tab-count').writeAttribute( 'title', ipb.chat.templates['count-title'].evaluate({ count: _newCount }) );
			}
		}
	},
	
	/*
	 * Initialize the moderator kick user links
	 */
	initKickLinks: function()
	{
		/*
		 * First let's unregister all the click observers 
		 * since this will get built more than once per page load
		 */
		$$('.kick_user', '.ban_user', '.priv_user', '.block_user', '.unblock_user').each( function(elem) {
			$(elem).stopObserving('click');
		});
		
		/*
		 * Now we register kick user link handler
		 */
		$$('.kick_user').each( function(elem) {
			$(elem).observe( 'click', ipb.chat.kickUser );
		});
		
		$$('.ban_user').each( function(elem) {
			$(elem).observe( 'click', ipb.chat.banUser );
		});
		
		$$('.priv_user').each( function(elem) {
			$(elem).observe( 'click', ipb.chat.privateChat );
		});
		
		$$('.block_user').each( function(elem) {
			$(elem).observe( 'click', ipb.chat.blockPrivateChats );
		});
		
		$$('.unblock_user').each( function(elem) {
			$(elem).observe( 'click', ipb.chat.unblockPrivateChats );
		});
	},

	/*
	 * Unblock private chats from a particular user
	 */
	unblockPrivateChats: function(e)
	{
		Event.stop(e);
		
		/*
		 * Get user id from element ID
		 */
		var elem	= Event.findElement( e, 'a' );
		var elemId	= elem.id.replace( "block_user_", "" );
		var elemP	= elemId.split( '_' );	// 0 is chat user id, 1 is forum user id
		
		/*
		 * Close menu
		 */
		ipb.menus.closeAll();
		
		/*
		 * Can't kick self
		 */
		if( elemId == userId )
		{
			alert( ipb.lang['cant_kick_self'] );
			return false;
		}
		else
		{
			Debug.write( "Unblocked the user with id " + elemId );
		}
		
		/*
		 * Send block request to IPB
		 */
		new Ajax.Request( 
							ipb.vars['base_url'] + "app=ipchat&module=ajax&section=block&md5check=" + ipb.vars['secure_hash'] + "&id=" + elemP[1] + "&block=0", 
							{ method: 'post' }
						);
		
		/*
		 * And add to dynamic block list
		 */
		ipb.chat.ignoreChats.unset( elemP[1] );
		
		/*
		 * And lastly, update the menu
		 */
		$(elem).removeClassName('unblock_user').addClassName('block_user');
		$(elem).writeAttribute( { title: ipb.lang['block_priv_user'] } );
		$(elem).update( "<img src='" + ipb.vars['img_url'] + "/comments_ignore.png' /> " + ipb.lang['block_priv_user'] );
		
		/*
		 * Re-initialize kick links
		 */
		ipb.chat.initKickLinks();

		/*
		 * Update last activity timestamp
		 */
		ipb.chat.lastAction	= parseInt( new Date().getTime().toString().substring( 0, 10 ) );

		return false;
	},

	/*
	 * Block private chats from a particular user
	 */
	blockPrivateChats: function(e)
	{
		Event.stop(e);
		
		/*
		 * Get user id from element ID
		 */
		var elem	= Event.findElement( e, 'a' );
		var elemId	= elem.id.replace( "block_user_", "" );
		var elemP	= elemId.split( '_' );	// 0 is chat user id, 1 is forum user id
		
		var _forumUser	= ipb.chat.forumIdMap.get( elemP[0] );
		
		if( !_forumUser[1] )
		{
			alert( ipb.lang['cant_block_user'] );
			return false;
		}
		
		/*
		 * Close menu
		 */
		ipb.menus.closeAll();
		
		/*
		 * Can't kick self
		 */
		if( elemId == userId )
		{
			alert( ipb.lang['cant_kick_self'] );
			return false;
		}
		else
		{
			Debug.write( "Blocked the user with id " + elemId );
		}
		
		/*
		 * Send block request to IPB
		 */
		new Ajax.Request( 
							ipb.vars['base_url'] + "app=ipchat&module=ajax&section=block&md5check=" + ipb.vars['secure_hash'] + "&id=" + elemP[1] + "&block=1", 
							{ method: 'post' }
						);
		
		/*
		 * And add to dynamic block list
		 */
		ipb.chat.ignoreChats.set( elemP[1], elemP[1] );
		
		/*
		 * And lastly, update the menu
		 */
		$(elem).removeClassName('block_user').addClassName('unblock_user');
		$(elem).writeAttribute( { title: ipb.lang['unblock_priv_user'] } );
		$(elem).update( "<img src='" + ipb.vars['img_url'] + "/comments_ignore.png' /> " + ipb.lang['unblock_priv_user'] );
		
		/*
		 * Re-initialize kick links
		 */
		ipb.chat.initKickLinks();

		/*
		 * Update last activity timestamp
		 */
		ipb.chat.lastAction	= parseInt( new Date().getTime().toString().substring( 0, 10 ) );

		return false;
	},

	/*
	 * Initiate a private chat
	 */
	privateChat: function(e)
	{
		Event.stop(e);
		
		/*
		 * Get user id from element ID
		 */
		var elem	= Event.findElement( e, 'a' );
		var elemId	= elem.id.replace( "priv_user_", "" );
		var elemP	= elemId.split( '_' );	// 0 is chat user id, 1 is forum user id
		
		/*
		 * Close menu
		 */
		ipb.menus.closeAll();
		
		/*
		 * Can't send a private chat to yourself
		 */
		if( elemId == userId )
		{
			alert( ipb.lang['cant_kick_self'] );
			return false;
		}
		
		if( ipb.chat.privateChats.get( 'privchatwindow_' + elemP[0] ) )
		{
			ipb.chat.privateChats.get( 'privchatwindow_' + elemP[0] ).show();
		}
		else
		{
			ipb.chat.privateChats.set( 'privchatwindow_' + elemP[0], new ipb.Popup( 'privchatwindow_' + elemP[0], {
									type: 'balloon',
									initial: ipb.chat.templates['send_private'].evaluate({ id: elemP[0] }),
									stem: true,
									hideAtStart: false,
									attach: { target: $( 'mod_link_' + elemP[0] ), position: 'auto' },
									w: '400px'
							}) );
							
			ipb.chat.privateChats.get( 'privchatwindow_' + elemP[0] ).show();
			
			Event.observe( 'priv_chat_text_' + elemP[0], 'keypress', ipb.chat.checkForSendPrivateChat );

			new ipb.Menu( $('mod_link_' + elemP[0] ), $( 'mod_link_' + elemP[0] + '_menucontent' ), {}, { afterOpen: ipb.chat.repositionModMenu } );
			
			ipb.chat.initKickLinks();
		}
		
		return false;
	},
		
	/*
	 * Ban a user from the chat room
	 */
	banUser: function(e)
	{
		Event.stop(e);
		
		/*
		 * Get user id from element ID
		 */
		var elem	= Event.findElement( e, 'a' );
		var elemId	= elem.id.replace( "ban_user_", "" );
		var elemP	= elemId.split( '_' );	// 0 is chat user id, 1 is forum user id
		
		/*
		 * Close menu
		 */
		ipb.menus.closeAll();
		
		/*
		 * Can't kick self
		 */
		if( elemId == userId )
		{
			alert( ipb.lang['cant_kick_self'] );
			return false;
		}
		else
		{
			Debug.write( "Banned the user with id " + elemId );
		}
		
		/*
		 * Send ban request to IPB
		 */
		new Ajax.Request( 
							ipb.vars['base_url'] + "app=ipchat&module=ajax&section=ban&md5check=" + ipb.vars['secure_hash'] + "&id=" + elemP[1], 
							{ method: 'post' }
						);
		
		/*
		 * Send it to the server
		 */
		ipb.chat.sendMessageToChild( "server=" + serverHost + "&path=" + serverPath + "&room=" + roomId + "&user=" + userId + "&access_key=" + accessKey + "&action=kick&against=" + elemP[0] );

		/*
		 * Update last activity timestamp
		 */
		ipb.chat.lastAction	= parseInt( new Date().getTime().toString().substring( 0, 10 ) );

		return false;
	},
	
	/*
	 * Kick a user from the chat room
	 */
	kickUser: function(e)
	{
		Event.stop(e);
		
		/*
		 * Get user id from element ID
		 */
		var elem	= Event.findElement( e, 'a' );
		var elemId	= elem.id.replace( "kick_user_", "" );
		
		/*
		 * Close menu
		 */
		ipb.menus.closeAll();
		
		/*
		 * Can't kick self
		 */
		if( elemId == userId )
		{
			alert( ipb.lang['cant_kick_self'] );
			return false;
		}
		else
		{
			Debug.write( "Kicked the user with id " + elemId );
		}
		
		/*
		 * Send it to the server
		 */
		ipb.chat.sendMessageToChild( "server=" + serverHost + "&path=" + serverPath + "&room=" + roomId + "&user=" + userId + "&access_key=" + accessKey + "&action=kick&against=" + elemId );

		/*
		 * Update last activity timestamp
		 */
		ipb.chat.lastAction	= parseInt( new Date().getTime().toString().substring( 0, 10 ) );

		return false;
	},
	
	/*
	 * "Clean" messages to preserve certain things
	 */
	cleanMessage: function( message )
	{
		message	= message.replace( /\r/g, '' );
		message	= message.replace( /\n/g, "__N__" );
		message	= message.replace( /,/g, "__C__" );
		message	= message.replace( /=/g, "__E__" );
		message	= message.replace( /\+/g, "__PS__" );
		message	= message.replace( /&/g, "__A__" );
		message	= message.replace( /%/g, "__P__" );
		
		return message;
	},
	
	/*
	 * "UN-Clean" messages to restore certain things
	 */
	unCleanMessage: function( message )
	{
		message	= message.replace( /__N__/g, "<br />" );
		message	= message.replace( /__C__/g, "," );
		message	= message.replace( /__E__/g, "=" );
		message	= message.replace( /__A__/g, "&" );
		message	= message.replace( /__P__/g, "%" );
		message	= message.replace( /__PS__/g, "+" );

		return message;
	},
				
	/*
	 * Verify a URL is valid
	 */
	isValidUrl: function( url )
	{
		Debug.write( "Checking url: " + url );
		
		var regexp = /(https?|ftp|file):\/\/([^<>\"\s]+|[a-z0-9/\._\- !&\#;,%\+\?:=])/i;

		return regexp.test( url );
	},
	
	/*
	 * Parse bbcode and emoticons
	 */
	parseEmoticonsAndBbcode: function( text )
	{
		/*
		 * First we have to parse out and store url bbcode tags, not replace
		 */
		var urlReplacements	= $H();
		var iteration		= 0;
		
		while( text.match( /\[url\](.+?)\[\/url\]/gi ) )
		{
			var matches	= /\[url\](.+?)\[\/url\]/gi.exec( text );
	
			if( matches != null )
			{
				matches[1]	= matches[1].replace( /&quot;/g, '' );

				if( ipb.chat.isValidUrl( matches[1] ) )
				{
					urlReplacements.set( "__URL" + iteration + "__", "<a target='_blank' class='bbc' href='" + matches[1] + "'>" + matches[1] + "</a>" );
					text	= text.replace( matches[0], "__URL" + iteration + "__" );
					iteration++;
				}
				else
				{
					text	= text.replace( matches[0], matches[1] );
				}
			}
		}

		while( text.match( /\[url=(.+?)\](.+?)\[\/url\]/gi ) )
		{		
			var matches	= /\[url=(.+?)\](.+?)\[\/url\]/gi.exec( text );
	
			if( matches != null )
			{
				matches[1]	= matches[1].replace( /&quot;/g, '' );

				if( ipb.chat.isValidUrl( matches[1] ) )
				{
					urlReplacements.set( "__URL" + iteration + "__", "<a target='_blank' class='bbc' href='" + matches[1].replace( /"/gi, '' ).replace( /&quot;/gi, '' ) + "'>" + matches[2] + "</a>" );
					text	= text.replace( matches[0], "__URL" + iteration + "__" );
					iteration++;
				}
				else
				{
					text	= text.replace( matches[0], matches[1] );
				}
			}
		}

		/*
		 * Now we parse the raw URLs
		 */
		var exp = /(\b(https?|ftp|file):\/\/([^<>\"\s]+|[a-z0-9/\._\- !&\#;,%\+\?:=]+))/ig;
		text	= text.replace(exp,"<a target='_blank' class='bbc' href='$1'>$1</a>" ); 

		/*
		 * And lastly, loop through and replace stored bbcode url tags
		 */
		urlReplacements.each( function(pair)
		{
			text	= text.replace( pair.key, pair.value );
		});
		
		/*
		 * IMG tags
		 * Images aren't very smooth - they redownload each time a new chat is inserted, and larger (slower)
		 * images this becomes very noticeable for.  Shelving img tag support until a later version when we can
		 * handle them differently.
		 */
		/*while( text.match( /\[img\](.+?)\[\/img\]/gi ) )
		{		
			var matches	= /\[img\](.+?)\[\/img\]/gi.exec( text );
	
			if( matches != null )
			{
				if( ipb.chat.isValidUrl( matches[1] ) )
				{
					var randId	= Math.floor(Math.random()*999999999);
					
					text	= text.replace( matches[0], "<img class='bbc_img' id='img_" + randId + "' src='" + matches[1] + "' />" );
					
					ipb.chat.resizeImage( randId );
				}
				else
				{
					text	= text.replace( matches[0], matches[1] );
				}
			}
		}*/

		/*
		 * Other basics
		 */
		text	= text.replace( /\[b\](.+?)\[\/b\]/gi, "<strong class='bbc'>$1</strong>" );
		text	= text.replace( /\[i\](.+?)\[\/i\]/gi, "<em class='bbc'>$1</em>" );
		text	= text.replace( /\[u\](.+?)\[\/u\]/gi, "<u class='bbc'>$1</u>" );
		
		/*
		 * Emoticons...
		 */
		text	= text.replace( '!', '&#33;' );
		
		ipb.chat.emoticons.each( function(emoticon) {
			var _tmp	= emoticon.value.split(',');
			
			/*
			 * We need to capture opening and closing space
			 */
			var img		= "$1<img src='" + ipb.vars['emoticon_url'] + '/' + _tmp[1] + "' class='bbc' />$2";
			
			var _regexp	= new RegExp( "(^|\\s)" + RegExp.escape(emoticon.key) + "(\\s|$)", "gi" );
			
			while( text.match( _regexp ) )
			{
				text	= text.replace( _regexp, img );
			}
		});
		
		/*
		 * Bad words...
		 */
		var badwordReplacements	= $H();
		var iteration			= 0;
		
		ipb.chat.badwords.each( function(badword) {
			if( !badword.value[1] )
			{
				badword.value[1]	= '######';
			}
			
			/*
			 * Have to treat 'ass' special, due to HTML use of 'class'
			 */
			if( badword.key == 'ass' )
			{
				if( badword.value[0] )
				{
					var pattern	= '(\\b|!|\\?|\\.|,|$)' + RegExp.escape( String.reverse( badword.key ) ) + '(?!lc)(^|\\b|\\s)';
				}
				else
				{
					var pattern	= RegExp.escape( String.reverse( badword.key ) ) + '(?!lc)';
				}

				var _test	= String.reverse( text );
				var _regexp	= new RegExp( pattern, "gi" );

				while( matches = _regexp.exec( _test ) )
				{
					if( matches != null )
					{
						if( badword.value[0] )
						{
							badwordReplacements.set( "__BW" + iteration + "__", String.reverse( matches[2] ) + badword.value[1] + String.reverse( matches[1] ) );
						}
						else
						{
							badwordReplacements.set( "__BW" + iteration + "__", badword.value[1] );
						}
						
						_test	= _test.replace( matches[0], "__" + iteration + "WB__" );

						iteration++;
					}
				}
				
				text	= String.reverse( _test );
				return;
			}

			if( badword.value[0] )
			{
				var pattern	= '(^|\\b|\\s)' + RegExp.escape(badword.key) + '(\\b|!|\\?|\\.|,|$)';
			}
			else
			{
				var pattern	= RegExp.escape(badword.key);
			}
			
			var _regexp	= new RegExp( pattern, "gi" );
			
			while( matches = _regexp.exec( text ) )
			{
				if( matches != null )
				{
					if( badword.value[0] )
					{
						badwordReplacements.set( "__BW" + iteration + "__", matches[1] + badword.value[1] + matches[2] );
					}
					else
					{
						badwordReplacements.set( "__BW" + iteration + "__", badword.value[1] );
					}
					
					text	= text.replace( matches[0], "__BW" + iteration + "__" );
					iteration++;
				}
			}
		});

		badwordReplacements.each( function(pair)
		{
			text	= text.replace( pair.key, pair.value );
		});

		return text;
	},
	
	/*
	 * Format user name
	 */
	formatName: function( _user_id, _user_name )
	{
		var _details	= ipb.chat.nameFormatting.get( _user_id );

		if( _details )
		{
			return _details[0].replace( /__DBQ__/g, '"' ) + _user_name + _details[1].replace( /__DBQ__/g, '"' );
		}
		
		return _user_name;
	},

	/*
	 * Ping the IPB installation in order to update "who's chatting"
	 */
	ping: function( pe )
	{
		/*
		 * If kicked, shut off executer
		 */
		if( ipb.chat.kicked )
		{
			pe.stop();
			return false;
		}
		
		new Ajax.Request( 
						ipb.vars['base_url'] + "app=ipchat&module=ajax&section=update&md5check="+ipb.vars['secure_hash'], 
						{ method: 'get' }
						);
	},

	/*
	 * This is here mainly for Safari.  In Safari, it automatically encodes chars
	 * and they are not valid for decodeURI/decodeURIComponent.
	 * Example: teraßyte becomes tera%DFyte
	 * This function will decode those characters
	 */
	manualDecode: function( string )
	{
		string	= string.replace( /\%([a-zA-Z0-9]{2})/gi, function( hex ) {
						return String.fromCharCode( parseInt( hex.replace( '%', '' ), 16 ) );
					});

		return string;
	},
	
	/*
	 * Attempt to fix position of moderator menu *sigh*
	 */
	repositionModMenu: function( menu )
	{
		/*
		 * Top position is only one we have trouble with >.<
		 */
		var _top	= $( menu.target ).getStyle('top').replace( /px/, '' );
		var _cont	= $('chatters-online-wrap').scrollTop;
		
		Debug.write( "Online chatters current top pos: " + _top );
		Debug.write( "Online chatters scroll offset: " + _cont );
		Debug.write( "New top pos: " + (_top - _cont) );
		
		$( menu.target ).setStyle( 'top:'  + (_top - _cont) + 'px;' );
	}
};

ipb.chat.init();
