/*
** This file contains three classes:
**
** Abl.UI.MenuManager		Collectively manages instances of Abl.UIU.Clamshell and Abl.UI.Menu
** Abl.UI.Clamshell			Manages a clamshell navigation control instance
** Abl.UI.Menu					Manages a horizontal or vertical drop-down menu instance
**
** This menu was inspired by the jQuery.jdMenu by Jonathan Sharp (http://jdsharp.us),
** particulary his technique for calculating the ul.subGroup sizes and his
** positionBy() method.
**
** 30.9.2009					Added vertical/horizontal menu options to the MenuManager
**									class and introduced the 'disableAction' option.
*/

// JSLint
// var Abl, $, jQuery, alert, setTimeout, clearTimeout;



/*******************************************************************************************
* Abl.UI.BaseMenu                                                                          *
*******************************************************************************************/
/*
** Provides a base class and common properties and functionality for the Abl.UI.Menu and
** Abl.UI.Clamshell classes.
*/
Abl.UI.BaseMenu = function(menu, options) {

	return (function(menu, options) {
	
		var	foo = {};
				foo.$menu = ((menu instanceof jQuery) ? menu : $(menu)).eq(0);			// Only one menu at a time!
				foo.$rootGroup = foo.$menu.children("ul.rootGroup");
				foo.$parentItems = foo.$menu.find("li.menuItem.hasChildren");			// All menu items with children
				foo.params = $.extend(true, {}, Abl.UI.BaseMenu.defaults, options);


		/*******************************************************************************************
		* Link Mapper                                                                              *
		*******************************************************************************************/
		// Applies 'location sensitive' context attributes to the li.menuItem/a.link elements
		// based on their relevance to the current page location.  The class attributes applied
		// are as follows:
		//
		//	'folderMatch'		=> The href folder matches the current page's folder
		//	'exactMatch'		=> The href path matches the current page's path
		//	'targetTrail'		=>	A descendent element is an 'exactMatch'
		foo.mapLocation = function() {
			var	pageUri = new Abl.Uri(),		// Get the uri of the current page (window.location)
					linkUri = new Abl.Uri(),		// Use to store the uri of each a.link element
					pagePath = pageUri.getPath(),
					$link, href;

			$("a.link", foo.$menu).each(function() {
				$link = $(this);
				href = $link.attr("href");
				if ((href) && (href.length > 0)) {
					// See if the link href folder matches that of the current page.  Note, it 
					// doesn't make any sense to match on just the root folder - so we don't!
					linkUri.setUri(href);
					if ((linkUri.getFolder()) &&  (linkUri.compare(pageUri, "folder", true))) {
						if ((pageUri.getFolder().length > 1) && (linkUri.getFolder().length > 1)) {
							$link.parent("li.menuItem").andSelf().addClass("folderMatch");
						}
						
						// While we are here, check to see if we have an exact match and, if
						// we do, build a 'targetTrail' for the ancestral hierarchy - note that
						// we also have to handle the situation when only the path is specified
						// and the web server loads the default page...
						// www.abldev.com/contactus => www.abldev.com/contactus/default.aspx
						if (
							((pagePath.substr(pagePath.length - 1, 1) === "/") && (linkUri.getFile().toLowerCase() === "default.aspx" ) && (linkUri.compare(pageUri, "folder", false))) || (linkUri.compare(pageUri, "path"))
						) {
							$link.parent("li.menuItem").andSelf().addClass("exactMatch");
							$link.parent("li.menuItem").parents("li.menuItem").each(function() {
								$(this).children("a.link").andSelf().addClass("targetTrail");
							});
						}
					}
				}
			});
		};



		/*******************************************************************************************
		* Event Management                                                                         *
		*******************************************************************************************/
		foo.wireEvents = function() {
			throw "Do not inherit from Abl.UI.BaseMenu.wireEvents()!";
		};



		/*******************************************************************************************
		* Object Initialisation                                                                    *
		*******************************************************************************************/
		foo.init = function() {
			if (foo.params.mapLocation) {
				foo.mapLocation();
			}
			foo.wireEvents();
		};



		/*******************************************************************************************
		* Object Disposal                                                                          *
		*******************************************************************************************/
		foo.dispose = function() {
			foo.$parentItems = null;
			foo.$rootGroup = null;
			foo.$menu = null;
		};


		return foo;
	}(menu, options));
};

Abl.UI.BaseMenu.defaults = {
	mapLocation: true
};





/*******************************************************************************************
* Abl.UI.Menu (Horizontal and Vertical)                                                    *
*******************************************************************************************/

Abl.UI.Menu = function(menu, options) {

	return (function(menu, options) {

		var	base = {},
				foo = Abl.UI.BaseMenu(menu, $.extend(true, {}, Abl.UI.Menu.defaults, options));


		/*******************************************************************************************
		* Helper Methods                                                                           *
		*******************************************************************************************/
		
		// clearTimer()
		// 
		// Utility method for clearing the timer event associated
		// with the ul.subGroup's expando data attribute.
		function clearTimer(subGroup) {
			if (subGroup.timerId)
			{
				clearTimeout(subGroup.timerId);
				subGroup.timerId = null;
			}
		}



		// setPosition()
		// 
		// Correctly positions the div.subGroupCanvass and its child ul.subGroup
		// in relation to their parent li.menuItem.  The technique is as follows:
		// 
		// 1) Normalise their css properties - we need a known/constant element
		//    profile to work with.
		// 2) Position the elements in a negative z,y quadrant so we can make
		//    the 'visible' (though unseen by the user).
		// 3) Obtain the dimensions of the bounding/enclosing rectangle - we have
		//    to play some tricks with the inner li elements here to keep IE6 happy.
		// 4) Optionally hide the elements.
		// 5) Move them into their correct/final positions.
		//
		// Parameters:
		// $canvass		div.subGroupCanvass element
		// params		{
		//						x: 120			-- horizonatl offset from parent li.menuItem
		//						y: 14				-- vertical offset from parent li.menuItem
		//						hide: true		-- hide/re-hide elements of positioning...
		//					}						-- calling method will apply animation effects
		function setPosition($canvass, params) {
			var	$subGroup = $canvass.children("ul.subGroup"),
					ulWidth = 0, ulHeight = 0, ulOuterWidth = 0, ulOuterHeight = 0;
			
			// Normalise the div.subGroupCanvass and the ul.subGroup elements
			$canvass.css({
				"display": "block",
				"position": "absolute",
				"left": "-5000px",		// Position way over to the top/left to avoid triggering scrollbars
				"top": "-5000px",
				"width": "1000px",		// Make sure the canvass is big enough to avoid constraining the subGroup
				"height": "1000px",
				"margin": "0",
				"padding": "0"
				// "background-color": "#000"
			}).show();
			
			$subGroup.css({
				"display": "block",
				"position": "absolute",
				"left": "0",
				"top": "0",
				"width": "auto",
				"height": "auto"
			}).show();

			// The important bit! Now they are both 'visible', we can interrogate their 
			// overall dimensions!  Note, we need the outerWidth/outerHeight to size
			// the background div.subGroupCanvass.
			ulWidth = $subGroup.width();
			ulHeight = $subGroup.height();
			ulOuterWidth = $subGroup.outerWidth(true);
			ulOuterHeight = $subGroup.outerHeight(true);

			// Abl.DEBUG.trace("width: " + ulWidth + "px, outerWidth: " + ulOterWidth + "px");
			// Abl.DEBUG.trace("height: " + ulHeight + "px, outerHeight: " + ulOuterHeight + "px");

			// Typically, li.menuItems are floated, so their widths shrink to accomodate
			// their descendant content - which is exactly what we want to happen (IE6
			// has a tendency to allow the li.menuItem element to grow to the width of
			// screen!).  The containing ul.subGroup element will therefore be sized to
			// accommodate the widest li.menuItem. The only problem with this is that if
			//  background styling is applied to the li.menuItems, it will fall short of
			// the full width and will be displayed as a 'ragged' right edge.
			// 
			// Since we now know the width of the ul.subGroup, we can do interrogate the
			// overall width of the li.menuItems, do some math, and fix accordingly! This
			// way all li.menuItems should a) be the same width and b) fill the 
			// ul.subGroup - perfect!
			$subGroup.children("li.menuItem").each(function() {
				var	$li = $(this),
						liWidth = $li.width(),
						liOuterWidth = $li.outerWidth(true),
						delta = ulWidth - liOuterWidth;
						
				// Abl.DEBUG.trace(liWidth + "/" + liOuterWidth + " in " + ulWidth + " (delta: " + delta +")");
				$li.width(liWidth + delta);
			});

			// Now, in reverse order, update the ul.subGroup and the div.subGroupCanvass...
			$subGroup.css({
				"display": (params.hide) ? "none" : "block"
			});

			$canvass.css({
				"display": (params.hide) ? "none" : "block",
				"width": ulOuterWidth + "px",
				"height": ulOuterHeight + "px",
				"left": params.x + "px",			// Position relative to the parent object
				"top": params.y + "px"
			}).data("ablMenu", {						// Add an expando flag to indicate that this
				hasLayout: true						// div.subGroupCanvass has been positioned
			});											// correctly for layout.
		}



		/*******************************************************************************************
		* Event Handlers                                                                           *
		*******************************************************************************************/
		foo.wireEvents = function()
		{
			// Add a class='hover' attribute to the a.link's parent li.menuItem element
			// as this will make life easier when formatting is applied to the li.menuItem
			foo.$menu.find("a.link").hover(function() {
				$(this).parent().addClass("hover");
			}, function() {
				$(this).parent().removeClass("hover");
			});


			// Provide an option for disabling the dynamic mouseenter/mouseleave events.  This gives
			// a fully styled and tagged menu structure withoput the expand and collpase effects which
			// can be useful when creating simple navigation structures (like OriginAM).
			if (!foo.params.disableAction) {
				// Mouseenter - the mouse is over a li.menuItem that has children, so
				// we need to expand the next menu level
				foo.$parentItems.mouseenter(function() {
					var	$item = $(this),												// li.menuItem
							$link = $item.children("a.link"),						// a.link
							$canvass = $item.children("div.subGroupCanvass"),	// div.subGroupCanvass
							canvassData = $canvass.data("ablMenu"),
							$subGroup = $canvass.children("ul.subGroup");		// div.subGroupCanvass > ul.subGroup

					clearTimer(this);
					this.timerId = setTimeout(function() {
						// It's possible that, even with the timer, this event is being run
						// even though animation from a previous event has already started.
						// By checking the existing state, we can prevent the menu 'hunting'
						// backwards and forwards.
						if ($subGroup.is(":visible")) { return; }
						
						// When you come to think about it, you only have to set the position of
						// each sub-menu once - the first time it is displayed.  After that, it's
						// just a simple matter of making in visible/invisible in-situ.
						// Conveniently, we get the setPosition() method to set an expando flag
						// against the div.subGroupCanvass element and here we just need to check
						// to see if it has been set or whether we are dealing a 'first instance'
						// display situation.
						if ((!canvassData) || (!canvassData.hasLayout)) {
							// Position the ul.subGroup relative to its parent.  We calculate the
							// absolute left,top offset from the parent li.menuItem as the
							// div.subGroupCanvass and it's ul.subGroup are absolutely positioned
							// relative to their parent li.menuItem.
							if ((foo.$menu.is(".horizontal")) && ($subGroup.is(".level1"))) {
								// In a horizontal menu, the first sub-menu's north-west corner
								// is positioned near to the south-west corner of its parent
								setPosition($canvass, {
									x: foo.params.xOffset,
									y: ($item.outerHeight() - foo.params.yOffset),
									hide: true
								});
							} else {
								// In all other instances, the sub-menu's north-west corner is
								// positioned near to the north-east corner of its parent
								setPosition($canvass, {
									x: ($item.outerWidth() - foo.params.xOffset),
									y: foo.params.yOffset,
									hide: true
								});
							}
						}
						// Dynamically apply css class attributes to this li.menuItem and
						// children.  Note the creation of a local z-index 'context stack'
						// this will ensure the expanded items overlay the rest of the menu
						// structure - even in IE 6!  Also note that we add the 'expanded'
						// class attribute directly to the a.link element. This makes life
						// easier when styling for browsers that do not support the 
						// 'immediate child' selector (e.g. li.menuItem > a.link).
						$item.addClass("expanded").css({"z-index": "1"});
						$link.addClass("expanded");
						$canvass.css({"z-index": "2"}).show();
						if (typeof jQuery.fn.bgiframe === "function") { $canvass.bgiframe(); }
						$subGroup.css({"z-index": "3"}).show(foo.params.expandSpeed);
					}, foo.params.expandDelay);
				});



				// Mouseleave - the mouse has left an expanded li.menuItem so we need to collapse
				// the expanded sub-menu items
				foo.$parentItems.mouseleave(function() {
					var	$item = $(this),												// li.menuItem
							$link = $item.children("a.link"),						// a.link
							$canvass = $item.children("div.subGroupCanvass"),	// div.subGroupCanvass
							$subGroup = $canvass.children("ul.subGroup");		// ul.subGroup

					clearTimer(this);
					this.timerId = setTimeout(function() {
						// It's possible that, even with the timer, this event is being run
						// even though animation from a previous event has already started.
						// By checking the existing state, we can prevent the menu 'hunting'
						// backwards and forwards.
						if (!$subGroup.is(":visible")) { return; }
						
						// Remember, if collapseSpeed is zero, the callback function
						// never gets called!
						$subGroup.hide(foo.params.collapseSpeed + 1, function() {
							$(this).css({"z-index": "0"});
							$canvass.hide().css({"z-index": "0"}).prev("iframe").remove();
							$link.removeClass("expanded");
							$item.removeClass("expanded").css({"z-index": "0"});
						});
					}, foo.params.collapseDelay);
				});
			}	// if (!foo.params.disableAction)
		};

		// The following code could be used if it becomes
		// necessary to remove the div.clearfix element from the menu structure
		// base.init = foo.init;
		// foo.init = function() {
		//		base.init();
		//		foo.$menu.height(foo.$rootGroup.outerHeight(true));
		// };

		/*******************************************************************************************
		* Object Disposal                                                                          *
		*******************************************************************************************/
		base.dispose = foo.dispose;
		foo.dispose = function() {
			$("a.link", foo.$menu).unbind();										// Remove 'hover' event
			$("div.subGroupCanvass", foo.$menu).removeData("ablMenu");	// Remove 'expando' hasLayout data from setPosition()
			foo.$parentItems.unbind().each(function() {						// Remove 'mouseenter'/'mouseleave' events
				clearTimer(this);														// Clear and remove li.menuItem.timerId expando
			});
			base.dispose();
		};


		return foo;
	}(menu, options));
};

Abl.UI.Menu.defaults = {
	expandSpeed: 200,
	collapseSpeed : 300,
	expandDelay: 150,
	collapseDelay : 250,
	xOffset: 5,
	yOffset: 3
};





/*******************************************************************************************
* Abl.UI.Clamshell                                                                         *
*******************************************************************************************/
// Notes:
// Because IE6 cannot handle the $.slideDown()/$.slideUp() methods correctly, we have to
// resort to using a custom height animation.  We calculate the original heights of the
// ul.subGroup elements and store it as expando data gainst the element.  This way we
// can animate the ul.subGroup element height to zero and restore it to its original
// dimension as requred.  See the captureSate() method in the initialisation section.

Abl.UI.Clamshell = function(menu, options) {
	return (function(menu, options) {
		var	base = {},
				foo = Abl.UI.BaseMenu(menu, $.extend(true, {}, Abl.UI.Clamshell.defaults, options));



		/*******************************************************************************************
		* Expand/Collapse Methods                                                                  *
		*******************************************************************************************/
		foo.collapseAll = function() {
			foo.$menu.find("ul.subGroup.expanded")
			.removeClass("expanded")
			.parent("li.menuItem").removeClass("expanded").end()
			.animate({height: 0}, foo.params.collapseSpeed, function() {
				$(this).hide();
			});
		};



		/*******************************************************************************************
		* Event Management                                                                         *
		*******************************************************************************************/
		foo.wireEvents = function() {
			foo.$parentItems.click(function(evt) {
				var	$item = $(this),
						$target = $(evt.target),
						$subGroup = $item.children("ul.subGroup");
				
				// Only animate if we're a section header item (li.hasChjildren) 
				if ($target.parents("li.menuItem:first").hasClass("hasChildren")) {
					evt.preventDefault();
					if ($subGroup.is(":visible")) {	// If it's visible then we need to collapse it
						$item
						.removeClass("expanded")
						.find("ul.subGroup")				// Close ALL expanded children
						.removeClass("expanded")
						.animate({height: 0}, foo.params.expandSpeed, function() {
							$(this).hide();
						});
					} else {
						if (foo.params.autoClose) { foo.collapseAll(); }
						$item.addClass("expanded");
						$subGroup
						.addClass("expanded")
						.animate({height: $subGroup.data("dims").height}, foo.params.expandSpeed);
					}
				}
			});
			
			// Add a class='hover' attribute to the a.link's parent li.menuItem element
			// as this will make life easier when formatting is applied to the li.menuItem
			foo.$menu.find("a.link").hover(function() {
				$(this).parent().addClass("hover");
			}, function() {
				$(this).parent().removeClass("hover");
			});
			
		};



		/*******************************************************************************************
		* Object Initialisation                                                                    *
		*******************************************************************************************/
		function captureState() {
			var	$subGroup;
			
			foo.$parentItems.find("ul.subGroup").each(function() {
				$subGroup = $(this);
				$subGroup
				.data("dims", {height: $subGroup.height()})	// Store original height
				.height(0);		// Set initial height to zero ready for the first expansion
			});
		}


		base.init = foo.init;
		foo.init = function() {
			captureState();
			base.init();
		};



		/*******************************************************************************************
		* Object Disposal                                                                          *
		*******************************************************************************************/
		base.dispose = foo.dispose;
		foo.dispose = function() {
			$("a.link", foo.$menu).unbind();
			foo.$parentItems.unbind().find("ul.subGroup").removeData();
			base.dispose();
		};


		return foo;
	}(menu, options));
};


Abl.UI.Clamshell.defaults = {
	expandSpeed: 600,
	collapseSpeed : 600,
	autoClose: true
};





/*******************************************************************************************
* Abl.UI.MenuManager                                                                       *
*******************************************************************************************/
Abl.UI.MenuManager = function(menu, options) {
	return (function(menu, options) {
		var	foo = {},
				menuStack = [];
				
		// Normalise arguments...
		if ((arguments.length === 1) && (!(arguments[0] instanceof jQuery))) {
			options = arguments[0];
			menu = null;
		}
		
		foo.params = $.extend(true, {}, Abl.UI.MenuManager.defaults, options);
		
		

		/*******************************************************************************************
		* Add Menu Method                                                                          *
		******************************************************************************************/
		foo.add = function(menu, options) {
			var	$menu = (menu instanceof jQuery) ? menu : $(menu);
			$menu.filter(foo.params.menuIdentifier).each(function() {
				var $menu = $(this), ablMenu = null;
				if ($menu.is(foo.params.clamshellClass)) {
					ablMenu = Abl.UI.Clamshell($menu, $.extend(true, {}, foo.params.clamshell, (options) ? options.clamshell || options : null));
				} else if ($menu.is(foo.params.horizontalMenuClass)) {
					ablMenu = Abl.UI.Menu($menu, $.extend(true, {}, foo.params.horizontalMenu, (options) ? options.horizontalMenu || options : null));
				} else if ($menu.is(foo.params.verticalMenuClass)) {
					ablMenu = Abl.UI.Menu($menu, $.extend(true, {}, foo.params.verticalMenu, (options) ? options.verticalMenu || options : null));
				} else {
					throw "Unidentified menu type '" + $menu.attr("class") + "'!";
				}
				menuStack.push(ablMenu);
				ablMenu.init();
			});
		};



		/*******************************************************************************************
		* Object Disposal                                                                          *
		*******************************************************************************************/
		foo.dispose = function() {
			var menu, i;
			for (i = 0; i < menuStack.length; i++) {
				menuStack[i].dispose();
			}
		};



		/*******************************************************************************************
		* Object Initialisation                                                                    *
		*******************************************************************************************/
		if (menu) {
			foo.add(menu);
			// Abl.DEBUG.trace("Abl.UI.MenuManager.init() - " + menuStack.length + " menu(s) added");
		}
		
		return foo;
	}(menu, options));
};

Abl.UI.MenuManager.defaults = {
	menuIdentifier: "div.ablMenu",
	clamshellClass: "div.clamshell",
	verticalMenuClass: "div.vertical",
	horizontalMenuClass: "div.horizontal",
	clamshell: {},
	horizontalMenu: { disableAction: false },
	verticalMenu: { disableAction: false }
};