// Antonin:
// this is static version of http://api.myspace.com/jsl/scriptrequest.ashx?type=JSL
// I'm trying to patch buggy webkit behavior here, 
// TODO: replace it with live version when fixed on myspace side

/*
Enums all caps
Variables camel case
Class names capitalized
*/
String.prototype.trim = function() {
	return this.replace(/^\s+|\s+$/g, "");
};
String.prototype.ltrim = function() {
	return this.replace(/^\s+/, "");
};
String.prototype.rtrim = function() {
	return this.replace(/\s+$/, "");
};

String.prototype.startsWith = function(str) {

	var isMatch = (this.match("^" + str) == str);

	return isMatch;
};

String.prototype.endsWith = function(str) {
	return (this.match(str + "$") == str);
};

var $get = function() {
	var elements = [];
	for (var i = 0; i < arguments.length; i++) {
		var element = arguments[i];
		if (typeof element == 'string') {
			element = document.getElementById(element);
		}
		if (arguments.length == 1) {
			return element;
		}

		elements.push(element);
	}
	return elements;
};

var MSID = new function() {

	var self = this, xxx = '';

	self.MODES = { 'DEBUG': 0, 'STAGE': 1, 'RELEASE': 2 };
	self.isSecure = (location.protocol === 'https:');
	self.mode = self.MODES.RELEASE;
	self.protocol = 'http://';

	self.baseUrl = self.protocol + 'api.myspace.com';
	self.loginBaseUrl = self.protocol + 'api.myspace.com';
	self.staticBaseUrl = self.protocol + 'x.myspace.com';
};

MSID.Core = new function() {
	var self = this;

	self.appName = navigator.appName;
	self.platform = navigator.platform;
	self.appVersion = navigator.appVersion;
	self.appCodeName = navigator.appCodeName;
	self.userAgent = navigator.userAgent;
	self.isSafari = self.userAgent.indexOf('Safari') != -1;

	//if it looks like safari
	//check for chrome
	if (self.isSafari) {
		self.isSafari = self.userAgent.indexOf('Chrome') == -1;
	}

	/* debugging
	alert(
	'appName:' + self.appName + ' | ' +
	'platform:' + self.platform + ' | ' +
	'appVersion:' + self.appVersion + ' | ' +
	'appCodeName:' + self.appCodename + ' | ' +
	'userAgent:' + self.userAgent + ' | ' +
	'isSafari:' + self.isSafari
	);
	*/

	/*
	Property set to true when javascript is ready.
	This value is set to true on the window.onload event.
	*/
	self.jsReady = false;

	/*
	? Self Explanatory
	*/
	self.helloWorld = function() {
		return 'Hello World!';
	};

	/*
	Summary:	Attaches given function to window.onload
	Parameters
	func:		function to run when the window.onload event is hit
	*/
	self.addLoadEvent = function(func) {

		var oldonload = window.onload;
		if (typeof window.onload != 'function') {
			window.onload = func;
		} else {
			window.onload = function() {

				if (oldonload) {
					oldonload();
				}
				func();
			};
		}

	};

	self.clone2 = function(obj) {
		var newObj = (obj instanceof Array) ? [] : {};
		for (var i in obj) {

			if (obj.hasOwnProperty(i)) {

				if (i == 'clone') {
					continue;
				}

				if (this[i] && typeof obj[i] == "object") {
					newObj[i] = obj[i].clone();
				} else {
					newObj[i] = obj[i];
				}
			}
		}

		return newObj;
	};

	self.addEvent = function(elm, evType, fn, useCapture) {
		var oldonload = window.onload;
		if (typeof window.onload != 'function') {
			window.onload = func;
		} else {
			window.onload = function() {

				if (oldonload) {
					oldonload();
				}
				func();
			};
		}
	};

	self.arrayContains = function(userArray, searchCriteria) {
		var searchString = userArray.toString();

		searchString.search(searchCriteria);
	};

	/*
	Summary:	Generate a random number
	Returns:	Number between 1 and 1000
	*/
	self.randomNumber = function() {
		var randomnumber = Math.floor(Math.random() * 1001);

		return randomnumber;
	};

	self.ua = function() {
		var w3cdom = typeof document.getElementById != 'undefined' &&
			typeof document.getElementsByTagName != 'undefined' &&
			typeof document.createElement != 'undefined', playerVersion = [0, 0, 0], d = null;

		if (typeof navigator.plugins != 'undefined' && typeof navigator.plugins['Shockwave Flash'] == 'object') {
			d = navigator.plugins['Shockwave Flash'].description;
			if (d && !(typeof navigator.mimeTypes != 'undefined' && navigator.mimeTypes['application/x-shockwave-flash'] && !navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin)) { // navigatorigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+
				d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
				playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10);
				playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10);
				playerVersion[2] = /r/.test(d) ? parseInt(d.replace(/^.*r(.*)$/, "$1"), 10) : 0;
			}
		}
		else if (typeof window.ActiveXObject != 'undefined') {
			var a = null, fp6Crash = false;
			try {
				a = new ActiveXObject('ShockwaveFlash.ShockwaveFlash' + ".7");
			}
			catch (e1) {
				try {
					a = new ActiveXObject('ShockwaveFlash.ShockwaveFlash' + ".6");
					playerVersion = [6, 0, 21];
					a.AllowScriptAccess = "always";  // Introduced in fp6.0.47
				}
				catch (e2) {
					if (playerVersion[0] == 6) {
						fp6Crash = true;
					}
				}
				if (!fp6Crash) {
					try {
						a = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
					}
					catch (e3) { }
				}
			}
			if (!fp6Crash && a) { // a will return null when ActiveX is disabled
				try {
					d = a.GetVariable("$version"); // Will crash fp6.0.21/23/29
					if (d) {
						d = d.split(" ")[1].split(",");
						playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
					}
				}
				catch (e4) { }
			}
		}
		var u = navigator.userAgent.toLowerCase(),
			p = navigator.platform.toLowerCase(),
			webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit
			ie = false,
			windows = p ? /win/.test(p) : /win/.test(u),
			mac = p ? /mac/.test(p) : /mac/.test(u);
		/*@cc_on
		ie = true;
		@if (@_win32)
		windows = true;
		@elif (@_mac)
				mac = true;
			@end
		@*/
		return { w3cdom: w3cdom, pv: playerVersion, webkit: webkit, ie: ie, win: windows, mac: mac };
	} ();

	/*
	Cookie Functions
	*/
	self.getCookie = function(name) {

		if (self.isSafari) { return localStorage.getItem(name); }

		var nameEQ = name + "=";
		var ca = document.cookie.split(';');
		for (var i = 0; i < ca.length; i++) {
			var c = ca[i];
			while (c.charAt(0) == ' ') { c = c.substring(1, c.length); }
			if (c.indexOf(nameEQ) == 0) { return unescape(c.substring(nameEQ.length, c.length)); }
		}

		return null;
	};

	/*
	args:
	name:		cookie name
	value:		cooie value
	expires:	the expiration time in minutes
	*/
	self.setCookie = function(name, value, expires, path, domain, secure) {

		if (self.isSafari) {
			localStorage.setItem(name, value);
			return;
		}


		var today = new Date();

		today.setTime(today.getTime());

		if (expires) {
			expires = expires * 1000 * 60;
		}

		var expires_date = new Date(today.getTime() + (expires));
		var cookie = name + '=' + escape(value) +
			((expires) ? ';expires=' + expires_date.toGMTString() : '') + //expires.toGMTString()
			((path) ? ';path=' + path : '') +
			((domain) ? ';domain=' + domain : '') +
			((secure) ? ';secure' : '');
		document.cookie = cookie;
	};

	self.deleteCookie = function(name, path, domain) {
		if (self.isSafari) {
			localStorage.clear();
			return;
		}

		if (self.getCookie(name)) {
			document.cookie = name + '=' + ((path) ? ';path=' + path : '') + ((domain) ? ';domain=' + domain : '') + ';expires=Thu, 01-Jan-1970 00:00:01 GMT';
		}
	};

	self.parseCookie = function(data) {
		var cookieBits = data.split('|');
		var parsedCookie = {};

		for (var i in cookieBits) {

			if (cookieBits.hasOwnProperty(i)) {

				var detail = i.split('=');

				parsedCookie[detail[0]] = detail[1];
			}
		}

		return parsedCookie;
	};

	self.appendIFrame = function(name) {
		var el = document.createElement("iframe");

		el.setAttribute("id", name);
		el.setAttribute("name", name);
		el.setAttribute("width", "0px");
		el.setAttribute("height", "0px");

		window.document.body.appendChild(el);
	};

	self.insertElementHTML = function(controlID, elementText) {
		var placeholder;

		if (typeof controlID == 'undefined') {
			placeholder = window.document.body;
		}
		else {
			placeholder = $get(controlID);
		}

		placeholder.innerHTML += elementText;
	};

	self.insertElementAt = function(controlID, tag, attributes) {
		var el, placeholder;

		if (typeof controlID == 'undefined') {
			placeholder = window.document.body;
		}
		else {
			placeholder = $get(controlID);
		}

		el = document.createElement(tag);

		for (var attribute in attributes) {
			if (attributes.hasOwnProperty(attribute)) {
				el.setAttribute(attribute.name, attributes[attribute.name]);
			}
		}

		placeholder.appendChild(el);
	};

	self.clone = function(obj) {

		if (obj === null || typeof (obj) != 'object') {
			return obj;
		}

		var temp = new obj.constructor(); // changed (twice)

		for (var key in obj) {
			if (obj.hasOwnProperty(key)) {
				temp[key] = clone(obj[key]);
			}
		}

		return temp;
	};

	self.toggle = function(obj) {
		var el = document.getElementById(obj);

		if (el.style.display != 'none') {
			el.style.display = 'none';
		}
		else {
			el.style.display = '';
		}
	};

	self.urlEncode = function(value) {
		// The Javascript escape and unescape functions do not correspond
		// with what browsers actually do...
		var SAFECHARS = "0123456789" + // Numeric
						"ABCDEFGHIJKLMNOPQRSTUVWXYZ" + // Alphabetic
						"abcdefghijklmnopqrstuvwxyz" +
						"-_.!~*'()"; // RFC2396 Mark characters
		var HEX = "0123456789ABCDEF";

		var plaintext = value;
		var encoded = "";
		for (var i = 0; i < plaintext.length; i++) {
			var ch = plaintext.charAt(i);
			if (ch == " ") {
				encoded += "+"; 			// x-www-urlencoded, rather than %20
			} else if (SAFECHARS.indexOf(ch) != -1) {
				encoded += ch;
			} else {
				var charCode = ch.charCodeAt(0);
				if (charCode > 255) {
					alert("Unicode Character '" + ch + "' cannot be encoded using standard URL encoding.\n (URL encoding only supports 8-bit characters.)\n A space (+) will be substituted.");
					encoded += "+";
				} else {
					encoded += "%";
					encoded += HEX.charAt((charCode >> 4) & 0xF);
					encoded += HEX.charAt(charCode & 0xF);
				}
			}
		} // for

		return encoded;
	};

	self.urlDecode = function(value) {
		// Replace + with ' '
		// Replace %xx with equivalent character
		// Put [ERROR] in output if %xx is invalid.
		var HEXCHARS = "0123456789ABCDEFabcdef";
		var encoded = value;
		var plaintext = "";
		var i = 0;
		while (i < encoded.length) {
			var ch = encoded.charAt(i);
			if (ch == "+") {
				plaintext += " ";
				i++;
			} else if (ch == "%") {
				if (i < (encoded.length - 2) &&
					HEXCHARS.indexOf(encoded.charAt(i + 1)) != -1 &&
					HEXCHARS.indexOf(encoded.charAt(i + 2)) != -1) {

					plaintext += unescape(encoded.substr(i, 3));
					i += 3;
				} else {
					alert('Bad escape combination near ...' + encoded.substr(i));
					plaintext += "%[ERROR]";
					i++;
				}
			} else {
				plaintext += ch;
				i++;
			}
		} // while

		return plaintext;
	};

	self.onLoad = function() {
		self.jsReady = true;

		//console.log('jsl is ready: ' + self.jsReady.toString());
	};
	/*
	Constructor
	*/
	self.Core = function() {
		self.addLoadEvent(self.onLoad);

		/* debugging - check local Storage
		if (self.isSafari) {
		self.setCookie('test1', 'value1');

			alert(self.getCookie('test1'));
		}
		*/
	} ();
};

MSID.GUID = new function() {

	var self = this;

	self.CONSTANTS = {
		numbers: "0123456789",
		alphas: "abcdefghijklmnopqrstuvwxyz",
		lowerAlphanumerics: "0123456789abcdefghijklmnopqrstuvwxyz",
		alphanumerics: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ",
		// http://tools.ietf.org/html/rfc1924
		base85: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+-;<=>?@^_`{|}~",
		epoch1970: (new Date(0))
	};

	//self.CONSTANTS.epoch = function(year) { return (new Date("Jan 1 " + year)).getTime(); }

	var lastTimestampUsed, randomSequenceLength = 5, counterSequenceLength = 1;

	var baseN = function(val) {
		if (val === 0) { return ""; }
		var rightMost = val % self.CONSTANTS.alphanumerics.length;
		var rightMostChar = self.CONSTANTS.alphanumerics.charAt(rightMost);
		var remaining = Math.floor(val / self.CONSTANTS.alphanumerics.length);
		return baseN(remaining) + rightMostChar;
	};

	self.generate = function() {

		var now = (new Date()).getTime() - self.CONSTANTS.epoch1970.getTime();
		var guid = baseN(now);

		counterSeqLength = (now == lastTimestampUsed ? counterSequenceLength + 1 : 1);
		guid += counterSequenceLength;

		for (var i = 0; i < randomSequenceLength; i++) {
			guid += self.CONSTANTS.alphanumerics.charAt(Math.floor(Math.random() * self.CONSTANTS.alphanumerics.length));
		}

		lastTimestampUsed = now;

		return guid;
	};
} ();

/*
Instance class Connect.Response
*/
MSID.ConnectResponse = function(state, msg) {
	var self = this;

	self.statusCode = state;
	self.statusMessage = msg;
};

MSID.Enums = {

	ConnectState: {
		'UNKNOWN': -1,
		'READY': 0,
		'NOCOOKIE': 1,
		'NOTLOGGEDIN': 2,
		'COULDNOTINITLIBRARY': 3,
		'TIMEOUT': 4,
		'MISSING_CONSUMER_KEY': 5,
		'MISSING_REMOTE_RELAY': 6,
		'INVALID_TARGET_DOMAIN': 7

	},
	ResponseCodes: {
		'Success': '1',
		'UserDeclined': '2',
		'InvalidTargetDomain': '3',
		'InvalidConsumerKey': '4',
		'MissingParameters': '5',
		'InternalError': '6'
	},
	CookieKeys: {
		'ResponseCode': 'myspaceid.response_code',
		'TargetDomain': 'myspaceid.target_domain',
		'Identity': 'myspaceid.identity',
		'RequestToken': 'myspaceid.request_token',
		'SessionToken': 'myspaceid.session_token',
		'Signature': 'myspaceid.signature',
		'Signed': 'myspaceid.signed',
		'FriendID': 'myspaceid.friendid'
	},
	/*
	usage:	getEnumKey(MSID.Enums.CookieKeys, MSID.Enums.CookieKeys.TargetDomain)
	getEnumKey(MSID.Enums.CookieKeys, 'myspaceid.target_domain')
	getEnumKey(MSID.Enums.CookieKeys, MSID.Enums.CookieKeys[0])
	*/
	getEnumKey: function(definedEnum, value) {

		for (var item in definedEnum) {

			if (value == definedEnum[item]) {
				return item;
			}
		}
	}

};

MSID.CommClient = new function() {

	var self = this;

	self.TRANSPORT = { FLASH: 0, IFRAME: 1 };
	self.CHANNELTYPE = { CLIENT: 0, SERVER: 1 };
	self.currentTransport = self.TRANSPORT.IFRAME;
	self.consumerKey = '';
	self.sessionToken = '';
	self.gadgetUrl = '';
	self.friendID = '';
	self.relayUrl = '';

	var method = 'POST', msconnectcookie = 'msconnectpersist', xdClient = null, xdServer = null;

	var checksuccess = function() {
		//console.log('i\'m ready');

		//clear firefox status bar
		window.status = '';
	};

	var handleResponse = function(msg) {
		//console.log(msg);
	};

	self.getPairedIDs = function() {
		var guid1, guid2, interval;

		guid1 = MSID.GUID.generate();
		interval = window.setInterval(function() { window.clearInterval(interval); }, 500);
		guid2 = MSID.GUID.generate();

		return [guid1, guid2];
	};

	self.setupChannels = function(responseCallback) {

		var ids = self.getPairedIDs(), clientChannel, serverChannel;

		clientChannel = ids[0];
		serverChannel = ids[1];

		//hardcoded userid temporary for test2@testies.com
		//console.log('Setting up IFPC Channel');

		MSID.Internal.setupDataChannel(self.friendID, self.consumerKey, self.sessionToken, serverChannel, clientChannel, self.gadgetUrl, responseCallback);

		window.setTimeout(checksuccess, 1000);
	};
};

MSID.Debug = new function() {

	var self = this;

	//~TODO: move this to debug later
	self.debugJSLCookieInfo = function(renderHtml) {
		var info;

		if (undefined !== renderHtml && renderHtml === true) {
			info = '<strong>myspaceid.response_code: </strong>' + MSID.Core.getCookie('myspaceid.response_code') + '<br />' +
				'<strong>myspaceid.target_domain: </strong>' + MSID.Core.getCookie('myspaceid.target_domain') + '<br />' +
			//'myspaceid.consumer_key: ' + MSID.Core.getCookie('myspaceid.consumer_key') + '<br />' +
				'<strong>myspaceid.identity: </strong>' + MSID.Core.getCookie('myspaceid.identity') + '<br />' +
				'<strong>myspaceid.request_token: </strong>' + MSID.Core.getCookie('myspaceid.request_token') + '<br />' +
				'<strong>myspaceid.session_token: </strong>' + MSID.Core.getCookie('myspaceid.session_token') + '<br />' +
				'<strong>myspaceid.signature: </strong>' + MSID.Core.getCookie('myspaceid.signature') + '<br />' +
				'<strong>myspaceid.signed: </strong>' + MSID.Core.getCookie('myspaceid.signed') + '<br />' +
				'<strong>myspaceid.friendid: </strong>' + MSID.Core.getCookie('myspaceid.friendid');
		}
		else {
			info = 'myspaceid.response_code: ' + MSID.Core.getCookie('myspaceid.response_code') + '\r\n' +
				'myspaceid.target_domain: ' + MSID.Core.getCookie('myspaceid.target_domain') + '\r\n' +
			//'myspaceid.consumer_key: ' + MSID.Core.getCookie('myspaceid.consumer_key') + '\r\n' +
				'myspaceid.identity: ' + MSID.Core.getCookie('myspaceid.identity') + '\r\n' +
				'myspaceid.request_token: ' + MSID.Core.getCookie('myspaceid.request_token') + '\r\n' +
				'myspaceid.session_token: ' + MSID.Core.getCookie('myspaceid.session_token') + '\r\n' +
				'myspaceid.signature: ' + MSID.Core.getCookie('myspaceid.signature') + '\r\n' +
				'myspaceid.signed: ' + MSID.Core.getCookie('myspaceid.signed') + '\r\n' +
				'myspaceid.friendid:' + MSID.Core.getCookie('myspaceid.friendid');
		}

		return info;
	};
};

MSID.Connect = new function() {
	var self = this,
		isValidated = false,
	//this url should be in the bootstrap and dynamically generated so that we can change it at anytime
		loginWindow,
		loginWindowHeight = 250,
		loginWindowWidth = 300,
		clientLoginCallback,
		loginButton = '<a href="#login" class="msid__login" onclick="Connect.login()"><img src="myspaceid.png" alt="Login with MySpaceID"/></a>',
		consumerKey = '',
		targetDomain = '',
		loginUrl = '',
		loginErrorUrl = '',
		connectCallbacks = [],
		tryConnectCallback = null,
		loginOnExpire = false,
		loginOnExpireCallback = null,
		responseStatus = '',
		responseMessage = '',
	/*
	How the jsl library is being hosted
		
	2 options:
		
	default: JSL is hosted directly on page
	iframe: JSL is being hosted on a page within an iframe
	*/
		requestTarget = 'default';

	//this is actually set by the bootstrap
	self.isConnected = false;
	self.connectStatusCode = '';
	self.loginTimeout = 30000; //30 seconds
	self.loginTimeoutID = 0;

	//~comment: determines if library is ready and loaded
	self.ready = false;

	//~comment: number of retry attempts to wait for the library to load
	self.readyRetries = 15;

	var persistResponse = function(value) {

		/*
		Success = 1,
		UserDeclined = 2,
		InvalidTargetDomain = 3,
		InvalidConsumerKey = 4,
		MissingParameters = 5,
		InternalError = 6
		*/

		var response = String(value);
		if (response.indexOf('#') === 0) {
			response = response.substr(1);
		}

		var responseDetail = response.split('&');

		for (var i = 0; i < responseDetail.length; i++) {

			var temp, cookieKey, cookieValue, firstInstance, lastInstance;

			firstInstance = responseDetail[i].indexOf('=');
			lastInstance = responseDetail[i].lastIndexOf('=');

			//24 hours
			if (firstInstance != lastInstance) {
				cookieValue = responseDetail[i].substring(firstInstance + 1);
				cookieKey = responseDetail[i].substring(0, firstInstance);
			}
			else {
				temp = responseDetail[i].split('=');

				cookieKey = temp[0];
				cookieValue = temp[1];
			}

			MSID.Core.setCookie(cookieKey, cookieValue, 1440, '/');

		}
	};

	var loginWindowTimeout = function() {
		/*
		if login has not been completed within the alloted
		timeout
		*/
		if (loginWindow) {
			try {
				if (loginWindow.opener !== null) {
					loginWindow.location = loginErrorUrl;
				}
			} catch (exception) {
				console.log(exception);
			}
		}

		if (self.loginTimeoutID !== 0) {
			window.clearTimeout(self.loginTimeoutID);
		}
	};

	self.setupChannel = function() {

		//console.log('setupChannel: start');

		//we know that the user has not logged in, let's exit out quickly
		if (self.connectStatusCode == MSID.Connect.Enums.status.COOKIE_NOT_FOUND) {
			//console.log('setupChannel: cookie not found');

			var connectResponse = new MSID.ConnectResponse(MSID.Connect.Enums.status.COOKIE_NOT_FOUND, MSID.Connect.Enums.getStatusMessage(MSID.Connect.Enums.status.COOKIE_NOT_FOUND));

			if (typeof tryConnectCallback !== 'undefined' && tryConnectCallback !== null) {
				tryConnectCallback(connectResponse);
			}

			return;
		}

		//console.log('setupChannel: isConnected-' + self.isConnected);

		//only setupChannels if we are connected and ready to make data calls
		//we should be logged in properly
		if (self.isConnected) {

			//let's do channel setup here
			MSID.CommClient.sessionToken = MSID.Core.getCookie(MSID.Enums.CookieKeys.SessionToken);
			MSID.CommClient.friendID = MSID.Core.getCookie(MSID.Enums.CookieKeys.FriendID);
			MSID.CommClient.setupChannels(tryConnectCallback);
		}
		else {
			//MSID.CommClient.setupChannels(responseCallback);
		}
	};

	/*
	Description:	This will fire a callback when tryConnect is successful.
	If the document is ready than it will fire right away, else
	it will wait till the document is ready.
	*/
	self.registerConnectHandler = function(onConnectCallback) {
		if (typeof onConnectCallback !== 'function') {
			return 'invalid function';
		}

		//if JavaScript is ready and page is loaded
		if (MSID.Core.jsReady) {
			onConnectCallback();
		} else {
			connectCallbacks.push(onConnectCallback);
		}
	};

	var deleteMSIDCookies = function() {

		var cookies = document.cookie.split(';');

		for (var i = 0; i < cookies.length; i++) {
			if (cookies[i].indexOf('myspaceid') != -1) {

				//split the key value pair
				var cookieDetail = cookies[i].split('=');

				//trim the cookie
				MSID.Core.deleteCookie(cookieDetail[0].trim(), '/');
			}
		}
	};

	/*
	Logout function for the JSL Library
	*/
	self.logout = function(logoutCallback) {

		var varType = typeof logoutCallback;

		//delete MSID Cookies
		deleteMSIDCookies();

		self.isConnected = false;

		MSID.Connect.connectStatusCode = MSID.Connect.Enums.status.COOKIE_NOT_FOUND;

		var container = MSID.Container.get();
		if (container)
			container.disable();

		if (varType !== 'undefined' && varType === 'function') {
			logoutCallback();
		}
	};

	self.loginReceiver = function(accessToken) {

		//close the login window
		loginWindow.close();

		//clear the timeout
		if (self.loginTimeoutID !== 0) {
			window.clearTimeout(self.loginTimeoutID);
		}

		//persist the response to a client cookie on the 3rd part domain
		persistResponse(accessToken);

		//~TODO: debug coookie info
		//if (MSID.mode == MSID.MODES.DEBUG) { console.log(MSID.Debug.debugJSLCookieInfo(false)); }

		var responseCode = MSID.Core.getCookie(MSID.Enums.CookieKeys.ResponseCode);

		if (responseCode == MSID.Connect.Enums.status.LOGIN_SUCCESS) {

			self.isConnected = true;
			self.connectStatusCode = MSID.Connect.Enums.status.LOGIN_SUCCESS;

			MSID.CommClient.sessionToken = MSID.Core.getCookie(MSID.Enums.CookieKeys.SessionToken);
			MSID.CommClient.friendID = MSID.Core.getCookie(MSID.Enums.CookieKeys.FriendID);
			MSID.CommClient.setupChannels(clientLoginCallback);

			if (connectCallbacks.length > 0) {
				var response = {
					'statusCode': MSID.Connect.Enums.status.READY,
					'statusMessage': MSID.Connect.Enums.getStatusMessage(MSID.Connect.Enums.status.READY)
				};

				for (var cb in connectCallbacks) {
					connectCallbacks[cb](response);
				}
			}
		}
		else {
			//if we failed do nothing, it is up to the 3rd party dev to handle this case
		}
	};

	self.requestLogin = function(loginCallback) {

		//check to see if tryConnect has been called
		if (typeof MSID.Connect.statusCode === 'undefined') {
			MSID.Connect.tryConnect();
		}

		var winWidth = 650, winHeight = 500, winX, winY, screenWidth, screenHeight;

		screenWidth = window.screen.width;
		screenHeight = window.screen.height;

		winX = Math.round((screenWidth - winWidth) / 2);
		winY = Math.round((screenHeight - winHeight) / 2);

		clientLoginCallback = loginCallback;
		loginWindow = window.open('', 'myspace_link_login', 'resize=1,location=0,status=1,scrollbars=1,width=' + winWidth + ',height=' + winHeight);

        try {
    		loginWindow.moveTo(0, 0);
    		loginWindow.moveBy(winX, winY);
    		loginWindow.location = loginUrl;
        } catch (e) {
        }
	};

	self.init = function(params) {
		if (params.hasOwnProperty("consumerKey")) {
			consumerKey = params.consumerKey;
		}

		if (params.hasOwnProperty("targetDomain")) {
			targetDomain = params.targetDomain;
		}

		if (params.hasOwnProperty("loginOnExpire")) {
			loginOnExpire = params.loginOnExpire;
		}

		if (params.hasOwnProperty("requestTarget")) {
			requestTarget = params.reqestTarget;
		}

		//if we are in a iframe set requestTarget = iframe
		if (top != self) {
			requestTarget = "iframe";
		}

		loginUrl = MSID.baseUrl + '/openid?myspaceid.request_target=' + requestTarget + '&myspaceid.consumer_key=' + consumerKey + '&myspaceid.target_domain=' + MSID.Core.urlEncode(targetDomain);
		loginErrorUrl = MSID.baseUrl + '/openid/Modules/OpenID/Pages/Error.aspx?displayclose=true';
		loginUrl = MSID.loginBaseUrl + '/openid?myspaceid.request_target=' + requestTarget + '&myspaceid.consumer_key=' + consumerKey + '&myspaceid.target_domain=' + MSID.Core.urlEncode(targetDomain);
		loginErrorUrl = MSID.loginBaseUrl + '/openid/Modules/OpenID/Pages/Error.aspx?displayclose=true';

		//~comment: if target domain does not end with a forward slash
		//if (targetDomain.charAt(targetDomain.length - 1) != '/') {
		//	targetDomain += '/';
		//}
		//var remoteRelayUrl = targetDomain + 'RelayReceiver.html';

		var remoteRelayUrl = targetDomain;

		MSID.CommClient.consumerKey = consumerKey;
		MSID.CommClient.relayUrl = remoteRelayUrl;
		MSID.Internal.remoteRelayUrl = remoteRelayUrl;
		

		MyOpenSpace.MySpaceContainer.container_ = new MyOpenSpace.MySpaceContainer(self.userId, self.userId, self.sessionToken, self.consumerKey, self.sideBySide);
	};

	var initiateCallbacks = function(response) {

		var timeoutWait = 300;
		var count = 1;
		var maxSpin = 15;
		var timeoutHandle = null;

		//if page is not ready, let's spin
		if (!MSID.Core.jsReady) {

			timeoutHandle = window.setTimeout(function() {
				//console.log('intiateCallbacks spin: ' + count.toString());

				count += 1;

				if (count == maxSpin) {
					window.clearTimeout(timeoutHandle);
					return;
				};

				if (MSID.Core.jsReady) {
					initiateCallbacksExecute(response);
				} //if

			}, timeoutWait);

		} else {
			initiateCallbacksExecute(response);
		}
	};

	var initiateCallbacksExecute = function(response) {
		//console.log('initiateCallbacks ready');

		//callback tryConnect callback first
		if (tryConnectCallback != null) {
			tryConnectCallback({
				'statusCode': response.statusCode,
				'statusMessage': response.statusMessage
			});
		}

		//channel is ready, onSuccess callbacks
		if (response.statusCode === MSID.Connect.Enums.status.READY) {
			if (connectCallbacks.length > 0) {
				for (var i = 0; i < connectCallbacks.length; i++) {
					connectCallbacks[i]();
				}
			}
		}
		else if (response.statusCode === MSID.Connect.Enums.status.INVALID_SESSION_TOKEN && loginOnExpire) {
			var varType = typeof loginOnExpireCallback;

			if (varType !== 'undefined' && varType === 'function') {
				self.requestLogin(loginOnExpireCallback, 0);
			}
			else {
				self.requestLogin();
			}
		} //if
	};

	var tryConnectResponse = function(code, message) {

		var varType = typeof tryConnectCallback;

		//if js is ready 
		if (MSID.Core.jsReady && varType !== 'undefined' && varType === 'function') {
			tryConnectCallback({
				statusCode: code,
				statusMessage: message
			});
		}
		else {
			responseStatus = code;
			responseMessage = message;
		}
	};

	self.tryConnect = function(cb) {
		var varType = typeof cb;

		if (varType !== 'undefined' && varType === 'function') {
			tryConnectCallback = cb;
		}

		//we've already connected - we don't need to go again, call the callbacks for api purposes
		if (typeof MSID.Connect.connectStatusCode !== 'undefined' && MSID.Connect.connectStatusCode === MSID.Connect.Enums.status.LOGIN_SUCCESS) {

			tryConnectResponse(
				MSID.Connect.Enums.status.READY,
				MSID.Connect.Enums.getStatusMessage(MSID.Connect.Enums.status.READY)
			);

			/*
			this multicast callback only gets called on success
			
			since we are connected and ready lets call these back
			*/
			if (connectCallbacks.length > 0) {
				var response = {
					'statusCode': MSID.Connect.Enums.status.READY,
					'statusMessage': MSID.Connect.Enums.getStatusMessage(MSID.Connect.Enums.status.READY)
				};
				for (var cb in connectCallbacks) {
					connectCallbacks[cb](response);
				}
			}

			//lets exit here
			return;
		}

		if (consumerKey === undefined || consumerKey === '') {
			tryConnectResponse(
				MSID.Connect.Enums.status.INVALID_CONSUMER_KEY,
				MSID.Connect.Enums.getStatusMessage(MSID.Connect.Enums.status.INVALID_CONSUMER_KEY)
			);
		}
		else if (targetDomain === undefined) {
			tryConnectResponse(
				MSID.Connect.Enums.status.INVALID_TARGET_DOMAIN,
				MSID.Connect.Enums.getStatusMessage(MSID.Connect.Enums.status.INVALID_TARGET_DOMAIN)
			);
		}

		if (self.ready) {
			//console.log("tryConnect: ready");
			self.setupChannel(); //setupChannel
		}
		else {
			//console.log("tryConnect: not ready, waiting for load to setup channel")
			MSID.Core.addLoadEvent(self.setupChannel);
		}
	}; //end tryConnect
};

/*
All user interface widgets go here.  They are here too assist 3rd party developers.
*/
MSID.Connect.UI = new function() {
	var self = this;
	self.loginButton = function(clientCallback) {
		if (clientCallback === undefined) { clientCallback = null; }

		return '<a href="#login" class="msid__login" onclick="MSID.Connect.requestLogin(' + clientCallback + ', 0)"><img src="myspaceid.png" alt="Login with MySpaceID"/></a>';
	};
};

MSID.Connect.Enums = new function() {

	var self = this;

	self.status = {
		'UNKNOWN_ERROR': -1,
		'NOT_INITIALIZED': 0,
		'LOGIN_SUCCESS': 1,
		'LOGIN_FAILED': 2,
		'INVALID_TARGET_DOMAIN': 3,
		'INVALID_CONSUMER_KEY': 4,
		'MISSING_PARAMETERS': 5,
		'INTERNAL_ERROR': 6,
		'READY': 7,
		'COOKIE_NOT_FOUND': 8,
		'NOT_LOGGED_IN': 9,
		'LIBRARY_INIT_FAILED': 10,
		'LIBRARY_INIT_TIMEOUT': 11,
		'INVALID_REMOTE_RELAY': 12,
		'INVALID_SESSION_TOKEN': 13
	};

	statusMessage = {
		'UNKNOWN_ERROR': 'An error has occured.',
		'NOT_INITIALIZED': 'Status in not initialized yet',
		'LOGIN_SUCCESS': 'Sucessfully logged in.',
		'LOGIN_FAILED': 'Failed to login, please check username/password and try again.',
		'INVALID_TARGET_DOMAIN': 'The target domain was invalid.',
		'INVALID_CONSUMER_KEY': 'Check your consumer key.',
		'MISSING_PARAMETERS': 'Not all the the required parameters are present.',
		'INTERNAL_ERROR': 'Expected error, try again.',
		'READY': 'Ready for request.',
		'COOKIE_NOT_FOUND': 'Required cookie was not found, please login.',
		'NOT_LOGGED_IN': 'You are currently not logged in.',
		'LIBRARY_INIT_FAILED': 'Library initiation failed, please reload and try again.',
		'LIBRARY_INIT_TIMEOUT': 'Library initiation timed out, please try again.',
		'INVALID_REMOTE_RELAY': 'The remote relay specified did not match what is specified in your application.  Please check and try again.',
		'INVALID_SESSION_TOKEN': 'Your session token is either expired or bad, please login again.'

	};

	self.getStatusMessage = function(status) {

		switch (status) {
			case self.status.UNKNOWN_ERROR:
				return statusMessage.UNKNOWN_ERROR;
			case self.status.NOT_INITIALIZED:
				return statusMessage.NOT_INITIALIZED;
			case self.status.LOGIN_SUCCESS:
				return statusMessage.LOGIN_SUCCESS;
			case self.status.LOGIN_FAILED:
				return statusMessage.LOGIN_FAILED;
			case self.status.INVALID_TARGET_DOMAIN:
				return statusMessage.INVALID_TARGET_DOMAIN;
			case self.status.INVALID_CONSUMER_KEY:
				return statusMessage.INVALID_CONSUMER_KEY;
			case self.status.MISSING_PARAMETERS:
				return statusMessage.MISSING_PARAMETERS;
			case self.status.INTERNAL_ERROR:
				return statusMessage.INTERNAL_ERROR;
			case self.status.INTERNAL_ERROR:
				return statusMessage.INTERNAL_ERROR;
			case self.status.READY:
				return statusMessage.READY;
			case self.status.COOKIE_NOT_FOUND:
				return statusMessage.COOKIE_NOT_FOUND;
			case self.status.NOT_LOGGED_IN:
				return statusMessage.NOT_LOGGED_IN;
			case self.status.LIBRARY_INIT_FAILED:
				return statusMessage.LIBRARY_INIT_FAILED;
			case self.status.LIBRARY_INIT_TIMEOUT:
				return statusMessage.LIBRARY_INIT_TIMEOUT;
			case self.status.INVALID_REMOTE_RELAY:
				return statusMessage.INVALID_REMOTE_RELAY;
			case self.status.INVALID_SESSION_TOKEN:
				return statusMessage.INVALID_SESSION_TOKEN;
			default:
				return '?';
		}
	};

	self.getEnumKey = function(definedEnum, value) {

		for (var item in definedEnum) {

			if (value == definedEnum[item]) {
				return item;
			}
		}
	};

	self.getError = function(responseCode, responseMessage) {

		if (responseCode === 'forbidden') {

			switch (responseMessage) {
				case 'error validating application from consumer key':
					return { 'code': self.status.INVALID_CONSUMER_KEY, 'message': self.getStatusMessage(self.status.INVALID_CONSUMER_KEY) };
				case 'session token has expired':
					return { 'code': self.status.INVALID_SESSION_TOKEN, 'message': self.getStatusMessage(self.status.INVALID_SESSION_TOKEN) };
				default:
					return { 'code': self.status.UNKNOWN_ERROR, 'message': self.getStatusMessage(self.status.UNKNOWN_ERROR) };
			}
		}
		else if (responseCode === 'internalError') {

			if (typeof responseMessage === 'string' && responseMessage.length > 0) {
				return { 'code': self.status.INTERNAL_ERROR, 'message': responseMessage };
			}
			else {
				return { 'code': self.status.INTERNAL_ERROR, 'message': self.getStatusMessage(self.status.INTERNAL_ERROR) };
			}
		}
		else if (responseCode === 'unauthorized' && responseMessage === 'You are not logged in') {
			return { 'code': self.status.NOT_LOGGED_IN, 'message': responseMessage };
		}
		else {

			if (typeof responseMessage === 'string' && responseMessage.length > 0) {
				return { 'code': self.status.UNKNOWN_ERROR, 'message': responseMessage };
			}
			else {
				return { 'code': self.status.UNKNOWN_ERROR, 'message': self.getStatusMessage(self.status.UNKNOWN_ERROR) };
			}
		}
	};
};


/*********** rpc ************/

/*
Copyright [yyyy] [name of copyright owner] 
 
Licensed under the Apache License, Version 2.0 (the "License"); 
you may not use this file except in compliance with the License. 
You may obtain a copy of the License at 
    
http://www.apache.org/licenses/LICENSE-2.0 
 
Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an "AS IS" BASIS, 
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
See the License for the specific language governing permissions and 
limitations under the License. 
*/

var gadgets = gadgets || {};

/**
* @fileoverview General purpose utilities that gadgets can use.
*/

/**
* @static
* @class Provides general-purpose utility functions.
* @name gadgets.util
*/
gadgets.util = function() {

	/**
	* Parses URL parameters into an object.
	* @return {Array.&lt;String&gt;} The parameters
	*/
	function parseUrlParams() {
		// Get settings from url, 'hash' takes precedence over 'search' component
		// don't use document.location.hash due to browser differences.
		var query;
		var l = document.location.href;
		var queryIdx = l.indexOf("?");
		var hashIdx = l.indexOf("#");
		if (hashIdx === -1) {
			query = l.substr(queryIdx + 1);
		} else {
			// essentially replaces "#" with "&"
			query = [l.substr(queryIdx + 1, hashIdx - queryIdx - 1), "&",
               l.substr(hashIdx + 1)].join("");
		}
		return query.split("&");
	}

	var parameters = null;
	var features = {};
	var onLoadHandlers = [];

	// Maps code points to the value to replace them with.
	// If the value is "false", the character is removed entirely, otherwise
	// it will be replaced with an html entity.
	var escapeCodePoints = {
		// nul; most browsers truncate because they use c strings under the covers.
		0: false,
		// new line
		10: true,
		// carriage return
		13: true,
		// double quote
		34: true,
		// single quote
		39: true,
		// less than
		60: true,
		// greater than
		62: true,
		// Backslash
		92: true,
		// line separator
		8232: true,
		// paragraph separator
		8233: true
	};

	/**
	* Regular expression callback that returns strings from unicode code points.
	*
	* @param {Array} match Ignored
	* @param {String} value The codepoint value to convert
	* @return {String} The character corresponding to value.
	*/
	function unescapeEntity(match, value) {
		return String.fromCharCode(value);
	}

	/**
	* Initializes feature parameters.
	*/
	function init(config) {
		features = config["core.util"] || {};
	}

	if (gadgets.config) {
		gadgets.config.register("core.util", null, init);
	}

	return /** @scope gadgets.util */{

	/**
	* Gets the URL parameters.
	*
	* @return {Object} Parameters passed into the query string
	* @member gadgets.util
	* @private Implementation detail.
	*/
	getUrlParameters: function() {
		if (parameters !== null) {
			return parameters;
		}
		parameters = {};
		var pairs = parseUrlParams();
		var unesc = window.decodeURIComponent ? decodeURIComponent : unescape;
		for (var i = 0, j = pairs.length; i < j; ++i) {
			var pos = pairs[i].indexOf('=');
			if (pos === -1) {
				continue;
			}
			var argName = pairs[i].substring(0, pos);
			var value = pairs[i].substring(pos + 1);
			// difference to IG_Prefs, is that args doesn't replace spaces in
			// argname. Unclear on if it should do:
			// argname = argname.replace(/\+/g, " ");
			value = value.replace(/\+/g, " ");
			parameters[argName] = unesc(value);
		}
		return parameters;
	},

	/**
	* Creates a closure that is suitable for passing as a callback.
	* Any number of arguments
	* may be passed to the callback;
	* they will be received in the order they are passed in.
	*
	* @param {Object} scope The execution scope; may be null if there is no
	*     need to associate a specific instance of an object with this
	*     callback
	* @param {Function} callback The callback to invoke when this is run;
	*     any arguments passed in will be passed after your initial arguments
	* @param {Object} var_args Initial arguments to be passed to the callback
	*
	* @member gadgets.util
	* @private Implementation detail.
	*/
	makeClosure: function(scope, callback, var_args) {
		// arguments isn't a real array, so we copy it into one.
		var baseArgs = [];
		for (var i = 2, j = arguments.length; i < j; ++i) {
			baseArgs.push(arguments[i]);
		}
		return function() {
			// append new arguments.
			var tmpArgs = baseArgs.slice();
			for (var i = 0, j = arguments.length; i < j; ++i) {
				tmpArgs.push(arguments[i]);
			}
			return callback.apply(scope, tmpArgs);
		};
	},

	/**
	* Utility function for generating an "enum" from an array.
	*
	* @param {Array.<String>} values The values to generate.
	* @return {Map&lt;String,String&gt;} An object with member fields to handle
	*   the enum.
	*
	* @private Implementation detail.
	*/
	makeEnum: function(values) {
		var obj = {};
		for (var i = 0, v; (v = values[i]); ++i) {
			obj[v] = v;
		}
		return obj;
	},

	/**
	* Gets the feature parameters.
	*
	* @param {String} feature The feature to get parameters for
	* @return {Object} The parameters for the given feature, or null
	*
	* @member gadgets.util
	*/
	getFeatureParameters: function(feature) {
		return typeof features[feature] === "undefined" ? null : features[feature];
	},

	/**
	* Returns whether the current feature is supported.
	*
	* @param {String} feature The feature to test for
	* @return {Boolean} True if the feature is supported
	*
	* @member gadgets.util
	*/
	hasFeature: function(feature) {
		return typeof features[feature] !== "undefined";
	},

	/**
	* Registers an onload handler.
	* @param {Function} callback The handler to run
	*
	* @member gadgets.util
	*/
	registerOnLoadHandler: function(callback) {
		onLoadHandlers.push(callback);
	},

	/**
	* Runs all functions registered via registerOnLoadHandler.
	* @private Only to be used by the container, not gadgets.
	*/
	runOnLoadHandlers: function() {
		for (var i = 0, j = onLoadHandlers.length; i < j; ++i) {
			onLoadHandlers[i]();
		}
	},

	/**
	* Escapes the input using html entities to make it safer.
	*
	* If the input is a string, uses gadgets.util.escapeString.
	* If it is an array, calls escape on each of the array elements
	* if it is an object, will only escape all the mapped keys and values if
	* the opt_escapeObjects flag is set. This operation involves creating an
	* entirely new object so only set the flag when the input is a simple
	* string to string map.
	* Otherwise, does not attempt to modify the input.
	*
	* @param {Object} input The object to escape
	* @param {Boolean} opt_escapeObjects Whether to escape objects.
	* @return {Object} The escaped object
	* @private Only to be used by the container, not gadgets.
	*/
	escape: function(input, opt_escapeObjects) {
		if (!input) {
			return input;
		} else if (typeof input === "string") {
			return gadgets.util.escapeString(input);
		} else if (typeof input === "array") {
			for (var i = 0, j = input.length; i < j; ++i) {
				input[i] = gadgets.util.escape(input[i]);
			}
		} else if (typeof input === "object" && opt_escapeObjects) {
			var newObject = {};
			for (var field in input) {
				if (input.hasOwnProperty(field)) {
					newObject[gadgets.util.escapeString(field)] = gadgets.util.escape(input[field], true);
				}
			}
			return newObject;
		}
		return input;
	},

	/**
	* Escapes the input using html entities to make it safer.
	*
	* Currently not in the spec -- future proposals may change
	* how this is handled.
	*
	* TODO: Parsing the string would probably be more accurate and faster than
	* a bunch of regular expressions.
	*
	* @param {String} str The string to escape
	* @return {String} The escaped string
	*/
	escapeString: function(str) {
		var out = [], ch, shouldEscape;
		for (var i = 0, j = str.length; i < j; ++i) {
			ch = str.charCodeAt(i);
			shouldEscape = escapeCodePoints[ch];
			if (shouldEscape === true) {
				out.push("&#", ch, ";");
			} else if (shouldEscape !== false) {
				// undefined or null are OK.
				out.push(str.charAt(i));
			}
		}
		return out.join("");
	},

	/**
	* Reverses escapeString
	*
	* @param {String} str The string to unescape.
	*/
	unescapeString: function(str) {
		return str.replace(/&#([0-9]+);/g, unescapeEntity);
	}
};



} ();


// Initialize url parameters so that hash data is pulled in before it can be


// altered by a click.


gadgets.util.getUrlParameters();



/**


* @fileoverview


* The global object gadgets.json contains two methods.


*


* gadgets.json.stringify(value) takes a JavaScript value and produces a JSON


* text. The value must not be cyclical.


*


* gadgets.json.parse(text) takes a JSON text and produces a JavaScript value.


* It will return false if there is an error.


*/



/**


* @static


* @class Provides operations for translating objects to and from JSON.


* @name gadgets.json


*/



/**


* Port of the public domain JSON library by Douglas Crockford.


* See: http://www.json.org/json2.js


*/


gadgets.json = function() {



	/**

	* Formats integers to 2 digits.

	* @param {Number} n

	*/


	function f(n) {


		return n < 10 ? '0' + n : n;


	}



	Date.prototype.toJSON = function() {


		return [this.getUTCFullYear(), '-',
           f(this.getUTCMonth() + 1), '-',
           f(this.getUTCDate()), 'T',
           f(this.getUTCHours()), ':',
           f(this.getUTCMinutes()), ':',
           f(this.getUTCSeconds()), 'Z'].join("");


	};



	// table of character substitutions


	var m = {


		'\b': '\\b',


		'\t': '\\t',


		'\n': '\\n',


		'\f': '\\f',


		'\r': '\\r',


		'"': '\\"',


		'\\': '\\\\'


	};



	/**

	* Converts a json object into a string.

	*/


	function stringify(value) {


		var a,          // The array holding the partial texts.
        i,          // The loop counter.
        k,          // The member key.
        l,          // Length.
        r = /["\\\x00-\x1f\x7f-\x9f]/g,
        v;          // The member value.



		switch (typeof value) {


			case 'string':


				// If the string contains no control characters, no quote characters, and no


				// backslash characters, then we can safely slap some quotes around it.


				// Otherwise we must also replace the offending characters with safe ones.


				return r.test(value) ?
          '"' + value.replace(r, function(a) {
          	var c = m[a];
          	if (c) {
          		return c;
          	}
          	c = a.charCodeAt();
          	return '\\u00' + Math.floor(c / 16).toString(16) +
                (c % 16).toString(16);
          }) + '"' : '"' + value + '"';


			case 'number':


				// JSON numbers must be finite. Encode non-finite numbers as null.


				return isFinite(value) ? String(value) : 'null';


			case 'boolean':


			case 'null':


				return String(value);


			case 'object':


				// Due to a specification blunder in ECMAScript,


				// typeof null is 'object', so watch out for that case.


				if (!value) {


					return 'null';


				}


				// toJSON check removed; re-implement when it doesn't break other libs.


				a = [];


				if (typeof value.length === 'number' &&
          !value.propertyIsEnumerable('length')) {


					// The object is an array. Stringify every element. Use null as a


					// placeholder for non-JSON values.


					l = value.length;


					for (i = 0; i < l; i += 1) {


						a.push(stringify(value[i]) || 'null');


					}


					// Join all of the elements together and wrap them in brackets.


					return '[' + a.join(',') + ']';


				}


				// Otherwise, iterate through all of the keys in the object.


				for (k in value) {


					if (value.hasOwnProperty(k)) {


						if (typeof k === 'string') {


							v = stringify(value[k]);


							if (v) {


								a.push(stringify(k) + ':' + v);


							}


						}


					}


				}


				// Join all of the member texts together and wrap them in braces.


				return '{' + a.join(',') + '}';


		}


	}



	return {


		stringify: stringify,


		parse: function(text) {


			// Parsing happens in three stages. In the first stage, we run the text against


			// regular expressions that look for non-JSON patterns. We are especially


			// concerned with '()' and 'new' because they can cause invocation, and '='


			// because it can cause mutation. But just to be safe, we want to reject all


			// unexpected forms.



			// We split the first stage into 4 regexp operations in order to work around


			// crippling inefficiencies in IE's and Safari's regexp engines. First we


			// replace all backslash pairs with '@' (a non-JSON character). Second, we


			// replace all simple value tokens with ']' characters. Third, we delete all


			// open brackets that follow a colon or comma or that begin the text. Finally,


			// we look to see that the remaining characters are only whitespace or ']' or


			// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.



			if (/^[\],:{}\s]*$/.test(text.replace(/\\["\\\/b-u]/g, '@').
          replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
          replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {


				return eval('(' + text + ')');


			}


			// If the text is not JSON parseable, then return false.



			return false;


		}


	};


} ();



/**


* @fileoverview Remote procedure call library for gadget-to-container,


* container-to-gadget, and gadget-to-gadget (thru container) communication.


*/



/**


* @static


* @class Provides operations for making rpc calls.


* @name MSID.rpc


*/


MSID.rpc = function() {


	// General constants.


	var CALLBACK_NAME = '__cb';


	var DEFAULT_NAME = '';



	// Consts for FrameElement.


	var FE_G2C_CHANNEL = '__g2c_rpc';


	var FE_C2G_CHANNEL = '__c2g_rpc';



	// Consts for NIX. VBScript doesn't


	// allow items to start with _ for some reason,


	// so we need to make these names quite unique, as


	// they will go into the global namespace.


	var NIX_WRAPPER = 'GRPC____NIXVBS_wrapper';


	var NIX_GET_WRAPPER = 'GRPC____NIXVBS_get_wrapper';


	var NIX_HANDLE_MESSAGE = 'GRPC____NIXVBS_handle_message';


	var NIX_CREATE_CHANNEL = 'GRPC____NIXVBS_create_channel';



	// JavaScript reference to the NIX VBScript wrappers.


	// Gadgets will have but a single channel under


	// nix_channels['..'] while containers will have a channel


	// per gadget stored under the gadget's ID.


	var nix_channels = {};



	var services = {};


	var iframePool = [];


	var relayUrl = {};


	var useLegacyProtocol = {};


	var authToken = {};


	var callId = 0;


	var callbacks = {};


	var setup = {};


	var sameDomain = {};


	var params = {};


	var relayChannel;



	/*

	* Return a short code representing the best available cross-domain

	* message transport available to the browser.

	*

	* + For those browsers that support native messaging (various implementations

	*   of the HTML5 postMessage method), use that. Officially defined at

	*   http://www.whatwg.org/specs/web-apps/current-work/multipage/comms.html.

	*

	*   postMessage is a native implementation of XDC. A page registers that

	*   it would like to receive messages by listening the the "message" event

	*   on the window (document in DPM) object. In turn, another page can

	*   raise that event by calling window.postMessage (document.postMessage

	*   in DPM) with a string representing the message and a string

	*   indicating on which domain the receiving page must be to receive

	*   the message. The target page will then have its "message" event raised

	*   if the domain matches and can, in turn, check the origin of the message

	*   and process the data contained within.

	*

	*     wpm: postMessage on the window object.

	*        - Internet Explorer 8+

	*        - Safari (latest nightlies as of 26/6/2008)

	*        - Firefox 3+

	*        - Opera 9+

	*

	*     dpm: postMessage on the document object.

	*        - Opera 8+

	*

	* + For Internet Explorer before version 8, the security model allows anyone

	*   parent to set the value of the "opener" property on another window,

	*   with only the receiving window able to read it.

	*   This method is dubbed "Native IE XDC" (NIX).

	*

	*   This method works by placing a handler object in the "opener" property

	*   of a gadget when the container sets up the authentication information

	*   for that gadget (by calling setAuthToken(...)). At that point, a NIX

	*   wrapper is created and placed into the gadget by calling

	*   theframe.contentWindow.opener = wrapper. Note that as a result, NIX can

	*   only be used by a container to call a particular gadget *after* that

	*   gadget has called the container at least once via NIX.

	*

	*   The NIX wrappers in this RPC implementation are instances of a VBScript

	*   class that is created when this implementation loads. The reason for

	*   using a VBScript class stems from the fact that any object can be passed

	*   into the opener property.

	*   While this is a good thing, as it lets us pass functions and setup a true

	*   bidirectional channel via callbacks, it opens a potential security hole

	*   by which the other page can get ahold of the "window" or "document"

	*   objects in the parent page and in turn wreak havok. This is due to the

	*   fact that any JS object useful for establishing such a bidirectional

	*   channel (such as a function) can be used to access a function

	*   (eg. obj.toString, or a function itself) created in a specific context,

	*   in particular the global context of the sender. Suppose container

	*   domain C passes object obj to gadget on domain G. Then the gadget can

	*   access C's global context using:

	*   var parentWindow = (new obj.toString.constructor("return window;"))();

	*   Nulling out all of obj's properties doesn't fix this, since IE helpfully

	*   restores them to their original values if you do something like:

	*   delete obj.toString; delete obj.toString;

	*   Thus, we wrap the necessary functions and information inside a VBScript

	*   object. VBScript objects in IE, like DOM objects, are in fact COM

	*   wrappers when used in JavaScript, so we can safely pass them around

	*   without worrying about a breach of context while at the same time

	*   allowing them to act as a pass-through mechanism for information

	*   and function calls. The implementation details of this VBScript wrapper

	*   can be found in the setupChannel() method below.

	*

	*     nix: Internet Explorer-specific window.opener trick.

	*       - Internet Explorer 6

	*       - Internet Explorer 7

	*

	* + For Gecko-based browsers, the security model allows a child to call a

	*   function on the frameElement of the iframe, even if the child is in

	*   a different domain. This method is dubbed "frameElement" (fe).

	*

	*   The ability to add and call such functions on the frameElement allows

	*   a bidirectional channel to be setup via the adding of simple function

	*   references on the frameElement object itself. In this implementation,

	*   when the container sets up the authentication information for that gadget

	*   (by calling setAuth(...)) it as well adds a special function on the

	*   gadget's iframe. This function can then be used by the gadget to send

	*   messages to the container. In turn, when the gadget tries to send a

	*   message, it checks to see if this function has its own function stored

	*   that can be used by the container to call the gadget. If not, the

	*   function is created and subsequently used by the container.

	*   Note that as a result, FE can only be used by a container to call a

	*   particular gadget *after* that gadget has called the container at

	*   least once via FE.

	*

	*     fe: Gecko-specific frameElement trick.

	*        - Firefox 1+

	*

	* + For all others, we have a fallback mechanism known as "ifpc". IFPC

	*   exploits the fact that while same-origin policy prohibits a frame from

	*   accessing members on a window not in the same domain, that frame can,

	*   however, navigate the window heirarchy (via parent). This is exploited by

	*   having a page on domain A that wants to talk to domain B create an iframe

	*   on domain B pointing to a special relay file and with a message encoded

	*   after the hash (#). This relay, in turn, finds the page on domain B, and

	*   can call a receipt function with the message given to it. The relay URL

	*   used by each caller is set via the gadgets.rpc.setRelayUrl(..) and

	*   *must* be called before the call method is used.

	*

	*     ifpc: Iframe-based method, utilizing a relay page, to send a message.

	*/

	function getRelayChannel() {

		return typeof window.postMessage === 'function' ? 'wpm' :
           typeof document.postMessage === 'function' ? 'dpm' :
		window.ActiveXObject ? (typeof window.postMessage === 'object' ? 'wpm' : 'nix') :

		//   window.ActiveXObject ? (typeof window.postMessage === 'object' ? 'wpm' : 'ifpc') :
           'ifpc';

	}
	// Pick the most efficient RPC relay mechanism


	relayChannel = getRelayChannel();



	/**

	* Helper function to process an RPC request

	* @param {Object} rpc RPC request object

	* @private

	*/


	function process(rpc) {


		//


		// RPC object contents:


		//   s: Service Name


		//   f: From


		//   c: The callback ID or 0 if none.


		//   a: The arguments for this RPC call.


		//   t: The authentication token.


		//    

		if (rpc && typeof rpc.s === 'string' && typeof rpc.f === 'string' &&
        rpc.a instanceof Array) {



			// Validate auth token.


			if (authToken[rpc.f]) {


				// We don't do type coercion here because all entries in the authToken


				// object are strings, as are all url params. See setAuthToken(...).


				if (authToken[rpc.f] !== rpc.t) {


					throw new Error("Invalid auth token. " +
              authToken[rpc.f] + " vs " + rpc.t);


				}


			}



			// If there is a callback for this service, attach a callback function


			// to the rpc context object for asynchronous rpc services.


			//


			// Synchronous rpc request handlers should simply ignore it and return a


			// value as usual.


			// Asynchronous rpc request handlers, on the other hand, should pass its


			// result to this callback function and not return a value on exit.


			//


			// For example, the following rpc handler passes the first parameter back


			// to its rpc client with a one-second delay.


			//


			// function asyncRpcHandler(param) {


			//   var me = this;


			//   setTimeout(function() {


			//     me.callback(param);


			//   }, 1000);


			// }


			if (rpc.c) {


				rpc.callback = function(result) {


					MSID.rpc.call(rpc.f, CALLBACK_NAME, null, rpc.c, result);


				};


			}



			// Call the requested RPC service.


			var result = (services[rpc.s] ||
                    services[DEFAULT_NAME]).apply(rpc, rpc.a);



			// If the rpc request handler returns a value, immediately pass it back


			// to the callback. Otherwise, do nothing, assuming that the rpc handler


			// will make an asynchronous call later.


			if (rpc.c && typeof result !== 'undefined') {


				MSID.rpc.call(rpc.f, CALLBACK_NAME, null, rpc.c, result);


			}


		}


	}



	/**

	* Conducts any initial global work necessary to setup the

	* channel type chosen.

	*/


	function setupChannel() {
		// If the channel type is one of the native
		// postMessage based ones, setup the handler to receive
		// messages.
		if (relayChannel === 'dpm' || relayChannel === 'wpm') {

			var fn = function(packet) { process(gadgets.json.parse(packet.data)); };

			if (!window.addEventListener) {

				window.attachEvent('onmessage', fn);

			}

			else {

				window.addEventListener('message', fn, false);

			}

		}

		// If the channel type is NIX, we need to ensure the
		// VBScript wrapper code is in the page and that the
		// global Javascript handlers have been set.
		if (relayChannel === 'nix') {
			// VBScript methods return a type of 'unknown' when
			// checked via the typeof operator in IE. Fortunately
			// for us, this only applies to COM objects, so we
			// won't see this for a real Javascript object.
			if (typeof window[NIX_GET_WRAPPER] !== 'unknown') {
				window[NIX_HANDLE_MESSAGE] = function(data) {
					window.setTimeout(function() { process(gadgets.json.parse(data)); }, 0);
				};

				window[NIX_CREATE_CHANNEL] = function(name, channel, token) {
					// Verify the authentication token of the gadget trying
					// to create a channel for us.
					if (authToken[name] === token) {
						nix_channels[name] = channel;
					}
				};

				// Inject the VBScript code needed.
				var vbscript = 'Class ' +
				// We create a class to act as a wrapper for
				// a Javascript call, to prevent a break in of
				// the context.
					NIX_WRAPPER + '\n ' +

				// An internal member for keeping track of the
				// name of the document (container or gadget)
				// for which this wrapper is intended. For
				// those wrappers created by gadgets, this is not
				// used (although it is set to "..")
          			'Private m_Intended\n' +

				// Stores the auth token used to communicate with
				// the gadget. The GetChannelCreator method returns
				// an object that returns this auth token. Upon matching
				// that with its own, the gadget uses the object
				// to actually establish the communication channel.
					'Private m_Auth\n' +

				// Method for internally setting the value
				// of the m_Intended property.
					'Public Sub SetIntendedName(name)\n ' +
					'If isEmpty(m_Intended) Then\n' +
					'm_Intended = name\n' +
					'End If\n' +
					'End Sub\n' +

				// Method for internally setting the value of the m_Auth property.
					'Public Sub SetAuth(auth)\n ' +
					'If isEmpty(m_Auth) Then\n' +
					'm_Auth = auth\n' +
					'End If\n' +
					'End Sub\n' +

				// A wrapper method which actually causes a
				// message to be sent to the other context.
					'Public Sub SendMessage(data)\n ' +
					NIX_HANDLE_MESSAGE + '(data)\n' +
					'End Sub\n' +

				// Returns the auth token to the gadget, so it can
				// confirm a match before initiating the connection
					'Public Function GetAuthToken()\n ' +
					'GetAuthToken = m_Auth\n' +
					'End Function\n' +

				// Method for setting up the container->gadget
				// channel. Not strictly needed in the gadget's
				// wrapper, but no reason to get rid of it. Note here
				// that we pass the intended name to the NIX_CREATE_CHANNEL
				// method so that it can save the channel in the proper place
				// *and* verify the channel via the authentication token passed
				// here.
					'Public Sub CreateChannel(channel, auth)\n ' +
					'Call ' + NIX_CREATE_CHANNEL + '(m_Intended, channel, auth)\n' +
					'End Sub\n' +
					'End Class\n' +

				// Function to get a reference to the wrapper.
					'Function ' + NIX_GET_WRAPPER + '(name, auth)\n' +
					'Dim wrap\n' +
					'Set wrap = New ' + NIX_WRAPPER + '\n' +
					'wrap.SetIntendedName name\n' +
					'wrap.SetAuth auth\n' +
					'Set ' + NIX_GET_WRAPPER + ' = wrap\n' +
					'End Function';

				try {
					window.execScript(vbscript, 'vbscript');
				} catch (e) {
					// Fall through to IFPC.
					relayChannel = 'ifpc';
				}
			}
		}
	}

	// Conduct any setup necessary for the chosen channel.


	setupChannel();



	// Create the Default RPC handler.


	services[DEFAULT_NAME] = function() {


		if (window['console'] && window['console']['log']) {


			window['console']['log']('Unknown RPC service: ' + this.s);


		}


	};



	// Create a Special RPC handler for callbacks.


	services[CALLBACK_NAME] = function(callbackId, result) {


		var callback = callbacks[callbackId];


		if (callback) {


			delete callbacks[callbackId];


			callback(result);


		}


	};



	/**

	* Conducts any frame-specific work necessary to setup

	* the channel type chosen. This method is called when

	* the container page first registers the gadget in the

	* RPC mechanism. Gadgets, in turn, will complete the setup

	* of the channel once they send their first messages.

	*/


	function setupFrame(frameId, token) {
		var frame; // used below
		if (setup[frameId]) {
			return;
		}

		if (relayChannel === 'fe') {
			try {
				frame = document.getElementById(frameId);
				frame[FE_G2C_CHANNEL] = function(args) {
					process(gadgets.json.parse(args));
				};
			} catch (e1) {
				// Something went wrong. System will fallback to
				// IFPC.
			}
		}

		if (relayChannel === 'nix') {
			try {

				frame = document.getElementById(frameId);

				var wrapper = window[NIX_GET_WRAPPER](frameId, String(token));

				frame.contentWindow.opener = wrapper;

			} catch (e2) {

				// Something went wrong. System will fallback to

				// IFPC.

			}

		}

		setup[frameId] = true;

	}


	/**

	* Encodes arguments for the legacy IFPC wire format.

	*

	* @param {Object} args

	* @return {String} the encoded args

	*/


	function encodeLegacyData(args) {
		var stringify = gadgets.json.stringify;

		var argsEscaped = [];

		for (var i = 0, j = args.length; i < j; ++i) {
			argsEscaped.push(encodeURIComponent(stringify(args[i])));
		}

		return argsEscaped.join('&');
	}


	/**

	* Helper function to emit an invisible IFrame.

	* @param {String} src SRC attribute of the IFrame to emit.

	* @private

	*/


	function emitInvisibleIframe(src) {

		var iframe;


		// Recycle IFrames

		for (var i = iframePool.length - 1; i >= 0; --i) {

			var ifr = iframePool[i];


			try {

				if (ifr && (ifr.recyclable || ifr.readyState === 'complete')) {

					ifr.parentNode.removeChild(ifr);

					if (window.ActiveXObject) {

						// For MSIE, delete any iframes that are no longer being used. MSIE

						// cannot reuse the IFRAME because a navigational click sound will

						// be triggered when we set the SRC attribute.

						// Other browsers scan the pool for a free iframe to reuse.

						iframePool[i] = ifr = null;

						iframePool.splice(i, 1);

					} else {

						ifr.recyclable = false;

						iframe = ifr;

						break;

					}

				}

			} catch (e) {

				// Ignore; IE7 throws an exception when trying to read readyState and

				// readyState isn't set.

			}

		}

		// Create IFrame if necessary

		if (!iframe) {

			iframe = document.createElement('iframe');

			iframe.style.border = iframe.style.width = iframe.style.height = '0px';

			iframe.style.visibility = 'hidden';

			iframe.style.position = 'absolute';

			iframe.onload = function() { this.recyclable = true; };

			iframePool.push(iframe);

		}

		iframe.src = src;

		setTimeout(function() { document.body.appendChild(iframe); }, 0);

	}

	/**

	* Conducts an RPC call to the specified

	* target with the specified data via the IFPC

	* method.

	*

	* @param {String} targetId Module Id of the RPC service provider.

	* @param {String} serviceName Service name to call.

	* @param {String} from Module Id of the calling provider.

	* @param {Object} rpcData The RPC data for this call.

	* @param {Array.<Object>} callArgs Original arguments to call()

	*/


	function callIfpc(targetId, serviceName, from, rpcData, callArgs) {


		// Retrieve the relay file used by IFPC. Note that


		// this must be set before the call, and so we conduct


		// an extra check to ensure it is not blank.


		var relay = MSID.rpc.getRelayUrl(targetId);



		if (!relay) {


			if (window['console'] && window['console']['log']) {


				window['console']['log']('No relay file assigned for IFPC');


			}


		}



		// The RPC mechanism supports two formats for IFPC (legacy and current).


		var src = null;


		if (useLegacyProtocol[targetId]) {


			// Format: #iframe_id&callId&num_packets&packet_num&block_of_data


			src = [relay, '#', encodeLegacyData([from, callId, 1, 0,
             encodeLegacyData([from, serviceName, '', '', from].concat(
               callArgs))])].join('');


		} else {


			// Format: #targetId & sourceId@callId & packetNum & packetId & packetData


			src = [relay, '#', targetId, '&', from, '@', callId,
             '&1&0&', encodeURIComponent(rpcData)].join('');


		}



		// Conduct the IFPC call by creating the Iframe with


		// the relay URL and appended message.


		emitInvisibleIframe(src);


	}



	/**

	* Attempts to conduct an RPC call to the specified

	* target with the specified data via the NIX

	* method. If this method fails, the system attempts again

	* using the known default of IFPC.

	*

	* @param {String} targetId Module Id of the RPC service provider.

	* @param {String} serviceName Name of the service to call.

	* @param {String} from Module Id of the calling provider.

	* @param {Object} rpcData The RPC data for this call.

	*/


	function callNix(targetId, serviceName, from, rpcData) {

		try {


			if (from !== '..') {


				// Call from gadget to the container.


				var handler = nix_channels['..'];



				// If the gadget has yet to retrieve a reference to


				// the NIX handler, try to do so now. We don't do a


				// typeof(window.opener.GetAuthToken) check here


				// because it means accessing that field on the COM object, which,


				// being an internal function reference, is not allowed.


				// "in" works because it merely checks for the prescence of


				// the key, rather than actually accessing the object's property.


				// This is just a sanity check, not a validity check.


				if (!handler && window.opener && "GetAuthToken" in window.opener) {


					handler = window.opener;



					// Create the channel to the parent/container.


					// First verify that it knows our auth token to ensure it's not


					// an impostor.


					if (handler.GetAuthToken() === authToken['..']) {


						// Auth match - pass it back along with our wrapper to finish.


						// own wrapper and our authentication token for co-verification.


						var token = authToken['..'];


						handler.CreateChannel(window[NIX_GET_WRAPPER]('..', token),
                                  token);


						// Set channel handler


						nix_channels['..'] = handler;


						window.opener = null;


					}


				}



				// If we have a handler, call it.


				if (handler) {


					handler.SendMessage(rpcData);


					return;


				}


			} else {


				// Call from container to a gadget[targetId].



				// If we have a handler, call it.


				if (nix_channels[targetId]) {


					nix_channels[targetId].SendMessage(rpcData);


					return;


				}


			}


		} catch (e) {


		}



		// If we have reached this point, something has failed


		// with the NIX method, so we default to using


		// IFPC for this call.


		callIfpc(targetId, serviceName, from, rpcData);


	}



	/**

	* Attempts to conduct an RPC call to the specified

	* target with the specified data via the FrameElement

	* method. If this method fails, the system attempts again

	* using the known default of IFPC.

	*

	* @param {String} targetId Module Id of the RPC service provider.

	* @param {String} serviceName Service name to call.

	* @param {String} from Module Id of the calling provider.

	* @param {Object} rpcData The RPC data for this call.

	* @param {Array.<Object>} callArgs Original arguments to call()

	*/


	function callFrameElement(targetId, serviceName, from, rpcData, callArgs) {


		try {


			if (from !== '..') {


				// Call from gadget to the container.


				var fe = window.frameElement;



				if (typeof fe[FE_G2C_CHANNEL] === 'function') {


					// Complete the setup of the FE channel if need be.


					if (typeof fe[FE_G2C_CHANNEL][FE_C2G_CHANNEL] !== 'function') {


						fe[FE_G2C_CHANNEL][FE_C2G_CHANNEL] = function(args) {


							process(gadgets.json.parse(args));


						};


					}



					// Conduct the RPC call.


					fe[FE_G2C_CHANNEL](rpcData);


					return;


				}


			} else {


				// Call from container to gadget[targetId].


				var frame = document.getElementById(targetId);



				if (typeof frame[FE_G2C_CHANNEL] === 'function' &&
            typeof frame[FE_G2C_CHANNEL][FE_C2G_CHANNEL] === 'function') {



					// Conduct the RPC call.


					frame[FE_G2C_CHANNEL][FE_C2G_CHANNEL](rpcData);


					return;


				}


			}


		} catch (e) {


		}



		// If we have reached this point, something has failed


		// with the FrameElement method, so we default to using


		// IFPC for this call.


		callIfpc(targetId, serviceName, from, rpcData, callArgs);


	}





	/**

	* Attempts to make an rpc by calling the target's receive method directly.

	* This works when gadgets are rendered on the same domain as their container,

	* a potentially useful optimization for trusted content which keeps

	* RPC behind a consistent interface.

	* @param {String} target Module id of the rpc service provider

	* @param {String} from Module id of the caller (this)

	* @param {String} callbackId Id of the call

	* @param {String} rpcData JSON-encoded RPC payload

	* @return

	*/


	function callSameDomain(target, rpc) {


		if (typeof sameDomain[target] === 'undefined') {


			// Seed with a negative, typed value to avoid


			// hitting this code path repeatedly


			sameDomain[target] = false;


			var targetEl = null;


			if (target === '..') {


				targetEl = parent;


			} else {


				targetEl = frames[target];


			}


			try {


				// If this succeeds, then same-domain policy applied


				sameDomain[target] = targetEl.MSID.rpc.receiveSameDomain;


			} catch (e) {


				// Usual case: different domains


			}


		}



		if (typeof sameDomain[target] === 'function') {


			// Call target's receive method


			sameDomain[target](rpc);


			return true;


		}



		return false;


	}


	return /** @scope MSID.rpc */{


	/**

	* Registers an RPC service.

	* @param {String} serviceName Service name to register.

	* @param {Function} handler Service handler.

	*

	* @member MSID.rpc

	*/


	register: function(serviceName, handler) {


		if (serviceName === CALLBACK_NAME) {


			throw new Error("Cannot overwrite callback service");


		}



		if (serviceName === DEFAULT_NAME) {


			throw new Error("Cannot overwrite default service: use registerDefault");


		}



		services[serviceName] = handler;


	},



	/**

	* Unregisters an RPC service.

	* @param {String} serviceName Service name to unregister.

	*

	* @member MSID.rpc

	*/


	unregister: function(serviceName) {


		if (serviceName === CALLBACK_NAME) {


			throw new Error("Cannot delete callback service");


		}



		if (serviceName === DEFAULT_NAME) {


			throw new Error("Cannot delete default service: use unregisterDefault");


		}



		delete services[serviceName];


	},



	/**

	* Registers a default service handler to processes all unknown

	* RPC calls which raise an exception by default.

	* @param {Function} handler Service handler.

	*

	* @member MSID.rpc

	*/


	registerDefault: function(handler) {


		services[''] = handler;


	},



	/**

	* Unregisters the default service handler. Future unknown RPC

	* calls will fail silently.

	*

	* @member MSID.rpc

	*/


	unregisterDefault: function() {


		delete services[''];


	},



	/**

	* Forces all subsequent calls to be made by a transport

	* method that allows the caller to verify the message receiver

	* (by way of the parent parameter, through getRelayUrl(...)).

	* At present this means IFPC or WPM.

	*/


	forceParentVerifiable: function() {


		if (relayChannel !== 'wpm') {


			relayChannel = 'ifpc';


		}


	},



	/**

	* Calls an RPC service.

	* @param {String} targetId Module Id of the RPC service provider.

	*                          Empty if calling the parent container.

	* @param {String} serviceName Service name to call.

	* @param {Function|null} callback Callback function (if any) to process

	*                                 the return value of the RPC request.

	* @param {*} var_args Parameters for the RPC request.

	*

	* @member MSID.rpc

	*/


	call: function(targetId, serviceName, callback, var_args) {


		++callId;


		targetId = targetId || '..';


		if (callback) {


			callbacks[callId] = callback;


		}



		// Default to the container calling.


		var from = '..';



		if (targetId === '..') {


			from = window.name;


		}



		// Not used by legacy, create it anyway...


		var rpc = {


			s: serviceName,


			f: from,


			c: callback ? callId : 0,


			a: Array.prototype.slice.call(arguments, 3),


			t: authToken[targetId]


		};



		// If target is on the same domain, call method directly


		if (callSameDomain(targetId, rpc)) {


			return;


		}



		var rpcData = gadgets.json.stringify(rpc);


		var channelType = relayChannel;



		// If we are told to use the legacy format, then we must


		// default to IFPC.


		if (useLegacyProtocol[targetId]) {


			channelType = 'ifpc';


		}


		switch (channelType) {


			case 'dpm': // use document.postMessage.


				var targetDoc = targetId === '..' ? parent.document :
                                              frames[targetId].document;


				targetDoc.postMessage(rpcData);


				break;



			case 'wpm': // use window.postMessage.


				var targetWin = targetId === '..' ? parent : frames[targetId];


				var relay = MSID.rpc.getRelayUrl(targetId);


				if (relay) {


					targetWin.postMessage(rpcData, relay);


				}


				break;



			case 'nix': // use NIX.


				callNix(targetId, serviceName, from, rpcData);


				break;



			case 'fe': // use FrameElement.


				callFrameElement(targetId, serviceName, from, rpcData, rpc.a);


				break;



			default: // use 'ifpc' as a fallback mechanism.


				callIfpc(targetId, serviceName, from, rpcData, rpc.a);


				break;


		}


	},



	/**

	* Gets the relay URL of a target frame.

	* @param {String} targetId Name of the target frame.

	* @return {String|undefined} Relay URL of the target frame.

	*

	* @member MSID.rpc

	*/


	getRelayUrl: function(targetId) {


		var url = relayUrl[targetId];


		// Some RPC methods (wpm, for one) are unhappy with schemeless URLs.


		if (url.indexOf('//') == 0) {


			url = document.location.protocol + url;


		}



		return url;


	},



	/**

	* Sets the relay URL of a target frame.

	* @param {String} targetId Name of the target frame.

	* @param {String} url Full relay URL of the target frame.

	* @param {Boolean} opt_useLegacy True if this relay needs the legacy IFPC

	*     wire format.

	*

	* @member MSID.rpc

	*/


	setRelayUrl: function(targetId, url, opt_useLegacy) {


		relayUrl[targetId] = url;


		useLegacyProtocol[targetId] = !!opt_useLegacy;


	},



	/**

	* Sets the auth token of a target frame.

	* @param {String} targetId Name of the target frame.

	* @param {String} token The authentication token to use for all

	*     calls to or from this target id.

	*

	* @member MSID.rpc

	*/


	setAuthToken: function(targetId, token) {

		token = token || "";



		// Coerce token to a String, ensuring that all authToken values


		// are strings. This ensures correct comparison with URL params


		// in the process(rpc) method.


		authToken[targetId] = String(token);
	},


	getAuthToken: function(targetId) {
		return authToken[targetId];
	},


	setup: function(targetId, token) {

		setupFrame(targetId, token);

	},

	/**

	* Gets the RPC relay mechanism.

	* @return {String} RPC relay mechanism. See above for

	*   a list of supported types.

	*

	* @member MSID.rpc

	*/


	getRelayChannel: function() {


		return relayChannel;


	},



	/**

	* Receives and processes an RPC request. (Not to be used directly.)

	* @param {Array.<String>} fragment An RPC request fragment encoded as

	*        an array. The first 4 elements are target id, source id & call id,

	*        total packet number, packet id. The last element stores the actual

	*        JSON-encoded and URI escaped packet data.

	*

	* @member MSID.rpc

	*/


	receive: function(fragment) {


		if (fragment.length > 4) {


			// TODO parse fragment[1..3] to merge multi-fragment messages


			process(gadgets.json.parse(
            decodeURIComponent(fragment[fragment.length - 1])));


		}


	},



	/**

	* Receives and processes an RPC request sent via the same domain.

	* (Not to be used directly). Converts the inbound rpc object's

	* Array into a local Array to pass the process() Array test.

	* @param {Object} rpc RPC object containing all request params

	*/


	receiveSameDomain: function(rpc) {


		// Pass through to local process method but converting to a local Array


		rpc.a = Array.prototype.slice.call(rpc.a);


		window.setTimeout(function() { process(rpc); }, 0);


	}


};

} ();

/**** MySpaceID RPC services ****/
MSID.Internal = new function() {
	var self = this;

	self.relayUrl = '';
	self.remoteRelayUrl = '';
	self.dataChannelResponseCallback = null;
	self.userId = '';
	self.consumerKey = '';
	self.sessionToken = '';
	self.sideBySide = false;

	self.handleDataChannelResponseCallback = function(response) {

		window.clearTimeout(self.timeoutCallback);

		if (response) {
			//init even if the session token isn't validated because it's passed along with every request anyways
			if (response.statusCode == MSID.Connect.Enums.status.READY || response.statusCode == MSID.Connect.Enums.status.INVALID_SESSION_TOKEN) {
				MSID.Bootstrap.isChannelReady = true;
				MSID.Relay.proxyCallId = response.proxyCallId;
				MSID.rpc.setup('myspaceid_proxy', MSID.rpc.getAuthToken('myspaceid_proxy'));
				MyOpenSpace.MySpaceContainer.container_ = new MyOpenSpace.MySpaceContainer(self.userId, self.userId, self.sessionToken, self.consumerKey, self.sideBySide);
			}
			else {
				MSID.Bootstrap.isChannelReady = false;
			}

			delete response.proxyCallId;

			//this fixes tokenuserid mismatch bug but also creates one
			//MSID.Connect.logout();

			if (typeof self.dataChannelResponseCallback !== 'undefined' && self.dataChannelResponseCallback !== null) {
				self.dataChannelResponseCallback(response);
			}
		}
	};

	self.setupDataChannel = function(userId, consumerKey, sessionToken, serverChannel, clientChannel, gadgetUrl, responseCallback) {

		self.userId = userId;
		self.consumerKey = consumerKey;
		self.sessionToken = sessionToken;

		var iframe, secret;
		secret = Math.round(Math.random() * 10000000);
		//Assume a fixed url for the MySpace Cross Domain Proxy
		//Set the api.myspace.com iframe relay

		this.timeoutCallback = window.setTimeout('MSID.Internal.handleDataChannelResponseCallback({ \'statusCode\': MSID.Connect.Enums.status.LIBRARY_INIT_TIMEOUT })', 5000);
		self.dataChannelResponseCallback = responseCallback;
		iframe = document.getElementById('myspaceid_proxy');
		if (iframe === null) {
			MSID.rpc.setRelayUrl('myspaceid_proxy', this.relayUrl);
			MSID.rpc.setAuthToken('myspaceid_proxy', secret);

			iframe = document.createElement("iframe");
			iframe.id = 'myspaceid_proxy';
			iframe.name = 'myspaceid_proxy';
			iframe.style.border = iframe.style.width = iframe.style.height = '0px';
			iframe.style.visibility = 'hidden';
			iframe.style.position = 'absolute';
			var gadgetSrc = gadgetUrl + '?remote_relay=' + self.remoteRelayUrl + '&consumer_key=' + self.consumerKey + '&serverchannel=' + serverChannel + '&clientchannel=' + clientChannel + "&mode=" + MSID.mode + "&sessionToken=" + sessionToken + "#rpctoken=" + secret;

			// We append to the body after setting the src otherwise MSIE will 'click'
			if (window.ActiveXObject && document.readyState && document.readyState != "complete") {
				document.body.attachEvent("onload", function() {
					document.body.appendChild(iframe);
					iframe.src = 'about:blank';
					iframe.src = gadgetSrc;
				});
			}
			else {
				document.body.appendChild(iframe);
				/*MSID.rpc.setAuthToken('myspaceid_proxy', secret);*/
				iframe.src = 'about:blank';
				iframe.src = gadgetSrc;
			}
		}
		else {
			MSID.Internal.handleDataChannelResponseCallback({ 'statusCode': MSID.Connect.Enums.status.READY, 'statusMessage': 'Ready', 'proxyCallId': MSID.Relay.proxyCallId });
		}
	};


};

MSID.rpc.register("dcCallback", MSID.Internal.handleDataChannelResponseCallback);

MSID.Relay = new function() {
	var self = this;
	self.proxyCallId = null;

	var Completed = [],
	Errored = [],
	Content_Complete = [],
	Content_Errored = [],

	/**
	* getResponseError_ Create an error object mapping the status code to the matching
	* ResponseItem.Error value.
	* @param {integer} statusCode response status code
	* @param {String} statusDescription response status description
	* @internal
	*/
	getResponseError = function(statusCode, statusDescription) {
		var errorCode;
		//if ("undefined" !== typeof (opensocial)) {
		if (true) {
			switch (statusCode) {
				case 400:
					errorCode = opensocial.ResponseItem.Error.BAD_REQUEST;
					break;
				case 403:
					errorCode = opensocial.ResponseItem.Error.FORBIDDEN;
					break;
				case 501:
					errorCode = opensocial.ResponseItem.Error.NOT_IMPLEMENTED;
					break;
				case 401:
					errorCode = opensocial.ResponseItem.Error.UNAUTHORIZED;
					break;
				default:
					errorCode = opensocial.ResponseItem.Error.INTERNAL_ERROR;
					break;
			}
		}
		else {
			errorCode = "Document is probably unloading.";
		}
		var error = { "errorCode": errorCode, "errorMessage": statusDescription };
		return error;
	};

	self.handleSendRequestCallback = function(status, statusText, responseText, responseXML, key, type, opt_key) {
		var status, statusText;

		if (200 === status || 201 === status) {
			var response = {};
			var xmlDoc = undefined;
			if (responseXML) {
				if (window.ActiveXObject) {
					xmlDoc = new ActiveXObject("MSXML2.DOMDocument");
					xmlDoc.async = false;
					xmlDoc.loadXML(responseText);
				}
				else {
					var dp = new DOMParser();
					xmlDoc = dp.parseFromString(responseText, "text/xml");
				}
			}
			response.responseXML = xmlDoc;
			response.responseText = responseText;
			Completed[key](response, type, opt_key);
		}
		else {

			//the token passed has expired, the user will need to request a new one
			if (status === 403 && statusText === 'esssion token has expired') {
				MSID.Connect.logout();
			}
			else if (status === 403 && statusText === 'cookie required') {
				MSID.Connect.logout();
			}
			//consumer key is not associated with the application or the application has been deleted
			else if (status === 403 && statusText === 'error validating application from consumer key') {
				//we should log the user out
				MSID.Connect.logout();
			}

			var error = getResponseError(status, statusText);
			Errored[key](error, opt_key);
		}
	};

	self.sendRequest = function(dataRequest, type, onComplete, onError, async, opt_key) {
		//construct endpoint Url From datarequest
		var method = dataRequest.method;
		var url = dataRequest.endPoint;
		var params = dataRequest.params;
		var sessionToken = dataRequest.osToken_;
		var consumerKey = dataRequest.consumerKey_;

		self.sendRequestHTTP(method, url, params, type, onComplete, onError, async, opt_key, sessionToken, consumerKey);
	};

	/*
	
	*/
	self.sendRequestHTTP = function(method, url, params, type, onComplete, onError, async, opt_key, sessionToken, consumerKey) {
		var key = opt_key ? opt_key : type;

		Completed[key] = onComplete;
		Errored[key] = onError;

		if (!MSID.Connect.isConnected) {
			self.handleSendRequestCallback(401, 'You are not logged in', null, null, key, type, opt_key);
			return;
		};

		if (async == null) async = true;

		try {
			MSID.rpc.call("myspaceid_proxy",
                self.proxyCallId,
                function(params) {
                	self.handleSendRequestCallback(params.status, params.statusText, params.response, params.responseXML, key, type, opt_key);
                },
                    method, url, params, async, key, sessionToken, consumerKey);
		}
		catch (e) {
		}

	};
};



MSID.Bootstrap = new function() {

	var self = this,
		loggedInStateCookie = 'myspaceid.response_code',
		relayUrl = MSID.baseUrl + '/JSL/msid_relay.html',
		gadgetUrl = MSID.baseUrl + '/JSL/RelayServer.ashx',
		loginTimeoutClient = 50000; //50 seconds

	self.connectStatusCode = MSID.Connect.Enums.status.NOT_INITIALIZED;
	self.isChannelReady = false;

	var checkConnected = function() {
		var cookie = MSID.Core.getCookie(loggedInStateCookie);

		if (cookie === null) {
			self.connectStatusCode = MSID.Connect.Enums.status.COOKIE_NOT_FOUND;
			return false;
		}

		if (cookie == MSID.Connect.Enums.status.LOGIN_SUCCESS) {
			self.connectStatusCode = MSID.Connect.Enums.status.READY;
			return true;
		}
		else {
			self.connectStatusCode = MSID.Connect.Enums.status.NOT_LOGGED_IN;
			return false;
		}
	};

	self.onLoad = function() {

		//setup CommClient
		MSID.CommClient.gadgetUrl = gadgetUrl;

		MSID.Connect.loginTimeout = loginTimeoutClient;

		//~comment: set relay url for cross domain calls
		MSID.Internal.relayUrl = relayUrl;

		MSID.Connect.isConnected = checkConnected();
		MSID.Connect.connectStatusCode = self.connectStatusCode;
		MSID.Connect.ready = true;

		ready = true;
	};

	/*
	description:	initiates a session with the MySpaceJSL library
	consumerKey:	the consumer key for the 3rd party
	targetDomain:	the domain for the 3rd party making the request on behalf of the user
	*/
	self.init = function(consumerKeyValue, targetDomainValue) {

		consumerKey = consumerKeyValue;
		targetDomain = targetDomainValue;
	};

	self.setSideBySide = function(isSideBySide) {
		sideBySide = isSideBySide;
	};

	/*
	Constructor
	*/
	var Bootstrap = function() {
		MSID.Core.addLoadEvent(self.onLoad);
	} ();
};


/* Activity Popup Class
* Description: This class is used to initiate the MySpaceID activity preview popup. */
MSID.Activity = new function() {
	var self = this,
		previewUrl = MSID.baseUrl + '/modules/activityingestion/pages/preview.aspx',
		popup = null,
		xdframe = null,
		__privateCallbacks = {};

	/* setPopupPosition (private): Positions the popup vertically and horizontally after being rendered.*/
	var setPopupPosition = function() {
		//Get Window Width
		var windowWidth = 0;
		if (typeof (window.innerWidth) == 'number') {
			windowWidth = window.innerWidth;
		}
		else {
			if (document.documentElement && document.documentElement.clientWidth) {
				windowWidth = document.documentElement.clientWidth;
			}
			else {
				if (document.body && document.body.clientHeight) {
					windowWidth = document.body.clientWidth;
				}
			}
		}

		//Get Window Height	
		var windowHeight = 0;
		if (typeof (window.innerHeight) == 'number') {
			windowHeight = window.innerHeight;
		}
		else {
			if (document.documentElement && document.documentElement.clientHeight) {
				windowHeight = document.documentElement.clientHeight;
			}
			else {
				if (document.body && document.body.clientHeight) {
					windowHeight = document.body.clientHeight;
				}
			}
		}

		//Position Popup Dialog
		if (document.getElementById) {
			if (popup !== null) {
				if (windowHeight > 0) {
					var contentHeight = popup.offsetHeight;
					var contentWidth = popup.offsetWidth;
					if (windowHeight - contentHeight > 0) {
						popup.style.position = 'fixed';
						popup.style.top = ((windowHeight / 2) - (contentHeight / 2)) + 'px';
						popup.style.left = ((windowWidth / 2) - (contentWidth / 2)) + 'px';
					}
					else {
						popup.style.position = 'static';
					}
				}
			}
		}
	};

	var buildResponse = function(responseItem, key, hasErrored) {

		var responseList = new Object();
		var response;

		responseList[key] = responseItem;

		response = new MSID.Container.get().newDataResponse(responseList, hasErrored);

		return response;
	};

	/* handleActivityCallback (public): closes the popup and handles callbacks from the activity popup.
	* params (Object): contains response data from activity event. */
	self.handleActivityCallback = function(params) {
		//Check to See if Popup Should be Closed
		if (params.closePopup) {
			//Hide Popup
			self.hideActivityPopup();
		}

		//Generate Response
		var responseItem = null;
		var response = null;
		var hasError = false;

		if (params.statusCode == 201) {
			responseItem = new MSID.Container.get().newResponseItem(1, null, "", "");
		}
		else if (params.statusCode == 400) {
			responseItem = new MSID.Container.get().newResponseItem(0, null, opensocial.ResponseItem.Error.BAD_REQUEST, params.statusDescription);
			hasError = true;
		}
		else if (params.statusCode == 401) {
			responseItem = new MSID.Container.get().newResponseItem(0, null, opensocial.ResponseItem.Error.UNAUTHORIZED, params.statusDescription);
			hasError = true;
		}
		else {
			responseItem = new MSID.Container.get().newResponseItem(0, null, opensocial.ResponseItem.Error.INTERNAL_ERROR, 'Internal Server Error');
			hasError = true;
		}

		response = buildResponse(responseItem, 'requestCreateActivity', hasError);

		//Callback to Activity
		if (__privateCallbacks.hasOwnProperty("activityCallback")) {
			__privateCallbacks.activityCallback(response);
		}
	};

	/* action_raiseActivity (public): Initiates the activity preview popup.
	* consumerKey (String): Consumer key for the application.
	* activityCallback (function): Function to call after the activity posts.
	* params (Object): Contains activity parameters (required), template id (required), and media items (optional). */
	self.action_raiseActivity = function(activityCallback, params) {
		//Set Callback
		__privateCallbacks.activityCallback = activityCallback;

		//Set Static Content Base
		var staticBase = MSID.staticBaseUrl;

		//Check if popup Exists, Remove if True
		popup = document.getElementById('myspaceid_activity_preview');
		if (popup !== null) {
			document.body.removeChild(popup);
		}
		//Check that XD Proxy Frame is Present
		xdframe = document.getElementById('myspaceid_proxy');
		if (xdframe !== null && params !== null) {
			//Create Popup Dialog and Display
			popup = document.createElement('div');
			popup.id = 'myspaceid_activity_preview';

			popup.style.backgroundColor = '#234495';
			popup.style.padding = '8px';
			popup.style.border = '1px solid #B9D1F0';
			popup.style.zIndex = '1000';
			popup.style.width = '500px';

			//Hide Until Position is Set
			popup.style.visibility = 'hidden';

			//Construct Preview Frame URLs
			var frameURL = previewUrl + '?mode=' + MSID.mode + '&oauth_consumer_key=' +
					MSID.CommClient.consumerKey + '&template=' + params['template'] + '&templateParameters=' +
						escape(params['templateParams']);

			//Check for Media Items and Add Them		
			if (params['mediaItems'] != null) {
				frameURL += '&mediaItems=' + escape(params['mediaItems']);
			}

			//Set Float Val
			var floatVal = 'styleFloat';
			if (!document.all) {
				floatVal = 'cssFloat';
			}

			//Create Outer Container
			var outerContainer = document.createElement('div');
			outerContainer.style.backgroundColor = '#FFFFFF';

			//Create Activity Header Container
			var activityHeaderContainer = document.createElement('div');
			activityHeaderContainer.id = 'activity_header';
			activityHeaderContainer.style.width = '484px';
			activityHeaderContainer.style.padding = '8px 8px 0px 8px';



			//Create Header Left & Add to Header Container
			var headerLeftContainer = document.createElement('div');
			headerLeftContainer.id = 'header_left';
			headerLeftContainer.style.textAlign = 'left';
			headerLeftContainer.style.width = '463px';
			headerLeftContainer.innerHTML = '<img alt="MySpace.com" border="0" src="' + staticBase + '/modules/activityingestion/static/img/myspace-logo-blue-clean.jpg"/>';
			headerLeftContainer.style[floatVal] = 'left';
			activityHeaderContainer.appendChild(headerLeftContainer);

			//Create Header Right & Add to Header Container
			var headerRightContainer = document.createElement('div');
			headerRightContainer.id = 'header_right';
			headerRightContainer.style.textAlign = 'right';
			headerRightContainer.innerHTML = '<a id="activity_close_x" href="javascript:MSID.Activity.cancelActivityPopup();"><img alt="[x]" border="0" src="' + staticBase + '/modules/activityingestion/static/img/myspaceid_close.gif"/></a>';
			headerRightContainer.style[floatVal] = 'left';
			activityHeaderContainer.appendChild(headerRightContainer);

			var headerClearDiv = document.createElement('div');
			headerClearDiv.style.clear = 'both';
			activityHeaderContainer.appendChild(headerClearDiv);

			//Add Header to Outer Container
			outerContainer.appendChild(activityHeaderContainer);

			//Create iFrame Container
			var iframeContainer = document.createElement('div');
			iframeContainer.id = 'activity_preview';
			iframeContainer.innerHTML = '<iframe id="activity_preview_iframe" scrolling="no" frameborder="0" style="background-color: #FFFFFF; width: 500px; height: 150px; border: 0px none ;" src="' + frameURL + '" ></iframe>';

			//Add iFrame to outer Container
			outerContainer.appendChild(iframeContainer);

			//Add Outer Container to Popup
			popup.appendChild(outerContainer);

			//Add Popup to Body
			document.body.appendChild(popup);

			//Set Popup Position
			setPopupPosition();

			//Show popup
			popup.style.visibility = 'visible';
		} else {
			if (params == null) {
				self.handleActivityCallback({ 'activityPosted': false, closePopup: false, statusCode: 400, 'statusDescription': 'Error: missing parameters.' });
			} else {
				self.handleActivityCallback({ 'activityPosted': false, closePopup: false, statusCode: 400, 'statusDescription': 'Error: page not ready.' });
			}
		}
	};

	/* cancelActivityPopup (public): closes the popup and removes it from the DOM. also fires the callback */
	self.cancelActivityPopup = function() {
		//Hide Popup
		self.hideActivityPopup();

		//Create User Cancel Repsonse
		var responseItem = new MSID.Container.get().newResponseItem(0, null, opensocial.ResponseItem.Error.UNAUTHORIZED, 'Activity was not successfully posted. User canceled action.');
		var response = buildResponse(responseItem, 'requestCreateActivity', true);

		//Callback to Activity
		if (__privateCallbacks.hasOwnProperty("activityCallback")) {
			__privateCallbacks.activityCallback(response);
		}
	};

	/* hideActivityPopup (public): closes the popup and removes it from the DOM. */
	self.hideActivityPopup = function() {
		//Get Popup Element
		popup = document.getElementById('myspaceid_activity_preview');

		//Hide Popup
		if (popup != null) {
			document.body.removeChild(popup);
		}
	};

	self.adjustActivityPopupHeight = function(height) {
		document.getElementById("activity_preview_iframe").style.height = height + 'px';
		var preview = document.getElementById("myspaceid_activity_preview");
		preview.style.position = "absolute";

		window.setTimeout(function() {
			document.getElementById("myspaceid_activity_preview").style.position = "fixed";
		}, 20);
	};


};

// Activity Popup Callback Registration
function doActivityCallback(response) {
	MSID.Activity.handleActivityCallback(response);
}
MSID.rpc.register("activityCallback", doActivityCallback);
MSID.rpc.register("adjustActivityPopupHeight", MSID.Activity.adjustActivityPopupHeight);

MSID.Container = function() { };

MSID.Container.container_ = null;

MSID.Container.setContainer = function(container) {
	MSID.Container.container_ = container;
};

MSID.Container.get = function() {
	return MSID.Container.container_;
};

MSID.newDataRequest = function() {
	return MSID.Container.get().newDataRequest();
};

MSID.newActivity = function(params) {
	return MSID.Container.get().newActivity(params);
};

MSID.requestCreateActivity = function(activity, priority, opt_callback) {
	if (!activity || (!activity.getField(opensocial.Activity.Field.TITLE)
        && !activity.getField(opensocial.Activity.Field.TITLE_ID))) {
		if (opt_callback) {
			opt_callback(new opensocial.ResponseItem(null, null,
                    opensocial.ResponseItem.Error.BAD_REQUEST,
                    "You must pass in an activity with a title or title id."));
		}
		return;
	}
	MSID.Container.get().requestCreateActivity(activity, priority, opt_callback);
};

MSID.newMediaItem = function(mimeType, url, opt_params) {
	return MSID.Container.get().newMediaItem(mimeType, url, opt_params);
};

MSID.newMessage = function(body, opt_params) {
	return MSID.Container.get().newMessage(body, opt_params);
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview
 *
 * Provides unified configuration for all features.
 *
 * This is a custom shindig library that has not yet been submitted for
 * standardization. It is designed to make developing of features for the
 * opensocial / gadgets platforms easier and is intended as a supplemental
 * tool to Shindig's standardized feature loading mechanism.
 *
 * Usage:
 * First, you must register a component that needs configuration:
 * <pre>
 *   var config = {
 *     name : gadgets.config.NonEmptyStringValidator,
 *     url : new gadgets.config.RegExValidator(/.+%mySpecialValue%.+/)
 *   };
 *   gadgets.config.register("my-feature", config, myCallback);
 * </pre>
 *
 * This will register a component named "my-feature" that expects input config
 * containing a "name" field with a value that is a non-empty string, and a
 * "url" field with a value that matches the given regular expression.
 *
 * When gadgets.config.init is invoked by the container, it will automatically
 * validate your registered configuration and will throw an exception if
 * the provided configuration does not match what was required.
 *
 * Your callback will be invoked by passing all configuration data passed to
 * gadgets.config.init, which allows you to optionally inspect configuration
 * from other features, if present.
 *
 * Note that the container may optionally bypass configuration validation for
 * performance reasons. This does not mean that you should duplicate validation
 * code, it simply means that validation will likely only be performed in debug
 * builds, and you should assume that production builds always have valid
 * configuration.
 */

var gadgets = gadgets || {};

gadgets.config = function() {
  var components = {};

  return {
    /**
     * Registers a configurable component and its configuration parameters.
     *
     * @param {String} component The name of the component to register. Should
     *     be the same as the fully qualified name of the <Require> feature or
     *     the fully qualified javascript object reference (e.g. gadgets.io).
     * @param {Object} opt_validators Mapping of option name to validation
     *     functions that take the form function(data) {return isValid(data);}
     * @param {Function} opt_callback A function to be invoked when a
     *     configuration is registered. If passed, this function will be invoked
     *     immediately after a call to init has been made. Do not assume that
     *     dependent libraries have been configured until after init is
     *     complete. If you rely on this, it is better to defer calling
     *     dependent libraries until you can be sure that configuration is
     *     complete. Takes the form function(config), where config will be
     *     all registered config data for all components. This allows your
     *     component to read configuration from other components.
     * @throws {Error} If the component has already been registered.
     */
    register: function(component, opt_validators, opt_callback) {
      if (components[component]) {
        throw new Error('Component "' + component + '" is already registered.');
      }
      components[component] = {
        validators: opt_validators || {},
        callback: opt_callback
      };
    },

    /**
     * Retrieves configuration data on demand.
     *
     * @param {String} opt_component The component to fetch. If not provided
     *     all configuration will be returned.
     * @return {Object} The requested configuration.
     * @throws {Error} If the given component has not been registered
     */
    get: function(opt_component) {
      if (opt_component) {
        if (!components[opt_component]) {
          throw new Error('Component "' + opt_component + '" not registered.');
        }
        return configuration[opt_component] || {};
      }
      return configuration;
    },

    /**
     * Initializes the configuration.
     *
     * @param {Object} config The full set of configuration data.
     * @param {Boolean} opt_noValidation True if you want to skip validation.
     * @throws {Error} If there is a configuration error.
     */
    init: function(config, opt_noValidation) {
      configuration = config;
      for (var name in components) if (components.hasOwnProperty(name)) {
        var component = components[name],
            conf = config[name],
            validators = component.validators;
        if (!opt_noValidation) {
          for (var v in validators) if (validators.hasOwnProperty(v)) {
            if (!validators[v](conf[v])) {
              throw new Error('Invalid config value "' + conf[v] +
                  '" for parameter "' + v + '" in component "' +
                  name + '"');
            }
          }
        }
        if (component.callback) {
          component.callback(config);
        }
      }
    },

    // Standard validators go here.

    /**
     * Ensures that data is one of a fixed set of items.
     * @param {Array.<String>} list The list of valid values.
     * Also supports argument sytax: EnumValidator("Dog", "Cat", "Fish");
     */
    EnumValidator: function(list) {
      var listItems = [];
      if (arguments.length > 1) {
        for (var i = 0, arg; arg = arguments[i]; ++i) {
          listItems.push(arg);
        }
      } else {
        listItems = list;
      }
      return function(data) {
        for (var i = 0, test; test = listItems[i]; ++i) {
          if (data === listItems[i]) {
            return true;
          }
        }
      };
      return false;
    },

    /**
     * Tests the value against a regular expression.
     */
    RegExValidator: function(re) {
      return function(data) {
        return re.test(data);
      };
    },

    /**
     * Validates that a value was provided.
     */
    ExistsValidator: function(data) {
      return typeof data !== "undefined";
    },

    /**
     * Validates that a value is a non-empty string.
     */
    NonEmptyStringValidator: function(data) {
      return typeof data === "string" && data.length > 0;
    },

    /**
     * Validates that the value is a boolean.
     */
    BooleanValidator: function(data) {
      return typeof data === "boolean";
    },

    /**
     * Similar to the ECMAScript 4 virtual typing system, ensures that
     * whatever object was passed in is "like" the existing object.
     * Doesn't actually do type validation though, but instead relies
     * on other validators.
     *
     * example:
     *
     *  var validator = new gadgets.config.LikeValidator(
     *    "booleanField" : gadgets.config.BooleanValidator,
     *    "regexField" : new gadgets.config.RegExValidator(/foo.+/);
     *  );
     *
     * This can be used recursively as well to validate sub-objects.
     *
     * @param {Object} test The object to test against.
     */
    LikeValidator : function(test) {
      return function(data) {
        for (var member in test) if (test.hasOwnProperty(member)) {
          var t = test[member];
          if (!t(data[member])) {
            return false;
          }
        }
        return true;
      };
    }
  };
}();
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*/

/**
* @fileoverview This library provides functions for navigating to and dealing
*     with views of the current gadget.
*/

var gadgets = gadgets || {};

/**
* Implements the gadgets.views API spec. See
* http://code.google.com/apis/gadgets/docs/reference/gadgets.views.html
*/
gadgets.views = function() {

    /**
    * Reference to the current view object.
    */
    var currentView = null;

    /**
    * Map of all supported views for this container.
    */
    var supportedViews = {};

    /**
    * Map of parameters passed to the current request.
    */
    var params = {};

    /**
    * Initializes views. Assumes that the current view is the "view"
    * url parameter (or default if "view" isn't supported), and that
    * all view parameters are in the form view-<name>
    * TODO: Use unified configuration when it becomes available.
    *
    */
    function init(config) {
        var supported = config["views"];

        for (var s in supported) if (supported.hasOwnProperty(s)) {
            var obj = supported[s];
            if (!obj) {
                continue;
            }
            supportedViews[s] = new gadgets.views.View(s, obj.isOnlyVisible);
            var aliases = obj.aliases || [];
            for (var i = 0, alias; alias = aliases[i]; ++i) {
                supportedViews[alias] = new gadgets.views.View(s, obj.isOnlyVisible);
            }
        }

        var urlParams = gadgets.util.getUrlParameters();
        // View parameters are passed as a single parameter.
        if (urlParams["p"]) {
            var tmpParams = gadgets.json.parse(
          decodeURIComponent(urlParams["p"]));
            if (tmpParams) {
                params = tmpParams;
                for (var p in params) if (params.hasOwnProperty(p)) {
                    params[p] = gadgets.util.escapeString(params[p]);
                }
            }
        }
        if (0 === urlParams.views.indexOf("profile.")) urlParams.views = gadgets.views.ViewType.PROFILE;
        currentView = supportedViews[urlParams.views] || supportedViews["default"];
    }

    gadgets.config.register("views", null, init);

    return {
        /**
        * Attempts to navigate to this gadget in a different view. If the container
        * supports parameters will pass the optional parameters along to the gadget
        * in the new view.
        *
        * @param {gadgets.views.View} view The view to navigate to
        * @param {Map.&lt;String, String&gt;} opt_params Parameters to pass to the
        *     gadget after it has been navigated to on the surface
        * @param {string} opt_ownerId The ID of the owner of the page to navigate to;
        *                 defaults to the current owner.
        */
        requestNavigateTo: function(view, opt_params, opt_ownerId) {
            gadgets.rpc.call(
          null, "requestNavigateTo", null, view.getName(), opt_params, opt_ownerId);
        },
        
        /**
        * Binds a URL template with variables in the passed environment to produce a URL string.
        * @param {String} urlTemplate A url template for a container view
        * @param {String} environment A set of named variables (for example, [OWNER | PATH | PARAMS | NAME]) of type string.
        *
        * @return {gadgets.views.View} The current view
        */
        bind: function(urlTemplate, environment){},

        /**
        * Returns the current view.
        *
        * @return {gadgets.views.View} The current view
        */
        getCurrentView: function() {
            return currentView;
        },

        /**
        * Returns a map of all the supported views. Keys each gadgets.view.View by
        * its name.
        *
        * @return {Map&lt;gadgets.views.ViewType | String, gadgets.views.View&gt;}
        *   All supported views, keyed by their name attribute.
        */
        getSupportedViews: function() {
            return supportedViews;
        },

        /**
        * Returns the parameters passed into this gadget for this view. Does not
        * include all url parameters, only the ones passed into
        * gadgets.views.requestNavigateTo
        *
        * @return {Map.&lt;String, String&gt;} The parameter map
        */
        getParams: function() {
            return params;
        }
    };
} ();

gadgets.views.View = function(name, opt_isOnlyVisible) {
    this.name_ = name;
    this.isOnlyVisible_ = !!opt_isOnlyVisible;
};

/**
* @return {String} The view name.
*/
gadgets.views.View.prototype.getName = function() {
    return this.name_;
};

/**
* @return {Boolean} True if this is the only visible gadget on the page.
*/
gadgets.views.View.prototype.isOnlyVisibleGadget = function() {
    return this.isOnlyVisible_;
};

gadgets.views.View.prototype.bind = function(environment) {};

gadgets.views.View.prototype.getUrlTemplate = function() {};

gadgets.views.ViewType = gadgets.util.makeEnum([
  "EXTERNAL"
]);

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Browser environment for interacting with people.
 */


/**
 * @static
 * @class
 * Namespace for top-level people functions.
 *
 * @name opensocial
 */

/**
 * Namespace for top level people functions.
 *
 * @private
 * @constructor (note: a constructor for JsDoc purposes)
 */
var opensocial = function() {};


/**
 * Requests the container to send a specific message to the specified users.
 *
 * <p>
 * The callback function is passed one parameter, an
 *    opensocial.ResponseItem. The error code will be set to reflect whether
 *    there were any problems with the request. If there was no error, the
 *    message was sent. If there was an error, you can use the response item's
 *    getErrorCode method to determine how to proceed. The data on the response
 *    item will not be set.
 * </p>
 *
 * <p>
 * If the container does not support this method
 * the callback will be called with an
 * opensocial.ResponseItem that has an error code of
 * NOT_IMPLEMENTED.
 * </p>
 *
 * @param {Array.&lt;String&gt; | String} recipients An ID, array of IDs, or a
 *     group reference; the supported keys are VIEWER, OWNER, VIEWER_FRIENDS,
 *    OWNER_FRIENDS, or a single ID within one of those groups
 * @param {opensocial.Message} message The message to send to the specified
 *     users
 * @param {Function} opt_callback The function to call once the request has been
 *    processed; either this callback will be called or the gadget will be
 *    reloaded from scratch
 * @param {opensocial.NavigationParameters} opt_params The optional parameters
 *     indicating where to send a user when a request is made, or when a request is
 *     accepted; options are of type
 *     <a href="opensocial.NavigationParameters.DestinationType.html">
 *     NavigationParameters.DestinationType</a>
 *
 * @member opensocial
 */
opensocial.requestSendMessage = function(recipients, message, opt_callback,
    opt_params) {
  opensocial.Container.get().requestSendMessage(recipients, message,
      opt_callback, opt_params);
};


/**
 * Requests the container to share this gadget with the specified users.
 *
 * <p>
 * The callback function is passed one parameter, an
 *    opensocial.ResponseItem. The error code will be set to reflect whether
 *    there were any problems with the request. If there was no error, the
 *    sharing request was sent. If there was an error, you can use the response
 *    item's getErrorCode method to determine how to proceed. The data on the
 *    response item will not be set.
 * </p>
 *
 * <p>
 * If the
 * container does not support this method the callback will be called with a
 * opensocial.ResponseItem. The response item will have its error code set to
 * NOT_IMPLEMENTED.
 * </p>
 *
 * @param {Array.&lt;String&gt; | String} recipients An ID, array of IDs, or a
 *     group reference; the supported keys are VIEWER, OWNER, VIEWER_FRIENDS,
 *    OWNER_FRIENDS, or a single ID within one of those groups
 * @param {opensocial.Message} reason The reason the user wants the gadget to
 *     share itself. This reason can be used by the container when prompting the
 *     user for permission to share the app. It may also be ignored.
 * @param {Function} opt_callback The function to call once the request has been
 *    processed; either this callback will be called or the gadget will be
 *    reloaded from scratch
 * @param {opensocial.NavigationParameters} opt_params The optional parameters
 *     indicating where to send a user when a request is made, or when a request is
 *     accepted; options are of type
 *     <a href="opensocial.NavigationParameters.DestinationType.html">
 *     NavigationParameters.DestinationType</a>
 *
 * @member opensocial
 */
opensocial.requestShareApp = function(recipients, reason, opt_callback,
    opt_params) {
  opensocial.Container.get().requestShareApp(recipients, reason, opt_callback,
      opt_params);
};


/**
 * Takes an activity and tries to create it,
 * without waiting for the operation to complete.
 * Optionally calls a function when the operation completes.
 * <p>
 * <b>See also:</b>
 * <a href="#newActivity">newActivity()</a>
 * </p>
 *
 * <p class="note">
 * <b>Note:</b>
 * If this is the first activity that has been created for the user and
 * the request is marked as HIGH priority then this call may open a user flow
 * and navigate away from your gadget.
 *
 * <p>
 * This callback will either be called or the gadget will be
 *    reloaded from scratch. This function will be passed one parameter, an
 *    opensocial.ResponseItem. The error code will be set to reflect whether
 *    there were any problems with the request. If there was no error, the
 *    activity was created. If there was an error, you can use the response
 *    item's getErrorCode method to determine how to proceed. The data on the
 *    response item will not be set.
 * </p>
 *
 * <p>
 * If the container does not support this method the callback will be called
 * with a opensocial.ResponseItem. The response item will have its error code
 * set to NOT_IMPLEMENTED.
 * </p>
 *
 * @param {opensocial.Activity} activity The <a href="opensocial.Activity.html">
 *    activity</a> to create
 * @param {opensocial.CreateActivityPriority} priority The
 *    <a href="opensocial.CreateActivityPriority.html">priority</a> for this
 *    request
 * @param {Function} opt_callback The function to call once the request has been
 *    processed.
 *
 * @member opensocial
 */
opensocial.requestCreateActivity = function(activity, priority, opt_callback) {
  if (!activity || (!activity.getField(opensocial.Activity.Field.TITLE)
      && !activity.getField(opensocial.Activity.Field.TITLE_ID))) {
    if (opt_callback) {
      opt_callback(new opensocial.ResponseItem(null, null,
          opensocial.ResponseItem.Error.BAD_REQUEST,
          "You must pass in an activity with a title or title id."));
    }
    return;
  }

 opensocial.Container.get().requestCreateActivity(activity, priority,
     opt_callback);
};


/**
 * @static
 * @class
 * The priorities a create activity request can have.
 * <p><b>See also:</b>
 * <a href="opensocial.html#requestCreateActivity">
 * opensocial.requestCreateActivity()</a>
 * </p>
 *
 * @name opensocial.CreateActivityPriority
 */
opensocial.CreateActivityPriority = {
  /**
   * If the activity is of high importance, it will be created even if this
   * requires asking the user for permission. This may cause the container to
   * open a user flow which may navigate away from your gagdet.
   *
   * @member opensocial.CreateActivityPriority
   */
  HIGH : 'HIGH',

  /**
   * If the activity is of low importance, it will not be created if the
   * user has not given permission for the current app to create activities.
   * With this priority, the requestCreateActivity call will never open a user
   * flow.
   *
   * @member opensocial.CreateActivityPriority
   */
  LOW : 'LOW'
};

/*
*  Returns the ContainerURLTemplate. 
*  @return {string}
*/
opensocial.getContainerUrlTemplate = function()
{
    return opensocial.getEnvironment().currentApplication.getField(MyOpenSpace.Application.Field.CANVAS_URL) + "?appId={name}";
};

/**
 * Returns true if the current gadget has access to the specified
 * permission. If the gadget calls opensocial.requestPermission and permissions
 * are granted then this function must return true on all subsequent calls.
 *
 * @param {opensocial.Permission} permission
 *    The <a href="opensocial.Permission.html">permission</a>
 * @return {Boolean}
 *    True if the gadget has access for the permission; false if it doesn't
 *
 * @member opensocial
 */
opensocial.hasPermission = function(permission) {
  return opensocial.Container.get().hasPermission(permission);
};


/**
 * Requests the user to grant access to the specified permissions. If the
 * container does not support this method the callback will be called with a
 * opensocial.ResponseItem. The response item will have its error code set to
 * NOT_IMPLEMENTED.
 *
 * @param {Array.&lt;opensocial.Permission&gt;} permissions
 *    The <a href="opensocial.Permission.html">permissions</a> to request
 *    from the viewer
 * @param {String} reason Displayed to the user as the reason why these
 *    permissions are needed
 * @param {Function} opt_callback The function to call once the request has been
 *    processed; either this callback will be called or the gadget will be
 *    reloaded from scratch. This function will be passed one parameter, an
 *    opensocial.ResponseItem. The error code will be set to reflect whether
 *    there were any problems with the request. If there was no error, all
 *    permissions were granted. If there was an error, you can use
 *    opensocial.hasPermission to check which permissions are still denied. The
 *    data on the response item will be set. It will be an array of the
 *    opensocial.Permissions that were granted.
 *
 * @member opensocial
 */
opensocial.requestPermission = function(permissions, reason, opt_callback) {
  opensocial.Container.get().requestPermission(permissions, reason,
      opt_callback);
};


/**
 * @static
 * @class
 *
 * The permissions an app can ask for.
 *
 * <p>
 * <b>See also:</b>
 * <a href="opensocial.html#hasPermission">
 * <code>opensocial.hasPermission()</code></a>,
 * <a href="opensocial.html#requestPermission">
 * <code>opensocial.requestPermission()</code></a>
 *
 * @name opensocial.Permission
 */
opensocial.Permission = {
  /**
   * Access to the viewer person object
   *
   * @member opensocial.Permission
   */
  VIEWER : 'viewer'
};


/**
 * Gets the current environment for this gadget. You can use the environment to
 * make queries such as what profile fields and surfaces are supported by this
 * container, what parameters were passed to the current gadget, and so on.
 *
 * @return {opensocial.Environment}
 *    The current <a href="opensocial.Environment.html">environment</a>
 *
 * @member opensocial
 */
opensocial.getEnvironment = function() {
  return opensocial.Container.get().getEnvironment();
};


/**
 * Creates a data request object to use for sending and fetching data from the
 * server.
 *
 * @return {opensocial.DataRequest} The
 *    <a href="opensocial.DataRequest.html">request</a> object
 * @member opensocial
 */
opensocial.newDataRequest = function() {
  return opensocial.Container.get().newDataRequest();
};


/**
 * Creates an activity object,
 * which represents an activity on the server.
 * <p>
 * <b>See also:</b>
 * <a href="#requestCreateActivity">requestCreateActivity()</a>,
 * </p>
 *
 * <p>It is only required to set one of TITLE_ID or TITLE. In addition, if you
 * are using any variables in your title or title template,
 * you must set TEMPLATE_PARAMS.</p>
 *
 * <p>Other possible fields to set are: URL, MEDIA_ITEMS, BODY_ID, BODY,
 * EXTERNAL_ID, PRIORITY, STREAM_TITLE, STREAM_URL, STREAM_SOURCE_URL,
 * and STREAM_FAVICON_URL.</p>
 *
 * <p>Containers are only required to use TITLE_ID or TITLE, and may choose to
 * ignore additional parameters.</p>
 *
 * <p>See <a href="opensocial.Activity.Field.html">Field</a>s are supported for
 * more details.</p>
 *
 * @param {Map.&lt;opensocial.Activity.Field, Object&gt;} params
 *    Parameters defining the activity.
 * @return {opensocial.Activity} The new
 *    <a href="opensocial.Activity.html">activity</a> object
 * @member opensocial
 */
opensocial.newActivity = function(params) {
  return opensocial.Container.get().newActivity(params);
};


/**
 * Creates a media item.
 * Represents images, movies, and audio.
 * Used when creating activities on the server.
 *
 * @param {String} mimeType
 *    <a href="opensocial.MediaItem.Type.html">MIME type</a> of the
 *    media
 * @param {String} url Where the media can be found
 * @param {Map.&lt;opensocial.MediaItem.Field, Object&gt;} opt_params
 *    Any other fields that should be set on the media item object;
 *    all of the defined
 *    <a href="opensocial.MediaItem.Field.html">Field</a>s
 *    are supported
 *
 * @return {opensocial.MediaItem} The new
 *    <a href="opensocial.MediaItem.html">media item</a> object
 * @member opensocial
 */
opensocial.newMediaItem = function(mimeType, url, opt_params) {
  return opensocial.Container.get().newMediaItem(mimeType, url, opt_params);
};


/**
 * Creates a media item associated with an activity.
 * Represents images, movies, and audio.
 * Used when creating activities on the server.
 *
 * @param {String} body The main text of the message.
 * @param {Map.&lt;opensocial.Message.Field, Object&gt;} opt_params
 *    Any other fields that should be set on the message object;
 *    all of the defined
 *    <a href="opensocial.Message.Field.html">Field</a>s
 *    are supported
 *
 * @return {opensocial.Message} The new
 *    <a href="opensocial.Message.html">message</a> object
 * @member opensocial
 */
opensocial.newMessage = function(body, opt_params) {
  return opensocial.Container.get().newMessage(body, opt_params);
};


/**
 * @static
 * @class
 * The types of escaping that can be applied to person data or fields.
 *
 * @name opensocial.EscapeType
 */
opensocial.EscapeType = {
  /**
   * When used will HTML-escape the data.
   * @member opensocial.EscapeType
   */
  HTML_ESCAPE : 'htmlEscape',
  /**
   * When used will not escape the data.
   *
   * @member opensocial.EscapeType
   */
  NONE : 'none'
};


/**
 * Creates an IdSpec object.
 *
 * @param {Map.&lt;opensocial.IdSpec.Field, Object&gt;} parameters
 *    Parameters defining the id spec.
 * @return {opensocial.IdSpec} The new
 *     <a href="opensocial.IdSpec.html">IdSpec</a> object
 * @member opensocial
 */
opensocial.newIdSpec = function(params) {
  return opensocial.Container.get().newIdSpec(params);
};


/**
 * Creates a NavigationParameters object.
 * <p>
 * <b>See also:</b>
 * <a href="#requestShareApp">requestShareApp()</a>
 * </p>
 *
 *
 * @param {Map.&lt;opensocial.NavigationParameters.Field, Object&gt;} parameters
 *     Parameters defining the navigation
 * @return {opensocial.NavigationParameters} The new
 *     <a href="opensocial.NavigationParameters.html">NavigationParameters</a>
 *     object
 * @member opensocial
 */
opensocial.newNavigationParameters = function(params) {
  return opensocial.Container.get().newNavigationParameters(params);
};


// TODO(doll): Util function - pull up the gadgets inherits in shindig so that
// opensocial and gadgets use the same one
/** @private */
Function.prototype.inherits = function(parentCtor) {
  function tempCtor() {};

  tempCtor.prototype = parentCtor.prototype;
  this.superClass_ = parentCtor.prototype;
  this.prototype = new tempCtor();
  this.prototype.constructor = this;
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Collection of multiple objects with useful accessors.
 *
 * May also represent subset of a larger collection (i.e. page 1 of 10), and
 * contain information about the larger collection.
 */


/**
 * @class
 * Collection of multiple objects with useful accessors.
 * May also represent subset of a larger collection
 * (for example, page 1 of 10)
 * and contain information about the larger collection.
 *
 * @name opensocial.Collection
 */


/**
 * Create a collection.
 *
 * @private
 * @constructor
 */
opensocial.Collection = function(array, opt_offset, opt_totalSize) {
  this.array_ = array || [];
  this.offset_ = opt_offset || 0;
  this.totalSize_ = opt_totalSize || this.array_.length;
};


/**
 * Finds the entry with the given ID value, or returns null if none is found.
 * @param {String} id The ID to look for
 * @return {Object?} The data
 */
opensocial.Collection.prototype.getById = function(id) {
   // TODO(doll): A non-linear search would be better
  for (var i = 0; i < this.size(); i++) {
    var item = this.array_[i];
    if (item.getId() == id) {
      return item;
    }
  }

  return null;
};


/**
 * Gets the size of this collection,
 * which is equal to or less than the
 * total size of the result.
 * @return {Number} The size of this collection
 */
opensocial.Collection.prototype.size = function() {
  return this.array_.length;
};


/**
 * Executes the provided function once per member of the collection,
 * with each member in turn as the
 * parameter to the function.
 * @param {Function} fn The function to call with each collection entry
 */
opensocial.Collection.prototype.each = function(fn) {
  for (var i = 0; i < this.size(); i++) {
    fn(this.array_[i]);
  }
};


/**
 * Returns an array of all the objects in this collection.
 * @return {Array.&lt;Object&gt;} The values in this collection
 */
opensocial.Collection.prototype.asArray = function() {
  return this.array_;
};


/**
 * Gets the total size of the larger result set
 * that this collection belongs to.
 * @return {Number} The total size of the result
 */
opensocial.Collection.prototype.getTotalSize = function() {
  return this.totalSize_;
};


/**
 * Gets the offset of this collection within a larger result set.
 * @return {Number} The offset into the total collection
 */
opensocial.Collection.prototype.getOffset = function() {
  return this.offset_;
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Representation of an enum.
 */


/**
 * @class
 * Base interface for all enum objects.
 * This class allows containers to use constants for fields that are usually
 * have a common set of values.
 * There are two main ways to use this class.
 *
 * <p>
 * If your gadget just wants to display how much of a smoker someone is,
 * it can simply use:
 * </p>
 *
 * <pre>html = "This person smokes: " + person.getField('smoker').getValue();</pre>
 *
 * <p>
 * This value field will be correctly set up by the container. This is a place
 * where the container can even localize the value for the gadget so that it
 * always shows the right thing.
 * </p>
 *
 * <p>
 * If your gadget wants to have some logic around the smoker
 * field it can use:
 * </p>
 *
 * <pre>if (person.getField('smoker').getKey() != "NO") { //gadget logic here }</pre>
 *
 * <p class="note">
 * <b>Note:</b>
 * The key may be null if the person's smoker field cannot be coerced
 * into one of the standard enum types.
 * The value, on the other hand, is never null.
 * </p>
 *
 * @name opensocial.Enum
 */


/**
 * Base interface for all enum objects.
 *
 * @private
 * @constructor
 */
opensocial.Enum = function(key, displayValue) {
  this.key = key;
  this.displayValue = displayValue;
};


/**
 * Use this for logic within your gadget. If they key is null then the value
 * does not fit in the defined enums.
 *
 * @return {String} The enum's key. This should be one of the defined enums
 *     below.
 */
opensocial.Enum.prototype.getKey = function() {
  return gadgets.util.escape(this.key);
};


/**
 * The value of this enum. This will be a user displayable string. If the
 * container supports localization, the string will be localized.
 *
 * @return {String} The enum's value.
 */
opensocial.Enum.prototype.getDisplayValue = function() {
  return gadgets.util.escape(this.displayValue);
};


/**
 * @static
 * @class
 * The enum keys used by the smoker field.
 * <p><b>See also:</b>
 * <a href="opensocial.Person.Field.html">
 * opensocial.Person.Field.Smoker</a>
 * </p>
 *
 * @name opensocial.Enum.Smoker
 */
opensocial.Enum.Smoker = {
  /** @member opensocial.Enum.Smoker */
  NO : 'NO',
  /** @member opensocial.Enum.Smoker */
  YES : 'YES',
  /** @member opensocial.Enum.Smoker */
  SOCIALLY : 'SOCIALLY',
  /** @member opensocial.Enum.Smoker */
  OCCASIONALLY : 'OCCASIONALLY',
  /** @member opensocial.Enum.Smoker */
  REGULARLY : 'REGULARLY',
  /** @member opensocial.Enum.Smoker */
  HEAVILY : 'HEAVILY',
  /** @member opensocial.Enum.Smoker */
  QUITTING : 'QUITTING',
  /** @member opensocial.Enum.Smoker */
  QUIT : 'QUIT'
};


/**
 * @static
 * @class
 * The enum keys used by the drinker field.
 * <p><b>See also:</b>
 * <a href="opensocial.Person.Field.html">
 * opensocial.Person.Field.Drinker</a>
 * </p>
 *
 * @name opensocial.Enum.Drinker
 */
opensocial.Enum.Drinker = {
  /** @member opensocial.Enum.Drinker */
  NO : 'NO',
  /** @member opensocial.Enum.Drinker */
  YES : 'YES',
  /** @member opensocial.Enum.Drinker */
  SOCIALLY : 'SOCIALLY',
  /** @member opensocial.Enum.Drinker */
  OCCASIONALLY : 'OCCASIONALLY',
  /** @member opensocial.Enum.Drinker */
  REGULARLY : 'REGULARLY',
  /** @member opensocial.Enum.Drinker */
  HEAVILY : 'HEAVILY',
  /** @member opensocial.Enum.Drinker */
  QUITTING : 'QUITTING',
  /** @member opensocial.Enum.Drinker */
  QUIT : 'QUIT'
};


/**
 * @static
 * @class
 * The enum keys used by the gender field.
 * <p><b>See also:</b>
 * <a href="opensocial.Person.Field.html">
 * opensocial.Person.Field.Gender</a>
 * </p>
 *
 * @name opensocial.Enum.Gender
 */
opensocial.Enum.Gender = {
  /** @member opensocial.Enum.Gender */
  MALE : 'MALE',
  /** @member opensocial.Enum.Gender */
  FEMALE : 'FEMALE'
};


/**
 * @static
 * @class
 * The enum keys used by the lookingFor field.
 * <p><b>See also:</b>
 * <a href="opensocial.Person.Field.html">
 * opensocial.Person.Field.LookingFor</a>
 * </p>
 *
 * @name opensocial.Enum.LookingFor
 */
opensocial.Enum.LookingFor = {
  /** @member opensocial.Enum.LookingFor */
  DATING : 'DATING',
  /** @member opensocial.Enum.LookingFor */
  FRIENDS : 'FRIENDS',
  /** @member opensocial.Enum.LookingFor */
  RELATIONSHIP : 'RELATIONSHIP',
  /** @member opensocial.Enum.LookingFor */
  NETWORKING : 'NETWORKING',
  /** @member opensocial.Enum.LookingFor */
  ACTIVITY_PARTNERS : 'ACTIVITY_PARTNERS',
  /** @member opensocial.Enum.LookingFor */
  RANDOM : 'RANDOM'
};


/**
 * @static
 * @class
 * The enum keys used by the networkPresence field.
 * <p><b>See also:</b>
 * <a href="opensocial.Person.Field.html">
 * opensocial.Person.Field.NetworkPresence</a>
 * </p>
 *
 * @name opensocial.Enum.Presence
 */
opensocial.Enum.Presence = {
  /** @member opensocial.Enum.Presence */
  AWAY : 'AWAY',
  /** @member opensocial.Enum.Presence */
  CHAT : 'CHAT',
  /** @member opensocial.Enum.Presence */
  DND : 'DND',
  /** @member opensocial.Enum.Presence */
  OFFLINE : 'OFFLINE',
  /** @member opensocial.Enum.Presence */
  ONLINE : 'ONLINE',
  /** @member opensocial.Enum.Presence */
  XA : 'XA'
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Object used to request social information from the container.
 * This includes data for friends, profiles, app data, and activities.
 *
 * All apps that require access to people information should send a dataRequest
 * in order to receieve a dataResponse
 */


/**
 * @class
 * <p>
 * Used to request social information from the container.
 * This includes data for friends, profiles, app data, and activities.
 * All apps that require access to people information
 * should send a DataRequest.
 * </p>
 *
 * <p>
 * Here's an example of creating, initializing, sending, and handling
 * the results of a data request:
 * </p>
 *
 * <pre>function requestMe() {
  var req = opensocial.newDataRequest();
  req.add(req.newFetchPersonRequest(
            opensocial.DataRequest.PersonId.VIEWER),
          "viewer");
  req.send(handleRequestMe);
};

function handleRequestMe(data) {
  var viewer = data.get("viewer");
  if (viewer.hadError()) {
    <em>//Handle error using viewer.getError()...</em>
    return;
  }

  <em>//No error. Do something with viewer.getData()...</em>
}
</pre>
 * <p>
 * <b>See also:</b>
 * <a href="opensocial.html#newDataRequest"><code>
 * opensocial.newDataRequest()</code></a>
 * </p>
 *
 * @name opensocial.DataRequest
 */

/**
 * Do not create a DataRequest directly, instead use
 * opensocial.createDataRequest();
 *
 * @private
 * @constructor
 */
opensocial.DataRequest = function() {
  this.requestObjects_ = [];
};


/**
 * {Array.<{key: string, request:opensocial.DataRequest.BaseDataRequest}>}
 *    requestObjects An array of
 *    data requests that the container should fetch data for
 * @private
 */
opensocial.DataRequest.prototype.requestObjects_ = null;


/**
 * Get the requested objects
 *
 * @return {Array.<{key:String, request:BaseDataRequest}>}
 *    requestObjects An array of data requests that the container should fetch
 *    data for
 * @private
 */
opensocial.DataRequest.prototype.getRequestObjects = function() {
  return this.requestObjects_;
};


/**
 * Adds an item to fetch (get) or update (set) data from the server.
 * A single DataRequest object can have multiple items.
 * As a rule, each item is executed in the order it was added,
 * starting with the item that was added first.
 * However, items that can't collide might be executed in parallel.
 *
 * @param {Object} request Specifies which data to fetch or update
 * @param {String} opt_key A key to map the generated response data to
 */
opensocial.DataRequest.prototype.add = function(request, opt_key) {
  return this.requestObjects_.push({'key': opt_key, 'request': request});
};


/**
 * Sends a data request to the server in order to get a data response.
 * Although the server may optimize these requests,
 * they will always be executed
 * as though they were serial.
 *
 * @param {Function} opt_callback The function to call with the
 *   <a href="opensocial.DataResponse.html">data response</a>
 *    generated by the server
 */
opensocial.DataRequest.prototype.send = function(opt_callback) {
  var callback = opt_callback || function(){};
  opensocial.Container.get().requestData(this, callback);
};


/**
 * @static
 * @class
 * The sort orders available for ordering person objects.
 *
 * @name opensocial.DataRequest.SortOrder
 */
opensocial.DataRequest.SortOrder = {
  /**
   * When used will sort people by the container's definition of top friends.
   * @member opensocial.DataRequest.SortOrder
   */
  TOP_FRIENDS : 'topFriends',
  /**
   * When used will sort people alphabetically by the name field.
   *
   * @member opensocial.DataRequest.SortOrder
   */
  NAME : 'name'
};


/**
 * @static
 * @class
 * The filters available for limiting person requests.
 *
 * @name opensocial.DataRequest.FilterType
 */
opensocial.DataRequest.FilterType = {
  /**
   * Retrieves all friends.
   *
   * @member opensocial.DataRequest.FilterType
   */
  ALL : 'all',
  /**
   * Retrieves all friends with any data for this application.
   *
   * @member opensocial.DataRequest.FilterType
   */
  HAS_APP : 'hasApp',
  /**
   * Retrieves only the user's top friends.
   *
   * @member opensocial.DataRequest.FilterType
   */
  TOP_FRIENDS : 'topFriends',
  /**
   * Will filter the people requested by checking if they are friends with
   * the given <a href="opensocial.IdSpec.html">idSpec</a>. Expects a
   *    filterOptions parameter to be passed with the following fields defined:
   *  - idSpec The <a href="opensocial.IdSpec.html">idSpec</a> that each person
   *        must be friends with.
  */
  IS_FRIENDS_WITH: 'isFriendsWith'
};


/**
 * @static
 * @class
 * @name opensocial.DataRequest.PeopleRequestFields
 */
opensocial.DataRequest.PeopleRequestFields = {
  /**
   * An array of
   * <a href="opensocial.Person.Field.html">
   * <code>opensocial.Person.Field</code></a>
   * specifying what profile data to fetch
   * for each of the person objects. The server will always include
   * ID, NAME, and THUMBNAIL_URL.
   *
   * @member opensocial.DataRequest.PeopleRequestFields
   */
  PROFILE_DETAILS : 'profileDetail',

  /**
   * A sort order for the people objects; defaults to TOP_FRIENDS.
   * Possible values are defined by
   * <a href="opensocial.DataRequest.SortOrder.html">SortOrder</a>.
   *
   * @member opensocial.DataRequest.PeopleRequestFields
   */
  SORT_ORDER : 'sortOrder',

  /**
   * How to filter the people objects; defaults to ALL.
   * Possible values are defined by
   * <a href="opensocial.DataRequest.FilterType.html">FilterType</a>.
   *
   * @member opensocial.DataRequest.PeopleRequestFields
   */
  FILTER : 'filter',

  /**
   * Additional options to be passed into the filter,
   * specified as a Map&lt;String, Object>.
   *
   * @member opensocial.DataRequest.PeopleRequestFields
   */
  FILTER_OPTIONS: 'filterOptions',

  /**
   * When paginating, the index of the first item to fetch.
   * Specified as a <code>Number</code>.
   *
   * @member opensocial.DataRequest.PeopleRequestFields
   */
  FIRST : 'first',

  /**
   * The maximum number of items to fetch; defaults to 20. If set to a larger
   * number, a container may honor the request, or may limit the number to a
   * container-specified limit of at least 20.
   * Specified as a <code>Number</code>.
   *
   * @member opensocial.DataRequest.PeopleRequestFields
   */
  MAX : 'max'
};


/**
 * If the named param does not exist sets it to the default value.
 *
 * @param {Map} params Parameter map.
 * @param {String} name of the param to check
 * @param {Object} defaultValue The value to set if the param does not exist.
 * @private
 */
opensocial.DataRequest.prototype.addDefaultParam = function(params, name,
    defaultValue) {
  params[name] = params[name] || defaultValue;
};


/**
 * Adds the default profile fields to the desired array.
 *
 * @param {Map} params Parameter map.
 * @private
 */
opensocial.DataRequest.prototype.addDefaultProfileFields = function(params) {
  var fields = opensocial.DataRequest.PeopleRequestFields;
  var profileFields = params[fields.PROFILE_DETAILS] || [];
  params[fields.PROFILE_DETAILS] = profileFields.concat(
      [opensocial.Person.Field.ID, opensocial.Person.Field.NAME,
       opensocial.Person.Field.THUMBNAIL_URL]);
};


/**
 * Returns the keys object as an array.
 *
 * @param {Object} keys
 * @private
 */
opensocial.DataRequest.prototype.asArray = function(keys) {
  if (opensocial.Container.isArray(keys)) {
    return keys;
  } else {
    return [keys];
  }
};


/**
 * Creates an item to request a profile for the specified person ID.
 * When processed, returns a
 * <a href="opensocial.Person.html"><code>Person</code></a> object.
 *
 * @param {String} id The ID of the person to fetch; can be the standard
 *    <a href="opensocial.IdSpec.PersonId.html">person ID</a>
 *    of VIEWER or OWNER
 * @param {Map.&lt;opensocial.DataRequest.PeopleRequestFields, Object&gt;}
 *  opt_params
 *    Additional
 *    <a href="opensocial.DataRequest.PeopleRequestFields.html">parameters</a>
 *    to pass to the request; this request supports PROFILE_DETAILS
 * @return {Object} A request object
 */
opensocial.DataRequest.prototype.newFetchPersonRequest = function(id,
    opt_params) {
  opt_params = opt_params || {};
  this.addDefaultProfileFields(opt_params);

  return opensocial.Container.get().newFetchPersonRequest(id, opt_params);
};


/**
 * Creates an item to request friends from the server.
 * When processed, returns a <a href="opensocial.Collection.html">Collection</a>
 * &lt;<a href="opensocial.Person.html">Person</a>&gt; object.
 *
 * @param {opensocial.IdSpec} idSpec An IdSpec used to specify
 *    which people to fetch. See also <a href="opensocial.IdSpec.html">IdSpec</a>.
 * @param {Map.&lt;opensocial.DataRequest.PeopleRequestFields, Object&gt;}
 *  opt_params
 *    Additional
 *    <a href="opensocial.DataRequest.PeopleRequestFields.html">params</a>
 *    to pass to the request
 * @return {Object} A request object
 */
opensocial.DataRequest.prototype.newFetchPeopleRequest = function(idSpec,
    opt_params) {
  opt_params = opt_params || {};
  var fields = opensocial.DataRequest.PeopleRequestFields;

  this.addDefaultProfileFields(opt_params);

  this.addDefaultParam(opt_params, fields.SORT_ORDER,
      opensocial.DataRequest.SortOrder.TOP_FRIENDS);

  this.addDefaultParam(opt_params, fields.FILTER,
      opensocial.DataRequest.FilterType.ALL);

  this.addDefaultParam(opt_params, fields.FIRST, 0);

  this.addDefaultParam(opt_params, fields.MAX, 20);

  return opensocial.Container.get().newFetchPeopleRequest(idSpec, opt_params);
};


/**
 * @static
 * @class
 * @name opensocial.DataRequest.DataRequestFields
 */
opensocial.DataRequest.DataRequestFields = {
  /**
   * How to escape person data returned from the server; defaults to HTML_ESCAPE.
   * Possible values are defined by
   * <a href="opensocial.EscapeType.html">EscapeType</a>.
   *
   * @member opensocial.DataRequest.DataRequestFields
   */
  ESCAPE_TYPE : 'escapeType'
};


/**
 * Creates an item to request app data for the given people.
 * When processed, returns a Map&lt;
 * <a href="opensocial.DataRequest.PersonId.html">PersonId</a>,
 * Map&lt;String, String&gt;&gt; object.
 * All of the data values returned will be valid json.
 *
 * @param {opensocial.IdSpec} idSpec An IdSpec used to specify which people to
 *     fetch. See also <a href="opensocial.IdSpec.html">IdSpec</a>.
 * @param {Array.&lt;String&gt; | String} keys The keys you want data for; this
 *     can be an array of key names, a single key name, or "*" to mean
 *     "all keys"
 * @param {Map.&lt;opensocial.DataRequest.DataRequestFields, Object&gt;}
 *  opt_params Additional
 *    <a href="opensocial.DataRequest.DataRequestFields.html">params</a>
 *    to pass to the request
 * @return {Object} A request object
 */
opensocial.DataRequest.prototype.newFetchPersonAppDataRequest = function(idSpec,
    keys, opt_params) {
  return opensocial.Container.get().newFetchPersonAppDataRequest(idSpec,
      this.asArray(keys), opt_params);
};


/**
 * Creates an item to request an update of an app field for the given person.
 * When processed, does not return any data.
 *
 * @param {String} id The ID of the person to update; only the
 *     special <code>VIEWER</code> ID is currently allowed.
 * @param {String} key The name of the key. This may only contain alphanumeric
 *     (A-Za-z0-9) characters, underscore(_), dot(.) or dash(-).
 * @param {String} value The value, must be valid json
 * @return {Object} A request object
 */
opensocial.DataRequest.prototype.newUpdatePersonAppDataRequest = function(id,
    key, value) {
  return opensocial.Container.get().newUpdatePersonAppDataRequest(id, key,
      value);
};


/**
 * Deletes the given keys from the datastore for the given person.
 * When processed, does not return any data.
 *
 * @param {String} id The ID of the person to update; only the
 *     special <code>VIEWER</code> ID is currently allowed.
 * @param {Array.&lt;String&gt; | String} keys The keys you want to delete from
 *     the datastore; this can be an array of key names, a single key name,
 *     or "*" to mean "all keys"
 * @return {Object} A request object
 */
opensocial.DataRequest.prototype.newRemovePersonAppDataRequest = function(id,
    keys) {
  return opensocial.Container.get().newRemovePersonAppDataRequest(id, keys);
};


/**
 * @static
 * @class
 * Used by
 * <a href="opensocial.DataRequest.html#newFetchActivitiesRequest">
 * <code>DataRequest.newFetchActivitiesRequest()</code></a>.
 * @name opensocial.DataRequest.ActivityRequestFields
 * @private
 */
opensocial.DataRequest.ActivityRequestFields = {
  /**
   * {String} If provided will filter all activities by this app Id.
   * @private - at the moment you can only request activities for your own app
   */
  APP_ID : 'appId'
};


/**
 * Creates an item to request an activity stream from the server.
 *
 * <p>
 * When processed, returns a Collection&lt;Activity&gt;.
 * </p>
 *
 * @param {opensocial.IdSpec} idSpec An IdSpec used to specify which people to
 *     fetch. See also <a href="opensocial.IdSpec.html">IdSpec</a>.
 * @param {Map.&lt;opensocial.DataRequest.ActivityRequestFields, Object&gt;}
 *  opt_params
 *    Additional parameters
 *    to pass to the request; not currently used
 * @return {Object} A request object
 */
opensocial.DataRequest.prototype.newFetchActivitiesRequest = function(idSpec,
    opt_params) {
  opt_params = opt_params || {};
  return opensocial.Container.get().newFetchActivitiesRequest(idSpec,
      opt_params);
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Interface for containers of people functionality.
 */


/**
 * Base interface for all containers.
 *
 * @constructor
 * @private
 */
opensocial.Container = function() {};


/**
 * The container instance.
 *
 * @type Container
 * @private
 */
opensocial.Container.container_ = null;


/**
 * Set the current container object.
 *
 * @param {opensocial.Container} container The container
 * @private
 */
opensocial.Container.setContainer = function(container) {
  opensocial.Container.container_ = container;
};


/**
 * Get the current container object.
 *
 * @return {opensocial.Container} container The current container
 * @private
 */
opensocial.Container.get = function() {
  return opensocial.Container.container_;
};


/**
 * Gets the current environment for this gadget. You can use the environment to
 * query things like what profile fields and surfaces are supported by this
 * container, what parameters were passed to the current gadget and so forth.
 *
 * @return {opensocial.Environment} The current environment
 *
 * @private
 */
opensocial.Container.prototype.getEnvironment = function() {};

/**
 * Requests the container to send a specific message to the specified users. If
 * the container does not support this method the callback will be called with a
 * opensocial.ResponseItem. The response item will have its error code set to
 * NOT_IMPLEMENTED.
 *
 * @param {Array.&lt;String&gt; | String} recipients An ID, array of IDs, or a
 *     group reference; the supported keys are VIEWER, OWNER, VIEWER_FRIENDS,
 *    OWNER_FRIENDS, or a single ID within one of those groups
 * @param {opensocial.Message} message The message to send to the specified
 *     users.
 * @param {Function} opt_callback The function to call once the request has been
 *    processed; either this callback will be called or the gadget will be
 *    reloaded from scratch. This function will be passed one parameter, an
 *    opensocial.ResponseItem. The error code will be set to reflect whether
 *    there were any problems with the request. If there was no error, the
 *    message was sent. If there was an error, you can use the response item's
 *    getErrorCode method to determine how to proceed. The data on the response
 *    item will not be set.
 *
 * @member opensocial
 * @private
 */
opensocial.Container.prototype.requestSendMessage = function(recipients,
    message, opt_callback, opt_params) {
  if (opt_callback) {
    window.setTimeout(function() {
      opt_callback(new opensocial.ResponseItem(
          null, null, opensocial.ResponseItem.Error.NOT_IMPLEMENTED, null));
    }, 0);
  }
};

/**
* Uploads a media item. Allows the container to provide some standard 
* UI and other items to post files to a site.
*
* @param {Array.&lt;String&gt; | String} albumId Indicates which 
* album/collection to insert the items into. If not present or set 
* to null|undefined, the items go into the default location for the container. 
* Partial upload is possible, such that if the container allows for multiple items, 
* some items may fail to be added.
* @param {Function} opt_callback 	Method is called when the upload 
* completes. The callback function is passed one parameter, an 
* opensocial.ResponseItem. The error code will be set to reflect whether there 
* were any problems with the request. If there was no error, the message was sent. 
* If there was an error, you can use the response item's getErrorCode method to 
* determine how to proceed. getErrorMessage will contain a comma separated list of 
* files that failed to upload. The data on the response item will be set to an array 
* of media item objects for successful items.
*
* @member opensocial
* @private
*/

opensocial.Container.prototype.requestUploadMediaItem = function(albumId, opt_callback, 
    opt_params) {
    if (opt_callback) {
        window.setTimeout(function() {
            opt_callback(new opensocial.ResponseItem(
          null, null, opensocial.ResponseItem.Error.NOT_IMPLEMENTED, null));
        }, 0);
    }
};


/**
 * Requests the container to share this gadget with the specified users. If the
 * container does not support this method the callback will be called with a
 * opensocial.ResponseItem. The response item will have its error code set to
 * NOT_IMPLEMENTED.
 *
 * @param {Array.&lt;String&gt; | String} recipients An ID, array of IDs, or a
 *     group reference; the supported keys are VIEWER, OWNER, VIEWER_FRIENDS,
 *    OWNER_FRIENDS, or a single ID within one of those groups
 * @param {opensocial.Message} reason The reason the user wants the gadget to
 *     share itself. This reason can be used by the container when prompting the
 *     user for permission to share the app. It may also be ignored.
 * @param {Function} opt_callback The function to call once the request has been
 *    processed; either this callback will be called or the gadget will be
 *    reloaded from scratch. This function will be passed one parameter, an
 *    opensocial.ResponseItem. The error code will be set to reflect whether
 *    there were any problems with the request. If there was no error, the
 *    sharing request was sent. If there was an error, you can use the response
 *    item's getErrorCode method to determine how to proceed. The data on the
 *    response item will not be set.
 *
 * @member opensocial
 * @private
 */
opensocial.Container.prototype.requestShareApp = function(recipients, reason,
    opt_callback, opt_params) {
  if (opt_callback) {
    window.setTimeout(function() {
      opt_callback(new opensocial.ResponseItem(
          null, null, opensocial.ResponseItem.Error.NOT_IMPLEMENTED, null));
    }, 0);
  }
};


/**
 * Request for the container to make the specified person not a friend.
 *
 * Note: If this is the first activity that has been created for the user and
 * the request is marked as HIGH priority then this call may open a user flow
 * and navigate away from your gadget.
 *
 * @param {Activity} activity The activity to create. The only required field is
 *     title.
 * @param {CreateActivityPriority} priority The priority for this request.
 * @param {Function} opt_callback Function to call once the request has been
 *    processed.
 * @private
 */
opensocial.Container.prototype.requestCreateActivity = function(activity,
    priority, opt_callback) {
  if (opt_callback) {
    window.setTimeout(function() {
      opt_callback(new opensocial.ResponseItem(
          null, null, opensocial.ResponseItem.Error.NOT_IMPLEMENTED, null));
    }, 0);
  }
};


/**
 * Returns whether the current gadget has access to the specified
 * permission.
 *
 * @param {opensocial.Permission | String} permission The permission
 * @return {Boolean} Whether the gadget has access for the permission.
 *
 * @private
 */
opensocial.Container.prototype.hasPermission = function(permission) {
  return false;
};


/**
 * Requests the user grants access to the specified permissions.
 *
 * @param {Array.<opensocial.Permission>} permissions The permissions to request
 *    access to from the viewer
 * @param {String} reason Will be displayed to the user as the reason why these
 *    permissions are needed.
 * @param {Function} opt_callback The function to call once the request has been
 *    processed. This callback will either be called or the gadget will be
 *    reloaded from scratch
 *
 * @private
 */
opensocial.Container.prototype.requestPermission = function(permissions, reason,
    opt_callback) {
  if (opt_callback) {
    window.setTimeout(function () {
      opt_callback(new opensocial.ResponseItem(
          null, null, opensocial.ResponseItem.Error.NOT_IMPLEMENTED, null));
    }, 0);
  }
};


/**
 * Calls the callback function with a dataResponse object containing the data
 * asked for in the dataRequest object.
 *
 * @param {opensocial.DataRequest} dataRequest Specifies which data to get from
 *    the server
 * @param {Function} callback Function to call after the data is fetched
 * @private
 */
opensocial.Container.prototype.requestData = function(dataRequest, callback) {};


/**
 * Request a profile for the specified person id.
 * When processed, returns a Person object.
 *
 * @param {String} id The id of the person to fetch. Can also be standard
 *    person IDs of VIEWER and OWNER.
 * @param {Map.<opensocial.DataRequest.PeopleRequestFields, Object>} opt_params
 *    Additional params to pass to the request. This request supports
 *    PROFILE_DETAILS.
 * @return {Object} a request object
 * @private
 */
opensocial.Container.prototype.newFetchPersonRequest = function(id,
    opt_params) {};


/**
 * Used to request friends from the server.
 * When processed, returns a Collection&lt;Person&gt; object.
 *
 * @param {opensocial.IdSpec} idSpec An IdSpec used to specify which people to
 *     fetch. See also <a href="opensocial.IdSpec.html">IdSpec</a>.
 * @param {Map.<opensocial.DataRequest.PeopleRequestFields, Object>} opt_params
 *    Additional params to pass to the request. This request supports
 *    PROFILE_DETAILS, SORT_ORDER, FILTER, FILTER_OPTIONS, FIRST, and MAX.
 * @return {Object} a request object
 * @private
 */
opensocial.Container.prototype.newFetchPeopleRequest = function(idSpec,
    opt_params) {};


/**
 * Used to request app data for the given people.
 * When processed, returns a Map&lt;person id, Map&lt;String, String&gt;&gt;
 * object.TODO: All of the data values returned will be valid json.
 *
 * @param {opensocial.IdSpec} idSpec An IdSpec used to specify which people to
 *     fetch. See also <a href="opensocial.IdSpec.html">IdSpec</a>.
 * @param {Array.<String> | String} keys The keys you want data for. This
 *     can be an array of key names, a single key name, or "*" to mean
 *     "all keys".
 * @param {Map.&lt;opensocial.DataRequest.DataRequestFields, Object&gt;}
 *  opt_params Additional
 *    <a href="opensocial.DataRequest.DataRequestFields.html">params</a>
 *    to pass to the request
 * @return {Object} a request object
 * @private
 */
opensocial.Container.prototype.newFetchPersonAppDataRequest = function(idSpec,
    keys, opt_params) {};


/**
 * Used to request an update of an app field for the given person.
 * When processed, does not return any data.
 *
 * @param {String} id The id of the person to update. (Right now only the
 *    special VIEWER id is allowed.)
 * @param {String} key The name of the key
 * @param {String} value The value
 * @return {Object} a request object
 * @private
 */
opensocial.Container.prototype.newUpdatePersonAppDataRequest = function(id,
    key, value) {};


/**
 * Deletes the given keys from the datastore for the given person.
 * When processed, does not return any data.
 *
 * @param {String} id The ID of the person to update; only the
 *     special <code>VIEWER</code> ID is currently allowed.
 * @param {Array.&lt;String&gt; | String} keys The keys you want to delete from
 *     the datastore; this can be an array of key names, a single key name,
 *     or "*" to mean "all keys"
 * @return {Object} A request object
 * @private
 */
opensocial.Container.prototype.newRemovePersonAppDataRequest = function(id,
    keys) {};


/**
 * Used to request an activity stream from the server.
 *
 * When processed, returns a Collection&lt;Activity&gt;.
 *
 * @param {opensocial.IdSpec} idSpec An IdSpec used to specify which people to
 *     fetch. See also <a href="opensocial.IdSpec.html">IdSpec</a>.
 * @param {Map.<opensocial.DataRequest.ActivityRequestFields, Object>} opt_params
 *    Additional params to pass to the request.
 * @return {Object} a request object
 * @private
 */
opensocial.Container.prototype.newFetchActivitiesRequest = function(idSpec,
    opt_params) {};


/**
 * Creates a new collection with caja support if enabled.
 * @return {opensocial.Collection} the collection object
 * @private
 */
opensocial.Container.prototype.newCollection = function(array, opt_offset,
    opt_totalSize) {
  return new opensocial.Collection(array, opt_offset, opt_totalSize);
};


/**
 * Creates a new person with caja support if enabled.
 * @return {opensocial.Person} the person object
 * @private
 */
opensocial.Container.prototype.newPerson = function(opt_params, opt_isOwner,
    opt_isViewer) {
  return new opensocial.Person(opt_params, opt_isOwner, opt_isViewer);
};


/**
 * Get an activity object used to create activities on the server
 *
 * @param {opensocial.Activity.Template || String} title The title of an
 *     activity, a template is reccommended, but this field can also be a
 *     string.
 * @param {Map.<opensocial.Activity.Field, Object>} opt_params Any other
 *    fields that should be set on the activity object. All of the defined
 *    Fields are supported.
 * @return {opensocial.Activity} the activity object
 * @private
 */
opensocial.Container.prototype.newActivity = function(opt_params) {
  return new opensocial.Activity(opt_params);
};


/**
 * Creates a media item. Represents images, movies, and audio.
 * Used when creating activities on the server.
 *
 * @param {String} mimeType of the media
 * @param {String} url where the media can be found
 * @param {Map.<opensocial.MediaItem.Field, Object>} opt_params
 *    Any other fields that should be set on the media item object.
 *    All of the defined Fields are supported.
 *
 * @return {opensocial.MediaItem} the media item object
 * @private
 */
opensocial.Container.prototype.newMediaItem = function(mimeType, url,
    opt_params) {
  return new opensocial.MediaItem(mimeType, url, opt_params);
};


/**
 * Creates a media item associated with an activity.
 * Represents images, movies, and audio.
 * Used when creating activities on the server.
 *
 * @param {String} body The main text of the message.
 * @param {Map.&lt;opensocial.Message.Field, Object&gt;} opt_params
 *    Any other fields that should be set on the message object;
 *    all of the defined
 *    <a href="opensocial.Message.Field.html">Field</a>s
 *    are supported
 *
 * @return {opensocial.Message} The new
 *    <a href="opensocial.Message.html">message</a> object
 * @private
 */
opensocial.Container.prototype.newMessage = function(body, opt_params) {
  return new opensocial.Message(body, opt_params);
};


/**
 * Creates an IdSpec object.
 *
 * @param {Map.&lt;opensocial.IdSpec.Field, Object&gt;} parameters
 *    Parameters defining the id spec.
 * @return {opensocial.IdSpec} The new
 *     <a href="opensocial.IdSpec.html">IdSpec</a> object
 * @private
 */
opensocial.Container.prototype.newIdSpec = function(params) {
  return new opensocial.IdSpec(params);
};


/**
 * Creates a NavigationParameters object.
 *
 * @param {Map.&lt;opensocial.NavigationParameters.Field, Object&gt;} parameters
 *     Parameters defining the navigation
 * @return {opensocial.NavigationParameters} The new
 *     <a href="opensocial.NavigationParameters.html">NavigationParameters</a>
 *     object
 * @private
 */
opensocial.Container.prototype.newNavigationParameters = function(params) {
  return new opensocial.NavigationParameters(params);
};


/**
 * Creates a new response item with caja support if enabled.
 * @return {opensocial.ResponseItem} the response item object
 * @private
 */
opensocial.Container.prototype.newResponseItem = function(originalDataRequest,
    data, opt_errorCode, opt_errorMessage) {
  return new opensocial.ResponseItem(originalDataRequest, data, opt_errorCode,
      opt_errorMessage);
};


/**
 * Creates a new data response with caja support if enabled.
 * @return {opensocial.DataResponse} the data response object
 * @private
 */
opensocial.Container.prototype.newDataResponse = function(responseItems,
    opt_globalError) {
  return new opensocial.DataResponse(responseItems, opt_globalError);
};


/**
 * Get a data request object to use for sending and fetching data from the
 * server.
 *
 * @return {opensocial.DataRequest} the request object
 * @private
 */
opensocial.Container.prototype.newDataRequest = function() {
  return new opensocial.DataRequest();
};


/**
 * Get a new environment object.
 *
 * @return {opensocial.Environment} the environment object
 * @private
 */
opensocial.Container.prototype.newEnvironment = function(domain,
    supportedFields) {
  return new opensocial.Environment(domain, supportedFields);
};


/**
 * Returns true if the specified value is an array
 * @param {Object} val Variable to test
 * @return {boolean} Whether variable is an array
 * @private
 */
opensocial.Container.isArray = function(val) {
  return val instanceof Array;
};


/**
 * Returns the value corresponding to the key in the fields map. Escapes
 * the value appropriately.
 * @param {Map<String, Object>} fields All of the values mapped by key.
 * @param {String} key The key to get data for.
 * @param {Map.&lt;opensocial.DataRequest.DataRequestFields, Object&gt;}
 *  opt_params Additional
 *    <a href="opensocial.DataRequest.DataRequestFields.html">params</a>
 *    to pass to the request.
 * @return {String} The data
 * @private
 */
opensocial.Container.getField = function(fields, key, opt_params) {
  var value = fields[key];
  return opensocial.Container.escape(value, opt_params, false);
};

opensocial.Container.escape = function(value, opt_params, opt_escapeObjects) {
  if (opt_params && opt_params['escapeType'] == 'none') {
    return value;
  } else {
    return gadgets.util.escape(value, opt_escapeObjects);
  }
};


/**
 * Caja Support.  See features/caja/*.js
 */
var cajita;
var ___;
var attachDocumentStub;
// See features/caja/domita.js for uriCallback's contract.
var uriCallback = {
  rewrite: function rewrite(uri, mimeTypes) {
    uri = String(uri);
    // By default, only allow references to anchors.
    if (/^#/.test(uri)) {
      return '#' + encodeURIComponent(decodeURIComponent(uri.substring(1)));
    // and files on the same host
    } else if (/^\/(?:[^\/][^?#]*)?$/) {
      return encodeURI(decodeURI(uri));
    }
    // This callback can be replaced with one that passes the URL through
    // a proxy that checks the mimetype.
    return null;
  }
};

/**
 * Enable Caja support
 *
 * @type Container
 * @private
 */

// TODO(doll): As caja evolves this method should get a lot smaller
opensocial.Container.prototype.enableCaja = function() {

  ___ = window["___"];
  cajita = window["cajita"];
  valijaMaker = window["valijaMaker"];
  attachDocumentStub = window["attachDocumentStub"];

  var imports = ___.copy(___.sharedImports);
  imports.outers = imports;
  imports.console = console;
  imports.$v = ___.asSimpleFunc(valijaMaker)(imports);
  ___.getNewModuleHandler().setImports(imports);

  
  attachDocumentStub('-g___', uriCallback, imports);
  var gadgetRoot = document.createElement('div');
  gadgetRoot.className = 'g___';
  imports.htmlEmitter___ = new HtmlEmitter(gadgetRoot);
  document.body.appendChild(gadgetRoot);

  // Add the opensocial APIs and mark them callable and readable.
  imports.gadgets = gadgets;
  imports.opensocial = opensocial;
  // The below described the opensocial reference APIs.
  // A prefix of "c_" specifies a class, "m_" a method, "f_" a field,
  // and "s_" a static member.
  // Derived from http://code.google.com/apis/opensocial/docs/0.8/reference/ .
  var opensocialSchema = {
    c_gadgets: {
      c_MiniMessage: {
        m_createDismissibleMessage: 0,
        m_createStaticMessage: 0,
        m_createTimerMessage: 0,
        m_dismissMessage: 0
      },
      c_Prefs: {
        m_getArray: 0,
        m_getBool: 0,
        m_getCountry: 0,
        m_getFloat: 0,
        m_getInt: 0,
        m_getLang: 0,
        m_getMsg: 0,
        m_getString: 0,
        m_set: 0,
        m_setArray: 0
      },
      c_Tab: {
        m_getCallback: 0,
        m_getContentContainer: 0,
        m_getIndex: 0,
        m_getName: 0,
        m_getNameContainer: 0
      },
      c_TabSet: {
        m_addTab: 0,
        m_alignTabs: 0,
        m_displayTabs: 0,
        m_getHeaderContainer: 0,
        m_getSelectedTab: 0,
        m_getTabs: 0,
        m_removeTab: 0,
        m_setSelectedTab: 0,
        m_swapTabs: 0
      },
      c_flash: {
        s_embedCachedFlash: 0,
        s_embedFlash: 0,
        s_getMajorVersion: 0
      },
      c_io: {
        c_AuthorizationType: {
          s_NONE: 0,
          s_OAUTH: 0,
          s_SIGNED: 0
        },
        c_ContentType: {
          s_DOM: 0,
          s_FEED: 0,
          s_JSON: 0,
          s_TEXT: 0
        },
        c_MethodType: {
          s_DELETE: 0,
          s_GET: 0,
          s_HEAD: 0,
          s_POST: 0,
          s_PUT: 0
        },
        c_ProxyUrlRequestParameters: {
          s_REFRESH_INTERVAL: 0
        },
        c_RequestParameters: {
          s_AUTHORIZATION: 0,
          s_CONTENT_TYPE: 0,
          s_GET_SUMMARIES: 0,
          s_HEADERS: 0,
          s_METHOD: 0,
          s_NUM_ENTRIES: 0,
          s_POST_DATA: 0
        },
        s_encodeValues: 0,
        s_getProxyUrl: 0,
        s_makeRequest: 0
      },
      c_json: {
        s_parse: 0,
        s_stringify: 0
      },
      c_pubsub: {
        s_publish: 0,
        s_subscribe: 0,
        s_unsubscribe: 0
      },
      c_rpc: {
        s_call: 0,
        s_register: 0,
        s_registerDefault: 0,
        s_unregister: 0,
        s_unregisterDefault: 0
      },
      c_skins: {
        c_Property: {
          s_ANCHOR_COLOR: 0,
          s_BG_COLOR: 0,
          s_BG_IMAGE: 0,
          s_FONT_COLOR: 0
        },
        s_getProperty: 0
      },
      c_util: {
        s_escapeString: 0,
        s_getFeatureParameters: 0,
        s_hasFeature: 0,
        s_registerOnLoadHandler: 0,
        s_unescapeString: 0
      },
      c_views: {
        c_View: {
          m_bind: 0,
          m_getUrlTemplate: 0,
          m_isOnlyVisibleGadget: 0
        },
        c_ViewType: {
          s_CANVAS: 0,
          s_HOME: 0,
          s_PREVIEW: 0,
          s_PROFILE: 0
        },
        s_bind: 0,
        s_getCurrentView: 0,
        s_getParams: 0,
        s_requestNavigateTo: 0
      },
      c_window: {
        s_adjustHeight: 0,
        s_getViewportDimensions: 0,
        s_setTitle: 0
      }
    },
    c_opensocial: {
      c_Activity: {
        c_Field: {
          s_APP_ID: 0,
          s_BODY: 0,
          s_BODY_ID: 0,
          s_EXTERNAL_ID: 0,
          s_ID: 0,
          s_MEDIA_ITEMS: 0,
          s_POSTED_TIME: 0,
          s_PRIORITY: 0,
          s_STREAM_FAVICON_URL: 0,
          s_STREAM_SOURCE_URL: 0,
          s_STREAM_TITLE: 0,
          s_STREAM_URL: 0,
          s_TEMPLATE_PARAMS: 0,
          s_TITLE: 0,
          s_TITLE_ID: 0,
          s_URL: 0,
          s_USER_ID: 0
        },
        m_getField: 0,
        m_getId: 0,
        m_setField: 0
      },
      c_Address: {
        c_Field: {
          s_COUNTRY: 0,
          s_EXTENDED_ADDRESS: 0,
          s_LATITUDE: 0,
          s_LOCALITY: 0,
          s_LONGITUDE: 0,
          s_POSTAL_CODE: 0,
          s_PO_BOX: 0,
          s_REGION: 0,
          s_STREET_ADDRESS: 0,
          s_TYPE: 0,
          s_UNSTRUCTURED_ADDRESS: 0
        },
        m_getField: 0
      },
      c_BodyType: {
        c_Field: {
          s_BUILD: 0,
          s_EYE_COLOR: 0,
          s_HAIR_COLOR: 0,
          s_HEIGHT: 0,
          s_WEIGHT: 0
        },
        m_getField: 0
      },
      c_Collection: {
        m_asArray: 0,
        m_each: 0,
        m_getById: 0,
        m_getOffset: 0,
        m_getTotalSize: 0,
        m_size: 0
      },
      c_CreateActivityPriority: {
        s_HIGH: 0,
        s_LOW: 0
      },
      c_DataRequest: {
        c_DataRequestFields: {
          s_ESCAPE_TYPE: 0
        },
        c_FilterType: {
          s_ALL: 0,
          s_HAS_APP: 0,
          s_TOP_FRIENDS: 0
        },
        c_PeopleRequestFields: {
          s_FILTER: 0,
          s_FILTER_OPTIONS: 0,
          s_FIRST: 0,
          s_MAX: 0,
          s_PROFILE_DETAILS: 0,
          s_SORT_ORDER: 0
        },
        c_SortOrder: {
          s_NAME: 0,
          s_TOP_FRIENDS: 0
        },
        m_add: 0,
        m_newFetchActivitiesRequest: 0,
        m_newFetchPeopleRequest: 0,
        m_newFetchPersonAppDataRequest: 0,
        m_newFetchPersonRequest: 0,
        m_newRemovePersonAppDataRequest: 0,
        m_newUpdatePersonAppDataRequest: 0,
        m_send: 0
      },
      c_DataResponse: {
        m_get: 0,
        m_getErrorMessage: 0,
        m_hadError: 0
      },
      c_Email: {
        c_Field: {
          s_ADDRESS: 0,
          s_TYPE: 0
        },
        m_getField: 0
      },
      c_Enum: {
        c_Drinker: {
          s_HEAVILY: 0,
          s_NO: 0,
          s_OCCASIONALLY: 0,
          s_QUIT: 0,
          s_QUITTING: 0,
          s_REGULARLY: 0,
          s_SOCIALLY: 0,
          s_YES: 0
        },
        c_Gender: {
          s_FEMALE: 0,
          s_MALE: 0
        },
        c_LookingFor: {
          s_ACTIVITY_PARTNERS: 0,
          s_DATING: 0,
          s_FRIENDS: 0,
          s_NETWORKING: 0,
          s_RANDOM: 0,
          s_RELATIONSHIP: 0
        },
        c_Presence: {
          s_AWAY: 0,
          s_CHAT: 0,
          s_DND: 0,
          s_OFFLINE: 0,
          s_ONLINE: 0,
          s_XA: 0
        },
        c_Smoker: {
          s_HEAVILY: 0,
          s_NO: 0,
          s_OCCASIONALLY: 0,
          s_QUIT: 0,
          s_QUITTING: 0,
          s_REGULARLY: 0,
          s_SOCIALLY: 0,
          s_YES: 0
        },
        m_getDisplayValue: 0,
        m_getKey: 0
      },
      c_Environment: {
        c_ObjectType: {
          s_ACTIVITY: 0,
          s_ACTIVITY_MEDIA_ITEM: 0,
          s_ADDRESS: 0,
          s_BODY_TYPE: 0,
          s_EMAIL: 0,
          s_FILTER_TYPE: 0,
          s_MESSAGE: 0,
          s_MESSAGE_TYPE: 0,
          s_NAME: 0,
          s_ORGANIZATION: 0,
          s_PERSON: 0,
          s_PHONE: 0,
          s_SORT_ORDER: 0,
          s_URL: 0
        },
        m_getDomain: 0,
        m_supportsField: 0
      },
      c_EscapeType: {
        s_HTML_ESCAPE: 0,
        s_NONE: 0
      },
      c_IdSpec: {
        c_Field: {
          s_GROUP_ID: 0,
          s_NETWORK_DISTANCE: 0,
          s_USER_ID: 0
        },
        c_PersonId: {
          s_OWNER: 0,
          s_VIEWER: 0
        },
        m_getField: 0,
        m_setField: 0
      },
      c_MediaItem: {
        c_Field: {
          s_MIME_TYPE: 0,
          s_TYPE: 0,
          s_URL: 0
        },
        c_Type: {
          s_AUDIO: 0,
          s_IMAGE: 0,
          s_VIDEO: 0
        },
        m_getField: 0,
        m_setField: 0
      },
      c_Message: {
        c_Field: {
          s_BODY: 0,
          s_BODY_ID: 0,
          s_TITLE: 0,
          s_TITLE_ID: 0,
          s_TYPE: 0
        },
        c_Type: {
          s_EMAIL: 0,
          s_NOTIFICATION: 0,
          s_PRIVATE_MESSAGE: 0,
          s_PUBLIC_MESSAGE: 0
        },
        m_getField: 0,
        m_setField: 0
      },
      c_Name: {
        c_Field: {
          s_ADDITIONAL_NAME: 0,
          s_FAMILY_NAME: 0,
          s_GIVEN_NAME: 0,
          s_HONORIFIC_PREFIX: 0,
          s_HONORIFIC_SUFFIX: 0,
          s_UNSTRUCTURED: 0
        },
        m_getField: 0
      },
      c_NavigationParameters: {
        c_DestinationType: {
          s_RECIPIENT_DESTINATION: 0,
          s_VIEWER_DESTINATION: 0
        },
        c_Field: {
          s_OWNER: 0,
          s_PARAMETERS: 0,
          s_VIEW: 0
        },
        m_getField: 0,
        m_setField: 0
      },
      c_Organization: {
        c_Field: {
          s_ADDRESS: 0,
          s_DESCRIPTION: 0,
          s_END_DATE: 0,
          s_FIELD: 0,
          s_NAME: 0,
          s_SALARY: 0,
          s_START_DATE: 0,
          s_SUB_FIELD: 0,
          s_TITLE: 0,
          s_WEBPAGE: 0
        },
        m_getField: 0
      },
      c_Permission: {
        s_VIEWER: 0
      },
      c_Person: {
        c_Field: {
          s_ABOUT_ME: 0,
          s_ACTIVITIES: 0,
          s_ADDRESSES: 0,
          s_AGE: 0,
          s_BODY_TYPE: 0,
          s_BOOKS: 0,
          s_CARS: 0,
          s_CHILDREN: 0,
          s_CURRENT_LOCATION: 0,
          s_DATE_OF_BIRTH: 0,
          s_DRINKER: 0,
          s_EMAILS: 0,
          s_ETHNICITY: 0,
          s_FASHION: 0,
          s_FOOD: 0,
          s_GENDER: 0,
          s_HAPPIEST_WHEN: 0,
          s_HAS_APP: 0,
          s_HEROES: 0,
          s_HUMOR: 0,
          s_ID: 0,
          s_INTERESTS: 0,
          s_JOBS: 0,
          s_JOB_INTERESTS: 0,
          s_LANGUAGES_SPOKEN: 0,
          s_LIVING_ARRANGEMENT: 0,
          s_LOOKING_FOR: 0,
          s_MOVIES: 0,
          s_MUSIC: 0,
          s_NAME: 0,
          s_NETWORK_PRESENCE: 0,
          s_NICKNAME: 0,
          s_PETS: 0,
          s_PHONE_NUMBERS: 0,
          s_POLITICAL_VIEWS: 0,
          s_PROFILE_SONG: 0,
          s_PROFILE_URL: 0,
          s_PROFILE_VIDEO: 0,
          s_QUOTES: 0,
          s_RELATIONSHIP_STATUS: 0,
          s_RELIGION: 0,
          s_ROMANCE: 0,
          s_SCARED_OF: 0,
          s_SCHOOLS: 0,
          s_SEXUAL_ORIENTATION: 0,
          s_SMOKER: 0,
          s_SPORTS: 0,
          s_STATUS: 0,
          s_TAGS: 0,
          s_THUMBNAIL_URL: 0,
          s_TIME_ZONE: 0,
          s_TURN_OFFS: 0,
          s_TURN_ONS: 0,
          s_TV_SHOWS: 0,
          s_URLS: 0
        },
        m_getDisplayName: 0,
        m_getField: 0,
        m_getId: 0,
        m_isOwner: 0,
        m_isViewer: 0
      },
      c_Phone: {
        c_Field: {
          s_NUMBER: 0,
          s_TYPE: 0
        },
        m_getField: 0
      },
      c_ResponseItem: {
        c_Error: {
          s_BAD_REQUEST: 0,
          s_FORBIDDEN: 0,
          s_INTERNAL_ERROR: 0,
          s_LIMIT_EXCEEDED: 0,
          s_NOT_IMPLEMENTED: 0,
          s_UNAUTHORIZED: 0
        },
        m_getData: 0,
        m_getErrorCode: 0,
        m_getErrorMessage: 0,
        m_getOriginalDataRequest: 0,
        m_hadError: 0
      },
      c_Url: {
        c_Field: {
          s_ADDRESS: 0,
          s_LINK_TEXT: 0,
          s_TYPE: 0
        },
        m_getField: 0
      },
      s_getEnvironment: 0,
      s_hasPermission: 0,
      s_newActivity: 0,
      s_newDataRequest: 0,
      s_newIdSpec: 0,
      s_newMediaItem: 0,
      s_newMessage: 0,
      s_newNavigationParameters: 0,
      s_requestCreateActivity: 0,
      s_requestPermission: 0,
      s_requestSendMessage: 0,
      s_requestShareApp: 0
    }
  };
  function whitelist(schema, obj) {
    if (!obj) { return; }  // Occurs for optional features
    for (var k in schema) {
      if (schema.hasOwnProperty(k)) {
        var m = k.match(/^([mcs])_(\w+)$/);
        var type = m[1], name = m[2];
        switch (type) {
          case 'c':
            ___.allowRead(obj, name);
            whitelist(schema[k], obj[name]);
            break;
          case 'm':
            ___.allowCall(obj.prototype, name);
            break;
          case 'f':
            ___.allowRead(obj.prototype, name);
            break;
          case 's':
            if ('function' === typeof obj[name]) {
              ___.allowCall(obj, name);
            } else {
              ___.allowRead(obj, name);
            }
            break;
        }
      }
    }
  }
  whitelist(opensocialSchema, window);
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Representation of a environment.
 */


/**
 * @class
 * Represents the current environment for a gadget.
 *
 * <p>
 * <b>See also:</b>
 * <a href="opensocial.html#getEnvironment">opensocial.getEnvironment()</a>,
 *
 * @name opensocial.Environment
 */


/**
 * Base interface for all environment objects.
 *
 * @param {String} domain The current domain
 * @param {Map.&lt;String, Map.&lt;String, Boolean&gt;&gt;} supportedFields
 *    The fields supported by this container
 *
 * @private
 * @constructor
 */
opensocial.Environment = function(domain, supportedFields) {
  this.domain = domain;
  this.supportedFields = supportedFields;
};


/**
 * Returns the current domain &mdash;
 * for example, "orkut.com" or "myspace.com".
 *
 * @return {String} The domain
 */
opensocial.Environment.prototype.getDomain = function() {
  return this.domain;
};


/**
 * @static
 * @class
 *
 * The types of objects in this container.
 *
 * <p>
 * <b>See also:</b>
 * <a href="opensocial.Environment.html#supportsField">
 * <code>Environment.supportsField()</code></a>
 *
 * @name opensocial.Environment.ObjectType
 */
opensocial.Environment.ObjectType = {
  /**
   * @member opensocial.Environment.ObjectType
   */
  PERSON : 'person',
  /**
   * @member opensocial.Environment.ObjectType
   */
  ADDRESS : 'address',
  /**
   * @member opensocial.Environment.ObjectType
   */
  BODY_TYPE : 'bodyType',
  /**
   * @member opensocial.Environment.ObjectType
   */
  EMAIL : 'email',
  /**
   * @member opensocial.Environment.ObjectType
   */
  NAME : 'name',
  /**
   * @member opensocial.Environment.ObjectType
   */
  ORGANIZATION : 'organization',
  /**
   * @member opensocial.Environment.ObjectType
   */
  PHONE : 'phone',
  /**
   * @member opensocial.Environment.ObjectType
   */
  URL : 'url',
  /**
   * @member opensocial.Environment.ObjectType
   */
  ACTIVITY : 'activity',
  /**
   * @member opensocial.Environment.ObjectType
   */
  MEDIA_ITEM : 'mediaItem',
  /**
   * @member opensocial.Environment.ObjectType
   */
  MESSAGE : 'message',
  /**
   * @member opensocial.Environment.ObjectType
   */
  MESSAGE_TYPE : 'messageType',
  /**
   * @member opensocial.Environment.ObjectType
   */
  SORT_ORDER : 'sortOrder',
  /**
   * @member opensocial.Environment.ObjectType
   */
  FILTER_TYPE : 'filterType'
};


/**
 * Returns true if the specified field is supported in this container on the
 * given object type.
 *
 * @param {opensocial.Environment.ObjectType} objectType
 *    The <a href="opensocial.Environment.ObjectType.html">object type</a>
 *    to check for the field
 * @param {String} fieldName The name of the field to check for
 * @return {Boolean} True if the field is supported on the specified object type
 */
opensocial.Environment.prototype.supportsField = function(objectType,
    fieldName) {
  var supportedObjectFields = this.supportedFields[objectType] || [];
  return !!supportedObjectFields[fieldName];
};/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Representation of an name.
 */


/**
 * @class
 * Base interface for all name objects.
 *
 * @name opensocial.Name
 */


/**
 * Base interface for all name objects.
 *
 * @private
 * @constructor
 */
opensocial.Name = function(opt_params) {
  this.fields_ = opt_params || {};
};


/**
 * @static
 * @class
 * All of the fields that a name has. These are the supported keys for the
 * <a href="opensocial.Name.html#getField">Name.getField()</a> method.
 *
 * @name opensocial.Name.Field
 */
opensocial.Name.Field = {
  /**
   * The family name. Specified as a String.
   *
   * @member opensocial.Name.Field
   */
  FAMILY_NAME : 'familyName',

  /**
   * The given name. Specified as a String.
   *
   * @member opensocial.Name.Field
   */
  GIVEN_NAME : 'givenName',

  /**
   * The additional name. Specified as a String.
   *
   * @member opensocial.Name.Field
   */
  ADDITIONAL_NAME : 'additionalName',

  /**
   * The honorific prefix. Specified as a String.
   *
   * @member opensocial.Name.Field
   */
  HONORIFIC_PREFIX : 'honorificPrefix',

  /**
   * The honorific suffix. Specified as a String.
   *
   * @member opensocial.Name.Field
   */
  HONORIFIC_SUFFIX : 'honorificSuffix',

  /**
   * The unstructured name. Specified as a String.
   *
   * @member opensocial.Name.Field
   */
  UNSTRUCTURED : 'unstructured'
};


/**
 * Gets data for this name that is associated with the specified key.
 *
 * @param {String} key The key to get data for;
 *    keys are defined in <a href="opensocial.Name.Field.html"><code>
 *    Name.Field</code></a>
 * @param {Map.&lt;opensocial.DataRequest.DataRequestFields, Object&gt;}
 *  opt_params Additional
 *    <a href="opensocial.DataRequest.DataRequestFields.html">params</a>
 *    to pass to the request.
 * @return {String} The data
 */
opensocial.Name.prototype.getField = function(key, opt_params) {
  return opensocial.Container.getField(this.fields_, key, opt_params);
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Representation of a person.
 */


/**
 * @class
 * Base interface for all person objects.
 *
 * @name opensocial.Person
 */


/**
 * Base interface for all person objects.
 *
 * @private
 * @constructor
 */
opensocial.Person = function(opt_params, opt_isOwner, opt_isViewer) {
  this.fields_ = opt_params || {};
  this.isOwner_ = opt_isOwner;
  this.isViewer_ = opt_isViewer;
};


/**
 * @static
 * @class
 * All of the fields that a person has. These are the supported keys for the
 * <a href="opensocial.Person.html#getField">Person.getField()</a> method.
 *
 * @name opensocial.Person.Field
 */
opensocial.Person.Field = {
  /**
   * A string ID that can be permanently associated with this person.
   * @member opensocial.Person.Field
   */
  ID : 'id',

  /**
   * A opensocial.Name object containing the person's name.
   * @member opensocial.Person.Field
   */
  NAME : 'name',

  /**
   * A String representing the person's nickname.
   * @member opensocial.Person.Field
   */
  NICKNAME : 'nickname',

  /**
   * Person's photo thumbnail URL, specified as a string.
   * This URL must be fully qualified. Relative URLs will not work in gadgets.
   * @member opensocial.Person.Field
   */
  THUMBNAIL_URL : 'thumbnailUrl',

  /**
   * Person's profile URL, specified as a string.
   * This URL must be fully qualified. Relative URLs will not work in gadgets.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  PROFILE_URL : 'profileUrl',

  /**
   * Person's current location, specified as an
   * <a href="opensocial.Address.html">Address</a>.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  CURRENT_LOCATION : 'currentLocation',

  /**
   * Addresses associated with the person, specified as an Array of
   * <a href="opensocial.Address.html">Address</a>es.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  ADDRESSES : 'addresses',

  /**
   * Emails associated with the person, specified as an Array of
   * <a href="opensocial.Email.html">Email</a>s.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  EMAILS : 'emails',

  /**
   * Phone numbers associated with the person, specified as an Array of
   * <a href="opensocial.Phone.html">Phone</a>s.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  PHONE_NUMBERS : 'phoneNumbers',

  /**
   * A general statement about the person, specified as a string.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  ABOUT_ME : 'aboutMe',

  /**
   * Person's status, headline or shoutout, specified as a string.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  STATUS : 'status',

  /**
   * Person's profile song, specified as an opensocial.Url.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  PROFILE_SONG : 'profileSong',

  /**
   * Person's profile video, specified as an opensocial.Url.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  PROFILE_VIDEO : 'profileVideo',

  /**
   * Person's gender, specified as an opensocial.Enum with the enum's
   * key referencing opensocial.Enum.Gender.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  GENDER : 'gender',

  /**
   * Person's sexual orientation, specified as a string.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  SEXUAL_ORIENTATION : 'sexualOrientation',

  /**
   * Person's relationship status, specified as a string.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  RELATIONSHIP_STATUS : 'relationshipStatus',

  /**
   * Person's age, specified as a number.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  AGE : 'age',

  /**
   * Person's date of birth, specified as a Date object.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  DATE_OF_BIRTH : 'dateOfBirth',

  /**
   * Person's body characteristics, specified as an opensocial.BodyType.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  BODY_TYPE : 'bodyType',

  /**
   * Person's ethnicity, specified as a string.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  ETHNICITY : 'ethnicity',

  /**
   * Person's smoking status, specified as an opensocial.Enum with the enum's
   * key referencing opensocial.Enum.Smoker.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  SMOKER : 'smoker',

  /**
   * Person's drinking status, specified as an opensocial.Enum with the enum's
   * key referencing opensocial.Enum.Drinker.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  DRINKER : 'drinker',

  /**
   * Description of the person's children, specified as a string.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  CHILDREN : 'children',

  /**
   * Description of the person's pets, specified as a string.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  PETS : 'pets',

  /**
   * Description of the person's living arrangement, specified as a string.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  LIVING_ARRANGEMENT : 'livingArrangement',

  /**
   * Person's time zone, specified as the difference in minutes between
   * Greenwich Mean Time (GMT) and the user's local time. See
   * Date.getTimezoneOffset() in javascript for more details on this format.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  TIME_ZONE : 'timeZone',

  /**
   * List of the languages that the person speaks as ISO 639-1 codes,
   * specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   * @member opensocial.Person.Field
   */
  LANGUAGES_SPOKEN : 'languagesSpoken',

  /**
   * Jobs the person has held, specified as an Array of
   * <a href="opensocial.Organization.html">Organization</a>s.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  JOBS : 'jobs',

  /**
   * Person's favorite jobs, or job interests and skills, specified as a string.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  JOB_INTERESTS : 'jobInterests',

  /**
   * Schools the person has attended, specified as an Array of
   * <a href="opensocial.Organization.html">Organization</a>s.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  SCHOOLS : 'schools',

  /**
   * Person's interests, hobbies or passions, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  INTERESTS : 'interests',

  /**
   * URLs related to the person, their webpages, or feeds. Specified as an
   * Array of opensocial.Url.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  URLS : 'urls',

  /**
   * Person's favorite music, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  MUSIC : 'music',

  /**
   * Person's favorite movies, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  MOVIES : 'movies',

  /**
   * Person's favorite TV shows, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  TV_SHOWS : 'tvShows',

  /**
   * Person's favorite books, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  BOOKS : 'books',

  /**
   * Person's favorite activities, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  ACTIVITIES : 'activities',

  /**
   * Person's favorite sports, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  SPORTS : 'sports',

  /**
   * Person's favorite heroes, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  HEROES : 'heroes',

  /**
   * Person's favorite quotes, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  QUOTES : 'quotes',

  /**
   * Person's favorite cars, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  CARS : 'cars',

  /**
   * Person's favorite food, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  FOOD : 'food',

  /**
   * Person's turn ons, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  TURN_ONS : 'turnOns',

  /**
   * Person's turn offs, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  TURN_OFFS : 'turnOffs',

  /**
   * Arbitrary tags about the person, specified as an Array of strings.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  TAGS : 'tags',

  /**
   * Person's comments about romance, specified as a string.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  ROMANCE : 'romance',

  /**
   * What the person is scared of, specified as a string.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  SCARED_OF : 'scaredOf',

  /**
   * Describes when the person is happiest, specified as a string.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  HAPPIEST_WHEN : 'happiestWhen',

  /**
   * Person's thoughts on fashion, specified as a string.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  FASHION : 'fashion',

  /**
   * Person's thoughts on humor, specified as a string.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  HUMOR : 'humor',

  /**
   * Person's statement about who or what they are looking for, or what they are
   * interested in meeting people for. Specified as an Array of opensocial.Enum
   * with the enum's key referencing opensocial.Enum.LookingFor.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  LOOKING_FOR : 'lookingFor',

  /**
   * Person's relgion or religious views, specified as a string.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  RELIGION : 'religion',

  /**
   * Person's political views, specified as a string.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  POLITICAL_VIEWS : 'politicalViews',

  /**
   * A boolean indicating whether the person has used the current app.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  HAS_APP : 'hasApp',

  /**
   * Person's current network status. Specified as an Enum with the enum's
   * key referencing opensocial.Enum.Presence.
   * Container support for this field is OPTIONAL.
   *
   * @member opensocial.Person.Field
   */
  NETWORK_PRESENCE : 'networkPresence'
};


/**
 * Gets an ID that can be permanently associated with this person.
 *
 * @return {String} The ID
 */
opensocial.Person.prototype.getId = function() {
  return this.getField(opensocial.Person.Field.ID);
};


var ORDERED_NAME_FIELDS_ = [
    opensocial.Name.Field.HONORIFIC_PREFIX,
    opensocial.Name.Field.GIVEN_NAME,
    opensocial.Name.Field.FAMILY_NAME,
    opensocial.Name.Field.HONORIFIC_SUFFIX,
    opensocial.Name.Field.ADDITIONAL_NAME];

/**
 * Gets a text display name for this person; guaranteed to return
 * a useful string.
 *
 * @return {String} The display name
 */
opensocial.Person.prototype.getDisplayName = function() {
  var name = this.getField(opensocial.Person.Field.NAME);
  if (name) {
    // Try unstructured field first
    var unstructured = name.getField(opensocial.Name.Field.UNSTRUCTURED);
    if (unstructured) {
      return unstructured;
    }

    // Next try to construct the name from the individual components
    var fullName = '';
    for (var i = 0; i < ORDERED_NAME_FIELDS_.length; i++) {
      var nameValue = name.getField(ORDERED_NAME_FIELDS_[i]);
      if (nameValue) {
        fullName += nameValue + ' ';
      }
    }
    return fullName.replace(/^\s+|\s+$/g, '') ;
  }

  // Finally, try the nickname field
  return this.getField(opensocial.Person.Field.NICKNAME);
};


/**
 * Gets data for this person that is associated with the specified key.
 *
 * @param {String} key The key to get data for;
 *    keys are defined in <a href="opensocial.Person.Field.html"><code>
 *    Person.Field</code></a>
 * @param {Map.&lt;opensocial.DataRequest.DataRequestFields, Object&gt;}
 *  opt_params Additional
 *    <a href="opensocial.DataRequest.DataRequestFields.html">params</a>
 *    to pass to the request.
 * @return {String} The data
 */
opensocial.Person.prototype.getField = function(key, opt_params) {
  return opensocial.Container.getField(this.fields_, key, opt_params);
};


/**
 * Returns true if this person object represents the currently logged in user.
 *
 * @return {Boolean} True if this is the currently logged in user;
 *   otherwise, false
 */
opensocial.Person.prototype.isViewer = function() {
  return !!this.isViewer_;
};


/**
 * Returns true if this person object represents the owner of the current page.
 *
 * @return {Boolean} True if this is the owner of the page;
 *   otherwise, false
 */
opensocial.Person.prototype.isOwner = function() {
  return !!this.isOwner_;
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Representation of an address.
 */


/**
 * @class
 * Base interface for all address objects.
 *
 * @name opensocial.Address
 */


/**
 * Base interface for all address objects.
 *
 * @private
 * @constructor
 */
opensocial.Address = function(opt_params) {
  this.fields_ = opt_params || {};
};


/**
 * @static
 * @class
 * All of the fields that an address has. These are the supported keys for the
 * <a href="opensocial.Address.html#getField">Address.getField()</a> method.
 *
 * @name opensocial.Address.Field
 */
opensocial.Address.Field = {
  /**
   * The address type or label. Examples: work, my favorite store, my house, etc
   * Specified as a String.
   *
   * @member opensocial.Address.Field
   */
  TYPE : 'type',

  /**
   * If the container does not have structured addresses in its data store,
   * this field will return the unstructured address that the user entered. Use
   * opensocial.getEnvironment().supportsField to see which fields are
   * supported. Specified as a String.
   *
   * @member opensocial.Address.Field
   */
  UNSTRUCTURED_ADDRESS : 'unstructuredAddress',

  /**
   * The po box of the address if there is one. Specified as a String.
   *
   * @member opensocial.Address.Field
   */
  PO_BOX : 'poBox',

  /**
   * The street address. Specified as a String.
   *
   * @member opensocial.Address.Field
   */
  STREET_ADDRESS : 'streetAddress',

  /**
   * The extended street address. Specified as a String.
   *
   * @member opensocial.Address.Field
   */
  EXTENDED_ADDRESS : 'extendedAddress',

  /**
   * The region. Specified as a String.
   *
   * @member opensocial.Address.Field
   */
  REGION : 'region',

  /**
   * The locality. Specified as a String.
   *
   * @member opensocial.Address.Field
   */
  LOCALITY : 'locality',

  /**
   * The postal code. Specified as a String.
   *
   * @member opensocial.Address.Field
   */
  POSTAL_CODE : 'postalCode',

  /**
   * The country. Specified as a String.
   *
   * @member opensocial.Address.Field
   */
  COUNTRY : 'country',

  /**
   * The latitude. Specified as a Number.
   *
   * @member opensocial.Address.Field
   */
  LATITUDE : 'latitude',

  /**
   * The longitude. Specified as a Number.
   *
   * @member opensocial.Address.Field
   */
  LONGITUDE : 'longitude'
};


/**
 * Gets data for this body type that is associated with the specified key.
 *
 * @param {String} key The key to get data for;
 *    keys are defined in <a href="opensocial.Address.Field.html"><code>
 *    Address.Field</code></a>
 * @param {Map.&lt;opensocial.DataRequest.DataRequestFields, Object&gt;}
 *  opt_params Additional
 *    <a href="opensocial.DataRequest.DataRequestFields.html">params</a>
 *    to pass to the request
 * @return {String} The data
 */
opensocial.Address.prototype.getField = function(key, opt_params) {
  return opensocial.Container.getField(this.fields_, key, opt_params);
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Representation of an group of people ids.
 */


/**
 * @class
 * Base interface for all id spec objects.
 *
 * @name opensocial.IdSpec
 */


/**
 * Base interface for all id spec objects. Use this class when specifying which
 * people you want to fetch.
 *
 * For example, opensocial.newIdSpec({userId : 'VIEWER', groupId : 'FRIENDS'})
 *                means you are looking for all of the viewer's friends.
 * For example, opensocial.newIdSpec({userId : 'VIEWER',
 *                                    groupId : 'FRIENDS', networkDistance : 2})
 *                means you are looking for all of the viewer's friends of friends.
 * For example, opensocial.newIdSpec({userId : 'OWNER'})
 *                means you are looking for the owner.
 *
 * Private, see opensocial.newIdSpec() for usage.
 *
 * @private
 * @constructor
 */
opensocial.IdSpec = function(opt_params) {
  this.fields_ = opt_params || {};
};


/**
 * @static
 * @class
 * All of the fields that id specs can have.
 *
 * <p>
 * <b>See also:</b>
 * <a
 * href="opensocial.IdSpec.html#getField">opensocial.IdSpec.getField()</a>
 * </p>
 *
 * @name opensocial.IdSpec.Field
 */
opensocial.IdSpec.Field = {
  /**
   * A string or an array of strings representing the user id. Can be
   * one of the opensocial.IdSpec.PersonId values.
   * @member opensocial.IdSpec.Field
   */
  USER_ID : 'userId',

  /**
   * A string representing the group id or one of the
   * opensocial.IdSpec.GroupId values. Defaults to SELF.
   * @member opensocial.IdSpec.Field
   */
  GROUP_ID : 'groupId',

  /**
   * An optional numeric parameter, used to specify how many "hops"
   * are allowed between two people still considered part of the
   * same group.
   * Defaults to 1 (they must be the same person or
   * directly be connected by the group).
   *
   * Not all containers will support networkDistances greater than 1.
   *
   * @member opensocial.IdSpec.Field
   */
  NETWORK_DISTANCE : 'networkDistance'
};


/**
 * @static
 * @class
 * Constant person IDs available when fetching person information.
 *
 * @name opensocial.IdSpec.PersonId
 */
opensocial.IdSpec.PersonId = {
 /**
  * @member opensocial.IdSpec.PersonId
  */
  OWNER : 'OWNER',
 /**
  * @member opensocial.IdSpec.PersonId
  */
  VIEWER : 'VIEWER'
};


 /**
 * @static
 * @class
 * Constant group IDs available when fetching collections of people.
 *
 * @name opensocial.IdSpec.GroupId
 */
opensocial.IdSpec.GroupId = {
 /**
  * @member opensocial.IdSpec.GroupId
  */
  SELF : 'SELF',
 /**
  * @member opensocial.IdSpec.GroupId
  */
  FRIENDS : 'FRIENDS',
 /**
  * @member opensocial.IdSpec.GroupId
  */
  ALL : 'ALL'
};


/**
 * Gets the id spec's data that's associated with the specified key.
 *
 * @param {String} key The key to get data for;
 *   see the <a href="opensocial.IdSpec.Field.html">Field</a> class
 * for possible values
 * @param {Map.&lt;opensocial.DataRequest.DataRequestFields, Object&gt;}
 *  opt_params Additional
 *    <a href="opensocial.DataRequest.DataRequestFields.html">params</a>
 *    to pass to the request.
 * @return {String} The data
 * @member opensocial.IdSpec
 */
opensocial.IdSpec.prototype.getField = function(key, opt_params) {
  return opensocial.Container.getField(this.fields_, key, opt_params);
};


/**
 * Sets data for this id spec associated with the given key.
 *
 * @param {String} key The key to set data for
 * @param {String} data The data to set
 */
opensocial.IdSpec.prototype.setField = function(key, data) {
  return this.fields_[key] = data;
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview DataResponse containing information about
 * friends, contacts, profile, app data, and activities.
 *
 * Whenever a dataRequest is sent to the server it will return a dataResponse
 * object. Values from the server will be mapped to the requested keys specified
 * in the dataRequest.
 */


/**
 * @class
 * This object contains the requested server data mapped to the requested keys.
 *
 * <p>
 * <b>See also:</b>
 * <a href="opensocial.DataRequest.html">DataRequest</a>
 * </p>
 *
 * @name opensocial.DataResponse
 */

/**
 * Construct the data response.
 * This object contains the requested server data mapped to the requested keys.
 *
 * @param {Map.<String, ResponseItem>} responseItems Key/value map of data
 *    response information
 * @param {Boolean} opt_globalError Optional field indicating whether there were
 *    any errors generating this data response
 *
 * @private
 * @constructor
 */
opensocial.DataResponse = function(responseItems, opt_globalError,
    opt_errorMessage) {
  this.responseItems_ = responseItems;
  this.globalError_ = opt_globalError;
  this.errorMessage_ = opt_errorMessage;
};


/**
 * Returns true if there was an error in fetching this data from the server.
 *
 * @return {Boolean} True if there was an error; otherwise, false
 * @member opensocial.DataResponse
 */
opensocial.DataResponse.prototype.hadError = function() {
  return !!this.globalError_;
};


/**
 * If the entire request had a batch level error, returns the error message.
 *
 * @return {String} A human-readable description of the error that occurred.
 */
opensocial.DataResponse.prototype.getErrorMessage = function() {
  return this.errorMessage_;
};


/**
 * Gets the ResponseItem for the requested field.
 *
 * @return {opensocial.ResponseItem} The requested
 *    <a href="opensocial.ResponseItem.html">response</a> calculated by the
 *    server
 * @member opensocial.DataResponse
 */
opensocial.DataResponse.prototype.get = function(key) {
  return this.responseItems_[key];
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview ResponseItem containing information about a specific response
 * from the server.
 */


/**
 * @class
 * Represents a response that was generated
 * by processing a data request item on the server.
 *
 * @name opensocial.ResponseItem
 */


/**
 * Represents a response that was generated by processing a data request item
 * on the server.
 *
 * @private
 * @constructor
 */
opensocial.ResponseItem = function(originalDataRequest, data,
    opt_errorCode, opt_errorMessage) {
  this.originalDataRequest_ = originalDataRequest;
  this.data_ = data;
  this.errorCode_ = opt_errorCode;
  this.errorMessage_ = opt_errorMessage;
};


/**
 * Returns true if there was an error in fetching this data from the server.
 *
 * @return {Boolean} True if there was an error; otherwise, false
 */
opensocial.ResponseItem.prototype.hadError = function() {
  return !!this.errorCode_;
};


/**
 * @static
 * @class
 *
 * Error codes that a response item can return.
 *
 * @name opensocial.ResponseItem.Error
 */
opensocial.ResponseItem.Error = {
  /**
   * This container does not support the request that was made.
   *
   * @member opensocial.ResponseItem.Error
   */
  NOT_IMPLEMENTED : 'notImplemented',

  /**
   * The gadget does not have access to the requested data.
   * To get access, use
   * <a href="opensocial.html#requestPermission">
   * opensocial.requestPermission()</a>.
   *
   * @member opensocial.ResponseItem.Error
   */
  UNAUTHORIZED : 'unauthorized',

  /**
   * The gadget can never have access to the requested data.
   *
   * @member opensocial.ResponseItem.Error
   */
  FORBIDDEN : 'forbidden',

   /**
   * The request was invalid. Example: 'max' was -1.
   *
   * @member opensocial.ResponseItem.Error
   */
  BAD_REQUEST : 'badRequest',

  /**
   * The request encountered an unexpected condition that
   * prevented it from fulfilling the request.
   *
   * @member opensocial.ResponseItem.Error
   */
  INTERNAL_ERROR : 'internalError',

  /**
   * The gadget exceeded a quota on the request. Example quotas include a
   * max number of calls per day, calls per user per day, calls within a
   * certain time period and so forth.
   *
   * @member opensocial.ResponseItem.Error
   */
  LIMIT_EXCEEDED : 'limitExceeded'
};


/**
 * If the request had an error, returns the error code.
 * The error code can be container-specific
 * or one of the values defined by
 * <a href="opensocial.ResponseItem.Error.html"><code>Error</code></a>.
 *
 * @return {String} The error code, or null if no error occurred
 */
opensocial.ResponseItem.prototype.getErrorCode = function() {
  return this.errorCode_;
};


/**
 * If the request had an error, returns the error message.
 *
 * @return {String} A human-readable description of the error that occurred;
 *    can be null, even if an error occurred
 */
opensocial.ResponseItem.prototype.getErrorMessage = function() {
  return this.errorMessage_;
};


/**
 * Returns the original data request.
 *
 * @return {opensocial.DataRequest} The data request used to fetch this data
 *    response
 */
opensocial.ResponseItem.prototype.getOriginalDataRequest = function() {
  return this.originalDataRequest_;
};


/**
 * Gets the response data.
 *
 * @return {Object} The requested value calculated by the server; the type of
 *    this value is defined by the type of request that was made
 */
opensocial.ResponseItem.prototype.getData = function() {
  return this.data_;
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @class
 * Representation of an activity.
 *
 * <p>Activities are rendered with a title and an optional activity body.</p>
 *
 * <p>You may set the title and body directly as strings when calling
 * opensocial.createActivity.</p>
 *
 * <p>However, it is usually beneficial to create activities using
 * Activity Templates for the title and body. Activity Templates support:</p>
 * <ul>
 *   <li>Internationalization</li>
 *   <li>Replacement variables in the message</li>
 *   <li>Activity Summaries, which are message variations used to summarize
 *     repeated activities that share something in common.</li>
 * </ul>
 *
 * <p>Activity Templates are defined as messages in the gadget specification.
 * To define messages, you create and reference message bundle XML files for
 * each locale you support.</p>
 *
 * <p>Example module spec in gadget XML:
 * <pre>
 * &lt;ModulePrefs title="ListenToThis"&gt;
 *   &lt;Locale messages="http://www.listentostuff.com/messages.xml"/&gt;
 *   &lt;Locale lang="de" messages="http://www.listentostuff.com/messages-DE.xml"/&gt;
 * &lt;/ModulePrefs&gt;
 * </pre>
 * </p>
 *
 * <p>Example message bundle:
 * <pre>
 * &lt;messagebundle&gt;
 *  &lt;msg name="LISTEN_TO_THIS_SONG"&gt;
 *     ${Subject.DisplayName} told ${Owner.DisplayName} to
 *     listen to a song!
 *  &lt;/msg&gt;
 * &lt;/messagebundle&gt;
 * </pre>
 * </p>
 *
 * <p>You can set custom key/value string pairs when posting an activity.
 * These values will be used for variable substitution in the templates.</p>
 * <p>Example JS call:
 * <pre>
 *   var owner = ...;
 *   var viewer = ...;
 *   var activity = opensocial.newActivity('LISTEN_TO_THIS_SONG',
 *    {Song: 'Do That There - (Young Einstein hoo-hoo mix)',
 *     Artist: 'Lyrics Born', Subject: viewer, Owner: owner})
 * </pre>
 * </p>
 *
 * <p> Associated message:
 * <pre>
 * &lt;msg name="LISTEN_TO_THIS_SONG"&gt;
 *     ${Subject.DisplayName} told ${Owner.DisplayName} to listen
 *     to ${Song} by ${Artist}
 * &lt;/msg&gt;
 * </pre>
 * </p>
 *
 * <p>People can also be set as values in key/value pairs when posting
 * an activity. You can then reference the following fields on a person:</p>
 * <ul>
 *  <li>${Person.DisplayName} The person's name</li>
 *  <li>${Person.Id} The user ID of the person</li>
 *  <li>${Person.ProfileUrl} The profile URL of the person</li>
 *  <li>${Person} This will show the display name, but containers may optionally
 *     provide special formatting, such as showing the name as a link</li>
 * </ul>
 *
 * <p>Users will have many activities in their activity streams, and containers
 * will not show every activity that is visible to a user. To help display
 * large numbers of activities, containers will summarize a list of activities
 * from a given source to a single entry.</p>
 *
 * <p>You can provide Activity Summaries to customize the text shown when
 * multiple activities are summarized. If no customization is provided, a
 * container may ignore your activities altogether or provide default text
 * such as "Bob changed his status message + 20 other events like this."</p>
 * <ul>
 *  <li>Activity Summaries will always summarize around a specific key in a
 *   key/value pair. This is so that the summary can say something concrete
 *   (this is clearer in the example below).</li>
 *  <li>Other variables will have synthetic "Count" variables created with
 *   the total number of items summarized.</li>
 *  <li>Message ID of the summary is the message ID of the main template + ":" +
 *   the data key</li>
 * </ul>
 *
 * <p>Example summaries:
 * <pre>
 * &lt;messagebundle&gt;
 *   &lt;msg name="LISTEN_TO_THIS_SONG:Artist"&gt;
 *     ${Subject.Count} of your friends have suggested listening to songs
 *     by ${Artist}!
 *   &lt;/msg&gt;
 *   &lt;msg name="LISTEN_TO_THIS_SONG:Song"&gt;
 *     ${Subject.Count} of your friends have suggested listening to ${Song}
 *   !&lt;/msg&gt;
 *   &lt;msg name="LISTEN_TO_THIS_SONG:Subject"&gt;
 *    ${Subject.DisplayName} has recommended ${Song.Count} songs to you.
 *   &lt;/msg&gt;
 * &lt;/messagebundle&gt;
 * </pre></p>
 *
 * <p>Activity Templates may only have the following HTML tags: &lt;b&gt;,
 * &lt;i&gt;, &lt;a&gt;, &lt;span&gt;. The container also has the option
 * to strip out these tags when rendering the activity.</p>
 *
 * <p>
 * <b>See also:</b>
 * <a href="opensocial.html#newActivity">opensocial.newActivity()</a>,
 * <a href="opensocial.html#requestCreateActivity">
 * opensocial.requestCreateActivity()</a>
 *
 * @name opensocial.Activity
 */


/**
 * Base interface for all activity objects.
 *
 * Private, see opensocial.createActivity() for usage.
 *
 * @param {Map.&lt;opensocial.Activity.Field, Object&gt;} params
 *    Parameters defining the activity.
 * @private
 * @constructor
 */
opensocial.Activity = function(params) {
    this.fields_ = params || {};
};


/**
 * @static
 * @class
 * All of the fields that activities can have.
 *
 * <p>It is only required to set one of TITLE_ID or TITLE. In addition, if you
 * are using any variables in your title or title template,
 * you must set TEMPLATE_PARAMS.</p>
 *
 * <p>Other possible fields to set are: URL, MEDIA_ITEMS, BODY_ID, BODY,
 * EXTERNAL_ID, PRIORITY, STREAM_TITLE, STREAM_URL, STREAM_SOURCE_URL,
 * and STREAM_FAVICON_URL.</p>
 *
 * <p>Containers are only required to use TITLE_ID or TITLE, they may ignore
 * additional parameters.</p>
 *
 * <p>
 * <b>See also:</b>
 * <a
 * href="opensocial.Activity.html#getField">opensocial.Activity.getField()</a>
 * </p>
 *
 * @name opensocial.Activity.Field
 */
opensocial.Activity.Field = {
  /**
   * <p>A string specifying the title template message ID in the gadget
   *   spec.</p>
   *
   * <p>The title is the primary text of an activity.</p>
   *
   * <p>Titles may only have the following HTML tags: &lt;b&gt; &lt;i&gt;,
   * &lt;a&gt;, &lt;span&gt;.
   * The container may ignore this formatting when rendering the activity.</p>
   *
   * @member opensocial.Activity.Field
   */
  TITLE_ID : 'titleId',

  /**
   * <p>A string specifying the primary text of an activity.</p>
   *
   * <p>Titles may only have the following HTML tags: &lt;b&gt; &lt;i&gt;,
   * &lt;a&gt;, &lt;span&gt;.
   * The container may ignore this formatting when rendering the activity.</p>
   *
   * @member opensocial.Activity.Field
   */
  TITLE : 'title',

  /**
   * <p>A map of custom keys to values associated with this activity.
   * These will be used for evaluation in templates.</p>
   *
   * <p>The data has type <code>Map&lt;String, Object&gt;</code>. The
   * object may be either a String or an opensocial.Person.</p>
   *
   * <p>When passing in a person with key PersonKey, can use the following
   * replacement variables in the template:</p>
   * <ul>
   *  <li>PersonKey.DisplayName - Display name for the person</li>
   *  <li>PersonKey.ProfileUrl. URL of the person's profile</li>
   *  <li>PersonKey.Id -  The ID of the person</li>
   *  <li>PersonKey - Container may replace with DisplayName, but may also
   *     optionally link to the user.</li>
   * </ul>
   *
   * @member opensocial.Activity.Field
   */
  TEMPLATE_PARAMS : 'templateParams',

  /**
   * A string specifying the
   * URL that represents this activity.
   * @member opensocial.Activity.Field
   */
  URL : 'url',

  /**
   * Any photos, videos, or images that should be associated
   * with the activity. Higher priority ones are higher in the list.
   * The data has type <code>Array&lt;
   * <a href="opensocial.MediaItem.html">MediaItem</a>&gt;</code>.
   * @member opensocial.Activity.Field
   */
  MEDIA_ITEMS : 'mediaItems',

  /**
   * <p>A string specifying the body template message ID in the gadget spec.</p>
   *
   * <p>The body is an optional expanded version of an activity.</p>
   *
   * <p>Bodies may only have the following HTML tags: &lt;b&gt; &lt;i&gt;,
   * &lt;a&gt;, &lt;span&gt;.
   * The container may ignore this formatting when rendering the activity.</p>
   *
   * @member opensocial.Activity.Field
   */
  BODY_ID : 'bodyId',

  /**
   * <p>A string specifying an optional expanded version of an activity.</p>
   *
   * <p>Bodies may only have the following HTML tags: &lt;b&gt; &lt;i&gt;,
   * &lt;a&gt;, &lt;span&gt;.
   * The container may ignore this formatting when rendering the activity.</p>
   *
   * @member opensocial.Activity.Field
   */
  BODY : 'body',

  /**
   * An optional string ID generated by the posting application.
   * @member opensocial.Activity.Field
   */
  EXTERNAL_ID : 'externalId',

  /**
   * A string specifing the title of the stream.
   * @member opensocial.Activity.Field
   */
  STREAM_TITLE : 'streamTitle',

  /**
   * A string specifying the stream's URL.
   * @member opensocial.Activity.Field
   */
  STREAM_URL : 'streamUrl',

  /**
   * A string specifying the stream's source URL.
   * @member opensocial.Activity.Field
   */
  STREAM_SOURCE_URL : 'streamSourceUrl',

  /**
   * A string specifying the URL for the stream's favicon.
   * @member opensocial.Activity.Field
   */
  STREAM_FAVICON_URL : 'streamFaviconUrl',

  /**
   * A number between 0 and 1 representing the relative priority of
   * this activity in relation to other activities from the same source
   * @member opensocial.Activity.Field
   */
  PRIORITY : 'priority',

  /**
   * A string ID that is permanently associated with this activity.
   * This value can not be set.
   * @member opensocial.Activity.Field
   */
  ID : 'id',

  /**
   * The string ID of the user who this activity is for.
   * This value can not be set.
   * @member opensocial.Activity.Field
   */
  USER_ID : 'userId',

  /**
   * A string specifying the application that this activity is associated with.
   * This value can not be set.
   * @member opensocial.Activity.Field
   */
  APP_ID : 'appId',

  /**
   * A string specifying the time at which this activity took place
   * in milliseconds since the epoch.
   * This value can not be set.
   * @member opensocial.Activity.Field
   */
  POSTED_TIME : 'postedTime'
};


/**
 * Gets an ID that can be permanently associated with this activity.
 *
 * @return {String} The ID
 * @member opensocial.Activity
 */
opensocial.Activity.prototype.getId = function() {
  return this.getField(opensocial.Activity.Field.ID);
};


/**
 * Gets the activity data that's associated with the specified key.
 *
 * @param {String} key The key to get data for;
 *   see the <a href="opensocial.Activity.Field.html">Field</a> class
 * for possible values
 * @param {Map.&lt;opensocial.DataRequest.DataRequestFields, Object&gt;}
 *  opt_params Additional
 *    <a href="opensocial.DataRequest.DataRequestFields.html">params</a>
 *    to pass to the request.
 * @return {String} The data
 * @member opensocial.Activity
 */
opensocial.Activity.prototype.getField = function(key, opt_params) {
  return opensocial.Container.getField(this.fields_, key, opt_params);
};


/**
 * Sets data for this activity associated with the given key.
 *
 * @param {String} key The key to set data for
 * @param {String} data The data to set
 */
opensocial.Activity.prototype.setField = function(key, data) {
  return this.fields_[key] = data;
};/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview Representation of a message.
 */


/**
 * @class
 * Base interface for all message objects.
 *
 * <p>
 * <b>See also:</b>
 * <a href="opensocial.html#newMessage">opensocial.newMessage()</a>,
 * <a href="opensocial.html#requestSendMessage">
 * opensocial.requestSendMessage()</a>
 *
 * @name opensocial.Message
 */


/**
 * Base interface for all message objects.
 *
 * @param {String} body The main text of the message.
 * @param {Map.<opensocial.Message.Field, Object>} opt_params Any other
 *    fields that should be set on the message object. All of the defined
 *    Fields are supported.
 * @private
 * @constructor
 */
opensocial.Message = function(body, opt_params) {
  this.fields_ = opt_params || {};
  this.fields_[opensocial.Message.Field.BODY] = body;
};


/**
 * @static
 * @class
 * All of the fields that messages can have.
 *
 * <p>
 * <b>See also:</b>
 * <a
 * href="opensocial.Message.html#getField">opensocial.Message.getField()</a>
 * </p>
 *
 * @name opensocial.Message.Field
 */
opensocial.Message.Field = {
  /**
   * The title of the message, specified as an opensocial.Message.Type.
   * @member opensocial.Message.Field
   */
  TYPE : 'type',

  /**
   * The title of the message. HTML attributes are allowed and are
   * sanitized by the container.
   * @member opensocial.Message.Field
   */
  TITLE : 'title',

  /**
   * The main text of the message. HTML attributes are allowed and are
   * sanitized by the container.
   * @member opensocial.Message.Field
   */
  BODY : 'body',

  /**
   * The title of the message as a message template. Specifies the
   * message ID to use in the gadget xml.
   * @member opensocial.Message.Field
   */
  TITLE_ID : 'titleId',

  /**
   * The main text of the message as a message template. Specifies the
   * message ID to use in the gadget xml.
   * @member opensocial.Message.Field
   */
  BODY_ID : 'bodyId'
};


/**
 * @static
 * @class
 * The types of messages that can be sent.
 *
 * @name opensocial.Message.Type
 */
opensocial.Message.Type = {
  /**
   * An email.
   *
   * @member opensocial.Message.Type
   */
  EMAIL : 'email',

  /**
   * A short private message.
   *
   * @member opensocial.Message.Type
   */
  NOTIFICATION : 'notification',

  /**
   * A message to a specific user that can be seen only by that user.
   *
   * @member opensocial.Message.Type
   */
  PRIVATE_MESSAGE : 'privateMessage',

  /**
   * A message to a specific user that can be seen by more than that user.
   * @member opensocial.Message.Type
   */
  PUBLIC_MESSAGE : 'publicMessage'
};


/**
 * Gets the message data that's associated with the specified key.
 *
 * @param {String} key The key to get data for;
 *   see the <a href="opensocial.Message.Field.html">Field</a> class
 * for possible values
 * @param {Map.&lt;opensocial.DataRequest.DataRequestFields, Object&gt;}
 *  opt_params Additional
 *    <a href="opensocial.DataRequest.DataRequestFields.html">params</a>
 *    to pass to the request.
 * @return {String} The data
 * @member opensocial.Message
 */
opensocial.Message.prototype.getField = function(key, opt_params) {
  return opensocial.Container.getField(this.fields_, key, opt_params);
};


/**
 * Sets data for this message associated with the given key.
 *
 * @param {String} key The key to set data for
 * @param {String} data The data to set
 */
opensocial.Message.prototype.setField = function(key, data) {
  return this.fields_[key] = data;
};
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @class
 * Represents images, movies, and audio.
 * Create a <code>MediaItem</code> object using the
 * <a href="opensocial.html#newMediaItem">
 * opensocial.newMediaItem()</a> method.
 *
 * @name opensocial.MediaItem
 */

/**
 * Represents images, movies, and audio.
 *
 * @param {String} mimeType The media's type
 * @param {String} url The media's location
 * @param {Map.<opensocial.MediaItem.Field, Object>} opt_params
 *    Any other fields that should be set on the media item object.
 *    All of the defined Fields are supported.
 * @constructor
 * @private
 */
opensocial.MediaItem = function(mimeType, url, opt_params) {
  this.fields_ = opt_params || {};
  this.fields_[opensocial.MediaItem.Field.MIME_TYPE] = mimeType;
  this.fields_[opensocial.MediaItem.Field.URL] = url;
};


/**
 * @static
 * @class
 * The possible types of media items.
 *
 * <p>
 * <b>See also:</b>
 * <a href="opensocial.MediaItem.Field.html">
 * opensocial.MediaItem.Field</a>
 * </p>
 *
 * @name opensocial.MediaItem.Type
 */
opensocial.MediaItem.Type = {
  /** @member opensocial.MediaItem.Type */
  IMAGE : 'image',
  /** @member opensocial.MediaItem.Type */
  VIDEO : 'video',
  /** @member opensocial.MediaItem.Type */
  AUDIO : 'audio'
}


/**
 * @static
 * @class
 * All of the fields that media items have.
 *
 * <p>
 * <b>See also:</b>
 * <a href="opensocial.MediaItem.html#getField">
 * opensocial.MediaItem.getField()</a>
 * </p>
 *
 * @name opensocial.MediaItem.Field
 */
opensocial.MediaItem.Field = {
  /**
   * The type of media, specified as a
   * <a href="opensocial.MediaItem.Type.html">
   * <code>MediaItem.Type</code></a> object.
   * @member opensocial.MediaItem.Field
   */
  TYPE : 'type',

  /**
   * The MIME type of media, specified as a String.
   * @member opensocial.MediaItem.Field
   */
  MIME_TYPE : 'mimeType',

  /**
   * A string specifying the URL where the media can be found.
   * @member opensocial.MediaItem.Field
   */
  URL : 'url'
};


/**
 * Gets the media item data that's associated with the specified key.
 *
 * @param {String} key The key to get data for; see the
 *   <a href="opensocial.MediaItem.Field.html">Field</a> class
 *   for possible values
 * @return {String} The data
 */
opensocial.MediaItem.prototype.getField = function(key, opt_params) {
  return opensocial.Container.getField(this.fields_, key, opt_params);
};


/**
 * Sets data for this media item associated with the given key.
 *
 * @param {String} key The key to set data for
 * @param {String} data The data to set
 */
opensocial.MediaItem.prototype.setField = function(key, data) {
  return this.fields_[key] = data;
};
/**
 * Copyright 2007 Fox Interactive Media
 * <DISCLAIMER HERE>
 * @fileoverview Interface for container and associated classes; everything needed to query/update MySpace API data via OpenSocial interface.
 * 
 * @author mnewbould [_at_] myspace [_dot_] com (Max Newbould)
 * @author crussell [_at_] myspace [_dot_] com (Chad Russell)
 * @author jreyes [_at_] myspace [_dot_] com (Jorge Reyes)
 * @author jmay [_at_] myspace [_dot_] com (Justin May)
 */
 
if (typeof(MyOpenSpace) == "undefined") 
/**
 * Namespace for top level MyOpenSpace functions.
 * @constructor (note: a constructor for JsDoc purposes)
 */
	MyOpenSpace = { Version: "0.8" };
/**
 * @internal
 */
MyOpenSpace.PrefetchParameters = { params_: {} };
/**
 * 
 * @param {Object} key
 * @param {Object} value
 * @internal
 */
MyOpenSpace.PrefetchParameters.registerParam = function(key, value) {
    this.params_[key] = value;
    //gadgets.views.getParams()[key] = value;
};
/**
 * @internal
 */
MyOpenSpace.PrefetchParameters.syncParams = function() {
    for (var key in this.params_) {
        if (typeof(gadgets.views.getParams()[key]) === "undefined") gadgets.views.getParams()[key] = this.params_[key];  
    }
};
/**
 * 
 * @param {Object} key
 * @internal
 */
MyOpenSpace.PrefetchParameters.getParam = function(key) {
    if (typeof (gadgets.views.getParams()[key]) !== "undefined") {
        return gadgets.views.getParams()[key];
    } else {
        return this.params_[key];
    }
};

/**
 * Defines the default page size for paginated requests.
 * Used as max/default for MAX of pagination.
 * @static
 * @constant
 */
MyOpenSpace.DefaultPageSize = 20;

/**
 * Defines the type of idSpec mapping value
 * @static
 * @class
 * @name MyOpenSpace.IdSpecMapping_
 * @constructor (note: a constructor for JsDoc purposes)
 * @internal
 */
MyOpenSpace.IdSpecMapping_ = {
    /**
     * The idSpec mapping for viewer friends.
     * @memberOf MyOpenSpace.IdSpecMapping_
     * @constant
     * @internal
     */
    VIEWER_FRIENDS:"VIEWER_FRIENDS",
    
    /**
     * The idSpec mapping for owner friends.
     * @memberOf MyOpenSpace.IdSpecMapping_
     * @constant
     * @internal
     */
    OWNER_FRIENDS:"OWNER_FRIENDS",
	/**
     * The idSpec mapping for viewer.
     * @memberOf MyOpenSpace.IdSpecMapping_
     * @constant
     * @internal
     */
	VIEWER : "VIEWER",
	/**
     * The idSpec mapping for owner.
     * @memberOf MyOpenSpace.IdSpecMapping_
     * @constant
     * @internal
     */	
	OWNER : "OWNER"
};

/**
 * <p>Enumerates the types of requests that can be made.</p>
 * <p>It is used to retreive ResponseItems if opt_key was not 
 * set when adding the request.</p>
 * <p> 
 * If more that one request of the same type is made, the first item can be retrieve 
 * directly using the enumeration value, the rest will have a "_#" postfix added 
 * to the enumeration value where # will be a sequential number starting with 1.</p>
 * @static
 * @class
 * @name MyOpenSpace.RequestType
 * @example
 * var container = MSID.Container.get();
 * var request = this.container.newDataRequest();
 * var fetchViewer = request.newFetchPersonRequest(opensocial.IdSpec.PersonId.VIEWER);
 * var fetchOwner = request.newFetchPersonRequest(opensocial.IdSpec.PersonId.OWNER);
 * request.add(fetchViewer); // opt_key not set
 * request.add(fetchOwner); // opt_key not set
 * request.send(callback);
 * function callback(response){
 *      var viewer = response.get(MyOpenSpace.RequestType.FETCH_PERSON);
 *      var owner = response.get(MyOpenSpace.RequestType.FETCH_PERSON + â€œ_1â€);
 * }
 */
MyOpenSpace.RequestType = {
    /**
     * Default key to retreive the opensocial.ResponseItem from an
     * opensocial.newFetchPerson.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    FETCH_PERSON:"FETCH_PERSON",
    /**
     * Default key to retreive the opensocial.ResponseItem from an
     * opensocial.DataRequest.newFetchPeople.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
	FETCH_PEOPLE:"FETCH_PEOPLE",
	/**
	 * Default key to retreive the opensocial.ResponseItem from an
     * opensocial.DataRequest.newFetchPersonAppDataRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
	FETCH_PERSON_DATA:"FETCH_PERSON_DATA",
	/**
     * Default key to retreive the opensocial.ResponseItem from an
     * opensocial.DataRequest.newFetchPersonAppDataRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
	UPDATE_PERSON_DATA:"UPDATE_PERSON_DATA",
	/**
     * Default key to retreive the opensocial.ResponseItem from an
     * opensocial.DataRequest.newRemovePersonAppDataRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
	REMOVE_PERSON_DATA:"REMOVE_PERSON_DATA",
	/**
     * Default key to retreive the opensocial.ResponseItem from an
     * opensocial.DataRequest.newFetchActivitiesRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
	FETCH_ACTIVITIES:"FETCH_ACTIVITIES",
	/**
     * Default key to retreive the opensocial.ResponseItem from a
     * MyOpenSpace.DataRequest.newFetchAlbumsRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    FETCH_ALBUMS:"FETCH_ALBUMS",
    /**
     * Default key to retreive the opensocial.ResponseItem from a
     * MyOpenSpace.DataRequest.newFetchAlbumRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    FETCH_ALBUM:"FETCH_ALBUM",
    /**
     * Default key to retreive the opensocial.ResponseItem from a
     * MyOpenSpace.DataRequest.newFetchVideosRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    FETCH_VIDEOS:"FETCH_VIDEOS",
    /**
     * Default key to retreive the opensocial.ResponseItem from a
     * MyOpenSpace.DataRequest.newFetchVideoRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    FETCH_VIDEO:"FETCH_VIDEO",
    /**
     * Default key to retreive the opensocial.ResponseItem from a
     * MyOpenSpace.DataRequest.newFetchPhotosRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    FETCH_PHOTOS:"FETCH_PHOTOS",
    /**
     * Default key to retreive the opensocial.ResponseItem from a
     * MyOpenSpace.DataRequest.newFetchPhotoRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    FETCH_PHOTO:"FETCH_PHOTO",
    /**
     * Default key to retreive the opensocial.ResponseItem from a
     * MyOpenSpace.DataRequest.newFetchIndicatorsRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    FETCH_INDICATORS:"FETCH_INDICATORS",
    
    /**
     * Default key to retreive the opensocial.ResponseItem from a
     * MyOpenSpace.DataRequest.newFetchPersonStatusRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    FETCH_PERSON_STATUS:"FETCH_PERSON_STATUS",
    
    /**
     * Default key to retreive the opensocial.ResponseItem from a
     * MyOpenSpace.DataRequest.newFetchPersonMoodRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    FETCH_PERSON_MOOD:"FETCH_PERSON_MOOD",
    
    /**
     * Default key to retreive the opensocial.ResponseItem from a
     * MyOpenSpace.DataRequest.newPeopleFriendshipRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    FETCH_PEOPLE_FRIENDSHIP: "FETCH_PEOPLE_FRIENDSHIP",
    
    /**
     * Default key to retreive the opensocial.ResponseItem from a
     * MyOpenSpace.DataRequest.newPersonFriendshipRequest.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    FETCH_PERSON_FRIENDSHIP: "FETCH_PERSON_FRIENDSHIP",
    
    /**
     * Default key to retreive the opensocial.ResponseItem for a
     * notification request.
     * @memberOf MyOpenSpace.RequestType
     * @constant
     */
    INSERT_NOTIFICATION: "INSERT_NOTIFICATION"
};

MyOpenSpace.RequestParameters = {
    NO_TIMESTAMP: "noTimestamp"
};

/**
 * @static
 * @class
 * A helper enum used for certain built-in media items.
 * @name MyOpenSpace.MediaItemHelper
 */
MyOpenSpace.MediaItemHelper = {
    /**
    * The user's current profile picture
    * @member MyOpenSpace.MediaItemHelper
    */
    PROFILE_PICTURE : 'profilePicture'
};

/**
* @ignore
*/
MyOpenSpace.Util = {};

/**
* @ignore
*/
MyOpenSpace.Util.idSpecMap = function(idspec){
    if(!idspec || !idspec.getField){
        return {
            "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
            "errorMessage": "Invalid idSpec."
        };
    }
    if(typeof(idspec.getField(opensocial.IdSpec.Field.USER_ID)) === "undefined"){
        return {
            "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
            "errorMessage": "USER_ID was not provided."
        };
    }

	var userId = idspec.getField(opensocial.IdSpec.Field.USER_ID);
    var networkDistance = 0;
    if (typeof(idspec.getField(opensocial.IdSpec.Field.NETWORK_DISTANCE)) !== 'undefined') {
		if (typeof(idspec.getField(opensocial.IdSpec.Field.NETWORK_DISTANCE)) !== 'number') {
			return {
				"errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
				"errorMessage": "NETWORK_DISTANCE must be an integer."
			};
		}
		networkDistance = idspec.getField(opensocial.IdSpec.Field.NETWORK_DISTANCE);
		if (networkDistance < 0 || networkDistance > 1) {
			return {
				"errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
				"errorMessage": "NETWORK_DISTANCE must be 0 or 1."
			};
		}
	}
	else if (typeof(idspec.getField(opensocial.IdSpec.Field.GROUP_ID)) !== 'undefined') {
		var groupId = idspec.getField(opensocial.IdSpec.Field.GROUP_ID);
		if (groupId === "FRIENDS"){
			networkDistance = 1;
		}
		else if (groupId === "SELF"){
			networkDistance = 0;
		}
		else{
			return {
				"errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
				"errorMessage": "GROUP_ID is not valid accepted values are FRIENDS or SELF."};
		}
	}

    if (userId === opensocial.IdSpec.PersonId.OWNER) {
        if (networkDistance === 0) {
            return MyOpenSpace.IdSpecMapping_.OWNER;
        }
        else if (networkDistance === 1) {
            return MyOpenSpace.IdSpecMapping_.OWNER_FRIENDS;
        }
    }
    else if (userId === opensocial.IdSpec.PersonId.VIEWER) {
        if (networkDistance === 0) {
            return MyOpenSpace.IdSpecMapping_.VIEWER;
        }
        else if (networkDistance === 1) {
            return MyOpenSpace.IdSpecMapping_.VIEWER_FRIENDS;
        }
    }
    else if (userId.constructor === Array) {
        if (networkDistance > 0) {
            return { "errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED, "errorMessage": "NETWORK_DISTANCE greater than 0 is not been implemented for arrays of Ids." };
        }
        var idArray = [];
        for (var i in userId) {
            var results = MyOpenSpace.Util.parseIdPrefix(userId[i]);
            if (results === null) {
                return { "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, 
                            "errorMessage": "opensocial.IdSpec.Field.USER_ID array contains invalid elements." };
            }
            idArray[i] = results;
        }
        return idArray;
    }
    else {
        var results = MyOpenSpace.Util.parseIdPrefix(userId);
        if(null === results){
            return { "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, 
                            "errorMessage": "Invalid opensocial.IdSpec.Field.USER_ID value." };
        }
        return results;
    }
};

MyOpenSpace.Util.parseIdPrefix = function(id){
    var results = ('' + id).match(/^(?:myspace\.com:)?(\d+)$/);
    if (results === null || results.length === 0) {
        return null;
    }
    return results[1];
};/**
 * Internal implementation of a simple hash table.
 * @private
 */
MyOpenSpace.Hash = function(){ 
    this._hash = {};
    this._num = 0;
};
MyOpenSpace.Hash.prototype = {
    _hash:null,
    _num:null,
	/**
	 * Add element to the hash
	 * @param {Object} key
	 * @param {Object} value
	 * @private
	 */
    add:function(key, value){
		if (typeof key !== 'string') return;
		if (!this.has(key)){
	        this._num++;			
		}
        this._hash[key] = {
			"value": value
		};

    },
	/**
	 * Remove element from the hash
	 * @param {Object} key
	 * @private
	 */
    remove:function(key){
		if (typeof key !== 'string') return;
        var val = null;
        if("undefined" !== typeof(this._hash[key])){
            val = this._hash[key];
            delete this._hash[key];
            this._num--;
        }
        return val;
    },
	/**
	 * 
	 * @param {Object} key
	 * @private
	 */
    get:function(key){
		if (typeof key !== 'string') return undefined;
		if (this.has(key)){
	        return this._hash[key].value;
		}
		else{
			return undefined;
		}
    },
	/**
	 * @private
	 */
    size:function(){
        return this._num;
    },
	/**
	 * 
	 * @param {Object} key
	 * @private
	 */
    has:function(key){
		if (typeof key !== 'string') return false;
        return "undefined" !== typeof(this._hash[key]);
    }
};/**
 * MySpace extension of the opensocial.Environment object.
 * @constructor
 * @param {Array<string>} supportedPostToTargets A string array of supported PostTo targets
 * @param {MyOpenSpace.Application} app An object representing the current application executing in the container
 * @class
 * @name MyOpenSpace.Environment
 * @private
 * @internal
 */
MyOpenSpace.Environment = function(supportedPostToTargets, app){
	 var supportedMapped = [];
     for (var i in supportedPostToTargets) {
        switch (supportedPostToTargets[i]){
			case MyOpenSpace.PostTo.Targets.SEND_MESSAGE:
				supportedMapped.push(opensocial.Message.Type.PRIVATE_MESSAGE);
				break;
			case MyOpenSpace.PostTo.Targets.BULLETINS:
				supportedMapped.push(opensocial.Message.Type.NOTIFICATION);
				break;
			case MyOpenSpace.PostTo.Targets.COMMENTS:
				supportedMapped.push(opensocial.Message.Type.PUBLIC_MESSAGE);
				break;
			default:
				supportedMapped.push(supportedPostToTargets[i]);
				break;
		}
    }
    this.supportedPostToTargets = supportedMapped;
	this.currentApplication = app;
};

/**
 * Accessor for the supportedPostToTargets array
 * @memberOf MyOpenSpace.Environment
 * @private
 */
MyOpenSpace.Environment.prototype.getSupportedPostToTargets = function(){
    return this.supportedPostToTargets;
};
/**
 * Accessor for the current Application executing in the environment
 * @memberOf MyOpenSpace.Environment
 * @private
 */
MyOpenSpace.Environment.prototype.getApplication = function(){
    return this.currentApplication;
};



/**
 * The types of extended objects in this container.
 * @static
 * @class
 * @name MyOpenSpace.Environment.ObjectType
 */
MyOpenSpace.Environment.ObjectType = {
    /**
     * @member MyOpenSpace.Environment.ObjectType
     */
    VIDEO : "VIDEO",
    /**
     * @member MyOpenSpace.Environment.ObjectType
     */
    PHOTO : "PHOTO",
    /**
     * @member MyOpenSpace.Environment.ObjectType
     */
    ALBUM : "ALBUM",
    /**
     * @member MyOpenSpace.Environment.ObjectType
     */
    PERSON : "PERSON"
};/* ================================================================
 * MyOpenSpace.Indicators
 * ================================================================
 */


 /**
 * A class representing an Indicators.
 * @constructor
 * @private
 */
MyOpenSpace.Indicators = function() {};
/**
 * The fields for MyOpenSpace.Indicators
 * @class
 * @name MyOpenSpace.Indicators.Field
 * @static
 */
MyOpenSpace.Indicators.Field = {
    /**
     * A boolean indicating the peroson has a mail.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    MAIL:"MAIL",
    
    /**
     * A string containing the mail url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    MAIL_URL:"MAIL_URL",
    
    /**
     * A boolean indication the person has a birthday.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    BIRTHDAY:"BIRTHDAY",
    
    /**
     * A string containing the birthday url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    BIRTHDAY_URL:"BIRTHDAY_URL",

    /**
     * A boolean indicating the person has a blog comment.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    BLOG_COMMENT:"BLOG_COMMENT",
    
    /**
     * A string containing the blog comment url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    BLOG_COMMENT_URL:"BLOG_COMMENT_URL",
    
    /**
     * A boolean indicating the person has a blog subscrition post.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    BLOG_SUBSCRIPTION_POST:"BLOG_SUBSCRIPTION_POST",
    
    /**
     * A string containing the blog subscription post url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    BLOG_SUBSCRIPTION_POST_URL:"BLOG_SUBSCRIPTION_POST_URL",
    
    /**
     * A boolean indicating the person has a comment.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    COMMENT:"COMMENT",
    
    /**
     * A string containing the comment url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    COMMENT_URL:"COMMENT_URL",
    
    /**
     * A boolean indicating the person has a event invitation.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    EVENT_INVITATION:"EVENT_INVITATION",
    
    /**
     * A string containing the event invitation url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    EVENT_INVITATION_URL:"EVENT_INVITATION_URL",
    
    /**
     * A boolean indicating the person has a friend request.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    FRIEND_REQUEST:"FRIEND_REQUEST",
    
    /**
     * A string containing the friend request invitation url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    FRIEND_REQUEST_URL:"FRIEND_REQUEST_URL",
    
    /**
     * A boolean indicating the person has a group notification.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    GROUP_NOTIFICATION:"GROUP_NOTIFICATION",
    
    /**
     * A string containing the group notification url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    GROUP_NOTIFICATION_URL:"GROUP_NOTIFICATION_URL",
    
    /**
     * A boolean indicating the person has a photo tag approval.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    PHOTO_TAG_APPROVAL:"PHOTO_TAG_APPROVAL",
    
    /**
     * A string containing the photo tag approval url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    PHOTO_TAG_APPROVAL_URL:"PHOTO_TAG_APPROVAL_URL",
    
    /**
     * A boolean indicating the person has a picture comment.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    PICTURE_COMMENT:"PICTURE_COMMENT",
    
    /**
     * A string containing the picture comment url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    PICTURE_COMMENT_URL:"PICTURE_COMMENT_URL",
    
    /**
     * A boolean indicating the person has a recently added friend.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    RECENTLY_ADDED_FRIEND:"RECENTLY_ADDED_FRIEND",
    
    /**
     * A string containing the recently added friend url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    RECENTLY_ADDED_FRIEND_URL:"RECENTLY_ADDED_FRIEND_URL",
    
    /**
     * A boolean indicating the person has a video comment.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    VIDEO_COMMENT:"VIDEO_COMMENT",
    
    /**
     * A string containing the video comment url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    VIDEO_COMMENT_URL:"VIDEO_COMMENT_URL",
    
    /**
     * A boolean indicating the person has a video process.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    VIDEO_PROCESS:"VIDEO_PROCESS",
    
    /**
     * A string containing the video process url.
     * @memberOf MyOpenSpace.Indicators.Field
     */
    VIDEO_PROCESS_URL:"VIDEO_PROCESS_URL"
};

/**
 * Returns the field specified by key
 * @param {String} key The key to search by
 * @return {MyOpenSpace.Indicators || undefined} The indicators if found, nothing otherwise.
 */
MyOpenSpace.Indicators.prototype.getField = function(key) { return this[key]; };
/**
 * Writes a value to the field specified by the key
 * @param {String} key The key to search by.
 * @param {String} val The value to set.
 * @private
 * @internal
 */
MyOpenSpace.Indicators.prototype.setField_ = function(key,val) { this[key] = val; };/**
 * An extension of opensocial.Person
 * @constructor
 * @private
 * @param {Array&lt;opensocial.Person.Field || MyOpenSpace.Person.Field&gt;} opt_params Contains an Array with opensocial.Person.Field or MyOpenSpace.Person.Field values with which to populate the object
 * @param {Boolean} opt_isOwner Sets the person as the owner
 * @param {Boolean} opt_isOwner Sets the person as the viewer
 */
MyOpenSpace.Person = function(opt_params, opt_isOwner, opt_isViewer) {
    opensocial.Person.call(this, opt_params, opt_isOwner, opt_isViewer);
    this["setField_"] = function(key,val) { this.fields_[key] = val; }; //TODO: find a better way to extend
    this._type = 0;
};

/**
 * The fields for the extended MyOpenSpace.Person
 * @class
 * @name MyOpenSpace.Person.Field
 * @static
 */
MyOpenSpace.Person.Field = opensocial.Person.Field;

/**
 * The URL of the medium version of the user's thumbnail
 * @memberOf MyOpenSpace.Person.Field
 */
MyOpenSpace.Person.Field.MEDIUM_IMAGE = "mediumImage";
/**
 * The URL of the large version of the user's thumbnail
 * @memberOf MyOpenSpace.Person.Field
 */
MyOpenSpace.Person.Field.LARGE_IMAGE ="largeImage";
	

MyOpenSpace.Person.inherits(opensocial.Person);

/**
 * A class representing an PersonMood.
 * @constructor
 * @private
 */
MyOpenSpace.PersonMood = function() {};
/**
 * The fields for MyOpenSpace.PersonMood
 * @class
 * @name MyOpenSpace.PersonMood.Field
 * @static
 */
MyOpenSpace.PersonMood.Field = {
    /**
     * A string indicating the person mood.
     * @memberOf MyOpenSpace.PersonMood.Field
     */
    MOOD:"MOOD",
    /**
     * A string indicating the person mood image url.
     * @memberOf MyOpenSpace.PersonMood.Field
     */
    MOOD_IMAGE_URL:"MOOD_IMAGE_URL",
    /**
     * A string indicating the last date time the mood was modified.
     * @memberOf MyOpenSpace.PersonMood.Field
     */
    MOOD_LAST_UPDATED:"MOOD_LAST_UPDATED"
};

/**
 * Returns the field specified by key
 * @param {String} key The key to search by
 * @return {MyOpenSpace.PersonMood || undefined} The PersonMood if found, nothing otherwise.
 */
MyOpenSpace.PersonMood.prototype.getField = function(key) { return this[key]; };

/**
 * Writes a value to the field specified by the key
 * @param {String} key The key to search by.
 * @param {String} val The value to set.
 * @private
 * @internal
 */
MyOpenSpace.PersonMood.prototype.setField_ = function(key,val) { this[key] = val; };/**
 * A class representing an PersonStatus.
 * @constructor
 * @private
 */
MyOpenSpace.PersonStatus = function() {};
/**
 * The fields for MyOpenSpace.PersonStatus
 * @class
 * @name MyOpenSpace.PersonStatus.Field
 * @static
 */
MyOpenSpace.PersonStatus.Field = {
    /**
     * A string indicating the person status.
     * @memberOf MyOpenSpace.PersonStatus.Field
     */
    STATUS:"STATUS"
};
/**
 * Returns the field specified by key
 * @param {String} key The key to search by
 * @return {MyOpenSpace.PersonStatus || undefined} The PersonStatus if found, nothing otherwise.
 */
MyOpenSpace.PersonStatus.prototype.getField = function(key) { return this[key]; };
/**
 * Writes a value to the field specified by the key
 * @param {String} key The key to search by.
 * @param {String} val The value to set.
 * @private
 * @internal
 */
MyOpenSpace.PersonStatus.prototype.setField_ = function(key,val) { this[key] = val; };/**
 * A class representing an FriendshipStatus.
 * @constructor
 * @private
 */
MyOpenSpace.Friendship = function() {};

/**
 * The fields for MyOpenSpace.Friendship
 * @class
 * @name MyOpenSpace.Friendship.Field
 * @static
 */
MyOpenSpace.Friendship.Field = {
    /**
     * A boolean indicating if the person is friend.
     * @memberOf MyOpenSpace.Friendship.Field
     */
    IS_FRIEND:"IS_FRIEND",
    
    /**
     * A string indicating the friend id.
     * @memberOf MyOpenSpace.Friendship.Field
     */
    FRIEND_ID:"FRIEND_ID"
};

/**
 * Returns the field specified by key
 * @param {String} key The key to search by
 * @return {MyOpenSpace.Friendship || undefined} The Frienship Field value if found, nothing otherwise.
 */
MyOpenSpace.Friendship.prototype.getField = function(key) { return this[key]; };

/**
 * Writes a value to the field specified by the key
 * @param {String} key The key to search by.
 * @param {String} val The value to set.
 * @private
 * @internal
 */
MyOpenSpace.Friendship.prototype.setField_ = function(key,val) { this[key] = val; };/**
 * Copyright 2007 Fox Interactive Media
 * <DISCLAIMER HERE>
 * @fileoverview Interface for container and associated classes; everything needed to query/update MySpace API data via OpenSocial interface.
 * 
 * @author mnewbould [_at_] myspace [_dot_] com (Max Newbould)
 * @author crussell [_at_] myspace [_dot_] com (Chad Russell)
 * @author jreyes [_at_] myspace [_dot_] com (Jorge Reyes)
 */
 
/**
 * Maps JSON responses from the API into the various objects
 * @class
 * @constructor
 * @name MyOpenSpace.DataMapper_
 * @internal
 */
MyOpenSpace.DataMapper_ = function() {
	this.mapData[MyOpenSpace.RequestType.FETCH_PEOPLE_FRIENDSHIP] = this.mapPeopleFriendship_;
    this.mapData[MyOpenSpace.RequestType.FETCH_PERSON_FRIENDSHIP] = this.mapPersonFriendship_;
    this.mapData[MyOpenSpace.RequestType.FETCH_PERSON] = this.mapPerson_;
    this.mapData[MyOpenSpace.RequestType.FETCH_INDICATORS] = this.mapIndicators_;
    this.mapData[MyOpenSpace.RequestType.FETCH_PERSON_STATUS] = this.mapPersonStatus_;
    this.mapData[MyOpenSpace.RequestType.FETCH_PERSON_MOOD] = this.mapPersonMood_;
    this.mapData[MyOpenSpace.RequestType.FETCH_PEOPLE] = this.mapPeople_;
    this.mapData[MyOpenSpace.RequestType.FETCH_ALBUMS] = this.mapAlbums_;
    this.mapData[MyOpenSpace.RequestType.FETCH_ALBUM] = this.mapAlbum_;
    this.mapData[MyOpenSpace.RequestType.FETCH_VIDEOS] = this.mapVideos_;
    this.mapData[MyOpenSpace.RequestType.FETCH_VIDEO] = this.mapVideo_;
    this.mapData[MyOpenSpace.RequestType.FETCH_PHOTOS] = this.mapPhotos_;
    this.mapData[MyOpenSpace.RequestType.FETCH_PHOTO] = this.mapPhoto_;
    this.mapData[MyOpenSpace.RequestType.FETCH_PERSON_DATA] = this.mapPersonAppData_;
    this.mapData[MyOpenSpace.RequestType.FETCH_ACTIVITIES] = this.mapActivities_;
	this.mapData['mapSimplePersonData_'] = this.mapSimplePersonData_;
	this.mapData['mapPersonData_'] = this.mapPersonData_;
};


MyOpenSpace.DataMapper_.prototype = {
    /**
    * Object that contains the mapping of MyOpenSpace.RequestType Enum element to the function that will
    * map the server response data.
    * @memberOf MyOpenSpace.DataMapper_
    * @private
    * @internal
    */
    mapData: {},
    /**
    * Maps a server response into a opensocial.Collection of MyOpenSpace.Photo objects
    * @function
    * @return {opensocial.Collection}
    * @param {XMLHttpRequest} obj The response from the server
    * @memberOf MyOpenSpace.DataMapper_
    * @internal
    */
    mapPhotos_: function(obj) {
        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }
        var photos = [];
        var photo;
        var ns = MyOpenSpace.Photo.Field;

        if (unmapped.photos) {
            for (var i = 0; i < unmapped.photos.length; i++) {
                photo = new MSID.Container.get().newPhoto();
                photo.setField_(ns.PHOTO_ID, unmapped.photos[i].id);
                photo.setField_(ns.PHOTO_URI, unmapped.photos[i].photoUri);
                photo.setField_(ns.IMAGE_URI, unmapped.photos[i].imageUri);
                photo.setField_(ns.CAPTION, unmapped.photos[i].caption);
                //photo.setField_(ns.COMMENTS_COUNT,unmapped.photos[i].commentsCount);
                photos.push(photo);
            }
        }

        return MSID.Container.get().newCollection(photos, 0, unmapped.count);
    },

    /**
    * Maps a server response into a MyOpenSpace.Photo object
    * @function
    * @return {MyOpenSpace.Photo}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    */
    mapPhoto_: function(obj) {
        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }
        var photo = new MSID.Container.get().newPhoto();
        var ns = MyOpenSpace.Photo.Field;

        if (unmapped) {
            photo.setField_(ns.PHOTO_ID, unmapped.id);
            photo.setField_(ns.PHOTO_URI, unmapped.photoUri);
            photo.setField_(ns.IMAGE_URI, unmapped.imageUri);
            photo.setField_(ns.CAPTION, unmapped.caption);
            //photo.setField_(ns.COMMENTS_COUNT,unmapped.commentsCount);
        }

        return photo;
    },
    /**
    * Maps a server response into a valid JSON Object.
    * @function
    * @return {Object}
    * @param {XMLHttpRequest} obj The response from the server
    * @param {opensocial.EscapeType}encoding Enum value to determine if the results will be html escape or not
    * @internal
    */
    mapPersonAppData_: function(obj, encoding) {
        if(null === obj || "undefined" === typeof(obj)) return null;
    
        var appDataXML = obj.responseXML;

        if (null === appDataXML || 
                "undefined" === typeof (appDataXML) ||
                "undefined" === typeof(appDataXML.childNodes))
            return null;

        var userCount = appDataXML.childNodes.length;
        var userNode = appDataXML.firstChild;
        var userId;
        var appDataNode;
        var appDataCount = 0;
        for (var i = 0; i < userNode.childNodes.length; i++) {
            if ("userid" === userNode.childNodes[i].nodeName)
                userId = userNode.childNodes[i].firstChild.nodeValue;
            if ("appdata" === userNode.childNodes[i].nodeName)
                appDataNode = userNode.childNodes[i];
            if ("appdatafriends" === userNode.childNodes[i].nodeName)
                appDataNode = userNode.childNodes[i];
        }
        i = 0;
        var personAppData = {};

        if ("appdatafriends" === appDataNode.nodeName) {
            var friendId = 0;
            var friendNode;
            var friendAppDataNode;
            for (var k = 0; k < appDataNode.childNodes.length; k++) {
                friendNode = appDataNode.childNodes[k];
                for (var m = 0; m < friendNode.childNodes.length; m++) {
                    if ("friendid" === friendNode.childNodes[m].nodeName) {
                        friendId = friendNode.childNodes[m].firstChild.nodeValue;
                    }
                    if ("appdata" === friendNode.childNodes[m].nodeName) {
                        friendAppDataNode = friendNode.childNodes[m];
                    }
                }
                personAppData[friendId] = {};
                appDataCount = friendAppDataNode.getAttribute("count");
                for (var j = 0; j < friendAppDataNode.childNodes.length; j++) {
                    personAppData[friendId][friendAppDataNode.childNodes[j].getAttribute("name")] = friendAppDataNode.childNodes[j].getAttribute("value");
                }
            }
        } else {
            personAppData[userId] = {};
            appDataCount = appDataNode.getAttribute("count");
            for (var i = 0; i < appDataNode.childNodes.length; i++) {
                if ("key" === appDataNode.childNodes[i].nodeName) {
                    var dataKey = appDataNode.childNodes[i].getAttribute("name");
                    var dataValue = gadgets.json.parse(appDataNode.childNodes[i].getAttribute("value"));
                    
                    if(!dataValue){
                        // for legacy 0.7 app data, which won't parse
                        dataValue = appDataNode.childNodes[i].getAttribute("value");
                    }
                    
                    if (encoding !== opensocial.EscapeType.NONE) {
                        dataValue = gadgets.util.escape(dataValue, true);
                    }
                    personAppData[userId][dataKey] = dataValue;
                }
            }
        }
        return personAppData;
    },
    /**
    * Maps a server response into a opensocial.Collection of MyOpenSpace.Album objects
    * @function
    * @return {opensocial.Collection}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    */
    mapAlbums_: function(obj) {
        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }
        var albums = [];
        var album;
        var ns = MyOpenSpace.Album.Field;

        if (unmapped.albums) {
            for (var i = 0; i < unmapped.albums.length; i++) {
                album = new MSID.Container.get().newAlbum();
                album.setField_(ns.ALBUM_ID, unmapped.albums[i].id);
                album.setField_(ns.ALBUM_URI, unmapped.albums[i].albumUri);
                album.setField_(ns.TITLE, unmapped.albums[i].title);
                album.setField_(ns.LOCATION, unmapped.albums[i].location);
                album.setField_(ns.DEFAULT_IMAGE, unmapped.albums[i].defaultImage);
                album.setField_(ns.PRIVACY, unmapped.albums[i].privacy);
                album.setField_(ns.PHOTO_COUNT, unmapped.albums[i].photoCount);
                album.setField_(ns.PHOTOS_URI, unmapped.albums[i].photosUri);
                albums.push(album);
            }
        }

        return MSID.Container.get().newCollection(albums, 0, unmapped.count);
    },

    /**
    * Maps a server response into a MyOpenSpace.Indicator object
    * @function
    * @return {MyOpenSpace.Indicators}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    */
    mapIndicators_: function(obj) {
        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }
        var indicators = new MSID.Container.get().newIndicators();
        var ns = MyOpenSpace.Indicators.Field;

        if (unmapped) {
            indicators.setField_(ns.MAIL, unmapped.mailurl ? true : false);
            indicators.setField_(ns.MAIL_URL, unmapped.mailurl);

            indicators.setField_(ns.BIRTHDAY, unmapped.birthdayurl ? true : false);
            indicators.setField_(ns.BIRTHDAY_URL, unmapped.birthdayurl);

            indicators.setField_(ns.BLOG_COMMENT, unmapped.blogcommenturl ? true : false);
            indicators.setField_(ns.BLOG_COMMENT_URL, unmapped.blogcommenturl);

            indicators.setField_(ns.BLOG_SUBSCRIPTION_POST, unmapped.blogsubscriptionposturl ? true : false);
            indicators.setField_(ns.BLOG_SUBSCRIPTION_POST_URL, unmapped.blogsubscriptionposturl);

            indicators.setField_(ns.COMMENT, unmapped.commenturl ? true : false);
            indicators.setField_(ns.COMMENT_URL, unmapped.commenturl);

            indicators.setField_(ns.EVENT_INVITATION, unmapped.eventinvitationurl ? true : false);
            indicators.setField_(ns.EVENT_INVITATION_URL, unmapped.eventinvitationurl);

            indicators.setField_(ns.FRIEND_REQUEST, unmapped.friendsrequesturl ? true : false);
            indicators.setField_(ns.FRIEND_REQUEST_URL, unmapped.friendsrequesturl);

            indicators.setField_(ns.GROUP_NOTIFICATION, unmapped.groupnotificationurl ? true : false);
            indicators.setField_(ns.GROUP_NOTIFICATION_URL, unmapped.groupnotificationurl);

            indicators.setField_(ns.PHOTO_TAG_APPROVAL, unmapped.phototagapprovalurl ? true : false);
            indicators.setField_(ns.PHOTO_TAG_APPROVAL_URL, unmapped.phototagapprovalurl);

            indicators.setField_(ns.PICTURE_COMMENT, unmapped.picturecommenturl ? true : false);
            indicators.setField_(ns.PICTURE_COMMENT_URL, unmapped.picturecommenturl);

            indicators.setField_(ns.RECENTLY_ADDED_FRIEND, unmapped.recentlyaddedfriendurl ? true : false);
            indicators.setField_(ns.RECENTLY_ADDED_FRIEND_URL, unmapped.recentlyaddedfriendurl);

            indicators.setField_(ns.VIDEO_COMMENT, unmapped.videocommenturl ? true : false);
            indicators.setField_(ns.VIDEO_COMMENT_URL, unmapped.videocommenturl);

            indicators.setField_(ns.VIDEO_PROCESS, unmapped.videoprocessurl ? true : false);
            indicators.setField_(ns.VIDEO_PROCESS_URL, unmapped.videoprocessurl);

        }

        return indicators;
    },

    /**
    * Maps a server response into a MyOpenSpace.PersonStatus object
    * @function
    * @return {MyOpenSpace.PersonStatus}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    */
    mapPersonStatus_: function(obj) {
        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }
        var personStatus = new MSID.Container.get().newPersonStatus();
        var ns = MyOpenSpace.PersonStatus.Field;

        if (unmapped) {
            personStatus.setField_(ns.STATUS, unmapped.status);
        }

        return personStatus;
    },

    /**
    * Maps a server response into a MyOpenSpace.PersonMood object
    * @function
    * @return {MyOpenSpace.PersonMood}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    */
    mapPersonMood_: function(obj) {
        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }
        var personMood = new MSID.Container.get().newPersonMood();
        var ns = MyOpenSpace.PersonMood.Field;

        if (unmapped) {
            personMood.setField_(ns.MOOD, unmapped.mood);
            personMood.setField_(ns.MOOD_IMAGE_URL, unmapped.moodImageUrl);
            personMood.setField_(ns.MOOD_LAST_UPDATED, unmapped.moodLastUpdated);
        }

        return personMood;
    },

    /**
    * Maps a server response into a MyOpenSpace.Album object
    * @function
    * @return {MyOpenSpace.Album}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    */
    mapAlbum_: function(obj) {
        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }
        var album = new MSID.Container.get().newAlbum();
        var ns = MyOpenSpace.Album.Field;

        if (unmapped) {
            album.setField_(ns.ALBUM_ID, unmapped.id);
            album.setField_(ns.ALBUM_URI, unmapped.albumUri);
            album.setField_(ns.TITLE, unmapped.title);
            album.setField_(ns.LOCATION, unmapped.location);
            album.setField_(ns.DEFAULT_IMAGE, unmapped.defaultImage);
            album.setField_(ns.PRIVACY, unmapped.privacy);
            album.setField_(ns.PHOTO_COUNT, unmapped.photoCount);
            album.setField_(ns.PHOTOS_URI, unmapped.photosUri);
        }

        return album;
    },

    /**
    * Maps a server response into a opensocial.Collection of MyOpenSpace.Friendship objects
    * @function
    * @return {opensocial.Collection}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    */
    mapPersonFriendship_: function(obj) {
        var unmapped;
        try {
            unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }
        var friendship = new MSID.Container.get().newFriendship();
        var ns = MyOpenSpace.Friendship.Field;

        if (unmapped.friendship) {

            friendship.setField_(ns.IS_FRIEND, unmapped.friendship[0].areFriends);
            friendship.setField_(ns.FRIEND_ID, unmapped.friendship[0].friendId);

        }

        return friendship;
    },

    /**
    * Maps a server response into a opensocial.Collection of MyOpenSpace.Friendship objects
    * @function
    * @return {opensocial.Collection}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    */
    mapPeopleFriendship_: function(obj) {
        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }
        var friendships = [];
        var ns = MyOpenSpace.Friendship.Field;

        if (unmapped.friendship) {
            for (var i = 0; i < unmapped.friendship.length; i++) {
                var friendship = new MSID.Container.get().newFriendship();
                friendship.setField_(ns.IS_FRIEND, unmapped.friendship[i].areFriends);
                friendship.setField_(ns.FRIEND_ID, unmapped.friendship[i].friendId);
                friendships.push(friendship);
            }
        }

        return MSID.Container.get().newCollection(friendships, 0, friendships.length);
    },


    /**
    * Maps a server response into a opensocial.Collection of MyOpenSpace.Video objects
    * @function
    * @return {opensocial.Collection}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    */
    mapVideos_: function(obj) {
        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }
        var videos = [];
        var video;
        var ns = MyOpenSpace.Video.Field;

        if (unmapped.videos) {
            for (var i = 0; i < unmapped.videos.length; i++) {
                video = new MSID.Container.get().newVideo();
                video.setField_(ns.VIDEO_ID, unmapped.videos[i].id);
                video.setField_(ns.VIDEO_URI, unmapped.videos[i].videoUri);
                video.setField_(ns.TITLE, unmapped.videos[i].title);
                video.setField_(ns.DATE_CREATED, unmapped.videos[i].datecreated);
                video.setField_(ns.LAST_UPDATE, unmapped.videos[i].dateupdated);
                video.setField_(ns.MEDIA_TYPE, unmapped.videos[i].mediatype);
                video.setField_(ns.THUMB_URI, unmapped.videos[i].thumbnail);
                video.setField_(ns.DESCRIPTION, unmapped.videos[i].description);
                video.setField_(ns.MEDIA_STATUS, unmapped.videos[i].mediastatus);
                video.setField_(ns.RUN_TIME, unmapped.videos[i].runtime);
                video.setField_(ns.TOTAL_VIEWS, unmapped.videos[i].totalviews);
                video.setField_(ns.TOTAL_COMMENTS, unmapped.videos[i].totalcomments);
                video.setField_(ns.TOTAL_RATING, unmapped.videos[i].totalrating);
                video.setField_(ns.TOTAL_VOTES, unmapped.videos[i].totalvotes);
                video.setField_(ns.COUNTRY, unmapped.videos[i].country);
                video.setField_(ns.LANGUAGE, unmapped.videos[i].language);
                videos.push(video);
            }
        }

        return MSID.Container.get().newCollection(videos, 0, unmapped.count);
    },

    /**
    * Maps a server response into a MyOpenSpace.Video object
    * @function
    * @return {MyOpenSpace.Video}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    * @private
    */
    mapVideo_: function(obj) {
        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }
        var video = new MSID.Container.get().newVideo();
        var ns = MyOpenSpace.Video.Field;

        if (unmapped) {
            video.setField_(ns.VIDEO_ID, unmapped.id);
            video.setField_(ns.VIDEO_URI, unmapped.videoUri);
            video.setField_(ns.TITLE, unmapped.title);
            video.setField_(ns.DATE_CREATED, unmapped.datecreated);
            video.setField_(ns.LAST_UPDATE, unmapped.dateupdated);
            video.setField_(ns.MEDIA_TYPE, unmapped.mediatype);
            video.setField_(ns.THUMB_URI, unmapped.thumbnail);
            video.setField_(ns.DESCRIPTION, unmapped.description);
            video.setField_(ns.MEDIA_STATUS, unmapped.mediastatus);
            video.setField_(ns.RUN_TIME, unmapped.runtime);
            video.setField_(ns.TOTAL_VIEWS, unmapped.totalviews);
            video.setField_(ns.TOTAL_COMMENTS, unmapped.totalcomments);
            video.setField_(ns.TOTAL_RATING, unmapped.totalrating);
            video.setField_(ns.TOTAL_VOTES, unmapped.totalvotes);
            video.setField_(ns.COUNTRY, unmapped.country);
            video.setField_(ns.LANGUAGE, unmapped.language);
        }

        return video;
    },

    /**
    * Maps a server response into a opensocial.Collection of MyOpenSpace.Person objects
    * @function
    * @return {opensocial.Collection}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    * @private
    */
    mapPeople_: function(obj) {
        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }
        var friends = [];
        var person;
        var unmappedData;


        if (typeof (unmapped.users) !== 'undefined') {
            unmappedData = [];
            for (var j in unmapped.users) {
                unmappedData[j] = {
                    "id": "myspace.com:" + unmapped.users[j].userId,
                    "nickname": unmapped.users[j].name,
                    "thumbnailUrl": unmapped.users[j].image,
                    "profileUrl": unmapped.users[j].webUri
                };
            }
        }
        else if (typeof (unmapped.entry) === 'undefined') {
            return MSID.Container.get().newCollection([], 0, 0);
        }
        else if (unmapped.entry.constructor == Array) {
            unmappedData = unmapped.entry;
        }
        else {
            unmappedData = [unmapped.entry];
        }

        var person;
        var friends = [];
        for (var i in unmappedData) {
            person = this.mapSimplePersonData_(unmappedData[i]);
            friends[i] = person;
        }
        var startIndex = 0;
        if (typeof (unmapped.startIndex) != 'undefined' && typeof (unmapped.itemsPerPage) != 'undefined') {
            startIndex = ((unmapped.startIndex - 1) * unmapped.itemsPerPage) + 1;
        }
        return MSID.Container.get().newCollection(friends, startIndex, unmapped.totalResults);
    },
    /**
    * Maps server response data object to a MyOpenSpace.Person object filling ID, Thumbnail,
    * nickname, profileUrl and name fiedls 
    * @function
    * @return {MyOpenSpace.Person}
    * @param {Object} unmappedData The response person object from the server
    * @internal
    */
    mapSimplePersonData_: function(unmappedData) {
        var isOwner = false;
        var isViewer = false;
        if (typeof (unmappedData.id) !== 'undefined') {
            var id = unmappedData.id.split(":")[1];
            var viewerId = gadgets.views.getParams().viewerId;
            var ownerId = gadgets.views.getParams().ownerId;
            isOwner = (id === ownerId);
            isViewer = (id === viewerId);
        }

        var person = new MSID.Container.get().newPerson(null, isOwner, isViewer);
        var ns = opensocial.Person.Field;
        /**
        * Generic method Populates person field specify by personField if obj is different from undefined.
        * @param {Object} personField Field to map.
        * @param {Object} obj Data to map
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapGeneric = function(personField, obj, explicitPerson) {
            if (typeof (obj) === 'undefined') return;
            var personObject = person || explicitPerson;
            personObject.setField_(personField, obj);
        };
        /**
        * Populates person HAS_APP field if hasApp parameter is present.
        * @param {Object} hasApp Value to map.
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapHasApp = function(hasApp, explicitPerson) {
            if (typeof (hasApp) === 'undefined') return;
            var personObject = person || explicitPerson;
            var hasAppObj = (('' + hasApp).toLowerCase() === 'true');
            personObject.setField_(ns.HAS_APP, hasAppObj);
        };
        /**
        * Populates person Name field with the information provided in name, familyName, givenName.
        * @param {Object} name Display name
        * @param {Object} familyName Family name
        * @param {Object} givenName Given Name
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapName = function(name, familyName, givenName, explicitPerson) {
            if (typeof (name) === 'undefined' && typeof (familyName) === 'undefined' && typeof (givenName) === 'undefined') return;
            var personObject = person || explicitPerson;
            var nameParams = {};
            if (typeof (name) !== 'undefined') nameParams[opensocial.Name.Field.UNSTRUCTURED] = name;
            if (typeof (givenName) !== 'undefined') nameParams[opensocial.Name.Field.GIVEN_NAME] = givenName;
            if (typeof (familyName) !== 'undefined') nameParams[opensocial.Name.Field.FAMILY_NAME] = familyName;
            var personName = new opensocial.Name(nameParams);
            personObject.setField_(ns.NAME, personName);
        };

        mapGeneric(ns.ID, unmappedData.id);
        mapGeneric(ns.THUMBNAIL_URL, unmappedData.thumbnailUrl);
        mapGeneric(ns.NICKNAME, unmappedData.nickname);
        mapGeneric(ns.PROFILE_URL, unmappedData.profileUrl);
        mapName(unmappedData.displayName, unmappedData.familyName, unmappedData.givenName);
        mapHasApp(unmappedData.hasApp || unmappedData.hasAppInstalled);

        return person;
    },
    /**
    * Maps a server response data object to a MyOpenSpace.Person object
    * @function
    * @return {MyOpenSpace.Person}
    * @param {Object}  The response person object from the server
    * @internal
    */
    mapPersonData_: function(unmappedData) {

        var person = this.mapSimplePersonData_(unmappedData);
        var ns = opensocial.Person.Field;
        var mns = MyOpenSpace.Person.Field;
        /**
        * Generic method Populates person field specify by personField if obj is different from undefined.
        * @param {Object} personField Field to map.
        * @param {Object} obj data to map
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapGeneric = function(personField, obj, explicitPerson) {
            if (typeof (obj) === 'undefined') return;
            var personObject = person || explicitPerson;
            personObject.setField_(personField, obj);
        };
        /**
        * Populates BODY_TYPE person field if bodyType is different from undefined.
        * @param {Object} bodyType Body type value.
        * @param {Object} explicitPerson pPerson object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapBodyType = function(bodyType, explicitPerson) {
            if (typeof (bodyType) === 'undefined') return;
            var personObject = person || explicitPerson;
            var bodyTypeParams = {};
            bodyTypeParams[opensocial.BodyType.Field.BUILD] = bodyType.build;
            bodyTypeParams[opensocial.BodyType.Field.HEIGHT] = bodyType.height;
            var bodyTypeObj = new opensocial.BodyType(bodyTypeParams);
            personObject.setField_(ns.BODY_TYPE, bodyTypeObj);
        };
        /**
        * Populates CURRENT_LOCATION  person field if currentLocation is different from undefined.
        * @param {Object} currentLocation Current location value.
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapCurrentLocation = function(currentLocation, explicitPerson) {
            if (typeof (currentLocation) === 'undefined') return;
            var personObject = person || explicitPerson;
            var addressParams = {};
            addressParams[opensocial.Address.Field.REGION] = currentLocation.region;
            addressParams[opensocial.Address.Field.POSTAL_CODE] = currentLocation.postalCode;
            addressParams[opensocial.Address.Field.COUNTRY] = currentLocation.country;
            var currentLocationObj = new opensocial.Address(addressParams);
            personObject.setField_(ns.CURRENT_LOCATION, currentLocationObj);
        };
        /**
        * Populates DATE_OF_BIRTH person field if dateOfBirth is different from undefined.
        * The date should be formatted yyyy-MM-ddTHH:mm:ss-00:00:00
        * @param {Object} dateOfBirth Formated date of birth value.
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapDateOfBirth = function(dateOfBirth, explicitPerson) {
            if (typeof (dateOfBirth) === 'undefined') return;
            var personObject = person || explicitPerson;
            var dateOfBirthObj = new Date();
            dateOfBirthObj.setDate(dateOfBirth.substr(8, 2));
            dateOfBirthObj.setMonth(dateOfBirth.substr(5, 2) - 1);
            dateOfBirthObj.setFullYear(dateOfBirth.substr(0, 4));
            dateOfBirthObj.setHours(0);
            dateOfBirthObj.setMinutes(0);
            dateOfBirthObj.setSeconds(0);
            dateOfBirthObj.setMilliseconds(0);
            personObject.setField_(ns.DATE_OF_BIRTH, dateOfBirthObj);
        };
        /**
        * Populates DRINKER person field if drinker is different from undefined.
        * @param {Object} drinker Drinker value object.
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapDrinker = function(drinker, explicitPerson) {
            if (typeof (drinker) === 'undefined') return;
            if (typeof (drinker.key) === 'undefined' && drinker.value === 'undefined') return;
            var personObject = person || explicitPerson;
            var drinkerKey = null;
            var drinkerValue = drinker.value;
            if (drinker.key !== null && typeof (drinker.key) !== 'undefined') {
                if (drinker.key.toLowerCase() === 'yes') drinkerKey = opensocial.Enum.Drinker.YES;
                else if (drinker.key.toLowerCase() === 'no') drinkerKey = opensocial.Enum.Drinker.NO;
                else if (drinker.key.toLowerCase() === 'heavily') drinkerKey = opensocial.Enum.Drinker.HEAVILY;
                else if (drinker.key.toLowerCase() === 'occasionally') drinkerKey = opensocial.Enum.Drinker.OCCASIONALLY;
                else if (drinker.key.toLowerCase() === 'quit') drinkerKey = opensocial.Enum.Drinker.QUIT;
                else if (drinker.key.toLowerCase() === 'quitting') drinkerKey = opensocial.Enum.Drinker.QUITTING;
                else if (drinker.key.toLowerCase() === 'regularly') drinkerKey = opensocial.Enum.Drinker.REGULARLY;
                else if (drinker.key.toLowerCase() === 'socially') drinkerKey = opensocial.Enum.Drinker.SOCIALLY;
            };
            personObject.setField_(ns.DRINKER, new opensocial.Enum(drinkerKey, drinkerValue));
        };
        /**
        * Populates GENDER person field if gender is different from undefined.
        * @param {Object} gender Gender value object
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapGender = function(gender, explicitPerson) {
            if (typeof (gender) === 'undefined') return;
            var personObject = person || explicitPerson;
            var genderObj;
            if (gender.toLowerCase() === "male") genderObj = opensocial.Enum.Gender.MALE;
            else if (gender.toLowerCase() === "female") genderObj = opensocial.Enum.Gender.FEMALE;
            if (typeof (genderObj) !== 'undefined')
                personObject.setField_(ns.GENDER, new opensocial.Enum(genderObj, genderObj));
        };
        /**
        * Populates JOBS person field if jobs is different from undefined.
        * @param {Object} jobs Array of jobs objects to map.
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapJobs = function(jobs, explicitPerson) {
            if (typeof (jobs) === 'undefined') return;
            var personObject = person || explicitPerson;
            var jobsObj = [];
            for (var i in jobs) {
                var organizationParams = {};
                organizationParams[opensocial.Organization.Field.NAME] = jobs[i].name;
                organizationParams[opensocial.Organization.Field.TITLE] = jobs[i].title;
                jobsObj[i] = new opensocial.Organization(organizationParams);
            }
            personObject.setField_(ns.JOBS, jobsObj);
        };
        /**
        * Populates NETWORK_PRESENCE person field if networkPresence is different from undefined.
        * @param {Object} networkPresence Network presence value object
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapNetworkPresence = function(networkPresence, explicitPerson) {
            if (typeof (networkPresence) === 'undefined') return;
            if (typeof (networkPresence.key) === 'undefined' && typeof (networkPresence.value) === 'undefined') return;
            var personObject = person || explicitPerson;
            var networkPresenceKey = null;
            var networkValue = networkPresence.value;
            if (networkPresence.key != null && typeof (networkPresence.key) !== 'undefined') {
                if (networkPresence.key.toLowerCase() === 'offline') networkPresenceKey = opensocial.Enum.Presence.OFFLINE;
                else if (networkPresence.key.toLowerCase() === 'online') networkPresenceKey = opensocial.Enum.Presence.ONLINE;
                else if (networkPresence.key.toLowerCase() === 'away') networkPresenceKey = opensocial.Enum.Presence.AWAY;
                else if (networkPresence.key.toLowerCase() === 'chat') networkPresenceKey = opensocial.Enum.Presence.CHAT;
                else if (networkPresence.key.toLowerCase() === 'dnd') networkPresenceKey = opensocial.Enum.Presence.DND;
                else if (networkPresence.key.toLowerCase() === 'xa') networkPresenceKey = opensocial.Enum.Presence.XA;
            }
            personObject.setField_(ns.NETWORK_PRESENCE, new opensocial.Enum(networkPresenceKey, networkValue));
        };
        /**
        * Populates PROFILE_SONG person field if profileSong is different from undefined.
        * @param {Object} profileSong Profile song value.
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapProfileSong = function(profileSong, explicitPerson) {
            if (typeof (profileSong) === 'undefined') return;
            var personObject = person || explicitPerson;
            var urlParams = {};
            urlParams[opensocial.Url.Field.LINK_TEXT] = profileSong;
            var urlObj = new opensocial.Url(urlParams);
            personObject.setField_(ns.PROFILE_SONG, urlObj);
        };
        /**
        * Populates LOOKING_FOR person field if lookingFor is different from undefined.
        * @param {Object} lookingFor Looking for value.
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapLookingFor = function(lookingFor, explicitPerson) {
            if (typeof (lookingFor) === 'undefined') return;
            var personObject = person || explicitPerson;
            personObject.setField_(ns.LOOKING_FOR, new opensocial.Enum(null, lookingFor));
        };
        /**
        * Populates SMOKER person field if smoker is different from undefined.
        * @param {Object} smoker smoker value object.
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapSmoker = function(smoker, explicitPerson) {
            if (typeof (smoker) === 'undefined') return;
            if (typeof (smoker.key) === 'undefined' && somoker.value === 'undefined') return;
            var personObject = person || explicitPerson;
            var smokerKey = null;
            var smokerValue = smoker.value;
            if (smoker.key !== null && typeof (smoker.key) !== 'undefined') {
                if (smoker.key.toLowerCase() === 'heavily') smokerKey = opensocial.Enum.Smoker.HEAVILY;
                else if (smoker.key.toLowerCase() === 'no') smokerKey = opensocial.Enum.Smoker.NO;
                else if (smoker.key.toLowerCase() === 'occasionally') smokerKey = opensocial.Enum.Smoker.OCCASIONALLY;
                else if (smoker.key.toLowerCase() === 'quit') smokerKey = opensocial.Enum.Smoker.QUIT;
                else if (smoker.key.toLowerCase() === 'quitting') smokerKey = opensocial.Enum.Smoker.QUITTING;
                else if (smoker.key.toLowerCase() === 'regularly') smokerKey = opensocial.Enum.Smoker.REGULARLY;
                else if (smoker.key.toLowerCase() === 'socially') smokerKey = opensocial.Enum.Smoker.SOCIALLY;
                else if (smoker.key.toLowerCase() === 'yes') smokerKey = opensocial.Enum.Smoker.YES;
            }

            personObject.setField_(ns.SMOKER, new opensocial.Enum(smokerKey, smokerValue));
        };
        /**
        * Populates URLS person field if urls is different from undefined.
        * @param {Object} urls Array of urls objects.
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapUrls = function(urls, explicitPerson) {
            if (typeof (urls) === 'undefined') return;
            var personObject = person || explicitPerson;
            var urlsObj = [];
            for (var i in urls) {
                var urlParams = {};
                urlParams[opensocial.Url.Field.ADDRESS] = urls[i].value;
                urlParams[opensocial.Url.Field.TYPE] = urls[i].type;
                urlsObj[i] = new opensocial.Url(urlParams);
            }
            personObject.setField_(ns.URLS, urlsObj);
        };
        /**
        * Populates the MySpace LARGE_IMAGE and MEDIUM_IMAGE extensions fields if photos is different from undefined.
        * @param {Object} photos photos value object.
        * @param {Object} explicitPerson Person object to use. If undefined the default person object will be used.
        * @internal
        */
        var mapPhotos = function(photos, explicitPerson) {
            if (typeof (photos) === 'undefined') return;
            var personObject = person || explicitPerson;
            var image;
            var urlParams;
            for (var i in photos) {
                var type = photos[i].type;
                var val = photos[i].value;
                if (type === 'medium' && typeof (val) !== 'undefined') {
                    urlParams = {};
                    urlParams[opensocial.Url.Field.ADDRESS] = val;
                    urlParams[opensocial.Url.Field.TYPE] = 'medium image';
                    personObject.setField_(mns.MEDIUM_IMAGE, new opensocial.Url(urlParams));
                }
                else if (type === 'large' && typeof (val) !== 'undefined') {
                    urlParams = {};
                    urlParams[opensocial.Url.Field.ADDRESS] = val;
                    urlParams[opensocial.Url.Field.TYPE] = 'large image';
                    personObject.setField_(mns.LARGE_IMAGE, new opensocial.Url(urlParams));
                }
            }
        }

        var addressns = opensocial.Address.Field;
        mapGeneric(ns.ABOUT_ME, unmappedData.aboutMe);
        mapGeneric(ns.AGE, unmappedData.age);
        mapBodyType(unmappedData.bodyType);
        mapGeneric(ns.BOOKS, unmappedData.books);
        mapGeneric(ns.CHILDREN, unmappedData.children);
        mapCurrentLocation(unmappedData.currentLocation);
        mapDateOfBirth(unmappedData.dateOfBirth);
        mapDrinker(unmappedData.drinker);
        mapGeneric(ns.ETHNICITY, unmappedData.ethnicity);
        mapGender(unmappedData.gender);

        mapGeneric(ns.HEROES, unmappedData.heroes);

        mapGeneric(ns.INTERESTS, unmappedData.interests);
        mapJobs(unmappedData.organizations);
        mapGeneric(ns.MOVIES, unmappedData.movies);
        mapGeneric(ns.MUSIC, unmappedData.music);
        mapNetworkPresence(unmappedData.networkPresence);

        mapLookingFor(unmappedData.lookingFor);
        mapProfileSong(unmappedData.profileSong);

        mapGeneric(ns.RELATIONSHIP_STATUS, unmappedData.relationshipStatus);
        mapGeneric(ns.RELIGION, unmappedData.religion);
        mapGeneric(ns.SEXUAL_ORIENTATION, unmappedData.sexualOrientation);
        mapSmoker(unmappedData.smoker);
        mapGeneric(ns.STATUS, unmappedData.status);

        mapGeneric(ns.TV_SHOWS, unmappedData.tvShows);
        mapUrls(unmappedData.urls);
        //MySpace Extensions
        mapPhotos(unmappedData.photos);

        return person;
    },
    /**
    * Maps a server response into a MyOpenSpace.Person object
    * @function
    * @return {MyOpenSpace.Person}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    */
    mapPerson_: function(obj) {
        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }

        if (typeof (unmapped) === 'undefined') return null;
        if (false === unmapped && !obj.responseText && Object === obj.constructor) unmapped = obj;
        if (typeof (unmapped.entry) === 'undefined' && typeof (unmapped.users) === 'undefined') return null;
        if (typeof (unmapped.users) !== 'undefined') {
            unmappedData = {
                "id": "myspace.com:" + unmapped.users[0].userId,
                "nickname": unmapped.users[0].name,
                "thumbnailUrl": unmapped.users[0].image,
                "displayName": unmapped.users[0].name,
                "profileUrl": unmapped.users[0].webUri
            };
        }
        else {
            unmappedData = unmapped.entry[0] || unmapped.entry;
        }

        return this.mapPersonData_(unmappedData);
    },

    /**
    * Maps a server response into a opensocial.Collection of opensocial.Activity objects
    * @function
    * @return {opensocial.Collection}
    * @param {XMLHttpRequest} obj The response from the server
    * @internal
    */
    mapActivities_: function(obj) {
        if (!obj) {
            return null;
        }

        if (obj && (!obj.responseText || obj.responseText.length < 1)) {
            return MSID.Container.get().newCollection([], 0, 0);
        }

        try {
            var unmapped = gadgets.json.parse(obj.responseText);
        }
        catch (err) {
            return null;
        }

        if (!unmapped) {
            return null;
        }

        var activities = [];
        var activity;
        var ns = opensocial.Activity.Field;

        if (unmapped.entry) {
            for (var i = 0; i < unmapped.entry.length; i++) {
                activity = new MSID.newActivity();
                activity.setField(ns.APP_ID, unmapped.entry[i].appId);
                activity.setField(ns.USER_ID, unmapped.entry[i].userId);
                activity.setField(ns.BODY, unmapped.entry[i].body);
                activity.setField(ns.POSTED_TIME, unmapped.entry[i].postedTime);
                activity.setField(ns.STREAM_FAVICON_URL, unmapped.entry[i].streamFavIconUrl);
                activity.setField(ns.TITLE, unmapped.entry[i].title);
                activity.setField(ns.TITLE_ID, unmapped.entry[i].titleId);
                activities.push(activity);
            }
        }
        return MSID.Container.get().newCollection(activities, 0, unmapped.count);
    }
};/**
 * Just a place-holder in order to extend MyOpenSpace.PostTo
 * @constructor
 */
MyOpenSpace.PostTo = {};

/**
 * Enumerates the targets for PostTo
 * @static
 * @class
 * @internal
 * @name MyOpenSpace.PostTo.Targets
 */
MyOpenSpace.PostTo.Targets = {
    /**
     * A user's profile, the specific section is specified on the Post To UI
     * @memberOf MyOpenSpace.PostTo.Targets
     */
    PROFILE:"PROFILE",
    
    /**
     * A message to another user's inbox
     * @memberOf MyOpenSpace.PostTo.Targets
     */
    SEND_MESSAGE:"SEND_MESSAGE",
    
    /**
     * A user's comments
     * @memberOf MyOpenSpace.PostTo.Targets
     */
    COMMENTS:"COMMENTS",
    
    /**
     * A user's bulletins
     * @memberOf MyOpenSpace.PostTo.Targets
     */
    BULLETINS:"BULLETINS",
    
    /**
     * A user's blog
     * @memberOf MyOpenSpace.PostTo.Targets
     */
    BLOG:"BLOG",

    /**
     * Request to share an app with another user
     * @memberOf MyOpenSpace.PostTo.Targets
     */
    SHARE_APP:"SHARE_APP",
    
    /**
     * Request to send a one to many notification
     * @memberOf MyOpenSpace.PostTo.Targets
     */
    ACTIVITY: "ACTIVITY",
    
    /**
    * Request to upload photos
    * @memberOf MyOpenSpace.PostTo.Targets
    */
    PHOTOS: "PHOTOS"
};


/**
 * Enumerates the PostTo result
 * @static
 * @class
 * @name MyOpenSpace.PostTo.Result
 */
MyOpenSpace.PostTo.Result = {
    /**
     * An error occurred in postTo request
     * @memberOf MyOpenSpace.PostTo.Result
     */
	ERROR: -1,
	
    /**
     * User pressed cancel before completion
     * @memberOf MyOpenSpace.PostTo.Result
     */
	CANCELLED: 0,
	
    /**
     * Successful postTo request completed
     * @memberOf MyOpenSpace.PostTo.Result
     */
	SUCCESS: 1
};
/**
 * A class representing a video.
 * @constructor
 * @private
 */
MyOpenSpace.Video = function() {};

/**
 * The fields for MyOpenSpace.Video
 * @class
 * @name MyOpenSpace.Video.Field
 * @static
 */
MyOpenSpace.Video.Field = {
    /**
     * A number representing a Video's unique identifier.
     * @memberOf MyOpenSpace.Video.Field
     */
    VIDEO_ID:"VIDEO_ID",
    
    /**
     * The RESTFUL URI with which to access the video on the API.
     * @memberOf MyOpenSpace.Video.Field
     */
    VIDEO_URI:"VIDEO_URI",
    
    /**
     * The video's title.
     * @memberOf MyOpenSpace.Video.Field
     */
    TITLE:"TITLE",
    
    /**
     * The date the video was created.
     * @memberOf MyOpenSpace.Video.Field
     */
    DATE_CREATED:"DATE_CREATED",
    
    /**
     * The date the video was last updated.
     * @memberOf MyOpenSpace.Video.Field
     */
    LAST_UPDATE:"LAST_UPDATE",
    
    /**
     * An integer representing the media type for this video.
     * @memberOf MyOpenSpace.Video.Field
     */
    MEDIA_TYPE:"MEDIA_TYPE",
    
    /**
     * A URL for the thumbnail image associated with this video.
     * @memberOf MyOpenSpace.Video.Field
     */
    THUMB_URI:"THUMB_URI",
    
    /**
     * A description of the video.
     * @memberOf MyOpenSpace.Video.Field
     */
    DESCRIPTION:"DESCRIPTION",
    
    /**
     * The current status of the video, such as "ProcessingFailed"
     * @memberOf MyOpenSpace.Video.Field
     */
    MEDIA_STATUS:"MEDIA_STATUS",
    
    /**
     * The length of the video.
     * @memberOf MyOpenSpace.Video.Field
     */
    RUN_TIME:"RUN_TIME",
    
    /**
     * An integer representing the number of views the video has currently received.
     * @memberOf MyOpenSpace.Video.Field
     */
    TOTAL_VIEWS:"TOTAL_VIEWS",
    
    /**
     * An integer representing the number of comments the video has currently received.
     * @memberOf MyOpenSpace.Video.Field
     */
    TOTAL_COMMENTS:"TOTAL_COMMENTS",
    
    /**
     * An integer representing the rating the video has currently received.
     * @memberOf MyOpenSpace.Video.Field
     */
    TOTAL_RATING:"TOTAL_RATING",
    
    /**
     * An integer representing the number of votes the video has currently received.
     * @memberOf MyOpenSpace.Video.Field
     */
    TOTAL_VOTES:"TOTAL_VOTES",
    
    /**
     * A string representing the video's country, in terms of identifying culture, not necessarily geographic location.
     * @memberOf MyOpenSpace.Video.Field
     */
    COUNTRY:"COUNTRY",
    
    /**
     * A string representing the video's language.
     * @memberOf MyOpenSpace.Video.Field
     */
    LANGUAGE:"LANGUAGE"
};

/**
 * Returns the field specified by key
 * @param {String} key The key to search by
 * @return {MyOpenSpace.Video || undefined} The video if found, nothing otherwise.
 */
MyOpenSpace.Video.prototype.getField = function(key) { return this[key]; };

/**
 * Writes a value to the field specified by the key
 * @param {String} key The key to search by.
 * @param {String} val The value to set.
 * @private
 * @internal
 */
MyOpenSpace.Video.prototype.setField_ = function(key,val) { this[key] = val; };/* ================================================================
 * MyOpenSpace.Album
 * ================================================================
 */
 
 /**
 * A class representing an album.
 * @constructor
 * @private
 * @name MyOpenSpace.Album
 */
MyOpenSpace.Album = function() {};
/**
 * The fields for MyOpenSpace.Album
 * @class
 * @name MyOpenSpace.Album.Field
 * @static
 */
MyOpenSpace.Album.Field = {
    /**
     * A number representing an Album's unique identifier.
     * @memberOf MyOpenSpace.Album.Field
     */
    ALBUM_ID:"ALBUM_ID",
    
    /**
     * The RESTFUL URI with which to access the album on the API.
     * @memberOf MyOpenSpace.Album.Field
     */
    ALBUM_URI:"ALBUM_URI",
    
    /**
     * The album's title.
     * @memberOf MyOpenSpace.Album.Field
     */
    TITLE:"TITLE",
    
    /**
     * The geographic location where the album's pictures were taken.
     * @memberOf MyOpenSpace.Album.Field
     */
    LOCATION:"LOCATION",
    
    /**
     * A URL for the album's default image.
     * @memberOf MyOpenSpace.Album.Field
     */
    DEFAULT_IMAGE:"DEFAULT_IMAGE",
    
    /**
     * A string representing the album's privacy setting, such as "Public" or "Private"
     * @memberOf MyOpenSpace.Album.Field
     */
    PRIVACY:"PRIVACY",
    
    /**
     * An integer representing the total number of photos in the album (not the number of photos actually contained within the current object).
     * @memberOf MyOpenSpace.Album.Field
     */
    PHOTO_COUNT:"PHOTO_COUNT",
    
    /**
     * A RESTFUL URI with which to access the photos contained in the album.
     * @memberOf MyOpenSpace.Album.Field
     */
    PHOTOS_URI:"PHOTOS_URI"
};

/**
 * Returns the field specified by key
 * @param {String} key The key to search by
 * @return {MyOpenSpace.Album || undefined} The album if found, nothing otherwise.
 */
MyOpenSpace.Album.prototype.getField = function(key) { return this[key]; };
/**
 * Writes a value to the field specified by the key
 * @param {String} key The key to search by.
 * @param {String} val The value to set.
 * @private
 * @internal
 */
MyOpenSpace.Album.prototype.setField_ = function(key,val) { this[key] = val; };/**
 * A class representing a photo.
 * @constructor
 * @private
 */
MyOpenSpace.Photo = function() {};

/**
 * The fields for MyOpenSpace.Photo
 * @class
 * @name MyOpenSpace.Photo.Field
 * @static
 */
MyOpenSpace.Photo.Field = {
    /**
     * A number representing a Photo's unique identifier.
     * @memberOf MyOpenSpace.Photo.Field
     */
    PHOTO_ID:"PHOTO_ID",
    
    /**
     * The RESTFUL URI with which to access the photo on the API.
     * @memberOf MyOpenSpace.Photo.Field
     */
    PHOTO_URI:"PHOTO_URI",
      
    /**
     * The URL of the photo.
     * @memberOf MyOpenSpace.Photo.Field
     */
    IMAGE_URI:"IMAGE_URI",
    
    /**
     * The photo's caption.
     * @memberOf MyOpenSpace.Photo.Field
     */
    CAPTION:"CAPTION"
};

/**
 * Returns the field specified by key
 * @param {String} key The key to search by
 * @return {MyOpenSpace.Photo || undefined} The photo if found, nothing otherwise.
 */
MyOpenSpace.Photo.prototype.getField = function(key) { return this[key]; };

/**
 * Writes a value to the field specified by the key
 * @param {String} key The key to search by.
 * @param {String} val The value to set.
 * @private
 * @internal
 */
MyOpenSpace.Photo.prototype.setField_ = function(key,val) { this[key] = val; };//------------------------------------------//
// Non namespaced global functions below
//------------------------------------------//

/**
 * @ignore
 */
Function.prototype.inherits = function(parentCtor) {
  function tempCtor() {};
  tempCtor.prototype = parentCtor.prototype;
  this.superClass_ = parentCtor.prototype;
  this.prototype = new tempCtor();
  this.prototype.constructor = this;
};

/** 
* @ignore 
* debugging utilities - reflect through an object's properties and trace values
*/
function reflect(obj, opt_dept, opt_currentDept){
	opt_dept = (opt_dept == undefined) ? 0 : opt_dept;
	opt_currentDept = (opt_currentDept == undefined) ? 0 : opt_currentDept;
	//trace(obj);
	if (typeof(obj) == "object"){
		for (var i in obj){
			var levelPrefix = '';
			for (var j = 0; j < opt_currentDept + 1; j++){
				levelPrefix += "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
			}
			trace( levelPrefix + i + " -> " + obj[i] + "\n");
			if (typeof(obj[i]) == "object"){
				if (opt_dept > opt_currentDept){
						reflect(obj[i], opt_dept, opt_currentDept + 1);
				}
			}
		}
	}
}

var batchTrace = "";
/**
 * @ignore
 */
function tracePublicMembers(obj, propertyDepth) {
    propertyDepth = propertyDepth || 0;
    for (var prop in obj) {
        if (typeof(obj[prop]) == "function") return;
       
        if (typeof(obj[prop]) == "object")
        {
            traceIndent(propertyDepth);
            dumpToBatch("<b>" + prop + "</b><BR>", propertyDepth);
            tracePublicMembers(obj[prop], propertyDepth+1);
        }
        else
        {
            if (prop.substring(prop.length-3) != "___") {
                 traceIndent(propertyDepth);
                 dumpToBatch("<i>" + prop + "</i>: " + obj[prop] + "<BR>", propertyDepth);
                 }
        }
    }
    if (propertyDepth == 0) flushBatchTrace();
}

/**
 * @ignore
 */
function flushBatchTrace() {
    trace(batchTrace);
    batchTrace="";
}
/**
 * @ignore
 */
function dumpToBatch(msg, depth) {
    if (depth == 0) {
        batchTrace = msg + batchTrace;
    }
    else
    {
        batchTrace += msg;
    }
}
/**
 * @ignore
 */
function traceIndent(depth) {
    if (depth>0) {
        dumpToBatch("|", depth);
        for (var i = 0;i<depth;i++) dumpToBatch("----", depth);
        dumpToBatch(">", depth); 
    }
}

var debugMessageDiv;
var debugElementEnable;

/**
 * @ignore
 */
function trace(msg, opt_no_break) {
    if (typeof (debugElementEnable) === 'undefined') {
        debugMessageDiv = document.getElementById('debugMessages');
        if (debugMessageDiv) {
            debugElementEnable = true;
        }
        else {
            debugElementEnable = false;
        }
    }

    if (debugElementEnable) {
         opt_no_break = (opt_no_break) ? "" : "<BR>";
         debugMessageDiv.innerHTML += opt_no_break + msg;  
    }
}

/**
* @ignore
*/
function jsonp(url, name, query, allowCaching) {
    if (url.indexOf("?") > -1)
        url += "&jsonp="
    else
        url += "?jsonp="
    url += name + "&";
    if (query)
        url += encodeURIComponent(query);
    if (allowCaching === false){
		url += "&";
    	url += new Date().getTime().toString(); // prevent caching        		
	}

    var script = document.createElement("script");
    script.setAttribute("src", url);
    script.setAttribute("type", "text/javascript");
    document.getElementsByTagName("head")[0].appendChild(script);
}

/**
 * Borrowed from prototype
 * @ignore
 */
var Try = {
    /**
     * @function
     * @param Array of statements to evaluate for returnValues.
     * @ignore
     */
  these: function() {
    var returnValue;

    for (var i = 0, length = arguments.length; i < length; i++) {
      var lambda = arguments[i];
      try {
        returnValue = lambda();
        break;
      } catch (e) {}
    }

    return returnValue;
  }
};


function parseUrl(url){
	var exp = /^(?:(\w+):\/\/(.+?)(?:\x3A(\d+))?)?(\/.*?)?(?:\?(.*?))?(?:\#(.*))?$/
	var match = exp.exec(url);
	if (match != null){
		var scheme = match[1];
		var domain = match[2];
		var port = match[3];
		var path = match[4];
		var query = match[5];
		var fragment = match[6];
		var items =[];

		if (typeof query !== 'undefined') {
			var params = query.split("&");
			for (var i = 0; i < params.length;  i++) {
				var item = params[i];
				var keyValue = item.split('=');
				items.push({
					"key": keyValue[0],
					"value": keyValue[1]
				});
			}
		}
		return {
			"scheme" : scheme,
			"domain" : domain,
			"port" : port,
			"path" : path,
			"query" : query,
			"queryValues" : items,
			"fragment" : fragment
		}
	}
	else{
		return null;
	}

}

/**
* API cache hashtable, used for REST calls to API only
* @class
* @internal
* @static
*/
var APICache = {
    /**
    * TODO Document
    */
    init: function() {
        if (this.cache == null) {
            this.cache = new MyOpenSpace.Hash();
        }
    },
    /**
    * TODO Document
    */
    cachableTypes: {
        'FETCH_PERSON': { cachable: true, defaultTTL: 600 },
        'FETCH_PEOPLE': { cachable: true, defaultTTL: 600 },
        'FETCH_INDICATORS': { cachable: false, defaultTTL: 0 },
        'FETCH_PERSON_STATUS': { cachable: false, defaultTTL: 0 },
        'FETCH_PERSON_MOOD': { cachable: false, defaultTTL: 0 },
        'FETCH_PERSON_FRIENDSHIP': { cachable: false, defaultTTL: 0 },
        'FETCH_PEOPLE_FRIENDSHIP': { cachable: false, defaultTTL: 0 },
        'FETCH_PHOTO': { cachable: false, defaultTTL: 0 },
        'FETCH_PHOTOS': { cachable: false, defaultTTL: 0 },
        'FETCH_ALBUM': { cachable: false, defaultTTL: 0 },
        'FETCH_ALBUMS': { cachable: false, defaultTTL: 0 },
        'FETCH_VIDEO': { cachable: false, defaultTTL: 0 },
        'FETCH_VIDEOS': { cachable: false, defaultTTL: 0 },
        'FETCH_PERSON_DATA': { cachable: false, defaultTTL: 0 },
        'UPDATE_PERSON_DATA': { cachable: false, defaultTTL: 0 },
        'REMOVE_PERSON_DATA': { cachable: false, defaultTTL: 0 },
        'FETCH_ACTIVITIES': { cachable: false, defaultTTL: 0 },
        'INSERT_NOTIFICATION': { cachable: false, defaultTTL: 0 }
    },
    getUniqueKey: function(endPoint) {
        var urlComponents = parseUrl(endPoint);
        if (urlComponents === null) return null;

        var key = urlComponents.path;
        for (var i = 0; i < urlComponents.queryValues.length; i++) {
            var keyValue = urlComponents.queryValues[i];
            if (keyValue.key === 'ts') continue;
            key += "&" + keyValue.key + "=" + keyValue.value;
        }
        return key;
    },
    add: function(endPoint, value, type) {
        if (MyOpenSpace.EnableClientCache !== true) { return; }
        if (typeof endPoint !== 'string' ||
			typeof value === 'undefined' || value === null ||
			typeof type !== 'string') return;

        cacheInfo = this.cachableTypes[type];
        //We just cache cachable types.
        if (typeof cacheInfo === 'undefined' || cacheInfo.cachable !== true) return;

        var key = APICache.getUniqueKey(endPoint);

        this.cache[key] = {
            'value': value,
            'timestamp': (new Date()).getTime(),
            'defaultTTL': cacheInfo.defaultTTL
        };

    },
    /**
    * TODO Document
    */
    isCached: function(endPoint) {
        if (MyOpenSpace.EnableClientCache !== true) { return false; }
        if (typeof endPoint !== 'string') return false;
        var key = APICache.getUniqueKey(endPoint);
        return "undefined" !== typeof (this.cache[key]);
    },
    /**
    * TODO Document
    */
    retrieve: function(endPoint) {
        if (MyOpenSpace.EnableClientCache !== true) { return undefined; }
        if (typeof endPoint !== 'string') return undefined;
        var key = APICache.getUniqueKey(endPoint);
        return this.cache[key].value;
    },
    /**
    * TODO Document
    */
    isExpired: function(endPoint, timeToLive) {
        if (MyOpenSpace.EnableClientCache !== true) { return true; }
        if (typeof endPoint !== 'string') return true;

        var key = APICache.getUniqueKey(endPoint);
        if (!this.isCached(endPoint)) return true;

        var val = this.cache[key];

        var ttl = typeof timeToLive !== 'number' ? val.defaultTTL : timeToLive;

        if (ttl === 0) return false;

        ttl = ttl * 1000;

        var lifespan = new Date().getTime() - val.timestamp;
        return (lifespan > ttl) ? true : false;
    }
};MyOpenSpace.EndPoint = function(userInfo, osToken, osMode) {

	var self = this;

	self.Server = {
		Localhost: 'http://localhost',
		Development: "http://local-api.myspace.com",
		Production: "http://{SUBDOMAIN}api.myspace.com"
	};
	self.ServerApiMySpace = {
		Localhost: "http://localhost",
		Development: "http://local-api.myspace.com",
		Production: "http://api.myspace.com"
	};
	self.People = {
		Viewer: "/myspaceid/v1/users/{VIEWER}",
		Owner: "/myspaceid/v1/users/{OWNER}",
		ID: "/myspaceid/v1/users/{PERSON_ID}",
		ViewerFriends: "/myspaceid/v1/users/{VIEWER}/friends",
		OwnerFriends: "/myspaceid/v1/users/{OWNER}/friends"
	};
	self.Person = {
		Viewer: "/myspaceid/v1/users/{VIEWER}",
		Owner: "/myspaceid/v1/users/{OWNER}",
		ID: "/myspaceid/v1/users/{PERSON_ID}"
	};
	self.Indicators = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/indicators",
		Owner: "/myspaceid/v1/users/{OWNER}/indicators"
	};
	self.Friendship = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/friendship/{PERSON_IDS}",
		Owner: "/myspaceid/v1/users/{OWNER}/friendship/{PERSON_IDS}"
	};
	self.PersonStatus = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/status",
		Owner: "/myspaceid/v1/users/{OWNER}/status"
	};
	self.PersonMood = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/mood",
		Owner: "/myspaceid/v1/users/{OWNER}/mood"
	};
	self.Albums = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/albums",
		Owner: "/myspaceid/v1/users/{OWNER}/albums"
	};
	self.Album = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/albums/{ALBUM_ID}",
		Owner: "/myspaceid/v1/users/{OWNER}/albums/{ALBUM_ID}"
	};
	self.Videos = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/videos",
		Owner: "/myspaceid/v1/users/{OWNER}/videos"
	};
	self.Video = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/videos/{VIDEO_ID}",
		Owner: "/myspaceid/v1/users/{OWNER}/videos/{VIDEO_ID}"
	};
	self.Photos = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/photos",
		Owner: "/myspaceid/v1/users/{OWNER}/photos"
	};
	self.AlbumPhotos = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/albums/{ALBUM_ID}/photos",
		Owner: "/myspaceid/v1/users/{OWNER}/albums/{ALBUM_ID}/photos"
	};
	self.Photo = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/photos/{PHOTO_ID}",
		Owner: "/myspaceid/v1/users/{OWNER}/photos/{PHOTO_ID}"
	};
	self.Permissions = {
		Viewer: "/v1/users/{PERSON_ID}/apps.jsnp",
		Owner: "/v1/users/{PERSON_ID}/apps.jsnp"
	};
	self.Activities = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/activities/app",
		Owner: "/myspaceid/v1/users/{OWNER}/activities/app",
		ViewerFriends: "/myspaceid/v1/users/{VIEWER}/friends/activities/app",
		OwnerFriends: "/myspaceid/v1/users/{OWNER}/friends/activities/app",
		ViewerInsert: "/myspaceid/v1/users/{VIEWER}/activities/app"
	};
	self.Notification = {
		//ViewerInsert: "/api-v2.svc/json/messages/@me/@notifications",
		OwnerInsert: "/myspaceid/v1/users/{SENDER_ID}/notifications"
	};
	self.PersonAppData = {
		Viewer: "/myspaceid/v1/users/{VIEWER}/appdata{KEYS}",
		ViewerFriends: "/myspaceid/v1/users/{VIEWER}/friends/appdata{KEYS}",
		Owner: "/myspaceid/v1/users/{OWNER}/appdata{KEYS}",
		OwnerFriends: "/myspaceid/v1/users/{OWNER}/friends/appdata{KEYS}",
		Global: "/myspaceid/v1/users/{VIEWER}/appdata/global{KEYS}"
	};

	self.CommonQueryString = "ts={TIME_STAMP}";
	self.DetailQueryString = "&fields={DETAIL_TYPE}";
	self.PagingQueryString = "&page={PAGE}&page_size={SIZE}";
	self.PagingQueryStringV2 = "&startIndex={PAGE}&count={SIZE}";
	self.FilterQueryString = "&filterBy={FILTER}";
	self.SortQueryString = "&sortBy={SORT_BY}&sortOrder={SORT_ORDER}";
	self.FormatQueryString = "&format=JSON";

	if (osToken === undefined || osToken === null) return;

	var server = MSID.baseUrl;
	var timeStamp = new Date().getTime();
	var params = gadgets.views.getParams();
	var ownerId = MyOpenSpace.PrefetchParameters.getParam("ownerid");
	var viewerId = MyOpenSpace.PrefetchParameters.getParam("viewerId");

	if ("undefined" === typeof (ownerId) && params) { ownerId = params.ownerId; }
	if ("undefined" === typeof (viewerId) && params) { viewerId = params.viewerId; }

	//endPoint.CommonQueryString = endPoint.CommonQueryString.replace("{OS_MODE}", osMode.getName()).replace("{TIME_STAMP}", timeStamp);
	self.CommonQueryString = self.CommonQueryString.replace("{TIME_STAMP}", timeStamp);

	self.Person.Viewer = server + self.Person.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString + self.DetailQueryString + self.FormatQueryString;
	self.Person.Owner = server + self.Person.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString + self.DetailQueryString + self.FormatQueryString;

	self.People.ID = server + self.People.ID + "?" + self.CommonQueryString + self.FormatQueryString;
	self.People.Viewer = server + self.People.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString + self.FormatQueryString;
	self.People.Owner = server + self.People.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString + self.FormatQueryString;
	self.People.ViewerFriends = server + self.People.ViewerFriends.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString + self.PagingQueryStringV2 + self.FormatQueryString;
	self.People.OwnerFriends = server + self.People.OwnerFriends.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString + self.PagingQueryStringV2 + self.FormatQueryString;

	self.Indicators.Viewer = server + self.Indicators.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString;
	self.Indicators.Owner = server + self.Indicators.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString;
	self.PersonStatus.Viewer = server + self.PersonStatus.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString;
	self.PersonStatus.Owner = server + self.PersonStatus.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString;
	self.PersonMood.Viewer = server + self.PersonMood.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString;
	self.PersonMood.Owner = server + self.PersonMood.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString;
	self.Friendship.Viewer = server + self.Friendship.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString;
	self.Friendship.Owner = server + self.Friendship.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString;

	self.Albums.Viewer = server + self.Albums.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString + self.PagingQueryString;
	self.Albums.Owner = server + self.Albums.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString + self.PagingQueryString;
	self.Album.Viewer = server + self.Album.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString;
	self.Album.Owner = server + self.Album.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString;
	self.Videos.Viewer = server + self.Videos.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString + self.PagingQueryString;
	self.Videos.Owner = server + self.Videos.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString + self.PagingQueryString;
	self.Video.Viewer = server + self.Video.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString;
	self.Video.Owner = server + self.Video.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString;
	self.Photos.Viewer = server + self.Photos.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString + self.PagingQueryString;
	self.Photos.Owner = server + self.Photos.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString + self.PagingQueryString;
	self.Photo.Viewer = server + self.Photo.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString;
	self.Photo.Owner = server + self.Photo.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString;
	self.AlbumPhotos.Viewer = server + self.AlbumPhotos.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString + self.PagingQueryString;
	self.AlbumPhotos.Owner = server + self.AlbumPhotos.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString + self.PagingQueryString;
	self.Activities.Viewer = server + self.Activities.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString,
        self.Activities.Owner = server + self.Activities.Owner.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString,
        self.Activities.ViewerFriends = server + self.Activities.ViewerFriends.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString,
        self.Activities.OwnerFriends = server + self.Activities.OwnerFriends.replace("{OWNER}", ownerId) + "?" + self.CommonQueryString,
        self.Activities.ViewerInsert = server + self.Activities.ViewerInsert.replace("{PERSON_ID}", ownerId) + "?" + self.CommonQueryString,
        self.Notification.OwnerInsert = server + self.Notification.OwnerInsert.replace("{SENDER_ID}", ownerId) + "?" + self.CommonQueryString,
        self.PersonAppData.Viewer = server + self.PersonAppData.Viewer.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString;
	self.PersonAppData.ViewerFriends = server + self.PersonAppData.ViewerFriends.replace("{VIEWER}", viewerId) + "?" + self.CommonQueryString;
	self.PersonAppData.Owner = server + self.PersonAppData.Owner.replace("{OWNER}", userInfo.OWNER) + "?" + self.CommonQueryString;
	self.PersonAppData.OwnerFriends = server + self.PersonAppData.OwnerFriends.replace("{OWNER}", userInfo.OWNER) + "?" + self.CommonQueryString;
	self.PersonAppData.Global = server + self.PersonAppData.Global + "?" + self.CommonQueryString;

};


/**  
* DataRequest is the internal MySpace implementation of an OpenSocial DataRequest
* @constructor Creates a new DataRequest factory and execution model
* @param {String} osToken OpenSocial token
* @param {MyOpenSpace.EndPoint} endPoint MyOpenSpace.EndPoint object for service endpoints
* @class
* @name MyOpenSpace.DataRequest
* 
*/

MyOpenSpace.DataRequest = function(osToken, endPoint, opt_consumerKey) {
    this.osToken_ = osToken;
    this.endPoint_ = endPoint;
    if(opt_consumerKey)
        this.consumerKey_ = opt_consumerKey;
    
    this.requestProcessor_ = new MyOpenSpace.RequestProcessor_();
    this.requestObjects_ = new MyOpenSpace.Hash();
    this.requestObjectCount_ = 0;
    this.busy_ = false;
    this.authTemplate_ = null;

};

MyOpenSpace.DataRequest.prototype = new opensocial.DataRequest();


/**
* Enumerates the extended filter types
* @static
* @class
* @name MyOpenSpace.DataRequest.FilterType
*/
MyOpenSpace.DataRequest.FilterType = {
    /**
    * The friends that are currently online.
    * @memberOf MyOpenSpace.DataRequest.FilterType
    */
    ONLINE_FRIENDS: "ONLINE_FRIENDS"
};

/**
* Enumerates the extended sort order types
* @static
* @class
* @name MyOpenSpace.DataRequest.SortOrder
*/
MyOpenSpace.DataRequest.SortOrder = {
    /**
    * The friends that are currently online.
    * @memberOf MyOpenSpace.DataRequest.FilterType
    */
    ID: "ID"
};

/**
* Enumerates cache control mechanism for API calls
* @static
* @class
* @name MyOpenSpace.DataRequest.CacheControl
*/
MyOpenSpace.DataRequest.CacheControl = {
    /**
    * Enable/disable the cache layer - boolean
    * @memberOf MyOpenSpace.DataRequest.CacheControl
    */
    USE_CACHE: "USE_CACHE",
    /**
    * Specify a TTL for the item in cache - specified in seconds: 0 means no expiration
    * @memberOf MyOpenSpace.DataRequest.CacheControl
    */
    REFRESH_INTERVAL: "REFRESH_INTERVAL"
};

/**
* Enumerates the request fields used by the Photo entity
* @static
* @class
* @name MyOpenSpace.DataRequest.PhotoRequestFields
*/
MyOpenSpace.DataRequest.PhotoRequestFields = {
    /**
    * Used to filter a photos request by album id, returns all photos for a particular album.
    * @memberOf MyOpenSpace.DataRequest.PhotoRequestFields
    */
    ALBUM_ID: "ALBUM_ID"
};

/**
* Holds some commonly used constants
* @static
* @class
* @internal
* @name MyOpenSpace.DataRequest.Constants
*/
MyOpenSpace.DataRequest.Constants = {
    /**
    * Lists the opensocial.Person.Fields that define a 'basic' person
    * @internal
    * @memberOf MyOpenSpace.DataRequest.Constants
    */
    BASIC_PERSON_FIELDS: "familyName,givenName,id,name,nickname,profileUrl,thumbnailUrl",
    
    /**
    * Lists the opensocial.Person.Fields that define a name
    * @internal
    * @memberOf MyOpenSpace.DataRequest.Constants
    */
    NAME_FIELDS: "familyName,givenName,name"
};

/**
* Creates an object to be used when sending to the server.
* @param {String} id The ID (VIEWER or OWNER) of the person who owns the photo
* @param {Number} photo_id The ID of the photo to fetch
* @param {undefined} opt_params Not used at this time.
* @return {Object} A request object
* @static
* @memberOf MyOpenSpace.DataRequest
*/
MyOpenSpace.DataRequest.newFetchPhotoRequest = function(id, photo_id, opt_params) {
    return MSID.Container.get().newFetchPhotoRequest(id, photo_id, opt_params);
};

/**
* Creates an object to be used when sending to the server
* @param {String} id The ID (VIEWER or OWNER) of the person who owns the photos
* @param {Map&lt;opensocial.DataRequest.PeopleRequestFields.FIRST || opensocial.DataRequest.PeopleRequestFields.MAX&gt;} opt_params Optional parameters specified when creating the photos.
* @return {Object} A request object
* @static
* @memberOf MyOpenSpace.DataRequest
*/
MyOpenSpace.DataRequest.newFetchPhotosRequest = function(id, opt_params) {
    return MSID.Container.get().newFetchPhotosRequest(id, opt_params);
};

/**
* Creates an object to be used when sending to the server
* @param {String} id The ID (VIEWER or OWNER) of the person who owns the album
* @param {Number} album_id The ID of the photo to fetch
* @param {undefined} opt_params Not used at this time.
* @return {Object} A request object
* @static
* @memberOf MyOpenSpace.DataRequest
*/
MyOpenSpace.DataRequest.newFetchAlbumRequest = function(id, album_id, opt_params) {
    return MSID.Container.get().newFetchAlbumRequest(id, album_id, opt_params);
};

/**
* Creates an object to be used when sending to the server
* @param {String} id The ID (VIEWER or OWNER) of the person who owns the albums
* @param {Map&lt;opensocial.DataRequest.PeopleRequestFields.FIRST || opensocial.DataRequest.PeopleRequestFields.MAX&gt;} opt_params Optional parameters specified when creating the albums.
* @return {Object} A request object
* @static
* @memberOf MyOpenSpace.DataRequest
*/
MyOpenSpace.DataRequest.newFetchAlbumsRequest = function(id, opt_params) {
    return MSID.Container.get().newFetchAlbumsRequest(id, opt_params);
};

/**
* Creates an object to be used when sending to the server
* @param {String} id The ID (VIEWER or OWNER) of the person who owns the album
* @param {Number} video_id The ID of the photo to fetch
* @param {undefined} opt_params Not used at this time.
* @return {Object} A request object
* @static
* @memberOf MyOpenSpace.DataRequest
*/
MyOpenSpace.DataRequest.newFetchVideoRequest = function(id, video_id, opt_params) {
    return MSID.Container.get().newFetchVideoRequest(id, video_id, opt_params);
};

/**
* Creates an object to be used when sending to the server
* @param {String} id The ID (VIEWER or OWNER) of the person
* @param {undefined} opt_params Not used at this time.
* @return {Object} A request object
* @static
* @memberOf MyOpenSpace.DataRequest
*/
MyOpenSpace.DataRequest.newFetchIndicatorsRequest = function(id, opt_params) {
    return MSID.Container.get().newFetchIndicatorsRequest(id, opt_params);
};

/**
* Creates an object to be used when sending to the server
* @param {String} id The ID (VIEWER or OWNER) of the person
* @param {undefined} opt_params Not used at this time.
* @return {Object} A request object
* @static
* @memberOf MyOpenSpace.DataRequest
*/
MyOpenSpace.DataRequest.newFetchPersonStatusRequest = function(id, opt_params) {
    return MSID.Container.get().newFetchPersonStatusRequest(id, opt_params);
};

/**
* Creates an object to be used when sending to the server
* @param {String} id The ID (VIEWER or OWNER) of the person
* @param {undefined} opt_params Not used at this time.
* @return {Object} A request object
* @static
* @memberOf MyOpenSpace.DataRequest
*/
MyOpenSpace.DataRequest.newFetchPersonMoodRequest = function(id, opt_params) {
    return MSID.Container.get().newFetchPersonMoodRequest(id, opt_params);
};

/**
* Creates an object to be used when sending to the server
* @param {String} id The ID (VIEWER or OWNER) of the person
* @param {String} key The ID of the person to verify friendship
* @param {undefined} opt_params Not used at this time.
* @return {Object} A request object
* @static
* @memberOf MyOpenSpace.DataRequest
*/
MyOpenSpace.DataRequest.newFetchPersonFriendshipRequest = function(id, key, opt_parms) {
    return MSID.Container.get().newFetchPersonFriendshipRequest(id, key, opt_parms);
};

/**
* Creates an object to be used when sending to the server
* @param {String} id The ID (VIEWER or OWNER) of the person
* @param {Array} key The array of IDs of the people to verify friendship.
* @param {undefined} opt_params Not used at this time.
* @return {Object} A request object
* @static
* @memberOf MyOpenSpace.DataRequest
*/
MyOpenSpace.DataRequest.newFetchPeopleFriendshipRequest = function(id, key, opt_parms) {
    return MSID.Container.get().newFetchPeopleFriendshipRequest(id, key, opt_parms);
};

/**
* Creates an object to be used when sending to the server
* @param {String} id The ID (VIEWER or OWNER) of the person who owns the albums
* @param {Map&lt;opensocial.DataRequest.PeopleRequestFields.FIRST || opensocial.DataRequest.PeopleRequestFields.MAX&gt;} opt_params Optional parameters specified when creating the videos.
* @return {Object} A request object
* @static
* @memberOf MyOpenSpace.DataRequest
*/
MyOpenSpace.DataRequest.newFetchVideosRequest = function(id, opt_params) {
    return MSID.Container.get().newFetchVideosRequest(id, opt_params);
};

/**
* Creates an object to be used when sending to the server
* @param {String} id The ID, only OWNER is supported
* @param {MyOpenSpace.Notification} notification The notification object.
* @return {Object} A request object
* @static
* @internal
* @memberOf MyOpenSpace.DataRequest
*/
MyOpenSpace.DataRequest.newInsertNotificationRequest = function(recipient, notification, params){
    return MSID.Container.get().newInsertNotificationRequest(recipient, notification, params);
};

MyOpenSpace.DataRequest.prototype = {
	allRequestsCompleteCallback_: function() {},

	getRequestObjects: function() {
		return this.requestObjects_;
	},
	
	add: function(request, opt_key){
        if(!this.busy_){
            opt_key ? request.key = opt_key : request.key = null;
            if("undefined" === typeof(opt_key) || !this.requestObjects_.has(opt_key)){ // check for duplicate entry
                this.requestProcessor_.addWorkItem(request);
                var id = request.type;
                if(request.parameters){
                    if(request.parameters.id) id += "-" + request.parameters.id;
                    else if(request.parameters.idSpec) id += "-" + request.parameters.idSpec;
                }
                opt_key ? this.requestObjects_.add(opt_key, request) : this.requestObjects_.add(id, request);
                this.requestObjectCount_++;
            }
        }
    },
	send: function(opt_callback){
		if(!this.busy_ && this.requestObjectCount_ > 0){
            this.busy_ = true;
		    if(opt_callback) this.allRequestsCompleteCallback_ = opt_callback;
            
		    MSID.Container.get().requestData(this, opt_callback);
		}
		
	},
	newFetchPersonRequest: function(id, opt_params) {
	    return MSID.Container.get().newFetchPersonRequest(id, opt_params);
	},
	newFetchPeopleRequest: function(id, opt_params) {
	    return MSID.Container.get().newFetchPeopleRequest(id, opt_params);
	},
	newFetchGlobalAppDataRequest: function() {
	    return MSID.Container.get().newFetchGlobalAppDataRequest();
	},
	newFetchInstanceAppDataRequest: function() {
	    return MSID.Container.get().newFetchInstanceAppDataRequest();
	},
	newUpdateInstanceAppDataRequest: function() {
	    return MSID.Container.get().newUpdateInstanceAppDataRequest();
	},
	newFetchPersonAppDataRequest: function(id, keys, opt_params) {
	    return MSID.Container.get().newFetchPersonAppDataRequest(id, keys, opt_params);
	},
	newUpdatePersonAppDataRequest: function(id, key, value) {
	    return MSID.Container.get().newUpdatePersonAppDataRequest(id, key, value);
	},
	newFetchActivitiesRequest: function(idSpec, opt_params) {
	    return MSID.Container.get().newFetchActivitiesRequest(idSpec, opt_params);
	},
	newRemovePersonAppDataRequest: function(id,keys){
		return MSID.Container.get().newRemovePersonAppDataRequest(id,keys);
	}
};

/**
 * @constructor
 * @name MyOpenSpace.DataRequest.RequestActions_
 * @internal
 */
MyOpenSpace.DataRequest.RequestActions_ = function(dataRequest) {
	this.dataRequest_ = dataRequest;
	this.itemsProcessed_ = 0;
	this.dataResponseValues_ = {};
};

/**
 * Does the logic for the requests and sets up callbacks
 * @memberOf MyOpenSpace.DataRequest
 * @private
 * @internal
 */
MyOpenSpace.DataRequest.RequestActions_.prototype = {
	itemsProcessed_: 0,
	dataResponseValues_: {},
	errored_: false,
	getRequestId_: function getRequestId_(id) {
		var table = this.dataRequest_.ReqestIdTable_;
		if (typeof table === 'undefined' || table === null) {
			this.dataRequest_.ReqestIdTable_ = {};
			table = this.dataRequest_.ReqestIdTable_;
		}
		var requestId = table[id];
		if (typeof requestId === 'undefined') {
			table[id] = 0;
			requestId = "";
		}
		else {
			table[id] += 1;
			requestId = "_" + table[id];
		}
		return requestId;
	},
	isViewerDenied: function(type, workitem, addResponse) {
		return false;
	},
	isBasicPerson: function(fields) {
		var sorted_string;
		if (fields.constructor === String) {
			if (Array.sort) {
				sorted_string = Array.sort(fields.split(",")).join(",");
			}
			else {
				sorted_string = fields.split(",").sort().join(",");
			}
		}
		else if (fields.constructor === Array) {
			if (Array.sort) {
				sorted_string = Array.sort(fields).join(",");
			}
			else {
				sorted_string = fields.sort().join(",");
			}
		}
		else {
			return false;
		}
		return (sorted_string === MyOpenSpace.DataRequest.Constants.BASIC_PERSON_FIELDS);
	},
	/**
	* Internal method that handles fetch person requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_PERSON": function FETCH_PERSON(workitem) {
		var personId = workitem.parameters.id;
		var type = MyOpenSpace.RequestType.FETCH_PERSON;
		var use_ifpc = false;

		var url;
		var profileDetail = workitem.parameters.profileDetail;
		if (profileDetail.unsupported != null) {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED, "errorMessage": "Request contains not implemented fields:" + profileDetail.unsupported }, workitem.key, true);
			return;
		}

		if (personId === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.Person.Viewer.replace("{DETAIL_TYPE}", profileDetail.fields);
			use_ifpc = (this.isBasicPerson(profileDetail.fields)) ? true : false;
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (personId === opensocial.IdSpec.PersonId.OWNER) {
			url = this.dataRequest_.endPoint_.Person.Owner.replace("{DETAIL_TYPE}", profileDetail.fields);
		}
		else {
			var results = MyOpenSpace.Util.parseIdPrefix(personId);
			if (results === null) {
				this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
					"errorMessage": "Unsupported id"
				}, type, true, workitem.key);
				return;
			}
			url = this.dataRequest_.endPoint_.Person.ID.replace("{PERSON_ID}", results);
		}

		this.invoke_(this, url, type, workitem.key, workitem.opt_params, use_ifpc);
	},
	/**
	* Internal method that handles fetch people requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_PEOPLE": function FETCH_PEOPLE(workitem) {
		var idSpec = MyOpenSpace.Util.idSpecMap(workitem.parameters.idSpec);
		var type = MyOpenSpace.RequestType.FETCH_PEOPLE;

		var url;
		if (typeof (idSpec.errorCode) !== 'undefined') {
			this.addResponseItem_(idSpec, type, true, workitem.key);
			return;
		}

		var first = workitem.parameters.first;
		var max = workitem.parameters.max;
		var filter = workitem.parameters.filter;
		var sort = workitem.parameters.sortOrder;
		var details = workitem.parameters.details;
		var sortToken = "";
		var filterToken = "";

		//details
		if (typeof (details) !== 'undefined') {
			this.addResponseItem_({
				"errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED,
				"errorMessage": "opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS is not implemented."
			}, type, true, workitem.key);
			return;
		}

		// paging
		var pagingError = this.getPagingError(first, max);
		if (pagingError !== "") {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": pagingError },
				type, true, workitem.key);
			return;
		}
		var paging = this.mapPagingParams_(workitem.parameters.first, workitem.parameters.max);

		// sorting
		if (typeof (sort) !== 'undefined') {
			if (opensocial.DataRequest.SortOrder.NAME === sort) {
				sortToken = this.dataRequest_.endPoint_.SortQueryString.replace("{SORT_BY}", "nickName").replace("{SORT_ORDER}", "asc");
			}
			else if (MyOpenSpace.DataRequest.SortOrder.ID === sort) {
				sortToken = this.dataRequest_.endPoint_.SortQueryString.replace("{SORT_BY}", "id").replace("{SORT_ORDER}", "asc");
			}
			else if (opensocial.DataRequest.SortOrder.TOP_FRIENDS === sort) {
				this.addResponseItem_({
					"errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED,
					"errorMessage": "Sort order value opensocial.DataRequest.SortOrder.TOP_FRIENDS is not implemented. If you want to filter top friends use opensocial.DataRequest.PeopleRequestFields.FILTER instead."
				}, type, true, workitem.key);
				return;
			}
			else {
				this.addResponseItem_({
					"errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
					"errorMessage": "Invalid sort order value."
				}, type, true, workitem.key);
				return;
			}
		}

		// filter


		// we can only have one filter at a time, so if more than one are specified we need to create an order of precedence
		if (filter) {
			if (opensocial.DataRequest.FilterType.HAS_APP === filter) filterToken = "app";
			else if (MyOpenSpace.DataRequest.FilterType.ONLINE_FRIENDS === filter) filterToken = "online";
			else if (opensocial.DataRequest.FilterType.TOP_FRIENDS === filter) filterToken = "top";
			else if (opensocial.DataRequest.FilterType.ALL === filter) filterToken = "all";
			else if (opensocial.DataRequest.FilterType.IS_FRIENDS_WITH === filter) {
				this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED, "errorMessage": "opensocial.DataRequest.FilterType.IS_FRIENDS_WITH is not implemented." },
				type, true, workitem.key);
				return;
			}
			else {
				this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "invalid filter." },
				type, true, workitem.key);
				return;
			}
			filterToken = this.dataRequest_.endPoint_.FilterQueryString.replace("{FILTER}", filterToken);
		}

		if ("number" === typeof (idSpec) || !isNaN(parseInt(idSpec, 10))) {
			url = this.dataRequest_.endPoint_.People.ID.replace("{PERSON_ID}", idSpec);
		}
		else if (idSpec === MyOpenSpace.IdSpecMapping_.VIEWER_FRIENDS) {
			url = this.dataRequest_.endPoint_.People.ViewerFriends.replace("{PAGE}", paging[0]).replace("{SIZE}", paging[1]) + filterToken + sortToken;
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (idSpec === MyOpenSpace.IdSpecMapping_.OWNER_FRIENDS) {
			url = this.dataRequest_.endPoint_.People.OwnerFriends.replace("{PAGE}", paging[0]).replace("{SIZE}", paging[1]) + filterToken + sortToken;
		}
		else if (idSpec === MyOpenSpace.IdSpecMapping_.OWNER) {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED,
				"errorMessage": "Unsupported idSpec. To get owner information use fetchPersonRequest."
			}, type, true, workitem.key);
			return;
		}
		else if (idSpec === MyOpenSpace.IdSpecMapping_.VIEWER) {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED,
				"errorMessage": "Unsupported idSpec To get viewer information use fetchPersonRequest."
			},
				type, true, workitem.key);
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
				"errorMessage": "Unsupported idSpec"
			},
				type, true, workitem.key);
			return;
		}

		this.invoke_(this, url, type, workitem.key, workitem.opt_params);
	},
	/**
	* Internal method that handles fetch indicators requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_INDICATORS": function FETCH_INDICATORS(workitem) {
		var personId = workitem.parameters.id;
		var type = MyOpenSpace.RequestType.FETCH_INDICATORS;

		var url;

		if (personId === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.Indicators.Viewer;
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (personId === opensocial.IdSpec.PersonId.OWNER) {
			url = this.dataRequest_.endPoint_.Indicators.Owner;
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported id" }, type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles fetch person status requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_PERSON_STATUS": function FETCH_PERSON_STATUS(workitem) {
		var personId = workitem.parameters.id;
		var type = MyOpenSpace.RequestType.FETCH_PERSON_STATUS;

		var url;

		if (personId === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.PersonStatus.Viewer;
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (personId === opensocial.IdSpec.PersonId.OWNER) {
			url = this.dataRequest_.endPoint_.PersonStatus.Owner;
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported id" },
				type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles fetch person mood requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_PERSON_MOOD": function FETCH_PERSON_MOOD(workitem) {

		var personId = workitem.parameters.id;
		var type = MyOpenSpace.RequestType.FETCH_PERSON_MOOD;

		var url;

		if (personId === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.PersonMood.Viewer;

			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (personId === opensocial.IdSpec.PersonId.OWNER) {
			url = this.dataRequest_.endPoint_.PersonMood.Owner;
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported id" },
				type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles fetch person friendship requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_PERSON_FRIENDSHIP": function FETCH_PERSON_FRIENDSHIP(workitem) {
		var personId = workitem.parameters.id;
		var key = workitem.parameters.key;
		var type = MyOpenSpace.RequestType.FETCH_PERSON_FRIENDSHIP;

		var url;

		if (personId === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.Friendship.Viewer.replace("{PERSON_IDS}", key);
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (personId === opensocial.IdSpec.PersonId.OWNER) {
			url = this.dataRequest_.endPoint_.Friendship.Owner.replace("{PERSON_IDS}", key);
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported id" },
				type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key);

	},
	/**
	* Internal method that handles fetch people friendship requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_PEOPLE_FRIENDSHIP": function FETCH_PEOPLE_FRIENDSHIP(workitem) {
		var personId = workitem.parameters.id;
		var type = MyOpenSpace.RequestType.FETCH_PEOPLE_FRIENDSHIP;

		var key = workitem.parameters.key;
		var url;

		if (key.constructor != Array) {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Key must be an array." },
				type, true, workitem.key);
			return;
		}
		if (key.length === 0) {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
				"errorMessage": "Key must be an array with at least one element."
			}, type, true, workitem.key);
			return;
		}
		var idArray = [];
		for (var i = 0; i < key.length; i++) {
			var results = MyOpenSpace.Util.parseIdPrefix(key[i]);
			if (results === null) {
				this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
					"errorMessage": "Key user array element is not an user Id."
				},
					type, true, workitem.key);
				return;
			}
			idArray[i] = results;
		}

		if (personId === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.Friendship.Viewer.replace("{PERSON_IDS}", idArray.join(";"));
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (personId === opensocial.IdSpec.PersonId.OWNER) {
			url = this.dataRequest_.endPoint_.Friendship.Owner.replace("{PERSON_IDS}", idArray.join(";"));
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported id" },
				type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles fetch photo requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_PHOTO": function FETCH_PHOTO(workitem) {
		var personId = workitem.parameters.id;
		var type = MyOpenSpace.RequestType.FETCH_PHOTO;

		var url;

		if (personId === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.Photo.Viewer.replace("{PHOTO_ID}", workitem.parameters.photo_id);
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (personId === opensocial.IdSpec.PersonId.OWNER) {
			url = this.dataRequest_.endPoint_.Photo.Owner.replace("{PHOTO_ID}", workitem.parameters.photo_id);
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported id" },
				type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles fetch photos requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_PHOTOS": function FETCH_PHOTOS(workitem) {
		var personId = workitem.parameters.id;
		var albumId = workitem.parameters.album_id;
		var type = MyOpenSpace.RequestType.FETCH_PHOTOS;

		//test to see if the page request is in the form first = a(n) +1 where n = max;
		var first = workitem.parameters.first;
		var max = workitem.parameters.max;
		var pagingError = this.getPagingError(first, max);
		if (pagingError !== "") {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": pagingError },
				type, true, workitem.key);
			return;
		}
		var paging = this.mapPagingParams_(workitem.parameters.first, workitem.parameters.max);
		var url;

		if (personId === opensocial.IdSpec.PersonId.VIEWER) {
			if (null !== albumId) {
				url = this.dataRequest_.endPoint_.AlbumPhotos.Viewer.replace("{ALBUM_ID}", albumId).replace("{PAGE}", paging[0]).replace("{SIZE}", paging[1]);
			}
			else {
				url = this.dataRequest_.endPoint_.Photos.Viewer.replace("{PAGE}", paging[0]).replace("{SIZE}", paging[1]);
			}

			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (personId === opensocial.IdSpec.PersonId.OWNER) {
			if (null !== albumId) {
				url = this.dataRequest_.endPoint_.AlbumPhotos.Owner.replace("{ALBUM_ID}", albumId).replace("{PAGE}", paging[0]).replace("{SIZE}", paging[1]);
			}
			else {
				url = this.dataRequest_.endPoint_.Photos.Owner.replace("{PAGE}", paging[0]).replace("{SIZE}", paging[1]);
			}
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported id" },
				type, true, workitem.key);
			return;
		}

		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles fetch album requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_ALBUM": function FETCH_ALBUM(workitem) {
		var personId = workitem.parameters.id;
		var type = MyOpenSpace.RequestType.FETCH_ALBUM;

		var url;

		if (personId === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.Album.Viewer.replace("{ALBUM_ID}", workitem.parameters.album_id);
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (personId === opensocial.IdSpec.PersonId.OWNER) {
			url = this.dataRequest_.endPoint_.Album.Owner.replace("{ALBUM_ID}", workitem.parameters.album_id);
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported id" },
				type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles fetch people albums requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_ALBUMS": function FETCH_ALBUMS(workitem) {
		var personId = workitem.parameters.id;
		var type = MyOpenSpace.RequestType.FETCH_ALBUMS;

		var first = workitem.parameters.first;
		var max = workitem.parameters.max;
		var pagingError = this.getPagingError(first, max);
		if (pagingError !== "") {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": pagingError },
				type, true, workitem.key);
			return;
		}
		var paging = this.mapPagingParams_(workitem.parameters.first, workitem.parameters.max);
		var url;


		if (personId === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.Albums.Viewer.replace("{PAGE}", paging[0]).replace("{SIZE}", paging[1]);

			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (personId === opensocial.IdSpec.PersonId.OWNER) {
			url = this.dataRequest_.endPoint_.Albums.Owner.replace("{PAGE}", paging[0]).replace("{SIZE}", paging[1]);
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported idSpec" },
				type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles fetch video requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_VIDEO": function FETCH_VIDEO(workitem) {
		var personId = workitem.parameters.id;
		var url;
		var type = MyOpenSpace.RequestType.FETCH_VIDEO;

		if (personId === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.Video.Viewer.replace("{VIDEO_ID}", workitem.parameters.video_id);
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (personId === opensocial.IdSpec.PersonId.OWNER) {
			url = this.dataRequest_.endPoint_.Video.Owner.replace("{VIDEO_ID}", workitem.parameters.video_id);
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported idSpec" },
				type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles fetch videos requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_VIDEOS": function FETCH_VIDEOS(workitem) {
		var personId = workitem.parameters.id;
		var type = MyOpenSpace.RequestType.FETCH_VIDEOS;

		var first = workitem.parameters.first;
		var max = workitem.parameters.max;
		var pagingError = this.getPagingError(first, max);


		if (pagingError !== "") {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": pagingError },
				type, true, workitem.key);
			return;
		}
		var paging = this.mapPagingParams_(workitem.parameters.first, workitem.parameters.max);
		var url;

		if (personId === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.Videos.Viewer.replace("{PAGE}", paging[0]).replace("{SIZE}", paging[1]);
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (personId === opensocial.IdSpec.PersonId.OWNER) {
			url = this.dataRequest_.endPoint_.Videos.Owner.replace("{PAGE}", paging[0]).replace("{SIZE}", paging[1]);
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported idSpec" },
				type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles fetch person data requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_PERSON_DATA": function FETCH_PERSON_DATA(workitem) {
		var idSpec = MyOpenSpace.Util.idSpecMap(workitem.parameters.idSpec);
		var type = MyOpenSpace.RequestType.FETCH_PERSON_DATA;

		if (typeof (idSpec.errorCode) !== 'undefined') {
			this.addResponseItem_(idSpec, type, true, workitem.key);
			return;
		}

		var keys = workitem.parameters.keys || "";
		var opt_params = workitem.parameters.opt_params;
		var getId = false;
		var requestId;
		var url;

		var arrayError = false;
		if (keys === "*") {
			keys = "";
		}
		else if (keys.constructor === Array) {
			for (var i = 0; i < keys.length; i++) {
				if (keys[i] === "" || keys[i] === "*") {
					arrayError = true;
					break;
				}
			}
			keys = "/" + keys.join(';');
		}
		else {
			requestId = "_" + keys;
			keys = "/" + keys;
		}

		var pointer = this;

		if (idSpec === MyOpenSpace.IdSpecMapping_.VIEWER) {
			url = this.dataRequest_.endPoint_.PersonAppData.Viewer.replace("{KEYS}", keys);
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (idSpec === MyOpenSpace.IdSpecMapping_.VIEWER_FRIENDS) {
			url = this.dataRequest_.endPoint_.PersonAppData.ViewerFriends.replace("{KEYS}", keys);
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (idSpec === MyOpenSpace.IdSpecMapping_.OWNER) {
			url = this.dataRequest_.endPoint_.PersonAppData.Owner.replace("{KEYS}", keys);
		}
		else if (idSpec === MyOpenSpace.IdSpecMapping_.OWNER_FRIENDS) {
			url = this.dataRequest_.endPoint_.PersonAppData.OwnerFriends.replace("{KEYS}", keys);
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported idSpec" },
				type, true, workitem.key);
			return;
		}
		if (arrayError) {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
				"errorMessage": "Invalid key array element. Array can contain neither empty string nor '*'. "
			}, type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key, opt_params);
	},
	/**
	* Internal method that handles update person data requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"UPDATE_PERSON_DATA": function UPDATE_PERSON_DATA(workitem) {
		var idSpec = workitem.parameters.id;
		var value = workitem.parameters.value;
		var key = workitem.parameters.key;
		var type = MyOpenSpace.RequestType.UPDATE_PERSON_DATA;

		var url;

		var validateAppDataKeyName = function(keyName) {
			var validRE = /^([a-z0-9\-_\.])+$/i;
			return validRE.test(keyName);
		}
		var validateAppDataValueSize = function(str) {
			if (str) {
				return str.length < 1024;
			}
			return true;
		}
		var getInvalidJsonErrorObject = function() {
			return { "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "AppData value must be valid JSON." };
		};

		//if it's an object, stringify it, then try to jsonify it
		if ("object" === typeof (value)) {
			value = gadgets.json.stringify(value);
			if (!gadgets.json.parse(value)) {
				this.addResponseItem_(getInvalidJsonErrorObject(), type, true, workitem.key);
				return;
			}
		}
		else if ("string" === typeof (value)) {
			if (!gadgets.json.parse(value)) {
				this.addResponseItem_(getInvalidJsonErrorObject(), type, true, workitem.key);
				return;
			}
		}
		else {
			this.addResponseItem_(getInvalidJsonErrorObject(), type, true, workitem.key);
			return;
		}

		if (!validateAppDataKeyName(key)) { // bad key name
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
				"errorMessage": "AppData key names can only consist of alphanumerics, dots, dashes and underscores."
			}, type, true, workitem.key);
			return;
		}
		if (!validateAppDataValueSize(value)) { // value too long
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST,
				"errorMessage": "AppData value must be less than 1kB (1024 bytes) in size, string size is: " + value.length + " bytes."
			}, type, true, workitem.key);
			return;
		}

		value = escape(value);
		value = value.replace(/\+/g, '%2B');

		this.dataRequest_.params = key + "=" + value;
		if (idSpec === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.PersonAppData.Viewer.replace("{KEYS}", "");

			if (this.isViewerDenied(type, workitem)) return;
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED, "errorMessage": "Unsupported idSpec" },
				type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles remove person data requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"REMOVE_PERSON_DATA": function REMOVE_PERSON_DATA(workitem) {
		var keys = workitem.parameters.keys || "";
		var type = MyOpenSpace.RequestType.REMOVE_PERSON_DATA;

		var requestId;
		var url;


		if (keys === "*" || keys === "") {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED,
				"errorMessage": "You must supply a key or an array of keys to remove, note that * isn't implemented."
			},
				type, true, workitem.key);
			return;
		}
		else if (keys.constructor === Array) {
			keys = "/" + keys.join(';');
		}
		else {
			requestId = "_" + keys;
			keys = "/" + keys;
		}

		var id = workitem.parameters.id;
		var pointer = this;

		if (id === opensocial.IdSpec.PersonId.VIEWER) {
			url = this.dataRequest_.endPoint_.PersonAppData.Viewer.replace("{KEYS}", keys);
			if (this.isViewerDenied(type, workitem)) return;
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.NOT_IMPLEMENTED, "errorMessage": "Unsupported idSpec" },
				type, true, workitem.key);
			return;
		}

		this.dataRequest_.params = "";
		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles fetch activities requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"FETCH_ACTIVITIES": function FETCH_ACTIVITIES(workitem) {
		var idSpec = MyOpenSpace.Util.idSpecMap(workitem.parameters.idSpec);
		var type = MyOpenSpace.RequestType.FETCH_ACTIVITIES;

		if (typeof (idSpec.errorCode) !== 'undefined') {
			this.addResponseItem_(idSpec, type, true, workitem.key);
			return;
		}

		var url;

		if (idSpec === MyOpenSpace.IdSpecMapping_.VIEWER) {
			url = this.dataRequest_.endPoint_.Activities.Viewer;
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (idSpec === MyOpenSpace.IdSpecMapping_.OWNER) {
			url = this.dataRequest_.endPoint_.Activities.Owner;
		}
		else if (idSpec === MyOpenSpace.IdSpecMapping_.VIEWER_FRIENDS) {
			url = this.dataRequest_.endPoint_.Activities.ViewerFriends;
			if (this.isViewerDenied(type, workitem)) return;
		}
		else if (idSpec === MyOpenSpace.IdSpecMapping_.OWNER_FRIENDS) {
			url = this.dataRequest_.endPoint_.Activities.OwnerFriends;
		}
		else {
			this.addResponseItem_({ "errorCode": opensocial.ResponseItem.Error.BAD_REQUEST, "errorMessage": "Unsupported idSpec" },
				type, true, workitem.key);
			return;
		}
		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal method that handles insert notification requests
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} workitem
	*/
	"INSERT_NOTIFICATION": function INSERT_NOTIFICATION(workitem) {
		var recipient = workitem.parameters.recipient;
		var params = workitem.parameters.params;
		var type = MyOpenSpace.RequestType.INSERT_NOTIFICATION;

		var template_params = params[MyOpenSpace.Notification.Field.TEMPLATE_PARAMS] || "";
		var media_items = params[MyOpenSpace.Notification.Field.MEDIA_ITEMS] || "";

		if (!this.hasPermissionAndSetRI(MyOpenSpace.Permission.VIEWER_SEND_NOTIFICATIONS, type, workitem.key)) {
			return;
		}

		var url = this.dataRequest_.endPoint_.Notification.OwnerInsert;

		this.dataRequest_.params = "recipients=" + recipient + "&"; // these are already escaped appropriately

		if (template_params && template_params.length > 0) {
			this.dataRequest_.params += "templateParameters=" + template_params + "&";
		}

		if (media_items && media_items.length > 0) {
			this.dataRequest_.params += "mediaItems=" + media_items;
		}

		this.invoke_(this, url, type, workitem.key);
	},
	/**
	* Internal utility method that handles permissions, if perms aren't granted, add the response item
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {MyOpenSpace.Permission} permission
	* @param {String} type
	* @param {String} key
	*/
	hasPermissionAndSetRI: function(permission, type, key) {
		var has = opensocial.hasPermission(permission);
		if (!has) {
			this.addResponseItem_(
                {
                	"errorCode": opensocial.ResponseItem.Error.UNAUTHORIZED,
                	"errorMessage": "You don't have permission to make requests of type: " + type
                },
				type, true, key);
		}
		return has;
	},
	/**
	* Internal utility method that handles verify pagin values
	* @internal
	* @memberOf MyOpenSpace.DataRequest.RequestActions_
	* @param {Object} first
	* @param {Object} max
	*/
	getPagingError: function(first, max) {
		if (isNaN(first) && first !== "") {
			return ("Paging Error: first must be an integer");
		}

		if (isNaN(max) && max !== "") {
			return ("Paging Error: max must be an integer");
		}

		if (first <= 0 && first !== "") {
			return ("Paging Error: first must be a positive integer greater than zero"); ;
		}

		if (max <= 0 && max !== "") {
			return ("Paging Error: max must be a positive integer greater than zero"); ;
		}

		if (((first % max !== 1) && max != 1 && !(first === "" || max === ""))) {
			return ("Paging Error: paging data must be in the form of max = n and first = x(n) + 1 where x is any positive integer. i.e max=5, first= 2(5)+1 =11");
		}

		return "";
	},

	mapPagingParams_: function(first, max) {
		if (first === null || typeof (first) === "undefined" || first < 1) first = 1;
		if (max === null || typeof (max) === "undefined" || max < 1) max = MyOpenSpace.DefaultPageSize;

		// page is not 0 indexed, it starts at 1	    
		var page_size = max;
		var page = Math.floor(first / page_size);
		if ((first % page_size) !== 0)
			page += 1;
		return [page, page_size];
	},
	invoke_: function(requestActions, url, type, opt_key, opt_params, use_ifpc) {

		var callback = function(item, errored, opt_key) {
			requestActions.addResponseItem_(item, type, errored, opt_key);
		};

		var dataRequestAction = requestActions.dataRequest_;

		switch (type) {
			case MyOpenSpace.RequestType.UPDATE_PERSON_DATA:
				dataRequestAction.method = "PUT";
				break;
			case MyOpenSpace.RequestType.INSERT_NOTIFICATION:
				dataRequestAction.method = "POST";
				break;
			case MyOpenSpace.RequestType.REMOVE_PERSON_DATA:
				dataRequestAction.method = "DELETE";
				break;
			default:
				dataRequestAction.method = "GET";
				dataRequestAction.params = "";
				break;
		}

		dataRequestAction.endPoint = url;

		opt_params = (typeof opt_params !== 'object' || opt_params === null) ? {} : opt_params;

		if (typeof opt_params.useCache === 'undefined' || opt_params.useCache !== false) {

			if (APICache.isCached(dataRequestAction.endPoint)) {
				if (!APICache.isExpired(dataRequestAction.endPoint, opt_params.refreshInterval)) {
					callback(APICache.retrieve(dataRequestAction.endPoint), false, opt_key);
					return;
				}
			}
		}

		MSID.Relay.sendRequest(dataRequestAction, type, completed_, errored_, true, opt_key);

		function completed_(response, type, opt_key) {
			var error = null;
			var result = null;
			var map = new MyOpenSpace.DataMapper_();

			if (type === MyOpenSpace.RequestType.REMOVE_PERSON_DATA ||
			            type === MyOpenSpace.RequestType.UPDATE_PERSON_DATA ||
			            type === MyOpenSpace.RequestType.INSERT_NOTIFICATION
            ) {
				callback(null, false, opt_key);
				return;
			}
			else if (type === MyOpenSpace.RequestType.FETCH_PERSON_DATA) {

				var encoding = opensocial.EscapeType.HTML_ESCAPE;
				if (typeof (opt_params) !== "undefined") {
					encoding = opt_params[opensocial.DataRequest.DataRequestFields.ESCAPE_TYPE];
					if (typeof (opt_params) === "undefined") {
						encoding = opensocial.EscapeType.HTML_ESCAPE;
					}
				}
				result = map.mapData[MyOpenSpace.RequestType.FETCH_PERSON_DATA](response, encoding);
			}
			else {
				result = map.mapData[type](response);
			}
			if (null === result) {
				callback({ "errorCode": opensocial.ResponseItem.Error.INTERNAL_ERROR, "errorMessage": "Unable to map entity" }, true, opt_key);
			} else {
				// short circuit for only cachable types here
				APICache.add(dataRequestAction.endPoint, result, type);
				callback(result, false, opt_key);
			}
		}

		function errored_(response, opt_key) {
			callback(response, true, opt_key);
		}

	},

	responseWrapper: function(responseValues, errored) {
		this.statusCode = 0;
		this.statusMessage = '';

		if (errored) {

			this.statusCode = -1;
			this.statusMessage = 'error';
		}
		else {
			this.statusCode = 1;
			this.statusMessage = 'hello';
		}
	},

	addResponseItem_: function(item, type, errored, opt_key) {
		var ri, error;
		
		if (errored) {
			error = MSID.Connect.Enums.getError(item.errorCode, item.errorMessage);
			ri = MSID.Container.get().newResponseItem(this.dataRequest_, null, error.code, error.message);
			this.errored_ = true;
		}
		else
			ri = MSID.Container.get().newResponseItem(this.dataRequest_, item, "", "");

		var responseKey;
		if (typeof (opt_key) === 'undefined' || opt_key === null) {
			responseKey = type + this.getRequestId_(type);
		}
		else {
			responseKey = opt_key;
		}
		this.dataResponseValues_[responseKey] = ri;
		this.itemsProcessed_++;

		//check to see if we're all done		
		if (this.itemsProcessed_ === this.dataRequest_.requestObjectCount_) {
			this.dataRequest_.ReqestIdTable_ = null;
			this.dataRequest_.requestObjectCount_ = 0;
			this.dataRequest_.requestObjects_ = new MyOpenSpace.Hash();
			this.dataRequest_.busy_ = false;
			this.dataRequest_.allRequestsCompleteCallback_(MSID.Container.get().newDataResponse(this.dataResponseValues_, this.errored_));
			//debugger;
			//this.dataRequest_.allRequestsCompleteCallback_(new this.responseWrapper(this.dataResponseValues_, this.errored_));
		}
		else if (this.dataRequest_.requestProcessor_.executionModel_ === MyOpenSpace.RequestProcessor_.ExecutionModel_.SERIAL)
			this.dataRequest_.requestProcessor_.startProcessing();
	}


};
/**
 * The MySpace container class, extends MSID.Container.  Use MSID.Container.get() to retrieve this singleton.
 * @constructor
 */
MyOpenSpace.MySpaceContainer = function(viewerId, ownerId, sessionToken, consumerKey, sideBySide) {
    var urlParams = gadgets.util.getUrlParameters();
    urlParams.views = "";
    
    var config = {};
    var supported_views = {};
    supported_views["default"] = new gadgets.views.View(gadgets.views.ViewType.EXTERNAL, true);
    supported_views[gadgets.views.ViewType.EXTERNAL] = new gadgets.views.View(gadgets.views.ViewType.EXTERNAL, true);
    config["views"] = supported_views;

    var uriFragment = window.location.hash;
    if (uriFragment && uriFragment.length >= 0) {
        uriFragment = uriFragment.substring(1, uriFragment.length);
        if (uriFragment.indexOf("&") >= 0) {
            uriFragment = uriFragment.substring(0, uriFragment.indexOf("&"));
        }
    }
   
    //TODO: set osToken/session token, should osToken_ be renamed or reused?
    this.osToken_ = sessionToken;
    this.consumerKey_ = consumerKey;

    gadgets.config.init(config);

    APICache.init();

    this.osMode_ = gadgets.views.getCurrentView();

    this.params_ = {};
        
    //New param registrations
    this.registerParam("ownerId", ownerId);
    this.registerParam("viewerId", viewerId);
    
    //TODO: configure permissions
    if (urlParams.perm) {
        var perm = gadgets.json.parse('{"permissions":' + urlParams.perm + '}');
        this.registerParam("ownerPerm", perm.permissions);
        if (urlParams.viewerId === urlParams.ownerId) {
            this.registerParam("viewerPerm", perm.permissions);
        }
        else if (urlParams.viewer_perm) {
            perm = gadgets.json.parse('"permissions":' + urlParams.viewer_perm);
            this.registerParam("viewerPerm", perm.permissions);
        }
    }

    if (urlParams.userLoggedOut) {
        this.registerParam("loggedOut", true);
    }
    else {
        this.registerParam("loggedOut", false);
    }

    if (urlParams.installState) {
        this.registerParam("installState", urlParams.installState);
    }

    var supportedPostToTargets = "";
    if (urlParams && urlParams.pto) {
        supportedPostToTargets = urlParams.pto.split(",");
        this.myspaceenvironment_ = this.newMySpaceEnvironment(supportedPostToTargets);
    }

    MyOpenSpace.EnableClientCache = false;

    if (urlParams && urlParams.mc) {
        var mc = urlParams.mc.split(",");
        for (var i = 0; i < mc.length; i++) {
            switch (mc[i]) {
                case "UOC":
                    MyOpenSpace.MDPContainerUseOpenCanvas = false;
                    break;
                case "RSAMR":
                    MyOpenSpace.MDPContainerRSAMultipleRecipients = false;
                    break;
                case "ECC":
                    MyOpenSpace.EnableClientCache = true;
                    break;
            }
        }
    }

    var supportedPersonFields = {};
    supportedPersonFields[opensocial.Person.Field.ABOUT_ME] = true;
    supportedPersonFields[opensocial.Person.Field.AGE] = true;
    supportedPersonFields[opensocial.Person.Field.BODY_TYPE] = true;
    supportedPersonFields[opensocial.Person.Field.BOOKS] = true;
    supportedPersonFields[opensocial.Person.Field.CHILDREN] = true;
    supportedPersonFields[opensocial.Person.Field.CURRENT_LOCATION] = true;
    supportedPersonFields[opensocial.Person.Field.DATE_OF_BIRTH] = true;
    supportedPersonFields[opensocial.Person.Field.DRINKER] = true;
    supportedPersonFields[opensocial.Person.Field.ETHNICITY] = true;
    supportedPersonFields[opensocial.Person.Field.GENDER] = true;
    supportedPersonFields[opensocial.Person.Field.HAS_APP] = true;
    supportedPersonFields[opensocial.Person.Field.HEROES] = true;
    supportedPersonFields[opensocial.Person.Field.ID] = true;
    supportedPersonFields[opensocial.Person.Field.INTERESTS] = true;
    supportedPersonFields[opensocial.Person.Field.JOBS] = true;
    supportedPersonFields[opensocial.Person.Field.LOOKING_FOR] = true;
    supportedPersonFields[opensocial.Person.Field.MOVIES] = true;
    supportedPersonFields[opensocial.Person.Field.MUSIC] = true;
    supportedPersonFields[opensocial.Person.Field.NAME] = true;
    supportedPersonFields[opensocial.Person.Field.NETWORK_PRESENCE] = true;
    supportedPersonFields[opensocial.Person.Field.NICKNAME] = true;
    supportedPersonFields[opensocial.Person.Field.PROFILE_SONG] = true;
    supportedPersonFields[opensocial.Person.Field.PROFILE_URL] = true;
    supportedPersonFields[opensocial.Person.Field.RELATIONSHIP_STATUS] = true;
    supportedPersonFields[opensocial.Person.Field.RELIGION] = true;
    supportedPersonFields[opensocial.Person.Field.SEXUAL_ORIENTATION] = true;
    supportedPersonFields[opensocial.Person.Field.SMOKER] = true;
    supportedPersonFields[opensocial.Person.Field.STATUS] = true;
    supportedPersonFields[opensocial.Person.Field.THUMBNAIL_URL] = true;
    supportedPersonFields[opensocial.Person.Field.TV_SHOWS] = true;
    supportedPersonFields[opensocial.Person.Field.URLS] = true;
    supportedPersonFields[MyOpenSpace.Person.Field.MEDIUM_IMAGE] = true;
    supportedPersonFields[MyOpenSpace.Person.Field.LARGE_IMAGE] = true;

    var supportedFilters = {};
    supportedFilters[opensocial.DataRequest.FilterType.ALL] = true;
    supportedFilters[opensocial.DataRequest.FilterType.HAS_APP] = true;
    supportedFilters[opensocial.DataRequest.FilterType.TOP_FRIENDS] = true;

    var supportedFields = {};
    supportedFields[opensocial.Environment.ObjectType.PERSON] = supportedPersonFields;
    supportedFields[MyOpenSpace.Environment.ObjectType.PERSON] = supportedPersonFields;
    supportedFields[MyOpenSpace.Environment.ObjectType.VIDEO] = MyOpenSpace.Video.Field;
    supportedFields[MyOpenSpace.Environment.ObjectType.ALBUM] = MyOpenSpace.Album.Field;
    supportedFields[MyOpenSpace.Environment.ObjectType.PHOTO] = MyOpenSpace.Photo.Field;
    supportedFields[opensocial.Environment.ObjectType.FILTER_TYPE] = supportedFilters;

    this.environment_ = this.newEnvironment("myspace.com", supportedFields);
    this.endPoint_ = new MyOpenSpace.EndPoint(opensocial.IdSpec.PersonId, this.osToken_, this.osMode_);

    // Expose as property for now
    MyOpenSpace.MySpaceContainer.OSToken = this.osToken_;

    this.sideBySide = sideBySide;
    MSID.Container.call(this, false);
    MSID.Container.setContainer(this);
    if(!this.sideBySide) {
        opensocial.Container.call(this, false);
        opensocial.Container.setContainer(this);
    }
};
MyOpenSpace.MySpaceContainer.inherits(opensocial.Container);

/**
 * The parameters that are passed between surfaces.
 * @private
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.params_ = null;

/**
 * Private instance of the request processor, use MySpaceContainer.getRequestProcessor to access.
 * @type {MyOpenSpace.RequestProcessor_}
 * @private
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.requestProcessor_ = null;

/**
 * Private instance of the environment, use MySpaceContainer.getEnvironment to access.
 * @type {opensocial.Environment}
 * @private
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.environment_ = null;

/**
 * Private instance of the MySpace environment, use MySpaceContainer.getMySpaceEnvironment to access.
 * @type {MyOpenSpace.Environment}
 * @private
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.myspaceenvironment_ = null;

/**
 * Returns the container's MySpace environment object
 * @return {MyOpenSpace.Environment}
 */
MyOpenSpace.MySpaceContainer.prototype.getMySpaceEnvironment = function() { return this.myspaceenvironment_; };

/**
 * Returns the container's request processor
 * @return {MyOpenSpace.RequestProcessor_}
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.getRequestProcessor = function() { return this.requestProcessor_; };

/**
 * Returns the container's environment object.
 * @return {opensocial.Environment}
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.getEnvironment = function() { return this.environment_; };

MyOpenSpace.MySpaceContainer.prototype.disable = function() {
    MSID.Container.get().osToken_ = null;
}

/**
 * Returns a new MyOpenSpace.Person instance
 * @param {Map&lt;opensocial.DataRequest.PeopleRequestFields&gt;} opt_params Optional parameters specified when creating the person.
 * @param {Boolean} opt_isViewer True if this is the viewer of the page otherwise, false.
 * @param {Boolean} opt_isOwner True if this is the owner of the page otherwise, false.
 * @return {MyOpenSpace.Person} The person object
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newPerson = function(opt_params, opt_isOwner, opt_isViewer){
    return new MyOpenSpace.Person(opt_params, opt_isOwner, opt_isViewer);
};

MyOpenSpace.MySpaceContainer.prototype.newName = function(opt_params){
    return new opensocial.Name(opt_params);
};


/**
 * Returns a new MyOpenSpace.Album instance
 * @param {undefined} opt_params Unused at this time.
 * @return {MyOpenSpace.Album} The album object
 */
MyOpenSpace.MySpaceContainer.prototype.newAlbum = function(opt_params){
    return new MyOpenSpace.Album(opt_params);
};

/**
 * Returns a new MyOpenSpace.Indicators instance
 * @param {undefined} opt_params Unused at this time.
 * @return {MyOpenSpace.Indicators} The indicators object
 */
MyOpenSpace.MySpaceContainer.prototype.newIndicators = function(opt_params){
    return new MyOpenSpace.Indicators(opt_params);
};
/**
 * Returns a new MyOpenSpace.PersonStatus instance
 * @param {undefined} opt_params Unused at this time.
 * @return {MyOpenSpace.PersonStatus} The PersonStatus object
 */
MyOpenSpace.MySpaceContainer.prototype.newPersonStatus = function(opt_params){
    return new MyOpenSpace.PersonStatus(opt_params);
};
/**
 * Returns a new MyOpenSpace.PersonMood instance
 * @param {undefined} opt_params Unused at this time.
 * @return {MyOpenSpace.PersonStatus} The PersonMood object
 */
MyOpenSpace.MySpaceContainer.prototype.newPersonMood = function(opt_params){
    return new MyOpenSpace.PersonMood(opt_params);
};
/**
 * Returns a new MyOpenSpace.Friendship instance
 * @param {undefined} opt_params Unused at this time.
 * @return {MyOpenSpace.Friendship} The Friendship object
 */
MyOpenSpace.MySpaceContainer.prototype.newFriendship = function(opt_params){
    return new MyOpenSpace.Friendship(opt_params);
};

/**
 * Returns a new MyOpenSpace.Video instance
 * @param {undefined} opt_params Unused at this time.
 * @return {MyOpenSpace.Video} The album object
 */
MyOpenSpace.MySpaceContainer.prototype.newVideo = function(opt_params){
    return new MyOpenSpace.Video(opt_params);
};


/**
 * Returns a new MyOpenSpace.Photo instance
 * @param {undefined} opt_params Unused at this time.
 * @return {MyOpenSpace.Photo} The photo object
 */
MyOpenSpace.MySpaceContainer.prototype.newPhoto = function(opt_params){
    return new MyOpenSpace.Photo(opt_params);
};

/**
 * Returns a new opensocial.DataRequest instance, which is overridden locally
 * @function
 * @return {opensocial.DataRequest} The data request object
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newDataRequest = function(){
    return new MyOpenSpace.DataRequest(this.osToken_, this.endPoint_, this.consumerKey_);
};

/**
 * Returns a new opensocial.ResponseItem instance
 * @function
 * @param {opensocial.DataRequest} originalDataRequest The ID of the photo to fetch
 * @param {Object || null} data The object retrieved, can be any entity, or null if nothing was found.
 * @param {String} opt_errorCode A code defining the error that occurred, if any.
 * @param {String} opt_errorMessage A message detailing the error that occurred, if any.
 * @return {opensocial.ResponseItem} The response item object
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newResponseItem = function(originalDataRequest, data, opt_errorCode, opt_errorMessage) {
    return new opensocial.ResponseItem(originalDataRequest, data, opt_errorCode, opt_errorMessage);
};

/**
 * Creates an object to be used when sending to the server.
 * @param {String} id The ID (VIEWER or OWNER) of the person.
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object.
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchPersonRequest = function(id, opt_params) {
	opt_params = opt_params || {};
    return {
		type: MyOpenSpace.RequestType.FETCH_PERSON,
		parameters: {
			id:id,
			profileDetail: this.mapPersonDetails_(opt_params)
		},
		'opt_params': {
			useCache: (opt_params[MyOpenSpace.DataRequest.CacheControl.USE_CACHE]),
			refreshInterval: (opt_params[MyOpenSpace.DataRequest.CacheControl.REFRESH_INTERVAL])
		}
	};
};

/**
 * Creates an object to be used when sending to the server.
 * @param {String} id The ID (VIEWER or OWNER) of the person who owns the photo
 * @param {Number} photo_id The ID of the photo to fetch
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchPhotoRequest = function(id, photo_id, opt_params){
    return {
		type: MyOpenSpace.RequestType.FETCH_PHOTO,
		parameters: {
			id:id,
			photo_id:photo_id
		}
	};
};

/**
 * Creates an object to be used when sending to the server
 * @param {String} id The ID (VIEWER or OWNER) of the person who owns the photos
 * @param {Map&lt;opensocial.DataRequest.PeopleRequestFields.FIRST || opensocial.DataRequest.PeopleRequestFields.MAX&gt;} opt_params Optional parameters specified when creating the photos.
 * @return {Object} A request object
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchPhotosRequest = function(id, opt_params){
    opt_params = opt_params || {};
    return {
		type: MyOpenSpace.RequestType.FETCH_PHOTOS,
		parameters: {
			id:id,
			album_id: opt_params[MyOpenSpace.DataRequest.PhotoRequestFields.ALBUM_ID] || null,
			first: opt_params[opensocial.DataRequest.PeopleRequestFields.FIRST] || 1,
			max: opt_params[opensocial.DataRequest.PeopleRequestFields.MAX] || MyOpenSpace.DefaultPageSize
		}
	};
};

/**
 * Creates an object to be used when sending to the server
 * @param {String} id The ID (VIEWER or OWNER) of the person who owns the album
 * @param {Number} album_id The ID of the photo to fetch
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchAlbumRequest = function(id, album_id, opt_params){
    return {
		type: MyOpenSpace.RequestType.FETCH_ALBUM,
		parameters: {
			id:id,
			album_id:album_id
		}
	};
};

/**
 * Creates an object to be used when sending to the server
 * @param {String} id The ID (VIEWER or OWNER) of the person who owns the albums
 * @param {Map&lt;opensocial.DataRequest.PeopleRequestFields.FIRST || opensocial.DataRequest.PeopleRequestFields.MAX&gt;} opt_params Optional parameters specified when creating the albums.
 * @return {Object} A request object
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchAlbumsRequest = function(id, opt_params){
    opt_params = opt_params || {};
    return {
		type: MyOpenSpace.RequestType.FETCH_ALBUMS,
		parameters: {
			id:id,
			first: opt_params[opensocial.DataRequest.PeopleRequestFields.FIRST] || 1,
			max: opt_params[opensocial.DataRequest.PeopleRequestFields.MAX] || MyOpenSpace.DefaultPageSize
		}
	};
};

/**
 * Creates an object to be used when sending to the server
 * @param {String} id The ID (VIEWER or OWNER) of the person who owns the album
 * @param {Number} video_id The ID of the photo to fetch
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchVideoRequest = function(id, video_id, opt_params){
    return {
		type: MyOpenSpace.RequestType.FETCH_VIDEO,
		parameters: {
			id:id,
			video_id:video_id
		}
	};
};

/**
 * Creates an object to be used when sending to the server
 * @param {String} id The ID (VIEWER or OWNER) of the person.
 * @param {Map&lt;opensocial.DataRequest.PeopleRequestFields.FIRST || opensocial.DataRequest.PeopleRequestFields.MAX&gt;} opt_params Optional parameters specified when creating the videos.
 * @return {Object} A request object
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchVideosRequest = function(id, opt_params){
    opt_params = opt_params || {};
    return {
		type: MyOpenSpace.RequestType.FETCH_VIDEOS,
		parameters: {
			id:id,
			first: opt_params[opensocial.DataRequest.PeopleRequestFields.FIRST] || 1,
			max: opt_params[opensocial.DataRequest.PeopleRequestFields.MAX] || MyOpenSpace.DefaultPageSize
		}
	};
};

/**
 * Creates an object to be used when sending to the server.
 * @param {String} id The ID (VIEWER or OWNER) of the person.
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object.
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchIndicatorsRequest = function(id, opt_parms){
	return {
		type: MyOpenSpace.RequestType.FETCH_INDICATORS,
		parameters: {
			id: id
		}
	};
};

/**
 * Creates an object to be used when sending to the server.
 * @param {String} id The ID (VIEWER or OWNER) of the person.
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object.
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchPersonStatusRequest = function(id, opt_parms){
	return {
		type: MyOpenSpace.RequestType.FETCH_PERSON_STATUS,
		parameters: {
			id: id
		}
	};
};

/**
 * Creates an object to be used when sending to the server.
 * @param {String} id The ID (VIEWER or OWNER) of the person.
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object.
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchPersonMoodRequest = function(id, opt_parms){
	return {
		type: MyOpenSpace.RequestType.FETCH_PERSON_MOOD,
		parameters: {
			id: id
		}
	};
};

/**
 * Creates an object to be used when sending to the server.
 * @param {String} id The ID (VIEWER or OWNER) of the person.
 * @param {String} person id.
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object.
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchPersonFriendshipRequest = function(id, key, opt_parms){
	return {
		type: MyOpenSpace.RequestType.FETCH_PERSON_FRIENDSHIP,
		parameters: {
			id: id,
			key: key
		}
	};
};

/**
 * Creates an object to be used when sending to the server.
 * @param {String} id The ID (VIEWER or OWNER) of the person.
 * @param {Array} array of person ids.
 * @param {undefined} opt_params Not used at this time.
 * @return {Object} A request object.
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchPeopleFriendshipRequest = function(id, key, opt_parms){
	return {
		type: MyOpenSpace.RequestType.FETCH_PEOPLE_FRIENDSHIP,
		parameters: {
			id: id,
			key: key
		}
	};
};

/**
 * Creates an item to request friends from the server.
 * When processed, returns an opensocial.Collection&lt;MyOpenSpace.Person&gt; object.
 * @param {Array.&lt;String&gt; | String} idSpec An ID reference used to specify which people to fetch; the supported keys VIEWER_FRIENDS or OWNER_FRIENDS.
 * @param {Map.&lt;opensocial.DataRequest.PeopleRequestFields} opt_params Additional opensocial.DataRequest.PeopleRequestFields params to pass to the request.
 * @return {Object} A request object.
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.newFetchPeopleRequest = function(idSpec, opt_params) {
    opt_params = opt_params || {};
	var fields = opensocial.DataRequest.PeopleRequestFields;
	var groups = opensocial.DataRequest.Group;
	var type = MyOpenSpace.RequestType.FETCH_PEOPLE;
	
	return {
	    type: type,
		parameters: {
		    idSpec:idSpec,
			sortOrder:opt_params[fields.SORT_ORDER],
		    details:opt_params[fields.PROFILE_DETAILS],
		    filter:opt_params[fields.FILTER] || opensocial.DataRequest.FilterType.ALL,
		    first: (typeof opt_params[fields.FIRST] === 'undefined')?1:opt_params[fields.FIRST],
		    max: (typeof  opt_params[fields.MAX] === 'undefined') ? MyOpenSpace.DefaultPageSize :opt_params[fields.MAX]
		},
		'opt_params': {
			useCache: (opt_params[MyOpenSpace.DataRequest.CacheControl.USE_CACHE]),
			refreshInterval: (opt_params[MyOpenSpace.DataRequest.CacheControl.REFRESH_INTERVAL])
		}
	};
};

/**
 * Determines if the requested Person will be mapped internally to BASIC, FULL or EXTENDED
 * @function
 * @return {MyOpenSpace.DetailType} The detail type of the requested person
 * @private
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.mapPersonDetails_ = function(params) {
    var details = params && params[opensocial.DataRequest.PeopleRequestFields.PROFILE_DETAILS];
	if(!details) return { fields : MyOpenSpace.DataRequest.Constants.BASIC_PERSON_FIELDS, unsupported : null };
	
	var fields = [];
	var unsupported = [];
	
	fields[opensocial.Person.Field.ID] = opensocial.Person.Field.ID;
	fields[opensocial.Person.Field.NAME] = MyOpenSpace.DataRequest.Constants.NAME_FIELDS;
	fields[opensocial.Person.Field.NICKNAME] = opensocial.Person.Field.NICKNAME;
	fields[opensocial.Person.Field.THUMBNAIL_URL] = opensocial.Person.Field.THUMBNAIL_URL;
	fields[opensocial.Person.Field.PROFILE_URL] = opensocial.Person.Field.PROFILE_URL;
	
	
	var environment = opensocial.getEnvironment();
	
	for(var i = 0; i < details.length; i++){
		switch (details[i]){
			case opensocial.Person.Field.NAME:
				//Default value Inclueded by default
				break;
			case opensocial.Person.Field.JOBS:
				fields[opensocial.Person.Field.JOBS] = 'organizations';
				break;
			case MyOpenSpace.Person.Field.LARGE_IMAGE:
			case MyOpenSpace.Person.Field.MEDIUM_IMAGE:
				fields['photos'] = 'photos';
				break;
			default: 
				if  (environment.supportsField(opensocial.Environment.ObjectType.PERSON, details[i])){
					fields[details[i]] = details[i];
				}
				else{
					unsupported[details[i]] = true;
				}
			break;
		}
	}

	unsupportedStr = '';
	for (var i in unsupported){
		if (unsupported.hasOwnProperty(i)){
			unsupportedStr += i + ',';			
		}
	} 
	if (unsupportedStr === '') {
		unsupportedStr = null;
	}
	else {
		unsupportedStr = unsupportedStr.substr(0, unsupportedStr.length - 1);
	}

    if(Array.sort){
        fields = Array.sort(fields);
    }
    else{
        fields = fields.sort(fields);
    }
	
	var fieldsStr = '';
	for (var i in fields) {
		if (fields.hasOwnProperty(i)) {
			fieldsStr += fields[i] + ',';
		}
	}
	fieldsStr = fieldsStr.substr(0, fieldsStr.length - 1);
	return { fields : fieldsStr, unsupported : unsupportedStr };
};



/**
 * Get a new MySpace environment object.
 * @return {MyOpenSpace.Environment} the MySpace environment object
 * @private
 */
MyOpenSpace.MySpaceContainer.prototype.newMySpaceEnvironment = function(supportedPostToTargets, app) {
  return new MyOpenSpace.Environment(supportedPostToTargets, app);
};

/**
 * Starts the sending process
 * @param {opensocial.DataRequest} dataRequest The request to get processed and sent
 * @internal
 */
MyOpenSpace.MySpaceContainer.prototype.requestData = function(dataRequest, callback) {
    dataRequest.requestProcessor_.prepareForSend(dataRequest);
    dataRequest.requestProcessor_.startProcessing();
};

MyOpenSpace.MySpaceContainer.prototype.newFetchGlobalAppDataRequest = function() {
    return {
		type: MyOpenSpace.RequestType.FETCH_GLOBAL_DATA
	};
};

MyOpenSpace.MySpaceContainer.prototype.newFetchInstanceAppDataRequest = function() {
    return {
		type: MyOpenSpace.RequestType.FETCH_INSTANCE_DATA
	};
};

MyOpenSpace.MySpaceContainer.prototype.newUpdateInstanceAppDataRequest = function() {
    return {
		type: MyOpenSpace.RequestType.UPDATE_INSTANCE_DATA
	};
};

MyOpenSpace.MySpaceContainer.prototype.newFetchPersonAppDataRequest = function(idSpec, keys, opt_params) {
    return {
		type: MyOpenSpace.RequestType.FETCH_PERSON_DATA,
		parameters : {
		    idSpec: idSpec,
		    keys: keys,
		    opt_params: opt_params
		}
	};
};

MyOpenSpace.MySpaceContainer.prototype.newUpdatePersonAppDataRequest = function(id, key, value) {
    return {
		type: MyOpenSpace.RequestType.UPDATE_PERSON_DATA,
		parameters : {
		    id: id,
		    key : key,
		    value : value
		}
		
	};
};

MyOpenSpace.MySpaceContainer.prototype.newRemovePersonAppDataRequest = function(id, keys) {
    return {
		type: MyOpenSpace.RequestType.REMOVE_PERSON_DATA,
		parameters : {
		    id: id,
		    keys: keys
		}
	};
};

MyOpenSpace.MySpaceContainer.prototype.newFetchActivitiesRequest = function(idSpec, opt_params) {
    return {
        type: MyOpenSpace.RequestType.FETCH_ACTIVITIES,
		parameters : {
		    idSpec: idSpec
		}
	};
};

MyOpenSpace.MySpaceContainer.prototype.requestCreateActivity = function(activity, priority, opt_callback){
    if (!activity || !activity.getField(opensocial.Activity.Field.TITLE_ID)) {
        if(opt_callback){
            var ri = MSID.Container.get().newResponseItem(null, null, opensocial.ResponseItem.Error.BAD_REQUEST, "You must supply an opensocial.Activity object with a TITLE_ID.");
            opt_callback(ri);
        }
        return;
    }
    
    var convertActivityToMessage = function(activity){
        var body, title, title_id, body_id, type = MyOpenSpace.PostTo.Targets.ACTIVITY;
        
        var miArray = activity.getField(opensocial.Activity.Field.MEDIA_ITEMS);
        var returnError = function(code, message){
			if(opt_callback){
                var ri = MSID.Container.get().newResponseItem(null, null, 
                        code, message);
                opt_callback(ri);
            } 
		}
		
        if(miArray){
            if(miArray.constructor !== Array){
				returnError(opensocial.ResponseItem.Error.BAD_REQUEST, 
                            "Media items must be supplied in an array.");
                return;
            }
            
            if(miArray.length > 3){
				returnError(opensocial.ResponseItem.Error.BAD_REQUEST, 
                            "You may only supply up to three media items for activities at this time.")
                return;
            }
			
			for (var i = 0; i < miArray.length; i++){
				var mediaItem = miArray[i];
	
				if (typeof(mediaItem.getField) === 'undefined'){
					returnError( 
							opensocial.ResponseItem.Error.BAD_REQUEST, 
							"Invalid MediaItem object.");
					return;
				}
				if (typeof(mediaItem.getField(opensocial.MediaItem.Field.URL)) === 'undefined'){
					returnError(opensocial.ResponseItem.Error.BAD_REQUEST, 
								"MediaItem should contain a URL.");
					return;
				}
			}
        }
        
        // parse media items, save them to the message body
        var miJson = MyOpenSpace.MySpaceContainer.parseMediaItemsToJson(miArray);
        
        if(miJson && miJson.length > 0){
            body = escape(miJson);
        }
        
        // parse template params, save them to the title
		var templateParams = activity.getField(opensocial.Activity.Field.TEMPLATE_PARAMS);
		if (typeof(templateParams) !== 'undefined') {
			if (typeof(templateParams) === 'object') {
				title = escape(gadgets.json.stringify(activity.getField(opensocial.Activity.Field.TEMPLATE_PARAMS)));
				for (var key in templateParams) {
					var templateValue = templateParams[key];
					if (typeof(templateValue) === 'string') {
						continue;
					}
					else 
						if (typeof(templateValue) === 'number') {
							continue;
						}
						else {
							returnError(opensocial.ResponseItem.Error.BAD_REQUEST, "Field opensocial.Activity.Field.TEMPLATE_PARAMS contains invalid types. Values shuold be strings.");
							return;
						}
				}
			}
			else {
				returnError(opensocial.ResponseItem.Error.BAD_REQUEST, "Field opensocial.Activity.Field.TEMPLATE_PARAMS is not an object.");
				
				return;
			}
		}
        
        // parse title id, save them to the title id
        title_id = escape(activity.getField(opensocial.Activity.Field.TITLE_ID));
        
        // parse priority, save them to the body id
        body_id = activity.getField(opensocial.Activity.Field.PRIORITY);
        
        var params = {};
        params[opensocial.Message.Field.TITLE] = title;
        params[opensocial.Message.Field.TITLE_ID] = title_id;
        params[opensocial.Message.Field.TYPE] = type;
        params[opensocial.Message.Field.BODY_ID] = priority;
        
        return opensocial.newMessage(body, params);
    };
    
    if(priority) activity.setField(opensocial.Activity.Field.PRIORITY, priority);
    var message = convertActivityToMessage(activity);
	
	var activityParams = null;
	if(message.getField(opensocial.Message.Field.BODY) != null 
		&& typeof(message.getField(opensocial.Message.Field.BODY)) !== 'undefined'){			
			activityParams = {"template" : message.getField(opensocial.Message.Field.TITLE_ID),
			"templateParams" : message.getField(opensocial.Message.Field.TITLE), 
			"mediaItems" : message.getField(opensocial.Message.Field.BODY)}	
	}
	else{
		activityParams = {"template" : message.getField(opensocial.Message.Field.TITLE_ID),
		"templateParams" : message.getField(opensocial.Message.Field.TITLE)}
	}
	
	if (typeof(activityParams) !== 'undefined'){
		MSID.Activity.action_raiseActivity(opt_callback, activityParams);		
	}
    

};

MyOpenSpace.MySpaceContainer.parseMediaItemsToJson = function(miArray){
    if(!miArray) return "";
    
    var miJson = "{";

    var miUrl
    for(var i = 0; i < miArray.length; i++){
        if(0 !== i){
            miJson += ",";
        }
        
        if(miArray[i].getField(opensocial.MediaItem.Field.URL) === MyOpenSpace.MediaItemHelper.PROFILE_PICTURE){
            miUrl = "http://api.myspace.com/v1/users/" + gadgets.views.getParams().ownerId;
        }
        else{
            miUrl = miArray[i].getField(opensocial.MediaItem.Field.URL);
        }
        miJson += "\"" + miUrl + "\"";
    }
    miJson += "}";
    
    return miJson;
};

MyOpenSpace.MySpaceContainer.prototype.registerParam = function(key, value) {
    this.params_[key] = value;
    gadgets.views.getParams()[key] = value;
};

MyOpenSpace.MySpaceContainer.container_ = null;if (typeof (MyOpenSpace.RequestProcessor_) == "undefined") MyOpenSpace.RequestProcessor_ = {};
/**
 * MySpace's implementation of a data request manager; linking security, queueing, callbacks, responses, and request actions all together.
 * Created to abstract away from OpenSocial's base objects so MySpace can link in EXTENDED entities/methods more easily.
 * @class
 * @name MyOpenSpace.RequestProcessor_
 * @private
 * @internal
 */
MyOpenSpace.RequestProcessor_ = function(){
    //this._resultCache = new QuickHash();
    this.executionInterval_ = 200;
    //this.executionModel_ = MyOpenSpace.RequestProcessor_.ExecutionModel_.ASYNC;
    this.executionModel_ = MyOpenSpace.RequestProcessor_.ExecutionModel_.SERIAL;
    //this.executionModel_ = MyOpenSpace.RequestProcessor_.ExecutionModel_.THROTTLED
    this.requestActions_ = null;
    this.TotalWorkItems;
    this.WorkItemsToProcess;
    this.WorkItemsProcessed;
    //this._readyToSend = false;
    this.paused_ = false;
    this.aborted_ = false;
    this.authorizationSchemaSet_ = false;
    
    this.init();
};

/**
 * Specifies how the RequestProcessor will process the queue of work items.
 * @static
 * @class
 * @internal
 */
MyOpenSpace.RequestProcessor_.ExecutionModel_ = { 
    /**
     * TODO document
     * @property {int} SERIAL Executes the queue async one after each other.  Essentially a blocking async.
     * @internal
     */
    SERIAL: "SERIAL",
    
    /**
     * Not currently supported.
     * @property {int} ASYNC Executes the queue async one after each other, without blocking.
     * @internal
     */
    ASYNC: "ASYNC",
    
    /**
     * Not currently supported.
     * @property {int} THROTTLED Same as SERIAL, but with a delay between each work item processed.  Use throttleProcessingSpeed(msDelay) to set delay time.
     * @internal
     */
    THROTTLED: "THROTTLED"
}; 


MyOpenSpace.RequestProcessor_.prototype = {
     /**
     * Init handles all initialization for the RequestProcessor_.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    init: function() {
        this.workItemPool_ = new this.delayShiftQueue();
    },
    
    /**
     * prepareForSend will setup the queue in preparation for processing the work items.
     * @param {opensocial.DataRequest} ptr Pointer to DataRequest
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    prepareForSend : function(ptr) {
        //this._readyToSend = true;
        this.requestActions_ = new MyOpenSpace.DataRequest.RequestActions_(ptr);
        //startProcessing();
        //this.workItemPool_.prioritize();
    },
    
     /**
     * setAuthorization sets the authorization template used to verify/set authorizations on each DataRequest's request.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @param {Object} template The authorization template to apply to this DataRequest.
     * @internal
     */
    setAuthorization: function(template) {
        this.authTemplate_ = template;
	    this.authorizationSchemaSet_ = true;
	},
	
	 /**
     * addWorkItem will add a work item to the RequestProcessor's queue.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @param {MyOpenSpace.RequestProcessor_.WorkItem} workItem The work item to add to the queue.
     * @internal
     */
    addWorkItem: function(workItem) { // was:addWorkItem: function(workItem, priority) {
        //if (!this._readyToSend){
            //TODO: put hasPermission/requestPermission here
            //workItem.priority = priority;
            this.workItemPool_.push(workItem);
            
        //}
    },
    
     /**
     * startProcessing will being executing work items from the queue, as defined by the ExecutionModel_.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    startProcessing: function() {
        switch (this.executionModel_) {
            case MyOpenSpace.RequestProcessor_.ExecutionModel_.SERIAL: //one after the other
                this.process();
                break;
            case MyOpenSpace.RequestProcessor_.ExecutionModel_.ASYNC: // all async at once
                for (var i=0;i<=this.workItemPool_.size();i++)
                {
                    var workItem = this.workItemPool_.pop();
                    this.process(workItem);
                }
                break;
            case MyOpenSpace.RequestProcessor_.ExecutionModel_.THROTTLED: // one after the other + delay
                this.processWithDelay();
                break;
        }
    },
    
    /**
     * process will execute a workItem
     * @param {MyOpenSpace.RequestProcessor_.WorkItem} workItem to process; if omitted - pop the next work item from queue.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    process: function(workItem) {
        if (workItem) {
            this.requestActions_[workItem.type](workItem);
            return;
        }
        
        workItem = this.workItemPool_.pop();
        if(workItem)
            this.requestActions_[workItem.type](workItem);
        
        if(this.executionModel_ === MyOpenSpace.RequestProcessor_.ExecutionModel_.THROTTLED)
            this.processWithDelay();
        //else if (this.workItemPool_.size() > 0 && !this.paused_ && !this.aborted_)
            //this.process(); // queue up next work item immediately
    },
    
    /**
     * processWithDelay will execute a workItem, then set an interval of executionInterval before executing the next work item; so long as pauseProcessing() or abortProcessing() has not be invoked.
     * @param {MyOpenSpace.RequestProcessor_.WorkItem} workItem to process; if omitted - pop the next work item from queue.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    processWithDelay: function() {
        //var workItem = this.workItemPool_.pop();
        var pointer = this;
        if (pointer.workItemPool_.size() > 0 && !pointer.paused_ && !pointer.aborted_)
            setTimeout(function () { pointer.process(); }, pointer.executionInterval_); // queue up next work item after executionInterval ms
        //this.requestActions_[workItem.type](workItem);
    },
    
    /**
     * pauseProcessing will pause the execution of SERIAL and THROTTLED execution models, resumeProcessing() will continue execution.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    pauseProcessing: function() {
        this.paused_ = true;
    },
    
    /**
     * abortProcessing will stop the execution of SERIAL and THROTTLED execution models, with no option to recover.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    abortProcessing: function() {
        this.aborted_ = true;
    },
    
    /**
     * resumeProcessing will unpause the execution of SERIAL and THROTTLED execution models.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    resumeProcessing: function() {
        this.paused_ = false;
    },
    
    /**
     * throttleProcessingSpeed sets the delay between work item processing in THROTTLED execution model.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    throttleProcessingSpeed: function(msDelay) {
        this.executionInterval_ = msDelay;
    },
    
    /**
     * delayShiftQueue is a delay shift queue implementation.  Only shifts queue after (len/2) pops to save cycles.
     * @memberOf MyOpenSpace.RequestProcessor_
     * @internal
     */
    delayShiftQueue: function(){ 
        var q=new Array();
        var qSpace=0;
        
        /**
         * push will add a work item to the queue
         * @param {MyOpenSpace.RequestProcessor_.WorkItem} element Work item to add to the queue. 
         * @internal
         */
        this.push=function(element){
            q.push(element);
        };
        
        /**
         * pop will return the next work item in the queue.  Queue will be shifted once length/2 calls have been made to this function.
         * @internal
         */
        this.pop=function(){
            if (q.length){
                var element=q[qSpace];
                if (++qSpace*2 >= q.length){
                    for (var i=qSpace;i<q.length;i++) q[i-qSpace]=q[i];
                    q.length-=qSpace;
                    qSpace=0;
                }
                return element;
            } else {
                return undefined;
            }
        };
        
        /**
         * size will return the number of work items in the queue.
         * @internal
         */
        this.size=function(){
            return q.length;
        };
        
        /**
         * prioritize will sort the queue based on priority (critical, high, normal, low)
         * @internal
         */
        this.prioritize=function(){
            q.sort(this.prioritySort_);
        };
        
        /**
         * prioritySort_ is an internal comparer for ordering work items based on priority.
         * @internal
         */
        this.prioritySort_=function(a,b){
            if (a.priority > b.priority) return -1;
            if (a.priority == b.priority) return 0;
            if (a.priority < b.priority) return 1;
        };
   }
};

if (typeof(MyOpenSpace.RequestProcessor_.WorkItem) == "undefined") MyOpenSpace.RequestProcessor_.WorkItem = {};

/**
 * Adds work item properties to a DataRequest for processing within the queue.
 * @constructor MyOpenSpace.RequestProcessor_.WorkItem
 * @param {MyOpenSpace._DataRequest} request The datarequest associated with the work item.
 * @internal
 */
MyOpenSpace.RequestProcessor_.WorkItem = new function(request){
    this.Request = request;
    this.CreationTime = new Date().getTime(); //ms in epoch time
    this.QueueTime = null;
    this.WorkItemType = null;
    this.ProcessStartTime = null;
    this.ProcessEndTime = null;
    this.Priority = MyOpenSpace.RequestProcessor_.WorkItem.NORMAL;
};

/**
 * Defines level of priority for work items.  Processed in this order: CRITICAL -> HIGH -> NORMAL -> LOW
 * Allows for a queue's priority to be calculated as a sum of all work item's priorities.
 * We may want to add in a dependency object for work items, so one may process first if another depends on it
 * Note: Currently we don't use priorities for anything.
 * @static
 * @class
 * @name Priority
 * @internal
 */
MyOpenSpace.RequestProcessor_.WorkItem.Priority = { 
    /**
     * The integer weight for a low priority item.  Decreases queue priority.
     * @memberOf MyOpenSpace.RequestProcessor_.WorkItem.Priority
     * @internal
     */
    "LOW": -1,
    
    /**
     * The integer weight for a normal priority item.  Does not affect queue priority.
     * @memberOf MyOpenSpace.RequestProcessor_.WorkItem.Priority
     * @internal
     */
    "NORMAL": 0,
    
    /**
     * The integer weight for a high priority item.  Increases queue's priority.
     * @memberOf MyOpenSpace.RequestProcessor_.WorkItem.Priority
     * @internal
     */
    "HIGH": 1,
    
    /**
     * The integer weight for a critical priority item.  Increases queue's priority by twice magnitude of high.
     * @memberOf MyOpenSpace.RequestProcessor_.WorkItem.Priority
     * @internal
     */
    "CRITICAL": 2
};
