(function () {

	var Overlay = YAHOO.widget.Overlay,
		Dom = YAHOO.util.Dom,

		// String constants
	
		_CONTEXT = "context",	
		_Y = "y",
		_MAX_HEIGHT = "maxheight",
		_MIN_SCROLL_HEIGHT = "minscrollheight",		
		_PREVENT_CONTEXT_OVERLAP = "preventcontextoverlap";


	YAHOO.widget.Menu.prototype.getConstrainedY = function (y) {
	
		var oMenu = this,
		
			aContext = oMenu.cfg.getProperty(_CONTEXT),
			nInitialMaxHeight = oMenu.cfg.getProperty(_MAX_HEIGHT),
	
			nMaxHeight,
	
			oOverlapPositions = {
	
				"trbr": true,
				"tlbl": true,
				"bltl": true,
				"brtr": true
	
			},
	
			bPotentialContextOverlap = (aContext && oOverlapPositions[aContext[1] + aContext[2]]),
		
			oMenuEl = oMenu.element,
			nMenuOffsetHeight = oMenuEl.offsetHeight,
		
			nViewportOffset = Overlay.VIEWPORT_OFFSET,
			viewPortHeight = Dom.getViewportHeight(),
			scrollY = Dom.getDocumentScrollTop(),
	
			bCanConstrain = 
				(oMenu.cfg.getProperty(_MIN_SCROLL_HEIGHT) + nViewportOffset < viewPortHeight),
	
			nAvailableHeight,
	
			oContextEl,
			nContextElY,
			nContextElHeight,
	
			bFlipped = false,
	
			nTopRegionHeight,
			nBottomRegionHeight,
	
			topConstraint = scrollY + nViewportOffset,
			bottomConstraint = scrollY + viewPortHeight - nMenuOffsetHeight - nViewportOffset,
	
			yNew = y;
			
	
		var flipVertical = function () {
	
			var nNewY;
		
			// The Menu is below the context element, flip it above
			if ((oMenu.cfg.getProperty(_Y) - scrollY) > nContextElY) { 
				nNewY = (nContextElY - nMenuOffsetHeight);
			}
			else {	// The Menu is above the context element, flip it below
				nNewY = (nContextElY + nContextElHeight);
			}
	
			oMenu.cfg.setProperty(_Y, (nNewY + scrollY), true);
			
			return nNewY;
		
		};
	
	
		/*
			 Uses the context element's position to calculate the availble height 
			 above and below it to display its corresponding Menu.
		*/
	
		var getDisplayRegionHeight = function () {
	
			// The Menu is below the context element
			if ((oMenu.cfg.getProperty(_Y) - scrollY) > nContextElY) {
				return (nBottomRegionHeight - nViewportOffset);				
			}
			else {	// The Menu is above the context element
				return (nTopRegionHeight - nViewportOffset);				
			}
	
		};
	
	
		/*
			Sets the Menu's "y" configuration property to the correct value based on its
			current orientation.
		*/ 
	
		var alignY = function () {
	
			var nNewY;
	
			if ((oMenu.cfg.getProperty(_Y) - scrollY) > nContextElY) { 
				nNewY = (nContextElY + nContextElHeight);
			}
			else {	
				nNewY = (nContextElY - oMenuEl.offsetHeight);
			}
	
			oMenu.cfg.setProperty(_Y, (nNewY + scrollY), true);
		
		};
	
	
		//	Resets the maxheight of the Menu to the value set by the user
	
		var resetMaxHeight = function () {
	
			oMenu._setScrollHeight(this.cfg.getProperty(_MAX_HEIGHT));
	
			oMenu.hideEvent.unsubscribe(resetMaxHeight);
		
		};
	
	
		/*
			Trys to place the Menu in the best possible position (either above or 
			below its corresponding context element).
		*/
	
		var setVerticalPosition = function () {
	
			var nDisplayRegionHeight = getDisplayRegionHeight(),
				bMenuHasItems = (oMenu.getItems().length > 0),
				nMenuMinScrollHeight,
				fnReturnVal,
				nNewY;
	
	
			if (nMenuOffsetHeight > nDisplayRegionHeight) {
	
				nMenuMinScrollHeight = 
					bMenuHasItems ? oMenu.cfg.getProperty(_MIN_SCROLL_HEIGHT) : nMenuOffsetHeight;
	
	
				if ((nDisplayRegionHeight > nMenuMinScrollHeight) && bMenuHasItems) {
					nMaxHeight = nDisplayRegionHeight;
				}
				else {
					nMaxHeight = nInitialMaxHeight;
				}
	
	
				oMenu._setScrollHeight(nMaxHeight);
				oMenu.hideEvent.subscribe(resetMaxHeight);
				
	
				// Re-align the Menu since its height has just changed
				// as a result of the setting of the maxheight property.
	
				alignY();
				
	
				if (nDisplayRegionHeight < nMenuMinScrollHeight) {
	
					if (bFlipped) {
		
						/*
							 All possible positions and values for the "maxheight" 
							 configuration property have been tried, but none were 
							 successful, so fall back to the original size and position.
						*/
	
						flipVertical();
						
					}
					else {
		
						flipVertical();
	
						bFlipped = true;
		
						fnReturnVal = setVerticalPosition();
		
					}
					
				}
			
			}
			else if (nMaxHeight && (nMaxHeight !== nInitialMaxHeight)) {
			
				oMenu._setScrollHeight(nInitialMaxHeight);
				oMenu.hideEvent.subscribe(resetMaxHeight);
	
				// Re-align the Menu since its height has just changed
				// as a result of the setting of the maxheight property.
	
				alignY();
			
			}
	
			return fnReturnVal;
	
		};
	
	
		// Determine if the current value for the Menu's "y" configuration property will
		// result in the Menu being positioned outside the boundaries of the viewport
	
		if (y < topConstraint || y  > bottomConstraint) {
	
			// The current value for the Menu's "y" configuration property WILL
			// result in the Menu being positioned outside the boundaries of the viewport
	
			if (bCanConstrain) {
	
				if (oMenu.cfg.getProperty(_PREVENT_CONTEXT_OVERLAP) && bPotentialContextOverlap) {
			
					//	SOLUTION #1:
					//	If the "preventcontextoverlap" configuration property is set to "true", 
					//	try to flip and/or scroll the Menu to both keep it inside the boundaries of the 
					//	viewport AND from overlaping its context element (MenuItem or MenuBarItem).
	
					oContextEl = aContext[0];
					nContextElHeight = oContextEl.offsetHeight;
					nContextElY = (Dom.getY(oContextEl) - scrollY);
		
					nTopRegionHeight = nContextElY;
					nBottomRegionHeight = (viewPortHeight - (nContextElY + nContextElHeight));
		
					setVerticalPosition();
					
					yNew = oMenu.cfg.getProperty(_Y);
			
				}
				else if (!(oMenu instanceof YAHOO.widget.MenuBar) && 
					nMenuOffsetHeight >= viewPortHeight) {
	
					//	SOLUTION #2:
					//	If the Menu exceeds the height of the viewport, introduce scroll bars
					//	to keep the Menu inside the boundaries of the viewport
	
					nAvailableHeight = (viewPortHeight - (nViewportOffset * 2));
			
					if (nAvailableHeight > oMenu.cfg.getProperty(_MIN_SCROLL_HEIGHT)) {
			
						oMenu._setScrollHeight(nAvailableHeight);
						oMenu.hideEvent.subscribe(resetMaxHeight);
			
						alignY();
						
						yNew = oMenu.cfg.getProperty(_Y);
					
					}
			
				}	
				else {
	
					//	SOLUTION #3:
				
					if (y < topConstraint) {
						yNew  = topConstraint;
					} else if (y  > bottomConstraint) {
						yNew  = bottomConstraint;
					}				
				
				}
	
			}
			else {
				//	The "y" configuration property cannot be set to a value that will keep
				//	entire Menu inside the boundary of the viewport.  Therefore, set  
				//	the "y" configuration property to scrollY to keep as much of the 
				//	Menu inside the viewport as possible.
				yNew = nViewportOffset + scrollY;
			}	
	
		}
	
		return yNew;
	
	};

}());