(function($) {
	$.fn.jMyCarousel = function(o) {
		o = $.extend({
			btnPrev: null,
			btnNext: null,
			mouseWheel: true, //true
			auto: false,//false
			speed: 500,
			easing: 'linear',
			vertical: false,
			circular: true,
			visible: '1',
			start: 0,
			scroll: 1,
			step: 150,
			eltByElt: false, //false
			evtStart: 'mouseover',
			evtStop: 'mouseout',
			beforeStart: null,
			afterEnd: null
		},
		o || {});
		return this.each(function() {
			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;
			var mousewheelN = 0;
			var defaultBtn = (o.btnNext === null && o.btnPrev === null) ? true: false;
			var cssU = (v.toString().indexOf("%") != -1 ? '%': (v.toString().indexOf("px") != -1) ? 'px': 'el');
			var direction = null;
			if (o.circular) {
				var imgSet = tLi.clone();
				ul.prepend(imgSet).append(imgSet.clone());
			}
			var li = $("li", ul);
			div.css("visibility", "visible");
			li.css("overflow", "hidden").css("float", o.vertical ? "none": "left").children().css("overflow", "hidden");
			if (!o.vertical) {
				li.css("display", "inline");
			}
			if (li.children().get(0).tagName.toLowerCase() == 'a' && !o.vertical) {
				li.children().css('float', 'left');
			}
			if (o.vertical && jQuery.browser.msie) {
				li.css('line-height', '4px').children().css('margin-bottom', '-4px');
			}
			ul.css("margin", "0").css("padding", "0").css("position", "relative").css("list-style-type", "none").css("z-index", "1");
			div.css("overflow", "hidden").css("position", "relative").css("z-index", "2").css("left", "0px");
			var liSize = o.vertical ? height(li) : width(li);
			var liSizeV = o.vertical ? elHeight(li) : height(li);
			var curr = o.start;
			var nbAllElts = li.size();
			var ulSize = liSize * nbAllElts;
			var nbElts = tl;
			var eltsSize = nbElts * liSize;
			var allEltsSize = nbAllElts * liSize;
			var step = o.step == 'default' ? liSize: o.step;
			o.btnPrev = defaultBtn ? $('<input type="button" class="' + (o.vertical ? 'up': 'prev') + '" />') : $(o.btnPrev);
			o.btnNext = defaultBtn ? $('<input type="button" class="' + (o.vertical ? 'down': 'next') + '" />') : $(o.btnNext);
			var prev = o.btnPrev;
			var next = o.btnNext;
			if (defaultBtn && o.auto !== true) {
				prev.css({
					//'opacity': '0.6'
				});
				next.css({
					//'opacity': '0.6'
				});
				div.prepend(prev);
				div.prepend(next);
				o.btnPrev = prev;
				o.btnNext = next;
			}
			if (o.eltByElt) {
				step = liSize;
				if (o.start % liSize !== 0) {
					var imgStart = parseInt(o.start / liSize);
					curr = o.start = (imgStart * liSize);
				}
			}
			if (o.circular) {
				o.start += (liSize * tl);
				curr += (liSize * tl);
			}
			var divSize, cssSize, cssUnity;
			if (cssU == '%') {
				divSize = 0;
				cssSize = parseInt(v);
				cssUnity = "%";
			} else if (cssU == 'px') {
				divSize = parseInt(v);
				cssSize = parseInt(v);
				cssUnity = "px";
			} else {
				divSize = liSize * parseInt(v);
				cssSize = liSize * parseInt(v);
				cssUnity = "px";
			}
			ul.css(sizeCss, ulSize + "%").css(animCss, -(o.start));
			div.css(sizeCss, cssSize + cssUnity);
			if (o.vertical && cssUnity == '%') {
				var pxsize = ((liSize * nbElts) * (parseInt(v) / 100));
				div.css(sizeCss, pxsize + 'px');
			}
			if (divSize === 0) {
				divSize = div.width();
			}
			if (o.vertical) {
				div.css("width", liSizeV + 'px');
				ul.css("width", liSizeV + 'px');
				li.css('margin-bottom', (parseInt(li.css('margin-bottom')) * 2) + 'px');
				li.eq(li.size() - 1).css('margin-bottom', li.css('margin-top'));
			} else {
				div.css('height', liSizeV + 'px');
				ul.css('height', liSizeV + 'px');
			}
			if (cssU == '%') {
				v = divSize / li.width();
				if (v % 1 !== 0) {
					v += 1;
				}
				v = parseInt(v);
			}
			var divVSize = div.height();
			if (defaultBtn) {
				next.css({
					'z-index': 200,
					'position': 'absolute'
				});
				prev.css({
					'z-index': 200,
					'position': 'absolute'
				});
				if (o.vertical) {
					prev.css({
						'width': prev.width(),
						'height': prev.height(),
						'top': '0px',
						'left': parseInt(liSizeV / 2) - parseInt(prev.width() / 2) + 'px'
					});
					next.css({
						'width': prev.width(),
						'height': prev.height(),
						'top': (divVSize - prev.height()) + 'px',
						'left': parseInt(liSizeV / 2) - parseInt(prev.width() / 2) + 'px'
					});
				} else {
					prev.css({
						'left': '0px',
						'top': parseInt(liSizeV / 2) - parseInt(prev.height() / 2) + 'px'
					});
					next.css({
						'right': '0px',
						'top': parseInt(liSizeV / 2) - parseInt(prev.height() / 2) + 'px'
					});
				}
			}
			if (o.btnPrev) {
				$(o.btnPrev).bind(o.evtStart,
				function() {
					if (defaultBtn) {
						//o.btnPrev.css('opacity', 0.9);
					}
					running = true;
					direction = 'backward';
					return backward();
				});

				$(o.btnPrev).bind(o.evtStop,
				function() {
					if (defaultBtn) {
						//o.btnPrev.css('opacity', 0.6);
					}
					running = false;
					direction = null;
					return stop();
				});
			}

			// Bind the events with the "next" button
			if (o.btnNext) {
				$(o.btnNext).bind(o.evtStart,
				function() {
					if (defaultBtn) {
						//o.btnNext.css('opacity', 0.9);
					}
					running = true;
					direction = 'forward';
					return forward();
				});
				$(o.btnNext).bind(o.evtStop,
				function() {
					if (defaultBtn) {
						//o.btnNext.css('opacity', 0.6);
					}
					running = false;
					direction = null;
					return stop();
				});
			}

			// auto scroll management (auto = true). => launch the animation
			if (o.auto === true) {
				running = true;
				forward();
			}

			// Mousewheel management	
			if (o.mouseWheel && div.mousewheel) {
				div.mousewheel(function(e, d) {
					if (!o.circular && (d > 0 ? (curr + divSize < ulSize) : (curr > 0)) || o.circular) { //prevents the mouse events to occur in case of circular mode
						mousewheelN += 1; //one more step to do, store it.
						if (running === false) {
							if (d > 0) {
								forward(step, true);
							} else {
								backward(step, true);
							}
							running = true;
						}
					}
				});
			}

			/**
		 * Animate the track by moving it forward according to the step size and the speed
		 * @param stepsize, the size of the step (optional)
		 * @param once, shall the animation continue endlessly until we set running to false ? (optional)
		 */
			function forward(stepsize, once) {
				var s = (stepsize ? stepsize: step);

				if (running === true && direction === "backward") {
					return;
				}

				//If not circular, no need to animate endlessly
				if (!o.circular) {
					//will the next step overtake the last  image ?
					if (curr + s + (o.vertical ? divVSize: divSize) > eltsSize) {
						s = eltsSize - (curr + (o.vertical ? divVSize: divSize));
					}
				}

				ul.animate(animCss == "left" ? {
					left: -(curr + s)
				}: {
					top: -(curr + s)
				},
				o.speed, o.easing,
				function() {
					curr += s; //Add step size
					//Calculate whether we cross the limit,
					//if so, put the carousel one time backward
					if (o.circular) {
						if (curr + (o.vertical ? divVSize: divSize) + liSize >= allEltsSize) {
							ul.css(o.vertical ? 'top': 'left', -curr + eltsSize);
							curr -= eltsSize;
						}
					}

					if (!once && running) {
						forward();
					} else if (once) {
						if (--mousewheelN > 0) {
							this.forward(step, true);
						} else {
							running = false;
							direction = null;
						}
					}
				});
			}

			/**
         * Animate the track by moving it backward according to the step size and the speed
         * @param stepsize, the size of the step (optional)
		 * @param once, shall the animation continue endlessly until we set running to false ? (optional)
         */
			function backward(stepsize, once) {
				var s = (stepsize ? stepsize: step);

				if (running === true && direction === "forward") {
					return;
				}

				//If not circular, no need to animate endlessly
				if (!o.circular) {
					//will the next step overtake the first image ?
					if (curr - s < 0) {
						s = curr - 0;
					}
				}

				ul.animate(animCss == "left" ? {
					left: -(curr - s)
				}: {
					top: -(curr - s)
				},
				o.speed, o.easing,
				function() {
					curr -= s;
					//Calculate if we cross the limit,
					//if so, put the carousel one time backward
					if (o.circular) {
						if (curr <= liSize) {
							ul.css(o.vertical ? 'top': 'left', -(curr + eltsSize));
							curr += eltsSize;
						}
					}

					if (!once && running) {
						backward();
					} else if (once) {
						if (--mousewheelN > 0) {
							backward(step, true);
						} else {
							running = false;
							direction = null;
						}
					}
				});
			}
			/**
          * Stops the animation
          * Basically, tells the animation not to continue
          */
			function stop() {
				if (!o.eltByElt) { //If we don't move elements by elements, then we can stop immediately
					ul.stop(); // stop the animation straight
					curr = 0 - parseInt(ul.css(animCss)); // We stopped suddenly, so the curr variable is not refreshed. We refresh it with the true value
				}
				running = false; // default value and in case we proceed element by element (eltByElt = true)
				direction = null;
			}

			/**
         * Return the size of the carousel, everything included (height or length depending on o.vertical)
         */
			/*function jmcSize(){
        	var img = $('ul li img', div);
        	var sizeLi = (o.vertical ? img.width() : img.height());
        	var elt = img;
        	while(elt.parent().get(0).tagName.toLowerCase() != 'div'){
        		sizeLi += (o.vertical ? (parseInt(elt.css('marginLeft')) + parseInt(elt.css('marginRight')) + parseInt(elt.css('paddingRight')) + parseInt(elt.css('paddingLeft'))) : (parseInt(elt.css('marginTop')) + parseInt(elt.css('marginBottom')) + parseInt(elt.css('paddingTop')) + parseInt(elt.css('paddingBottom'))));
        		elt = elt.parent();
        	} 
        	return sizeLi;
        }*/

			/**
         * Calculate and return the size of the image in the element
         * @param el, the element
         * @param dimension, 'width' or 'height'.
         * @return the requested size in pixels.
         */
			function imgSize(el, dimension) {
				if (dimension == 'width') {
					return el.find('img').width();
				} else {
					return el.find('img').height();
				}
			}

			/**
		 * Size of an element li with its margin calculated from scratch (without any call to width except for the image size)
		 * usefull in case of vertical carousel, when the size of each element is 100%.
		 * @param el, the element
		 * @return the size of the element in pixels
		 */
			function elHeight(el) {
				var elImg = el.find('img');
				if (o.vertical) {
					return parseInt(el.css('margin-left')) + parseInt(el.css('margin-right')) + parseInt(elImg.width()) + parseInt(el.css('border-left-width')) + parseInt(el.css('border-right-width')) + parseInt(el.css('padding-right')) + parseInt(el.css('padding-left'));
				} else {
					return parseInt(el.css('margin-top')) + parseInt(el.css('margin-bottom')) + parseInt(elImg.width()) + parseInt(el.css('border-top-height')) + parseInt(el.css('border-bottom-height')) + parseInt(el.css('padding-top')) + parseInt(el.css('padding-bottom'));
				}
			}

			function debug(html) {
				$('#debug').html($('#debug').html() + html + "<br/>");
			}

		});
	};

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

})(jQuery);
