//=== begin aimapi.pow.js
//--- Web AIM 0.19r2.1 by S. G. Chipman customized for PictureOfWhat.com */

/* these object prototypes are required by the API */
String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ""); };
String.prototype.toBoolean = function() {if(this == "false") return false; return true;};
Array.prototype.indexOf = function(val) { for(i=0;i<this.length;i++) if(this[i] == val) return i; return -1;};

// define your own baseResourceURI before this script is sourced to over-ride the default
if(typeof(baseResourceURI) == "undefined") var baseResourceURI = "http://o.aolcdn.com/aim/web-aim/default-theme/";
if(typeof(baseLang) == "undefined") var baseLang = "en-us";

var AIM = {
	init: function() {
		if (!AIM.params.wimKey)
			return alert(AIM.params.text.errors.missingKey);
		AIM.util.addEvent(window,AIM.widgets.buddyList.kill,"beforeunload");
		AIM.util.addEvent(document,AIM.eventHandlers.handleMouseMove,"mousemove");
		AIM.util.addEvent(document,function(){AIM.util.mDown=false;},"mouseup");
	}
}

/* parameters that define how the API behaves; see developer.aim.com */
AIM.params = {
	user: null,
	owner: null,
	sessionId: null,
	assertCaps: '',
	interestCaps: '',
	baseTransactionURI: 'http://api.oscar.aol.com/', 
	baseAuthURI: 'https://api.screenname.aol.com/',
	listenerURI: null,
	wimKey: 'ee1fp8JCWwuisIyV',
	token: null,
	language: 'en-us', // deprecated in favor of global baseLang
	transactions: {
		getBuddyInfo:"presence/get",
		getBuddyList:"presence/get",
		getToken:"auth/getToken",
		sendTextIM:"im/sendIM",
		sendDataIM:"im/sendDataIM",
		getPresenceInfo:"presence/get",
		startSession:"aim/startSession",
		setState:"presence/setState",
		typingStatus:"im/setTyping",
		endSession:"aim/endSession",
		logout:"auth/logout"
	},
	callbacks: {
		getBuddyInfo: ["AIM.callbacks.getBuddyInfo"],
		getBuddyList: ["AIM.callbacks.getBuddyList"],
		getToken:["AIM.callbacks.getToken"],
		sendTextIM: ["AIM.callbacks.sendTextIM"],
		sendDataIM:["AIM.callbacks.sendDataIM"],
		getPresenceInfo: ["AIM.callbacks.getPresenceInfo"],
		startSession:["AIM.callbacks.startSession"],
		typingStatus:["AIM.callbacks.typingStatus"],
		setState:["AIM.callbacks.setState"],
		endSession:["AIM.callbacks.endSession"],
		listener: {
			im: ["AIM.ui.acceptIncomingMessage"],
			offlineIM:["AIM.ui.acceptIncomingMessage"],
			imData:["AIM.callbacks.acceptDataIM"],
			buddylist:["AIM.ui.createBuddyList"],
			presence:["AIM.ui.updateBuddyList"],
			typing:["AIM.ui.updateTypingStatus"],
			sessionEnded:["AIM.callbacks.sessionEnded"]
		}
	},
	
	// AIM.params.text moved to language specific files, i.e. aimapi.text.en-us.js
	SEND_OFFLINE_IM:1,
	DOCUMENT_TITLE:document.title,
	DEBUG:false,
	BUDDY_LIST_DRAG:false,
	DEFAULT_ICON: defaultIconSrc,
	AWAY_MSG_LIMIT:1024,
	REQUEST_TIMEOUT:((navigator.userAgent.indexOf("Firefox")>-1 || navigator.userAgent.indexOf("Camino") >-1 || window.opera) && !document.all)?2000:60000,
	NOTIFICATION_THROB: 1000,
	RENDER_SEND_BUTTON: true,
	UPDATE_DURATION:5000, 
	SHOW_OFFLINE:false,
	CREATE_AVAILABILITY_MENU_BL:true,
	CREATE_AVAILABILITY_MENU_IM:true,
	RETAIN_WINDOW:true,
	SHOW_TIMESTAMP: true,
	TWENTY_FOUR_HOUR_CLOCK: false,
	VISUAL_NOTIFICATION: true,
	MOZILLA:navigator.userAgent.indexOf("Firefox")>-1 && !document.all,
	MSIE:document.all && !window.opera,
	OPERA:window.opera
}

/**
*	Widget object to contain launch, kill and appearance definitions
*/
AIM.widgets = {
	buddyList: {
		launch:function() {
			if (document.getElementById('AIMBuddyList') != null)
				return;
			AIM.init();
			if	(document.getElementById('AIMBuddyListContainer') == null)
				return alert(AIM.params.text.errors.noAIMContainer);
			if (!AIM.core.supportedBrowser())
				alert(AIM.params.text.unsupportedBrowser);
			AIM.util.createStyleSheet(AIM.widgets.buddyList.appearance.styleSheetURI);
			//AIM.params.sessionId = null;
			if (arguments[0])
				AIM.core.subscriptions = arguments[0];
			else
				AIM.core.subscriptions = "buddylist,presence,im,typing,offlineIM";
			AIM.transactions.getToken(AIM.core.subscriptions);
		},
		kill: function() {
			//document.getElementById("AIMBuddyList").parentNode.removeChild(document.getElementById("AIMBuddyList"));
			document.getElementById("AIMBuddyListContainer").innerHTML = "";
			if(document.getElementById("AIMBuddyListAwayBox")) document.getElementById("AIMBuddyListAwayBox").parentNode.removeChild(document.getElementById("AIMBuddyListAwayBox"));
			//AIM.util.removeEvent(document,AIM.eventHandlers.handleMouseMove,"mousemove");
			AIM.ui.removeAllIMWindows();
			AIM.transactions.endSession();
			AIM.util.cleanUp();
			AIM.core.destroyListenerObject(false);
			AIM.core.activeSession = false;
			actionBuddyListKill();
		},
		appearance: {
			styleSheetURI: '/css/default-theme.css'
		}
	},
	IM: {
		launch: function() {
			AIM.init();
			if(!document.getElementById("AIMBuddyListContainer")) return alert(AIM.params.text.errors.noAIMContainer);
			if(!AIM.core.supportedBrowser()) alert(AIM.params.text.unsupportedBrowser);
			AIM.util.createStyleSheet(AIM.widgets.IM.appearance.styleSheetURI);
			AIM.util.addEvent(document,AIM.eventHandlers.handleMouseMove,"mousemove");
			if(arguments[0]) {
				AIM.ui.createIMWindow(arguments[0]);
			} else {
				AIM.ui.aimIdPrompt(AIM.params.owner);
			}
			//AIM.params.sessionId = null;
			//AIM.core.subscriptions = arguments[0]?arguments[0]:"im,typing";
			AIM.core.subscriptions = "im,typing";
			AIM.transactions.getToken(AIM.core.subscriptions);
		},
		kill: function() {
			AIM.ui.closeIMWindow(aimId);
			AIM.util.removeEvent(document,AIM.eventHandlers.handleMouseMove,"mousemove");
			AIM.transactions.endSession();
		},
		appearance: {
			styleSheetURI: '/css/default-theme.css'
		}
	},
	getInfo: {
		launch: function() {
			AIM.init();
			AIM.ui.prepBuddyInfo(srcObj);
			AIM.transactions.getBuddyInfo(aimId);
		},
		kill: function() {
			document.getElementById("AIMBuddyListBuddyInfo").parentNode.removeChild(document.getElementById("AIMBuddyListBuddyInfo"));
		},
		appearance: {
			styleSheetURI: '/css/default-theme.css'
		}
	},
	presence: {
		launch: function() {
			var fn = function() {
				AIM.init();
				AIM.transactions.getPresenceInfo();
				AIM.util.addEvent(document,AIM.eventHandlers.handleMouseMove,"mousemove");
			}
			window.addEventListener?window.addEventListener("load",fn,false):window.attachEvent("onload",fn);
		},
		kill: function() {
			AIM.util.addEvent(document,AIM.eventHandlers.handleMouseMove,"mousemove");
		},
		appearance: {
			styleSheetURI: '/css/default-theme.css'
		}
	}
}

/**
*	AIM.core contains all of the methods that are REQUIRED for the API to funtion.
*/
AIM.core = {
	AIMData:[],
	authAttempts:0,
	requestInterval:null,
	subscriptions: null,
	activeSession: false,
	pendingTransaction: null,
	/**
	*	Creates a DIV element that contains an IFRAME element for authentication/consent
	*	@param { String } url The url that will be the src of the iframe
	*/
	createAuthWindow: function(url) {
		var oIframe;
		if(document.getElementById("AIMFrameContainer_AIMwindow")) {
			oIframe = document.getElementById("AIMReqFrame");
		} else {
			document.body.style.cursor = 'wait';
			var win = AIM.ui.createWindowFrame("AIMFrameContainer","AIMFrameContainer","Web AIM");
			win.style.display = 'none';
			oIframe = document.createElement("iframe");
			oIframe.setAttribute("id","AIMReqFrame");
			oIframe.setAttribute("frameborder","0");
			win.appendChild(oIframe);
			win.style.left = (document.getElementsByTagName("body")[0].offsetWidth - 510)/2 + "px";
			document.getElementsByTagName("body")[0].appendChild(win);
			win.style.display = 'block';
			document.body.style.cursor = 'auto';
		}
		oIframe.src = "about:blank";
		oIframe.src = url + "&nocache=" + Date.parse(new Date());
		document.getElementById("AIMFrameContainer_AIMwindow").style.display = "block";
		//document.getElementById("AIMFrameContainer_AIMwindow").style.top = (AIM.util.getScrollOffset(1) + document.getElementById("AIMFrameContainer_AIMwindow").offsetTop) + "px";
		window.scrollTo(0,0);
		AIM.core.debug("createAuthWindow: " + url + "&nocache=" + Date.parse(new Date()));
		actionEndCreateAuthWindow();
		AIM.core.watchAuthRequest();
	},
	/**
	*	Checks to see if the browser the user is in is part of the "officially supported browser matrix"
	*/
	supportedBrowser: function() {
		if(window.opera || document.layers || !document.getElementById) return false;
		var FF = navigator.userAgent.match("Firefox/[1-2]\.");
		var MSIE = navigator.userAgent.match(/MSIE [6-9]/);
		var SAF = navigator.userAgent.match(/Safari\/[4-9]/);
		if(FF || MSIE || SAF) return true;
		return false;
	},
	
	/**
	*	Called via setTimeout to check the status of the fragment identifier set by the authentication service
	*	when authentication/consent is complete.
	*  	1. AUTHCANCEL - user canceled login
   	*	2. AUTHDONE - user successfully logged in
   	*	3. INVALIDCALLBACK - invalid jsonp callback
   	*	4. CONSENTINVALIDTOKEN - getconsent called with invalid enc token
  	*	5. CONSENTDONE - consent done
  	*	6. CONSENTCANCEL - user denied consent 
  	*
	*/
	watchAuthRequest: function() {
		var oLoc = location.href;
		if(oLoc.indexOf("#AUTHDONE") > -1 || oLoc.indexOf("#CONSENTDONE") > -1 || oLoc.indexOf("#CONSENTCANCEL") > -1 || oLoc.indexOf("#AUTHCANCEL") > -1 || oLoc.indexOf("#CONSENTINVALIDTOKEN") > -1) {
			if(oLoc.indexOf("#AUTHDONE") > -1) {
				AIM.core.debug("AIM.core.watchAuthRequest: Making a token request.");
				AIM.transactions.getToken(AIM.core.subscriptions);
			} else if (oLoc.indexOf("#CONSENTDONE") > -1) {
			 	if(!AIM.params.sessionId) { 
					AIM.core.debug("AIM.core.watchAuthRequest: No session id. Requesting one.");
					AIM.transactions.startSession(AIM.core.subscriptions);
				} else {
					AIM.core.destroyListenerObject(true);
				}
				if(AIM.core.pendingTransaction) {
					switch(AIM.core.pendingTransaction.type) {
						case "textIM":
							AIM.transactions.sendTextIM(AIM.core.pendingTransaction.to,AIM.core.pendingTransaction.msg);
							AIM.core.pendingTransaction = null;
							break;
						case "sendDataIM":
							AIM.transactions.sendDataIM(AIM.core.pendingTransaction.to,AIM.core.pendingTransaction.msg,AIM.core.pendingTransaction.cap,AIM.core.pendingTransaction.dType);
							AIM.core.pendingTransaction = null;
							break;
						case "setState":
							AIM.transactions.setAwayMessage(AIM.params.text.awayMessage); 
							AIM.core.prendingTransaction = null;
							break;
					}
					/*
					if(AIM.core.pendingTransaction.type == "textIM") {
						AIM.transactions.sendTextIM(AIM.core.pendingTransaction.to,AIM.core.pendingTransaction.msg);
						AIM.core.pendingTransaction = null;
					} else {
						AIM.transactions.sendDataIM(AIM.core.pendingTransaction.to,AIM.core.pendingTransaction.msg,AIM.core.pendingTransaction.cap,AIM.core.pendingTransaction.dType);
						AIM.core.pendingTransaction = null;
					}
					*/
				}
			}
			else if (oLoc.indexOf("#CONSENTINVALIDTOKEN")>-1) {
				AIM.transactions.endSession();
				alert("Please log in again!");
			}
			location.replace("#");
			document.getElementById("AIMFrameContainer_AIMwindow").style.display = "none"
			//document.getElementById("AIMFrameContainer_AIMwindow").parentNode.removeChild(document.getElementById("AIMFrameContainer_AIMwindow"));
		} else {
			clearTimeout(oTimeout);
			var oTimeout = setTimeout(AIM.core.watchAuthRequest,500);
		}
	},
	
	/**
	*	Sends a request to the host, i.e, an instant message, status update, etc.
	*	@param { Object } transactionObject An object defined by the AIM.transactions.* methods with properties required by the transaction
	*/
	requestData: function(transactionObject) {
		var len = AIM.core.AIMData.length;
		transactionObject.timestamp = Date.parse(new Date());
		AIM.core.AIMData[len] = {}; 
		AIM.core.AIMData[len].oScript = document.createElement("script");
		AIM.core.AIMData[len].oScript.setAttribute("id","AIMBuddyList-AIMData-" + len);
		AIM.core.AIMData[len].oScript.setAttribute("type","text/javascript");
		AIM.core.AIMData[len].objData = transactionObject;
		if(transactionObject.dataURI.indexOf("?") == -1) {
			transactionObject.dataURI+="?r=" + len + "&nocache=" + Date.parse(new Date());
		} else {
			transactionObject.dataURI+="&r=" + len + "&nocache=" + Date.parse(new Date());
		}
        AIM.core.debug("requestData: " + transactionObject.dataURI);
		AIM.core.AIMData[len].oScript.setAttribute("src",transactionObject.dataURI);
		document.getElementsByTagName("head")[0].appendChild(AIM.core.AIMData[len].oScript);
	},
	
	/**
	*	Accepts all the incoming responses that result from requestData and routes them to the appropriate callback(s)
	*	@param { Object } json The JSON response from the host.
	*/
	acceptData:function(json) {
		var requestId = parseInt(json.response.requestId);
		var code = parseInt(json.response.statusCode);
		if(code != 200) {
			AIM.core.debug("AIM.core.acceptData: Response Error! Code is " + code + "(" + AIM.params.text.errors.serverErrors[code] + "), transaction was " + AIM.core.AIMData[requestId].objData.type);
			if(code == 401) { 
				var t = AIM.core.AIMData[requestId].objData.type;
				// only these transactions expect a 401 - if we get it on any other, kill the session
				if(t != "getToken" && t != "startSession" && t != "endSession") {
					AIM.params.sessionId = null;
					AIM.params.token = null;
					AIM.transactions.endSession();
					AIM.transactions.getToken(AIM.core.subscriptions);
				}
			}
		}
		try{ 
			AIM.core.debug("<b>AIM.core.acceptData:</b><br />" + json.response.toSource());
		} catch(err) { }
		var type = AIM.core.AIMData[requestId].objData.type;
		fn = eval("AIM.params.callbacks." + type);
		var i = fn.length;
		while(i-- >0)	{
			try { 
				eval(fn[i] +"(json)"); 
			} catch(err) { 
				AIM.core.debug("AIM.core.acceptData: Callback error! " + err.message + " (line " + err.line + ")")
			}
		}		
		try {
			if(AIM.core.AIMData[requestId]) {
				if(AIM.core.AIMData[requestId].oScript) {
					// Following line will cause IE to crash on a reload of the page and a relaunch of the app...go figure.
					if(!AIM.params.MSIE) AIM.core.AIMData[requestId].oScript.parentNode.removeChild(AIM.core.AIMData[requestId].oScript);
				}
			}
		} catch(err) {
			AIM.core.debug("AIM.core.acceptData: Unable to remove AIM.core.AIMData[" + requestId + "] -- " + err.message);
		}
	},
	/**
	*	Listens for event updates (i.e., a buddy signs off) from the host and routes to the appropriate callback(s)
	*	@param { Object } json The JSON response from the host.
	*/
	listen:function(json) {
		if(!json.response.data) return;
		//if(json.response.data.events.length == 0) return;
		AIM.core.destroyListenerObject(false);
		AIM.params.listenerURI = json.response.data.fetchBaseURL + "&f=json&c=AIM%2Ecore%2Elisten&timeout=" + AIM.params.REQUEST_TIMEOUT;
		if(json.response.data.events) {
				try {
				if(json.response.data.events.length > 0) AIM.core.debug("<b>AIM.core.listen:</b><br />" + json.response.data.toSource());
				} catch(err) { }
				if(json.response.statusCode == 200) {
					json.response.data.events = json.response.data.events.reverse();
					var i = json.response.data.events.length;
					while(i-- > 0) {
						AIM.core.debug("<b>AIM.core.listen</b>:" +  json.response.data.events[i].type);
						fn = eval("AIM.params.callbacks.listener." + json.response.data.events[i].type);
						var j = fn.length;
						while(j-- > 0) {
							try {
								var oResponse = json.response.data.events[i].eventData;
								eval(fn[j] +"(oResponse)");
							} catch(err) { 
								AIM.core.debug("AIM.core.listen: Callback Error! " + err.message + " (line " + err.line + ")");
							}
						}
					}
				} else {
					// kill the session on a non 200 listener response?
				}
			}
		AIM.core.requestInterval = setTimeout("AIM.core.destroyListenerObject(true)",json.response.data.timeToNextFetch);
	},
	
	/**
	*	Creates a script element that "listens" for event updates from the host.
	*/
	createListenerObject:function() {
		clearTimeout(AIM.core.requestInterval);
		AIM.core.destroyListenerObject(false);
		var oListener = document.createElement("script");
		oListener.setAttribute("type","text/javascript");
		oListener.setAttribute("src", AIM.params.listenerURI + "&"  + Date.parse(new Date()));
		oListener.setAttribute("id","AIMListener");
		document.getElementsByTagName("head")[0].appendChild(oListener);
	},
	
	/**
	*	Destroys the data container object that houses the script element that made the request and all data associated with it.
	*	@param { Variant } objIndex The index in the AIM.core.AIMData array that corresponds to the data to be destroyed. This is generally the requestId property of the JSON response
	*/
	destroyDataObject:function(objIndex) {
		return; // this function causing FF to crash all of a sudden...I hate teh intarwebs
		try {
			if(AIM.core.AIMData[objIndex]) {
				if(AIM.core.AIMData[objIndex].oScript) AIM.core.AIMData[objIndex].oScript.parentNode.removeChild(AIM.core.AIMData[objIndex].oScript);
			}
		} catch(err) { }
		AIM.core.AIMData[objIndex] = null;
	},
	
	/**
	*	Destroys the listener script object, and creates a new one if createNew is true.
	*	@param { Boolean } createNew Creates a new listener if true.
	*/
	destroyListenerObject: function(createNew) {
		clearInterval(AIM.core.requestInterval);
		if(document.getElementById("AIMListener")) document.getElementById("AIMListener").parentNode.removeChild(document.getElementById("AIMListener"));
		if(createNew) AIM.core.createListenerObject();
	},
	
	/**
	*	Adds a callback to the callback object
	*	@param { Array } callbackObject The array that contains the callback functions for the event
	*	@param {String } newCallBack The name of the function to be called.
	*/
	addCallback: function(callbackObject,newCallback) {
		callbackObject.push(newCallback);
	},
	
	/**
	*	Removes a callback from the specified callback array
	*	@param { Array } callbackObject The array that contains the callback function to be removed
	*	@param { String } oldCallback The callback to be removed
	*/
	removeCallback: function(callbackObject,oldCallback) {
		callbackObject.splice(callbackObject.indexOf(oldCallback));
	},
	
	/**
	*	General debug function for the application. Appends a DIV to the body element with an id of "AIMDebugger" and writes out debug data if the DEBUG param is true.
	*	@param { String } str The string to be written out to the debug div.
	*/
	debug: function(str) {
		if(!AIM.params.DEBUG) return;
		if(!document.getElementById("AIMDebugger")) {
			var dbg = document.getElementsByTagName("body")[0].appendChild(document.createElement("div"));
			dbg.setAttribute("id","AIMDebugger");
		}
		document.getElementById("AIMDebugger").innerHTML = "<p><span style=\"color:green;\">" + new Date() + "(" + Date.parse(new Date()) + ")</span><br />" + str + "</p>" + document.getElementById("AIMDebugger").innerHTML;
	}
}

/**
*	Callback object contains all the functions that AIM.core.acceptData will call upon receiving a host response
*	for the given event type.
*/
AIM.callbacks = {
	/**
	*	The callback for a buddyList event
	*	@param { Object } json The json response from the host
	*/
	getBuddyList:function(json) {
		var i = response.events.length;
		while(i-- > 0) {
			if("getBuddyList" in response.events[i]) {
				var rIndex = i; 
				break;
			}
		}
		AIM.ui.createBuddyList(response.events[rIndex].getBuddyList);
	},
	
	/**
	*	Called when an endSession event is received. Destroys IM windows, resets session data and cleans up.
	*	@param { Object } json The JSON response from the host
	*/
	endSession:function(json) {
		var buddyList = document.getElementById('AIMBuddyList');
		if (buddyList != null)
			buddyList.parentNode.removeChild(buddyList);
		AIM.core.destroyDataObject(json.response.requestId);
		AIM.core.destroyListenerObject(false);
		AIM.ui.destroyAllIMWindows();
		AIM.core.activeSession = false;
		AIM.util.cleanUp();
	},
	
	/**
	*	Requests a new session if a token is granted, otherwise pops the authorization window so the user can log in.
	*	@param { Object } json The JSON response from the host
	*/
	getToken: function(json) {
		if(json.response.statusCode == 200) {
			//AIM.params.user = json.response.data.token.loginId;
			AIM.params.token = json.response.data.token.a;
			AIM.transactions.startSession(AIM.core.AIMData[json.response.requestId].objData.eventList);
		} else if (json.response.statusCode == 450 || json.response.statusCode == 401 || json.response.statusCode == 330) {
			AIM.core.createAuthWindow(json.response.data.redirectURL + "?k=" + AIM.params.wimKey);
		} else {
			alert(AIM.params.text.errors.serverErrors[json.response.statusCode]);
		} 
		// for some reason the following line will crash IE6.
		//AIM.core.destroyDataObject(json.response.requestId);
	},
	
	/**
	*	Starts a new session on a successful startSession request. Creates the listener object for the session.
	*	@param { Object } json The JSON response from the host.
	*/
	startSession: function(json) {
		if(json.response.statusCode == 200) {
			//if(AIM.params.user == "undefined") 
			AIM.params.user = json.response.data.myInfo.displayId;
			AIM.params.sessionId = json.response.data.aimsid;
			AIM.params.listenerURI = json.response.data.fetchBaseURL + "&f=json&c=AIM%2Ecore%2Elisten&timeout=" + AIM.params.REQUEST_TIMEOUT;
			AIM.core.destroyListenerObject(true);
			AIM.core.activeSession = true;
		} else if(json.response.statusCode == 450) {
			AIM.core.createAuthWindow(json.response.data.redirectURL + "&k=" + AIM.params.wimKey);
		} else if (json.response.statusCode == 451) {
			AIM.transactions.endSession();
			return alert(AIM.params.text.permissionDenied);
		} else {
			AIM.core.debug("Unable to start a session. Code is " + json.response.statusCode);
			AIM.transactions.endSession();
			actionUnableToStartSession();
			//alert(AIM.params.text.startSessionFailed);
		}
		AIM.core.destroyDataObject(json.response.requestId);
	},
	
	/**
	*	Called when the session ends, kills the buddy list widget
	*/
	sessionEnded: function() {
		if(document.getElementById("AIMBuddyList")) {
			AIM.widgets.buddyList.kill();
		}
	},
	
	/**
	*	Callback for sendTextIM. Populates the message window upon success, or prompts the user for permission if needed.
	*	@param { Object } json The JSON response from the host.
	*/
	sendTextIM: function(json) {
		if(json.response.statusCode != 450) {
			AIM.ui.populateMessageWindow(json);
			AIM.core.destroyDataObject(json.response.requestId);
		} else if(json.response.statusCode == 450) {
			AIM.core.createAuthWindow(json.response.data.redirectURL + "&k=" + AIM.params.wimKey);
			var winID = decodeURIComponent(AIM.core.AIMData[json.response.requestId].objData.to);
			if(winID.indexOf("+") == 0) winID = winID.replace(/\+/,"SMS");
			AIM.core.debug("AIM.callbacks.sendTextIM: winID == " + winID);
			AIM.core.destroyListenerObject(false);
			AIM.core.pendingTransaction = {
				msg:decodeURIComponent(AIM.core.AIMData[json.response.requestId].objData.msg),
				to:winID,
				type:"textIM"
			}
		} else if(json.response.statusCode == 451) {
			return alert(AIM.params.text.permissionDenied);
		}
	},
	
	sendDataIM: function(json) {
		
	},
	
	acceptDataIM: function(json) {
	
	},
	
	getBuddyInfo: function(response) {
		AIM.ui.showBuddyInfo(response.data);
	},
	
	/**
	*	Updates the UI after the user changes their online status.
	*	@param { Object } json The json response from the host.
	*/
	setState: function(json) {
		if(json.response.statusCode == 450) {
			AIM.core.pendingTransaction = {
				type:"setState"
			}
			AIM.core.createAuthWindow(json.response.data.redirectURL + "&k=" + AIM.params.wimKey);
			return;
		}
		var oElements = AIM.util.getElementsByClassName(document.getElementsByTagName("body")[0],"span","AIMBuddyListAvailabilityMenuActionPoint");
		switch(AIM.util.currentState) {
			case 0:
				var remover = ["Available","Invisible"];
				var text = AIM.params.text.availabilityMenuAwayText;
				var adder = "Away";
				break;
			case 1:
				var remover = ["Away","Invisible"];
				var text = AIM.params.text.availabilityMenuAvailableText;
				var adder = "Available";
				AIM.util.resetUserNotified();
				break;
			case 2:
				var remover = ["Away","Available"];
				var text = AIM.params.text.availabilityMenuInvisibleText;
				var adder = "Invisible";
				break;
		}
		var i = oElements.length;
		while(i-- > 0) {
			oElements[i].innerHTML = text;
			AIM.util.removeClass(oElements[i].parentNode,"AIMBuddyListAvailabilityMenu" + remover[0]);
			AIM.util.removeClass(oElements[i].parentNode,"AIMBuddyListAvailabilityMenu" + remover[1]);
			AIM.util.addClass(oElements[i].parentNode,"AIMBuddyListAvailabilityMenu" + adder);
		}
		AIM.core.destroyDataObject(json.response.requestId);
	},
	
	/**
	*	Simply a callback to destroy the typing status data object when the status returns.
	*	@param { Object } json The JSON response from the host.
	*/
	typingStatus: function(json) {
		AIM.core.destroyDataObject(json.response.requestId);
	},
	
	/**
	*	Updates the UI with data from getPresenceInfo
	*	@param { Object } json The JSON response from the host
	*/
	getPresenceInfo: function(json) {
		AIM.ui.updatePresenceWidgets(json.response.data.users);
		AIM.core.destroyDataObject(json.response.requestId);
	}
}

/**
*	User interface helper methods.
*/
AIM.ui = {
	userIconSrc:null,
	storedBuddyInfo:[],
	winZIndex:10000,
	activeWindow:null,
	
	/**
	*	Creates a window that allows the user to define a custom away message.
	*/
	createAwayMessage: function() {
		if(!document.getElementById("AIMBuddyListAwayBox_AIMwindow")) {
			var awayBox = AIM.ui.createWindowFrame("AIMBuddyListAwayBox","AIMBuddyListAwayBox",AIM.params.text.awayMessageWindowTitle);
			var p = document.createElement("p");
			p.appendChild(document.createTextNode(AIM.params.text.awayMessageWindowInstructions));
		
			var txt = document.createElement("input");
			txt.setAttribute("type","text");
			txt.setAttribute("maxlength","1024");
			txt.setAttribute("id","AIMBuddyListAwayMessageInput");
			txt.className = "AIMBuddyListIMWindowTextInput";
			txt.onkeyup = function(e) {
				keyCode = window.event?event.keyCode:e.keyCode;
				if(keyCode == 13) {
					AIM.transactions.setAwayMessage(this.value); 
					this.parentNode.style.display = "none";
				}
			}
			var btn = document.createElement("button");
			btn.setAttribute("type","button");
			btn.appendChild(document.createTextNode("Ok"));
			AIM.util.addEvent(btn,function() { AIM.transactions.setAwayMessage(document.getElementById("AIMBuddyListAwayMessageInput").value); this.parentNode.style.display = "none"; },"click");

			btn.className = "AIMBuddyListIMWindowButton";
		
			awayBox.appendChild(p);
			awayBox.appendChild(txt);
			awayBox.appendChild(btn);
			document.getElementById("AIMBuddyListContainer").appendChild(awayBox);
			//awayBox.style.top = ((AIM.util.getScrollOffset(1) + document.getElementById("AIMBuddyListContainer").offsetTop)) + "px";
			//awayBox.style.left = document.getElementById("AIMBuddyListContainer").offsetWidth + 15 + "px";
		}
		var ab = document.getElementById("AIMBuddyListAwayBox_AIMwindow");
		ab.style.display = "block";
		ab.style.zIndex = "19000";
		ab.style.top = ((AIM.util.getScrollOffset(1) + document.getElementById("AIMBuddyListContainer").offsetTop)) + "px";
		ab.style.left = document.getElementById("AIMBuddyListContainer").offsetWidth + 15 + "px";
	},
	
	/**
	*	Renders the buddy list
	*	@param { Object } data The JSON response from the host.
	*/
	createBuddyList: function(data) {
		actionStartCreateBuddyList();
		var createStart = Date.parse(new Date());
		if(document.getElementById("AIMBuddyList")) document.getElementById("AIMBuddyList").parentNode.removeChild(document.getElementById("AIMBuddyList"));
	
		var ul = document.createElement("ul");
		ul.setAttribute("id","AIMBuddyList");
		
		var br = document.createElement("div");
		br.className = "AIMBuddyListBranding";
		br.setAttribute("id","AIMBuddyListBrandingArea");
		br.appendChild(document.createTextNode(AIM.params.text.brandingText));
		if(AIM.params.BUDDY_LIST_DRAG) {
			if(document.all) {
				br.onmousedown = function(e) { this.parentNode.style.position = "absolute"; AIM.util.captureOffset(); }
			} else {
				AIM.util.addEvent(br,AIM.util.captureOffset,"mousedown");
				AIM.util.addEvent(br,function() { this.parentNode.style.position = "absolute"; },"mousedown");
			}
			AIM.util.addEvent(br,function() { AIM.util.mDown = false; },"mouseup");
		}
		
		//ul.appendChild(br);
		if(!document.getElementById("AIMBuddyListBrandingArea")) document.getElementById("AIMBuddyListContainer").appendChild(br);
		
		var nameDiv = document.createElement("div");
		nameDiv.setAttribute('id', 'userNameDiv');
		nameDiv.className = "AIMBuddyListUserScreenName";
		nameDiv.appendChild(document.createTextNode(AIM.params.user));
		//var img = document.createElement('img');
		//img.setAttribute('src', AIM.params.DEFAULT_ICON);
		//nameDiv.appendChild(img);
		ul.appendChild(nameDiv);
		
		if(AIM.params.CREATE_AVAILABILITY_MENU_BL) {
			//document.getElementById("AIMBuddyListContainer").appendChild(AIM.ui.createAvailabilityMenu());
			ul.appendChild(AIM.ui.createAvailabilityMenu());
		}
		userIconSrc = data.buddyIcon;
		if (!userIconSrc)
			userIconSrc = data.icon;
		var groupings = data.groups;
		groupings = groupings.reverse();
		var glen = groupings.length;
		
		var headerState = AIM.util.cookie.get("headerState");
		if(headerState == null) {
			var k=0; headerState = "";
			while(k < glen) {
				headerState += "1"; 
				k++;
			}
			AIM.util.cookie.set("headerState",headerState,true);
		}
		headerState = headerState.split("");
		
		var i = glen;
		while(--i >= 0) {
			var li = document.createElement("li");
			var h2 = document.createElement("h2");
			
			h2.appendChild(document.createTextNode(groupings[i].name));
			h2.xindex = i;
			AIM.util.addEvent(h2,function() { AIM.ui.setHeaderState(this); },"click");
			h2.className = "AIMBuddyListHeading";
			li.appendChild(h2);
			
			var sul = document.createElement("ul");

			if(parseInt(headerState[i]) == 1 || typeof(headerState[i]) == "undefined") {
				sul.style.display = "block";
			} else {
				sul.style.display = "none";
			}
			var bClassName = groupings[i].name;
			bClassName = bClassName.replace(/ /g,"");
			sul.className = "AIMBuddyListGroup " + bClassName;
			sul.className += i%2?" AIMBuddyListGroupEven":" AIMBuddyListGroupOdd";
			
			var buddies = groupings[i].buddies;
			
			var blen = buddies?buddies.length:0;
			if(blen) buddies = buddies.reverse();
			var j = blen;
			while(--j >= 0) {
				var sli = document.createElement("li");
				var oGroupings = groupings[i].name.replace(/ /g,"_");
				sli.setAttribute("id", oGroupings + "_" + buddies[j].aimId);
				sli.setAttribute("wim_id",buddies[j].aimId);
				sli.setAttribute("wim_last_update",0);
				sli.setAttribute("wim_timestamp", Date.parse(new Date())/1000);
				sli.className = "buddy " + buddies[j].state + " " + buddies[j].aimId;
				sli.appendChild(document.createTextNode(buddies[j].displayId));
				if(!AIM.params.SHOW_OFFLINE && buddies[j].state == "offline") sli.style.display = "none";
				sul.appendChild(sli);
				buddies[j].timestamp = Date.parse(new Date())/1000;
				AIM.ui.storedBuddyInfo[buddies[j].aimId] = buddies[j];
				
				AIM.util.addEvent(sli,AIM.eventHandlers.handleMouseover,"mouseover");
				AIM.util.addEvent(sli,AIM.eventHandlers.handleMouseout,"mouseout");
				AIM.util.addEvent(sli,AIM.eventHandlers.handleClick,"click");
			}
			li.appendChild(sul);
			ul.appendChild(li);
		}
		document.getElementById("AIMBuddyListContainer").appendChild(ul);
		AIM.ui.zebraStripeList(AIM.util.getAIMIDCollection(document.getElementById("AIMBuddyList").parentNode));
		document.getElementById("AIMBuddyList").style.display = "block";
		document.getElementById("AIMBuddyListContainer").style.display = "block";
		var createTotal = Date.parse(new Date()) - createStart;
		AIM.core.debug("AIM.ui.createBuddyList: creation took " + createTotal + "ms");
		actionEndCreateBuddyList();
	},
	
	/**
	*	Creates the availability menu that contains things like "Set Away Message", "Send an IM" etc.
	*	Menu items and actions are defined in the AIM.params.text.availabilityMenu construct.
	*/
	createAvailabilityMenu:function() {
		var avMenu = document.createElement("div");
		avMenu.className = "AIMBuddyListAvailabilityMenu";
		AIM.util.addClass(avMenu,AIM.util.currentState?"AIMBuddyListAvailabilityMenuAvailable":"AIMBuddyListAvailabilityMenuAway");
		var sp = document.createElement("span");
		sp.className = "AIMBuddyListAvailabilityMenuActionPoint";
		sp.appendChild(document.createTextNode(AIM.util.currentState?AIM.params.text.availabilityMenuAvailableText:AIM.params.text.availabilityMenuAwayText));
		avMenu.appendChild(sp);
		
		avMenuFN = function() { 
			var sm = this.getElementsByTagName("ul")[0];
			if(sm.style.display == "none") {
				sm.style.display = "block";
			} else {
				sm.style.display = "none";
			}
		}
		
		AIM.util.addEvent(avMenu,avMenuFN,"click");
		
		var avSubMenu = document.createElement("ul");
		avSubMenu.className = "AIMBuddyListAvailabilitySubMenu";
		avSubMenu.style.display = "none";
		
		for(var i in AIM.params.text.availabilityMenuItems) {
			if(AIM.params.text.availabilityMenuItems[i].text != null) {
				var n = document.createElement("li");
				n.className = "AIMBuddyListMenuItem";
				n.appendChild(document.createTextNode(AIM.params.text.availabilityMenuItems[i].text));
				if(AIM.params.text.availabilityMenuItems[i].cls) AIM.util.addClass(n,AIM.params.text.availabilityMenuItems[i].cls);
				n.xonclick =AIM.params.text.availabilityMenuItems[i].method;
				AIM.util.addEvent(n,AIM.eventHandlers.handleClick,"click");
			} else {
				var n = document.createElement("hr");
			}
			avSubMenu.appendChild(n);
		}
		avMenu.appendChild(avSubMenu);
		return avMenu;
	},
	
	/**
	*	Collapses or uncollapses a buddy group and sets a cookie to remember the state of the group.
	*/
	setHeaderState: function(header) {
		state = header.nextSibling.style.display == "block"?0:1;
		header.nextSibling.style.display = state?"block":"none";
		var hState = AIM.util.cookie.get("headerState").split("");
		hState[header.xindex] = state;
		hState = hState.toString().replace(/,/g,"");
		AIM.util.cookie.set("headerState",hState,true);
	},
	
	/**
	*	Sets the z-index of the activeWin argument above all other IM windows
	*	@param { String } activeWin The value of the id attribute of the window whos z-index needs to be raised.
	*/
	setIMWindowZIndex:function(activeWin) {
		try {
			if(activeWin.indexOf("+") == 0) activeWin = activeWin.replace(/\+/,"SMS");
			var oWin = document.getElementById(activeWin);
			AIM.ui.clearVisualNotification(activeWin);
			if(oWin.AIMTopWindow == "true") return;
			oWin.style.zIndex = AIM.ui.winZIndex;
			oWin.AIMTopWindow = "true";
			var windowCollection = AIM.ui.getIMWindows();
			var i = windowCollection.length;
			while(i-- > 0) {
				if(windowCollection[i].getAttribute("id") != activeWin) {
					windowCollection[i].style.zIndex = AIM.ui.winZIndex - 1;
					windowCollection[i].AIMTopWindow = "false";	
				}
			}
		} catch(err) {
			AIM.core.debug("AIM.ui.setIMWindowZIndex: " + err.message);
		}
	},
	
	/**
	*	Updates the UI to reflect the typing status of a buddy
	*	@param { Object } resonse The JSON response from the host.
	*/
	updateTypingStatus: function(response) {
		return;
		if(!document.getElementById(response.aimId + "_typingStatus") && response.event != "none") return;
		var winID = response.aimId;
		if(winID.indexOf("+") == 0) winID = winID.replace(/\+/,"SMS");
		var obj = document.getElementById(winID + "_typingStatus");
		switch(response.typingStatus) {
			case "typing":
				obj.innerHTML = AIM.params.text.userTyping;
				obj.className = "AIMBuddyListTypingStatusTyping";
				break;
			case "typed":
				obj.innerHTML =  AIM.params.text.userTyped;
				obj.className = "AIMBuddyListTypingStatusTyped";
				break;
			case "none":
				obj.innerHTML = AIM.params.text.userStoppedTyping;
				obj.className = "AIMBuddyListTypingStatusStoppedTyping";
				break;
		}
	},
	
	/**
	*	Updates the Buddy List UI when users sign on/off, go away, etc.
	*	@param { Object } response The JSON response from the host.
	*/
	updateBuddyList: function(response) {
		var aimIds = AIM.util.getElementsByAIMID(response.aimId,document.getElementById("AIMBuddyListContainer"));
		var i = aimIds.length;
		while(i-- > 0) {
			AIM.util.removeClass(aimIds[i],"online");
			AIM.util.removeClass(aimIds[i],"idle");
			AIM.util.removeClass(aimIds[i],"away");
			AIM.util.removeClass(aimIds[i],"mobile");
			AIM.util.removeClass(aimIds[i],"offline");
			AIM.util.addClass(aimIds[i],response.state);
			
			if(response.state != "offline") {
				aimIds[i].style.display = "block";
				aimIds[i].setAttribute("wim_timestamp",	Date.parse(new Date())/1000);
			} else {
				aimIds[i].style.display = AIM.params.SHOW_OFFLINE?"block":"none";
			}
			
			if(response.displayId != aimIds[i].innerHTML.trim()) aimIds[i].innerHTML = response.displayId;
		}
		response.timestamp = Date.parse(new Date())/1000;
		AIM.ui.storedBuddyInfo[response.aimId] = response;
		AIM.ui.zebraStripeList(AIM.util.getAIMIDCollection(document.getElementById("AIMBuddyListContainer")));
	},
	
	/**
	*	Updates presence widgets DOM-wide to reflect their owners current status
	*	@param { Array } users The id's of the users who's status we're updating.
	*/
	updatePresenceWidgets: function(users) {
		var i = users.length;
		while(i-->0) {
			var oElements = AIM.util.getElementsByClassName(document.body,"*",users[i].aimId);
			AIM.ui.storedBuddyInfo[users[i].aimId] = users[i];
			var j = oElements.length;
			while(j-->0) {
				AIM.util.removeClass(oElements[j],"AIMPresenceWidget_online");
				AIM.util.removeClass(oElements[j],"AIMPresenceWidget_offline");
				AIM.util.removeClass(oElements[j],"AIMPresenceWidget_away");
				AIM.util.addClass(oElements[j],"AIMPresenceWidget_" + users[i].state);
				//oElements[j].setAttribute("title",users[i].displayId + " is " + users[i].state);
				oElements[j].setAttribute("wim_id",users[i].aimId);
				if(users[i].state != "offline") {
					AIM.util.addEvent(oElements[j],AIM.eventHandlers.handleMouseover,"mouseover");
				} else {
					oElements[j].setAttribute("title",users[i].displayId + " is not available right now.");
				}
			}
		}
		AIM.ui.prepBuddyInfo(document.body);
	},
	
	/**
	*	Prompts a dialog to allow the user to enter any ID they wish to send an IM to.
	*	@param { String } aimId The id of the user to send the IM to.
	*/
	aimIdPrompt: function(aimId) {
		if(!document.getElementById("AIMIDPrompt_AIMwindow")) {
			var win = AIM.ui.createWindowFrame("AIMIDPrompt","AIMBuddyListAwayBox","Send IM");
			var p = document.createElement("p");
			p.appendChild(document.createTextNode(AIM.params.text.aimIdPromptMessage));
			
			var txt = document.createElement("input");
			txt.setAttribute("type","text");
			txt.setAttribute("maxlength","96");
			txt.setAttribute("id","AIMIDInput");
			txt.className = "AIMBuddyListIMWindowTextInput";
			txt.value = aimId
			txt.onkeyup = function(e) {
				var keyCode = window.event?event.keyCode:e.keyCode;
				if(keyCode == 13) {
					var aimId = this.value.trim();
					if(aimId != "") AIM.ui.createIMWindow(aimId.toLowerCase());
					document.getElementById("AIMIDPrompt_AIMwindow").style.display = "none";
				}
			}
			
			var btn = document.createElement("button");
			btn.setAttribute("type","button");
			btn.appendChild(document.createTextNode("Ok"));
			var fn = function() {
				var aimId = document.getElementById("AIMIDInput").value.trim();
				if(aimId != "") AIM.ui.createIMWindow(aimId.toLowerCase());
				document.getElementById("AIMIDPrompt_AIMwindow").style.display = "none";
			}
			AIM.util.addEvent(btn,fn,"click");

			win.appendChild(p);
			win.appendChild(txt);
			win.appendChild(btn);
			document.getElementById("AIMBuddyListContainer").appendChild(win);
			//win.style.left = (document.getElementsByTagName("body")[0].offsetWidth - win.offsetWidth)/2 + "px";
			//win.style.top = "10px";
			btn.className = "AIMBuddyListIMWindowButton";
			
		}
		var ab = document.getElementById("AIMIDPrompt_AIMwindow");
		ab.style.zIndex = "19000";
		//ab.style.top = ((AIM.util.getScrollOffset(1) + document.getElementById("AIMBuddyListContainer").offsetTop)) + "px";
		//ab.style.left = document.getElementById("AIMBuddyListContainer").offsetWidth + 15 + "px";
		ab.style.position = 'absolute';
		ab.style.top = '25px';
		ab.style.left = '165px';
		ab.style.display = "block";
	},
	
	/**
	*	Accepts an incoming instant mesage and routes it to the appropriate window.
	*	@param { Object } response The JSON response from the host.
	*/
	acceptIncomingMessage:function(response) {
		var aimId = response.source.aimId;
		var winID = aimId;
		if(winID.indexOf("+") == 0) winID = winID.replace(/\+/,"SMS");
		AIM.ui.createIMWindow(aimId);
		AIM.ui.populateIncomingMessageWindow(response);
		document.getElementById(winID + "_typingStatus").innerHTML = "";
		if(AIM.params.VISUAL_NOTIFICATION) {
			document.title = "IM received from " + aimId;
			if(!AIM.util.visualNotificationTimer[winID + "_AIMwindow"]) AIM.ui.showVisualNotification(winID + "_AIMwindow");
		}
		
		if(!AIM.util.currentState && !AIM.util.userNotified[aimId]) {
			var msg = AIM.params.text.awayMessage;
			try { 
				msg = decodeURIComponent(msg); 
			} catch(err) { }
			AIM.transactions.sendTextIM(aimId, AIM.params.text.autoReplyNotice + " " + msg);
			AIM.util.userNotified[aimId] = true;
		}
	},
	
	/**
	*	Gives access to all of the currently open IM windows
	*	@returns An array containing object references to all of the windows.
	*	@type Array
	*/
	getIMWindows:function() {
		var winArray = document.getElementById("AIMBuddyListContainer").getElementsByTagName("div");
		var collection = [];
		var i = winArray.length;
		while(i-- > 0) if(winArray[i].getAttribute("id")) if(winArray[i].getAttribute("id").indexOf("_AIMwindow") != -1) collection.push(winArray[i]);	
		return collection;
	},
		
	/**
	*	Iterates through the buddy list, applying even/odd classes to elements in the buddy list
	*	@param { Array } objectCollection An array of objects to loop over an apply the classes to.
	*/
	zebraStripeList: function(objectCollection) {
		var i = objectCollection.length;
		var tracker = i;
		while(i-- > 0) {
			if(objectCollection[i].style.display != "none") {
				var obj = objectCollection[i];
				AIM.util.removeClass(obj,"even");
				AIM.util.removeClass(obj,"odd");
				AIM.util.addClass(obj,tracker%2?"even":"odd");
				tracker--;
			}
		}
	},
	
	/**
	*	Called on an interval that sets the title bar of the IM window to On/Off to act as a notification that an IM has been recieved.
	*	@param { String } windowId The id of the window to apply the notification to.
	*/
	showVisualNotification: function(windowId) {
		var win = document.getElementById(windowId);
		if(!win) return;
		var h2 = win.getElementsByTagName("h2")[0];
		if(h2.className.indexOf("AIMBuddyListIMWindowNotifyOff") > -1) {
			AIM.util.removeClass(h2,"AIMBuddyListIMWindowNotifyOff");
			AIM.util.addClass(h2,"AIMBuddyListIMWindowNotifyOn");
		} else {
			AIM.util.removeClass(h2,"AIMBuddyListIMWindowNotifyOn");
			AIM.util.addClass(h2,"AIMBuddyListIMWindowNotifyOff");
		}

		var fn = function() { AIM.ui.showVisualNotification(windowId); }
		AIM.util.visualNotificationTimer[windowId] = setTimeout(fn,AIM.params.NOTIFICATION_THROB);
	},
	
	/**
	*	Clears the running notification interval and sets the window title bar back to normal.
	*	@param { String } windowId The id of the window to clear the notification from.
	*/
	clearVisualNotification: function(windowId) {
		var win = document.getElementById(windowId);
		if(win) {
			var h2 = win.getElementsByTagName("h2")[0];
			h2.className = "AIMBuddyListWindowTitleBar";
		}
		clearTimeout(AIM.util.visualNotificationTimer[windowId]);
		document.title = AIM.params.DOCUMENT_TITLE;
		AIM.util.visualNotificationTimer[windowId] = null;
	},
	
	/**
	*	Populates the hovering element that displays buddy information like Away Message, Profile, etc.
	*	@param { Object } buddyInfo Contains all of the relevant data about the user. Comes from a presence update and initial buddy list event update.
	*/
	showBuddyInfo:function(buddyInfo) {
		var AIMInfo = document.getElementById("AIMBuddyListBuddyInfo");
		if (AIMInfo == null)
			return;
		if (buddyInfo == null)
			return;
		if(!buddyInfo.icon) {
			if(buddyInfo.buddyIcon)
				buddyInfo.icon = buddyInfo.buddyIcon;
		}
		if(!buddyInfo.icon && !buddyInfo.buddyIcon) buddyInfo.icon = AIM.params.DEFAULT_ICON;
		if(buddyInfo.state == "offline") {
			var mHTML = "<table cellpadding=\"2\" cellspacing=\"0\"><tr><td>" + buddyInfo.displayId + " is not currently signed on.</td></tr></table>"
		} else {
			if(buddyInfo.state == "mobile") {
				var onlineTime = "";
			} else {
				var elapsedSinceLaunch = (Date.parse(new Date()) / 1000) - buddyInfo.timestamp;
				var oTime = buddyInfo.onlineTime + elapsedSinceLaunch;
				var onlineTime = "<br />Online For: " + AIM.util.elapsedFromSeconds(oTime);
			}
			
			if(buddyInfo.idleTime) {
				//var elapsedSinceLaunch = (Date.parse(new Date()) / 1000) - buddyInfo.timestamp;
				//var oTime = buddyInfo.idleTime + elapsedSinceLaunch;
				if(buddyInfo.idleTime >=60) {
					var oTime = Math.floor(buddyInfo.idleTime/60) + " hours.";
				} else {
					var oTime = buddyInfo.idleTime + " minutes.";
				}
				var oIdleTime = "<br />Idle For: " + oTime;
			} else {
				var oIdleTime = "";
			}
			
			var mHTML = "<table cellpadding=\"2\" cellspacing=\"0\"><tr><td><img src=\"" + buddyInfo.icon + "\" width=\"48\" height=\"48\" alt=\"Buddy Icon\" /></td>"
			mHTML += "<td><b>" + buddyInfo.displayId + "</b>" + onlineTime + oIdleTime + "</td></tr>";
	
			if(buddyInfo.profileMsg) mHTML += "<tr><td><b>Profile</b></td><td><p>" + buddyInfo.profileMsg + "</p></td></tr>";
			mHTML+="</table>";
			if(buddyInfo.awayMsg) {
				var elapsedSinceLaunch = (Date.parse(new Date()) / 1000) - buddyInfo.timestamp;
				var oTime = buddyInfo.awayTime + elapsedSinceLaunch;
				var oAwayTime = AIM.util.elapsedFromSeconds(oTime);
				var msg = buddyInfo.awayMsg;
				try { 
					//msg = unescape(msg);
					msg = decodeURIComponent(msg); 
				} catch(err) {
					//msg = unescape(msg);
				}
				mHTML +="<table cellpadding=\"0\" cellspacing=\"0\" class=\"away\"><tr valign=\"top\"><td width=\"33%\"><b>Away Message:</b></td><td>" + msg + "</td></tr><tr><td><b>Away For:</b></td><td>" + oAwayTime + "</td></tr></table>";
			}
		}
		AIMInfo.innerHTML = mHTML;
		AIMInfo.style.display = "block";
	},

	/**
	*	Populates the correct window with outgoing IMs once the host has responded after the IM is sent.
	*	@param { Object } json The JSON response from the host.
	*/
	populateMessageWindow:function(json) {
		var requestId = parseInt(json.response.requestId);
		var winSN = AIM.core.AIMData[requestId].objData.to;
		var winID = decodeURIComponent(winSN);
		if(winID.indexOf("+") == 0) winID = winID.replace(/\+/,"SMS");
		var msg = AIM.core.AIMData[requestId].objData.msg;
		try { 
			//msg = unescape(msg);
			msg = decodeURIComponent(msg); 
		} catch(err) {
			AIM.core.debug("AIM.ui.populateMessageWindow:" + err.message);
		}
				
		oSN = AIM.params.user;
	
		msg = AIM.util.formatMessage(msg);
		
		var msgWin = document.getElementById("AIMTextArea_" + winID)
		var xHTML = msgWin.innerHTML;
		oSN == AIM.params.user?clsName = "AIMBuddyListUser":clsName="AIMBuddyListUserBuddy";
		var ts = AIM.params.SHOW_TIMESTAMP?AIM.util.formatTimeStamp(new Date()):"";

		xHTML+= "<p class=\"even\"><b class=\"" + clsName + "\">" + oSN + ": </b><span class=\"AIMBuddyListTimeStamp\">" + ts + "</span> " + msg + "</p>";
		if(json.response.statusCode != 200) {
			msg = AIM.params.text.errors.serverErrors[json.response.statusCode];// + "(" + json.response.statusCode + ")";
			var sysmsg = "<p class=\"even\"><b class=\"AIMBuddyListUserBuddy\">System Message: </b><span class=\"AIMBuddyListTimeStamp\">" + ts + "</span> " + msg + "</p>";
			xHTML+=sysmsg;
		}
		msgWin.innerHTML = xHTML;
		msgWin.scrollTop = msgWin.scrollHeight;

		AIM.core.destroyDataObject(requestId);
	},
	
	
	/**
	*	Populates the correct IM window with incoming IMs
	*	@param { Object } response The JSON object from the host. Comes via the listener.
	*/
	populateIncomingMessageWindow: function(response) {
		var aimId = response.source.aimId
		var msg = response.message;
		if(msg.match(/<a ([^>]*)>([^<]*)<\/a>/g))  msg = msg.replace(/href/gi,"target=\"_blank\" href");
		if(msg.match(/<img ([^>]*)>/g))  {
			msg = msg.replace(/</g,"&lt;");
			msg = msg.replace(/>/g,"&gt;");
		}

		try {
			//msg = unescape(msg);
			msg = decodeURIComponent(msg);
		} catch(err) { }
		
		var winID = aimId;
		if(winID.indexOf("+") == 0) winID = winID.replace(/\+/,"SMS");
		var msgWin = document.getElementById("AIMTextArea_" + winID)
		var xHTML = msgWin.innerHTML;
		var ts = AIM.params.SHOW_TIMESTAMP?AIM.util.formatTimeStamp(new Date(parseInt(response.timestamp) * 1000)):"";
		
		if(msg.indexOf("<div") == 0) {
			var breaker = " style=\"display:inline;\"";
		} else {
			var breaker = "";
		}
		xHTML+= "<p class=\"odd\"" + breaker + "><b class=\"AIMBuddyListUserBuddy\">" + response.source.displayId + ": </b><span class=\"AIMBuddyListTimeStamp\">" + ts + "</span> " + msg + "</p>";
		if(msg.indexOf("<div") == 0) xHTML +="<br />";
		msgWin.innerHTML = xHTML;
		msgWin.scrollTop = msgWin.scrollHeight;
	},
	
	/**
	*	Creates the basic elements required for a window, and sets up drag and title.
	*	@param { String } identifier The "id" of the window.
	*	@param { String } clsName The "class" of the window.
	*	@param { String } winTitle The title of the window.
	*	@return { HTMLObject } An DIV element styled like a window, w/o content.
	*	@type HTMLObject
	*/
	createWindowFrame: function(identifier,clsName,winTitle) {
		var win = document.createElement("div");
		win.style.display = 'none';
		win.setAttribute("id",identifier + "_AIMwindow");
		win.style.zIndex = 10000;
		win.className = clsName;
		win.AIMTopWindow = "false";
		
		var h2 = document.createElement("h2");
		h2.appendChild(document.createTextNode(winTitle));
		AIM.util.addEvent(h2,AIM.util.captureOffset,"mousedown");
		h2.className = "AIMBuddyListWindowTitleBar";
		AIM.util.addEvent(h2,function() { AIM.util.mDown = false; AIM.util.removeClass(this.parentNode,"AIMBuddyListIMWindowDragState"); },"mouseup");
		win.appendChild(h2);
					
		var clBtn = document.createElement("div");
		clBtn.className = "AIMBuddyListWindowCloseButton";
		clBtn.setAttribute("title","Close this Window.");
		AIM.util.addEvent(clBtn,function() { AIM.ui.removeIMWindow(identifier + "_AIMwindow"); },"click");
		h2.appendChild(clBtn);
		win.style.display = 'block';
		return win;
	},
	
	/**
	*	Creates an IM window for the given id
	*	@param { String} aimId The ID of the user for whom to create the window.
	*/
	createIMWindow: function(aimId) {
		windowId = aimId;
		if(windowId.indexOf("+") == 0) windowId = windowId.replace(/\+/,"SMS");
		var IMWin = document.getElementById(windowId + "_AIMwindow")
		if(!IMWin) {
			var win = AIM.ui.createWindowFrame(windowId,"AIMBuddyListIMWindow",aimId);
			var txtArea = document.createElement("div");
			txtArea.className = "AIMBuddyListIMWindowTextArea";
			txtArea.setAttribute("id","AIMTextArea_" + windowId);
			
			var txtInput = document.createElement("input");
			txtInput.setAttribute("type","text");
			txtInput.className = "AIMBuddyListIMWindowTextInput";
			txtInput.setAttribute("id","AIMTextInput_" + windowId);
			txtInput.setAttribute("wim_aimId",aimId);
			txtInput.setAttribute("maxlength","1024");
			AIM.util.addEvent(txtInput,AIM.eventHandlers.handleKeyUp,"keyup");
			
			if(AIM.params.RENDER_SEND_BUTTON) {
				var okBtn = document.createElement("button");
				okBtn.setAttribute("type","button");
				okBtn.className="AIMBuddyListIMWindowButton";
				okBtn.setAttribute("id","AIMBuddyListIMWindowButton_" + windowId);
				okBtn.setAttribute("wim_aimId",aimId);
				okBtn.appendChild(document.createTextNode(AIM.params.text.sendButtonText));
				AIM.util.addEvent(okBtn,AIM.eventHandlers.handleClick,"click");
			}
			
			var typingStatus = document.createElement("span");
			typingStatus.className = "AIMBuddyListTypingStatus";
			typingStatus.setAttribute("id",windowId + "_typingStatus");
			
			if(AIM.params.CREATE_AVAILABILITY_MENU_IM) win.appendChild(AIM.ui.createAvailabilityMenu());
			win.appendChild(txtArea); 
			win.appendChild(txtInput); 
			if(AIM.params.RENDER_SEND_BUTTON) win.appendChild(okBtn);
			win.appendChild(typingStatus);
			
			win.style.position = 'absolute';
			var y = 50;
			var x = document.getElementById("AIMBuddyListContainer").offsetWidth + 15;
			var isOverlap = function(x,y) {
				var win = AIM.ui.getIMWindows();
				var i = win.length;
				while(i-->0)
					if (win[i].offsetLeft == x && win[i].offsetTop == y)
						return true;
				return false;
			}
			
			while(isOverlap(x,y)) {
				x+=20;
				y+=20;
			}
			win.style.top = y + "px";
			win.style.left = x + "px";
			AIM.util.addEvent(win,function() { AIM.ui.setIMWindowZIndex(this.getAttribute("id")); },"click");
			document.getElementById("AIMBuddyListContainer").appendChild(win);
			
		} 
		document.getElementById(windowId + "_AIMwindow").style.display = "block";
		/*
		try {
			document.getElementById("AIMTextInput_" + windowId).focus();
		} catch(err) {
			AIM.core.debug("AIM.ui.createIMWindow: " + err.message);
		}*/
	},
	
	/**
	*	Removes an IM window from view. Sets display to none if RETAIN_WINDOW is true, removes from the DOM otherwise.
	*	@param { String } windowId The id of the window to be removed.
	*/
	removeIMWindow: function(windowID) {
		var rWin = document.getElementById(windowID);
		if(AIM.params.RETAIN_WINDOW) {
			rWin.style.display = "none";
		} else {
			rWin.parentNode.removeChild(rWin);
		}
	},
	
	/**
	*	Removes all IM windows from view. Sets display to none if RETAIN_WINDOW is true, removes from the DOM otherwise.
	*/
	removeAllIMWindows: function() {
		var rWins = AIM.ui.getIMWindows();
		var i = rWins.length;
		while(i-- > 0) {
			if(AIM.params.RETAIN_WINDOW) {
				rWins[i].style.display = "none";
			} else {
				rWins[i].parentNode.removeChild(rWins[i]);
			}
		}
	},
	
	/**
	*	Destroys all IM windows, removing them from the DOM. Ignores RETAIN_WINDOW
	*/
	destroyAllIMWindows: function() {
		var rWins = AIM.ui.getIMWindows();
		var i = rWins.length;
		while(i-- > 0) rWins[i].parentNode.removeChild(rWins[i]);
	},
	
	/**
	*	Prepares the buddy info display element, placing it at the appropriate coordinates, etc.
	*	@param { Object } cObj The object that spawned the event, used to determine where to place the element for display.
	*/
	prepBuddyInfo: function(cObj) {
		var container = document.getElementById('AIMBuddyListContainer');
		var infoBox = document.getElementById('AIMBuddyListBuddyInfo');
		if (infoBox == null) {
			infoBox = document.createElement('div');
			infoBox.setAttribute('id', 'AIMBuddyListBuddyInfo');
			infoBox.style.zIndex = '20000';
			container.appendChild(infoBox);
			try {
				AIM.util.addEvent(container,
					function() {
						var box = document.getElementById('AIMBuddyListBuddyInfo');
						if (box != null)
							box.style.display = 'none';
					},
					'mouseout');
			}
			catch (e) {
			}
		}
		infoBox.style.position = 'absolute';
		var top = 0;
		var child = cObj;
		var root = document.getElementById('AIMBuddyListContainer');
		while (child != root) {
			top += child.offsetTop;
			child = child.offsetParent;
		}
		infoBox.style.top = (top-20)+'px';
		infoBox.style.left = '190px';
	}
}

/**
*	A set of methods for sending data to the host.
*/
AIM.transactions = {
	/**
	*	Sends a text IM
	*	@param { String } aimId The id to whom the IM should go.
	*	@param { String } txt The message to tbe sent.
	*/
	sendTextIM:function(aimId,txt) {
		if(txt.trim() == "") return;
		aimId = encodeURIComponent(aimId);
		txt = encodeURIComponent(txt);
		tObj = {
				dataURI:AIM.params.baseTransactionURI + AIM.params.transactions.sendTextIM + "?aimsid=" + AIM.params.sessionId + "&message=" + txt + "&t=" + aimId + "&f=json&c=AIM%2Ecore%2EacceptData&offlineIM=" + AIM.params.SEND_OFFLINE_IM,
				type:"sendTextIM",
				to:aimId,
				msg:txt
			}
		AIM.core.requestData(tObj);
	},
	 
	sendDataIM: function(aimId,data,cap,type) {
		if(data.trim() == "") return;
		var aimId = encodeURIComponent(aimId);
		var data = encodeURIComponent(data);
		var cap = encodeURIComponent(cap);
		var type = encodeURIComponent(type);
		
		var tObj = {
			dataURI: AIM.params.baseTransactionURI + AIM.params.transactions.sendDataIM + "?aimsid=" + AIM.params.sessionId + "&data=" + data + "&k=" + AIM.params.wimKey + "&t=" + aimId + "&type=" + type + "&cap=" + cap + "&f=json&c=AIM%2Ecore%2EacceptData",
			type:"sendDataIM",
			to:aimId,
			data:data,
			dType:type,
			cap:cap
		}		
		AIM.core.requestData(tObj);
	},
	
	/**
	*	Deprecated. Buddy list data is returned by the listener when a session begins and buddylist is subscribed to.
	*/
	getBuddyList:function() {
		var tObj = {
			dataURI:AIM.params.baseTransactionURI + AIM.params.transactions.getBuddyList,
			aimId:AIM.params.user,
			type:"getBuddyList"
		};
		AIM.core.requestData(tObj);
	},
	
	/**
	*	Deprecated. Buddy info comes from the initial buddylist event, and subsequent presence events when buddylist and presence are subscribed to
	*/
	getBuddyInfo: function(oScreenName) {
		var tObj = {
			dataURI:AIM.params.baseTransactionURI + AIM.params.transactions.getBuddyInfo + "?displayId=" + oScreenName,
			aimId:oScreenName,
			type:"getBuddyInfo"
		}
		AIM.core.requestData(tObj);
	},
	
	/**
	*	Requests a token from the host. This is the first method to be called - the API will not function without a valid token.
	*	@param { String } eList A comma-delimited list of events the application should subscribe to. Defined in AIM.core.subscriptions.
	*/
	getToken: function(eList) {
		if(AIM.params.token) {
			AIM.transactions.startSession(eList);
			return;
		}
		var tObj = {
			dataURI:AIM.params.baseAuthURI + AIM.params.transactions.getToken + "?k=" + AIM.params.wimKey + "&f=json&c=AIM%2Ecore%2EacceptData",
			type:"getToken",
			eventList:eList
		}
		AIM.core.requestData(tObj);
	},
	
	/**
	*	Request presence data from the host. Call is made anonymously (no login required).
	*/
	getPresenceInfo: function() {
		presence = AIM.util.getPresenceContainers();
		var i = presence.length, paramString = "";
		while(--i >= 0)
			paramString += "t=" + presence[i].aimId + "&";
		paramString = paramString.substring(0,paramString.lastIndexOf("&"));
		var tObj = {
			dataURI: AIM.params.baseTransactionURI + AIM.params.transactions.getPresenceInfo + "?" + paramString + "&k=" + AIM.params.wimKey + "&awayMsg=1&f=json&c=AIM%2Ecore%2EacceptData",
			presenceObject: presence,
			type:"getPresenceInfo"
		}
		AIM.core.requestData(tObj);
	},
	getPresenceInfoFor: function(screenName) {
		presence = AIM.util.getPresenceContainers();
		var paramString = "t="+screenName;
		var tObj = {
			dataURI: AIM.params.baseTransactionURI + AIM.params.transactions.getPresenceInfo + "?" + paramString + "&k=" + AIM.params.wimKey + "&awayMsg=1&f=json&c=AIM%2Ecore%2EacceptData",
			presenceObject: presence,
			type:"getPresenceInfo"
		}
		AIM.core.requestData(tObj);
	},
	
	/**
	*	Starts a session with the service - called by getToken's callback on successful token receipt.
	*	@param { String } eventList List of events to subscribe to. Defined in AIM.core.subscriptions.
	*/
	startSession: function(eventList) {
		if(!eventList) eventList = AIM.core.subscriptions;
		var ses = AIM.params.sessionId?"&aimsid=" + AIM.params.sessionId:"";
		var tObj = {
			dataURI:AIM.params.baseTransactionURI + AIM.params.transactions.startSession + "?k=" + AIM.params.wimKey + ses + "&events=" + eventList + "&a=" + AIM.params.token + "&assertCaps=" + AIM.params.assertCaps + "&interestCaps=" + AIM.params.interestCaps + "&f=json&c=AIM%2Ecore%2EacceptData",
			type:"startSession"
		}
		AIM.core.requestData(tObj);
	},
	
	/**
	*	Ends the session with the AIM service and the SNS service, logging the user out.
	*/
	endSession: function() {
		var tObj = {
			dataURI: AIM.params.baseTransactionURI + AIM.params.transactions.endSession + "?k=" + AIM.params.wimKey + "&aimsid=" + AIM.params.sessionId + "&f=json&c=AIM%2Ecore%2EacceptData",
			type:"endSession"
		}
		AIM.core.requestData(tObj);
		var tObj = {
			dataURI: AIM.params.baseAuthURI + AIM.params.transactions.logout + "?k=" + AIM.params.wimKey + "&f=json&a=" + AIM.params.token + "&c=AIM%2Ecore%2EacceptData",
			type:"endSession"
		}
		AIM.core.requestData(tObj);
		document.getElementById('aimExtraDiv').style.display = 'none';
		showLogin();
		//window.scrollTo(0, 0);
		var container = document.getElementById('AIMBuddyListContainer');
		if (container != null)
			container.style.display = 'none';
	},
	
	/**
	*	Notifies the host that the users online status has changed (i.e, away, idle, etc)
	*	@param { String } status The status of the user. away, idle, mobile, offline, invisible
	*/
	setState:function(status) {
		if(status == "away") {
			var awayMessage = "&away=" + AIM.params.text.awayMessage;
			AIM.util.currentState = 0;
		} else if (status == "invisible") {
			var awayMessage = "";
			AIM.util.currentState = 2;
		} else {
			var awayMessage = "";
			AIM.util.currentState = 1;
		}
		var tObj = {
			dataURI:AIM.params.baseTransactionURI + AIM.params.transactions.setState + "?aimsid=" + AIM.params.sessionId + "&f=json&c=AIM%2Ecore%2EacceptData&view=" + status + "" + awayMessage,
			type:"setState"
		}
		AIM.core.requestData(tObj);
	},
	
	/**
	*	Sets the users away message, and then changes the users status to Away.
	*	@param { String } msg The away message to be used.
	*/
	setAwayMessage: function(msg) {
		msg = AIM.util.formatMessage(msg);
		if(msg.length>AIM.params.AWAY_MSG_LIMIT) msg = msg.substring(0,AIM.params.AWAY_MSG_LIMIT);
		if(msg.trim() == "") msg = "I am away from my computer right now.";
		msg = encodeURIComponent(msg);
		AIM.params.text.awayMessage = msg;
		AIM.util.resetUserNotified();
		AIM.transactions.setState("away");
	},
	
	/**
	*	Not yet implimented.
	*/
	setProfile:function() {
	
	},
	
	/**
	*	Notifies the host that the user is typing.
	*	@param { String } tStatus The status of the typing. typing typed or none.
	*	@param { aimId } The id of the user to notify of the typing status.
	*/
	typingStatus:function(tStatus,aimId) {
		if(new Date() - AIM.util.typingStatusTimeStamp < 2000) return;
		AIM.util.typingStatusTimeStamp = new Date();
		aimId = encodeURIComponent(aimId);
		var tObj = {
			dataURI:AIM.params.baseTransactionURI + AIM.params.transactions.typingStatus + "?aimsid=" + AIM.params.sessionId + "&t=" + aimId + "&typingStatus=" + tStatus + "&f=json&c=AIM%2Ecore%2EacceptData",
			type:"typingStatus"
		}
		AIM.core.requestData(tObj);
	}
}

/**
*	Numerous utility methods 
*/

AIM.util = {
	offsetX:0,
	offsetY:0,
	dragObj:null,
	mDown:false,
	typingTimer: [],
	typingStatusTimeStamp: new Date(),
	visualNotificationTimer: [],
	userNotified: [],
	currentState: 1,
	
	/**
	*	Adds an event handler for the specified event to the specified object
	*	@param { HTMLObject } oElement The element to appy the event handler to
	*	@param { Function } oFunction The function to act as the event handler
	*	@param { String } strEvent The event name (without the "on" prefix), i.e., mousemove or load
	*/
	addEvent: function(oElement,fnFunction,strEvent) {
		if(oElement.addEventListener) {
			oElement.addEventListener(strEvent,fnFunction,false);
		} else {
			// attachEvent is useless w/o "this" support...
			//oElement.attachEvent("on" + strEvent,fnFunction);
			eval("oElement.on" + strEvent + "= fnFunction");
		}
	},
	
	removeEvent: function(oElement,fnFunction,strEvent) {
		oElement.removeEventListener?oElement.removeEventListener(strEvent,fnFunction,false):oElement.detachEvent("on" + strEvent,fnFunction);
	},
	/**
	*	Called from the beforeunload event to unhook various things and end the session.
	*/
	cleanUp: function() {
		document.title = AIM.params.DOCUMENT_TITLE;
		//if(document.getElementById("AIMReqFrame")) document.getElementById("AIMReqFrame").parentNode.removeChild(document.getElementById("AIMReqFrame"));
		AIM.util.currentState = 1;
		if(AIM.core.activeSession) {
			AIM.transactions.endSession();
			//--why? alert(AIM.params.text.autoLogOut);
		}
		AIM.params.token = null;
		AIM.params.sessionId = null;
	},
	
	/**
	*	Creates a script element and appends it to the head element
	*	@param { String } uri The url of the javascript file to import
	*/
	importScript: function(uri) {
		var oScript = document.createElement("script");
		oScript.setAttribute("type","text/javascript");
		oScript.setAttribute("src",uri);
		document.getElementsByTagName("head")[0].appendChild(oScript);
	},
	
	/**
	*	Captures the x,y offset of the mouse pointer relative to the element that was clicked
	*	@param { Event } e The click event.
	*/
	captureOffset:function(e) {
		AIM.util.mDown = true;
		AIM.util.dragObj = e?e.target.parentNode:event.srcElement.parentNode;
		var nx = AIM.util.dragObj.offsetLeft;
		var ny = AIM.util.dragObj.offsetTop;

		if(window.event) {
			AIM.util.offsetX=window.event.clientX - nx;
			AIM.util.offsetY=window.event.clientY - ny;
		} else {
			AIM.util.offsetX = e.pageX - nx;
			AIM.util.offsetY = e.pageY - ny;
		}
	},

	/**
	*	Calculates the offset of an element all the way to the nth offsetParent
	*	@param { HTMLObject } obj The object for which the calculations should be performed
	*	@return An object with x and y properties
	*	@type Object
	*/
	calculateOffset:function(obj) {
		var offset = { x:0,y:0 }
		while(obj.offsetParent) {
			if(obj.offsetParent) {
				if(obj.offsetTop)  offset.y += obj.offsetTop;
				if(obj.offsetLeft) offset.x += obj.offsetLeft;
				var obj = obj.offsetParent;
			}
		}
		return offset;
	},
	
	/**
	*	Resets the array that keeps track of if a user has recieved the clients away message. Called when the user comes back or changes their away message.
	*/
	resetUserNotified: function() {
		for(i in AIM.util.userNotified) AIM.util.userNotified[i] = false;
	},
	
	/**
	*	Creates a link element and appends it to the head of the document.
	*	@param { String } uri The URI to the css file.
	*/
	createStyleSheet: function(uri) {
		var oLinks = document.getElementsByTagName("head")[0].getElementsByTagName("link");
		var i = oLinks.length;
		while(i-->0) if(oLinks[i].getAttribute("href") == uri) return;
		var css = document.createElement("link");
		css.setAttribute("type","text/css");
		css.setAttribute("rel","stylesheet");
		css.setAttribute("href",uri);
		document.getElementsByTagName("head")[0].appendChild(css);
	},
	
	/**
	*	Cookie handler methods.
	*/
	cookie: {
		/**
		*	Gets the value of a cookie
		*	@param { String } cookieName The name of the cookie who's value you want
		*	@return The value of the cookie. null if the cookie isnt found.
		*	@type String
		*/
		get: function(cookieName) {
			var c = document.cookie;
			c = c.split(";");
			var i=0;
			do {
				var v = c[i].split("=");
				if(v[0].trim() == cookieName.trim()) {
					return v[1];
					break;
				}
				i++;
			} while(c[i]);
			return null;
		},
		/**
		*	Rudimentary cookie setting function
		*	@param { String } cookieName The name of the cookie to set.
		*	@param { String } cookieValue The value the cookie.
		*	@param { Boolean } session If the cookie is a session cookie or not. Sets the expire to current date + one year if false.
		*/
		set: function(cookieName,cookieValue,session) {
			var cookieString = cookieName + "=" + cookieValue + ";domain=" + document.domain + ";path=/";
			if(!session) cookieString += ";expires=" + new Date(Date.parse(new Date()) + 31536000000);
			document.cookie = cookieString;
		}
	},
	/**
	*	Adds a class to an element.
	*	@param { HTMLObject } oElement The element to apply the class to.
	*	@param { String } oClassName The class to apply.
	*/
	addClass: function(oElement,oClassName) {
		if (!oElement.className) {
			oElement.className = oClassName;
		} else {
			var newClassName = oElement.className + " " + oClassName;
			oElement.className = newClassName;
		}
	},
	/**
	*	Removes a class from an element.
	*	@param { HTMLObject } oElement The element to remove the class from.
	*	@param { String } oClassName The class to be removed.
	*/
	removeClass: function(oElement, oClassName) {
		var re = new RegExp('(?:^|\\s+)' + oClassName + '(?:\\s+|$)', 'g');
		oElement.className = oElement.className.replace(re," ");
	},
	
	/**
	*	Formats a string, replacing < with &lt;, etc.
	*	@param { String } oString The string to format.
	*	@return The formatted string.
	*	@type String
	*/
	formatMessage: function(oString) {
		/*
		if(oString.match(/<a ([^>]*)>([^<]*)<\/a>/g))  oString = oString.replace(/href/gi,"target=\"_blank\" href");
		if(oString.match(/<img ([^>]*)>/g))  {
			oString = oString.replace(/</g,"&lt;");
			oString = oString.replace(/>/g,"&gt;");
		}
		*/
		//oString = oString.replace(/&/g,"&amp;");
		oString = oString.replace(/</g,"&lt;");
		oString = oString.replace(/>/g,"&gt;");
		return oString;
	},
	
	/**
	*	Takes a UTC timestamp and converts it to something human-readable
	*	@param { Long } oTimeStamp A UTC timestamp
	*	@return The UTC timestamp converted to [HH:MM]
	*	@type String
	*/
	formatTimeStamp: function(oTimeStamp) {
		var h = oTimeStamp.getHours(); 
		if(!AIM.params.TWENTY_FOUR_HOUR_CLOCK) if(h>12)h-=12;
		var m = oTimeStamp.getMinutes();
		if(m<10) m = "0" + m;
		return "[" + h + ":" + m + "]";
	},
	
	/**
	*	Calculates the amount of time that has elapsed based on the number of seconds passed in
	*	@param { Integer } oElapsed The number of seconds elapsed since the person came online, went offline, went away, etc
	*	@return A formated string of the elapsed time, i.e., 12 Hours, 8 minutes.
	*	@type String
	*/
	elapsedFromSeconds:function (oElapsed) {
		var seconds = oElapsed % 60;
		Math.floor(oElapsed/=60);
		var minutes = Math.round(oElapsed % 60);
		Math.floor(oElapsed/=60);
		var hours = Math.floor(oElapsed%24);
		var days = Math.floor(oElapsed/24);
		
	 	var oString = "";
	 	if(days > 0) oString += days + " Days, ";
	 	if(hours > 0) oString += hours + " Hours, ";
	 	if(minutes > 0) oString += minutes + " Minutes";
	 	
	 	if(oString == "") oString = "1 Minute";
	 	return oString;
		
	},
	/**
	*	Gets a reference to all of the presence widgets on the page.
	*	@return A reference to all of the presence widgets on the page.
	*	@type Array
	*/
	getPresenceContainers: function() {
		var ele = AIM.util.getElementsByClassName(document.getElementsByTagName("body")[0],"*","AIMPresenceWidget");
		var presenceObj = [];
		var i = ele.length;
		while(i-- > 0) {
			presenceObj[i] = {
				element:ele[i],
				aimId: ele[i].className.substring(ele[i].className.indexOf(" "),ele[i].className.length).trim()
			}
		}
		return presenceObj;
	},
	
	/**
	*	Provides object references to all of the elements that represent screen names
	*	@param { HTMLObject } parentObj The object that contains the elements. Generally AIMBuddyList or AIMBuddyListContainer
	*	@return An array of HTML Elements that represent screen names in a buddy list.
	*	@type Array
	*/
	getAIMIDCollection: function(parentObj) {
		var objs = parentObj.getElementsByTagName("*");
		var i = objs.length;
		var returnArr = [];
		while(i-- >0) if(objs[i].getAttribute("wim_id")) returnArr.push(objs[i]);
		return returnArr;
	},
	
	/**
	*	Provides a reference to all elements that represent a given screen name
	*	@param { String } wimId The screen name to query for
	*	@param { HTMLObject } The HTML element to run the query in
	*	@return An array of HTML elements that represent that screen name
	*	@type Array
	*/
	getElementsByAIMID: function(wimId,oContainer) {
		var objs = oContainer.getElementsByTagName("*");
		var returnArr = [];
		var i = objs.length;
		while(i-- > 0) {
			oWIM = objs[i].getAttribute("wim_id")
			if(oWIM) if(oWIM == wimId) returnArr.push(objs[i]);
		}
		return returnArr;
	},
	/**
	*	Adaptation of Snook/Nyman's getElementsByClassName method: http://www.robertnyman.com/2005/11/07/the-ultimate-getelementsbyclassname/
	*	@param { HTMLObject } oElm The element to search within
	*	@param { String } strTagName The tag name to query on - "*" for all of 'em
	*	@param { String } strClassName The class to query for
	*	@return Any elements who's className match strClassName
	*	@type Array
	*/
	getElementsByClassName: function (oElm, strTagName, strClassName){
    	var arrElements = (strTagName == "*" && document.all)? document.all : oElm.getElementsByTagName(strTagName);
    	var arrReturnElements = [];
   		strClassName = strClassName.replace(/\-/g, "\\-");
    	var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
    	var i = arrElements.length;
    	while(i-- > 0) if(oRegExp.test(arrElements[i].className)) arrReturnElements.push(arrElements[i]);
    	return (arrReturnElements)
	},
	
	/**
	*	Returns the scroll offset of the window
	*	@param { Boolean } which True for vertical offset, false for horizontal
	*	@return The scroll offset of the window
	*	@type Integer
	*/
	getScrollOffset:function(which) {
		if(which) {
			if(document.body.scrollTop != 0)return document.body.scrollTop;
			if(document.documentElement.scrollTop != 0)return document.documentElement.scrollTop;
		} else {
			if(document.body.scrollLeft != 0)return document.body.scrollTop;
			if(document.documentElement.scrollLeft != 0)return document.documentElement.scrollLeft;
		}
		return 0;
	}
}

AIM.eventHandlers = {
	handleMouseover: function(e) {
		var srcObj = e?e.target:event.srcElement; 
		fn = function() { AIM.ui.showBuddyInfo(srcObj); }
		if(srcObj.className.indexOf("buddy") >-1 || srcObj.className.indexOf("AIMPresenceWidget") > -1) {
				AIM.ui.prepBuddyInfo(srcObj);
				AIM.ui.showBuddyInfo(AIM.ui.storedBuddyInfo[srcObj.getAttribute("wim_id")]);
		}
	},
	
	handleMouseout: function(e) {
		try {
			var srcObj = e?e.target:event.srcElement;
			if(srcObj.className.indexOf("buddy") > -1) {
				if(document.getElementById("AIMBuddyListBuddyInfo"))document.getElementById("AIMBuddyListBuddyInfo").style.display = "none";
			}
		} catch(err) {
			//AIM.core.debug("AIM.eventHandlers.handleMouseout: " + err.message);
		}
	},
	
	handleClick:function(e) {
		var srcObj = e?e.target:event.srcElement;
		if(srcObj.className.indexOf("buddy") > -1) {
			AIM.ui.createIMWindow(srcObj.getAttribute("wim_id"));
		} else if(srcObj.className == "AIMBuddyListIMWindowButton") {
			var winID = srcObj.getAttribute("wim_aimId");
			if(winID.indexOf("+") == 0) winID = winID.replace(/\+/,"SMS");
			AIM.transactions.sendTextIM(srcObj.getAttribute("wim_aimId"),srcObj.oValue);
			srcObj.oValue = "";
			document.getElementById("AIMTextInput_"	+ winID).value = "";
		} else if(srcObj.className.indexOf("AIMBuddyListMenuItem") == 0) {
			eval(srcObj.xonclick);
		}
	},
	
	handleMouseMove:function(e) {
		if(!AIM.util.mDown) return;
		y=window.event?window.event.clientY:e.clientY;
		if(y<=5) return;
		x=window.event?window.event.clientX - AIM.util.offsetX:e.clientX - AIM.util.offsetX;
		y=window.event?window.event.clientY - AIM.util.offsetY:e.clientY - AIM.util.offsetY;
		if(AIM.params.MOZILLA) {
			y+=AIM.util.getScrollOffset(1);
			x+=AIM.util.getScrollOffset(0);
		}
		AIM.util.dragObj.style.top = y + "px";
		AIM.util.dragObj.style.left = x + "px";
		if(AIM.util.dragObj.className.indexOf("AIMBuddyListIMWindow") > -1 )  {
			if(AIM.util.dragObj.className.indexOf("DragState") == -1) AIM.util.addClass(AIM.util.dragObj,"AIMBuddyListIMWindowDragState");
			AIM.ui.setIMWindowZIndex(AIM.util.dragObj.id);
		}
	},
	
	handleKeyUp:function(e) {
		var srcObj = e?e.target:event.srcElement;
		var keyCode = window.event?window.event.keyCode:e.keyCode;
		if(srcObj.getAttribute("wim_aimId")) {
			var oSN = srcObj.getAttribute("wim_aimId");
			var winID = oSN;
			if(winID.indexOf("+") == 0) winID = winID.replace(/\+/,"SMS");
			AIM.ui.setIMWindowZIndex(winID + "_AIMwindow");
			if(!AIM.util.typingTimer[oSN]) {
				var fn = function() { AIM.transactions.typingStatus("typed",oSN);}
				AIM.util.typingTimer[oSN] = setTimeout(fn,8000);
			} else {
				AIM.transactions.typingStatus("typing",oSN);
			}
			//if(srcObj.value == "") AIM.transactions.typingStatus("none",oSN);
			if(keyCode == 13) {
				clearTimeout(AIM.util.typingTimer[oSN]);
				AIM.util.typingTimer[oSN] = null;
				AIM.transactions.sendTextIM(oSN, srcObj.value);
				//AIM.transactions.typingStatus("none",oSN);
				srcObj.value = "";
			} else {
				if(document.getElementById("AIMBuddyListIMWindowButton_" + winID)) document.getElementById("AIMBuddyListIMWindowButton_" + winID).oValue = srcObj.value;
			}
		}
	}
}

setPowAIM(AIM);


// deprecations
AIM.widgets.IMMe = AIM.widgets.IM;


// language file
// [2008.04.19] commenting this out because the remote file has a syntax error
//AIM.util.importScript("http://o.aolcdn.com/aim/web-aim/lang/aimapi.text." + baseLang + ".js");
// [2008.04.19] language file reproduced below, with syntax error fixed
AIM.params.text = {
	sendButtonText:"Send",
	awayMessageWindowTitle:"Set Your Away Message",
	awayMessageWindowInstructions:"Enter your away message:",
	aimIdPromptMessage:"Enter the AIM ID you want to Instant Message:",
	aimIdPromptDefaultSN:"",
	brandingText:"",
	userTyping:"Your buddy is typing...",
	userTyped:"Your buddy has entered text...",
	userStoppedTyping:"",
	availabilityMenuInvisibleText:"I'm Invisible",
	availabilityMenuAwayText:"I'm Away",
	availabilityMenuAvailableText:"I'm Available",
	awayMessage:"I am away from my computer right now.",
	autoReplyNotice:"(Autoreply)",
	unsupportedBrowser:"The browser you're using is not one officially supported by AOL's Web AIM API. Because of this, we can't guarantee that the experience you'll have in this browser will be top notch, but we'll let you be the judge of that. Its your browser -- who are we to tell you what you can and can't do?\n\nBrowsers that we have done loads of testing in are Internet Explorer 6 and higher on Windows XP, Firefox 1.5.x on OS X and Windows XP (we're still testing Firefox 2) and Safari 2.0 and higher on OS X. We hope you'll revisit this page with one of those browsers for the full Web AIM experience.",
	permissionDenied:"We're sorry, this account doesn't have the appropriate rights to use this application. We apologize for any inconvenience.",
	startSessionFailed:"It seems we're having technical difficulties.  Please give us a moment to gather our wits and then try again.",
	autoLogOut:"Automatically logging you out...",
	// following defines the menu. You can add more menu options by following the text/method format. The method will be executed from the onclick event.
	// use cls property to define additional class names for the create elements
	availabilityMenuItems: {
		available: {
			text:"Available",
			method:"AIM.transactions.setState('online')"	
		},
		away: {
			text:"Away",
			method:"AIM.transactions.setState('away')"
		},
		invisible: {
			text:"Invisible",
			method:"AIM.transactions.setState('invisible')"
		},
		setAwayMessage: {
			text:"Set Away Message",
			method:"AIM.ui.createAwayMessage()"
		},
		divider1: {
			text:null,
			method:null
		},
		sendMessage: {
			text:"Send an IM",
			method:"AIM.ui.aimIdPrompt('')"
		},
		divider2: {
			text:null,
			method:null
		},
		endSession: {
			text:"Sign Off",
			method:"AIM.widgets.buddyList.kill()"
		}
	},
	
	errors: {
		noAIMContainer: "Unable to locate an element with an id of AIMBuddyListContainer.",
		missingKey:"Unable to locate your developer key. The element with the id attribute of AIMBuddyListContainer requires an attribute of \"wim_key\" with the value of the key given to you at developer.aim.com.",
		serverErrors:{
			"304":"The previously submitted request was completed successfully. However, no remotely-stored data was modified.",
			"330":"Please re-enter your screen name and password and try again.",
			"340":"The request did not complete successfully because the application does not have the appropriate rights to complete the request.",
			"400":"Please verify the number and format of the parameters are correct and try again.",
			"401":"Please enter your screen name and password.",
			"405":"This application does not have sufficient privileges to make this request.",
			"408":"The request was not completed because there was no response from the remote server.",
			"430":"The application has exceeded the rate limit. Please wait a few minutes and try again.",
			"440":"Please verify that the application key is valid and try again. Please visit http://developer.aim.com for more information regarding keys.",
			"441":"The maximum number of requests allowed for this key has been exceeded. Please wait 24 hours for the key usage counter to reset. If the request still returns this error, the maximum number of requests for the month has been exceeded. If this is the case, you can apply for an unlimited key at http://developer.aim.com.",
			"442":"The originating hostname is not permitted to use this key.",
			"443":"The referring URL is not permitted to use this key.",
			"450":"The application does not have the appropriate rights to complete the request.",
			"451":"The user does not have the appropriate rights to complete the request.",
			"460":"Please verify that all of the required parameters are specified and try again",
			"461":"Please specify the event source and submit the request again.",
			"462":"Please verify that all required parameters are specified in the appropriate format and try again.",
			"500":"The request failed due to a server error. Please try again.",
			"600":"Please verify that a valid screen name is used and try again.",
			"601":"Please verify that a valid screen name is used and try again.",
			"602":"User is not available.",
			"603":"The message was not received because the sender is blocked.",
			"604":"The user is not allowed to access this service.",
			"605":"The target does not support this request.",
			"606":"Please reduce the size of the message and try again.",
			"607":"The application has exceeded the rate limit. Please wait a few minutes and try again."
		}
	}
}
//=== end aimapi.pow.js
