/// <reference path="jquery/jquery-vsdoc.js" />

var SystemTime;
var SystemUptime = 0;
var SystemID = "";
var SystemUsage1;
var SystemUsage2;
var SystemUsage3;

var systemMap = {};
var idMap = {};
var subTypeMap = {};
var subContextMap = {};
var allTypeList = new Array();
var crankMap = {};
var requestMap = {};
var nodeMap = {};
var spaceMap = {};
var componentMap = {};
var allTriggerList = new Array();
var allPostList = {};
var componentTypes = {
	0 : "",
	1 : "Whiteboard",
	2 : "Catalog",
	3 : "Stream",
	4 : "Feed",
	5 : "Service",
	6 : "Module",
	7 : "External Module"
};
var subscriptions = {};
var allContextList = new Array();
var activeContexts = {};
var inactiveContexts = {};

var NOTYPE = "0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0";

var latestSystemXML = "";


function ToggleShowHideAll(clss) {
	console.log("Toggle all" + clss);
	$('.'+clss).toggle();
}

function ToggleShowHide(id) {
	console.log("Toggle " + id);
	$('#'+id+"_in").toggle();
	$('#'+id+"_out").toggle();
}

function GetPsyTimeAgeUS(t) {
	if (!t.isValid())
		return 0;
	return PsyTimeDiffUS(SystemTime, t);
}

function GetPsyTimeAgeMS(t) {
	if (!t.isValid())
		return 0;
	return PsyTimeDiffMS(SystemTime, t);
}

function PrintPsyTimeAge(t) {
	if (!t.isValid())
		return "N/A";
	return PrintPsyTimeDiff(SystemTime, t);
}

function PrintPsyTimeAgeFull(t) {
	if (!t.isValid())
		return "N/A";
	return PrintPsyTimeDiffFull(SystemTime, t);
}

function PrintPsyTimeAgeApprox(t) {
	if (!t.isValid())
		return "N/A";
	return PrintPsyTimeDiffApprox(SystemTime, t);
}

function GetNodeName(id) {
	if (id == 0)
		return "System";
	else if (!(id in nodeMap))
		return "N/A ("+id+")";
	return nodeMap[id].name;
}

function GetComponentName(id) {
	if (id == 0)
		return "System";
	else if (!(id in componentMap))
		return "N/A ("+id+")";
	return componentMap[id].name;
}

function GetNodeSpaces(id) {
	var result = {};
	for (var spc in spaceMap) {
		var space = spaceMap[spc];
		if (space.node == id)
			result[space.name] = space;
	}
	return result;
}

function GetNodeComponents(id) {
	var result = {};
	for (var comp in componentMap) {
		if ( (id == 0) || (componentMap[comp].node == id) )
			result[component.name] = component;
	}
	return result;
}

function GetComponentID(name) {
	for (var comp in componentMap) {
		if (componentMap[comp].name == name)
			return comp;
	}
	return 0;
}

function GetNodeID(name) {
	for (var id in nodeMap) {
		if (nodeMap[id].name == name)
			return id;
	}
	return 0;
}

function GetComponentNames() {
	var result = new Array();
	for (var comp in componentMap) {
		result.push(componentMap[comp].name);
	}
	return result;
}

function GetCatalogNames() {
	var result = new Array();
	for (var comp in componentMap) {
		var component = componentMap[comp];
		if ( (component.type == 1) || (component.type == 2) )
			result.push(component.name);
	}
	return result;
}

function GetModuleNames() {
	var result = new Array();
	for (var comp in componentMap) {
		var component = componentMap[comp];
		if ( (component.type == 6) || (component.type == 7) )
			result.push(component.name);
	}
	return result;
}

function GetNodeCatalogs(id) {
	var result = {};
	for (var comp in componentMap) {
		var component = componentMap[comp];
		if ( (id == 0) || (component.node == id) ) {
			if ( (component.type == 1) || (component.type == 2) )
				result[component.name] = component;
		}
	}
	return result;
}

function GetNodeModules(id) {
	var result = {};
	for (var comp in componentMap) {
		var component = componentMap[comp];
		if ( (id == 0) || (component.node == id) ) {
			if ( (component.type == 6) || (component.type == 7) )
				result[component.name] = component;
		}
	}
	return result;
}

function GetTypeFromMsg(msg) {
	if (msg.contextchange)
		return "Context: " + GetContext(msg.contextchange);
	else
		return GetType(msg.type);
}

function GetType(type) {
	if (type == NOTYPE)
		return "";
	var text = "";
	var split = type.split('.');
	$.each(split, function (n) {
		if ( (n == 1) && (split[n] == 1) ) {
			return subTypeMap[split[2]];
		}
		else if (split[n] > 0)
			text += subTypeMap[split[n]] + ".";
	});
	//console.log("GetType("+type+"): " + text);
	if (!text.length)
		return "";
	return text.substring(0, text.length-1);
}

function IsContextActive(context) {
	return (!context.length || (context in activeContexts));
}

function GetContext(context) {
	var text = "";
	var split = context.split('.');
	$.each(split, function (n) {
		if (split[n] > 0)
			text += subContextMap[split[n]] + ".";
	});
	//console.log("GetContext("+context+"): " + text);
	if (!text.length)
		return "--ALL--";
	return text.substring(0, text.length-1);
}

function GetContextTriggers(context) {
	var result = new Array();
	for (var key in subscriptions)
		result = result.concat(subscriptions[key].getContextTriggers(context));
	return result;
}

function GetTriggers(compID, isActive) {
	var result = new Array();
	for (var key in subscriptions)
		result = result.concat(subscriptions[key].getTriggers(compID, isActive));
	return result;
}


















// Object definitions
function SubType (node) {
	if (typeof node != "undefined") {
		this.id = node.attr("id");
		this.name = node.attr("name");
		this.status = node.attr("status");
		this.time = new PsyTime(node.attr("time"));
	}
	this.toString = function() {
		return this.name;
	};
	this.getHTMLHeader = function() {
		return "<tr><th>ID</th><th>Name</th><th>Status</th><th>Created</th></tr>";
	};
	this.toHTML = function() {
		return "<tr><td>" + this.id + "</td><td>" + this.name + "</td><td>" + this.status + "</td><td>" + this.time + "</td></tr>";
	};
}

function SubContext (node) {
	if (typeof node != "undefined") {
		this.id = node.attr("id");
		this.name = node.attr("name");
		this.status = node.attr("status");
		this.time = new PsyTime(node.attr("time"));
	}
	this.toString = function() {
		return this.name;
	};
	this.getHTMLHeader = function() {
		return "<tr><th>ID</th><th>Name</th><th>Status</th><th>Created</th></tr>";
	};
	this.toHTML = function() {
		return "<tr><td>" + this.id + "</td><td>" + this.name + "</td><td>" + this.status + "</td><td>" + this.time + "</td></tr>";
	};
}

function Crank (node) {
	this.id = node.attr("id");
	this.name = node.attr("name");
	this.status = node.attr("status");
	this.time = new PsyTime(node.attr("time"));
	this.compid = node.attr("compid");
	this.func = node.attr("function");
	this.library = node.attr("library");
	this.toString = function() {
		return this.name;
	};
	this.getHTMLHeader = function() {
		return "<tr><th>ID</th><th>Name</th><th>Status</th><th>Component</th><th>Function</th><th>Library</th><th>Created</th></tr>";
	};
	this.toHTML = function() {
		return "<tr><td>" + this.id + "</td><td>" + this.name + "</td><td>" + this.status + "</td><td>" + GetComponentName(this.compid) +
			"</td><td>" + this.func + "</td><td>" + this.library + "</td><td>" + this.time + "</td></tr>";
	};
}

function PerfEntry (node) {
	if (typeof node != "undefined") {
		this.from = node.attr('from');
		this.to = node.attr('to');
		this.countpersec1 = node.attr('countpersec1');
		this.valpersec1 = node.attr('valpersec1');
		this.countpersec2 = node.attr('countpersec2');
		this.valpersec2 = node.attr('valpersec2');
		this.countpersec3 = node.attr('countpersec3');
		this.valpersec3 = node.attr('valpersec3');
	}
	this.toString = function() {
		return this.from + "-" + this.to;
	}
	this.toHTML = function() {
		var html = "";

		if (this.countpersec1 > 0)
			html += sprintf("<font size=-3 color=#00AA00><nobr>%s / %.1f</nobr><br></font>\n", SizifyDec(this.valpersec1,1), this.countpersec1);
		else
			html += "<font size=-3 color=#00AA00><nobr>0B / 0</nobr><br></font>\n";
		
		if (this.countpersec2 > 0)
			html += sprintf("<font size=-3 color=#228822><nobr>%s / %.1f</nobr><br></font>\n", SizifyDec(this.valpersec2,1), this.countpersec2);
		else
			html += "<font size=-3 color=#228822><nobr>0B / 0</nobr><br></font>\n";
			
		if (this.countpersec3 > 0)
			html += sprintf("<font size=-3 color=#555555><nobr>%s / %.1f</nobr><br></font>\n", SizifyDec(this.valpersec3,1), this.countpersec3);
		else
			html += "<font size=-3 color=#555555><nobr>0B / 0</nobr><br></font>\n";
		
		return html;
	}
}

function PerformanceStats (node) {
	if (typeof node != "undefined") {
		this.time = node.attr('time');
		this.ms1 = node.attr('ms1');
		this.ms2 = node.attr('ms2');
		this.ms3 = node.attr('ms3');
		this.maxmemory = node.attr('maxmemory');
		this.percentofsystemcpu1 = node.attr('percentofsystemcpu1');
		this.percentofsystemcpu2 = node.attr('percentofsystemcpu2');
		this.percentofsystemcpu3 = node.attr('percentofsystemcpu3');
		this.percentofoscpu1 = node.attr('percentofoscpu1');
		this.percentofoscpu2 = node.attr('percentofoscpu2');
		this.percentofoscpu3 = node.attr('percentofoscpu3');
		this.memorybytesaverage1 = node.attr('memorybytesaverage1');
		this.memorybytesaverage2 = node.attr('memorybytesaverage2');
		this.memorybytesaverage3 = node.attr('memorybytesaverage3');
		this.datainputbytespersec1 = node.attr('datainputbytespersec1');
		this.datainputbytespersec2 = node.attr('datainputbytespersec2');
		this.datainputbytespersec3 = node.attr('datainputbytespersec3');
		this.datainputcountpersec1 = node.attr('datainputcountpersec1');
		this.datainputcountpersec2 = node.attr('datainputcountpersec2');
		this.datainputcountpersec3 = node.attr('datainputcountpersec3');
		this.dataoutputbytespersec1 = node.attr('dataoutputbytespersec1');
		this.dataoutputbytespersec2 = node.attr('dataoutputbytespersec2');
		this.dataoutputbytespersec3 = node.attr('dataoutputbytespersec3');
		this.dataoutputcountpersec1 = node.attr('dataoutputcountpersec1');
		this.dataoutputcountpersec2 = node.attr('dataoutputcountpersec2');
		this.dataoutputcountpersec3 = node.attr('dataoutputcountpersec3');
		this.dataqueuebytesaverage1 = node.attr('dataqueuebytesaverage1');
		this.dataqueuebytesaverage2 = node.attr('dataqueuebytesaverage2');
		this.dataqueuebytesaverage3 = node.attr('dataqueuebytesaverage3');
		this.dataqueuecountaverage1 = node.attr('dataqueuecountaverage1');
		this.dataqueuecountaverage2 = node.attr('dataqueuecountaverage2');
		this.dataqueuecountaverage3 = node.attr('dataqueuecountaverage3');
		this.cyclecountpersec1 = node.attr('cyclecountpersec1');
		this.cyclecountpersec2 = node.attr('cyclecountpersec2');
		this.cyclecountpersec3 = node.attr('cyclecountpersec3');
		this.localsystemcpuusage1 = node.attr('localsystemcpuusage1');
		this.localsystemcpuusage2 = node.attr('localsystemcpuusage2');
		this.localsystemcpuusage3 = node.attr('localsystemcpuusage3');
		this.localsystemmemoryusage1 = node.attr('localsystemmemoryusage1');
		this.localsystemmemoryusage2 = node.attr('localsystemmemoryusage2');
		this.localsystemmemoryusage3 = node.attr('localsystemmemoryusage3');
		this.computercpuusage1 = node.attr('computercpuusage1');
		this.computercpuusage2 = node.attr('computercpuusage2');
		this.computercpuusage3 = node.attr('computercpuusage3');
		this.computermemoryusage1 = node.attr('computermemoryusage1');
		this.computermemoryusage2 = node.attr('computermemoryusage2');
		this.computermemoryusage3 = node.attr('computermemoryusage3');
	}
	this.toString = function() {
		return "Performance Stats";
	};
	this.getHTMLHeader = function() {
		return "";
	};
	this.toHTML = function() {
		return "";
	};
}


function Request (node) {
	this.id = node.attr("id");
	this.name = node.attr("name");
	this.status = node.attr("status");
	this.time = new PsyTime(node.attr("time"));
	this.lastupdate = new PsyTime(node.attr("lastupdate"));
	this.from = node.attr("from");
	this.to = node.attr("to");
	this.remoteid = node.attr("remoteid");
	this.msgid = node.attr("msgid");
	this.msgeol = node.attr("msgeol");

	this.toString = function() {
		return this.name;
	};
	this.getHTMLHeader = function() {
		return "<tr><th>ID</th><th>Name</th><th>Status</th><th>From</th><th>To</th><th>Created</th><th>Last updated</th></tr>";
	};
	this.toHTML = function() {
		return "<tr><td>" + this.id + "</td><td>" + this.name + "</td><td>" + this.status + "</td><td>" + GetComponentName(this.from) +
			"</td><td>" + GetComponentName(this.to) + "</td><td>" + this.time + "</td><td>" + PrintPsyTimeAgeFull(this.lastupdate) + "</td></tr>";
	};
}

function Node (node) {
	this.id = node.attr("id");
	this.name = node.attr("name");
	this.address = node.attr("address");
	this.port = node.attr("port");
	this.group = node.attr("group");
	this.time = new PsyTime(node.attr("time"));
	this.createdTime = new PsyTime(node.attr("created"));
	this.perfStats = new PerformanceStats($stats = node.find('performance:first'));
	this.toString = function() {
		return this.name;
	};
	this.getHTMLHeader = function() {
		return "<tr><th>ID</th><th>Name</th><th>Address</th><th>Port</th><th>Group</th><th>Created</th></tr>";
	};
	this.toHTML = function() {
		return "<tr><td>" + this.id + "</td><td>" + this.name + "</td><td>" + this.address + "</td><td>" + this.port +
			"</td><td>" + this.group + "</td><td>" + this.createdTime + "</td></tr>";
	};
	this.toHTMLTree = function() {
		var html = "<B>" + this.name + "</B><sup>" + this.id + "</sup>";
		html += "<ul>";
		for (var space in spaceMap) {
			//console.log(spaceMap[space].node + " == " +  this.id);
			if (spaceMap[space].node == this.id) {
				html += "<li>" + spaceMap[space].toHTMLTree() + "</li>";
			}
		}
		html += "</ul>";
		//console.log(html + " space keys: " + spaceMap[1]);
		return html;
	};
}

function Space (node) {
	this.id = node.attr("id");
	this.name = node.attr("name");
	this.node = node.attr("node");
	this.status = node.attr("status");
	this.osid = node.attr("osid");
	this.commandline = node.attr("commandline");
	this.time = new PsyTime(node.attr("created"));
	this.lastseen = new PsyTime(node.attr("lastseen"));
	this.stats = new ComStats($stats = node.find('processstats:first'));
	this.perfStats = new PerformanceStats($stats = node.find('performance:first'));
	this.stats.parentName = nodeMap[this.node]+"."+this.name;
	
	this.toString = function() {
		return this.name;
	};
	this.getHTMLHeader = function() {
		return "<tr><th>ID</th><th>Name</th><th>Status</th><th>Node</th><th>OSID</th><th>Commandline</th><th>Created</th><th>Last Seen</th><th>Stats</th></tr>";
	};
	this.toHTML = function() {
		return "<tr><td>" + this.id + "</td><td>" + this.name + "</td><td>" + this.status + "</td><td>" + nodeMap[this.node] +
			"</td><td>" + this.osid + "</td><td>" + this.commandline + "</td><td>" + this.time + "</td><td>" + PrintPsyTimeAge(this.lastseen) + "</td><td>" + this.stats + "</td></tr>";
	};
	this.toHTMLTree = function() {
		var html = "<B>" + this.name + "</B><sup>" + this.id + "</sup>";
		html += "<ul>";
		for (var comp in componentMap) {
			var component = componentMap[comp];
			if ((component.node == this.node) && (component.space == this.id)) {
				html += "<li>" + component.toHTMLTree() + "</li>";
			}
		}
		html += "</ul>";
		return html;
	};
}

function Component (node) {
	this.id = node.attr("id");
	this.name = node.attr("name");
	this.node = node.attr("node");
	this.space = node.attr("proc");
	this.type = node.attr("type");
	this.policy = node.attr("policy");
	this.paramcount = node.attr("paramcount");
	this.datacount = node.attr("datacount");
	this.status = node.attr("status");
	this.time = new PsyTime(node.attr("createdtime"));
	this.lastupdatetime = new PsyTime(node.attr("lastupdatetime"));
	this.stats = new ComStats($stats = node.find('componentstats:first'));
	this.stats.parentName = this.name;
	this.perfStats = new PerformanceStats($stats = node.find('performance:first'));

	this.toString = function() {
		return this.name;
	};
	this.getHTMLHeader = function() {
		return "<tr><th>ID</th><th>Name</th><th>Status</th><th>Node</th><th>Space</th><th>Type</th><th>Created</th><th>Last Updated</th></tr>";
	};
	this.toHTML = function() {
		return "<tr><td>" + this.id + "</td><td>" + this.name + "</td><td>" + this.status + "</td><td>" + nodeMap[this.node] +
			"</td><td>" + spaceMap[this.space] + "</td><td>" + componentTypes[this.type] + "</td><td>" + this.time + "</td><td>" + PrintPsyTimeDiff(SystemTime, this.lastupdatetime) + "</td></tr>";
	};
	this.toHTMLTree = function() {
		var html = "<span onclick=\"ToggleShowHide('Stats_"+this.name+"')\">" + componentTypes[this.type] + " <B>" + this.name + "</B><sup>" + this.id + "</sup> <i>(" +
			PrintPsyTimeAge(this.lastupdatetime) + ")</i> ";
		html += this.stats.toHTMLTree() + "</span>";
		return html;
	};
	this.toHTMLTriggers = function (isActive) {
		var checkForActive = ((typeof isActive != "undefined") && (isActive));
		var html = "";
		var contextTriggers = {};
		var contextPosts = {};

		for (var trigger of allTriggerList) {
			if (trigger.compid == this.id) {
				if (typeof contextTriggers[trigger.context] == "undefined")
					contextTriggers[trigger.context] = new Array();
				if (checkForActive) {
					if (trigger.isActive()) {
						contextTriggers[trigger.context].push(trigger);
						//contextPosts[trigger.context] = trigger.posts;
					}
				}
				else {
					contextTriggers[trigger.context].push(trigger);
					//contextPosts[trigger.context] = trigger.posts;
				}
			}
		}

		for (var context in contextTriggers) {
			html += "Context: <span class='TextContext'>" + GetContext(context) + "</span><ul>";
			var posts = {};
			for (var trigger of contextTriggers[context]) {
				html += "<li>" + trigger.toHTMLComponent(posts) + "</li>";
			}
			html += "<br>";
			for (var post in posts) {
				//console.log("Post Comp: " + typeof posts[post]);
				html += "<li>" + posts[post].toHTML(this.compid, false) + "</li>";
			}
			html += "</ul>";
		}

		var spaceName = "";
		if (typeof this.msgincount != "undefined")
			spaceName = "/ " + spaceMap[this.space]

		return "<tr><td><i>" + componentTypes[this.type] + "</i></td><td><b>" + this.name + "</b> (" + nodeMap[this.node] + spaceName + ")</td></tr>" +
			"<tr><td></td><td>" + html + "</td></tr>" +
			"<tr><td colspan=2>&nbsp</td></tr>"
		;
	};
}

function DataMessageEntry(node) {
	this.name = node.attr("name");
	this.type = node.attr("type");
	this.value = "";
	if (node[0].hasAttribute("value"))
		this.value = node.attr("value");
	this.size = 0;
	if (node[0].hasAttribute("size"))
		this.size = node.attr("size");
	this.text = "";
	if (node[0].hasAttribute("text"))
		this.text = node.attr("text");

	this.toString = function () {
		if (this.type == "binary")
			return "Binary (size: " + this.size + ")";
		else if (this.type == "messageinfo")
			return "Message (size: " + this.size + ")";
		else if (this.type == "textinfo")
			return "Text (size: " + this.size + ")";
		else if (this.type == "time")
			return this.text;
		else
			return this.value + " ("+this.type+")";
	};
}

function DataMessage (node) {
	this.size = node.attr("size");
	this.memid = node.attr("memid");
	this.time = new PsyTime(node.attr("time"));
	this.sendtime = new PsyTime(node.attr("sendtime"));
	this.recvtime = new PsyTime(node.attr("recvtime"));
	this.origin = node.attr("origin");
	this.destination = node.attr("destination");
	this.ttl = node.attr("ttl");
	this.priority = node.attr("priority");
	this.policy = node.attr("policy");
	this.from = node.attr("from");
	this.to = node.attr("to");
	this.type = node.attr("type");
	this.tag = node.attr("tag");
	this.status = node.attr("status");
	this.reference = node.attr("reference");
	this.serial = node.attr("serial");
	this.contextchange = node.attr("contextchange");
	this.userdatacount = node.attr("userdatacount");
	this.userdatasize = node.attr("userdatasize");
	this.inmsg = new Array();
	this.outmsg = new Array();

	this.userEntries = new Map();
	this.userMaps = new Map();

	var self = this;
	node.children("data").each(function () {
		var entry = new DataMessageEntry($(this));
		self.userEntries.set($(this).attr("name"), entry);
		//console.log("User key: '" + $(this).attr("name") + "' value: '" + $(this).attr("value") + "'");
	});
	node.children("array").each(function () {
		var arrName = $(this).attr("name");
		$(this).children("data").each(function () {
			var entry = new DataMessageEntry($(this));
			self.userEntries.set(arrName + "[" + $(this).attr("name") + "]", entry);
			//console.log("User key: '" + $(this).attr("name") + "' value: '" + $(this).attr("value") + "'");
		});
	});
	node.children("map").each(function () {
		var mapName = $(this).attr("name");
		$(this).children("data").each(function () {
			var entry = new DataMessageEntry($(this));
			self.userEntries.set(mapName + "[" + $(this).attr("name") + "]", entry);
			//console.log("User key: '" + $(this).attr("name") + "' value: '" + $(this).attr("value") + "'");
		});
	});

	this.getRemainingTTLUS = function() {
		return (this.ttl - GetPsyTimeAgeUS(this.time));
	};
	this.isLive = function() {
		if (!this.memid)
			return false;
		else
			return (this.getRemainingTTLUS() > 1000000); // > one second
	};

	this.getStatus = function() {
		if (!this.memid)
			return "<font color='gray'><i>Not stored</i></font>";
		else {
			var ttlRemainUS = this.getRemainingTTLUS();
			if (ttlRemainUS <= 0)
				return "<font color='gray'><i>Deleted " + PrintTimeDiffFull(Math.abs(ttlRemainUS)) + " ago</i></font>";
			else
				return "<font color='green'><b>Live " + PrintTimeDiffFull(ttlRemainUS) + "</b></font>";
		}
	};
	this.toString = function() {
		return "DataMessage";
	};
	this.getHTMLHeader = function() {
		return "";
	};
	this.toHTML = function() {
		return "Msg " + GetType(this.type) + " from " + GetComponentName(this.from) + "<sup>[" + Sizify(this.size) + "]</sup>";
	};
}

function messageXML2Table(xmldoc, templateName, idName) {
	if (!xmldoc) return;
	var $xml = $(xmldoc);

	var msgs = new Array();
	$xml.children().each(function () {
		msgs.push(new DataMessage($(this)));
	});
	return messageArray2Table(msgs, templateName, idName);
}

function messageArray2Table(msgs, templateName, idName) {
	var html = "";
	
	var ttl;
	var lineTemplateName;
	var c = 0;
	$.each( msgs, function( index, msg ){
		if (msg.ttl == 0)
			ttl = "<font color=gray><i>Sent "+PrintPsyTimeAgeApprox(msg.time)+" ago</i></font>";
		else {
			var timeRemainUS = msg.getRemainingTTLUS();
			if (timeRemainUS > 1000000)
				ttl = "<font color=red><b>STORED </b><i> (until " + PrintTimeDiffApprox(timeRemainUS) + ")</i></font>";
				// ttl = "<font color=red><b>LIVE </b><i> ("+timeRemainUS+")</i></font>";
			else if (timeRemainUS > -1000000)
				ttl = "<font color=gray><i>Just expired</i></font>";
			else {
				//var newTime = new PsyTime(); // = msg.time;
				var newTime = jQuery.extend(true, {}, msg.time);
				newTime.addUS(msg.ttl);
				ttl = "<font color=gray><B>Expired "+PrintPsyTimeAgeApprox(newTime)+" ago</B></font>";
			}
		}

		lineTemplateName = templateName + "Line" + ((++c%2) ? "Alternate" : "");
		html += "<tr class='"+lineTemplateName+"' id='"+idName+c+"'><td><font color=blue>" + GetTypeFromMsg(msg) + "</font></td>"+
			"<td><font color=green>" + GetComponentName(msg.from) + "</font></td>"+
			"<td><font color=gray>" + Sizify(msg.size) + "</font></td>"+
			"<td>"+ttl+"</td></tr>";
	});

	return "<table class='"+templateName+"' id='"+idName+"'><tr><td>Type</td><td>From</td><td>Size</td><td>Age</td>" + html + "</table>";
}

function ComStats (node) {
	this.runcount = node.attr("runcount");
	this.msgincount = node.attr("msgincount");
	this.msginbytes = node.attr("msginbytes");
	this.msgoutcount = node.attr("msgoutcount");
	this.msgoutbytes = node.attr("msgoutbytes");
	this.time = node.attr("time");
	this.memusage = node.attr("memusage");
	this.inmsg = new Array();
	this.outmsg = new Array();
	this.parentName = "";
	
	var me = this;
	$inmsgnode = node.find('inmsg:first');
	// $inmsgnode.find("datamessage").each(function () {
	$inmsgnode.children().each(function () {
		me.inmsg.push(new DataMessage($(this)));
	});
	$outmsgnode = node.find('outmsg:first');
	//$outmsgnode.find("datamessage").each(function () {
	$outmsgnode.children().each(function () {
		me.outmsg.push(new DataMessage($(this)));
	});
	//console.log("ComStats with " + this.msgincount + " " + this.inmsg.length + " input and " + this.outmsg.length + " output msgs");

	
	this.toString = function() {
		return "ComStats with " + this.inmsg.length + " input and " + this.outmsg.length + " output msgs";
	};
	this.getHTMLHeader = function() {
		return "";
	};
	this.toHTML = function() {
		return "";
	};
	this.toHTMLTree = function() {
		if ( (typeof this.msgincount == "undefined") && (typeof this.msgoutcount == "undefined") )
			return this.toString();

		var html = "";
		if ( typeof this.msgincount != "undefined" )
			html += "MsgIn: " + this.msgincount + "<sup>[" + Sizify(this.msginbytes) + "]</sup> ";
		if ( typeof this.msgoutcount != "undefined" )
			html += "MsgOut: " + this.msgoutcount + "<sup>[" + Sizify(this.msgoutbytes) + "]</sup>";
		if ( typeof this.msgincount != "undefined" ) {
			html += "<ul id='Stats_" + this.parentName + "_in' class='Stats'><li>Latest inputs<ul>";
			$.each( this.inmsg, function( index, value ){
				html += "<li>" + value.toHTML() + "</li>";
			});
			html += "</ul></ul>";
		}
		if ( typeof this.msgoutcount != "undefined" ) {
			html += "<ul id='Stats_" + this.parentName + "_out' class='Stats'><li>Latest outputs<ul>";
			$.each( this.outmsg, function( index, value ){
				html += "<li>" + value.toHTML() + "</li>";
			});
			html += "</ul></ul>";
		}
		//console.log(html);
		return html;
	};
}

function SubTrigger (node) {
	this.subtype = node.attr("subtype");
	this.level = node.attr("level");
	
	this.subtriggers = {};
	this.triggers = new Array();
	
	var me = this;
	node.children().each(function () {
		if (this.nodeName == "SUBTRIGGER")
			me.subtriggers[$(this).attr("subtype")] = new SubTrigger($(this));
		else {
			me.triggers.push(new Trigger($(this)));
			allTriggerList.push(new Trigger($(this)));
		}
	});
	
	this.toString = function() {
		return "[" + this.level + "] SubTrigger " + subTypeMap[this.subtype] + "(" + this.subtype + ") with " +
		Object.keys(this.subtriggers).length + " subtriggers and " + this.triggers.length + " level triggers";
	};

	this.getContextTriggers = function(context) {
		var result = new Array();
		for (var key in this.subtriggers)
			result = result.concat(this.subtriggers[key].getContextTriggers(context));
		for (var key2 in this.triggers) {
			//console.log(key2 + " context trigger for context " + GetContext(context));
			var trigger = this.triggers[key2];
			if (trigger.context == context)
				result.push(trigger);
			//else
			//	console.log(trigger.context + " != " + context);
		}
		return result;
	};
	
	this.getTriggers = function(compID, isActive) {
		var result = new Array();
		for (var key in this.subtriggers)
			result = result.concat(this.subtriggers[key].getTriggers(compID, isActive));
		for (var key2 in this.triggers) {
			var trigger = this.triggers[key2];
			if ( (trigger.compid == compID) && (trigger.isActive() == isActive))
				result.push(trigger);
		}
		return result;
	};
	
	this.getHTMLHeader = function() {
		return "";
	};
	this.toHTML = function(isActive, compID, showButtons) {
		var checkForActive = ((typeof isActive != "undefined") && (isActive));
		var checkForCompID = ((typeof compID != "undefined") && (compID > 0));
		var useButtons = ((typeof showButtons != "undefined") && (showButtons));
		var activeCount = 0;
		var thtml = "";
		var html = "<B>" + subTypeMap[this.subtype] + "</B><sup>" + this.subtype + "</sup>";
		if (Object.keys(this.subtriggers).length) {
			html += "<ul>";
			for (var key in this.subtriggers) {
				thtml = this.subtriggers[key].toHTML(isActive, compID, useButtons);
				if (thtml.length) {
					activeCount++;
					html += "<li>" + thtml + "</li>";
				}
			}
			html += "</ul>";
		}
		if (this.triggers.length) {
			html += "<ul>";
			for (var key2 in this.triggers) {
				var trigger = this.triggers[key2];
				if (checkForCompID) {
					if (trigger.compid != compID)
						continue;
				}
				if (checkForActive) {
					if (trigger.isActive()) {
						html += "<li class='ActiveTrigger'>" + this.triggers[key2].toHTML(useButtons) + "</li>";
						activeCount++;
					}
				}
				else {
					if (trigger.isActive())
						html += "<li class='ActiveTrigger'>" + this.triggers[key2].toHTML(useButtons) + "</li>";
					else
						html += "<li class='InactiveTrigger'>" + this.triggers[key2].toHTML(false) + "</li>";
					activeCount++;
				}
				
			}
			html += "</ul>";
		}
		// console.log("ActiveCount: " + activeCount);
		if ( (checkForActive || checkForCompID) && !activeCount)
			return "";
		else
			return html;
	};
}

function Trigger (node) {
	this.name = node.attr("name");
	this.type = node.attr("type");
	this.compid = node.attr("compid");
	this.interval = node.attr("interval");
	this.from = node.attr("from");
	this.to = node.attr("to");
	this.tag = node.attr("tag");
	this.filtercount = node.attr("filtercount");
	this.signalcount = node.attr("signalcount");
	this.postcount = node.attr("postcount");
	this.context = node.attr("context");
	this.triggerContext = node.attr("triggercontext");

	//console.log("Trigger compid: " + this.compid);

	if (!ArrayContains(allTypeList, this.type))
		allTypeList.push(this.type);
	if (!ArrayContains(allContextList, this.context))
		allContextList.push(this.context);

	// console.log("Context: " + GetContext(this.context));
	
	this.filters = new Array();
	this.signals = new Array();
	this.posts = new Array();
	
	var me = this;
	node.find("filter").each(function () {
		me.filters.push(new Filter($(this)));
	});
	node.find("signal").each(function () {
		me.signals.push(new Signal($(this)));
	});
	node.find("post").each(function () {
		var post = new Post($(this), me.compid);
		me.posts.push(post);
		//console.log("Post Trigger init ["+post.name+"]: " + typeof post);
		if (typeof allPostList[post.fullName] == "undefined")
			allPostList[post.fullName] = new Post($(this), me.compid);
	});

	this.isActive = function() {
		return IsContextActive(this.context);
	}
	
	this.toString = function() {
		return "Trigger '"+name+"' to " + GetComponentName(this.compid) + " context " + GetContext(this.context) +
			"(" + this.posts.length + " posts, " + this.filters.length + " filters, " +
			this.signals.length + " signals)";
	};
	this.getHTMLHeader = function() {
		return "";
	};
	this.toHTML = function(showButtons) {
		var useButtons = ((typeof showButtons != "undefined") && (showButtons));

		var compName = GetComponentName(this.compid);
		
		var buttonHTML = "";
		if (useButtons)
			buttonHTML = "<button class='TriggerActivateButton' onclick='TriggerActivate(" + this.compid + ",\"" + this.name + "\")'>test</button>";
		
		var html;
		if (this.triggerContext.length)
			html = "Trigger '"+this.name+"' on context <span class='TextContext'>" + GetContext(this.triggerContext) + "</span> to <span class='TextComponentName'>" + compName + "</span><sup>" + this.compid + "</sup> " + buttonHTML;
		else
			html = "Trigger '"+this.name+"' on type <span class='TextType'>" + GetType(this.type) + "</span> to <span class='TextComponentName'>" + compName + "</span><sup>" + this.compid + "</sup> context <span class='TextContext'>" + GetContext(this.context) + "</span> " + buttonHTML;
			
		if (this.filters.length) {
			html += "<ul>";
			for (var filter of this.filters) {
				html += "<li>" + filter.toHTML() + "</li>";
			}
			html += "</ul>";
		}
		if (this.signals.length) {
			html += "<ul>";
			for (var signal of this.signals) {
				html += "<li>" + signal.toHTML() + "</li>";
			}
			html += "</ul>";
		}

		if (this.posts.length) {
			html += "<ul>";
			for (var post of this.posts) {
				//console.log("Post Trigger [" + key + "]");
				//console.log("Post Trigger [" + key + "]: " + typeof this.posts[key] + " = " + this.posts[key].toString());
				html += "<li>" + post.toHTML(this.compid, useButtons, true) + "</li>";
			}
			html += "</ul>";
		}
		
		return html;
	};

	this.toHTMLComponent = function (posts) {
		var compName = GetComponentName(this.compid);

		var html;
		if (this.triggerContext.length)
			html = "Trigger <b>'" + this.name + "'</b> on context <span class='TextContext'>" + GetContext(this.triggerContext) + "</span>";
		else
			html = "Trigger <b>'" + this.name + "'</b> on type <span class='TextType'>" + GetType(this.type) + "</span> ";

		if (this.filters.length) {
			html += "<ul>";
			for (var key in this.filters) {
				html += "<li>" + this.filters[key].toHTML() + "</li>";
			}
			html += "</ul>";
		}
		if (this.signals.length) {
			html += "<ul>";
			for (var key in this.signals) {
				html += "<li>" + this.signals[key].toHTML() + "</li>";
			}
			html += "</ul>";
		}

		if (this.posts.length) {
			for (var post of this.posts) {
				// console.log("Post Trigger Comp ["+key+"]: " + typeof this.posts[key]);
				posts[post.name] = post;
			}
		}

		return html;
	};
}

function Filter (node) {
	// <filter filter=\"%u\" time=\"%llu\" key=\"%s\" value=\"%s\"
	this.filter = node.attr("filter");
	this.time = new PsyTime(node.attr("time"));
	this.key = node.attr("key");
	this.value = node.attr("value");
	
	this.toString = function() {
		return "Filter type " + this.filter + " key " + this.key + " value " + this.value + " time " + this.time;
	};
	this.getHTMLHeader = function() {
		return "";
	};
	this.toHTML = function() {
		return this.toString();
	};
}

function Signal (node) {
	// <signal type=\"%s\" name=\"%s\" />
	this.type = node.attr("type");
	this.name = node.attr("name");
	
	this.toString = function() {
		return "Signal name " + this.name + " type " + GetType(this.type);
	};
	this.getHTMLHeader = function() {
		return "";
	};
	this.toHTML = function() {
		return this.toString();
	};
}

function Post (node, compid) {
	this.to = node.attr("to");
	this.type = node.attr("type");
	this.context = node.attr("context");
	this.contextchange = node.attr("contextchange");
	this.name = node.attr("name");
	this.compid = compid;
	this.fullName =
		this.compid + "_" +
		this.context + "_" +
		this.type + "_" +
		this.contextchange + "_";

	//console.log("Post compid: " + this.compid);

	if (!ArrayContains(allTypeList, this.type))
		allTypeList.push(this.type);
	if (!ArrayContains(allContextList, this.context))
		allContextList.push(this.context);

	this.toString = function() {
		var text = "";
		if (this.to != "0")
			text += " to <b>" + GetComponentName(this.to) + "</b>";
		if (this.type != NOTYPE) {
			text += " type " + GetType(this.type);
			text += " context " + GetContext(this.context);
		}
		if (this.contextchange.length) {
			text += " context change to " + GetContext(this.contextchange);
			text += " context " + GetContext(this.context);
		}
		return "Post " + text;
	};
	this.getHTMLHeader = function() {
		return "";
	};
	this.toHTML = function(compID, showButtons, showContext) {
		var useButtons = ((typeof showButtons != "undefined") && (showButtons));
		var viewContext = ((typeof showContext != "undefined") && (showContext));
		var buttonHTML = "";
		if (useButtons)
			buttonHTML = "<button class='PostActivateButton' onclick='PostActivate(" + compID + ",\"" + this.name + "\")'>test</button>";

		var text = "";
		if (this.to != "0")
			text += " to " + GetComponentName(this.to) + "<sup>" + this.to + "</sup>";
		if (this.type != NOTYPE) {
			text += " type <span class='TextType'>" + GetType(this.type) + "</span>";
			if (viewContext)
				text += " context <span class='TextType'>" + GetContext(this.context) + "</span>";
		}
		if (this.contextchange.length) {
			text += " context change to <b><font color='purple'>" + GetContext(this.contextchange) + "</font></b>";
			if (viewContext)
				text += " context <i><font color='green'>" + GetContext(this.context) + "</font></i>";
		}
		return "Post <b>'" + this.name + "'</b> " + text + " " + buttonHTML;
	};
}


function GetFullMessage(memID) {
	var url = "/api/getmessage?memid=" + memID + "&format=xml";
	console.log("GetFullMessage: " + url);
	return loadURL(url);
}

function GetPerformanceMatrix() {
	var url = "performancematrix.xml";
	return loadURL(url);
}

function GetComponentDataString(compID, dataName) {
	var url = "/api/getcomponentdata?compid=" + compID + "&name=" + dataName + "&format=text";
	console.log("GetPrivateData: " + url);
	return loadURL(url);
}

function TriggerActivate(compID, name) {
	var url = "/api/triggeractivate?compid=" + compID + "&name=" + name;
	// console.log("TriggerActivate: " + url);
	var response = loadURL(url);
	$xml = $(response);
	if ($xml.attr('code') == "0")
		flashStatus("Trigger simulated activation: " + $xml.attr('text'));
	else
		flashStatus("Trigger simulated activation failed with error: " + $xml.attr('text'));
}

function PostActivate(compID, name) {
	var url = "/api/postactivate?compid=" + compID + "&name=" + name;
	// console.log("PostActivate: " + url);
	var response = loadURL(url);
	$xml = $(response);
	if ($xml.attr('code') == "0")
		flashStatus("Post simulated activation: " + $xml.attr('text'));
	else
		flashStatus("Post simulated activation failed with error: " + $xml.attr('text'));
}

function processSystemXML(xmldoc) {
	if (!xmldoc) return;
	latestSystemXML = xmldoc;
	$xml = $(xmldoc);
	processSystemXMLNode($xml);
}

function processSystemXMLNode(xml) {
	
	SystemTime = new PsyTime(xml.attr("time"));
	SystemUptime = xml.attr("uptime");
	SystemID = xml.attr("id");

	// console.log("Time now: " + new PsyTime(xml.attr("time")));

	var starType = new SubType();
	starType.id = 65535;
	starType.name = "*";
	starType.status = 2;
	starType.time = SystemTime;
	subTypeMap[65535] = starType;
	var starContext = new SubContext();
	starContext.id = 65535;
	starContext.name = "*";
	starContext.status = 2;
	starContext.time = SystemTime;
	subContextMap[65535] = starContext;
	
	$ids = xml.find('ids:first');
	$ids.find("type").each(function () {
		subTypeMap[$(this).attr("id")] = new SubType($(this));
	});
	$ids.find("context").each(function () {
		subContextMap[$(this).attr("id")] = new SubContext($(this));
	});
	$ids.find("crank").each(function () {
		crankMap[$(this).attr("id")] = new Crank($(this));
	});
	$ids.find("request").each(function () {
		requestMap[$(this).attr("id")] = new Request($(this));
	});
	var c = 0;
	SystemUsage1 = SystemUsage2 = SystemUsage3;
	xml.find("node").each(function () {
		var n = new Node($(this));
		nodeMap[$(this).attr("id")] = n;
		SystemUsage1 += n.perfStats.localsystemcpuusage1;
		SystemUsage2 += n.perfStats.localsystemcpuusage2;
		SystemUsage3 += n.perfStats.localsystemcpuusage3;
		c++;
	});
	if (c) {
		SystemUsage1 /= c;
		SystemUsage2 /= c;
		SystemUsage3 /= c;
	}
	
	xml.find("space").each(function () {
		spaceMap[$(this).attr("id") + ":" + $(this).attr("node")] = new Space($(this));
	});
	xml.find("component").each(function () {
		componentMap[$(this).attr("id")] = new Component($(this));
	});

	// Calculate total system performance stats

	allTriggerList = new Array();
	allPostList = new Array();
	allContextList = new Array();
	allTypeList = new Array();

	$subs = xml.find('subscriptions:first');
	$subs.children().each(function () {
		subscriptions[$(this).attr("subtype")] = new SubTrigger($(this));
	});

	activeContexts = {};
	xml.find('contexts active').each(function () {
		activeContexts[$(this).attr("context")] = new PsyTime($(this).attr("time"));
		// console.log("Active Context: " + GetContext($(this).attr("context")));
	});

	inactiveContexts = {};
	xml.find('contexts inactive').each(function () {
		inactiveContexts[$(this).attr("context")] = new PsyTime($(this).attr("time"));
		//console.log("Inactive Context: " + GetContext($(this).attr("context")));
	});
	
	//subscriptions
}













