/**
*	@fileoverview		AIM Javascript API
*	@author	Steven G. Chipman - AOL - http://developer.aim.com
*	@filename aimapi.js
*	@copyright Copyright (c) 2007 AOL LLC. All rights reserved
*	@version	0.19r2.1
*	@revision	08.20.2007
*/ 

/**
*	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
baseResourceURI = "http://127.0.0.1/CW/style/";
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 = {
/**
 * General entry point for the API.
 */
	init: function() {
		if(!AIM.params.wimKey) AIM.params.wimKey = document.getElementById("AIMBuddyListContainer").getAttribute("wim_key");
		if(!AIM.params.wimKey) return alert(AIM.params.text.errors.missingKey);
		AIM.util.addEvent(window,AIM.util.cleanUp,"beforeunload");
		AIM.util.addEvent(document,AIM.eventHandlers.handleMouseMove,"mousemove");
		AIM.util.addEvent(document,function(){AIM.util.mDown=false;},"mouseup");
		if(AIM.util.cookie.get("ablsnd") != null) AIM.params.SOUND = AIM.util.cookie.get("ablsnd").toBoolean();
	}
}

/**
*	Parameters that define how the API behaves. See the full documentation on developer.aim.com for details.
*/
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:null,
	sound:true,
	token:null,
	language:"en-us", // deprecated by 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"
	},
	sounds: {
		IM: baseResourceURI + "im.wav"
	},
	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"]
		}
	},
	emoticons: {
		">:o":"angry",
		":-\\[":"blush",
		":'\\(":"crying",
		":-!":"foot-in-mouth",
		":-\\(":"frown",
		":\\(":"frown",
		"=-o":"gasp",
		":-D":"grin",
		":D":"grin",
		"O:-\\)":"halo",
		":-\\*":"kiss",
		":-x":"lips-sealed",
		":-\\$":"money-mouth",
		":-\\)":"smile",
		":\\)":"smile",
		":-p":"tongue",
		":p":"tongue",
		":-\\\\":"undecided",
		":-\\/":"undecided",
		"8-\\)":"cool",
		"8\\)":"cool",
		";-\\)":"wink",
		";\\)":"wink"
	},
	
	// AIM.params.text moved to language specific files, i.e. aimapi.text.en-us.js
	USE_EMOTICONS:false,
	SEND_OFFLINE_IM:1,
	DOCUMENT_TITLE:document.title,
	DEBUG:false,
	BUDDY_LIST_DRAG:false,
	DEFAULT_ICON: baseResourceURI + "default_icon.png",
	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 appearence definitions
*/
AIM.widgets = {
	buddyList: {
		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.buddyList.appearance.styleSheetURI);			
			if(document.getElementById("AIMBuddyList")) {
				var AIMBL = document.getElementById("AIMBuddyList")
				AIMBL.style.position = "static";
				AIMBL.style.top = "0";
				AIMBL.style.left = "0";
				return;
			}
			AIM.util.createSoundObjects();
			//AIM.params.sessionId = null;
			AIM.core.subscriptions = arguments[0]?arguments[0]:"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();
		},
		appearance: {
			styleSheetURI: baseResourceURI + "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: baseResourceURI + "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: baseResourceURI + "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: baseResourceURI + "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) {
		if(document.getElementById("AIMFrameContainer_AIMwindow")) {
			var oIframe = document.getElementById("AIMReqFrame");
		} else {
			var win = AIM.ui.createWindowFrame("AIMFrameContainer","AIMFrameContainer","Web AIM");
			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);
		}
		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()));
		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-3]\.");
		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("#aim");
			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) {
		try {
			if(document.getElementById("AIMBuddyList")) document.getElementById("AIMBuddyList").parentNode.removeChild(document.getElementById("AIMBuddyList"));
		} catch(err) {
			AIM.core.debug("AIM.callbacks.endSession: " + err.message);
		}
		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 succesfull 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);
			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 = {
	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) {
		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 sp = document.createElement("div");
		sp.className = "AIMBuddyListUserScreenName";
		sp.appendChild(document.createTextNode(AIM.params.user));
		ul.appendChild(sp);
		
		if(AIM.params.CREATE_AVAILABILITY_MENU_BL) {
			//document.getElementById("AIMBuddyListContainer").appendChild(AIM.ui.createAvailabilityMenu());
			ul.appendChild(AIM.ui.createAvailabilityMenu());
		}
		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");
	},
	
	/**
	*	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;
	},
	
	/**
	*	Toggles the sound on and off.
	*/
	toggleSound: function() {
		var sndMenuItems = AIM.util.getElementsByClassName(document.getElementById("AIMBuddyListContainer"),"li","AIMBuddyListSoundToggle");
		var i = sndMenuItems.length;
		AIM.params.sound = AIM.params.sound?false:true;
		while(i-->0) {
			AIM.util.removeClass(sndMenuItems[i],"AIMBuddyListSoundOff");
			AIM.util.removeClass(sndMenuItems[i],"AIMBuddyListSoundOn");
			AIM.util.addClass(sndMenuItems[i],AIM.params.sound?"AIMBuddyListSoundOn":"AIMBuddyListSoundOff");
		}
		AIM.util.cookie.set("ablsnd",AIM.params.sound,false);
	},
	
	/**
	*	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.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) 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);
		msg = AIM.ui.addEmoticons(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);
	},
	
	/**
	*	Replaces emoticons with images, i.e., :) becomes smile.png
	*	@param { String } txt The text to have the regexp permformed on
	*	@return { String} the modifed string
	*/
	addEmoticons: function(txt) {
		if(AIM.params.USE_EMOTICONS) {
			for(var i in AIM.params.emoticons) {
				var r = eval ("/(" + i + ")/gi");
 				if(txt.match(r)) txt =txt.replace(r,"<img src=\"" + baseResourceURI + "emoticons/" + AIM.params.emoticons[i] + ".png\" alt=\"(" + AIM.params.emoticons[i] + ")\" />");
			}
		}
		return txt;
	},
	
	/**
	*	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) { }
		
		msg = AIM.ui.addEmoticons(msg);
		
		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;
		if(AIM.params.sound) AIM.util.playSound("IM");
		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.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);
		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);
			
			var y = ((AIM.util.getScrollOffset(1) + document.getElementById("AIMBuddyListContainer").offsetTop));
			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) {
		if(!document.getElementById("AIMBuddyListBuddyInfo")) {
			var div = document.createElement("div");
			div.setAttribute("id","AIMBuddyListBuddyInfo");
			div.style.zIndex = "20000";
			document.getElementById("AIMBuddyListContainer").appendChild(div);
			try {
				AIM.util.addEvent(document.getElementById("AIMBuddyListContainer"),function() { if(document.getElementById("AIMBuddyListBuddyInfo")) document.getElementById("AIMBuddyListBuddyInfo").style.display = "none"; },"mouseout");
			} catch(err) { }
		}
		try {
			AIMInfo = document.getElementById("AIMBuddyListBuddyInfo");
			//var y = (AIM.util.calculateOffset(cObj).y + (cObj.offsetHeight +25));// - document.getElementById("AIMBuddyList").scrollTop;
			var y =AIM.util.calculateOffset(cObj).y;
			if(document.getElementById("AIMBuddyList")) y-= document.getElementById("AIMBuddyList").scrollTop
			if(document.getElementById("AIMBuddyListBrandingArea")) y -= document.getElementById("AIMBuddyListBrandingArea").offsetHeight;
			AIMInfo.style.top = y + "px"; //(AIM.util.calculateOffset(cObj).y + (cObj.offsetHeight+5)) + "px";
			//AIMInfo.style.top = (cObj.offsetTop + (cObj.offsetHeight+10)) + "px";
			//var x = AIM.util.calculateOffset(cObj).x;
			var x = (cObj.offsetLeft + cObj.offsetWidth) + 20;
			//if(document.getElementById("AIMBuddyListContainer")) x+= document.getElementById("AIMBuddyListContainer").offsetWidth;

			AIMInfo.style.left =x + "px";
		} catch(err) { }
	}
}

/**
*	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 dilimited 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);
	},
	
	/**
	*	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);
		if(document.getElementById("AIMBuddyListContainer")) document.getElementById("AIMBuddyListContainer").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;
		//try {
			//if(document.getElementById("AIMReqFrame")) document.getElementById("AIMReqFrame").parentNode.removeChild(document.getElementById("AIMReqFrame"));
			AIM.util.currentState = 1;
			if(AIM.core.activeSession) {
				AIM.transactions.endSession();
				alert(AIM.params.text.autoLogOut);
			}
		//} catch(err) { }
		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," ");
	},
	/**
	*	Creates embeds for all of the sounds defined in AIM.params.sounds. Not used by MSIE or Opera.
	*/
	createSoundObjects: function() {
		if(document.all) return;
		for(var i in AIM.params.sounds) {
			if(document.getElementById("snd_" + i)) continue;
			var emb = document.createElement("embed");
			emb.setAttribute("autostart","false");
			emb.setAttribute("src",AIM.params.sounds[i]);
			emb.setAttribute("id","snd_" + i);
			emb.setAttribute("name","snd_" + i);
			emb.setAttribute("hidden","true");
			emb.setAttribute("width","0");
			emb.setAttribute("height","0");
			emb.setAttribute("enablejavascript","true");
			emb.setAttribute("type","audio/x-wav");
			emb.className = "AIMBuddyListSoundObject";
			document.getElementById("AIMBuddyListContainer").appendChild(emb);
		}
	},
	
	/**
	*	Plays a sound.
	*	@param { String } sndType The type of sound to play. Should match the property in AIM.params.sounds.
	*/
	playSound: function(sndType) {
		if(!AIM.params.sound) return;
		try {
			if(document.all) {
				if(document.getElementById("msieSndObj")) document.getElementById("msieSndObj").parentNode.removeChild(document.getElementById("msieSndObj"));
				var sndObj = document.createElement("bgsound");
				sndObj.setAttribute("src",AIM.params.sounds[sndType]);
				sndObj.setAttribute("id","msieSndObj");
				document.getElementById("AIMBuddyListContainer").appendChild(sndObj);
			} else {
				//var sndObj = document.getElementById("snd_" + sndType);
				var sndObj = eval("document.snd_"+ sndType);
				sndObj.Stop();
				sndObj.Rewind();
				sndObj.Play();
			}
		} catch(err) {
			AIM.core.debug("AIM.util.playSound: " + err.message);
		}
	},
	
	/**
	*	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;
			}
		}
	}
}
// language file
AIM.util.importScript("http://o.aolcdn.com/aim/web-aim/lang/aimapi.text." + baseLang + ".js");
// deprecations
AIM.widgets.IMMe = AIM.widgets.IM;

