/**
 * @author 					Dave Shepard
 * @updated_at 				May 15, 2009
 * @required libraries:		Prototype 1.6 or later
 * 
 * Scroller object for easy implementation of scrolling area.
 *
 * Implementation:
 * ===============
 * After the object is available in the DOM (recommended using the
 * document.observe('dom:loaded') command for best results), run 
 * the following command to initialize the scroller object:
 * 
 *     new Scroller('scroller_id','next_button_id','previous_button_id'[, params, options]);
 * 
 * Parameters:
 * ---------------------------------------------------------------
 * scroller_id				[string] (required) The ID to be assigned as scroller_id. 
 * 									 This is the immediate parent container of the 
 * 									 scrolled elements
 * next_button_id			[string] (optional) The ID of the "next" button
 * previous_button_id		[string] (optional) The ID of the "previous" button
 *
 */

var Scroller = Class.create({
	animating: false,				// [bool] Internal variable for animation to protect click spamming
	scroller_id: "",				// [string] ID of immediate parent to scrolling elements
	container_id: "",				// [string] Internal variable determined as immediate parent to scroller_id
	right_id: "",					// [string] ID of right/next button
	left_id: "",					// [string] ID of left/previous button
	childWidth: 0,					// [int] Internal variable calculated automatically as width of the scrolling elements
	maxLeftOffset: 0,				// [int] Internal variable calculated automatically as the maximum left offset value to prevent next button from scrolling too far 
	options: {
		duration: 0.5,				// [float] Duration of scrolling animation in seconds (default 0.5 seconds)
		loop: false,				// [bool] Should the scroller loop back to the first element (true) or stop at the last element to scroll to (false)?
		shiftSingle: false,			// [bool] Should the scroller scroll the visible area (false) or one element at a time (true)? (default false)
		extraOffset: 0,				// [int] Additional offset in pixels of element width that is visually cropped off
		transition: Effect.Transitions.sinoidal,		// Transition Effect.Transitions.[linear|sinoidal|reverse|flicker|wobble|pulse|spring|full]
		scrolling: false			// [bool] Enable scroll wheel navigation of pages.
	},
	calcVals: function(params){
		var s = this;
		this.container_id = $(this.scroller_id).up(0).identify();

		$(this.scroller_id).childElements().each(function(e){
			s.childWidth += e.getDimensions().width;
		});

		if (this.options.shiftSingle == false) {
			this.width = $(this.container_id).getDimensions().width + this.options.extraOffset;
			this.maxLeftOffset = ((Math.ceil((this.childWidth / this.width))) * this.width) - this.width;
		} else {
			this.width = $(this.scroller_id).childElements()[0].getDimensions().width;
			this.maxLeftOffset = ($(this.scroller_id).childElements().length * this.width) - $(this.container_id).getDimensions().width - this.options.extraOffset;
		}
		
		this.assign(params);
	},

	initialize: function(sID,rbID,lbID,options,params){
		if(!Object.isUndefined(options)) {
			var optionKeys = Object.keys(options);
			var optionVals = Object.values(options);
			for(i=0 ; i < optionKeys.length ; i++){
				this.options[optionKeys[i]] = optionVals[i];
			}
		}
		
		this.scroller_id = sID;
		if(rbID != null) this.right_id = rbID;
		if(lbID != null) this.left_id = lbID;
		
		this.calcVals(params);
		this.updateButtons();
		if(this.options.scrolling == true) this.initScroll(params);
	},

	// Previous Button Click Action
	// ====================================================
	// Assigned to the previous button. Takes an object passed as a parameter.
	// Can be called independently to move the scroller object. For example, if
	// the scroller will be scrolled automatically in an interval, you would 
	// call this function and optionally pass the callback object.
	//
	// Example:
	// Scroller.previous({
	// 		callBack: function(){
	//			... CALL BACK FUNCTION HERE ...
	//		}
	// });
	//
	// Passed object accepts the following methods:
	// 		callBack	-	[function] Call back function that will be run after
	//						the click action is complete
	//
	previous: function(params){
		var s = this;
		if (s.animating != true) {
			if ($(s.scroller_id).positionedOffset().left < 0) {
				s.move(s.width,params);
			} else if(s.options.loop == true && $(s.scroller_id).positionedOffset().left == 0) {
				s.move((0 - s.maxLeftOffset),params);
			}
		}
	},

	// Next Button Click Action
	// ====================================================
	// Assigned to the next button. Takes an object passed as a parameter.
	// Can be called independently to move the scroller object. For example, if
	// the scroller will be scrolled automatically in an interval, you would 
	// call this function and optionally pass the callback object.
	//
	// Example:
	// Scroller.next({
	// 		callBack: function(){
	//			... CALL BACK FUNCTION HERE ...
	//		}
	// });
	//
	// Passed object accepts the following methods:
	// 		callBack	-	[function] Call back function that will be run after
	//						the click action is complete
	next: function(params){
		var s = this;
		if (s.animating != true) {
			if ($(s.scroller_id).positionedOffset().left <= 0 && $(s.scroller_id).positionedOffset().left > (0 - s.maxLeftOffset)) {
				s.move((0 - s.width),params);
			} else if(s.options.loop == true && $(s.scroller_id).positionedOffset().left == (0 - s.maxLeftOffset)) {
				s.move(s.maxLeftOffset,params);
			}
		}
	},
	
	// Click assign routine
	// ====================================================
	// Assigns the click actions to the previous and next buttons
	assign: function(params){
		var s = this;
		if(!Object.isUndefined(params)) {
			if(Object.isUndefined(params.previous)) {
				params.previous = {
					callBack: function(){
						return false;
					}
				}
			}
			if(Object.isUndefined(params.next)) {
				params.next = {
					callBack: function(){
						return false;
					}
				}
			}
		} else {
			params = {
				previous: {
					callBack: function(){
						return false;
					}
				},
				next: {
					callBack: function(){
						return false;
					}
				}
			}
		}
		
		if ($(this.left_id)) {
			$(this.left_id).observe('click', function(event){
				Event.stop(event);
				s.previous(params.previous);
			}).onclick = function(){
				return false;
			};
		}
	
		if ($(this.right_id)) {
			$(this.right_id).observe('click', function(event){
				Event.stop(event);
				s.next(params.next);
			}).onclick = function(){
				return false;
			};
		}
	},
	
	// Update the previous and next buttons when scrolling is finished.
	// This method will disable the buttons appropriately if looping is disabled.
	updateButtons: function(){
		if (this.options.loop == false) {
			if ($(this.scroller_id).positionedOffset().left >= 0) {
				if ($(this.left_id)) 
					$(this.left_id).addClassName('disabled');
			}
			else {
				if ($(this.left_id)) 
					$(this.left_id).removeClassName('disabled');
			}
			if ($(this.scroller_id).positionedOffset().left == (0 - this.maxLeftOffset)) {
				if ($(this.right_id)) 
					$(this.right_id).addClassName('disabled');
			}
			else {
				if ($(this.right_id)) 
					$(this.right_id).removeClassName('disabled');
			}
		}
		
		this.animating = false;
	},
	
	// Internal function to move the scroller object
	move: function(moveDistance,params){
		var s = this;
		s.animating = true;
		new Effect.Move(s.scroller_id, {
			x: moveDistance,
			y: 0,
			mode: 'relative',
			duration: s.options.duration,
			transition: s.options.transition,
			afterFinish: function(){
				s.updateButtons();
				if(!Object.isUndefined(params)) {
					if(Object.isFunction(params.callBack)) {
						params.callBack();
					}
				}
			}
		});
	},
	
	// Go directly to a page
	gotoPage: function(pageNumber,params){
		var s = this;
		s.animating = true;
		
		var position = 0 - (pageNumber * s.width);
		
		new Effect.Move(s.scroller_id, {
			x: position,
			y: 0,
			mode: 'absolute',
			duration: s.options.duration,
			transition: s.options.transition,
			afterFinish: function(){
				s.updateButtons();
				if(!Object.isUndefined(params)) {
					if(Object.isFunction(params.callBack)) {
						params.callBack();
					}
				}
			}
		});
	},
	
	// Scroller
	initScroll: function(params){
		var s = this;
		if(!Object.isUndefined(params)) {
			if(Object.isUndefined(params.previous)) {
				params.previous = {
					callBack: function(){
						return false;
					}
				}
			}
			if(Object.isUndefined(params.next)) {
				params.next = {
					callBack: function(){
						return false;
					}
				}
			}
		} else {
			params = {
				previous: {
					callBack: function(){
						return false;
					}
				},
				next: {
					callBack: function(){
						return false;
					}
				}
			}
		}
		
		$(this.scroller_id).observe('mousewheel', this.mouseScroll.bindAsEventListener(this,params));
		$(this.scroller_id).observe('DOMMouseScroll', this.mouseScroll.bindAsEventListener(this,params));
	},
	
	mouseScroll: function(event,params){
		Event.stop(event);
		if(Event.wheel(event) < 0) {
			this.next(params.next);
		} else {
			this.previous(params.previous);
		}
	},
	
	// Utility function to reset the Scroller paramters. Usefull if being
	// initiated on returned markup from an AJAX call.
	resetObject: function(){
		this.animating = false;
		this.scroller_id = "";
		this.container_id = "";
		this.left_id = "";
		this.right_id = "";
		this.childWidth = 0;
		this.maxLeftOffset = 0;
		this.width = 0;
	}
});

/**
 * Scroll Wheel event monitor
 */
Object.extend(Event, {
	wheel:function (event){
		var delta = 0;
		if (!event) event = window.event;
		if (event.wheelDelta) {
			delta = event.wheelDelta/120; 
			if (window.opera) delta = -delta;
		} else if (event.detail) { delta = -event.detail/3;	}
		return Math.round(delta); //Safari Round
	}
});
