function isArray (arg) {
	return Object.prototype.toString.call(arg).slice(8, -1) === 'Array';
}
/**
 * [chunk Array chunk iteration]
 * @param  { array  }  arr     [array to iterate]
 * @param  {function}  fn      [function to process array]
 * @param  {integer }  intv    [iteration interval milliseconds]
 * @param  {function}  success [function for async invocation when iteration complete]
 * @param  { object }  ctx     [context to invoke the function]
 *
 * usage:
 * chunk([1, 2, 3], function(e, i, a){
 *     console.log(i, e);
 * });
 */
function chunk(arr, fn, intv, success, ctx) {
	var copy, idx;
	if(!isArray(arr)) {
		console.log('Error: chunk - parameter 1 must be an array.');
		return;
	}
	if(!arr.length) {
		console.log('Warning: chunk - parameter 1 must be an array with at least one element.');
		return;
	}
	if(typeof fn !== 'function') {
		console.log('Error: chunk - parameter 2 must be a function.');
		return;
	}
	if(intv != null && (typeof intv !== 'number' || intv !== ~~intv)) {
		console.log('Error: chunk - parameter 3 must be an integer.');
		return;
	}
	if(success != null && typeof success !== 'function') {
		console.log('Error: chunk - parameter 4 must be a function for async invocation.');
		return;
	}
	if(ctx != null && typeof ctx !== 'object') {
		console.log('Error: chunk - parameter 5 must be an object.');
		return;
	}
	copy = arr.slice();
	idx = 0;
	intv = intv || 100;
	intv = intv > 0 ? ~~intv : 0; // get the positve integer interval milliseconds
	setTimeout(function f() {
		var itm = copy.shift();
		fn.call(ctx, itm, idx++, arr);
		if(copy.length > 0) {
			setTimeout(f, intv);
		} else if(typeof success === 'function') {
			// used for async invocation
			success();
		}
	}, 0);
}
/**
 * [throttle Function throttle]
 * @param  {function}  fn     [invoked function to throttle]
 * @param  {integer }  intv   [interval milliseconds]
 * @param  { array  }  params [parameters passed into the invoked function]
 * @param  { object }  ctx    [context to invoke the function]
 *
 * usage:
 * document.addEventListener('click', function(e){
 *     throttle(doSomething, 1000, e.target.nodeType);
 *     throttle(doSomething, 1000, e.target.childNodes);
 *     throttle(doSomething, 1000, [null]);
 * });
 * function doSomething(data){
 *     console.log(data);
 * }
 */
function throttle (fn, intv, params, ctx) {
	if(typeof fn !== 'function') {
		console.log('Error: throttle - parameter 1 must be a function.');
		return;
	}
	if(intv != null && typeof intv !== 'number') {
		console.log('Error: throttle - parameter 2 must be a number.');
		return;
	}
	if(ctx != null && typeof ctx !== 'object') {
		console.log('Error: throttle - parameter 4 must be an object.');
		return;
	}
	clearTimeout(fn.tId);
	intv = intv || 100;
	if(params == null) params = [];
	if(!isArray(params)) {
		if(typeof params.length === 'number' && typeof params[0] !== 'undefined') {
			params = Array.prototype.slice.call(params);
		} else {
			params = [params];
		}
	}
	fn.tId = setTimeout(function () {
		fn.apply(ctx, params);
	}, intv);
}
/**
 * [advDuff Iterator for large data]
 * @param  { object } arrLike [object has length property to access elements]
 * @param  {Function} fn      [function to process arrLike]
 *
 * usage:
 * advDuf([1,2,3], (e, i) => {console.log(i, e);});
 * advDuff(document.querySelectorAll('li'), (e, i) => {console.log(i, e);});
 */
function advDuff (arrLike, fn) {
	var len, trav, left, i;
	if(typeof arrLike !== 'object' || typeof arrLike.length !== 'number') {
		console.log('Error: advDuff - parameter 1 must be an array or array-like object');
		return;
	}
	if(!arrLike.length) return;
	if(typeof fn !== 'function') {
		console.log('Error: advDuff - parameter 2 must be a function to process the parameter 1.');
		return;
	}
	len = arrLike.length;
	trav = ~~(len / 8); // get the integer turns of iteration
	left = len % 8; // get the remainder of turns
	i = 0;
	if(left> 0) {
		do{
			fn(arrLike[i++], i, arrLike);
		} while (--left>0);
	}
	if(!trav) return;
	do{
		fn(arrLike[i++], i, arrLike);
		fn(arrLike[i++], i, arrLike);
		fn(arrLike[i++], i, arrLike);
		fn(arrLike[i++], i, arrLike);
		fn(arrLike[i++], i, arrLike);
		fn(arrLike[i++], i, arrLike);
		fn(arrLike[i++], i, arrLike);
		fn(arrLike[i++], i, arrLike);
	} while (--trav > 0);
}

/**
 * [trampoline To optimize the recursion]
 * @param  {function} fn [function to recurse]
 * @return {  any   }    [final result of recursive function]
 *
 * usage:
 * function fibonacci (n, ac1 = 1, ac2 = 1) {
 *     return n <= 1 ? ac2 : () => fibonacci(n - 1, ac2, ac1 + ac2);
 * }
 * trampoline(fibonacci(1000, 1, 1));
 */
function trampoline (fn) {
	while (fn && fn instanceof Function) {
		fn = fn();
	}
	return fn;
}
/**
 * [tail Tail-call optimizing function]
 * @param  {function} fn [function to recurse]
 * @return {  any   }    [final result of recursive function]
 *
 * usage:
 * var fibonacci = tail((n, ac1 = 1, ac2 = 1) => {
 *    return n <= 1 ? ac2 : fibonacci(n - 1, ac2, ac1 + ac2);
 * });
 * fibonacci(1000, 1, 1);
 */
function tail (fn) {
	var value,
		active = false,
		stack = [];
	return function () {
		stack.push(arguments);
		if(!active) {
		 	active = true;
		 	while (stack.length) {
		 		value = fn.apply(this, stack.shift());
		 	}
		 	active = false;
		 	return value;
		}
	};
}