jQuery.jScrollPane = {
	active : []
};
jQuery.fn.jScrollPane = function(settings) {
  settings = jQuery.extend({}, jQuery.fn.jScrollPane.defaults, settings);

  var rf = function() { return false; };

  return this.each(
		function() {
		  var $this = jQuery(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 (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;
		    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);
					}
				);

		  }

		  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 (jQuery.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(
					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.arrowWidth + 'px' })
								.html('Scroll up')
								.bind('mousedown', function() {
								  currentArrowButton = jQuery(this);
								  currentArrowDirection = -1;
								  onArrowMouseDown();
								  this.blur();
								  return false;
								})
								.bind('click', rf),
							jQuery('<a></a>')
								.attr({ 'href': 'javascript:;', 'className': 'jScrollArrowDown' })
								.css({ 'width': settings.arrowWidth + 'px' })
								.html('Scroll down')
								.bind('mousedown', function() {
								  currentArrowButton = jQuery(this);
								  currentArrowDirection = 1;
								  onArrowMouseDown();
								  this.blur();
								  return false;
								})
								.bind('click', rf)
						);
		      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);

		    $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 = 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);

		    // Deal with it when the user tabs to a link or form element within this scrollpane
		    $('*', this).bind(
					'focus',
					function(event) {
					  var eleTop = $(this).position().top;
					  var viewportTop = -parseInt($pane.css('top')) || 0;
					  var maxVisibleEleTop = viewportTop + paneHeight;
					  var eleInView = eleTop > viewportTop && eleTop < maxVisibleEleTop;
					  if (!eleInView) {
					    $container.scrollTop(0);
					    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) {
		      // the timeout needs to be longer in IE when not loading from cache...
		      setTimeout(function() {
		        $(location.hash, $this).trigger('focus');
		      }, $.browser.msie ? 100 : 0);
		    }

		    // 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');
					    //	console.log(h);
					    if (h.substr(0, 1) == '#') {
					      $linkedEle = $(h, $this);
					      //		console.log($linkedEle);
					      if ($linkedEle.length) {
					        $linkedEle.trigger('focus');
					        return false;
					      }
					    }
					  }
					}
				);

		    jQuery.jScrollPane.active.push($this[0]);

		  } else {
		    $this.css(
					{
					  'height': paneHeight + 'px',
					  'width': paneWidth - this.originalSidePaddingTotal + 'px',
					  'padding': this.originalPadding
					}
				);
		    // remove from active list?
		  }

		}
	)
};

jQuery.fn.jScrollPane.defaults = {
	scrollbarWidth : 3,
	scrollbarMargin : 10,
	wheelSpeed : 18,
	showArrows : false,
	arrowSize: 0,
	arrowWidth:12,
	animateTo : false,
	dragMinHeight : 1,
	dragMaxHeight : 99999,
	animateInterval : 100,
	animateStep: 3,
	maintainPosition: true,
	scrollbarOnLeft: false,
	reinitialiseOnImageLoad: false
};

// 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;
		}
	}
);