"use strict";


function SmoothScroll(strEaseFunction, nMillisecondsDuration)
{
	if(typeof strEaseFunction!=="string")
		throw new Error("Invalid ease function name. See SmoothScroll.easingFunctions property.");
	
	if(!SmoothScroll.easingFunctions.hasOwnProperty(strEaseFunction))
		throw new Error("Invalid ease function.");
	
	if(typeof nMillisecondsDuration!=="number")
		throw new Error("nMillisecondsDuration must be of type Number.");
	
	if(nMillisecondsDuration>60000)
		throw new Error("Animation longer than 60 seconds?");
	
	if(nMillisecondsDuration<0)
		throw new Error("nMillisecondsDuration must be >=0.");
	
	this._strEaseFunction=strEaseFunction;
	this._nMillisecondsDuration=nMillisecondsDuration;
}


SmoothScroll.prototype={
	
	/**
	* Easing function name from SmoothScroll.easingFunctions.
	* @protected
	*/
	_strEaseFunction: null,
	
	
	/**
	* Easing duration in milliseconds.
	* @protected
	*/
	_nMillisecondsDuration: null,
	
	
	/**
	* Unix timestamp in milliseconds from when the animation has started.
	* @protected
	*/
	_nMillisecondsUnixTimeAnimationStart: null,
	
	
	/**
	* Scroll position where the animation started.
	* @protected
	*/
	_nScrollStart: null,
	
	
	/**
	* Scroll position where the animation will end.
	* @protected
	*/
	_nScrollEnd: null,
	
	
	/**
	* @protected
	*/
	_nScrollRequestCount: 0, 
	

	/**
	* Scrolls the page using the easing function and duration member properties.
	* @param HTMLElement elTargetElement.
	* @public
	*/
	scroll: function(elTargetElement)
	{
		this._nScrollRequestCount++;
		
		this._nMillisecondsUnixTimeAnimationStart=new Date().getTime();
		
		this._nScrollStart=window.scrollY;
		
		var nDocumentTopOfElement=window.scrollY+elTargetElement.getBoundingClientRect().top;
		this._nScrollEnd=parseInt(nDocumentTopOfElement+elTargetElement.offsetHeight/2-window.innerHeight/2);
		
		this._scrollStep(this._nScrollRequestCount);
	},
	
	
	/**
	* @protected
	*/
	_scrollStep: function(nScrollRequestID)
	{
		if(this._nScrollRequestCount!=nScrollRequestID)
			return;
		
		var nMillisecondsElapsed=new Date().getTime()-this._nMillisecondsUnixTimeAnimationStart;

		window.scroll(window.scrollX, SmoothScroll.easingFunctions[this._strEaseFunction](1-this._nMillisecondsUnixTimeAnimationStart/new Date().getTime(), nMillisecondsElapsed, this._nScrollStart, this._nScrollEnd-this._nScrollStart, this._nMillisecondsDuration));
		
		if (nMillisecondsElapsed <= this._nMillisecondsDuration)
		{
			SmoothScroll.requestAnimationFrame.call(
				window, 
				(function(_self){
					return function(){
						_self._scrollStep(nScrollRequestID);
					};
				})(this)
			);
		}
		else
		{
			window.scroll(window.scrollX, this._nScrollEnd);
			this._nMillisecondsUnixTimeAnimationStart=null;
			this._nScrollStart=null;
			this._nScrollEnd=null;
		}
	}
};


/**
* Easing functions param explanation from: http://stackoverflow.com/a/6824695/584490
*/
SmoothScroll.easingFunctions={
	// no easing, no acceleration
	linear: function (x, t, b, c, d) { return t; },
	
	// BSD licensed (2013-11-18) easing functions from https://github.com/danro/jquery-easing/blob/master/jquery.easing.js
	easeInOutCubic: function (x, t, b, c, d) {
		if ((t/=d/2) < 1) return c/2*t*t*t + b;
		return c/2*((t-=2)*t*t + 2) + b;
	}
};


SmoothScroll.requestAnimationFrame=
	window.requestAnimationFrame
	|| window.mozRequestAnimationFrame 
	|| window.webkitRequestAnimationFrame
;


if(!SmoothScroll.requestAnimationFrame)
{
	SmoothScroll.nUnixTimestampMilliseconds=new Date().getTime();
	
	SmoothScroll.requestAnimationFrame=function(fnCallback){
		window.setTimeout(
			function(){
				fnCallback(new Date().getTime()-SmoothScroll.nUnixTimestampMilliseconds);
			}, 
			15
		);
	};
}
