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.0';
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 fader = new Fader({unfade: true});

var magicBall = new MagicBall(); 
 
  
if (display.isMobile()) 
	setTimeout("display.setTop()", 2000);
		      
// Classes
function MagicBall () 
{ 
	var iBall = this;
	var magicNode = null; 
	var fortunes = ["As I see it, yes", "Ask again later", "Better not tell you now", "Cannot predict now", "Concentrate and<br> ask again", "Don't count on it", 
		"It is certain", "It is decidedly so", "Most likely", "My reply is no", "My sources say no", "Outlook good", "Outlook not so good", 
		"Reply hazy, try again", "Signs point to yes", "Very doubtful", "Without a doubt", "Yes", "Yes - definitely", "You may rely on it"];

	this.init = function ()  
	{
		var domNode = app.getDomNode();
		domNode.className = "center"; 
		this.magicNode = dom.create("td", _, _, _, "Magic<br>Ball");
		this.magicNode.style.textAlign = "center"; 
		this.magicNode.style.fontFamily = "Georgia"; 
		this.magicNode.style.color = "white";
		this.magicNode.style.fontSize = "3em"; 
		fader.start({element:iBall.magicNode}); 
		  
		
		var magicTable = dom.table(_, _, {align: "center"}, dom.create("tr", _, _, _, this.magicNode));
		magicTable.style.width = magicTable.style.height = 318;   
		magicTable.style.backgroundImage = "url(images/MagicBall.png)";
		magicTable.style.backgroundRepeat = "no-repeat"; 
		domNode.appendChild(magicTable);
		eventX.add(magicTable, "click", function () {iBall.shake()}); 
		
		var shakeButton = dom.button(_, "SHAKE", function () {iBall.shake()});
		domNode.appendChild(shakeButton);  
		
		domNode.appendChild(dom.create("br"));
		domNode.appendChild(dom.create("br"));
	}
	
	this.shake = function () 
	{
		this.magicNode.style.fontSize = "1em"; 
		var magicNumber = Math.floor(Math.random() * fortunes.length); 
		
		dom.setOpacity(this.magicNode, 0); 
		this.magicNode.innerHTML = fortunes[magicNumber]; 
		fader.start({element: iBall.magicNode}); 
	}
	
	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) 
	{
		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;
		this.opacity = 10; 
		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, 10 - 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, 10 - this.opacity); 
				setTimeout(function() { iFader.fade() }, 100);
			} 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()]; 
	}
}
