var Runtime = function()
{
	var form = null;
	var datepicker = null;
	var eventRaised = false;	
	
	window.onload = function()
	{
		initialize();
		setFocus();
		window.setTimeout(restoreScrollPosition, 1);
	};
	
	var emptyHintFields = function()
	{
		for(var i = 0; i < form.elements.length; i++)
		{
			var element = form.elements[i];

			if(element.className && element.className.match(' hint$') == ' hint')
				element.value = "";
		}
	};
	
	var initialize = function()
	{
		if(form == null)
		{
			form = document.forms[0];
			form.onsubmit = function() 
			{
				if(form.submitExit && !eventRaised)
					raiseEvent(form.submitExit.value);
				
				return false;
			};
			
			// Little hack: 
			// This adds a hidden submit button to every page so that the user can always submit by hitting enter.
			var button = document.createElement("input");
			button.type = "submit";
			button.style.position = "absolute";
			button.style.top = "-200px";
			button.style.height = "100px";
			form.appendChild(button);
		}
	};
	
	var raiseEvent = function(source, type, args)
	{
		initialize();
		emptyHintFields();
		storeScrollPosition();
		
		eventRaised = true;
		
		form.requestType.value = "event";
		form.eventSource.value = source;
		form.eventType.value = type || "";
		form.eventArgs.value = args || "";
		form.submit();
	};
	
	var setViewState = function(fieldName, value)
	{
		initialize();
		
		var viewStateField = form["V_" + fieldName];
		
		if(value == null)
		{
			if(viewStateField != null)
				viewStateField.parentNode.removeChild(viewStateField);
			
			return;
		}
		
		if(viewStateField == null)
		{
			viewStateField = document.createElement("input");
			viewStateField.type = "hidden";
			viewStateField.name = "V_" + fieldName;
			
			form.appendChild(viewStateField);
		}
		else if(viewStateField.parentNode == null)
			form.appendChild(viewStateField);

		viewStateField.value = value.toString();
	};
	
	var getViewState = function(fieldName)
	{
		initialize();
		
		var viewStateField = form["V_" + fieldName];
		
		if(viewStateField == null)
			return null;
			
		return viewStateField.value;
	};
	
	var goBack = function()
	{
		var postbackCount = getViewState("postbackCount") || "0";
		history.go( -(parseInt(postbackCount) + 1));
	};
	
	var submit = function()
	{
		initialize();

		var selectedButton = getViewState("selectedButton");

		if(selectedButton)
			raiseEvent(selectedButton);
		else
			alert(pub.Messages.YouHaveMadeNoChoiceYet);
	};
	
	var gotoNextNode = function()
	{
		initialize();
		emptyHintFields();
		
		form.requestType.value = "gotoNextNode";
		form.submit();
	};
	
	var setValue = function(field, value)
	{
		initialize();
		form[field].value = value;
	};
	
	var getPosition = function(el)
	{
		var position = { left: 0, top: 0};

		for(var node = el.parentNode; node.tagName.toLowerCase() != "body"; node = node.parentNode)
		{
			position.left -= node.scrollLeft;
			position.top -= node.scrollTop;
		}
		
		while(el != null)
		{
			position.left += el.offsetLeft;
			position.top += el.offsetTop;
			el = el.offsetParent;
		}
		
		return position;
	};

	var getDate = function(hiddenField)
	{
		var dateparts = hiddenField.value.split("-");
		return new Date(dateparts[0], parseInt(dateparts[1], 10) - 1, dateparts[2]);
	};
	
	var setDate = function(hiddenField, date)
	{
		if(date)
			hiddenField.value = date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate();
		else
			hiddenField.value = "";
	};
	
	var showDatePicker = function(fieldId, doPostback, hintText, formatString)
	{
		var hiddenField = form[fieldId];
		var visibleText = document.getElementById(fieldId + "_text");
		
		if(datepicker == null)
			datepicker = new DatePicker();
			
		var position = getPosition(visibleText);

		if(hiddenField.value != "")
			datepicker.selectDate(getDate(hiddenField));
		
		datepicker.showFooter(true);
		datepicker.show();
		datepicker.setPosition(position.left, position.top + 20);
		
		datepicker.onselect = function()
		{
			var date = datepicker.getSelectedDate();
			setDate(hiddenField, date);
			
			if (date)
			{
				visibleText.innerHTML = date.toFormatString(formatString);
				visibleText.className = "Date";
			}
			else
			{
				visibleText.innerHTML = hintText || "&nbsp";
				
				if(hintText)
					visibleText.className = "Date hint";
			}
			
			datepicker.hide();
			
			if (doPostback)
				postback();
		};
	};
	
	var initializeCalendar = function(fieldId, doPostback)
	{
		initialize();
		
		var hiddenField = form[fieldId];
		var element = document.getElementById(fieldId + "_container");

		var datepicker = new DatePicker(element);
		datepicker.showDropdown(false);
		datepicker.show();

		if(hiddenField.value != "")
			datepicker.selectDate(getDate(hiddenField));
		else
			setDate(hiddenField, new Date());

		datepicker.onselect = function()
		{
			var date = datepicker.getSelectedDate();
			setDate(hiddenField, date);

			if(doPostback)
				postback();
		};
	};

	var initializeDateTextBoxes = function(fieldId, autoPostback)
	{
		initialize();
	
		var hiddenField = form[fieldId];
		var dayElement = document.getElementById(fieldId + "_day");
		var monthElement = document.getElementById(fieldId + "_month");
		var yearElement = document.getElementById(fieldId + "_year");
		
		dayElement.onchange = function()
		{
			hiddenField.value = yearElement.value + "-" + monthElement.value + "-" + dayElement.value;
			
			if(autoPostback && dayElement.value != "" && monthElement.value != "" && yearElement.value != "")
				postback();
		};
		
		monthElement.onchange = function()
		{
			hiddenField.value = yearElement.value + "-" + monthElement.value + "-" + dayElement.value;

			if(autoPostback && dayElement.value != "" && monthElement.value != "" && yearElement.value != "")
				postback();
		};
		
		yearElement.onchange = function()
		{
			hiddenField.value = yearElement.value + "-" + monthElement.value + "-" + dayElement.value;

			if(autoPostback && dayElement.value != "" && monthElement.value != "" && yearElement.value != "")
				postback();
		};
	};

	var initializeDateDropDownLists = function(fieldId, autoPostback)
	{
		initialize();
		
		var hiddenField = form[fieldId];
		var dayElement = document.getElementById(fieldId + "_day");
		var monthElement = document.getElementById(fieldId + "_month");
		var yearElement = document.getElementById(fieldId + "_year");

		var setHiddenFieldValue = function()
		{
			hiddenField.value = (
				yearElement.options[yearElement.selectedIndex].value + "-" + 
				monthElement.options[monthElement.selectedIndex].value + "-" +
				dayElement.options[dayElement.selectedIndex].value
			);
		};

		var populateDayElement = function()
		{
			var selectedIndex = dayElement.selectedIndex;
			var month = monthElement.options[monthElement.selectedIndex].value;
			
			while(dayElement.options.length > 1)
				dayElement.options.remove(dayElement.options.length - 1);
				
			var nrOfDays = 0;

			if(month == "" || month == "01" || month == "03" || month == "05" || month == "07" || month == "08" || month == "10" || month == "12")
				nrOfDays = 31;
			else if(month == "04" || month == "06" || month == "09" || month == "11")
				nrOfDays = 30;
			else
			{
				var year = yearElement.options[yearElement.selectedIndex].value;
				nrOfDays = year != "" && (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) ? 29 : 28;
			}
			
			for(var i=1; i<=nrOfDays; i++)
			{
				var option = document.createElement("option");
				option.text = i;
				option.value = i < 10 ? ("0" + i) : i;
				dayElement.options.add(option);
			}
			
			dayElement.selectedIndex = Math.min(selectedIndex, dayElement.options.length-1);
		};

		dayElement.onchange = function()
		{
			setHiddenFieldValue();

			if(autoPostback && dayElement.selectedIndex != 0 && monthElement.selectedIndex != 0 && yearElement.selectedIndex != 0)
				postback();
		};
		
		monthElement.onchange = function()
		{
			populateDayElement();
			setHiddenFieldValue();
			
			if(autoPostback && dayElement.selectedIndex != 0 && monthElement.selectedIndex != 0 && yearElement.selectedIndex != 0)
				postback();
		};

		yearElement.onchange = function()
		{
			if(monthElement.options[monthElement.selectedIndex].value == "02")
				populateDayElement();
				
			setHiddenFieldValue();

			if(autoPostback && dayElement.selectedIndex != 0 && monthElement.selectedIndex != 0 && yearElement.selectedIndex != 0)
				postback();
		};

		populateDayElement();
		
		if(hiddenField.value)
		{
			var parts = hiddenField.value.split("-");
			
			for(var i=0; i<yearElement.options.length-1; i++)
			{
				if(yearElement.options[i].value == parts[0])
				{
					yearElement.selectedIndex = i;
					break;
				}
			}
			
			for(var i=0; i<monthElement.options.length-1; i++)
			{
				if(monthElement.options[i].value == parts[1])
				{
					monthElement.selectedIndex = i;
					break;
				}
			}
			
			for(var i=0; i<dayElement.options.length-1; i++)
			{
				if(dayElement.options[i].value == parts[2])
				{
					dayElement.selectedIndex = i;
					break;
				}
			}

			populateDayElement();
		}
	};

	var initializeRatingNode = function(fieldId, maxValue, roundDisplayValue, isInput, autoPostback, roundInput, decimalPoint, numberOfDecimals)
	{
		initialize();
		
		/* TEST CODE 

		var testEl = document.createElement("div");
		testEl.style.position = "absolute";
		testEl.style.color = "Blue";
		testEl.style.top = "0px";
		testEl.style.left = "0px";
		document.body.appendChild(testEl);

		 TEST CODE */

		var outerSpan = document.getElementById("ratingSpanFor_" + fieldId);
		var hiddenField = form[fieldId];
		var inputChanged = false;

		var i = 0;

		while(outerSpan.childNodes[i].nodeType != 1 || outerSpan.childNodes[i].tagName.toLowerCase() != "span")
			i++;

		var innerSpan = outerSpan.childNodes[i];

		var imageWidth = outerSpan.clientWidth;
		outerSpan.style.width = (maxValue * imageWidth) + "px";

		var displayRating = function(value)
		{
			value = value == typeof("string") ? parseFloat(value) : value;
			innerSpan.style.width = (value * imageWidth) + "px";
		};

		if(isInput)
		{
			var getValue = function(clientX)
			{
				var mouseX = clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
				var valueInPixels = mouseX - getPosition(outerSpan).left;
				var value = valueInPixels / imageWidth;

				return roundInput ? Math.ceil(value) : value;
			};

			outerSpan.onmousemove = innerSpan.onmousemove = function(e)
			{
				e = e || event;

				var tempValue = getValue(e.clientX);

				if(tempValue <= maxValue)
					displayRating(tempValue);
			};

			/* TEST CODE
			
			outerSpan.onmousemove = innerSpan.onmousemove = function(e)
			{
				e = e || event;

				var tempValue = getValue(e.clientX);

				var mousePos = {
					x: e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft,
					y: e.clientY + document.body.scrollTop + document.documentElement.scrollTop
				};

				var elementPos = getPosition(outerSpan);

				testEl.innerHTML = "mouse={x:" + mousePos.x + ",y:" + mousePos.y + "}, span={left:" + elementPos.left + ",top:" + elementPos.top + "}";

				if(mousePos.x <= elementPos.left || mousePos.x >= (elementPos.left + outerSpan.offsetWidth) ||
					mousePos.y <= elementPos.top || mousePos.y >= (elementPos.top + outerSpan.offsetHeight))
				{
					var value = hiddenField.value.formatNumber(".", numberOfDecimals);
					displayRating((!inputChanged && roundDisplayValue) ? Math.round(value) : value);
				}
				else
					displayRating(tempValue);
			};
			
			TEST CODE */

			innerSpan.onmouseout = outerSpan.onmouseout = function(e)
			{
				e = e || event;

				var mousePos = {
					x: e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft,
					y: e.clientY + document.body.scrollTop + document.documentElement.scrollTop
				};

				var elementPos = getPosition(outerSpan);

				if(mousePos.x <= elementPos.left || mousePos.x >= (elementPos.left + outerSpan.offsetWidth) ||
					mousePos.y <= elementPos.top || mousePos.y >= (elementPos.top + outerSpan.offsetHeight))
				{
					var value = hiddenField.value.formatNumber(".", numberOfDecimals);
					displayRating((!inputChanged && roundDisplayValue) ? Math.round(value) : value);
				}
			};

			outerSpan.onclick = innerSpan.onclick = function(e)
			{
				e = e || event;

				var value = getValue(e.clientX).toString();
				hiddenField.value = value.formatNumber(decimalPoint, numberOfDecimals);

				inputChanged = true;

				if(autoPostback)
					Runtime.postback();
			};
		}

		var value = hiddenField.value ? parseFloat(hiddenField.value.formatNumber(".", numberOfDecimals)) : 0;
		displayRating(roundDisplayValue ? Math.round(value) : value);
	};
	
	var enableAutoCompletion = function(input, minChars, maxSuggestions, source)
	{
		initialize();
		input = form[input]; 
	
		var keyCodes = { tab : 9, enter : 13, escape : 27, up : 38, down : 40 };
		var position = getPosition(input);
		var element = document.createElement("div");
		
		element.className = "autocompletion";
		element.onmousedown = function() { return false; }
		document.body.insertBefore(element, document.body.firstChild);
		
		var timer = null;
		var value = null;
		var selectedIndex = -1;
		
		var updateSuggestions = function()
		{
			if (value == input.value)
				return;
				
			value = input.value;
			
			if (value.length < minChars)
			{
				element.style.display = "none";			
				return;
			}
			
			var request = (window.XMLHttpRequest) ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
			request.open("GET", source + "&text=" + encodeURIComponent(value) + "&maxSuggestions=" + maxSuggestions);
			
			request.onreadystatechange = function()
			{
				if(request.readyState != 4)
					return;
					
				var suggestions = eval(request.responseText);
			
				if (suggestions.length == 0)
				{
					element.style.display = "none";					
					return;
				}
				
				element.style.display = "block";
				element.innerHTML = "";
				selectedIndex = -1;
				
				for(var i=0; i<suggestions.length; i++)
				{
					with({ i : i, item : document.createElement("div") })
					{
						item.appendChild(document.createTextNode(suggestions[i]));
						item.setAttribute("unselectable", "on");
						element.appendChild(item);
						item.onclick = function() { updateInput(item); };
						
						item.onmouseover = function() {
							if(selectedIndex != -1)
								element.childNodes[selectedIndex].className = "";
							
							selectedIndex = i;
							element.childNodes[selectedIndex].className = "highlight";
						};
					}
				}
			};
			
			request.send(null);
		};
		
		var updateInput = function(item)
		{
			value = item.childNodes[0].nodeValue;
			input.value = value;
			element.style.display = "none";
		};
		
		var innerOnFocus = input.onfocus;
		var innerOnBlur = input.onblur;
		
		input.onfocus = function(e)
		{
			if(innerOnFocus)
				innerOnFocus.apply(input);
			
			element.style.top = (position.top + input.offsetHeight + 1) + "px";
			element.style.left = position.left + "px";				
		
			value = input.value;
			timer = window.setInterval(updateSuggestions, 20);
		};
		
		input.onblur = function(e)
		{
			if(innerOnBlur)
				innerOnBlur.apply(input);
		
			window.clearInterval(updateSuggestions);
			element.style.display = "none";
		};
		
		input.onkeydown = function(e)
		{
			e = e || event;

			if (element.style.display != "block")
				return;
			
			if(selectedIndex == -1 && e.keyCode != keyCodes.down)
				return;
			
			if(selectedIndex != -1)
				element.childNodes[selectedIndex].className = "";
			
			switch(e.keyCode)
			{
				case keyCodes.down:
					selectedIndex = Math.min(selectedIndex+1, element.childNodes.length-1);
					element.childNodes[selectedIndex].className = "highlight";
					return false;
				case keyCodes.up:
					selectedIndex = Math.max(selectedIndex-1, 0);
					element.childNodes[selectedIndex].className = "highlight";
					return false;
				case keyCodes.tab:
				case keyCodes.enter:
					updateInput(element.childNodes[selectedIndex]);
					return false;
			}
		};
	};
	
	var postback = function(element)
	{
		initialize();
		emptyHintFields();
		storeScrollPosition();
		
		form.requestType.value = "postback";
		
		if(element && element.name && element.focus)
		{
			if(element.type == "radio" || element.type == "checkbox")
				setViewState("focus", element.name + ":" + element.value);
			else
				setViewState("focus", element.name);
		}
			
		form.submit();
	};
	
	var crosslink = function(itemId, isFlowchart)
	{
		initialize();
		emptyHintFields();
		
		form.requestType.value = "crosslink";
		
		if(isFlowchart)
			form.targetFlowchart.value = itemId;
		else
		{
			form.currentNavigationItem.value = itemId;
			form.targetFlowchart.value = "";
		}
		
		form.submit();
	};
	
	var subroutine = function(itemId, isFlowchart)
	{
		initialize();
		emptyHintFields();
		
		form.requestType.value = "subroutine";
		
		if(isFlowchart)
			form.targetFlowchart.value = itemId;
		else
		{
			form.currentNavigationItem.value = itemId;
			form.targetFlowchart.value = "";
		}

		form.submit();
	};
	
	var setSupportingContent = function(content)
	{
		// The content can either be an id of a ContentTemplate or url to an external page.
		initialize();
		form.supportingContent.value = content;
		postback();
	};
	
	// This is a helper method that stores the scrollPosition in the viewstate. It is invoked by the postback()
	// and raiseEvent() methods. It stores both document.documentElement.scrollTop and document.body.scrollTop
	// to be compatible with the different browsers. It also stores the scrollPositions of the elements that
	// are included in the pub.scrollElements array.
	var storeScrollPosition = function()
	{
		var pos = 
			((document.documentElement ? document.documentElement.scrollTop : 0) || 0) + ":" +
			(document.body.scrollTop || 0);
		
		for(var i=0; i<pub.scrollElements.length; i++)
		{
			var element = document.getElementById(pub.scrollElements[i]);
			pos += ":" + (element ? element.scrollTop : "0");
		}
		
		setViewState("scrollPosition", pos);
	};
	
	var restoreScrollPosition = function()
	{
		var pos = getViewState("scrollPosition");
		
		if(pos != null)
		{
			pos = pos.split(":");
			
			if(pos[0] != "0")
				document.documentElement.scrollTop = parseInt(pos[0]);
				
			if(pos[1] != "0")
				document.body.scrollTop = parseInt(pos[1]);
				
			for(var i=2; i<pos.length; i++)
			{
				var element = document.getElementById(pub.scrollElements[i-2]);
				if(element)
					element.scrollTop = parseInt(pos[i]);
			}
		}
	};
	
	// This is a helper method that restores the focus of a form field after a postback.
	var setFocus = function()
	{
		var focusField = getViewState("focus");
		
		// Clear the "focus" field from the viewstate, for it was only relevant for the current postback.
		setViewState("focus", null);
		
		if(focusField == null)
		{
			// Set the focus on the first text field in the form.
			for(var i=0; i<form.length; i++)
			{
				if(form[i].type == "text" && form[i].focus)
				{
					// Ignore hint textfields. A hint textfield shouldn't get autofocus, because then the hint is not visible.
					if(form[i].className && form[i].className.match(' hint$') == ' hint')	
						continue;			
				
					form[i].focus();
					break;
				}
			}
			
			return;			
		}
		
		var parts = focusField.split(":");
		var field = form[parts[0]];
		var fieldValue = (parts.length == 2) ? parts[1] : null;

		if(field == null)
			return;	
			
		if(field.focus)
		{
			field.focus();
			return;
		}

		if(field.length)
		{
			for(var i=0; i<field.length; i++)
			{
				if(field[i].value == fieldValue)
				{
					field[i].focus();
					return;
				}
			}
		}
	};

	var pub = {
		setViewState : setViewState,
		getViewState : getViewState,
		raiseEvent : raiseEvent,
		submit : submit,
		gotoNextNode : gotoNextNode,
		goBack : goBack,
		postback : postback,
		crosslink : crosslink,
		subroutine : subroutine,
		setValue : setValue,
		showDatePicker : showDatePicker,
		setSupportingContent : setSupportingContent,
		initializeCalendar : initializeCalendar,
		initializeDateTextBoxes : initializeDateTextBoxes,
		initializeDateDropDownLists : initializeDateDropDownLists,
		initializeRatingNode : initializeRatingNode,
		enableAutoCompletion : enableAutoCompletion,
		scrollElements : [],
		Messages : {
			YouHaveMadeNoChoiceYet : null
		}
	};
	
	return pub;
}();

Date.prototype.toFormatString = function(formatString)
{
	var month = this.getMonth() + 1;
	var day = this.getDate();
	
	return (formatString
		.replace("yyyy", this.getFullYear())
		.replace("MM", month < 10 ? ("0" + month) : month)
		.replace("dd", day < 10 ? ("0" + day) : day)
		.replace("yy", this.getFullYear() % 100)
		.replace("M", month)
		.replace("d", day)
	);
};

String.prototype.formatNumber = function(decimalPoint, numberOfDecimals)
{
	var regex = /^\s*(-?\d+)(\.|,)?(\d*)\s*$/;
	var parts = regex.exec(this);

	/*
		If this matches the regex, then the parts array will contain 4 elements:
			0:	the complete string
			1:	the numeric part before the decimal point
			2:	the decimal point (if any) or else undefined
			3:	the numeric part after the decimal point (if any) or else ""
		If this doesn't match the regex, then the parts array will be null.
	*/

	if(parts == null)
		return this;

	if(numberOfDecimals == -1)
	{
		if(parts[2] == null)
			return parts[1];
		else
			return parts[1] + decimalPoint + parts[3];
	}

	if(numberOfDecimals == 0)
		return parts[1];

	if(parts[3].length > numberOfDecimals)
	{
		parts[3] = parts[3].substr(0, numberOfDecimals);
	}
	else
	{
		while(parts[3].length < numberOfDecimals)
			parts[3] += "0";
	}

	return parts[1] + decimalPoint + parts[3];
};
