// Amira Abdel-Rahman
// (c) Massachusetts Institute of Technology 2019

////////////utils/////////////////////
function map (value, x1, y1, x2, y2) {
	return (value - x1) * (y2 - x2) / (y1 - x1) + x2;
}

function enforceBounds(x) {
	if (x < 0) {
		return 0;
	} else if (x > 1){
		return 1;
	} else {
		return x;
	}
}

function interpolateLinearly(x, values) {
	// Split values into four lists
	var x_values = [];
	var r_values = [];
	var g_values = [];
	var b_values = [];
	for (i in values) {
		x_values.push(values[i][0]);
		r_values.push(values[i][1][0]);
		g_values.push(values[i][1][1]);
		b_values.push(values[i][1][2]);
	}
	var i = 1;
	while (x_values[i] < x) {
		i = i+1;
	}
	i = i-1;
	var width = Math.abs(x_values[i] - x_values[i+1]);
	var scaling_factor = (x - x_values[i]) / width;
	// Get the new color values though interpolation
	var r = r_values[i] + scaling_factor * (r_values[i+1] - r_values[i])
	var g = g_values[i] + scaling_factor * (g_values[i+1] - g_values[i])
	var b = b_values[i] + scaling_factor * (b_values[i+1] - b_values[i])
	return [enforceBounds(r), enforceBounds(g), enforceBounds(b)];
}

function getColor(viz,stress){
    var val=map(stress,viz.minStress,viz.maxStress,1.0,0.0);
    color=interpolateLinearly(val, viz.colorMaps[viz.colorMap]);
    return new THREE.Color(color[0],color[1],color[2]).getHex();

}

function getColorRGB(viz,stress){
    var val=map(stress,viz.minStress,viz.maxStress,1.0,0.0);
    color=interpolateLinearly(val, viz.colorMaps[viz.colorMap]);
    return new THREE.Color(color[0],color[1],color[2]);

}

function toThreeVec(point){
	return new THREE.Vector3(point.x,point.y,point.z);
}

function toPosition(array){
	return { x:array[0] ,y:array[1],z:array[2]};

}

function toTf3D(point){
	return tf.tensor([point.x,point.y,point.z]);

}

function toPos(point){
	return { x:point.x ,y:point.y,z:point.z};

}

function tf_delete(tensor,remove_indices,axis){
	var unstacked=tf.unstack(tensor,axis);
	var newTensor=[];
	for(var i=0;i<unstacked.length;i++){
		if(!remove_indices.includes(i)){
			newTensor.push(unstacked[i]);
		}
	}
	return tf.stack(newTensor,axis);
}

function add(p1,p2){
    return {x:p1.x+p2.x,y:p1.y+p2.y,z:p1.z+p2.z};
}

//////////nodes///////////////

function nodeAt(setup,pos){
	var tolerance=1e-6;
	return setup.nodes.find(node => 
		(node.position.x>pos.x-tolerance && node.position.x<pos.x+tolerance) && 
		(node.position.y>pos.y-tolerance && node.position.y<pos.y+tolerance) && 
		(node.position.z>pos.z-tolerance && node.position.z<pos.z+tolerance)
		);
	return setup.nodes.find(node => 
		(node.position.x==pos.x) && 
		(node.position.y== pos.y) && 
		(node.position.z== pos.z)
		);
    return setup.nodes.filter(node => node.position.x==pos.x && node.position.y== pos.y && node.position.z== pos.z);
}
function getNodebyName(setup,name){
    return setup.nodes.find(node => node.name===name);
}
function edgeNeeded(setup,source,target){
    var e=setup.edges.find(edge => (edge.source===source && edge.target===target) || (edge.source===target && edge.target===source));
    var b=(typeof e === 'undefined');
    return b;
}

//////////////
function updateDisplacement(X){
	var count=0;
	for(var i=0;i<setup.nodes.length;i++){
		if(!setup.nodes[i].restrained_degrees_of_freedom[0]){
			setup.nodes[i].displacement.x =X[count++];
		}
		if(!setup.nodes[i].restrained_degrees_of_freedom[1]){
			setup.nodes[i].displacement.y =X[count++];
		}
		if(!setup.nodes[i].restrained_degrees_of_freedom[2]){
			setup.nodes[i].displacement.z =X[count++];
		}
		if(!setup.bar){
			if(!setup.nodes[i].restrained_degrees_of_freedom[3]){
				setup.nodes[i].angle.x =X[count++];
			}
			if(!setup.nodes[i].restrained_degrees_of_freedom[4]){
				setup.nodes[i].angle.y =X[count++];
			}
			if(!setup.nodes[i].restrained_degrees_of_freedom[5]){
				setup.nodes[i].angle.z =X[count++];
			}
		}
	}
}

function updateStresses(S){
	
	setup.viz.minStress=Math.min(...S)*1.1;
	setup.viz.maxStress=Math.max(...S)*1.1;

	var count=0;
	for(var ii=0;ii<setup.edges.length;ii++){
		var element=setup.edges[ii];
		element.stress=S[ii];
	}
	if(!node){
		three.colorEdges(); //todo check!!
	}
	
}

//////////inverse///////////////
// Lower Upper Solver
function lusolve(A, b, update) {
	var lu = ludcmp(A, update)
	if (lu === undefined){
		console.log("Singular Matrix!")
		return // Singular Matrix!
	} 
	return lubksb(lu, b, update)
}
 
// Lower Upper Decomposition
function ludcmp(A, update) {
	// A is a matrix that we want to decompose into Lower and Upper matrices.
	var d = true;
	var n = A.length;
	// var n= A.shape[0];
	var idx = new Array(n) // Output vector with row permutations from partial pivoting
	var vv = new Array(n)  // Scaling information
 
	for (var i=0; i<n; i++) {
		var max = 0
		for (var j=0; j<n; j++) {
			var temp = Math.abs(A[i][j])
			if (temp > max) max = temp
		}
		if (max == 0) return // Singular Matrix!
		vv[i] = 1 / max // Scaling
	}
 
	if (!update) { // make a copy of A 
		var Acpy = new Array(n)
		for (var i=0; i<n; i++) {		
			var Ai = A[i] 
			Acpyi = new Array(Ai.length)
			for (j=0; j<Ai.length; j+=1) Acpyi[j] = Ai[j]
			Acpy[i] = Acpyi
		}
		A = Acpy
	}
 
	var tiny = 1e-20 // in case pivot element is zero
	for (var i=0; ; i++) {
		for (var j=0; j<i; j++) {
			var sum = A[j][i]
			for (var k=0; k<j; k++) sum -= A[j][k] * A[k][i];
			A[j][i] = sum
		}
		var jmax = 0
		var max = 0;
		for (var j=i; j<n; j++) {
			var sum = A[j][i]
			for (var k=0; k<i; k++) sum -= A[j][k] * A[k][i];
			A[j][i] = sum
			var temp = vv[j] * Math.abs(sum)
			if (temp >= max) {
				max = temp
				jmax = j
			}
		}
		if (i <= jmax) {
			for (var j=0; j<n; j++) {
				var temp = A[jmax][j]
				A[jmax][j] = A[i][j]
				A[i][j] = temp
			}
			d = !d;
			vv[jmax] = vv[i]
		}
		idx[i] = jmax;
		if (i == n-1) break;
		var temp = A[i][i]
		if (temp == 0) A[i][i] = temp = tiny
		temp = 1 / temp
		for (var j=i+1; j<n; j++) A[j][i] *= temp
	}
	return {A:A, idx:idx, d:d}
}
 
// Lower Upper Back Substitution
function lubksb(lu, b, update) {
	// solves the set of n linear equations A*x = b.
	// lu is the object containing A, idx and d as determined by the routine ludcmp.
	var A = lu.A
	var idx = lu.idx
	var n = idx.length
 
	if (!update) { // make a copy of b
		var bcpy = new Array(n) 
		for (var i=0; i<b.length; i+=1) bcpy[i] = b[i]
		b = bcpy
	}
 
	for (var ii=-1, i=0; i<n; i++) {
		var ix = idx[i]
		var sum = b[ix]
		b[ix] = b[i]
		if (ii > -1)
			for (var j=ii; j<i; j++) sum -= A[i][j] * b[j]
		else if (sum)
			ii = i
		b[i] = sum
	}
	for (var i=n-1; i>=0; i--) {
		var sum = b[i]
		for (var j=i+1; j<n; j++) sum -= A[i][j] * b[j]
		b[i] = sum / A[i][i]
	}
	return b // solution vector x
}


///save json 
function saveJSON(data, filename){

	if(!data) {
		console.error('No data')
		return;
	}

	if(!filename) filename = 'console.json'

	if(typeof data === "object"){
		data = JSON.stringify(data, undefined, 4)
	}

	var blob = new Blob([data], {type: 'text/json'}),
		e    = document.createEvent('MouseEvents'),
		a    = document.createElement('a')

	a.download = filename
	a.href = window.URL.createObjectURL(blob)
	a.dataset.downloadurl =  ['text/json', a.download, a.href].join(':')
	e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
	a.dispatchEvent(e)
}