/*

Jappix - An open social platform
These are the Jappix Mini JS scripts for Jappix

-------------------------------------------------

License: dual-licensed under AGPL and MPLv2
Authors: Valérian Saliou, hunterjm, Camaran, regilero, Kloadut
Last revision: 10/06/13

*/

// Jappix Mini globals
var MINI_DISCONNECT				= false;
var MINI_AUTOCONNECT			= false;
var MINI_SHOWPANE				= false;
var MINI_INITIALIZED			= false;
var MINI_ANONYMOUS				= false;
var MINI_ANIMATE				= false;
var MINI_RANDNICK				= false;
var MINI_GROUPCHAT_PRESENCE		= false;
var MINI_DISABLE_MOBILE			= false;
var MINI_NICKNAME				= '';
var MINI_TITLE					= null;
var MINI_DOMAIN					= null;
var MINI_USER					= null;
var MINI_PASSWORD				= null;
var MINI_ACTIVE					= null;
var MINI_RECONNECT				= 0;
var MINI_CHATS					= [];
var MINI_GROUPCHATS				= [];
var MINI_SUGGEST_CHATS			= [];
var MINI_SUGGEST_GROUPCHATS		= [];
var MINI_SUGGEST_PASSWORDS		= [];
var MINI_PASSWORDS				= [];
var MINI_PRIORITY				= 1;
var MINI_RESOURCE				= JAPPIX_RESOURCE + ' Mini';
var MINI_ERROR_LINK				= 'https://mini.jappix.com/issues';

// Setups connection handlers
function setupConMini(con) {
	con.registerHandler('message', handleMessageMini);
	con.registerHandler('presence', handlePresenceMini);
	con.registerHandler('iq', handleIQMini);
	con.registerHandler('onerror', handleErrorMini);
	con.registerHandler('onconnect', connectedMini);
}

// Connects the user with the given logins
function connectMini(domain, user, password) {
	try {
		// We define the http binding parameters
		oArgs = new Object();
		
		if(HOST_BOSH_MINI)
			oArgs.httpbase = HOST_BOSH_MINI;
		else
			oArgs.httpbase = HOST_BOSH;
		
		// Check BOSH origin
		BOSH_SAME_ORIGIN = isSameOrigin(oArgs.httpbase);
		
		// We create the new http-binding connection
		con = new JSJaCHttpBindingConnection(oArgs);
		
		// And we handle everything that happen
		setupConMini(con);
		
		// Generate a resource
		var random_resource = getDB('jappix-mini', 'resource');
		
		if(!random_resource)
			random_resource = MINI_RESOURCE + ' (' + (new Date()).getTime() + ')';
		
		// We retrieve what the user typed in the login inputs
		oArgs = new Object();
		oArgs.secure = true;
		oArgs.xmllang = XML_LANG;
		oArgs.resource = random_resource;
		oArgs.domain = domain;
		
		// Store the resource (for reconnection)
		setDB('jappix-mini', 'resource', random_resource);
		
		// Anonymous login?
		if(MINI_ANONYMOUS) {
			// Anonymous mode disabled?
			if(!allowedAnonymous()) {
				logThis('Not allowed to use anonymous mode.', 2);
				
				// Notify this error
				notifyErrorMini();
				
				return false;
			}
			
			// Bad domain?
			else if(lockHost() && (domain != HOST_ANONYMOUS)) {
				logThis('Not allowed to connect to this anonymous domain: ' + domain, 2);
				
				// Notify this error
				notifyErrorMini();
				
				return false;
			}
			
			oArgs.authtype = 'saslanon';
		}
		
		// Normal login
		else {
			// Bad domain?
			if(lockHost() && (domain != HOST_MAIN)) {
				logThis('Not allowed to connect to this main domain: ' + domain, 2);
				
				// Notify this error
				notifyErrorMini();
				
				return false;
			}
			
			// No nickname?
			if(!MINI_NICKNAME)
				MINI_NICKNAME = user;
			
			oArgs.username = user;
			oArgs.pass = password;
		}
		
		// We connect !
		con.connect(oArgs);
		
		logThis('Jappix Mini is connecting...', 3);
	}
	
	catch(e) {
		// Logs errors
		logThis('Error while logging in: ' + e, 1);
		
		// Reset Jappix Mini
		disconnectedMini();
	}
	
	finally {
		return false;
	}
}

// When the user is connected
function connectedMini() {
	// Update the roster
	jQuery('#jappix_mini a.jm_pane.jm_button span.jm_counter').text('0');
	
	// Do not get the roster if anonymous
	if(MINI_ANONYMOUS)
		initializeMini();
	else
		getRosterMini();
	
	// For logger
	if(MINI_RECONNECT)
		logThis('Jappix Mini is now reconnected.', 3);
	else
		logThis('Jappix Mini is now connected.', 3);
	
	// Reset reconnect var
	MINI_RECONNECT = 0;
}

// When the user disconnects
function saveSessionMini() {
	// Not connected?
	if(!isConnected())
		return;
	
	// Save the actual Jappix Mini DOM
	setDB('jappix-mini', 'dom', jQuery('#jappix_mini').html());
	setDB('jappix-mini', 'nickname', MINI_NICKNAME);
	
	// Save the scrollbar position
	var scroll_position = '';
	var scroll_hash = jQuery('#jappix_mini div.jm_conversation:has(a.jm_pane.jm_clicked)').attr('data-hash');
	
	if(scroll_hash)
		scroll_position = document.getElementById('received-' + scroll_hash).scrollTop + '';
	
	setDB('jappix-mini', 'scroll', scroll_position);
	
	// Save the session stamp
	setDB('jappix-mini', 'stamp', getTimeStamp());
	
	// Suspend connection
	con.suspend(false);
	
	logThis('Jappix Mini session save tool launched.', 3);
}

// Disconnects the connected user
function disconnectMini() {
	// No connection?
	if(!isConnected())
		return false;
	
	logThis('Jappix Mini is disconnecting...', 3);
	
	// Change markers
	MINI_DISCONNECT = true;
	MINI_INITIALIZED = false;
	
	// Add disconnection handler
	con.registerHandler('ondisconnect', disconnectedMini);
	
	// Disconnect the user
	con.disconnect();
	
	return false;
}

// When the user is disconnected
function disconnectedMini() {
	// Remove the stored items
	removeDB('jappix-mini', 'dom');
	removeDB('jappix-mini', 'nickname');
	removeDB('jappix-mini', 'scroll');
	removeDB('jappix-mini', 'stamp');
	
	// Connection error?
	if(!MINI_DISCONNECT || MINI_INITIALIZED) {
		// Browser error?
		notifyErrorMini();
		
		// Reset reconnect timer
		jQuery('#jappix_mini').stopTime();
		
		// Try to reconnect after a while
		if(MINI_INITIALIZED && (MINI_RECONNECT < 5)) {
			// Reconnect interval
			var reconnect_interval = 10;
			
			if(MINI_RECONNECT)
				reconnect_interval = (5 + (5 * MINI_RECONNECT)) * 1000;
			
			MINI_RECONNECT++;
			
			// Set timer
			jQuery('#jappix_mini').oneTime(reconnect_interval, function() {
				launchMini(true, MINI_SHOWPANE, MINI_DOMAIN, MINI_USER, MINI_PASSWORD);
			});
		}
	}
	
	// Normal disconnection?
	else
		launchMini(false, MINI_SHOWPANE, MINI_DOMAIN, MINI_USER, MINI_PASSWORD);
	
	// Reset markers
	MINI_DISCONNECT = false;
	MINI_INITIALIZED = false;
	
	logThis('Jappix Mini is now disconnected.', 3);
}

// Handles the incoming errors
function handleErrorMini(err) {
	// First level error (connection error)
	if(jQuery(err).is('error')) {
		// Notify this error
		disconnectedMini();
		
		logThis('First level error received.', 1);
	}
}

// Handles the incoming messages
function handleMessageMini(msg) {
	try {
		var type = msg.getType();
		
		// This is a message Jappix can handle
		if((type == 'chat') || (type == 'normal') || (type == 'groupchat') || !type) {
			// Get the packet data
			var node = msg.getNode();
			var subject = trim(msg.getSubject());
			var body = subject ? subject : trim(msg.getBody());
			
			// Get the sender data
			var from = fullXID(getStanzaFrom(msg));
			var xid = bareXID(from);
			var hash = hex_md5(xid);
			
			// Any attached message body?
			if(body) {
				// Get more sender data
				var use_xid = xid;
				var nick = thisResource(from);
				
				// Read the delay
				var delay = readMessageDelay(node);
				var d_stamp;
				
				// Manage this delay
				if(delay) {
					time = relativeDate(delay);
					d_stamp = Date.jab2date(delay);
				}
				
				else {
					time = getCompleteTime();
					d_stamp = new Date();
				}
				
				// Get the stamp
				var stamp = extractStamp(d_stamp);
				
				// Is this a groupchat private message?
				if(exists('#jappix_mini #chat-' + hash + '[data-type="groupchat"]')) {
					// Regenerate some stuffs
					if((type == 'chat') || (type == 'normal') || !type) {
						xid = from;
						hash = hex_md5(xid);
					}
					
					// XID to use for a groupchat
					else
						use_xid = from;
				}
				
				// Message type
				var message_type = 'user-message';
				
				// Grouphat values
				if(type == 'groupchat') {
					// Old message
					if(msg.getChild('delay', NS_URN_DELAY) || msg.getChild('x', NS_DELAY))
						message_type = 'old-message';
					
					// System message?
					if(!nick || subject) {
						nick = '';
						message_type = 'system-message';
					}
				}
				
				// Chat values
				else {
					nick = jQuery('#jappix_mini a#friend-' + hash).text().revertHtmlEnc();
					
					// No nickname?
					if(!nick) {
						// If the roster does not give us any nick the user may have send us a nickname to use with his first message
						// @see http://xmpp.org/extensions/xep-0172.html
						var unknown_entry = jQuery('#jappix_mini a.jm_unknown[data-xid="' + xid + '"]');
						
						if(unknown_entry.size() > 0) {
							nick =  unknown_entry.attr('data-nick');
						} else {
							var msgnick = msg.getNick();
							nick = getXIDNick(xid);
							
							if(msgnick) {
							 	// If there is a nickname in the message which differs from the jid-extracted nick then tell it to the user
								if(nick != msgnick)
									 nick = msgnick + ' (' + nick + ')';
							}
							
							// Push that unknown guy in a temporary roster entry
							var unknown_entry = jQuery('<a class="jm_unknown jm_offline" href="#"></a>').attr('data-nick', nick).attr('data-xid', xid);
							unknown_entry.appendTo('#jappix_mini div.jm_roster div.jm_buddies');
						 }
					}
				}
				
				// Define the target div
				var target = '#jappix_mini #chat-' + hash;
				
				// Create the chat if it does not exist
				if(!exists(target) && (type != 'groupchat'))
					chatMini(type, xid, nick, hash);
				
				// Display the message
				displayMessageMini(type, body, use_xid, nick, hash, time, stamp, message_type);
				
				// Notify the user if not focused & the message is not a groupchat old one
				if((!jQuery(target + ' a.jm_chat-tab').hasClass('jm_clicked') || !isFocused() || (MINI_ACTIVE != hash)) && (message_type == 'user-message')) {
					// Play a sound
					if(type != 'groupchat')
						soundPlayMini();
					
					// Show a notification bubble
					notifyMessageMini(hash);
				}
				
				logThis('Message received from: ' + from);
			}
			
			// Chatstate groupchat filter
			if(exists('#jappix_mini #chat-' + hash + '[data-type="groupchat"]')) {
				xid = from;
				hash = hex_md5(xid);
			}
			
			// Reset current chatstate
			resetChatstateMini(xid, hash, type);
			
			// Apply new chatstate (if supported)
			if(jQuery(node).find('active[xmlns="' + NS_CHATSTATES + '"]').size() || jQuery(node).find('composing[xmlns="' + NS_CHATSTATES + '"]').size()) {
				// Set marker to tell other user supports chatstates
				jQuery('#jappix_mini #chat-' + hash + ' input.jm_send-messages').attr('data-chatstates', 'true');
				
				// Composing?
				if(jQuery(node).find('composing[xmlns="' + NS_CHATSTATES + '"]').size())
					displayChatstateMini('composing', xid, hash, type);
			}
		}
	} catch(e) {
		logThis('Error on message handler: ' + e, 1);
	}
}

// Handles the incoming IQs
function handleIQMini(iq) {
	try {
		// Define some variables
		var iqFrom = fullXID(getStanzaFrom(iq));
		var iqID = iq.getID();
		var iqQueryXMLNS = iq.getQueryXMLNS();
		var iqType = iq.getType();
		var iqNode = iq.getNode();
		
		// Build the response
		var iqResponse = new JSJaCIQ();
		
		iqResponse.setID(iqID);
		iqResponse.setTo(iqFrom);
		iqResponse.setType('result');
		
		// Software version query
		if((iqQueryXMLNS == NS_VERSION) && (iqType == 'get')) {
			/* REF: http://xmpp.org/extensions/xep-0092.html */
			
			var iqQuery = iqResponse.setQuery(NS_VERSION);
			
			iqQuery.appendChild(iq.buildNode('name', {'xmlns': NS_VERSION}, 'Jappix Mini'));
			iqQuery.appendChild(iq.buildNode('version', {'xmlns': NS_VERSION}, JAPPIX_VERSION));
			iqQuery.appendChild(iq.buildNode('os', {'xmlns': NS_VERSION}, navigator.platform));
			
			con.send(iqResponse);
			
			logThis('Received software version query: ' + iqFrom);
		}
		
		// Roster push
		else if((iqQueryXMLNS == NS_ROSTER) && (iqType == 'set')) {
			// Display the friend
			handleRosterMini(iq);
			
			con.send(iqResponse);
			
			logThis('Received a roster push.');
		}
		
		// Disco info query
		else if((iqQueryXMLNS == NS_DISCO_INFO) && (iqType == 'get')) {
			/* REF: http://xmpp.org/extensions/xep-0030.html */
			
			var iqQuery = iqResponse.setQuery(NS_DISCO_INFO);
			
			// We set the name of the client
			iqQuery.appendChild(iq.appendNode('identity', {
				'category': 'client',
				'type': 'web',
				'name': 'Jappix Mini',
				'xmlns': NS_DISCO_INFO
			}));
			
			// We set all the supported features
			var fArray = new Array(
				NS_DISCO_INFO,
				NS_VERSION,
				NS_ROSTER,
				NS_MUC,
				NS_VERSION,
				NS_URN_TIME
			);
			
			for(i in fArray)
				iqQuery.appendChild(iq.buildNode('feature', {'var': fArray[i], 'xmlns': NS_DISCO_INFO}));
			
			con.send(iqResponse);
			
			logThis('Received a disco#infos query.');
		}
		
		// User time query
		else if(jQuery(iqNode).find('time').size() && (iqType == 'get')) {
			/* REF: http://xmpp.org/extensions/xep-0202.html */
			
			var iqTime = iqResponse.appendNode('time', {'xmlns': NS_URN_TIME});
			iqTime.appendChild(iq.buildNode('tzo', {'xmlns': NS_URN_TIME}, getDateTZO()));
			iqTime.appendChild(iq.buildNode('utc', {'xmlns': NS_URN_TIME}, getXMPPTime('utc')));
			
			con.send(iqResponse);
			
			logThis('Received local time query: ' + iqFrom);
		}
		
		// Ping
		else if(jQuery(iqNode).find('ping').size() && (iqType == 'get')) {
			/* REF: http://xmpp.org/extensions/xep-0199.html */
			
			con.send(iqResponse);
			
			logThis('Received a ping: ' + iqFrom);
		}
		
		// Not implemented
		else if(!jQuery(iqNode).find('error').size() && ((iqType == 'get') || (iqType == 'set'))) {
			// Append stanza content
			for(var i = 0; i < iqNode.childNodes.length; i++)
				iqResponse.getNode().appendChild(iqNode.childNodes.item(i).cloneNode(true));
			
			// Append error content
			var iqError = iqResponse.appendNode('error', {'xmlns': NS_CLIENT, 'code': '501', 'type': 'cancel'});
			iqError.appendChild(iq.buildNode('feature-not-implemented', {'xmlns': NS_STANZAS}));
			iqError.appendChild(iq.buildNode('text', {'xmlns': NS_STANZAS}, _e("The feature requested is not implemented by the recipient or server and therefore cannot be processed.")));
			
			con.send(iqResponse);
			
			logThis('Received an unsupported IQ query from: ' + iqFrom);
		}
	} catch(e) {
		logThis('Error on IQ handler: ' + e, 1);
	}
}

// Handles the incoming presences
function handlePresenceMini(pr) {
	try {
		// Get the values
		var from = fullXID(getStanzaFrom(pr));
		var xid = bareXID(from);
		var resource = thisResource(from);
		var hash = hex_md5(xid);
		var type = pr.getType();
		var show = pr.getShow();
		
		// Manage the received presence values
		if((type == 'error') || (type == 'unavailable')) {
			show = 'unavailable';
		} else {
			switch(show) {
				case 'chat':
				case 'away':
				case 'xa':
				case 'dnd':
					break;
				
				default:
					show = 'available';
					
					break;
			}
		}
		
		// Is this a groupchat presence?
		var groupchat_path = '#jappix_mini #chat-' + hash + '[data-type="groupchat"]';
		var is_groupchat = false;
		
		if(exists(groupchat_path)) {
			// Groupchat exists
			is_groupchat = true;
			
			// Groupchat buddy presence (not me)
			if(resource != unescape(jQuery(groupchat_path).attr('data-nick'))) {
				// Regenerate some stuffs
				var groupchat = xid;
				var groupchat_hash = hash;
				xid = from;
				hash = hex_md5(xid);
				
				// Process this groupchat user presence
				var log_message;

				if(show == 'unavailable') {
					// Remove from roster view
					removeBuddyMini(hash, groupchat);

					// Generate log message
					log_message = printf(_e("%s left"), resource.htmlEnc());
				} else {
					// Add to roster view
					addBuddyMini(xid, hash, resource, groupchat);

					// Generate log message
					log_message = printf(_e("%s joined"), resource.htmlEnc());
				}

				// Log message in chat view
				if(MINI_GROUPCHAT_PRESENCE && log_message && (jQuery(groupchat_path).attr('data-init') == 'true'))
					displayMessageMini('groupchat', log_message, xid, '', groupchat_hash, getCompleteTime(), getTimeStamp(), 'system-message');
			}
		}
		
		// Friend path
		var chat = '#jappix_mini #chat-' + hash;
		var friend = '#jappix_mini a#friend-' + hash;
		var send_input = chat + ' input.jm_send-messages';
		
		// Is this friend online?
		if(show == 'unavailable') {
			// Offline marker
			jQuery(friend).addClass('jm_offline').removeClass('jm_online jm_hover');
			
			// Hide the friend just to be safe since the search uses .hide() and .show() which can override the CSS display attribute
			jQuery(friend).hide();
			
			// Disable the chat tools
			if(is_groupchat) {
				jQuery(chat).addClass('jm_disabled').attr('data-init', 'false');
				jQuery(send_input).blur().attr('disabled', true).attr('data-value', _e("Unavailable")).val(_e("Unavailable"));
			}
		} else {
			// Online marker
			jQuery(friend).removeClass('jm_offline').addClass('jm_online');
			
			// Check against search string
			var search = jQuery('#jappix_mini div.jm_roster div.jm_search input.jm_searchbox').val();
			var regex = new RegExp('((^)|( ))' + escapeRegex(search), 'gi');
			var nick = unescape(jQuery(friend).data('nick'));

			if(search && !nick.match(regex))
				jQuery(friend).hide();
			else
				jQuery(friend).show();
			
			// Enable the chat input
			if(is_groupchat) {
				jQuery(chat).removeClass('jm_disabled');
				jQuery(send_input).removeAttr('disabled').val('');
			}
		}
		
		// Change the show presence of this buddy
		jQuery(friend + ' span.jm_presence, ' + chat + ' span.jm_presence').attr('class', 'jm_presence jm_images jm_' + show);
		
		// Update the presence counter
		updateRosterMini();
		
		logThis('Presence received from: ' + from);
	} catch(e) {
		logThis('Error on presence handler: ' + e, 1);
	}
}

// Handles the MUC main elements
function handleMUCMini(pr) {
	// We get the xml content
	var xml = pr.getNode();
	var from = fullXID(getStanzaFrom(pr));
	var room = bareXID(from);
	var hash = hex_md5(room);
	var resource = thisResource(from);
	
	// Is it a valid server presence?
	var valid = false;
	
	if(!resource || (resource == unescape(jQuery('#jappix_mini #chat-' + hash + '[data-type="groupchat"]').attr('data-nick'))))
		valid = true;
	
	// Password required?
	if(valid && jQuery(xml).find('error[type="auth"] not-authorized').size()) {
		// Create a new prompt
		openPromptMini(printf(_e("This room (%s) is protected with a password."), room));
		
		// When prompt submitted
		jQuery('#jappix_popup div.jm_prompt form').submit(function() {
			try {
				// Read the value
				var password = closePromptMini();
				
				// Any submitted chat to join?
				if(password) {
					// Send the password
					presenceMini('', '', '', '', from, password, true, handleMUCMini);
					
					// Focus on the pane again
					switchPaneMini('chat-' + hash, hash);
				}
			}
			
			catch(e) {}
			
			finally {
				return false;
			}
		});
		
		return;
	}
	
	// Nickname conflict?
	else if(valid && jQuery(xml).find('error[type="cancel"] conflict').size()) {
		// New nickname
		var nickname = resource + '_';
		
		// Send the new presence
		presenceMini('', '', '', '', room + '/' + nickname, '', true, handleMUCMini);
		
		// Update the nickname marker
		jQuery('#jappix_mini #chat-' + hash).attr('data-nick', escape(nickname));
	}
	
	// Handle normal presence
	else {
		// Start the initial timer
		jQuery('#jappix_mini #chat-' + hash).oneTime('10s', function() {
			jQuery(this).attr('data-init', 'true');
		});

		// Trigger presence handler
		handlePresenceMini(pr);
	}
}

// Updates the user presence
function presenceMini(type, show, priority, status, to, password, limit_history, handler) {
	var pr = new JSJaCPresence();
	
	// Add the attributes
	if(to)
		pr.setTo(to);
	if(type)
		pr.setType(type);
	if(show)
		pr.setShow(show);
	if(status)
		pr.setStatus(status);

	if(priority)
		pr.setPriority(priority);
	else if(MINI_PRIORITY && !to)
		pr.setPriority(MINI_PRIORITY);

	// Special presence elements
	if(password || limit_history) {
		var x = pr.appendNode('x', {'xmlns': NS_MUC});
		
		// Any password?
		if(password)
			x.appendChild(pr.buildNode('password', {'xmlns': NS_MUC}, password));
		
		// Any history limit?
		if(limit_history)
			x.appendChild(pr.buildNode('history', {'maxstanzas': 10, 'seconds': 86400, 'xmlns': NS_MUC}));
	}
	
	// Send the packet
	if(handler)
		con.send(pr, handler);
	else
		con.send(pr);
	
	// No type?
	if(!type)
		type = 'available';
	
	logThis('Presence sent: ' + type, 3);
}

// Sends a given message
function sendMessageMini(aForm) {
	try {
		var body = trim(aForm.body.value);
		var xid = aForm.xid.value;
		var type = aForm.type.value;
		var hash = hex_md5(xid);
		
		if(body && xid) {
			// Send the message
			var aMsg = new JSJaCMessage();
			
			// If the roster does not give us any nick the user may have send us a nickname to use with his first message
            // @see http://xmpp.org/extensions/xep-0172.html
            var known_roster_entry = jQuery('#jappix_mini a.jm_friend[data-xid="' + xid + '"]');
            
			if(known_roster_entry.size() == 0) {
		        var subscription = known_roster_entry.attr('data-sub');
		        
		        // The other may not know my nickname if we do not have both a roster entry, or if he doesn't have one
		        if(('both' != subscription) && ('from' != subscription))
	                aMsg.setNick(MINI_NICKNAME);
			}
			
			// Message data
			aMsg.setTo(xid);
			aMsg.setType(type);
			aMsg.setBody(body);
			
			// Chatstate
			aMsg.appendNode('active', {'xmlns': NS_CHATSTATES});
			
			con.send(aMsg);
			
			// Clear the input
			aForm.body.value = '';
			
			// Display the message we sent
			if(type != 'groupchat')
				displayMessageMini(type, body, getXID(), 'me', hash, getCompleteTime(), getTimeStamp(), 'user-message');
			
			logThis('Message (' + type + ') sent to: ' + xid);
		}
	}
	
	catch(e) {}
	
	finally {
		return false;
	}
}

// Generates the asked smiley image
function smileyMini(image, text) {
	return ' <img class="jm_smiley jm_smiley-' + image + ' jm_images" alt="' + encodeQuotes(text) + '" src="' + JAPPIX_STATIC + 'img/others/blank.gif' + '" /> ';
}

// Notifies incoming chat messages
function notifyMessageMini(hash) {
	// Define the paths
	var tab = '#jappix_mini #chat-' + hash + ' a.jm_chat-tab';
	var notify = tab + ' span.jm_notify';
	var notify_middle = notify + ' span.jm_notify_middle';
	
	// Notification box not yet added?
	if(!exists(notify))
		jQuery(tab).append(
			'<span class="jm_notify">' + 
				'<span class="jm_notify_left jm_images"></span>' + 
				'<span class="jm_notify_middle">0</span>' + 
				'<span class="jm_notify_right jm_images"></span>' + 
			'</span>'
		);
	
	// Increment the notification number
	var number = parseInt(jQuery(notify_middle).text());
	jQuery(notify_middle).text(number + 1);
	
	// Update the notification counters
	notifyCountersMini();
}

// Notifies the user from a session error
function notifyErrorMini() {
	// Replace the Jappix Mini DOM content
	jQuery('#jappix_mini').html(
		'<div class="jm_starter">' + 
			'<a class="jm_pane jm_button jm_images" href="' + MINI_ERROR_LINK + '" target="_blank" title="' + _e("Click here to solve the error") + '">' + 
				'<span class="jm_counter jm_error jm_images">' + _e("Error") + '</span>' + 
			'</a>' + 
		'</div>'
	);
}

// Updates the global counter with the new notifications
function notifyCountersMini() {
	// Count the number of notifications
	var number = 0;
	
	jQuery('#jappix_mini span.jm_notify span.jm_notify_middle').each(function() {
		number = number + parseInt(jQuery(this).text());
	});
	
	// Update the notification link counters
	jQuery('#jappix_mini a.jm_switch').removeClass('jm_notifnav');
	
	if(number) {
		// Left?
		if(jQuery('#jappix_mini div.jm_conversation:visible:first').prevAll().find('span.jm_notify').size())
			jQuery('#jappix_mini a.jm_switch.jm_left').addClass('jm_notifnav');
		
		// Right?
		if(jQuery('#jappix_mini div.jm_conversation:visible:last').nextAll().find('span.jm_notify').size())
			jQuery('#jappix_mini a.jm_switch.jm_right').addClass('jm_notifnav');
	}
	
	// No saved title? Abort!
	if(MINI_TITLE == null)
		return;
	
	// Page title code
	var title = MINI_TITLE;
	
	// No new stuffs? Reset the title!
	if(number)
		title = '[' + number + '] ' + title;
	
	// Apply the title
	document.title = title;
	
	return;
}

// Clears the notifications
function clearNotificationsMini(hash) {
	// Not focused?
	if(!isFocused())
		return false;
	
	// Remove the notifications counter
	jQuery('#jappix_mini #chat-' + hash + ' span.jm_notify').remove();
	
	// Update the global counters
	notifyCountersMini();
	
	return true;
}

// Updates the roster counter
function updateRosterMini() {
	// Update online counter
	jQuery('#jappix_mini a.jm_button span.jm_counter').text(jQuery('#jappix_mini a.jm_online').size());
}

// Updates the chat overflow
function updateOverflowMini() {
	// Process overflow
	var number_visible = parseInt((jQuery(window).width() - 380) / 140);
	var number_visible_dom = jQuery('#jappix_mini div.jm_conversation:visible').size();

	if(number_visible <= 0)
		number_visible = 1;

	// Need to reprocess?
	if(number_visible != number_visible_dom) {
		// Show hidden chats
		jQuery('#jappix_mini div.jm_conversation:hidden').show();
		
		// Get total number of chats
		var number_total = jQuery('#jappix_mini div.jm_conversation').size();
		
		// Must add the overflow switcher?
		if(number_visible < number_total) {
			// Create the overflow handler?
			if(!jQuery('#jappix_mini a.jm_switch').size()) {
				// Add the navigation links
				jQuery('#jappix_mini div.jm_conversations').before(
					'<a class="jm_switch jm_left jm_pane jm_images" href="#">' + 
						'<span class="jm_navigation jm_images"></span>' + 
					'</a>'
				);
				
				jQuery('#jappix_mini div.jm_conversations').after(
					'<a class="jm_switch jm_right jm_pane jm_images" href="#">' + 
						'<span class="jm_navigation jm_images"></span>' + 
					'</a>'
				);
				
				// Add the click events
				overflowEventsMini();
			}
			
			// Show first visible chats
			var first_visible = jQuery('#jappix_mini div.jm_conversation:visible:first').index();
			var index_visible = number_visible - first_visible - 1;

			jQuery('#jappix_mini div.jm_conversation:visible:gt(' + index_visible + ')').hide();
			
			// Close the opened chat
			if(jQuery('#jappix_mini div.jm_conversation:hidden a.jm_pane.jm_clicked').size())
				switchPaneMini();
			
			// Update navigation buttons
			jQuery('#jappix_mini a.jm_switch').removeClass('jm_nonav');
			
			if(!jQuery('#jappix_mini div.jm_conversation:visible:first').prev().size())
				jQuery('#jappix_mini a.jm_switch.jm_left').addClass('jm_nonav');
			if(!jQuery('#jappix_mini div.jm_conversation:visible:last').next().size())
				jQuery('#jappix_mini a.jm_switch.jm_right').addClass('jm_nonav');
		}
		
		// Must remove the overflow switcher?
		else {
			jQuery('#jappix_mini a.jm_switch').remove();
			jQuery('#jappix_mini div.jm_conversation:hidden').show();
		}
	}
}

// Click events on the chat overflow
function overflowEventsMini() {
	jQuery('#jappix_mini a.jm_switch').click(function() {
		// Nothing to do?
		if(jQuery(this).hasClass('jm_nonav'))
			return false;
		
		var hide_this = show_this = '';
		
		// Go left?
		if(jQuery(this).is('.jm_left')) {
			show_this = jQuery('#jappix_mini div.jm_conversation:visible:first').prev();
			
			if(show_this.size())
				hide_this = jQuery('#jappix_mini div.jm_conversation:visible:last');
		}
		
		// Go right?
		else {
			show_this = jQuery('#jappix_mini div.jm_conversation:visible:last').next();
			
			if(show_this.size())
				hide_this = jQuery('#jappix_mini div.jm_conversation:visible:first');
		}
		
		// Update overflow content
		if(show_this && show_this.size()) {
			// Hide
			if(hide_this && hide_this.size()) {
				hide_this.hide();
				
				// Close the opened chat
				if(hide_this.find('a.jm_pane').hasClass('jm_clicked'))
					switchPaneMini();
			}
			
			// Show
			show_this.show();
			
			// Update navigation buttons
			jQuery('#jappix_mini a.jm_switch').removeClass('jm_nonav');
			
			if((jQuery(this).is('.jm_right') && !show_this.next().size()) || (jQuery(this).is('.jm_left') && !show_this.prev().size()))
				jQuery(this).addClass('jm_nonav');
			
			// Update notification counters
			notifyCountersMini();
		}
		
		return false;
	});
}

// Creates the Jappix Mini DOM content
function createMini(domain, user, password) {
	// Try to restore the DOM
    var dom = getDB('jappix-mini', 'dom');
    var stamp = parseInt(getDB('jappix-mini', 'stamp'));
	var suspended = false;
	
	// Invalid stored DOM?
	if(dom && isNaN(jQuery(dom).find('a.jm_pane.jm_button span.jm_counter').text()))
		dom = null;
	
	// Can resume a session?
	con = new JSJaCHttpBindingConnection();
	setupConMini(con);
	
	// Old DOM?
	if(dom && ((getTimeStamp() - stamp) < JSJACHBC_MAX_WAIT) && con.resume()) {
		// Read the old nickname
		MINI_NICKNAME = getDB('jappix-mini', 'nickname');
		
		// Marker
		suspended = true;
	}
	
	// New DOM?
	else {
		dom = '<div class="jm_position">' + 
				'<div class="jm_conversations"></div>' + 
				
				'<div class="jm_starter">' + 
					'<div class="jm_roster">' + 
						'<div class="jm_actions">' + 
							'<a class="jm_logo jm_images" href="https://mini.jappix.com/" target="_blank"></a>' + 
							'<a class="jm_one-action jm_join jm_images" title="' + _e("Join a chat") + '" href="#"></a>' + 
							'<a class="jm_one-action jm_status" title="' + _e("Status") + '" href="#">' + 
								'<span class="jm_presence jm_images jm_available"></span>' + 
							'</a>' + 
							
							'<div class="jm_status_picker">' + 
								'<a href="#" data-status="available">' + 
									'<span class="jm_presence jm_images jm_available"></span>' + 
									'<span class="jm_show_text">' + _e("Available") + '</span>' + 
								'</a>' + 
								
								'<a href="#" data-status="away">' + 
									'<span class="jm_presence jm_images jm_away"></span>' + 
									'<span class="jm_show_text">' + _e("Away") + '</span>' + 
								'</a>' + 
								
								'<a href="#" data-status="dnd">' + 
									'<span class="jm_presence jm_images jm_dnd"></span>' + 
									'<span class="jm_show_text">' + _e("Busy") + '</span>' + 
								'</a>' + 
								
								'<a href="#" data-status="unavailable">' + 
									'<span class="jm_presence jm_images jm_unavailable"></span>' + 
									'<span class="jm_show_text">' + _e("Offline") + '</span>' + 
								'</a>' + 
							'</div>' + 
						'</div>' + 
						'<div class="jm_buddies"></div>' + 
						'<div class="jm_search">' + 
							'<input type="text" class="jm_searchbox jm_images" placeholder="' + _e("Filter") + '" data-value="" />' + 
						'</div>' + 
					'</div>' + 
					
					'<a class="jm_pane jm_button jm_images" href="#">' + 
						'<span class="jm_counter jm_images">' + _e("Please wait...") + '</span>' + 
					'</a>' + 
				'</div>' + 
			  '</div>';
	}
	
	// Create the DOM
	jQuery('body').append('<div id="jappix_mini" style="display: none;" dir="' + (isRTL() ? 'rtl' : 'ltr') + '">' + dom + '</div>');
	
	// Hide the roster picker panels
	jQuery('#jappix_mini a.jm_status.active, #jappix_mini a.jm_join.active').removeClass('active');
	jQuery('#jappix_mini div.jm_status_picker').hide();
	jQuery('#jappix_mini div.jm_chan_suggest').remove();
	
	// Chat navigation overflow events
	overflowEventsMini();

	// Delay to fix DOM lag bug (CSS file not yet loaded)
	jQuery('#jappix_mini').everyTime(10, function() {
		if(jQuery(this).is(':visible')) {
			logThis('CSS loaded asynchronously.', 3);

			jQuery(this).stopTime();

			// Re-process chat overflow
			updateOverflowMini();

			// Adapt roster height
			adaptRosterMini();
		}
	});
	
	// CSS refresh (Safari display bug when restoring old DOM)
	jQuery('#jappix_mini div.jm_starter').css('float', 'right');
	jQuery('#jappix_mini div.jm_conversations, #jappix_mini div.jm_conversation, #jappix_mini a.jm_switch').css('float', 'left');
	
	// The click events
	jQuery('#jappix_mini a.jm_button').click(function() {
		// Using a try/catch override IE issues
		try {
			// Presence counter
			var counter = '#jappix_mini a.jm_pane.jm_button span.jm_counter';
			
			// Cannot open the roster?
			if(jQuery(counter).text() == _e("Please wait..."))
				return false;
			
			// Not yet connected?
			if(jQuery(counter).text() == _e("Chat")) {
				// Remove the animated bubble
				jQuery('#jappix_mini div.jm_starter span.jm_animate').remove();
				
				// Add a waiting marker
				jQuery(counter).text(_e("Please wait..."));
				
				// Launch the connection!
				connectMini(domain, user, password);
				
				return false;
			}
			
			// Normal actions
			if(!jQuery(this).hasClass('jm_clicked'))
				showRosterMini();
			else
				hideRosterMini();
		}
		
		catch(e) {}
		
		finally {
			return false;
		}
	});

	jQuery('#jappix_mini a.jm_status').click(function() {
		// Using a try/catch override IE issues
		try {
			var is_active = jQuery(this).hasClass('active');
			jQuery('#jappix_mini div.jm_actions a').blur().removeClass('active');
			
			if(is_active) {
				jQuery('#jappix_mini div.jm_status_picker').hide();
			} else {
				jQuery('#jappix_mini div.jm_chan_suggest').remove();
				jQuery('#jappix_mini div.jm_status_picker').show();
				jQuery(this).addClass('active');
			}
		}
		
		catch(e) {}
		
		finally {
			return false;
		}
	});

	jQuery('#jappix_mini div.jm_status_picker a').click(function() {
		// Using a try/catch override IE issues
		try {
			// Generate an array of presence change XIDs
			var pr_xid = [''];
			
			jQuery('#jappix_mini div.jm_conversation[data-type="groupchat"]').each(function() {
				pr_xid.push(jQuery(this).attr('data-xid'));
			});
			
			// Loop on XIDs
			var new_status = jQuery(this).data('status');
			
			jQuery.each(pr_xid, function(key, value) {
				switch(new_status) {
					case 'available':
						presenceMini('', '', '', '', value);
						break;
					
					case 'away':
						presenceMini('', 'away', '', '', value);
						break;
					
					case 'dnd':
						presenceMini('', 'dnd', '', '', value);
						break;
					
					case 'unavailable':
						disconnectMini();
						break;
					
					default:
						presenceMini('', '', '', '', value);
						break;
				}
			});
			
			// Switch the status
			if(new_status != 'unavailable') {
				jQuery('#jappix_mini a.jm_status span').removeClass('jm_available jm_away jm_dnd jm_unavailable')
				                                       .addClass('jm_' + jQuery(this).data('status'));
				
				jQuery('#jappix_mini div.jm_status_picker').hide();
				jQuery('#jappix_mini a.jm_status').blur().removeClass('active');
			}
		}
		
		catch(e) {}
		
		finally {
			return false;
		}
	});
	
	jQuery('#jappix_mini div.jm_actions a.jm_join').click(function() {
		// Using a try/catch override IE issues
		try {
			// Any suggested chat/groupchat?
			if((MINI_SUGGEST_CHATS && MINI_SUGGEST_CHATS.length) || (MINI_SUGGEST_GROUPCHATS && MINI_SUGGEST_GROUPCHATS.length)) {
				var is_active = jQuery(this).hasClass('active');
				jQuery('#jappix_mini div.jm_actions a').blur().removeClass('active');
				
				if(is_active) {
					jQuery('#jappix_mini div.jm_chan_suggest').remove();
				} else {
					// Button style
					jQuery('#jappix_mini div.jm_status_picker').hide();
					jQuery(this).addClass('active');
					
					// Generate selector code
					var chans_html = '';
					
					// Generate the groupchat links HTML
					for(var i = 0; i < MINI_SUGGEST_GROUPCHATS.length; i++) {
						// Empty value?
						if(!MINI_SUGGEST_GROUPCHATS[i])
							continue;
						
						// Using a try/catch override IE issues
						try {
							var chat_room = bareXID(generateXID(MINI_SUGGEST_GROUPCHATS[i], 'groupchat'));
							var chat_pwd = MINI_SUGGEST_PASSWORDS[i] || '';
							
							chans_html += '<a class="jm_suggest_groupchat" href="#" data-xid="' + escape(chat_room) + '" data-pwd="' + escape(chat_pwd) + '">' + 
								'<span class="jm_chan_icon jm_images"></span>' + 
								'<span class="jm_chan_name">' + getXIDNick(chat_room).htmlEnc() + '</span>' + 
							'</a>';
						}
						
						catch(e) {}
					}
					
					// Any separation space to add?
					if(chans_html)
						chans_html += '<div class="jm_space"></div>';
					
					// Generate the chat links HTML
					for(var j = 0; j < MINI_SUGGEST_CHATS.length; j++) {
						// Empty value?
						if(!MINI_SUGGEST_CHATS[j])
							continue;
						
						// Using a try/catch override IE issues
						try {
							// Read current chat values
							var chat_xid = bareXID(generateXID(MINI_SUGGEST_CHATS[j], 'chat'));
							var chat_hash = hex_md5(chat_xid);
							var chat_nick = jQuery('#jappix_mini a#friend-' + chat_hash).attr('data-nick');
							
							// Get current chat nickname
							if(!chat_nick)
								chat_nick = getXIDNick(chat_xid);
							else
								chat_nick = unescape(chat_nick);
							
							// Generate HTML for current chat
							chans_html += '<a class="jm_suggest_chat" href="#" data-xid="' + escape(chat_xid) + '">' + 
								'<span class="jm_chan_icon jm_images"></span>' + 
								'<span class="jm_chan_name">' + getXIDNick(chat_nick).htmlEnc() + '</span>' + 
							'</a>';
						}
						
						catch(e) {}
					}
					
					// Any separation space to add?
					if(chans_html)
						chans_html += '<div class="jm_space"></div>';
					
					// Append selector code
					jQuery('#jappix_mini div.jm_actions').append(
						'<div class="jm_chan_suggest">' + 
							chans_html + 
							
							'<a class="jm_suggest_prompt" href="#">' + 
								'<span class="jm_chan_icon"></span>' + 
								'<span class="jm_chan_name">' + _e("Other") + '</span>' + 
							'</a>' + 
						'</div>'
					);
					
					// Click events
					jQuery('#jappix_mini div.jm_chan_suggest a').click(function() {
						// Using a try/catch override IE issues
						try {
							// Chat?
							if(jQuery(this).is('.jm_suggest_chat')) {
								var current_chat = unescape(jQuery(this).attr('data-xid'));
								
								chatMini('chat', current_chat, jQuery(this).find('span.jm_chan_name').text(), hex_md5(current_chat));
							}
							
							// Groupchat?
							else if(jQuery(this).is('.jm_suggest_groupchat')) {
								var current_groupchat = unescape(jQuery(this).attr('data-xid'));
								var current_password = jQuery(this).attr('data-pwd') || null;
								
								if(current_password)
									current_password = unescape(current_password);
								
								chatMini('groupchat', current_groupchat, jQuery(this).find('span.jm_chan_name').text(), hex_md5(current_groupchat), current_password);
							}
							
							// Default prompt?
							else
								groupchatPromptMini();
						}
						
						catch(e) {}
						
						finally {
							return false;
						}
					});
					
					// Adapt chan suggest height
					adaptRosterMini();
				}
			}
			
			// Default action
			else
				groupchatPromptMini();
		}
		
		catch(e) {}
		
		finally {
			return false;
		}
	});
	
	// Updates the roster with only searched terms
	jQuery('#jappix_mini div.jm_roster div.jm_search input.jm_searchbox').keyup(function(e) {
		// Avoid buddy navigation to be reseted
		if((e.keyCode == 38) || (e.keyCode == 40))
			return;
		
		// Escape key pressed?
		if(e.keyCode == 27)
			jQuery(this).val('');
		
		// Save current value
		jQuery(this).attr('data-value', jQuery(this).val());
		
		// Don't filter at each key up (faster for computer)
		var self = this;
		
		typewatch(function() {
			// Using a try/catch to override IE issues
			try {
				// Get values
				var search = jQuery(self).val();
				var regex = new RegExp('((^)|( ))' + escapeRegex(search), 'gi');
				
				// Reset results
				jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover');
				jQuery('#jappix_mini div.jm_roster div.jm_grouped').show();
				
				// If there is no search, we don't need to loop over buddies
				if(!search) {
					jQuery('#jappix_mini div.jm_roster div.jm_buddies a.jm_online').show();
					
					return;
				}
				
				// Filter buddies
				jQuery('#jappix_mini div.jm_roster div.jm_buddies a.jm_online').each(function() {
					var nick = unescape(jQuery(this).data('nick'));
					
					if(nick.match(regex))
						jQuery(this).show();
					else
						jQuery(this).hide();
				});
				
				// Filter groups
				jQuery('#jappix_mini div.jm_roster div.jm_grouped').each(function() {
					if(!jQuery(this).find('a.jm_online:visible').size())
						jQuery(this).hide();
				});
				
				// Focus on the first buddy
				jQuery('#jappix_mini div.jm_roster div.jm_buddies a.jm_online:visible:first').addClass('jm_hover');
			}
			
			catch(e) {}
			
			finally {
				return false;
			}
		}, 500);
	});
	
	// Roster navigation
	jQuery(document).keydown(function(e) {
		// Cannot work if roster is not opened
		if(jQuery('#jappix_mini div.jm_roster').is(':hidden'))
			return;
		
		// Up/Down keys
		if((e.keyCode == 38) || (e.keyCode == 40)) {
			// Hover the last/first buddy
			if(!jQuery('#jappix_mini a.jm_online.jm_hover').size()) {
				if(e.keyCode == 38)
					jQuery('#jappix_mini a.jm_online:visible:last').addClass('jm_hover');
				else
					jQuery('#jappix_mini a.jm_online:visible:first').addClass('jm_hover');
			}
			
			// Hover the previous/next buddy
			else if(jQuery('#jappix_mini a.jm_online:visible').size() > 1) {
				var hover_index = jQuery('#jappix_mini a.jm_online:visible').index(jQuery('a.jm_hover'));
				
				// Up (decrement) or down (increment)?
				if(e.keyCode == 38)
					hover_index--;
				else
					hover_index++;
				
				if(!hover_index)
					hover_index = 0;
				
				// No buddy before/after?
				if(!jQuery('#jappix_mini a.jm_online:visible').eq(hover_index).size()) {
					if(e.keyCode == 38)
						hover_index = jQuery('#jappix_mini a.jm_online:visible:last').index();
					else
						hover_index = 0;
				}
				
				// Hover the previous/next buddy
				jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover');
				jQuery('#jappix_mini a.jm_online:visible').eq(hover_index).addClass('jm_hover');
			}
			
			// Scroll to the hovered buddy (if out of limits)
			jQuery('#jappix_mini div.jm_roster div.jm_buddies').scrollTo(jQuery('#jappix_mini a.jm_online.jm_hover'), 0, {margin: true});
			
			return false;
		}
		
		// Enter key
		if((e.keyCode == 13) && jQuery('#jappix_mini a.jm_friend.jm_hover').size()) {
			jQuery('#jappix_mini a.jm_friend.jm_hover').click();
			
			return false;
		}
	});

	// Chat type re-focus
	jQuery(document).keypress(function(e) {
		// Cannot work if an input/textarea is already focused or chat is not opened
		var path = '#jappix_mini div.jm_conversation div.jm_chat-content';

		if(jQuery('input, textarea').is(':focus') || !jQuery(path).is(':visible'))
			return;

		// May cause some compatibility issues
		try {
			// Get key value
			var key_value = trim(String.fromCharCode(e.which));
			
			// Re-focus on opened chat?
			if(key_value) {
				// Path to chat input
				var path_input = path + ' input.jm_send-messages';

				// Use a timer to override the DOM lag issue
				jQuery(document).oneTime(10, function() {
					// Get input values
					select_input = jQuery(path_input);
					value_input = select_input.val();

					// Append pressed key value
					select_input.val(value_input + key_value);
					select_input.focus();

					// Put cursor at the end of input
					select_input[0].selectionStart = select_input[0].selectionEnd = value_input.length + 1;
				});
			}
		} catch(e) {}
	});
	
	// Hides the roster when clicking away of Jappix Mini
	jQuery(document).click(function(evt) {
		if(!jQuery(evt.target).parents('#jappix_mini').size() && !exists('#jappix_popup'))
			hideRosterMini();
	});
	
	// Hides all panes double clicking away of Jappix Mini
	jQuery(document).dblclick(function(evt) {
		if(!jQuery(evt.target).parents('#jappix_mini').size() && !exists('#jappix_popup'))
			switchPaneMini();
	});
	
	// Suspended session resumed?
	if(suspended) {
		// Initialized marker
		MINI_INITIALIZED = true;
		
		// Restore chat input values
		jQuery('#jappix_mini div.jm_conversation input.jm_send-messages').each(function() {
			var chat_value = jQuery(this).attr('data-value');
			
			if(chat_value)
				jQuery(this).val(chat_value);
		});
		
		// Restore roster filter value
		var search_box = jQuery('#jappix_mini div.jm_roster div.jm_search input.jm_searchbox');
		var filter_value = search_box.attr('data-value');
		
		if(filter_value)
			search_box.val(filter_value).keyup();
		
		// Restore buddy click events
		jQuery('#jappix_mini a.jm_friend').click(function() {
			// Using a try/catch override IE issues
			try {
				chatMini('chat', unescape(jQuery(this).attr('data-xid')), unescape(jQuery(this).attr('data-nick')), jQuery(this).attr('data-hash'));
			}
			
			catch(e) {}
			
			finally {
				return false;
			}
		});
		
		// Restore buddy hover events
		jQuery('#jappix_mini a.jm_friend').hover(function() {
			jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover');			jQuery(this).addClass('jm_hover');
		}, function() {
			jQuery(this).removeClass('jm_hover');
		});
		
		// Restore buddy mousedown events
		jQuery('#jappix_mini a.jm_friend').mousedown(function() {
			jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover');
			jQuery(this).addClass('jm_hover');
		});
		
		// Restore buddy focus events
		jQuery('#jappix_mini a.jm_friend').focus(function() {
			jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover');
			jQuery(this).addClass('jm_hover');
		});
		
		// Restore buddy blur events
		jQuery('#jappix_mini a.jm_friend').blur(function() {
			jQuery(this).removeClass('jm_hover');
		});
		
		// Restore chat click events
		jQuery('#jappix_mini div.jm_conversation').each(function() {
			chatEventsMini(jQuery(this).attr('data-type'), unescape(jQuery(this).attr('data-xid')), jQuery(this).attr('data-hash'));
		});

		// Restore init marker on all groupchats
		jQuery('#jappix_mini div.jm_conversation[data-type="groupchat"]').attr('data-init', 'true');
		
		// Scroll down to the last message
		var scroll_hash = jQuery('#jappix_mini div.jm_conversation:has(a.jm_pane.jm_clicked)').attr('data-hash');
		var scroll_position = getDB('jappix-mini', 'scroll');
		
		// Any scroll position?
		if(scroll_position)
			scroll_position = parseInt(scroll_position);
		
		if(scroll_hash) {
			// Use a timer to override the DOM lag issue
			jQuery(document).oneTime(200, function() {
				messageScrollMini(scroll_hash, scroll_position);
			});
		}
		
		// Update notification counters
		notifyCountersMini();
	}
	
	// Can auto-connect?
	else if(MINI_AUTOCONNECT)
		connectMini(domain, user, password);
	
	// Cannot auto-connect?
	else {
		// Chat text
		jQuery('#jappix_mini a.jm_pane.jm_button span.jm_counter').text(_e("Chat"));
		
		// Must animate?
		if(MINI_ANIMATE) {
			// Add content
			jQuery('#jappix_mini div.jm_starter').prepend(
				'<span class="jm_animate jm_images_animate"></span>'
			);
		}
	}
}

// Displays a given message
function displayMessageMini(type, body, xid, nick, hash, time, stamp, message_type) {
	// Generate path
	var path = '#chat-' + hash;
	
	// Can scroll?
	var cont_scroll = document.getElementById('received-' + hash);
	var can_scroll = false;
	
	if(!cont_scroll.scrollTop || ((cont_scroll.clientHeight + cont_scroll.scrollTop) == cont_scroll.scrollHeight))
		can_scroll = true;
	
	// Remove the previous message border if needed
	var last = jQuery(path + ' div.jm_group:last');
	var last_stamp = parseInt(last.attr('data-stamp'));
	var last_b = jQuery(path + ' b:last');
	var last_xid = last_b.attr('data-xid');
	var last_type = last.attr('data-type');
	var grouped = false;
	var header = '';
	
	if((last_xid == xid) && (message_type == last_type) && ((stamp - last_stamp) <= 1800))
		grouped = true;
	
	else {
		// Write the message date
		if(nick)
			header += '<span class="jm_date">' + time + '</span>';
		
		// Write the buddy name at the top of the message group
		if(type == 'groupchat')
			header += '<b class="jm_name" style="color: ' + generateColor(nick) + ';" data-xid="' + encodeQuotes(xid) + '">' + nick.htmlEnc() + '</b>';
		else if(nick == 'me')
			header += '<b class="jm_name jm_me" data-xid="' + encodeQuotes(xid) + '">' + _e("You") + '</b>';
		else
			header += '<b class="jm_name jm_him" data-xid="' + encodeQuotes(xid) + '">' + nick.htmlEnc() + '</b>';
	}
	
	// Apply the /me command
	var me_command = false;
	
	if(body.match(/^\/me /i)) {
		body = body.replace(/^\/me /i, nick + ' ');
		
		// Marker
		me_command = true;
	}
	
	// HTML-encode the message
	body = body.htmlEnc();
	
	// Apply the smileys
	body = body.replace(/(;-?\))(\s|$)/gi, smileyMini('wink', '$1'))
	           .replace(/(:-?3)(\s|$)/gi, smileyMini('waii', '$1'))
	           .replace(/(:-?\()(\s|$)/gi, smileyMini('unhappy', '$1'))
	           .replace(/(:-?P)(\s|$)/gi, smileyMini('tongue', '$1'))
	           .replace(/(:-?O)(\s|$)/gi, smileyMini('surprised', '$1'))
	           .replace(/(:-?\))(\s|$)/gi, smileyMini('smile', '$1'))
	           .replace(/(\^_?\^)(\s|$)/gi, smileyMini('happy', '$1'))
	           .replace(/(:-?D)(\s|$)/gi, smileyMini('grin', '$1'));
	
	// Format the text
	body = body.replace(/(^|\s|>|\()((\*)([^<>'"\*]+)(\*))($|\s|<|\))/gi, '$1<b>$2</b>$6')
	           .replace(/(^|\s|>|\()((\/)([^<>'"\/]+)(\/))($|\s|<|\))/gi, '$1<em>$2</em>$6')
	           .replace(/(^|\s|>|\()((_)([^<>'"_]+)(_))($|\s|<|\))/gi, '$1<span style="text-decoration: underline;">$2</span>$6');
	
	// Filter the links
	body = applyLinks(body, 'mini');
	
	// Generate the message code
	if(me_command)
		body = '<em>' + body + '</em>';
	
	body = '<p>' + body + '</p>';
	
	// Create the message
	if(grouped)
		jQuery('#jappix_mini #chat-' + hash + ' div.jm_received-messages div.jm_group:last').append(body);
	else
		jQuery('#jappix_mini #chat-' + hash + ' div.jm_chatstate_typing').before('<div class="jm_group jm_' + message_type + '" data-type="' + message_type + '" data-stamp="' + stamp + '">' + header + body + '</div>');
	
	// Scroll to this message
	if(can_scroll)
		messageScrollMini(hash);
}

// Switches to a given point
function switchPaneMini(element, hash) {
	// Hide every item
	hideRosterMini();
	jQuery('#jappix_mini a.jm_pane').removeClass('jm_clicked');
	jQuery('#jappix_mini div.jm_chat-content').hide();
	
	// Show the asked element
	if(element && (element != 'roster')) {
		var current = '#jappix_mini #' + element;
		
		// Navigate to this chat
		if(jQuery(current).size() && jQuery(current).is(':hidden')) {
			var click_nav = '';
			
			// Before or after?
			if(jQuery('#jappix_mini div.jm_conversation:visible:first').prevAll().is('#' + element))
				click_nav = jQuery('#jappix_mini a.jm_switch.jm_left');
			else
				click_nav = jQuery('#jappix_mini a.jm_switch.jm_right');
			
			// Click previous or next
			if(click_nav) {
				while(jQuery(current).is(':hidden') && !click_nav.hasClass('jm_nonav'))
					click_nav.click();
			}
		}
		
		// Show it
		jQuery(current + ' a.jm_pane').addClass('jm_clicked');
		jQuery(current + ' div.jm_chat-content').show();
		
		// Use a timer to override the DOM lag issue
		jQuery(document).oneTime(10, function() {
			jQuery(current + ' input.jm_send-messages').focus();
		});
		
		// Scroll to the last message
		if(hash)
			messageScrollMini(hash);
	}
}

// Scrolls to the last chat message
function messageScrollMini(hash, position) {
	var id = document.getElementById('received-' + hash);
	
	// No defined position?
	if(!position)
		position = id.scrollHeight;
	
	id.scrollTop = position;
}

// Prompts the user with a given text
function openPromptMini(text, value) {
	// Initialize
	var prompt = '#jappix_popup div.jm_prompt';
	var input = prompt + ' form input';
	var value_input = input + '[type="text"]';
	
	// Remove the existing prompt
	closePromptMini();
	
	// Add the prompt
	jQuery('body').append(
		'<div id="jappix_popup" dir="' + (isRTL() ? 'rtl' : 'ltr') + '">' + 
			'<div class="jm_prompt">' + 
				'<form>' + 
					text + 
					'<input class="jm_text" type="text" value="" />' + 
					'<input class="jm_submit" type="submit" value="' + _e("Submit") + '" />' + 
					'<input class="jm_submit" type="reset" value="' + _e("Cancel") + '" />' + 
					'<div class="jm_clear"></div>' + 
				'</form>' + 
			'</div>' + 
		'</div>'
	);
	
	// Vertical center
	var vert_pos = '-' + ((jQuery(prompt).height() / 2) + 10) + 'px';
	jQuery(prompt).css('margin-top', vert_pos);
	
	// Apply the value?
	if(value)
		jQuery(value_input).val(value);
	
	// Focus on the input
	jQuery(document).oneTime(10, function() {
		jQuery(value_input).focus();
	});
	
	// Cancel event
	jQuery(input + '[type="reset"]').click(function() {
		try {
			closePromptMini();
		}
		
		catch(e) {}
		
		finally {
			return false;
		}
	});
}

// Returns the prompt value
function closePromptMini() {
	// Read the value
	var value = jQuery('#jappix_popup div.jm_prompt form input').val();
	
	// Remove the popup
	jQuery('#jappix_popup').remove();
	
	return value;
}

// Opens the new groupchat prompt
function groupchatPromptMini() {
	// Create a new prompt
	openPromptMini(_e("Please enter the group chat address to join."));
	
	// When prompt submitted
	jQuery('#jappix_popup div.jm_prompt form').submit(function() {
		try {
			// Read the value
			var join_this = closePromptMini();
			
			// Any submitted chat to join?
			if(join_this) {
				// Get the chat room to join
				chat_room = bareXID(generateXID(join_this, 'groupchat'));
				
				// Create a new groupchat
				chatMini('groupchat', chat_room, getXIDNick(chat_room), hex_md5(chat_room));
			}
		}
		
		catch(e) {}
		
		finally {
			return false;
		}
	});
}

// Manages and creates a chat
function chatMini(type, xid, nick, hash, pwd, show_pane) {
	var current = '#jappix_mini #chat-' + hash;
	
	// Not yet added?
	if(!exists(current)) {
		// Groupchat nickname
		if(type == 'groupchat') {
			// Random nickname?
			if(!MINI_NICKNAME && MINI_RANDNICK)
				MINI_NICKNAME = randomNickMini();
			
			var nickname = MINI_NICKNAME;
			
			// No nickname?
			if(!nickname) {
				// Create a new prompt
				openPromptMini(printf(_e("Please enter your nickname to join %s."), xid));
				
				// When prompt submitted
				jQuery('#jappix_popup div.jm_prompt form').submit(function() {
					try {
						// Read the value
						var nickname = closePromptMini();
						
						// Update the stored one
						if(nickname)
							MINI_NICKNAME = nickname;
						
						// Launch it again!
						chatMini(type, xid, nick, hash, pwd);
					}
					
					catch(e) {}
					
					finally {
						return false;
					}
				});
				
				return;
			}
		}
		
		// Create the HTML markup
		var html = '<div class="jm_conversation jm_type_' + type + '" id="chat-' + hash + '" data-xid="' + escape(xid) + '" data-type="' + type + '" data-nick="' + escape(nick) + '" data-hash="' + hash + '" data-origin="' + escape(cutResource(xid)) + '">' + 
				'<div class="jm_chat-content">' + 
					'<div class="jm_actions">' + 
						'<span class="jm_nick">' + nick + '</span>';
		
		// Check if the chat/groupchat exists
		var groupchat_exists = false;
		var chat_exists = false;
		
		if((type == 'groupchat') && MINI_GROUPCHATS && MINI_GROUPCHATS.length) {
			for(g in MINI_GROUPCHATS) {
				if(xid == bareXID(generateXID(MINI_GROUPCHATS[g], 'groupchat'))) {
					groupchat_exists = true;
					
					break;
				}
			}
		}
		
		if((type == 'chat') && MINI_CHATS && MINI_CHATS.length) {
			for(c in MINI_CHATS) {
				if(xid == bareXID(generateXID(MINI_CHATS[c], 'chat'))) {
					chat_exists = true;
					
					break;
				}
			}
		}
		
		// Any close button to display?
		if(((type == 'groupchat') && !groupchat_exists) || ((type == 'chat') && !chat_exists) || ((type != 'groupchat') && (type != 'chat')))
			html += '<a class="jm_one-action jm_close jm_images" title="' + _e("Close") + '" href="#"></a>';
		
		html += '</div>' + 
				
				'<div class="jm_received-messages" id="received-' + hash + '">' + 
					'<div class="jm_chatstate_typing">' + printf(_e("%s is typing..."), nick.htmlEnc()) + '</div>' + 
				'</div>' + 
				
				'<form action="#" method="post">' + 
					'<input type="text" class="jm_send-messages" name="body" autocomplete="off" placeholder="' + _e("Chat") + '" data-value="" />' + 
					'<input type="hidden" name="xid" value="' + xid + '" />' + 
					'<input type="hidden" name="type" value="' + type + '" />' + 
				'</form>' + 
			'</div>' + 
			
			'<a class="jm_pane jm_chat-tab jm_images" href="#">' + 
				'<span class="jm_name">' + nick.htmlEnc() + '</span>' + 
			'</a>' + 
		'</div>';
		
		jQuery('#jappix_mini div.jm_conversations').prepend(html);
		
		// Get the presence of this friend
		if(type != 'groupchat') {
			var selector = jQuery('#jappix_mini a#friend-' + hash + ' span.jm_presence');
			
			// Default presence
			var show = 'available';
			
			// Read the presence
			if(selector.hasClass('jm_unavailable') || !selector.size())
				show = 'unavailable';
			else if(selector.hasClass('jm_chat'))
				show = 'chat';
			else if(selector.hasClass('jm_away'))
				show = 'away';
			else if(selector.hasClass('jm_xa'))
				show = 'xa';
			else if(selector.hasClass('jm_dnd'))
				show = 'dnd';
			
			// Create the presence marker
			jQuery(current + ' a.jm_chat-tab').prepend('<span class="jm_presence jm_images jm_' + show + '"></span>');
		}
		
		// The chat events
		chatEventsMini(type, xid, hash);
		
		// Join the groupchat
		if(type == 'groupchat') {
			// Add nickname & init values
			jQuery(current).attr('data-nick', escape(nickname))
			               .attr('data-init', 'false');
			
			// Send the first groupchat presence
			presenceMini('', '', '', '', xid + '/' + nickname, pwd, true, handleMUCMini);
		}
	}
	
	// Focus on our pane
	if(show_pane != false)
		jQuery(document).oneTime(10, function() {
			switchPaneMini('chat-' + hash, hash);
		});
	
	// Update chat overflow
	updateOverflowMini();
	
	return false;
}

// Events on the chat tool
function chatEventsMini(type, xid, hash) {
	var current = '#jappix_mini #chat-' + hash;
	
	// Submit the form
	jQuery(current + ' form').submit(function() {
		return sendMessageMini(this);
	});
	
	// Click on the tab
	jQuery(current + ' a.jm_chat-tab').click(function() {
		// Using a try/catch override IE issues
		try {
			// Not yet opened: open it!
			if(!jQuery(this).hasClass('jm_clicked')) {
				// Show it!
				switchPaneMini('chat-' + hash, hash);
				
				// Clear the eventual notifications
				clearNotificationsMini(hash);
			} else {
				switchPaneMini();
			}
		}
		
		catch(e) {}
		
		finally {
			return false;
		}
	});
	
	// Click on the close button
	jQuery(current + ' div.jm_actions').click(function(e) {
		// Using a try/catch override IE issues
		try {
			// Close button?
			if(jQuery(e.target).is('a.jm_close')) {
				// Gone chatstate
				if(type != 'groupchat')
					sendChatstateMini('gone', xid, hash);
				
				jQuery(current).stopTime().remove();
				
				// Quit the groupchat?
				if(type == 'groupchat') {
					// Send an unavailable presence
					presenceMini('unavailable', '', '', '', xid + '/' + unescape(jQuery(current).attr('data-nick')));
					
					// Remove this groupchat!
					removeGroupchatMini(xid);
				}
				
				// Update chat overflow
				updateOverflowMini();
			} else {
				// Minimize current chat
				jQuery(current + ' a.jm_chat-tab.jm_clicked').click();
			}
		}
		
		catch(e) {}
		
		finally {
			return false;
		}
	});
	
	// Focus on the chat input
	jQuery(current + ' input.jm_send-messages').focus(function() {
		clearNotificationsMini(hash);
	})
	
	// Change on the chat input
	.keyup(function() {
		jQuery(this).attr('data-value', jQuery(this).val());
	})
	
	// Chat tabulate or escape press
	.keydown(function(e) {
		// Tabulate?
		if(e.keyCode == 9) {
			switchChatMini();
			
			return false;
		}
		
		// Escape?
		if(e.keyCode == 27) {
			if(jQuery(current + ' a.jm_close').size()) {
				// Open next/previous chat
				if(jQuery(current).next('div.jm_conversation').size())
					jQuery(current).next('div.jm_conversation').find('a.jm_pane').click();
				else if(jQuery(current).prev('div.jm_conversation').size())
					jQuery(current).prev('div.jm_conversation').find('a.jm_pane').click();
				
				// Close current chat
				jQuery(current + ' a.jm_close').click();
			}
			
			return false;
		}
	});
	
	// Focus/Blur events
	jQuery('#jappix_mini #chat-' + hash + ' input.jm_send-messages').focus(function() {
		// Store active chat
		MINI_ACTIVE = hash;
	})
	
	.blur(function() {
		// Reset active chat
		if(MINI_ACTIVE == hash)
			MINI_ACTIVE = null;
	});
	
	// Chatstate events
	eventsChatstateMini(xid, hash, type);
}

// Opens the next chat
function switchChatMini() {
	if(jQuery('#jappix_mini div.jm_conversation').size() <= 1)
		return;
	
	// Get the current chat index
	var chat_index = jQuery('#jappix_mini div.jm_conversation:has(a.jm_pane.jm_clicked)').index();
	chat_index++;
	
	if(!chat_index)
		chat_index = 0;
	
	// No chat after?
	if(!jQuery('#jappix_mini div.jm_conversation').eq(chat_index).size())
		chat_index = 0;
	
	// Avoid disabled chats
	while(jQuery('#jappix_mini div.jm_conversation').eq(chat_index).hasClass('jm_disabled'))
		chat_index++;
	
	// Show the next chat
	var chat_hash = jQuery('#jappix_mini div.jm_conversation').eq(chat_index).attr('data-hash');
	
	if(chat_hash)
		switchPaneMini('chat-' + chat_hash, chat_hash);
}

// Shows the roster
function showRosterMini() {
	switchPaneMini('roster');
	jQuery('#jappix_mini div.jm_roster').show();
	jQuery('#jappix_mini a.jm_button').addClass('jm_clicked');
	
	jQuery(document).oneTime(10, function() {
		jQuery('#jappix_mini div.jm_roster div.jm_search input.jm_searchbox').focus();
	});
}

// Hides the roster
function hideRosterMini() {
	// Close the roster pickers
	jQuery('#jappix_mini a.jm_status.active, #jappix_mini a.jm_join.active').click();
	
	// Hide the roster box
	jQuery('#jappix_mini div.jm_roster').hide();
	jQuery('#jappix_mini a.jm_button').removeClass('jm_clicked');
	
	// Clear the search box and show all online contacts
	jQuery('#jappix_mini div.jm_roster div.jm_search input.jm_searchbox').val('').attr('data-value', '');
	jQuery('#jappix_mini div.jm_roster div.jm_grouped').show();
	jQuery('#jappix_mini div.jm_roster div.jm_buddies a.jm_online').show();
	jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover');
}

// Removes a groupchat from DOM
function removeGroupchatMini(xid) {
	// Remove the groupchat private chats & the groupchat buddies from the roster
	jQuery('#jappix_mini div.jm_conversation[data-origin="' + escape(cutResource(xid)) + '"], #jappix_mini div.jm_roster div.jm_grouped[data-xid="' + escape(xid) + '"]').remove();
	
	// Update the presence counter
	updateRosterMini();
}

// Initializes Jappix Mini
function initializeMini() {
	// Update the marker
	MINI_INITIALIZED = true;
	
	// Send the initial presence
	if(!MINI_ANONYMOUS)
		presenceMini();
	
	// Join the groupchats (first)
	for(var i = 0; i < MINI_GROUPCHATS.length; i++) {
		// Empty value?
		if(!MINI_GROUPCHATS[i])
			continue;
		
		// Using a try/catch override IE issues
		try {
			// Current chat room
			var chat_room = bareXID(generateXID(MINI_GROUPCHATS[i], 'groupchat'));
			
			// Open the current chat
			chatMini('groupchat', chat_room, getXIDNick(chat_room), hex_md5(chat_room), MINI_PASSWORDS[i], MINI_SHOWPANE);
		}
		
		catch(e) {}
	}
	
	// Join the chats (then)
	for(var j = 0; j < MINI_CHATS.length; j++) {
		// Empty value?
		if(!MINI_CHATS[j])
			continue;
		
		// Using a try/catch override IE issues
		try {
			// Current chat user
			var chat_xid = bareXID(generateXID(MINI_CHATS[j], 'chat'));
			var chat_hash = hex_md5(chat_xid);
			var chat_nick = jQuery('#jappix_mini a#friend-' + chat_hash).attr('data-nick');
			
			if(!chat_nick)
				chat_nick = getXIDNick(chat_xid);
			else
				chat_nick = unescape(chat_nick);
			
			// Open the current chat
			chatMini('chat', chat_xid, chat_nick, chat_hash);
		}
		
		catch(e) {}
	}
	
	// Must show the roster?
	if(!MINI_AUTOCONNECT && !MINI_GROUPCHATS.length && !MINI_CHATS.length)
		jQuery(document).oneTime(10, function() {
			showRosterMini();
		});
}

// Displays a roster buddy
function addBuddyMini(xid, hash, nick, groupchat, subscription) {
	// Element
	var element = '#jappix_mini a.jm_friend#friend-' + hash;
	
	// Yet added?
	if(exists(element))
		return false;
	
	// Generate the path
	var path = '#jappix_mini div.jm_roster div.jm_buddies';
	
	// Groupchat buddy
	if(groupchat) {
		// Generate the groupchat group path
		path = '#jappix_mini div.jm_roster div.jm_grouped[data-xid="' + escape(groupchat) + '"]';
		
		// Must add a groupchat group?
		if(!exists(path)) {
			jQuery('#jappix_mini div.jm_roster div.jm_buddies').append(
				'<div class="jm_grouped" data-xid="' + escape(groupchat) + '">' + 
					'<div class="jm_name">' + getXIDNick(groupchat).htmlEnc() + '</div>' + 
				'</div>'
			);
		}
	}
	
	if (subscription) {
	  substr = ' data-sub="' + subscription +'" ';
	} else {
	  substr = '';
	}
	// Append this buddy content
	var code = '<a class="jm_friend jm_offline" id="friend-' + hash
	           + '" data-xid="' + escape(xid)
	           + '" data-nick="' + escape(nick)
	           +  '" data-hash="' + hash + '" href="#" '
	           + substr + '><span class="jm_presence jm_images jm_unavailable"></span>'
	           + nick.htmlEnc() + '</a>';
	
	if(groupchat)
		jQuery(path).append(code);
	else
		jQuery(path).prepend(code);
	
	// Click event on this buddy
	jQuery(element).click(function() {
		// Using a try/catch override IE issues
		try {
			chatMini('chat', xid, nick, hash);
		}
		
		catch(e) {}
		
		finally {
			return false;
		}
	});
	
	// Hover event on this buddy
	jQuery(element).hover(function() {
		jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover');
		jQuery(this).addClass('jm_hover');
	}, function() {
		jQuery(this).removeClass('jm_hover');
	});
	
	// Mousedown event on this buddy
	jQuery('#jappix_mini a.jm_friend').mousedown(function() {
		jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover');
		jQuery(this).addClass('jm_hover');
	});
	
	// Focus events on this buddy
	jQuery(element).focus(function() {
		jQuery('#jappix_mini a.jm_friend.jm_hover').removeClass('jm_hover');
		jQuery(this).addClass('jm_hover');
	});
	
	// Blur events on this buddy
	jQuery(element).blur(function() {
		jQuery(this).removeClass('jm_hover');
	});
	
	return true;
}

// Removes a roster buddy
function removeBuddyMini(hash, groupchat) {
	// Remove the buddy from the roster
	jQuery('#jappix_mini a.jm_friend#friend-' + hash).remove();
	
	// Empty group?
	var group = '#jappix_mini div.jm_roster div.jm_grouped[data-xid="' + escape(groupchat) + '"]';
	
	if(groupchat && !jQuery(group + ' a.jm_friend').size())
		jQuery(group).remove();
	
	return true;
}

// Gets the user's roster
function getRosterMini() {
	var iq = new JSJaCIQ();
	iq.setType('get');
	iq.setQuery(NS_ROSTER);
	con.send(iq, handleRosterMini);
	
	logThis('Getting roster...', 3);
}

// Handles the user's roster
function handleRosterMini(iq) {
	// Added to sort buddies by name
    var buddies = [];
    var i = 0;
	
	// Parse the roster
	jQuery(iq.getQuery()).find('item').each(function() {
		// Get the values
		var current = jQuery(this);
		var xid = current.attr('jid');
		var subscription = current.attr('subscription');
		
		// Not a gateway
		if(!isGateway(xid)) {
			var nick = current.attr('name');
			var hash = hex_md5(xid);
			
			// Multidimentional array
			buddies[i] = [];
			
			// No name is defined?
			if(!nick)
				nick = getXIDNick(xid);
			
			// Populate buddy array
            buddies[i][0] = nick;
            buddies[i][1] = hash;
            buddies[i][2] = xid;
            buddies[i][3] = subscription;
		}
		
		// Increment counter
		i++;
	});
	
	// Sort array and loop reverse
	var buddies = buddies.sort();
	var x = buddies.length;
	var nick, hash, xid, subscription;
	
	for(var i = 0; i < x; i++) {
		if(!buddies[i])
			continue;
		
		// Current buddy information
		nick = buddies[i][0];
		hash = buddies[i][1];
		xid = buddies[i][2];
		subscription = buddies[i][3];
		
		// Apply current buddy action
		if(subscription == 'remove')
			removeBuddyMini(hash);
		else
			addBuddyMini(xid, hash, nick, null, subscription);
	}
	
	// Not yet initialized
	if(!MINI_INITIALIZED)
		initializeMini();
	
	logThis('Roster got.', 3);
}

// Adapts the roster height to the window
function adaptRosterMini() {
	// Adapt buddy list height
	var roster_height = jQuery(window).height() - 85;
	jQuery('#jappix_mini div.jm_roster div.jm_buddies').css('max-height', roster_height);
	
	// Adapt chan suggest height
	var suggest_height = jQuery('#jappix_mini div.jm_roster').height() - 46;
	jQuery('#jappix_mini div.jm_chan_suggest').css('max-height', suggest_height);
}

// Generates a random nickname
function randomNickMini() {
	// First nickname block
	var first_arr = [
		'Just',
		'Bob',
		'Jar',
		'Pedr',
		'Yod',
		'Maz',
		'Vez',
		'Car',
		'Erw',
		'Tiet',
		'Iot',
		'Wal',
		'Bez',
		'Pop',
		'Klop',
		'Zaz',
		'Yoy',
		'Raz'
	];
	
	// Second nickname block
	var second_arr = [
		'io',
		'ice',
		'a',
		'u',
		'o',
		'ou',
		'oi',
		'ana',
		'oro',
		'izi',
		'ozo',
		'aza',
		'ato',
		'ito',
		'ofa',
		'oki',
		'ima',
		'omi'
	];
	
	// Last nickname block
	var last_arr = [
		't',
		'z',
		'r',
		'n',
		'tt',
		'zz',
		'pp',
		'j',
		'k',
		'v',
		'c',
		'x',
		'ti',
		'to',
		'ta',
		'ra',
		'ro',
		'ri'
	];
	
	// Select random values from the arrays
	var rand_nick = randomArrayValue(first_arr) + randomArrayValue(second_arr) + randomArrayValue(last_arr);
	
	return rand_nick;
}

// Sends a given chatstate to a given entity
function sendChatstateMini(state, xid, hash) {
	var user_type = jQuery('#jappix_mini #chat-' + hash).attr('data-type');
	var user_storage = jQuery('#jappix_mini #chat-' + hash + ' input.jm_send-messages');
	
	// If the friend client supports chatstates and is online
	if((user_type == 'groupchat') || ((user_type == 'chat') && user_storage.attr('data-chatstates') && !exists('#jappix_mini a#friend-' + hash + '.jm_offline'))) {
		// Already sent?
		if(user_storage.attr('data-chatstate') == state)
			return;
		
		// Store the state
		user_storage.attr('data-chatstate', state);
		
		// Send the state
		var aMsg = new JSJaCMessage();
		aMsg.setTo(xid);
		aMsg.setType(user_type);
		
		aMsg.appendNode(state, {'xmlns': NS_CHATSTATES});
		
		con.send(aMsg);
		
		logThis('Sent ' + state + ' chatstate to ' + xid);
	}
}

// Displays a given chatstate in a given chat
function displayChatstateMini(state, xid, hash, type) {
	// Groupchat not supported
	if(type == 'groupchat')
		return;
	
	// Composing?
	if(state == 'composing')
		jQuery('#jappix_mini #chat-' + hash + ' div.jm_chatstate_typing').css('visibility', 'visible');
	else
		resetChatstateMini(xid, hash, type);
	
	logThis('Received ' + state + ' chatstate from ' + xid);
}

// Resets the chatstate switcher marker
function resetChatstateMini(xid, hash, type) {
	// Groupchat not supported
	if(type == 'groupchat')
		return;
	
	jQuery('#jappix_mini #chat-' + hash + ' div.jm_chatstate_typing').css('visibility', 'hidden');
}

// Adds the chatstate events
function eventsChatstateMini(xid, hash, type) {
	// Groupchat not supported
	if(type == 'groupchat')
		return;
	
	jQuery('#jappix_mini #chat-' + hash + ' input.jm_send-messages').keyup(function(e) {
		if(e.keyCode != 13) {
			// Composing a message
			if(jQuery(this).val() && (jQuery(this).attr('data-composing') != 'on')) {
				// We change the state detect input
				jQuery(this).attr('data-composing', 'on');
				
				// We send the friend a "composing" chatstate
				sendChatstateMini('composing', xid, hash);
			}
			
			// Stopped composing a message
			else if(!jQuery(this).val() && (jQuery(this).attr('data-composing') == 'on')) {
				// We change the state detect input
				jQuery(this).attr('data-composing', 'off');
				
				// We send the friend an "active" chatstate
				sendChatstateMini('active', xid, hash);
			}
		}
	})
	
	.change(function() {
		// Reset the composing database entry
		jQuery(this).attr('data-composing', 'off');
	})
	
	.focus(function() {
		// Not needed
		if(jQuery(this).is(':disabled'))
			return;
		
		// Nothing in the input, user is active
		if(!jQuery(this).val())
			sendChatstateMini('active', xid, hash);
		else
			sendChatstateMini('composing', xid, hash);
	})
	
	.blur(function() {
		// Not needed
		if(jQuery(this).is(':disabled'))
			return;
		
		// Nothing in the input, user is inactive
		if(!jQuery(this).val())
			sendChatstateMini('inactive', xid, hash);
		else
			sendChatstateMini('paused', xid, hash);
	});
}

// Plays a sound
function soundPlayMini() {
	try {
		// Not supported!
		if((BrowserDetect.browser == 'Explorer') && (BrowserDetect.version < 9))
			return false;
		
		// Append the sound container
		if(!exists('#jappix_mini #jm_audio')) {
			jQuery('#jappix_mini').append(
				'<div id="jm_audio">' + 
					'<audio preload="auto">' + 
						'<source src="' + JAPPIX_STATIC + 'snd/receive-message.mp3" />' + 
						'<source src="' + JAPPIX_STATIC + 'snd/receive-message.oga" />' + 
					'</audio>' + 
				'</div>'
			);
		}
		
		// Play the sound
		var audio_select = document.getElementById('jm_audio').getElementsByTagName('audio')[0];
		
		// Avoids Safari bug (2011 and less versions)
		try {
			audio_select.load();
		} finally {
			audio_select.play();
		}
	}
	
	catch(e) {}
	
	finally {
		return false;
	}
}

// TypeWatch to set a timeout to input value reading
var typewatch = (function() {
	var timer = 0;
	
	return function(callback, ms) {
	    clearTimeout(timer);
	    timer = setTimeout(callback, ms);
	}  
})();

// Plugin launcher
function launchMini(autoconnect, show_pane, domain, user, password, priority) {
	// Disabled on mobile?
	if(MINI_DISABLE_MOBILE && isMobile()) {
		logThis('Jappix Mini disabled on mobile.');

		return;
	}

	// Save infos to reconnect
	MINI_DOMAIN = domain;
	MINI_USER = user;
	MINI_PASSWORD = password;

	if(priority != undefined)
		MINI_PRIORITY = priority;
	
	// Anonymous mode?
	if(!user || !password)
		MINI_ANONYMOUS = true;
	else
		MINI_ANONYMOUS = false;
	
	// Autoconnect (only if storage available to avoid floods)?
	if(autoconnect && hasDB())
		MINI_AUTOCONNECT = true;
	else
		MINI_AUTOCONNECT = false;
	
	// Show pane?
	if(show_pane)
		MINI_SHOWPANE = true;
	else
		MINI_SHOWPANE = false;
	
	// Remove Jappix Mini
	jQuery('#jappix_mini').remove();
	
	// Reconnect?
	if(MINI_RECONNECT) {
		logThis('Trying to reconnect (try: ' + MINI_RECONNECT + ')!');
		
		return createMini(domain, user, password);
	}
	
	// Append the Mini stylesheet
	jQuery('head').append('<link rel="stylesheet" href="' + JAPPIX_STATIC + 'css/mini.css' + '" type="text/css" media="all" />');
	
	// Legacy IE stylesheet
	if((BrowserDetect.browser == 'Explorer') && (BrowserDetect.version < 7))
		jQuery('head').append('<link rel="stylesheet" href="' + JAPPIX_STATIC + 'css/mini-ie.css' + '" type="text/css" media="all" />');
	
	// Disables the browser HTTP-requests stopper
	jQuery(document).keydown(function(e) {
		if((e.keyCode == 27) && !isDeveloper())
			return false;
	});
	
	// Save the page title
	MINI_TITLE = document.title;
	
	// Adapts the content to the window size
	jQuery(window).resize(function() {
		adaptRosterMini();
		updateOverflowMini();
	});
	
	// Logouts when Jappix is closed
	if(BrowserDetect.browser == 'Opera') {
		// Emulates onbeforeunload on Opera (link clicked)
		jQuery('a[href]:not([onclick])').click(function() {
			// Link attributes
			var href = jQuery(this).attr('href') || '';
			var target = jQuery(this).attr('target') || '';
			
			// Not new window or JS link
			if(href && !href.match(/^#/i) && !target.match(/_blank|_new/i))
				saveSessionMini();
		});
		
		// Emulates onbeforeunload on Opera (form submitted)
		jQuery('form:not([onsubmit])').submit(saveSessionMini);
	}
	
	jQuery(window).bind('beforeunload', saveSessionMini);
	
	// Create the Jappix Mini DOM content
	createMini(domain, user, password);
	
	logThis('Welcome to Jappix Mini! Happy coding in developer mode!');
}