/**
 * this class uses jQuery
 *
 * pass in an element selected by jQuery and the Scroller does the rest
 * optionally add some options
 *
 * options:
 *    - direction: vertical, horizontal
 *    - aimation_length: amount of time (in milis) it takes for one 'scroll'
 *    - animation_pause_length: amount of time (in milis) the scroller pauses in between scrolls
 *
 * $Id: scroller.js,v 1.11 2010/04/19 18:59:43 gschwart Exp $
 */

function Scroller (el, options)
{
    var self = this;

    // initialize
        self.el = el;
        self.queue = new ActionQueue();

        // pull in options
        self.options = options || {};

        var default_options = {
            'animation_length':         3000,
            'animation_pause_length':   3000,
            'orientation':              'vertical',
            'index_update':             []
        };

        for (var opt in default_options)
        {
            if( typeof(self.options[opt]) == "undefined" )
            {
                self.options[opt] = default_options[opt];
            }
        }

    self.cycle_inner = function (direction, cycles)
    {
        cycles = cycles || 1;

        if (direction == 'prev')
        {
            for(var i = 0; i < cycles; i++)
            {
                $(self.inner).children().eq($(self.inner).children().size() - 1).detach().prependTo(self.inner);
            }
        }
        else if (direction == 'next')
        {
            for(var i = 0; i < cycles; i++)
            {
                $(self.inner).children().eq(0).detach().appendTo(self.inner);
            }
        }
    }

    self.push_index = function ()
    {
        // assume self.options.index_update is an array of functions
        for (var i = 0; i < self.options.index_update.length; i++)
        {
            self.options.index_update[i](self.item_index);
        }
    }

    self.update_index = function (new_index)
    {
        self.item_index = new_index;

        self.push_index();
    }

    self.move_index = function (direction)
    {
        if (direction == 'next')
        {
            self.item_index++;
        }
        else if (direction == 'prev')
        {
            self.item_index--;
        }

        if (self.item_index < 0)
            self.item_index += self.num_items;

        self.item_index = self.item_index % self.num_items;

        self.push_index();
    }

    // generate any scroll action (scrolls through only one item)
    self.scroll_action = function (direction, fast)
    {
        var animation;
        var inner_css;
        if (self.options.orientation == 'vertical')
        {
            if (direction == 'next')
            {
                animation = {top: '-' + self.item_height + 'px'};
                inner_css = {top: '0px'};
            }
            else if (direction == 'prev')
            {
                animation = {top: 0};
                inner_css = {top: '-' + self.item_height + 'px'};
            }
        }
        else if (self.options.orientation == 'horizontal')
        {
            if (direction == 'next')
            {
                animation = {left: '-' + self.item_width + 'px'};
                inner_css = {left: '0px'};
            }
            else if (direction == 'prev')
            {
                animation = {left: 0};
                inner_css = {left: '-' + self.item_width + 'px'};
            }
        }

        var animation_length = self.options.animation_length;

        if (fast)
        {
            animation_length = animation_length / 2;
        }

        return function (callback)
        {
            if (direction == 'prev')
            {
                // pop child off bottom and put it on top
                self.cycle_inner(direction);
                $(self.inner).css(inner_css);
            }

            $(self.inner).animate(
                animation,
                animation_length,
                'swing',
                function () {
                    if (direction == 'next')
                    {
                        // pop child off top and put it on the bottom
                        self.cycle_inner(direction);
                        $(self.inner).css(inner_css);
                    }

                    self.move_index(direction);

                    callback();
                }
            );
        }
    }

    self.jump_action = function (jump_index)
    {
        if (jump_index < 0 || jump_index >= self.num_items)
        {
            // out of bounds
            return null;
        }

        // all calculations must be done in-action since item_index hasn't been updated
        return function (callback)
        {
            if (jump_index == self.item_index)
            {
                // already here!
                callback();
                return;
            }

            var jump_forward = jump_index - self.item_index;
            if (jump_forward < 0)
                jump_forward += self.num_items;

            var jump_backward = self.item_index - jump_index;
            if (jump_backward < 0)
                jump_backward += self.num_items;

            var direction;
            var jump_count;
            if (jump_forward < jump_backward)
            {
                // moving forward
                direction  = 'next';
                jump_count = jump_forward;
            }
            else
            {
                // moving backward
                direction  = 'prev';
                jump_count = jump_backward;
            }

            var animation;
            var inner_css;
            if (self.options.orientation == 'vertical')
            {
                if (direction == 'next')
                {
                    animation = {top: '-' + (self.item_height * jump_count) + 'px'};
                    inner_css = {top: '0px'};
                }
                else
                {
                    animation = {top: 0};
                    inner_css = {top: '-' + (self.item_height * jump_count) + 'px'};
                }
            }
            else if (self.options.orientation == 'horizontal')
            {
                if (direction == 'next')
                {
                    animation = {left: '-' + (self.item_width * jump_count) + 'px'};
                    inner_css = {left: '0px'};
                }
                else
                {
                    animation = {left: 0};
                    inner_css = {left: '-' + (self.item_width * jump_count) + 'px'};
                }
            }

            var animation_length = (self.options.animation_length / 3) * jump_count;

            // actually do stuff
            if (direction == 'prev')
            {
                self.cycle_inner(direction, jump_count);
                $(self.inner).css(inner_css);
            }

            $(self.inner).animate(
                animation,
                animation_length,
                'swing',
                function () {
                    if (direction == 'next')
                    {
                        self.cycle_inner(direction, jump_count);
                        $(self.inner).css(inner_css);
                    }

                    self.update_index(jump_index);

                    callback();
                }
            );
        }
    }

    // front-end
    self.start_scrolling = function ()
    {
        self.queue.queue_recurring(new Action('scroll', self.scroll_action('next', false)));
        self.queue.queue_recurring(new Action('pause', function (callback) {setTimeout(callback, self.options.animation_pause_length);}));
        self.queue.start();
    }

    self.stop_scrolling = function ()
    {
        self.queue.clear_recurring_queue();
        self.queue.stop();
    }

    self.prev = function ()
    {
        self.queue.clear_recurring_queue();
        self.queue.quash('pause');
        self.queue.queue(new Action('prev', self.scroll_action('prev', true)));
        self.queue.start();
    }

    self.next = function ()
    {
        self.queue.clear_recurring_queue();
        self.queue.quash('pause');
        self.queue.queue(new Action('next', self.scroll_action('next', true)));
        self.queue.start();
    }

    self.jump = function (jump_index)
    {
        self.queue.clear_recurring_queue();
        self.queue.quash('pause');
        self.queue.queue(new Action('jump', self.jump_action(jump_index)));
        self.queue.start();
    }

    // "constructor"
        self.items          = $(self.el).children();
        self.item_height    = self.items.eq(0).outerHeight(true);
        self.item_width     = self.items.eq(0).outerWidth(true);
        self.num_items      = self.items.size();
        self.item_index     = 0;

        $(self.el).css({
            'position': 'relative',
            'overflow': 'hidden'
        });

        // make an inner div and move all scrollables in there
        self.inner = $('<div></div>').appendTo(self.el).get(0);
        self.items.each(function () {
            $(this).remove().appendTo(self.inner);
        });

        if (self.options.direction == 'vertical')
        {
            $(self.inner).css(
                {
                    'height': self.item_height * self.num_items,
                    'width':  self.item_width
                }
            );
        }
        else
        {
            $(self.inner).css(
                {
                    'width':  self.item_width * self.num_items,
                    'height': self.item_height
                }
            );
        }

        $(self.inner).css({
            'position': 'absolute'
        });

        //console.log(self);

        self.start_scrolling();
}

/*
 * $Log: scroller.js,v $
 * Revision 1.11  2010/04/19 18:59:43  gschwart
 * after implementing ActionQueue, we used it here
 * scroller can now queue actions like a pro... no more quick cuts instead of scrolls
 * also, scroller can now scroll to an arbitrary index in one motion
 * also implemented a 'index callback' so something can know when a scroller scrolls and what index it is on
 *
 * Revision 1.10  2010/04/06 16:42:09  gschwart
 * modified scroller to use our new Action Queue
 *
 * Revision 1.9  2009/12/05 03:40:02  jmuller
 * Add initial support for next and previous slide.
 *
 * Revision 1.8  2009/12/02 21:42:54  gschwart
 * fixed some bugs
 *
 * Revision 1.7  2009/12/02 20:57:03  gschwart
 * added the ability to stop and start the scroller
 *
 * Revision 1.6  2009/12/02 19:51:28  gschwart
 * inverted how we did the scroller
 * now it keeps the original scroller element as the outer-most element (so css rules can still apply)
 * also removed the items_displayed feature which allowed you to control how many items show at once.  this will now have to be controlled manually by setting the size of the outer element yourself
 * i think its a good idea to leave as much power as possible to the script user
 *
 * Revision 1.5  2009/05/28 21:10:32  gschwart
 * re-spaced things
 *
 * Revision 1.4  2009/04/30 18:20:06  jmuller
 * Add log lines.
 *
 */
