// basin.js 1.5
// Implements the display of fishes in a pool
// Original by John Bell, version 1.5 modified by Jon Ippolito to tweak fish size.
function PoolBasin(fishArray){
	//note that basin and unrankedBasin must both be set before this function is called.
	
	//Turn off the basins while drawing fish, it's a lot faster.
	this.basin = basin;
	this.unrankedBasin = unrankedBasin;
	
	this.basin.style.display = 'none';
	this.unrankedBasin.style.display = 'none';
	
	//Set up the containers for UI settings
	this.state = {panels: new Array(), axes: new Array(), filters: new Array()};
	
	//Draw the fish themselves.  Remember they aren't placed here, so PoolBasin.moveFishes must be called seperately
	Fishes = new Array();
	ActiveFishes = new Array();
	var i=0;
	for(var fishVar in fishArray){
		switch(fishArray[fishVar].pool_type){
			case 'artreference':	
				var workingFish = new ReferenceFish(fishArray[fishVar].id, fishArray[fishVar].title, 'artreference', fishArray[fishVar]);
			break;
			case 'art':
				var workingFish = new ArtFish(fishArray[fishVar]);
			break;
			case 'code':
				var workingFish = new CodeFish(fishArray[fishVar]);
			break;
			case 'codereference':
				var workingFish = new ReferenceFish(fishArray[fishVar].id, fishArray[fishVar].title, 'codereference', fishArray[fishVar]);
			break;
			default:
		}
		workingFish.fishID = fishVar;
		Fishes.push(workingFish);
		this.basin.appendChild(workingFish.fishDiv);
		if(i < 10){
			ActiveFishes.push(workingFish);
		}
		i++;
	}   
}

PoolBasin.prototype.moveFishes = function(xAxis, yAxis, scale){
	//Moves created fish to their normalized positions
	//xAxis, yAxis, and scale are indexes in the Fishes[fish] array for the two axes
	//Note that the use of 'eval' in this function allows x and yAxis to be multidimensional, ie: 'subratings.perceptual'
	
	//store the axes for later use, and copy them to the state manager
	this.xAxis = this.state.axes.x = xAxis;
	this.yAxis = this.state.axes.y = yAxis;
	this.scale = this.state.axes.z = scale;
	this.minDate = 800000000000000000000;
	this.maxDate = 0;
	
	if(!defined(this.xAccuracy)){
		this.xAccuracy = this.yAccuracy = this.scaleAccuracy = 4;
		this.state.axes.xAcc = this.state.axes.yAcc = this.state.axes.zAcc = 4;
	}
	
	//create an array of Fishes keys ordered by x and y axes
	//Pull out the axis data from the main array, setting aside the y=0 fish
	this.orderedX = new Array();
	this.orderedY = new Array();
	this.orderedScale = new Array();
	this.ungraphed = new Array();
		
	for(var fishVar in Fishes){	
		if(this.validateAxes(Fishes[fishVar].dataArray)){
//			console.log(Fishes[fishVar].dataArray);
			this.orderedX.push([eval('Fishes['+fishVar+'].dataArray.'+xAxis), fishVar]);
			this.orderedY.push([eval('Fishes['+fishVar+'].dataArray.'+yAxis), fishVar]);
			this.orderedScale.push([eval('Fishes['+fishVar+'].dataArray.'+scale), fishVar]);
			if(Fishes[fishVar].dataArray.lm_unix > this.maxDate) this.maxDate = Fishes[fishVar].dataArray.lm_unix;
			if(Fishes[fishVar].dataArray.lm_unix < this.minDate) this.minDate = Fishes[fishVar].dataArray.lm_unix;			
		} else {
			this.ungraphed.push([Fishes[fishVar].dataArray.title, fishVar]);
		}
	} 


	if(this.orderedX.length > 0){
		//Turn min and max dates into human readable dates
		var oMinDate = new Date(this.minDate * 1000);
		var sMinDate = oMinDate.toGMTString();
		var aMinDate = sMinDate.split(' ');
		this.minDate = new Array(aMinDate[1], aMinDate[2], aMinDate[3]).join(' ');
		
		var oMaxDate = new Date(this.maxDate * 1000);
		var sMaxDate = oMaxDate.toGMTString();
		var aMaxDate = sMaxDate.split(' ');
		this.maxDate = new Array(aMaxDate[1], aMaxDate[2], aMaxDate[3]).join(' ');
		
		//order it and find min/max
		this.ungraphed.sort(browsers.strSortArray);
		this.orderedScale.sort(browsers.numSortArray);
		this.orderedX.sort(browsers.numSortArray);
		this.orderedY.sort(browsers.numSortArray);
		this.minScale = 0;
		this.minX = this.orderedX[0][0];
		this.minY = this.orderedY[0][0];
		this.maxScale = this.orderedScale[this.orderedScale.length - 1][0];
		this.maxX = this.orderedX[this.orderedX.length - 1][0];
		this.maxY = this.orderedY[this.orderedY.length - 1][0];
	
		this.xRange = this.maxX - this.minX;
		this.yRange = this.maxY - this.minY;
		
		//START
		//Based on the y range, divide the fish up into 10 thermoclines (Edit:  was strata, changed to make fishier.)
		this.thermoclines = [new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array(), new Array()];
		for(var i=0; i<this.orderedX.length; i++){
			if((this.maxY - this.minY) == 0) this.maxY++;
			var cline = Math.floor(((this.maxY - parseFloat(eval('Fishes['+this.orderedX[i][1]+'].dataArray.'+yAxis))) / (this.maxY - this.minY) * 9));
			//console.log(this.maxY - this.minY);
			//try{console.log(this.maxY, parseFloat(eval('Fishes['+this.orderedX[i][1]+'].dataArray.'+yAxis)), this.minY)} catch (e){}
			this.thermoclines[cline].push(this.orderedX[i][1]);
		}
		
		//Calculate max legibility positions
		//Assume 5 vertical items per column before you have to split, since this is what Jon's calcs always worked out to.
		for(var i=this.thermoclines.length - 1; i>=0; i--){
			var cols = this.thermoclines[i].length / 10;	
			var colWidth = 98 / cols; //98 is the max width percentage for a basin
			var colXOffset = colWidth / 10;
			var colTop = i * 10;
			var colYOffset = 1;
			for(var j=0; j<this.thermoclines[i].length; j++){
				var col = Math.floor(j / 10);
				Fishes[this.thermoclines[i][j]].legibleX = (col * colWidth) + ((j % 10) * colXOffset);
				Fishes[this.thermoclines[i][j]].legibleY = colTop + ((j % 10) * colYOffset);
			}
		}
		//END
	}
		
	//Get positions (x.xx% of basin) of ranked fish
	//Set font size and color as well
	for(var i=0; i<this.orderedX.length; i++){
		//positioning
		var workingFish = Fishes[this.orderedX[i][1]];
		this.setGraphed(workingFish);
	}
	
	//move unranked fish to unranked basin and set their position
	for(var i=0; i<this.ungraphed.length; i++){
		var fishIdx = Fishes[this.ungraphed[i][1]];
		unrankedBasin.appendChild(fishIdx.fishDiv);
		fishIdx.idealY = Math.round(i / this.ungraphed.length*98);
		fishIdx.fishDiv.style.top = fishIdx.idealY+'%';
		fishIdx.fishDiv.style.left = '0%';
		var fishScale = eval('fishIdx.dataArray.'+scale);	
		var fishSize = this.setFishSize({fishScaleArg: fishScale, maxScaleArg: this.maxScale}) ;
		if(fishSize < 9) fishIdx.idealFontSize = 9 ;
		else fishIdx.idealFontSize = parseInt(fishSize);
		var fontLine = Math.line({begin: {x: 9, y:0}, end: {x: fishIdx.idealFontSize, y: 0}, steps: 10});
		fishIdx.fontSizes = new Array();
		for(var j=0; j<fontLine.length; j++) fishIdx.fontSizes.push(fontLine[j].x);	
		fishIdx.fishDiv.style.fontSize = fishIdx.fontSizes[this.scaleAccuracy] + 'px';

		fishIdx.fishDiv.style.color = 'rgb(128,128,128)';
		fishIdx.baseColor = [128,128,128];
	}
	
	//Record the current state in the URL for bookmarkability
	if(init == false) ProtoHistoryObj.savePool(this.state);
}

PoolBasin.prototype.validateAxes = function(dataArray){
//Makes sure that each of the axes are defined in the passed dataArray.  If not, returns false.
	//split the axes and eval each part to make sure it exists.  Have to do it this way, since if you try to access a nested array where the top level doesn't exist you get errors
//	console.log(dataArray);
	var xAxisParts = this.xAxis.split('.');
	var yAxisParts = this.yAxis.split('.');
	var scaleParts = this.scale.split('.');
	
	var xAxisCurrent = '';
	var yAxisCurrent = '';
	var scaleCurrent = '';
	
	for(var i=0; i<xAxisParts.length; i++){
		xAxisCurrent += '.'+xAxisParts[i];
		if(!defined(eval('dataArray'+xAxisCurrent))) return false;
	}
	for(var i=0; i<yAxisParts.length; i++){
		yAxisCurrent += '.'+yAxisParts[i];
		if(!defined(eval('dataArray'+yAxisCurrent))) return false;
	}
	for(var i=0; i<scaleParts.length; i++){
		scaleCurrent += '.'+scaleParts[i];
		if(!defined(eval('dataArray'+scaleCurrent))) return false;
	}	
	//console.log(this.scale);	
//	console.log(dataArray.id + ': x='+eval('dataArray.'+this.xAxis) + ', y='+eval('dataArray.'+this.yAxis)+', z='+eval('dataArray.'+this.scale));
	
	if(eval('dataArray.'+this.yAxis) <= 0) return false;
	if(eval('dataArray.'+this.xAxis) <= 0) return false;
	if(eval('dataArray.'+this.scale) <= 0) return false;
	
	return true;
}

PoolBasin.prototype.addUnrankedFish = function(dataArray){
//Adds a new unranked fish to the array
	switch(dataArray.pool_type){
		case 'art':
			var workingFish = new ArtFish(dataArray);		
		break;
		case 'code':
			var workingFish = new CodeFish(dataArray);
		break;
		case 'codereference':
			var workingFish = new ReferenceFish(dataArray.id, dataArray.title, 'codereference', dataArray);
		break;
		case 'artreference':
			var workingFish = new ReferenceFish(dataArray.id, dataArray.title, 'artreference', dataArray);
		break;
	}

	workingFish.poolType = dataArray.pool_type.substr(0,1).toUpperCase() + dataArray.pool_type.substr(1);
	Fishes.push(workingFish);
	var fishVar = Fishes.length - 1;
	this.unrankedBasin.appendChild(workingFish.fishDiv);
	this.ungraphed.push([dataArray.title, fishVar]);
	this.ungraphed.sort(browsers.strSortArray);
	
	for(var i=0; i<this.ungraphed.length; i++){
		var fishIdx = Fishes[this.ungraphed[i][1]];
		unrankedBasin.appendChild(fishIdx.fishDiv);
		fishIdx.idealY = Math.round(i / this.ungraphed.length*98);
		fishIdx.fishDiv.style.top = fishIdx.idealY+'%';
		var fishScale = eval('fishIdx.dataArray.'+this.scale);	
		var fishSize = this.setFishSize({fishScaleArg: fishScale, maxScaleArg: this.maxScale}) ;
		if(fishSize < 9) fishIdx.fishDiv.style.fontSize = '9px';
		else fishIdx.fishDiv.style.fontSize = parseInt(fishSize) + 'px';
		fishIdx.fishDiv.style.color = 'rgb(128,128,128)';
		fishIdx.baseColor = [128,128,128];
	}
}

PoolBasin.prototype.setGraphed = function(workingFish){
//Sets the position of a graphed fish.
	if(!defined(workingFish.dataArray)) return false;
	var fishX = eval('workingFish.dataArray.'+this.xAxis);
	var fishY = eval('workingFish.dataArray.'+this.yAxis);
	var fishScale = eval('workingFish.dataArray.'+this.scale);
	workingFish.idealX = (((fishX-this.minX) / (this.xRange))*80);
	workingFish.idealY = 98 - (((fishY-this.minY) / (this.yRange))*98);
	//workingFish.fishDiv.style.top = workingFish.idealY+'%';
	//workingFish.fishDiv.style.left = workingFish.idealX+'%';
	
	//START
	//since legibleX and legibleY should have already been set in moveFishes, we can build the coord array
	workingFish.coords = Math.line({begin: {x: workingFish.legibleX, y: workingFish.legibleY}, end: {x: workingFish.idealX, y: workingFish.idealY}, steps: 10});
	workingFish.fishDiv.style.top = workingFish.coords[this.yAccuracy].y + '%';
	workingFish.fishDiv.style.left = workingFish.coords[this.xAccuracy].x + '%'
	//END
	
	//coloring
	var green = parseInt(((fishY)/this.maxY)*255); //How good the rating is.
	var red = parseInt(((this.maxY-fishY)/this.maxY)*255); //How bad the rating is.
	var blue = parseInt(((this.maxX-fishX)/this.maxX)*128); //How reliable the sample basis is.
	workingFish.fishDiv.style.color = 'rgb('+red+','+green+','+blue+')';
	workingFish.baseColor = [red, green, blue];
	
	//font size
	var fishSize = this.setFishSize({fishScaleArg: fishScale, maxScaleArg: this.maxScale}) ;
	if(fishSize < 9) workingFish.idealFontSize = 9 ;
	else workingFish.idealFontSize = parseInt(fishSize);
	
	var fontLine = Math.line({begin: {x: 9, y:0}, end: {x: workingFish.idealFontSize, y: 0}, steps: 10});
	workingFish.fontSizes = new Array();
	for(var i=0; i<fontLine.length; i++) workingFish.fontSizes.push(fontLine[i].x);	
	
	workingFish.fishDiv.style.fontSize = workingFish.fontSizes[this.scaleAccuracy] + 'px';
}

PoolBasin.prototype.setAccuracy = function(xAcc, yAcc, scaleAcc){
	//Adjusts the accuracy of the basin and repositions fish as necessary
	xAcc = xAcc > 9 ? 9 : xAcc;
	yAcc = yAcc > 9 ? 9 : yAcc;
	scaleAcc = scaleAcc > 9 ? 9 : scaleAcc;
	
	this.yAccuracy = this.state.axes.yAcc = yAcc;
	this.xAccuracy = this.state.axes.xAcc = xAcc;
	this.scaleAccuracy = this.state.axes.zAcc = scaleAcc;
	for(var i=0; i<this.orderedX.length; i++){
		var workingFish = Fishes[this.orderedX[i][1]];
		workingFish.fishDiv.style.top = workingFish.coords[this.yAccuracy].y + '%';
		workingFish.fishDiv.style.left = workingFish.coords[this.xAccuracy].x + '%';
		workingFish.fishDiv.style.fontSize = workingFish.fontSizes[this.scaleAccuracy] + 'px';	
	}
	for(var i=0; i<this.ungraphed.length; i++){
		Fishes[this.ungraphed[i][1]].fishDiv.style.fontSize = Fishes[this.ungraphed[i][1]].fontSizes[this.scaleAccuracy] + 'px';
	}
	if (init == false) ProtoHistoryObj.savePool(workingBasin.state);
}

PoolBasin.prototype.updateFish = function(fishArray){
	//updates the position, color, and scale of a fish based on the data in fishArray
	//This only works after moveFishes has been called, and uses the axes set in that function
	
	//get the fish to be updated
	var workingFish = false;
	for(var fishVar in Fishes){
		if(Fishes[fishVar].dataArray.id == fishArray.id){
			workingFish = Fishes[fishVar];
			break;
		}
	}
	//See if the fish is in the ungraphed array, but needs to be moved to the graph
	if(eval('fishArray.'+this.yAxis) > 0 && eval('fishArray.'+this.xAxis) > 0){
		for(var i=0; i<this.ungraphed.length; i++){
			if(Fishes[this.ungraphed[i][1]].dataArray.id == fishArray.id){
				this.basin.appendChild(Fishes[this.ungraphed[i][1]].fishDiv);
			}
		}
		workingFish.dataArray = fishArray;
		this.setGraphed(workingFish);
	}
}

// Filter functions for search box.
PoolBasin.prototype.searchFilter =function(searchStr, elementName) {
	if(elementName == 'searchBox'){
		//find the title criterion, if it already exists, and remove it from the criterion array
		for(var i=criterion.length - 1; i>=0; i--){
			if(criterion[i][0] == 'title'){
				var filterList = topMenu.selectors["Master"].element;
				var anchorList = filterList.getElementsByTagName('a');
				var thisAnchor = null;
				for(var j=0; j<anchorList.length; j++) if(anchorList[j].id == 'title') thisAnchor = anchorList[j];
				removeCriterion(criterion[i][0], criterion[i][1], thisAnchor);
			}
		}
		if(searchStr.length > 0) {
			//have to reset this since removeCriterion cleared it
			document.getElementById('searchBox').value = searchStr;	
			addCriterion('title', searchStr);
		}
		prevSearchStr = searchStr;
	}
}

PoolBasin.prototype.filterFish = function(){
	for(var i=0; i<Fishes.length; i++) Fishes[i].fishDiv.style.display = 'none';
	
	for(var fishVar in Fishes){
		var criterionMet = 0;
		for(var i=0; i<criterion.length; i++){
			//begin changed
			if(criterion[i][0] == 'title'){
				//Title searches are handled differently...
				var objRegex = new RegExp(criterion[i][1], "i");
				objRegex.compile(criterion[i][1], "i");
				if(objRegex.test(Fishes[fishVar].dataArray.title)) criterionMet++;	
			}
			//end
			var fishCriterion = eval('Fishes[fishVar].dataArray.'+selectorMap[criterion[i][0]]);
			if(defined(fishCriterion) == 'object'){
				for(var item in fishCriterion){
					if(item == criterion[i][1]) criterionMet++;
				}
			}
			else {
				if(fishCriterion == criterion[i][1]) criterionMet++;
			}
		}
		if(criterionMet == criterion.length) Fishes[fishVar].fishDiv.style.display = 'block';
	}
}

PoolBasin.prototype.toggleBasin = function(){
	if(this.basin.style.display == 'none') {
		this.basin.style.display = 'block';
		this.unrankedBasin.style.display = 'block';
	} else {
		this.basin.style.display = 'none';
		this.unrankedBasin.style.display = 'none';
	}
}

PoolBasin.prototype.findFish = function(fishID){
	//Finds a fish via the ID
	for(var i=0; i<Fishes.length; i++){
		if(Fishes[i].fishID == fishID) return Fishes[i];
	}
}

PoolBasin.prototype.setActive = function(){
	//Sets up the cycle arrays for active fish
	for(var i=0; i<ActiveFishes.length; i++){
		ActiveFishes[i].makeActive();
	}
}

PoolBasin.prototype.activate = function(){
	//animation cycle for active fish
	if(ActiveFishes.length == 0) return false;
	if(!defined(this.animationCycle)) this.animationCycle = 0;
	this.animationCycle++;
	if(this.animationCycle == ActiveFishes[0].cycleArray.red.length) this.animationCycle = 0;
	for(var i=0; i<ActiveFishes.length; i++){
		var colorArray = ActiveFishes[i].cycleArray;
		ActiveFishes[i].fishDiv.style.color = 'rgb('+colorArray.red[this.animationCycle]+','+colorArray.green[this.animationCycle]+','+colorArray.blue[this.animationCycle]+')';
	}
	setTimeout(workingBasin.activate, 150);
}
PoolBasin.prototype.setFishSize = function(argObj) {
	// WAS (fishScale * 30)/this.maxScale;
	// Jon used square root to emphasize initial contributions over later ones.
	var fishSizeVar = ( Math.sqrt(argObj.fishScaleArg) * 60 )/argObj.maxScaleArg;
	return parseInt(((9 / 10) * fishSizeVar) + (((10 - 9)/10) * 10));
}

PoolBasin.prototype.basinType = function(){
	//extracts the type of basin from the URL and returns it
	var urlFull = document.location;
	var regex = new RegExp(/\/\w+_/);
	var poolType = regex.exec(document.location).toString();
	return poolType.substring(1, poolType.length - 1);
}

PoolBasin.prototype.resetPool = function(){
	document.location.href = document.location.href.indexOf('?') != -1 ? document.location.href.split('?')[0] : document.location.href.split('#')[0];
}

PoolBasin.prototype.readHash = function(){
	var hash = ProtoHistoryObj.getHash();
	if(!hash) return false;
	var hashArray = hash.split('&');
	var filterArray = new Array();
	var titleArray = new Array();
	var panelArray = new Array();
	var triggerRedraw = false;
	for(var i=0; i<hashArray.length; i++){
		var hashType = hashArray[i].slice(0,1);
		var hashValue = hashArray[i].slice(1);
		switch(hashType){
			case 'a':
				//axes
				var axesValue = hashValue.split('=');
				switch(axesValue[0]){
					case 'x':	workingBasin.xAxis = workingBasin.state.axes.x = axesValue[1];	break;
					case 'y':	workingBasin.yAxis = workingBasin.state.axes.y = axesValue[1];	break;
					case 'z':	workingBasin.scale = workingBasin.state.axes.z = axesValue[1];	break;
					case 'zAcc':	workingBasin.scaleAccuracy = workingBasin.state.axes.zAcc = axesValue[1];	break;
					case 'yAcc':	workingBasin.yAccuracy = workingBasin.state.axes.yAcc = axesValue[1];	break;
					case 'xAcc':	workingBasin.xAccuracy = workingBasin.state.axes.xAcc = axesValue[1];	break;
				}
				triggerRedraw = true;
			break;
			case 'f':
				//filters
				hashValue = hashValue.slice(1);
				var filterValue = hashValue.split('|');
				addCriterion(filterValue[0], filterValue[1]);
				if(filterValue[0] == 'title'){
					document.getElementById('searchBox').value = filterValue[1];	
				}
			break;
			case 'p':
				//panels
				hashValue = hashValue.slice(1);
				var panelValue = hashValue.split('|');
				
				switch(panelValue[0]){
					case 'person':
						var panelType = 'personPanel';
						var fishId = panelValue[1];
						var fishType = 'person';
						var fishCallback = function(ajaxData){
							var personData = eval('('+ajaxData+')');
							var p = new PersonPanel(personData.person.written);
							p.loadData(personData);		
						}
					break;
					case 'code':
						var panelType = 'codePanel';
						var fishId = panelValue[1];
						var fishType = 'project';
						var fishCallback = function(ajaxData){
							var artifactData = eval('('+ajaxData+')');
							var displayTitle = artifactData.project.title.length < 60 ? artifactData.project.title : artifactData.project.title.substring(0,57)+'...';
							var p = new ArtifactPanel(displayTitle);	
							p.loadData(artifactData);
						}
					break;
					case 'codereference':
						var panelType = 'codeReferencePanel';
						var fishId = panelValue[1];
						var fishType = 'reference';
						
						var fishCallback = function(ajaxData){
							var referenceData = eval('('+ajaxData+')');
							var p = new ReferencePanel(referenceData.reference.title);
							p.loadData(referenceData);	
						}	
					break;
					case 'artreference':
						var panelType = 'artReferencePanel';
						var fishId = panelValue[1];
						var fishType = 'reference';
						
						var fishCallback = function(ajaxData){
							var referenceData = eval('('+ajaxData+')');
							var p = new ReferencePanel(referenceData.reference.title);
							p.loadData(referenceData);	
						}	
					break;					
					default:
						var panelType = 'artPanel';
						var fishId = panelValue[1];
						var fishType = 'project';
						var fishCallback = function(ajaxData){
							var artifactData = eval('('+ajaxData+')');
							var displayTitle = artifactData.project.title.length < 60 ? artifactData.project.title : artifactData.project.title.substring(0,57)+'...';
							var p = new ArtifactPanel(displayTitle);
							p.loadData(artifactData);	
						}				
				}
				Loader.require(panelType, ajax.get, [siteBaseDir + 'js/jsCache.js.php', {id: fishId, type: fishType}, fishCallback]);
			break;
		}
	}
	if(triggerRedraw){
		new GraphPanel(workingBasin.basinType(), true);
		GraphPanel.changeAxes(workingBasin.state.axes.x, workingBasin.state.axes.y, workingBasin.state.axes.z);
	}
}