(function($){
"use strict";
// SmoothScroll v1.2.1
// Licensed under the terms of the MIT license.

// People involved
//  - Balazs Galambosi (maintainer)  
//  - Patrick Brunner  (original idea)
//  - Michael Herf     (Pulse Algorithm)

// Scroll Variables (tweakable)
var defaultOptions = {

	// Scrolling Core
	frameRate        : 60, // [Hz]
	animationTime    : 700, // [px]
	stepSize         : 30, // [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   : false,  // option
	arrowScroll       : 50,     // [px]

	// Other
	touchpadSupport   : true,
	fixedBackground   : false, 
	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 deltaBuffer = [ 120, 120, 120 ];

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


/***********************************************
 * SETTINGS
 ***********************************************/

//chrome.storage.sync.get(defaultOptions, function (syncedOptions) {
//
//    options = syncedOptions;
//
//    // it seems that sometimes settings come late
//    // and we need to test again for excluded pages
//    initTest();
//});


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

/**
 * Tests if smooth scrolling is allowed. Shuts down everything if not.
 */
function initTest() {

	var disableKeyboard = false; 

	// disable keys for google reader (spacebar conflict)
	if (document.URL.indexOf("google.com/reader/view") > -1) {
		disableKeyboard = true;
	}

	// disable everything if the page is blacklisted
	if (options.excluded) {
		var domains = options.excluded.split(/[,\n] ?/);
		domains.push("mail.google.com"); // exclude Gmail for now
		for (var i = domains.length; i--;) {
			if (document.URL.indexOf(domains[i]) > -1) {
				observer && observer.disconnect();
				removeEvent("mousewheel", wheel);
				disableKeyboard = true;
				isExcluded = true;
				break;
			}
		}
	}
	
	// disable keyboard support if anything above requested it
	if (disableKeyboard) {
		removeEvent("keydown", keydown);
	}

	if (options.keyboardSupport && !disableKeyboard) {
		addEvent("keydown", keydown);
	}
}

/**
 * Sets up scrolls array, determines if frames are involved.
 */
function init() {
  
	if (!document.body) return;

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

	if($('body').hasClass('no-smooth-scroll'))
		return;
	
	// check compat mode for root element
	root = (document.compatMode.indexOf('CSS') >= 0) ? html : body;
	activeElement = body;
	
	initTest();
	initDone = true;

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

	/**
	 * 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)) {

		// DOMChange (throttle): fix height
		var pending = false;
		var refresh = function () {
			if (!pending && html.scrollHeight != document.height) {
				pending = true; // add a new pending action
				setTimeout(function () {
					html.style.height = document.height + 'px';
					pending = false;
				}, 500); // act rarely to stay fast
			}
		};
		// html.style.height = 'auto';
		setTimeout(refresh, 10);

		var config = {
			attributes: true, 
			childList: true, 
			characterData: false 
		};

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

		// clearfix
		if (root.offsetHeight <= windowHeight) {
			var underlay = document.createElement("div"); 	
			underlay.style.clear = "both";
			body.appendChild(underlay);
		}
	}
	
	// gmail performance fix
	if (document.URL.indexOf("mail.google.com") > -1) {
		var s = document.createElement("style");
		s.innerHTML = ".iu { visibility: hidden }";
		(document.getElementsByTagName("head")[0] || html).appendChild(s);
	}
	// facebook better home timeline performance
	// all the HTML resized images make rendering CPU intensive
	else if (document.URL.indexOf("www.facebook.com") > -1) {
		var home_stream = document.getElementById("home_stream");
		home_stream && (home_stream.style.webkitTransform = "translateZ(0)");
	} 
	// disable fixed background
	if (!options.fixedBackground && !isExcluded) {
		body.style.backgroundAttachment = "scroll";
		html.style.backgroundAttachment = "scroll";
	}
}


/************************************************
 * SCROLLING 
 ************************************************/
 
var que = [];
var pending = false;
var lastScroll = +new Date;

/**
 * Pushes scroll actions to the scrolling queue.
 */
function scrollArray(elem, left, top, delay) {
	
	delay || (delay = 1000);
	directionCheck(left, top);

	if (options.accelerationMax != 1) {
		var now = +new Date;
		var elapsed = now - lastScroll;
		if (elapsed < options.accelerationDelta) {
			var factor = (1 + (30 / elapsed)) / 2;
			if (factor > 1) {
				factor = Math.min(factor, options.accelerationMax);
				left *= factor;
				top  *= factor;
			}
		}
		lastScroll = +new Date;
	}          
	
	// 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: +new Date
	});
		
	// don't act if there's a pending queue
	if (pending) {
		return;
	}  

	var scrollWindow = (elem === document.body);
	
	var step = function (time) {
		var now = +new Date;
		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, (delay / 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    
	if (!overflowing || event.defaultPrevented ||
		isNodeName(activeElement, "embed") ||
	   (isNodeName(target, "embed") && /\.pdf/i.test(target.src))) {
		return true;
	}

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

	// 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();
}

/**
 * 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);
	
	// do nothing if user is editing text
	// or using a modifier key (except shift)
	// or in a dropdown
	if ( /input|textarea|select|embed/i.test(target.nodeName) ||
		 target.isContentEditable || 
		 event.defaultPrevented   ||
		 modifier ) {
	  return true;
	}
	// spacebar should trigger button press
	if (isNodeName(target, "button") &&
		event.keyCode === key.spacebar) {
	  return true;
	}
	
	var shift, x = 0, y = 0;
	var elem = overflowingAncestor(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();
}

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


/***********************************************
 * OVERFLOW
 ***********************************************/
 
var cache = {}; // cleared out every once in while
setInterval(function () { cache = {}; }, 10 * 1000);

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

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

function overflowingAncestor(el) {
	var elems = [];
	var overflow;
	var rootScrollHeight = root.scrollHeight;
	do {
		var cached = cache[uniqueID(el)];
		if (cached) {
			return setCache(elems, cached);
		}
		elems.push(el);
		if (rootScrollHeight === el.scrollHeight) {
			if (!isFrame || root.clientHeight + 10 < rootScrollHeight) {
				return setCache(elems, document.body); // scrolling root in WebKit
			}
		} else if (el.clientHeight + 10 < el.scrollHeight) {
			overflow = getComputedStyle(el, "").getPropertyValue("overflow-y");
			if (overflow === "scroll" || overflow === "auto") {
				return setCache(elems, el);
			}
		}
	} while (el = el.parentNode);
}


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

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

function removeEvent(type, fn, bubble) {
	window.removeEventListener(type, fn, (bubble||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;

function isTouchpad(deltaY) {
	if (!deltaY) return;
	deltaY = Math.abs(deltaY)
	deltaBuffer.push(deltaY);
	deltaBuffer.shift();
	clearTimeout(deltaBufferTimer);
	deltaBufferTimer = setTimeout(function () {
		chrome.storage.local.set({ deltaBuffer: deltaBuffer });
	}, 1000);
	var allEquals    = (deltaBuffer[0] == deltaBuffer[1] && 
						deltaBuffer[1] == deltaBuffer[2]);
	var allDivisable = (isDivisible(deltaBuffer[0], 120) &&
						isDivisible(deltaBuffer[1], 120) &&
						isDivisible(deltaBuffer[2], 120));
	return !(allEquals || allDivisable);
} 

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

//chrome.storage.local.get('deltaBuffer', function (stored) {
//    if (stored.deltaBuffer) {
//        deltaBuffer = stored.deltaBuffer;
//    }
//});

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

var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;  


/***********************************************
 * PULSE
 ***********************************************/
 
/**
 * 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);
}

if(!$('body').hasClass('no-smooth-scroll')) {
	if(!mobile && !ipad) {
		addEvent("mousedown", mousedown);
		addEvent("mousewheel", wheel);
		addEvent("load", init);
	}
}

})(jQuery);