/*
 * jdMenu 1.3.beta2 (2007-03-06)
 *
 * Copyright (c) 2006,2007 Jonathan Sharp (http://jdsharp.us)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses.
 *
 * http://jdsharp.us/
 *
 * Built upon jQuery 1.1.1 (http://jquery.com)
 * This also requires the jQuery dimensions plugin
 *
 *
 *
 * Modified 2007-09-17 by Kentucky.gov/Kentucky Interactive (KII)
 * Tweaked menu positioning for KII styled menus, and added custom offsets.
 *
 */
(function($){
	// This will store an element list of all our menu objects
	var jdMenu = [];
	
	// Public methods
	$.fn.jdMenu = function(inSettings) {
		var settings = $.extend({}, arguments.callee.defaults, inSettings);
		return this.each(function() {
			jdMenu.push(this);
			$(this).addClass('jd_menu_flag_root');
			this.$settings = $.extend({}, settings, {isVerticalMenu: $(this).is('.jd_menu_vertical')});
			addEvents(this);
		});
	};
	$.fn.jdMenuShow = function() {
		return this.each(function() {
			showMenuLI.apply(this);
		});
	};
	$.fn.jdMenuHide = function() {
		return this.each(function() {
			hideMenuUL.apply(this);
		});
	};

	// Private methods and logic
	$(window)
		// Bind a click event to hide all visible menus when the document is clicked
		.bind('click', function(){
			$(jdMenu).find('ul:visible').jdMenuHide();
		})
		// Cleanup after ourself by nulling the $settings object
		.bind('unload', function() {
			$(jdMenu).each(function() {
				this.$settings = null;
			});
		});

	// These are our default settings for this plugin
	//  activateDelay:  How long in milliseconds before menu shows if no menus are currently showing
	//  showDelay:  How long in milliseconds before a mouse event will dismiss currently shown menu and display the new one;
	//  hideDelay:  How long in milliseconds before a menu is dismissed if user mouses out and does not call another menu.
	$.fn.jdMenu.defaults = {
		activateDelay: 0,    
		showDelay: 50,
		hideDelay: 550,
		onShow: null,
		onHideCheck: null,
		onHide: null,
		onAnimate: null,
		onClick: null,
/*CHANGED:  Changed default offsets to 0 (Bryce Fields, 2007-09-17 **********************************************************************/
		/*offsetX: 4,
		offsetY: 2,*/
		offsetX: 0,
		offsetY: 0,
		iframe: $.browser.msie
	};
	
	// Our special parentsUntil method to get all parents up to and including the matched element
	$.fn.parentsUntil = function(match) {
		var a = [];
		$(this[0]).parents().each(function() {
			a.push(this);
			return !$(this).is(match);
		});
		return this.pushStack(a, arguments);
	};

	// Returns our settings object for this menu
	function getSettings(el) {
		try {
			return $(el).parents('ul.jd_menu_flag_root')[0].$settings;
		}
		catch (e) { return false; }
	}

	// Unbind any events and then rebind them
	function addEvents(ul) {
		removeEvents(ul);
		$('> li', ul)
			.hover(hoverOverLI, hoverOutLI)
			.bind('click', itemClick)
			.find('> a.accessible')
				.bind('click', accessibleClick);
	};
	
	// Remove all events for this menu
	function removeEvents(ul) {
		$('> li', ul)
			.unbind('mouseover').unbind('mouseout')
			.unbind('click')
			.find('> a.accessible')
				.unbind('click');
	};
	
	function hoverOverLI() {
		var cls = 'jd_menu_hover' + ($(this).parent().is('.jd_menu_flag_root') ? '_menubar' : '');
		$(this).addClass(cls).find('> a').addClass(cls);
		
		if (this.$timer) {
			clearTimeout(this.$timer);
		}

		// Do we have a sub menu?
		if ($('> ul', this).size() > 0) {
			var settings = getSettings(this);
			
			// Which delay to use, the longer activate one or the shorter show delay if a menu is already visible
			var delay = ($(this).parents('ul.jd_menu_flag_root').find('ul:visible').size() == 0) 
							? settings.activateDelay : settings.showDelay;
			var t = this;
			this.$timer = setTimeout(function() {
				showMenuLI.apply(t);
			}, delay);
		}
	};
	
	function hoverOutLI() {
		// Remove both classes so we do not have to test which one we are
		$(this)	.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar')
			.find('> a')
				.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar');
		
		if (this.$timer) {
			clearTimeout(this.$timer);
		}

		// TODO: Possible bug with our test for visibility in that parent menus are hidden child menus are not

		// If we have a visible menu, hide it
		if ($(this).is(':visible') && $('> ul', this).size() > 0) {
			var settings = getSettings(this);
			var ul = $('> ul', this)[0];
			this.$timer = setTimeout(function() {
				hideMenuUL.apply(ul);
			}, settings.hideDelay);
		}
	};
	
	// "this" is a reference to the LI element that contains 
	// the UL that will be shown
	function showMenuLI() {
		var ul = $('> ul', this).get(0);
		// We are already visible, just return
		if ($(ul).is(':visible')) {
			return false;
		}

		// Clear our timer if it exists
		if (this.$timer) {
			clearTimeout(this.$timer);
		}

		// Get our settings object
		var settings = getSettings(this);

		// Call our callback
		if (settings.onShow != null && settings.onShow.apply(this) == false) {
			return false;
		}

		// Add hover classes, needed for accessible functionality
		var isRoot = $(this).parent().is('.jd_menu_flag_root');
		var c = 'jd_menu_active' + (isRoot ? '_menubar' : '');
		$(this).addClass(c).find('> a').addClass(c);

		if (!isRoot) {
			// Add the active class to the parent list item which maybe our menubar
			var c = 'jd_menu_active' + ($(this).parent().parent().parent().is('.jd_menu_flag_root') ? '_menubar' : '');
			$(this).parent().parent().addClass(c).find('> a').addClass(c);
		}

		// Hide any existing menues at the same level
		$(this).parent().find('> li > ul:visible').not(ul).each(function() {
			hideMenuUL.apply(this);
		});

		addEvents(ul);

		// Our range object is used in calculating menu positions
		var Range = function(x1, x2, y1, y2) {
			this.x1	= x1;
			this.x2 = x2;
			this.y1 = y1;
			this.y2 = y2;
		}
		
		Range.prototype.contains = function(range) {
			return 	(this.x1 <= range.x1 && range.x2 <= this.x2) 
					&& 
					(this.y1 <= range.y1 && range.y2 <= this.y2);
		}
		Range.prototype.transform = function(x, y) {
			return new Range(this.x1 + x, this.x2 + x, this.y1 + y, this.y2 + y);
		}
		Range.prototype.nudgeX = function(range) {
			if (this.x1 < range.x1) {
				return new Range(range.x1, range.x1 + (this.x2 - this.x1), this.y1, this.y2);
			} else if (this.x2 > range.x2) {
				return new Range(range.x2 - (this.x2 - this.x1), range.x2, this.y1, this.y2);
			}
			return this;
		}
		Range.prototype.nudgeY = function(range) {
			if (this.y1 < range.y1) {
				return new Range(this.x1, this.x2, range.y1, range.y1 + (this.y2 - this.y1));
			} else if (this.y2 > range.y2) {
				return new Range(this.x1, this.x2, range.y2 - (this.y2 - this.y1), range.y2);
			}
			return this;
		}

		// window width & scroll offset
		var sx = $(window).scrollLeft()
		var sy = $(window).scrollTop();
		var ww = $(window).innerWidth();
		var wh = $(window).innerHeight();

		var viewport = new Range(	sx, sx + ww, 
									sy, sy + wh);
		
		// "Show" our menu so we can calculate its width, set left and top so that it does not accidentally
		// go offscreen and trigger browser scroll bars
		
/*CHANGED:  Removed jdMenu reset of positioning which throws off original CSS settings (Bryce Fields, 2007-09-17*****************************************/
		/*$(ul).css({visibility: 'hidden', left: 0, top: 0}).show();*/
		$(ul).css({visibility: 'hidden'}).show();

		var menuWidth		= $(ul).outerWidth();
		var menuHeight		= $(ul).outerHeight();
		
		// Get the LI parent UL outerwidth in case borders are applied to it
		var tp 				= $(this).parent();
		var thisWidth		= tp.outerWidth();
		
/*CHANGED:  If no border is set in CSS, then IE assigns a default border width value of "medium" instead of 0, which causes border width computations to be "NaN,
			which in turn throws off calculations for where the menu should be positioned. Added a separate left border and right border test.  If the value is "NaN",
			border width is assumed to be zero, otherwise border width is computed normally.  Left border and right border are tested seperately in case
			one is set and the other isn't.  (Bryce Fields, 2007-10-09*****************************************/
		var leftBorderWidth = isNaN(parseInt(tp.css('borderLeftWidth'))) ? 0 : parseInt(tp.css('borderLeftWidth'));
		var rightBorderWidth = isNaN(parseInt(tp.css('borderRightWidth'))) ? 0 : parseInt(tp.css('borderRightWidth'));
		var thisBorderWidth	= leftBorderWidth + rightBorderWidth;
		
		//var thisBorderTop 	= parseInt(tp.css('borderTopWidth'));
		var thisHeight		= $(this).outerHeight();
		var thisOffset 		= $(this).offset({border: false});

		$(ul).hide().css({visibility: ''});

		// We define a list of valid positions for our menu and then test against them to find one that works best
		var position = [];
	// Bottom Horizontal
		// Menu is directly below and left edges aligned to parent item
		position[0] = new Range(thisOffset.left, thisOffset.left + menuWidth, 
								thisOffset.top + thisHeight, thisOffset.top + thisHeight + menuHeight);
		// Menu is directly below and right edges aligned to parent item
		position[1] = new Range((thisOffset.left + thisWidth) - menuWidth, thisOffset.left + thisWidth,
								position[0].y1, position[0].y2);
		// Menu is "nudged" horizontally below parent item
		position[2] = position[0].nudgeX(viewport);

	// Right vertical
		// Menu is directly right and top edge aligned to parent item
		position[3] = new Range(thisOffset.left + thisWidth - thisBorderWidth, thisOffset.left + thisWidth - thisBorderWidth + menuWidth,
								thisOffset.top, thisOffset.top + menuHeight);
		// Menu is directly right and bottom edges aligned with parent item
		position[4] = new Range(position[3].x1, position[3].x2, 
								position[0].y1 - menuHeight, position[0].y1);
		// Menu is "nudged" vertically to right of parent item
		position[5] = position[3].nudgeY(viewport);

	// Top Horizontal
		// Menu is directly top and left edges aligned to parent item
		position[6] = new Range(thisOffset.left, thisOffset.left + menuWidth, 
								thisOffset.top - menuHeight, thisOffset.top);
		// Menu is directly top and right edges aligned to parent item
		position[7] = new Range((thisOffset.left + thisWidth) - menuWidth, thisOffset.left + thisWidth,
								position[6].y1, position[6].y2);
		// Menu is "nudged" horizontally to the top of parent item
		position[8] = position[6].nudgeX(viewport);
	
	// Left vertical
		// Menu is directly left and top edges aligned to parent item
		position[9] = new Range(thisOffset.left - menuWidth, thisOffset.left, 
								position[3].y1, position[3].y2);
		// Menu is directly left and bottom edges aligned to parent item
		position[10]= new Range(position[9].x1, position[9].x2, 
								position[4].y1 + thisHeight - menuHeight, position[4].y1 + thisHeight);
		// Menu is "nudged" vertically to left of parent item
		position[11]= position[10].nudgeY(viewport);

		// This defines the order in which we test our positions
		var order = [];
		if ($(this).parent().is('.jd_menu_flag_root') && !settings.isVerticalMenu) {
			order = [0, 1, 2, 6, 7, 8, 5, 11];
		} else {
			order = [3, 4, 5, 9, 10, 11, 0, 1, 2, 6, 7, 8];
		}

		// Set our default position (first position) if no others can be found
		var pos = order[0];
		for (var i = 0, j = order.length; i < j; i++) {
			// If this position for our menu is within the viewport of the browser, use this position
			if (viewport.contains(position[order[i]])) {
				pos = order[i];
				break;
			}
		}
		var menuPosition = position[pos];

		// Find if we are absolutely positioned or have an absolutely positioned parent
		$(this).add($(this).parents()).each(function() {

			if ($(this).css('position') == 'absolute') {
				var abs = $(this).offset();
				// Transform our coordinates to be relative to the absolute parent
				menuPosition = menuPosition.transform(-abs.left, -abs.top);
				return false;
			}
		});

		switch (pos) {
			case 3:
				menuPosition.y1 += settings.offsetY;
			case 4:
				menuPosition.x1 -= settings.offsetX;
				break;
			
			case 9:
				menuPosition.y1 += settings.offsetY;
			case 10:
				menuPosition.x1 += settings.offsetX;
				break;
		}
		
		if (settings.iframe) {
			$(ul).bgiframe();
		}
		
		
/*CHANGED:  removed KII custom show/hide engine that required no CSS in favor of modified original jdMenu show/hide engine (Bryce Fields, 2007-09-17 *********/
		/*if (settings.onAnimate) {
			// The onAnimate method is expected to "show" the element it is passed
			settings.onAnimate.apply(ul, [true]);
		} else {
			$(ul).show();
		}*/
		

/* Basic logic: If the menu is a top level menu, don't apply any jdMenu mods before showing, else apply jdMenu's calculated positions. */
		if (settings.onAnimate) {
			if ($(this).parent().is('.jd_menu_flag_root') && !settings.isVerticalMenu) {
				// The onAnimate method is expected to "show" the element it is passed
				settings.onAnimate.apply(ul, [true]);
			} else {
				$(ul).css({left: menuPosition.x1, top: menuPosition.y1});
				// The onAnimate method is expected to "show" the element it is passed
				settings.onAnimate.apply(ul, [true]);
			}
		} else {
			if ($(this).parent().is('.jd_menu_flag_root') && !settings.isVerticalMenu) {
				$(ul).show();
			} else {
/*CHANGED: We shouldn't have to change the y1 position in the current KII schema. FYI, it is being recalculated incorrectly, and will need addressed should we find we need it.*/
				/*$(ul).css({left: menuPosition.x1, top: menuPosition.y1}).show();*/
				$(ul).css({left: menuPosition.x1}).show();
			}
		}
		return true;
	}

	// "this" is a reference to a UL menu to be hidden
	function hideMenuUL(recurse) {
		if (!$(this).is(':visible')) {
			return false;
		}

		var settings = getSettings(this);

		// Test if this menu should get hidden
		if (settings.onHideCheck != null && settings.onHideCheck.apply(this) == false) {
			return false;
		}
		
		// Hide all of our child menus first
		$('> li ul:visible', this).each(function() {
			hideMenuUL.apply(this, [false]);
		});

		// If we are the root, do not hide ourself
		if ($(this).is('.jd_menu_flag_root')) {
			//alert('We are root');
			return false;
		}

		var elms = $('> li', this).add($(this).parent());
		elms.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar')
			.removeClass('jd_menu_active').removeClass('jd_menu_active_menubar')
			.find('> a')
				.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar')
				.removeClass('jd_menu_active').removeClass('jd_menu_active_menubar');

		removeEvents(this);
		$(this).each(function() {
			if (settings.onAnimate != null) {
				settings.onAnimate.apply(this, [false]);
			} else {
				$(this).hide();
			}
		}).find('> .bgiframe').remove();
		// Our callback for after our menu is hidden
		if (settings.onHide != null) {
			settings.onHide.apply(this);
		}

		// Recursively hide our parent menus
		if (recurse == true) {
			$(this).parentsUntil('ul.jd_menu_flag_root')
					.removeClass('jd_menu_hover').removeClass('jd_menu_hover_menubar')
				.not('.jd_menu_flag_root').filter('ul')
					.each(function() {
						hideMenuUL.apply(this, [false]);
					});
		}

		return true;
	}

	// Prevent the default (usually following a link)
	function accessibleClick(e) {
		if ($(this).is('.accessible')) {
			// Stop the browser from the default link action allowing the 
			// click event to propagate to propagate to our LI (itemClick function)
			e.preventDefault();
		}
	}

	// Trigger a menu click
	function itemClick(e) {
		e.stopPropagation();

		var settings = getSettings(this);
		if (settings.onClick != null && settings.onClick.apply(this) == false) {
			return false;
		}

		if ($('> ul', this).size() > 0) {
			showMenuLI.apply(this);
		} else {
			if (e.target == this) {
				var link = $('> a', e.target).not('.accessible');
				if (link.size() > 0) {
					var a = link.get(0);
					if (!a.onclick) {
						window.open(a.href, a.target || '_self');
					} else {
						$(a).click();
					}
				}
			}
			
			hideMenuUL.apply($(this).parent(), [true]);
		}
	}
})(jQuery);
