enum MATH_OPERATOR {
	add,      //  0
	subtract, //  1
	multiply, //  2
	divide,   //  3
	power,    //  4
	root,     //  5
		
	sin,      //  6
	cos,      //  7
	tan,      //  8
		
	modulo,   //  9
		
	floor,    // 10
	ceiling,  // 11
	round,    // 12
	
	lerp,     // 13
	abs,      // 14
	
	clamp,    // 15
	snap,     // 16
	fract,    // 17
	
	map,
	
	length,
}

global.node_math_keys     = [	"add", "subtract", "multiply", "divide", "power", "root", 
								"+", "-", "*", "/", "^", 
                        		"sin", "cos", "tan", "modulo", "round", 
                        		"ceiling", "floor", "lerp", "abs", "fract", 
                        		"clamp", "snap", "map" ];
              
global.node_math_keys_map = [	MATH_OPERATOR.add,     MATH_OPERATOR.subtract, MATH_OPERATOR.multiply, MATH_OPERATOR.divide, MATH_OPERATOR.power, MATH_OPERATOR.root, 
								MATH_OPERATOR.add,     MATH_OPERATOR.subtract, MATH_OPERATOR.multiply, MATH_OPERATOR.divide, MATH_OPERATOR.power, 
                        		MATH_OPERATOR.sin,     MATH_OPERATOR.cos,      MATH_OPERATOR.tan,      MATH_OPERATOR.modulo, MATH_OPERATOR.round, 
                        		MATH_OPERATOR.ceiling, MATH_OPERATOR.floor,    MATH_OPERATOR.lerp,     MATH_OPERATOR.abs,    MATH_OPERATOR.fract, 
                        		MATH_OPERATOR.clamp,   MATH_OPERATOR.snap,     MATH_OPERATOR.map, ];

global.node_math_names    = [  /* 0 -  9*/ "Add", "Subtract", "Multiply", "Divide", "Power", "Root", "Sin", "Cos", "Tan", "Modulo", 
							   /*10 - 20*/ "Floor", "Ceil", "Round", "Lerp", "Abs", "Clamp", "Snap", "Fract", "Map", ];

global.node_math_scroll   = array_create_ext(array_length(global.node_math_names), function(i) /*=>*/ {return new scrollItem(global.node_math_names[i], s_node_math_operators, i)});

function Node_create_Math(_x, _y, _group = noone, _param = {}) {
	var query = struct_try_get(_param, "query", "");
	var node  = new Node_Math(_x, _y, _group).skipDefault();

	var ind = array_find(global.node_math_keys, query);
	if(ind != -1) node.inputs[0].setValue(global.node_math_keys_map[ind]);

	return node;
}

function Node_Math(_x, _y, _group = noone) : Node(_x, _y, _group) constructor {
	name		= "Math";
	color		= COLORS.node_blend_number;
	setDimension(96, 48);
	
	newInput(0, nodeValue_Enum_Scroll("Type", self, 0, global.node_math_scroll))
		.rejectArray();
	
	newInput(1, nodeValue_Float("a", self, 0))
		.setVisible(true, true);
		
	newInput(2, nodeValue_Float("b", self, 0))
		.setVisible(true, true);
		
	newInput(3, nodeValue_Bool("Degree angle", self, true));
	
	newInput(4, nodeValue_Bool("To integer", self, false));
	
	newInput(5, nodeValue_Float("Amount", self, 0));
	
	newInput(6, nodeValue_Vec2("From", self, [ 0, 1 ]));
	
	newInput(7, nodeValue_Vec2("To",   self, [ 0, 1 ]));
	
	input_display_list = [ 0, 
		["Values",   false], 1, 2, 5, 6, 7, 
		["Settings", false], 3, 4, 
	];
		
	newOutput(0, nodeValue_Output("Result", self, VALUE_TYPE.float, 0));
	
	use_mod = 0;
	use_deg = false;
	
	static onValueUpdate = function(index = 0) {
		if(index != 0) return;
		
		var _type = inputs[0].getValue();
		setDisplayName(global.node_math_names[_type]);
	}
	
	static _eval = function(a, b, c = 0) {
		switch(use_mod) {
			case MATH_OPERATOR.add :		return a + b;    
			case MATH_OPERATOR.subtract :	return a - b;
			case MATH_OPERATOR.multiply :	return a * b;
			case MATH_OPERATOR.divide :		return b == 0? 0 : a / b;
				
			case MATH_OPERATOR.power :		return power(a, b);
			case MATH_OPERATOR.root :		return b == 0? 0 : power(a, 1 / b);
			
			case MATH_OPERATOR.sin :		return (use_deg? dsin(a) : sin(a)) * b;
			case MATH_OPERATOR.cos :		return (use_deg? dcos(a) : cos(a)) * b;
			case MATH_OPERATOR.tan :		return (use_deg? dtan(a) : tan(a)) * b;
			case MATH_OPERATOR.modulo :		return safe_mod(a, b);
			
			case MATH_OPERATOR.floor :		return floor(a);
			case MATH_OPERATOR.ceiling :	return ceil(a);
			case MATH_OPERATOR.round :		return round(a);
			
			case MATH_OPERATOR.lerp :		return lerp(a, b, c);
			case MATH_OPERATOR.abs :		return abs(a);
			
			case MATH_OPERATOR.clamp :		return clamp(a, b, c);
			case MATH_OPERATOR.snap :		return value_snap(a, b);
			case MATH_OPERATOR.fract :		return frac(a);
		}
		return 0;
	}
	
	static _evalMap = function(a, fr, to) {
		var _frMin = fr[0];
		var _frMax = fr[1];
		var _toMin = to[0];
		var _toMax = to[1];
		
		return lerp(_toMin, _toMax, (a - _frMin) / (_frMax - _frMin));
	}
	
	function evalArray(a, b, c = 0) {
		var _as = is_array(a);
		var _bs = is_array(b);
		var _cs = is_array(c);
		
		if(!_as && !_bs && !_cs)
			return _eval(a, b, c);
		
		if(!_as) a = [ a ];
		if(!_bs) b = [ b ];
		if(!_cs) c = [ c ];
		
		var al = array_length(a);
		var bl = array_length(b);
		var cl = array_length(c);
		
		var amo = max(al, bl, cl);
		var val = array_create(amo);
		
		for( var i = 0; i < amo; i++ ) 
			val[i] = evalArray( 
				array_safe_get(a, i,, ARRAY_OVERFLOW.loop), 
				array_safe_get(b, i,, ARRAY_OVERFLOW.loop),
				array_safe_get(c, i,, ARRAY_OVERFLOW.loop),
			);
		
		return val;
	}
	
	__mode   = noone;
	doUpdate = doUpdateLite;
	static update = function(frame = CURRENT_FRAME) { 
		
		use_mod = inputs[0].getValue();
		use_deg = inputs[3].getValue();
		
		var a  = inputs[1].getValue();
		var b  = inputs[2].getValue();
		var c  = inputs[5].getValue();
		var val;
		
		fr = inputs[6].getValue();
		to = inputs[7].getValue();
		
		switch(use_mod) {
			case MATH_OPERATOR.map : 
				if(is_array(a)) val = array_map_ext(a, function(v) /*=>*/ {return _evalMap(v, fr, to)});
				else            val = _evalMap(a, fr, to); 
				break;
			
			default :
				val = evalArray(a, b, c);
				break;
		}
		
		outputs[0].setValue(val);
		
		if(__mode == use_mod) return;
		__mode = use_mod;
	
		inputs[2].setVisible(false, false);
		inputs[3].setVisible(false, false);
		inputs[5].setVisible(false, false);
		inputs[6].setVisible(false, false);
		inputs[7].setVisible(false, false);
		
		switch(use_mod) {
			case MATH_OPERATOR.sin :
			case MATH_OPERATOR.cos :
			case MATH_OPERATOR.tan :
				inputs[3].setVisible(true);
				break;
				
			case MATH_OPERATOR.map :
				inputs[6].setVisible(true, true);
				inputs[7].setVisible(true, true);
				break;
		}
		
		switch(use_mod) {
			case MATH_OPERATOR.round :
			case MATH_OPERATOR.floor :
			case MATH_OPERATOR.ceiling :
				inputs[4].setVisible(true);
				
				var int = getInputData(4);
				if(int) outputs[0].setType(VALUE_TYPE.integer);
				else	outputs[0].setType(VALUE_TYPE.float);
				break;
				
			default:
				inputs[4].setVisible(false);
				
				outputs[0].setType(VALUE_TYPE.float);
				break;
		}
		
		switch(use_mod) {
			case MATH_OPERATOR.add :
			case MATH_OPERATOR.subtract :
			case MATH_OPERATOR.multiply :
			case MATH_OPERATOR.divide :
			case MATH_OPERATOR.power :
			case MATH_OPERATOR.root :	
			case MATH_OPERATOR.modulo :	
				inputs[2].name = "b";
				
				inputs[2].setVisible(true, true);
				break;
				
			case MATH_OPERATOR.sin :
			case MATH_OPERATOR.cos :
			case MATH_OPERATOR.tan :
				inputs[2].name = "Amplitude";
				
				inputs[2].setVisible(true, true);
				break;
				
			case MATH_OPERATOR.lerp :
				inputs[2].name = "To";
				inputs[5].name = "Amount";
				
				inputs[2].setVisible(true, true);
				inputs[5].setVisible(true, true);
				break;
				
			case MATH_OPERATOR.clamp :
				inputs[2].name = "Min";
				inputs[5].name = "Max";
				
				inputs[2].setVisible(true, true);
				inputs[5].setVisible(true, true);
				break;
				
			case MATH_OPERATOR.snap :
				inputs[2].name = "Snap";
				
				inputs[2].setVisible(true, true);
				break;
		}
		
	}
	
	static onDrawNode = function(xx, yy, _mx, _my, _s, _hover, _focus) {
		draw_set_text(f_sdf, fa_center, fa_center, COLORS._main_text);
		var str = "";
		var typ = getInputData(0);
		
		switch(typ) {
			case MATH_OPERATOR.add :		str = "+"; break;
			case MATH_OPERATOR.subtract :	str = "-"; break;
			case MATH_OPERATOR.multiply :	str = "*"; break;
			case MATH_OPERATOR.divide :		str = "/"; break;
			default: str = string_lower(global.node_math_names[typ]);
		}
		
		var bbox = drawGetBbox(xx, yy, _s);
		var ss	= string_scale(str, bbox.w, bbox.h);
		draw_text_transformed(bbox.xc, bbox.yc, str, ss * 0.8, ss * 0.8, 0);
	}
}