/**

 * 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( 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 );
