function defineHierarchies(cb) {
  //reportId - Global
  hierarchyLoadStatus.callback = cb; 
  
  getDiseaseHierarchy(reportId, hierarchyLoadStatus);
  getProcessHierarchy(reportId, hierarchyLoadStatus);
  getPathwayHierarchy(reportId, hierarchyLoadStatus);
  getInteractionHierarchy(reportId, hierarchyLoadStatus);

}

var hierarchyLoadStatus = new function() {

	this.update = function(comp) {
		if (comp == "diseases") {
			this.diseasesLoaded = true;
		} else if (comp == "pathways") {
			this.pathwaysLoaded = true;
		} else if (comp == "processes") {
			this.processesLoaded = true;
		} else if (comp == "interactions") {
			this.interactionsLoaded = true;
		}

		if(this.pathwaysLoaded && this.processesLoaded && this.diseasesLoaded && this.interactionsLoaded){

      updateHierarchies();
		  hierarchyLoadStatus.callback();      
		}
	};
};

function getDiseaseHierarchy(reportId, hierarchyLoadStatus) {
  var options = {
    method: 'get',
    async: true,
    callback : function updateDiseaseHierarchy() {
      var diseaseHierarchyJson = JSON.parse(this.responseText);
	    var node = [];

    	for (var i = 0; i < diseaseHierarchyJson.length; i++) {
    		var hierNode = diseaseHierarchyJson[i];
    		node[i] = new HierarchyNode(hierNode.name, hierNode.id, hierNode.index, hierNode.parentIndex, hierNode.childIndex, hierNode.displayable);
    	}
    	
    	hierarchies[0] = new TopicHierarchy(node, "Diseases", "disease", diseases, node.length, {h:36,s:85, v:98}, {h:0,s:10,v:-12});
    	hierarchyMap["disease"] = hierarchies[0];
    	hierarchyLoadStatus.update("diseases");
    }
  }
  
	var rand = new Date().getTime();
  x$(window).xhr('/bda/data/diseases/' + reportId +'/hierarchy' + '?' + rand, options);
  // $.getJSON('/bda/data/diseases/' + reportId +'/hierarchy' + '?' + rand, );
}

function getProcessHierarchy(reportId, hierarchyLoadStatus) {
  
  var options = {
    method: 'get',
    async: true,
    callback : function updateProcessHierarchy() {
      var processHierarchyJson = JSON.parse(this.responseText);
  		var node = [];
  		for (var i = 0; i < processHierarchyJson.length; i++) {
  			var hierNode = processHierarchyJson[i];
  			node[i] = new HierarchyNode(hierNode.name, hierNode.id, hierNode.index, hierNode.parentIndex, hierNode.childIndex, hierNode.displayable);
  		}
  		hierarchies[1] = new TopicHierarchy(node, "Processes", "biological process", bioProcesses, node.length,  {h:302,s:51, v:89}, {h:0,s:10,v:-12});
  		hierarchyMap["process"] = hierarchies[1];
  		hierarchyLoadStatus.update("processes");
  	}
  }
  
	var rand = new Date().getTime();
  x$(window).xhr('/bda/data/processes/' + reportId +'/hierarchy' + '?' + rand, options);
}

function getPathwayHierarchy(reportId, hierarchyLoadStatus) {
  
  var options = {
    method: 'get',
    async: true,
    callback : function updatePathwayHierarchy() {
      var pwyHierarchyJson = JSON.parse(this.responseText);
       var node = [];
       for (var i = 0; i < pwyHierarchyJson.length; i++) {
         var hierNode = pwyHierarchyJson[i];
         node[i] = new HierarchyNode(hierNode.name, hierNode.id, hierNode.index, hierNode.parentIndex, hierNode.childIndex, hierNode.displayable);
       }
       hierarchies[2] = new TopicHierarchy(node, "Pathways", "canonical pathway", pathways, node.length,  {h:81,s:82, v:81}, {h:0,s:10,v:-12});
       hierarchyMap["pathway"] = hierarchies[2];
       hierarchyLoadStatus.update("pathways");
  	}
  }
  
	var rand = new Date().getTime();
  x$(window).xhr('/bda/data/pathways/' + reportId +'/hierarchy' + '?' + rand, options);

}

function getInteractionHierarchy(reportId, hierarchyLoadStatus) {

  var options = {
    method: 'get',
    async: true,
    callback : function updateInteractionHierarchy() {
      var interactionHierarchyJson = JSON.parse(this.responseText);
  		var node = [];
  		for (var i = 0; i < interactionHierarchyJson.length; i++) {
  			var hierNode = interactionHierarchyJson[i];
  			node[i] = new HierarchyNode(hierNode.name, hierNode.id, hierNode.index, hierNode.parentIndex, hierNode.childIndex, hierNode.displayable);
  			var grp = interactionTypeGroups.getGroupByName(hierNode.name);
  			if(grp) {  // could be the root which doesn't have a group
  				node[i].topicObj = grp;
  				grp.hierarchyNodes = [node[i]];  // for wheel wedgies
  			}

  		}
  		hierarchies[3] = new TopicHierarchy(node, "Interactions", "molecular interaction", interactions, node.length, {h:203,s:70, v:90}, {h:0,s:10,v:-12});
  		hierarchyMap["interaction"] = hierarchies[3];
  		hierarchyLoadStatus.update("interactions");
  	}
  }
  
	var rand = new Date().getTime();
  x$(window).xhr('/bda/data/interactions/' + reportId +'/hierarchy' + '?' + rand, options);

}


function TopicHierarchy(hierarchyArray, topicName, topicLabel, topicArray, size, baseHSV, deltaHSV)  {
	this.root = hierarchyArray[0];
	this.name = topicName;
	this.topicArray = topicArray;
	this.size = size;

	this.baseHSV = {h:Math.min(1, Math.max(0, baseHSV.h/360)),
					s:Math.min(1,   Math.max(0, baseHSV.s/100)),
					v:Math.min(1,   Math.max(0, baseHSV.v/100))};
	this.deltaHSV = {h:deltaHSV.h/360,
					s:deltaHSV.s/100,
					v:deltaHSV.v/100};

	this.color = [];
	this.topic = this.name.toLowerCase().substring(0,this.name.length-1);
	if(this.topic.indexOf("rocess")>0) this.topic = "process";  // used to be processe
	this.topicLabel = topicLabel;
	this.nodes = hierarchyArray;
	this.idHash = {};

	for (var i=0; i<5; i++) {
		var h = Math.min(1, Math.max(0, this.baseHSV.h + i*this.deltaHSV.h));
		var s = Math.min(1, Math.max(0, this.baseHSV.s + i*this.deltaHSV.s));
		var v = Math.min(1, Math.max(0, this.baseHSV.v + i*this.deltaHSV.v));
		this.color[i] = "hsb(" + h + ", " + s + ", " + v + ")";
	}

	for (i=0; i<this.nodes.length; i++ ) {  // determine the level and stow the parent object
		var curLev = 0;
		var par = this.nodes[i].parentIndex;
		while(par != -1) {
			par = this.nodes[par].parentIndex;
			curLev++;
		}
		this.nodes[i].level = curLev;
		if (this.nodes[i].parentIndex != -1) {
			this.nodes[i].parentObj = this.nodes[this.nodes[i].parentIndex];
		} else {
			this.nodes[i].parentObj = null;
		}
		this.nodes[i].hierarchyParentObj = this;
		this.nodes[i].topic = this.topic;
		this.nodes[i].topicArray = this.topicArray;
		this.nodes[i].topicLabel = this.topicLabel;
		this.nodes[i].type = (this.nodes[i].children.length == 0 && this.nodes[i].parentObj != null)? "leaf": "category";

		// hash the id's for faster topic correlation
		
		id = this.nodes[i].id.toLowerCase().replace(/ing:/, "");  // strip off the leading ing:  (why?)
		this.idHash[id] = this.nodes[i];
			
	}

	this.deleteRimReferences = function() {
		for (var i=0; i<this.nodes.length; i++) {
			this.nodes[i].rimChip = null;
		}
	};
	
	this.getColorForLevel = function(level) {
		return this.color[level];
	};
	this.getIndexByID = function(idIn) {
		for (var i=0; i<this.nodes.length; i++) {
			if(this.nodes[i].id == idIn) return i;
		}
		return -1;
	};
	
	this.getByID = function(id) {
		for (var i=0; i<this.nodes.length; i++) {
			if(this.nodes[i].id == id) return this.nodes[i];
		}
		return null;

	};

	this.getGeneList = function() {
		return this.root.getGeneList();
	};
	
	this.setContext = function() {  // determine which nodes in this hierarchy are in the context

/*
		for (var i=0; i<this.nodes.length; i++) {
			this.nodes[i].inContext =  false;
		}
		for (i=0; i<this.nodes.length; i++) {
			if(this.nodes[i].type == "leaf") {
				var n = this.nodes[i];
				if(n.topicObj.inContext) {  // if a leaf is in then all its ancestors are in as well.
					n.inContext = true;
					var par = n.parentObj;
					while(par) {
						par.inContext = true;
						par = par.parentObj;
					} 
				}
			}
		}
*/
	}
	
	this.correlateTopicObj = function() {
		var errorCount = [];
		for (var i=0; i<this.topicArray.list.length; i++) { // over the topic
			this.topicArray.list[i].hierarchyNodes = [];
			var topicID = this.topicArray.list[i].id;
			for (var j=0; j<this.nodes.length; j++) {
				if(topicID == this.nodes[j].id) {
					var o = this.nodes[j];
					this.topicArray.list[i].hierarchyNodes.push(o);
					o.topicObj = this.topicArray.list[i];
				}
			}
			if(this.topicArray.list[i].hierarchyNodes.length ==0) errorCount.push(i);			
		}
	}
	
}



function HierarchyNode(name, id, index, parent, children, showOnRim) {
	this.name = name;
	this.id = id;
	this.index = index;
	this.parentIndex = parent;  // this is an index in the hierarchy for this node's parent
	this.isInData = true;  // hasn't been pruned yet...
	this.parentObj = null; // an object reference
	this.level = 0;
	this.hierarchyParentObj = null;  // i.e. pathways or diseases or ... defined in TopicHierarchy constructor
	this.topicObj = null;            // ie pathways.list[i]  -- the microarray data object
	this.children = children;
	this.type = (this.children.length > 0)? "category" : "leaf";
	this.numDescendants = 0;
	this.angles = {start:0, end:0};
	this.showOnRim = showOnRim;
	this.rimChip = null;
	this.topic = null;
	this.topicLabel = null;
	this.inContext = false;

	this.getId = function() {return this.id;};
	this.getTopic = function() {return this.topic;};
	this.getTopicLabel = function() {return this.topicLabel;};
	this.getName = function() {return this.name;};
	this.getIndex = function() {return this.index;};

	this.getDescendantCount = function() {
		var total = sumChildren(this);
		this.numDescendants = total;
		return this.numDescendants;
	};

	this.getRankQuartile = function() {
		return this.topicObj.getRankQuartile();
	};
	
// this should really be done and cached at startup... instead of every time a chip is moused over.
	
	this.getRank = function() {  // returns rank in [0,1] ... or average for a category
		if(this.topicArray != pathways && this.topicArray != diseases && this.topicArray != bioProcesses) {
			// cant rank it
			return {rank:null, min:null, max:null, topicMin:null, topicMax:null};
		}
		var tmp = getChildObjects(this);
		if(tmp.length) {
			var co = tmp;
		} else {
			co = [tmp];
		}
		var min = 9999999999;
		var max = -min;
		var rsum = 0;
		for(var i=0;i<co.length; i++) {
			var rank = co[i].topicObj.getRank();
			min = Math.min(min, rank);
			max = Math.max(max, rank);
			rsum+= rank;
		};
		var ave = rsum/co.length;  // note that ave, min and max are normalized, but topicMin and topicMax are not
		return {rank:ave, min:min, max:max, topicMin:this.topicArray.rankRange.min, topicMax:this.topicArray.rankRange.max};	
	};
	
	this.getRimObject = function() {
		// find the closest parent that has a rimchip
		if(this.rimChip) return this;
		var rc = null;
		var par = this;
		while (!rc && par) {
			par = par.parentObj;
			if(par) rc = par.rimChip;
		}
		return par;
	};
	
	this.getGenesInContext = function() {
		var gList = arrayIntersection(sumGenes(this), genes.indexList);
		var gl = [];
		for (var i=0; i<gList.length; i++) {
			if(genes.list[gList[i]].inContext) gl.push(gList[i])
		}
		return gl;
	};
	this.select = function() {
		var type = this.hierarchyParentObj.topic;
	};
	this.getGeneList = function() {
		var gList = arrayIntersection(sumGenes(this), genes.indexList);  // duplicates not yet removed... not yet implemented
		return gList;
	};
	this.getChildObjects = function(sortByRank) {
		var cList = getChildObjects(this);
		if(sortByRank != true) return cList;
		if(this.topicArray != pathways && this.topicArray != diseases && this.topicArray != bioProcesses) {
			return cList;
		} else {
			cList.sort(function(a,b) {  // for the big three - sort them by rank
				return a.getRank() - b.getRank();
			});
			return cList;
		}
	};
}

function getChildObjects(par) {
	var gl=new Array();
	if(par.type == "category") {
		for (var i=0; i<par.children.length; i++) {
			var child = par.hierarchyParentObj.nodes[par.children[i]];
			var curl = gl.concat(getChildObjects(child));
			gl = curl;
		}
	} else {
		if(par.topicObj) {  // topicObj is the biological object in the corresponding topic array, ie. pathways.list[i], etc
			gl = par;
		}
	}
	return gl;
}

function sumGenes(par) {
	var gl=new Array();
	if(par.type == "category") {
		for (var i=0; i<par.children.length; i++) {
			var child = par.hierarchyParentObj.nodes[par.children[i]];
				var curl = gl.concat(sumGenes(child));
				gl = curl;
		}
	} else {  // a leaf node .. get the topic and gene list
		if(par.topicObj) {
			var curl2 = gl.concat(par.topicObj.genes);
			gl = curl2;
		}
	}
	return gl;
}

function sumChildren(par) {  // version = 2
	var t=0;
	if(par.type == "category") {
		for (var i=0; i<par.children.length; i++) {
			var child = par.hierarchyParentObj.nodes[par.children[i]];
			t+= sumChildren(child);
		}
	} else {
		t+=1;  // for this node (par)
	}
	par.numDescendants = t;
	return t;
}

function generateMolFunctionHierarchy() {
	var nodes = [];
	var cs = [];
	nodes[0] = new HierarchyNode("Molecular Function", "proFamily", 0, -1, [1])
	for (var i=0; i<functionGroups.list.length; i++) {
		var n =new HierarchyNode(functionGroups.list[i].getName(), "proFamily" + i, i, 0, []);
		n.topicObj = functionGroups.list[i];
		nodes.push(n);
		cs.push(i+1)
	}
	nodes[0].children = cs;
	nodes[0].type = "category"
	return nodes;
}

function generateInteractionHierarchy() {
	var node = [];

	node[0] = new HierarchyNode("Interactions", "none", 0, -1, [1,2,3, 4, 5, 6, 7,8, 9, 10, 11])
	node[1] = new HierarchyNode("Expression", "none", 1, 0, []);
	node[2] = new HierarchyNode("Chemical Reaction", "none", 2, 0, []);
	node[3] = new HierarchyNode("Activation", "none", 3, 0, []);
	node[4] = new HierarchyNode("Regulation", "none", 4, 0, []);
	node[5] = new HierarchyNode("Localization", "none", 5, 0, []);
	node[6] = new HierarchyNode("Modification", "none", 6, 0, []);
	node[7] = new HierarchyNode("Inactivation", "none", 7, 0, []);
	node[8] = new HierarchyNode("Translocation", "none", 8, 0, []);
	node[9] = new HierarchyNode("RNA-RNA", "none", 9, 0, []);
	node[10] = new HierarchyNode("Transcription", "none", 10, 0, []);
	node[11] = new HierarchyNode("Protein-Protein", "none", 11, 0, []);

	return node;
}

function generateBiomarkerHierarchy() {
	var node = [];

	node[0] = new HierarchyNode("Biomarker", "none", 0, -1, [1,2])
	node[1] = new HierarchyNode("Drug Discovery", "none", 1, 0, [3,4,5,6,7]);
	node[2] = new HierarchyNode("Gene Product", "none", 2, 0, [8,9,10]);

	node[3] = new HierarchyNode("Safety", "none", 3, 1, []);
	node[4] = new HierarchyNode("Efficacy", "none", 4, 1, []);
	node[5] = new HierarchyNode("Diagnosis", "none", 5, 1, []);
	node[6] = new HierarchyNode("Disease Progression", "none", 6, 1, []);
	node[7] = new HierarchyNode("Prognosis", "none", 7, 1, []);

	node[8] = new HierarchyNode("Gene Product", "none", 8, 2, []);
	node[9] = new HierarchyNode("mRNA Product", "none", 9, 2, []);
	node[10] = new HierarchyNode("Protein Product", "none", 10, 2, []);

	return node;
}

function updateHierarchies() {
	for (var i=0;i<diseases.list.length; i++) diseases.list[i].inContext = true;
	for (i=0;i<pathways.list.length; i++) pathways.list[i].inContext = true;
	for (i=0;i<bioProcesses.list.length; i++) bioProcesses.list[i].inContext = true;
	for (i=0; i<interactionTypeGroups.list.length; i++) {
		var itg = interactionTypeGroups.list[i];
		itg.inContext = false;
		for (var j=0;j<itg.genes.length; j++) {
			if(genes.list[itg.genes[j]].inContext) {
				itg.inContext = true;
				break;
			};
		};
	};
	for (i=0; i< hierarchies.length; i++) {
		hierarchies[i].correlateTopicObj();
		hierarchies[i].root.getDescendantCount();  // recursively calculate
		hierarchies[i].setContext();  // this sets their .inContext attribute -- since this is time=0 this could be simplified..
	}

	firstLoad = false;
	loadWin = false;
	hierarchyLoaded = true;
}

