/**
 * jQuery.serialScroll
 * Copyright (c) 2007-2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Dual licensed under MIT and GPL.
 * Date: 3/20/2008
 *
 * @projectDescription Animated scrolling of series.
 * @author Ariel Flesler
 * @version 1.2.1
 *
 * @id jQuery.serialScroll
 * @id jQuery.fn.serialScroll
 * @param {Object} settings Hash of settings, it is passed in to jQuery.ScrollTo, none is required.
 * @return {jQuery} Returns the same jQuery object, for chaining.
 *
 * http://flesler.blogspot.com/2008/02/jqueryserialscroll.html
 *
 * Notes:
 *  - The plugin requires jQuery.ScrollTo.
 *  - The hash of settings, is passed to jQuery.ScrollTo, so its settings can be used as well.
 */
;(function($){

  var $serialScroll = $.serialScroll = function(settings){
    $.scrollTo.window().serialScroll(settings);
  };

  //Many of these defaults, belong to jQuery.ScrollTo, check it's demo for an example of each option.
  //see http://flesler.webs/jQuery.ScrollTo/
  $serialScroll.defaults = {  //the defaults are public and can be overriden.
    duration  : 1000,         //how long to animate.
    axis      : 'x',          //which of top and left should be scrolled
    event     : 'click',      //on which event to react.
    start     : 0,            //first element (zero-based index)
    step      : 1,            //how many elements to scroll on each action
    lock      : true,         //ignore events if already animating
    cycle     : true,         //cycle endlessly (constant velocity)
    constant  : true          //use contant speed ?
    /*
    navigation: null,         //if specified, it's a selector a collection of items to navigate the container
    target    : null,         //if specified, it's a selector to the element to be scrolled.
    interval  : 0,            //it's the number of milliseconds to automatically go to the next
    lazy      : false,        //go find the elements each time (allows AJAX or JS content, or reordering)
    stop      : false,        //stop any previous animations to avoid queueing
    force     : false,        //force the scroll to the first element on start ?
    jump      : false,        //if true, when the event is triggered on an element, the pane scrolls to it
    items     : null,         //selector to the items (relative to the matched elements)
    prev      : null,         //selector to the 'prev' button
    next      : null,         //selector to the 'next' button
    onBefore  : function(){}, //function called before scrolling, if it returns false, the event is ignored
    exclude   : 0             //exclude the last x elements, so we cannot scroll past the end
    */
  };

  $.fn.serialScroll = function(settings){
    settings = $.extend({}, $serialScroll.defaults, settings);
    var event = settings.event, //this one is just to get shorter code when compressed
      step    = settings.step,  // idem
      lazy    = settings.lazy;  //idem

    return this.each(function(){
      var
        context = settings.target ? this : document, //if a target is specified, then everything's relative to 'this'.
        $pane   = $(settings.target || this, context),//the element to be scrolled (will carry all the events)
        pane    = $pane[0],             //will be reused, save it into a variable
        items   = settings.items,       //will hold a lazy list of elements
        active  = settings.start,       //active index
        auto    = settings.interval,    //boolean, do auto or not
        nav     = settings.navigation,  //save it now to make the code shorter
        timer; //holds the interval id

      if(!lazy)//if not lazy, go get the items now
        items = getItems();

      if(settings.force)
        jump({}, active);//generate an initial call

      // Button binding, optionall
      $(settings.prev||[], context).bind(event, -step, move);
      $(settings.next||[], context).bind(event, step, move);

      // Custom events bound to the container
      if(!pane.ssbound)//don't bind more than once
        $pane
          .bind('prev.serialScroll', -step, move) //you can trigger with just 'prev'
          .bind('next.serialScroll', step, move) //for example: $(container).trigger('next');
          .bind('goto.serialScroll', jump); //for example: $(container).trigger('goto', [4]);
      if(auto)
        $pane
          .bind('start.serialScroll', function(e){
            if(!auto){
              clear();
              auto = true;
              next();
            }
           })
          .bind('stop.serialScroll', function(){//stop a current animation
            clear();
            auto = false;
          });
      $pane.bind('notify.serialScroll', function(e, elem){//let serialScroll know that the index changed externally
        var i = index(elem);
        if(i > -1)
          active = i;
      });
      pane.ssbound = true;//avoid many bindings

      if(settings.jump)//can't use jump if using lazy items and a non-bubbling event
        (lazy ? $pane : getItems()).bind(event, function(e){
          jump(e, index(e.target));
        });

      if(nav)
        nav = $(nav, context).bind(event, function(e){
          e.data = Math.round(getItems().length / nav.length) * nav.index(this);
          jump(e, this);
        });

      function move(e){
        e.data += active;
        jump(e, this);
      };
      function jump(e, button){
        if(!isNaN(button)){//initial or special call from the outside $(container).trigger('goto',[index]);
          e.data = button;
          button = pane;
        }

        var
          pos       = e.data, n,
          real      = e.type, //is a real event triggering ?
          $items    = settings.exclude ? getItems().slice(0,-settings.exclude) : getItems(),//handle a possible exclude
          limit     = $items.length,
          elem      = $items[pos],
          duration  = settings.duration;

        if(real)//real event object
          e.preventDefault();

        if(auto){
          clear();//clear any possible automatic scrolling.
          timer = setTimeout(next, settings.interval);
        }

        if(!elem){ //exceeded the limits
          n = pos < 0 ? 0 : limit - 1;
          if(pos <= n && active != n) //we exceeded for the first time
            pos = n;
          else if(!settings.cycle)    //this is a bad case
            return;
          else
            pos = limit - n - 1;      //invert, go to the other side
          elem = $items[pos];
        }

        if(!elem || real && active == pos || //could happen, save some CPU cycles in vain
          settings.lock && $pane.is(':animated') || //no animations while busy
          real && settings.onBefore && //callback returns false ?
          settings.onBefore.call(button, e, elem, $pane, getItems(), pos) === false) return;

        if(settings.stop)
          $pane.queue('fx',[]).stop();//remove all its animations

        if(settings.constant)
          duration = Math.abs(duration/step * (active - pos));//keep constant velocity

        $pane
          .scrollTo(elem, duration, settings)//do scroll
          .trigger('notify.serialScroll',[pos]);//in case serialScroll was called on this elem more than once.
      };
      function next(){//I'll use the namespace to avoid conflicts
        $pane.trigger('next.serialScroll');
      };
      function clear(){
        clearTimeout(timer);
      };
      function getItems(){
        return $(items, pane);
      };
      function index(elem){
        if(!isNaN(elem)) return elem;//number
        var $items = getItems(), i;
        while((i = $items.index(elem)) == -1 && elem != pane)//see if it matches or one of its ancestors
          elem = elem.parentNode;
        return i;
      };
    });
  };
})(jQuery);