Source: utility.js

/**
* @class
 * "Inside-out" implementation of Fisher-Yates shuffle
 */
function shuffleArray(source)
{
	if (!source.length)
		return [];

	let result = [source[0]];
	for (let i = 1; i < source.length; ++i)
	{
		let j = randIntInclusive(0, i);
		result[i] = result[j];
		result[j] = source[i];
	}
	return result;
}

/**
 * Generates each permutation of the given array and runs the callback function on it.
 * Uses the given clone function on each item of the array.
 * Creating arrays with all permutations of the given array has a bad memory footprint.
 * Algorithm by B. R. Heap. Changes the input array.
 */
function heapsPermute(array, cloneFunc, callback)
{
	let c = new Array(array.length).fill(0);

	callback(array.map(cloneFunc));

	let i = 0;
	while (i < array.length)
	{
		if (c[i] < i)
		{
			let swapIndex = i % 2 ? c[i] : 0;
			let swapValue = cloneFunc(array[swapIndex]);
			array[swapIndex] = array[i];
			array[i] = swapValue;

			callback(array.map(cloneFunc));

			++c[i];
			i = 0;
		}
		else
		{
			c[i] = 0;
			++i;
		}
	}
}

/**
 * Compare two variables recursively. This compares better than a quick
 * JSON.stringify check since we also check undefineds, Sets and the like.
 *
 * @param first - Any javascript instance.
 * @param second - Any javascript instance.
 * @return {boolean} Whether first and second are equal.
 */
function deepCompare(first, second)
{
	// If the value of either variable is empty we can instantly compare
	// them and check for equality.
	if (first === null || first === undefined ||
	    second === null || second === undefined)
		return first === second;

	// Make sure both variables have the same type.
	if (first.constructor !== second.constructor)
		return false;

	// We know that the variables are of the same type so all we need to do is
	// check what type one of the objects is, and then compare them.

	// Check numbers seperately. Make sure this works with NaN, Infinity etc.
	if (typeof first == "number")
		return uneval(first) === uneval(second);

	// Functions and RegExps must have the same reference to be equal.
	if (first instanceof Function || first instanceof RegExp)
		return first === second;

	// If we are comparing simple objects, we can just compare them.
	if (first === second || first.valueOf() === second.valueOf())
		return true;

	// Dates would have equal valueOf if they are equal.
	if (first instanceof Date)
		return false;

	// From now we will assume we have some kind of objects so that
	// we can do a recursive check of the keys and values.
	if (!(first instanceof Object) || !(second instanceof Object))
		return false;

	// We cannot iterate over Sets, so collapse them to Arrays.
	if (first instanceof Set)
	{
		first = Array.from(first);
		second = Array.from(second);
	}

	// Objects need the same number of keys in order to be equal.
	const firstKeys = Object.keys(first);
	if (firstKeys.length != Object.keys(second).length)
		return false;

	// Make sure that all the object keys on this level of the object are the same.
	// Finally, we pass all the values of our of each object recursively to
	// make sure everything matches.
	return Object.keys(second).every(i => firstKeys.includes(i)) &&
	       firstKeys.every(i => deepCompare(first[i], second[i]));
}

/**
 * Removes prefixing path from a path or filename, leaving just the file's name (with extension)
 *
 * ie. a/b/c/file.ext -> file.ext
 */
function basename(path)
{
	return path.split("/").pop();
}

/**
 * Returns the directories of a given path.
 *
 * ie. a/b/c/file.ext -> a/b/c
 */
function dirname(path)
{
	return path.split("/").slice(0, -1).join("/");
}

/**
 * Returns names of files found in the given directory, stripping the directory path and file extension.
 */
function listFiles(path, extension, recurse)
{
	return Engine.ListDirectoryFiles(path, "*" + extension, recurse).map(filename => filename.slice(path.length, -extension.length));
}