/**
 * Fanta.com Custom Scrollbar Plugin Javascript
 * 
 * - To be included on every page on Fanta.com
 * - Used in the share footer module
 *
 * @copyright Fantasy Interactive
 * @author Hakim Elhattab (Fantasy Interactive)
 * @author Karl Stanton (Fantasy Interactive)
 * @author Brian Fegan (Fantasy Interactive)
 * @version 0.1 - Extracted source from Hakim's original codebase
 * @version 0.2 - Refactored code base into reusable plugin design
 * @version 0.3 - Documented
 * @version 0.4 - Added x-axis scroll functionality
 * @version 0.5 - Dynamic scrubber width
 * @version 0.6 - Added snapping capability
 * 
 * @param {Object} oCustomOptions (See oDefaults)
 *  
 */
 
(function ($) {

	$.fn.scrollbar = function (oCustomOptions) {
	
		// If no element found, then return immediately
		if (!this[0]) {
			return;
		};
		
		var oDefaults = {
			scrollAxis: 'y',
			bDynamicScrubber: false,
			bMarkers: false,
			bSnapping: false,
			iNumChildren: 0,
			iItemsPerPage: 0,
			iStartEndLengths: 0,
			iNumPages: 0,
			iScrollbarStartEndPadding: 0,
			iSnapThreshold: 0.5
		};
		
		var oOptions = $.extend(oDefaults, oCustomOptions || {});
		
		// Begin
		return this.each(function () {

			// Containers
			var oScrollableItem,
				oScrollbar,
				oScrubber,
				oMarkers,
				oContainer,
				// Offset of the scrubber
				scrubberOffset,
				// Positions, dimensions of the scrollbar and content
				iScrollPosition,
				iContentPosition,
				iScrollbarWidth,
				iScrollbarHeight;
			
			oContainer		= $(this);
			
			oScrollableItem	= $(this).children();
			
			// remove noJS class
			oContainer.removeClass('noJS');

			// Determine the # of pages if we have the required parameters
			if (oOptions.iNumChildren !== 0 && oOptions.iItemsPerPage !== 0) {
				oOptions.iNumPages = oOptions.iNumChildren / oOptions.iItemsPerPage;
			}
					
			// Builds out the scrollbar
			BuildScrollbar();
			
			// Are we snapping to points?
			if (oOptions.bMarkers && oOptions.iNumPages !== 0) {
				BuildMarkers();
			}

			// Bind the event listeners for the menu
			BindEvents();

//** Instantiation _______________________________________________________ **//

			/**
			 * Builds a dynamic scrubber
			 * 
			 * @author Karl Stanton
			 * @since 0.5
			 * @param {Object} oContainer
			 */
			function BuildScrollbar () {
				
				var sOrientation;

				// Determine the orientation of the scrollbar
				if (oOptions.scrollAxis === 'y') {
					sOrientation = 'top: 0px;';
				} else {
					sOrientation = 'left: 0px;';
				}
				
				// Add the scrollbar
				oContainer.append('<div class="scrollbar"></div>');
				
				// Get the scrollbar
				oScrollbar	= $('div.scrollbar', oContainer);
			
				// Get the width of the scrollbar
				iScrollbarWidth		= oScrollbar.width();
				iScrollbarHeight	= oScrollbar.height();
					
				// If the user has chosen to use a dynamic scrollbar, we must alter the
				// markup to make this possible
				if (oOptions.bDynamicScrubber && oOptions.iStartEndLengths !== 0) {
					oScrubber	= BuildDynamicScrubber($('.scrollbar', oContainer), sOrientation);
				}
				// Else enter a fixed width scrubber
				else {
					oScrubber	= $('<div class="scrubber" style="' + sOrientation + '"></div>');
				}
				
				// Now add the scrubber
				$('div.scrollbar', oContainer).append(oScrubber);
			
				// Set up our elements
				oScrubber	= $('div.scrubber', oScrollbar);
				
			}
			
			/**
			 * Builds a dynamic scrubber
			 * 
			 * @author Karl Stanton
			 * @since 0.5
			 * @param {Object} sOrientation (X or Y?)
			 * @return {String} sScrubber
			 */
			function BuildDynamicScrubber (oScrollbar, sOrientation) {
				
				var sScrubber			= '',
					iStretchLength, sStretchLength, sClass, iScrubberLength;
				
				// First part of the equation defines the width by dividing the scrollbard with by the number of pages
				// The second part compensates for the Left and Right bumpers
				if (oOptions.scrollAxis === 'x') {
					
					iStretchLength = Math.floor(iScrollbarWidth / oOptions.iNumPages) - (oOptions.iStartEndLengths * 2);
					
					sStretchLength = 'width: ' + iStretchLength + 'px;';
					
					// Now we can set the length	
					iScrubberLength = 'width: ' + (oOptions.iStartEndLengths + oOptions.iStartEndLengths + iStretchLength) + 'px;';
					
				}
				else {
					
					iStretchLength = Math.floor(iScrollbarHeight / oOptions.iNumPages) - (oOptions.iStartEndLengths * 2);
					
					sStretchLength = 'height: ' + iStretchLength + 'px;';
					
					// Now we can set the length	
					iScrubberLength = 'height: ' + (oOptions.iStartEndLengths + oOptions.iStartEndLengths + iStretchLength) + 'px;';
					
				}
				
				

				// Setup the markup with our new properties
				sScrubber	 = '<div class="scrubber" style="background: none; z-index: 100;' + iScrubberLength + '">';
				sScrubber	+= '<div class="scrubber-start-' + oOptions.scrollAxis + '"></div>';
				sScrubber	+= '<div class="scrubber-stretch-' + oOptions.scrollAxis + '" style="' + sOrientation + sStretchLength + '"></div>';
				sScrubber	+= '<div class="scrubber-end-' + oOptions.scrollAxis + '"></div>';
				sScrubber	+= '</div>';
				
				return $(sScrubber);
				
			}

			/**
			 * Based on the number of pages, we can determine how many points
			 * to draw, and how to snap to them
			 */
			function BuildMarkers () {
				
				var oMarkers, iMargin,
					i;
					
				// Create the snapping container
				oMarkers = $('<div class="scrollbar-snap-' + oOptions.scrollAxis + '" style="position: relative; top: 0px; left: 0px; width: ' + iScrollbarWidth + '"></div>');
				
				// Margin between each point
				// -1 for the width of the div
				iMargin = ((iScrollbarWidth - oOptions.iScrollbarStartEndPadding) / oOptions.iNumPages);
				
				// For each page, create a marker, and set the margin left to be an equal split of it
				// the marker should be 1px in width
				
				for (i = 1; i < oOptions.iNumPages; i++) {
					
					if (i === 0) {
						//iMarginNew /= 2;
					}
					
					oMarkers.append('<div class="scrollbar-snappoint-' + oOptions.scrollAxis + '" style="float: left; height: ' + iScrollbarHeight + 'px; width: 1px; margin-left: ' + iMargin + 'px;"></div>');
				}
				
				oScrollbar.append(oMarkers);
				
			}
			
//** Events ______________________________________________________________ **//

			/**
			 * Builds the carousel from pre-existing HTML
			 * 
			 * @author Hakim Elhattab
			 */
			function BindEvents () {
				
				// Bind events to track when the user is over the carousel list
				if (oOptions.scrollAxis == 'y') {
					oScrollableItem.hover(StartMouseWheel, StopMouseWheel);
				}
				
				// Bind events for the scrollbar scrubber
				oScrubber.hover(function (e) {
					oScrubber.addClass('over');
				}, function (e) {
					oScrubber.removeClass('over');
				});
				
				oScrubber.mousedown(function (e) {
					oScrubber.addClass('down');
				});
				
				oScrollbar.bind('mousedown', function(e) {
					onScrollbarMouseDown(e);
					return false;
				});
				
			}
			
			/**
			 * @author Hakim Elhattab
			 * @since 0.1
			 * 
			 * @param {Object} event
			 */
			function onScrollbarMouseDown (event) {
				
				// Do nothing if we are disabled
				if ($(this).hasClass('scrollbar-disabled')) {
					return;
				}
				
				// If the mouse is NOT pressed down on the scrubber, we need to move the scrubber to where the mouse currently is
				if(!$(oScrubber).hasClass('down')) {
					
					if (oOptions.scrollAxis == 'y'){
						oScrubber.css('top', event.clientY - oScrollbar.offset().top - (oScrubber.height() * 0.5));
					} else {
						oScrubber.css('left', event.clientX - oScrollbar.offset().left - (oScrubber.width() * 0.5));
					}
					
				}
				
				// Move the scrubber
				if (oOptions.scrollAxis == 'y') {
					scrubberOffset = event.clientY - oScrubber.position().top;
				} else {
					scrubberOffset = event.clientX - oScrubber.position().left;
				}
				
				
				$(document).bind('mousemove', ScrollBarMouseMove);
				$(document).bind('mouseup',	ScrollBarMouseUp);
				
				// Trigger an initial update of content positioning to make sure it is in sync with where the scrubber is
				ScrollBarMouseMove(event);
					
			}
			
			/**
			 * Starts up mouse wheel scrolling, this will disable
			 * mouse wheel usage for scrolling the browser window.
			 * 
			 * @author Hakim Elhattab
			 */
			function StartMouseWheel (event) {
				
				if (window.addEventListener) {
					
					if ($.browser.safari) {
						// Safari / Chrome
						window.addEventListener('mousewheel', MouseWheel, false);
						
					}
					else {
						// Mozilla
						window.addEventListener('DOMMouseScroll', MouseWheel, false);
					}
				}
				else {
					// IE
					window.mousewheel = document.mousewheel = MouseWheel;
				}
				
			}
			
			/**
			 * Stops the mouse wheel scrolling by detaching all
			 * event hooks.
			 * 
			 * @author Hakim Elhattab
			 */
			function StopMouseWheel (event) {
							
				if (window.addEventListener) {
					
					if ($.browser.safari) {
						// Safari / Chrome
						window.removeEventListener('mousewheel', MouseWheel, false);
					}
					else {
						// Mozilla
						window.removeEventListener('DOMMouseScroll', MouseWheel, false);
					}
					
				}
				else {
					// IE
					window.onmousewheel = document.onmousewheel = null;
				}
			}
			
			/**
			 * Handles the Mouse Wheel / Scroll event, while this
			 * listener is active it will prevent default browser
			 * mouse wheel behavior.
			 * 
			 * Cross browser delta calculation courtesy of http://adomas.org/javascript-mouse-wheel/.
			 * 
			 * @author Hakim Elhattab
			 */
			function MouseWheel (event) {
				
				// Update the current scrubber offset
				oScrubber	= $("div.scrubber", oScrollbar);
					
				if (oOptions.scrollAxis == 'y') {
					scrubberOffset = event.clientY - oScrubber.position().top;
				} else {
					scrubberOffset = event.clientX - oScrubber.position().left;
				}
				//scrubberYOffset = event.clientY - oScrubber.position().top;
				
				var delta = 0;
				
				// For IE.
				if (!event) {
					event = window.event;
				}
				
				// IE/Opera.
				if (event.wheelDelta) { 
					delta = event.wheelDelta / 120;
					
					if (window.opera) {
						delta = -delta;
					}
				}
				
				// In Mozilla, sign of delta is different than in IE. Also, delta is multiple of 3.
				if (event.detail) {
					delta = -event.detail/3;
				}
				
				
				// If delta is nonzero, handle it. Basically, delta is now positive if wheel was scrolled up,
				// and negative, if wheel was scrolled down.
				if (delta) {
					
					var offset = delta > 0 ? -50 : 50;
					
					if (oOptions.scrollAxis == 'y'){
						UpdateScroll(event.clientY + offset);	
					} else {
						UpdateScroll(event.clientX + offset);
					}
					
				}
				
				// Prevent default mouse wheel functionality
				if(event.stopPropagation) event.stopPropagation();
				if(event.preventDefault) event.preventDefault();
				if(event.returnValue) event.returnValue = false;
				
				return false;
			}
			
			/**
			 * @author Hakim Elhattab
			 */
			function ScrollBarMouseMove (event) {
				if (oOptions.scrollAxis == 'y') {
					UpdateScroll(event.clientY);
				} else {
					UpdateScroll(event.clientX);
				}
			}
			
			/**
			 * @author Hakim Elhattab
			 */
			function ScrollBarMouseUp (e) {
				
				oScrubber.removeClass('down');
				
				$(document).unbind('mousemove', ScrollBarMouseMove);
				$(document).unbind('mouseup', ScrollBarMouseUp);
				
				// If we have snapping turned on, run that calcuation
				if (oOptions.bSnapping) {
					SnapToItem();
				}
				
			}
			
			/**
			 * Updates the scroll position of the scrubber and the content
			 * 
			 * @author Hakim Elhattab
			 * @author Brian Fegan
			 * @author Karl Stanton
			 * @since 0.1
			 * 
			 * @version 0.1
			 * @version 0.2 - Added X / Y axis
			 * @version 0.3 - Added margin Offset
			 * @param {Object} global (x or y)
			 */
			function UpdateScroll (global) {
				
				var iScrollDistance,
					iScrollScale,
					
					// HTC CODE ONLY!
					// karl.stanton
					//
					// Margin offset - Assumes that the scroll object is UL > LI{n}  
					// and that the margins are different between the first and last element.
					// this calculates the difference and applies to to the width
					iMarginOffset;
					
					
				// Axis Y
				if (oOptions.scrollAxis == 'y') {
					
					// Determine the height of the scrollbar
					iScrollDistance = oScrollbar.height();
					
					// Calculate the position to move the scrubber to (within max/min bounds)
					iScrollPosition = Math.max(Math.min(global - scrubberOffset, iScrollDistance - oScrubber.height()), 0);
					
					// Calculate the scroll scale, ranged from 0-1 where 1 is the full bottom scroll 
					iScrollScale	= iScrollPosition / (iScrollDistance - oScrubber.height());

					// The offset for uneven margins
					iMarginOffset	= oScrollableItem.children(':first').outerHeight(true) - oScrollableItem.children(':first').height();
					
					// Calculate the new position to move the content to [ y * ((totalHeight - marginOffset) - containerHeight ) ] 
					iContentPosition = -iScrollScale * ((oScrollableItem.height() - iMarginOffset) - oContainer.height());
					
					oScrubber.css('top', iScrollPosition);
					oScrollableItem.css('top', iContentPosition);
				
				// Axis X					
				} else {
					
					// Determine the height of the scrollbar
					iScrollDistance = oScrollbar.width();
					
					// Calculate the position to move the scrubber to (within max/min bounds)
					iScrollPosition = Math.max(Math.min(global - scrubberOffset, iScrollDistance - oScrubber.width()), 0);
					
					// Calculate the scroll scale, ranged from 0-1 where 1 is the full bottom scroll 
					iScrollScale	= iScrollPosition / (iScrollDistance - oScrubber.width());

					// The offset for uneven margins
					iMarginOffset	= oScrollableItem.children(':first').outerWidth(true) - oScrollableItem.children(':first').width();
					
					// Calculate the new position to move the content to [ x * ((totalWidth - marginOffset) - containerWidth ) ] 
					iContentPosition = -iScrollScale * ((oScrollableItem.width() - iMarginOffset) - oContainer.width());
					
					oScrubber.css('left', iScrollPosition);
					oScrollableItem.css('left', iContentPosition);
					
				}
				
			}
			
			/**
			 * Calculates what item to snap too, so that no item is left half way through the scroll.
			 * 
			 * @author Karl Stanton
			 * @since 0.6
			 */
			function SnapToItem () {
				
				var iScrollableItemLeft,
					iSizeOfItem,
					iCurrentItemVisibility,
					iNewPosition,
					iDistance,
					iRatio,
					iScrubberDistance,
					j;
				
				// Left position of the scrollable item
				iScrollableItemLeft = oScrollableItem.position().left;
				
				// Size of am item within the scrollable item
				iSizeOfItem = oScrollableItem.innerWidth() / oOptions.iNumChildren;
				
				// We know how far left the scrollable item is, so this should tell us what item is currently 
				// the first in vew on the left hand side
				iCurrentItemVisibility = -iScrollableItemLeft / iSizeOfItem;
				
				// Is the current item more than (oOptions.iSnapThreshold) hidden of it's total width
				// "j" is a floored copy of iCurrentItem. this means that ie: 2.61 = 2.
				// Then we add (oOptions.iSnapThreshold) to it to give us a perfect
				// (oOptions.iSnapThreshold) integer of iCurrentItem
				j = Math.floor(iCurrentItemVisibility) + oOptions.iSnapThreshold;
				
				// If iCurrentItemVisiblity is more than oOptions.iSnapThreshold
				// 
				// Snap to the NEXT item
				if (iCurrentItemVisibility > j) {
					iNewPosition = (Math.floor(iCurrentItemVisibility) * -iSizeOfItem) - iSizeOfItem;
				}
				// Snap to the CURRENT item
				else {
					iNewPosition = (Math.floor(iCurrentItemVisibility) * -iSizeOfItem);
				}
				// Set the new position
				oScrollableItem.animate({'left': iNewPosition}, 'fast');
				
				// Also, update the scrollbar to the new position

				// What is the distance to move?
				iDistance = iScrollableItemLeft - iNewPosition;
				
				// Ratio between size of scrollbar and content
				iRatio		= iScrollbarWidth / oScrollableItem.width();
				
				// Scrollbar distance to move
				iScrubberDistance = oScrubber.position().left + (iRatio * iDistance);
				
				oScrubber.animate({'left': iScrubberDistance}, 'fast');
				
				
				//console.log('iRatio: ' + iRatio + ' :: iDistance: ' + iDistance + ' :: iNewPosition: ' + iNewPosition + ' :: iScrollableItemLeft: ' + iScrollableItemLeft + ' :: iCurrentItemVisibility: ' + iCurrentItemVisibility);
			}
			
		});
		
	};

})(jQuery);

