/*
 * While not inheriting BLIP.Unit, this file is inherently linked to Unit.js, thus the placement.
 *
 * This is a mixin for Unit.js. It adds a method, isScrollable, to BLIP.Unit.prototype.
 *
 * For best results, set the Unit's container to a fixed height. It won't really work unless you do that.
 *
 * This method, when invoked, wraps the content of the Unit in a new div within the container with
 * the classname ScrollContainer.
 *
 * It adds the classname "Scrollable" to the Unit's domRoot.
 *
 * It adds a new div.Scrollbar to the domRoot, containing an a.Handle.
 *
 * Private member functions are included as inner functions, so they don't take up memory
 * unless .isScrollable(); is invoked.
 *
 * Things it takes care of:
 *
 *  - Sizing the Scrollbar to the height of the domRoot
 *  - Sizing the Handle based on amount of content viewable without scrolling further
 *    - sets a base minimum height of 10% the Scrollbar's height
 *  - Dragging the Handle scrolls the ScrollContainer proportionally
 *  - When the Scrollable mouseOver, mousewheel events scroll the ScrollContainer
 *
 * Things you need to take care of:
 *
 *  -Unit's domRoot needs an absolute height.
 *  -You need to position the .Scrollbar element.
 *  -You should probably set z-indices.
 *
 */

BLIP.Mixin.create("BLIP.Mixin.Scrollable", function(config) {
	var thisContext = this,
			enablePageScrolling = false,
			scrollbarVerticalOffset = 20,
			scrollContent = this.domRoot.find(".Scrollable"),
			scrollbarHandle = $("<a class='Handle'></a>"),
			scrollbar = $("<div class='Scrollbar'></div>").append(scrollbarHandle),
			scrollContainer = $("<div class='ScrollContainer'>"),
			containerActive = false,
			notEnoughContent = false,
			contentPosition = 0,
			handleActive = false,
			mouseY = 0,
			on_scrollToBottom = this.on_scrollToBottom = new BLIP.Utils.LocalEvent("scrollToBottom", this),
			on_scrollToTop = this.on_scrollToTop = new BLIP.Utils.LocalEvent("scrollToTop", this),
			buildControls,
			setScrollbarHeight,
			getContentProportion,
			getScrollbarPosition,
			getUsableScrollbarHeight,
			getUsableContentHeight,
			getScrollbarProportion,
			getNormalizedContentPosition,
			getNormalizedScrollbarPosition,
			getContentMaxY,
			isValidPosition,
			isTopPosition,
			isBottomPosition,
			isNearBottomPosition,
			updateScrollbarPosition,
			updateContentPosition,
			setContentPosition,
			setScrollbarPosition,
			on_scrollWheel,
			on_mouseMove,
			on_mouseUp,
			on_handleMouseDown,
			on_scrollbarMouseDown,
			registerEvents;

	this.scrollTo = function(offset) {
		setContentPosition(offset);
		setScrollbarHeight();
		updateScrollbarPosition();
	};

	this.recalculateScrollbar = function() {
		setScrollbarHeight();
		updateScrollbarPosition();
	};

	buildControls = function buildControls() {
		scrollContent.wrap(scrollContainer);
		scrollContainer = scrollContent.parent(".ScrollContainer");
		scrollContainer.prepend(scrollbar);
		scrollbar.height(scrollContainer.innerHeight() - scrollbarVerticalOffset * 2);
		if(scrollContent.height() <= 0 || getContentProportion() > 1) {
			scrollbar.hide();
			notEnoughContent = true;
		}
	};

	setScrollbarHeight = function setScrollbarHeight() {
		scrollbarHandle.height(
			Math.max(
				scrollbarHandle.width(),
				getContentProportion() * scrollbar.height()
			)
		);
	};

	getContentProportion = function getContentProportion() {
		// viewable area divided by content area
		return (scrollContainer.height() / scrollContent.height());
	};

	getScrollbarPosition = function getScrollbarPosition() {
		return Number(scrollbarHandle.css('top').replace('px',''));
	};

	getUsableScrollbarHeight = function getUsableScrollbarHeight() {
		return scrollbar.height() - scrollbarHandle.height();
	};

	getUsableContentHeight = function getUsableContentHeight() {
		return scrollContent.height() - scrollContainer.height();
	};

	getScrollbarProportion = function getScrollbarProportion() {
		// scrollbar height divided by content height
		return getUsableScrollbarHeight() / (scrollContent.height() - scrollContainer.height());
	};

	getNormalizedContentPosition = function getNormalizedContentPosition() {
		// content position as a percentage of available scrolling space

		return contentPosition / (scrollContent.height() - scrollContainer.height());
	};

	getNormalizedScrollbarPosition = function getNormalizedScrollbarPosition() {
		var top = getScrollbarPosition();
		return top / (scrollbar.height() - scrollbarHandle.height());
	};

	getContentMaxY = this.getContentMaxY = function getContentMaxY() {
		return scrollContent.height() - scrollContainer.height();
	};

	isValidPosition = function isValidPosition(position) {
		return position > 0 && position < getContentMaxY();
	};

	isTopPosition = function isTopPosition(position) {
		return position < 0;
	};

	isBottomPosition = function isBottomPosition(position) {
		return position >= getContentMaxY();
	};

	isNearBottomPosition = function isNearBottomPosition(position) {
		return position >= getContentMaxY()*0.95;
	};

	updateScrollbarPosition = function updateScrollbarPosition() {
		// no params - they're calculated based on content position
		var positionPercentage = getNormalizedContentPosition(),
				scrollPosition = positionPercentage * getUsableScrollbarHeight();

		scrollbarHandle.css('top', scrollPosition+'px');
	};

	updateContentPosition = function updateContentPosition() {
		// no params - they're calculated based on scrollbar position
		var positionPercentage = getNormalizedScrollbarPosition(),
				contentPosition = positionPercentage * getUsableContentHeight();

		setContentPosition(contentPosition);
	};


	setContentPosition = function setContentPosition(position) {
		if(isValidPosition(position)) {
			contentPosition = position;
			scrollContent.css('top', (-1 * position + "px"));
		}
		else if(position < 0) {
			contentPosition = 0;
			scrollContent.css('top', 0 + 'px');
		}
		else if(position >= getContentMaxY()) {
			contentPosition = getContentMaxY();
			scrollContent.css('top', -1 * contentPosition + 'px');
			on_scrollToBottom.fire();
		}
		else if(isTopPosition(position)) {
			on_scrollToTop.fire();
			return;
		}
		else if(isBottomPosition(position)) {
			scrollContent.css('top', (-1 * getContentMaxY() + 'px'));
			on_scrollToBottom.fire();
			return;
		}
		if(isNearBottomPosition(position)) { // catch-all
			on_scrollToBottom.fire();
		}
	};

	setScrollbarPosition = function(position) {
		if (position >= 0 && position <= getUsableScrollbarHeight()) {
			scrollbarHandle.css('top', position+'px');
		}
		updateContentPosition();
	};

	on_scrollWheel = function on_scrollWheel(event) {
		if(containerActive) {
			var delta = event.wheelDelta ? event.wheelDelta : event.detail * -100;
			setContentPosition(contentPosition - delta);
			updateScrollbarPosition();
			event.preventDefault();
		}
	};

	on_mouseMove = function on_mouseMove(event) {
		if(handleActive) {
			var mouseYDelta = event.clientY - mouseY;
			mouseY = event.clientY;
			setScrollbarPosition(mouseYDelta + Number(scrollbarHandle.css('top').replace('px','')));
			updateContentPosition();
		}
	};

	on_mouseUp = function on_handleMouseUp(event) {
		handleActive = false;
	};

	on_handleMouseDown = function on_handleMouseDown(event) {
		handleActive = true;
		mouseY = event.clientY;
		event.preventDefault();
		event.stopPropagation();
	};

	on_scrollbarMouseDown = function on_scrollbarMouseDown(event) {
		var clickY = event.clientY + $(window).scrollTop(),
				position = contentPosition,
				handleTopOffset,
				handleBottomOffset,
				pageHeight = scrollContainer.height();

		//  v- number of pixels between the top of document and the top of the scrollbar handle
		handleTopOffset = scrollbar.offset().top + getScrollbarPosition();

		//  v- number of pixels between top of document and bottom of the scrollbar handle
		handleBottomOffset = handleTopOffset + scrollbarHandle.height();

		if (clickY < handleTopOffset) {
			position -= pageHeight; // page up
		}
		else if(clickY > handleBottomOffset) {
			position += pageHeight; // page down
		}

		setContentPosition(position);
		updateScrollbarPosition();
	};

	registerEvents = function registerEvents() {
		scrollContainer.hover(function() {
			containerActive = true;
		}, function() {
			containerActive = false;
		});

		$(document).bind("mousewheel", on_scrollWheel);
		$(window).bind('mousewheel', on_scrollWheel);
		$(window).bind("DOMMouseScroll", on_scrollWheel);

		$(scrollbarHandle).bind("mousedown", on_handleMouseDown);
		$(document).bind("mouseup", on_mouseUp);
		$(document).bind("mousemove", on_mouseMove);

		$(scrollbar).bind("mousedown", on_scrollbarMouseDown);

	};

	buildControls();
	setScrollbarHeight();
	if(!notEnoughContent) {
		registerEvents();
	}
});

