OSB.InvitationController = function() {
	/*global $, MyOpenSpace, console, myOpenSocialAppOpts, opensocial */
	/*jslint browser: true */
	
	// UI configurable variables
	var FADE_SPEED = 300;
	var ERASE_SPEED = 100;
	
	// Internal variables
	var userAgent = navigator.userAgent;
	var container;						// OpenSocial Container;
	var firstViewedFriend;				// index of first viewed friend in full controller
	var lastViewedFriend;				// index of last viewed friend in full controller
	var $friendContainer;
	var $invitationController;
	var $lightBoxShadow;
	var isControlsLocked = false;
	var isInvitationDialogOpen = false;
	var totalSelectedFriends;			// total selected friends - integer
	var friendsData = [];				// array of objects of friends data after exludedIds
	var numOfFriends;					// numberOfFriends fetched from server after excludedIds
	var currentFriendIndex;				// current index of viewed friend in small controller
	var controllerData;
	var typeANameString = "Start Typing a Friend's Name";
	
	// ------------------------------------
	// ---------- Public methods ----------
	// ------------------------------------
	
	/**
	 * Public Instance Method
	 * Opens Full Invitation Controller (pop-up).
	 * @param {ControllerData Object} data Parameters related to the controller.
	 */
	this.openFullInvitationController = function(data) {
		console.debug("openFullInvitationController()");
		if (checkIfInvitationDialogOpen()) {
			return;
		}
		controllerData = data;
		controllerData.set("popUp",true);
		console.debug('popUp = "true"');
		controllerData.set("controllerType","full");
		console.debug('controllerType = "full"');
		filterFrieds();
		showFullController();
	};
	
	/**
	 * Public Instance Method
	 * Constructs and returns Full Invitation Controller reperesented by jQuery Object.
	 * @param {ControllerData Object} data Parameters related to the controller.
	 * @return jQuery
	 */
	this.buildFullInvitationController = function(data) {
		console.debug("buildFullInvitationController()");
		if (checkIfInvitationDialogOpen()) {
			return;
		}
		controllerData = data;
		controllerData.set("controllerType","full");
		console.debug('controllerType = "full"');
		filterFrieds();
		return showFullController();
	};

	this.buildMultiFriendSelector = function(data) {
		console.debug("buildMultiFriendSelector()");
		if (checkIfInvitationDialogOpen()) {
			return;
		}
		controllerData = data;
		controllerData.set("controllerType","multi");
		console.debug('controllerType = "multi"');
		filterFrieds();
		return getMultiController();
	};
	
	/**
	 * Public Instance Method
	 * Opens Small Invitation Controller (pop-up).
	 * @param {jQuery Event} _event This is click event that triggers the opening of the controller.
	 * @param {ControllerData Object} data Parameters related to the controller.
	 */
	this.openSmallInvitationController = function(_event, data) {
		console.debug("openSmallInvitationController()");
		if (checkIfInvitationDialogOpen()) {
			return;
		}
		controllerData = data;
		controllerData.set("popUp",true);
		console.debug('popUp = "true"');
		controllerData.set("controllerType","small");
		controllerData.set("event",_event);
		console.debug('controllerType = "small"');
		filterFrieds();
		showSmallController();
	};
	
	/**
	 * Public Instance Method.
	 * Constructs and returns Small Invitation Controller reperesented by jQuery Object.
	 * @param {Object} data
	 * @return jQuery
	 */
	this.buildSmallInvitationController = function(data) {
		console.debug("buildSmallInvitationController()");
		if (checkIfInvitationDialogOpen()) {
			return;
		}
		controllerData = data;
		controllerData.set("controllerType","small");
		console.debug('controllerType = "small"');
		filterFrieds();
		return showSmallController();
	};
	
	/**
	 * Public Instance Method
	 * Executes Button Invitation Controller.
	 * @param {ControllerData Object} data Parameters related to the controller.
	 */
	this.executeButtonController = function(data) {
		console.debug("executeButtonController()");
		if (checkIfInvitationDialogOpen()) {
			return;
		}
		controllerData = data;
		controllerData.set("controllerType","button");
		console.debug('controllerType = "button"');
		if (controllerData.get("buttonType") == "all" || controllerData.get("buttonType") == "random") {
			filterFrieds();
			processButtonController();
		} else if (controllerData.get("buttonType") == "specific") {
			// For Button of specific type the specificUserId is requiered. Check that it
			// have been supplied. 
			if (controllerData.get("specificUserId") !== null) {
				send(controllerData.get("specificUserId"));
			} else {
				console.error("You need to specify specificUserId for Button controller of type specific.");
			}
		}
	};
	
	/**
	 * Public Instance Method.
	 * Constructs and returns Condensed Invitation Controller reperesented by jQuery Object.
	 * @param {Object} data
	 * @return jQuery
	 */
	this.buildCondensedInvitationController = function(data) {
		console.debug("buildCondensedInvitationController(",data,")");
		if (checkIfInvitationDialogOpen()) {
			return;
		}
		controllerData = data;
		controllerData.set("controllerType","condensed");
		console.debug('controllerType = "condensed"');
		filterFrieds();
		return getCondensedController();
	};
	
	/**
	 * Public Instance Method
	 * Creates controller data object to pass to the invitation controller methods.
	 * Constructor initializes default parameters needed to execute the controllers.
	 * To set new values use ControllerData set() method.
	 */
	this.ControllerData = function() {
		
		// Set default values to private variables on Constructor invocation.
		var data = {
			invitationCommentFlag:	"invitation",		// invitation/comment/dynamic
			maxFriendsRows:			3,					// for Full Invitation/Comment Controller
			friendsPerRow:			6,					// for Full Invitation/Comment Controller
			controllerTitle:		"Select Friends.",
			numOfRandomFriends:		3,					// for Button/Small Controllers
			content:				"",					// content of message or invitation
			excludeId:				[],					// array of friends id's to exlucde from invitation
			buttonType:				"all",				// all/random/specific - from button controller
			specificUserId:			null,				// for specific button controller
			serverUrl:				"",					// server URL for sending data
			controllerType:			"",					// full/small/button/condensed
			popUp:					false,				// For full and small controller
			maxFriendsPerView:		18,					// computed automatically
			event:					"",					// for small controller
			messageType:			"public",			// type of the message - public/private/notification/email
			messageTitle:			"",					// Title of the message
			filter:					"",
			max:					null,				// The maximum number of users that can be selected. null for no limit.
			
			additionalFormParams:	[],
			loadResponsePage:		false,
			
			// Condensed Controller Related
			mode:					"2part",			// 2part/normal/noWayBack - Condensed Controller Mode
			unselectedRows:			8,					// num of rows of unselected friends list in Condensed Controller
			selectedRows:			4,					// num of rows of selected friends list in Condensed Controller
			buttonText:				"Send Invitation",	// button text of Condensed Invitation Controller
			preselected:			0					// number or "all" string
		};
		
		/**
		 * Sets values for controllerData parameters. Sets only predefined parameters.
		 * This method can be called in two different ways: set(JSON) - sets many
		 * parameters at once, and set(key,value) - sets one parameter at a time. 
		 * @param {JOSN} data JSON key:value pairs of the paramters. If second parameter
		 * also supplied then this parameter should hold string value for parameter key.
		 * @param {parameter} value Use when setting single parameter value.
		 * @member ControllerData
		 */
		this.set = function() {
			if (arguments.length == 1) {
				// If only one parameter has been supllied, i.e.: set(JSON)
				var newData = arguments[0];
				for (var property in newData) {
					// Set only known properties
					if (property in data) {
						setProperty(property, newData[property]);
					} else {
						console.error("There is no such property = "+property+" defined for ControllerData");
					}
				}
			} else if (arguments.length == 2) {
				// If two parameter have been supplied, i.e.: set(key,value)
				// Set only known properties
				if (arguments[0] in data) {
					setProperty(arguments[0], arguments[1]);
				} else {
					console.error("There is no such property = "+property+" defined for ControllerData");
				}
			} else {
				return null;
			}
		};
		
		/**
		 * @param {Object} property
		 * @param {Object} value
		 * @private
		 */
		function setProperty(property, value) {
			data[property] = value;
			if (property == "maxFriendsRows" || property == "friendsPerRow") {
				data.maxFriendsPerView = data.friendsPerRow*data.maxFriendsRows;		
			}
			// For Booleans
			if (property == "popUp" || property == "loadResponsePage") {
				data[property] = (String(value) === "true") ? true : false;
			}
			// For strings with comma separated values.
			if (property == "excludeId" || property == "additionalFormParams") {
				if (typeof value == "string") {
					data[property] = value.split(/\s*,\s*/);
				} else if (typeof value.length == "number") { // array
					data[property] = value;
				}
			}
			// For Integers
			if (property == "max") {
				data[property] = parseInt(value, 10);
			}
			if (property == "content") {
				data.content = value.replace("&lt;","<").replace("&gt;",">").replace("&amp;","&");
			}
		}
		
		/**
		 * Public Instance Method.
		 * Gets controllerData parameters. If key is supllied, returns value of that key.
		 * If none parameters supllied returns a JSON with parameters as key:value.
		 * @param {String} key Parameter key.
		 */
		this.get = function() {
			if (arguments.length == 1) {
				return data[arguments[0]];
			} else {
				return data;
			}
		};
	};
	
	// -------------------------------------
	// ---------- Private methods ----------
	// -------------------------------------
	
	/**
	 * Function that checks if controller is already open/running. To prevent successive
	 * calls to conroller methods.
	 */
	function checkIfInvitationDialogOpen() {
		if (!isInvitationDialogOpen) {
			isInvitationDialogOpen = true;
			return false;
		} else {
			console.debug("isInvitationDialogOpen == true (quitting)");
			return true;
		}	
	}
	
	/**
	 * Filters friends by excludeId supplied in controllerData (if no supplied it would be
	 * empty). Copy all filtered friend id's to friendsData private variable.
	 */
	function filterFrieds() {
		console.group("filterFrieds()");
		friendsData = [];
		var excludeId = controllerData.get("excludeId");
		console.debug("excludeId: ", excludeId);
		
		for (var i = 0; i < OSB.friendsData.length; i++) {
			// include or ignore friends from selector accroding to hasApp
			// property and the controller type
			var invitationOrMessage = controllerData.get("invitationCommentFlag");
			if (  !((invitationOrMessage == 'invitation' && !OSB.friendsData[i].hasApp) ||
					(invitationOrMessage == 'comment' && OSB.friendsData[i].hasApp) ||
					invitationOrMessage == 'dynamic')) {
				continue;
			}

			var friendId = OSB.friendsData[i].friendId.replace("myspace.com:","");
			var exclude = false;
			for (var j = 0; j < excludeId.length; j++) {
				if (excludeId[j] == friendId) {
					console.debug("FriendId "+friendId+" is excluded.");
					exclude = true;
					continue;
				}
			}
			if (exclude) {
				continue;
			}
			
			// Clone the friend Data.
			friendsData.push({
				friendName: 	OSB.friendsData[i].friendName,
				friendId:		OSB.friendsData[i].friendId,
				thumbnailUrl:	OSB.friendsData[i].thumbnailUrl,
				hasApp:			OSB.friendsData[i].hasApp,
				selected:		false
			});
		}
		
		numOfFriends = friendsData.length;
		console.debug("Number of friends after excludeIds filter = "+numOfFriends);
		
		// Shuffle Friends
		// Richard Durstenfeld algorithm (complexity = n)
		console.debug("Suffle friends");
		var n = numOfFriends;
		var k; // random numver
		var temp;
		while(n > 1) {
			k = Math.floor(Math.random()*n);
			temp = friendsData[n-1];
			friendsData[n-1] = friendsData[k];
			friendsData[k] = temp;
			n--;
		}
		
		console.groupEnd("filterFrieds()");
	}
	
	/**
	 * Initializes, builds HTML code and registers event handlers for Full invitation
	 * controller. If pop-up was set to true, shows the controller, otherwise returns
	 * jQuery object of the controller with css property display:none.
	 */
	function showFullController() {
		console.group("showFullController()");
		
		$invitationController = OSB.templates.getTemplate("fullInvitationController");
		
		// Initialize counters
		totalSelectedFriends = 0;
		$$("#totalFriends").text(numOfFriends);
		$$(".totalSelectedFriends").text(totalSelectedFriends);
		$$(".title").text(controllerData.get("controllerTitle"));
		$$(".sendButton").val(controllerData.get("buttonText"));
		
		var legend = setLegend();
		if (controllerData.get("invitationCommentFlag") == "invitation" ||
			controllerData.get("invitationCommentFlag") == "comment") {
			$$(".legend1").html("<img src='" + legend.imgSrc + "'/> = " + legend.messageTypeLegend);
			$$(".legend2").css("display","none");
		} else if (controllerData.get("invitationCommentFlag") == "dynamic") {
			$$(".legend1").html("<img src='" + legend[0].imgSrc + "'/> = " + legend[0].messageTypeLegend);
			$$(".legend2").html("<img src='" + legend[1].imgSrc + "'/> = " + legend[1].messageTypeLegend);
		}
		
		// Initialize indexes for first and last friends that will be viewed on the
		// first page. Decide if to show pagination controls.
		firstViewedFriend = 0;
		if (numOfFriends <= controllerData.get("maxFriendsPerView")) {
			lastViewedFriend = numOfFriends-1;
			$$("#pagination").css("display","none");
		}
		else {
			lastViewedFriend = controllerData.get("maxFriendsPerView")-1;
			$$("#previous").addClass("locked");
			$$("#totalPages").text(Math.ceil(numOfFriends/controllerData.get("maxFriendsPerView")));
		}
		console.debug("firstViewedFriend = "+firstViewedFriend);
		console.debug("lastViewedFriend = "+lastViewedFriend);
		
		// Build friend containers
		for (var i = firstViewedFriend; i <= lastViewedFriend; i++) {
			buildfriendContainer(i).appendTo($$(".friends"));
		}
		$friendContainer = $$(".friendContainer");
		$friendContainer.css("display","inline");
		
		// Bind event handlers for control buttons
		$$("#close").bind("click",function() {
			if (isControlsLocked) { return false; }
			close();
			return false;
		});
		$$(".selectAll").bind("click",function() {
			if (isControlsLocked) { return false; }
			$friendContainer.addClass("selected");
			for (var i = 0; i < numOfFriends; i++) {
				friendsData[i].selected = true;
			}
			totalSelectedFriends = numOfFriends;
			$$(".totalSelectedFriends").text(totalSelectedFriends);
			return false;
		});
		$$(".unselectAll").bind("click",function() {
			if (isControlsLocked) { return false; }
			$friendContainer.removeClass("selected");
			for (var i = 0; i < numOfFriends; i++) {
				friendsData[i].selected = false;
			}
			totalSelectedFriends = 0;
			$$(".totalSelectedFriends").text(totalSelectedFriends);
			return false;
		});
		$$(".sendButton").bind("click",function() {
			if (isControlsLocked) { return false; }
			if (totalSelectedFriends > 0) {
				send();		
			}
			return false;
		});		
		$$("#next").bind("click",function() {
			if (isControlsLocked) { return false; }
			if (firstViewedFriend + controllerData.get("maxFriendsPerView") > numOfFriends-1) { return false; }
			browseFriendsFullController("next");
			return false;
		});
		$$("#previous").bind("click",function() {
			if (isControlsLocked) { return false; }
			if (firstViewedFriend - controllerData.get("maxFriendsPerView") < 0) { return false; }
			browseFriendsFullController("prev");
			return false;
		});

		$invitationController.css({
			display:	"block",
			position:	"absolute",
			visibility:	"hidden"
		});
		$("body").append($invitationController);		
		var friendsContainerWidth = $$(".friendContainer").outerWidth(true);
		var newControllerWidth = friendsContainerWidth*controllerData.get("friendsPerRow");
		if (!jQuery.boxModel) {
			newControllerWidth += parseInt($invitationController.css("border-right-width")) +
					parseInt($invitationController.css("border-left-width")) +
					parseInt($invitationController.css("padding-right")) +
					parseInt($invitationController.css("padding-left"));
		}
		$invitationController.css("width",newControllerWidth+"px");
		$invitationController.css({
			position:	"relative"
		});
				
		if (controllerData.get("popUp") === true) {
			// Build shadow buffer.
			buildShadow();
			$("body").append($lightBoxShadow);
			
			// Calculate center of the screen
			var pageClientDim = getViewPortDimensions();
			var pageScrollPos = getPageScrollPosition();
			var center = {
				left:	pageScrollPos.left + parseInt(pageClientDim.width/2,10),
				top:	pageScrollPos.top + parseInt(pageClientDim.height/2,10)
			};
			
			$invitationController.css({
				position:	"absolute",
				visibility:	"visible",
				top: 		(center.top - parseInt($invitationController.outerHeight(true)/2,10)) + "px",
				left: 		(center.left - parseInt($invitationController.outerWidth(true)/2,10)) + "px",
				zIndex:		100
			});		
		} else {
			$$("#close").remove();
			console.groupEnd();
			return $invitationController;
		}
		
		console.groupEnd();
	}

	/**
	 * Class "filteredSelected" (display:none) set on all list items when user presses "Selected" tab
	 * and where not selected by user previously.
	 * Class "filtered" (display:none) set on all list items that doesn't match filtered
	 * string in search bar.
	 * The rest of the selection logic and binding of event handlers is done in the
	 * buildfriendContainer() method
	 */
	function getMultiController() {
		console.group("getMultiController()");
		
		$invitationController = OSB.templates.getTemplate("multiFriendSelector");
		
		var selectButtonText = "all";
		var max = controllerData.get("max");
		if (max != null) {
			selectButtonText = max + "";
		}
		
		// Initialize counters
		totalSelectedFriends = 0;
		$$(".selectAll").text("select " + selectButtonText);
		$$(".totalSelectedFriends").text(totalSelectedFriends);
		$$(".title").text(controllerData.get("controllerTitle"));
		$$(".sendButton").val(controllerData.get("buttonText"));
		var selectedTab = "all";
		
		var legend = setLegend();
		if (controllerData.get("invitationCommentFlag") == "invitation" ||
			controllerData.get("invitationCommentFlag") == "comment") {
			$$(".legend1").html("<img src='" + legend.imgSrc + "'/> = " + legend.messageTypeLegend);
			$$(".legend2").css("display","none");
		} else if (controllerData.get("invitationCommentFlag") == "dynamic") {
			$$(".legend1").html("<img src='" + legend[0].imgSrc + "'/> = " + legend[0].messageTypeLegend);
			$$(".legend2").html("<img src='" + legend[1].imgSrc + "'/> = " + legend[1].messageTypeLegend);
		}
		
		// Build friend containers
		for (var i = 0; i < friendsData.length; i++) {
			$friendContainer = buildfriendContainer(i).appendTo($$(".friends"));
			$friendContainer.wrap("<li></li>");
		}
		$friendContainer = $$(".friendContainer");
		$friendContainer.css("display","block");
		
		$$(".sendButton").bind("click", function(event) {
			if (totalSelectedFriends > 0) {
				send();
			}
			return false;
		});
		$$(".tab.selectedFriends").bind("click", function(event) {
			// Check if "slelected" tab isn't already shown
			if ($(this).hasClass("selected")) {	return;	}
			$(this).addClass("selected");
			$$(".tab."+selectedTab).removeClass("selected");
			selectedTab = "selectedFriends";
			
			// Hide all list items that are not selected by adding them
			// "filteredSelcted" class.
			$$(".friends li").each(function (index) {
				var friendDataIndex = $(".friendContainer",this).data("friendsDataIndex");
				if (!friendsData[friendDataIndex].selected) {
					$(this).addClass("filteredSelected");
				}
			});
		});
		$$(".tab.all").bind("click", function(event) {
			// Check if "all" tab isn't already shown
			if ($(this).hasClass("selected")) {	return;	}
			$(this).addClass("selected");
			$$(".tab."+selectedTab).removeClass("selected");
			selectedTab = "all";
			
			// Remove "filteredSelected" class from all list items.
			$$(".friends li").removeClass("filteredSelected");
		});
		$$(".friendContainer").bind("click", function(event) {
			// Check that user is in the "Selected" tab.
			if (selectedTab != "selectedFriends") { return; }
			var $li = $(this).parent();
			if (!$li.data("fadingOut")) {
				$li.data("fadingOut",true);
				$li.stop();
				$li.animate({opacity: 0}, "slow", function(){
					$li.css({
						opacity:	1
					});
					$li.addClass("filteredSelected");
				});
			} else {
				$li.data("fadingOut",false);
				$li.stop();
				$li.animate({opacity: 1}, "slow");				
			}
		});
		$$(".finder").bind("focus", function() {
			if (this.value == typeANameString) {
				this.value = "";
			}
			//$(this).bind("keyup", filterNames);
			//$(this).bind("blur", typeANameBlur);
		});
		$$(".finder").bind("keyup", function() {
			var searchString = this.value;
			var regExp = new RegExp(searchString, "i");
			var $friendsListItems = $$("li");
			$friendsListItems.each(function(index) {
				var text = $(this).find(".friendsName").text();
				if (regExp.test(text)) {
					$(this).removeClass("filtered");
					var newHtml = "";
					if (searchString != "") {
						newHtml = text.replace(regExp, "<span class='highlighted'>$&</span>");
					} else {
						newHtml = text;
					}
					$(this).find(".friendsName").html(newHtml);
				} else {
					$(this).addClass("filtered");
				}
			});
		});
		$$(".finder").bind("blur", function() {
			if (this.value == "")
				this.value = typeANameString;
			//$(this).unbind("keyup", filterNames);
			//$(this).unbind("blur", typeANameBlur);			
		});
		$$(".selectAll").bind("click", function() {
			if (max != null) {
				// Select max number of friends.
				$friendContainer.each(function() {
					// if already maximum number of friends selected, exit loop 
					if (totalSelectedFriends == max) { return false; }
					// if this friend is already selected move on to the next friend
					if ($(this).hasClass("selected")) { return true; }
					$(this).addClass("selected");
					var friendsDataIndex = $(this).data("friendsDataIndex");
					friendsData[friendsDataIndex].selected = true;
					totalSelectedFriends++;
					
					// If the user in the "Selected" tab, remove the "filteredSelected" class from this friend
					if (selectedTab == "selectedFriends") {
						$(this).parent().removeClass("filteredSelected");
					}
				});
			} else {
				// Select all friends
				$friendContainer.addClass("selected");
				for (var i = 0; i < numOfFriends; i++) {
					friendsData[i].selected = true;
				}
				totalSelectedFriends = numOfFriends;
				
				// If user in the "Selected" tab remove the "filteredSelected" class 
				// from all friends.
				if (selectedTab == "selectedFriends") {
					$$(".friends li").removeClass("filteredSelected");
				}
			}
			
			$$(".totalSelectedFriends").text(totalSelectedFriends);
		});
		$$(".unselectAll").bind("click", function() {
			// Unselect all friends.
			$friendContainer.removeClass("selected");
			for (var i = 0; i < numOfFriends; i++) {
				friendsData[i].selected = false;
			}
			totalSelectedFriends = 0;
			$$(".totalSelectedFriends").text(totalSelectedFriends);
						
			// If user in the "Selected" tab add the "filteredSelected" class
			// on all friends.
			if (selectedTab == "selectedFriends") {
				$$(".friends li").addClass("filteredSelected");
			}
		});
		
		// Set true layout and get dimensions.
		$invitationController.css({
			position:		"absolute",
			top:			"-1000px",
			left:			"-1000px"
		});
		$("body").append($invitationController);
		var $friends = $$(".friends");
		var listItemOuterHeight = $$(".friends li").outerHeight(true);
		var listItemOuterWidth = $$(".friends li").outerWidth(true);
		var friendsUnorderedListHeight = listItemOuterHeight*controllerData.get("maxFriendsRows");
		if (!jQuery.boxModel) {
			friendsUnorderedListHeight += parseInt($friends.css("border-top-width")) +
					parseInt($friends.css("border-bottom-width")) +
					parseInt($friends.css("padding-top")) +
					parseInt($friends.css("padding-bottom"));
		}
		$friends.css("height",friendsUnorderedListHeight+"px");
		
		var friendsULPadding = parseInt($friends.css("padding-left")) + parseInt($friends.css("padding-right"));
		var friendsULBorder = parseInt($friends.css("border-left-width")) + parseInt($friends.css("border-right-width"));
		var friendsULMargin = parseInt($friends.css("margin-left")) + parseInt($friends.css("margin-right"));
		var newControllerWidth = listItemOuterWidth*controllerData.get("friendsPerRow")+
				friendsULPadding+friendsULBorder+friendsULMargin+19; // 19px is the scrollbar width
		if (!jQuery.boxModel) {
			newControllerWidth += parseInt($invitationController.css("border-right-width")) +
					parseInt($invitationController.css("border-left-width")) +
					parseInt($invitationController.css("padding-right")) +
					parseInt($invitationController.css("padding-left"));
		}
		$invitationController.css("width",newControllerWidth+"px");
		$invitationController.css({
			position:		"relative",
			top:			"0px",
			left:			"0px",
			visibility:		"hidden"
		});
				
		console.groupEnd();
		return $invitationController;
	}
	
	/**
	 * Initializes, builds HTML code and registers event handlers of the Small
	 * invitation controller. If pop-up was set to true, shows the controller,
	 * otherwise returns jQuery object of the controller with css property display:none.
	 */	
	function showSmallController() {
		console.group("showSmallController()");
		
		var randomIndex = Math.floor(Math.random()*numOfFriends);
		currentFriendIndex = randomIndex;
		
		$invitationController = OSB.templates.getTemplate("smallInvitationController");
		$$(".photoWrapper img").attr("src",friendsData[randomIndex].thumbnailUrl);
		$$(".photoWrapper img").attr("alt",friendsData[randomIndex].friendName);
		$$(".friendsName").text(friendsData[randomIndex].friendName);
		$$("#inviteX").val("Send to "+controllerData.get("numOfRandomFriends")+" Firends");
		
		var legend = setLegend();
		if (controllerData.get("invitationCommentFlag") == "invitation" ||
			controllerData.get("invitationCommentFlag") == "comment") {
			$$(".legend").text("(" + legend.messageTypeLegend + ")");
		} else if (controllerData.get("invitationCommentFlag") == "dynamic") {
			$$(".legend").text("(" + legend[0].messageTypeLegend + ")");
		}

		// Bind event handlers
		$$("#inviteAll").bind("click",function() {
			for (var i = 0; i < numOfFriends; i++) {
				friendsData[i].selected = true;
			}
			send();
			return false;
		});
		$$("#inviteX").bind("click",function() {
			selectRandomUsers();
			send();
			return false;
		});
		$$("#inviteThis").bind("click",function() {
			send(friendsData[currentFriendIndex].friendId);
			return false;
		});
		$$("#next").bind("click",function() {
			if (currentFriendIndex+1 < numOfFriends) {
				currentFriendIndex++;
				browseFriendsSmallController(currentFriendIndex);
			}
			return false;
		});
		$$("#previous").bind("click",function() {
			if (currentFriendIndex-1 >= 0) {
				currentFriendIndex--;
				browseFriendsSmallController(currentFriendIndex);
			}
			return false;
		});
		
		if (controllerData.get("popUp") === true) {
			$invitationController.bind("mouseleave",function() {
				$(this).bind("mouseenter",function() {
					$(this).stop();
					$(this).animate({opacity:1},"slow");
				});
				$(this).animate({opacity:0},"slow",function() {
					close();
				});
			});
		
			// Position the top left corner of the invitationController at the clicked
			// coordinates if there is enough place without exiting page dimensions, otherwise
			// position bottom right corner of the invitationController at the clicked
			// coordinates.
			
			// Append to body to set dimensions
			$invitationController.css("display","none");
			$invitationController.appendTo("body");
			
			var documentWidth = $(document).width();
			var documentHeight = $(document).height();
			var dialogOuterWidth = $invitationController.outerWidth();
			var dialogOuterHeight = $invitationController.outerHeight();
			var insertedPadding = 10;
			
			var event = controllerData.get("event");
			
			var dialogLeft;
			if (event.pageX + dialogOuterWidth <= documentWidth) {
				dialogLeft = event.pageX - insertedPadding;
			} else {
				dialogLeft = event.pageX - dialogOuterWidth + insertedPadding;
			}
			
			var dialogTop;
			if (event.pageY + dialogOuterHeight <= documentHeight) {
				dialogTop = event.pageY - insertedPadding;
			} else {
				dialogTop = event.pageY - dialogOuterHeight + insertedPadding;
			}
			
			// show the invitationController with some graceful fadeIn animation
			$invitationController.css({
				display:	"block",
				position:	"absolute",
				left:		dialogLeft+"px",
				top:		dialogTop+"px",
				zIndex:		100,
				opacity:	0
			});
			$invitationController.animate({opacity:1},"slow");
		} else {
			$invitationController.css({
				display:	"block",
				visibility:	"hidden"
			});
			console.groupEnd();
			return $invitationController;
		}
		
		console.groupEnd();
	}

	/**
	 * Initializes, builds HTML code and registers event handlers of the Condensed
	 * invitation controller.
	 * Returns jQuery object of the controller with css property display:none.
	 */	
	function getCondensedController() {
		console.group("getCondensedController()");
		
		$invitationController = OSB.templates.getTemplate("condensedController");
		
		var selectButtonText = "all";
		var max = controllerData.get("max");
		if (max != null) {
			selectButtonText = max + "";
		}
		$$(".selectAll").text("select " + selectButtonText);
		
		var legend = setLegend();
		if (controllerData.get("invitationCommentFlag") == "invitation" ||
			controllerData.get("invitationCommentFlag") == "comment") {
			$$(".legend").text("(" + legend.messageTypeLegend + ")");
		} else if (controllerData.get("invitationCommentFlag") == "dynamic") {
			$$(".legend").text("(" + legend[0].messageTypeLegend + ")");
		}

		totalSelectedFriends = 0;
		
		var checkbox = '<input type="checkbox"/>';
		var mode = controllerData.get("mode");
		if (controllerData.get("selectedRows") == 0) {
			mode = "normal";
		}
		if (mode == "noWayBack" || mode == "normal") {
			$$(".selectedList").css("display","none");
		}	
		
		var numOfPreselcted = controllerData.get("preselected");
		if (mode == "2part" || mode == "normal") {
			if (numOfPreselcted == "all") {
				if (max == null) {
					for (var i = 0; i < numOfFriends; i++) {
						friendsData[i].selected = true;
					}
					totalSelectedFriends = numOfFriends;
				} else {
					console.error("You can't use max and num_of_preselected='all' attributes together");
				}
			} else {
				if (numOfPreselcted != 0) {
					if (max != null) {
						numOfPreselcted = Math.min(numOfPreselcted, max);
					}
					controllerData.set("numOfRandomFriends", numOfPreselcted);
					selectRandomUsers();
					totalSelectedFriends = numOfPreselcted;
				}
			}
		}
		
		var $listItem = null;
		for (var i = 0; i < numOfFriends; i++) {
			$listItem = $("<li>"+checkbox+friendsData[i].friendName+"</li>");				
			$listItem.data("friendsDataIndex", i);
			if (friendsData[i].selected) {
				$listItem.find(":checkbox").attr("checked", "checked");
				if (mode == "normal") {
					$$(".unselectedList").prepend($listItem);
				}
				if (mode == "2part") {
					$$(".selectedList").append($listItem);
				}
			} else {
				$$(".unselectedList").append($listItem);
			}
		}
		$$(".selectAll").bind("click", function() {
			$(".wrapper li").each(function(index) {
				var friendsDataIndex = $(this).data("friendsDataIndex");
				var isSelected = friendsData[friendsDataIndex].selected;
				if (!isSelected && (max == null || (max != null && max != totalSelectedFriends))) {
					friendsData[friendsDataIndex].selected = true;
					totalSelectedFriends++;
					$(this).find(":checkbox").attr("checked","checked");
					if (mode == "noWayBack" || mode == "2part") {
						$$(".selectedList").append($(this));
					}
				}
			});
			return false;
		});
		$$(".unselectAll").bind("click", function() {
			$(".wrapper li").each(function(index) {
				var friendsDataIndex = $(this).data("friendsDataIndex");
				var isSelected = friendsData[friendsDataIndex].selected;
				if (isSelected) {
					friendsData[friendsDataIndex].selected = false;
					totalSelectedFriends--;
					$(this).find(":checkbox").removeAttr("checked");
					if (mode == "noWayBack" || mode == "2part") {
						$$(".unselectedList").append($(this));
					}
				}
			});
			return false;
		});		
		$$(".unselectedList li, .selectedList li").bind("click", function(event) {
			var friendsDataIndex = $(this).data("friendsDataIndex");
			var isSelected = friendsData[friendsDataIndex].selected;
			if (isSelected) {
				friendsData[friendsDataIndex].selected = false;
				totalSelectedFriends--;
				$(this).find(":checkbox").removeAttr("checked");
				if (mode == "noWayBack" || mode == "2part") {
					$$(".unselectedList").append($(this));
				}
				console.debug("friend id="+friendsData[friendsDataIndex].friendId+" unselected");
			} else {
				if (max != null && totalSelectedFriends == max) { return false; }
				friendsData[friendsDataIndex].selected = true;
				totalSelectedFriends++;
				$(this).find(":checkbox").attr("checked","checked");
				if (mode == "noWayBack" || mode == "2part") {
					$$(".selectedList").append($(this));
				}
				console.debug("friend id="+friendsData[friendsDataIndex].friendId+" selected");
			}
		});
		$$(".sendButton").bind("click", function(event) {
			if (totalSelectedFriends > 0) {
				send();
			}
			return false;
		});
		$$(".filter").bind("focus", function() {
			if (this.value == typeANameString) {
				this.value = "";
			}
			//$(this).bind("keyup", filterNames);
			//$(this).bind("blur", typeANameBlur);
		});
		$$(".filter").bind("keyup", function() {
			var searchString = this.value;
			var regExp = new RegExp(searchString, "i");
			var $friendsListItems = $$("li");
			$friendsListItems.each(function(index) {
				var text = $(this).text();
				if (regExp.test(text)) {
					$(this).removeClass("filtered");
					var html = "";
					if (searchString != "") {
						html = text.replace(regExp, "<span class='highlighted'>$&</span>");
					} else {
						html = text;
					}
					$(this).html(checkbox+html);
					var friendsDataIndex = $(this).data("friendsDataIndex");
					var isSelected = friendsData[friendsDataIndex].selected;
					if (isSelected) {
						$(this).find(":checkbox").attr("checked","checked");
					}
				} else {
					$(this).addClass("filtered");
				}
			});
		});
		$$(".filter").bind("blur", function() {
			if (this.value == "")
				this.value = typeANameString;
			//$(this).unbind("keyup", filterNames);
			//$(this).unbind("blur", typeANameBlur);			
		});
		
		// Get height of list item
		// append controller to body to render its dimensions
		$invitationController.css({
			position:		"absolute",
			top:			"-1000px",
			left:			"-1000px"
		});
		$invitationController.appendTo("body");
		
		var $unselectedList = $$("ul.unselectedList");
		var $selectedList = $$("ul.selectedList");
		var listItemHeight = $invitationController.find("li").outerHeight(true);
		var unselectedListHeight = listItemHeight*controllerData.get("unselectedRows");
		var selectedListHeight = listItemHeight*controllerData.get("selectedRows");
		if (!jQuery.boxModel) {
			unselectedListHeight += parseInt($unselectedList.css("border-top-width")) +
					parseInt($unselectedList.css("border-bottom-width")) +
					parseInt($unselectedList.css("padding-top")) +
					parseInt($unselectedList.css("padding-bottom"));
			selectedListHeight += parseInt($selectedList.css("border-top-width")) +
					parseInt($selectedList.css("border-bottom-width")) +
					parseInt($selectedList.css("padding-top")) +
					parseInt($selectedList.css("padding-bottom"));
		}
		$unselectedList.css("height", unselectedListHeight+"px");
		$selectedList.css("height", selectedListHeight+"px");
		$$(".sendButton").val(controllerData.get("buttonText"));

		$invitationController.css({
			position:		"relative",
			top:			"0px",
			left:			"0px",
			visibility:		"hidden"
		});	
		
		console.groupEnd();
		
		return $invitationController;
	}
	
	/**
	 * Process friends and send the invitation/comments. (for Button controller)
	 * If button type is all, select and send invitation/comments to all friends.
	 * If button type is random, select X random friends and send invitation/comments.
	 */
	function processButtonController() {
		if (controllerData.get("buttonType") == "all") {
			for (var i = 0; i < numOfFriends; i++) {
				friendsData[i].selected = true;
			}
			send();
		} else if (controllerData.get("buttonType") == "random") {
			selectRandomUsers();
			send();			
		}
	}
	
	/**
	 * Build shadow buffer for Full invitation controller.
	 */
	function buildShadow() {
		$lightBoxShadow = $("<div id='lightBoxShadow'></div>");
		var pageScrollDim = getPageScrollDimensions();
		var pageClientDim = getViewPortDimensions();
		var shadowWidth = pageScrollDim.width > pageClientDim.width ? pageScrollDim.width : pageClientDim.width;
		var shadowHeight = pageScrollDim.height > pageClientDim.height ? pageScrollDim.height : pageClientDim.height;
		$lightBoxShadow.css({
			position:			"absolute",
			top:				"0px",
			left:				"0px",	
			width:				shadowWidth+"px",
			height:				shadowHeight+"px",
		//	height:				"100%",
			filter:				"progid:DXImageTransform.Microsoft.Alpha(opacity=50)",
			opacity:			0.5,
			backgroundColor:	"#000000",
			zIndex:				99
		});		
	}
	
	/**
	 * Builds friendContainer:
	 * With initial css inline property display:none
	 * @param {Integer} index Index of friend as appeared in friendsData Array
	 * @return $container jQuery
	 */
	function buildfriendContainer(index) {
		// The single friendContainer looks like this:
		//	<div class="friendContainer">
		//		<div class="photoWrapper">
		//			<img src="{thumbnailUrl}" alt="{name}" />
		//			<div class="icon" style="background-image:url({iconSrc});"></div>
		//		</div>
		//		<div class="friendsName">{name}</div>
		//	</div>
		var iconSrc = OSB.INVITATION_ICON_URL;
		if (controllerData.get("invitationCommentFlag") == "comment" ||
			(controllerData.get("invitationCommentFlag") == "dynamic" && friendsData[index].hasApp)) {
			switch (controllerData.get("messageType")) {
				case "public"		: iconSrc = OSB.PUBLIC_MESSAGE_ICON_URL; break;
				case "private"		: iconSrc = OSB.PRIVATE_MESSAGE_ICON_URL; break;
				case "notification"	: iconSrc = OSB.NOTIFICATION_MESSAGE_ICON_URL; break;
				case "email"		: iconSrc = OSB.EMAIL_MESSAGE_ICON_URL; break;
			}
		}
		var $container = $('' +
			'<div class="friendContainer">' +
				'<div class="photoWrapper">' +
					'<img src="'+friendsData[index].thumbnailUrl+'" alt="'+friendsData[index].friendName+'" />' +
					'<div class="icon" style="background-image:url('+iconSrc+');"></div>' +
				'</div>' +
				'<div class="friendsName">'+friendsData[index].friendName+'</div>' +
			'</div>');
		// Attach friendsId and its index in the friendsData Array to the jQuery data
		$container.data("friendId",friendsData[index].friendId);
		$container.data("friendsDataIndex",index);
		$container.css("display","none");

		// Make $container selected if it was selected previously. When browsing between
		// friends, controller "remembers" which friends were selected.
		if (friendsData[index].selected) {
			$container.addClass("selected");
		}
		
		// Bind event Handlers to friendContainer
		$container.bind("mouseenter",function() {
			$(this).addClass("hover");
		});
		$container.bind("mouseleave",function() {
			$(this).removeClass("hover");
		});
		$container.bind("click",function() {
			var friendsDataIndex = $(this).data("friendsDataIndex");
			var isSelected = friendsData[friendsDataIndex].selected;
			if (isSelected) {
				friendsData[friendsDataIndex].selected = false;
				totalSelectedFriends--;
				$$(".totalSelectedFriends").text(totalSelectedFriends);
			} else {
				if (controllerData.get("max") != null && totalSelectedFriends == controllerData.get("max")) { return false; }
				friendsData[friendsDataIndex].selected = true;
				totalSelectedFriends++;
				$$(".totalSelectedFriends").text(totalSelectedFriends);
			}
			$(this).toggleClass("selected");
		});
		
		return $container;
	}
	
	/**
	 * Pagination algorithm with graceful animation. When animating, locks the control
	 * buttons to prefent conflicts.
	 * @param {String} dir Direction of pagination "next" or "prev"
	 */
	function browseFriendsFullController(dir) {
		console.group("browseFriendsFullController("+dir+")");
		
		// set fix height to .friends container so in case of paginating to the last page
		// it wont shrink if the last page is not full.
		$$(".friends").height($$(".friends").height());
		
		// Lock the controls
		lockControls();
		var isFadeOutComplete = false;
		var isFadeInComplete = false;
		
		var i_out = 0;
		var last_out = lastViewedFriend-firstViewedFriend;
		console.debug("i_out = "+i_out);
		console.debug("last_out = "+last_out);
		
		var maxFriendsPerView = controllerData.get("maxFriendsPerView");
		
		if (dir == "next") {
			// calculate new boundary indexes
			firstViewedFriend += maxFriendsPerView;
			if (lastViewedFriend + maxFriendsPerView < numOfFriends) {
				lastViewedFriend += maxFriendsPerView;
			} else {
				lastViewedFriend = numOfFriends-1;
			}
		} else {
			// calculate new boundary indexes
			firstViewedFriend -= maxFriendsPerView;
			lastViewedFriend = firstViewedFriend + maxFriendsPerView - 1;
		}
		$$("#currentPage").text(firstViewedFriend/maxFriendsPerView+1);
		
		var i_in = firstViewedFriend;
		console.debug("i_in = "+i_in);
		console.debug("lastViewedFriend = "+lastViewedFriend);
		
		function animateFadeOut() {
			if (i_out <= last_out) {
				var $container = $friendContainer.eq(i_out);
				var _i = ++i_out;
				$container.animate({opacity:0},FADE_SPEED,function() {
					if (_i > last_out) {
						console.debug("fadeOutComplete");
						isFadeOutComplete = true;
					}
					animateFadeIn($container);
				});
				window.setTimeout(animateFadeOut,ERASE_SPEED);
			}
		}
		animateFadeOut();
				
		function animateFadeIn($containerToReplace) {
			if (i_in <= lastViewedFriend) {
				var $container = buildfriendContainer(i_in);
				var _i = ++i_in;
				$container.css({
					display:	"inline",
					opacity:	0
				});
				if ($containerToReplace !== undefined) {
					$containerToReplace.replaceWith($container);
				} else {
					$container.appendTo(".friends");
				}
				$container.animate({opacity:1},FADE_SPEED,function() {
					if (_i > lastViewedFriend) {
						console.debug("fadeInComplete");
						isFadeInComplete = true;
						animateFadeIn();
					}
				});
				if (isFadeOutComplete && (i_in <= lastViewedFriend)) {
					window.setTimeout(function(){animateFadeIn();},ERASE_SPEED);
				}
			} else {
				if (isFadeOutComplete && isFadeInComplete) {
					console.debug("finalize browseFriends()");
					$friendContainer.remove();
					$friendContainer = $$(".friendContainer");
					unlockControls();
					console.groupEnd();		
				}
			}
		}
	}
	
	/**
	 * Pagination algorithm with graceful animation. When animating, does not locks 
	 * control buttons like in Full controller, so basically user can rapidly hit
	 * next/prev buttons.
	 * @param {String} dir Direction of pagination "next" or "prev"
	 */
	function browseFriendsSmallController(newIndex) {
		var $friendContainer = $$(".friendContainer");
		
		// Stop animation if user hit controll button (next/prev) before the animation
		// is over. If user hit next/prev button when friend's photo was on fadeOut
		// animation it will continue to fade out, and if user hit next/prev button
		// when photo was on fadeIn animation it will fadeOut and fadeIn next
		// firend's photo.
		$friendContainer.stop();
		$friendContainer.animate({opacity:0},FADE_SPEED,function(){
			$$(".friendContainer img").attr("src",friendsData[newIndex].thumbnailUrl);
			$$(".friendContainer .friendsName").text(friendsData[newIndex].friendName);
			$friendContainer.animate({opacity:1},FADE_SPEED);
		});
	}

	/**
	 * Selects X different random users.
	 */
	function selectRandomUsers() {
		if (controllerData.get("numOfRandomFriends") >= numOfFriends) {
			console.error("You can't have number of random friends >= number of friends!");
			return false;
		}
		// Randomize X (numOfRandomFriends) different indexes of friends.
		// 2*n complexity
		var i;
		var friendIndexes = [];
		for (i = 0; i < numOfFriends; i++) {
			friendIndexes.push(i);
		}
		for (i = 0; i < controllerData.get("numOfRandomFriends"); i++) {
			var randomIndex = Math.floor(Math.random()*friendIndexes.length);
			var friendIndex = friendIndexes[randomIndex];
			friendIndexes.splice(randomIndex, 1);
			console.debug("Got "+(i+1)+"th random user index = "+friendIndex);
			friendsData[friendIndex].selected = true;
		}		
	}
	
	/**
	 * Send invitations/comments. If no friendId parameter was supllied, sends
	 * invitations/comments to all friends that was selected in friendsData array,
	 * otherwise send invitation/comments only to one frined which Id was supllied in
	 * the parameter.
	 * @param {Integer} friendId Frient's Id to send invitation/comment to.
	 */
	function send() {
		console.group("send()");
		
		var selectedIds = [];
		var selectedIdsHasApp = [];
		var selectedIdsDoesntHasApp = [];
		var content = prepareContent();
		console.debug("content = "+content);
		
		// If no parameter was supplied exrtract all selected friends from
		// friendsData array. 
		if (arguments.length === 0) {
			for (var i = 0; i < friendsData.length; i++) {
				if (friendsData[i].selected) {
					selectedIds.push(friendsData[i].friendId);
					// separate users that has app and users that does not hass app.
					if (friendsData[i].hasApp) {
						selectedIdsHasApp.push(friendsData[i].friendId);
					} else {
						selectedIdsDoesntHasApp.push(friendsData[i].friendId);
					}
				}
			}
		} else {
			// When friendsId was supplied, send invitation/comment only to this friend. 
			selectedIds.push(arguments[0]);
		}
		console.debug(selectedIds.length+" users have been selected with ID's: "+selectedIds);
		console.groupEnd();
	
		if (selectedIds.length > 0) {
			var message = opensocial.newMessage(content);
			
			// Send invitations or comments according to the flag in the controllerData.
			if (controllerData.get("invitationCommentFlag") == "dynamic") {
				console.debug("invitationCommentFlag == dynamic");
				// Dynamic type sends invitations to users that does not have application
				// and comments to users that have application installed.
				// These who does not has app send them invitation
				console.debug("executing requestShareApp...");
				sendInvitation(selectedIdsDoesntHasApp, message, function(finalRecipientsIdsDoesntHasApp) {
					// After invitation are sent these who has app send them message
					console.debug("executing requestSendMessage...");
					sendMessage(selectedIdsHasApp, message, function(finalRecipientsIdsHasApp) {
						// create array of final recipients data consisting of
						// invitation and message recipients. If some of the
						// messages or invitations were canceled these recipients
						// array will be changed accordingly.
						var finalRecipientsIds = finalRecipientsIdsDoesntHasApp.concat(finalRecipientsIdsHasApp);
						if (finalRecipientsIds.length === 0) {
							console.debug("All invitations/messages has been canceled! exiting.");
							return;
						}
						sendInfoToServer(finalRecipientsIds);
					});
				});
			}
			if (controllerData.get("invitationCommentFlag") == "invitation") {
				sendInvitation(selectedIds, message, function(finalRecipientsIds) {
					if (finalRecipientsIds.length === 0) {
						console.debug("All invitations has been canceled! exiting.");
						return;
					}
					sendInfoToServer(finalRecipientsIds);
				});
			}
			if (controllerData.get("invitationCommentFlag") == "comment") {
				sendMessage(selectedIds, message, function(finalRecipientsIds) {
					if (finalRecipientsIds.length === 0) {
						console.debug("All messages has been canceled! exiting.");
						return;
					}
					sendInfoToServer(finalRecipientsIds);
				});
			}
			if (controllerData.get("popUp") === true) {
				close();
			}
		}
	}
	
	function sendInvitation(selectedIds, message, callback) {
		if (selectedIds.length > 0) {
			message.setField(opensocial.Message.Field.TITLE, controllerData.get("messageTitle"));
			OSB.requestShareApp(selectedIds, message, callback);
		} else {
			console.debug("no users selected for invitation");
			callback(selectedIds);
		}
	}
	
	function sendMessage(selectedIds, message, callback) {
		if (selectedIds.length > 0) {
			message.setField(opensocial.Message.Field.TITLE, controllerData.get("messageTitle"));
			var type = "";
			switch (controllerData.get("messageType")) {
				case "public"		: type = opensocial.Message.Type.PUBLIC_MESSAGE; break;
				case "private"		: type = opensocial.Message.Type.PRIVATE_MESSAGE; break;
				case "notification"	: type = opensocial.Message.Type.NOTIFICATION; break;
				case "email"		: type = opensocial.Message.Type.EMAIL; break;
			}
			message.setField(opensocial.Message.Field.TYPE, type);
			OSB.requestSendMessage(selectedIds, message, callback);
		} else {
			console.debug("no users selected for sending message");
			callback(selectedIds);
		}
	}
	
	/**
	 * Substitues {parameters} in the message content with suitable input parameter.
	 * For example, parameter {mood} in the message content will be substituted
	 * with value of input element with name="mood". The "mood" parameter should be
	 * passed in the controller's additional_form_params attribute.
	 */
	function prepareContent() {
		console.group("prepareContent()");
		var content = controllerData.get("content");
		console.debug("content = "+content);
		var formParams = controllerData.get("additionalFormParams");
		console.debug("formParams = ",formParams);
		jQuery.each(formParams, function(index, value){
			var paramName = value;
			console.debug("paramName = "+paramName);
			var $formElement = $("[name="+value+"]");
			var paramValue = "";
			if ($formElement.length > 0) {
				var tagName = $formElement.get(0).tagName.toLowerCase();
				if (tagName == "input") {
					var type = $formElement.attr("type");
					switch (type) {
						case "text":
						case "hidden":
							paramValue = $formElement.val();
							break;
						case "checkbox":
						case "radio":
							paramValue = $formElement.filter(":checked").val();
							break;
					}
				}
				if (tagName == "textarea" || tagName == "select") {
					paramValue = $formElement.val();
				}
			}
			console.debug("paramValue = "+paramValue);
			var regExp = new RegExp("{"+paramName+"}", "g");
			content = content.replace(regExp, paramValue);
		});
		
		// Replace predefined key words
		// ----------------------------
		content = content.replace(/\{sender_name\}/g, OSB.owner.getDisplayName());
		content = content.replace(/\{os_app_id\}/g, OSB.APP_ID);
		content = content.replace(/\{(swf_id_\d+)\}/g, function(matchedString, swfId) {
			console.debug("swfId = "+swfId);
			var $flashObject = $("object#"+swfId);
			if ($flashObject.length) {
				$newFlashObject = $flashObject.eq(0).clone();
				var flashObjectHtml = $("<div></div>").append($newFlashObject).html();
				console.debug("flashObjectHtml = " + flashObjectHtml);
				return flashObjectHtml;
			} else {
				// Did not find <object> tag with spevified id, return matched string.
				console.debug("Did not find <object> tag with id = " + swfId);
				return matchedString;
			}
		});
		
		console.groupEnd();
		return content;
	}
	
	/**
	 * Send info to the server with user id's to which invitations/comments were sent.
	 * @param {Array} selectedIds
	 */
	function sendInfoToServer(selectedIds) {
		console.group("sendInfoToServer(selectedIds)");
		console.debug("Invitation/messages have been sent to "+selectedIds.length+" users.");
		console.debug("Sending user Id = " + OSB.VIEWER_ID);
		console.debug("Recipient user Id = " + selectedIds);
		console.debug("Application Id = " + OSB.APP_ID);
		
		if (selectedIds.length == 0) {
			console.debug("All invitations/message has been canceled! Terminating request!");
			console.groupEnd();
			return;
		}
		
		var url = controllerData.get("serverUrl");
		var additionalParameters = "";
		
		// Append recipient ids array to the querystring.
		var id;
		var recipientIds = OSB.urlParameters.RECIPIENT_ID + '=[';
		for (var i = 0; i < selectedIds.length; i++) {
			if (i > 0) {
				recipientIds += ',';
			}
			// cast possible integer to string
			selectedIds[i] += "";
			id = selectedIds[i].replace(/myspace\.com:/,"");
			recipientIds += '"'+id+'"';
		}
		recipientIds += ']';
		url = OSB.utils.appendUrlParameters(url, recipientIds);
		
		// If there is additional form parameters passed to controller data
		// serialize and append their values to the querystring.
		var formParams = controllerData.get("additionalFormParams");
		jQuery.each(formParams, function(index, value){
			if (index > 0) {
				additionalParameters += "&";
			}
			additionalParameters += $("[name="+value+"]").serialize();
		});
		if (additionalParameters) {
			url = OSB.utils.appendUrlParameters(url, additionalParameters);
		}
		console.groupEnd();
		
		if (controllerData.get("loadResponsePage")) {
			// In requestPage() regular parameters (viewerid,appid,domain) are added.
			console.debug("loadResponsePage is set to true - loading page...");
			OSB.PageLoader.requestPage(url);
		} else {
			console.debug("loadResponsePage is set to false - sending info to the server...");
			additionalParameters = "" + 
					OSB.urlParameters.VIEWER_ID + "=" + OSB.VIEWER_ID + 
					"&" + OSB.urlParameters.APP_ID + "=" + OSB.APP_ID +
					"&" + OSB.urlParameters.DOMAIN + "=" + OSB.DOMAIN;
			url = OSB.utils.appendUrlParameters(url, additionalParameters);
			OSB.sendRequestToServer(url);
		}
	}
	
	/**
	 * Close Invitation controller, remove relevant elements from DOM, release
	 * isInvitationDialogOpen flag.
	 */
	function close() {
		console.debug("close()");
		if (controllerData.get("controllerType") != "button") {
			$invitationController.remove();
		}
		if (controllerData.get("controllerType") == "full") {
			$lightBoxShadow.remove();
		}
		isInvitationDialogOpen = false;		
	}
	
	/**
	 * Locks controls when pagination animation plays.
	 * (for Full Invitation Controller)
	 */
	function lockControls() {
		isControlsLocked = true;
		$$("a").addClass("locked");
	}
	
	function setLegend() {
		var legend = {
			messageTypeLegend:	"invite",
			imgSrc:				OSB.INVITATION_ICON_URL
		};
		
		if (controllerData.get("invitationCommentFlag") == "comment" ||
			controllerData.get("invitationCommentFlag") == "dynamic") {
			var imgSrc, messageTypeLegend;
			
			switch (controllerData.get("messageType")) {
				case "public"		: imgSrc = OSB.PUBLIC_MESSAGE_ICON_URL; break;
				case "private"		: imgSrc = OSB.PRIVATE_MESSAGE_ICON_URL; break;
				case "notification"	: imgSrc = OSB.NOTIFICATION_MESSAGE_ICON_URL; break;
				case "email"		: imgSrc = OSB.EMAIL_MESSAGE_ICON_URL; break;
			}

			messageTypeLegend = OSB.Capabilities.getCapability("messageTypeLegend");
			if (!messageTypeLegend) {
				switch (controllerData.get("messageType")) {
					case "public"		: messageTypeLegend = "public message"; break;
					case "private"		: messageTypeLegend = "private message"; break;
					case "notification"	: messageTypeLegend = "notification"; break;
					case "email"		: messageTypeLegend = "email"; break;
				}
			} else {
				messageTypeLegend = messageTypeLegend[controllerData.get("messageType")];
			}
			
			if (controllerData.get("invitationCommentFlag") == "comment") {
				legend = {
					messageTypeLegend:	messageTypeLegend,
					imgSrc:				imgSrc
				};
			} else if (controllerData.get("invitationCommentFlag") == "dynamic") {
				legend = [legend, {
					messageTypeLegend:	messageTypeLegend,
					imgSrc:				imgSrc
				}];
			}
		}

		return legend;
	}
	
	/**
	 * Unlocks controls after pagination animation completes.
	 * Checks if first or last page and locks prev/next buttons accordingly.
	 * (for Full Invitation Controller)
	 */
	function unlockControls() {
		isControlsLocked = false;
		$$("a").removeClass("locked");
		if (firstViewedFriend === 0) {
			$$("#previous").addClass("locked");
		}
		if (lastViewedFriend == numOfFriends-1) {
			$$("#next").addClass("locked");
		}
	}
	
	function getPageScrollDimensions() {
		var offsetWidth = document.body.offsetWidth;
		var offsetHeight = document.body.offsetHeight;
		return {width:offsetWidth, height:offsetHeight};
	}
	
	function getViewPortDimensions() {
		isOpera = /Opera/.test(userAgent);
		var viewPortWidth, viewPortHeight;
		if (!isOpera) {
			viewPortWidth = document.documentElement.clientWidth;
			viewPortHeight = document.documentElement.clientHeight;
		}
		else {
			viewPortWidth = document.body.clientWidth;
			viewPortHeight = document.body.clientHeight;
		}
		return {width:viewPortWidth, height:viewPortHeight};
	}
	
	function getPageScrollPosition() {
		var scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
		var scrollTop = window.pageYOffset || document.documentElement.scrollTop;
		return {left:scrollLeft, top:scrollTop};
	}

	/**
	 * Overloading jQuery object
	 * This method overloads core jQuery by adding context for selectors which is
	 * invitationController. This prevent conflicts between possible similar
	 * classes on the page itself.
	 * @return jQuery
	 */
	function $$(selector) {
		return $(selector,$invitationController);
	}
};