<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
	<title>Client</title>
	
	<style type="text/css">
		.page {
			visibility: hidden;
			position: absolute;
			top: 40px;
			left: 10px;
			border: 1px solid black;
			padding: 5px;
			width: 550px;
		}
		
		td, th {
			border: 1px solid black;
			margin: 0px;
		}
		
		tr {
			padding: 0px;
			margin: 0px;
		}
		
		table {
			padding: 0px;
			border: 1px solid black;
		}
			
	</style>
	
	<script src="ajax.js" ></script>
	
	<script>

var localNodeAddr = '';
var originURL = 'Unknown Source';
var connected = false;
var numberOfNodes = 0;
var connections = new Array();	

var tests = new Array();
var testLocks = new Array();
var lockTrys = new Array();
var myLocks = new Array();
var runningTests = new Array();

var rangeLocks = new Array();

var finalResults = new Array();
var doneStage = 0;

var runTry = null;

var dlc = 0; 
var msgQ = new Array();

function error(msg) {
	log("ERROR: " + msg);
	alert("ERROR: " + msg);
	clearInterval(cronID);
}

/* basic ping function for local node
 *  if successful, 
 *    if we haven't greeted the server, we do, 
 *    otherwise nothing (just a heartbeat)
 *  if it fails, lets the user know we have lost 
 *    connection to the node server
 */
function ping() {
	var http = ajaxConnect();
	
	var retfn = returnfn(http,
		function(resp) {
			if (!connected) {
				connected = true;
				greet();
			}
		},
		function(resp) {
			connected = false;
			var html = "Connection to local Node failed.  Try reloading from <a href=\"" + originURL + "\" target=\"window\">" + originURL + "</a>";
			document.getElementById('statusLabel').innerHTML = html;	
			
		}

	);			
		
	ajaxSend(http, "cmd=ping\n\n", retfn);
}

/*  Greets the server
 *    so far just gets the server's IP and origin URL
 */
function greet() {
	var http = ajaxConnect();
	
	var retfn = returnfn(http, 
		function(resp) {
			var arr = resp.split(" ");
			localNodeAddr = arr[0];
			originURL = arr[1];
			document.getElementById('statusLabel').innerHTML = "I am <b>" + localNodeAddr + "</b>";
			init();
		}
	);
	ajaxSend(http, "cmd=greet\n\n", retfn);
}

function killServer() {
	announceDead(localNodeAddr);
	var http = ajaxConnect();
	
	var retfn = returnfn(http,
		function(resp) {
			// got a resp, server not dead
			killServer();
		});
	ajaxSend(http, "cmd=kill\n\n", retfn);
}

function getLog() {
	var http = ajaxConnect();


	var retfn = returnfn(http,
		function(resp) {
			var log = document.getElementById('log');
			//alert("getLog() resp: " + resp);
			log.value =  resp + log.value;
			// Since getLog returns (a call back is called later)
			// this is tail recursive friendly
			getLog();
		});
	ajaxSend(http, "cmd=getLog\n\n", retfn);
}

// two digits
function d2(str) {
	if (str <10)
		return "0"+str;
	else
		return str;
}

function getTime() {
	var date = new Date();
	return d2(date.getHours()) + ":" + d2(date.getMinutes()) + ":" + d2(date.getSeconds())
}

var logCheckElem = null;
function log(str) {
	if (logCheckElem == null)
		logCheckElem = document.getElementById("logCheck");
	if (logCheckElem.checked == true) {
		str =  getTime() + ": " + str+"\n";
		var log = document.getElementById('log');
		log.value =  str + log.value;
	}
}

function log2(str) {
	str =  getTime() + ": " + str+"\n";
	var log = document.getElementById('log');
	log.value =  str + log.value;
}

function getMsgs() {
	var http = ajaxConnect();
	var retfn = returnfn(http, 
		function(resp) {
			//log("getMsgs: len > 0");
			log("getMsgs: " + resp);
			queueMsgs(resp);
			// Since getMsgs returns (a call back is called later)
			// this is tail recursive friendly
			getMsgs(); 
		});
	ajaxSend(http, "cmd=getMsgs\n\n", retfn);
}

function connectedTo(addr) {
	if (addr == localNodeAddr)
		return true;
	for(i = 0; i < connections.length; i++) {
		if (connections[i] == addr) 
			return true;
	}
	return false;
}

function addConnection(addr) {
	if(!connectedTo(addr)) {
		connections[connections.length] = addr;
		numberOfNodes++;
		return true;
	} else
		return false;
}

function genNodeList() { 
	var str = "";//localNodeAddr+",";
	for (var i =0; i < connections.length; i++) {
		str += connections[i] + ",";
	}
	if (connections.length > 0)
		str = str.substring(0,str.length-1);
	return str;
}

function advertiseNewNode(addr) {
	var m = new Object();
	m['query'] = "newNode";
	m['addr'] = addr;
	bcastMsg(m);
}

function announceDead(addr) {
	var m = new Object();
	m['query'] = "deadNode";
	m['addr'] = addr;
	bcastMsg(m);
}

function handleNewNode(resp, welcome) {

	if (welcome == null)
		welcome = false;
	var addr = resp.addr;
	//log2("handle new node " + addr + " from " + resp.origin);
	
	// handles re adding if reloaded html
	//if (!connectedTo(addr))
	//{
	var ret = genNodeList();
	var n = addConnection(addr);
	if (welcome == true) {
		sendDLC(addr);
		sendWelcome(ret, addr);	
		sendLocks(addr);
		sendTests(addr);
	}
	if (n == true) {
		sendReqs(addr);
		advertiseNewNode(addr);
	}
	
	
}

function sendDLC(addr) {
	var m = new Object();
	m.query = "DLC";
	sendMsg(m, addr);
}

function sendReqs(addr) {
	for(i in lockTrys) {
		var lock = lockTrys[i];
		if (lock == null)
			continue;
			
		var m = new Object();
		m["query"] = "getLock";
		m["type"] = i;
		m["name"] = lock["name"];
		m["time"] = lock.time;
		m["addr"] = localNodeAddr;
		if (i == "range") {
			m["start"] = lock.start;
			m["end"] = lock.end;
		}
		sendMsg(m, addr);
	}
}

function sendWelcome(addrList, addr) {
	var m = new Object();
	m["query"] = "welcome";
	m["addrList"] = addrList;
	sendMsg(m, addr);
}

function sendLocks(addr) {
	for (var i in testLocks) {
		log ("testLog[" + i + "]");
		var l = testLocks[i];
		var m = new Object();
		
		m["query"] = "addLock";
		m["type"] = "test";
		m["name"] = l["name"];
		m["addr"] = l["addr"];
		m["locked"] = l["locked"];
		m["perjob"] =
		
			
		m["time"] = l["time"];
		if (l.running) {
			m["running"] = true;
			m["min"] = l["min"];
			m["max"] = l["max"];
			m["perjob"] = l["perjob"];
		}
		sendMsg(m, addr);
	}
	for (var i in rangeLocks) {
		var r = rangeLocks[i];
		while(r) {
			log("rangeLock[" + i +" [" + r.start + "-" + r.end +"]]");
			var m = new Object();
			m["query"] = "addLock";
			m["type"] = "range";
			m["name"] = i;
			m["addr"] = r["addr"]
			m["locked"] = r["locked"];
			m["start"] = r["start"];
			m["end"] = r["end"];
			m["done"] = r["done"];
			if (r['results'])
				m["results"] = r["results"];
			
			sendMsg(m, addr);
			r = r.next;
		}
	}
}

function sendTests(addr) {
	for(var i in tests) {
		var m = makeTestMsg(name);
		sendMsg(m, addr);
	}
}

function handleAddLock(resp) {
	if(resp['locked'] == null) 
			resp['locked'] = false;
	// dont add/overwrite if exists?		
	var lock = retrieveLock(resp);
		
	if (resp['type'] == "test" ) {//&& (lock == null || lock.addr == null)) {
		var locks = getLockType(resp['type']);
		
		
		locks[resp['name']] = resp
		if(resp['running'] == true) {
			runningTests.push(resp['name']);
		}
	} else if(resp['type'] == "range" && lock.addr == null) {
		log("Add range lock: " + resp['name'] + " (" + resp['start'] + " to " + resp['end'] + ") : ");
		var lock = getRangeLock(resp['name'], resp['start'], resp['end']);
		
		lock.addr = resp['addr'];
		lock.locked = resp['locked'];
		lock.done = resp['done'];
		lock.results = resp['results'];
		lock.name = resp['name'];
	}
}

function packArray(arr) {
	if (arr instanceof Object) {
		var str = "{";
		for(var i in arr) {
			str += i +":" + packArray(arr[i]) + ",";
		}
		str += "}";
		return str;
	
	} else {
		if (arr == null)
			return "";
		return arr.toString();
	}
}

function unpackArray(str) {
	if (str[0] == "{") {
		var arr = new Object();
		//log2(str);
		str = str.substring(1,str.length-1);
		//log2(str);
		var parts = str.split(",");
		for (var i=0; i < parts.length;i++) {
			if (parts[i] == "")
				continue;
			var iparts = parts[i].split(":");
			arr[iparts[0]] = iparts[1];
		}
		return arr;
	} else if (str == "true") 
		return true;
	else if (str == "false")
		return false;
	else 
		return Number(str);

}

function unpackResults(str) {
	log('UNPACK: "' + str + '"');
	var arr = new Array();
	var parts = str.split(", ");
	for(var i =0; i<parts.length; i++) {
		log(parts[i]);
		if (parts[i] == "")
			continue;
		var iparts = parts[i].split("=", 2);
		log(iparts[0] + " AND " + iparts[1]);
		var item = new Object();
		item.key= Number(iparts[0]);
		item.value =unpackArray(iparts[1]);
		arr.push(item);
	}
	return arr;
}

function packResults(res) {
	str = '';
	if (res == null)
		return "";
	for (var i=0; i<res.length; i++) {
		str += res[i].key + "=" + packArray(res[i].value);
		if (i < res.length-1)
			str+= ", ";
		
	}
	return str;
}

function packObject(m, defaultValue) {
	
	var str = "";
	for (var i in m) {
		if (m[i] == '')
		{
			if (defaultValue == null) {
				continue;
			} else {
				m[i] = defaultValue;
			}
		}
		if (i == "results") {
			str += i + "=" + packResults(m[i]) + "\n";
		} else {
			str += i + "=" + m[i] + "\n";
		}
	}
	return str;
}

function removeNodeRangeLocks(addr) {
	for(var i in rangeLocks) {
		log("freeing " + i + " locks");
		var rlock = rangeLocks[i];
		while (rlock != null) {
			log("looking at " + i + " (" + rlock.start + " to " + rlock.end + ") : " + rlock.addr);
			if (rlock.addr == addr) {
				log("unlocking");
				rlock.locked = false;
			}
			rlock = rlock.next;
		}
	}
}

function disconnect(addr) {
	log("--------------DISCONNECT " + addr + "----------");
	// remove from connections list
	for (var i=0; i < connections.length; i++) {
		if (connections[i] == addr) {
			connections.splice(i,1);
			numberOfNodes--;
			break;
		}
	}
	// remove locks
	// test locks
	for(var i in testLocks) {
		var lock = testLocks[i];
		if (!lock)
			continue;
		if (lock.addr == addr) {
			lock.locked = false;
		}
	}
	//  range locks
	removeNodeRangeLocks(addr);

	log("disconnect: freeing range locks");
	
	
}

function sendMsg(m, addr, time, mtype) {
	if(time == null) 
		time = dlc;
	if(mtype == null)
		mtype = "single";

	var str = "cmd=sendMsg\n";
	str += "origin=" + localNodeAddr + "\n";
	str += "dlc=" + time + "\n";
	str += "mtype=" + mtype + "\n";
	
	str += packObject(m);
	
	str += "\n";
	log ("SEND: " + str);
	
	var http = ajaxConnect();
	var retfn = returnfn(http, null,
		function() {
			disconnect(addr);
			announceDead(addr);
			log("ERROR> message to " + addr + " not delivered: '" + str + "'");
		});
		
	ajaxSend(http, str, retfn, addr);
}

function bcastMsg(m) {
	if (m.query != "heartBeat")
		dlc ++;
	//log("BCAST conLen: " + connections.length);
	for(var i=0; i < connections.length; i++) {
		log("bcast "+ i +" " + m.query + " to " + connections[i] + " dlc:" + dlc);
		sendMsg(m, connections[i], dlc, "bcast");
		//log("back from sendMsg, i:" + i + " conlen:" + connections.length);
	}

}

function encodeStr(str) {
	var res="";
	for(i=0; i<str.length;i++) {
		if(str[i] == "$")
			res += "$$";
		else if(str[i] == "\n")
			res += "$n";
		else if(str[i] == "\r")
			res += "$r";
		else if(str[i] == "=")
			res += "$e"
		else
			res += str[i];
	}
	return res;
}

function decodeStr(str) {
	var res = "";
	if (!str)
		return "";
	for(i=0; i<str.length; i++) {
		if(str[i] == "$") {
			i++;
			if(str[i] == "$")
				res += "$";
			else if(str[i] == "n")
				res += "\n";
			else if(str[i] == "r")
				res += "\r";
			else if(str[i] == "e")
				res += "=";
		} else
			res += str[i];
	}	
	return res;
}

function addMsg(resp) {
	//log("addMsg mdlc: " + resp.dlc + " mqlen: " + msgQ.length + " type:" + resp.mtype + " query: " + resp.query);
	for(var i=msgQ.length-1; i>=0; i--) {
		//log("resp.dlc:" + resp.dlc + " msgQ[i].dlc:" + msgQ[i].dlc);
		if (resp.dlc > msgQ[i].dlc) {
			//log("moving " + i + " to " + (i+1));
			msgQ[i+1] = msgQ[i];
		} else {
			//log("first, moving " + i + " to " + (i+1));
			//msgQ[i+1] = msgQ[i];
			//log("putting msg in " + i + " + 1, RETURN");
			msgQ[i+1] = resp;
			return;
		}
	}
	//log("END: putting msg in 0");
	msgQ[0] = resp;
}

function queueMsgs(mstr) {
	log("GOT messages: " + mstr);
	var resps = mstr.split("\n\n");
	for(var rcount=0; rcount < resps.length; rcount++) {
		var resp = resps[rcount];
		if (resp == "") 
			continue;
		resp = parseResp(resp);
		if (resp.query == "DLC") {
			dlc = resp.dlc;
			//var m = new Object();
			//m.query = "DLC";
			//bcastMsg(m);
		} else {
			addMsg(resp);
		}
	}
	var s = "QUEUE: "
	for (var i =0; i< msgQ.length; i++) {
		s += msgQ[i].dlc + " ";
	}
	log(s);
	log("mqlen:" + msgQ.length);
	while (msgQ.length>0 && ((msgQ[msgQ.length-1].dlc - 1) <= dlc)) {
		var r = msgQ.pop();
		log("mdlc: " + r.dlc + " dlc: " + dlc + " mqlen: " + msgQ.length + " type:" + r.mtype + " query: " + r.query);
		if (r.mtype == "bcast")
			dlc = r.dlc
		processMsg(r);
	}
			
}

function processMsg(resp) {
	if (resp["query"] == "join_network") {
		log("Message join_network received");
		handleNewNode(resp, true);
	} else 	if (resp["query"] == "welcome") {
		handleWelcome(resp);
	} else if (resp["query"] == "newNode") {
		//addConnection(resp["addr"]);
		handleNewNode(resp, false);
	} else if (resp["query"] == "deadNode") {
		disconnect(resp["addr"]);
	} else if(resp["query"] == "addLock") {
		handleAddLock(resp);
	} else if (resp["query"] == "getLock") {
		handleLockReq(resp);
	} else if (resp["query"] == "lockResp") {
		handleLockResp(resp);
	} else if (resp["query"] == "releaseLock") {
		handleLockRelease(resp);
	} else if(resp["query"] == "testUpdate") {
		handleTestUpdate(resp);
	} else if(resp["query"] == "activateJob") {
		handleActivateJob(resp);
	} else if(resp["query"] == "results") {
		handleResults(resp);
	} else if(resp["query"] == "new_network") {
		join_network(resp["addr"], false);
	} else if(resp["query"] == "heartBeat") {
		// do nothing;
		return;
	} else {
		log("Error: Unkown message '" + resp["query"] + "' received from " + resp["origin"]);
	}
}
	
function handleWelcome(resp) {
	log("Welcome message received");
	//var myNodes = genNodeList();
	addConnection(resp['origin']);
	if (resp['addrList']){
		var nodes = resp['addrList'].split(",");
		for(var i=0; i < nodes.length; i++){	
			addConnection(nodes[i]);
		}
	}
	
	for(var i =0; i< connections.length; i++) {
		var addr = connections[i];
		if (addr == localNodeAddr)
			continue;
		
		advertiseNewNode(addr);
	}
}	
	
function extractHost(url) {
	//  http: or file:
	var host = url.substr(5);
	while (host.charAt(0) == '/')
		host = host.substr(1);
	var end = host.indexOf('/');
	
	if (end > 0 ) 
		host = host.substr(0, end);
	return host;

}


function setStatus() {
	document.getElementById('numberOfNodes').innerHTML = "Number of nodes: " + numberOfNodes;
	document.getElementById('connections').innerHTML = "Connected to: " + genNodeList();
	document.getElementById('dlc').innerHTML = "DLC: " + dlc;
}

function init() { 
	if (connected == false)
		return;
	numberOfNodes = 1;
	if (originURL.substr(0, 4) == "file") {
		// we are the first
		//numberOfNodes = 1; // ourself
		log("Loaded from file");
		// now we sit and wait
	} else { // it was http, so we were loaded from another node
		log("Loaded from " + originURL);
		originAddress = extractHost(originURL);
		
		join_network(originAddress);
	}
		    
}

function parseResp(resp) {
	var lines = resp.split("\n");
	var arr = [];
	for(var i=0; i < lines.length; i++) {
		//var kv = lines[i].split("=");
		var mid = lines[i].indexOf("=");
		var kv = new Array();
		kv[0] = lines[i].substring(0, mid);
		kv[1] = lines[i].substring(mid+1);
		if (kv[1] == "true")
			kv[1] = true;
		else if(kv[0] == "false")
			kv[1] = false;
		else if (kv[0] == "dlc" ||
			kv[0] == "start" ||
			kv[0] == "end" ||
			kv[0] == "min" ||
			kv[0] == "max") 
			kv[1] = Number(kv[1]);
		else if (kv[0] == "results")
			kv[1] = unpackResults(kv[1]);
		arr[kv[0]] = kv[1];
	}
	return arr;
}


function join_network(address, bcast) {
	if(bcast == null) {
		bcast = true;
	}
	log("join_network -> " + address);
	var m = new Object();
	m["query"] = "join_network";
	m["addr"] = localNodeAddr;
	//m["nodes"] = genNodeList();
	sendMsg(m, address);
	if (bcast == true) {
		m = new Object();
		m["query"] = "new_network";
		m["addr"] = address;	
		bcastMsg(m);
	}
}


	
var runCount = 0;
var workMod = 1;
var maxWorkChange = 10;

// every 100 milisseconds
function cron() {
	if (connected == false)
		return;
		
	
	if(runCount % workMod == 0)	{
		
		var dateObj = new Date();
		var workStart = dateObj.getTime();
		
			
		doWork();
		dateObj = new Date();
		var workEnd = dateObj.getTime();
		
		
		if (isWorking == true) {
			var newWorkMod = Math.abs(workEnd-workStart+1);
			//log2("newWorkMod: " + newWorkMod);
			if (newWorkMod - workMod > maxWorkChange) {
				workMod = workMod + maxWorkChange;
			} else if(workMod - newWorkMod > maxWorkChange){
				workMod = Math.abs(workMod - maxWorkChange);
			} else {
				workMod = newWorkMod;
			}
		} else {
			workMod = 10;
		}
		//log("workMod: " + workMod + " start: " + workStart + " end: " + workEnd + " delta: " + (workEnd-workStart));
		//log("workPos: " + workMapPos + " workMod: " + workMod + " delta: " + (workEnd-workStart));
		//log("workMod: " + workMod);
		// map or reduce
		//var lock = myLocks["range"];
		//if (workMapPos <= lock.end) 
		
	}
	
	/*if (runCount % 5 == 0) {
		getMsgs();
	}*/
	
	// once a second
	if (runCount % 10 == 0) {
		ping();
		//getLog();
		setStatus();
		checkLocks();
		updateTestsStatus();
		if ( doneStage == 1 ) {
			updateFinalResults();
		
		}
	}
	
	if (runCount % 20 == 0) {
		lookForWork();
	}
	
	if (runCount % 40 == 0) {
		updateJobsList();
	}
	
	if (runCount % 20 == 0)
		updateResults();
		
	if (runCount % 300 == 0)
		heartBeat();	
	
	// 180 seconds, or 3 minutes
	if (runCount == 1800)
		runCount = 0;
	runCount++;
}

function heartBeat() {
	var m = new Object();
	m.query = "heartBeat";
	bcastMsg(m);
}

function reloadHTML() {
	var http = ajaxConnect();
	var retfn = returnfn(http);
	ajaxSend(http, "cmd=reloadHTML\n\n",retfn);
}

function poke() {
	var http = ajaxConnect();
	document.getElementById("pingNodeResp").innerHTML = "";
	var dest = document.getElementById("pingNode").value;
	
	var retfn = returnfn(http, 
		function(resp) {
			document.getElementById("pingNodeResp").innerHTML += resp + "<br>";
			var retfn = returnfn(http, 
				function(resp) {
					document.getElementById("pingNodeResp").innerHTML += resp + "<br>";
				});
			ajaxSend(http, "cmd=greet\n\n", retfn, dest);
		});
		
	ajaxSend(http, "cmd=ping\n\n", retfn, dest);
	
	
}

function setTestStatus(str, col) {
	var e = document.getElementById('testStatus');
	e.innerHTML = str;
	if (col == null)
		col = "white";
	e.style.backgroundColor = col;
	setJobStatus(str, col);
}

function getLockType(type) {
	if (type == "test") 
		return testLocks;
	else if(type == "range")
		return rangeLocks;
	else 
		return null;
}

function isLocked(lockType, lockName, start, end) {
	if (lockType == "range") {
		var rlock = getRangeLock(lockname, start, end);
		if (rlock)
			return rlock.locked;
		else return false;
	} else {
		var locks = getLockType(lockType);
		if (locks[lockName] == null)
			return false;
		else if(locks[lockName].locked != true)
			return false;
		else
			return true;
	}
}


function loadTest(tname) {
	if(tname == '') {
		setTestStatus("Invalid name '" + tname + "'", "red");
		return false;
	}
	if(tname.indexOf(' ') >= 0) {
		setTestStatus("Invalid name '" + tname + "', Cannot contain spaces", "red");
		return false;
	}
	
	if(isLocked("test", tname)) {
		setTestStatus("Test '" + tname + "' locked by " + testLocks[tname].addr, "red");
		return false;
	}
	
	
	getLock("test", tname);
	
}

function retrieveLock(struct) {
	if (struct.type == "test") {
		return testLocks[struct.name];
	} else if(struct.type == "range") {
		var l = getRangeLock(struct.name, struct.start, struct.end);
		if (l == null) 
			error("retrieveLock for range got null");
		return l;
	}
}

function getLock(type, name, start, end) {
	var locks = getLockType(type);
	var lockTry;
	
	if (type == "test") {
		if(locks[name] == null) {
			locks[name] = new Object();
		}
		lockTry = locks[name];
	} else if (type == "range") {
		lockTry = getRangeLock(name, start, end);
		if (!lockTry) {
			error("Cannot get range lock in getLock(" + name +", "+ start + ", " + end + ")");
			return;
		}
	}
	
	
	lockTry.time = getTime();
	lockTry.dlc = dlc+1;
	lockTry.name = name;
	lockTry.addr = localNodeAddr;
	lockTry.type = type;
	lockTry.okay = 1;
	lockTry.locked = false;
	var d = new Date();
	lockTry.localTime = d.getTime();
	
	if (type == "test") {
		lockTrys[type] = lockTry;
		setTestStatus("Acquiring lock for test '" + name + "' " + lockTry.okay + "/" + numberOfNodes + "...", "yellow");
	} else if (type == "range") {
		lockTrys[type] = lockTry;
		setWorkStatus("Acquiring lock for work '" + name + "' (" + lockTry.start + " to " + lockTry.end + ")" + lockTry.okay + "/" + numberOfNodes + "...", "yellow");
	} 
	
	
	
	var m = new Object();
	m["query"] = "getLock";
	m["type"] = type;
	m["name"] = name;
	m["time"] = lockTry.time;
	m["addr"] = localNodeAddr;
	if (type == "range") {
		m["start"] = start;
		m["end"] = end;
	}
	bcastMsg(m);
}

function lessTime(t1, t2) {
	var t1a = t1.split(":");
	var t2a = t2.split(":");
	if(t1a[0] < t2a[0]) 
		return true;
	else if (t1a[0] == t2a[0] && t1a[1] < t2a[1])
		return true;
	else if (t1a[0] == t2a[0] && t1a[1] == t2a[1] && t1a[2] < t2a[2])
		return true;
	return false;
}

function cameFirst(a, b) {
	log("cameFirst? " + a.dlc + ":" + a.addr + " and " + b.dlc + ":" + b.addr);
	if (b.dlc == null)
		return true;
	if (a.dlc < b.dlc)
		return true;
	else if (a.dlc > b.dlc)
		return false;
	else {
		var aip = a.addr.split(":")[0].split(".");
		aip[4] = a.addr.split(":")[1];
		var bip = b.addr.split(":")[0].split(".");
		bip[4] = b.addr.split(":")[1];
		for(var i = 0; i < 5; i++) {
			if (aip[i] < bip[i])
				return true;
			else if (aip[i] > bip[i])
				return false;
		}
		alert("cameFirst: SAME NODE?");
		log("cameFirst: SAME NODE?");
		return false;
	}

}

function locksEqual(a, b) {
	if(a == null || b == null)
		return false;
	if(a['type'] != b['type'])
		return false;
	if(a['type'] == 'test') {
		if (a.name == b.name)
			return true;
		else
			return false;
	} else if (a['type'] == "range") {
		log("locksEqual? " + a.name + " (" + a.start + " to " + a.end + ") and " + b.name + " (" + b.start + " to " + b.end + ")");
		if (a.name == b.name && a.start == b.start && a.end == b.end) {
			log("true");
			return true;
		} else {
			log("false");
			return false;
		}
	}
}


function grantLock(resp, lock) {
	removeNodeRangeLocks(resp.origin);
	lock.name = resp['name'];
	lock.addr = resp['origin'];
	lock.time = resp['time'];
	lock.dlc = resp['dlc'];
	lock.locked = true;
	//locks[lock.name] = lock;
	respLockReq(resp, "okay");
}

function denyLock(resp, lock) {
	respLockReq(resp, lock.addr);
}

function handleLockReq(resp) {
	var locks = getLockType(resp["type"]);
	var lock;
	
	if(resp['type'] == "test") {
		lock = locks[resp["name"]];
	} else if(resp['type'] == "range") {
		lock = getRangeLock(resp['name'], resp['start'], resp['end']);
		if (lock == null) {
			// error?
			respLockReq(resp, "no");
			return;
		}
	}
	
	var tryLock = lockTrys[resp['type']];
	
	if (lock == null) { 
	// can only be for test, null for range is error
		testLocks[resp['name']] = resp;
		grantLock(resp, testLocks[resp['name']]);
	} else { // we have it
	
		// is it?
		// locked, or we are trying for it
		// was it JUST locked and not confirmed, did this req COME FIRST? 
		if (lock.locked == true) {
			if (lock.addr == resp.origin) {
				grantLock(resp, lock);
			} else
			if (cameFirst(resp, lock) || resp.origin == lock.addr) {
				log("true");
				grantLock(resp, lock);
			} else {
				log("false");
				denyLock(resp, lock);
			}
		} else if (locksEqual(tryLock, lock)) {
			if (cameFirst(resp, tryLock)) {
				// we lost
				log("true");
				lockTrys[resp['type']] = null;
				grantLock(resp, lock);
			} else {
				//we win
				log("false");
				denyLock(resp, lock);
			}
		} else {
			grantLock(resp, lock);
		}
	}
}
			 
			
		
	
	/* 	1) if we dont have a record of the lock, GRANT
	 *	2) if we have a record of it
	 *		2.1) is it locked or tring to be locked (us or other)?
	 *			2.1.1) yes? was our request BEFORE?
	 *				2.1.1.1) yes? GRANT
	 *				2.1.1.2) no? NO?
	 *			2.1.2) no? GRANT
	 */	
	
	/*if (lock == null ||
		(lockTrys[resp['type']] && lockTrys[resp['type']].name == resp['name'] && cameFirst(resp, lock)) || 
		((!lockTrys[resp['type']] || lockTrys[resp['type']].name != resp['name']) && lock.locked == false)) {*/
	/*	if (rlock == null ||
			(rlock.locked == false && locksEqual(rlock, tlock) != true) ||
			(locksEqual(rlock, tlock) == true && cameFirst, resp, rlock)) {
	
		//lock = new Object();
		rlock.name = resp['name'];
		rlock.addr = resp['origin'];
		rlock.time = resp['time'];
		rlock.dlc = resp['dlc'];
		rlock.locked = true;
		//locks[lock.name] = lock;
		respLockReq(resp, "okay");
	} else {
		respLockReq(resp, rlock.addr);
	}
}*/

function handleLockResp(resp) {
	if (!lockTrys[resp['type']])
		return;

	var tryLock = lockTrys[resp['type']];
	var lock = retrieveLock(resp);
	if( !locksEqual(tryLock, lock)) 
		return;
	else if (tryLock.time != lock.time)
		return;
		
		
	if (resp['resp'] == "okay") {
		lock.okay++;
		
		if (resp['type'] == "test") 
			setTestStatus("Acquiring lock for test '" + lock["name"] + "' " + lock.okay + "/" + numberOfNodes + "...", "yellow");
		else if(resp['type'] == "range")
			setWorkStatus("Acquiring lock for work '" + lock.name + "' (" + lock.start + " to " + lock.end + ")" + lock.okay + "/" + numberOfNodes + "...", "yellow");
	} else {
		if (resp['resp'] != 'no'){
			lock.locked = true;
			lock.addr = resp.resp;
		}
		if (resp['type'] == "test") 
			setTestStatus("Test '" + lock['name'] + "' locked by " + resp['resp'], "red");
		else
			setWorkStatus("Work '" + lock.name + "' (" + lock.start + " to " + lock.end + ") locked by " + resp['resp'], "red");
		
		
		lockTrys[resp['type']] = null;
		runTry = null;
	}

}



function openTest(name) {
	if (!tests[name])
		tests[name] = new Array();
	var map = "";
	if (tests[name]['mapfn'])	
		map = tests[name]['mapfn'];
	var reduce = "";
	if (tests[name]['reducefn']) 
		reduce = tests[name]['reducefn'];
	document.getElementById('mapEditor').value = map;
	document.getElementById('reduceEditor').value = reduce;

}

function lockGranted(type, lock) {
	if (type == "test") {
		setTestStatus("Lock for '" + lock['name'] + "' Granted!", "lime");
	} else if (type == "range") {
		setWorkStatus("Lock for work " + lock.name + " (" + lock.start + " to " + lock.end + ") Granted!", "lime");
	}
	
	myLocks[lock['type']] = lock;
	
	if (type == "test") {
		if (runTry != null && lock['name'] == runTry) {
			runTry = null;
			startJobRun(lock);
	
		} else {
	
			openTest(lock['name']);
	
			document.getElementById('mapEditor').readOnly = false;
			document.getElementById('reduceEditor').readOnly = false;
			document.getElementById('testLoad').disabled = true;
			document.getElementById('testRelease').disabled = false;
		}
	} else if (type == "range") {
		compileTest(lock.name);
	}
}

function checkLocks() {
	var timeOut = Math.max(5000, Math.min(15000, numberOfNodes*1500));
	var d = new Date();
	if (lockTrys["test"]) {
		var lock = lockTrys["test"];
		
		if (d.getTime() - lock.localTime > timeOut) {
			// reclaime
			//log2("RECLAIMING LOCK!!")
			myLocks["test"] = lockTrys["test"];
			lockTrys["test"] = null;
			setTestStatus("Lock acquire timed out", "red");
			releaseLock("test");
		} else
		
		if (lock["okay"] >= numberOfNodes) {
			lock["locked"] = true;
			lockGranted("test", lock);
			lockTrys["test"] = null;
		}
		
	}
	if (lockTrys["range"]) {
		var lock = lockTrys["range"];
		//log2("rangeLocks: " + d.getTime() + " - " + lock.localTime + " = " + (d.getTime() - lock.localTime));
		if (d.getTime() - lock.localTime > timeOut) {
			// reclaime
			//log2("RECLAIMING LOCK!!")
			myLocks["range"] = lockTrys["range"];
			lockTrys["range"] = null;
			setJobStatus("Lock acquire timed out", "red");
			releaseLock("range");
		} else
		if (lock["okay"] >= numberOfNodes) {
			lock["locked"] = true;
			lockGranted("range", lock);
			lockTrys["range"] = null;
		}
	}
}

function respLockReq(resp, ret) {
	var m = new Object();
	m.query = "lockResp";
	m.resp = ret;
	m.name = resp["name"];
	m.type = resp["type"];
	if (resp.type == "range")
	{
		m.start = resp.start;
		m.end = resp.end;
	}
	sendMsg(m, resp["origin"]);
}

function updateTestsStatus() {
	var e = document.getElementById("testsList");
	var str = "";
	for(name in testLocks) {
		var lock = testLocks[name];
		var d = '<div style="background-color: ';
		if (lock.locked == true)
			if (lock.running) 
				d += "orange";
			else
				d += "red";
		else 
			d += "lime";
		d += '">' + lock.name;
		if (lock.locked) {
			if (lock.running)
				d += " running... ";
			else
				d += " locked by " + lock.addr;
		}
		d += "</div>";
		str += d;
		
	}
	e.innerHTML = str;
}

function updateJobsList() {
	var e = document.getElementById("jobsList");
	var str = '<table>';
	str += '<tr><th rowspan="2">Test</th><th colspan="2">Range</th><th rowspan="2">Items/Job</th><th rowspan="2">Go</th></tr>';
	str += "<tr><th>Min</th><th>Max</th></tr>";
	for(name in tests) {
		var lock = testLocks[name];
		var d = '<tr style="background-color: ';
		if(lock.locked == true)
			if (lock.running)
				d += "orange";
			else
				d += "red";
		else
			d += "lime";
		d += '"><td>' + lock.name+"</td>";
		var val = 1;
		var elem = document.getElementById(name+'_min');
		if (elem) val = elem.value;
		d += '<td><input size="8" id="' + lock.name + '_min" value="'+val+'"';
		if (lock.locked) d+= ' readonly="true" ';
		d += '/ ></td>';
	 	val = 101;
		elem = document.getElementById(name+'_max');
		if (elem) val = elem.value;
		d += '<td><input size="14" id="' + lock.name + '_max" value="'+val+'"';
		if (lock.locked) d += ' readonly="true" ';
		d += '/ ></td>';
		val = 10;
		elem = document.getElementById(name+'_perjob');
		if (elem) val = elem.value;
		d += '<td><input size="6" id="' + lock.name + '_perjob" value="'+val+'"';
		if (lock.locked) d += ' readonly="true" ';
		d += '/ ></td>';
		d += '<td><input type="button" value="Start!" onClick="startJob(' + "'" + lock.name + "'" + ');"/ ></td>';
		d += "</tr>";
		str += d;
	}
	str += "</table>";
	e.innerHTML = str;

}

function setJobStatus(str, col) {
	var e = document.getElementById('jobStatus');
	e.innerHTML = str;
	if (col == null)
		col = "white";
	e.style.backgroundColor = col;
}


function startJob(name) {
	var min = Number(document.getElementById(name+'_min').value);
	var max = Number(document.getElementById(name+'_max').value);
	var perjob = Number(document.getElementById(name + '_perjob').value);
	
	if(min == NaN) {
		setJobStatus("No Min Value", "red");
		return;
	}
	if(max == '') {
		setJobStatus("No Max Value", "red");
		return;
	}
	if(max <= min) {
		setJobStatus("Max <= Min", "red");
		return;
	}
	
	if ((max-min) % perjob != 0) {
		setJobStatus("Max not divisible by items/job", "red");
		return;
	}
	var lock = testLocks[name];
	if(lock.locked == true) {
		setJobStatus("Test is locked by " + lock.addr);
		return;
	} 
		

	//setJobStatus("Running ...", "");
	runTry = name;
	getLock("test", name);

}

function startJobRun(lock) {
	var min = Number(document.getElementById(lock.name+'_min').value);
	var max = Number(document.getElementById(lock.name+'_max').value);
	var perjob = Number(document.getElementById(name + '_perjob').value);

	var m = new Object();
	m.min = min;
	m.max = max;
	m.perjob = perjob;
	m.name = lock.name;
	m.query = "activateJob";
	bcastMsg(m);
	handleActivateJob(m);
	setJobStatus(" Running '" + lock.name + "'...", "orange");

}

function handleActivateJob(resp) {
	var locks = getLockType("test");
	var lock = locks[resp['name']];
	lock.running = true;
	lock.min = resp.min;
	lock.max = resp.max;
	lock.perjob = resp.perjob;
	runningTests.push(resp.name);
	doneStage = 0;
	setResultsStatus("Proccessing...", "orange");
	lookForWork();
}

function makeTestMsg(name) {
	var m = new Object();
	m.query = "testUpdate";
	m.name = name;
	m.mapfn = encodeStr(tests[name]["mapfn"]);
	m.reducefn = encodeStr(tests[name]["reducefn"]);
	
	return m;
}

function releaseTestLock() {
	// sync data
	var name = myLocks["test"].name;
	tests[name]["mapfn"] = document.getElementById('mapEditor').value;
	document.getElementById('mapEditor').readOnly = true;
	document.getElementById('mapEditor').value = "";

	tests[name]["reducefn"] = document.getElementById('reduceEditor').value;
	document.getElementById('reduceEditor').readOnly = true;
	document.getElementById('reduceEditor').value = "";	

	m = makeTestMsg(name);
	bcastMsg(m);
	
	document.getElementById('testLoad').disabled = false;
	document.getElementById('testRelease').disabled = true;
	setTestStatus("");
	releaseLock("test");
}

function handleTestUpdate(resp) {
	if (!tests[resp['name']])
		tests[resp['name']] = new Array();
	tests[resp['name']]['mapfn'] = decodeStr(resp["mapfn"]);
	tests[resp['name']]['reducefn'] = decodeStr(resp["reducefn"]);
}

function releaseLock(type) {
	var lock = myLocks[type];
	myLocks[type] = null;
	
	lock.locked = false;
	
	
	var m = new Object();
	m["query"] = "releaseLock";
	m["type"] = type;
	m["name"] = lock.name;
	if (type == "range") {
		m["start"] = lock.start;
		m["end"] = lock.end;
	}
		
	bcastMsg(m);
}

function handleLockRelease(resp) {
	//var locks = getLockType(resp['type']);
	//locks[resp['name']].locked = false;
	var lock = retrieveLock(resp);
	if (lock) 
		lock.locked = false;
}

function setWorkStatus(str, col) {
	var e = document.getElementById('workStatus');
	e.innerHTML = str;
	if (col == null)
		col = "white";
	e.style.backgroundColor = col;
}

var workSize = 10;

function lookForWork() {
	//log("Look For Work!");
	if (myLocks["range"] != null || lockTrys["range"] != null) {
		// have work
		return;
	}
	if (runningTests.length > 0) {
		for (var i =0; i < runningTests.length; i++) {
			var lock = testLocks[runningTests[i]];
			//log("Looking for work in test " + lock.name);
			// hilarious test
			//workSize = Math.round(Math.random()*25);
			var rlock = makeRangeLock(lock, lock['perjob'])
			if (rlock) {
				getLock("range", lock.name, rlock.start, rlock.end);
				return;
			} 
			// else no work left in this task
		}
		setWorkStatus("No work available (All tasks' work is locked and/or done)");
		doneStage = 1;
	} else {
		setWorkStatus("No work available (no running tests)");
	}
}

function getRangeLock(name, start, end) {
	log("getRangeLock for (" + name + " from " + start + " to " + end + ")");
	if (!rangeLocks[name]) {
		log("no locks for " + name + ", so MAKING");
		rangeLocks[name] = genRangeCell(name, start, end);
		return rangeLocks[name];
	} 
	log("locks exist for " + name + " so SEARCHING forward");
	var rlock = rangeLocks[name];
	
	if(end < rlock.start) {
		log("Space before first lock, MAKING HERE");
		rangeLocks[name] = genRangeCell(name, start, end);
		rangeLocks[name].next = rlock;
		return rangeLocks[name];
	}
	
	while(rlock.next && rlock.start < rlock.end+1) {
		log("looking at (" + rlock.start + " to " + rlock.end + ")");
		if (rlock.start == start && rlock.end == end) {
			log("FOUND IT");
			return rlock;
		}
		
		if (rlock.end < start && rlock.next.start > end) {
			log("GAP between (" + rlock.start + " to " + rlock.end + ") and (" + rlock.next.start + " to " + rlock.next.end + ") where we should be so MAKING THERE");
			var nlock = rlock.next;
			rlock.next = genRangeCell(name, start, end);
			rlock.next.next = nlock;
			return rlock.next;
		}
		
		rlock = rlock.next;
	}
	log("SEARCH ended");
	if (rlock.start == start && rlock.end == end) {
		log("Found it!");	
		return rlock;
	} else {
		if (rlock.end < start) {
			log("There is space at the end to make what we want");
			rlock.next = genRangeCell(name, start, end);
			return rlock.next;
		} else {
			log("search ended after what we wanted, FAIL");
			return null;
		}
	}
	
}

function makeRangeLock(tlock, suggestedSize) {
	suggestedSize--;
	
	//log("makeRangeLock or size " + suggestedSize + " for (" + tlock.name + " from " + tlock.min + " to " + tlock.max + ")");
	
	if (!rangeLocks[tlock.name]) {
		//log("no locks for " + tlock.name + ", so MAKING");
		
		rangeLocks[tlock.name] = genRangeCell( tlock.name, tlock.min,
				 Math.min(tlock.max, tlock.min + suggestedSize));
		return rangeLocks[tlock.name];
	} else {
		//log("some locks for " + tlock.name + " so FINDING");
		var rlock = rangeLocks[tlock.name];
		if (rlock.start > tlock.min) {
			//log("First lock is above min, so making NEW lock before it to use");
			rangeLocks[tlock.name] = genRangeCell(tlock.name, Math.max(tlock.min, rlock.start-1-suggestedSize), rlock.start-1);
			rangeLocks[tlock.name].next = rlock;
			return rangeLocks[tlock.name];
		}
		//log("Searching linked list forward");
		while (rlock.next && (rlock.locked || rlock.done)) {
			//log("looking at (" + rlock.start + " to " + rlock.end + ")");
			if (rlock.next.start != rlock.end +1) {
				//log("GAP between (" + rlock.start + " to " + rlock.end + ") and (" + rlock.next.start + " to " + rlock.next.end + ") so MAKING THERE");
				var nlock = rlock.next;
				rlock.next = genRangeCell(tlock.name, rlock.end+1, 
					Math.min(rlock.end+1+suggestedSize, nlock.start-1));
				rlock.next.next = nlock;
				return rlock.next;
			}
			rlock = rlock.next;
		}
		//log("DONE searching");
		if (rlock.locked || rlock.done) {
			//log("this one not acceptable");
			if (rlock.end == tlock.max) {
				//log ("this one also at end, so FAIL");
				return null
			} else {
				//log("still more room at end, so MAKING at end");
				rlock.next = genRangeCell(tlock.name, rlock.end+1,
				Math.min(tlock.max, rlock.end+1+ suggestedSize));
				return rlock.next;
			}
		} else {
			//log("this one acceptable (" + rlock.start + " to " + rlock.end + ")");
			return rlock;
		}
	}
}

function genRangeCell(name, start, end, next) {
	var c = new Object();
	c.name = name;
	c.start=start;
	c.end = end;
	c.next = next;
	c.locked = false;
	c.done = false;
	return c;
}

var map_test = null;
var reduce = null;
function compileTest(name) {
	mapCode = tests[name].mapfn;
	mapCode = "map_test = function(value) {\n" + mapCode + "\n}";
	//log("evaling: " + mapCode);
	try {
		eval(mapCode);
	} catch (exception) {
		error("compiling map_test: " + exception);
	}
	
	reduceCode = tests[name].reducefn;
	reduceCode = "reduce = function(list) {\n" + reduceCode + "}\n";
	//log("evaling: " + reduceCode);
	try {
		eval(reduceCode);
	} catch (exception) {
		error("compiling reduce: " + exception);
	}
}


var workMapPos = null;
var workReduce = false;
var workList = new Array();
var isWorking = false;
function doWork() {
	if (myLocks["range"]) {
		isWorking = true;
		var lock = myLocks["range"];
		//log("Acquired work...");
		setWorkStatus("Working on " + lock.name + " set (" + lock.start + " to " + lock.end + ")...", "lime");
		if (workMapPos == null)
			workMapPos = lock.start;

		if (workMapPos <= lock.end) {
			//log("Mapping on " + workMapPos);
			var item = new Object();
			item.key = workMapPos;
			item.value = map_test(workMapPos);
			//log("result = " + item.value);	
			workList.push(item);
			workMapPos++;
		} else if (workReduce == false) {
			//log("Reducing");

			if(reduce == null)
				error ("no reduce");
			lock.results = reduce(workList);
			
			//log("FINAL RESULTS: " + lock.results);
			
			workMapPos = null;
			workReduce = false;
			workList = new Array();
			
			lock.done = true;
			sendResults(lock);
			releaseLock("range");
			lookForWork();
		}
	} else {
		isWorking = false;
	}
		
}

function sendResults(lock) {
	m = new Object();
	m.query = "results";
	m.name = lock.name;
	m.start = lock.start;
	m.end = lock.end;
	m.results = lock.results;
	
	bcastMsg(m);
}

function handleResults(resp) {
	lock = getRangeLock(resp.name, resp.start, resp.end);
	if(lock == null) {
		error("handleResults: lock == null");
		return;
	}
	log("Setting results for " + resp.name + " (" + resp.start + " to " + resp.end + ") to " + resp.results);
	lock.done = true;
	lock.results = resp.results;
}

function updateResults() {
	var e = document.getElementById("workDiv");
	var str = "";
	//for (var i =0; i < runningTests.length; i++) {
	for (var i in rangeLocks) {
		//var lock = testLocks[runningTests[i]];
		var lock = testLocks[i];
		str += "<i>" + lock.name + " (" + lock.min + " to " + lock.max + "):</i><br>";
		var rlock = rangeLocks[lock.name];
		while(rlock != null) {
			str += '<div style="background-color:';
			if (rlock.locked == false && rlock.done == false)
				str += "red";
			else if(rlock.locked == true && rlock.done != true)
				str += "orange";
			else if(rlock.done == true) 
				str += "lime";
			str += ';">' + rlock.start + "-" + rlock.end + ": ";
			if(rlock.locked == false && rlock.done == false)
				str += "AVAILABLE";
			else if(rlock.locked == true && rlock.done != true)
				str += "being PROCESSED at " + rlock.addr;
			else if(rlock.done == true) {
				//str += "DONE";
				str += packResults(rlock.results);
			}
			str += "</div>";
			rlock = rlock.next;
		}
		str += "<br\/ ><br \/ >";
	}
	e.innerHTML = str;
}

function setResultsStatus(str, col) {
	var e = document.getElementById('resultsStatus');
	e.innerHTML = str;
	if (col == null)
		col = "white";
	e.style.backgroundColor = col;
}

function addArray(a, newRes) {
	if (!newRes)
		return;
	for(var i = 0; i <  newRes.length; i++) {
		a.push(newRes[i]);
	}
}

function updateFinalResults() { 
	
	if (doneStage == 1) { // all locked, make sure all done
		for (var i =0; i < runningTests.length; i++) {
			var lock = testLocks[runningTests[i]];
			var rlock = rangeLocks[runningTests[i]];
			
			while(rlock) {
				if (rlock.done != true)
					return;
				rlock = rlock.next;
			}
		}
		doneStage = 2;
	} 
	// pricess new results
	if (doneStage == 2) {
		setResultsStatus("Final Reducing...", "yellow");
		for(var i=0; i < runningTests.length; i++ ) {
			var lock = testLocks[runningTests[i]];
			var rlock = rangeLocks[runningTests[i]];
			var fr = finalResults[runningTests[i]];
			if (fr)
				continue;
			
			var results = new Array();
			while (rlock) {
				addArray(results, rlock.results);
				rlock = rlock.next;
			}
			results = reduce(results);
			
			finalResults[runningTests[i]] = results;
		}
		doneStage++;
	}
	
	if(doneStage == 3) {
		// all work done!
		setResultsStatus("Done", "lime");
		var e = document.getElementById("resultsDiv");
		var str = "";
		
		//for(var i=0; i < runningTests.length; i++ ) {
		for (i in finalResults) {
			var lock = testLocks[i];
			//var rlock = rangeLocks[i];
			str += "<i>" + lock.name + " (" + lock.min + " to " + lock.max + "):</i><br>";
			var results = finalResults[i];
			
			str += packResults(results);
			str += "<br><br>";
		}
		
		e.innerHTML = str;
	}
}


	

var cronID = setInterval("cron()", 100);
getMsgs();
getLog();


function page(p) {
	// turn off all pages
	document.getElementById("pageHome").style.visibility="hidden";
	document.getElementById("pageTests").style.visibility="hidden";
	document.getElementById("pageJobs").style.visibility="hidden";
	document.getElementById("pageWork").style.visibility="hidden";
	document.getElementById("pageResults").style.visibility="hidden";
	
	// turn on page
	document.getElementById("page"+p).style.visibility="visible";
}

function install_defaults() {
	var m = new Object();
	m.name = "primes";
	m.type = "test";
	m.locked = false;
	handleAddLock(m);
	
	m = new Object();
	m.name = "primes";
	m.mapfn = 'var max = Math.round(Math.sqrt(value));\nfor(var i=2; i <= max; i ++) {\n        if (value % i == 0) \n                return false;\n}\nreturn true;\n';
	
	m.reducefn = 'var r = new Array();\nfor (var i = 0; i < list.length; i++) {\n        if (list[i].value == true)\n                r.push(list[i]);\n}\nreturn r;\n';
	
	handleTestUpdate(m);
}

install_defaults();


	</script>
</head>
<body>

<input type="button" value="Home" onClick="page('Home');">
<input type="button" value="Tests" onClick="page('Tests');">
<input type="button" value="Jobs" onClick="page('Jobs');">
<input type="button" value="Work" onClick="page('Work');">
<input type="button" value="Results" onClick="page('Results');">

<!-- HOME -->

<div id="pageHome" class="page">
<div id="statusLabel"></div>
<div id="numberOfNodes">Number of nodes: <script> document.write(numberOfNodes); </script></div>
<div id="connections">Connected to:</div>
<div id="dlc">DLC:</div>
<input id="networkAddr"><input type="button" value="Join Network" onClick="join_network(document.getElementById('networkAddr').value);"><br/>
<br />
<b>Log:</b> <input type="checkbox"  value="log"  id="logCheck"/>Log?<br/>
<textarea rows="7" cols="60" id="log" readonly="true"></textarea>
<br/>
<input type="button" value="Reload HTML" onclick="reloadHTML()" />		
<br/><br/>
<input type="button" value="Kill Server" onClick="killServer()" />
	
<script>ping();</script>	
</div>



<!-- TESTS -->

<div id="pageTests" class="page">
<b>Tests</b><br/>
Tests:
<div id="testsList"></div>
<br/>
<input id="testName" value="">
<input id="testLoad" type="button" value="Load Test" onClick="loadTest(document.getElementById('testName').value);">
<div id="testStatus"></div>
<br/>map_test (value) {
<textarea rows="10" cols="60" id="mapEditor" readonly="true"></textarea>
<br/>}<br/>
<br/>
<i>// list :: array of hashes with 'key' and 'value' as elements</i><br/>
reduce (list) { 
<textarea rows="10" cols="60" id="reduceEditor" readonly="true"></textarea>
<br/>}<br/>
<br/>
<input id="testRelease" type="button" disabled="true" value="Save/Release" onClick="releaseTestLock()">
</div>


<!-- JOBS -->

<div id="pageJobs" class="page">
<b>Jobs</b><br/><br/>

<div id="jobStatus"></div><br/>
<div id="jobsList">
</div>

</div>

<!-- WORK -->

<div id="pageWork" class="page">
<b>Work</b><br/><br/>

Currently:<br/>
<div id="workStatus"></div>
<br/><br/>
Results:<br/>
<div id="workDiv"></div>


</div>

<div id="pageResults" class="page">
<b>Results</b><br/><br/>
<div id="resultsStatus">Not Proccessing</div>
<div id="resultsDiv"></div>
</div>

<!--- CODE -->
<div id="pageCode" class="page">
</div>

<script>
page("Home");
</script>
</body>
</html>