/**
 * @license
 * jQuery Tools 1.2.5a Scrollable - New wave UI design
 *
 * NO COPYRIGHTS OR LICENSES. DO WHAT YOU LIKE.
 *
 * Modified by Timewasted to better support fluid width scrollables.
 * Incorporated changes previously made by PawPrint.net (http://flowplayer.org/tools/forum/35/46918)
 *
 * http://flowplayer.org/tools/scrollable.html
 *
 * Since: March 2008
 * Date:    Wed Oct 13 02:34:45 2010 +0000
 */
(function($) {
	// static constructs
	$.tools = $.tools || {version: '1.2.5a'};

	$.tools.scrollable = {
		conf: {
			activeClass: 'active',
			circular: false,
			clonedClass: 'cloned',
			disabledClass: 'disabled',
			easing: 'swing',
			initialIndex: 0,
			item: null,
			items: '.items',
			keyboard: true,
			mousewheel: false,
			next: '.next',
			prev: '.prev',
			speed: 400,
			vertical: false,
			touch: 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);
			},

			/* Modified from official */
			getVisibleItemCount: function(i) {
				if( i < 0 )
					i = 0;
				if( i >= self.getSize() )
					return 1;

				var wrapPos = 0,
					startIndex = i,
					rootSize = vertical ? root.innerHeight(true) : root.innerWidth(true);
				for( var loop = 0; loop < i; loop++ )
					wrapPos -= vertical ? self.getItems().eq(loop).outerHeight(true) : self.getItems().eq(loop).outerWidth(true);
				for( i; i < self.getSize(); i++ ) {
					var item = self.getItems().eq(i),
						itemPos = vertical ? item.position().top + wrapPos : item.position().left + wrapPos,
						itemSize = vertical ? item.outerHeight(true) : item.outerWidth(true);
					if( itemPos + itemSize > rootSize )
						break;
				}
				return i - startIndex;
			},

			/* Modified from official */
			getLastEffectiveIndex: function() {
				var i = 0,
					wrapPos = 0,
					rootSize = vertical ? root.innerHeight(true) : root.innerWidth(true),
					lastItem = self.getItems().last(),
					lastItemPos = vertical ? lastItem.position().top : lastItem.position().left,
					lastItemSize = vertical ? lastItem.outerHeight(true) : lastItem.outerWidth(true);
				for( i; i < self.getSize(); i++ ) {
					if( lastItemPos + wrapPos + lastItemSize <= rootSize )
						break;
					wrapPos -= vertical ? self.getItems().eq(i).outerHeight(true) : self.getItems().eq(i).outerWidth(true);
				}
				return i;
			},

			/* Modified from official */
			isAnimating: function() {
				return itemWrap.is(':animated');
			},

			/* Modified from official */
			move: function(offset, time) {
				if( !self.isAnimating() && (conf.circular || (index + offset >= 0 && index + offset <= self.getLastEffectiveIndex())) ) {
					return self.seekTo(index + offset, time);
				} else {
					return self;
				}
			},

			next: function(time) {
				return self.move(1, time);
			},

			prev: function(time) {
				return self.move(-1, time);
			},

			begin: function(time) {
				return self.seekTo(0, time);
			},

			/* Modified from official */
			end: function(time) {
				return self.seekTo(self.getLastEffectiveIndex(), time);
			},

			focus: function() {
				current = self;
				return self;
			},

			addItem: function(item) {
				item = $(item);

				if (!conf.circular)  {
					itemWrap.append(item);
				} else {
					itemWrap.children("." + conf.clonedClass + ":last").before(item);
					itemWrap.children("." + conf.clonedClass + ":first").replaceWith(item.clone().addClass(conf.clonedClass));
				}

				fire.trigger("onAddItem", [item]);
				return self;
			},


			/* all seeking functions depend on this */
			seekTo: function(i, time, fn) {
				// ensure numeric index
				if (!i.jquery) { i *= 1; }

				// avoid seeking from end clone to the beginning
				if (conf.circular && i === 0 && index == -1 && time !== 0) { return self; }

				// check that index is sane
				/* Modified from official */
				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) {
				if (fn) { $(self).bind(name, fn); }
				return self;
			};
		});

		// circular loop
		if (conf.circular) {
			/* Begin modified from official */
			var cloned1 = null,
				cloned2 = null;

			cloned2 = self.getItems().slice(0, self.getVisibleItemCount(index)).clone().addClass(conf.clonedClass).appendTo(itemWrap);	// first [4] at the end
			cloned1 = self.getItems().slice(-1).clone().addClass(conf.clonedClass).prependTo(itemWrap);										// last [4] at the start

			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.eq(-1), time, function() {
						self.seekTo(self.getSize()-1, 0, function(){});
					});
					return e.preventDefault();
				} else if (i == self.getSize()) {
					self.seekTo(cloned2.eq(0), time, function() {
						self.begin(0);
					});
				}
			});
			/* End modified from official */

			// seek over the cloned item
			self.seekTo(0, 0, function() {});
		}

		// 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) {
				/* Modified from official */
				window.setTimeout(function() {
					if( !e.isDefaultPrevented() ) {
						if( self.getSize() <= self.getVisibleItemCount(0) ) {
							prev.addClass(conf.disabledClass);
							next.addClass(conf.disabledClass);
						} else {
							prev.toggleClass(conf.disabledClass, i <= 0);
							next.toggleClass(conf.disabledClass, i >= self.getLastEffectiveIndex());
						}
					}
				}, 1);
			});

			if (!conf.initialIndex) {
				prev.addClass(conf.disabledClass);
			}
		}

		// 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;
				}
			});
		}

		// touch event
		if (conf.touch) {
			var touch = {};

			itemWrap[0].ontouchstart = function(e) {
				var t = e.touches[0];
				touch.x = t.clientX;
				touch.y = t.clientY;
			};

			itemWrap[0].ontouchmove = function(e) {
				// only deal with one finger
				if (e.touches.length == 1 && !itemWrap.is(":animated")) {
					var t = e.touches[0],
						deltaX = touch.x - t.clientX,
						deltaY = touch.y - t.clientY;

					self[vertical && deltaY > 0 || !vertical && deltaX > 0 ? 'next' : 'prev']();
					e.preventDefault();
				}
			};
		}

		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
		/* Modified from official */
		setTimeout(function() {
			var vSeekIdx = Math.min(conf.initialIndex, Math.max(0, (self.getSize() - self.getVisibleItemCount())));
			self.seekTo(vSeekIdx);
		}, conf.speed);
	}


	// 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);

