var fs = require('fs');

var CSVParser = require('csv-parse');
var hamming = require('compute-hamming');
var unionfind = require('union-find');


function createTree(fileName, callback){
	var data = fs.readFileSync(fileName, 'utf8');
	var Tree, levels;
	CSVParser(data, {delimiter: '\t'}, function(err, output){
		if (!err){
		  var data_object = {};
		  data_object.lvs = [];
		  data_object.ids = [];

		  data_object.profiles = output.map(function(x){
		  	data_object.ids.push(x[0]);
		  	x = x.slice(1);
		  	data_object.lvs.push(x.map(function(){return 0;}));
		  	return x;});

		  data_object.labels = data_object.profiles[0];
		  data_object.profiles = data_object.profiles.slice(1);
		  data_object.ids = data_object.ids.splice(1);

		  data_object.lvs = data_object.profiles.map(function(x){
								    return x.map(function(x){return 0;});
								  });

		}
		else{
			console.log('An error has occurred loading the file');
			return false;
		}

		computeHamming(data_object, function(data){ 
			goeBURST(data, function(Tree, levels){
				data.edges = Tree; //Replacement for MST edges
				data.levels = levels;
				callback(data);
			}); 

		});
	});
}


function computeHamming(data, callback){

	profileLength = Object.keys(data.profiles).length;
	data.edges = [];
	
	for (var i = 0; i < profileLength - 1; i++) {
		for (var j = i + 1; j < profileLength; j++){
			diff = hamming(data.profiles[i], data.profiles[j]);
			if (diff == 0) continue;  //MUDAR fazer check for duplciates o inicio

			data.lvs[i][diff-1] += 1;
			data.lvs[j][diff-1] += 1;

			data.edges.push([i, j]);
		}
		
	}
	callback(data);

}

function goeBURST(data, callback){

	function edgeComparator(e, f) {

	    var elevel = hamming(data.profiles[e[0]], data.profiles[e[1]]);
	    var flevel = hamming(data.profiles[f[0]], data.profiles[f[1]]);
	    var n = data.lvs[e[0]].length;

	    if (elevel != flevel) return elevel - flevel; // First is by profile difference (level)

	    for (var l = 0; l < n; l++) {
	      maxe = Math.max(data.lvs[e[0]][l], data.lvs[e[1]][l]);
	      maxf = Math.max(data.lvs[f[0]][l], data.lvs[f[1]][l]);


	      if (maxe != maxf) return maxf - maxe; //Then if we have ties on the level, we pass to the number of LVs

	      mine = Math.min(data.lvs[e[0]][l], data.lvs[e[1]][l]);
	      minf = Math.min(data.lvs[f[0]][l], data.lvs[f[1]][l]);


	      if (mine != minf) return minf - mine;
	    }

	    maxe = Math.max(e[0], e[1]); //The last chance is by the profile order
	    maxf = Math.max(f[0], f[1]);

	    if (maxe != maxf) return maxe - maxf;

	    mine = Math.min(e[0], e[1]);
	    minf = Math.min(f[0], f[1]);

	    return minf - mine;
  	}


	data.edges.sort(edgeComparator);

	//Because edges are sorted by level and LVs, the first ones are the ones used to construct the MST
	//Kruskal algorithm. Selects the lightest edges. In this case they are the first ones on the edge array

	tree = [];
	levels = [];
	var sets = new unionfind(data.profiles.length);  //Each set is the index corresponding to each profile
	for (var k = 0; k < data.edges.length && tree.length < data.profiles.length - 1; k++) {
	    if (sets.find(data.edges[k][0]) != sets.find(data.edges[k][1])) { //If the root is different
	      sets.link(data.edges[k][0], data.edges[k][1]); //We link those sets
	      diff = hamming(data.profiles[data.edges[k][0]], data.profiles[data.edges[k][1]]);
	      tree.push(data.edges[k]); //And add to the tree
	      levels.push(diff);
	    }
	  }

	callback(tree, levels);
}


module.exports = function (path, callback){

	var ToExport = {};

 	createTree(path, function(data){
 		callback(data);
 	});

}