/* 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 4765 2008-02-17 21:14:59Z kelvinluck $
*/

/**
* 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)
* @return jQuery
* @cat Plugins/jScrollPane
* @author Kelvin Luck (kelvin AT kelvinluck DOT com || http://www.kelvinluck.com)
*/
jQuery.jScrollPane = {
active : []
};
jQuery.fn.jScrollPane = function(settings)
{
settings = jQuery.extend(
{
scrollbarWidth : 10,
scrollbarMargin : 5,
wheelSpeed : 18,
showArrows : true,
arrowSize : 10,
animateTo : false,
dragMinHeight : 1,
dragMaxHeight : 99999,
animateInterval : 100,
animateStep: 3,
maintainPosition: true,
scrollbarOnLeft: false
}, settings
);
return this.each(
function()
{
var $this = jQuery(this);

if (jQuery(this).parent().is('.jScrollPaneContainer')) {
var currentScrollPosition = settings.maintainPosition ? $this.offset({relativeTo:jQuery(this).parent()[0]}).top : 0;
var $c = jQuery(this).parent();
var paneWidth = $c.innerWidth();
var paneHeight = $c.outerHeight();
var trackHeight = paneHeight;
if ($c.unmousewheel) {
$c.unmousewheel();
}
jQuery('>.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(
jQuery('<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
jQuery(document).bind(
'emchange', 
function(e, cur, prev)
{
$this.jScrollPane(settings);
}
);
}
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(
		    jQuery('<div></div>').attr({'className':'jScrollPaneTrack'}).css({'width':settings.scrollbarWidth+'px'}).append(
		    jQuery('<div></div>').attr({'className':'jScrollPaneDrag'}).css({'width':settings.scrollbarWidth+'px'}).append(
		    jQuery('<div></div>').attr({'className':'jScrollPaneDragTop'}).css({'width':settings.scrollbarWidth+'px'}),
		    jQuery('<div></div>').attr({'className':'jScrollPaneDragBottom'}).css({'width':settings.scrollbarWidth+'px'})
		    )
		    )
		    );
		    
		    var $track = jQuery('>.jScrollPaneTrack', $container);
		    var $drag = jQuery('>.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)
		    {
		    jQuery('html').unbind('mouseup', onArrowMouseUp);
		    currentArrowButton.removeClass('jScrollActiveArrowButton');
		    clearInterval(currentArrowInterval);
		    //console.log($(event.target));
		    //currentArrowButton.parent().removeClass('jScrollArrowUpClicked jScrollArrowDownClicked');
		    };
		    var onArrowMouseDown = function() {
		    //console.log(direction);
		    //currentArrowButton = $(this);
		    jQuery('html').bind('mouseup', onArrowMouseUp);
		    currentArrowButton.addClass('jScrollActiveArrowButton');
		    currentArrowInc = 0;
		    whileArrowButtonDown();
		    currentArrowInterval = setInterval(whileArrowButtonDown, 100);
		    };
		    $container
		    .append(
		    jQuery('<a></a>')
		    .attr({'href':'javascript:;', 'className':'jScrollArrowUp'})
		    .css({'width':settings.scrollbarWidth+'px'})
		    .html('Scroll up')
		    .bind('mousedown', function()
		    {
		    currentArrowButton = jQuery(this);
		    currentArrowDirection = -1;
		    onArrowMouseDown();
		    this.blur();
		    return false;
		    }),
		    jQuery('<a></a>')
		    .attr({'href':'javascript:;', 'className':'jScrollArrowDown'})
		    .css({'width':settings.scrollbarWidth+'px'})
		    .html('Scroll down')
		    .bind('mousedown', function()
		    {
		    currentArrowButton = jQuery(this);
		    currentArrowDirection = 1;
		    onArrowMouseDown();
		    this.blur();
		    return false;
		    })
		    );
		    var $upArrow = jQuery('>.jScrollArrowUp', $container);
		    var $downArrow = jQuery('>.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 = jQuery(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;
		    jQuery('html').bind('mouseup', onStopDrag).bind('mousemove', updateScroll);
		    if (jQuery.browser.msie) {
		    jQuery('html').bind('dragstart', ignoreNativeDrag).bind('selectstart', ignoreNativeDrag);
		    }
		    return false;
		    };
		    var onStopDrag = function()
		    {
		    jQuery('html').unbind('mouseup', onStopDrag).unbind('mousemove', updateScroll);
		    dragMiddle = percentInView*paneHeight/2;
		    if (jQuery.browser.msie) {
		    jQuery('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);
			    jQuery('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;
			    jQuery('html').bind('mouseup', onStopTrackClick).bind('mousemove', onTrackMouseMove);
			    trackScrollInterval = setInterval(doTrackScroll, 100);
			    doTrackScroll();
			    };
			    
			    $track.bind('mousedown', onTrackClick);
			    
			    // if the mousewheel plugin has been included then also react to the mousewheel
			    if ($container.mousewheel) {
			    $container.mousewheel(
			    function (event, delta) {
			    initDrag();
			    ceaseAnimation();
			    var d = dragPosition;
			    positionDrag(dragPosition - delta * mouseWheelMultiplier);
			    var dragOccured = d != dragPosition;
			    return !dragOccured;
			    },
			    false
			    );					
			    }
			    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 = jQuery(pos, this);
						   if (!$e.length) return;
						   pos = $e.offset().top - $this.offset().top;
						   }
						   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);
						   
						   jQuery.jScrollPane.active.push($this[0]);
						   
						   } else {
						   $this.css(
						   {
						   'height':paneHeight+'px',
						   'width':paneWidth-this.originalSidePaddingTotal+'px',
						   'padding':this.originalPadding
						   }
						   );
						   // remove from active list?
						   }
						   
						   }
						   )
						   };
						   
						   // clean up the scrollTo expandos
						   jQuery(window)
						   .bind('unload', function() {
						   var els = jQuery.jScrollPane.active; 
						   for (var i=0; i<els.length; i++) {
									       els[i].scrollTo = els[i].scrollBy = null;
									       }
									       }
);
