/* Copyright (c) 2006 Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 * 
 * See http://kelvinluck.com/assets/jquery/jScrollPane/
 * $Id: jScrollPane.js 33 2008-12-10 22:55:28Z kelvin.luck $
 */

/**
 * Replace the vertical scroll bars on any matched elements with a fancy
 * styleable (via CSS) version. With JS disabled the elements will
 * gracefully degrade to the browsers own implementation of overflow:auto.
 * If the mousewheel plugin has been included on the page then the scrollable areas will also
 * respond to the mouse wheel.
 *
 * @example jQuery(".scroll-pane").jScrollPane();
 *
 * @name jScrollPane
 * @type jQuery
 * @param Object	settings	hash with options, described below.
 *								scrollbarWidth	-	The width of the generated scrollbar in pixels
 *								scrollbarMargin	-	The amount of space to leave on the side of the scrollbar in pixels
 *								wheelSpeed		-	The speed the pane will scroll in response to the mouse wheel in pixels
 *								showArrows		-	Whether to display arrows for the user to scroll with
 *								arrowSize		-	The height of the arrow buttons if showArrows=true
 *								animateTo		-	Whether to animate when calling scrollTo and scrollBy
 *								dragMinHeight	-	The minimum height to allow the drag bar to be
 *								dragMaxHeight	-	The maximum height to allow the drag bar to be
 *								animateInterval	-	The interval in milliseconds to update an animating scrollPane (default 100)
 *								animateStep		-	The amount to divide the remaining scroll distance by when animating (default 3)
 *								maintainPosition-	Whether you want the contents of the scroll pane to maintain it's position when you re-initialise it - so it doesn't scroll as you add more content (default true)
 *								scrollbarOnLeft	-	Display the scrollbar on the left side?  (needs stylesheet changes, see examples.html)
 *								reinitialiseOnImageLoad - Whether the jScrollPane should automatically re-initialise itself when any contained images are loaded
 * @return jQuery
 * @cat Plugins/jScrollPane
 * @author Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)
 */

(function($) {

    $.jScrollPane = {
        active : []
    };
    $.fn.jScrollPane = function(settings)
    {
        settings = $.extend({}, $.fn.jScrollPane.defaults, settings);

        var rf = function() {
            return false;
        };
	
        return this.each(
            function()
            {
                var $this = $(this);
                // Switch the element's overflow to hidden to ensure we get the size of the element without the scrollbars [http://plugins.jquery.com/node/1208]
                $this.css('overflow', 'hidden');
                var paneEle = this;
			
                if ($(this).parent().is('.jScrollPaneContainer')) {
                    var currentScrollPosition = settings.maintainPosition ? $this.position().top : 0;
                    var $c = $(this).parent();
                    var paneWidth = $c.innerWidth();
                    var paneHeight = $c.outerHeight();
                    var trackHeight = paneHeight;
                    $('>.jScrollPaneTrack, >.jScrollArrowUp, >.jScrollArrowDown', $c).remove();
                    $this.css({
                        'top':0
                    });
                } else {
                    var currentScrollPosition = 0;
                    this.originalPadding = $this.css('paddingTop') + ' ' + $this.css('paddingRight') + ' ' + $this.css('paddingBottom') + ' ' + $this.css('paddingLeft');
                    this.originalSidePaddingTotal = (parseInt($this.css('paddingLeft')) || 0) + (parseInt($this.css('paddingRight')) || 0);
                    var paneWidth = $this.innerWidth();
                    var paneHeight = $this.innerHeight();
                    var trackHeight = paneHeight;

                    $this.wrap(
                        $('<div></div>').attr(
                        {
                            'className':'jScrollPaneContainer'
                        }
                        ).css(
                        {
                            'height':paneHeight+'px',
                            'width':paneWidth+'px'
                        }
                        )
                        );
                    // deal with text size changes (if the jquery.em plugin is included)
                    // and re-initialise the scrollPane so the track maintains the
                    // correct size
                    $(document).bind(
                        'emchange',
                        function(e, cur, prev)
                        {
                            $this.jScrollPane(settings);
                        }
                        );
				
                }
			
                if (settings.reinitialiseOnImageLoad) {
                    // code inspired by jquery.onImagesLoad: http://plugins.jquery.com/project/onImagesLoad
                    // except we re-initialise the scroll pane when each image loads so that the scroll pane is always up to size...
                    // TODO: Do I even need to store it in $.data? Is a local variable here the same since I don't pass the reinitialiseOnImageLoad when I re-initialise?
                    var $imagesToLoad = $.data(paneEle, 'jScrollPaneImagesToLoad') || $('img', $this);
                    var loadedImages = [];
				
                    if ($imagesToLoad.length) {
                        $imagesToLoad.each(function(i, val)	{
                            $(this).bind('load', function() {
                                if($.inArray(i, loadedImages) == -1){ //don't double count images
                                    loadedImages.push(val); //keep a record of images we've seen
                                    $imagesToLoad = $.grep($imagesToLoad, function(n, i) {
                                        return n != val;
                                    });
                                    $.data(paneEle, 'jScrollPaneImagesToLoad', $imagesToLoad);
                                    settings.reinitialiseOnImageLoad = false;
                                    $this.jScrollPane(settings); // re-initialise
                                }
                            }).each(function(i, val) {
                                if(this.complete || this.complete===undefined) {
                                    //needed for potential cached images
                                    this.src = this.src;
                                }
                            });
                        });
                    };
                }

                var p = this.originalSidePaddingTotal;
			
                var cssToApply = {
                    'height':'auto',
                    'width':paneWidth - settings.scrollbarWidth - settings.scrollbarMargin - p + 'px'
                }

                if(settings.scrollbarOnLeft) {
                    cssToApply.paddingLeft = settings.scrollbarMargin + settings.scrollbarWidth + 'px';
                } else {
                    cssToApply.paddingRight = settings.scrollbarMargin + 'px';
                }

                $this.css(cssToApply);

                var contentHeight = $this.outerHeight();
                var percentInView = paneHeight / contentHeight;

                if (percentInView < .99) {
                    var $container = $this.parent();
                    $container.append(
                        $('<div></div>').attr({
                            'className':'jScrollPaneTrack'
                        }).css({
                            'width':settings.scrollbarWidth+'px'
                        }).append(
                            $('<div></div>').attr({
                                'className':'jScrollPaneDrag'
                            }).css({
                                'width':settings.scrollbarWidth+'px'
                            }).append(
                                $('<div></div>').attr({
                                    'className':'jScrollPaneDragTop'
                                }).css({
                                    'width':settings.scrollbarWidth+'px'
                                }),
                                $('<div></div>').attr({
                                    'className':'jScrollPaneDragBottom'
                                }).css({
                                    'width':settings.scrollbarWidth+'px'
                                })
                                )
                            )
                        );
				
                    var $track = $('>.jScrollPaneTrack', $container);
                    var $drag = $('>.jScrollPaneTrack .jScrollPaneDrag', $container);
				
                    if (settings.showArrows) {
					
                        var currentArrowButton;
                        var currentArrowDirection;
                        var currentArrowInterval;
                        var currentArrowInc;
                        var whileArrowButtonDown = function()
                        {
                            if (currentArrowInc > 4 || currentArrowInc%4==0) {
                                positionDrag(dragPosition + currentArrowDirection * mouseWheelMultiplier);
                            }
                            currentArrowInc ++;
                        };
                        var onArrowMouseUp = function(event)
                        {
                            $('html').unbind('mouseup', onArrowMouseUp);
                            currentArrowButton.removeClass('jScrollActiveArrowButton');
                            clearInterval(currentArrowInterval);
                        };
                        var onArrowMouseDown = function() {
                            $('html').bind('mouseup', onArrowMouseUp);
                            currentArrowButton.addClass('jScrollActiveArrowButton');
                            currentArrowInc = 0;
                            whileArrowButtonDown();
                            currentArrowInterval = setInterval(whileArrowButtonDown, 100);
                        };
                        $container
                        .append(
                            $('<a></a>')
                            .attr({
                                'href':'javascript:;',
                                'className':'jScrollArrowUp'
                            })
                            .css({
                                'width':settings.scrollbarWidth+'px'
                            })
                            .html('Scroll up')
                            .bind('mousedown', function()
                            {
                                currentArrowButton = $(this);
                                currentArrowDirection = -1;
                                onArrowMouseDown();
                                this.blur();
                                return false;
                            })
                            .bind('click', rf),
                            $('<a></a>')
                            .attr({
                                'href':'javascript:;',
                                'className':'jScrollArrowDown'
                            })
                            .css({
                                'width':settings.scrollbarWidth+'px'
                            })
                            .html('Scroll down')
                            .bind('mousedown', function()
                            {
                                currentArrowButton = $(this);
                                currentArrowDirection = 1;
                                onArrowMouseDown();
                                this.blur();
                                return false;
                            })
                            .bind('click', rf)
                            );
                        var $upArrow = $('>.jScrollArrowUp', $container);
                        var $downArrow = $('>.jScrollArrowDown', $container);
                        if (settings.arrowSize) {
                            trackHeight = paneHeight - settings.arrowSize - settings.arrowSize;
                            $track
                            .css({
                                'height': trackHeight+'px',
                                top:settings.arrowSize+'px'
                            })
                        } else {
                            var topArrowHeight = $upArrow.height();
                            settings.arrowSize = topArrowHeight;
                            trackHeight = paneHeight - topArrowHeight - $downArrow.height();
                            $track
                            .css({
                                'height': trackHeight+'px',
                                top:topArrowHeight+'px'
                            })
                        }
                    }
				
                    var $pane = $(this).css({
                        'position':'absolute',
                        'overflow':'visible'
                    });
				
                    var currentOffset;
                    var maxY;
                    var mouseWheelMultiplier;
                    // store this in a seperate variable so we can keep track more accurately than just updating the css property..
                    var dragPosition = 0;
                    var dragMiddle = percentInView*paneHeight/2;
				
                    // pos function borrowed from tooltip plugin and adapted...
                    var getPos = function (event, c) {
                        var p = c == 'X' ? 'Left' : 'Top';
                        return event['page' + c] || (event['client' + c] + (document.documentElement['scroll' + p] || document.body['scroll' + p])) || 0;
                    };
				
                    var ignoreNativeDrag = function() {
                        return false;
                    };
				
                    var initDrag = function()
                    {
                        ceaseAnimation();
                        currentOffset = $drag.offset(false);
                        currentOffset.top -= dragPosition;
                        maxY = trackHeight - $drag[0].offsetHeight;
                        mouseWheelMultiplier = 2 * settings.wheelSpeed * maxY / contentHeight;
                    };
				
                    var onStartDrag = function(event)
                    {
                        initDrag();
                        dragMiddle = getPos(event, 'Y') - dragPosition - currentOffset.top;
                        $('html').bind('mouseup', onStopDrag).bind('mousemove', updateScroll);
                        if ($.browser.msie) {
                            $('html').bind('dragstart', ignoreNativeDrag).bind('selectstart', ignoreNativeDrag);
                        }
                        return false;
                    };
                    var onStopDrag = function()
                    {
                        $('html').unbind('mouseup', onStopDrag).unbind('mousemove', updateScroll);
                        dragMiddle = percentInView*paneHeight/2;
                        if ($.browser.msie) {
                            $('html').unbind('dragstart', ignoreNativeDrag).unbind('selectstart', ignoreNativeDrag);
                        }
                    };
                    var positionDrag = function(destY)
                    {
                        destY = destY < 0 ? 0 : (destY > maxY ? maxY : destY);
                        dragPosition = destY;
                        $drag.css({
                            'top':destY+'px'
                        });
                        var p = destY / maxY;
                        $pane.css({
                            'top':((paneHeight-contentHeight)*p) + 'px'
                        });
                        $this.trigger('scroll');
                        if (settings.showArrows) {
                            $upArrow[destY == 0 ? 'addClass' : 'removeClass']('disabled');
                            $downArrow[destY == maxY ? 'addClass' : 'removeClass']('disabled');
                        }
                    };
                    var updateScroll = function(e)
                    {
                        positionDrag(getPos(e, 'Y') - currentOffset.top - dragMiddle);
                    };
				
                    var dragH = Math.max(Math.min(percentInView*(paneHeight-settings.arrowSize*2), settings.dragMaxHeight), settings.dragMinHeight);
				
                    $drag.css(
                    {
                        'height':dragH+'px'
                    }
                    ).bind('mousedown', onStartDrag);
				
                    var trackScrollInterval;
                    var trackScrollInc;
                    var trackScrollMousePos;
                    var doTrackScroll = function()
                    {
                        if (trackScrollInc > 8 || trackScrollInc%4==0) {
                            positionDrag((dragPosition - ((dragPosition - trackScrollMousePos) / 2)));
                        }
                        trackScrollInc ++;
                    };
                    var onStopTrackClick = function()
                    {
                        clearInterval(trackScrollInterval);
                        $('html').unbind('mouseup', onStopTrackClick).unbind('mousemove', onTrackMouseMove);
                    };
                    var onTrackMouseMove = function(event)
                    {
                        trackScrollMousePos = getPos(event, 'Y') - currentOffset.top - dragMiddle;
                    };
                    var onTrackClick = function(event)
                    {
                        initDrag();
                        onTrackMouseMove(event);
                        trackScrollInc = 0;
                        $('html').bind('mouseup', onStopTrackClick).bind('mousemove', onTrackMouseMove);
                        trackScrollInterval = setInterval(doTrackScroll, 100);
                        doTrackScroll();
                    };
				
                    $track.bind('mousedown', onTrackClick);
				
                    $container.bind(
                        'mousewheel',
                        function (event, delta) {
                            initDrag();
                            ceaseAnimation();
                            var d = dragPosition;
                            positionDrag(dragPosition - delta * mouseWheelMultiplier);
                            var dragOccured = d != dragPosition;
                            return !dragOccured;
                        }
                        );

                    var _animateToPosition;
                    var _animateToInterval;
                    function animateToPosition()
                    {
                        var diff = (_animateToPosition - dragPosition) / settings.animateStep;
                        if (diff > 1 || diff < -1) {
                            positionDrag(dragPosition + diff);
                        } else {
                            positionDrag(_animateToPosition);
                            ceaseAnimation();
                        }
                    }
                    var ceaseAnimation = function()
                    {
                        if (_animateToInterval) {
                            clearInterval(_animateToInterval);
                            delete _animateToPosition;
                        }
                    };
                    var scrollTo = function(pos, preventAni)
                    {
                        if (typeof pos == "string") {
                            $e = $(pos, $this);
                            if (!$e.length) return;
                            pos = $e.offset().top - $this.offset().top;
                        }
                        $container.scrollTop(0);
                        ceaseAnimation();
                        var destDragPosition = -pos/(paneHeight-contentHeight) * maxY;
                        if (preventAni || !settings.animateTo) {
                            positionDrag(destDragPosition);
                        } else {
                            _animateToPosition = destDragPosition;
                            _animateToInterval = setInterval(animateToPosition, settings.animateInterval);
                        }
                    };
                    $this[0].scrollTo = scrollTo;
				
                    $this[0].scrollBy = function(delta)
                    {
                        var currentPos = -parseInt($pane.css('top')) || 0;
                        scrollTo(currentPos + delta);
                    };
				
                    initDrag();
				
                    scrollTo(-currentScrollPosition, true);
			
                    // Deal with it when the user tabs to a link or form element within this scrollpane
                    $('*', this).bind(
                        'focus',
                        function(event)
                        {
                            var $e = $(this);
						
                            // loop through parents adding the offset top of any elements that are relatively positioned between
                            // the focused element and the jScrollPaneContainer so we can get the true distance from the top
                            // of the focused element to the top of the scrollpane...
                            var eleTop = 0;
						
                            while ($e[0] != $this[0]) {
                                eleTop += $e.position().top;
                                $e = $e.offsetParent();
                            }
						
                            var viewportTop = -parseInt($pane.css('top')) || 0;
                            var maxVisibleEleTop = viewportTop + paneHeight;
                            var eleInView = eleTop > viewportTop && eleTop < maxVisibleEleTop;
                            if (!eleInView) {
                                var destPos = eleTop - settings.scrollbarMargin;
                                if (eleTop > viewportTop) { // element is below viewport - scroll so it is at bottom.
                                    destPos += $(this).height() + 15 + settings.scrollbarMargin - paneHeight;
                                }
                                scrollTo(destPos);
                            }
                        }
                        )
				
				
                    if (location.hash) {
                        scrollTo(location.hash);
                    }
				
                    // use event delegation to listen for all clicks on links and hijack them if they are links to
                    // anchors within our content...
                    $(document).bind(
                        'click',
                        function(e)
                        {
                            $target = $(e.target);
                            if ($target.is('a')) {
                                var h = $target.attr('href');
                                if (h.substr(0, 1) == '#') {
                                    scrollTo(h);
                                }
                            }
                        }
                        );
				
                    $.jScrollPane.active.push($this[0]);
				
                } else {
                    $this.css(
                    {
                        'height':paneHeight+'px',
                        'width':paneWidth-this.originalSidePaddingTotal+'px',
                        'padding':this.originalPadding
                    }
                    );
                    // remove from active list?
                    $this.parent().unbind('mousewheel');
                }
			
            }
            )
    };

    $.fn.jScrollPane.defaults = {
        scrollbarWidth : 15,
        scrollbarMargin : 10,
        wheelSpeed : 18,
        showArrows : false,
        arrowSize : 0,
        animateTo : false,
        dragMinHeight : 1,
        dragMaxHeight : 999,
        animateInterval : 10,
        animateStep: 3,
        maintainPosition: true,
        scrollbarOnLeft: false,
        reinitialiseOnImageLoad: false
    };

    // clean up the scrollTo expandos
    $(window)
    .bind('unload', function() {
        var els = $.jScrollPane.active;
        for (var i=0; i<els.length; i++) {
            els[i].scrollTo = els[i].scrollBy = null;
        }
    }
    );

})(jQuery);

$(document).ready(function () {
   $('#pane3').jScrollPane();
});
