/******************************************
 * object that manages a menu bar.
 * accepts menuOptions config collection, and menuBtnMenus, collection of menuBtn IDs and their associated menu IDs.
 * hooks up all handlers etc, no other code is required.
 * 
 * @param menuOptions (object, required):
 * 	menuBtnActiveClassName (string, default 'open'): css class to add to menu btn to style it when it's active.
 * 	hideMenusInitially (boolean, default false): true to hide menu when this obj is initialized.
 * 	menuRevealMethod (string, default 'left'): way menus are shown and hidden; see setMenuVisibility().
 * 	doMenuPositioning (boolean, default true): true to position menu w js when showing it; false if handled by css.
 * @param menuBtnMenus (object, required):
 * 	value object w an entry for each menu btn ID, value is ID of menu that that btn should show.
 * 
 * END-USER BEHAVIOR:
 * menuBtns show their associated menu.
 * once a menu is open, can drag across all btns in menu bar, that menu is shown.
 * active menu is closed by any of:
 * 	- clicking the btn that opened it again
 * 	- clicking anywhere else besides a menuBtn in this menu bar
 * 	- pressing any key
 * 	- deactivating the window
 * 	NOTE: "blur" handler and its setup is different for IE than other browsers
 * 
 * HOW THIS WORKS:
 * an instance of this obj is created for each menu it's used for
 * when instantiated, it adds click handlers to all configured menuBtns, which open their associated menu.
 * opening a menu adds other handlers, which are removed when menu is closed:
 * 	- document click and key handlers close active menu.
 * 	- mouseover handlers on all menuBtns except active one close active menu and open menu associated w that btn
 * each menuBar instance tracks which menu in its menu bar is active, if any.
 * cs_MenuBarActive global var in 'top' scope tracks which menuBar obj is is active, if any.
 * 	hiding a menu calls the public hideActiveMenu() method of the active menuBar obj, which closes its active menu.
 * 	done that way because handlers that have to be removed are private to each menuBar obj, so removal has to be done by owning object.
 * 
 * NOTE: initialization code is at the end.
 * 
 * NOTE: this contains private versions of various utilities also used elsewhere.
 * 	this is used in different contexts, which currently don't share any common utility collections.
 *****************************************/
function cs_MenuBar(menuOptions, menuBtnMenus)
{	
	/******************************************
	 * EVENT HANDLERS
	 *****************************************/

	/**
	 * handles click on menu btn, shows menu associated w this btn
	 * @param e (event, optional)
	 */
	function menuBtnClick(e)
	{
		var menu;
		var evt = getEvent(e);
		stopEvent(evt);
		// look up DOM tree til we hit actual menu btn or body element 
		var menuBtn = evt.target;
		while(!getMenuForBtn(menuBtn.id) && menuBtn.nodeName != 'body')
			menuBtn = menuBtn.parentNode;
		// if a menu btn was found, show that menu
		var menu = getMenuForBtn(menuBtn.id);
		if(menu)
		{
			var activeMenu = getActiveMenu();
			if(menu == activeMenu)
				hideMenu(menu);
			else
				showMenu(menu.id, menuBtn);
		}
		evt.target.blur();
		return true;
	}
	/**
	 * handles mouseover on menu btn when a menu is active, shows menu associated w this btn 
	 * @param e (event, optional, not passed by IE, as usual)
	 */
	function menuBtnMouseOver(e)
	{
		var evt = getEvent(e);
		var activeMenuBar = getActiveMenuBar();
		var menuBtn = evt.target;
		var menu = getMenuForBtn(menuBtn.id);
		// look up dom tree til we find a real menu btn (has an assoc menu bar), we hit this menu bar, or worst case, have nowhere further to go
		while(!menu && menuBtn != activeMenuBar && menuBtn.parentNode)
		{
			menuBtn = menuBtn.parentNode;
			menu = getMenuForBtn(menuBtn.id);
		}
		// if btn has an assoc menu (ie, really a menu btn), and a menu is active but not the one for this btn (should be), show assoc menu
		if(menu)
		{
			var activeMenu = getActiveMenu();
			if(activeMenu && menu != activeMenu)
				showMenu(menu.id, menuBtn, evt);
		}
		return true;
	}
   /**
   * handles mouseout on menu btn when a menu is active.
   */
   function menuBtnMouseOut(e)
   {
		var evt = getEvent(e);
		var activeMenu = getActiveMenu();
		var activeMenuBar = getActiveMenuBar();
		var menuBtn = evt.target;
		var menu = getMenuForBtn(menuBtn.id);
		// look up dom tree til we find a real menu btn (has an assoc menu bar), we hit this menu bar, or worst case, have nowhere further to go
		while(!menu && menuBtn != activeMenu && menuBtn.parentNode)
		{
			menuBtn = menuBtn.parentNode;
			menu = getMenuForBtn(menuBtn.id);
		}
		// if btn has an assoc menu (ie, really a menu btn), and a menu is active but not the one for this btn (should be), show assoc menu
		if(menu && !activeMenu)
         hideActiveMenuBar();
      return true;
   }
   
	/**
	 * tells active menu bar, if there is one, to hide its active menu, if there is one
	 */
	function hideActiveMenuBar()
	{
		var activeMenuBar = getActiveMenuBar();
		if(activeMenuBar)
			activeMenuBar.hideActiveMenu();
		hideForMenus(true);	
		return true;
	}
	
	/******************************************
	 * MENU SHOW AND HIDE METHODS
	 *****************************************/
	
	/**
	 * shows requested menu
	 * @param menuID (int, required): ID of menu to show
	 * @param menuBtn (DOM object, required): menu btn that opens this menu
	 */
	function showMenu(menuID, menuBtn)
	{
		menuBtn.blur();
		var activeMenu = getActiveMenu();
		// bail if req menu is already active
		if(activeMenu && menuID == activeMenu.id)
			return;
		// hide any menu that's already active, for any menu bar
		hideActiveMenuBar();
		// get ref to menu by its id
		var menu = document.getElementById(menuID);
		// track that it's the active menu
		setActiveMenu(menu);
		// store a reference to this menu's btn in menu itself, so we can style btn as inactive when menu gets hidden
		menu.menuBtn = menuBtn;
		// position the menu and make it visible
		setMenuPosition(menu, menuBtn);
		setMenuVisibility(menu, true);
		// style the menu btn to show it's active
		addClassName(menuBtn, menuBtnActiveClassName);
		// install event handlers used while a menu is open
		addActiveMenuEventHandlers(menu);
		hideForMenus(true);
	}
	/**
	 * hides requested menu
	 * @param menuID (int, required): ID of menu to hide
	 */
	function hideMenu(menu)
	{
		// hide menu
		setMenuVisibility(menu, false);
		// style the menu btn to show it's not active any more
		removeClassName(menu.menuBtn, menuBtnActiveClassName);
		// remove event handlers used while a menu is open
		removeActiveMenuEventHandlers();
		hideForMenus(false);
		// track that there's no active menu any more
		setActiveMenu(false);
	}
	/**
	 * public method, hides active menu if there is one
	 */
	this.hideActiveMenu = function()
	{
		var activeMenu = getActiveMenu();
		if(activeMenu)
			hideMenu(activeMenu);
		return true;
	}
	
	/******************************************
	 * EVENT HANDLER INSTALLATION
	 *****************************************/
	
	/**
	 * installs click handlers for objs configured to be menu btns; handler shows its associated menu
	 */
	function addBtnClickHandlers()
	{
		for(var menuBtnID in menuBtnMenus)
		{
			var menuBtn = document.getElementById(menuBtnID);
			if(menuBtn)
				addEventListener(menuBtn, 'click', menuBtnClick.bind(this), false);
		}
	}
	/**
	 * installs event handlers used when a menu is active
	 * btn mouseover handlers show their associated menu, so drag across menu bar while it's active shows each menu
	 * document click and keydown handlers hide active menu bar
	 * window blur hides active menu bar too; hides when switching to another window, or more importantly, iframe
	 */
	function addActiveMenuEventHandlers(activeMenu)
	{
		addBtnMouseoverHandlers(activeMenu);
		addEventListener(document, 'click', hideActiveMenuBar, false);
		addEventListener(document, 'keydown', hideActiveMenuBar, false);
		if(self.isIE)
		{
			addEventListener(document.documentElement, 'focusout', onFocusOutHandler, false);
			addEventListener(document.documentElement, 'focusout', hideForMenus, false);
		}	
		else
		{
			addEventListener(window, 'blur', hideActiveMenuBar, false);
			addEventListener(window, 'blur', hideForMenus, false);
		}	
	}
	function onFocusOutHandler(evt)
	{
		// don't kill menu when you click links in it
		// also, toElement is null if click is in IE debugger or another tab or app, errors or crashes IE
		// 	when IE doesn't crash or error, click after that isn't handled right, so close then too
		if((!(evt.toElement && evt.toElement.nodeName)) || evt.toElement.nodeName != 'A')
		{
			hideActiveMenuBar();
		}
		hideForMenus(false);
	}	
	/**
	 * uninstalls event handlers used when a menu is active
	 * see addActiveMenuEventHandlers for handler info
	 */
	function removeActiveMenuEventHandlers()
	{
		removeBtnMouseoverHandlers();
		removeEventListener(document, 'click', hideActiveMenuBar, false);
		removeEventListener(document, 'keydown', hideActiveMenuBar, false);
		if(self.isIE)
			removeEventListener(document.documentElement, 'focusout', onFocusOutHandler, false);
		else
			removeEventListener(window, 'blur', hideActiveMenuBar, false);
	}
	/**
	 * loops through objs configured as menu btns, adds mouseover and mouseout handers to show associated menu
	 */
	function addBtnMouseoverHandlers(activeMenu)
	{
		for(var menuBtnID in menuBtnMenus)
		{
			if(getMenuForBtn(menuBtnID) != activeMenu)
			{
				if(!mouseoverHandlers[menuBtnID])
					mouseoverHandlers[menuBtnID] = menuBtnMouseOver.bind(self);
            if(!mouseoutHandlers[menuBtnID])
               mouseoutHandlers[menuBtnID] = menuBtnMouseOut.bind(self);

				var menuBtn = document.getElementById(menuBtnID);
				if(menuBtn)
            {
					addEventListener(menuBtn, 'mouseover', mouseoverHandlers[menuBtnID], false);
               addEventListener(menuBtn, 'mouseout', mouseoutHandlers[menuBtnID], false);
            }   

			}
		}
	}
	/**
	 * loops through objs configured as menu btns, removes mouseover and mouseout handers installed when menu was activated
	 */
	function removeBtnMouseoverHandlers()
	{
		for(var menuBtnID in menuBtnMenus)
		{
			var menuBtn = document.getElementById(menuBtnID);
			if(menuBtn && mouseoverHandlers[menuBtnID])
				removeEventListener(menuBtn, 'mouseover', mouseoverHandlers[menuBtnID], false);
			if(menuBtn && mouseoutHandlers[menuBtnID])
				removeEventListener(menuBtn, 'mouseout', mouseoutHandlers[menuBtnID], false);
		}
	}
	
	/******************************************
	 * CURRENTLY ACTIVE MENU TRACKING
	 * get and set active menu bar, and active menu for this bar
	 *****************************************/
	
	/**
	 * sets var that tracks active menu
	 * @param menu (object or null, required): active menu dom obj if a menu is active, else null
	 */
	function setActiveMenu(menu)
	{
		activeMenu = menu;
		setActiveMenuBar(menu ? self : null);
	}
	/**
	 * returns currently active menu object, null if none
	 */
	function getActiveMenu()
	{
		return activeMenu;
	}
	
	/**
	 * sets var that tracks active menu bar
	 * @param menuBar (object or null, required): active menu bar obj if one is active, else null
	 */
	function setActiveMenuBar(menuBar)
	{
		top.cs_MenuBarActive = menuBar;
	}
	/**
	 * returns currently active menu bar object, null if none
	 */
	function getActiveMenuBar()
	{
		return top.cs_MenuBarActive;
	}
	
	/*****************************************
	 * MENU DISPLAY AND POSITIONING
	 *****************************************/
	
	/**
	 * if this obj is configured to do so, positions menu in relation to menu btn and avail screen space
	 * position at bottom left corner of btn; if it doesn't fit horizontally, show at bottom right
	 * @param menu (DOM obj, required): menu to adjust location of
	 * @param menuBtn (DOM obj, required): menuBtn to position menu in relation to
	 * @required doMenuPositioning (boolean): instance var set from this config; see comment at start
	 */
	function setMenuPosition(menu, menuBtn)
	{
		if(!doMenuPositioning)
			return;
		var menuLocation = getCoordinates(menu);
		var btnLocation = getCoordinates(menuBtn);
		var viewportSize = getViewportSize();
		menu.style.position = 'absolute'; // in case a stylesheet has wrongly spec'd or omitted it
		menu.style.top = (btnLocation.top + btnLocation.height) + 'px';
		if(btnLocation.left + menuLocation.width < viewportSize.width)
			menu.style.left = btnLocation.left + 'px';
		else
			menu.style.left = (btnLocation.right - menuLocation.width + 1) + 'px';
	}
	/**
	 * makes menu visible or not as requested, using method this obj is configured to use
	 * @param menu (DOM obj, required): menu to show or hide
	 * @param show (boolean, required): true to show menu, false to hide it
	 * @required menuRevealMethod (string): instance var w this config; see comment at start
	 * 	'left': hides by moving offscreen to left, shows by restoring to left=0
	 * 	'block': hides by setting display=none, restores w display=block
	 * 	'inline': same, but restores w display=inline
	 */
	function setMenuVisibility(menu, show)
	{
		if(menu)
		{
			hideForMenus(show);
			if(show)
			{
				if(menuRevealMethod == 'left')
					menu.style.left = 0;
				else if(menuRevealMethod == 'block')
					menu.style.display = 'block';
				else if(menuRevealMethod == 'inline')
					menu.style.display = 'inline';
			}
			else
			{
				if(menuRevealMethod == 'left')
					menu.style.left = '-100em';
				else if(menuRevealMethod == 'block')
					menu.style.display = 'none';
				else if(menuRevealMethod == 'inline')
					menu.style.display = 'none';
			}
		}
	}
	
	/******************************************
	 * MISC
	 *****************************************/
	
	/**
	 * returns menu associated w passed menuBtnID, based on menuBtnMenus passed when this obj is created
	 */
	function getMenuForBtn(menuBtnID)
	{
		var menuID = menuBtnMenus[menuBtnID];
		return document.getElementById(menuID);
	}
	/**
	 * shows or hides css class 'cpHideForMenus', to hide object elements that would appear in fron of menu
	 * @param {hide} boolean: true to hide
	 */
	function hideForMenus(hide)
	{
		var wasDeferred = typeof hide != 'boolean'; // hide is boolean unless called from setTimeout
		if(hide || wasDeferred)
		{
			if(wasDeferred)
				hide = getActiveMenu();
			var prop = hide ? 'hidden' : 'visible';
			commonspot.util.css.setHideForMenusForAllFrames('cs_maincss','cpHideForMenus','visibility',prop);
			//commonspot.util.css.setStyleRuleProperty('cs_maincss', 'cpHideForMenus', 'visibility', prop);
		}
		else // showing; wait a bit, so objects don't flash at boundary between menus
			setTimeout(hideForMenus, 100);
	}
	
	
	/******************************************
	 * UTILITIES
	 *****************************************/
	
	/**
	 * returns function bound w a reference to passed object
	 */
	Function.prototype.bind = function (object)
	{
		var method = this;
		var oldArguments = toArray(arguments).slice(1);
		return function()
		{
			var newArguments = toArray(arguments);
			return method.apply(object, oldArguments.concat(newArguments));
		};
	};
	
	/**
	 * cross-browser event, event listener, and event stop abstractions
	 */
	function getEvent(e)
	{
		if(!e)
			var e = window.event;
		if((!e.target) && e.srcElement)
			e.target = e.srcElement;
		if (e.target && e.target.nodeType == 3) // defeat Safari bug
			e.target = e.target.parentNode;
		e.destTarget = e.toElement || e.relatedTarget;
		return e;
	}
	function addEventListener(element, eventType, handler, capture)
	{
		try
		{
			if(element.addEventListener)
				element.addEventListener(eventType, handler, capture);
			else if(element.attachEvent)
				element.attachEvent('on' + eventType, handler);
		}
		catch (e) {}
	}
	function removeEventListener(element, eventType, handler, capture)
	{
		try
		{
			if(element.removeEventListener)
				element.removeEventListener(eventType, handler, capture);
			else if(element.detachEvent)
				element.detachEvent('on' + eventType, handler);
		}
		catch (e) {}
	}
	function stopEvent(event)
	{
		if(event.preventDefault)
		{
			event.preventDefault();
			event.stopPropagation();
		}
		else
		{
			event.returnValue = false;
			event.cancelBubble = true;
		}
	}
	
	/**
	 * cross-browser add/remove css class for an obj
	 */
	function addClassName(elem, className)
	{
		if((!elem.className) || elem.className == '')
			elem.className = className;
		else if(!elem.className.match(new RegExp('\\b' + className + '\\b', 'i')))
			elem.className += ' ' + className;
	}
	function removeClassName(elem, className)
	{
		var re = new RegExp('\\b' + className + '\\b *', 'gi');
		elem.className = elem.className.replace(re, '').replace(/\s+$/, '');
	}
	
	/**
	 * returns left, top, width and height of requested obj, even if currently hidden
	 */
	function getCoordinates(obj)
	{
		var oldVisibility = obj.style.visibility;
		var oldDisplay = obj.style.display;
		obj.style.visibility = 'hidden';
		obj.style.display = '';
		var coords = getPosition(obj);
		coords.width = obj.offsetWidth;
		coords.height = obj.offsetHeight;
		coords.right = coords.left + coords.width;
		coords.bottom = coords.top + coords.height;
		obj.style.display = oldDisplay;
		obj.style.visibility = oldVisibility;
		return coords;
		
		/**
		 * returns left and top position of requested obj
		 * private to getCoordinates, which handles case when obj is currently hidden
		 */
		function getPosition(obj)
		{
			var top = 0, left = 0;
			if(obj.offsetParent)
			{
				left = obj.offsetLeft;
				top = obj.offsetTop;
				while(obj = obj.offsetParent)
				{
					left += obj.offsetLeft;
					top += obj.offsetTop;
					if((!obj.offsetParent) || obj.offsetParent.nodeName == 'BODY')
						break;
				}
			}
			return{left: left, top: top};
		}
	}
	/**
	 * cross-browser viewport measurement
	 */
	function getViewportSize()
	{
		var w, h;
		if(self.innerHeight)
		{
			w = self.innerWidth;
			h = self.innerHeight;
		}
		else if(document.documentElement && document.documentElement.clientHeight)
		{
			w = document.documentElement.clientWidth;
			h = document.documentElement.clientHeight;
		}
		else if(document.body)
		{
			w = document.body.clientWidth;
			h = document.body.clientHeight;
		}
		return {width: w, height: h};
	}
	
	/**
	 * converts an array-like object to a real array
	 */
	function toArray(pseudoArray)
	{
		var result = [];
		for (var i = 0; i < pseudoArray.length; i++)
			result.push(pseudoArray[i]);
		return result;
	}
	
	/**
	 * next section is a clone of some css class manipulation code that's in util.js
	 * that's not available in read mode, so we test if it exists, create needed heirarchy and functions if needed
	 */
	if(typeof commonspot == 'undefined')
		commonspot = {};
	if(typeof commonspot.util == 'undefined')
		commonspot.util = {};
	if(typeof commonspot.util.css == 'undefined')
	{
		/**
		 * commonspot.util.css: package for css-related utilities
		 */
		commonspot.util.css = {};
		
		/**
		 * commonspot.util.css.showHideCSSClass: shows or hides a css class
		 */
		commonspot.util.css.showHideCSSClass = function(show, stylesheetID, targetClass)
		{
			var display = show ? '' : 'none';
			commonspot.util.css.setStyleRuleProperty(stylesheetID, targetClass, 'display', display);
		}
		
		/**
		 * commonspot.util.css.setStyleRuleProperty: sets a requested property of a stylesheet class
		 */
		commonspot.util.css.setStyleRuleProperty = function(stylesheetID, targetClass, propertyName, value)
		{	// note that propertyNames need to use their js-style names, ie, 'whiteSpace', not 'white-space'
			var cssRules, ruleIndex;
			var ss = document.getElementById(stylesheetID);
			if(!ss)
				return;
			if(!ss.sheet) // IE
			{
				ss = document.styleSheets[stylesheetID];
				cssRules = document.styleSheets[stylesheetID].rules;
			}
			else // Firefox
			{
				cssRules = ss.sheet.cssRules;
			}
			ruleIndex = getCSSRuleIndex(cssRules, '.' + targetClass);
			if(ruleIndex != null)
				cssRules[ruleIndex].style[propertyName] = value;
			return;
		
			function getCSSRuleIndex(cssRules, selectorText)
			{
				for(var i = 0; i < cssRules.length; i++)
				{
					if(cssRules[i].selectorText == selectorText)
						return i;
				}
				return null;
			}
		}
	}
	
	
	/******************************************
	 * INITIALIZATION
	 * 
	 * NOTE: all vars declared here are private instance vars of this obj, shared by various methods
	 * 		exception is top.cs_MenuBarActive, which tracks the currently open menu bar object
	 * 		that's done at top window level, so we can hide the active menu regardless of what iframe it's in
	 *****************************************/
	
	// reference to this object that doesn't conflict w 'this' as this window
	var self = this;
	
	self.isIE = (navigator.userAgent.indexOf('MSIE') >= 0);
		
	// init tracking of which menu is active in this menu bar
	var activeMenu = null;
	
	// init tracking of which menu bar is currently active
	if(typeof top.cs_MenuBarActive == 'undefined')
		top.cs_MenuBarActive = null;
	
	// store menu options as private instance vars of this obj
	var menuBtnActiveClassName = menuOptions.menuBtnActiveClassName || 'open';
	var hideMenusInitially = (typeof menuOptions.hideMenusInitially != 'undefined') ? menuOptions.hideMenusInitially : false;
	var menuRevealMethod = menuOptions.menuRevealMethod || 'left'; // left, block, inline
	var doMenuPositioning = (typeof menuOptions.doMenuPositioning != 'undefined') ? menuOptions.doMenuPositioning : true;
	
	// menu specs as private instance vars of this obj
	var menuBtnMenus = menuBtnMenus;
	
	// init storage for bound mouseover handlers, so we only have to bind each one once
	var mouseoverHandlers = {};
   var mouseoutHandlers = {};
	
	// init menu btn click handlers, to show associated menu when a btn is clicked
	addBtnClickHandlers();
}

