/*
	Script: itemScroller.js
	Author: Regina Luk
*/

/*
	Class: itemScroller
		Used to scroll items into view when there is more than one row of itmes.  Assumes unit is 'px' and that each item is the same size.

		When wrapEnds is true, we scroll left (next) by changing an item's margin-left from 0 to -(width of the item), then inject it after the last item in the list, and then changing the margin back to 0.  To scroll right (prev), we move the last item in the list to the beginning of the list, and change the margin of the item from -(width of the item) to 0.

		When wrapEnds is false (default), we scroll left and right by changing the margin-left of the itemsContainer instead of the item from 0 to negative and back to 0 depending on the direction we're scrolling.

		If any of the padding/margin is not styled or set to auto, itemScroller will default those to 0 if it uses them.

	Arguments:
		element - html element containing all of the scrolled items and controls
		options - scroll options

	Options:
		containerSelector -
			css selector for html element containing scrolled items.
			Default: 'ul'
		itemSelector -
			css selector for scrolled items.
			Default: 'li'
		viewPortSelector -
			css selector for div with dimensional limits on what's viewable.
			Default: '.wrap'
		scrollerSelector -
			css selector for scroll controls.
			Grabs only first instance of this element from within the container element.
			Default: '.item_scroller'
		nextSelector -
			css selector for scroll next controls.
			Assumes control for on is an html anchor 'a', and off state is an html 'span'.
			Grabs only first instance of each element.
			Default: '.next'
		prevSelector -
			css selector for scroll prev controls.
			Assumes control for on is an html anchor 'a', and off state is an html 'span'.
			Grabs only first instance of each element.
			Default: '.prev'
		itemsPerScroll -
			number of items to move per scroll.
			Normalizes to 1 if passed a number that is less than 1 or this.options.wrapEnds is true.
			Used to control height of viewPort if mode is set to vertical.
			Normalizes to the largest possible number of items we can scroll when itemsPerScroll causes the scroll size to be larger than the viewable width in horizontal mode.
			Default: 1
		wrapEnds -
			Boolean used to check if we want to wrap items around when we move pass ends.
			Default: false
		mode -
			Set it to vertical or horizontal.
			Default: horizontal
	
	Example:
		(start code)
		<script type="text/css">
			#containItems div.clear{clear: both;height: 0;font-size: 0;overflow: hidden;}
			#containItems div.wrap{overflow:hidden;position:relative;width:100%;}
			#containItems ul{list-style:none;margin:-6px 0 0;padding:0;}
			#containItems ul li{display:inline;float:left;margin:6px 6px 0 0;padding:0;height:80px;width:100px;}
		</script>

		<div id="containItems">
			<div class="wrap">
				<ul>
					<li>scroll item</li>
					<li>scroll item</li>
					<li>scroll item</li>
				</ul>
				<div class="clear"></div>
				<!-- item_scroller needs to be inside our containing element -->
				<div class="item_scroller" style="display:none">
					<!-- 
						each anchor has a corresponding span for when wrapEnds is false
						that is used to turn off the link when you have reach on of the ends
					-->
					<a href="#prev" class="prev" style="display:none">previous</a>
					<span class="prev" style="display:none">previous</span>
					<a href="#next" class="next" style="display:none">next</a>
					<span class="next" style="display:none">next</span>
				</div>
			</div>
		</div>
		(end)
*/
var itemScroller = new Class({
	options: {
		containerSelector: 'ul',
		itemSelector: 'li',
		viewPortSelector: '.wrap',
		scrollerSelector: '.item_scroller',
		nextSelector: '.next',
		prevSelector: '.prev',
		itemsPerScroll: 1,
		wrapEnds: false,
		mode: 'horizontal'
	},

	initialize: function(el,options) {
		// quit if no main element passed in
		if (!$chk(el)) return;

		if ($chk(options)) this.setOptions(this.options, options);

		this.els = {};
		this.els.mainModule = el;
		this.els.controls = {container: $E(this.options.scrollerSelector, this.els.mainModule)};
		this.els.controls.next = {
			on: $E('a' + this.options.nextSelector, this.els.controls.container),
			off: $E('span' + this.options.nextSelector, this.els.controls.container)
		};
		this.els.controls.prev = {
			on: $E('a' + this.options.prevSelector, this.els.controls.container),
			off: $E('span' + this.options.prevSelector, this.els.controls.container)
		};
		this.els.viewPort = $E(this.options.viewPortSelector, this.els.mainModule);
		// if there is a viewPort, then set its overflow to hidden;
		if($chk(this.els.viewPort)) {
			this.els.viewPort.setStyle('overflow','hidden');
		}
		this.els.itemsContainer = $E(this.options.containerSelector, this.els.mainModule);
		this.els.items = $ES(this.options.itemSelector, this.els.itemsContainer);

		// quit if any of the required items do not exist
		if (!$chk(this.els.controls.container) || !$chk(this.els.controls.next.off) || !$chk(this.els.controls.prev.off) || !$chk(this.els.controls.next.off) || !$chk(this.els.controls.prev.off) ||!$chk(this.els.itemsContainer) || !$chk(this.els.items)) return;

		// find out how tall and wide each item is and add that to any extra margins to find final height and width of each item
		this.itemSize = this.els.items[0].getSize().size;
		this.itemSize.x += this.normalizeUnit(this.els.items[0].getStyle('margin-left')) + this.normalizeUnit(this.els.items[0].getStyle('margin-right'));
		this.itemSize.y += this.normalizeUnit(this.els.items[0].getStyle('margin-top')) + this.normalizeUnit(this.els.items[0].getStyle('margin-bottom'));

		// are we scrolling horizontally or vertically?
		if (this.options.mode == 'horizontal')
		{
			// make sure items per scroll is not more than number of items visible or less than 1.
			this.viewableSize = this.els.mainModule.getSize().size.x - this.normalizeUnit(this.els.mainModule.getStyle('padding-left')) - this.normalizeUnit(this.els.mainModule.getStyle('padding-right'));
			if (this.options.wrapEnds || this.options.itemsPerScroll < 1) {
				this.options.itemsPerScroll = 1;
			} else {
				if (this.options.itemsPerScroll * this.itemSize.x > this.viewableSize) this.options.itemsPerScroll = (this.viewableSize/this.itemSize.x).toInt();
			}
			this.scrollData = {'style':'margin-left','current':0,'distance':this.options.itemsPerScroll*this.itemSize.x,'totalDistance':this.itemSize.x * this.els.items.length};
			this.els.itemsContainer.setStyles({
				'height':this.itemSize.y + 'px',
				'width':(this.itemSize.x * this.els.items.length) + 'px',
				'overflow':'hidden',
				'margin-left':'0'
			});
			if($chk(this.els.viewPort)) {
				// if viewPort does not have width specified, set it
				if (this.normalizeUnit(this.els.viewPort.getSize().size.x) == this.els.itemsContainer.getStyle('width').toInt()) {
					this.els.viewPort.setStyle('width',this.viewableSize+'px');
				}
			}
		} else {
			if (this.options.itemsPerScroll < 1) {
				this.options.itemsPerScroll = 1;
			}
			// set viewableSize here so that it doesn't always have to be equal to the height of an item when wrapping ends
			this.viewableSize = this.options.itemsPerScroll * this.itemSize.y;
			if (this.options.wrapEnds) this.options.itemsPerScroll = 1;
			this.scrollData = {'style':'margin-top','current':0,'distance':this.options.itemsPerScroll*this.itemSize.y,'totalDistance':this.itemSize.y * this.els.items.length};

			this.els.itemsContainer.setStyles({
				'height':(this.itemSize.y * this.els.items.length) + 'px',
				'width':this.itemSize.x + 'px',
				'overflow':'hidden',
				'margin-top':'0'
			});
			// if viewPort does not have width specified, set it
			if (this.normalizeUnit(this.els.viewPort.getSize().size.y) == this.els.itemsContainer.getStyle('height').toInt()) {
				this.els.viewPort.setStyle('height',this.viewableSize+'px');
			}
		}

		this.initControls();		
	},
	
	initControls: function() {
		// reset all controls to display:none 
		this.els.controls.prev.on.setStyle('display','none');
		this.els.controls.prev.off.setStyle('display','none');
		this.els.controls.next.on.setStyle('display','none');
		this.els.controls.next.off.setStyle('display','none');

		if (this.options.itemsPerScroll < this.els.items.length) {
			// show scroll controls if there are items to scroll
			this.els.controls.prev.on.addEvent('click', function(e) {
				e = new Event(e);
				this.scroll.bind(this)('prev');
				e.stop();
			}.bind(this));
			this.els.controls.next.on.addEvent('click', function(e) {
				e = new Event(e);
				this.scroll.bind(this)('next');
				e.stop();
			}.bind(this));

			this.els.controls.next.on.setStyle('display','inline');
			if (this.options.wrapEnds) {
				this.els.controls.prev.on.setStyle('display','inline');
			} else {
				this.els.controls.prev.off.setStyle('display','inline');
			}
			
			this.els.controls.container.setStyle('display','block');
			this.currentlyScrolling = false;
		}
	},
	
	/*
		Property: normalizeUnit
			parses a string to an integer.
		
		Returns:
			0 if the string is not a number, otherwise, the string as an integer.
	*/
	normalizeUnit: function(value) {
		return ( isNaN(value.toInt()) || ($type(value.toInt()) != 'number') ) ? 0 : value.toInt();
	},

	scroll: function(dir) {
		var scrollEffect, scrollEnd;

		// don't do anything if we're currently scrolling already or we might end up scrolling into the middle of something else
		if (!this.currentlyScrolling) {
			this.currentlyScrolling = true;
			// when this.options.wrapEnds is true, only scroll one item at a time
			// because we don't have anything to handle wrapping for more than one item yet.
			if (this.options.wrapEnds) {
				if (dir == 'prev') {
					// dir is prev
					this.els.itemsContainer.getLast().injectBefore(this.els.itemsContainer.getFirst());
					this.els.itemsContainer.getFirst().setStyle(this.scrollData.style,(this.scrollData.distance * (-1)) + 'px');
					scrollEffect = new Fx.Style(this.els.itemsContainer.getFirst(), this.scrollData.style, {
						onComplete: function() {
							this.currentlyScrolling = false;
						}.bind(this)
					});
					scrollEffect.start(0);
				} else {
					// dir is next
					scrollEffect = new Fx.Style(this.els.itemsContainer.getFirst(), this.scrollData.style, {
						onComplete: function() {
							this.els.itemsContainer.getFirst().injectAfter(this.els.itemsContainer.getLast());
							this.els.itemsContainer.getLast().setStyle(this.scrollData.style,'0');
							this.currentlyScrolling = false;
						}.bind(this)
					});
					// make sure we are not starting with auto value
					scrollEffect.start(this.normalizeUnit(this.els.itemsContainer.getFirst().getStyle(this.scrollData.style)),this.scrollData.distance * (-1));
				}
			} else {
				// when this.options.wrapEnds is not true, we can scroll one or more items
				scrollEffect = new Fx.Style(this.els.itemsContainer, this.scrollData.style, {
					onComplete: function() {
						this.scrollData.current = this.normalizeUnit(this.els.itemsContainer.getStyle(this.scrollData.style));
						if (this.scrollData.current < 0) {
							this.els.controls.prev.on.setStyle('display','inline');
							this.els.controls.prev.off.setStyle('display','none');
							if (this.viewableSize >= (this.scrollData.totalDistance + this.scrollData.current)) {
								// if there's only one page left, turn off next
								this.els.controls.next.on.setStyle('display','none');
								this.els.controls.next.off.setStyle('display','inline');
							} else if (this.els.controls.next.on.getStyle('display') == 'none') {
								// if there are still more pages and next is turned off, turn it back on
								this.els.controls.next.on.setStyle('display','inline');
								this.els.controls.next.off.setStyle('display','none');
							}
						} else {
							// if we're back at 0, turn off prev and turn on next
							this.els.controls.prev.on.setStyle('display','none');
							this.els.controls.prev.off.setStyle('display','inline');
							this.els.controls.next.on.setStyle('display','inline');
							this.els.controls.next.off.setStyle('display','none');
						}
						this.currentlyScrolling = false;
					}.bind(this)
				});
				if (dir == 'prev') {
					scrollEnd = this.scrollData.current + (this.scrollData.distance);
				} else {
					scrollEnd = this.scrollData.current - (this.scrollData.distance);
				}
				// don't let users scroll into nothingness
				if ((scrollEnd <= 0) && (Math.abs(scrollEnd) < this.scrollData.totalDistance)) {
					scrollEffect.start(this.scrollData.current,scrollEnd);
				}
			} // end wrapEnds
		} // end currentlyScrolling
	}

});

itemScroller.implement(new Options);