var isDOM = document.getElementById ? 1 : 0, isIE = document.all ? 1 : 0, isNS4 = navigator.appName == 'Netscape'
		&& !isDOM ? 1 : 0, isOp = self.opera ? 1 : 0, isDyn = isDOM || isIE
		|| isNS4;

function getRef(i, p) {
	p = !p ? document : p.navigator ? p.document : p;
	return isIE ? p.all[i] : isDOM ? (p.getElementById ? p : p.ownerDocument)
			.getElementById(i) : isNS4 ? p.layers[i] : null;
};

function getSty(i, p) {
	var r = getRef(i, p);
	return r ? isNS4 ? r : r.style : null;
};

if (!self.LayerObj)
	var LayerObj = new Function('i', 'p',
			'this.ref=getRef(i, p); this.sty=getSty(i, p); return this');
function getLyr(i, p) {
	return new LayerObj(i, p)
};

function LyrFn(n, f) {
	LayerObj.prototype[n] = new Function(
			'var a=arguments,p=a[0],px=isNS4||isOp?0:"px"; ' + 'with (this) { ' + f + ' }');
};
LyrFn('x', 'if (!isNaN(p)) sty.left=p+px; else return parseInt(sty.left)');
LyrFn('y', 'if (!isNaN(p)) sty.top=p+px; else return parseInt(sty.top)');

if (typeof addEvent != 'function') {
	var addEvent = function(o, t, f, l) {
		var d = 'addEventListener', n = 'on' + t, rO = o, rT = t, rF = f, rL = l;
		if (o[d] && !l)
			return o[d](t, f, false);
		if (!o._evts)
			o._evts = {};
		if (!o._evts[t]) {
			o._evts[t] = o[n] ? {
				b :o[n]
			} : {};
			o[n] = new Function(
					'e',
					'var r = true, o = this, a = o._evts["'
							+ t
							+ '"], i; for (i in a) {'
							+ 'o._f = a[i]; r = o._f(e||window.event) != false && r; o._f = null;'
							+ '} return r');
			if (t != 'unload')
				addEvent(window, 'unload', function() {
					removeEvent(rO, rT, rF, rL);
				});
		}
		if (!f._i)
			f._i = addEvent._i++;
		o._evts[t][f._i] = f;
	};
	addEvent._i = 1;
	var removeEvent = function(o, t, f, l) {
		var d = 'removeEventListener';
		if (o[d] && !l)
			return o[d](t, f, false);
		if (o._evts && o._evts[t] && f._i)
			delete o._evts[t][f._i];
	};
}

// *** CORE MENU OBJECT AND FUNCTIONS ***

function ListMenu(myName, nested, cssProp, cssVis, cssHid) {
	// This is the base object that users create.
	// It stores menu properties, and has a 'menus' associative array to store a
	// list of menu nodes,
	// and allow timeouts to refer to nodes by name (e.g.
	// menuObject.menus.nodeName).

	this.myName = myName;
	this.nested = true;
	// Some CSS settings users can specify.
	this.cssProp = 'display';
	this.cssVis = 'block';
	this.cssHid = 'none';
	this.cssLitClass = 'highlighted';
	// The 'root' menu only exists for list menus, and is created in the
	// activation function.
	// For non-nested menus, here's an imaginary node that acts as a parent to
	// other nodes.
	this.menus = {
		root :new ListMenuNode('root', true, this)
	};
	this.menuToShow = [];
	this.mtsTimer = null;
	// Other configurable defaults.
	this.showDelay = 250;
	this.switchDelay = 0;
	this.hideDelay = 250;
	this.showOnClick = 0;
	this.hideOnClick = true;
	// Animation speeds: set to a number between 0 and 1. Larger = faster. 1 =
	// disabled.
	this.animInSpeed = 0.2;
	this.animOutSpeed = 0.2;
	this.animations = [ ListMenu.animFade ];

	// Free memory onunload in IE.
	// if (isIE && !isOp) addEvent(window, 'unload', new Function(myName + ' =
	// null'));
};

ListMenu.prototype.show = function(mN) {
	with (this) {
		// Set menuToShow to the function parameters. Use a loop to copy values
		// so we don't leak memory.
		menuToShow.length = arguments.length;
		for ( var i = 0; i < arguments.length; i++)
			menuToShow[i] = arguments[i];
		// Use a timer to call a virtual 'root' menu over() function for
		// non-nested menus.
		// For nested/list menus event bubbling will call the real root menu
		// node over() function.
		clearTimeout(mtsTimer);
		if (!nested)
			mtsTimer = setTimeout(myName + '.menus.root.over()', 10);
	}
};

ListMenu.prototype.hide = function(mN) {
	with (this) {
		// Clear the above timer and route the hide event to the appropriate
		// menu node.
		clearTimeout(mtsTimer);
		if (menus[mN])
			menus[mN].out();
	}
};

ListMenu.prototype.hideAll = function() {
	with (this) {
		// Hides all non-root menus, mercilessly!

		for ( var m in menus)
			if (menus[m].visible && !menus[m].isRoot)
				menus[m].hide(true);
	}
};

function ListMenuNode(id, isRoot, obj) {
	// Each menu is represented by a ListMenuNode object.
	// This is the node constructor function, with the properties and functions
	// needed by that node.
	// It's passed its own name in the menus[] array, whether this is a root
	// level menu (boolean),
	// and a reference to the parent ListMenu object.

	this.id = id;
	this.isRoot = isRoot;
	this.obj = obj;
	this.lyr = this.child = this.par = this.timer = this.visible = null;
	this.args = [];
	var node = this;

	// These next per-node over/out functions are an example of closures in
	// JavaScript.
	// Since they're instantiated here, they can use the node's variables as if
	// they were their own.

	this.over = function(evt) {
		with (node)
			with (obj) {
				// Basically, over() gets called when the onmouseover event
				// reaches the menu container,
				// which might be a DIV or UL tag in the document. The event
				// source is a tag inside
				// that container that calls ListMenu.show() and sets the
				// menuToShow array (see above).
				// Browsers will then 'bubble' the event upwards, reaching this
				// function which is set
				// as the onmouseoover/focus/click handler on a menu container.
				// This picks up the
				// properties in menuToShow and shows the given menu as a child
				// of this one.
				// Note that for non-nested menus there is a default timer
				// (mtsTimer) that will mimic
				// an outermost 'root' menu that picks up the event if no other
				// menus intercept it first.

				// Ensure NS4 calls the show/hide function within this layer
				// first.
				if (isNS4 && evt && lyr.ref)
					lyr.ref.routeEvent(evt);
				// Stop this menu hiding itself and intercept the default root
				// show handler (if any).
				clearTimeout(timer);
				clearTimeout(mtsTimer);

				// A quick check; if this menu isn't visible, show it (i.e.
				// cancel any animation).
				if (!isRoot && !visible)
					node.show();

				if (menuToShow.length) {
					// Pull information out of menuToShow[].
					var a = menuToShow, m = a[0];
					if (!menus[m] || !menus[m].lyr.ref)
						menus[m] = new ListMenuNode(m, false, obj);
					// c = reference to new child menu that will be shown.
					var c = menus[m];
					// Just clear the menuToShow array and return if we're
					// "showing" the current menu...!
					if (c == node) {
						menuToShow.length = 0;
						return;
					}
					// Otherwise, stop any impending show/hide of the child
					// menu, and check if it's a new child.
					clearTimeout(c.timer);
					if (c != child && c.lyr.ref) {
						// We have a genuinely new child menu to show. Give it
						// some properties, set a timer to show it.
						// Again, try and avoid memory leaks, but copy over the
						// a/menuToShow arguments.
						c.args.length = a.length;
						for ( var i = 0; i < a.length; i++)
							c.args[i] = a[i];
						// Decide which delay to use -- switchDelay if we
						// already have a child menu, showDelay otherwise.
						var delay = child ? switchDelay : showDelay;
						c.timer = setTimeout('with(' + myName + ') { menus["'
								+ c.id + '"].par = menus["' + node.id
								+ '"]; menus["' + c.id + '"].show() }',
								delay ? delay : 1);
					}
					// Try, try, try to avoid leaking memory... :).
					menuToShow.length = 0;
				}

				// For non-nested menus, mimic event bubbling.
				if (!nested && par)
					par.over();
			}
	};

	this.out = function(evt) {
		with (node)
			with (obj) {
				// Basically the same as over(), this cancels impending events
				// and sets a hide timer.
				if (isNS4 && evt && lyr && lyr.ref)
					lyr.ref.routeEvent(evt);
				clearTimeout(timer);
				// We never hide the root menu. Otherwise, mimic event-bubbling
				// for non-nested menus.
				if (!isRoot && hideDelay >= 0) {
					timer = setTimeout(myName + '.menus["' + id + '"].hide()',
							hideDelay);
					if (!nested && par)
						par.out();
				}
			}
	};

	// Finally, now we have created our menu node, get a layer object for the
	// right ID'd element
	// in the page, and assign it onmouseout/onmouseover events.
	// Don't do for virtual root node (in the case of non-nested menus).
	if (this.id != 'root')
		with (this)
			with (lyr = getLyr(id))
				if (ref) {
					if (isNS4)
						ref.captureEvents(Event.MOUSEOVER | Event.MOUSEOUT);
					addEvent(ref, 'mouseover', this.over);
					addEvent(ref, 'mouseout', this.out);
					// For nested UL/LI menus, assign focus/blur events for
					// accessibility.
					if (obj.nested) {
						addEvent(ref, 'focus', this.over);
						addEvent(ref, 'click', this.over);
						addEvent(ref, 'blur', this.out);
					}
				}
};

ListMenuNode.prototype.show = function(forced) {
	with (this)
		with (obj) {
			// This is called to show the menu node of which it's a method.
			// It sets the parent's child to this, and hides any existing
			// children of the parent node.
			if (!lyr || !lyr.ref)
				return;

			if (par) {
				if (par.child && par.child != this)
					par.child.hide();
				par.child = this;
			}

			// This is the positioning routine, it can be deleted if you're not
			// using it.
			// It pulls values out of the stored args[] array, and uses the
			// page.elmPos function in the
			// cross-browser code to find the pixel position of the parent item
			// + menu.
			var offR = args[1], offX = args[2], offY = args[3], lX = 0, lY = 0, doX = '' + offX != 'undefined', doY = '' + offY != 'undefined';
			if (self.page && offR && (doX || doY)) {
				with (page.elmPos(offR, par.lyr ? par.lyr.ref : 0))
					lX = x, lY = y;
				if (doX)
					lyr.x(lX + eval(offX));
				if (doY)
					lyr.y(lY + eval(offY));
			}

			// Highlight the triggering element, if any.
			if (offR)
				lightParent(offR, 1);

			// Show the menu and trigger any 'onshow' events.
			// Also raise the parent <li> to avoid z-index issues in MSIE.
			visible = 1;
			if (obj.onshow)
				obj.onshow(id);
			lyr.ref.parentNode.style.zIndex = '2';
			setVis(1, forced);
		}
};

ListMenuNode.prototype.hide = function(forced) {
	with (this)
		with (obj) {
			// Same as show() above, but this clears the child/parent settings
			// and hides the menu.
			if (!lyr || !lyr.ref || !visible)
				return;

			// This is an NS4 hack as its mouse events are notoriously
			// unreliable. Remove as needed.
			if (isNS4 && self.isMouseIn && isMouseIn(lyr.ref))
				return show();
			// Dim the triggering element.
			if (args[1])
				lightParent(args[1], 0);

			// Route the hide call through any child nodes, and clear the
			// par/child references.
			if (child)
				child.hide();
			if (par && par.child == this)
				par.child = null;

			// Hide the menu node element, trigger an 'onhide' event if set,
			// lower the
			// parent <li> and finally show the menu.
			if (lyr) {
				visible = 0;
				if (obj.onhide)
					obj.onhide(id);
				lyr.ref.parentNode.style.zIndex = '1';
				setVis(0, forced);
			}
		}
};

ListMenuNode.prototype.lightParent = function(elm, lit) {
	with (this)
		with (obj) {
			// This is passed a reference to the parent triggering element, and
			// whether it is lit or not.

			if (!cssLitClass || isNS4)
				return;
			// By default the cssLitClass value is appended/removed to any
			// existing class.
			// Otherwise, if hiding, remove all trailing instances of it (in
			// case of script errors).
			if (lit)
				elm.className += (elm.className ? ' ' : '') + cssLitClass;
			else
				elm.className = elm.className.replace(new RegExp(
						'(\\s*' + cssLitClass + ')+$'), '');
		}
};

ListMenuNode.prototype.setVis = function(sh, forced) {
	with (this)
		with (obj) {
			// This is passed two parameteres: whether its menu is shown
			// (boolean), and
			// whether its a "forced" animation (can't be interrupted by an
			// unforced one).
			// It sets the chosen CSS property of the menu element, and
			// repeatedly calls itself if
			// one or more animations have been specified in the animations
			// property.

			if (lyr.forced && !forced)
				return;
			lyr.forced = forced;
			lyr.timer = lyr.timer || 0;
			lyr.counter = lyr.counter || 0;
			with (lyr) {
				clearTimeout(timer);
				if (sh && !counter)
					sty[cssProp] = cssVis;
				var speed = sh ? animInSpeed : animOutSpeed;

				if (isDOM && speed < 1)
					for ( var a = 0; a < animations.length; a++)
						animations[a](ref, counter, sh);

				counter += speed * (sh ? 1 : -1);
				if (counter > 1) {
					counter = 1;
					lyr.forced = false
				} else if (counter < 0) {
					counter = 0;
					sty[cssProp] = cssHid;
					lyr.forced = false
				} else if (isDOM) {
					timer = setTimeout(myName + '.menus["' + id + '"].setVis('
							+ sh + ',' + forced + ')', 50)
				}
			}
		}
};

ListMenu.animFade = function(ref, counter, show) {
	// Use IE's ActiveX alpha filter or the W3C/Mozilla 'opacity' to fade menu.
	var done = (counter == 1);
	if (ref.filters) {
		var alpha = !done ? ' alpha(opacity=' + parseInt(counter * 100) + ')'
				: '';
		if (ref.style.filter.indexOf("alpha") == -1)
			ref.style.filter += alpha;
		else
			ref.style.filter = ref.style.filter.replace(/\s*alpha\([^\)]*\)/i,
					alpha);
	} else
		ref.style.opacity = ref.style.MozOpacity = counter / 1.001;
};

ListMenu.prototype.activateMenu = function(id, subInd) {
	with (this) {
		if (!isDOM || !document.documentElement)
			return;

		// First of all, disable the CSS menu behaviour.
		var fsmFB = getRef('ListMenu-fallback');
		if (fsmFB) {
			fsmFB.rel = 'alternate stylesheet';
			fsmFB.disabled = true;
		}

		// References to menu elements for a given level.
		var a, ul, li, parUL, mRoot = getRef(id), nodes, count = 1;

		// Loop through all sub-lists under the given menu.
		var lists = mRoot.getElementsByTagName('ul');
		for ( var i = 0; i < lists.length; i++) {
			// Find a parent LI node, if any, by recursing upwards from the UL.
			// Quit if not found.
			li = ul = lists[i];
			while (li) {
				if (li.nodeName.toLowerCase() == 'li')
					break;
				li = li.parentNode;
			}
			if (!li)
				continue;
			// Next, find the parent UL to that LI node.
			parUL = li;
			while (parUL) {
				if (parUL.nodeName.toLowerCase() == 'ul')
					break;
				parUL = parUL.parentNode;
			}

			// Now, find the anchor tag that triggers this menu; it should be a
			// child of the LI.
			a = null;
			for ( var j = 0; j < li.childNodes.length; j++)
				if (li.childNodes[j].nodeName.toLowerCase() == 'a')
					a = li.childNodes[j];
			if (!a)
				continue;

			// We've found a menu node by now, so give it an ID and event
			// handlers.
			var menuID = myName + '-id-' + count++;
			// Only assign a new ID if it doesn't have one already.
			if (ul.id)
				menuID = ul.id;
			else
				ul.setAttribute('id', menuID);

			// Attach focus/mouse events to the triggering anchor tag.
			// Check if this link will be triggered onclick instead of
			// onmouseover.
			// If so, we only respect mouseovers/focuses when the menu is
			// visible from a click.
			var sOC = (showOnClick == 1 && li.parentNode == mRoot)
					|| (showOnClick == 2);
			// Safari 1.x is buggy and crashes when you access "returnValue".
			// Joy.
			// Also Opera defaults to "false" so use something different there.
			var evtProp = navigator.userAgent.indexOf('Safari') > -1 || isOp ? 'safRtnVal'
					: 'returnValue';
			// Create the functions that handle show & hide events.
			var eShow = new Function('with (' + myName + ') { '
					+ 'var m = menus["' + menuID + '"], pM = menus["'
					+ parUL.id + '"];'
					+ (sOC ? 'if ((pM && pM.child) || (m && m.visible))' : '')
					+ ' show("' + menuID + '", this) }');
			var eHide = new Function('e', 'if (e.' + evtProp + ' != false) '
					+ myName + '.hide("' + menuID + '")');
			// Assign them to the <a> element.
			addEvent(a, 'mouseover', eShow);
			addEvent(a, 'focus', eShow);
			addEvent(a, 'mouseout', eHide);
			addEvent(a, 'blur', eHide);
			if (sOC)
				addEvent(
						a,
						'click',
						new Function(
								'e',
								myName
										+ '.show("'
										+ menuID
										+ '", this); if (e.cancelable && e.preventDefault) e.preventDefault(); '
										+ 'e.' + evtProp
										+ ' = false; return false'));

			// Prepend an arrow indicator to the anchor tag content if given.
			if (subInd)
				a.insertBefore(subInd.cloneNode(true), a.firstChild);
		}

		// Because IE sucks, we emulate onfocus/blur event bubbling for
		// accessibility,
		// which allows users to tab/shift+tab through the menus.
		if (isIE && !isOp) {
			var aNodes = mRoot.getElementsByTagName('a');
			for ( var i = 0; i < aNodes.length; i++) {
				// A setTimeout helps ensure the focus is always after any blur.
				addEvent(
						aNodes[i],
						'focus',
						new Function(
								'e',
								'var node = this.parentNode; while(node) { if (node.onfocus) node.onfocus(e); node = node.parentNode }'));
				addEvent(
						aNodes[i],
						'blur',
						new Function(
								'e',
								'var node = this.parentNode; while(node) { if (node.onblur) node.onblur(e); node = node.parentNode }'));
			}
		}

		// If this menu hides its children onclick, setup that now.
		if (hideOnClick)
			addEvent(mRoot, 'click', new Function(myName + '.hideAll()'));

		// Finally, create/activate the root node.
		menus[id] = new ListMenuNode(id, true, this);
	}
};