extendLanguage(); 

// Globals 
var _ = null;
var zIndex = 0;
var misc = new Misc(); 
var ajax = new Ajax();
var ajaxMAX = new AjaxMAX();   
var app = new WebApplication("web_application"); 
app.version = '1.1.6';
var debug = new Debug("debug_output");
var ad = new AdContainer("ad"); 
var eventX = new Event();
var cookie = new Cookie();
var json = new Json();
var dom = new DOM();
var display = new Display();
var keyboard = new Keyboard();
var user = new User(); 
var html = new Html();
var borderLight = new BorderHighlighter();
var connect = new Connect(); 
var fader = new Fader(); 
  
if (display.isMobile()) 
	setTimeout("display.setTop()", 2000);
		      
// Classes
function Connect () 
{ 
	var iConnect = this;
	this.chatrooms = new Chatrooms();   
//	this.faves = new Faves();
//	this.settings = new Settings();  
//	this.msgPreview = null; 
//	this.users = new Users(); 
	this.fun = new Fun(); 
	
	this.init = function ()  
	{
		app.getDomNode().appendChild(this.fun.domNode); 
		this.fun.show(); 
		//var tabs = new TabContainer("connect", app.getDomNode());
/*		tabs.addTab("faves", "Faves", this.faves.domNode, function () {iConnect.faves.show()});
		//tabs.addTab("blogs", "Blogs", "List of chatrooms", function () {debug.log("Update chatrooms")});
		tabs.addTab("chatrooms", "Chat", this.chatrooms.getDomNode(), function () {iConnect.chatrooms.showSelection()});
		tabs.addTab("fun", "Fun", this.fun.domNode, function () {iConnect.fun.show()});
		tabs.addTab("users", "Users", this.users.domNode, function () {iConnect.users.show()});
		tabs.addTab("settings", "Settings", this.settings.getDomNode(), function () {iConnect.settings.show() });
		 
		switch (tabs.currentTabId)
		{		 
			case "chatrooms": this.chatrooms.showSelection(); break;
			case "settings": this.settings.show(); break;
			case "users": this.users.show(); break;
			case "fun": this.fun.show(); break;    
			case "faves": 
			default: 
				this.faves.show(); 
		} */ 
		
		user.onLogin = user.onLogout =
			function ()  
			{
				iConnect.fun.show(); 
			}
			
		user.onRegister = 
			function () 
			{
			}
	}
	this.init(); 
}

function Fun () 
{
	var iFun = this; 
	this.domNode = dom.create("div"); 
	this.loaded = false; 
	this.funBox = null; 
	
	this.show = function () 
	{
		if (!this.loaded) 
			this.domNode.innerHTML = "<div class='center gray'>Retrieving funny / interesting stories...</div>";
		ajax.send("?obj=fun&method=get", 
			function (req) 
			{
				iFun.domNode.innerHTML = req.responseText;
		 		iFun.loaded = true; 
			}); 
	} 
	
	
	this.sayIt = function () 
	{
		var story = getEle("funText");
		var recent = getEle("recentFun");  
		if (recent && story && story.value) 
		{
			if (recent.childNodes && recent.childNodes.length && recent.firstChild.className == "gray") 
				recent.innerHTML = ""; 
			var userName = user.userName ? user.userName : "guest"; 
			var storyRow = dom.create("div", _, "storyRow", _, "<span class='story'><b><span class='user_active' onclick='user.showInteractMenu(" + 
				user.userId + ")'>" + userName + "</span></b> says <blockquote class='storyQuote'>" + story.value.toHtmlEntities() + "</blockquote></span>" + 
				"<span class='storyScore'>0</span><span class='storyScore'>0</span>");
			if (recent.childNodes && recent.childNodes.length) 
				recent.insertBefore(storyRow, recent.firstChild); 
			else
				recent.appendChild(storyRow);
			
			ajax.send("?obj=fun&method=sayIt&story=" + encodeURIComponent(story.value));
			story.value = "";    
		}
	} 
	
	this.wow = function (storyId, buttonDom, rowId, callRandom) 
	{
		buttonDom.style.display = "none"; 
		var wow = getEle("fun_" + rowId + "_wow"); 
		if (wow) 
			wow.innerHTML = parseInt(wow.innerHTML) + 1; 
			
		ajax.send("?obj=fun&method=wow&storyId=" + storyId);

		if (callRandom) 
			setTimeout(function () {iFun.random(storyId);}, 300);   		 
	} 
	
	this.lol = function (storyId, buttonDom, rowId, callRandom)
	{
		buttonDom.style.display = "none"; 
		var lol = getEle("fun_" + rowId + "_lol"); 
		if (lol) 
			lol.innerHTML = parseInt(lol.innerHTML) + 1; 
			
		ajax.send("?obj=fun&method=lol&storyId=" + storyId);
		
		if (callRandom) 
			setTimeout(function () {iFun.random(storyId);}, 300);   		 
	}
	
	this.random = function (storyId) 
	{
		ajax.send("?obj=fun&method=getRandom&storyId=" + storyId, 
			function (req) 
			{
				var random = getEle("randomFun"); 
				if (random && req.responseText) 
					random.innerHTML = req.responseText; 
			}); 
	}
	
	this.showWhatISaid = function () 
	{
		ajax.send("?obj=fun&method=getWhatISaid", 
			function (req) 
			{
				if (!iFun.funBox) 
				{
					var attribs = {title:"Things I Said", easyClose:true, draggable:true};  
					if (!display.isMobile()) 
						attribs.width = 600; 
					iFun.funBox = new Popup(attribs);
				} 
				
				iFun.funBox.setBody(req.responseText); 
				iFun.funBox.show(); 
			}); 
	}
}

function Users () 
{
	var iUsers = this; 
	this.domNode = dom.create("div");
	this.loaded = false;  
	
	this.show = function () 
	{
		if (!this.loaded)
			this.domNode.innerHTML = "<div class='center gray'>Retrieving top 50 active users...</div>";
		
		ajax.send("?obj=users&method=getActives", 
			function (req) 
			{
				if (req.responseText) 
				{
					iUsers.domNode.innerHTML = req.responseText; 
					iUsers.loaded = true; 
				} else
				{ 
					this.domNode.innerHTML = "The server is busy. Please try again later.";
					iUsers.loaded = false; 
				} 
			});   
	}

}

function Settings () 
{
	var iSettings = this;
	this.domNode = dom.create("div");
	this.loaded = false;   
	
	this.init = function () 
	{
		
	} 
	
	this.getDomNode = function () 
	{
		return this.domNode; 
	}
	
	this.show = function (reload) 
	{
		if (!this.loaded)
		{ 
			this.domNode.innerHTML = "<div class='center gray'>Retrieving your settings...</div>";
		} else 
			getEle("setting_status").innerHTML = ""; 
		ajax.send("?obj=settings&method=get", 
			function (req) 
			{
				if (req.responseText)
				{
					iSettings.domNode.innerHTML = req.responseText;
					iSettings.loaded = true;  
				} else 
					iSettings.domNode.innerHTML = "The server is busy. Please try again later.";  					
			}); 
	}
	
	this.save = function () 
	{
		var email = getEle("settings_email"); 
		if (email && email.value && email.value.length)
			ajax.send("?obj=settings&method=save", 
				function (req) 
				{
					getEle("setting_status").innerHTML = "Saved";  
				}, {params:{email:email.value}});  
	}
	
	this.init(); 
}

function Faves () 
{
	var iFaves = this; 
	this.domNode = dom.create("div");
	this.listingBox = null; 	 
	this.confirmBox = null; 
	
	this.show = function () 
	{
		if (!this.domNode.childNodes.length)
			this.domNode.innerHTML = "<div class='center gray'>Retrieving your favorites...</div>";
			
		ajax.send("?obj=faves&method=get", 
			function (req) 
			{
				if (req.responseText) 
					iFaves.domNode.innerHTML = req.responseText; 
				else 
					iFaves.domNode.innerHTML = "<span class='center gray'>Uh oh, there was a problem connecting to the server. Please try again later.</span>"; 
			}); 
	} 
	
	this.showAllFriends = function () 
	{
		if (!this.listingBox) 
		{
			this.listingBox = new Popup({draggable:true, focusable:false});
			if (!display.isMobile()) 
				this.listingBox.setWidth(790); 
		} 
		
		ajax.send("?obj=faves&method=getFriends&listAll=1", 
			function (req) 
			{
				iFaves.listingBox.setTitle ("All My Friends"); 
				iFaves.listingBox.setBody(req.responseText); 
				iFaves.listingBox.show(); 
			}); 
	}
	
	this.addFriend = function (id) 
	{
		if (id)
			ajax.send("?obj=faves&method=addFriend&friendId=" + encodeURIComponent(id), 
				function () 
				{
					iFaves.show(); 
				});  
	}
	
	this.removeFriend = function (id, domNode, confirmed) 
	{
		var name = domNode.firstChild.firstChild.innerHTML;
		if (!this.confirmBox)
			this.confirmBox = new Popup();   
		this.confirmBox.setTitle("Delete from Faves: " + name); 
		this.confirmBox.setBody(
			dom.create("div", _, _, _, 
			[
				dom.text("Delete this friend (" + name + ")?"),
				dom.create("br"), 
				dom.create("br"), 
				dom.button(_, "Yes", 
					function () 
					{
						domNode.style.display = "none"; 
						iFaves.confirmBox.hide(); 
						ajax.send("?obj=faves&method=removeFriend&friendId=" + encodeURIComponent(id)); 
					}),  
				dom.button(_, "Cancel", function () {iFaves.confirmBox.hide()}) 
			])); 
		if (!display.isMobile()) 
			this.confirmBox.setTop(dom.getTop(domNode) - 50); 
		this.confirmBox.show();
	}
	
	this.showAllRecentTopics = function () 
	{
		if (!this.listingBox) 
		{
			this.listingBox = new Popup({draggable:true, focusable:false});
			if (!display.isMobile()) 
				this.listingBox.setWidth(790); 
		} 
		
		ajax.send("?obj=faves&method=getRecentTopics&listAll=1", 
			function (req) 
			{
				iFaves.listingBox.setTitle ("All Recently Viewed Topics"); 
				iFaves.listingBox.setBody(req.responseText); 
				iFaves.listingBox.show(); 
			}); 
	}
	
	this.showAllMessages = function () 
	{
		if (!this.listingBox) 
		{
			this.listingBox = new Popup({draggable:true, focusable:false});
			if (!display.isMobile()) 
				this.listingBox.setWidth(790); 
		} 
		
		ajax.send("?obj=faves&method=getAllMessages&listAll=1", 
			function (req) 
			{
				iFaves.listingBox.setTitle ("All Messages"); 
				iFaves.listingBox.setBody(req.responseText); 
				iFaves.listingBox.show(); 
			}); 
	}
}

function Chatrooms () 
{
	var iChatrooms = this; 
	this.categoriesNode = null; 
	this.domNode = null; 
	this.roomsNode = null;
	this.newTopicBox = null; 
	
	this.categories = []; 
	this.rooms = []; 
	this.currentCategoryId = 0; 
	this.openTopics = [];
	
	this.getDomNode = function (emptyNode) 
	{
		if (!this.domNode || !this.domNode.innerHTML) 
			this.createDomNode(emptyNode); 
		
		return this.domNode; 
	} 
	
	this.createDomNode = function () 
	{
		this.domNode = dom.create("div");
	}
	
	this.showSelection = function () 
	{
		if (this.currentCategoryId) 
		{
			if (this.domNode.firstChild != this.roomsNode || !this.roomsNode)
			{ 
				 
				
				//if (!this.roomsNode)
				{
					this.roomsNode = dom.create("div"); 
					this.roomsNode.innerHTML = "<div class='center gray'>Retrieving topics...</div>";
				}
			}
		} else
		{ 
			if (this.domNode.firstChild != this.categoriesNode || !this.categoriesNode)
			{ 
				if (!this.categoriesNode)
				{
					this.categoriesNode = dom.create("div"); 
					this.categoriesNode.innerHTML = "<div class='center gray'>Retrieving categories...</div>";
				}
				
				iChatrooms.domNode.innerHTML = "";
				iChatrooms.domNode.appendChild(iChatrooms.categoriesNode);
				
				if (display.isMobile())
				{
					var appTop = dom.getTop(app.getDomNode()); 
					if (display.getTop() > appTop) 
						display.setTop(appTop);
				}								
			}
		} 
		
		ajax.send("?obj=chatrooms&method=" + (this.currentCategoryId ? "getRooms" : "getCategories") + "&catId=" + this.currentCategoryId, 
			function (req) 
			{						 
				var cats = []; 
				if (req.responseText) 
				{											
					if (iChatrooms.currentCategoryId)
					{
						iChatrooms.domNode.innerHTML = ""; 
						iChatrooms.domNode.appendChild(iChatrooms.roomsNode);
					}			
					//else 
					//	iChatrooms.domNode.appendChild(iChatrooms.categoriesNode);
					
					cats = json.decode(req.responseText);
					var name = iChatrooms.currentCategoryId ? "Topic" : "Category"; 
					var buffer = dom.create("div");  
					buffer.innerHTML = "<table style='font-size:1em; margin-bottom:0.5em;'><tr><td style='width:10em; white-space:nowrap'>" + (iChatrooms.currentCategoryId ? 
						html.button(_, "<<", "connect.chatrooms.currentCategoryId = null; connect.chatrooms.showSelection()") : "") + 
						html.button(_, "New Topic", "connect.chatrooms.newTopic()") + 
						"</td><td class='big' style='width:60%' " + (display.isMobile() ? "align='center'" : "") +">Select a " + (name) + "</td></tr></table>";
					buffer.style.width = "100%"; 
					var link = dom.create("div", _, "chatroom_link", _, 
						[
							dom.create("span", _, "max_table_title chatroom_name_title", _, name), 
							dom.create("span", _, "max_table_title chatroom_post_count", _, (name == "Topic") ? "Posts" : "Topics"), 
							dom.create("span", _, "max_table_title chatroom_user_count", _, "Users")
						]); 
					buffer.appendChild(link);						
					if (cats && cats.length)
					{						

						var list = iChatrooms.currentCategoryId ? "rooms" : "categories"; 
						iChatrooms[list] = []; 
						var nameClass; 
						for (var i = 0; i < cats.length; i++) 
						{
							iChatrooms[list][cats[i].room_id] = cats[i].name;
							if (cats[i].private == 1)
								nameClass = "chatroom_private"; 
							else if (cats[i].room_id == 1 || iChatrooms.currentCategoryId)
								nameClass = "chatroom_name"; 
							else 
								nameClass = "chat_category_name";  
							var topic = dom.create("span", _, nameClass, {roomId:cats[i].room_id, name:cats[i].name}, (cats[i].name));
							var link = dom.create("div", _, "chatroom_link", _, 
								[
									topic, 
									dom.create("span", _, "chatroom_post_count", {roomId:cats[i].room_id, name:cats[i].name}, cats[i].postCount), 
									dom.create("span", _, "chatroom_user_count", {roomId:cats[i].room_id, name:cats[i].name}, cats[i].userCount)
								]);	
							if (cats[i].room_id == 1 || iChatrooms.currentCategoryId || cats[i].private == 1) 
								eventX.add(link, "click", 
									function (e) 						 
									{
										var ele = e.target || e.srcElement;  
										iChatrooms.joinTopic(ele.getAttribute("roomId"), ele.getAttribute("name"));
									}); 
							else 
								eventX.add(link, "click", 
									function (e) 
									{					 
										var ele = e.target || e.srcElement;  
										iChatrooms.currentCategoryId = ele.getAttribute("roomId");									  
										iChatrooms.showSelection(); 
									});  
							buffer.appendChild(link); 
						}
						iChatrooms.domNode.firstChild.innerHTML = ""; 
						iChatrooms.domNode.firstChild.appendChild(buffer); 	
					} else  
						cats = []; 
					if (!cats.length) 
					{
						if (iChatrooms.currentCategoryId) 
						{
							iChatrooms.domNode.firstChild.innerHTML = "";
							iChatrooms.domNode.firstChild.appendChild(buffer);  
							iChatrooms.domNode.firstChild.appendChild(dom.create("span", _, "gray", _, "There are no topics in this category.<br><br>Click on [New Topic] to create a new one.")); 
						} else 
						{
							iChatrooms.domNode.firstChild.innerHTML = "<span class='gray'>I am sorry, but the server seems to be unavailable at the moment. Please try again later.</span>"; 
						}				
					}
				
				}
				

				
				if (iChatrooms.currentCategoryId && display.isMobile())
				{ 
					var appTop = dom.getTop(app.getDomNode()); 
					if (display.getTop() > appTop) 
						display.setTop(appTop);
				} 
				
			}); 
	}
	
	this.closeAllTopics = function () 
	{
		for (var id in this.openTopics) 
			this.openTopics[id].popup.hide(); 
	}
	
	this.joinTopic = function (id, name, targetId, targetName) 
	{
		if (this.openTopics[id])
			this.openTopics[id].activate(); 		 
		else 
			this.openTopics[id] = new Topic(id, name, 
			function () 
			{
				delete iChatrooms.openTopics[id]; 
			}, targetId, targetName); 
	}	
	
	this.newTopic = function () 
	{
		var currentCategory = (this.currentCategoryId) ? this.categories[this.currentCategoryId] : "General"; 
		
		if (!this.newTopicBox)
		{
			 
			this.newTopicBox = new Popup({title:"New Topic", hideAd:false});
			this.newTopicBox.categoryNode = dom.select(_, _, _, topicCategories, "General");
			this.newTopicBox.categoryNode.style.width = "100%";   
			this.newTopicBox.topicNode = dom.create("input", _, _, {type:"text"}); 
			this.newTopicBox.privateNode = dom.create("input", _, _, {type:"checkbox"});
			this.newTopicBox.privateNode.style.marginTop = "0.2em";
			this.newTopicBox.privateNode.style.width = "auto";  
			this.newTopicBox.errorNode = dom.create("span"); 
			this.newTopicBox.errorNode.style.fontColor = "red";
			var categoryTd = dom.create("td", _, _, _, dom.create("span", _, "max_field_title", _, "Category: ")); 
			categoryTd.style.width = "30%";  
			var createNewTopic = function () 
						{
							var category = iChatrooms.newTopicBox.categoryNode.options[iChatrooms.newTopicBox.categoryNode.selectedIndex].value;
								
							var topic = iChatrooms.newTopicBox.topicNode.value; 
							var private = iChatrooms.newTopicBox.privateNode.checked ? 1 : 0;
							if (category && topic.length) 
								ajax.send("?obj=topic&method=newTopic&category=" + encodeURIComponent(category) + "&topic=" + topic + "&private=" + private,
									function (req) 
									{
										var response = json.decode(req.responseText);
										if (response && response.topicId)
										{
											keyboard.hide(); 
											iChatrooms.newTopicBox.hide(); 
											iChatrooms.currentCategoryId = response.categoryId; 										
											iChatrooms.joinTopic(response.topicId, topic.toHtmlEntities());
											iChatrooms.showSelection();  
										} else 
										{
											iChatrooms.newTopicBox.errorNode.innerHTML = "It's busy. Please try later."; 
										} 
									});   
						}
			eventX.add(this.newTopicBox.topicNode, "keydown",  
				function (e) 
				{
					checkEnter(e, createNewTopic); 
				}); 
			var body =
				dom.create("div", _, _, _, 
				[ 
					dom.table(_, _, _, 
					[
						dom.create("tr", _, _, _, 
							[categoryTd,
							dom.create("td", _, _, _, this.newTopicBox.categoryNode)]
						), 
						dom.create("tr", _, _, _, 
							[dom.create("td", _, _, _, dom.create("span", _, "max_field_title", _, "Topic: ")), 
							dom.create("td", _, _, _, this.newTopicBox.topicNode)]
						),
						dom.create("tr", _, _, _, 
							[dom.create("td", _, _, _, dom.create("span", _, "max_field_title", _, "Private:")), 
							dom.create("td", _, _, _, this.newTopicBox.privateNode)]
						),
						dom.create("tr", _, _, _, dom.create("td", _, _, _, "&nbsp;"))
					]),
					dom.button(_, "Create", createNewTopic),
					dom.text(" "),  
					dom.button(_, "Cancel", function () { iChatrooms.newTopicBox.hide() }), 
					dom.text(" "), 
					this.newTopicBox.errorNode										
				]); 
			this.newTopicBox.setBody(body);
		} 
		
		if (this.newTopicBox.categoryNode && this.newTopicBox.categoryNode.options) 
		{
			var foundIndex = -1; 			
			for (var i = 0; i < this.newTopicBox.categoryNode.options.length; i++)
				if (this.newTopicBox.categoryNode.options[i].value == currentCategory)
				{
					foundIndex = i;
					break; 
				} 
			if (foundIndex >= 0)
				this.newTopicBox.categoryNode.selectedIndex = foundIndex;  
		}
		this.newTopicBox.show();
		this.newTopicBox.domNode.style.overflow = "visible";  
		this.newTopicBox.topicNode.focus(); 
		this.newTopicBox.topicNode.value = ""; 
	}
	
	this.instantMessage = function (targetId)
	{
		if (!targetId) return; 
		
		ajax.send("?obj=chatrooms&method=instantMessage&targetId=" + encodeURIComponent(targetId), 
			function (req) 
			{
				if (req.responseText) 
				{
					var response = json.decode(req.responseText);
					if (response.topicId) 
						iChatrooms.joinTopic(response.topicId, "To: " + response.targetName, targetId, response.targetName); 
				}
			}); 
	}	
}

function Topic (id, name, onClose, targetId, targetName) 
{
	var iTopic = this; 
	this.id = id; 
	this.name = name;
	this.targetId = targetId; 
	this.targetName = targetName;  
	this.requestIds = {}; 
	this.updateId = 0; 
	this.onClose = onClose; 
	this.msgCount = 0; 
	
	this.init = function () 
	{
		this.chatWindow = dom.create("div", _, "chat_window");
		this.textInput = dom.create("input", _, "chat_input", {autocorrect:"off"});
		this.textInput.style.fontSize = "1em";
		
		this.textInput.onkeydown = function (e) 
			{
				checkEnter(e, function () {iTopic.sendMessage()}); 
			}   
		//eventX.add(this.textInput, "keydown", 
		//	function (e) 
		//	{
		//		checkEnter(e, function () {iTopic.sendMessage()}); 
		//	}); 
		this.sendButton = dom.button(_, "Send", function () {iTopic.sendMessage()});
		if (display.isMobile())
		{
			//this.sendButton.style.fontSize = "0.8em";
			eventX.add(this.textInput, "focus", 
				function () 
				{
					setTimeout(
						function () 
						{
							var heightDelta = 176; 
							if (document.width && document.width == 480) 
								heightDelta = 70;
							if (display.isMobile())
								iTopic.popup.setLeft(0); 
							display.setTop(dom.getTop(iTopic.textInput) - heightDelta)
						}, 400);  
				}); 
		}	

		var bodyHtml; 		 
		if (display.isMobile())
			bodyHtml = "Come chat with me! :) I am waiting for you in <a href=\"" + app.url + "?topicId=" + iTopic.id + "\">" + iTopic.name.toHtmlEntities() + "</a>.<br><br>Service provided by <b><a href=\"" + app.url + "\">Connect<font color=\"goldenrod\"><i>MAX</i></font></a></b>";
		else 
			bodyHtml = "Come chat with me! :) I am waiting for you in " + iTopic.name + " at " + app.url + "?topicId=" + iTopic.id;
		var inviteHtml = this.targetId ? "" : ("To invite your friends, click <a href='mailto:?subject=Chat with me in " + encodeURIComponent(iTopic.name.toHtmlEntities()) + "&body=" + bodyHtml + "'>here</a>."); 			
		
		var section0; 
		if (isNull(user.userName) || user.userName === "")
		{ 
			section0 = dom.table(_, _, _, 
					dom.create("tr", _, _, _, 					
						dom.create("td", _, _, _, dom.create("div", _, _, _, "<big>To start chatting, please <a href='#' onclick='connect.chatrooms.openTopics[" + this.id + "].popup.hide(); user.login(); return false' style='color:blue'>login</a> or <a href='#' onclick='connect.chatrooms.openTopics[" + this.id + "].popup.hide(); user.register(); return false' style='color:blue'>register</a>.</big>")) 
					) 
				);
			inviteHtml = "";  		
		} else 
			section0 = dom.table(_, _, _, 
					dom.create("tr", _, _, _, 
					[
						dom.create("td", _, _, _, this.textInput), 
						dom.create("td", _, _, {width:"1%"}, this.sendButton)
					]) 
				); 		
		var section1 = dom.create("span", _, "chat_section", _, [this.chatWindow, section0]);
		if (inviteHtml) 
			section1.appendChild(dom.create("div", _, "inviteLink", _, inviteHtml));
		//if (this.targetId) 
		//{
		//	var target = dom.create("div", _, "instantMessageTo", _, "<span class='max_field_title'>To:</span> " + this.targetName); 
		//	section1.insertBefore(target, section1.firstChild); 
		//}
		this.userList = dom.create("span", _, "chat_users", _, "<div class='center gray'>Joining the topic...</div>");     
		
		var body = dom.create("div", _, _, _, this.targetId ? section1 : [section1, this.userList]);  
 
 		if (name.indexOf("<") != -1) 
 			name = name.toHtmlEntities();
		this.popup = new Popup({title:name, draggable:true, body:body}, 
			{close: function () 
			{
				iTopic.destroy(); 
				if (iTopic.onClose) 
					iTopic.onClose(); 
			}});
		if (!display.isMobile())
		{ 
			if (this.targetId)
			{
				this.chatWindow.style.height = 200;
				this.popup.setWidth (461);
			} else 
				this.popup.setWidth (600);
			this.popup.setLeft(75 + 50 * Math.random());
			this.popup.setTop(150 + 50 * Math.random());
		} else if (this.targetId)
		{ 
			//this.popup.setHeight(227);			
			this.chatWindow.style.height = 132;
			section1.style.height = 190;
			this.popup.setHeight(200);
		} 
		this.activate();  
	}
	
	
	this.activate = function ()
	{
		if (!this.requestIds["update"])
		{ 
			this.requestIds["update"] = ajaxMAX.addRequest({obj:"topic", method:"getMessages", topicId:iTopic.id, updateId:iTopic.updateId, targetId:iTopic.targetId, 
				callback: function (msgs) 
				{
					if (msgs) 
					{
						var autoScroll = false; 
						if (!iTopic.updateId || (iTopic.chatWindow.scrollHeight - iTopic.chatWindow.scrollTop) < iTopic.chatWindow.offsetHeight + 50)
							autoScroll = true; 
						for (var id in msgs) 
						{							 
							var userClass = (msgs[id].senderId == user.userId) ? "user_self" : (msgs[id].active ? "user_active" : "user_inactive");
							id = parseInt(id); 
							if (id > iTopic.updateId) // prevent dupes for slow connection
								iTopic.showMessage({senderId:msgs[id].senderId, sender:msgs[id].sender, message:msgs[id].message, userClass:userClass});
							iTopic.setUpdateId(id);
						}
						if (autoScroll)
							iTopic.chatWindow.scrollTop = iTopic.chatWindow.scrollHeight; 
					}					
				}});
			if (!iTopic.targetId) 
				this.requestIds["chatter"] = ajaxMAX.addRequest({obj:"topic", method:"getChatters", topicId:iTopic.id, delay: 10, 
					callback: function (chatters) 
					{
						iTopic.userList.innerHTML = chatters; 
					}});
			ajaxMAX.sendRequests(true);
		} 
		this.popup.show();
		if (!display.isMobile() && isString(user.userName) && user.userName !== "") 
			iTopic.textInput.focus();   
	}	
	
	this.showMessage = function (msg) 
	{
		if (!msg.senderId || !msg.message) 
			return; 
		if (!msg.userClass) 
			msg.userClass = "user_active";
		if (!msg.sender) 
			msg.sender = "guest";
		 
		this.chatWindow.appendChild(dom.create("div", _, "chat_message", _, 
			"<b class='" + msg.userClass + "' onclick='user.showInteractMenu(" + msg.senderId + ", this)'>" + msg.sender + "</b>: " + msg.message));
			
		if (this.msgCount++ > 50) 
			this.chatWindow.removeChild(this.chatWindow.firstChild); 
	}
	
	this.sendMessage = function () 
	{
		if (this.textInput.value.length) 
		{
			var message = this.textInput.value;
			this.textInput.value = "";
			this.showMessage({userClass:"user_self", sender:user.userName, senderId:user.userId, message:misc.enhanceLinks(message.toHtmlEntities())});
			iTopic.chatWindow.scrollTop = iTopic.chatWindow.scrollHeight; 

			ajax.send("?obj=topic&method=addMessage&topicId=" + this.id + "&message=" + encodeURIComponent(message) + "&targetId=" + encodeURIComponent(this.targetId), 
				function (req) 
				{
					if (req.responseText) 
					{
						var response = json.decode(req.responseText);
						if (!iTopic.updateId)
						{
							iTopic.setUpdateId(response.messageId);
						}						 
					}
				}); 			
		} 
	}
	
	this.setUpdateId = function (id) 
	{
		if (id && id > this.updateId)  
			this.updateId = id;
		if (this.requestIds["update"] && ajaxMAX.requests[this.requestIds["update"]]) 
			ajaxMAX.requests[this.requestIds["update"]].updateId = id; 
	}
	
	this.destroy = function () 
	{
		for (var id in this.requestIds) 
			ajaxMAX.removeRequest(this.requestIds[id]);
	}
	
	this.init();  
}

function WebApplication (container_id) 
{
	this.domNode = getEle(container_id);
	this.url = window.location.protocol + "//" + window.location.host; 
	
	this.init = function () 
	{
		this.domNode.innerHTML = "";
		
		ajaxMAX.addRequest({obj:"app", method:"getUpdate", delay: 15, mustDelay: true, callback:
			function (response) 
			{
				if (response && response.version && response.version != app.version)
				{
					keyboard.hide(); 
					var refreshSeconds = 45; 
					var upgradeNotice = new Popup({title:"Automatic Update", 
						body: "<div style='padding: 0.5em'>This web application has been updated. Your browser will auto-refresh in <span id='updateTimer'>" + refreshSeconds + "</span> seconds.<br><br><b>Update notes for v" + response.version + ":</b><ul style='margin-top: 0.5em'><li>" + response.notes.join("</li><li>") + "</li></ul>" + 
						"<div class='center'>" + html.button(_, "&nbsp;Ok&nbsp;", "") + " " + html.button(_, "Refresh Now", "window.location.reload()") + "</div></div>", easyClose:true});
					upgradeNotice.show();
					setInterval(
						function () 
						{
							refreshSeconds--; 
							if (refreshSeconds < 1) 
								window.location.reload(); 
							else
								getEle("updateTimer").innerHTML = refreshSeconds;   
						}, 1000);   
				}
			}});  
	}
	
	this.getDomNode = function () 
	{
		return this.domNode; 
	}
	
	this.init(); 
}
function Keyboard () 
{
	if (display.isMobile()) 
	{
		this.hiddenInput = dom.create("input", _, _, {type:"checkbox"});	
		this.hiddenInput.style.fontSize = "0.01em"; 
		this.hiddenInput.style.width = "1px"; 
		this.hiddenInput.style.height = "1px"; 
		this.hiddenInput.style.border = "none"; 
		var body = document.getElementsByTagName("body")[0]; 
		body.appendChild(this.hiddenInput);
	}	
	
	this.hide = function () 
	{
		if (display.isMobile())
			this.hiddenInput.focus(); 
	} 	 	
}

function Debug(outElementId) 
{	
	this.output = getEle(outElementId);
	this.logs = new Array();  
	
	this.log = function (msg, show_alert) 
	{
		this.logs.push(defined(msg) ? (msg === null ? "null" : (typeof msg == "string" ? msg.replace(/</g, "&lt;") : msg.toString())) : "undefined"); 
		this.showLogs();
		
		if (defined(show_alert) && show_alert) 
			alert(msg);   
	}
	
	this.alert = function(msg) 
	{
		this.log(msg, true); 
	}
	
	this.showLogs = function () 
	{
		if (this.output && this.logs.length)
			this.output.innerHTML = this.logs.join('<br>');   
	}
	
	this.dump = function (object, numeric_only) 
	{
		if (typeof object !== 'undefined' && object) 
		{
			if (typeof object == "string") 
				this.logs.push(object); 
			else 
				for (var member in object) 
				{
					try { 
						if (defined(numeric_only) && numeric_only && isNaN(parseFloat(object[member])))
							continue; 
						this.logs.push("<pre><b>" + member + '</b>: ' + object[member] + "</pre>");
					} catch (e)   
					{
						this.logs.push("<pre><b>" + member + '</b>: (Exception) ' + e.name + "</pre>");
					}
				}
			this.showLogs(); 
		} else 
			this.log(object, true); 
	}	
	
	this.clear = function () 
	{
		this.logs = []; 
	}
}

function Event () 
{
	this.add  = function (obj, eventName, callback) 
	{
		if (obj.addEventListener) 
			obj.addEventListener(eventName, callback, false); 
		else if (obj.attachEvent)
			obj.attachEvent("on" + eventName, callback);
		else 
			debug.log(obj + " doesn't support event (" + eventName + ") attachments.");
	}
	
	this.remove = function (obj, eventName, callback) 
	{
		if (obj.removeEventListener) 
			obj.removeEventListener(eventName, callback, false);
		else if (obj.detachEvent) 
			obj.detachEvent(eventName, callback); 
		else 
			debug.log(obj + " doesn't support event (" + eventName + ") detachments.");
	}
	
	this.stop = function (eventObj) 
	{
		var e = eventObj || window.event; 
		if (typeof e === "undefined" || !e) return;  
	 
		e.cancelBubble = true; 
		if (e.stopPropagation) 
			e.stopPropagation();
		if (e.preventDefault) 
			e.preventDefault(); 
	}
}
function Display() 
{
	this.container = getEle('display_container');
	
	this.isMobile = function () 
	{
		return navigator.appVersion.indexOf("Mobile") >= 0 || navigator.platform && navigator.platform == "iPhone";  
	}
	 
	this.getTop = function () 
	{
	 	return window.pageYOffset || document.body.scrollTop;
	}
	
	this.getHeight = function () 
	{
		//if (this.container && this.container.className == "mobile_container")
		return window.innerHeight || document.body.offsetHeight; 
		//else 
		//	return this.container.offsetHeight; 
	}
	 
	this.getBottom = function () 
	{
		return this.getTop() + this.getHeight();
	}
	
	this.getWidth = function (container) 
	{
		if (hasValue(container) && container) 
			return this.container.offsetWidth; 
		else 
			return document.body.offsetWidth || window.innerWidth;
	}
	
	
	this.getLeft = function () 
	{
		if (this.container)
			return this.container.offsetLeft;
		else 
			return 0;  
	}
	
	this.setTop = function(top) 
	{
		window.scrollTo(0, parseInt(top) ? parseInt(top) : 1);
	}  
}	
 
 
function DOM ()
{
	this.create = function (tag, id, className, attribs, childrenOrHtml) 
	{
		var ele = null; 
		if (hasValue(attribs) && (attribs.name || attribs.checked))
		{	// IE workaround for creating named elements. 
			try {
				ele = document.createElement("<" + tag + " name='" + attribs.name + "'" + (attribs.checked ? "checked='checked'" : "") + ">");
			} catch (e) {
				ele = document.createElement(tag);
			}
		} else
		if (isNull(ele)) 
			ele = document.createElement(tag);
		if (hasValue(id)) 
			ele.id = id; 
		if (hasValue(className)) 
			ele.className = className; 
		if (hasValue(attribs)) 
			for (var key in attribs)
			{
				ele.setAttribute(key, attribs[key]);
			}
		if (hasValue(childrenOrHtml))
		{
			if (isArray(childrenOrHtml)) 
				for (var i = 0; i < childrenOrHtml.length; i++) 
					ele.appendChild(childrenOrHtml[i]);
			else if (typeof childrenOrHtml == "object")
			{ 
				ele.appendChild(childrenOrHtml)
			} else
				ele.innerHTML = childrenOrHtml; 
		}		 
		return ele;   
	}
	
	this.text = function (text) 
	{
		return document.createTextNode(text); 
	}
	
	this.button = function (id, name, onclick) 
	{
		butt_name = this.text(name);
		/* 
		butt = this.create("a"); 
		butt.setAttribute("href", "#");
		butt.id = id; 
		butt.className = "max_button"; 
		butt.appendChild(butt_name);
		*/
		butt = this.create("button"); 
		butt.appendChild(butt_name); 
		butt.className = "max_button"; 
		butt.id = id;  
		if (defined(onclick))
			eventX.add(butt, "click", onclick); 
		
		return butt; 
	}
	
	this.getStyle = function (id, name)
	{
		var ele = getEle(id);
 		if (ele) 
 		{
	 		if (ele.currentStyle)
	   			return ele.currentStyle[name];
	 		else
				try {
					var cs = document.defaultView.getComputedStyle(ele,null);
	     			return cs.getPropertyValue(name);
	   			} catch(e) {}
		}
		   			
   		return false; 
 	}
	
	this.setStyle = function (id, name, style) 
	{ 
		var ele = getEle(id);
 		if (ele) 
 		{  		
	 		if (defined(style)) {
	 			if (ele.runtimeStyle)
	     			return ele.runtimeStyle[name] = style;
	   			else
	     			return ele.style[name] = style;
	 		} else 
	 			return this.getStyle(id, name);
	 	} 
	 	
	 	return false;  	
	}
	
	this.select = function (id, className, attribs, values, value)
	{
		var options = new Array(); 
		
		if (!className) 
			className = "max_select"; 
		if (hasValue(values)) 
			for (var i = 0; i < values.length; i++)
			{
				var opt_attribs = {}; 
				opt_attribs.value = values[i];
				if (isArray(value) && value.length) 
				{
					for (var j = 0; j < value.length; j++) 
						if (values[i] == value[j])  
							opt_attribs.selected = "selected";    
				} else if (values[i] == value)  
							opt_attribs.selected = "selected";
				options.push(this.create("option", null, null, opt_attribs, values[i]));			
			}	
		
		var select = this.create("select", id, className, attribs, options);

		return select;  
	} 
	
	this.mdd = function (id, className, attribs, values, selectedValues, events)
	{
		id = hasValue(id) ? id : ("mdd_" + getRandomId);
		
		if (!attribs) 
			attribs = {}; 
		attribs.multiple = true; 
		var normalMdd = this.select(id, className, attribs, values, selectedValues);
		
		if (hasValue(events)) 
			for (var name in events) 
				normalMdd["on" + name] = events[name]; 
		
		return normalMdd;  		
	}
	
	this.inputSelect = function (id, value, dbName, attribs) 
	{
		var input = dom.create("input"); 
		input.type = "text";
		input.id = id; 
		input.value = value;
	
		if (hasValue(attribs)) 
			for (var key in attribs) 
				input.setAttribute(key, attribs[key]); 

		return input; 		
	}
	
	this.table = function (id, className, attribs, children) 
	{
		var tbody = this.create("tbody", _, _, _, children);
		var table = this.create("table", id, className, attribs, tbody);
		
		return table; 
	}
	
	this.check = function (id, className, attribs, name) 
	{
		if (isNull(attribs)) 
			attribs = {}; 
		if (isNull(id)) 
			id = "checkbox_" + getRandomId(); 
		attribs["type"] = "checkbox";
		var check = dom.create("input", id, _, attribs);
		check.style.width = "auto";
		check.style.border = "none";
		check.checked = attribs.checked; 
		var label = dom.create("label", _, _, _, dom.text(name));		
		var container = dom.create("span", _, className, _, [check, label]);
		eventX.add(container, "click", 
			function(evt) 		
			{
				var evt = evt || window.event; 
				var target = evt.target || evt.srcElement; 				
				if (target.tagName !== "INPUT")
					check.checked = !check.checked;
			})
		eventX.add(container, "mouseout", 
			function () 		
			{
				container.style.backgroundColor = "#ffffff";  
			}); 
		eventX.add(container, "mouseover", 
			function () 		
			{
				container.style.backgroundColor = "#f7f7ff";  
			}); 		
		return container; 
	}

	this.radio = function (id, className, attribs, name) 
	{
		if (!attribs) 
			attribs = {}; 
		var checkAttribs = {}; 
		var internalId = "radio_" + getRandomId();
		if (isNull(id)) 
			id = internalId;  
		attribs["type"] = "radio"; 
		attribs["name"] = id;   
		var check = dom.create("input", internalId, _, attribs);
		check.name = id; 		
		check.style.width = "auto";
		check.style.border = "none";
		check.checked = attribs.checked;    
		var label = dom.create("label", _, _, _, dom.text(name)); 
		var container = dom.create("span", id, className, _, [check, label]);
		eventX.add(container, "click", 
			function(evt) 		
			{	
				var evt = evt || window.event; 
				var target = evt.target || evt.srcElement;
				if (target.tagName !== "INPUT")
					check.checked = true;
			})
		eventX.add(container, "mouseout", 
			function () 		
			{
				container.style.backgroundColor = "#ffffff";  
			}); 
		eventX.add(container, "mouseover", 
			function () 		
			{
				container.style.backgroundColor = "#f7f7ff";  
			}); 
		return container; 
	}
	
	this.getTop = function (element, breakOnAbsolute)
	{
		var top = 0;
		while (element != null) {
			top += element.offsetTop;
			element = element.offsetParent;
			if (breakOnAbsolute && element && element.className == "max_popup") break; 
		}
		
		return top;
	}
	
	this.getLeft = function (element, breakOnAbsolute)
	{
		var left = 0;
		while (element != null) {
			left += element.offsetLeft;
			element = element.offsetParent;
			if (breakOnAbsolute && element && element.className == "max_popup") break;
		}
		
		return left;
	}
	
	this.getHeight = function (ele) 
	{
		return ele.offsetHeight; 
	}
	
	this.getWidth = function (ele) 
	{
		return ele.offsetWidth; 
	}
	
	this.fieldSet = function (id, className, attribs, title, children) 
	{
		var legend = dom.create("legend", _, _, _, dom.text(title)); 
		var set = dom.create("fieldset", id, className, attribs, legend); 
		if (hasValue(children) && children.length) 
			for (var i = 0; i < children.length; i++) 
				set.appendChild(children[i]); 
		return set; 
	}
	
	this.setOpacity = function (obj, value) {
		if (obj && obj.style) 
		{
			obj.style.opacity = value / 10;
			obj.style.filter = 'alpha(opacity=' + value * 10 + ')';
		}
	}	
} 

function Html () 
{
	this.button = function (id, name, onclick) 
	{
		return "<button id='" + id + "' onclick='" + onclick + "' class='max_button'>" + name + "</button>"; 
	}
	
	this.input = function (id, value, attribs) 
	{
		var attribs_str = ""; 
		
		if (defined(attribs) && attribs)
		{ 
			if (!attribs.type)
				attribs.type = "text";  
			for (var key in attribs) 
				attribs_str += key + "='" + attribs[key] + "' ";
		} else 
			attribs_str = "type='text'"; 
			 		
		if (!defined(value)) 
			value = ""; 
		if (!defined(id)) 
			id = ""; 
		
		return "<input id='" + id + "' value='" + value + "' " + attribs_str + " />"; 
	}
	

}


function Select (attribs)
{
	// new mdd 
	this.id = "mdd_" + getRandomId(); 
	this.values = hasValue(attribs) && attribs.allValues ? attribs.allValues : []; 
	this.multiple = hasValue(attribs) && attribs.multiple ? attribs.multiple : false; 
	this.selectedValues = hasValue(attribs) && attribs.selectedValues ? attribs.selectedValues : (this.multiple ? [] : "");
	this.onChange = hasValue(attribs) && attribs.events && attribs.events.change ? attribs.events.change : null;
	this.domNode = null; 
	
	this.createDomNode = function () 
	{
		var select = this; 
		var summary = dom.create("div", this.id + "_summary", "max_select_summary"); 
		var values = dom.create("div", this.id + "_values", "max_select_values");  
	
		// set selected values
		if (this.values && this.values.length)
		{
			var onChange = null; 
			var onChange = function (evt) 
			{
				setTimeout(
					function() 
					{
						if (!select.multiple)
						{ 
							if (select.onChange) 
								select.onChange(); 				 						
							values.style.display = "none";
						} 
						select.updateSummary();
					}, 100); 
			} 
			var attribs = {}; 
			for (var i = 0; i < this.values.length; i++) 
			{				
				delete attribs.checked;
				if (this.selectedValues)
				{ 
					if (this.multiple)
					{ 
						for (var j = 0; j < this.selectedValues.length; j++) 
							if (this.values[i] == this.selectedValues[j])
								attribs.checked = "checked";
					} else if (this.values[i] == this.selectedValues) 
						attribs.checked = "checked";					 
				} 
				if (this.multiple) 
					var check = dom.check(_, "max_select_value", attribs, this.values[i].toString().toHtmlEntities());
				else 
				{
					var check = dom.radio(this.id + "_value", "max_select_value", attribs, this.values[i].toString().toHtmlEntities());
					check.firstChild.style.display = "none";
				}
				eventX.add(check, "click", onChange); 
				values.appendChild(check); 
			}
		} else 
		{
			var none = dom.create("div", _, _, _, dom.text("None")); 
			none.className = "gray"; 
			none.style.textAlign = "center"; 
			values.appendChild(none);
		}
		var doClose = function () 
				{
					values.style.display = "none";  
					if (select.onChange) 
						select.onChange(); 				 											
				}; 
		//if (this.multiple)
		{ 
			var doneButton = dom.create("div", _, _, _, dom.button(_, "Done"));
			eventX.add(doneButton, "click", doClose);			
			doneButton.style.textAlign = "center";
			doneButton.style.borderTop = "1px dashed #999999";
			doneButton.style.padding = "0.2em";    
			values.appendChild(doneButton);
		} 

		summary.innerHTML = this.getSummary();
			
		eventX.add(summary, "click", 
			function () 
			{
				values.style.top = dom.getTop(summary, true) + summary.offsetHeight; 
				if (values.style.display == "inline") 
				{
					values.style.display = "none";  			 																
				} else 
				{
					var divs = document.getElementsByTagName("div");
					for (var i = 0; i < divs.length; i++) 
						if (divs[i].className == "max_select_values") 
							divs[i].style.display = "none"; 
					values.style.display = "inline";
				}				
			});
			
		this.domNode = dom.create("div", this.id, "max_select", _, [summary, values]);
		eventX.add(values, "mouseout", 
			function(evt) 
			{
				var evt = evt || window.event;
				var x = evt.clientX; 
				var y = evt.clientY; 
				var top = dom.getTop(values);
				var left = dom.getLeft(values); 
				var right = left + dom.getWidth(values);
				var bottom = top + dom.getHeight(values);
				//debug.log(x + " " + y + " " + top + " " + bottom); 
				//debug.log(right + " " + bottom);   
				//debug.log (bottom + " " + y); 
				if (x <= left || x >=right || y >= bottom || y <= top)
				{ 
					doClose(); 
				}
			}); 
	}
	
	this.getDomNode = function () 
	{
		if (!this.domNode) 
			this.createDomNode(); 
			
		return this.domNode; 
	}
	
	this.updateSummary = function () 
	{
		var summary = getEle(this.id + "_summary"); 
		
		if (summary) 
			summary.innerHTML = this.getSummary();
	}
	
	this.getSummary = function () 
	{
		var values = this.getSelectedValues();
		var summary = "";  
		
		if (isArray(values)) 
		{
			if (values.length == 1) 
				summary = values[0]; 
			else 
				for (var i = 0; i < values.length; i++)
					summary += values[i].charAt(0) + "&nbsp;";  				
		} else 
			summary = values; 
		
		if (!summary) 
			summary = "<span class='gray'>None</span>";  
		
		return summary; 
	}
	
	this.getSelectedValues = function () 
	{
		var valuesDiv = getEle(this.id + "_values");
		if (valuesDiv) 
		{
			var selectedValues = []; 
			try {
				for (var i = 0; i < valuesDiv.childNodes.length; i++)
					if (valuesDiv.childNodes[i].firstChild.checked) 
						selectedValues.push(valuesDiv.childNodes[i].lastChild.firstChild.nodeValue.toString().fromHtmlEntities());
			} catch (e)
			{
				debug.log("Select.getSelectedValues() " + e); 
			}
			if (this.multiple)
			{ 
				this.selectedValues = selectedValues;
			} else 
				this.selectedValues = hasValue(selectedValues[0]) ? selectedValues[0] : ""; 
		}		 
		if (isArray(this.selectedValues)) 
			this.selectedValues.sort(); 
		return this.selectedValues;  
	}
	
	this.getSelectedValue = function () 
	{
		return this.getSelectedValues(); 
	}
	
	this.setSelectedValues = function (values) 
	{
		var valuesDiv = getEle(this.id + "_values");
		if (valuesDiv) 
		{
			try {
				for (var i = 0; i < valuesDiv.childNodes.length; i++)
				{
					valuesDiv.childNodes[i].firstChild.checked = false; 
					for (var j = 0; j < values.length; j++) 
						if (valuesDiv.childNodes[i].lastChild.innerHTML.fromHtmlEntities() == values[j])
							valuesDiv.childNodes[i].firstChild.checked = true;
						 
					//if (valuesDiv.childNodes[i].firstChild.checked) 
					//	selectedValues.push(valuesDiv.childNodes[i].lastChild.firstChild.nodeValue.toString().fromHtmlEntities());
				}
			} catch (e)
			{
				debug.log("Select.getSelectedValues() " + e); 
			}
			if (this.multiple) 
				this.selectedValues = values; 
			else 
				this.selectedValues = hasValue(values[0]) ? values[0] : "";
			this.updateSummary();
			if (this.onChange) 
				this.onChange();  
		}	
	}
	
	this.setSelectedValue = function (value) 
	{
		this.setSelectedValues([value]);
	}
}


function Popup (attribs, events) 
{
	// title, body, visible, easyClose,
	var iPopup = this;  
	this.title = "";  
	this.body = ""; 
	this.width = display.isMobile() ? 312 : 400; 
	this.height = 0; 
	this.left = 0; 
	this.top = 0;
	this.easyClose = false;
	this.events = events ? events : {};
	this.domNode = null; 
	this.titleNode = null; 
	this.bodyNode = null;
	this.hideAd = true;  
	this.focusable = true; 
	  
	if (attribs) 
		for (var name in attribs) 
			this[name] = attribs[name]; 
	  
	this.show = function() 
	{ 
		this.displayTop = display.getTop();
		
		if (this.domNode)
		{
			this.domNode.style.display = "inline";
			this.domNode.style.zIndex = ++zIndex; 			 
			this.setTop(this.top); 
		} else 
			this.createDomNode();
	 
	 	if (this.hideAd) 
	 		ad.hide(); 
	}
	
	this.hide = function() 
	{
		if (this.domNode)
		{
			this.domNode.style.display = "none";
			
			ad.show();  				
			if (Math.abs(this.displayTop - display.getTop()) > 100)
				display.setTop(this.displayTop);
			if (this.events.close) 
				this.events.close(); 
		}
	}
		
	this.createDomNode = function() 
	{	
		this.titleNode = dom.create('td');
		this.bodyNode = dom.create('div');
		td3 = dom.create('td'); 
		this.domNode = dom.create("div");  
		tr = dom.create('tr'); 
		//tr2 = dom.create('tr');
		tbody = dom.create('tbody');   
		table = dom.create('table');
		table.style.width = "100%"; 
		
		if (this.title) 
		{
			this.titleNode.className = "max_popup_title";
			this.titleNode.id = this.id + "_title";
			if (isString(this.title))
				this.titleNode.innerHTML = this.title;
			else if (hasValue(this.title)) 
				this.titleNode.appendChild(this.title);
		}     
		this.bodyNode.className = "max_popup_body";
		//this.bodyNode.style.height = "20px"; 
		this.bodyNode.id = this.id + "_body";
		if (isString(this.body))
			this.bodyNode.innerHTML = this.body;
		else if (hasValue(this.body)) 
			this.bodyNode.appendChild(this.body); 
		//this.bodyNode.setAttribute("colspan", 2);
		td3.className = "max_popup_title"; 
		td3.width = 1;
		iPopup.displayTop = display.getTop();
		var close = function() 
			{
				iPopup.hide();  
			}
		td3.appendChild(dom.button(null, "X", close));
		if (this.draggable) 
			new Draggable(this.titleNode, this.domNode);

		if (this.title) 
		{
			tr.appendChild(this.titleNode);
			tr.appendChild(td3);
			//tr2.appendChild(this.bodyNode);		
			tbody.appendChild(tr);
			if (display.isMobile())
				eventX.add(tr, "click", close); 
		} 
		//tbody.appendChild(tr2);
		table.appendChild(tbody); 
		table.cellSpacing = 0;   
				
		this.domNode.appendChild(table);		
		this.domNode.appendChild(this.bodyNode); 
		this.domNode.className = "max_popup";
		this.domNode.style.height = this.height ? this.height : "auto"; 
		//var top = this.top ? display.getTop() + this.top : (display.getTop() + (display.getHeight() - this.height) / 2);
		//if (top < 1) 
		//	top = 1; 
		//this.domNode.style.top = top; 		
				 
		this.domNode.style.left = (this.left ? display.getLeft() + this.left : ((display.getWidth() - this.width)/2) - 1) + "px";
		this.domNode.style.width = this.width + "px"; 
		
		this.domNode.style.display = "inline";
		this.domNode.style.zIndex = ++zIndex;
		if (!display.isMobile() && this.focusable)  
			eventX.add(this.domNode, "click", 
				function () 
				{
					iPopup.domNode.style.zIndex = ++zIndex; 
				}); 
		if (this.hideAd)
			ad.hide(); 
		
		body = document.getElementsByTagName("body")[0];
		body.appendChild(this.domNode);
		
		if (this.transparent)
			dom.setOpacity(this.domNode, 9);
		
		this.setTop(this.top); 
		if (this.easyClose) 
			this.enableEasyClose(); 		 
	}
	
	this.enableEasyClose = function ()
	{		 
		if (this.bodyNode) 
		{
			this.easyClose = true;
			iPopup.displayTop = display.getTop(); 			
			eventX.add(display.isMobile() || !this.title ? this.domNode : this.bodyNode, "click", 
				function() {
					if (iPopup.easyClose) 
						iPopup.hide();
				}); 
		}
	}
	
	this.setTitle = function (title) 
	{
		this.title = title;
		
		if (this.titleNode)
		{ 
			if (isString(title))
				this.titleNode.innerHTML = title;
			else if (hasValue(title))
			{
				this.titleNode.innerHTML = "";
				this.titleNode.appendChild(title);
			} 
		}   
	}
	
	this.setBody = function (body) 
	{
		this.body = body; 
		
		if (this.bodyNode) 
		{
			if (isString(body)) 
				this.bodyNode.innerHTML = body;
			else if (hasValue(body))
			{
				this.bodyNode.innerHTML = ""; 
				this.bodyNode.appendChild(body);
			} 
		}   
	}
	
	this.setTop = function (top) 
	{			 	  
		this.top = top;
		if (this.domNode) 
		{
			top = this.top ? display.getTop() + this.top : (display.getTop() + (display.getHeight() - this.domNode.offsetHeight) / 2);
			 			
			if (top <= display.getTop()) 
				top = display.getTop() + 2; 
			this.domNode.style.top = top + "px"; 			
		}  
	}

	this.setLeft = function(left) 
	{
		this.left = left;  
		
		if (this.domNode) 
			this.domNode.style.left = (this.left ? display.getLeft() + this.left : ((display.getWidth() - this.width)/2) - 1) + "px"; 
	}
	
	this.setWidth = function(width) 
	{
		this.width = width; 
		
		if (this.domNode) 
		{
			this.domNode.style.width = width;
			this.setLeft(this.left);
		} 
	}
	
	this.setHeight = function (height) 
	{
		this.height = height; 
		
		if (this.domNode) 
		{
			this.domNode.style.height = height;
			this.setTop(this.top); 
		} 
	}
}

function Draggable (target, dragElement) 
{
	var iDraggable = this; 
	this.offsetX = 0; 
	this.offsetY = 0;
	  
	if (!dragElement) 
		dragElement = target; 
	
	eventX.add(target, "mousedown", 
		function (e)  
		{
			dragElement.style.zIndex = ++zIndex;
			target.style.cursor = "move"; 
			iDraggable.dragging = true; 
			iDraggable.offsetX = e.clientX - parseInt(dom.getLeft(target)); 
			iDraggable.offsetY = e.clientY - parseInt(dom.getTop(target));
		}); 
	
	eventX.add(document, "mousemove", 
		function (e) 
		{
			if (iDraggable.dragging) 
			{				
				dragElement.style.left = e.clientX - iDraggable.offsetX; 
				dragElement.style.top = e.clientY - iDraggable.offsetY;
			} 
		}); 

	eventX.add(document, "mouseup", 
		function (e)  
		{
			iDraggable.dragging = false;
			target.style.cursor = "default"; 
		}); 
}

function AdContainer (id) 
{
	this.domNode = getEle(id);
	
	this.hide = function () 
	{				 
		if (this.domNode && display.isMobile()) 
			this.domNode.style.display = "none";	
	}
	
	this.show = function () 
	{				 
		if (this.domNode && display.isMobile()) 
			this.domNode.style.display = "";	
	}
}

function TabContainer(id, parent) 
{
	if (defined(id) && id) 
		this.id = id; 
	else 
		this.id = "tab_" + getRandomId();

	this.tabs = {};
	this.currentTabId = null;
	this.container = null; 
	this.domNode = null; 
	this.cookieTabId = cookie.get(this.id + "_currentTabId"); 
	
	if (defined(parent)) 
		this.parent = parent; 
	else 
		this.parent = null;  

		
	this.createContainer = function() 
	{
		this.domNode = this.container = dom.create("div"); 
		this.container.className = "max_tab_container"; 
		this.container.id = this.id; 
		
		this.containerTitle = dom.create("ul");
		this.containerTitle.className = "max_tab_container_title";  
		this.containerBody = dom.create("div"); 
		this.containerBody.className = "max_tab_container_body";
		
		this.container.appendChild(this.containerTitle); 
		this.container.appendChild(this.containerBody);  
		
		if (this.parent && this.parent.appendChild) 
			this.parent.appendChild(this.container); 
	}
	
	this.getDomNode = function () 
	{
		return this.container; 
	}
	
	this.setCurrentTab = function (id) 
	{
		if (this.currentTabId != id) 
		{
			if (this.currentTabId) 
			{
				var tab = getEle(this.id + "_" + this.currentTabId); 
				if (tab) 
					tab.className = "max_tab_title"; 
				var tab_body = getEle(this.id + "_" + this.currentTabId + "_body"); 
				if (tab_body) 
					tab_body.className = "max_tab_body";
			} 		

			tab = getEle(this.id + "_" + id);			  
			if (tab) 
				tab.className = "max_tab_title_active"; 
			tab_body = getEle(this.id + "_" + id + "_body");
			if (tab_body) 
				tab_body.className = "max_tab_body_active";
			
			this.currentTabId = id; 
			cookie.set(this.id + "_currentTabId", id);  		
		}
	}
	
	this.addTab = function (id, title, body, onClick) 
	{
		var container_id = this.id; 
		var tab_id = this.tabs[id] = this.id + "_" + id;
		var container = this; 
					
		var tab = dom.create("li"); 
		tab.className = "max_tab_title"; 
		tab.innerHTML = title;
		tab.id = this.tabs[id];		  
		eventX.add(tab, "click", function() 
			{
				container.setCurrentTab(id); 
				if (onClick) 
					onClick(); 
			}); 
		this.containerTitle.appendChild(tab); 
		
		var spacer = dom.create("li"); 
		spacer.className = "max_tab_spacer"; 
		spacer.innerHTML = "&nbsp;";
		//this.containerTitle.appendChild(spacer); 
		
		var tab_body = dom.create("div"); 
		tab_body.className = "max_tab_body";
		tab_body.id = this.tabs[id] + "_body";
		if (typeof body == "object")
			tab_body.appendChild(body); 
		else 
			tab_body.innerHTML = body; 
		this.containerBody.appendChild(tab_body);  
				
		if (!this.currentTabId && !this.cookieTabId || this.cookieTabId == id) 
			this.setCurrentTab(id); 
	}
	
	this.removeTab = function (id) 
	{
	}
	
	this.showTab = function (id) 
	{
	} 
	
	this.hideTab = function (id) 
	{
		
	} 
	
	this.hide = function() 
	{
		if (this.container) 
			this.container.style.display = "none"; 
	}
	
	this.show = function() 
	{
		if (!this.container) 
			this.createContainer();
		
		this.container.style.display = ""; 
	}
	
	this.show();  
}
 

function User() 
{
	var iUser = this; 
	this.loggedIn = false; 
	this.loginBox = null; 
	this.logoutBox = null;
	this.userNameId = "userName"; 
	this.passwordId = "password"; 
	this.errorId = "loginError"; 
	this.emailId = "userEmail"; 
	this.loginStatusId = "loginStatus"; 
	this.onLogin = null; 
	this.onLogout = null;
	this.onRegister = null;  
	this.sessionId = null; 
	this.userId = cookie.get("USER_ID");
	this.profileBox = null; 
	this.confirmBox = null;
	this.interactMenu = null; 
	this.passwordBox = null; 
	
	this.init = function () 
	{
		if (!this.userId) 
		{
			ajax.send("?obj=user&method=getId", 
				function (req) 
				{
					if (req.responseText)
					{
						var response = json.decode(req.responseText);
						if (response.userId) 
							iUser.userId = response.userId; 
						else 
							debug.log("User.init() Failed to get User ID"); 					
					}
				}); 
		}
	}

	this.login = function (register, submit) 
	{
		if (!defined(register)) 
			register = false; 
					
		if (defined(submit) && submit) 
		{
			this.clearError(); 
			var name = getEle(this.userNameId); 
			var pass = getEle(this.passwordId);
			var email = getEle(this.emailId); 
			
			if (defined(name) && defined(pass) && name.value.length && pass.value.length) 
			{
				var params = "userName=" + encodeURIComponent(name.value) + "&password=" + encodeURIComponent(pass.value) + "&email=" + encodeURIComponent(email ? email.value : "");
				var action = register ? "register" : "login"; 
				ajax.send("?obj=user&method=" + action, function (req) 
					{
						var response = json.decode(req.responseText); 
						if (response) 
						{
							if (response.error) 
								user.setError(response.error); 
							else if (response.sessionId)
							{
								if (display.isMobile()) 
									keyboard.hide();      
								user.loginBox.hide();  
								user.userName = name.value.toHtmlEntities();
								user.sessionId = response.sessionId;
								user.userId = response.userId;								  
								user.updateLoginStatus(); 
								if (!register && user.onLogin)
									user.onLogin();  
								if (register && user.onRegister)
									user.onRegister();  
								if (response.passwordExpired == 1) 
									user.changePassword(true); 
							} else 
								user.setError("Server is unavailable. Please try again later.");
						} else 
							user.setError("Server is unavailable. Please try again later."); 
					}, {method:"post", params:params});
			} else 
				this.setError("Name and password are required.");  
		} else 
		{
			if (!this.loginBox)
				this.loginBox = new Popup();
			var name = html.input(this.userNameId, "", {autocorrect:"off"});
			var pass = html.input(this.passwordId, "", {autocapitalize:"off", autocorrect:"off"});
			var email = html.input(this.emailId, "", {autocapitalize:"off", autocorrect:"off"});			 
			var regButt = html.button(null, register ? "Register" : "Login", "user.login(" + register + ", true)"); 
			var cancelButt = html.button(null, "Cancel", "user.loginBox.hide()");
			var forgotButton = html.button(null, "Forgot Password", "user.loginBox.hide(); user.forgotPassword()"); 
			var body = "<table><tr><td class='max_field_title' style='width: 7em'>User Name:</td><td>" + name + "</td></tr>" + 
				"<tr><td class='max_field_title'>Password:</td><td>" + pass + "</td></tr>" + 
				(register ? "<tr><td class='max_field_title'>Email*:</td><td>" + email + "</td></tr>" : "") +
				"<tr><td>&nbsp;</td><td class='max_error_msg' id='" + this.errorId + "'></td></tr></table>" + 
				"<table><tr><td>" + regButt + "&nbsp;" + cancelButt + "</td><td align='right'>" + (register ? "" : forgotButton) + "</td></tr></table>" +  
				(register ? "<br><div class='gray'>* Optional (lost password retrieval)</div>" : "");  
			this.loginBox.setTitle(register ? "Register" : "Login");
			this.loginBox.setBody(body); 
			if (display.isMobile() && register) 
				setTimeout(function () {iUser.loginBox.setTop(3)}, 400); 
			this.loginBox.show();
			 
			name = getEle(this.userNameId); 
			pass = getEle(this.passwordId);  
			email = getEle(this.emailId);
			name.focus();
			var doLogin = function (evt) 
				{
					if (checkEnter(evt)) 
					{
						pass.blur();	
						user.login(register, true);					 
					}
				}
			eventX.add(name, "keydown", doLogin);    	
			eventX.add(pass, "keydown", doLogin);
			if (email) 
				eventX.add(email, "keydown", doLogin);
		}
	}
	
	this.forgotPassword = function (submit)  
	{
		if (submit) 
		{
			var email = getEle("retrievalEmail").value; 
			if (email)	
			{
				ajax.send("?obj=user&method=sendPassword&email=" + encodeURIComponent(email));
				this.passwordBox.setBody("<div class='center'>If the email is registered, your password will be sent there shortly.<br><br>" + html.button("", "Ok", "user.passwordBox.hide()") + "</div>");
			}			
		} else 
		{
			if (!this.passwordBox)
				this.passwordBox = new Popup(); 
			
			this.passwordBox.setTitle("Forgot Password"); 
			this.passwordBox.setBody("<div style='padding: 0.3em'>If you had provided your email during registration, " + 
				"you can enter the email below to retrieve the password.<br><br>" + 
				"<table><tr><td class='max_field_title' style='width: 4em'>Email:</td><td>" + html.input("retrievalEmail", "", {autocapitalize:"off", autocorrect:"off"}) + "</td></tr></table>" +  
				"<br>" + html.button(_, "Retrieve Password", "user.forgotPassword(true)") + " " + 
				html.button(_, "Cancel", "user.passwordBox.hide()") + "</div>");
				
			this.passwordBox.show();
			if (display.isMobile()) 
				setTimeout(function () { iUser.passwordBox.setTop(1) }, 400); 
			var email = getEle("retrievalEmail"); 
			email.focus();
			eventX.add(email, "keydown", 
				function (e) 
				{
					checkEnter(e, function () {iUser.forgotPassword(true)}); 
				}); 
			
		}  
	}
	
	this.changePassword = function (expired, submit) 
	{
		if (submit) 
		{
			var password = getEle("newPassword"); 
			if (password && hasValue(password.value) && password.value.length)
			{
				user.passwordBox.hide();
				ajax.send("?obj=user&method=changePassword", 
					function (req) 
					{
						if (req.responseText) 
						{
							var response = json.decode(req.responseText); 
							if (response.sessionId) 
								iUser.sessionId = response.sessionId; 
						}
					}, {params:{password:encodeURIComponent(password.value)}}); 				 
			}
		} else 
		{
			if (!this.passwordBox) 
				this.passwordBox = new Popup(); 
			if (this.passwordBox.title && expired || !expired) 
				this.passwordBox.setTitle(expired ? "Password Expired" : "Change Password"); 
			this.passwordBox.setBody("<div style='padding: 0.3em'>" + (expired ? "Your password has expired. " : "") + "Please enter a new password.<br><br>" + 
				"<table><tr><td class='max_field_title' style='width: 7em'>New Password:</td><td>" + 
				html.input("newPassword", "", {autocapitalize:"off", autocorrect:"off"}) + "</td></tr></table><br>" + 
				html.button(_, "Save", "user.changePassword(" + (expired ? 1 : 0) + ", true)") + 
				(expired ? "" : " " + html.button(_, "Cancel", "user.passwordBox.hide()"))) + "</div>";
				
			this.passwordBox.show();			 
			var password = getEle("newPassword"); 
			password.focus(); 
			eventX.add(password, "keydown", 
				function (e)
				{
					checkEnter(e, function () {iUser.changePassword(expired, true)}); 
				}); 
		}						 
	}	
	
	this.register = function () 
	{
		this.login(true); 
	}
	
	this.logout = function (confirmed) 
	{	
		if (defined(confirmed) && confirmed) 
		{
			cookie.remove("USER_ID"); 
			cookie.remove("SESSION_ID");
			this.sessionId = null;
			this.userId = null;  
			this.userName = null; 
			this.logoutBox.hide();
			this.updateLoginStatus();
			ajax.send("?obj=user&method=logout", 
				function (req) 
				{
					if (req.responseText) 
					{
						var response = json.decode(req.responseText); 
						if (response.userId) 
							user.userId = response.userId; 
					}
				}); 
			if (this.onLogout)
				this.onLogout(); 
		} else if (this.logoutBox) 
			this.logoutBox.show(); 
		else
		{						 
			var yesButt = html.button(null, "Yes, log me out.", "user.logout(true);"); 
			var noButton = html.button(null, "Cancel", "user.logoutBox.hide()");			 
			this.logoutBox = new Popup({title:"Logout", body:"Are you sure? You will be logged out.<br><br>" + yesButt + "&nbsp;" + noButton});
			this.logoutBox.show();   
		} 		
	}
	
	this.updateLoginStatus = function () 
	{
		var sessionId = this.sessionId || cookie.get("SESSION_ID");
		var loginStatus = getEle(this.loginStatusId);  
		if (loginStatus) 
		{			
			if (sessionId) 
			{
				if (!this.userName) 
				{
					ajax.send("?obj=user&method=getName",
						function (req) 
						{
							var obj = json.decode(req.responseText); 
							if (obj && obj.name)
								user.userName = obj.name.toHtmlEntities();
							user.updateLoginStatus();  
						}, {aync: false});  
				}
				var name = this.userName ? this.userName : "?";  
				loginStatus.innerHTML = "You are logged in as <b>" + name + "</b>. <a href='#' onclick='user.logout(); return false' style='color: blue'>Logout</a>"; 
			} else
			{
				loginStatus.innerHTML = "<a href='#' onclick='user.login(); return false' $style>Login</a> or <a href='#' onclick='user.register(); return false' $style>register</a> to enhance your experience while&nbsp;using this web application."; 
			} 
		}			
	}
	
	this.setError = function (err) 
	{
		if (errCell = getEle(this.errorId)) 
			errCell.innerHTML = err; 
	}
	
	this.clearError = function () 
	{
		this.setError(""); 
	}
	
	this.viewProfile = function (id) 
	{
		if (hasValue(id)) 
			ajax.send("?obj=user&method=getProfile&userId=" + parseInt(id), 
				function (req) 
				{
					if (!iUser.profileBox)
					{
						var width = display.isMobile() ? 310 : 600;
						iUser.profileBox = new Popup({easyClose: true, draggable:true, width: width});
					}
					var response = json.decode(req.responseText);  
					
					iUser.profileBox.easyClose = true; 
					iUser.profileBox.setTitle(dom.text("Profile: " + (response.name ? response.name : "guest")));
					iUser.profileBox.setBody(response.profile); 
					iUser.profileBox.show();  
				})  
	}
	
	this.editProfile = function (save) 
	{
		var rawProfile = getEle("rawProfileData");
		var formattedProfile = getEle("formattedProfileData");
		var buttons = getEle("profileButtons");   
		var profileTip = getEle("profileTip"); 
		
		if (rawProfile && formattedProfile && buttons && profileTip) 
		{
			if (save) 
			{
				var profileData = rawProfile.value; 
				ajax.send("?obj=user&method=saveProfile", 
					function (req) 
					{
						iUser.viewProfile(user.userId);
					}, {params:{profileData:profileData}}); 				  
			} else 
			{
				iUser.profileBox.easyClose = false; 
				formattedProfile.style.display = "none"; 
				buttons.innerHTML = html.button(_, "Save", "user.editProfile(true)") + " " + html.button(_, "Cancel", "user.profileBox.hide()"); 
				rawProfile.style.display = "";
				profileTip.style.display = ""; 
				if (!display.isMobile())
					rawProfile.focus(); 
			}  
		}
	}
	
	this.showInteractMenu = function (id, nameNode) 
	{
		return; 
		if (!id) return; 		
		
		if (id == user.userId) 
			user.viewProfile(id); 
		else 
			ajax.send("?obj=user&method=getInteractions&targetId=" + id, 
				function (req) 
				{
					if (req.responseText) 
					{
						if (!iUser.interactMenu) 
							iUser.interactMenu = new Popup({width:250});
						if (nameNode) 
							iUser.interactMenu.setTitle(nameNode.innerHTML);
						var menu = dom.create("span", _, _, _, req.responseText); 
						eventX.add(menu, "click", function () {iUser.interactMenu.hide()});  
						iUser.interactMenu.setBody(dom.create("div", _, "center", _, menu)); 
						iUser.interactMenu.show();  
					} else 
						iUser.viewProfile(id); 
				}); 
		
	}
	
	this.mute = function (id) 
	{
		if (!id) return; 
		
		ajax.send("?obj=user&method=mute&targetId=" + id); 
	}
	
	this.ban = function (id) 
	{
		if (!id) return; 
		
		ajax.send("?obj=user&method=ban&targetId=" + id); 
	}
		
	this.init(); 
}

function Json () 
{
	this.encode = function (obj) 
	{
		return this.stringify(obj); 
	}
	
	this.decode = function (str) 
	{
		var obj = {}; 
		
		try { 
			obj = eval("(" + str + ")");
		} catch (e)  
		{
			debug.log("Json.decode() " + e);
			debug.log(str);  	
		}
		
		return obj; 
	}
	
    function f(n) {
        return n < 10 ? '0' + n : n;
    }
	
    Date.prototype.toJSON = function (key) {

        return this.getUTCFullYear()   + '-' +
                f(this.getUTCMonth() + 1) + '-' +
                f(this.getUTCDate())      + 'T' +
                f(this.getUTCHours())     + ':' +
                f(this.getUTCMinutes())   + ':' +
                f(this.getUTCSeconds())   + 'Z';
    };

	var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
    	escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
    	gap,
    	indent,
    	meta = {    // table of character substitutions
	        '\b': '\\b',
	        '\t': '\\t',
	        '\n': '\\n',
	        '\f': '\\f',
	        '\r': '\\r',
	        '"' : '\\"',
	        '\\': '\\\\'
	    },
    	rep;

	function quote(string) {
	    escapeable.lastIndex = 0;
	    return escapeable.test(string) ?
	        '"' + string.replace(escapeable, function (a) {
	            var c = meta[a];
	            if (typeof c === 'string') {
	                return c;
	            }
	            return '\\u' + ('0000' +
	                    (+(a.charCodeAt(0))).toString(16)).slice(-4);
	        }) + '"' :
	        '"' + string + '"';
	}


	function str(key, holder) {
	    var i,          // The loop counter.
	        k,          // The member key.
	        v,          // The member value.
	        length,
	        mind = gap,
	        partial,
	        value = holder[key];
	
	    if (value && typeof value === 'object' &&
	            typeof value.toJSON === 'function') {
	        value = value.toJSON(key);
	    }
	    if (typeof rep === 'function') {
	        value = rep.call(holder, key, value);
	    }

		switch (typeof value) {
	    case 'string':
	        return quote(value);
	
	    case 'number':
	
	        return isFinite(value) ? String(value) : 'null';
	
	    case 'boolean':
	    case 'null':
	
	        return String(value);
	
	    case 'object':
	
	        if (!value) {
	            return 'null';
	        }
	
	        gap += indent;
	        partial = [];

                if (typeof value.length === 'number' &&
                        !(value.propertyIsEnumerable('length'))) {
                    length = value.length;
                    for (i = 0; i < length; i += 1) {
                        partial[i] = str(i, value) || 'null';
                    }
                    v = partial.length === 0 ? '[]' :
                        gap ? '[\n' + gap +
                                partial.join(',\n' + gap) + '\n' +
                                    mind + ']' :
                              '[' + partial.join(',') + ']';
                    gap = mind;
                    return v;
                }
                if (rep && typeof rep === 'object') {
                    length = rep.length;
                    for (i = 0; i < length; i += 1) {
                        k = rep[i];
                        if (typeof k === 'string') {
                            v = str(k, value, rep);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                } else {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = str(k, value, rep);
                            if (v) {
                                partial.push(quote(k) + (gap ? ': ' : ':') + v);
                            }
                        }
                    }
                }
                v = partial.length === 0 ? '{}' :
                    gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                            mind + '}' : '{' + partial.join(',') + '}';
                gap = mind;
                return v;
		}
	}

	this.stringify = function (value, replacer, space) {
		var i;
		gap = '';
		indent = '';
		
		if (typeof space === 'number') {
		    for (i = 0; i < space; i += 1) {
		        indent += ' ';
		    }
		
		
		} else if (typeof space === 'string') {
		    indent = space;
		}
		
		
		rep = replacer;
		if (replacer && typeof replacer !== 'function' &&
		        (typeof replacer !== 'object' ||
		         typeof replacer.length !== 'number')) {
		    throw new Error('JSON.stringify');
		}
		
		return str('', {'': value});
	}
		
		
	this.parse = function (text, reviver) 
	{
	    var j;
		
	    function walk(holder, key) 
	    {
	        var k, v, value = holder[key];
	        if (value && typeof value === 'object') {
	            for (k in value) {
	                if (Object.hasOwnProperty.call(value, k)) {
	                    v = walk(value, k);
	                    if (v !== undefined) {
	                        value[k] = v;
	                    } else {
	                        delete value[k];
	                    }
	                }
	            }
	        }
	        return reviver.call(holder, key, value);
	    }
		
	    cx.lastIndex = 0;
	    if (cx.test(text)) {
	        text = text.replace(cx, function (a) {
	            return '\\u' + ('0000' +
	                    (+(a.charCodeAt(0))).toString(16)).slice(-4);
	        });
	    }
	
	    if (/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) 
	    {
			j = eval('(' + text + ')');
			return typeof reviver === 'function' ?
			walk({'': j}, '') : j;
		}
		throw new SyntaxError('JSON.parse');
	}
}

function AjaxMAX (interval) 
{
	var iAjaxMAX = this; 
	this.ajax = new Ajax(); 
	this.interval = interval ? interval * 1000 : 4000;
	this.requests = {};
	this.requestCount = 0; 
	this.delayCounter = 0; 
	
	this.init = function () 
	{
		setInterval(function () {iAjaxMAX.sendRequests();}, iAjaxMAX.interval);			  		
	} 
	
	this.addRequest = function (request) 
	{
		var id = "request_" + getRandomId(); 
		this.requests[id] = request;
		this.requestCount++; 
		
		return id;    
	}
	
	this.removeRequest = function (requestId) 
	{
		if (this.requests[requestId])
		{ 
			delete this.requests[requestId];
			this.requestCount--; 
		}
	}
	
	this.sendRequests = function (sendAll) 
	{
		//debug.clear(); 
		if (this.requestCount) 
		{			
			var qualifiedRequests = {};
			var count = 0;  
			for (var id in this.requests) 
				if (sendAll && !this.requests[id].mustDelay || !this.requests[id].delay || this.delayCounter % this.requests[id].delay == 0)
				{ 
					qualifiedRequests[id] = this.requests[id];
					count++; 
				} 
			if (count) 
			{
				//debug.log("All requests + " + this.delayCounter); 
				//debug.dump(this.requests); 
				//debug.log("Sent:");
				//debug.dump(qualifiedRequests); 
				var qualifiedRequests_str = json.encode(qualifiedRequests);
				this.ajax.send("?obj=ajaxMAX", 
					function (req) 
					{
						var response = json.decode(req.responseText);
						if (response) 
						{
							for (var requestId in response)
								if (iAjaxMAX.requests[requestId] && iAjaxMAX.requests[requestId].callback) 
									iAjaxMAX.requests[requestId].callback(response[requestId]);  
						}
						//if (!sendAll)
						//	setTimeout(function () {iAjaxMAX.sendRequests();}, iAjaxMAX.interval);
					}, {params:{"requests":qualifiedRequests_str}}); 
			} //else if (!sendAll)
			//	setTimeout(function () {iAjaxMAX.sendRequests();}, iAjaxMAX.interval);
			this.delayCounter++;
		}// else if (!sendAll)
			//setTimeout(function () {iAjaxMAX.sendRequests();}, iAjaxMAX.interval); 
	}
		
	this.init(); 
}

function Ajax ()
{
	var iAjax = this; 
	this.requestPool = []; 
	
	this.getHTTPRequest = function () {
		var http_request = false; 
		try {
			http_request = new XMLHttpRequest();
			http_request.overrideMimeType('application/text'); 
		}
		catch (e) {
			try {
				http_request = new ActiveXObject("Msxml2.XMLHTTP");
			}
			catch (e) {
				try {
					http_request = new ActiveXObject("Microsoft.XMLHTTP");
				}
				catch (e) {
					http_request = false;
				}
			}
		}
	
		return http_request;
	} 
	
	this.request = this.getHTTPRequest();
 		
	if (this.request === false)
		debug.alert("This web application requires a modern browser to work. Please upgrade to the latest release of Safari, Firefox, or Internet Explorer. Hope to see you soon! :)"); 

	this.send = function (url, callback_function, options)
	{
		if (this.request)
		{				 
			if (this.request.readyState == 0 || this.request.readyState == 4) 
			{ 				
				if (!defined(options)) 	
					options = {}; 
				if (options.add_random_number)
				{
					if (url.indexOf('?') != -1)
						url += '&x=' + Math.random();
					else 
						url += '?x=' + Math.random();
				}

				if (user) 
				{
					if (user.badBrowser) 
					{								
						if (url.indexOf('?') != -1)
							url += '&sessionId=' + user.sessionId;
						else 
							url += '?sessionId=' + user.sessionId;
						url += '&USER_ID=' + user.userId; 					
					} else if (!cookie.get("USER_ID"))
					{	
						if (options.params) 
							options.params["userId"] = user.userId;
						else 
							options.params = {userId:user.userId};
						if (user.sessionId) 
							options.params["sessionId"] = user.sessionId;
					}
				}
				if (!defined(options.async))	
					options.async = true;
									
				if (!options.method)
					options.method = "post";
				if (!options.params) 
					options.params = null; 
					  
				this.request.open(options.method, url, options.async);
				if (options.method == "post") 
				{
					this.request.setRequestHeader("content-type", "application/x-www-form-urlencoded");
					this.request.setRequestHeader("content-length", options.params ? options.params.length : 0);
					this.request.setRequestHeader("connection", "close");				
				}
				if (defined(callback_function) && callback_function) 
				{
					this.request.onreadystatechange = function() 
					{						 
						if (iAjax.request && iAjax.request.readyState == 4) 
						{
							if (iAjax.request.status == 200) 
								callback_function(iAjax.request);
							else 
								debug.log("Ajax request failed (URL: " + url + " | Status: " + ajax.request.status + "). <br>Result: " + ajax.request.responseText); 
							iAjax.checkPooledRequest(); // checks pool for more requests.							 
						}
					};
				} else 
					this.request.onreadystatechange = function () {}; 
				
				var params_str = null; 
				if (options.params) 
				{
					switch (typeof options.params)
					{
						case "object":
							var params_arr = []; 
							for (var key in options.params)
								params_arr.push(key + "=" + encodeURIComponent(options.params[key])); 
							params_str = params_arr.join("&"); 
							break;  
						case "string":
							params_str = options.params;  
							break; 
						default: 
							debug.log("Ajax.send() options.params must be an object or a string"); 
					}
				}
				if (debug.showRequest) 
					debug.log("Request: " + url + " (" + params_str + ")"); 
				this.request.send(params_str);
			} else 
				this.poolRequest({'url': url, 'callback': callback_function, 'options': options}); 		
		}
	}		
	
	 
	this.poolRequest = function(req) 
	{			
		if (defined(req) && req) 
			this.requestPool.push(req);
		else if (this.requestPool.length)
		{
			req = this.requestPool.pop();
			this.send(req.url, req.callback, req.options);  
		}
	}
	
	this.checkPooledRequest = function() 
	{
		this.poolRequest(); 
	}
}

function Fader (attribs, events) 
{
	var iFader = this; 
	this.attribs = hasValue(attribs) ? attribs : {}; 
	this.element = null;				// value set at end.
	this.speed = 1;						// value set at end   	 
	this.started = false; 
	this.opacity = 10; 
	this.events = events; 
 
	this.start = function (attribs, events) 
	{
		this.events = events; 
		if (attribs.element === this.element) 
			return; // avoid repeated calls with the same element. causes browser to use lots of CPU. 
			 
		if (this.started) 
			this.stop(); 
		if (attribs) 
		{
			this.setElement(attribs.element); 
			this.setSpeed(attribs.speed);
		} 
		
		if (this.element) 
		{
			if (this.element.style) 
			{	
				this.opacity = 10;
				dom.setOpacity(this.element, this.opacity);			
				this.fade(); 
				this.started = true;
			} else
				debug.log("BorderHighlighter.start() Element (" + this.element + ") doesn't have a style attribute.");  
		}
	}
	
	this.stop = function () 
	{			
		if (this.element) 
			this.element = null;
		this.started = false;		
		if (this.events && this.events.faded) 
			this.events.faded();  
	}
	
	this.setElement = function (element) 
	{
		element = (typeof element == "string") ? getEle(element) : element; 
		if (this.started)
			this.start(element); 
		else 
			this.element = hasValue(element) ? element : null; 
	}
	
	this.fade = function () 
	{
		if (this.element) 
		{
			this.opacity -= this.speed; 
			if (this.opacity >= 0)
			{
				dom.setOpacity(this.element, this.opacity); 
				setTimeout(function() { iFader.fade() }, 200);
			} else 
				this.stop(); 
		} 
	}
	
	this.setSpeed = function (speed) 
	{
		this.speed = hasValue(speed) ? speed : (this.speed ? this.speed : 1);
		if (this.speed < 1) 
			this.speed = 1; 
	}
	
	this.setElement(this.attribs.element);
	this.setSpeed(this.attribs.speed);   	
	
	if (this.element)
		this.start()  		
}
 
function BorderHighlighter(attribs) 
{
	var highlightInstance = this;
	this.attribs = hasValue(attribs) ? attribs : {type: "rainbow"}; 
	this.colors = []; 
	this.type = null;					// value set at end. 		Possible Values: red, green, blue, rainbow
	this.element = null;				// value set at end.
	this.speed = null;					// value set at end   	 
	this.elementOriginalBorder = null; 	// value st at end.  
	this.started = false; 
	this.colorIndex = 0;
	this.rainbowColors = new Array('#E56717', '#F87217', '#F88017', '#E8A317', '#FBB117', '#FBB917', '#FDD017', '#EDDA74', '#CCFB5D', '#B1FB17', '#59E817', '#57E964', '#41A317', '#41A317', '#57E964', '#59E817', '#B1FB17', '#CCFB5D', '#EDDA74', '#FDD017', '#FBB917', '#FBB117', '#E8A317', '#F88017', '#F87217');
 
	this.start = function (attribs) 
	{
		if (attribs.element === this.element) 
			return; // avoid repeated calls with the same element. causes browser to use lots of CPU. 
			 
		if (this.started) 
			this.stop(); 
		if (attribs) 
		{
			this.setElement(attribs.element); 
			this.setOriginalBorder(attribs.border); 
			this.setType(attribs.type); 
			this.setSpeed(attribs.speed);
			  
			//this.element = hasValue(element) ? element : this.element;
			//this.elementOriginalBorder = hasValue(originalBorder) ? originalBorder : this.elementOriginalBorder;
		} 
		
		if (this.element) 
		{
			if (this.element.style) 
			{				
				if (!this.elementOriginalBorder && this.element.style.border) 
					this.elementOriginalBorder = this.element.style.border; 
				this.changeBorder(); 
				this.started = true;
			} else
				debug.log("BorderHighlighter.start() Element (" + this.element + ") doesn't have a style attribute.");  
		}
	}
	
	this.stop = function () 
	{
		if (this.element) 
		{
			if (this.elementOriginalBorder) 
				this.element.style.border = this.elementOriginalBorder;
			else 
				this.element.style.border = "1px solid #ffffff";  
			this.element = null;
			this.elementOriginalBorder = ""; 
		}		
		this.started = false;  
	}
	
	this.setElement = function (element) 
	{
		element = (typeof element == "string") ? getEle(element) : element; 
		if (this.started)
			this.start(element); 
		else 
			this.element = hasValue(element) ? element : null; 
	}
	
	this.changeBorder = function () 
	{
		if (this.element) 
		{
			this.colorIndex += this.speed; 
			if (this.colorIndex >= this.colors.length - 1)
				this.colorIndex = 0; 
			this.element.style.border = "1px solid " + this.colors[this.colorIndex];
			setTimeout(function() { highlightInstance.changeBorder() }, 300);
		} 
	}
	
	this.setType = function (type)
	{
		this.type = hasValue(type) ? type : (this.type ? this.type : "rainbow");
		this.colors = [];  
		switch (this.type) 
		{		 
			case "red":
				for (var i = 0; i < 255; i+=20) 
					this.colors.push("rgb(255, " + i + ", " + i + ")"); 
				for (var i = 255; i > 0; i-=20) 
					this.colors.push("rgb(255, " + i + ", " + i + ")"); 							
				break; 
			case "green": 
				for (var i = 0; i < 255; i+=20) 
					this.colors.push("rgb(" + i + ", 255, " + i + ")"); 
				for (var i = 255; i > 0; i-=20) 
					this.colors.push("rgb(" + i + ", 255, " + i + ")"); 							
				break; 
			case "blue": 
				for (var i = 0; i < 255; i+=20) 
					this.colors.push("rgb(" + i + ", " + i + ", 255)"); 
				for (var i = 255; i > 0; i-=20) 
					this.colors.push("rgb(" + i + ", " + i + ", 255)"); 							
				break; 
			case "rainbow": 
			default: 
				this.colors = this.rainbowColors;  	
		} 
	}
	
	this.setSpeed = function (speed) 
	{
		this.speed = hasValue(speed) ? speed : (this.speed ? this.speed : 1);
		if (this.speed < 1) 
			this.speed = 1; 
	}
	
	this.setOriginalBorder = function (border) 
	{
		this.elementOriginalBorder = hasValue(border) ? border : (this.elementOriginalBorder ? this.elementOriginalBorder : ""); 
	}
	
	this.setType(this.attribs.type);  
	this.setElement(this.attribs.element);
	this.setSpeed(this.attribs.speed);   	
	this.setOriginalBorder(this.attribs.border); 	
	
	if (this.element && this.type)
		this.start()  		
}

function Cookie() 
{
	this.path = "/"; // default values for set / remove. 
	this.domain = ".almightymax.com"; 	
	
	this.set = function (name, value, expires, path, domain, secure)
	{
		if (!defined(path)) 
			path = this.path; 
		if (!defined(domain)) 
			domain = this.domain;
		 
		var today = new Date();
		today.setTime(today.getTime());
	
		if (expires)
			expires = expires * 1000 * 60 * 60 * 24;
		var expires_date = new Date(today.getTime() + expires);
		
		document.cookie = name + "=" +encodeURIComponent(value) +
		((expires) ? ";expires=" + expires_date.toGMTString() : "" ) + 
		((path) ? ";path=" + path : "" ) + 
		((domain) ? ";domain=" + domain : "" ) +
		((secure) ? ";secure" : "" );
	}
	
	this.get = function (name) 
	{
		if (document.cookie) 
		{
			cookies = document.cookie.split("; "); 
			for (var i = 0; i < cookies.length; i++) 
			{
				var pair = cookies[i].split("=");
				if (pair[0] === name) 
					return unescape(pair[1]);   
			} 
		} 
	}
	
	this.remove = function (name, path, domain) 
	{
		if (!defined(path)) 
			path = this.path; 
		if (!defined(domain)) 
			domain = this.domain; 
		var expires_date = new Date(1); 
		document.cookie = name + "=" +
		";expires=" + expires_date.toGMTString() + 
		((path) ? ";path=" + path : "" ) + 
		((domain) ? ";domain=" + domain : "" );
	} 
}

function Misc () 
{
	this.enhanceLinks = function (str) 
	{
		//a-zA-Z\/0-9\.\-\_?=&\+%#
		///([a-zA-Z\/0-9\.\-\_?=&\+%#]+@[a-zA-Z\/0-9\.\-\_?=&\+%#]+\.(com|net|us|org|gov|edu|info|mil|mobi)[a-zA-Z\/0-9\.\-\_?=&\+%#]+)/gi
		
		var reg = /([a-zA-Z\/0-9\.\-\_?=&\+%#]+@[a-zA-Z\/0-9\.\-\_?=&\+%#]+\.(com|net|us|org|gov|edu|info|mil|mobi)[a-zA-Z\/0-9\.\-\_?=&\+%#]*)/gi;
		//var emailCount = 0;
		if (reg.test(str))   
			str = str.replace(reg, "<a href='mailto:$1'>$1</a>");
		else 
		{
			reg = /(http?:\/\/)?([a-zA-Z\/0-9\.\-\_?=&\+%#]+\.(com|net|us|org|gov|edu|info|mil|mobi)[a-zA-Z\/0-9\.\-\_?=&\+%#]*)/gi
			str = str.replace(reg, "<a href='http://$2' target='external_link'>$1$2</a>");
		}
				
		return str;  
	}
}

function extendLanguage() 
{
	// Global functions
	window.getEle = function (id) 
	{
		return document.getElementById(id); 
	}		
	
	window.checkEnter = function (e, callback, obj) 
	{
		e = e || window.event; 			
		keyCode = e.keyCode || e.which;
		 		
		if (keyCode==13 || keyCode==10)
		{ 
			if (hasValue(callback)) 
				callback(obj);
			return true; 				
		} 
	}
	
	window.defined = function (obj) 
	{
		return typeof obj !== 'undefined'; 
	}
	
	window.isNull = function (obj) 
	{
		if (!defined(obj) || defined(obj) && obj === null) 
			return true; 
		else 
			return false; 
	}
	
	window.isArray = function (obj) 
	{
		return hasValue(obj) && obj.constructor == Array; 
	}
	
	window.isString = function (obj) 
	{
		return typeof obj == "string";  
	}	
	
	window.hasValue = function (obj) 
	{
		return !isNull(obj); 
	}
	
	window.getRandomId = function () 
	{
		return Math.floor(Math.random() * 1000000000 + 1); 
	}
	
	window.range = function (start, end, step) 
	{	
		var range = new Array();
		step = hasValue(step) ? step : 1; 
		
		for (var i = start; i <= end; i+=step)
			range.push(i);   
			
		if (i < end) 
			range.push(end); 
			
		return range; 
	}
	
	// Prototypes
	String.prototype.trim = function () {
		return this.replace(/^\s+|\s+$/g,'');
	}
	
	String.prototype.toHtmlEntities = function () {
		return this.replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/'/g, "&#39;").replace(/"/g, "&#34;"); 
	}
	
	String.prototype.fromHtmlEntities = function () {
		return this.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&#39;/g, "'").replace(/&#34;/g, "\""); 
	}
	
	String.prototype.toMonth = function () 
	{
		var date = new Date(); 
		var month = this.toString(); 
		
		for (var i = 0; i < date.monthNames.length; i++) 
			if (date.monthNames[i] == month) 
				return i + 1;
		
		return null;  
	}
	
	Number.prototype.addCommas = String.prototype.addCommas = function () {
		var str = this.toString().replace(/\.[0-9]*(.*)$/, ""); 
		
		var reg = /(\d+)(\d{3})/g;  
		while (reg.test(str)) 
			str = str.replace(reg, "$1,$2");
		
		return str;  
	}
	
	
	Date.prototype.monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; 
	Date.prototype.getMonthName = function (month) 
	{
		if (typeof month != 'undefined' && month !== null && month >= 1 && month <= 12) 
			return this.monthNames[month - 1]; 
		else 
			return this.monthNames[this.getMonth()]; 
	}
}
