
var Scroller = function() {
    return this.initialize.apply(this, arguments);
};

Scroller.prototype = {
    defaults: {
        speed : 12,
        delay: 60,
        mouseWheel: true,
        wheelSpeed : 24,
        buttonEvent : 'mousedown',
        nextButtonHTML: '<a>more&raquo;</a>',
        prevButtonHTML: '<a>&laquo;back</a>',
        scrollbar: true,
        horizontal: false,
        animate : false,
        animateSpeed: 100,
        paginate: false,
        preInit: null,
        postInit: null
    },

    timeoutID: 0,
    scrollCursor: 0,
    
    
    initialize: function (obj, settings) {
        var self = this;
        self.settings = jQuery.extend({}, self.defaults, settings);
        self.container = obj;
        var $container = $(self.container);

        // Set up Callbacks
        self.preInit = self.settings.preInit;
        self.postInit = self.settings.postInit;
        if (self.settings.preInit) self.preInit();


        var dir_class = self.settings.horizontal ? 'scroller-horizontal' : 'scroller-vertical'

        $container.css({
            overflow: 'hidden'
         }).addClass('jquery-scroller ' + dir_class );

        
        // Dynamic attributes based on scroll direction
        var dim;
        if (self.settings.horizontal) {
            self.dim = dim = {
                scrollPosition: 'scrollLeft',
                scrollLength: 'scrollWidth',
                position: 'left',
                length: 'width',
                axis: 'x'
            };
        }
        else {
            self.dim = dim = {
                scrollPosition: 'scrollTop',
                scrollLength: 'scrollHeight',
                position: 'top',
                length: 'height',
                axis: 'y'
            };
        }
        
        // Buttons
        $('.scroller-controls', $container.parent()).remove();
        $controls = $('<div class="scroller-controls ' + dir_class + '"></div>').insertAfter($container);
        $prev = $(self.settings.prevButtonHTML).
            addClass('scroller-prev').addClass('disabled').appendTo($controls);
        $next = $(self.settings.nextButtonHTML).
            addClass('scroller-next').appendTo($controls);
        self.prevButton = $prev[0];
        self.nextButton = $next[0];
        self.controls = $controls[0];

        if (self.settings.animate ) {
            var amount = self.settings.animateSpeed;
            if (self.settings.paginate) {
                amount = $container[dim.length]();
                self.settings.animateSpeed = amount;
            } 
            $(self.nextButton).click(function() {
                self.update(amount, true);
            });
            $(self.prevButton).click(function() {
                self.update(0 - amount, true);
            });
        } else {
            switch (self.settings.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.settings.mouseWheel)
            $container.mousewheel( function(e, delta){ self.wheel(e, delta); return false; } );

        // Scrollbar
        if (self.settings.scrollbar) {
            var $scrollbar = $('<div class="scroller-scrollbar"><div class="scroller-handle"></div></div>')
                .appendTo($controls);
            
            // set the scrollbar size
            $scrollbar[dim.length]( $controls[dim.length]() - $next[dim.length]() - $prev[dim.length]());
            var length_r = Math.round($scrollbar[dim.length]() * ($container[dim.length]() / self.container[dim.scrollLength]));
            if (length_r < 50) length_r = 50; 
            $scrollbar.find('.scroller-handle')[dim.length]( length_r );
            
            // Create handle
            var $handle = $('.scroller-handle', $scrollbar);
            self.scrollbar = $scrollbar[0];

            // Set handle position on scroll
            $container.bind('scrolling', function(e, animate) {
                if ( self.dragging ) return;
                var total_length = self.container[dim.scrollLength] - $container[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 = {}
                    a[dim.position] = pos;
                    $handle.animate(a);
                } 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] - $container[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
        $container.bind('scrollbeginning', function(){
            $(self.prevButton).addClass('disabled');
            if (self.settings.animate) {
                self.stopScroll();                
            }
        });
        $container.bind('scrollend', function(){
            $(self.nextButton).addClass('disabled');;
            if (self.settings.animate) {
                self.stopScroll();                
            }
        });

        self.reset();

        if (self.postInit) self.postInit();
    },

    reset: function() {
        var self = this;
        self.scrollTo(0, false);
        if ( self.scrollNeeded() ) {
            $(self.controls).show();
        } else {
            $(self.controls).hide()
        }
        $(self.prevButton).addClass('disabled');
        $(self.container).trigger('scrollreset');
    },

    // Update the scroll by t pixels
    update: function(t) {
        var self = this;
        var animate = (typeof(arguments[1]) == 'undefined') ? self.settings.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 $container = $(self.container);
        var animate = (typeof(arguments[1]) == 'undefined') ? self.settings.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] + $container[0].scrollLeft - $container.offset()[self.dim.position];
            s = offset;
        }
        // Adjust the scroll amount to always fall on boundaries
        // set by animateSpeed variable
        else if (animate) {
            speed = self.settings.animateSpeed;
            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;
            $container.trigger('scrollbeginning');
        } else { $(self.prevButton).removeClass('disabled'); }
       
        // Check for end of scroll
        var dim = self.dim;
        var scrollLength = self.container[dim.scrollLength];
        var length = $container[dim.length]();
        if (self.scrollCursor >= (scrollLength - length) ) {
            self.scrollCursor = scrollLength - length;
            $container.trigger('scrollend');
        } else { $(self.nextButton).removeClass('disabled'); }
        
        // Actually set the scroll on the DOM element
        if (animate) {
            var animation_options = {
                easing: 'linear'
            };
            animation_options[dim.scrollPosition] = self.scrollCursor;
            $container.animate(animation_options);
        } else {
            self.container[dim.scrollPosition] = self.scrollCursor;
            self.scrollCursor = self.container[dim.scrollPosition];
        }
        
        // Send custom scroll event for listeners
        $container.trigger('scrolling', [animate]);
    },

    scrollNeeded: function() {
        var self = this;
        var scrollLength, length;
        var $container = $(self.container);
        length = $container[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.settings.speed);        
        self.timeoutID = setTimeout(function(){self.scrollPrev();}, self.settings.delay);
    },

    scrollNext: function() {
        var self = this;
        self.update(self.settings.speed);
        self.timeoutID = setTimeout(function(){self.scrollNext();}, self.settings.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.settings.wheelSpeed * direction, false);
        return false;
    }        
};


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