(function() 
{
	FlickrService = function(oConfig) 
	{
		if(FlickrService.getInstance() != null)
			throw new Error("Only one FlickrService at a time, please.");
		if(typeof oConfig.apiKey == "undefined")
			throw new Error("Invalid arguments to FlickrService constructor.  Please specify an API key.");
		if(typeof oConfig.secret == "undefined")
			throw new Error("Invalid arguments to FlickrService constructor.  Please specify a shared secret.");
		if(oConfig.permissionLevel)
			this._permissionLevel = oConfig.permissionLevel;
		this._apiKey = oConfig.apiKey;
		this._secret = oConfig.secret;
		this._loadCookieSettings();
		
		FlickrService._instance = this;
		//todo: bring this back, but browser independent (IE does not work with onload)
		//window.onload = this._checkFrob.call(this);
	};
	
	FlickrService.prototype._apiKey				= null;
	FlickrService.prototype._secret				= null;
	FlickrService.prototype._query				= null;
	FlickrService.prototype._scriptTag			= null;
	FlickrService.prototype._authToken			= null;
	FlickrService.prototype._permissionLevel	= null;	   
	FlickrService.prototype._user				= null;
	FlickrService.prototype._verifiedPerms		= null;

	FlickrService._instance						= null;
	FlickrService._apiUrl						= "http://api.flickr.com/services/rest/?";
	FlickrService._authUrl						= "http://flickr.com/services/auth/?";
	FlickrService._scriptTagId					= "flickrservice-jsonp";
	FlickrService._authCookieName				= "flickrservice_auth";
	FlickrService._userCookieName				= "flickrservice_user";	   
	
	FlickrService.photoSizes = 
	{
		smallSquare : "s", /* 75x75 */
		thumbnail	: "t", /* 100 on longest side */
		small		: "m", /* 240 on longest side */
		medium		: "",  /* 500 on longest side */
		large		: "b"  /* 1024 on longest side */
	};
	
	FlickrService._authCallback = function(oRes) 
	{
		var self = FlickrService.getInstance(),
			stat = oRes.stat,
			token = null;
		if(stat == "fail") 
			throw new Error("Flickr API authentication error: " + oRes.message);
		else {
			token = oRes.auth.token._content;
			// set cookies to expire in 14 days
			self._setCookie({
				name: FlickrService._authCookieName,
				value: token + "|" + oRes.auth.perms._content,
				expires: 14
			});
			self._setCookie({
				name: FlickrService._userCookieName,
				value: oRes.auth.user.nsid + "|" + oRes.auth.user.username + "|" + oRes.auth.user.fullname,
				expires: 14
			});
			self._user = oRes.auth.user;
			self._verifiedPerms = oRes.auth.perms._content;
		}
	};

	FlickrService.getInstance = function() 
	{
		return FlickrService._instance;
	};
	

	FlickrService.prototype._callMethod = function(sMethod, oConfig) 
	{
		if(typeof oConfig.onComplete == "undefined")
			throw new Error("Invalid arguments to '" + sMethod + "'.  Please specify an onComplete callback method.");
		if(oConfig.onLoading) 
		{
			if(typeof oConfig.onLoading != "function")
				throw new Error("Invalid arguments to '" + sMethod + "'.  onLoading parameter must be a function.");
			else
				oConfig.onLoading.apply(oConfig.onLoading);
		}
		if(oConfig.perms && !this._getAuthToken()) 
		{
			this._authenticate(oConfig.perms); 
		}			 
		else 
		{
			this._buildQuery(sMethod, oConfig);
			this._sendRequest();
		}
	};
	

	FlickrService.prototype._buildQuery = function(sMethod, oConfig) 
	{
		var q = [],
			callback = null,
			now = (new Date).getTime();		   
		if(typeof oConfig.onComplete == "function") 
		{
			callback = "FlickrService.callback_"+now;
			FlickrService["callback_"+now] = oConfig.onComplete;
		} 
		else 
		{
			callback = oConfig.onComplete;
		}
		q.push("api_key="+this._apiKey);
		q.push("format=json");
		q.push("jsoncallback="+callback);
		q.push("method="+sMethod);
		delete oConfig.onComplete;
		delete oConfig.onLoading;
		for(o in oConfig) 
		{
			if(oConfig.hasOwnProperty(o) && o != "perms")
				q.push(o+"="+escape(oConfig[o]));
		}
		this._query = FlickrService._apiUrl + q.join("&");	  
		if(oConfig.perms || oConfig.signCall) 
		{
			this._query += "&auth_token=" + this._getAuthToken();
			this._buildApiSig(q);
		}
	};
	
	FlickrService.prototype._buildApiSig = function(aQuery) 
	{
		aQuery.push("auth_token="+this._getAuthToken());
		aQuery.sort(function(a, b) 
		{
			return a.split('=')[0] > b.split('=')[0];
		});
		this._query += "&api_sig=" + FlickrService.util.md5((this._secret + aQuery.join("")).replace(/=/g,""));
	};
	
	FlickrService.prototype._createScriptTag = function(sSrc) 
	{
		this._scriptTag = document.createElement("script");
		this._scriptTag.type = "text/javascript";
		this._scriptTag.id = FlickrService._scriptTagId;
		this._scriptTag.src = sSrc;
	};
	
	FlickrService.prototype._sendRequest = function() 
	{
		if(document.getElementById(FlickrService._scriptTagId)) {
			// @todo: if uncommented it causes webkit based browser to only run the last callback
			//this._scriptTag.parentNode.removeChild(this._scriptTag);
		}		 
		this._createScriptTag(this._query);
		document.body.appendChild(this._scriptTag);		   
	};
	
	FlickrService.prototype._authenticate = function(sType) {
		var url = FlickrService._authUrl,
			perms = (this._permissionLevel == "delete") ? "delete" : (this._permissionLevel == "write") ? "write" : sType,
			input = this._secret + "api_key" + this._apiKey + "perms" + perms,
			apiSig = FlickrService.util.md5(input);
		url += "api_key=" + this._apiKey + "&perms=" + perms + "&api_sig=" + apiSig;
		window.location = url;
	};	  
	
	FlickrService.prototype._getCookie = function(sName) 
	{
		var cookieNameEq = sName + "=",
			cookies = document.cookie.split(';');
		for(var i=0, len=cookies.length; i<len; i++) {
			var cookie = cookies[i];
			while(cookie.charAt(0) == ' ')
				cookie = cookie.substring(1, cookie.length);
			if(cookie.indexOf(cookieNameEq) == 0)
				return cookie.substring(cookieNameEq.length, cookie.length);
		}
		return null;		
	};
	
	FlickrService.prototype._setCookie = function(oConfig) 
	{
		var date = new Date(),
			expires = null;
		date.setTime(date.getTime() + oConfig.expires*24*60*60*60*1000); // set cookies to expire in 2 weeks
		expires = date.toGMTString();		 
		document.cookie = oConfig.name + "=" + oConfig.value + "; expires=" + expires + "; path=/";
	};
	
	FlickrService.prototype._loadCookieSettings = function() 
	{ 
		if(this._getCookie(FlickrService._authCookieName)) 
		{
			this._authToken = this._getAuthToken();
			this._verifiedPerms = this._getVerifiedPerms();
			if(this._getCookie(FlickrService._userCookieName)) 
			{
				this._user = this.getUser();
			}
		}	 
	};
	
	FlickrService.prototype._checkFrob = function() 
	{
		var location = window.location,
			params = location.search;
		if(params.match(/frob=[a-zA-Z0-9]+/)) 
		{
			var frob = params.match(/frob=([a-zA-Z0-9-_]+)/)[1];
			this._fetchAuthToken(frob);
		}
	};
	
	FlickrService.prototype._fetchAuthToken = function(sFrob) 
	{
		var input = this._secret + "api_key" + this._apiKey + "formatjsonfrob" + sFrob + "jsoncallbackFlickrService._authCallbackmethodflickr.auth.getToken",
			apiSig = FlickrService.util.md5(input),
			url = "http://flickr.com/services/rest/?format=json&jsoncallback=FlickrService._authCallback&method=flickr.auth.getToken&api_key=4854b66d900b3214426f88b147d31b3f&frob=" + sFrob + "&api_sig=" + apiSig,
			scriptTag = document.createElement("script");
		scriptTag.type = "text/javascript";
		scriptTag.src = url;
		document.body.appendChild(scriptTag);
	},
	
	FlickrService.prototype._getAuthToken = function() 
	{
		if(this._authToken != null)
			return this._authToken;
		var authCookie = this._getCookie(FlickrService._authCookieName);
		if(authCookie == null)
			return false;
		return authCookie.split('|')[0];
	};
	
	FlickrService.prototype._getVerifiedPerms = function() 
	{
		if(this._verifiedPerms != null)
			return this._verifiedPerms;
		var authCookie = this._getCookie(FlickrService._authCookieName);
		if(authCookie == null)
			return false;
		return authCookie.split('|')[1];
	};
	
	FlickrService.prototype.getUser = function() 
	{
		if(this._user != null)
			return this._user;
		var userCookie = this._getCookie(FlickrService._userCookieName);
		if(userCookie == null)
			return false;
		return {
			nsid: userCookie.split('|')[0],
			username: userCookie.split('|')[1],
			fullname: userCookie.split('|')[2]
		};
	};	  
	
	FlickrService.prototype.deleteCookies = function() 
	{
		this._setCookie({
			name: FlickrService._authCookieName,
			value: "",
			exipres: -1
		});
		this._setCookie({
			name: FlickrService._userCookieName,
			value: "",
			exipres: -1
		});		   
	};
	
	FlickrService.prototype.getPhotoUrl = function(oPhoto, sSize) 
	{
		var farm	 = oPhoto.farm,
			server	   = oPhoto.server,
			id		   = oPhoto.id,
			secret	  = oPhoto.secret,
			size	 = null,
			url		= null;
		switch(sSize) {
			case FlickrService.photoSizes.smallSquare:
				size = FlickrService.photoSizes.smallSquare;
				break;
			case FlickrService.photoSizes.thumbnail:
				size = FlickrService.photoSizes.thumbnail;
				break;				  
			case FlickrService.photoSizes.small:
				size = FlickrService.photoSizes.small;			  
				break;				  
			case FlickrService.photoSizes.medium:
				size = FlickrService.photoSizes.medium;			   
				break;
			case FlickrService.photoSizes.large:
				size = FlickrService.photoSizes.large;			  
				break;
			case null:
			case undefined:
				size = FlickrService.photoSizes.medium;
				break;
			default:
				throw new Error("Invalid size argument for photos.	Must be either s, t, m, b or <empty>.  See http://www.flickr.com/services/api/misc.urls.html");
				break;
		};
		if(size === FlickrService.photoSizes.medium)
			url = "http://farm"+farm+".static.flickr.com/"+server+"/"+id+"_"+secret+".jpg";
		else
			url = "http://farm"+farm+".static.flickr.com/"+server+"/"+id+"_"+secret+"_"+size+".jpg";
		return url;
	};
	
	FlickrService.prototype.activity = 
	{
		
		/* Requires AUTH (read) */
		userComments: function(oConfig) 
		{
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.activity.userComments', oConfig);
		},

		/* Requires AUTH (read) */		  
		userPhotos: function(oConfig) 
		{
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.activity.userPhotos', oConfig);
		}
		
	};
	
	FlickrService.prototype.auth = 
	{
		
		checkToken: function(oConfig) 
		{
			oConfig.signCall = true;
			return FlickrService.getInstance()._callMethod('flickr.auth.checkToken', oConfig);
		},
		
		getFrob: function(oConfig) {
			oConfig.signCall = true;
			return FlickrService.getInstance()._callMethod('flickr.auth.getFrob', oConfig);
		},
		
		getFullToken: function(oConfig) {
			oConfig.signCall = true;
			return FlickrService.getInstance()._callMethod('flickr.auth.getFullToken', oConfig);
		},		  
		
		getToken: function(oConfig) {
			oConfig.signCall = true;
			return FlickrService.getInstance()._callMethod('flickr.auth.getToken', oConfig);
		}		 
		
	};
	
	FlickrService.prototype.blogs = {

		/* Requires AUTH (read) */
		getList: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.blogs.getList', oConfig);			
		},
		
		/* Requires AUTH (write); Requires HTTP POST */
		postPhoto: function(oConfig) {

		}
		
	};
	
	FlickrService.prototype.contacts = {
		
		/* Requires AUTH (read) */
		getList: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.contacts.getList', oConfig);			   
		},
		
		getPublicList: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.contacts.getPublicList', oConfig);			 
		}
		
	};
	
	FlickrService.prototype.favorites = {
		
		/* Requires AUTH (write); Requires HTTP POST */
		add: function(oConfig) {

		},
		
		/* Requires AUTH (read) */
		getList: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.favorites.getList', oConfig);			
		},
		
		getPublicList: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.favorites.getPublicList', oConfig);			  
		},
		
		/* Requires AUTH (write); Requires HTTP POST */
		remove: function(oConfig) {

		}
		
	};
	
	FlickrService.prototype.groups = {
		/* Requires AUTH (read) */
		browse: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.groups.browse', oConfig);			
		},
		
		getInfo: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.groups.getInfo', oConfig);			 
		},
		
		search: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.groups.search', oConfig);			
		},
		
		pools: {
			
			/* Requires AUTH (write); Requires HTTP POST */
			add: function(oConfig) {

			},
			
			getContext: function(oConfig) {
				return FlickrService.getInstance()._callMethod('flickr.groups.pools.getContext', oConfig);
			},
			
			/* Requires AUTH (read) */
			getGroups: function(oConfig) {
				oConfig.perms = "read";
				return FlickrService.getInstance()._callMethod('flickr.groups.pools.getGroups', oConfig);			 
			},
			
			getPhotos: function(oConfig) {
				return FlickrService.getInstance()._callMethod('flickr.groups.pools.getPhotos', oConfig);
			},
			
			/* Requires AUTH (write); Requires HTTP POST */
			remove: function(oConfig) {

			}		 
			
		}
		
	};
	
	FlickrService.prototype.interestingness = {

		getList: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.interestingness.getList', oConfig);
		}
		
	};
	
	FlickrService.prototype.people = {
		
		findByEmail: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.people.findByEmail', oConfig);
		},
		
		findByUsername: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.people.findByUsername', oConfig);
		},
		
		getInfo: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.people.getInfo', oConfig);
		},		  
		
		getPublicGroups: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.people.getPublicGroups', oConfig);
		},		  
		
		getPublicPhotos: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.people.getPublicPhotos', oConfig);
		},		  
		
		getUploadStatus: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.people.getUploadStatus', oConfig);
		}
	};
	
	FlickrService.prototype.photos = {

		/* Requires AUTH (write); Requires HTTP POST */
		addTags: function(oConfig) {
			
		},
		
		/* Requires AUTH (write); Requires HTTP POST */
		/* Have to rename to deletePhoto b/c of Webkit's treatment of delete keyword */
		deletePhoto: function(oConfig) {
			
		},
		
		getAllContexts: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photos.getAllContexts', oConfig);
		},		  
		
		/* Requires AUTH (read) */
		getContactsPhotos: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.photos.getContactsPhotos', oConfig);
		},		  
		
		getContactsPublicPhotos: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photos.getContactsPublicPhotos', oConfig);
		},		  
		
		getContext: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photos.getContext', oConfig);
		},

		/* Requires AUTH (read) */
		getCounts: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.photos.getCounts', oConfig);			   
		},
		
		getExif: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photos.getExif', oConfig);
		},
		
		getFavorites: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photos.getFavorites', oConfig);
		},
		
		getInfo: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photos.getInfo', oConfig);
		},		  
				
		/* Requires AUTH (read) */
		getNotInSet: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.photos.getNotInSet', oConfig);			 
		},

		/* Requires AUTH (read) */
		getPerms: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.photos.getPerms', oConfig);			  
		},

		getRecent: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photos.getRecent', oConfig);
		},		  

		getSizes: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photos.getSizes', oConfig);
		},
		
		/* Requires AUTH (read) */
		getUntagged: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.photos.getUntagged', oConfig);			 
		},				  
		
		/* Requires AUTH (read) */
		getWithGeoData: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.photos.getWithGeoData', oConfig);			
		},
		
		/* Requires AUTH (read) */
		getWithoutGeoData: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.photos.getWithoutGeoData', oConfig);			   
		},

		/* Requires AUTH (read) */
		getRecentlyUpdated: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.photos.getRecentlyUpdated', oConfig);			
		},
		
		/* Requires AUTH (write); Requires HTTP POST */
		removeTag: function(oConfig) {},

		search: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photos.search', oConfig);
		},
		
		/* Requires AUTH (write); Requires HTTP POST */
		setContentType: function(oConfig) {},
		
		/* Requires AUTH (write); Requires HTTP POST */
		setDates: function(oConfig) {},
		
		/* Requires AUTH (write); Requires HTTP POST */
		setMeta: function(oConfig) {},

		/* Requires AUTH (write); Requires HTTP POST */
		setPerms: function(oConfig) {},
		
		/* Requires AUTH (write); Requires HTTP POST */
		setSafetyLevel: function(oConfig) {},
		
		/* Requires AUTH (write); Requires HTTP POST */
		setTags: function(oConfig) {},
		
		comments: {
			/* Requires AUTH (write); Requires HTTP POST */
			addComment: function(oConfig) {},
			
			/* Requires AUTH (write); Requires HTTP POST */
			deleteComment: function(oConfig) {},
			
			/* Requires AUTH (write); Requires HTTP POST */
			editComment: function(oConfig) {},
			
			getList: function(oConfig) {
				return FlickrService.getInstance()._callMethod('flickr.photos.comments.getList', oConfig);
			}
		},
		
		geo: {
			
			getLocation: function(oConfig) {
				return FlickrService.getInstance()._callMethod('flickr.photos.geo.getLocation', oConfig);
			},
			
			/* Require AUTH (read) */
			getPerms: function(oConfig) {
				oConfig.perms = "read";
				return FlickrService.getInstance()._callMethod('flickr.photos.geo.getPerms', oConfig);
			},
			
			/* Requires AUTH (write); Requires HTTP POST */
			removeLocation: function(oConfig) {

			},
			
			/* Requires AUTH (write); Requires HTTP POST */
			setLocation: function(oConfig) {

			},
			
			/* Requires AUTH (write); Requires HTTP POST */
			setPerms: function(oConfig) {

			}
			
		},
		
		licenses: {
			getInfo: function(oConfig) {
				return FlickrService.getInstance()._callMethod('flickr.photos.licenses.getInfo', oConfig);
			},			  

			/* Requires AUTH (write); Requires HTTP POST */
			setLicense: function(oConfig) {}
		},
		
		notes: {
			/* Requires AUTH (write); Requires HTTP POST */
			add: function(oConfig) {},
			
			/* Requires AUTH (write); Requires HTTP POST */
			/* Have to rename to deleteNote b/c of Webkit's treatment of delete keyword */
			deleteNote: function(oConfig) {},
			
			/* Requires AUTH (write); Requires HTTP POST */
			edit: function(oConfig) {}
		},
		
		transform: {
			/* Requires AUTH (write); Requires HTTP POST */
			rotate: function(oConfig) {}
		},
		
		upload: {
			checkTickets: function(oConfig) {
				return FlickrService.getInstance()._callMethod('flickr.photos.upload.checkTickets', oConfig);
			}
		}
	};
	
	FlickrService.prototype.photosets = {
		
		/* Requires AUTH (write); Requires HTTP POST */
		addPhoto: function(oConfig) {

		},
		
		/* Requires AUTH (write); Requires HTTP POST */
		create: function(oConfig) {

		},	  
		
		/* Requires AUTH (write); Requires HTTP POST */
		/* Have to rename to deletePhotoset b/c of Webkit's treatment of delete keyword */
		deletePhotoset: function(oConfig) {},
		
		/* Requires AUTH (write); Requires HTTP POST */
		editMeta: function(oConfig) {},

		/* Requires AUTH (write); Requires HTTP POST */
		editPhotos: function(oConfig) {},
		
		getContext: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photosets.getContext', oConfig);
		},

		getInfo: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photosets.getInfo', oConfig);
		},		  
		
		getList: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photosets.getList', oConfig);
		},		  
		
		getPhotos: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.photosets.getPhotos', oConfig);
		},	  
		
		/* Requires AUTH (write); Requires HTTP POST */
		orderSets: function(oConfig) {},		  
		
		/* Requires AUTH (write); Requires HTTP POST */
		removePhoto: function(oConfig) {},
		
		comments: {
			/* Requires AUTH (write); Requires HTTP POST */
			addComment: function(oConfig) {},
			
			/* Requires AUTH (write); Requires HTTP POST */
			deleteComment: function(oConfig) {

			},		  
						
			/* Requires AUTH (write); Requires HTTP POST */
			editComment: function(oConfig) {},		  
			
			/* Requires AUTH (write); Requires HTTP POST */
			getList: function(oConfig) {}	 
		}
		
	};
	
	FlickrService.prototype.places = {

		find: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.places.find', oConfig);
		},	  
		
		findByLatLon: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.places.findByLatLon', oConfig);
		},	  
		
		resolvePlaceId: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.places.resolvePlaceId', oConfig);
		},
		
		resolvePlaceUrl: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.places.resolvePlaceUrl', oConfig);
		}
		
	};
	
	FlickrService.prototype.prefs = {
		
		/* Requires AUTH (read) */
		getContentType: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.prefs.getContentType', oConfig);
		},
		
		/* Requires AUTH (read) */
		getGeoPerms: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.prefs.getGeoPerms', oConfig);
		},		  
		
		/* Requires AUTH (read) */
		getHidden: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.prefs.getHidden', oConfig);
		},		  
		
		/* Requires AUTH (read) */
		getPrivacy: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.prefs.getPrivacy', oConfig);
		},		  
		
		/* Requires AUTH (read) */
		getSafetyLevel: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.prefs.getSafetyLevel', oConfig);
		}		 
		
	};
	
	FlickrService.prototype.reflection = {
		
		getMethodInfo: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.reflection.getMethodInfo', oConfig);
		},

		getMethods: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.reflection.getMethods', oConfig);
		}		 
		
	};
	
	FlickrService.prototype.tags = {
		
		getHotList: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.tags.getHotList', oConfig);
		},		  
		
		getListPhoto: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.tags.getListPhoto', oConfig);
		},		  
		
		getListUser: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.tags.getListUser', oConfig);
		},		  
		
		getListUserPopular: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.tags.getListUserPopular', oConfig);
		},		  
		
		getListUserRaw: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.tags.getListUserRaw', oConfig);
		},		  
		
		getRelated: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.tags.getRelated', oConfig);
		}	 
		
	};
	
	FlickrService.prototype.test = {
		
		echo: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.test.echo', oConfig);
		},		  
		
		/* Requires AUTH (read) */
		login: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.test.login', oConfig);
		},
		
		/* Requires AUTH (read) */
		/* NOTE: changing name from null to testNull -> safari keyword */
		testNull: function(oConfig) {
			oConfig.perms = "read";
			return FlickrService.getInstance()._callMethod('flickr.test.null', oConfig);
		}	 
		
	};
	
	FlickrService.prototype.urls = {
		
		getGroup: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.urls.getGroup', oConfig);
		},
		
		getUserPhotos: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.urls.getUserPhotos', oConfig);
		},		  
		
		getUserProfile: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.urls.getUserProfile', oConfig);
		},		  
		
		lookupGroup: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.urls.lookupGroup', oConfig);
		},		  
		
		lookupUser: function(oConfig) {
			return FlickrService.getInstance()._callMethod('flickr.urls.lookupUser', oConfig);
		}
		
	};
	
	// http://kevin.vanzonneveld.net
	// +   original by: Webtoolkit.info (http://www.webtoolkit.info/)
	// +   improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)		   
	FlickrService.util = {
		utf8_encode: function(str_data) {
			str_data = str_data.replace(/\r\n/g,"\n");
			var tmp_arr = [], ac = 0;

			for (var n = 0; n < str_data.length; n++) {
				var c = str_data.charCodeAt(n);
				if (c < 128) {
					tmp_arr[ac++] = String.fromCharCode(c);
				} else if((c > 127) && (c < 2048)) {
					tmp_arr[ac++] = String.fromCharCode((c >> 6) | 192);
					tmp_arr[ac++] = String.fromCharCode((c & 63) | 128);
				} else {
					tmp_arr[ac++] = String.fromCharCode((c >> 12) | 224);
					tmp_arr[ac++] = String.fromCharCode(((c >> 6) & 63) | 128);
					tmp_arr[ac++] = String.fromCharCode((c & 63) | 128);
				}
			}

			return tmp_arr.join('');		
		},

		md5: function(str) {
			var RotateLeft = function(lValue, iShiftBits) {
					return (lValue<<iShiftBits) | (lValue>>>(32-iShiftBits));
				};

			var AddUnsigned = function(lX,lY) {
					var lX4,lY4,lX8,lY8,lResult;
					lX8 = (lX & 0x80000000);
					lY8 = (lY & 0x80000000);
					lX4 = (lX & 0x40000000);
					lY4 = (lY & 0x40000000);
					lResult = (lX & 0x3FFFFFFF)+(lY & 0x3FFFFFFF);
					if (lX4 & lY4) {
						return (lResult ^ 0x80000000 ^ lX8 ^ lY8);
					}
					if (lX4 | lY4) {
						if (lResult & 0x40000000) {
							return (lResult ^ 0xC0000000 ^ lX8 ^ lY8);
						} else {
							return (lResult ^ 0x40000000 ^ lX8 ^ lY8);
						}
					} else {
						return (lResult ^ lX8 ^ lY8);
					}
				};

			var F = function(x,y,z) { return (x & y) | ((~x) & z); };
			var G = function(x,y,z) { return (x & z) | (y & (~z)); };
			var H = function(x,y,z) { return (x ^ y ^ z); };
			var I = function(x,y,z) { return (y ^ (x | (~z))); };

			var FF = function(a,b,c,d,x,s,ac) {
					a = AddUnsigned(a, AddUnsigned(AddUnsigned(F(b, c, d), x), ac));
					return AddUnsigned(RotateLeft(a, s), b);
				};

			var GG = function(a,b,c,d,x,s,ac) {
					a = AddUnsigned(a, AddUnsigned(AddUnsigned(G(b, c, d), x), ac));
					return AddUnsigned(RotateLeft(a, s), b);
				};

			var HH = function(a,b,c,d,x,s,ac) {
					a = AddUnsigned(a, AddUnsigned(AddUnsigned(H(b, c, d), x), ac));
					return AddUnsigned(RotateLeft(a, s), b);
				};

			var II = function(a,b,c,d,x,s,ac) {
					a = AddUnsigned(a, AddUnsigned(AddUnsigned(I(b, c, d), x), ac));
					return AddUnsigned(RotateLeft(a, s), b);
				};

			var ConvertToWordArray = function(str) {
					var lWordCount;
					var lMessageLength = str.length;
					var lNumberOfWords_temp1=lMessageLength + 8;
					var lNumberOfWords_temp2=(lNumberOfWords_temp1-(lNumberOfWords_temp1 % 64))/64;
					var lNumberOfWords = (lNumberOfWords_temp2+1)*16;
					var lWordArray=Array(lNumberOfWords-1);
					var lBytePosition = 0;
					var lByteCount = 0;
					while ( lByteCount < lMessageLength ) {
						lWordCount = (lByteCount-(lByteCount % 4))/4;
						lBytePosition = (lByteCount % 4)*8;
						lWordArray[lWordCount] = (lWordArray[lWordCount] | (str.charCodeAt(lByteCount)<<lBytePosition));
						lByteCount++;
					}
					lWordCount = (lByteCount-(lByteCount % 4))/4;
					lBytePosition = (lByteCount % 4)*8;
					lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80<<lBytePosition);
					lWordArray[lNumberOfWords-2] = lMessageLength<<3;
					lWordArray[lNumberOfWords-1] = lMessageLength>>>29;
					return lWordArray;
				};

			var WordToHex = function(lValue) {
					var WordToHexValue="",WordToHexValue_temp="",lByte,lCount;
					for (lCount = 0;lCount<=3;lCount++) {
						lByte = (lValue>>>(lCount*8)) & 255;
						WordToHexValue_temp = "0" + lByte.toString(16);
						WordToHexValue = WordToHexValue + WordToHexValue_temp.substr(WordToHexValue_temp.length-2,2);
					}
					return WordToHexValue;
				};

			var x=Array();
			var k,AA,BB,CC,DD,a,b,c,d;
			var S11=7, S12=12, S13=17, S14=22;
			var S21=5, S22=9 , S23=14, S24=20;
			var S31=4, S32=11, S33=16, S34=23;
			var S41=6, S42=10, S43=15, S44=21;

			str = FlickrService.util.utf8_encode(str);
			x = ConvertToWordArray(str);
			a = 0x67452301; b = 0xEFCDAB89; c = 0x98BADCFE; d = 0x10325476;

			for (k=0;k<x.length;k+=16) {
				AA=a; BB=b; CC=c; DD=d;
				a=FF(a,b,c,d,x[k+0], S11,0xD76AA478);
				d=FF(d,a,b,c,x[k+1], S12,0xE8C7B756);
				c=FF(c,d,a,b,x[k+2], S13,0x242070DB);
				b=FF(b,c,d,a,x[k+3], S14,0xC1BDCEEE);
				a=FF(a,b,c,d,x[k+4], S11,0xF57C0FAF);
				d=FF(d,a,b,c,x[k+5], S12,0x4787C62A);
				c=FF(c,d,a,b,x[k+6], S13,0xA8304613);
				b=FF(b,c,d,a,x[k+7], S14,0xFD469501);
				a=FF(a,b,c,d,x[k+8], S11,0x698098D8);
				d=FF(d,a,b,c,x[k+9], S12,0x8B44F7AF);
				c=FF(c,d,a,b,x[k+10],S13,0xFFFF5BB1);
				b=FF(b,c,d,a,x[k+11],S14,0x895CD7BE);
				a=FF(a,b,c,d,x[k+12],S11,0x6B901122);
				d=FF(d,a,b,c,x[k+13],S12,0xFD987193);
				c=FF(c,d,a,b,x[k+14],S13,0xA679438E);
				b=FF(b,c,d,a,x[k+15],S14,0x49B40821);
				a=GG(a,b,c,d,x[k+1], S21,0xF61E2562);
				d=GG(d,a,b,c,x[k+6], S22,0xC040B340);
				c=GG(c,d,a,b,x[k+11],S23,0x265E5A51);
				b=GG(b,c,d,a,x[k+0], S24,0xE9B6C7AA);
				a=GG(a,b,c,d,x[k+5], S21,0xD62F105D);
				d=GG(d,a,b,c,x[k+10],S22,0x2441453);
				c=GG(c,d,a,b,x[k+15],S23,0xD8A1E681);
				b=GG(b,c,d,a,x[k+4], S24,0xE7D3FBC8);
				a=GG(a,b,c,d,x[k+9], S21,0x21E1CDE6);
				d=GG(d,a,b,c,x[k+14],S22,0xC33707D6);
				c=GG(c,d,a,b,x[k+3], S23,0xF4D50D87);
				b=GG(b,c,d,a,x[k+8], S24,0x455A14ED);
				a=GG(a,b,c,d,x[k+13],S21,0xA9E3E905);
				d=GG(d,a,b,c,x[k+2], S22,0xFCEFA3F8);
				c=GG(c,d,a,b,x[k+7], S23,0x676F02D9);
				b=GG(b,c,d,a,x[k+12],S24,0x8D2A4C8A);
				a=HH(a,b,c,d,x[k+5], S31,0xFFFA3942);
				d=HH(d,a,b,c,x[k+8], S32,0x8771F681);
				c=HH(c,d,a,b,x[k+11],S33,0x6D9D6122);
				b=HH(b,c,d,a,x[k+14],S34,0xFDE5380C);
				a=HH(a,b,c,d,x[k+1], S31,0xA4BEEA44);
				d=HH(d,a,b,c,x[k+4], S32,0x4BDECFA9);
				c=HH(c,d,a,b,x[k+7], S33,0xF6BB4B60);
				b=HH(b,c,d,a,x[k+10],S34,0xBEBFBC70);
				a=HH(a,b,c,d,x[k+13],S31,0x289B7EC6);
				d=HH(d,a,b,c,x[k+0], S32,0xEAA127FA);
				c=HH(c,d,a,b,x[k+3], S33,0xD4EF3085);
				b=HH(b,c,d,a,x[k+6], S34,0x4881D05);
				a=HH(a,b,c,d,x[k+9], S31,0xD9D4D039);
				d=HH(d,a,b,c,x[k+12],S32,0xE6DB99E5);
				c=HH(c,d,a,b,x[k+15],S33,0x1FA27CF8);
				b=HH(b,c,d,a,x[k+2], S34,0xC4AC5665);
				a=II(a,b,c,d,x[k+0], S41,0xF4292244);
				d=II(d,a,b,c,x[k+7], S42,0x432AFF97);
				c=II(c,d,a,b,x[k+14],S43,0xAB9423A7);
				b=II(b,c,d,a,x[k+5], S44,0xFC93A039);
				a=II(a,b,c,d,x[k+12],S41,0x655B59C3);
				d=II(d,a,b,c,x[k+3], S42,0x8F0CCC92);
				c=II(c,d,a,b,x[k+10],S43,0xFFEFF47D);
				b=II(b,c,d,a,x[k+1], S44,0x85845DD1);
				a=II(a,b,c,d,x[k+8], S41,0x6FA87E4F);
				d=II(d,a,b,c,x[k+15],S42,0xFE2CE6E0);
				c=II(c,d,a,b,x[k+6], S43,0xA3014314);
				b=II(b,c,d,a,x[k+13],S44,0x4E0811A1);
				a=II(a,b,c,d,x[k+4], S41,0xF7537E82);
				d=II(d,a,b,c,x[k+11],S42,0xBD3AF235);
				c=II(c,d,a,b,x[k+2], S43,0x2AD7D2BB);
				b=II(b,c,d,a,x[k+9], S44,0xEB86D391);
				a=AddUnsigned(a,AA);
				b=AddUnsigned(b,BB);
				c=AddUnsigned(c,CC);
				d=AddUnsigned(d,DD);
			}

			var temp = WordToHex(a)+WordToHex(b)+WordToHex(c)+WordToHex(d);

			return temp.toLowerCase();
		}	 
	};	  
})();