/***************************************************
** sliderule.js
**
** relies on prototype.js and scriptaculous effects.js
** Design Kitchen
** @author: Matthew Story
****************************************************/
var SlideRule = Class.create();
SlideRule.prototype = {
	showBox: false,
	slider: false,
	listeners: [],
	transition: false,
	onPageChange: false,
	elementByElement: false,
	
	//these attributes handle the behavior for easing on mouse hold
	clickMove: true,
	startSpeed: 600,
	maxSpeed: 150,
	currentSpeed: false,
	//this boolean is true for moving foreward, false for moving backwards
	direction: false,
	slideTimeout: false,
	
	//this is a scriptaculous transition effect . . . the real effect not the name:
	//e.g. Effect.Transitions.slowstop
	transitionEffect: false,
	
	/*the constructor takes 2 arguments and an optional 3rd
	 * 1. the viewer/showBox element, this is the outer element
	 * 2. the element that will be sliding to and fro inside the viewer/showBox
	 * 3. (optional) options object with the following options:
	 * 	- pageForeward: element/element id or array of elements/element ids that on click go foreward a page
	 * 	- pageBackward: element/element id or array of elements/element ids that on click go backward a page
	 * 	- transition: boolean, weather or not to use a scriptaculous effect to slide
	 * 	- transitionEffect: class constructor of a scriptaculous transition effect, to define a custom transition
	 * 	- elementByElement: boolean, sides one element at a time if true, one slide viewer width if false
	 * 	- clickMove: boolean, if true moves on click, false, moves on mousedown
	 * 	- startSpeed: length of transition at start (in MS)
	 * 	- maxSpeed: length of transition at max (in MS)
	 * 	- onPageChange: function to be called after a page change (usefull for enabling/disabling nav
	*/
	initialize: function(showBox, slider) {
		var defaults = {
			pageForeward: false,
			pageBackward: false,
			transition: false,
			transitionEffect: false,
			elementByElement: false,
			clickMove: true,
			startSpeed: 600,
			maxSpeed: 150,
			onPageChange: function() {}
		};
		var options = (arguments[2]) ? arguments[2]:{};
		Object.extend(defaults, options);
		defaults.pageForeward = this.toArray(defaults.pageForeward);
		defaults.pageBackward = this.toArray(defaults.pageBackward);
		defaults.transition = (defaults.transitionEffect) ? true:defaults.transition;

		//set up the sliderule
		this.showBox = $(showBox);
		this.slider = $(slider);

		//lets hard set the slider width if it's not
		if (!this.slider.style.width) {
			this.hardWidthifySlider();
		}

		this.clip(this.showBox);
		this.relativize(this.slider);
		this.position(this.slider);
		
		//set up effect
		this.transition = defaults.transition;
		this.transitionEffect = defaults.transitionEffect;
		this.onPageChange = defaults.onPageChange;

		//set up how the transition occurs
		this.elementByElement = defaults.elementByElement;
		this.clickMove = defaults.clickMove;
		this.startSpeed = defaults.startSpeed;
		this.maxSpeed = (defaults.maxSpeed >= 0) ? defaults.maxSpeed:1;
		
		//set up listeners
		for (var i=0;i<defaults.pageForeward.length;i++) {
			this.registerPageForewardListener(defaults.pageForeward[i]);
		}
		for (var i=0;i<defaults.pageBackward.length;i++) {
			this.registerPageBackwardListener(defaults.pageBackward[i]);
		}
		
		//turn to first page
		this.goToPage(1);
	},

	destroy: function() {
		this.showBox = false;
		this.slider = false;
		this.destroyListeners();
		this.transition = false;
		this.transitionEffect = false;

		return true;
	},
	
	getNumPages: function() {
		var pageNum = (this.elementByElement) ? this._getNumElements():this._getNumPages;
		return pageNum;
	},
	
	getCurrentPage: function() {
		var currentPage = (this.elementByElement) ? this._getCurrentElement():this._getCurrentPage();
		return currentPage;
	},
	
	getElementOffset: function(elementIndex) {
		return this.slider.childElements()[elementIndex-1].offsetWidth;
	},

	turnPage: function() {
		this.direction = true;
		this.goToPage(this.getCurrentPage() + 1);
		return true;
	},

	turnBackPage: function() {
		this.direction = false;
		this.goToPage(Math.max((this.getCurrentPage() - 1), 1));
		return true;
	},
	
	startSliding: function(direction) {
		this.direction = direction;
		this.currentSpeed = this.startSpeed;
		this.slide();
	},

	stopSliding: function() {
		this.direction = false;
		this.currentSpeed = false;

		return true;
	},
	
	slide: function() {
		if (this.currentSpeed) {
			var pageNum = (this.direction) ? this.getCurrentPage()+1:this.getCurrentPage()-1;
			this.goToPage(pageNum);
			this.slideTimeout = window.setTimeout(this.slide.bindAsEventListener(this), this.currentSpeed+10);
			this.currentSpeed = (this.currentSpeed-100 > this.maxSpeed) ? this.currentSpeed-100:this.maxSpeed;
		} else {
			this.onPageChange(this.getCurrentPage(), this);
		}

		return true;
	},

	goToPage: function(pageNum) {
		if (this.elementByElement) {
			this._goToElement(pageNum);
		} else {
			this._goToPage(pageNum);
		}

		this.onPageChange(pageNum+1, this);
	},

	_goToElement: function(elementNum) {

	},

	bindEventToPage: function(page, element) {
		var event = (arguments[2]) ? arguments[2]:'click';
		var listener = this.goToPage.bindAsEventListener(this);
		this.registerListener(element, event, function() {listener(page);});

		return true;
	},
	
	registerPageForewardListener: function(element) {
		if (this.clickMove) {
			var event = (arguments[1]) ? arguments[1]:'click';
			this.registerListener(element, event, this.turnPage.bindAsEventListener(this));
		} else {
			var mouseDown = function() {
				this.startSliding(true);
			};
			this.registerListener(element, 'mousedown', mouseDown.bindAsEventListener(this));
			this.registerListener(element, 'mouseup', this.stopSliding.bindAsEventListener(this));
		}

		return true;
	},

	registerPageBackwardListener: function(element) {
		if (this.clickMove) {
			var event = (arguments[1]) ? arguments[1]:'click';
			this.registerListener(element, event, this.turnBackPage.bindAsEventListener(this));
		} else {
			var mouseDown = function() {
				this.startSliding(false);
			};
			this.registerListener(element, 'mousedown', mouseDown.bindAsEventListener(this));
			this.registerListener(element, 'mouseup', this.stopSliding.bindAsEventListener(this));
		}

		return true;
	},

	registerListener: function(element, event, handler) {
		this.listeners.push({
			element: element,
			event: event,
			handler: handler
		});
		Event.observe(element, event, handler);
	},
	
	destroyListener: function(element, event, handler) {
		var newListeners = [];
		for (var i=0;i<this.listeners.length;i++) {
			if (this.listeners[i] == {element: element, event: event, handler: handler}) {
				Event.stopObserving(this.listeners[i].element, this.listeners[i].event, this.listeners[i].handler);
			} else {
				newListeners.push(this.listeners[i]);
			}
		}
		this.listeners = newListeners;

		return true;
	},
	
	destroyListeners: function() {
		for (var i=0;i<this.listeners.length;i++) {
			Event.stopObserving(this.listeners[i].element, this.listeners[i].event, this.listeners[i].handler);
		}
		this.listeners = [];
	},

	clip: function(element) {
		element.style.overflow = 'hidden';
		return true;
	},

	unclip: function(element) {
		element.style.overflow = '';
		return true;
	},

	relativize: function(element) {
		element.style.position = 'relative';
		return true;
	},

	unrelativize: function(element) {
		element.style.position = '';
		return true;
	},

	position: function(element) {
		var offsetRight = (arguments[1]) ? arguments[1]:0;
		element.style.left = offsetRight + 'px';

		return true;
	},

	unposition: function(element) {
		element.style.right = '';
		return true;
	},
	
	hardWidthifySlider: function() {
		var width = 0;
		for (var i=0;i<this.slider.childNodes.length;i++) {
			if (this.slider.childNodes[i].nodeType != 3) {
				width += this.slider.childNodes[i].offsetWidth;
			}
		}
		this.slider.style.width = width + 'px';
	},

	toArray: function(value) {
		value = (value && value.constructor != Array) ? [value]:value;
		return value;
	},

    _goToPage: function(pageNum) {
        pageNum = (pageNum > this.getNumPages()) ? this.getNumPages()-1:pageNum-1;
        if (!this.transition) {
            this._move(this.showBox.clientWidth*pageNum*-1);
        } else if (this.pageNum != this.getCurrentPage()-1) {
            this._transitionMove((this.getCurrentPage()-pageNum-1)*this.showBox.clientWidth);
        }

        return true;
    },

    _goToElement: function(elementNum) {
		elementNum = (elementNum > this.getNumPages()) ? this.getNumPages()-1:elementNum-1;
		if (!this.transition && elementNum != this.getCurrentPage()-1) {
			var offset = parseInt(this.slider.style.left);
			offset += (elementNum > this.getCurrentPage()-1) ? this.getElementOffset(elementNum+1)*-1:this.getElementOffset(elementNum+1);
			this._move(offset);
		} else if (this.getCurrentPage() && elementNum != this.getCurrentPage()-1 && elementNum > 0) {
			var offset = (elementNum > this.getCurrentPage()-1) ? this.getElementOffset(elementNum+1)*-1:this.getElementOffset(elementNum+1);
			this._transitionMove(offset);
		}

		return true;
    },

	_move: function(newPosition) {
		this.position(this.slider, newPosition);
		return true;
	},

	_transitionMove: function(moveBy) {
		var options = (this.transitionEffect) ? {transition: this.transitionEffect}:{};
		if (this.currentSpeed) {
			options.duration = this.currentSpeed/1000;
		}
		new Effect.MoveBy(this.slider, 0, moveBy, options);

		return true;
	},
	
	_getNumPages: function() {
    	return Math.floor(this.slider.offsetWidth/this.showBox.clientWidth)+1;
	},

	_getNumElements: function() {
		return this.slider.childElements().length;
	},
    
	_getCurrentPage: function() {
		return Math.floor(Math.abs(parseInt(this.slider.style.left)/this.showBox.clientWidth) + 1);
	},

	_getCurrentElement: function() {
		var children = this.slider.childElements();
		//if the slider hasn't moved . . . it's the first element
		if (parseInt(this.slider.style.left) == 0) {
			return 1;
		}
		
		//otherwise we go and find where the last visable element is
		for (var i=0;i<children.length;i++) {
			if (children[i].offsetLeft == Math.abs(parseInt(this.slider.style.left)) + this.showBox.clientWidth) {
				return i;
			}
		}
		if (Math.abs(parseInt(this.slider.style.left)) + this.showBox.clientWidth >= children[children.length-1].offsetLeft) {
			return children.length;
		}

		return false;
	}
};
