// Copyright 2003-2004, Rod Mancisidor.
// All rights reserved.
// Use of this software prohibited except by written permission from the author.

// are we in debug mode? ------------------------------------------------------

var DEBUG = true;

// browser capabilities -------------------------------------------------------

// MSIE for the Mac delays requests to submit a form until all the javascript
// code has finished running
immediateSubmit = ! (navigator.userAgent.indexOf('MSIE') >= 0 &&
					 navigator.userAgent.indexOf('Mac')  >= 0);

// browser portability --------------------------------------------------------

function toLocaleUpperCase(str) {
	if (str.toLocaleUpperCase) {
		return str.toLocaleUpperCase();
	} else {
		return str.toUpperCase();
	}
}

function shift(array) {
	if (array.shift) {
		return array.shift();
	} else {
		var result = array[0];
		var i;
		for (i=1; i<array.length; ++i) array[i-1] = array[i];
		array.length = array.length-1;
		return result;
	}
}

function unshift(array, elem) {
	if (array.unshift) {
		return array.unshift(elem);
	} else {
		for (i=array.length; i>0; --i) array[i] = array[i-1];
		array[0] = elem;
		return array.length;
	}
}

// _getForm -------------------------------------------------------------------

var _form = null;
function _getForm() {
	if (_form == null) {
		_form = document.getElementById('mainForm');
	}
	return _form;
}

// Form submission framework --------------------------------------------------

var _oldFormAttrs = {};
function _setFormAttrToSubmit(name, value) {
	var form = _getForm();
	_oldFormAttrs[name] = form[name];
	form[name] = value;
}

var _oldElemValues = {};
function _setFieldToSubmit(name, value) {
	_oldElemValues[name] = _getFieldValues(name);
	_setField(name, value);
}

function _resetAfterSubmit(forget) {
	var form = _getForm();
	var name;
	for (name in _oldFormAttrs) {
		form[name] = _oldFormAttrs[name];
	}
	for (name in _oldElemValues) {
		_setFieldValues(name, _oldElemValues[name]);
	}
	if (forget) {
		_oldFormAttrs  = {};
		_oldElemValues = {};
	}
}

function submitForm(toPage) {
	_setFieldToSubmit('toPage', toPage || getElem('fromPage').value);
	var FieldScroll = getElem('FieldScroll');
	if (FieldScroll) {
		var scroll = _saveScroll();
		FieldScroll.value = scroll.x + ',' + scroll.y;
	}
	_getForm().submit();
	if (immediateSubmit) {
		_resetAfterSubmit(false);
	} else if (typeof _oldFormAttrs['target'] != 'undefined') {
		// FIXME: On MSIE for the Mac, we need to reset since this page is
		// staying in place, but we have to wait until the other window is
		// loaded (or requested to be loaded)! Otherwise, the other window gets
		// an "about:blank".
		// If the timeout is too short, we get "about:blank" in the new window
		// If the timeout is too long and the user clicks another link quickly,
		// they incorrectly go to the new window.
		setTimeout('_resetAfterSubmit(false)', 1000);
	}
	return false;
}

function submitFormWithAction(fromPage, toPage, action) {
	_setFieldToSubmit('fromPage', fromPage);
	_setFormAttrToSubmit('action', action);
	return submitForm(toPage);
}

function submitFormToPageOnWindow(toPage, onWindow) {
	_setFormAttrToSubmit('target', onWindow);
	return submitForm(toPage);
}

function submitFormToAux(toPage) {
	getAuxWindow();
	submitFormToPageOnWindow(toPage, 'auxWindow');
}

function doOp(op, toPage) {
	_setFieldToSubmit('FieldOp', op);
	submitForm(toPage);
}

function doOpWithTarget(op, target, toPage) {
	_setFieldToSubmit('FieldTarget', target);
	doOp(op, toPage);
}

function submitOnEnter(evt, toPage) {
	// onkeyup event handler to submit form on enter; toPage is optional
	// did the user type enter?
	evt = evt || event;
	var chr = evt.charCode || evt.which || evt.keyCode;
	var isEnter = chr == 13 || chr == 3; // 3 is for Mac
	if (isEnter) {
		submitForm(toPage);
	}
}

// Sorting function -----------------------------------------------------------

function dsuSort(array, transf, compare) {
	// Sorts an array based on the given transf and compare functions.
	//   array   -- Array to sort.
	//   transf  -- function that takes one array element and returns
	//              something that will be used to compare.  It does not have to
	//              deal with nulls.  It defaults to the identity function.
	//   compare -- function that takes two objects produced by transf
	//              and returns an integer representing their relative order.
	//              It does not have to deal with nulls.
	//              It defaults to using the '<' operator on the arguments.
	// decorate
	var trans;
	if (transf) {
		trans = function(x) {return x == null ? null : transf(x)};
	} else {
		trans = function(x) {return x};
	}
	var i, decorated = new Array(array.length);
	for (i=0; i<array.length; ++i) decorated[i] = [trans(array[i]),i,array[i]];
	// sort
	if (! compare) compare = function(a, b) {return a<b ? -1 : b<a ? 1 : 0};
	function cmp(a, b) {
		var e1 = a[0], e2 = b[0];
		if (e1 != null && e2 != null) {
			var result = compare(e1, e2);
			if (result != 0) return result;
		} else {
			if (e2 != null) return -1;
			if (e1 != null) return  1;
		}
		return a[1]-b[1];
	}
	decorated.sort(cmp);
	// undecorate
	for (i=0; i<array.length; ++i) array[i] = decorated[i][2];
	return array;
}

function reverseCompare(compare) {
	return function(a, b) {
		return compare(b, a);
	}
}

// onload framework -----------------------------------------------------------

_onLoadHandlers = [];

function addOnLoadHandler(handler) {
	_onLoadHandlers[_onLoadHandlers.length] = handler;
}

window.onload = function(evt) {
	var i;
	for (i=0; i<_onLoadHandlers.length; ++i) _onLoadHandlers[i](evt);
}

// javascript loading framework -----------------------------------------------

function loadJs(id) {
	// This function requests a dynamic JS page from the server, which is then
	// executed by the browser.
	// id is the id of the script element as well as the id of the toPage.
	// id is followed by an arbitrary number of parameters indicating the
	// parameters that need to be submitted to the server.  The special
	// parameter 'noCache' forces the browser to request the script even when
	// it already has requested a JS file with the given parameters.
	var form = _getForm();
	var url  = form.action;
	var args = ['toPage='   + id,
				'fromPage=' + getElem('fromPage').value];
	var i, j, key, values;
	for (i=1; i<arguments.length; ++i) {
		key = arguments[i];
		if (key == 'noCache') {
			values = [new Date().toUTCString()];
		} else {
			values = _getFieldValues(key);
		}
		for (j=0; j<values.length; ++j) {
			// FIXME: should use encodeURIComponent but it is available only
			// in NN6 & IE5.5W
			args[args.length] = key + '=' + escape(values[j]);
		}
	}
	url +=  '?';
	url += args.join('&');
	var script = document.getElementById(id);
	script.src = url;
	// This is needed by NN6:
	document.getElementsByTagName('head')[0].appendChild(script);
}

// messages -------------------------------------------------------------------

function _getMsg(msg) {
	return document.getElementById(msg).title;
}

// getElem... -----------------------------------------------------------------

function _getElems(name) {
	// return all the input elements of the form with the given name
	var form     = _getForm();
	if (! form) return [];
	var elements = form.elements;
	var result   = [];
	var j        = 0;
	var i;
	for (i=0; i<elements.length; ++i) {
		if (elements[i].name == name) result[j++] = elements[i];
	}
	return result;
}

function getElem(name) {
	// first try to find an input element of the form with the given name
	var elems = _getElems(name);
	if (elems.length == 1) return elems[0];
	// if that fails, find it by its id
	return document.getElementById(name);
}

function _getElement(idOrElem) {
	return typeof idOrElem == 'string' ? getElem(idOrElem) : idOrElem;
}

function _getElemPosition(idOrElem) {
	// works for NN6 & IE5 and above
	var elem = _getElement(idOrElem);
	var x = 0, y = 0;
	while (elem) {
		x += elem.offsetLeft;
		y += elem.offsetTop;
		elem = elem.offsetParent;
	}
	var body = document.body;
	if (navigator.userAgent.indexOf('Mac') != -1 &&
		typeof body.leftMargin != 'undefined') {
		x += body.leftMargin;
		y += body.topMargin;
	}
	return [x, y];
}

function _getElemDims(idOrElem) {
	// works for NN6 & IE5 and above
	var elem = _getElement(idOrElem);
	return [elem.offsetWidth, elem.offsetHeight];
}

// _resetField ----------------------------------------------------------------

function _resetHiddenBooleanField(field) {
	_getElement(field).value = '0';
}

function _resetField(field) {
	var fields = _getElems(field);
	field = fields[0];
	switch (field.type) {
	case 'hidden':
	case 'text':
	case 'textarea':
	case 'password':
	case 'file':
		field.value = '';
		break;
	case 'radio':
	case 'checkbox':
		var i;
		for (i=0; i<fields.length; ++i) fields[i].checked = false;
		break;
	case 'select-one':
	case 'select-multiple':
		var options = field.options;
		var i, option;
		for (i=0; i<options.length; ++i) {
			option = options[i];
			option.selected = option.value == '';
		}
		break;
	}
}

// _setField ------------------------------------------------------------------

function _setField(field, value) {
	var fields = _getElems(field);
	field = fields[0];
	var success = false;
	switch (field.type) {
	case 'hidden':
	case 'text':
	case 'textarea':
	case 'password':
	case 'file':
		field.value = value;
		success = true;
		break;
	case 'radio':
	case 'checkbox':
		var i;
		for (i=0; i<fields.length; ++i) {
			field = fields[i];
			field.checked = field.value == value;
			success = success || field.value == value;
		}
		break;
	case 'select-one':
	case 'select-multiple':
		var options = field.options;
		var i, option, selected;
		for (i=0; i<options.length; ++i) {
			option = options[i];
			selected = option.value == value;
			// MSIE for the Mac requires change to defaultSelected to refresh
			// the UI
			option.defaultSelected = option.selected = selected;
			success = success || selected;
		}
		break;
	}
	return success;
}

// _setFieldValues ------------------------------------------------------------

function _setFieldValues(field, values) {
	var fields = _getElems(field);
	field = fields[0];
	var success = false;
	switch (field.type) {
	case 'hidden':
	case 'text':
	case 'textarea':
	case 'password':
	case 'file':
	case 'radio':
	case 'select-one':
		if (values.length == 1) {
			_setField(field.name, values[0]);
		} else {
			alert('bad field type: ' + field.type);
		}
		break;
	case 'checkbox':
		var i, j;
		for (i=0; i<fields.length; ++i) {
			field = fields[i];
			field.checked = false;
		}
		for (i=0; i<fields.length; ++i) {
			field = fields[i];
			for (j=0; j<values.length; ++j) {
				if (field.value == values[j]) {
					field.checked = success = true;
					break;
				}
			}
		}
		break;
	case 'select-multiple':
		var options = field.options;
		var i, j, option;
		for (i=0; i<options.length; ++i) {
			option = options[i];
			option.defaultSelected = option.selected = false;
		}
		for (i=0; i<options.length; ++i) {
			option = options[i];
			for (j=0; j<values.length; ++j) {
				if (option.value == values[j]) {
					// MSIE for the Mac requires change to defaultSelected to
					// refresh the UI
					option.defaultSelected = option.selected = success = true;
					break;
				}
			}
		}
		break;
	}
	return success;
}

// _getFieldValues ------------------------------------------------------------

function _getFieldValues(field) {
	// returns the current list of values of a field
	var fields = _getElems(field);
	field = fields[0];
	var result = [];
	switch (field.type) {
	case 'hidden':
	case 'text':
	case 'textarea':
	case 'password':
	case 'file':
		result[result.length] = field.value;
		break;
	case 'radio':
	case 'checkbox':
		var i;
		for (i=0; i<fields.length; ++i) {
			field = fields[i];
			if (field.checked) result[result.length] = field.value;
		}
		break;
	case 'select-one':
	case 'select-multiple':
		var options = field.options;
		var i, option;
		for (i=0; i<options.length; ++i) {
			option = options[i];
			if (option.selected) result[result.length] = option.value;
		}
		break;
	}
	return result;
}

// _getFieldValue -------------------------------------------------------------

function _getFieldValue(self) {
	// FIXME: _getFieldValue is too slow for IE5 Mac and Safari
	// returns the current value of a field; if it has many values, it returns
	// the first one; if it has no values, it returns null
	switch (self.type) {
	case 'hidden':
	case 'text':
	case 'textarea':
	case 'password':
	case 'file':
		return self.value;
	case 'radio':
	case 'checkbox':
		var fields = _getElems(self.name);
		var i, field;
		for (i=0; i<fields.length; ++i) {
			field = fields[i];
			if (field.checked) return field.value;
		}
		return null;
	case 'select-one':
	case 'select-multiple':
		var selectedIndex = self.selectedIndex;
		if (selectedIndex == -1) {
			return null;
		} else {
			return self.options[selectedIndex].value;
		}
	}
}

// scrolling ------------------------------------------------------------------

function _saveScroll() {
	result = {}
	if (typeof window.scrollX != 'undefined') {
		// NN6
		result.x = scrollX;
		result.y = scrollY;
	} else if (document.compatMode == "CSS1Compat") {
		// IE6 std mode
		result.x = document.body.parentNode.scrollLeft;
		result.y = document.body.parentNode.scrollTop;
	} else if (typeof document.body.scrollLeft != 'undefined') {
		// IE 6 non-std mode and IE5W
		result.x = document.body.scrollLeft;
		result.y = document.body.scrollTop;
	} else {
		result.x = 0;
		result.y = 0;
	}
	return result;
}

function _restoreScroll(scroll) {
	// must set a timeout for scrollTo to work on IE6
	setTimeout('scrollTo(' + scroll.x + ',' + scroll.y + ')', 0);
}

// _alert... ------------------------------------------------------------------

function _alertElem(elem) {
	_alertObj(_getElement(elem));
}

function _alertObj(obj) {
	if (! obj) {
		str = 'NOT AN OBJECT';
	} else {
		var props = Array();
		var i = 0;
		var prop;
		for (prop in obj) props[i++] = prop;
		dsuSort(props, function(str) {return str.toUpperCase()});
		var str = '';
		var idx, value;
		for (idx=0; idx<i; ++idx) {
			try {
				value = obj[props[idx]] + '';
			} catch (exc) {
				value = '<Exception>';
			}
			str += props[idx] + '=' + value + '\n';
		}
	}
	alert(str);
}

// _showBlockIf ---------------------------------------------------------------

function _showBlockIf(showThis, ifThis) {
	ifThis   = _getElement(ifThis);
	showThis = _getElement(showThis);
	if (ifThis && showThis) {
		var display = ifThis.type == 'checkbox'
			? ifThis.checked
			: ifThis.value == '1';
		showThis.style.display = display ? 'block' : 'none';
	}
}

function _displayInline(id) {
	var elem = getElem(id);
	if (elem) {
		elem.style.display = 'inline';
	}
}

function _displayBlock(id) {
	var elem = getElem(id);
	if (elem) {
		elem.style.display = 'block';
	}
}

function _hide(id) {
	var elem = getElem(id);
	if (elem) {
		elem.style.display = 'none';
	}
}

// toggle visibility of images or links ---------------------------------------

function toggleBooleanElem(self) {
	// works in conjunction with the KindBooleanImage and KindBooleanLink field
	// type to toggle the value when the user clicks it.
	// self is the <img> or <a> element on which the user clicked
	if (self == null) return;
	var id = self.id;
	var length = id.length;
	var willBe = id.charAt(length-1) == '1' ? '0' : '1';
	// toggle value of hidden field
	getElem(id.substring(0, length-2)).value = willBe;
	// toggle image/link display (hide me and show the other link/image)
	getElem(id.substring(0, length-1) + willBe).style.display = 'inline';
	self.style.display = 'none';
}

function _showProperBooleanImage(id) {
	var hideId, showId;
	if (getElem(id).value == '1') {
		hideId = id + '_0';
		showId = id + '_1';
	} else {
		hideId = id + '_1';
		showId = id + '_0';
	}
	var hide = getElem(hideId);
	if (hide != null) hide.style.display = 'none';
	var show = getElem(showId);
	if (show != null) show.style.display = 'inline';
}

// display and hide doIt field ------------------------------------------------

function displayDoIt(self, evt) {
	var suffixes = ['lower', 'upper', '0', '1', '2'];
	var name = self.name;
	name = name.substring(0, name.indexOf('Expert')) + 'Expert_';
	var done = true;
	var i, elem;
	for (i=0; i<suffixes.length; ++i) {
		elem = getElem(name + suffixes[i]);
		if (elem && ! elem.value) {
			done = false;
			break;
		}
	}
	// did the user type enter?
	evt = evt || event;
	var chr = evt.charCode || evt.which || evt.keyCode;
	var isEnter = chr == 13 || chr == 3; // 3 is for Mac
	if (isEnter) {
		// if so, focus the next blank input subfield if available or else
		// blur this one so the onchange handler can submit
		if (! done) {
			elem.focus();
		} else {
			self.blur();
		}
	}
	if (done) _displayInline(name + 'doIt');
}

function hideDoIt(self) {
	var name = self.name;
	name = name.substring(0, name.indexOf('Expert'));
	_hide(name + 'Expert_doIt');
}

// drop-down menus ------------------------------------------------------------

function mouseoverMenu(self) {
	// the id of a menu is foo_menu and the id of the dropdown is foo_items
	window.status='';
	var id = self.id;
	_displayBlock(id.substring(0, id.length-4) + 'items');
}

function mouseoutMenu(self) {
	// the id of a menu is foo_menu and the id of the dropdown is foo_items
	var id = self.id;
	_hide(id.substring(0, id.length-4) + 'items');
}

function clickMenuItem(id, value) {
	_setField(id, value);
	submitForm();
	return false;
}

// loadImages -----------------------------------------------------------------

// the global array of precached images for this page
_imageArray = Array();

function loadImages() {
	// This function takes any number of arguments, each one is the url of an
	// image to be cached with this page.
	for (i=0; i<arguments.length; ++i) {
		_imageArray[i] = new Image();
		_imageArray[i].src = arguments[i];
	}
}

// facilities to create DOM elements ------------------------------------------

function _newElem(tag) {
	// tag is the new element's tag.  all remaining arguments are objects
	// containing its properties
	// IE4 can only create img, area and option elements
	var i, props, propName, value, result = document.createElement(tag);
	for (i=1; i<arguments.length; ++i) {
		props = arguments[i];
		for (propName in props) {
			value = props[propName];
			if (value != null) {
				result[propName] = value;
			}
		}
	}
	return result;
}

// framework to set properties on DOM elements based on some criteria ---------

function performAction(elem, filter, action) {
	// Performs an arbitrary action on all elements under elem (including
	// elem) that satisfy the given filter.
	// elem   -- the root of the tree of elements to work on.
	// filter -- a function that takes an element and returns true IFF the
	//           element satisfies the filter.
	// action -- a function that takes an element and is executed on each
	//           element under elem that satisfies the filter.
	if (elem) {
		if (filter(elem)) action(elem);
		var i, childNodes = elem.childNodes;
		for (i=0; i<childNodes.length; ++i) {
			performAction(childNodes[i], filter, action);
		}
	}
}

// sample action functions and function factories

function setAttributes(attributes) {
	// Returns a function that sets the specified attributes of an element
	return function(elem) {for (key in attributes) elem[key] = attributes[key]}
}

// sample filter functions and function factories

function alwaysTrue() {
	// A filter that always returns true
	return true;
}

function hasClass(className) {
	// Returns a filter that accepts elements that have the given className
	return function(elem) {
		if (elem.className) {
			var i, classNames = elem.className.split(' ');
			for (i=0; i<classNames.length; ++i) {
				if (className == classNames[i]) return true;
			}
		}
		return false;
	}
}

function hasTag(tag) {
	// Returns a filter that accepts elements with the given tag
	if (DEBUG && tag != tag.toUpperCase()) {
		alert('hasTag() invoked with lowercase tag: ' + tag);
	}
	return function(elem) {
		return elem.tagName && elem.tagName == tag;
	}
}

function and() {
	// Takes an arbitrary number of filters and returns a filter that accepts
	// elements IFF all the filters accept it
	var filters = arguments;
	return function(elem) {
		var i;
		for (i=0; i< filters.length; ++i) {
			if ( ! filters[i](elem)) return false;
		}
		return true;
	}
}

function or() {
	// Takes an arbitrary number of filters and returns a filter that accepts
	// elements IFF any of the filters accept it
	var filters = arguments;
	return function(elem) {
		var i;
		for (i=0; i< filters.length; ++i) {
			if (filters[i](elem)) return true;
		}
		return false;
	}
}

function not(filter) {
	// Takes a filter and returns a filter that is its negation
	return function(elem) {
		return ! filter(elem);
	}
}

// other DOM-related functions ------------------------------------------------

function getElemForPred(evt, pred) {
	// Given an event and a predicate, find the closest element to the source
	// of the event that satisfies the predicate
	evt = evt || event;
	var elem = evt.target || evt.srcElement;
	while ( ! pred(elem)) elem = elem.parentNode;
	return elem;
}

function findIndex(evt, pred) {
	// used to find an index such as the row number of column number where an
	// event occurred.
	// evt is somewhere inside an element that complies with predicate
	// find the proper element that complies with predicate
	var elem = getElemForPred(evt, pred);
	// find how many siblings that comply with predicate precede this one
	var idx = 0;
	for (elem=elem.previousSibling; elem; elem=elem.previousSibling) {
		if (pred(elem)) ++idx;
	}
	return idx;
}

// setLang --------------------------------------------------------------------

function setLang(lang) {
	_setField('lang', lang);
	return submitForm();
}

// clickShortField ------------------------------------------------------------

function clickShortField(self, name) {
	// The hyperlink of a short field (such as Language) has been clicked,
	// hide it and show its hidden field
	// self is the hyperlink to the short field
	// name is the name of the hidden input for that short field
	self.style.display = 'none';
	getElem(name + '_input').style.display = 'inline';
	// The next line has no effect on IE and throws an exception on Netscape
	// getElem(name).click()
	return false;
}

// onload handlers ------------------------------------------------------------

// For MSIE for the Mac, we need to reset when the page is loaded, this happens
// when the user goes back in the browser history.  For other browsers, we also
// do this in case before the page finished processing _resetAfterSubmit, the
// browser received the new page (this would leave the old page in an
// inconsistent state).
//
addOnLoadHandler(function() {
	_resetAfterSubmit(true);
});

addOnLoadHandler(function() {
	var FieldScroll = getElem('FieldScroll');
	if (FieldScroll && FieldScroll.value) {
		setTimeout('scrollTo(' + FieldScroll.value + ')', 0);
	}
});

// *************************   L O G I P I C K   ******************************
// submit feedback button -----------------------------------------------------

function submitFeedback(page) {
	// if the user wants a reply, we must have an email address
	if (_getFieldValues('wantReply') == 'yes' && _getForm().email.value == '') {
		alert('To receive a reply, please provide your email address.');
	} else {
		doOp('processFeedback', page || 'PageFeedbackBiz');
		close();
	}
}
