/* 

 * 

 * Copyright (c) 2007 e-nova technologies pvt. ltd. (kevin.muller@enova-tech.net || http://www.enova-tech.net)

 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)

 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.

 *            __             ___      ___    __  __     __     

 *          /'__`\    __   /' _ `\   / __`\ /\ \/\ \  /'__`\   

 *         /\  __/  /\__\  /\ \/\ \ /\ \_\ \\ \ \_/ |/\ \_\.\_ 

 *  (o_    \ \____\ \/__/  \ \_\ \_\\ \____/ \ \___/ \ \__/.\_\    _o)

 *  (/)_    \/____/         \/_/\/_/ \/___/   \/__/   \/__/\/_/   _(\)

 *       

 *           Prevents Headaches !  

 * 

 * JMyCarousel is inspired and based on JCarouselLite, an original concept by Ganeshji Marwaha

 *  

 *

 * $LastChangedDate: 2007-06-22 20:08:34 -0500 (Thu, 22 Nov 2007) $

 * $Rev: 15 $

 *

 * Version: 0.1

 */



(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: true,			// run in circular mode. Means : images never reach the end.

        visible: '590px',			// 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 = o.vertical ? height(li) : width(li);   // 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){ 					//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);
