/* 
 * 
 * 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: '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 = 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();
	   	  backward();
	   	}
	   	
	   	// 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);
