(function () {
	window.DEBUG = document.cookie.indexOf('wmdebug') > -1;

	window.$ = document.querySelector.bind(document);
	window.$all = (selector) => [...document.querySelectorAll(selector)];
	var alphaNum = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';

	/**
	 * The following 2 functions are supposed to find the next/previous sibling until the
	 * passed `selector` is matched. But for now it actually finds the next/previous
	 * element of `this` element in the list of `selector` matched element inside `this`'s
	 * parent.
	 * @param  Selector that should match for next siblings
	 * @return element Next element that mathes `selector`
	 */
	Node.prototype.nextUntil = function (selector) {
		const siblings = [...this.parentNode.querySelectorAll(selector)];
		const index = siblings.indexOf(this);
		return siblings[index + 1];
	};

	/*
	 * @param  Selector that should match for next siblings
	 * @return element Next element that mathes `selector`
	 */
	Node.prototype.previousUntil = function (selector) {
		const siblings = [...this.parentNode.querySelectorAll(selector)];
		const index = siblings.indexOf(this);
		return siblings[index - 1];
	};

	// https://github.com/substack/semver-compare/blob/master/index.js
	function semverCompare(a, b) {
		var pa = a.split('.');
		var pb = b.split('.');
		for (var i = 0; i < 3; i++) {
			var na = Number(pa[i]);
			var nb = Number(pb[i]);
			if (na > nb) { return 1; }
			if (nb > na) { return -1; }
			if (!isNaN(na) && isNaN(nb)) { return 1; }
			if (isNaN(na) && !isNaN(nb)) { return -1; }
		}
		return 0;
	}

	function generateRandomId(len) {
		var length = len || 10;
		var id = '';
		for (var i = length; i--;) {
			id += alphaNum[~~(Math.random() * alphaNum.length)];
		}
		return id;
	}

	function onButtonClick(btn, listener) {
		btn.addEventListener('click', function buttonClickListener(e) {
			listener(e);
			return false;
		});
	}

	function log() {
		if (window.DEBUG) {
			console.log(...arguments);
		}
	}

	/**
	 * Adds timed limit on the loops found in the passed code.
	 * Contributed by Ariya Hidayat!
	 * @param code {string}	Code to be protected from infinite loops.
	 */
	function addInfiniteLoopProtection(code) {
		var loopId = 1;
		var patches = [];
		var varPrefix = '_wmloopvar';
		var varStr = 'var %d = Date.now();\n'
		var checkStr = '\nif (Date.now() - %d > 1000) { window.top.previewException(new Error("Infinite loop")); break;}\n'

		esprima.parse(code, { tolerant: true, range: true, jsx: true }, function (node) {
			switch (node.type) {
			case 'DoWhileStatement':
			case 'ForStatement':
			case 'ForInStatement':
			case 'ForOfStatement':
			case 'WhileStatement':
				var start = 1 + node.body.range[0];
				var end = node.body.range[1];
				var prolog = checkStr.replace('%d', varPrefix + loopId);
				var epilog = '';

				if (node.body.type !== 'BlockStatement') {
					// `while(1) doThat()` becomes `while(1) {doThat()}`
					prolog = '{' + prolog;
					epilog = '}';
					--start;
				}

				patches.push({ pos: start, str: prolog });
				patches.push({ pos: end, str: epilog });
				patches.push({ pos: node.range[0], str: varStr.replace('%d', varPrefix + loopId) });
				++loopId;
				break;

			default:
				break;
			}
		});

		/* eslint-disable no-param-reassign */
		patches.sort(function (a, b) { return b.pos - a.pos }).forEach(function (patch) {
			code = code.slice(0, patch.pos) + patch.str + code.slice(patch.pos);
		});

		/* eslint-disable no-param-reassign */
		return code;
	}

	function getHumanDate(timestamp) {
		var d = new Date(timestamp);
		var retVal = d.getDate() + ' '
			+ [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'][d.getMonth()] + ' '
			+ d.getFullYear();
		return retVal;
	}

	// create a one-time event
	function once(node, type, callback) {
		// create event
		node.addEventListener(type, function(e) {
			// remove event
			e.target.removeEventListener(type, arguments.callee);
			// call handler
			return callback(e);
		});

	}

	window.utils = {
		semverCompare: semverCompare,
		generateRandomId: generateRandomId,
		onButtonClick: onButtonClick,
		addInfiniteLoopProtection: addInfiniteLoopProtection,
		getHumanDate: getHumanDate,
		log: log,
		once: once
	};
})();
