//
// SmoothScroll for websites v1.4.4 (Balazs Galambosi)
// http://www.smoothscroll.net/
//
// Licensed under the terms of the MIT license.
//
// You may use it in your theme if you credit me.
// It is also free to use on any individual website.
//
// Exception:
// The only restriction is to not publish any
// extension for browsers or native application
// without getting a written permission first.
//

(function () {

// Scroll Variables (tweakable)
	var defaultOptions = {

		// Scrolling Core
		frameRate: 150, // [Hz]
		animationTime: 500, // [px]
		stepSize: 150, // [px]

		// Pulse (less tweakable)
		// ratio of "tail" to "acceleration"
		pulseAlgorithm: true,
		pulseScale: 6,
		pulseNormalize: 1,

		// Acceleration
		accelerationDelta: 20,  // 20
		accelerationMax: 1,   // 1

		// Keyboard Settings
		keyboardSupport   : true,  // option
		arrowScroll       : 50,    // [px]

		// Other
		touchpadSupport   : false, // ignore touchpad by default
		fixedBackground   : true,
		excluded          : ''
	};

	var options = defaultOptions;


// Other Variables
	var isExcluded = false;
	var isFrame = false;
	var direction = { x: 0, y: 0 };
	var initDone  = false;
	var root = document.documentElement;
	var activeElement;
	var observer;
	var refreshSize;
	var deltaBuffer = [];
	var isMac = /^Mac/.test(navigator.platform);

	var key = { left: 37, up: 38, right: 39, down: 40, spacebar: 32,
		pageup: 33, pagedown: 34, end: 35, home: 36 };
	var arrowKeys = { 37: 1, 38: 1, 39: 1, 40: 1 };

	/***********************************************
	 * INITIALIZE
	 ***********************************************/

	/**
	 * Tests if smooth scrolling is allowed. Shuts down everything if not.
	 */
	function initTest() {
		if (options.keyboardSupport) {
			addEvent('keydown', keydown);
		}
	}

	/**
	 * Sets up scrolls array, determines if frames are involved.
	 */
	function init() {

		if (initDone || !document.body) return;

		initDone = true;

		var body = document.body;
		var html = document.documentElement;
		var windowHeight = window.innerHeight;
		var scrollHeight = body.scrollHeight;

		// check compat mode for root element
		root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
		activeElement = body;

		initTest();

		// Checks if this script is running in a frame
		if (top != self) {
			isFrame = true;
		}

		/**
		 * Please duplicate this radar for a Safari fix!
		 * rdar://22376037
		 * https://openradar.appspot.com/radar?id=4965070979203072
		 *
		 * Only applies to Safari now, Chrome fixed it in v45:
		 * This fixes a bug where the areas left and right to
		 * the content does not trigger the onmousewheel event
		 * on some pages. e.g.: html, body { height: 100% }
		 */
		else if (scrollHeight > windowHeight &&
			(body.offsetHeight <= windowHeight ||
			html.offsetHeight <= windowHeight)) {

			var fullPageElem = document.createElement('div');
			fullPageElem.style.cssText = 'position:absolute; z-index:-10000; ' +
				'top:0; left:0; right:0; height:' +
				root.scrollHeight + 'px';
			document.body.appendChild(fullPageElem);

			// DOM changed (throttled) to fix height
			var pendingRefresh;
			refreshSize = function () {
				if (pendingRefresh) return; // could also be: clearTimeout(pendingRefresh);
				pendingRefresh = setTimeout(function () {
					if (isExcluded) return; // could be running after cleanup
					fullPageElem.style.height = '0';
					fullPageElem.style.height = root.scrollHeight + 'px';
					pendingRefresh = null;
				}, 500); // act rarely to stay fast
			};

			setTimeout(refreshSize, 10);

			addEvent('resize', refreshSize);

			// TODO: attributeFilter?
			var config = {
				attributes: true,
				childList: true,
				characterData: false
				// subtree: true
			};

			observer = new MutationObserver(refreshSize);
			observer.observe(body, config);

			if (root.offsetHeight <= windowHeight) {
				var clearfix = document.createElement('div');
				clearfix.style.clear = 'both';
				body.appendChild(clearfix);
			}
		}

		// disable fixed background
		if (!options.fixedBackground && !isExcluded) {
			body.style.backgroundAttachment = 'scroll';
			html.style.backgroundAttachment = 'scroll';
		}
	}

	/**
	 * Removes event listeners and other traces left on the page.
	 */
	function cleanup() {
		observer && observer.disconnect();
		removeEvent(wheelEvent, wheel);
		removeEvent('mousedown', mousedown);
		removeEvent('keydown', keydown);
		removeEvent('resize', refreshSize);
		removeEvent('load', init);
	}


	/************************************************
	 * SCROLLING
	 ************************************************/

	var que = [];
	var pending = false;
	var lastScroll = Date.now();

	/**
	 * Pushes scroll actions to the scrolling queue.
	 */
	function scrollArray(elem, left, top) {

		directionCheck(left, top);

		if (options.accelerationMax != 1) {
			var now = Date.now();
			var elapsed = now - lastScroll;
			if (elapsed < options.accelerationDelta) {
				var factor = (1 + (50 / elapsed)) / 2;
				if (factor > 1) {
					factor = Math.min(factor, options.accelerationMax);
					left *= factor;
					top  *= factor;
				}
			}
			lastScroll = Date.now();
		}

		// push a scroll command
		que.push({
			x: left,
			y: top,
			lastX: (left < 0) ? 0.99 : -0.99,
			lastY: (top  < 0) ? 0.99 : -0.99,
			start: Date.now()
		});

		// don't act if there's a pending queue
		if (pending) {
			return;
		}

		var scrollWindow = (elem === document.body);

		var step = function (time) {

			var now = Date.now();
			var scrollX = 0;
			var scrollY = 0;

			for (var i = 0; i < que.length; i++) {

				var item = que[i];
				var elapsed  = now - item.start;
				var finished = (elapsed >= options.animationTime);

				// scroll position: [0, 1]
				var position = (finished) ? 1 : elapsed / options.animationTime;

				// easing [optional]
				if (options.pulseAlgorithm) {
					position = pulse(position);
				}

				// only need the difference
				var x = (item.x * position - item.lastX) >> 0;
				var y = (item.y * position - item.lastY) >> 0;

				// add this to the total scrolling
				scrollX += x;
				scrollY += y;

				// update last values
				item.lastX += x;
				item.lastY += y;

				// delete and step back if it's over
				if (finished) {
					que.splice(i, 1); i--;
				}
			}

			// scroll left and top
			if (scrollWindow) {
				window.scrollBy(scrollX, scrollY);
			}
			else {
				if (scrollX) elem.scrollLeft += scrollX;
				if (scrollY) elem.scrollTop  += scrollY;
			}

			// clean up if there's nothing left to do
			if (!left && !top) {
				que = [];
			}

			if (que.length) {
				requestFrame(step, elem, (1000 / options.frameRate + 1));
			} else {
				pending = false;
			}
		};

		// start a new queue of actions
		requestFrame(step, elem, 0);
		pending = true;
	}


	/***********************************************
	 * EVENTS
	 ***********************************************/

	/**
	 * Mouse wheel handler.
	 * @param {Object} event
	 */
	function wheel(event) {

		if (!initDone) {
			init();
		}

		var target = event.target;
		var overflowing = overflowingAncestor(target);

		// use default if there's no overflowing
		// element or default action is prevented
		// or it's a zooming event with CTRL
		if (!overflowing || event.defaultPrevented || event.ctrlKey) {
			return true;
		}

		// leave embedded content alone (flash & pdf)
		if (isNodeName(activeElement, 'embed') ||
			(isNodeName(target, 'embed') && /\.pdf/i.test(target.src)) ||
			isNodeName(activeElement, 'object') ||
			target.shadowRoot) {
			return true;
		}

		var deltaX = -event.wheelDeltaX || event.deltaX || 0;
		var deltaY = -event.wheelDeltaY || event.deltaY || 0;

		if (isMac) {
			if (event.wheelDeltaX && isDivisible(event.wheelDeltaX, 120)) {
				deltaX = -120 * (event.wheelDeltaX / Math.abs(event.wheelDeltaX));
			}
			if (event.wheelDeltaY && isDivisible(event.wheelDeltaY, 120)) {
				deltaY = -120 * (event.wheelDeltaY / Math.abs(event.wheelDeltaY));
			}
		}

		// use wheelDelta if deltaX/Y is not available
		if (!deltaX && !deltaY) {
			deltaY = -event.wheelDelta || 0;
		}

		// line based scrolling (Firefox mostly)
		if (event.deltaMode === 1) {
			deltaX *= 40;
			deltaY *= 40;
		}

		// check if it's a touchpad scroll that should be ignored
		if (!options.touchpadSupport && isTouchpad(deltaY)) {
			return true;
		}

		// scale by step size
		// delta is 120 most of the time
		// synaptics seems to send 1 sometimes
		if (Math.abs(deltaX) > 1.2) {
			deltaX *= options.stepSize / 120;
		}
		if (Math.abs(deltaY) > 1.2) {
			deltaY *= options.stepSize / 120;
		}

		scrollArray(overflowing, deltaX, deltaY);
		event.preventDefault();
		scheduleClearCache();
	}

	/**
	 * Keydown event handler.
	 * @param {Object} event
	 */
	function keydown(event) {

		var target   = event.target;
		var modifier = event.ctrlKey || event.altKey || event.metaKey ||
			(event.shiftKey && event.keyCode !== key.spacebar);

		// our own tracked active element could've been removed from the DOM
		if (!document.body.contains(activeElement)) {
			activeElement = document.activeElement;
		}

		// do nothing if user is editing text
		// or using a modifier key (except shift)
		// or in a dropdown
		// or inside interactive elements
		var inputNodeNames = /^(textarea|select|embed|object)$/i;
		var buttonTypes = /^(button|submit|radio|checkbox|file|color|image)$/i;
		if ( event.defaultPrevented ||
			inputNodeNames.test(target.nodeName) ||
			isNodeName(target, 'input') && !buttonTypes.test(target.type) ||
			isNodeName(activeElement, 'video') ||
			isInsideYoutubeVideo(event) ||
			target.isContentEditable ||
			modifier ) {
			return true;
		}

		// [spacebar] should trigger button press, leave it alone
		if ((isNodeName(target, 'button') ||
			isNodeName(target, 'input') && buttonTypes.test(target.type)) &&
			event.keyCode === key.spacebar) {
			return true;
		}

		// [arrwow keys] on radio buttons should be left alone
		if (isNodeName(target, 'input') && target.type == 'radio' &&
			arrowKeys[event.keyCode])  {
			return true;
		}

		var shift, x = 0, y = 0;
		var elem = overflowingAncestor(document.activeElement);
		var clientHeight = elem.clientHeight;

		if (elem == document.body) {
			clientHeight = window.innerHeight;
		}

		switch (event.keyCode) {
			case key.up:
				y = -options.arrowScroll;
				break;
			case key.down:
				y = options.arrowScroll;
				break;
			case key.spacebar: // (+ shift)
				shift = event.shiftKey ? 1 : -1;
				y = -shift * clientHeight * 0.9;
				break;
			case key.pageup:
				y = -clientHeight * 0.9;
				break;
			case key.pagedown:
				y = clientHeight * 0.9;
				break;
			case key.home:
				y = -elem.scrollTop;
				break;
			case key.end:
				var damt = elem.scrollHeight - elem.scrollTop - clientHeight;
				y = (damt > 0) ? damt+10 : 0;
				break;
			case key.left:
				x = -options.arrowScroll;
				break;
			case key.right:
				x = options.arrowScroll;
				break;
			default:
				return true; // a key we don't care about
		}

		scrollArray(elem, x, y);
		event.preventDefault();
		scheduleClearCache();
	}

	/**
	 * Mousedown event only for updating activeElement
	 */
	function mousedown(event) {
		activeElement = event.target;
	}


	/***********************************************
	 * OVERFLOW
	 ***********************************************/

	var uniqueID = (function () {
		var i = 0;
		return function (el) {
			return el.uniqueID || (el.uniqueID = i++);
		};
	})();

	var cache = {}; // cleared out after a scrolling session
	var clearCacheTimer;

//setInterval(function () { cache = {}; }, 10 * 1000);

	function scheduleClearCache() {
		clearTimeout(clearCacheTimer);
		clearCacheTimer = setInterval(function () { cache = {}; }, 1*1000);
	}

	function setCache(elems, overflowing) {
		for (var i = elems.length; i--;)
			cache[uniqueID(elems[i])] = overflowing;
		return overflowing;
	}

//  (body)                (root)
//         | hidden | visible | scroll |  auto  |
// hidden  |   no   |    no   |   YES  |   YES  |
// visible |   no   |   YES   |   YES  |   YES  |
// scroll  |   no   |   YES   |   YES  |   YES  |
// auto    |   no   |   YES   |   YES  |   YES  |

	function overflowingAncestor(el) {
		var elems = [];
		var body = document.body;
		var rootScrollHeight = root.scrollHeight;
		do {
			var cached = cache[uniqueID(el)];
			if (cached) {
				return setCache(elems, cached);
			}
			elems.push(el);
			if (rootScrollHeight === el.scrollHeight) {
				var topOverflowsNotHidden = overflowNotHidden(root) && overflowNotHidden(body);
				var isOverflowCSS = topOverflowsNotHidden || overflowAutoOrScroll(root);
				if (isFrame && isContentOverflowing(root) ||
					!isFrame && isOverflowCSS) {
					return setCache(elems, getScrollRoot());
				}
			} else if (isContentOverflowing(el) && overflowAutoOrScroll(el)) {
				return setCache(elems, el);
			}
		} while (el = el.parentElement);
	}

	function isContentOverflowing(el) {
		return (el.clientHeight + 10 < el.scrollHeight);
	}

// typically for <body> and <html>
	function overflowNotHidden(el) {
		var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
		return (overflow !== 'hidden');
	}

// for all other elements
	function overflowAutoOrScroll(el) {
		var overflow = getComputedStyle(el, '').getPropertyValue('overflow-y');
		return (overflow === 'scroll' || overflow === 'auto');
	}


	/***********************************************
	 * HELPERS
	 ***********************************************/

	function addEvent(type, fn) {
		window.addEventListener(type, fn, false);
	}

	function removeEvent(type, fn) {
		window.removeEventListener(type, fn, false);
	}

	function isNodeName(el, tag) {
		return (el.nodeName||'').toLowerCase() === tag.toLowerCase();
	}

	function directionCheck(x, y) {
		x = (x > 0) ? 1 : -1;
		y = (y > 0) ? 1 : -1;
		if (direction.x !== x || direction.y !== y) {
			direction.x = x;
			direction.y = y;
			que = [];
			lastScroll = 0;
		}
	}

	var deltaBufferTimer;

	if (window.localStorage && localStorage.SS_deltaBuffer) {
		deltaBuffer = localStorage.SS_deltaBuffer.split(',');
	}

	function isTouchpad(deltaY) {
		if (!deltaY) return;
		if (!deltaBuffer.length) {
			deltaBuffer = [deltaY, deltaY, deltaY];
		}
		deltaY = Math.abs(deltaY);
		deltaBuffer.push(deltaY);
		deltaBuffer.shift();
		clearTimeout(deltaBufferTimer);
		deltaBufferTimer = setTimeout(function () {
			if (window.localStorage) {
				localStorage.SS_deltaBuffer = deltaBuffer.join(',');
			}
		}, 1000);
		return !allDeltasDivisableBy(120) && !allDeltasDivisableBy(100);
	}

	function isDivisible(n, divisor) {
		return (Math.floor(n / divisor) == n / divisor);
	}

	function allDeltasDivisableBy(divisor) {
		return (isDivisible(deltaBuffer[0], divisor) &&
		isDivisible(deltaBuffer[1], divisor) &&
		isDivisible(deltaBuffer[2], divisor));
	}

	function isInsideYoutubeVideo(event) {
		var elem = event.target;
		var isControl = false;
		if (document.URL.indexOf ('www.youtube.com/watch') != -1) {
			do {
				isControl = (elem.classList &&
				elem.classList.contains('html5-video-controls'));
				if (isControl) break;
			} while (elem = elem.parentNode);
		}
		return isControl;
	}

	var requestFrame = (function () {
		return (window.requestAnimationFrame       ||
		window.webkitRequestAnimationFrame ||
		window.mozRequestAnimationFrame    ||
		function (callback, element, delay) {
			window.setTimeout(callback, delay || (1000/60));
		});
	})();

	var MutationObserver = (window.MutationObserver ||
	window.WebKitMutationObserver ||
	window.MozMutationObserver);

	var getScrollRoot = (function() {
		var SCROLL_ROOT;
		return function() {
			if (!SCROLL_ROOT) {
				var dummy = document.createElement('div');
				dummy.style.cssText = 'height:10000px;width:1px;';
				document.body.appendChild(dummy);
				var bodyScrollTop  = document.body.scrollTop;
				var docElScrollTop = document.documentElement.scrollTop;
				window.scrollBy(0, 3);
				if (document.body.scrollTop != bodyScrollTop)
					(SCROLL_ROOT = document.body);
				else
					(SCROLL_ROOT = document.documentElement);
				window.scrollBy(0, -3);
				document.body.removeChild(dummy);
			}
			return SCROLL_ROOT;
		};
	})();


	/***********************************************
	 * PULSE (by Michael Herf)
	 ***********************************************/

	/**
	 * Viscous fluid with a pulse for part and decay for the rest.
	 * - Applies a fixed force over an interval (a damped acceleration), and
	 * - Lets the exponential bleed away the velocity over a longer interval
	 * - Michael Herf, http://stereopsis.com/stopping/
	 */
	function pulse_(x) {
		var val, start, expx;
		// test
		x = x * options.pulseScale;
		if (x < 1) { // acceleartion
			val = x - (1 - Math.exp(-x));
		} else {     // tail
			// the previous animation ended here:
			start = Math.exp(-1);
			// simple viscous drag
			x -= 1;
			expx = 1 - Math.exp(-x);
			val = start + (expx * (1 - start));
		}
		return val * options.pulseNormalize;
	}

	function pulse(x) {
		if (x >= 1) return 1;
		if (x <= 0) return 0;

		if (options.pulseNormalize == 1) {
			options.pulseNormalize /= pulse_(1);
		}
		return pulse_(x);
	}


	/***********************************************
	 * FIRST RUN
	 ***********************************************/

	var userAgent = window.navigator.userAgent;
	var isEdge    = /Edge/.test(userAgent); // thank you MS
	var isChrome  = /chrome/i.test(userAgent) && !isEdge;
	var isSafari  = /safari/i.test(userAgent) && !isEdge;
	var isMobile  = /mobile/i.test(userAgent);
	var isIEWin7  = /Windows NT 6.1/i.test(userAgent) && /rv:11/i.test(userAgent);
	var isEnabledForBrowser = (isChrome || isSafari || isIEWin7) && !isMobile;

	var wheelEvent;
	if ('onwheel' in document.createElement('div'))
		wheelEvent = 'wheel';
	else if ('onmousewheel' in document.createElement('div'))
		wheelEvent = 'mousewheel';

	if (wheelEvent && isEnabledForBrowser) {
		addEvent(wheelEvent, wheel);
		addEvent('mousedown', mousedown);
		addEvent('load', init);
	}


	/***********************************************
	 * PUBLIC INTERFACE
	 ***********************************************/

	function SmoothScroll(optionsToSet) {
		for (var key in optionsToSet)
			if (defaultOptions.hasOwnProperty(key))
				options[key] = optionsToSet[key];
	}
	SmoothScroll.destroy = cleanup;

	if (window.SmoothScrollOptions) // async API
		SmoothScroll(window.SmoothScrollOptions);

	if (typeof define === 'function' && define.amd)
		define(function() {
			return SmoothScroll;
		});
	else if ('object' == typeof exports)
		module.exports = SmoothScroll;
	else
		window.SmoothScroll = SmoothScroll;

})();
