(function ( $ ) {                  // Compliant with jquery.noConflict()
    $.fn.jMyCarousel = function(o) {   
        o = $.extend({
            btnPrev: null,			// previous button customization
            btnNext: null,			// next button customization
            mouseWheel: true,		// shall the carousel handle the mousewheel event to animate ?
            auto: false,			// shall the carousel start automatically

            speed: 500,				// speed in ms of the animation.
            easing: 'linear',		// linear animation.

            vertical: false,		// set the carousel in a vertical mode
            circular: false,			// run in circular mode. Means : images never reach the end.
            visible: '4',			// size of the carousel on the screen. Can be in percent '100%', in pixels '100px', or in images '3' (for 3 images)
            start: 0,				// position in pixels that the carousel shall start at
            scroll: 1,
        
            step: 50,				// value in pixels, or "default"
            eltByElt: false,		// if activated, the carousel will move image by image, not more, not less.
            evtStart : 'mouseover',	// start event that we want for the animation (click, mouseover, mousedown, etc..)
            evtStop : 'mouseout',	// stop event that we want for the animation (blur, mouseout, mouseup, etc..)
            beforeStart: null,		// Not used yet
            afterEnd: null			// Not used yet
        }, 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;
            var mousewheelN = 0; // will help for the mousewheel effect (to count how many steps we have to walk ahead)
            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; // used to keep in memory in which direction the animation is moving
        
            // circular mode management
            // we add at the end and at the beginning some fake images to make the circular effect more linear, so it never breaks
            // It is still possible to improve the memory management by adding exactly the number of images requested.
            if(o.circular) {
                var imgSet = tLi.clone();
                ul.prepend(imgSet).append(imgSet.clone());
            }
                   
            var li = $("li", ul);								// list       
            div.css("visibility", "visible");
            li.css("overflow", "hidden")                        // If the list item size is bigger than required
            //.css("float", o.vertical ? "none" : "left")     // Horizontal list
            .children().css("overflow", "hidden");          // If the item within li overflows its size, hide'em
            if(!o.vertical){
                li.css("display", "inline");
            }		// IE double margin bug - rooo..
            if(li.children().get(0).tagName.toLowerCase() == 'a' && !o.vertical){
                li.children().css('float','left');
            }
            if(o.vertical && jQuery.browser.msie){				// Hack IE (again..) / purpose is to cancel the white space below the image when the carousel is in vertical mode
                // The issue comes up when li is not in float:left. so we put it in float:left and adjust the size
                li.css('line-height', '4px').children().css('margin-bottom', '-4px');
            }
        

            ul.css("margin", "0")                               // Browsers apply default margin 
            .css("padding", "0")                            // and padding. It is reset here.
            .css("position", "relative")                    // IE BUG - width as min-width
            .css("list-style-type", "none")                 // We dont need any icons representing each list item.
            .css("z-index", "1");                           // IE doesnt respect width. So z-index smaller than div

            div.css("overflow", "hidden")                       // Overflows - works in FF
            .css("position", "relative")                    // position relative and z-index for IE
            .css("z-index", "2")                            // more than ul so that div displays on top of ul
            .css("left", "0px");                            // after creating carousel show it on screen
        
            var liSize = 160;   // Full li size(incl margin)-Used for animation
            var liSizeV = o.vertical ? elHeight(li) : height(li);	// size of the main layer, in its side          
            var curr = o.start;   								// Current position in pixels  
            var nbAllElts = li.size();							// Total number of items  
		
		
		
		
            var ulSize = liSize * nbAllElts;                   	// size of full ul(total length, not just for the visible items)
				
            var nbElts = tl;									// number of elements (only visible items)
            var eltsSize = nbElts * liSize;						// size of the visible elements only
            var allEltsSize = nbAllElts * liSize;				// Total size of the elements
            //var jmcSize = jmcSize();							// Size of the carousel
            var step = o.step == 'default' ? liSize : o.step;	// step size
        
            //debug("liSize=" + liSize + "; liSizeV=" + liSizeV + "; curr=" + curr + "; visible : " + liSize * v); // debug
  		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;
        
            
        
            /******* Buttons **********/
            if(defaultBtn && o.auto !== true && ulSize >=980){ 					//Add buttons when necessary (In default mode and not auto)
                prev.css({
                    'opacity':'0.6'
                });
                next.css({
                    'opacity' :'0.6'
                });
                div.prepend(prev);
                div.prepend(next);
                o.btnPrev = prev;
                o.btnNext = next;
            }
        
            // Element by element management (eltBYElt = true)
            if(o.eltByElt){ 
                step = liSize;									// the step size is necessarily the size of the element
                if(o.start % liSize !== 0){						// If a start position was given and was not exactly positionned between 2 images
                    var imgStart = parseInt(o.start / liSize);	// we adjust it
                    curr = o.start = (imgStart * liSize);		// we set the starting position at a fixed point, between 2 images.
                }
            }
        
            // Adjust the start position in case of circular mode
            if(o.circular){
                o.start += (liSize * tl);  						// The start position is one carousel length ahead due to the optical effect
                curr += (liSize * tl);							// used for the animation
            }
        
            // Calculates the size of the main div according to the given size (can be in percent, in value or in pixels)
            var divSize, cssSize, cssUnity;
            if(cssU == '%'){									// in percent 
                divSize = 0;									// We don't have the value in pixels unless we set the percent value first. So 0, and will catch it later
                cssSize = parseInt(v);  
                cssUnity = "%";
            }
            else if(cssU == 'px'){									// in pixels
                divSize = parseInt(v);
                cssSize = parseInt(v);
                cssUnity = "px";
            }
            else{													// in elements (number of elements to display)
                divSize = liSize * parseInt(v); 
                cssSize = liSize * parseInt(v);
                cssUnity = "px";
            }                      								  

            // Adjust the carousel size with the correct values
            //li.css("width", imgSize(li, 'width'))              	// inner li width. this is the box model width
            //.css("height", imgSize(li), 'height');           	// inner li height. this is the box model height
            ul.css(sizeCss, ulSize + "px")                      	// Width of the UL is the full length for all the images
            .css(animCss, -(o.start));                  	 	// Set the starting item
            div.css(sizeCss, cssSize + cssUnity);                	// Width of the DIV. length of visible images
            if(o.vertical && cssUnity == '%'){						// Bugfix - % in vertical mode are badly handled by the browsers
                var pxsize = ((liSize * nbElts) * (parseInt(v) / 100));
                div.css(sizeCss,  pxsize + 'px');					// The height of the carousel is based on the visible elements size
            }
        
            if(divSize === 0){										// We didn't have the size in pixels in case of % size. Catch up !
                divSize = div.width();								// The size is simply the calculated size in pixels
            }
		
            // Adjust the height of the carousel (width in vertical mode)
            if(o.vertical){											// vertical mode
                div.css("width" , liSizeV + 'px');
                ul.css("width", liSizeV + 'px');
                li.css('margin-bottom', (parseInt(li.css('margin-bottom')) * 2) + 'px');	// bypass the "margin collapsing" effect by multiplying the margin-bottom by 2 
                li.eq(li.size() - 1).css('margin-bottom', li.css('margin-top'));			// Last element has to be the right margin since no margin collapse there
            }else{													// horizontal mode
                div.css('height', liSizeV + 'px');
                ul.css('height', liSizeV + 'px');	
            }
								
            // Calculate the number of visible elements inside (in case of size in percent)							
            if(cssU == '%'){
                v = divSize / li.width();						
                if(v % 1 !== 0){
                    v +=1;
                }
                v = parseInt(v);
            }
		
            var divVSize = div.height();													// div height
		
            ////////////////////////
            // Buttons management //
            ////////////////////////
            if(defaultBtn){
                next.css({
                    'z-index':200, 
                    'position':'absolute'
                });
                prev.css({
                    'z-index':200, 
                    'position':'absolute'
                });
                //Positionate the arrows and adjust the arrow images
                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'
                        });
                }
            }

            // Bind the events with the "previous" button
            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);
