/*

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

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

License: AGPL
Author: Vanaryon
Last revision: 23/09/11

*/

// Sends the user first presence
var FIRST_PRESENCE_SENT = false;

function firstPresence(checksum) {
	logThis('First presence sent.', 3);
	
	// Jappix is now ready: change the title
	pageTitle('talk');
	
	// Anonymous check
	var is_anonymous = isAnonymous();
	
	// Update our marker
	FIRST_PRESENCE_SENT = true;
	
	// Try to use the last status message
	var status = getDB('options', 'presence-status');
	
	if(!status)
		status = '';
	
	// We tell the world that we are online
	if(!is_anonymous)
		sendPresence('', '', '', status, checksum);
	
	// Any status to apply?
	if(status)
		$('#presence-status').val(status);
	
	// Enable the presence picker
	$('#presence-status').removeAttr('disabled');
	$('#my-infos .f-presence a.picker').removeClass('disabled');
	
	// We set the last activity stamp
	PRESENCE_LAST_ACTIVITY = getTimeStamp();
	
	// We store our presence
	setDB('presence-show', 1, 'available');
	
	// Not anonymous
	if(!is_anonymous) {
		// We get the stored bookmarks (because of the photo hash and some other stuffs, we must get it later)
		getStorage(NS_BOOKMARKS);
		
		// We open a new chat if a XMPP link was submitted
		if((parent.location.hash != '#OK') && LINK_VARS['x']) {
			// A link is submitted in the URL
			xmppLink(LINK_VARS['x']);
			
			// Set a OK status
			parent.location.hash = 'OK';
		}
	}
}

// Handles incoming presence packets
function handlePresence(presence) {
	// We define everything needed here
	var from = fullXID(getStanzaFrom(presence));
	var hash = hex_md5(from);
	var node = presence.getNode();
	var xid = bareXID(from);
	var xidHash = hex_md5(xid);
	
	// We get the type content
	var type = presence.getType();
	if(!type)
		type = '';
	
	// We get the priority content
	var priority = presence.getPriority() + '';
	if(!priority || (type == 'error'))
		priority = '0';
	
	// We get the show content
	var show = presence.getShow();
	if(!show || (type == 'error'))
		show = '';
	
	// We get the status content
	var status = presence.getStatus();
	if(!status || (type == 'error'))
		status = '';
	
	// We get the photo content
	var photo = $(node).find('x[xmlns=' + NS_VCARD_P + ']:first photo');
	var checksum = photo.text();
	var hasPhoto = photo.size();
	
	if(hasPhoto && (type != 'error'))
		hasPhoto = 'true';
	else
		hasPhoto = 'false';
	
	// We get the CAPS content
	var caps = $(node).find('c[xmlns=' + NS_CAPS + ']:first').attr('ver');
	if(!caps || (type == 'error'))
		caps = '';
	
	// This presence comes from another resource of my account with a difference avatar checksum
	if((xid == getXID()) && (hasPhoto == 'true') && (checksum != getDB('checksum', 1)))
		getAvatar(getXID(), 'force', 'true', 'forget');
	
	// This presence comes from a groupchat
	if(isPrivate(xid)) {
		var x_muc = $(node).find('x[xmlns=' + NS_MUC_USER + ']:first');
		var item = x_muc.find('item');
		var affiliation = item.attr('affiliation');
		var role = item.attr('role');
		var reason = item.find('reason').text();
		var iXID = item.attr('jid');
		var iNick = item.attr('nick');
		var nick = thisResource(from);
		var messageTime = getCompleteTime();
		var notInitial = true;
		
		// Read the status code
		var status_code = new Array();
		
		x_muc.find('status').each(function() {
			status_code.push(parseInt($(this).attr('code')));
		});
		
		// If this is an initial presence (when user join the room)
		if(exists('#' + xidHash + '[data-initial=true]'))
			notInitial = false;
		
		// If one user is quitting
		if(type && (type == 'unavailable')) {
			displayMucPresence(from, xidHash, hash, type, show, status, affiliation, role, reason, status_code, iXID, iNick, messageTime, nick, notInitial);
			
			removeDB('presence', from);
		}
		
		// If one user is joining
		else {
			// Fixes M-Link first presence bug (missing ID!)
			if((nick == getMUCNick(xidHash)) && (presence.getID() == null) && !exists('#page-engine #' + xidHash + ' .list .' + hash)) {
				handleMUC(presence);
				
				logThis('Passed M-Link MUC first presence handling.', 2);
			}
			
			else {
				displayMucPresence(from, xidHash, hash, type, show, status, affiliation, role, reason, status_code, iXID, iNick, messageTime, nick, notInitial);
				
				var xml = '<presence from="' + encodeQuotes(from) + '"><priority>' + priority.htmlEnc() + '</priority><show>' + show.htmlEnc() + '</show><type>' + type.htmlEnc() + '</type><status>' + status.htmlEnc() + '</status><avatar>' + hasPhoto.htmlEnc() + '</avatar><checksum>' + checksum.htmlEnc() + '</checksum><caps>' + caps.htmlEnc() + '</caps></presence>';
				
				setDB('presence', from, xml);
			}
		}
		
		// Manage the presence
		presenceFunnel(from, hash);
	}
	
	// This presence comes from an user or a gateway
	else {
		// Subscribed & unsubscribed stanzas
		if((type == 'subscribed') || (type == 'unsubscribed'))
			return;
		
		// Subscribe stanza
		else if(type == 'subscribe') {
			// This is a buddy we can safely authorize, because we added him to our roster
			if(exists('#buddy-list .buddy[data-xid=' + escape(xid) + ']'))
				acceptSubscribe(xid);
			
			// We do not know this entity, we'd be better ask the user
			else {
				// Get the nickname
				var nickname = $(node).find('nick[xmlns=' + NS_NICK + ']:first').text();
				
				// New notification
				newNotification('subscribe', xid, [xid, nickname], status);
			}
		}
		
		// Unsubscribe stanza
		else if(type == 'unsubscribe')
			sendRoster(xid, 'remove');
		
		// Other stanzas
		else {
			// Unavailable/error presence
			if(type == 'unavailable')
				removeDB('presence', from);
			
			// Other presence (available, subscribe...)
			else {
				var xml = '<presence from="' + encodeQuotes(from) + '"><priority>' + priority.htmlEnc() + '</priority><show>' + show.htmlEnc() + '</show><type>' + type.htmlEnc() + '</type><status>' + status.htmlEnc() + '</status><avatar>' + hasPhoto.htmlEnc() + '</avatar><checksum>' + checksum.htmlEnc() + '</checksum><caps>' + caps.htmlEnc() + '</caps></presence>';
				
				setDB('presence', from, xml);
			}
			
			// We manage the presence
			presenceFunnel(xid, xidHash);
			
			// We display the presence in the current chat
			if(exists('#' + xidHash)) {
				var dStatus = filterStatus(xid, status, false);
				
				if(dStatus)
					dStatus = ' (' + dStatus + ')';
				
				// Generate the presence-in-chat code
				var dName = getBuddyName(from).htmlEnc();
				var dBody = dName + ' (' + from + ') ' + _e("is now") + ' ' + humanShow(show, type) + dStatus;
				
				// Check whether it has been previously displayed
				var can_display = true;
				
				if($('#' + xidHash + ' .one-line.system-message:last').html() == dBody)
					can_display = false;
				
				if(can_display)
					displayMessage('chat', xid, xidHash, dName, dBody, getCompleteTime(), getTimeStamp(), 'system-message', false);
			}
		}
	}
	
	// For logger
	if(!show) {
		if(!type)
			show = 'available';
		else
			show = 'unavailable';
	}
	
	logThis('Presence received: ' + show + ', from ' + from);
}

// Displays a MUC presence
function displayMucPresence(from, roomHash, hash, type, show, status, affiliation, role, reason, status_code, iXID, iNick, messageTime, nick, initial) {
	// Generate the values
	var thisUser = '#page-engine #' + roomHash + ' .list .' + hash;
	var thisPrivate = $('#' + hash + ' .message-area');
	var nick_html = nick.htmlEnc();
	var real_xid = '';
	var write = nick_html + ' ';
	var notify = false;
	
	// Reset data?
	if(!role)
		role = 'participant';
	if(!affiliation)
		affiliation = 'none';
	
	// Must update the role?
	if(exists(thisUser) && (($(thisUser).attr('data-role') != role) || ($(thisUser).attr('data-affiliation') != affiliation)))
		$(thisUser).remove();
	
	// Any XID submitted?
	if(iXID) {
		real_xid = ' data-realxid="' + iXID + '"';
		iXID = bareXID(iXID);
		write += ' (<a onclick="return checkChatCreate(\'' + encodeOnclick(iXID) + '\', \'chat\');" href="xmpp:' + encodeOnclick(iXID) + '">' + iXID + '</a>) ';
	}
	
	// User does not exists yet
	if(!exists(thisUser) && (!type || (type == 'available'))) {
		var myself = '';
		
		// Is it me?
		if(nick == getMUCNick(roomHash)) {
			// Enable the room
			$('#' + roomHash + ' .message-area').removeAttr('disabled');
			
			// Marker
			myself = ' myself';
		}
		
		// Set the user in the MUC list
		$('#' + roomHash + ' .list .' + role + ' .title').after(
			'<div class="user ' + hash + myself + '" data-xid="' + encodeQuotes(from) + '" data-nick="' + escape(nick) + '"' + real_xid + ' data-role="' + encodeQuotes(role) + '" data-affiliation="' + encodeQuotes(affiliation) + '">' + 
				'<div class="name talk-images available">' + nick_html + '</div>' + 
				
				'<div class="avatar-container">' + 
					'<img class="avatar" src="' + './img/others/default-avatar.png' + '" alt="" />' + 
				'</div>' + 
			'</div>'
		);
		
		// Click event
		if(nick != getMUCNick(roomHash))
			$(thisUser).live('click', function() {
				checkChatCreate(from, 'private');
			});
		
		// We tell the user that someone entered the room
		if(!initial) {
			notify = true;
			write += _e("joined the chat room");
			
			// Any status?
			if(status)
				write += ' (' + filterThisMessage(status, nick_html, true) + ')';
			else
				write += ' (' + _e("no status") + ')';
		}
		
		// Enable the private chat input
		thisPrivate.removeAttr('disabled');
	}
	
	else if((type == 'unavailable') || (type == 'error')) {
		// Is it me?
		if(nick == getMUCNick(roomHash)) {
			$(thisUser).remove();
			
			// Disable the groupchat input
			$('#' + roomHash + ' .message-area').attr('disabled', true);
			
			// Remove all the groupchat users
			$('#' + roomHash + ' .list .user').remove();
		}
		
		// Someone has been kicked or banned?
		if(existArrayValue(status_code, 301) || existArrayValue(status_code, 307)) {
			$(thisUser).remove();
			notify = true;
			
			// Kicked?
			if(existArrayValue(status_code, 307))
				write += _e("has been kicked");
			
			// Banned?
			if(existArrayValue(status_code, 301))
				write += _e("has been banned");
			
			// Any reason?
			if(reason)
				write += ' (' + filterThisMessage(reason, nick_html, true) + ')';
			else
				write += ' (' + _e("no reason") + ')';
		}
		
		// Nickname change?
		else if(existArrayValue(status_code, 303) && iNick) {
			notify = true;
			write += printf(_e("changed his/her nickname to %s"), iNick.htmlEnc());
			
			// New values
			var new_xid = cutResource(from) + '/' + iNick;
			var new_hash = hex_md5(new_xid);
			var new_class = 'user ' + new_hash;
			
			if($(thisUser).hasClass('myself'))
				new_class += ' myself';
			
			// Die the click event
			$(thisUser).die('click');
			
			// Change to the new nickname
			$(thisUser).attr('data-nick', iNick)
			           .attr('data-xid', new_xid)
			           .find('.name').text(iNick);
			
			// Change the user class
			$(thisUser).attr('class', new_class);
			
			// New click event
			$('#page-engine #' + roomHash + ' .list .' + new_hash).live('click', function() {
				checkChatCreate(new_xid, 'private');
			});
		}
		
		// We tell the user that someone left the room
		else if(!initial) {
			$(thisUser).remove();
			notify = true;
			write += _e("left the chat room");
			
			// Any status?
			if(status)
				write += ' (' + filterThisMessage(status, nick_html, true) + ')';
			else
				write += ' (' + _e("no status") + ')';
		}
		
		// Disable the private chat input
		thisPrivate.attr('disabled', true);
	}
	
	// Must notify something
	if(notify)
		displayMessage('groupchat', from, roomHash, nick_html, write, messageTime, getTimeStamp(), 'system-message', false);
	
	// Set the good status show icon
	switch(show) {
		case 'chat':
		case 'away':
		case 'xa':
		case 'dnd':
			break;
		
		default:
			show = 'available';
			break;
	}
	
	$(thisUser + ' .name').attr('class', 'name talk-images ' + show);
	
	// Set the good status text
	var uTitle = nick;
	
	// Any XID to add?
	if(iXID)
		uTitle += ' (' + iXID + ')';
	
	// Any status to add?
	if(status)
		uTitle += ' - ' + status;
	
	$(thisUser).attr('title', uTitle);
	
	// Show or hide the role category, depending of its content
	$('#' + roomHash + ' .list .role').each(function() {
		if($(this).find('.user').size())
			$(this).show();
		else
			$(this).hide();
	});
}

// Filters a given status
function filterStatus(xid, status, cut) {
	var dStatus = '';
	
	if(!status)
		status = '';
	
	else {
		if(cut)
			dStatus = truncate(status, 50);
		else
			dStatus = status;
		
		dStatus = filterThisMessage(dStatus, getBuddyName(xid).htmlEnc(), true);
	}
	
	return dStatus;
}

// Displays a user's presence
function displayPresence(value, type, show, status, hash, xid, avatar, checksum, caps) {
	// Display the presence in the roster
	var path = '#buddy-list .' + hash;
	var buddy = $('#buddy-list .content .' + hash);
	var dStatus = filterStatus(xid, status, false);
	var tStatus = encodeQuotes(status);
	var biStatus;
	
	// The buddy presence behind his name
	$(path + ' .name .buddy-presence').replaceWith('<p class="buddy-presence talk-images ' + type + '">' + value + '</p>');
	
	// The buddy presence in the buddy infos
	if(dStatus)
		biStatus = dStatus;
	else
		biStatus = value;
	
	$(path + ' .bi-status').replaceWith('<p class="bi-status talk-images ' + type + '" title="' + tStatus + '">' + biStatus + '</p>');
	
	// When the buddy disconnect himself, we hide him
	if((type == 'unavailable') || (type == 'error')) {
		// Set a special class to the buddy
		buddy.addClass('hidden-buddy');
		
		// No filtering is launched?
		if(!SEARCH_FILTERED)
			buddy.hide();
		
		// All the buddies are shown?
		if(BLIST_ALL)
			buddy.show();
		
		// Chat stuffs
		if(exists('#' + hash)) {
			// Remove the chatstate stuffs
			resetChatState(hash);
			$('#' + hash + ' .chatstate').remove();
			$('#' + hash + ' .message-area').removeAttr('data-chatstates');
			
			// Get the buddy avatar (only if a chat is opened)
			getAvatar(xid, 'cache', 'true', 'forget');
		}
	}
	
	// If the buddy is online
	else {
		// When the buddy is online, we show it
		buddy.removeClass('hidden-buddy');
		
		// No filtering is launched?
		if(!SEARCH_FILTERED)
			buddy.show();
		
		// Get the online buddy avatar if not a gateway
		getAvatar(xid, 'cache', avatar, checksum);
	}
	
	// Display the presence in the chat
	if(exists('#' + hash)) {
		// We generate a well formed status message
		if(dStatus) {
			// No need to write the same status two times
			if(dStatus == value)
				dStatus = '';
			else
				dStatus = ' (' + dStatus + ')';
		}
		
		// We show the presence value
		$('#' + hash + ' .bc-infos').replaceWith('<p class="bc-infos" title="' + tStatus + '"><span class="' + type + ' show talk-images">' + value + '</span>' + dStatus + '</p>');
		
		// Process the new status position
		adaptChatPresence(hash);
		
		// Get the disco#infos for this user
		var highest = getHighestResource(xid);
		
		if(highest)
			getDiscoInfos(highest, caps);
		else
			displayDiscoInfos(xid, '');
	}
	
	// Display the presence in the switcher
	if(exists('#page-switch .' + hash))
		$('#page-switch .' + hash + ' .icon').removeClass('available unavailable error away busy').addClass(type);
	
	// Update roster groups
	if(!SEARCH_FILTERED)
		updateGroups();
	else
		funnelFilterBuddySearch();
}

// Process the chat presence position
function adaptChatPresence(hash) {
	// Get values
	var pep_numb = $('#' + hash + ' .bc-pep').find('a').size();
	
	// Process the right position
	var presence_right = 12;
	
	if(pep_numb)
		presence_right = (pep_numb * 20) + 18;
	
	// Apply the right position
	$('#' + hash + ' p.bc-infos').css('right', presence_right);
}

// Convert the presence "show" element into a human-readable output
function humanShow(show, type) {
	if(type == 'unavailable')
		show = _e("Unavailable");
	
	else if(type == 'error')
		show = _e("Error");
	
	else {
		switch(show) {
			case 'chat':
				show = _e("Talkative");
				break;
			
			case 'away':
				show = _e("Away");
				break;
			
			case 'xa':
				show = _e("Not available");
				break;
			
			case 'dnd':
				show = _e("Busy");
				break;
			
			default:
				show = _e("Available");
				break;
		}
	}
	
	return show;
}

// Makes the presence data go in the right way
function presenceIA(type, show, status, hash, xid, avatar, checksum, caps) {
	// Is there a status defined?
	if(!status)
		status = humanShow(show, type);
	
	// Then we can handle the events
	if(type == 'error')
		displayPresence(_e("Error"), 'error', show, status, hash, xid, avatar, checksum, caps);
	
	else if(type == 'unavailable')
		displayPresence(_e("Unavailable"), 'unavailable', show, status, hash, xid, avatar, checksum, caps);
	
	else {
		switch(show) {
			case 'chat':
				displayPresence(_e("Talkative"), 'available', show, status, hash, xid, avatar, checksum, caps);
				break;
			
			case 'away':
				displayPresence(_e("Away"), 'away', show, status, hash, xid, avatar, checksum, caps);
				break;
			
			case 'xa':
				displayPresence(_e("Not available"), 'busy', show, status, hash, xid, avatar, checksum, caps);
				break;
			
			case 'dnd':
				displayPresence(_e("Busy"), 'busy', show, status, hash, xid, avatar, checksum, caps);
				break;
			
			default:
				displayPresence(_e("Available"), 'available', show, status, hash, xid, avatar, checksum, caps);
				break;
		}
	}
}

// Gets the highest resource priority for an user
function highestPriority(xid) {
	var maximum = null;
	var selector, priority, type, highest;
	
	// This is a groupchat presence
	if(xid.indexOf('/') != -1)
		highest = XMLFromString(getDB('presence', xid));
	
	// This is a "normal" presence: get the highest priority resource
	else {
		for(var i = 0; i < sessionStorage.length; i++) {
			// Get the pointer values
			var current = sessionStorage.key(i);
			
			// If the pointer is on a stored presence
			if(explodeThis('_', current, 0) == 'presence') {
				// Get the current XID
				var now = bareXID(explodeThis('_', current, 1));
				
				// If the current XID equals the asked XID
				if(now == xid) {
					var xml = XMLFromString(sessionStorage.getItem(current));
					var priority = parseInt($(xml).find('priority').text());
					
					// Higher priority
					if((priority >= maximum) || (maximum == null)) {
						maximum = priority;
						highest = xml;
					}
				}
			}
		}
	}
	
	// The user might be offline if no highest
	if(!highest)
		highest = XMLFromString('<presence><type>unavailable</type></presence>');
	
	return highest;
}

// Gets the resource from a XID which has the highest priority
function getHighestResource(xid) {
	var xml = $(highestPriority(xid));
	var highest = xml.find('presence').attr('from');
	var type = xml.find('type').text();
	
	// If the use is online, we can return its highest resource
	if(!type || (type == 'available') || (type == 'null'))
		return highest;
	else
		return false;
}

// Makes something easy to process for the presence IA
function presenceFunnel(xid, hash) {
	// Get the highest priority presence value
	var xml = $(highestPriority(xid));
	var type = xml.find('type').text();
	var show = xml.find('show').text();
	var status = xml.find('status').text();
	var avatar = xml.find('avatar').text();
	var checksum = xml.find('checksum').text();
	var caps = xml.find('caps').text();
	
	// Display the presence with that stored value
	if(!type && !show)
		presenceIA('', 'available', status, hash, xid, avatar, checksum, caps);
	else
		presenceIA(type, show, status, hash, xid, avatar, checksum, caps);
}

// Sends a defined presence packet
function sendPresence(to, type, show, status, checksum, limit_history, password, handle) {
	// Get some stuffs
	var priority = getDB('priority', 1);
	
	if(!priority)
		priority = '1';
	if(!checksum)
		checksum = getDB('checksum', 1);
	if(show == 'available')
		show = '';
	if(type == 'available')
		type = '';
	
	// New presence
	var presence = new JSJaCPresence();
	
	// Avoid "null" or "none" if nothing stored
	if(!checksum || (checksum == 'none'))
		checksum = '';
	
	// Presence headers
	if(to)
		presence.setTo(to);
	if(type)
		presence.setType(type);
	if(show)
		presence.setShow(show);
	if(status)
		presence.setStatus(status);
	
	presence.setPriority(priority);
	
	// CAPS (entity capabilities)
	presence.appendNode('c', {'xmlns': NS_CAPS, 'hash': 'sha-1', 'node': 'https://www.jappix.com/', 'ver': myCaps()});
	
	// Nickname
	var nickname = getName();
	
	if(nickname)
		presence.appendNode('nick', {'xmlns': NS_NICK}, nickname);
	
	// vcard-temp:x:update node
	var x = presence.appendNode('x', {'xmlns': NS_VCARD_P});
	x.appendChild(presence.buildNode('photo', {'xmlns': NS_VCARD_P}, checksum));
	
	// MUC X data
	if(limit_history || password) {
		var xMUC = presence.appendNode('x', {'xmlns': NS_MUC});
		
		// Max messages age (for MUC)
		if(limit_history)
			xMUC.appendChild(presence.buildNode('history', {'maxstanzas': 20, 'seconds': 86400, 'xmlns': NS_MUC}));
		
		// Room password
		if(password)
			xMUC.appendChild(presence.buildNode('password', {'xmlns': NS_MUC}, password));
	}
	
	// If away, send a last activity time
	if((show == 'away') || (show == 'xa')) {
		/* REF: http://xmpp.org/extensions/xep-0256.html */
		
		presence.appendNode(presence.buildNode('query', {
			'xmlns': NS_LAST,
			'seconds': getPresenceLast()
		}));
	}
	
	// Else, set a new last activity stamp
	else
		PRESENCE_LAST_ACTIVITY = getTimeStamp();
	
	// Send the presence packet
	if(handle)
		con.send(presence, handle);
	else
		con.send(presence);
	
	if(!type)
		type = 'available';
	
	logThis('Presence sent: ' + type, 3);
}

// Performs all the actions to get the presence data
function presenceSend(checksum, autoidle) {
	// We get the values of the inputs
	var show = getUserShow();
	var status = getUserStatus();
	
	// Send the presence
	if(!isAnonymous())
		sendPresence('', '', show, status, checksum);
	
	// We set the good icon
	presenceIcon(show);
	
	// We store our presence
	if(!autoidle)
		setDB('presence-show', 1, show);
	
	// We send the presence to our active MUC
	$('.page-engine-chan[data-type=groupchat]').each(function() {
		var tmp_nick = $(this).attr('data-nick');
		
		if(!tmp_nick)
			return;
		
		var room = unescape($(this).attr('data-xid'));
		var nick = unescape(tmp_nick);
		
		// Must re-initialize?
		if(RESUME)
			getMUC(room, nick);
		
		// Not disabled?
		else if(!$(this).find('.message-area').attr('disabled'))
			sendPresence(room + '/' + nick, '', show, status, '', true);
	});
}

// Changes the presence icon
function presenceIcon(value) {
	$('#my-infos .f-presence a.picker').attr('data-value', value);
}

// Sends a subscribe stanza
function sendSubscribe(to, type) {
	var status = '';
	
	// Subscribe request?
	if(type == 'subscribe')
		status = printf(_e("Hi, I am %s, I would like to add you as my friend."), getName());
	
	sendPresence(to, type, '', status);
}

// Accepts the subscription from another entity
function acceptSubscribe(xid, name) {
	// We update our chat
	$('#' + hex_md5(xid) + ' .tools-add').hide();
	
	// We send a subsribed presence (to confirm)
	sendSubscribe(xid, 'subscribed');
	
	// We send a subscription request (subscribe both sides)
	sendSubscribe(xid, 'subscribe');
	
	// Specify the buddy name (if any)
	if(name)
		sendRoster(xid, '', name)
}

// Sends automatic away presence
var AUTO_IDLE = false;

function autoIdle() {
	// Not connected?
	if(!isConnected())
		return;
	
	// Stop if an xa presence was set manually
	var last_presence = getUserShow();
	
	if(!AUTO_IDLE && ((last_presence == 'away') || (last_presence == 'xa')))
		return;
	
	var idle_presence;
	var activity_limit;
	
	// Can we extend to auto extended away mode (20 minutes)?
	if(AUTO_IDLE && (last_presence == 'away')) {
		idle_presence = 'xa';
		activity_limit = 1200;
	}
	
	// We must set the user to auto-away (10 minutes)
	else {
		idle_presence = 'away';
		activity_limit = 600;
	}
	
	// The user is really inactive and has set another presence than extended away
	if(((!AUTO_IDLE && (last_presence != 'away')) || (AUTO_IDLE && (last_presence == 'away'))) && (getLastActivity() >= activity_limit)) {
		// Then tell we use an auto presence
		AUTO_IDLE = true;
		
		// Get the old status message
		var status = getDB('options', 'presence-status');
		
		if(!status)
			status = '';
		
		// Change the presence input
		$('#my-infos .f-presence a.picker').attr('data-value', idle_presence);
		$('#presence-status').val(status);
		
		// Then send the xa presence
		presenceSend('', true);
		
		logThis('Auto-idle presence sent: ' + idle_presence, 3);
	}
}

// Restores the old presence on a document bind
function eventIdle() {
	// If we were idle, restore our old presence
	if(AUTO_IDLE) {
		// Get the values
		var show = getDB('presence-show', 1);
		var status = getDB('options', 'presence-status');
		
		// Change the presence input
		$('#my-infos .f-presence a.picker').attr('data-value', show);
		$('#presence-status').val(status);
		$('#presence-status').placeholder();
		
		// Then restore the old presence
		presenceSend('', true);
		
		if(!show)
			show = 'available';
		
		logThis('Presence restored: ' + show, 3);
	}
	
	// Apply some values
	AUTO_IDLE = false;
	LAST_ACTIVITY = getTimeStamp();
}

// Lives the auto idle functions
function liveIdle() {
	// Apply the autoIdle function every minute
	AUTO_IDLE = false;
	$('#my-infos .f-presence').everyTime('30s', autoIdle);
	
	// On body bind (click & key event)
	$('body').live('mousedown', eventIdle)
		 .live('mousemove', eventIdle)
		 .live('keydown', eventIdle);
}

// Kills the auto idle functions
function dieIdle() {
	// Remove the event detector
	$('body').die('mousedown', eventIdle)
		 .die('mousemove', eventIdle)
		 .die('keydown', eventIdle);
}

// Gets the user presence show
function getUserShow() {
	return $('#my-infos .f-presence a.picker').attr('data-value');
}

// Gets the user presence status
function getUserStatus() {
	return $('#presence-status').val();
}

// Plugin launcher
function launchPresence() {
	// Click event for user presence show
	$('#my-infos .f-presence a.picker').click(function() {
		// Disabled?
		if($(this).hasClass('disabled'))
			return false;
		
		// Initialize some vars
		var path = '#my-infos .f-presence div.bubble';
		var show_id = ['xa', 'away', 'available'];
		var show_lang = [_e("Not available"), _e("Away"), _e("Available")];
		var show_val = getUserShow();
		
		// Yet displayed?
		var can_append = true;
		
		if(exists(path))
			can_append = false;
		
		// Add this bubble!
		showBubble(path);
		
		if(!can_append)
			return false;
		
		// Generate the HTML code
		var html = '<div class="bubble removable">';
		
		for(i in show_id) {
			// Yet in use: no need to display it!
			if(show_id[i] == show_val)
				continue;
			
			html += '<a href="#" class="talk-images" data-value="' + show_id[i] + '" title="' + show_lang[i] + '"></a>';
		}
		
		html += '</div>';
		
		// Append the HTML code
		$('#my-infos .f-presence').append(html);
		
		// Click event
		$(path + ' a').click(function() {
			// Update the presence show marker
			$('#my-infos .f-presence a.picker').attr('data-value', $(this).attr('data-value'));
			
			// Close the bubble
			closeBubbles();
			
			// Focus on the status input
			$(document).oneTime(10, function() {
				$('#presence-status').focus();
			});
			
			return false;
		});
		
		return false;
	});
	
	// Submit events for user presence status
	$('#presence-status').placeholder()
	
	.keyup(function(e) {
		if(e.keyCode == 13) {
			$(this).blur();
			
			return false;
		}
	})
	
	.blur(function() {
		// Read the parameters
		var show = getUserShow();
		var status = getUserStatus();
		
		// Read the old parameters
		var old_show = getDB('presence-show', 1);
		var old_status = getDB('options', 'presence-status');
		
		// Must send the presence?
		if((show != old_show) || (status != old_status)) {
			// Update the local stored status
			setDB('options', 'presence-status', status);
			
			// Update the server stored status
			if(status != old_status)
				storeOptions();
			
			// Send the presence
			presenceSend();
		}
	})
	
	// Input focus handler
	.focus(function() {
		closeBubbles();
	});
}