//<script>
function TreeNodeInfo(elem)
{
	this.elem  = elem;
	this.level = elem.getAttribute("tree:level");
	this.id    = elem.getAttribute("tree:id");
	this.iid   = elem.getAttribute("tree:iid");
	this.value = elem.getAttribute("tree:value");
	this.link  = elem.getAttribute("tree:link");
}

TreeNodeInfo.prototype.getIndexedId = function()
{
	return (this.iid == null) ? this.id : this.iid;
}

var Tree =
{
	onClick: function(oInfo, evt) { }, // overridable
	onShow: function() { },	// overridable

	// Main rendering function. Apply XSL transform to XML data, and put the results in designated <div>.
	show: function(where)
	{
		var div = this.container;
		if (div == null || this.xmlData == null || this.xmlStylesheet == null)
			return;
		var root = this.xmlData.documentElement;
		var s = root.transformNode(this.xmlStylesheet);
		div.innerHTML = s;
		this.root = div;
		this.onShow();
	},
	setContainer     : function(oElem)         { this.container = oElem; },
	setXmlData       : function(xmlData)       { this.xmlData = xmlData; this.show("setXmlData"); },
	setXmlStylesheet : function(xmlStylesheet) { this.xmlStylesheet = xmlStylesheet; this.show("setXmlStylesheet"); },
	
	// Retrieving tree node information.
	// We assume "tree:level" attribute is always present and valid (root has level 0, etc.)
	getLevel: function(elem) { return elem.getAttribute("tree:level"); },

	getId : function(elem) { return elem.getAttribute("tree:id"); },
	// tree:iid attribute is optional; if specified, it overrides tree:id for the purpose of searching
	getIndexedId : function(elem)
	{
		var iid = elem.getAttribute("tree:iid");
		return (iid == null) ? this.getId(elem) : iid;
	},

	// Get all node info packed in an object.
	getInfo: function(elem) { return new TreeNodeInfo(elem); },
	// Get linked list of "info" objects, starting with elem and up to the root.
	getInfoChain: function(elem)
	{
		var ret = this.getInfo(elem);
		for (var info = ret; (elem = this.getParent(elem)) != null; info = info.parent)
			info.parent = this.getInfo(elem);
		info.parent = null;
		return ret;
	},

	// Find nearest ancestor that belongs to the tree (has the "tree:level" attribute)
	findNode: function(elem)
	{ 
		while (elem && this.getLevel(elem) == null) 
		{
			if (elem == this.root)
				return null;
			elem = elem.parentNode;
		}
		return elem;
	},
	getParent: function(elem) { return (this.getLevel(elem) == 0) ? null : this.findNode(elem.parentNode); },
	setActive: function(elem)
	{
		if (this.activeNode == elem)
			return false;
		if (this.activeNode != null)
			this.activeNode.className = "";
		this.activeNode = elem;
		this.activeNode.className = "active";
		return true;
	},
	findNodeById: function(treeID)
	{
		var id = "netis_tree_" + treeID;
		return document.getElementById(id);
	},
	setActiveNode: function(node)
	{
		for (var child = node.firstChild; child != null; child = child.nextSibling)
		{
			if (child.tagName == "SPAN" && !child.className.match(/^bullet/)) {
				child.scrollIntoView(true);
				this.setActive(child);
				break;
			}
		}
	},
	prevNextPage : function(evt, delta)
	{
		evt = Compat.getEvent(evt);
		var node = this.findNode(evt.target);
		if (node == null)
			return;
		var page = new Number(node.getAttribute("tree:page"));
		// Find the nearest parent which has onclick() defined.
		// evt.target may be its' child node.
		var loadingStateNode = evt.target;
		while (loadingStateNode.onclick == null)
			loadingStateNode = loadingStateNode.parentNode;
		getChildrenAsync(node, page + delta, 0, null, loadingStateNode);
	},
	prevPage: function(evt)
	{
		this.prevNextPage(evt, -1);
	},
	nextPage: function(evt)
	{
		this.prevNextPage(evt, +1);
	}
}

function toggle(evt)
{
	evt = Compat.getEvent(evt);
	evt.stopPropagation();		// don't bubble this event up the DOM tree.
	toggleNode(Tree.findNode(evt.target), 0, null);
}

function setLoadingState(node)
{
	for (var child = node.firstChild; child != null; child = child.nextSibling)
	{
		if (child.tagName == "SPAN" && child.getAttribute("tree:flag"))
		{
			child.innerHTML = "<img src=\"NetisUtils/images/loading-small.gif\">";
			child.className = "";
			break;
		}
	}
}

function getChildrenAsync(node, nPage, childId, onComplete, loadingStateNode)
{
	if (loadingStateNode == null)
		loadingStateNode = node;
	setLoadingState(loadingStateNode);

	var id = node.getAttribute("tree:id");
	var url = "NetisUtils/srvrutil_getTree.aspx?dbID=" + id + "&page=" + nPage + "&childId=" + childId;
	XmlDoc.createIsland(url, function(data)
	{
		var root = data.documentElement;
		if (root == null)
			return;
		root.setAttribute("expanding", "true");
		var page = root.getAttribute("page");
		// For unknown reason, root.transformNode sometimes is null in Mozilla.
		var s = data.transformNode(Tree.xmlStylesheet);
		node.innerHTML = s;
		node.setAttribute("tree:page", page);
		onRenderChildren(node, id);
		if (typeof(onComplete) == "function")
			onComplete(node);
	});
}

function expandAncestors(nodeId)
{
	XmlDoc.createIsland("NetisUtils/srvrutil_getAncestors.aspx?dbID=" + nodeId,
	function(data) {
		var root = data.documentElement;
		if (root == null)
			return;
		if (root.tagName == "error")
			alert(root.text);
		else
			expandHelper(root.firstChild);
	});
}

function expandHelper(oXmlNode)
{
	if (oXmlNode == null) return;
	var id = oXmlNode.getAttribute("id");
	var node = Tree.findNodeById(id);
	Tree.setActiveNode(node);
	oXmlNode = oXmlNode.nextSibling;
	if (oXmlNode != null)
	{
		var childId = oXmlNode.getAttribute("id");
		expandNode(node, childId, function() { expandHelper(oXmlNode); });
	}
	else
		Tree.onClick(Tree.getInfoChain(node), null);
}

function expandNode(node, childId, onComplete)
{
	var f = node.getAttribute("tree:flag");
	if (Tree.findNodeById(childId) == null)
		getChildrenAsync(node, 0, childId, onComplete);
	else if (f == "+")
		toggleNode(node, childId, onComplete);
	else if (typeof(onComplete) == "function")
		onComplete(node);
}

function toggleNode(node, childId, onComplete)
{
	// Depending on whether the branch is expanding or collapsing, several things must be changed.
	// For briefness, we pack them all into one neat object.
	var o;
	switch (node.getAttribute("tree:flag")) {
		case "+": o = { displayType: "block", treeFlag: "-", innerHtml: "&ndash;", className: "bulletMinus" }; break;
		case "-": o = { displayType: "none",  treeFlag: "+", innerHtml: "+",       className: "bulletPlus" }; break;
	}
	// ----
	// Note: if o is null here, you probably failed to initialize the "tree:flag" attribute.
	// ----
	node.setAttribute("tree:flag", o.treeFlag);
	var childrenLoaded = false;
	for (var child = node.firstChild; child != null; child = child.nextSibling)
	{
		switch (child.tagName) {
			case "UL":
				if (child.childNodes.length != 0)
					childrenLoaded = true;
				child.style.display = o.displayType;
				break;
			case "SPAN":
				if (child.getAttribute("tree:flag"))
				{
					child.innerHTML = o.innerHtml;
					child.className = o.className;
					child.setAttribute("tree:flag", o.treeFlag);
					break;
				}
		}
	}
	if (!childrenLoaded)
		getChildrenAsync(node, 1, childId, onComplete);
	else if (typeof(onComplete) == "function")
		onComplete(node);
}

function getNode(evt)
{
	evt = Compat.getEvent(evt);
	evt.stopPropagation();		// don't bubble this event up the DOM tree.
	return Tree.findNode(evt.target);
}

function listProps(e)
{
	var ar = [];
	for (var prop in e)
		ar.push(prop + "=" + e[prop]);
	return ar.join("\n");
}

function handleClick(evt)
{
	evt = Compat.getEvent(evt);
	evt.stopPropagation();		// don't bubble this event up the DOM tree.

	if (!Tree.setActive(evt.target))
		return;
	var clickedNode = Tree.findNode(evt.target);
	Tree.onClick(Tree.getInfoChain(clickedNode), evt);
}

function setHover(evt, bSet)
{
	evt = Compat.getEvent(evt);
	var elem = evt.target;
	var currentClass = elem.className;
	if (bSet)
		elem.className = "hover";
	else if (currentClass == "hover")
		elem.className = elem.getAttribute("origClass");

	elem.setAttribute("origClass", currentClass);
}
//////////
var NodeState =
{
	m_arState: new Array(),
	m_arChildren: new Array(),
	onClick: function(state) {},// To be implemented on Toc.js
	setCheck: function(id, bSet)
	{
		this.m_arState[id] = bSet;
	},
	getCheck: function(id)
	{
		return this.m_arState[id];
	},
	addChild: function(nodeId, childId)
	{
		var ar = this.m_arChildren[nodeId];
		if (ar == null)
			this.m_arChildren[nodeId] = ar = new Array();
		ar[childId] = true;
	},
	clear: function(id)
	{
		delete this.m_arState[id];
		var ar = this.m_arChildren[id];
		if (ar == null)
			return;
		for (var childId in ar)
		{
			this.clear(childId);
		}
		this.m_arChildren[id] = null;
	},
	clearAll: function()
	{
		this.m_arState = null;
		this.m_arChildren = null;
		this.m_arState = new Array();
		this.m_arChildren = new Array();
	},
	isEmpty: function()
	{
		for (var item in this.m_arState)
			return false;
		return true;
	},
	getTerms: function()
	{
		function combine(ar) { return "<or f=\"AncestorID\">" + ar.join("") + "</or>"; }

		var arInclude = new Array();
		var arExclude = new Array();
		for (var id in this.m_arState)
		{
			var node = Tree.findNodeById(id);
			var iid = Tree.getIndexedId(node);
			var s = "<t type=\"num\" v=\"" + iid + "\"/>";
			if (this.m_arState[id])
				arInclude.push(s);
			else
				arExclude.push(s);
		}
		
		if (arExclude.length == 0)
		{
			if (arInclude.length == 0)
				return "";
			return combine(arInclude);
		}
		else
		{
			return "<not>" + combine(arInclude) + combine(arExclude) + "</not>";
		}
		//return "Include:   " + ((arInclude.length == 0) ? "(none)" : arInclude.join(", ")) +
		//	 "\nExclude:   " + ((arExclude.length == 0) ? "(none)" : arExclude.join(", "));
	}
}

function onCheckNode(oNode, bSet)
{
	for (var node = oNode.firstChild; node != null; node = node.nextSibling)
	{
		if (node.nodeType != 1)
			continue;
		if (node.tagName == "INPUT" && node.type == "checkbox")
			node.checked = bSet;
		onCheckNode(node, bSet);
	}
}

// Go up the ancestor chain, stopping at the first node that has a defined state.
// Returns the state (true/false) or null if no such node found.
function getInheritedState(node, id)
{
	while ((bCheck = NodeState.getCheck(id)) == null && (node = Tree.getParent(node)) != null)
		id = Tree.getId(node);
	return bCheck;
}

function onRenderChildren(oNode, id)
{
	var bCheck = getInheritedState(oNode, id);
	updateCheckboxes(oNode, bCheck);
}

function updateCheckboxes(oNode, bCheck)
{
	var id;
	for (var node = oNode.firstChild; node != null; node = node.nextSibling)
	{
		if (node.nodeType != 1)
			continue;
		var _id = node.getAttribute("tree:id");
		if (_id != null)
			id = _id;
		if (bCheck != null && node.tagName == "INPUT" && node.type == "checkbox")
			node.checked = bCheck;
		if (node.firstChild != null)
		{
			var _bCheck = NodeState.getCheck(id);
			if (_bCheck == null)
				_bCheck = bCheck;
			updateCheckboxes(node, _bCheck);
		}
	}
}

function setCheck(evt)
{
	evt = Compat.getEvent(evt);
	evt.stopPropagation();
	var node = Tree.findNode(evt.target);
	var bSet = evt.target.checked;
	onCheckNode(node, bSet);
	var id = Tree.getId(node);
	var bCheck = NodeState.getCheck(id);
	NodeState.clear(id);
	if (bCheck == null)
		NodeState.setCheck(id, bSet);
	var papa = Tree.getParent(node);
	if (papa != null)
		NodeState.addChild(Tree.getId(papa), id);

	NodeState.onClick(!NodeState.isEmpty());
}

function getSearchTerm()
{
	return NodeState.getTerms();
}

