/*
 * For C# functions that use "params string[] rest" type arguments (where you can either pass in an array,
 * or keep passing in additional function arguments that will be placed into an array)
 * this function can be called, and given the "arguments" value as the first param, and the number of 'base'
 * function arguments as the second param, and it will return the aggregated array
 */
function restArgs(args, numArgs)
{
	var ret = [];
	if (args.length == numArgs + 1 && Array.isArray(args[numArgs]))
	{
		ret = args[numArgs];
	}
	else
	{
		ret = Array.apply(null, args).slice(numArgs);
	}
	
	return ret;
}

Object.defineProperty(Object.prototype, 'addRange', {
    enumerable: false,
    value: function(more) {
    	for (var key of Object.keys(more))
    	{
    		this[key] = more[key];
    	}
	}
});

Object.defineProperty(Object.prototype, 'containsKey', {
    enumerable: false,
    value: function(key) {
    	if (this[key] !== undefined)
    	{
    		return true;
    	}
    	return false;
	}
});

// handy dandy function that SHOULD ALREADY EXIST IN THE ARRAY object, but doesn't because JAVASCRIPT HATES DEVELOPERS
Object.defineProperty(Array.prototype, 'remove', {
    enumerable: false,
    value: function(elem) {	
    	var index = this.indexOf(elem);
		if (index != -1)
		{
			this.splice(index, 1);
			return true;
		}
		return false;
	}
});

Object.defineProperty(Array.prototype, 'contains', {
    enumerable: false,
    value: function(elem) { return this.indexOf(elem) != -1; }
});

Object.defineProperty(Array.prototype, 'any', {
    enumerable: false,
    value: function(func) { return this.some(func || function(x) { return x }); }
});

Object.defineProperty(Array.prototype, 'getRandomElement', {
    enumerable: false,
    value: function() { return this.length == 0 ? null : this[Math.floor(Engine.Utils.randomDouble() * this.length)]; }
});

Object.defineProperty(Array.prototype, 'last', {
    enumerable: false,
    value: function(func) { return this[this.length - 1]; }
});

Object.defineProperty(Array.prototype, 'firstOrDefault', {
    enumerable: false,
    value: function(filter /*optional*/) {
		if (filter !== undefined && typeof filter === "function")
		{
			for (var i = 0; i < this.length; i++)
			{
				if (filter(this[i])) return this[i];				
			}
			return null;
		}
		return this.length == 0 ? null : this[0];
	}
});

Object.defineProperty(Array.prototype, 'lastOrDefault', {
    enumerable: false,
    value: function(filter /*optional*/) {
		if (filter !== undefined && typeof filter === "function")
		{
			for (var i = this.length - 1; i >= 0; i--)
			{
				if (filter(this[i])) return this[i];				
			}
			return null;
		}
		return this.length == 0 ? null : this[this.length - 1];
	}
});

Object.defineProperty(Array.prototype, 'flatten', {
    enumerable: false,
    value: function() {
		var ret = [];
		for (var i of this)
		{
			if (Array.isArray(i))
			{
				for (var j of i)
				{
					ret.push(j);
				}
			}
			else
			{
				ret.push(i);
			}
		}
		return ret;
	}
});

Object.defineProperty(Array.prototype, 'average', {
    enumerable: false,
    value: function(func) {
		if (this.length == 0) return undefined;
		var sum = 0;
		for (var i in this)
		{
			sum += (func === undefined) ? this[i] : func(this[i]);
		}
		return sum / this.length;
	}
});

Object.defineProperty(Array.prototype, 'all', {
    enumerable: false,
    value: function(func) { 
    	for (var i = 0; i < this.length; i++)
    	{
    		if (!func(this[i])) return false;
    	}
    	return true;
    }
});

Object.defineProperty(Array.prototype, 'none', {
    enumerable: false,
    value: function(func) { 
    	for (var i = 0; i < this.length; i++)
    	{
    		if (func(this[i])) return false;
    	}
    	return true;
    }
});

Object.defineProperty(Array.prototype, 'max', {
    enumerable: false,
    value: function(func) {
    	var max = Number.MIN_VALUE;
    	if (this.length == 0) return 0;
    	if (func === undefined) func = (v) => { return v;}
    	for (var i = 0; i < this.length; i++)
    	{
    		var val = func(this[i]);
    		if (val > max) max = val;
    	}
    	return max;
    }
});

Object.defineProperty(Array.prototype, 'maxItem', {
    enumerable: false,
    value: function(func) {
    	var max = Number.MIN_VALUE;
    	var maxIndex = 0;
    	if (this.length == 0) return null;
    	if (func === undefined) func = (v) => { return v;}
    	for (var i = 0; i < this.length; i++)
    	{
    		var val = func(this[i]);
    		if (val > max) 
			{
    			maxIndex = i;
    			max = val;
    		}
    	}
    	return this[maxIndex];
    }
});

Object.defineProperty(Array.prototype, 'min', {
    enumerable: false,
    value: function(func)
    {
    	var min = Number.MAX_VALUE;
    	if (this.length == 0) return 0;
    	if (func === undefined) func = (v) => { return v;}
    	for (var i = 0; i < this.length; i++)
    	{
    		var val = func(this[i]);
    		if (val < min) min = val;
    	}
    	return min;
    }
});

Object.defineProperty(Array.prototype, 'sum', {
    enumerable: false,
    value: function(func) {
    	var sum = 0;
    	for (var i = 0; i < this.length; i++)
    	{
    		sum += func(this[i]);
    	}
    	return sum;
    }
});


Object.defineProperty(Array.prototype, 'addRange', {
    enumerable: false,
    value: function(more) {
		if (Array.isArray(more))
		{
			for (var i = 0; i < more.length; i++)
			{
				this.push(more[i]);
			}
		}
		else if (more instanceof Set)
		{
			this.addRange(more.values);
		}
    }
});

Object.defineProperty(Array.prototype, 'removeRange', {
    enumerable: false,
    value: function(remove) {
		if (Array.isArray(remove))
		{
			for (var i = 0; i < remove.length; i++)
			{
				var index = this.indexOf(remove[i]);
				if (index != -1)
				{
					this.splice(index, 1);
				}
			}
		}
		else if (remove instanceof Set)
		{
			this.removeRange(remove.values);
		}
    }
});


String.prototype.replaceAll = function(search, replacement) {
    var target = this;
    return target.split(search).join(replacement);
};

String.prototype.effectKey = function() {
    var target = this;
    return target.split(",")[0];
};

String.prototype.toCamelCase = function(firstUpper) {
    var target = this;
    if (firstUpper === undefined) firstUpper = true;
    var result = target.replace(/(?:^\w|[A-Z]|\b\w)/g, function(letter, index) { return index == 0 && !firstUpper ? letter.toLowerCase() : letter.toUpperCase(); }).replace(/\s+/g, '');
    return result;
};

String.prototype.toUnderlineCase = function(search, replacement) {
    var target = this;
    var toUnderlineCaseReg = /[^A-Z0-9][A-Z0-9]/g;
    return target.replace(toUnderlineCaseReg, match => [match.slice(0, 1), "_", match.slice(1)].join('')).toLowerCase();
};

Object.defineProperty(Array.prototype, 'distinct', {
    enumerable: false,
    value: function()
	{
		return this.filter((value, index, self) => self.indexOf(value) === index);
	}
});

Object.defineProperty(Array.prototype, 'sortNum', {
    enumerable: false,
    value: function()
	{
		return this.sort((a, b) => a - b);
	}
});

Number.isNumeric = function(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
}

Math.clamp = function(value, min, max) {
  return Math.min(Math.max(value, min), max);
};

function isNumeric(n) {
  return !isNaN(parseFloat(n)) && isFinite(n);
}

var CircularQueueStatics = {};
CircularQueueStatics.SizeMode = {
	Add:0,
	Mult:1
}
class CircularQueue
{
	constructor(startSize, sizeMode, increase)
	{
		if (startSize === undefined) startSize = 0;
		if (sizeMode === undefined)
		{
			sizeMode = CircularQueueStatics.SizeMode.Mult;
			increase = 2;
		}
		if (increase === undefined)
		{
			increase = 2;
			if (sizeMode == CircularQueueStatics.SizeMode.Add) increase = 50;
		}
		startSize = Math.max(0, startSize);
		
		this.startSize = this.startSize;
		
		this.backing = new Array(startSize);
		
		this.sizeMode = sizeMode;
		this.increase = increase;
		
		this.start = 0;
		this.end = 0;
	}
	
	get length() { return (this.end + this.backing.length - this.start) % this.backing.length; }
	
	enqueue(v)
	{
		if (this.length == this.backing.length)
		{
			var newSize = this.backing.length + 1;
			if (this.sizeMode == CircularQueueStatics.SizeMode.Add)
			{
				newSize = Math.max(newSize, this.backing.length + this.increase);
			}
			if (this.sizeMode == CircularQueueStatics.SizeMode.Mult)
			{
				newSize = Math.max(newSize, Math.ceil(this.backing.length * this.increase));
			}
			
			var sizeIncrease = newSize - this.backing.length;
			
			var newBacking = new Array(newSize);
			if (this.end >= this.start)
			{
				for (var i = this.start; i < this.end; i++)
				{
					newBacking[i] = this.backing[i];
				}
			}
			else
			{
				for (var i = this.start; i < this.backing.length; i++)
				{
					newBacking[i + sizeIncrease] = this.backing[i];
				}
				for (var i = 0; i < this.end; i++)
				{
					newBacking[i] = this.backing[i];
				}
			}
			
			this.backing = newBacking;
		}
		
		this.backing[this.end] = v;
		
		this.end = (this.end + 1) % this.backing.length;
		
	}
	
	dequeue()
	{
		if (this.length == 0) return undefined;
		var ret = this.backing[this.start];
		this.start = (this.start + 1) % this.backing.length;
		return ret;
	}
	
	pop()
	{
		if (this.length == 0) return undefined;
		var ret = this.backing[this.end];
		this.end = (this.end - 1 + this.backing.length) % this.backing.length;
		return ret;
	}
	
	clear(resetBackingSize)
	{
		if (resetBackingSize === undefined) resetBackingSize = false;
		
		if (typeof resetBackingSize == "boolean")
		{
			if (resetBackingSize)
			{
				this.backing = new Array(this.startSize);
			}
		}
		if (typeof resetBackingSize == "number")
		{
			this.startSize = Math.max(0, resetBackingSize);
			this.backing = new Array(this.startSize);
		}
		
		this.start = 0;
		this.end = 0;
	}
	
	get(index)
	{
		if (index < 0) return undefined;
		if (index >= this.length) return undefined;
		
		index = (this.start + index) % this.backing.length;
		
		return this.backing[index];
	}
}