/**  
 * @fileoverview This file contains functions that provide routine "helper" operations.
 * @author Mark Hendrickson <mhendric@bowdoin.edu>
 * @version 1.0
 */

/**
 * Shorthand function for document.getElementById().
 * @param {String} element_id Element ID
 * @return Element object
 */
function $(element_id)
{
	return document.getElementById(element_id);
}

/**
 * Stops the propogation of an event to prevent unwanted consequences.
 * @param {Object} event Event object
 */
function stopPropagation(event)
{
	if(!event)
		var event = window.event;

	if(event.stopPropagation) 
		event.stopPropagation();
	else if(window.event.cancelBubble == false) 
		window.event.cancelBubble = true;
}

/**
 * Removes all the children nodes of an element.
 * @param {Object} element Element object
 */
function removeChildren(element)
{
	var children = element.childNodes;
	var children_length = children.length; 

	for(var i=0; i<children_length; i++)
		element.removeChild(children[i]);
}

/**
 * Returns the first "real" (non-whitespace) element of an element.
 * @param {Object} parent Element object
 * @return Element or null
 */
function firstElement(parent)
{
	var children = parent.childNodes;
	for(var i=0; i < children.length; i++)
	{
		if(children[i].nodeType == '1')
			return children[i];
	}
	return null;
}

/**
 * Returns the text contained within an element.
 * @param {Object} parent Element object
 * @return Element or null
 */
function elementText(parent)
{
	var children = parent.childNodes;
	for(var i=0; i < children.length; i++)
	{
		if(children[i].nodeType == '3')
			return children[i].nodeValue;
	}
	return null;
}

/**
 * Appends one element to another as a node.
 * @param {Object} parent Element object to be appended
 * @param {Object} target Element object that will be appended to
 */
function appendNode(parent,target)
{
	if(parent.nodeType == 1)
	{
		var node = document.createElement(parent.tagName);

		if(parent.attributes)
		{
			for(var i=0; i<parent.attributes.length; i++)
				setNodeAttribute(node,parent.attributes[i].name,parent.attributes[i].value);
		}

		if(parent.hasChildNodes())
		{
			for(var i=0; i<parent.childNodes.length; i++)
				appendNode(parent.childNodes[i],node);
		}

		target.appendChild(node);
	}
	else if(parent.nodeType == 3)
	{
		var node = document.createTextNode(parent.nodeValue);
		target.appendChild(node);
	}
}

/**
 * Sets the value of a node's attribute.
 * NOTE: This function is mostly useful for its "special case" attribute assignments and should be reviewed thoroughly. These special case assignments are those that don't work through normal means with one or more browsers.
 * @param {Object} node Element object
 * @param {String} attribute_name Attribute name
 * @param {String) attribute_value New attribute value
 */
function setNodeAttribute(node,attribute_name,attribute_value)
{
	if(attribute_name == 'href' || attribute_name == 'onclick' || attribute_name == 'onClick')
	{
		attribute_value = attribute_value.replace(/&amp;/g,'&');
		attribute_value = attribute_value.replace(/&#38;/g,'&');
	}
	
	if(attribute_name == 'class')
		node.className = attribute_value;
	else if(attribute_name == 'style')
		node.style.cssText = attribute_value;
	else if(attribute_name == 'colspan')
		node.colSpan = attribute_value;
	else if(attribute_name == 'valign')
		node.vAlign = attribute_value;
	else if(attribute_name == 'onClick' || attribute_name == 'onclick')
		node.onclick = function(event) { eval(attribute_value); };
	else if(attribute_name == 'onSubmit' || attribute_name == 'onsubmit')
		node.onsubmit = function(event) 
						{ 
							if(attribute_value == 'submit_directions')
								return submitDirections();
							else if(attribute_value == 'load_dining_menu_thorne')
								return loadDiningMenu('thorne');
							else if(attribute_value == 'load_dining_menu_moulton')
								return loadDiningMenu('moulton');
							else if(attribute_value == 'load_dining_menu_thorne_separate')
								return loadDiningMenu('thorne',true);
							else if(attribute_value == 'load_dining_menu_moulton_separate')
								return loadDiningMenu('moulton',true);
							else if(attribute_value == 'load_student_calendar')
								return loadStudentCalendar();
								
						};
	else if(attribute_name == 'onMouseOver' || attribute_name == 'onmouseover')
		node.onmouseover = function(event) { eval(attribute_value); };
	else if(attribute_name == 'onChange' || attribute_name == 'onchange')
		node.onchange = function(event) { eval(attribute_value); };
	else if(attribute_name == 'onMouseOut' || attribute_name == 'onmouseout')
		node.onmouseout = function(event) { eval(attribute_value); };
	else if(attribute_name == 'onMouseDown' || attribute_name == 'onmousedown')
		node.onmousedown = function(event) { eval(attribute_value); };
	else if(attribute_name == 'onMouseUp' || attribute_name == 'onmouseup')
		node.onmouseup = function(event) { eval(attribute_value); };
	else
		node.setAttribute(attribute_name,attribute_value);
}

/**
 * This appends a node to an element before another node already in that element.
 * @param {Object} parent Element object to be appended
 * @param {Object} target Element object that will be appended to
 * @param {Object} sub_target Element object within the target before which should be appended the parent
 */
function appendNodeBefore(parent,target,sub_target)
{
	var temp_container = document.createElement('div');
	appendNode(parent,temp_container);
	target.insertBefore(temp_container.firstChild,sub_target);
}

/**
 * Returns the target of an event (element clicked on, moved over, etc.)
 * @param {Object} event Event object
 * @return Element object
 */
function getEventTarget(event)
{
	var target;

	// Gecko
	if(event.target)
		target = event.target;
	// IE
	else if(event.srcElement)
		target = event.srcElement;
	
	// Fix for Safari bug
	if(target && target.nodeType == 3)
		target = target.parentNode;

	return target;
}

/**
 * Returns the related target of an event (for example, element just left by cursor [for mouseover events] or just entered by cursor [for mouseout events])
 * @param {Object} event Event object
 * @return Element object
 */
function getEventRelatedTarget(event)
{
	var related_target;

	// Gecko
	if(event.relatedTarget)
		related_target = event.relatedTarget;
	// IE
	else if(event.toElement)
		related_target = event.toElement;
	else if(event.fromElement)
		related_target = event.fromElement;
	
	// Fix for Safari bug
	if(related_target && related_target.nodeType == 3)
		related_target = related_target.parentNode;

	return related_target;
}

/**
 * Returns horizontal position (in pixels) of an event on a particular element.
 * @param {Object} event Event object
 * @return Horizontal position value
 * @type Integer
 */
function getEventLayerX(event)
{
	var layerX;
	
	if(event.layerX)
		layerX = event.layerX;
	else if(event.offsetX)
		layerX = event.offsetX;
	else
		layerX = 0;

	return layerX;
}

/**
 * Returns vertical position (in pixels) of an event on a particular element.
 * @param {Object} event Event object
 * @return Vertical position value
 * @type Integer
 */
function getEventLayerY(event)
{
	var layerY;
	
	if(event.layerY)
		layerY = event.layerY;
	else if(event.offsetY)
		layerY = event.offsetY;
	else
		layerY = 0;

	return layerY;
}

/**
 * Returns horizontal position (in pixels) of an event on entire page.
 * @param {Object} event Event object
 * @return Horizontal position value
 * @type Integer
 */
function getEventPageX(event)
{
	var pageX;
	
	if(event.pageX)
		pageX = event.pageX;
	else if(event.clientX)
	{
		if(document.documentElement && document.documentElement.scrollLeft)
			pageX = event.clientX + document.documentElement.scrollLeft;
		else if(document.body && document.body.scrollLeft)
			pageX = event.clientX + document.body.scrollLeft;
		else
			pageX = event.clientX;
	}
	else
		pageX = 0;

	return pageX;
}

/**
 * Returns vertical position (in pixels) of an event on entire page.
 * @param {Object} event Event object
 * @return Vertical position value
 * @type Integer
 */
function getEventPageY(event)
{
	var pageY;
	
	if(event.pageY)
		pageY = event.pageY;
	else if(event.clientY)
	{
		if(document.documentElement && document.documentElement.scrollTop)
			pageY = event.clientY + document.documentElement.scrollTop;
		else if(document.body && document.body.scrollTop)
			pageY = event.clientY + document.body.scrollTop;
		else
			pageY = event.clientY;
	}
	else
		pageY = 0;

	return pageY;
}

/** 
 * Determine whether x and y coordinates are within an element, and perhaps return hemisphere indication if so.
 * Note: Hemisphere is an indication of whether coordinate is in upper or lower half of element.
 * @param {Integer} x Horizontal coordinate value
 * @param {Integer} y Vertical coordinate value
 * @param {Object} element Element object
 * @param {Boolean} hemisphere Whether or not function should return hemisphere indication
 * @return If hemisphere desired, "upper", "lower", or false; otherwise, true or false
 * @type Boolean or string
*/
function isWithin(x,y,element,hemisphere)
{
	var child;
	var offsetTop;
	var offsetLeft;
	var offsetHeight;
	var offsetWidth;

	child = element;
	offsetTop = getY(child);
	offsetLeft = getX(child);
	
	if(child.className == 'column')
		offsetHeight = $('panels_container').offsetHeight;
	else
		offsetHeight = child.offsetHeight;
	
	offsetWidth = child.offsetWidth;

	if(offsetTop <= y && offsetLeft <= x && (offsetTop+offsetHeight) >= y && (offsetLeft+offsetWidth) >= x)
	{
		if(hemisphere)
		{
			if(y < offsetTop+offsetHeight/2)
				return 'upper';
			else
				return 'lower';
		}
		else
			return true;
	}
	else
		return false;
}

/**
 * Returns horizontal position (in pixels) of an element on entire page.
 * @param {Object} element Element object
 * @return Horizontal position value
 * @type Integer
 */
function getX(element)
{
	var offsetLeft;

	if(!(element.className == 'panel' && element.style.position != 'absolute' && element.offsetLeft > 10))
		offsetLeft = element.offsetLeft;
	else
		offsetLeft = 0;

	while(element.parentNode)
	{
		element = element.parentNode;
		if(element.offsetLeft)
			offsetLeft = offsetLeft + element.offsetLeft;
	}

	return offsetLeft;
}

/**
 * Returns vertical position (in pixels) of an element on entire page.
 * @param {Object} element Element object
 * @return Vertical position value
 * @type Integer
 */
function getY(element)
{
	var offsetTop = element.offsetTop;

	while(element.parentNode)
	{
		element = element.parentNode;
		if(element.offsetTop)
			offsetTop = offsetTop + element.offsetTop;
	}

	return offsetTop;
}

/**
 * Fades an element into view by gradually changing opacity.
 * Note: Element must not have "display: none" CSS property or effect will not render.
 * @param {String} element_id Element ID
 */
function fadeIn(element_id)
{
	var element = $(element_id);
	element.style.opacity = 0;
	element.style.filter = 'alpha(opacity=0)';
	fadeInSub(element_id,1);
}

/**
 * Called exclusively by fadeIn function; performs fade-in effect for element
 * @param {String) element_id Element ID
 * @param {Integer} step Numeric step in fade-in process
 * @see #fadeIn
 */
function fadeInSub(element_id,step)
{
	if(step <= 10)
	{
		var element = $(element_id);
		element.style.opacity = step/10;
		element.style.filter = 'alpha(opacity='+step*10+')';
		setTimeout('fadeInSub("'+element_id+'",'+(step+1)+')',1);
	}
}

/**
 * Fades an element out of view by gradually changing opacity.
 * Note: Element must not have "display: none" CSS property or effect will not render.
 * @param {String} element_id Element ID
 */
function fadeOut(element_id)
{
	var element = $(element_id);
	element.style.opacity = 1;
	element.style.filter = 'alpha(opacity=100)';
	fadeOutSub(element_id,10);
}

/**
 * Called exclusively by fadeOut function; performs fade-out effect for element
 * @param {String) element_id Element ID
 * @param {Integer} step Numeric step in fade-out process
 * @see #fadeOut
 */
function fadeOutSub(element_id,step)
{
	var element = $(element_id);

	if(element && step >= 0)
	{
		element.style.opacity = step/10;
		element.style.filter = 'alpha(opacity='+step*10+')';
		setTimeout('fadeOutSub("'+element_id+'",'+(step-1)+')',1);
	}
}

var echo_number = 0;
/**
 * Displays content in upper-right hand corner of screen for diagnostic/testing purposes.
 * @param {String} content Content to display
 */
function echo(content)
{
	if(!$('echo'))
	{
		var body = document.getElementsByTagName('body')[0];
		var echo = document.createElement('div');
		var echo_ul = document.createElement('ul');
		echo.setAttribute('id','echo');
		echo_ul.setAttribute('id','echo_ul');
		echo.appendChild(echo_ul);
		body.appendChild(echo);
	}

	echo_number++;

	var echo = $('echo');
	echo.style.display = 'block';

	var echo_ul = $('echo_ul');
	var first_li = echo_ul.firstChild;
	var new_li = document.createElement('li');
	new_li.innerHTML = echo_number+'. '+content;
	echo_ul.insertBefore(new_li,first_li);
}

/**
 * Search a simple array of strings for a particular value and report whether it is found.
 * @param {String} needle String to be found
 * @param {Array} haystack Array to look in
 * @param {String} appendage Value to add to contents of array to match with needle.
 * @return Boolean of whether needle found
 */
function searchArray(needle, haystack, appendage)
{
	for(var i=0; i<haystack.length; i++)
	{
		if(haystack[i]+appendage == needle)
			return true;
	}
	return false;
}

/**
 * Generate an XMLHttpRequest object.
 * @return XMLHttpRequest object.
 */
function initAjax()
{
	if(window.XMLHttpRequest)
		return new XMLHttpRequest();
	else if(window.ActiveXObject)
		return new ActiveXObject("Microsoft.XMLHTTP");
}

/**
 * Trim a string of any surrounding whitespace.
 * @param {String} str Original string
 * @return Trimmed string
 */
function trim(str)
{
   return str.replace(/^\s*|\s*$/g,"");
}

var sally = 0;
/**
 * Shhhhh. Secret function that Robert doesn't want to know about. Does secret things. You'll never guess what it does.
 * @param {Integer} num A secret number
 * @param {Object} event A secret event
 */
function arnold(num,event)
{
	if(num == 1 && sally != 1)
		sally = sally + 1;
	else if(num == 2 && sally != 2)
		sally = sally + 2;

	if(sally == 3)
	{
		liftPanel('announcements',event);
		sally = 0;
	}
}

/**
 * Returns whether user is using the Firefox browser on a Mac.
 * @type Boolean
 */
function isMacFirefox()
{
	if(current_browser == 'Firefox' && current_os == 'Mac')
		return true;
	else
		return false;
}

/**
 * Returns an array of elements chosen by class name.
 * Note: Function provided by Robert Nyman - http://www.robertnyman.com/ . 
 * Handy function, but I don't think I'm actually using it on the gateway anymore.
 */
function getElementsByClassName(oElm, strTagName, strClassName)
{
    var arrElements = (strTagName == "*" && oElm.all)? oElm.all : oElm.getElementsByTagName(strTagName);
    var arrReturnElements = new Array();
    strClassName = strClassName.replace(/\-/g, "\\-");
    var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$)");
    var oElement;
    for(var i=0; i<arrElements.length; i++){
        oElement = arrElements[i];      
        if(oRegExp.test(oElement.className)){
            arrReturnElements.push(oElement);
        }   
    }
    return (arrReturnElements)
}

/**
 * BrowserDetect class (?) provided by Quirks Mode.
 * I don't try to understand it; it gives me what I need to know in current_browser and current_os variables. Use sparingly.
 */ 
var BrowserDetect = {
	init: function () {
		this.browser = this.searchString(this.dataBrowser) || "An unknown browser";
		this.version = this.searchVersion(navigator.userAgent)
			|| this.searchVersion(navigator.appVersion)
			|| "an unknown version";
		this.OS = this.searchString(this.dataOS) || "an unknown OS";
	},
	searchString: function (data) {
		for (var i=0;i<data.length;i++)	{
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			this.versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString) {
				if (dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	},
	searchVersion: function (dataString) {
		var index = dataString.indexOf(this.versionSearchString);
		if (index == -1) return;
		return parseFloat(dataString.substring(index+this.versionSearchString.length+1));
	},
	dataBrowser: [
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{	// for newer Netscapes (6+)
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "Explorer",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 	// for older Netscapes (4-)
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	],
	dataOS : [
		{
			string: navigator.platform,
			subString: "Win",
			identity: "Windows"
		},
		{
			string: navigator.platform,
			subString: "Mac",
			identity: "Mac"
		},
		{
			string: navigator.platform,
			subString: "Linux",
			identity: "Linux"
		}
	]

};
BrowserDetect.init();
var current_browser = BrowserDetect.browser;
var current_os = BrowserDetect.OS;

/**
 * Refreshes an image by getting a new copy from the server.
 * @param {String} image_id Element ID
 * @param {String} original_src Source address of image
 */
function refreshImage(image_id,original_src)
{
	var tmp = new Date();
	tmp = "?"+tmp.getTime();
	$(image_id).src = original_src+tmp;
}
