/*!
 * jCarousel - Riding carousels with jQuery
 *   http://sorgalla.com/jcarousel/
 *
 * Copyright (c) 2006 Jan Sorgalla (http://sorgalla.com)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Built on top of the jQuery library
 *   http://jquery.com
 *
 * Inspired by the "Carousel Component" by Bill Scott
 *   http://billwscott.com/carousel/
 */

(function($) {
	$.fn.jcarousel = function(o) {
		if (typeof o == 'string') {
			var instance = $(this).data('jcarousel'), args = Array.prototype.slice
					.call(arguments, 1);
			return instance[o].apply(instance, args);
		} else
			return this.each(function() {
				$(this).data('jcarousel', new $jc(this, o));
			});
	};
	var defaults = {
		vertical : false,
		start : 1,
		offset : 1,
		size : null,
		scroll : 3,
		visible : null,
		animation : 'normal',
		easing : 'swing',
		auto : 0,
		wrap : null,
		initCallback : null,
		reloadCallback : null,
		itemLoadCallback : null,
		itemFirstInCallback : null,
		itemFirstOutCallback : null,
		itemLastInCallback : null,
		itemLastOutCallback : null,
		itemVisibleInCallback : null,
		itemVisibleOutCallback : null,
		buttonNextHTML : '<div></div>',
		buttonPrevHTML : '<div></div>',
		buttonNextEvent : 'click',
		buttonPrevEvent : 'click',
		buttonNextCallback : null,
		buttonPrevCallback : null
	};
	$.jcarousel = function(e, o) {
		this.options = $.extend( {}, defaults, o || {});
		this.locked = false;
		this.container = null;
		this.clip = null;
		this.list = null;
		this.buttonNext = null;
		this.buttonPrev = null;
		this.wh = !this.options.vertical ? 'width' : 'height';
		this.lt = !this.options.vertical ? 'left' : 'top';
		var skin = '', split = e.className.split(' ');
		for ( var i = 0; i < split.length; i++) {
			if (split[i].indexOf('jcarousel-skin') != -1) {
				$(e).removeClass(split[i]);
				skin = split[i];
				break;
			}
		}
		if (e.nodeName == 'UL' || e.nodeName == 'OL') {
			this.list = $(e);
			this.container = this.list.parent();
			if (this.container.hasClass('jcarousel-clip')) {
				if (!this.container.parent().hasClass('jcarousel-container'))
					this.container = this.container.wrap('<div></div>');
				this.container = this.container.parent();
			} else if (!this.container.hasClass('jcarousel-container'))
				this.container = this.list.wrap('<div></div>').parent();
		} else {
			this.container = $(e);
			this.list = this.container.find('ul,ol').eq(0);
		}
		if (skin != ''
				&& this.container.parent()[0].className
						.indexOf('jcarousel-skin') == -1)
			this.container.wrap('<div class=" ' + skin + '"></div>');
		this.clip = this.list.parent();
		if (!this.clip.length || !this.clip.hasClass('jcarousel-clip'))
			this.clip = this.list.wrap('<div></div>').parent();
		this.buttonNext = $('.jcarousel-next', this.container);
		if (this.buttonNext.size() == 0 && this.options.buttonNextHTML != null)
			this.buttonNext = this.clip.after(this.options.buttonNextHTML)
					.next();
		this.buttonNext.addClass(this.className('jcarousel-next'));
		this.buttonPrev = $('.jcarousel-prev', this.container);
		if (this.buttonPrev.size() == 0 && this.options.buttonPrevHTML != null)
			this.buttonPrev = this.clip.after(this.options.buttonPrevHTML)
					.next();
		this.buttonPrev.addClass(this.className('jcarousel-prev'));
		this.clip.addClass(this.className('jcarousel-clip')).css( {
			overflow : 'hidden',
			position : 'relative'
		});
		this.list.addClass(this.className('jcarousel-list')).css( {
			overflow : 'hidden',
			position : 'relative',
			top : 0,
			left : 0,
			margin : 0,
			padding : 0
		});
		this.container.addClass(this.className('jcarousel-container')).css( {
			position : 'relative'
		});
		var di = this.options.visible != null ? Math.ceil(this.clipping()
				/ this.options.visible) : null;
		var li = this.list.children('li');
		var self = this;
		if (li.size() > 0) {
			var wh = 0, i = this.options.offset;
			li.each(function() {
				self.format(this, i++);
				wh += self.dimension(this, di);
			});
			this.list.css(this.wh, wh + 'px');
			if (!o || o.size === undefined)
				this.options.size = li.size();
		}
		this.container.css('display', 'block');
		this.buttonNext.css('display', 'block');
		this.buttonPrev.css('display', 'block');
		this.funcNext = function() {
			self.next();
		};
		this.funcPrev = function() {
			self.prev();
		};
		this.funcResize = function() {
			self.reload();
		};
		if (this.options.initCallback != null)
			this.options.initCallback(this, 'init');
		if ($.browser.safari) {
			this.buttons(false, false);
			$(window).bind('load.jcarousel', function() {
				self.setup();
			});
		} else
			this.setup();
	};
	var $jc = $.jcarousel;
	$jc.fn = $jc.prototype = {
		jcarousel : '0.2.4'
	};
	$jc.fn.extend = $jc.extend = $.extend;
	$jc.fn
			.extend( {
				setup : function() {
					this.first = null;
					this.last = null;
					this.prevFirst = null;
					this.prevLast = null;
					this.animating = false;
					this.timer = null;
					this.tail = null;
					this.inTail = false;
					if (this.locked)
						return;
					this.list
							.css(this.lt, this.pos(this.options.offset) + 'px');
					var p = this.pos(this.options.start);
					this.prevFirst = this.prevLast = null;
					this.animate(p, false);
					$(window).unbind('resize.jcarousel', this.funcResize).bind(
							'resize.jcarousel', this.funcResize);
				},
				reset : function() {
					this.list.empty();
					this.list.css(this.lt, '0px');
					this.list.css(this.wh, '10px');
					if (this.options.initCallback != null)
						this.options.initCallback(this, 'reset');
					this.setup();
				},
				reload : function() {
					if (this.tail != null && this.inTail)
						this.list.css(this.lt, $jc.intval(this.list
								.css(this.lt))
								+ this.tail);
					this.tail = null;
					this.inTail = false;
					if (this.options.reloadCallback != null)
						this.options.reloadCallback(this);
					if (this.options.visible != null) {
						var self = this;
						var di = Math.ceil(this.clipping()
								/ this.options.visible), wh = 0, lt = 0;
						$('li', this.list).each(function(i) {
							wh += self.dimension(this, di);
							if (i + 1 < self.first)
								lt = wh;
						});
						this.list.css(this.wh, wh + 'px');
						this.list.css(this.lt, -lt + 'px');
					}
					this.scroll(this.first, false);
				},
				lock : function() {
					this.locked = true;
					this.buttons();
				},
				unlock : function() {
					this.locked = false;
					this.buttons();
				},
				size : function(s) {
					if (s != undefined) {
						this.options.size = s;
						if (!this.locked)
							this.buttons();
					}
					return this.options.size;
				},
				has : function(i, i2) {
					if (i2 == undefined || !i2)
						i2 = i;
					if (this.options.size !== null && i2 > this.options.size)
						i2 = this.options.size;
					for ( var j = i; j <= i2; j++) {
						var e = this.get(j);
						if (!e.length
								|| e.hasClass('jcarousel-item-placeholder'))
							return false;
					}
					return true;
				},
				get : function(i) {
					return $('.jcarousel-item-' + i, this.list);
				},
				add : function(i, s) {
					var e = this.get(i), old = 0, add = 0;
					if (e.length == 0) {
						var c, e = this.create(i), j = $jc.intval(i);
						while (c = this.get(--j)) {
							if (j <= 0 || c.length) {
								j <= 0 ? this.list.prepend(e) : c.after(e);
								break;
							}
						}
					} else
						old = this.dimension(e);
					e.removeClass(this.className('jcarousel-item-placeholder'));
					typeof s == 'string' ? e.html(s) : e.empty().append(s);
					var di = this.options.visible != null ? Math.ceil(this
							.clipping()
							/ this.options.visible) : null;
					var wh = this.dimension(e, di) - old;
					if (i > 0 && i < this.first)
						this.list.css(this.lt, $jc.intval(this.list
								.css(this.lt))
								- wh + 'px');
					this.list.css(this.wh, $jc.intval(this.list.css(this.wh))
							+ wh + 'px');
					return e;
				},
				remove : function(i) {
					var e = this.get(i);
					if (!e.length || (i >= this.first && i <= this.last))
						return;
					var d = this.dimension(e);
					if (i < this.first)
						this.list.css(this.lt, $jc.intval(this.list
								.css(this.lt))
								+ d + 'px');
					e.remove();
					this.list.css(this.wh, $jc.intval(this.list.css(this.wh))
							- d + 'px');
				},
				next : function() {
					this.stopAuto();
					if (this.tail != null && !this.inTail)
						this.scrollTail(false);
					else
						this
								.scroll(((this.options.wrap == 'both' || this.options.wrap == 'last')
										&& this.options.size != null && this.last == this.options.size) ? 1
										: this.first + this.options.scroll);
				},
				prev : function() {
					this.stopAuto();
					if (this.tail != null && this.inTail)
						this.scrollTail(true);
					else
						this
								.scroll(((this.options.wrap == 'both' || this.options.wrap == 'first')
										&& this.options.size != null && this.first == 1) ? this.options.size
										: this.first - this.options.scroll);
				},
				scrollTail : function(b) {
					if (this.locked || this.animating || !this.tail)
						return;
					var pos = $jc.intval(this.list.css(this.lt));
					!b ? pos -= this.tail : pos += this.tail;
					this.inTail = !b;
					this.prevFirst = this.first;
					this.prevLast = this.last;
					this.animate(pos);
				},
				scroll : function(i, a) {
					if (this.locked || this.animating)
						return;
					this.animate(this.pos(i), a);
				},
				pos : function(i) {
					var pos = $jc.intval(this.list.css(this.lt));
					if (this.locked || this.animating)
						return pos;
					if (this.options.wrap != 'circular')
						i = i < 1 ? 1 : (this.options.size
								&& i > this.options.size ? this.options.size
								: i);
					var back = this.first > i;
					var f = this.options.wrap != 'circular' && this.first <= 1 ? 1
							: this.first;
					var c = back ? this.get(f) : this.get(this.last);
					var j = back ? f : f - 1;
					var e = null, l = 0, p = false, d = 0, g;
					while (back ? --j >= i : ++j < i) {
						e = this.get(j);
						p = !e.length;
						if (e.length == 0) {
							e = this
									.create(j)
									.addClass(
											this
													.className('jcarousel-item-placeholder'));
							c[back ? 'before' : 'after'](e);
							if (this.first != null
									&& this.options.wrap == 'circular'
									&& this.options.size !== null
									&& (j <= 0 || j > this.options.size)) {
								g = this.get(this.index(j));
								if (g.length)
									this.add(j, g.children().clone(true));
							}
						}
						c = e;
						d = this.dimension(e);
						if (p)
							l += d;
						if (this.first != null
								&& (this.options.wrap == 'circular' || (j >= 1 && (this.options.size == null || j <= this.options.size))))
							pos = back ? pos + d : pos - d;
					}
					var clipping = this.clipping();
					var cache = [];
					var visible = 0, j = i, v = 0;
					var c = this.get(i - 1);
					while (++visible) {
						e = this.get(j);
						p = !e.length;
						if (e.length == 0) {
							e = this
									.create(j)
									.addClass(
											this
													.className('jcarousel-item-placeholder'));
							c.length == 0 ? this.list.prepend(e)
									: c[back ? 'before' : 'after'](e);
							if (this.first != null
									&& this.options.wrap == 'circular'
									&& this.options.size !== null
									&& (j <= 0 || j > this.options.size)) {
								g = this.get(this.index(j));
								if (g.length)
									this.add(j, g.find('>*').clone(true));
							}
						}
						c = e;
						var d = this.dimension(e);
						if (d == 0) {
							//alert('jCarousel: No width/height set for items. This will cause an infinite loop. Aborting...');
							break;
							return 0;
						}
						if (this.options.wrap != 'circular'
								&& this.options.size !== null
								&& j > this.options.size)
							cache.push(e);
						else if (p)
							l += d;
						v += d;
						if (v >= clipping)
							break;
						j++;
					}
					for ( var x = 0; x < cache.length; x++)
						cache[x].remove();
					if (l > 0) {
						this.list.css(this.wh, this.dimension(this.list) + l
								+ 'px');
						if (back) {
							pos -= l;
							this.list.css(this.lt, $jc.intval(this.list
									.css(this.lt))
									- l + 'px');
						}
					}
					var last = i + visible - 1;
					if (this.options.wrap != 'circular' && this.options.size
							&& last > this.options.size)
						last = this.options.size;
					if (j > last) {
						visible = 0, j = last, v = 0;
						while (++visible) {
							var e = this.get(j--);
							if (!e.length)
								break;
							v += this.dimension(e);
							if (v >= clipping)
								break;
						}
					}
					var first = last - visible + 1;
					if (this.options.wrap != 'circular' && first < 1)
						first = 1;
					if (this.inTail && back) {
						pos += this.tail;
						this.inTail = false;
					}
					this.tail = null;
					if (this.options.wrap != 'circular'
							&& last == this.options.size
							&& (last - visible + 1) >= 1) {
						var m = $jc.margin(this.get(last),
								!this.options.vertical ? 'marginRight'
										: 'marginBottom');
						if ((v - m) > clipping)
							this.tail = v - clipping - m;
					}
					while (i-- > first)
						pos += this.dimension(this.get(i));
					this.prevFirst = this.first;
					this.prevLast = this.last;
					this.first = first;
					this.last = last;
					return pos;
				},
				animate : function(p, a) {
					if (this.locked || this.animating)
						return;
					this.animating = true;
					var self = this;
					var scrolled = function() {
						self.animating = false;
						if (p == 0)
							self.list.css(self.lt, 0);
						if (self.options.wrap == 'circular'
								|| self.options.wrap == 'both'
								|| self.options.wrap == 'last'
								|| self.options.size == null
								|| self.last < self.options.size)
							self.startAuto();
						self.buttons();
						self.notify('onAfterAnimation');
					};
					this.notify('onBeforeAnimation');
					if (!this.options.animation || a == false) {
						this.list.css(this.lt, p + 'px');
						scrolled();
					} else {
						var o = !this.options.vertical ? {
							'left' : p
						} : {
							'top' : p
						};
						this.list.animate(o, this.options.animation,
								this.options.easing, scrolled);
					}
				},
				startAuto : function(s) {
					if (s != undefined)
						this.options.auto = s;
					if (this.options.auto == 0)
						return this.stopAuto();
					if (this.timer != null)
						return;
					var self = this;
					this.timer = setTimeout(function() {
						self.next();
					}, this.options.auto * 1000);
				},
				stopAuto : function() {
					if (this.timer == null)
						return;
					clearTimeout(this.timer);
					this.timer = null;
				},
				buttons : function(n, p) {
					if (n == undefined || n == null) {
						var n = !this.locked
								&& this.options.size !== 0
								&& ((this.options.wrap && this.options.wrap != 'first')
										|| this.options.size == null || this.last < this.options.size);
						if (!this.locked
								&& (!this.options.wrap || this.options.wrap == 'first')
								&& this.options.size != null
								&& this.last >= this.options.size)
							n = this.tail != null && !this.inTail;
					}
					if (p == undefined || p == null) {
						var p = !this.locked
								&& this.options.size !== 0
								&& ((this.options.wrap && this.options.wrap != 'last') || this.first > 1);
						if (!this.locked
								&& (!this.options.wrap || this.options.wrap == 'last')
								&& this.options.size != null && this.first == 1)
							p = this.tail != null && this.inTail;
					}
					var self = this;
					this.buttonNext[n ? 'bind' : 'unbind'](
							this.options.buttonNextEvent + '.jcarousel',
							this.funcNext)[n ? 'removeClass' : 'addClass'](
							this.className('jcarousel-next-disabled')).attr(
							'disabled', n ? false : true);
					this.buttonPrev[p ? 'bind' : 'unbind'](
							this.options.buttonPrevEvent + '.jcarousel',
							this.funcPrev)[p ? 'removeClass' : 'addClass'](
							this.className('jcarousel-prev-disabled')).attr(
							'disabled', p ? false : true);
					if (this.buttonNext.length > 0
							&& (this.buttonNext[0].jcarouselstate == undefined || this.buttonNext[0].jcarouselstate != n)
							&& this.options.buttonNextCallback != null) {
						this.buttonNext.each(function() {
							self.options.buttonNextCallback(self, this, n);
						});
						this.buttonNext[0].jcarouselstate = n;
					}
					if (this.buttonPrev.length > 0
							&& (this.buttonPrev[0].jcarouselstate == undefined || this.buttonPrev[0].jcarouselstate != p)
							&& this.options.buttonPrevCallback != null) {
						this.buttonPrev.each(function() {
							self.options.buttonPrevCallback(self, this, p);
						});
						this.buttonPrev[0].jcarouselstate = p;
					}
				},
				notify : function(evt) {
					var state = this.prevFirst == null ? 'init'
							: (this.prevFirst < this.first ? 'next' : 'prev');
					this.callback('itemLoadCallback', evt, state);
					if (this.prevFirst !== this.first) {
						this.callback('itemFirstInCallback', evt, state,
								this.first);
						this.callback('itemFirstOutCallback', evt, state,
								this.prevFirst);
					}
					if (this.prevLast !== this.last) {
						this.callback('itemLastInCallback', evt, state,
								this.last);
						this.callback('itemLastOutCallback', evt, state,
								this.prevLast);
					}
					this.callback('itemVisibleInCallback', evt, state,
							this.first, this.last, this.prevFirst,
							this.prevLast);
					this.callback('itemVisibleOutCallback', evt, state,
							this.prevFirst, this.prevLast, this.first,
							this.last);
				},
				callback : function(cb, evt, state, i1, i2, i3, i4) {
					if (this.options[cb] == undefined
							|| (typeof this.options[cb] != 'object' && evt != 'onAfterAnimation'))
						return;
					var callback = typeof this.options[cb] == 'object' ? this.options[cb][evt]
							: this.options[cb];
					if (!$.isFunction(callback))
						return;
					var self = this;
					if (i1 === undefined)
						callback(self, state, evt);
					else if (i2 === undefined)
						this.get(i1).each(function() {
							callback(self, this, i1, state, evt);
						});
					else {
						for ( var i = i1; i <= i2; i++)
							if (i !== null && !(i >= i3 && i <= i4))
								this.get(i).each(function() {
									callback(self, this, i, state, evt);
								});
					}
				},
				create : function(i) {
					return this.format('<li></li>', i);
				},
				format : function(e, i) {
					var $e = $(e).addClass(this.className('jcarousel-item'))
							.addClass(this.className('jcarousel-item-' + i))
							.css( {
								'float' : 'left',
								'list-style' : 'none'
							});
					$e.attr('jcarouselindex', i);
					return $e;
				},
				className : function(c) {
					return c
							+ ' '
							+ c
							+ (!this.options.vertical ? '-horizontal'
									: '-vertical');
				},
				dimension : function(e, d) {
					var el = e.jquery != undefined ? e[0] : e;
					var old = !this.options.vertical ? el.offsetWidth
							+ $jc.margin(el, 'marginLeft')
							+ $jc.margin(el, 'marginRight') : el.offsetHeight
							+ $jc.margin(el, 'marginTop')
							+ $jc.margin(el, 'marginBottom');
					if (d == undefined || old == d)
						return old;
					var w = !this.options.vertical ? d
							- $jc.margin(el, 'marginLeft')
							- $jc.margin(el, 'marginRight') : d
							- $jc.margin(el, 'marginTop')
							- $jc.margin(el, 'marginBottom');
					$(el).css(this.wh, w + 'px');
					return this.dimension(el);
				},
				clipping : function() {
					return !this.options.vertical ? this.clip[0].offsetWidth
							- $jc.intval(this.clip.css('borderLeftWidth'))
							- $jc.intval(this.clip.css('borderRightWidth'))
							: this.clip[0].offsetHeight
									- $jc.intval(this.clip
											.css('borderTopWidth'))
									- $jc.intval(this.clip
											.css('borderBottomWidth'));
				},
				index : function(i, s) {
					if (s == undefined)
						s = this.options.size;
					return Math.round((((i - 1) / s) - Math.floor((i - 1) / s))
							* s) + 1;
				}
			});
	$jc.extend( {
		defaults : function(d) {
			return $.extend(defaults, d || {});
		},
		margin : function(e, p) {
			if (!e)
				return 0;
			var el = e.jquery != undefined ? e[0] : e;
			if (p == 'marginRight' && $.browser.safari) {
				var old = {
					'display' : 'block',
					'float' : 'none',
					'width' : 'auto'
				}, oWidth, oWidth2;
				$.swap(el, old, function() {
					oWidth = el.offsetWidth;
				});
				old['marginRight'] = 0;
				$.swap(el, old, function() {
					oWidth2 = el.offsetWidth;
				});
				return oWidth2 - oWidth;
			}
			return $jc.intval($.css(el, p));
		},
		intval : function(v) {
			v = parseInt(v);
			return isNaN(v) ? 0 : v;
		}
	});
})(jQuery);

