

/*******************************************/
/**      MENU CONTROLLER                  **/
/*******************************************/


var ComboMenu = Class.create();
ComboMenu.prototype = {
	
	target:null,
	menus:null,
	pendingBreadcrumbItem:null,
	pendingBreadcrumbTime:0,
	hoveredBreadcrumbItem:null,
	currentMenu:null,
	mouse:null,
	MOUSE_SPEED_THRESHOLD: 0.01,
	MOUSE_DELAY_MILLISECONDS: 300,
	MOUSE_OUTSIDE_PADDING_TOP: 20,
	MOUSE_OUTSIDE_PADDING_BOTTOM: 0,
	MOUSE_OUTSIDE_PADDING_LEFT: 20,
	MOUSE_OUTSIDE_PADDING_RIGHT: 50,
	clicked:false,
	
	/** Constructor */
	initialize:function( target ) {		
		this.target = target;
		this.initEvents();
		this.mouse = new ComboMenuMouseUtil( this.handleMouseUpdate.bind( this ) );
		this.initScroll();
	},
	
	unload:function() {
		this.target = null;
		this.menus.each(function(m){
			m.scrollUtil.unload();
			m.scrollUtil = null;
		});
		this.currentMenu = null;
	},
	
	initScroll:function() {
		this.menus = this.target.getElementsBySelector( "li ul" );
		this.currentMenu = this.menus[this.menus.length-1];
		for( var i = 0, len = this.menus.length; i < len; i++ ) {
			var m = this.menus[i];
			var hover = ( i != len - 1 );
			m.scrollUtil = new ComboMenuScrollUtil( this.target, m, hover );
		}
	},
	
	initEvents:function() {
		Event.observe( this.target, "mouseover", this.handleMouseOver.bindAsEventListener( this ) );
		Event.observe( this.target, "mouseout", this.handleMouseOut.bindAsEventListener( this ) );
		Event.observe( this.target, "click", this.handleMouseClick.bindAsEventListener( this ) );
		Event.observe( this.target, "mousedown", this.handleMouseClick.bindAsEventListener( this ) );
		Event.observe( document, "click", this.handleDocumentClick.bind( this ) );
	},
	
	handleMouseOver:function( event ) {
		var t = $(Event.element( event ));
		if( t.tagName.toLowerCase() == "a" || t.hasClassName("title") ) {
			this.setPendingBreadcrumbItem( t.up("li") );
		}
	},
	
	handleMouseOut:function( event ) {
		this.setPendingBreadcrumbItem( null );
	},
	
	handleMouseClick:function( event ) {
		var element = $(Event.element(event));
		if( "A" == element.tagName.toUpperCase() ) {
			if( element.up("li").hasClassName("disabled") ) {
				if( this.currentMenu != null ) {
					event.stop();
				}			
			}
			this.clicked = true;
		}
	},
	
	handleDocumentClick:function( event ) {
		var et = Event.element(event);
		if( "A" == et.tagName.toUpperCase() && et.descendantOf(this.target) ) { return; }
		this.hoverBreadcrumbItem( null );
	},
	
	setPendingBreadcrumbItem:function( li ) {
		if( li == null ) {
			this.pendingBreadcrumbItem = null;
			return;
		}
		if( this.isBreadcrumbItem( li ) ) {
			if( this.pendingBreadcrumbItem != li ) {
				this.pendingBreadcrumbItem = li;
				this.pendingBreadcrumbTime = (new Date()).getTime();
			}
		}
	},
	
	isBreadcrumbItem:function( node ) {
		return node.up(1) == this.target;
	},
	
	handleMouseUpdate:function() {
		this.checkPendingBreadcrumb();
		this.checkMouseOutside();
	},
	
	checkMouseOutside:function() {
		var p = Position.positionedOffset(this.target);
		
		var y1 = p[1];
		var y2 = y1 + this.target.getHeight();
		var ym = this.mouse.pos.y;
		if( ym < ( y1 - this.MOUSE_OUTSIDE_PADDING_TOP ) || ym > ( y2 + this.MOUSE_OUTSIDE_PADDING_BOTTOM ) ) {
			this.hoverBreadcrumbItem( null );
			return;
		}
		
		var x1 = p[0];
		var x2 = x1 + this.target.getWidth();
		var xm = this.mouse.pos.x;
		if( xm < ( x1 - this.MOUSE_OUTSIDE_PADDING_LEFT ) || xm > ( x2 + this.MOUSE_OUTSIDE_PADDING_RIGHT ) ) {
			this.hoverBreadcrumbItem( null );
			return;
		}
	},
	
	checkPendingBreadcrumb:function() {
		var time = (new Date()).getTime();
		var passedTime = time - this.pendingBreadcrumbTime;
		if( passedTime < this.MOUSE_DELAY_MILLISECONDS  ) {
			return;
		}
		if( Math.abs( this.mouse.speed ) <= this.MOUSE_SPEED_THRESHOLD && this.pendingBreadcrumbItem != null ) {
			if( this.isSelectedBreadcrumb( this.pendingBreadcrumbItem ) ) {
				this.hoverBreadcrumbItem( null );
			} else {
				this.hoverBreadcrumbItem( this.pendingBreadcrumbItem );
			}
		}
	},
	
	hoverBreadcrumbItem:function( li ) {
		if( li != null && li.down("ul") == null ) {
			return;
		}
		this.setPendingBreadcrumbItem( null );
		if( li == this.hoveredBreadcrumbItem ) {
			return;
		}
		this.hoveredBreadcrumbItem = li;
		var node = this.target.down( "li" );
		while( node != null ) {
			node.removeClassName("hovered");
			var down = node.down("ul");
			if( down != null ) { down.scrollUtil.disable() };
			node = node.next("li");
		}
		var defaultMenu = this.target.getElementsBySelector("li.selected ul")[0];
		if( li != null ) {
			li.addClassName("hovered");
			li.down("ul").scrollUtil.enable();
			this.currentMenu = li.down("ul");
			defaultMenu.hide();
		} else {
			this.currentMenu = defaultMenu;
			defaultMenu.show();
			defaultMenu.scrollUtil.enable();
		}
	},
	
	isSelectedBreadcrumb:function( li ) {
		return li.hasClassName( "selected" );
	}
	
}


/*******************************************/
/**      SCROLL                           **/
/*******************************************/



var ComboMenuScrollUtil = Class.create();
ComboMenuScrollUtil.prototype = {
	
	CLIP_WIDTH:960,
	CLIP_THRESHOLD_WIDTH:994,
	
	target:null,
	button:null,
	
	leftStartPos:0,
	isRTL:false,
	
	contentWidth:0,
	overflowWidth:0,
	reverse:false,
	enabled:false,
	
	initialize:function( outerTarget, menuTarget, isHoverMenu ) {
		this.target = menuTarget;
		this.isRTL = this.target.down('li').getStyle('float') == 'right';
		this.button = $( outerTarget.appendChild( Builder.node( "a", { href:"#", className:"scrollButton" } ) ) );
		Event.observe( this.button, "mousedown", this.handleButtonClick.bindAsEventListener( this ) );
		Event.observe( this.button, "click", function(e){Event.stop(e)} );
		
		if( isHoverMenu ) {
			this.button.addClassName( "hoverMenu" );
			this.button.hide();
		} else {
			this.enable();
		}
	},
	
	unload:function() {
		this.target = null;
		this.button = null;
	},
	
	handleButtonClick:function( event ) {
		Event.stop( event );
		this.doNextScroll();
	},
	
	doNextScroll:function() {
		if( !this.reverse ) {
			if( this.isRTL ) {
				this.target.style.right = -this.overflowWidth + 'px';
			} else {
				new Effect.MoveBy( this.target, 0, -this.overflowWidth, {duration: 0.4,queue:{position:'end',scope:'menuScroll'}} );
			}
			this.reverse = true;
			this.button.addClassName( "reverse" );
			this.updateItemVisibility( -this.overflowWidth );
		} else {
			if( this.isRTL ) {
				this.target.style.right = 0;
				this.updateItemVisibility(0);
			} else {
				function afterFinish() { this.updateItemVisibility(0); }
				new Effect.MoveBy( this.target, 0,  this.overflowWidth, {duration: 0.4,queue:{position:'end',scope:'menuScroll'},afterFinish:afterFinish.bind(this)} );
			}
			this.reverse = false;
			this.button.removeClassName( "reverse" );
		}
	},
	
	/**
	 * If a menu item crosses the menu edge, it should be marked as disabled.
	 */
	updateItemVisibility:function( scrollOffset ) {
		if( !this.enabled ) { return; }
		var li = this.target.down("li");
		while( li != null ) {
			var isAtEdge = false;
			if( this.isRTL ) {
				var left = scrollOffset + this.target.getWidth() - li.offsetLeft;
				var right = left - li.getWidth();
				isAtEdge = left > this.CLIP_WIDTH && right < this.CLIP_WIDTH;
			} else {
				var left = scrollOffset + li.offsetLeft;
				var right = left + li.getWidth();
				isAtEdge = left < this.CLIP_WIDTH && right > this.CLIP_WIDTH;
			}
			if( isAtEdge && !li.hasClassName("disabled") ) {
				li.addClassName("disabled")
			} else if( !isAtEdge && li.hasClassName("disabled") ) {
				li.removeClassName("disabled");
			}
			li = li.next("li");
		}
	},
	
	enable:function() {
		this.updateContentWidth();
		this.resetPosition();
		if( this.contentWidth > this.CLIP_THRESHOLD_WIDTH ) {
			this.button.show();
			this.enabled = true;
		} else {
			this.button.hide();
			this.enabled = false;
		}
	},
	
	disable:function() {
		this.button.hide();
		this.enabled = false;
	},
	
	updateContentWidth:function() {
		var width = 0;
		var li = this.target.down("li");
		while( li != null ) {
			width += li.getWidth();
			li = li.next("li");
		}
		this.contentWidth = width;
		this.overflowWidth = Math.max( 0, width - this.CLIP_WIDTH );
	},
	
	isItemOnOrBeyondEdge:function( li ) {
		if( this.isRTL ) {
			var left = this.target.getWidth() - li.offsetLeft ; // scrollOffset + li.offsetRight;
			var right = left - li.getWidth();
			isAtEdge = left > this.CLIP_WIDTH && right < this.CLIP_WIDTH;
		} else {
			var right = li.offsetLeft + li.getWidth();
			return right > this.CLIP_WIDTH;
		}
	},
	
	resetPosition:function() {
		var selectedIsOutside = false;
		var selectedItem = this.target.getElementsBySelector("li.selected")[0];
		if( selectedItem != null ) {
			selectedIsOutside = this.isItemOnOrBeyondEdge( selectedItem );
		}
		if( this.isRTL ) {
			this.target.style.right = 0;
		} else {
			if( selectedIsOutside ) {
				this.target.style.left = -this.overflowWidth + "px";
				this.updateItemVisibility(-this.overflowWidth);
				this.reverse = true;
				this.button.addClassName( "reverse" );
			} else {
				this.target.style.left = 0;
				this.updateItemVisibility(0);
				this.reverse = false;
				this.button.removeClassName( "reverse" );
			}
			
		}	
	}
	
}


/*******************************************/
/**      MOUSE                            **/
/*******************************************/


var ComboMenuMouseUtil = Class.create();
ComboMenuMouseUtil.prototype = {
	
	pos:{x:0,y:0}, lastPos:{x:0,y:0},
	time:0,
	speed:0,
	updateFunction:function(){},
	
	initialize:function( updateFunction ) {
		this.updateFunction = updateFunction;
		var d = new Date();
		this.time = d.getTime();
		Event.observe( document, "mousemove", this.handleMouseMove.bindAsEventListener( this ) );
		new PeriodicalExecuter( this.handleInterval.bind(this), 0.04 );
	},
	
	handleMouseMove:function( event ) {
		this.pos.x = Event.pointerX( event );
		this.pos.y = Event.pointerY( event );
	},
	
	handleInterval:function() {
		var newTime = ( new Date() ).getTime();
		var deltaTime = newTime - this.time;
		this.time = newTime;
		this.speed = ( ( this.pos.x - this.lastPos.x ) + ( this.pos.y - this.lastPos.y ) ) / deltaTime;
		var debug = $("DebugSpeed");
		if( debug != null ) { debug.innerHTML = this.speed; }
		this.lastPos.x = this.pos.x;
		this.lastPos.y = this.pos.y;
		this.updateFunction();
	}
}


























