 
 /* 
  
  TODO Carousel will work horizontally and vertically - Needs testing but I've gone for implementing for both.
  TODO DONE Needs to be able to rewind when reaches the end of the content
  TODO DONE Needs to be able to scroll continually.
  TODO Endpoints will fire events. So that buttons can be dynamically Disabled/Enabled
  TODO DONE moving the carousel will fire an event - Built-in to animation
  TODO DONE Randomisation
  TODO DONE Pagination Generation
  TODO Update pagination based on movement and when clicked.
  TODO Base slide size on width (horizontal) or height (vertical) of container.
  
 // Pseudo-code for bombproof wraparound.
 moveto = currentpos + number to move
 if (moveto <= number of slides){
    goto(moveto) 
 }elseif (moveto > number of slides){
   goto(currentpos - number of slides)
 }
 
 
 
 
 -3-2-1                    101112  
  7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
        0+3=3 3+3=6 6+3=9 9+3=12 
        0 1 2 
              3 4 5
                    6 7 8
                          9 0 1
      -1+3=2              
      9 0 1 2+3=5
            2 3 4 5+3=8
                  5 6 7 8+3=11
                        8 9 0
    8 9 0  
          1 2 3
                4 5 6
                      7 8 9
                            0 1 2                                     
  TODO This.toSlide must go to position 0 regardless of continuous mode or not.
  
  */
  
  /* Notes: 
  
  The container is the div that provides the view of ths slides. The size of everything is dictated by this.
  The carousel is the UL that contains the lis. 
  A slide is equivalent to an LI
  
  toSlide advances to the exact slide asked for. 1 is the first slide 2 is the seconds etc.
  
 */

  YAHOO.namespace('YAHOO.EU.widget.Carousel');
  YAHOO.namespace('YAHOO.EU.Shopping');
  YAHOO.EU.widget.Carousel = function(oConfig){
    this.oContainer = (typeof oConfig.vContainer == 'string') ? document.getElementById(oConfig.vContainer) : oConfig.vContainer; // The element containing th UL which will become a carousel
    this.oCarousel = YAHOO.util.Dom.getElementsByClassName('carousel', 'ul', this.oContainer)[0]; // The carousel is the ul containing the lis which are the slides.
    this.sLoopMode = oConfig.sLoopMode || null; // Whether the carousel should loop continuously or rewind.
    this.iNumSlidesVisible = oConfig.iNumSlidesVisible || 1; // Number of slides (List-items) visible in the carousel at any one time
    this.iSlideAdvance = oConfig.iSlideAdvance || 1; // Number of slides (List-items) advanced at any one time. This won't exceed this.nSlidesVisible because that would be daft.
    this.aSlides = this.getSlides(this.oCarousel); // Array of slides (List-items)
    this.iNumSlides = this.aSlides.length; // Number of slides (List-items)
    this.bRandomStart = oConfig.bRandomStart || false;
    this.iCurrentSlide = (this.bRandomStart) ? this.getRandomStart() : 0; //  This should always reflect the current slide irrespective of cloned slides (List-items) created to make continuous mode possible.
    this.sJsEnabledClass = oConfig.sJsEnabledClass || 'js'; // Class to set on the container when js is enabled. This makes it easier to provide fallbacks.
    this.sAspect = oConfig.sAspect || 'horizontal'; // 'horizontal' or 'vertical' - defaults to horizontal.
    this.nAnimDuration = oConfig.nAnimDuration || 0.5; // Set the animation speed
    this.oAnimation = new YAHOO.util.Anim(this.oCarousel); // Init Animation
    this.bAutoPlay = oConfig.bAutoPlay || false; // Autoplay. Defaults to false.
    this.iAutoPlayCount = oConfig.iAutoPlayCount || -1; // AutoPlay count defaults to -1 which is unlimited.
    this.iAutoPlayInterval = oConfig.iAutoPlayInterval || 2000; // AutoPlay interval in millseconds.
    this.oPagination = oConfig.oPagination || false;
    this.oControls = oConfig.oControls || false;
    this.vElmNextSlide =  oConfig.vElmNextSlide || null;
    this.vElmPrevSlide =  oConfig.vElmPrevSlide || null;
    this.iRemainderSlides = null;
    this.createCarousel();
  	return this;
  };
  
  
  YAHOO.EU.widget.Carousel.prototype = {
   
  getRandomStart : function(){
    return (((Math.floor(Math.random() * (this.iNumSlides/this.iSlideAdvance) + 1))-1) * this.iSlideAdvance);
  }, 
  
  /* This function sets up the carousel and moves it to the right position if there are cloned elements */ 
  createCarousel : function(){
    if (this.oContainer){
      // Dimensions of the Carousel
      YAHOO.util.Dom.setStyle(this.oContainer, 'position', 'relative'); // Required for IE because it's a bug ridden piece of crap.
      YAHOO.util.Dom.setStyle(this.oContainer, 'overflow', 'hidden');
      this.oCarouselRegion = YAHOO.util.Dom.getRegion(this.oContainer);
      YAHOO.util.Dom.addClass(this.oContainer,this.sJsEnabledClass);      
      
      // If this carousel is continuous we need some generated slides and front and end of the carousel
      // this.iSlideAdvance;
      if (this.sLoopMode == 'continuous'){
        var firstSlide = this.aSlides[0];
        for(var i=0; i<this.iNumSlidesVisible; i++) {
          j=this.aSlides[i].cloneNode(true); 
          k=this.aSlides[(this.iNumSlides-1)-i].cloneNode(true); 
          this.oCarousel.appendChild(j);
          this.oCarousel.insertBefore(k, firstSlide);
          firstSlide = k;
        };  
      }
      switch(this.sAspect){
        case 'horizontal':
          // Get the padding of the containing element
          var horizPadding = parseInt(YAHOO.util.Dom.getStyle(this.oContainer, 'paddingLeft')) + parseInt(YAHOO.util.Dom.getStyle(this.oContainer, 'paddingRight')) + parseInt(YAHOO.util.Dom.getStyle(this.oContainer, 'borderLeftWidth')) + parseInt(YAHOO.util.Dom.getStyle(this.oContainer, 'borderRightWidth'));      
          // Get the width of the carousel 'view'
          this.iCarouselWidth = 610; //this.oCarouselRegion['right'] - this.oCarouselRegion['left'] - horizPadding;
          // Get the amount to move
          this.nMovement = (this.iCarouselWidth/this.iNumSlidesVisible);
          break;
        case 'vertical':
          var vertPadding = parseInt(YAHOO.util.Dom.getStyle(this.oContainer, 'paddingTop')) + parseInt(YAHOO.util.Dom.getStyle(this.oContainer, 'paddingBottom')) + parseInt(YAHOO.util.Dom.getStyle(this.oContainer, 'borderTopWidth')) + parseInt(YAHOO.util.Dom.getStyle(this.oContainer, 'borderBottomWidth'));
          this.iCarouselHeight = this.oCarouselRegion['top'] - this.oCarouselRegion['bottom'] - vertPadding;
          // Get the amount to move
          this.nMovement = (this.iCarouselHeight/this.iNumSlidesVisible);
          break;
      }
      //  Move to correct starting position having cloned nodes for the continuous mode.
      if (this.sLoopMode == 'continuous' && !this.bRandomStart) this.toSlide(0,0);
      // Move to random position if randomisation is enabled.
      if (this.bRandomStart) this.toSlide(this.iCurrentSlide,0);
      // Start autoplay if configured  
      if (this.bAutoPlay){
        var that = this;
        this.autoPlayTimer = setInterval(function(){
          //alert('fired');
          if (that.iAutoPlayCount > -1 && that.iAutoPlayCount > 0){
            that.iAutoPlayCount--;
            that.next();
          }else if (that.iAutoPlayCount == -1){
            that.next();
          }else{
            clearInterval(that.autoPlayTimer);
          }
        },this.iAutoPlayInterval);
      }
    }    
    // Events for previous and next.
    if (this.vElmPrevSlide) YAHOO.util.Event.onAvailable(this.vElmPrevSlide, function(e){
      YAHOO.util.Event.addListener(this.vElmPrevSlide, 'click', this.previous, this, true);
    }, this, true);
    if (this.vElmNextSlide) YAHOO.util.Event.onAvailable(this.vElmNextSlide, function(e){
      YAHOO.util.Event.addListener(this.vElmNextSlide, 'click', this.next, this, true);
    }, this, true);
        
    // Custom Events for endpoints
    this.onFarEnd 	= new YAHOO.util.CustomEvent("onFarEnd");
    this.onNearEnd	= new YAHOO.util.CustomEvent("onNearEnd");
    
    if (this.oPagination || this.oControls) this.buildControls();
    if (this.oPagination) this.updatePagination();

  },

  insertAfter:function(oElm, vRefElm){
    var vRefElm = (typeof vRefElm == 'string') ? document.getElementById(vRefElm) : vRefElm;
  	var oParent = vRefElm.parentNode;
  	(oParent.lastChild === vRefElm) ? oParent.appendChild(oElm) : oParent.insertBefore(oElm, vRefElm.nextSibling);
  },

  
  /*
  Creates a set of links that creates paging for the items 
  Takes an object for configuration
  
  oPagination = {
    sId: 'blah', // Optional
    sInsertion: 'append', // append, before, after
    vParent: 'blahparent', // Needed for insertion
    vRef: 'blah2' // Reference id needed for insertBefore and insertAfter.
  }
  */
  buildControls:function(){
    
    var insertControls = function(oElm, vRef, sInsertionMethod, o){       
      var vRef = YAHOO.util.Dom.get(vRef);
      switch (sInsertionMethod){
        case 'before':
          if (vRef) var oParent = YAHOO.util.Dom.get(vRef).parentNode;
          if (oParent && vRef) oParent.insertBefore(oElm,vRef);
        break;                                                   
        case 'after':                                            
          if (vRef) o.insertAfter(oElm,vRef);
        break;
        case 'append': default:
          if (vRef) vRef.appendChild(oElm);
        break;
      }
    };
    if (this.oControls){
      var ul = document.createElement('ul');
      if (this.oControls.sId) ul.id = this.oControls.sId;
      YAHOO.util.Dom.addClass(ul, this.oControls.sClassName);      
      var next = document.createElement('a');
      var prev = document.createElement('a');
      next.href="#";
      prev.href="#";
      next.className="next";
      prev.className="prev";
      next.appendChild(document.createTextNode(this.oControls.sNextTxt));
      prev.appendChild(document.createTextNode(this.oControls.sPrevTxt));
      var li = document.createElement('li');
      var li2 = document.createElement('li');
      li.appendChild(prev);
      li2.appendChild(next);
      YAHOO.util.Event.addListener(next, 'click', function(e){this.next(e);}, this, true);
      YAHOO.util.Event.addListener(prev, 'click', function(e){this.previous(e);}, this, true);
      ul.appendChild(li);
      ul.appendChild(li2);
      insertControls(ul, this.oControls.vRef, this.oControls.sInsertion, this);
    }
    if (this.oPagination){   
      this.nPageItems = Math.ceil(this.iNumSlides / this.iSlideAdvance);
      var sPageText = this.oPagination.sPageText || 'Page';
      var  ol = document.createElement('ol');
      // Set-up updating the pagination.
      this.oAnimation.onComplete.subscribe(this.updatePagination, this, true);
      if (this.oPagination.sId) ol.id = this.oPagination.sId;
      if (this.oPagination.sClassName) ol.className = this.oPagination.sClassName;
      // Add id if set.
      for (var i=0,j=this.nPageItems; i < j; i++){
        var  li = document.createElement('li');
        var  a = document.createElement('a');
        var  sp = document.createElement('span');
        a.href = '#';
        var txt = document.createTextNode(sPageText+(i+1));
        a.appendChild(txt);
        // This is so we can track the target both when we click it and for the update of the pagination
        a.className = '_'+(i*this.iSlideAdvance);
        li.appendChild(a);
        ol.appendChild(li);
      };
      YAHOO.util.Event.addListener(ol, 'click', function(e){
        var target = YAHOO.util.Event.getTarget(e);
        if (this.oAnimation.isAnimated()) return;
        if (target.nodeName.toLowerCase() == 'a'){
          this.toSlide(target.className.substring(1));
        }
        YAHOO.util.Event.preventDefault(e);
      }, this, true);
      insertControls(ol, this.oPagination.vRef, this.oPagination.sInsertion, this);      
      // Cache ol for retrieval in updatePagination later
      this.oControlsOl = ol;
    }
  },
   
  updatePagination: function(){
    if (this.oPagination && this.oControlsOl){  
      aLis = this.oControlsOl.getElementsByTagName('li');
      for (var i = 0, j = aLis.length; i < j; i++){
        if (i == this.iCurrentSlide) {
          YAHOO.util.Dom.addClass(aLis[i], 'active');
        }else{
          YAHOO.util.Dom.removeClass(aLis[i], 'active');
        }
      }
    }
  },
  
   
  getSlides: function(oCarousel){
    var aSlides = [];
    if (oCarousel.hasChildNodes()){
      for (var i=0, j = oCarousel.childNodes.length; i<j; i++){
        if (oCarousel.childNodes[i].nodeName.toLowerCase() == 'li'){
          aSlides.push(oCarousel.childNodes[i]);
        }
      }
      return aSlides;
    }else{
      return false;
    }
  },
  
  /* this function advances slides relative to the current position 
  e.g: 1 moves forward 1 * this.iSlideAdvance,  -1 moves -1 * this.iSlideAdvance.
  */
  
  advanceRelative : function(nSlide){
    // the plus in-front of this iCurrentSlide ensures that it's treated as a number
    // add in test to move the carousel a smaller number than iSlideAdvance if required
    if(nSlide > 0) {
        iRemainderSlides = this.iNumSlides - this.iCurrentSlide - this.iSlideAdvance;
    } else {
        iRemainderSlides = this.iCurrentSlide % this.iSlideAdvance;
    }
    var iSlideAdvance = this.iSlideAdvance;
    if(this.iSlideAdvance > iRemainderSlides && iRemainderSlides != 0) {
       iSlideAdvance = iRemainderSlides;
    }
    this.iToAdvance = (+this.iCurrentSlide + (nSlide*iSlideAdvance));
    // THis handles irregular numbers of slides and always flips to the matching segment. 
    this.flip  = function(){
      if (this.iToAdvance > (this.iNumSlides-this.iSlideAdvance)){
        this.toSlide(this.iToAdvance-this.iNumSlides, 0);
        this.oAnimation.onComplete.unsubscribe(this.flip);
      } else if(this.iToAdvance < 0) {
        this.toSlide(this.iToAdvance+this.iNumSlides, 0);
        this.oAnimation.onComplete.unsubscribe(this.flip);
      }
      if (this.oPagination) this.updatePagination();
    };

    switch(this.sLoopMode){
      case 'continuous':
        // If we head up into the cloned slide we jump to the the right place
        this.toSlide(this.iToAdvance);
        if (this.iToAdvance > (this.iNumSlides-this.iSlideAdvance) || this.iToAdvance < 0){
          this.oAnimation.onComplete.subscribe(this.flip, this, true);
        }
        break;
      case 'rewind':
        // If where we're heading up to is out of range rewind to square one.
        if (this.iToAdvance > (this.iNumSlides-this.iNumSlidesVisible)){
          this.toSlide(0);
        // if going down is out of range do nothing.
        }else if (this.iToAdvance < 0){
          return;
        // else as you were.  
        }else{  
          this.toSlide(this.iToAdvance);
        }
        break;
      default:
        // Continue normally unless out of range.
        if (this.iToAdvance <= (this.iNumSlides - this.iNumSlidesVisible) && this.iToAdvance >= 0){
          this.toSlide(this.iToAdvance);
        }
        break;
    }
  },
  
  goToSlide: function(e,nSlideIndex){
    if (e) YAHOO.util.Event.stopEvent(e);
    // If sent by event and autoplay is running kill the autoplay.
    if (e && this.autoPlayTimer) clearInterval(this.autoPlayTimer);
    this.toSlide(nSlideIndex);
  },
  
  /* This function will always takes into account moving continously so you can always feed it the slide you want to move to
  toSlide advances to the exact slide asked for. It's zero indexed so 0 is the first slide 1 is the seconds etc. */
  toSlide: function(nSlideIndex,nDuration,funcEasing){
    
    var iAmount,nDistance,sDirection;
    this.oAnimation.duration = (nDuration === 0) ? 0 : +nDuration || 0.5;
    this.oAnimation.easing = funcEasing || YAHOO.util.Easing.easeBoth;
   
    switch (this.sLoopMode){
      case 'continuous':
        if (nSlideIndex === 0){
          // If index is zero then we want the slide ahead of the cloned slides.
          nDistance = -(this.nMovement * this.iSlideAdvance);
        }else{
          // Otherwise we want to work out where we are headed based on the other slides.
          YAHOO.log('nSlideIndex:' + nSlideIndex);
          nDistance = -(this.nMovement * (+nSlideIndex)) - (this.nMovement * this.iSlideAdvance);
        }
        break;
      case 'rewind': 
      default:
        // we need to check here if were over the end of the carousel... later..
        nDistance = (nSlideIndex === 0) ? 0 : -(+this.nMovement * (+nSlideIndex));
        break;
    }
    // Set style manually to save invoking the animation stuff. this can be removed when a pending patch that fixes animation for 0 duration goes in.
    if (nDuration === 0){
      YAHOO.util.Dom.setStyle(this.oCarousel, (this.sAspect == 'horizontal' ? 'left' : 'top'), nDistance+'px');
      if (this.oPagination) this.updatePagination();
    // Animate
    }else{
      switch(this.sAspect){
        case 'horizontal':
          this.oAnimation.attributes = { left : {to : nDistance }};
          break;
        case 'vertical':
          this.oAnimation.attributes = { top : {to : nDistance }};
          break;
      }      
      this.oAnimation.onComplete.subscribe( function(){
        this.iCurrentSlide = nSlideIndex;
      }, this, true);
      this.oAnimation.animate();
    }
    this.iCurrentSlide = nSlideIndex;
  },
  previous:function(e){
    if (e && this.autoPlayTimer) clearInterval(this.autoPlayTimer);
    if (this.oAnimation.isAnimated()) return;
    this.advanceRelative(-1);
    //alert('fail');
    if (e) YAHOO.util.Event.preventDefault(e);
  },
  next: function(e){
    if (e && this.autoPlayTimer) clearInterval(this.autoPlayTimer);
    if (this.oAnimation.isAnimated()) return;
    this.advanceRelative(1);
    //alert('win');
    if (e) YAHOO.util.Event.preventDefault(e);
  }
};
