/* 
 * jQuery UI - Scrollbar plugin
 * Create custom scrollbars that behave like native scrollbars
 *
 * @author Zach Waugh <zwaugh@gmail.com>
 * @version 1.1
 * @requires ui.core.js
 *
 * Copyright (c) 2009 Zach Waugh MIT License
 */

(function($){
    /**
     * Main plugin function
     */
    $.widget("ui.scrollbar", 
    {
        _init: function()
        {
            var self = this, options = this.options;
            this._isDragging = false;
            this._handleOffset = 0;
            this._scrollbarContents = this.element.find('.ui-scrollbar-contents');
            this._timer = null;
            this.element.addClass('ui-scrollable').css('overflow', options.overflow);
            
            if (options.vertical)
            {
                this._initVertical();
            }
            else
            {
                this._initHorizontal();
            }
        }, 
        
    /**
     * Initialize a vertical scrollbar
     */
    _initVertical: function ()
    {
        var self = this;
        
        // Get height of scrollable area
        var windowHeight = this.element.innerHeight();
        this._windowHeight = windowHeight;
        
        var contentsHeight = this._scrollbarContents.outerHeight(true);
        this._contentsHeight = contentsHeight;

        // Only add scrollbar if needed
        if (contentsHeight > windowHeight)
        {
            var trackHeight = windowHeight - (this.options.buttonHeight * 2);
            var handleHeight = 37; //(windowHeight / contentsHeight) * trackHeight;
            
            var scrollbar = '<div class="ui-scrollbar ui-scrollbar-vertical">';
            scrollbar += '<a href="#" class="ui-scrollbar-button-up"></a>';
            scrollbar += '<div class="ui-scrollbar-track" style="top:' + this.options.buttonHeight + 'px;height:' + trackHeight + 'px;">';
            scrollbar += '<div class="ui-scrollbar-handle" style="height:' + handleHeight + 'px;"></div>';
            scrollbar += '</div>'; // .ui-scrollbar-track
            scrollbar += '<a href="#" class="ui-scrollbar-button-down"></a>';
            scrollbar += '</div>'; // .ui-scrollbar
            
            this._scrollbarContents.css({position: 'absolute', top: 0});
            this.element.prepend(scrollbar);

            // cache references to scrollbar pieces
            this._scrollbar = $('.ui-scrollbar', this.element);
            this._scrollbarTrack = $('.ui-scrollbar-track', this.element);
            this._scrollbarHandle = $('.ui-scrollbar-handle', this.element);
        
            // Bind Events
            this._scrollbarTrack.mousedown(function(event) { return self._start(event); });
            this.element.mousewheel(function(event, delta) { return self._scrollWheelVertical(event, delta); });
            
            this.element.find('.ui-scrollbar-button-up').mousedown(function(event) { return self._goUp(); }).mouseup(function() { return self._clearTimer(); }).click(function(){ return false; });
            this.element.find('.ui-scrollbar-button-down').mousedown(function(event) { return self._goDown(); }).mouseup(function() { return self._clearTimer(); }).click(function(){ return false; });
        }
    },
    
    /**
     * Initialize a horizontal scrollbar
     */
    _initHorizontal: function ()
    {
        var self = this;
        
        // Get width of scrollable area
        var windowWidth = this.element.innerWidth();
        this._windowWidth = windowWidth;
        
        //var contentsWidth = this._scrollbarContents.innerWidth();
        // Compute actual width of scrollbar contents
        // TODO: code assumes contents is wrapped in a ul - should make option
        var contentsWidth = this._scrollbarContents.find('li').outerWidth(true) * this._scrollbarContents.find('li').length;
        // Update container with actual width
        this._scrollbarContents.css('width', contentsWidth);
        
        this._contentsWidth = contentsWidth;

        // Only add scrollbar if needed
        if (contentsWidth > windowWidth)
        {
            var trackWidth = windowWidth - (this.options.buttonWidth * 2);
            var handleWidth = (windowWidth / contentsWidth) * trackWidth;
            
            var scrollbar = '<div class="ui-scrollbar ui-scrollbar-horizontal">';
            scrollbar += '<a href="#" class="ui-scrollbar-button-left"></a>';
            scrollbar += '<div class="ui-scrollbar-track" style="left:' + this.options.buttonWidth + 'px;width:' + trackWidth + 'px;">';
            scrollbar += '<div class="ui-scrollbar-handle" style="width:' + handleWidth + 'px;"></div>';
            scrollbar += '</div>'; // .ui-scrollbar-track
            scrollbar += '<a href="#" class="ui-scrollbar-button-right"></a>';
            scrollbar += '</div>'; // .ui-scrollbar
            
            this._scrollbarContents.css({position: 'absolute', left: 0})
            this.element.prepend(scrollbar);

            // cache references to scrollbar pieces
            this._scrollbar = $('.ui-scrollbar', this.element);
            this._scrollbarTrack = $('.ui-scrollbar-track', this.element);
            this._scrollbarHandle = $('.ui-scrollbar-handle', this.element);
        
            // Bind Events
            this._scrollbarTrack.mousedown(function(event) { return self._start(event); });
            this.element.mousewheel(function(event, delta) { return self._scrollWheelHorizontal(event, delta); });
            
            this.element.find('.ui-scrollbar-button-left').mousedown(function(event) { return self._goLeft(); }).mouseup(function() { return self._clearTimer(); }).click(function(){ return false; });
            this.element.find('.ui-scrollbar-button-right').mousedown(function(event) { return self._goRight(); }).mouseup(function() { return self._clearTimer(); }).click(function(){ return false; });
        }
    },

    /**
     * Event handler - mousedown
     * Called when mouse is clicked in scrollbar or scrollbar handle
     * @params event (jQuery Event Object)
     */
    _start: function(event)
    {
        var self = this;
        
        $('html').mouseup(function(event) { return self._stop(event); }).mousemove(function(event) { return self._drag(event); }).mouseleave(function(event) { return self._stop(event); });

        // scroll contents if clicked in scrollbar track and not handle
        if (event.target == this._scrollbarTrack.get(0))
        {
            var offset = this._relativeMousePosition(event);

            if (this.options.vertical)
            {
                this._scrollVertical(offset);
            }
            else
            {
                this._scrollHorizontal(offset);
            }
        }
        else
        {
            this._handleOffset = this._calcHandleOffset(event);
        }

        this._isDragging = true;

        return false;
    },

    /**
     * Event Handler - mousemove
     * Called while mouse is moving after startDrag event
     */
    _drag: function(event)
    {
        if (this._isDragging)
        {
            var position = this._relativeMousePosition(event) - this._handleOffset;
            
            if (this.options.vertical)
            {
                this._scrollVertical(position);
            }
            else
            {
                this._scrollHorizontal(position);
            }
        }

        return false;
    },

    /**
     * Event Handler - mouseup
     * unbind events and change state
     */
    _stop: function(event)
    {
        if (this._isDragging)
        {
            // unbind events when dragging ends
            $('html').unbind('mouseup').unbind('mousemove').unbind('mouseleave');

            this._isDragging = false;
        }
        
        return false;
    },
    
    /**
     * Update scrollbar vertical
     */
    updateScrollBarVertical: function()
    {
        var contentsHeight = this._scrollbarContents.outerHeight(true);
        this._contentsHeight = contentsHeight;
        var windowHeight = this._windowHeight;
        
        if (contentsHeight > windowHeight)
        {
            var trackHeight = windowHeight - (this.options.buttonHeight * 2);
            var handleHeight = (windowHeight / contentsHeight) * trackHeight;
        
            this._scrollbarTrack.css({height: parseInt(trackHeight)});      
            this._scrollbarHandle.css({height: parseInt(handleHeight)});
        }
        else
        {
            this._scrollbarHandle.css('height', this._scrollbarTrack.height());
        }
        
        this._scrollVertical(0);
    },
    
    /**
     * Function the actually scrolls the content and positions the handle (Vertically)
     */
    _scrollVertical: function(top)
    {
        if (this._contentsHeight != this._scrollbarContents.outerHeight(true))
        {
            this.updateScrollBarVertical();
        }
    
        if (top < 0)
        {
            top = 0;
        }
        else if (top >= (this._scrollbarTrack.height() - this._scrollbarHandle.height()))
        {
            top = this._scrollbarTrack.height() - this._scrollbarHandle.height();
        }

        // Set handle position
        this._scrollbarHandle.css({top: top});

        // Scroll the contents
        this._scrollContentsVertical(top);
    },
    
    /**
     * Function the actually scrolls the content and positions the handle (Horizontally)
     */
    _scrollHorizontal: function(left)
    {
        if (left < 0)
        {
            left = 0;
        }
        else if (left >= (this._scrollbarTrack.width() - this._scrollbarHandle.width()))
        {
            left = this._scrollbarTrack.width() - this._scrollbarHandle.width();
        }

        // Set handle position
        this._scrollbarHandle.css({left: left});

        // Scroll the contents
        this._scrollContentsHorizontal(left);
    },

    /**
     * Scroll content pane vertically
     */
    _scrollContentsVertical: function(top)
    {
        var scrollbar_height = this._scrollbarTrack.height() - this._scrollbarHandle.height();
        var percent = top / scrollbar_height;
        var contents_top = (this._contentsHeight - this.element.height()) * percent * -1;

        this._scrollbarContents.css({top: contents_top});
    },

    /**
     * Scroll content pane horizontally
     */
    _scrollContentsHorizontal: function(left)
    {
        var scrollbar_width = this._scrollbarTrack.width() - this._scrollbarHandle.width();
        var percent = left / scrollbar_width;
        var contents_left = (this._contentsWidth - this.element.width()) * percent * -1;

        this._scrollbarContents.css({left: contents_left});
    },

    /**
     * Handle moving left by clicking scrollbar arrow
     */
    _goLeft: function ()
    {
        var self = this;
        
        // Scroll once immediately
        this._scrollHorizontal(parseInt(this._scrollbarHandle.css('left')) - this.options.scrollInterval);
        
        this._timer = setInterval(function() {
            self._scrollHorizontal(parseInt(self._scrollbarHandle.css('left')) - self.options.scrollInterval);
        }, 50);
        
        return false;
    },
    
    /**
     * Handle moving right by clicking scrollbar arrow
     */
    _goRight: function ()
    {
        var self = this;
        
        // Scroll once immediately
        this._scrollHorizontal(parseInt(this._scrollbarHandle.css('left')) + this.options.scrollInterval);
        
        // Setup timer to keep scrolling while mouse is down
        this._timer = setInterval(function() {
            self._scrollHorizontal(parseInt(self._scrollbarHandle.css('left')) + self.options.scrollInterval);
        }, 50);
        
        return false;
    },
    
    /**
     * Handle moving up by clicking scrollbar arrow
     */
    _goUp: function ()
    {
        var self = this;
        
        // Scroll once immediately
        this._scrollVertical(parseInt(this._scrollbarHandle.css('top')) - this.options.scrollInterval);
        
        this._timer = setInterval(function() {
            self._scrollVertical(parseInt(self._scrollbarHandle.css('top')) - self.options.scrollInterval);
        }, 50);
        
        return false;
    },
    
    /**
     * Handle moving down by clicking scrollbar arrow
     */
    _goDown: function ()
    {
        var self = this;
        
        // Scroll once immediately
        this._scrollVertical(parseInt(this._scrollbarHandle.css('top')) + this.options.scrollInterval);
        
        // Setup timer to keep scrolling while mouse is down
        this._timer = setInterval(function() {
            self._scrollVertical(parseInt(self._scrollbarHandle.css('top')) + self.options.scrollInterval);
        }, 50);
        
        return false;
    },
    
    _clearTimer: function ()
    {
        clearInterval(this._timer);
        
        return false;
    },
    
    /**
     * Event Handler - mousewheel (vertical)
     * respond to mousewheel moving
     */
    _scrollWheelVertical: function(event, delta)
    {
        var top;

        if (delta > 0)
        {
            top = parseInt(this._scrollbarHandle.css('top'));
            top -= (delta * this.options.scrollInterval);
        }
        else
        {
            top = parseInt(this._scrollbarHandle.css('top'));
            top += (delta * -1 * this.options.scrollInterval);
        }

        this._scrollVertical(top);

        return false;
    },

    /**
     * Event Handler - mousewheel (horizontal)
     * respond to mousewheel moving
     */
    _scrollWheelHorizontal: function(event, delta)
    {
        var left;

        if (delta > 0)
        {
            left = parseInt(this._scrollbarHandle.css('left'));
            left -= (delta * this.options.scrollInterval);
        }
        else
        {
            left = parseInt(this._scrollbarHandle.css('left'));
            left += (delta * -1 * this.options.scrollInterval);
        }

        this._scrollHorizontal(left);

        return false;
    },
    
    /**
     * Calculate the absolute mouse position in the window to the relative position in the scrollbar
     */
    _relativeMousePosition: function(event)
    {
        if (this.options.vertical)
        {
            return event.pageY - this.element.offset().top - this.options.buttonWidth;
        }
        else
        {
            return event.pageX - this.element.offset().left - this.options.buttonWidth;
        }
    },

    /**
     * Calculate where the mouse is clicked within the scroll handle
     */
    _calcHandleOffset: function(event)
    {
        var position = (this.options.vertical) ? 'top' : 'left';
        return this._relativeMousePosition(event) - parseInt(this._scrollbarHandle.css(position));
    }
});

})(jQuery);

$.extend($.ui.scrollbar, {
    version: "1.7.1",
    defaults: {
        vertical: true,
        buttonWidth: 18,
        buttonHeight: 18,
        scrollInterval: 30,
        overflow: 'hidden'
    }
});


/*! Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 * Thanks to: http://adomas.org/javascript-mouse-wheel/ for some pointers.
 * Thanks to: Mathias Bank(http://www.mathias-bank.de) for a scope bug fix.
 *
 * Version: 3.0.2
 * 
 * Requires: 1.2.2+
 */

(function($) {

var types = ['DOMMouseScroll', 'mousewheel'];

$.event.special.mousewheel = {
    setup: function() {
        if ( this.addEventListener )
            for ( var i=types.length; i; )
                this.addEventListener( types[--i], handler, false );
        else
            this.onmousewheel = handler;
    },
    
    teardown: function() {
        if ( this.removeEventListener )
            for ( var i=types.length; i; )
                this.removeEventListener( types[--i], handler, false );
        else
            this.onmousewheel = null;
    }
};

$.fn.extend({
    mousewheel: function(fn) {
        return fn ? this.bind("mousewheel", fn) : this.trigger("mousewheel");
    },
    
    unmousewheel: function(fn) {
        return this.unbind("mousewheel", fn);
    }
});


function handler(event) {
    var args = [].slice.call( arguments, 1 ), delta = 0, returnValue = true;
    
    event = $.event.fix(event || window.event);
    event.type = "mousewheel";
    
    if ( event.wheelDelta ) delta = event.wheelDelta/120;
    if ( event.detail     ) delta = -event.detail/3;
    
    // Add events and delta to the front of the arguments
    args.unshift(event, delta);

    return $.event.handle.apply(this, args);
}

})(jQuery);
