var Molang = {
	parse: function (input, variables) {

		if (typeof input === 'number') {
			return isNaN(input) ? 0 : input
		}
		if (typeof input !== 'string') return 0;
		input = input.toLowerCase();
		if (input.substr(-1) === ';') input = input.substr(0, input.length-1)

		if (Molang.cache_enabled && Molang._cached[input]) {
			var expression = Molang._cached[input];
		} else {
			var expression = new Molang.expression(input)
			if (Molang.cache_enabled) {
				Molang._cached[input] = expression;
			}
		}
		var value = Molang.calculate(expression, variables)
		return value;
	},
	global_variables: {},
	cache_enabled: true,
	use_radians: false,
	expression: function(string) {
		this.original_input = string;
		this.data = Molang._itS(string);
	},
	calculate: function(expression, variables) {
		Molang._current_variables = variables||0;
		return Molang._itEx(expression.data);
	},
	Comp: function(operator, a, b, c) {
		this.operator = operator;
		this.a = Molang._itS(a);
		if (b !== undefined) this.b = Molang._itS(b);
		if (c !== undefined) this.c = Molang._itS(c);
	},

	_cached: {},
	_itS: function(s) {
		//Iterates through string, returns float, string or comp;
		if (!s) return 0;
		var M = Molang;
		if (!isNaN(s)) return parseFloat(s);

		s = s.replace(/\s/g, '')

		while (M._canTrimParentheses(s)) {
			s = s.substr(1, s.length-2);
		}

		//ternary
		var split = Molang._splitString(s, '?');
		if (split) {
			let ab = Molang._splitString(split[1], ':');
			if (ab && ab.length) {
				return new Molang.Comp(10, split[0], ab[0], ab[1]);
			}
		}

		//2 part operators
		var comp = (
			M._testOp(s, '&&', 11) ||
			M._testOp(s, '||', 12) ||
			M._testOp(s, '<', 13) ||
			M._testOp(s, '<=', 14) ||
			M._testOp(s, '>', 15) ||
			M._testOp(s, '>=', 16) ||
			M._testOp(s, '==', 17) ||
			M._testOp(s, '!=', 18) ||

			M._testOp(s, '+', 1, true) ||
			M._testMinus(s, '-', 2, true) ||
			M._testOp(s, '*', 3) ||
			M._testOp(s, '/', 4)
		)
		if (comp) return comp;

		if (s.substr(0, 5) === 'math.') {
			if (s.substr(0, 7) === 'math.pi') {
				return Math.PI
			}
			let begin = s.search(/\(/);
			let operator = s.substr(5, begin-5);
			let inner = s.substr(begin+1, s.length-begin-2)
			let params = Molang._splitString(inner, ',')||[inner];
			if (params.length > 1) {
				var last2 = Molang._splitString(params[1], ',')
				if (last2 && last2.length > 1) {
					params[1] = last2[0];
					params[2] = last2[1];
				}
			}

			switch (operator) {
				case 'abs':
					return new M.Comp(20, params[0]);
					break;
				case 'sin':
					return new M.Comp(21, params[0]);
					break;
				case 'cos':
					return new M.Comp(22, params[0]);
					break;
				case 'exp':
					return new M.Comp(23, params[0]);
					break;
				case 'ln':
					return new M.Comp(24, params[0]);
					break;
				case 'pow':
					return new M.Comp(25, params[0], params[1]);
					break;
				case 'sqrt':
					return new M.Comp(26, params[0]);
					break;
				case 'random':
					return new M.Comp(27, params[0], params[1]);
					break;
				case 'ceil':
					return new M.Comp(28, params[0]);
					break;
				case 'round':
					return new M.Comp(29, params[0]);
					break;
				case 'trunc':
					return new M.Comp(30, params[0]);
					break;
				case 'floor':
					return new M.Comp(31, params[0]);
					break;
				case 'mod':
					return new M.Comp(32, params[0], params[1]);
					break;
				case 'min':
					return new M.Comp(33, params[0], params[1]);
					break;
				case 'max':
					return new M.Comp(34, params[0], params[1]);
					break;
				case 'clamp':
					return new M.Comp(35, params[0], params[1], params[2]);
					break;
				case 'lerp':
					return new M.Comp(36, params[0], params[1], params[2]);
					break;
				case 'lerprotate':
					return new M.Comp(37, params[0], params[1], params[2]);
					break;
			}
		}
		split = s.match(/[a-zA-Z0-9._]{2,}/g)
		if (split && split.length === 1) {
			return s;
		}
		return 0;
	},
	_canTrimParentheses: function(s) {
		if (s.substr(0, 1) === '(' && s.substr(-1) === ')') {
			let level = 0;
			for (var i = 0; i < s.length-1; i++) {
				switch (s[i]) {
					case '(': level++; break;
					case ')': level--; break;
				}
				if (level == 0) return false;
			}
			return true;
		}
	},
	_testOp: function(s, char, operator, inverse) {

		var split = Molang._splitString(s, char, inverse)
		if (split) {
			return new Molang.Comp(operator, split[0], split[1])
		}
	},
	_testMinus: function(s, char, operator, inverse) {

		var split = Molang._splitString(s, char, inverse)
		if (split) {
			if (split[0].length === 0) {
				return new Molang.Comp(operator, 0, split[1])
			} else if ('+*/<>=|&?:'.includes(split[0].substr(-1)) === false) {
				return new Molang.Comp(operator, split[0], split[1])
			}
		}
	},
	_splitString: function(s, char, inverse) {
		var direction = inverse ? -1 : 1;
		var i = inverse ? s.length-1 : 0;
		var level = 0;
		var is_string = typeof char === 'string'
		while (inverse ? i >= 0 : i < s.length) {
			let c = s[i];
			if (c === '(') {
				level += direction;
			} else if (c === ')') {
				level -= direction;
			} else if (level === 0) {
				var letters = s.substr(i, char.length)
				if (is_string && letters === char) {
					return [
						s.substr(0, i),
						s.substr(i+char.length)
					];
				} else if (!is_string) {
					for (var xi = 0; xi < char.length; xi++) {
						if (char[xi] === letters) {
							return [
								s.substr(0, i),
								s.substr(i+char[xi].length)
							];
						}
					}
				}
			}
			i += direction;
		}
	},
	get _angleFactor() {
		return this.use_radians ? 1 : (Math.PI/180);
	},
	_itEx: function(T) {
		if (typeof T === 'number') {
			return T
		} else if (typeof T === 'string') {
			var val = Molang._current_variables[T]
			if (val === undefined) {
				val = Molang.global_variables[T];
			}
			if (val === undefined && typeof Molang.variableHandler === 'function') {
				val = Molang.variableHandler(T)
			}
			if (typeof val === 'string') {
				val = Molang.parse(val, Molang._current_variables)
			}
			return val||0;
		} else if (T instanceof Molang.Comp) {
			var M = Molang;
			switch (T.operator) {
				//Basic
				case 1:
					return M._itEx(T.a) + M._itEx(T.b);
					break;
				case 2:
					return M._itEx(T.a) - M._itEx(T.b);
					break;
				case 3:
					return M._itEx(T.a) * M._itEx(T.b);
					break;
				case 4:
					return M._itEx(T.a) / M._itEx(T.b);
					break;
				//Boolean
				case 10:
					return M._itEx(T.a) ? M._itEx(T.b) : M._itEx(T.c);
					break;
				case 11:
					return M._itEx(T.a) && M._itEx(T.b) ? 1 : 0;
					break;
				case 12:
					return M._itEx(T.a) || M._itEx(T.b) ? 1 : 0;
					break;
				case 13:
					return M._itEx(T.a) < M._itEx(T.b) ? 1 : 0;
					break;
				case 14:
					return M._itEx(T.a) <= M._itEx(T.b) ? 1 : 0;
					break;
				case 15:
					return M._itEx(T.a) > M._itEx(T.b) ? 1 : 0;
					break;
				case 16:
					return M._itEx(T.a) >= M._itEx(T.b) ? 1 : 0;
					break;
				case 17:
					return M._itEx(T.a) === M._itEx(T.b) ? 1 : 0;
					break;
				case 18:
					return M._itEx(T.a) !== M._itEx(T.b) ? 1 : 0;
					break;
				//Math
				case 20:
					return Math.abs(M._itEx(T.a));
					break;
				case 21:
					return Math.sin(M._itEx(T.a) * Molang._angleFactor);
					break;
				case 22:
					return Math.cos(M._itEx(T.a) * Molang._angleFactor);
					break;
				case 23:
					return Math.exp(M._itEx(T.a));
					break;
				case 24:
					return Math.log(M._itEx(T.a));
					break;
				case 25:
					return Math.pow(M._itEx(T.a), M._itEx(T.b));
					break;
				case 26:
					return Math.sqrt(M._itEx(T.a));
					break;
				case 27:
					return Molang._random(M._itEx(T.a), M._itEx(T.b), M._itEx(T.c));
					break;
				case 28:
					return Math.ceil(M._itEx(T.a));
					break;
				case 29:
					return Math.round(M._itEx(T.a));
					break;
				case 30:
					return Math.trunc(M._itEx(T.a));
					break;
				case 31:
					return Math.floor(M._itEx(T.a));
					break;
				case 32:
					return M._itEx(T.a) % M._itEx(T.b);
					break;
				case 33:
					return Math.min(M._itEx(T.a), M._itEx(T.b));
					break;
				case 34:
					return Math.max(M._itEx(T.a), M._itEx(T.b));
					break;
				case 35:
					return Molang._clamp(M._itEx(T.a), M._itEx(T.b), M._itEx(T.c));
					break;
				case 36:
					let n1 = M._itEx(T.a);
					return n1 + (M._itEx(T.b) - n1) * M._itEx(T.c);
					break;
				case 37:
					let a = (((M._itEx(T.a) + 180) % 360) +180) % 360
					let b = (((M._itEx(T.b) + 180) % 360) +180) % 360
					let d = b-a
					let i = M._itEx(T.c)
					if (Math.abs(d) > 180) {
						i *= -1
					}
					return a + v*i
					break;
			}
		}
	}
}

Molang._random = function(a, b) {
	return a + Math.random() * (b-a)
}
Molang._clamp = function(number, min, max) {
	if (number > max) number = max;
	if (number < min || isNaN(number)) number = min;
	return number;
}