//////////////////////////////////////////////////////////
// Copyright 1998, 1999, Attotron Biosensor Corporation //
//         This program may not be distributed          //
//           without written permission from            //
//            Attotron Biosensor Corporation.           //
// Email: info@attotron.com    http://www.attotron.com  //
//////////////////////////////////////////////////////////


// global parameters
// gel layout
var numLanes = 13;				// number of lanes. 13 = 12 samples plus marker
var setsPerLane = 6;			// number of DNA fragment sets per lane (used by getBands)
var leftMargin = 10;			// extra margin on left side of gel
var wellSpacing = 30;			// Total space required for a lane, including half-spaces on each side.


var fluorescenceCoeff = 0.105596;
			// set minimum visible to 25ng/band; max intensity = 4 ug/band
			// min = 1; max = 128
			// #umol*#bp * 660ug/umol*bp = ug; thus 25ng = (0.025/660)umol of bp = .00003788 umol*bp = 37.88 fmol*bp
			// 37.88 fmol*bp = 1/fluorescenceCoeff
			// fluorescenceCoeff = 1/37.88fmol*bp = 0.026399(fmol^-1*bp^-1)
			// reduced by 10^3 1/20/99; adjusted empirically
var runTime = parseFloat(0);	// virtual hour counter
var minuteCounter = 0;			// those virtual minutes add up; make every one count.
var voltage = 0;				// virtual volt*hours = number of migration units (pixels) in gel. Read from input field at run time.
var biggestBand = 7000;
var smallestBand = 1;
var m = 1/( Math.log(smallestBand) - Math.log(biggestBand) );
			// migration coefficient - should depend on agarose concentration
var b = 1 + Math.log(smallestBand) * m * (-1);
			// volts*time*b = full length of gel = migration of (theoretical) fastest particle on log curve. 
			// For a "smallestBand" bp band to run to the end of 300 pixel long gel in 3 virtual hours at 100V
			// b = 1 + Math.log(smallestBand) * m * (-1)						(migration = 300)
			// For a "biggestBand" bp long band to stay at the top of the gel	(migration = 0)
			// m = -b/Math.log(biggestBand) = [...] = 1/( Math.log(smallestBand) - Math.log(biggestBand) )
var bbMigration = 0.9;			// Bromophenol blue migration constant
var xcMigration = 0.5;			// Xylene cyanol migration constant
var timerID;	//ID number set by setTimeOut() when power supply timer starts

// sequence handling functions
function deStick(aString) {
	// removes sticky end notation, leaving only the single strand sequence
	// example: deStick(ac|ccaagc[tt]) = acccaagc
	// Bases in [square brackets] are not really part of the strand shown; they represent
	// the complement of the protruding bases on the complementary strand.
	// test sequences for deStick function = gatc|catcgatgaattcaagcttcatatgctgca[g] ; acg|ttac[gc]
	// to be used by electrophoresis program.

	var outString = aString;
	outString = outString.replace(/\|/,"");			// simply remove vertical bars
	outString = outString.replace(/\[[^\[\]]*\]/,"");	// ... and anything in square brackets
	return outString;
}


// object definitions
function band(size,quantity) {
	this.size = size;
	this.q = quantity
	this.intensity = this.size * this.q * fluorescenceCoeff;
	if (this.intensity<=15){
		this.bandHeight = 1;
	}else{
		this.bandHeight = parseInt(this.intensity/16);			
	}
	this.relMigration = ( b + (m * Math.log(this.size) ) );
}


// function used for manipulating hexadecimal values.
function num2String (inNum,outBase,maxDigits) {
	// inNum is a base10 number
	// outBase is the string defining the counting system into which the number will be translated.
	// maxDigits =  number of digits in output, counting from 0. Used to determine number of leading zeros.
	var retString = "";
	var reMainder = inNum;
	
	for (var i = maxDigits; i >= 0; i--) {						// from most significant digit at left on down
			retString += outBase.charAt(parseInt(reMainder/Math.pow(outBase.length,i) ) );
			reMainder = parseInt(reMainder%Math.pow(outBase.length,i) );
	}
	
	return retString;
}


// powerSupply functions

function newGel(){
	// erase fluorescent layer
	document.gel.document.open();
	document.gel.document.write("");
	document.gel.document.close();
	// erase dye layer
	document.room.document.dye.document.open();
	document.room.document.dye.document.write("");
	document.room.document.dye.document.close();
	// reset hour counter
	runTime = parseFloat(0);
	minuteCounter = 0;
	// UV light starts off
	if (UVstatus == "off") document.gel.visibility='hide';
}


function getInfo(layer){
	var bandInfoString = "<html><body bgcolor=yellow>";
	bandInfoString = "lane number = " + layer.name.substring(layer.name.indexOf("l") + 1,layer.name.indexOf("b"));
	bandInfoString += ", band number = " + layer.name.substring(layer.name.indexOf("b") + 1,layer.name.length);
	bandInfoString += "<br>band migration distance = " + (layer.top/3) + " millimeters";
	
	var bandEvalString = "lane" + layer.name.substring(layer.name.indexOf('l') + 1,layer.name.indexOf('b'));
	bandEvalString += "[" + layer.name.substring(layer.name.indexOf('b') + 1,layer.name.length) + "]";
	
	bandInfoString += "<br>DNA quantity = " + eval(bandEvalString + ".q") + " fmol";
	bandInfoString += "<br>band.size = " + eval(bandEvalString + ".size") + " bp";
	bandInfoString += "<br>band.intensity = " + eval(bandEvalString + ".intensity") + " fluorescence units";

	bandInfoString += "</body></html>";
	
	document.infoLayer.document.open();
	document.infoLayer.document.write(bandInfoString);
	document.infoLayer.document.close();
}


function clearInfo(){
	document.infoLayer.document.open();
	document.infoLayer.document.write(".");
	document.infoLayer.document.close();
}


// declare lane objects as global variables
for (var i=1; i<=numLanes; i++) {
	//make a lane array to be filled with band objects
	eval("var lane" + i + " =  new Array;");
}

function getBandSizes() {
	// Calculates the sizes of DNA strands in the input string and creates band objects
	// Called onLoad. Be sure this function is only called once! 
	// 		Otherwise it will copy the data again onto the ends of the band objects.

	// strings defining fragment ends
	var leftFragEnd = "5-";			// string marking left end of DNA fragment
	var rightFragEnd = "-3";		// string marking right end of DNA fragment

	// for each lane (i)
	for (var i=1; i<=numLanes-1; i++) {
		// keep track of the overall fragment number within the lane
		var k=0;
		// for each set (j)
		for (var j=1; j<=setsPerLane;j++){
			var inString = parent.Frame1.document.forms[i].elements[j].value;
			var bandQuantity = parent.Frame1.document.forms[i].elements[setsPerLane+j].value;
			var leftFragIndex = 0;			// position of next left fragment end marker
			var rightFragIndex = 0;			// position of next right fragment end marker
			var oldLeftFragIndex = 0;		// position of previous fragment left end marker
			var fragMent = "";				// sequence of one fragment in set
			var k = eval("lane" + i + ".length - 1");
									// keeps track of which fragment it's on within a lane
			// for each fragment in the lane (k)
			while (leftFragIndex >= 0 ) {
				 // next leftFragEnd after last one
				leftFragIndex = inString.indexOf(leftFragEnd, oldLeftFragIndex);
				// That's all folks. Exiting here avoids using -1 as index
				if ( leftFragIndex < 0 ) {
					break;
				} 
				// next rightFragEnd after leftFragEnd
				rightFragIndex = inString.indexOf(rightFragEnd, leftFragIndex);	
				oldLeftFragIndex = leftFragIndex + 1;
				// extract sequence of next fragment
				fragMent = inString.substring(leftFragIndex + leftFragEnd.length, rightFragIndex);	
				// figure band size. "deStick(fragMent).length;" = size not counting sticky ends, but that approach uses a lot of memory
				bandSize = deStick(fragMent).length;
				// create a new object in lane i with the size and quantity of the fragment.
				eval("lane" + i + "[" + k + "] = new band(bandSize,bandQuantity);");
				// count the fragment
				k++;
			}
		}
				
		// set the first element in the lane array to the number of bands in the lane
//		eval("lane" + i + "[0] =  k;");	// test!!
	}

	// for molecular weight marker	
	i=numLanes;
	k=0;
	// for each set (j)
	for (var j=1; j<=setsPerLane;j++){
		inString = parent.Frame1.document.forms[26].elements[j].value;	// MW marker
		bandQuantity = parent.Frame1.document.forms[26].elements[setsPerLane+j].value;
		leftFragIndex = 0;			// position of next left fragment end marker
		rightFragIndex = 0;			// position of next right fragment end marker
		oldLeftFragIndex = 0;		// position of previous fragment left end marker
		fragMent = "";				// sequence of one fragment in set
		k = eval("lane" + i + ".length - 1");
								// keeps track of which fragment it's on within a lane
		// for each fragment in the lane (k)
		while (leftFragIndex >= 0 ) {
			 // next leftFragEnd after last one
			leftFragIndex = inString.indexOf(leftFragEnd, oldLeftFragIndex);
			// That's all folks. Exiting here avoids using -1 as index
			if ( leftFragIndex < 0 ) {
				break;
			} 
			// next rightFragEnd after leftFragEnd
			rightFragIndex = inString.indexOf(rightFragEnd, leftFragIndex);	
			oldLeftFragIndex = leftFragIndex + 1;
			// extract sequence of next fragment
			fragMent = inString.substring(leftFragIndex + leftFragEnd.length, rightFragIndex);	
			// figure band size. "deStick(fragMent).length;" = size not counting sticky ends, but that approach uses a lot of memory
			bandSize = fragMent.length;
			// count the fragment
			k++;
			// create a new object in lane i with the size and quantity of the fragment.
			eval("lane" + i + "[" + k + "] = new band(bandSize,bandQuantity);");
		}
	}
	// set the first element in the lane array to the number of bands in the lane
//	lane13[0] =  6;		//test!!
}


function loadGel(){
	newGel();
	document.gel.document.open();
	for (var i=1; i<=numLanes; i++){
		for (var j=0;j<eval("lane" + i + ".length");j++){

			eval("var redIndex = lane" + i + "[j].intensity");
			
			if (eval("lane" + i + "[j].intensity")>15){redIndex=15};
														// intensities from 0 to 15 are indicated by color;
														// intensities above 15 are indicated by band thickness.
			var greenIndex = parseInt((redIndex-2)/2);	// green color trails red to make orange
			greenIndex = (greenIndex<0?0:greenIndex);	// no negative green color.
			var blueIndex = parseInt((redIndex-4)/2);	// a touch of blue to make it brighter
			blueIndex = (blueIndex<0?0:blueIndex);		// no negative blue
			
			var bandString = "<layer id=l" + i + "b" + j + " height=" + eval("lane" + i + "[j].bandHeight") + " width=20";
			bandString += " top=" + (-eval("lane" + i + "[j].bandHeight"));
			bandString += " left=" + parseInt(leftMargin + (i-1)*wellSpacing);
			bandString += " bgcolor=#";
			bandString += num2String(redIndex,'0123456789ABCDEF',0);
			bandString += "0";
			bandString += num2String(greenIndex,'0123456789ABCDEF',0);
			bandString += "0";
			bandString += num2String(blueIndex,'0123456789ABCDEF',0);
			bandString += "0";
			bandString += " onMouseover=getInfo(this) onMouseout=clearInfo() ";
			bandString += ">";
			if (eval("lane" + i + "[j].bandHeight")>2){
				bandString += "<IMG NAME='mask' SRC='../images/bandMask.GIF' HEIGHT=" + eval("lane" + i + "[j].bandHeight") + " WIDTH=20>"
			}
			bandString += "</layer>";
			document.gel.document.write(bandString);
		}
	}
	document.gel.document.close();
	
	// create bb and xc layers for each lane
	document.room.document.dye.document.open();
	for (var i=1; i<=numLanes; i++){
		var dyeString = "<layer id=l" + i + "xc height=5 width=20 top=-5 left=" + parseInt(leftMargin + (i-1)*wellSpacing) + " bgcolor=5555ff></layer>"
		dyeString += "<layer id=l" + i + "bb height=5 width=20 top=-5 left=" + parseInt(leftMargin + (i-1)*wellSpacing) + " bgcolor=000055></layer>"
		document.room.document.dye.document.write(dyeString);
	}
	document.room.document.dye.document.close();
	
	// create loading dye layers for each well
	document.room.document.dyeNwell.document.open();
	for (var i=1; i<=numLanes; i++){
		var wellString = "<layer height=10 width=20 left=" + (i-1)*wellSpacing;
		wellString += " bgcolor=blue";		// only if that lane is used
		wellString += " ></layer>\r";
		document.room.document.dyeNwell.document.write(wellString);
	}
	document.room.document.dyeNwell.document.close();
	document.room.document.dyeNwell.clip.top = 0;			//initialize loading dye height in well
	
	//document.room.document.dyeNwell.clip top = 10;  onLoad='dyeNwell.clip.top=5'
}


function powerOn() {
	for (var i=0;i<document.room.document.boxFrame.document.cathode.document.images.length;i++){
		document.room.document.boxFrame.document.cathode.document.images[i].src="../images/silvBubb.GIF";
	}
	for (var i=0;i<document.room.document.boxFrame.document.cathode.document.cathode2.document.images.length;i++){
		document.room.document.boxFrame.document.cathode.document.cathode2.document.images[i].src="../images/silvBubb.GIF";
	}
	for (var i=0;i<document.room.document.boxFrame.document.anode.document.images.length;i++){
		document.room.document.boxFrame.document.anode.document.images[i].src="../images/silvBubb.GIF";
	}
	document.powerSupply.document.LED.src = '../images/LEDred.GIF';
	runGel();
}


function powerOff() {
	for (var i=0;i<document.room.document.boxFrame.document.cathode.document.images.length;i++){
		document.room.document.boxFrame.document.cathode.document.images[i].src="../images/noBubb.GIF";
	}
	for (var i=0;i<document.room.document.boxFrame.document.cathode.document.cathode2.document.images.length;i++){
		document.room.document.boxFrame.document.cathode.document.cathode2.document.images[i].src="../images/noBubb.GIF";
	}
	for (var i=0;i<document.room.document.boxFrame.document.anode.document.images.length;i++){
		document.room.document.boxFrame.document.anode.document.images[i].src="../images/noBubb.GIF";
	}
	document.powerSupply.document.LED.src = '../images/LEDgray.GIF';
	clearTimeout(timerID);
}

function togglePower(){
	if (document.powerSupply.document.LED.src.substring(document.powerSupply.document.LED.src.lastIndexOf('/')+1,document.powerSupply.document.LED.src.length) == 'LEDgray.GIF'){
	// if off, turn on
		powerOn();	// this function sets timerID
	} else {
	// must be on; turn off
		powerOff();	// this function clears timerID
	}
}

function runGel(){
	// count down
	if (document.powerSupply.document.forms[0].minutes.value <= 0) {
		if (document.powerSupply.document.forms[0].hours.value <= 0) {
			// alert("Time is up!");	// all done
			powerOff();
			return;
		} else {
			document.powerSupply.document.forms[0].hours.value--;
			document.powerSupply.document.forms[0].minutes.value +=60;
		}
	}
	// move bands
	voltage = parseInt(document.powerSupply.document.forms[0].volts.value);
	minuteCounter++;
	runTime = parseFloat(minuteCounter/60);
	//window.status = "run time = " + Math.floor(runTime) + "." + parseInt(100*runTime%100) + " virtual hours.";
	  for (var i=1; i<=numLanes; i++){	//var i=1;		//@@@ for only one practice lane numLanes
		for (var j=0;j<eval("lane" + i + ".length");j++){
			var bandMigration = parseInt(runTime * voltage * eval("lane" + i + "[j].relMigration") );
			if (!(bandMigration>=0)) { bandMigration = parseInt(0) };	// no negative migration.
		// I don't know why, but band [9] of the 150 bp marker comes out with a height of NaN
		// This started after I reindexed the lanes[] arrays from zero and reinstated the deStick function with REs.
			if (!eval("lane" + i + "[j].bandHeight >= 0")) {
			//	alert("lane = " + i + "\nj = " + j + "\nbandHeight !>= 0");
				eval("lane" + i + "[j].bandHeight = 0");
			}
			
			eval("window.document.gel.document.l" + i + "b" + j + ".top = (bandMigration-lane" + i + "[j].bandHeight)" );
		}
		eval("document.room.document.dye.document.l" + i + "xc.top = Math.round(runTime * voltage * xcMigration)-5");
		eval("document.room.document.dye.document.l" + i + "bb.top = Math.round(runTime * voltage * bbMigration)-5");
	  }
	if (document.room.document.dyeNwell.clip.top<10) {
		document.room.document.dyeNwell.clip.top += Math.round(voltage/50)+1;
	}		
	document.powerSupply.document.forms[0].minutes.value--;
	timerID = setTimeout("runGel()",100);
}


function toggleSwitch(){
	if (document.lightSwitch.document.sWitch.src.substring(document.lightSwitch.document.sWitch.src.lastIndexOf('/')+1,document.lightSwitch.document.sWitch.src.length) == 'onSw.GIF'){
		document.lightSwitch.document.sWitch.src = '../images/offSw.GIF';
		document.room.visibility='hide';
		if (UVstatus == "off") document.dragable.visibility='hide';
	} else {
		document.lightSwitch.document.sWitch.src = '../images/onSw.GIF';
		document.room.visibility='show';
		document.dragable.visibility='show';
	}
}

function statusAlert(meSsage){
	window.status=meSsage;
	return true;
}

var UVstatus = "off";		// whether UV light is on or off

function onUV(){
	document.gelBoxControl.document.LED.src="../images/LEDred.GIF";
	statusAlert("CAUTION! Ultraviolet light! Protect eyes and skin.");
	document.gel.visibility="show";
	UVstatus = "on";
	document.dragable.visibility='show';
}


function offUV(){
	document.gelBoxControl.document.LED.src="../images/LEDgray.GIF";
	document.gel.visibility="hide";
	UVstatus = "off";
	if (document.room.visibility=='hide') document.dragable.visibility='hide';
}


