(function($) {
	var methods = {
		init:	function(o) {

			$(this).data('carousel', { paused: false });

			function css(el, prop) {
				return parseInt($.css(el[0], prop)) || 0;
			};
			function width(el) {
				return  el[0].offsetWidth + css(el, 'marginLeft') + css(el, 'marginRight');
			};
			function height(el) {
				return el[0].offsetHeight + css(el, 'marginTop') + css(el, 'marginBottom');
			};

			o = $.extend({
				btnPrev: null,
				btnNext: null,
				btnGo: null,
				mouseWheel: false,
				auto: null,

				speed: 200,
				easing: null,

				vertical: false,
				circular: true,
				visible: 3,
				start: 0,
				scroll: 1,

				beforeStart: null,
				afterEnd: null
			}, o || {});

			return this.each(function() {                           // Returns the element collection. Chainable.

				var running = false, animCss=o.vertical?"top":"left", sizeCss=o.vertical?"height":"width";
				var div = $(this), ul = $("ul", div), tLi = $("li", ul), tl = tLi.size(), v = o.visible;

				if(o.circular) {
					ul.prepend(tLi.slice(tl-v-1+1).clone())
					  .append(tLi.slice(0,v).clone());
					o.start += v;
				}

				var li = $("li", ul), itemLength = li.size(), curr = o.start;
				div.css("visibility", "visible");

				li.css({overflow: "hidden", float: o.vertical ? "none" : "left"});
				ul.css({margin: "0", padding: "0", position: "relative", "list-style-type": "none", "z-index": "1"});
				div.css({overflow: "hidden", position: "absolute", "z-index": "2", left: "0px"});

				var liSize = o.vertical ? height(li) : width(li);   // Full li size(incl margin)-Used for animation
				var ulSize = liSize * itemLength;                   // size of full ul(total length, not just for the visible items)
				var divSize = liSize * v;                           // size of entire div(total length for just the visible items)

				li.css({width: li.width(), height: li.height()});
				ul.css(sizeCss, ulSize+"px").css(animCss, -(curr*liSize));

				div.css(sizeCss, divSize+"px");                     // Width of the DIV. length of visible images

				var $this = $(this), data = $(this).data('carousel');

				if(o.btnPrev)
					$(o.btnPrev).click(function() {
						data.paused = true;
						return data.goRight();
					});

				if(o.btnNext)
					$(o.btnNext).click(function() {
						data.paused = true;
						return data.goLeft();
					});

				if(o.btnGo) {
					$(o.btnGo).each(function(i, val) {
						$(val).click(function() {
							//console.log(i);
							data.paused = true;
							return data.go(o.circular ? o.visible+i : i);
						});
					});
				}

				if(o.mouseWheel && div.mousewheel)
					div.mousewheel(function(e, d) {
						return d>0 ? data.go(curr-o.scroll) : data.go(curr+o.scroll);
					});

				if(o.auto) {

					setInterval(function() {
						if(!data.paused) {
							data.goLeft();
							data.paused = false;
						}
					}, o.auto+o.speed);
				}

				function vis() {
					return li.slice(curr).slice(0,v);
				};

				data.goLeft = function() {
					data.paused = true;

					if(o.beforeGoRight) 
						o.beforeGoRight.call(this, vis());

					data.go(curr+o.scroll);
				}

				data.goRight = function() {
					data.paused = true;

					if(o.beforeGoLeft) 
						o.beforeGoLeft.call(this, vis());

					data.go(curr-o.scroll);
				}

				data.skipTo = function(to) {
					data.paused = true;

					if(o.beforeSkipTo) 
						o.beforeSkipTo.call(this, vis(), to);

					data.go(to);
				}

				data.go = function(to) {

					if(!running) {

						if(o.beforeStart)
							o.beforeStart.call(this, vis(), to);

						if(o.circular) {            // If circular we are in first or last, then goto the other end
							if(to<=o.start-v-1) {           // If first, then goto last
								ul.css(animCss, -((itemLength-(v*2))*liSize)+"px");
								// If "scroll" > 1, then the "to" might not be equal to the condition; it can be lesser depending on the number of elements.
								curr = to==o.start-v-1 ? itemLength-(v*2)-1 : itemLength-(v*2)-o.scroll;
							} else if(to>=itemLength-v+1) { // If last, then goto first
								ul.css(animCss, -( (v) * liSize ) + "px" );
								// If "scroll" > 1, then the "to" might not be equal to the condition; it can be greater depending on the number of elements.
								curr = to==itemLength-v+1 ? v+1 : v+o.scroll;
							} else curr = to;
						} else {                    // If non-circular and to points to first or last, we just return.
							if(to<0 || to>itemLength-v) return;
							else curr = to;
						}                           // If neither overrides it, the curr will still be "to" and we can proceed.

						running = true;

						//console.log(-(curr * liSize));

						ul.animate(
							animCss == "left" ? { left: -(curr * liSize) } : { top: -(curr*liSize) }, o.speed, o.easing,
							function() {
								if(o.afterEnd)
									o.afterEnd.call(this, vis());
								running = false;
							}
						);
						// Disable buttons when the carousel reaches the last/first, and enable when not
						if(!o.circular) {
							$(o.btnPrev + "," + o.btnNext).removeClass("disabled");
							$( (curr-o.scroll<0 && o.btnPrev)
								||
							   (curr+o.scroll > itemLength-v && o.btnNext)
								||
							   []
							 ).addClass("disabled");
						}

					}
					return false;
				};
			});

		},

		pause:	function() {
			var $this = $(this), data = $this.data('carousel');
			data.paused = true;
		},

		skipTo: function(index) {
			var $this = $(this), data = $this.data('carousel');
			data.skipTo(index);
		},

		goLeft: function(index) {
			var $this = $(this), data = $this.data('carousel');
			data.goLeft();
		},

		goRight: function(index) {
			var $this = $(this), data = $this.data('carousel');
			data.goRight();
		}
	};

	$.fn.carousel = function(method) {
		if(methods[method]) {
			return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
		}
		else if(typeof method === 'object') {
			return methods.init.apply(this, arguments);
		}
		else {
			$.error('Method ' + method + ' does not exist on jQuery.carousel');
		}
	}
})(jQuery);

