/**
 * Introducing Menu.js
 * 
 * Menu.js is a free, small JavaScript (drop-down) menu for developers. It's
 * unobtrusive and only requires a HTML unordered list and your own CSS styles.
 * http://www.menujs.net/
 * 
 * Requires Prototype JS version 1.5.0 or greater. (Also supports version
 * 1.6.0.*, avoiding all deprecated methods) http://www.prototypejs.org/
 * _____________________________________________________________________________
 * 
 * As of version 1.2, released under the MIT License:
 * 
 * Copyright (c) 2007-2009 Charming Design, Niek Kouwenberg
 * http://www.charmingdesign.net/
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 * _____________________________________________________________________________
 * 
 * Special thanks to CARE Internet Services B.V. http://www.care.nl/
 */
Menu = {

	/* Version of the Menu class */
	Version : "1.3.1",

	/*
	 * CONSTANTS
	 */

	/* Constant for a horizontal menu */
	HORIZONTAL : 1,

	/* Constant for a vertical menu */
	VERTICAL : 2,

	/*
	 * MENU
	 */

	/* Hold the ID of the menu */
	_menuId : null,

	/* Hold the menu UL node */
	_menuNode : null,

	/*
	 * TEMPORARY VARIABLES
	 */

	/* Hold the show timer */
	_showTimeout : null,

	/* Hold the hide timer */
	_hideTimeout : null,

	/* Will hold the link, submenu and level of the active menu node */
	_activeNodes : new Array(),

	/*
	 * OPTIONS
	 */

	/* Orientation of the menu (horizontal or vertical) */
	_orientation : 1,

	/* Time in milliseconds before showing the sub menu */
	_showPause : 0,

	/* Time in milliseconds before hiding the sub menu */
	_hidePause : 1000,

	/* Opacity (0 = transparent, 1 = opaque) */
	_opacity : 1,

	/*
	 * METHODS
	 */

	/**
	 * Sets the time to wait before hiding the sub menu.
	 * 
	 * @param int
	 *            secs
	 * 
	 * @deprecated Please use the options argument for Menu.init() instead.
	 */
	setHidePause : function(secs) {
		Menu._hidePause = secs * 1000;

		/* deprecation warning */
		alert("Deprecated method Menu.setHidePause() used: Please use the options argument for Menu.init().");
	}, // function setHidePause

	/**
	 * Initializes the dynamic menu.
	 * 
	 * @param string
	 *            menuId
	 * @param object
	 *            options
	 * 
	 * Available options: - orientation (int; one of: Menu.HORIZONTAL,
	 * Menu.VERTICAL) - showPause (float; in seconds; default: 0.0) - hidePause
	 * (float; in seconds; default: 1.0) - opacity (float; 0 = transparent, 1 =
	 * opaque; default: 1; transparency of the sub menu)
	 * 
	 * Example usage: <script type="text/javascript"> Menu.init("menu",
	 * {"hidePause": 0.5}); </script>
	 * 
	 * This method can be called after document load, but it is preferred to be
	 * called directly from your page (HTML <head>, before document load). This
	 * way the menu loads faster and can be interacted with much sooner.
	 */
	init : function(menuId, options) {
		/* Save menu ID (fall back to the default ID "menu") */
		Menu._menuId = (typeof menuId == "string") ? menuId : "menu";

		/* Save options */
		if (options) {
			/* Orientation */
			if (options.orientation != undefined) {
				Menu._orientation = options.orientation;
			}
			/* Show timeout in seconds */
			if (options.showPause != undefined) {
				Menu._showPause = options.showPause * 1000;
			}
			/* Hide timeout in seconds */
			if (options.hidePause != undefined) {
				Menu._hidePause = options.hidePause * 1000;
			}
			/* Sub menu opacity */
			if (options.opacity != undefined) {
				Menu._opacity = options.opacity;
			}
		}

		/*
		 * Check if the document is already loaded. Prototype 1.6.0 introduces
		 * the document.loaded boolean, for 1.5*, check if we can retrieve an
		 * element from the DOM (fails when document is not loaded).
		 */
		if (document.loaded === true || $(Menu._menuId)) {
			Menu._doInit();
		}
		/* This is how it should work (init called before document load) */
		else {
			/*
			 * Do the actual initialization on document load. The "dom:loaded"
			 * event is preffered, but only available since 1.6.0 (with
			 * document.observe construction). Fall back to the window onload
			 * when not available.
			 */
			if (document.observe) {
				document.observe("dom:loaded", Menu._doInit);
			} else {
				Element.observe(window, "load", Menu._doInit);
			}
		}
	}, // function init

	/**
	 * Initializes the drop down menu.
	 * 
	 * Should be called on page load.
	 */
	_doInit : function() {
		/* After the DOM is loaded, save the menu node */
		Menu._menuNode = $(Menu._menuId);

		/*
		 * Add events to each first level menu node (if any with a submenu). The
		 * Menu._addEvents() method will add events recursively.
		 */
		Menu._addEvents(Menu._menuNode);
	}, // function _doInit

	/**
	 * Recursively attaches events to the menu UL.
	 * 
	 * @param HTMLUListElement
	 *            ulElement
	 * @param int
	 *            level
	 */
	_addEvents : function(ulElement, level) {
		/* If level argument is not given, */
		if (isNaN(level)) {
			level = 1;
		}

		/* Find all menu nodes */
		var elements = (Element.select) ? ulElement.select("li") : ulElement
				.getElementsBySelector("li")
		for ( var i = 0; i < elements.length; i++) {
			/* Only use the direct descendants (we're using recursion) */
			if (elements[i].parentNode == ulElement) {
				/* Check if it has a sub menu (should return 1 or zero nodes) */
				var subMenus = (Element.select) ? elements[i].select("ul")
						: elements[i].getElementsBySelector("ul");
				if (subMenus.length > 0) {
					/* Add expand listener to the node */
					elements[i].observe("mouseover", Menu._showSubMenu
							.bindAsEventListener(elements[i], level));

					/*
					 * Add collapse listener to the node if it's the first level
					 * (because the LI contains all other submenu's, therefore
					 * any other listeners are over kill).
					 */
					if (level == 1) {
						elements[i].observe("mouseout", Menu._hideSubMenu
								.bindAsEventListener(elements[i], level));
					}

					/* Add "submenu" class for this node link */
					var a = (Element.select) ? elements[i].select("a")[0]
							: elements[i].getElementsBySelector("a")[0];
					a.addClassName("submenu");

					/* Recursion: check if the sub menu has nodes as well */
					Menu._addEvents(subMenus[0], (level + 1));
				}
				/* No sub menu */
				else {
					/* Only hide any expanded sub menu when hovering this node */
					elements[i].observe("mouseover", Menu._quickHideSubMenu
							.bindAsEventListener(elements[i], level));
				}
			}
		}
	}, // function _addEvents

	/**
	 * Shows the sub menu.
	 * 
	 * @param Event
	 *            e
	 * @param int
	 *            level
	 */
	_showSubMenu : function(e, level) {
		/* Don't bubble up */
		Event.stop(e);

		/*
		 * is this the first menu node to be opened (need to check if we should
		 * apply the show timeout )
		 */
		var isFirst = (Menu._activeNodes.length == 0);

		/* Hide previous opened sub menu */
		Menu._quickHideSubMenu(e, level);

		/* Is this the first menu node and do we have a show delay? */
		if (isFirst && Menu._showPause > 0) {
			/* Show in x (mili)seconds */
			Menu._showTimeout = window.setTimeout(function() {
				Menu._doShowSubMenu(this, level);
			}.bind(this), Menu._showPause);
		} else {
			Menu._doShowSubMenu(this, level);
		}
	}, // function _showSubMenu

	/**
	 * Shows the sub menu.
	 * 
	 * @param Event
	 *            e
	 * @param int
	 *            level
	 */
	_doShowSubMenu : function(node, level) {
		/* Clear possible timeout */
		if (Menu._showTimeout) {
			window.clearTimeout(Menu._showTimeout);
		}

		/* Get node link and sub menu */
		var a = (Element.select) ? node.select("a")[0] : node
				.getElementsBySelector("a")[0];
		var subMenu = (Element.select) ? node.select("ul")[0] : node
				.getElementsBySelector("ul")[0];

		/* Keep hover style as long as opened */
		a.addClassName("menu_open");
		/* Since 1.3, the class is also applied to the parent LI */
		a.parentNode.addClassName("menu_open");

		/* Show sub menu */
		subMenu.style.visibility = "visible";
		subMenu.style.position = "absolute";

		/*
		 * Set correct position. (Note: the horizontal/vertical properties only
		 * apply to the first level nodes. All other levels are fixed vertical.
		 */
		var pos = (Element.positionedOffset) ? node.positionedOffset()
				: Position.positionedOffset(node);
		if (level == 1 && Menu._orientation == Menu.HORIZONTAL) {
			subMenu.style.left = pos[0] + "px";
			subMenu.style.top = (pos[1] + node.getHeight()) + "px";
		} else {
			subMenu.style.left = (pos[0] + node.getWidth()) + "px";
			subMenu.style.top = pos[1] + "px";
		}

		/*
		 * Apply opacity if not fully opaque. (Apply for sub menu of first level
		 * only, because otherwise opacity would be doubled.)
		 */
		if (level == 1 && Menu._opacity > 0 && Menu._opacity < 1) {
			subMenu.setOpacity(Menu._opacity);
		}

		/* Save submenu */
		Menu._activeNodes.push( {
			"level" : level,
			"link" : a,
			"subMenu" : subMenu
		});
	}, // function _doShowSubMenu

	/**
	 * Immediately hides the active menu.
	 * 
	 * @param Event
	 *            e
	 * @param int
	 *            level
	 */
	_quickHideSubMenu : function(e, level) {
		/* Don't bubble up */
		Event.stop(e);

		/* Clear possible timeout */
		if (Menu._hideTimeout) {
			window.clearTimeout(Menu._hideTimeout);
		}

		/* And hide the open menus from the given level up */
		Menu._doHideSubMenusFromLevel(level);
	}, // function _quickHideSubMenu

	/**
	 * Method for hiding all sub menus.
	 * 
	 * Triggered onmouseout of first sub menu (level 2).
	 * 
	 * @param Event
	 *            e
	 * @param int
	 *            level
	 */
	_hideSubMenu : function(e, level) {
		/* Don't bubble up */
		Event.stop(e);

		/* if hiding (lost focus on first level), do not show! */
		if (Menu._showTimeout) {
			window.clearTimeout(Menu._showTimeout);
		}

		/* No pause? Don't use the timeout */
		if (Menu._hidePause <= 0) {
			/* Hide all sub menus */
			Menu._doHideSubMenusFromLevel(1);
		} else {
			/* Hide in x (mili)seconds */
			Menu._hideTimeout = window.setTimeout(function() {
				Menu._doHideSubMenusFromLevel(1);
			}, Menu._hidePause);
		}
	}, // function _hideSubMenu

	/**
	 * Hides all active sub menus from the given level.
	 * 
	 * @param int
	 *            level (Default: 1)
	 * 
	 * @return boolean
	 */
	_doHideSubMenusFromLevel : function(level) {
		/* If level argument is not given, default to 1 */
		if (isNaN(level)) {
			level = 1;
		}

		/* Remove these sub menus from the array */
		Menu._activeNodes = Menu._activeNodes.findAll(function(node) {
			/*
			 * Hide all sub menus with a level equal or higher than the given
			 * level, and return false to remove these from the array.
			 */
			if (node.level >= level) {
				/* Remove hover style */
				if (node.link) {
					node.link.removeClassName("menu_open");
					node.link.parentNode.removeClassName("menu_open");
				}
				/* Hide sub menu */
				if (node.subMenu) {
					node.subMenu.style.visibility = "hidden";
				}
				/* Return false to remove node from the array */
				return false;
			}
			/* Return true for the other nodes, keeping them in the array */
			return true;
		});
	} // function _doHideSubMenusFromLevel

}; // class Menu
