/**
 * @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.
		}, 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 == 'UL'){
			return; // user clicked in padding/margin of ul
		}
		if(el.nodeName != 'LI'){
			el = el.up('LI');
		}
		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'});
});
*/

var NoJsRemover = {
	initialize: function(){
		$$(".nojs").invoke('removeClassName', 'nojs');
	}
}

document.observe("dom:loaded", function(){
	NoJsRemover.initialize();
});


/**
 * @class Tabby
 * @author Dan Dean for POP (www.pop.us)
 * @version 1.0
 * Tabby is a lightweight prototype for a tabbed interface.
 */
var Tabby = Class.create({
	/**
	 * @member Tabby
	 * @param {Element/String} element The element (or element ID of the wrapper element) for the
	 * 		tabbed interface widget.
	 */
	initialize: function(element, options) {
		this.wrapper = $(element).addClassName("tabby");
		this.options = Object.extend({startIndex: 0}, options || {});
		
		// Find content LI's
		this.contents = $$("#" + this.wrapper.identify() + " ul.contents > li");
		
		// Find tab LI's
		this.tabs = $$("#" + this.wrapper.id + " ul.tabs > li");
		if (this.tabs.length == 0) {
			// If no LI's, create them automatically
			this.wrapper.insert({top: new Element("ul", {"class" : "tabs"}).update("<li>Tab</li>".times(this.contents.length))});
			this.tabs = $$("#" + this.wrapper.id + " ul.tabs > li");
		}
		
		this.tabs.each(function(li, i) {
			li.observe("click", this.__click.bind(this)); // Bind click events to tabs
			if (this.options.tabText && this.options.tabText[i]) {
				li.update(this.options.tabText[i]); // Set tab text if specified
			}
		}, this);
		
		this.set(this.options.startIndex, true); // Start on default tab
	},
	
	/**
	 * @member Tabby
	 * @private
	 * @param {Event} e The DOM Event which invoked this method
	 */
	__click: function(e) {
		this.set(this.tabs.indexOf(e.findElement("li")));
	},
	
	/**
	 * @member Tabby
	 * @param {int} index The tab index to select
	 * @param {bool} suppress Whether to suppress the "tabby:set" event or not
	 * @return void
	 */
	set: function(index, suppress) {
		index = Math.max(Math.min(index, this.contents.length - 1), 0); // >= 0 && <= tab count
		this.tabs.invoke("removeClassName", "on"); // turn all off
		this.contents.invoke("removeClassName", "on"); // turn all off
		this.tabs[index].addClassName("on"); // Turn specified on
		this.contents[index].addClassName("on"); // Turn specified on
		if (!suppress) {
			document.fire("tabby:set", {control: this, index: index}); // Notify listeners
		}
	}
});

// Make Tabby into a Prototype Plugin
Element.addMethods({
	tabby: function(element, options) {
		new Tabby(element, options);
		return element;
	}
});
