/* 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 19 2008-11-13 06:00:09Z 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)
 */
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.scrollbarWidth+'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.scrollbarWidth+'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');
	      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 : 10,
  scrollbarMargin : 5,
  wheelSpeed : 18,
  showArrows : false,
  arrowSize : 0,
  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;
    }
  }
);
/* Copyright (c) 2006 Brandon Aaron (brandon.aaron@gmail.com || 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.
 *
 * $LastChangedDate: 2007-12-20 09:02:08 -0600 (Thu, 20 Dec 2007) $
 * $Rev: 4265 $
 *
 * Version: 3.0
 * 
 * Requires: $ 1.2.2+
 */

(function($) {

$.event.special.mousewheel = {
  setup: function() {
    var handler = $.event.special.mousewheel.handler;
    
    // Fix pageX, pageY, clientX and clientY for mozilla
    if ( $.browser.mozilla )
      $(this).bind('mousemove.mousewheel', function(event) {
	$.data(this, 'mwcursorposdata', {
	  pageX: event.pageX,
	  pageY: event.pageY,
	  clientX: event.clientX,
	  clientY: event.clientY
	});
      });
  
    if ( this.addEventListener )
      this.addEventListener( ($.browser.mozilla ? 'DOMMouseScroll' : 'mousewheel'), handler, false);
    else
      this.onmousewheel = handler;
  },
  
  teardown: function() {
    var handler = $.event.special.mousewheel.handler;
    
    $(this).unbind('mousemove.mousewheel');
    
    if ( this.removeEventListener )
      this.removeEventListener( ($.browser.mozilla ? 'DOMMouseScroll' : 'mousewheel'), handler, false);
    else
      this.onmousewheel = function(){};
    
    $.removeData(this, 'mwcursorposdata');
  },
  
  handler: function(event) {
    var args = Array.prototype.slice.call( arguments, 1 );
    
    event = $.event.fix(event || window.event);
    // Get correct pageX, pageY, clientX and clientY for mozilla
    $.extend( event, $.data(this, 'mwcursorposdata') || {} );
    var delta = 0, returnValue = true;
    
    if ( event.wheelDelta ) delta = event.wheelDelta/120;
    if ( event.detail     ) delta = -event.detail/3;
//    if ( $.browser.opera  ) delta = -event.wheelDelta;
    
    event.data  = event.data || {};
    event.type  = "mousewheel";
    
    // Add delta to the front of the arguments
    args.unshift(delta);
    // Add event to the front of the arguments
    args.unshift(event);

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

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

})(jQuery);

/*
  jQuery anchor handler - 0.5
  http://code.google.com/p/jquery-utils/

  (c) Maxime Haineault <haineault@gmail.com>
  http://haineault.com   

  MIT License (http://www.opensource.org/licenses/mit-license.php)

*/
(function($){
    var hash = window.location.hash;
    var handlers  = [];
    var opt = {};

        $.extend({
                anchorHandler: {
            apply: function() {
                $.map(handlers, function(handler){
                    var match = hash.match(handler.r) && hash.match(handler.r)[0] || false;
                    if (match)  { handler.cb.apply($('a[href*='+match+']').get(0), [handler.r, hash || '']); }
                });
                return $.anchorHandler;
            },
                        add: function(regexp, callback, options) {
                var opt  = $.extend({handleClick: true, preserveHash: true}, options);
                if (opt.handleClick) { 
                    $('a[href*=#]').each(function(i, a){
                        if (a.href.match(regexp)) {
                            $(a).bind('click.anchorHandler', function(){
                                if (opt.preserveHash) { window.location.hash = a.hash; }
                                return callback.apply(this, [regexp, a.href]);
                                });
                        }
                    }); 
                }
                                handlers.push({r: regexp, cb: callback});
                $($.anchorHandler.apply);
                                return $.anchorHandler;
                        }
                }
        });
})(jQuery);

