/**
 * @author POP webdev [tw]
 * @version 0.3.3
 * @classDescription A Scroller to move a LIst vertically or horizontally.
 * @lastModified 1/9/2009 [tw]
 * @return {Object}	Returns an new instance.
 * This class depends on Prototype v1.6 and Scriptaculous effects.
 */
/*
TO IMPROVE:
	- add optional hooks into the move effect, either through functions or firing custom events. 
	e.g. fnOnBeforeMove and fnOnAfterMove or scroller:move and scroller:stop
*/

 var Scroller = Class.create({
	initialize: function(slideFrame, options) {
		// set options
		this.options = Object.extend({
			initialIndex: 0,
			durationPerSlide: 1,
			orientation: 'horizontal', 	// ['horizontal' | 'vertical']
			restAlignment: 'left', 		// horizontal: [left|center|right], vertical: [top|center|bottom]
			selectOnMove: true, 		// whether or not to "select" a slide when a move or jump method is called.
			selectOnClick: true, 		// whether or not to "select" a slide it is clicked.
			scrollItemParent: 'UL',
			scrollItem: 'LI'
		}, options || {});

		// construction tasks
		this.slideFrame = $(slideFrame).makePositioned();
		this.slideList = $(slideFrame).down().cleanWhitespace().makePositioned();
		this.slides = this.slideList.childElements().invoke('cleanWhitespace'); // get some clean list items
		
		this.slideFrameDim = this.slideFrame.getDimensions();

		// BEGIN horrible ie6 hack to get appropriate slideFrame height (ie6 added overflown elements to height);
		var IE6 = false/*@cc_on || @_jscript_version < 5.7@*/;
		if (IE6) {
			this.slideList.hide();

			this.slideFrameDim = this.slideFrame.getDimensions();
			this.slideList.show();
		}
		// END horrible ie6 hack

		this.currentSlideIndex = this.options.initialIndex; // resting slide
		this.selectedSlideIndex = -1; // user selected slide
		this.moveEffect = null;
		this.isMoving = false;


		// go to the initial index
		this.jumpTo(this.currentSlideIndex, this.options.selectOnMove);

		// optional event handler
		if(this.options.selectOnClick){
			this.slideList.observe('click', this.__listClick.bindAsEventListener(this));
		}
	},
	
	__listClick: function(e){
		var el = e.element();
		
		if(el.nodeName == options.scrollItemParent){
			return; // user clicked in padding/margin of ul
		}
		if(el.nodeName != options.scrollItem){
			el = el.up(options.scrollItem);
		}
		this.setSelectedSlide(el);
	},
	
	_calculateMoveByX: function(slideIndex) {
		var moveByX = 0;

		var slideListCurrentLeftPos = this.slideList.positionedOffset().left;
		var slideCurrentLeftPos = this.slides[slideIndex].positionedOffset().left;
		var slideFrameWidth = this.slideFrameDim.width;
		var slideWidth = this.slides[slideIndex].getWidth();

		// switch on this.options.restAlignment to determine resting place
		switch (this.options.restAlignment) {
			case 'left':
				moveByX = ((slideFrameWidth - slideListCurrentLeftPos) - (slideCurrentLeftPos)) - slideFrameWidth;
				break;
			case 'right':
				moveByX = ((slideFrameWidth - slideCurrentLeftPos) - slideListCurrentLeftPos) - (slideWidth);
				break;
			case 'center':
			default:
				// center (default)
				moveByX = (((slideFrameWidth / 2) - slideCurrentLeftPos) - slideListCurrentLeftPos) - (slideWidth / 2);
				break;
		}
		return Math.round(moveByX);
	},
	
	_calculateMoveByY: function(slideIndex) {
		var moveByY = 0;

		var slideListCurrentTopPos = this.slideList.positionedOffset().top;
		var slideCurrentTopPos = this.slides[slideIndex].positionedOffset().top;
		var slideFrameHeight = this.slideFrameDim.height;

		var slideHeight = this.slides[slideIndex].getHeight();

		// switch on this.options.restAlignment to determine resting place
		switch (this.options.restAlignment) {
			case 'top':
				moveByY = ((slideFrameHeight - slideListCurrentTopPos) - (slideCurrentTopPos)) - slideFrameHeight;
				break;
			case 'bottom':
				moveByY = ((slideFrameHeight - slideCurrentTopPos) - slideListCurrentTopPos) - (slideHeight);
				break;
			case 'center':
			default:
				// center (default)
				moveByY = (((slideFrameHeight / 2) - slideCurrentTopPos) - slideListCurrentTopPos) - (slideHeight / 2);
				break;
		}
		return Math.round(moveByY);
	},

	// moveToSlide
	moveTo: function(slideIndex) {
		// allow argument to be a slide LI itself
		if(!Object.isNumber(slideIndex) && Object.isElement(slideIndex)){
			slideIndex = this.slides.indexOf(slideIndex);
		}
			
		var slideCount = this.getSlideCount();
		if (slideIndex > (slideCount - 1)) { slideIndex = slideCount - 1; } // make sure we're within bounds

		this.stopMoving(); // A.D.D. protection.
		var seconds = Math.abs(this.currentSlideIndex - slideIndex) * this.options.durationPerSlide;
		this.currentSlideIndex = slideIndex;
		
		if(this.options.selectOnMove){
			this.setSelectedSlide(slideIndex);
		}
		
		var effectOptions = {
				//y: moveByY,
				duration: seconds,
				afterFinish: this.stopMoving.bind(this),
				transition: Effect.Transitions.sinoidal,
				fps: 20
			};
		
		// set the x or y delta in the options depending on orientation
		switch(this.options.orientation){
			case 'horizontal':
				var moveByX = this._calculateMoveByX(slideIndex);
				effectOptions.x = moveByX;
				break;
			case 'vertical':
				var moveByY = this._calculateMoveByY(slideIndex);
				effectOptions.y = moveByY;
				break;
		}
				
		this.isMoving = true;
		// all that for this
		this.moveEffect = new Effect.Move(this.slideList, effectOptions);
	},

	moveToNext: function(){
		var nextIndex = this.currentSlideIndex +1;
		if(nextIndex < this.getSlideCount()){
			this.moveTo(nextIndex);
		}
	},

	moveToPrevious: function(){
		var prevIndex = this.currentSlideIndex -1;
		if(prevIndex >= 0){
			this.moveTo(prevIndex);
		}		
	},
	
	moveToFirst: function(){
		this.moveTo(0);	
	},
	
	moveToLast: function(){
		this.moveTo(this.getSlideCount() -1);	
	},	
	
	stopMoving: function() {
		if (this.isMoving) {
			this.moveEffect.cancel(); // stops animation wherever it is
		}
		this.isMoving = false;
	},

	jumpTo: function(slideIndex) {
		// allow argument to be a slide LI itself
		if(!Object.isNumber(slideIndex) && Object.isElement(slideIndex)){
			slideIndex = this.slides.indexOf(slideIndex);
		}
			
		// stop the animation and go directly to the given index.
		this.currentSlideIndex = slideIndex;
				
		this.stopMoving();
		
		switch(this.options.orientation){
			case 'horizontal':
				this._setLeftPosition(slideIndex);
				break;
			case 'vertical':
				this._setTopPosition(slideIndex);
				break;
		}
		
		// use optional second argument to skip setSelectedSlide
		var allowSelection = (Object.isUndefined(arguments[1])) ? this.options.selectOnMove : arguments[1];
		if(allowSelection){
			this.setSelectedSlide(slideIndex);
		}
	},
	
	_setTopPosition: function(slideIndex){
		var slideListCurrentTopPos = this.slideList.positionedOffset()[1];
		var slideCurrentTopPos = this.slides[slideIndex].positionedOffset()[1];
		var slideFrameHeight = this.slideFrameDim.height;
		var slideHeight = this.slides[slideIndex].getHeight();

		var topPos = 0;
		switch (this.options.restAlignment) {
			case 'top':
				topPos = -slideCurrentTopPos;
				break;
			case 'bottom':
				topPos = (slideFrameHeight - slideHeight) - (slideCurrentTopPos);
				break;
			case 'center':
			default:
				// center
				topPos = (slideFrameHeight / 2 - slideCurrentTopPos) - (slideHeight / 2);
				break;
		}
		this.slideList.setStyle({ top: topPos + 'px' });		
	},
	
	_setLeftPosition: function(slideIndex){
		var slideListCurrentLeftPos = this.slideList.positionedOffset()[0];
		var slideCurrentLeftPos = this.slides[slideIndex].positionedOffset()[0];
		var slideFrameWidth = this.slideFrameDim.width;
		var slideWidth = this.slides[slideIndex].getWidth();

		var leftPos = 0;
		switch (this.options.restAlignment) {
			case 'left':
				leftPos = -slideCurrentLeftPos;
				break;
			case 'right':
				leftPos = (slideFrameWidth - slideWidth) - (slideCurrentLeftPos);
				break;
			case 'center':
			default:
				// center
				leftPos = (slideFrameWidth / 2 - slideCurrentLeftPos) - (slideWidth / 2);
				break;
		}
		this.slideList.setStyle({ left: leftPos + 'px' });		
	},
	

	reset: function() {
		this.currentSlideIndex = this.options.initialIndex;
		this.jumpTo(this.currentSlideIndex);
	},

	addSlide: function(elLi, bAddtoBeginning) {
		// add slide to list
		(bAddtoBeginning) ? this.slideList.insert({ top: elLi }) : this.slideList.insert(elLi);
		
		this.slides = this.slideList.childElements();
		
		if(bAddtoBeginning){
			// adjust list item indexing
			this.currentSlideIndex++;
			this.selectedSlideIndex++;
			this.jumpTo(this.currentSlideIndex, false);
		}
	},

	removeSlide: function(slideIndex) {
		this.slides[slideIndex].remove();
		this.slides = this.slideList.childElements();
		if(this.currentSlideIndex > slideIndex){
			// adjust list item indexing
			this.currentSlideIndex--;
			this.jumpTo(this.currentSlideIndex);
		}
	},
	
	sendSlideToEnd: function(slideIndex){
		this.relocateSlide(slideIndex, this.getSlideCount()-1 );
	},
	
	sendSlideToBeginning: function(slideIndex){
		this.relocateSlide(slideIndex, 0);
	},
	
	// want to be able move from beginning to end, or vice versa
	relocateSlide: function(oldIndex, newIndex) {
		if(newIndex > oldIndex){ 
			// ordering slide farther forward
			Element.insert(this.slides[newIndex], {after: this.slides[oldIndex]});
		}
		else{ 
			// farther back
			Element.insert(this.slides[newIndex], {before: this.slides[oldIndex]});
		}
		
		this.slides = this.slideList.childElements();// reset indexing.
		
		// updated the selected index if it was somewhere in the field of change
		var changeRange = (oldIndex > newIndex) ? $R(newIndex, oldIndex) : $R(oldIndex, newIndex);
		if(changeRange.include(this.selectedSlideIndex)){
			var selSlide = this.findSelectedSlide();
			this.selectedSlideIndex = this.getIndexForSlide(selSlide);
		}
	},	

	getSlideCount: function() {
		return this.slideList.childElements().length;
	},
	
	getIndexForSlide: function(elSlide){
		return this.slides.indexOf(elSlide);
	},
	
	getSlide: function(slideIndex){
		return this.slides[slideIndex];
	},
	
	getSelectedSlide: function(){
		return this.slides[this.selectedSlideIndex];
	},
	
	findSelectedSlide: function(){
		return this.slides.find(function(slide){
			return slide.hasClassName('selected');
		});
	},
	
	setSelectedSlide: function(slideIndex){
		// allow argument to be a slide LI itself
		if(!Object.isNumber(slideIndex) && Object.isElement(slideIndex)){
			slideIndex = this.slides.indexOf(slideIndex);
		}
		// set the selected class and fire custom event.
		for (var i = 0, len = this.getSlideCount(); i < len; i++) {
			if (i === slideIndex) {
				this.slides[i].addClassName('selected');
				this.selectedSlideIndex = slideIndex;
				this.slideFrame.fire('scroller:slideselected', {slide: this.slides[i], slideIndex: i });
			}
			else {
				this.slides[i].removeClassName('selected');
			}
		}
		return slideIndex;// for future reference
	}

});
/*
document.observe('dom:loaded', function(){
	vs = new Scroller($('verticalscroller_frame'), {orientation: 'vertical', durationPerSlide: 0.25, restAlignment: 'top'});
});
*/
