/*
 * A scroller widget for styled javascript based scroll pane.
 * 
 * Dependencies dependencies based on options:
 *  ui.core.js
 *  ui.draggable.js
 *  jquery.mousewheel.js
 *
 */
var Scroller = function() {
    return this.initialize.apply(this, arguments);
};

Scroller.prototype = {
    defaults: {
        buttonEvent : 'mousedown',  // Event to use for scrolling (mousedown or hover)
        mouseWheel: true,           // Use mousewheel event for scrolling
        wheelSpeed : 24,            // Amout to scroll for each wheel movement.
        speed : 12,                 // speed of movement for buttonEvent mousedown/hover 
        delay: 60,                  // Delay ms for buttonEvent mousedown/hover
        nextButtonHTML: '<a>more&raquo;</a>',   // html tag for next button
        prevButtonHTML: '<a>&laquo;back</a>',   // html tag for prev button
        scrollbar: true,            // Use a scrollbar
        scrollHandleAutosize: false,   // Resize the scroll handle depending on scroll ratio
        horizontal: false,          // horizontal? (default is vertical scrolling)
        autoResize: false,          // Automatically resize scroller dimensions if container changes
        
        // Animation options
        animate : false,            // Animate the scrolling (this overrides buttonEvent to use click instead)
        animateAmount: 100,         // Amount to animate the scroller for each click.
        animateSpeed: 'slow',       // Speed of the animation
        animateEasing: 'swing',     // Easing for animation.
        paginate: false             // Automatically adjusts the animateAmount to scroll one page at a time.
    },

    timeoutID: 0,
    scrollCursor: 0,
    
    initialize: function (obj, opts) {
        var self = this;
        self.o = jQuery.extend({}, self.defaults, opts);
        self.container = obj;
        self.active = false;

        var $$ = $(self.container);
        var dir_class = self.o.horizontal ? 'scroller-horizontal' : 'scroller-vertical';

        // Dynamic attributes based on scroll direction
        var dim;
        if (self.o.horizontal) {
            self.dim = dim = {
                scrollPosition: 'scrollLeft',
                scrollLength: 'scrollWidth',
                position: 'left',
                length: 'width',
                axis: 'x',
                overflow: 'overflow-x'
            };
        }
        else {
            self.dim = dim = {
                scrollPosition: 'scrollTop',
                scrollLength: 'scrollHeight',
                position: 'top',
                length: 'height',
                axis: 'y',
                overflow: 'overflow-y'
            };
        }
 
        $$.css(dim.overflow, 'hidden').addClass('jquery-scroller ' + dir_class );

        if (self.o.autoResize) {
            // Watch for changes in the size of the scroller content.
            var saved_scrollLength = self.container[dim.scrollLength];
            setInterval(function() {
                var cur_len = self.container[dim.scrollLength];
                if (cur_len != saved_scrollLength) {
                    saved_scrollLength = cur_len;
                    self.reset(self.scrollCursor);
                }
            }, 200);

            $(window).resize(function() {
                self.reset(self.scrollCursor);
            });
        }

        // Buttons
        $('.scroller-controls', $$.parent()).remove();
        $controls = $('<div class="scroller-controls ' + dir_class + '"></div>').insertAfter($$);
        $prev = $(self.o.prevButtonHTML).
            addClass('scroller-prev').addClass('disabled').appendTo($controls);
        $next = $(self.o.nextButtonHTML).
            addClass('scroller-next').appendTo($controls);
        self.prevButton = $prev[0];
        self.nextButton = $next[0];
        self.controls = $controls[0];
        
        if (self.o.paginate) self.o.animate = true;
        if (self.o.animate ) {
            if (self.o.paginate) {
                self.o.animateAmount = $$[dim.length]();
            } 
            $(self.nextButton).click(function() {
                self.update(self.o.animateAmount, true);
            });
            $(self.prevButton).click(function() {
                self.update(0 - self.o.animateAmount, true);
            });
        } else {
            switch (self.o.buttonEvent) {
                case 'mousedown':            
                    $(self.nextButton).bind('mousedown', function(e){self.scrollNext()}).
                        bind('mouseup', function(e){self.stopScroll()});
                    $(self.prevButton).bind('mousedown', function(e){self.scrollPrev()}).
                        bind('mouseup', function(e){self.stopScroll()});
                    break;
                case 'hover':
                    $(self.nextButton).hover(function(e){self.scrollNext()},function(e){self.stopScroll()});
                    $(self.prevButton).hover(function(e){self.scrollPrev()},function(e){self.stopScroll()});
                    break;
            }
        }

        // Mouse Wheel
        if (self.o.mouseWheel)
            $$.mousewheel( function(e, delta){ self.wheel(e, delta); return false; } );

        // Scrollbar
        if (self.o.scrollbar) {
            var $scrollbar = $('<div class="scroller-scrollbar"><div class="scroller-handle"></div></div>')
                .appendTo($controls.addClass('has_scrollbar'));
            
            // Create handle
            var $handle = $('.scroller-handle', $scrollbar);
            self.scrollbar = $scrollbar[0];
            self.resetScrollbar();
            
            // Set handle position on scroll
            $$.bind('scrolling', function(e, animate) {
                if ( self.dragging ) return;
                var total_length = self.container[dim.scrollLength] - $$[dim.length]();
                var p = total_length > 0 ? (self.scrollCursor / total_length ) : 0;
                var pos = (p * ($scrollbar[dim.length]() - $handle[dim.length]()));
                if (animate) {
                    var a = {
                        easing: self.o.animateEasing
                    }
                    a[dim.position] = pos;
                    $handle.animate(a, self.o.animateSpeed);
                } else {
                    $handle.css(dim.position, pos);
                }
            });

            // Drag event for handle
            $handle.draggable({
                axis: dim.axis,
                containment: 'parent',
                start: function(){ 
                    self.dragging = true; 
                    $handle.addClass('hover');
                },
                stop: function() {
                    self.dragging = false; 
                    $handle.removeClass('hover');
                },
                drag: function(e, ui) {
                    var total_length = self.container[dim.scrollLength] - $$[dim.length]();
                    var scrollable_length = $scrollbar[dim.length]() - $handle[dim.length]();
                    var pos = parseInt($handle.css(dim.position));
                    var percentage = (pos / scrollable_length);
                    var scroll = (percentage * total_length);
                    self.scrollTo(scroll, false);
                }
            });
        }
        
        // Custom Events
        $$.bind('scrollbeginning', function(){
            $(self.prevButton).addClass('disabled');
            if (self.o.animate) {
                self.stopScroll();                
            }
        });
        $$.bind('scrollend', function(){
            $(self.nextButton).addClass('disabled');;
            if (self.o.animate) {
                self.stopScroll();                
            }
        });

        self.reset();
    },

    set_active: function() {
        var self = this;
        $(self.controls).show().removeClass('inactive-scroller');
        $(self.container).removeClass('inactive-scroller').addClass('active-scroller');
        if (self.o.scrollbar) {
            self.resetScrollbar();                
        }
        self.active = true;
    },

    set_inactive: function() {
        var self = this;
        $(self.controls).hide().addClass('inactive-scroller');
        $(self.container).removeClass('active-scroller').addClass('inactive-scroller');
        self.active = false;
    },
    
    /* Resets the scroller. */
    reset: function(s) {
        var self = this;
        var pos = s || 0;
        
        if ( self.scrollNeeded() ) {
            self.set_active();
        } else {
            self.set_inactive();
        }
        
        self.scrollTo(s, false);
        if (pos == 0) $(self.prevButton).addClass('disabled');
        $(self.container).trigger('scrollreset');
    },

    resetScrollbar: function() {
        // reset the scrollbar size
        var self = this;
        var dim = self.dim;
        var $controls = $(self.controls);
        var $scrollbar = $(self.scrollbar);
        $controls[dim.length]($(self.container)[dim.length]());
        $scrollbar[dim.length]( $controls[dim.length]() - $next[dim.length]() - $prev[dim.length]());
        if (self.o.scrollHandleAutosize) {
            var length_r = Math.round($scrollbar[dim.length]() * ($(self.container)[dim.length]() / self.container[dim.scrollLength]));
            if (!length_r || length_r < 50) length_r = 50; 
            $scrollbar.find('.scroller-handle')[dim.length]( length_r );
        }
    },

    // Update the scroll by t pixels
    update: function(t) {
        var self = this;
        var animate = (typeof(arguments[1]) == 'undefined') ? self.o.animate : arguments[1];
        var s = self.scrollCursor + t;
        self.scrollTo(s, animate);
    },

    // Set the scroll position to s pixels
    scrollTo: function(s){
        var self = this;
        var $$ = $(self.container);
        var animate = (typeof(arguments[1]) == 'undefined') ? self.o.animate : arguments[1];
        
        // if s is an object then scroll to that object.
        if (typeof(s) == 'object') {
            var $$ = $(s);
            // make sure obj is a child of container
            if ($$.parents().index(self.container) == -1) return; 
            var offset = $$.offset()[self.dim.position] + $$[0][self.dim.scrollPosition] - $$.offset()[self.dim.position];
            s = offset;
        }
        // Adjust the scroll amount to always fall on boundaries
        // set by animateAmount variable
        else if (animate) {
            speed = self.o.animateAmount;
            if (s >= self.scrollCursor )
                s = Math.floor(s / speed) * speed;
            else 
                s = Math.ceil(s / speed) * speed;
        }

        // Update cursor before sending any events
        self.scrollCursor = s;

        // Check for beginning of scroll
        if ( self.scrollCursor <= 0 ){
            self.scrollCursor = 0;
            $$.trigger('scrollbeginning');
        } else { $(self.prevButton).removeClass('disabled'); }
       
        // Check for end of scroll
        var dim = self.dim;
        var scrollLength = self.container[dim.scrollLength];
        var length = $$[dim.length]();
        if (self.scrollCursor >= (scrollLength - length) ) {
            self.scrollCursor = scrollLength - length;
            $$.trigger('scrollend');
        } else { $(self.nextButton).removeClass('disabled'); }
        
        // Actually set the scroll on the DOM element
        if (animate) {
            var animation_options = {
                easing: self.o.animateEasing
            };
            animation_options[dim.scrollPosition] = self.scrollCursor;
            $$.animate(animation_options, self.o.animateSpeed);
        } else {
            self.container[dim.scrollPosition] = self.scrollCursor;
            self.scrollCursor = self.container[dim.scrollPosition];
        }
        
        // Send custom scroll event for listeners
        $$.trigger('scrolling', [animate]);
    },

    scrollNeeded: function() {
        var self = this;
        var scrollLength;
        var $$ = $(self.container);
        var length = $$[self.dim.length]();
        scrollLength = self.container[self.dim.scrollLength];
        if (scrollLength > length) return true;        
        return false;
    },

    stopScroll: function() {
        var self = this;
        clearTimeout(self.timeoutID);
    },

    scrollPrev: function() {
        var self = this;
        self.update(0 - self.o.speed);        
        self.timeoutID = setTimeout(function(){self.scrollPrev();}, self.o.delay);
    },

    scrollNext: function() {
        var self = this;
        self.update(self.o.speed);
        self.timeoutID = setTimeout(function(){self.scrollNext();}, self.o.delay);
    },

    wheel: function(event, delta) {
        var self = this;
        if ($.browser.opera) delta = delta * -1; // delta sign oppisite in opera
        direction = (delta < 0)? 1 : -1;
        self.update(self.o.wheelSpeed * direction, false);
        return false;
    }        
};

jQuery.fn.Scroller = function(opts) {
    return this.each( function (i,el) {
        var scroller = $.data(el, 'scroller');
        if (scroller) {
            scroller.reset();            
        } else {
            scroller =  new Scroller(el, opts);
            $.data(el, 'scroller', scroller);
        }
    });
};

