/**
 * Based on jQuery Tools 1.2.5 Scrollable
 *
 * http://flowplayer.org/tools/scrollable.html
 *
 * Modified by Matt Hinchliffe, 21/03/2011
 * - Removed keyboard, mouse wheel and touch events
 * - Added support for multiple slides
 * - Added pixel offset
 * - Focus class is applied to current frame
 *
 * KEEP THIS SYNCRONISED BETWEEN JAEGER/AQUASCUTUM
 */
(function($)
{
	// static constructs
	$.tools = $.tools || {version: '1.2.5'};

	$.tools.scrollable = {

		conf: {
			focusClass: 'focus',
			circular: false,
			clonedClass: 'cloned',
			disabledClass: 'disabled',
			initialIndex: 0,
			itemsToDisplay: 1,
			item: null,
			items: '.items',
			next: '.next',
			prev: '.prev',
			speed: 300,
			offset:false
		}
	};

	// 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;

		if (!current)
			current = self;

		if (itemWrap.length > 1)
			itemWrap = $(conf.items, root);

		// Work out maximum scrollable width
		var maximum = false;

		if (!conf.circular)
		{
			maximum = 0;

			itemWrap.children().each(function()
			{
				maximum += parseInt($(this).outerWidth(true));
			});

			itemWrap.css('width', maximum + 'px')
		}

		// Set offset
		if (conf.offset)
			itemWrap.css('left', conf.offset + 'px');

		// 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;
			},

			setFocusClass: function()
			{
				if (conf.focusClass)
				{
					var $_target = itemWrap.find('.' + conf.focusClass).length > 0 ? self.getIndex():conf.initialIndex;

					self.getItems()
						.eq($_target)
						.addClass(conf.focusClass)
						.siblings()
						.removeClass(conf.focusClass);
				}
			},

			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;
			},

			disableButtons: function(i)
			{
				moves = moves || 0;

				prev.toggleClass(conf.disabledClass, (!i || moves <= 0) );

				next.toggleClass(conf.disabledClass, (moves >= maxMoves || (maximum !== false && maximum <= 0)) );
			},

			/* 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
				if (!conf.circular && i < 0 || i > self.getSize() || i < -1)
					return self;

				if (!conf.circular && conf.itemsToDisplay > 1)
					i = i < maxMoves ? i : maxMoves;

				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;
				}

				// Offset and maximum
				var scrollGoTo = 0;

				scrollGoTo = -parseInt(item.position().left);

				if (conf.offset)
					scrollGoTo += conf.offset				

				if (!conf.circular && scrollGoTo < -maximum)
					scrollGoTo = -maximum;

				var props = {left: '' + scrollGoTo + 'px'};

				index = i;
				current = self;
				moves = index < maxMoves ? index : maxMoves;

				if (time === undefined)
					time = conf.speed;

				itemWrap.animate(props, time, null, fn || function()
				{
					fire.trigger("onSeek", [i]);
				});

				if (!conf.circular)
					self.disableButtons(index);

				if (conf.itemsToDisplay == 1)
					self.setFocusClass();

				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)
		{
			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, function(){});
		}

		// Maximum moves
		var size = size = self.getSize() - 1,
		    moves = 0,
		    maxMoves = size - conf.itemsToDisplay + 1;

		// next/prev buttons
		var prev = find(root, conf.prev).click(function(e)
		{
			self.prev();
			e.preventDefault();
		}),
		next = find(root, conf.next).click(function(e)
		{
			self.next();
			e.preventDefault();
		});

		if (!conf.circular && self.getSize() > (1 || conf.itemsToDisplay))
			self.disableButtons(conf.initialIndex);

		// initial index
		if (conf.initialIndex)
			self.seekTo(conf.initialIndex, 0, function(){});

		// Set initial focus
		if (conf.itemsToDisplay == 1)
			self.setFocusClass();
	}

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

		if (el)
			return el;
			
		if ($(conf.items).length == 0)
			return;

		conf = $.extend({}, $.tools.scrollable.conf, conf);

		this.each(function()
		{
			el = new Scrollable($(this), conf);
			$(this).data("scrollable", el);
		});

		return conf.api ? el:this;
	};
})(jQuery);
