/**
 * @license 
 * jQuery Tools 1.2.3 Scrollable - New wave UI design
 * 
 * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
 * 
 * http://flowplayer.org/tools/scrollable.html
 *
 * Since: March 2008
 * Date:    Mon Jun 7 13:43:53 2010 +0000 
 */

/*

Configuration

Here is a list of all available configuration options:

clonedClass	'cloned'			The plugin behaves so that the first and last items are cloned 
								at both ends. These cloned items are assigned the CSS class 
								name given in this configuration variable.
disabledClass	"disabled"	 	The CSS class name for disabled next and prev elements. For 
								example, the prev element is disabled when there are no 
								previous items to scroll to. Typically you assign the 
								visibility: 'hidden' CSS definition to disabled elements.
easing	"swing"	 				The type of "easing" applied to the scrolling animation. 
								'swing' means that there is an acceleration, followed by a 
								constant speed, followed by a deceleration. 'linear' means 
								that the whole animation happens at a constant speed. 
								You can also make your own easing effects.
items	".items"	 			The scrollable root element is not directly used as a parent 
								for the scrollable items. The root must have one nested 
								element that contains the actual items. By default scrollable 
								uses the first and only element inside the root as a wrapper 
								for the items. It does not have to have any particular CSS 
								class name. A simple div is fine.
								If for some reason you have multiple elements inside the root 
								element, then scrollable looks for the element specified by 
								this property. By default an element with the class name "items" 
								is used, but you can use any jQuery selector you like for 
								this property.

								See the minimal setup for an understanding of the HTML setup 
								for scrollable.

keyboard	true	 			Whether or not keyboard arrow key navigation is enabled. The 
								horizontal scroller moves backwards/forwards with the 
								left/right arrow keys; the vertical scroller moves with the 
								up/down keys. The arrow keys are valid for the scrollable 
								that was used most recently. Since version 1.1.0 you can 
								also supply the value 'static' here which means that the 
								scrollable is always controlled with the arrow keys and it 
								does not have to be active.
								You can also use the focus() method to force the focus to a 
								certain scrollable on the page. You can take a look at the A 
								complete scrollable navigational system demo which takes 
								advantage of the "static" value and the focus() method.

								If you want to disable the keyboard for a particular scrollable, 
								you can do the following:

								// grab second scrollabe and its API
								var instance = $(".horizontal").eq(1).data("scrollable");

								// disable keyboard navigation
								instance.getConf().keyboard = false;
								
circular	false	 			A property indicating whether scrolling starts from the beginning 
								when the last item is exceeded making an "infinite loop".
								
next	".next"	 				Selector for the elements to which a "scroll forwards" action 
								should be bound. These elements should have a mutual wrapper 
								element together with the scrollable itself.
								
prev	".prev"	 				Selector for the elements to which a "scroll backwards" action 
								should be bound. These elements should have a mutual wrapper 
								element together with the scrollable itself.
								
speed	400	 					The time (in milliseconds) of the scrolling animation.

vertical	false	 			The scrollable is good at guessing whether it's vertical or 
								horizontal by investigating it's dimensions. If the height is 
								larger than the width then the scrollable is vertical. 
								This can also be manually configured with this configuration 
								option which overrides the automatic determination.
 
*/ 
 
(function($) { 

	// static constructs
	$.tools = $.tools || {version: '1.2.3'};
	
	$.tools.scrollable = {
		
		conf: {	
			activeClass: 'active',
			circular: true,
			clonedClass: 'cloned',
			disabledClass: 'enabled',
			easing: 'swing',
			initialIndex: 0,
			item: null,
			items: '.items',
			keyboard: true,
			mousewheel: false,
			next: '.next',   
			prev: '.prev', 
			speed: 400,
			vertical: false,
			wheelSpeed: 0
		} 
	};
					
	// get hidden element's width or height even though it's hidden
	function dim(el, key) {
		var v = parseInt(el.css(key), 10);
		if (v) { return v; }
		var s = el[0].currentStyle; 
		return s && s.width && parseInt(s.width, 10);	
	}

	function find(root, query) { 
		var el = $(query);
		return el.length < 2 ? el : root.parent().find(query);
	}
	
	var current;		
	
	// constructor
	function Scrollable(root, conf) {   
		
		// current instance
		var self = this, 
			 fire = root.add(self),
			 itemWrap = root.children(),
			 index = 0,
			 vertical = conf.vertical;
				
		if (!current) { current = self; } 
		if (itemWrap.length > 1) { itemWrap = $(conf.items, root); }
		
		// methods
		$.extend(self, {
				
			getConf: function() {
				return conf;	
			},			
			
			getIndex: function() {
				return index;	
			}, 

			getSize: function() {
				return self.getItems().size();	
			},

			getNaviButtons: function() {
				return prev.add(next);	
			},
			
			getRoot: function() {
				return root;	
			},
			
			getItemWrap: function() {
				return itemWrap;	
			},
			
			getItems: function() {
				return itemWrap.children(conf.item).not("." + conf.clonedClass);	
			},
							
			move: function(offset, time) {
				return self.seekTo(index + offset, time);
			},
			
			next: function(time) {
				return self.move(1, time);	
			},
			
			prev: function(time) {
				return self.move(-1, time);	
			},
			
			begin: function(time) {
				return self.seekTo(0, time);	
			},
			
			end: function(time) {
				return self.seekTo(self.getSize() -1, time);	
			},	
			
			focus: function() {
				current = self;
				return self;
			},
			
			addItem: function(item) {
				item = $(item);
				
				if (!conf.circular)  {
					itemWrap.append(item);
				} else {
					$(".cloned:last").before(item);
					$(".cloned:first").replaceWith(item.clone().addClass(conf.clonedClass)); 						
				}
				
				fire.trigger("onAddItem", [item]);
				return self;
			},
			
			
			/* all seeking functions depend on this */		
			seekTo: function(i, time, fn) {	
				
				// avoid seeking from end clone to the beginning
				if (conf.circular && i === 0 && index == -1 && time !== 0) { return self; }
				
				// check that index is sane
				if (!conf.circular && i < 0 || i > self.getSize() || i < -1) { return self; }
				
				var item = i;
			
				if (i.jquery) {
					i = self.getItems().index(i);	
				} else {
					item = self.getItems().eq(i);
				}  
				
				// onBeforeSeek
				var e = $.Event("onBeforeSeek"); 
				if (!fn) {
					fire.trigger(e, [i, time]);				
					if (e.isDefaultPrevented() || !item.length) { return self; }			
				}  
	
				var props = vertical ? {top: -item.position().top} : {left: -item.position().left};  
				
				index = i;
				current = self;  
				if (time === undefined) { time = conf.speed; }   
				
				itemWrap.animate(props, time, conf.easing, fn || function() { 
					fire.trigger("onSeek", [i]);		
				});	 
				
				return self; 
			}					
			
		});
				
		// callbacks	
		$.each(['onBeforeSeek', 'onSeek', 'onAddItem'], function(i, name) {
				
			// configuration
			if ($.isFunction(conf[name])) { 
				$(self).bind(name, conf[name]); 
			}
			
			self[name] = function(fn) {
				$(self).bind(name, fn);
				return self;
			};
		});  
		
		// circular loop
		if (conf.circular) {
			
			var cloned1 = self.getItems().slice(-1).clone().prependTo(itemWrap),
				 cloned2 = self.getItems().eq(1).clone().appendTo(itemWrap);
				
			cloned1.add(cloned2).addClass(conf.clonedClass);
			
			self.onBeforeSeek(function(e, i, time) {

				
				if (e.isDefaultPrevented()) { return; }
				
				/*
					1. animate to the clone without event triggering
					2. seek to correct position with 0 speed
				*/
				if (i == -1) {
					self.seekTo(cloned1, time, function()  {
						self.end(0);		
					});          
					return e.preventDefault();
					
				} else if (i == self.getSize()) {
					self.seekTo(cloned2, time, function()  {
						self.begin(0);		
					});	
				}
				
			});
			
			// seek over the cloned item
			self.seekTo(0, 0);
		}
		
		// next/prev buttons
		var prev = find(root, conf.prev).click(function() { self.prev(); }),
			 next = find(root, conf.next).click(function() { self.next(); });	
		
		if (!conf.circular && self.getSize() > 1) {
			
			self.onBeforeSeek(function(e, i) {
				setTimeout(function() {
					if (!e.isDefaultPrevented()) {
						prev.toggleClass(conf.disabledClass, i <= 0);
						next.toggleClass(conf.disabledClass, i >= self.getSize() -1);
					}
				}, 1);
			}); 
		}
			
		// mousewheel support
		if (conf.mousewheel && $.fn.mousewheel) {
			root.mousewheel(function(e, delta)  {
				if (conf.mousewheel) {
					self.move(delta < 0 ? 1 : -1, conf.wheelSpeed || 50);
					return false;
				}
			});			
		}
		
		if (conf.keyboard)  {
			
			$(document).bind("keydown.scrollable", function(evt) {

				// skip certain conditions
				if (!conf.keyboard || evt.altKey || evt.ctrlKey || $(evt.target).is(":input")) { return; }
				
				// does this instance have focus?
				if (conf.keyboard != 'static' && current != self) { return; }
					
				var key = evt.keyCode;
			
				if (vertical && (key == 38 || key == 40)) {
					self.move(key == 38 ? -1 : 1);
					return evt.preventDefault();
				}
				
				if (!vertical && (key == 37 || key == 39)) {					
					self.move(key == 37 ? -1 : 1);
					return evt.preventDefault();
				}	  
				
			});  
		}
		
		// initial index
		$(self).trigger("onBeforeSeek", [conf.initialIndex]);
	} 

		
	// jQuery plugin implementation
	$.fn.scrollable = function(conf) { 
			
		// already constructed --> return API
		var el = this.data("scrollable");
		if (el) { return el; }		 

		conf = $.extend({}, $.tools.scrollable.conf, conf); 
		
		this.each(function() {			
			el = new Scrollable($(this), conf);
			$(this).data("scrollable", el);	
		});
		
		return conf.api ? el: this; 
		
	};
			
	
})(jQuery);

