var GenericTree = Class.create({
	openerCSSRule: "span.PDLUINodeOpener",
	initialize: function(element, openerCSSRule){
		this.element = $(element);
		if (!Object.isUndefined(openerCSSRule)) this.openerCSSRule = openerCSSRule;
		if (!this.element) return false;
		this.setEventListeners.bind(this).defer();
		return true;
	},

	setEventListeners: function(){
		Event.observe(this.element, "click", this.onClick.bindAsEventListener(this));
	},

	onClick: function(event){
		var element = Event.element(event);
		if (Element.match(element, this.openerCSSRule)) this.toggleOpen(element);
		var nodeCSSRule = "dt";
		if (Element.match(element, nodeCSSRule)) return this.selectNode(element);
		var nodeParent = Element.up(element, nodeCSSRule);
		if (nodeParent && Element.descendantOf(nodeParent, this.element)) return this.selectNode(nodeParent);
		else this.selectNode(null);
	},

	isClosed: function(button){
		if (!button) return void LogE("PDLTree.isClosed(): element is null");
		return Element.hasClassName(button, "Closed");
	},

	selectNode: function(node){
		if (this.selectedNode == node) return;
		if (this.selectedNode){
			Element.fire(this.selectedNode, GenericTree.events["blur"]);
			Element.removeClassName(this.selectedNode, "Selected");
		}
		this.selectedNode = node;
		if (!this.selectedNode) return;
		Element.addClassName(this.selectedNode, "Selected");
		this.scrollTo(this.element, this.selectedNode);
	},

	checkElement: function(element){ return element && Element.descendantOf(element, this.element); },
	checkElementMatch: function(element, cssRule){ return this.checkElement(element) && Element.match(element, cssRule); },

	getSelectedNode: function(){
		if (!this.checkElement(this.selectedNode)) return null;
		return this.selectedNode;
	},

	getButton: function(dt){
		if (!dt) return null;
		if (!dt.button) dt.button = Element.childElements(dt).find(function(element){
				return Element.match(element, this.openerCSSRule);
			}, this);
		return dt.button;
	},

	setClosed: function(element){ this.toggleOpen(element, false); },
	setOpen: function(element){ this.toggleOpen(element, true); },

	toggleOpen: function(button, setOpen){
		if (Element.match(button, "dt")) button = this.getButton(button);
		if (!button) return;
		if (!button.block) button.block = Element.next(Element.up(button, "dt"), "dd");
		if (!button.block) return void LogE("The corresponding block not found");
		if (typeof setOpen == "undefined") setOpen = this.isClosed(button);
		Element.setStyle(button.block, {"display": setOpen ? "" : "none"});
		Element.removeClassName(button, setOpen ? "Closed" : "Open");
		Element.addClassName(button, setOpen ? "Open" : "Closed");
		if (!setOpen){
			var selectedNode = this.getSelectedNode();
			if (selectedNode && Element.descendantOf(selectedNode, button.block)) this.selectNode(Element.up(button, "dt"));
		}
	},

	scrollTo: Element.scrollContainerTo,

	onKeyPress: function(event){
		switch (event.keyCode){
			case Event.KEY_LEFT: this.setSelectedNodeOpenOrGoDown(false); break;
			case Event.KEY_RIGHT: this.setSelectedNodeOpenOrGoDown(true); break;
			case Event.KEY_UP: this.selectRelative("up"); break;
			case Event.KEY_DOWN: this.selectRelative("down"); break;
			default: return;
		}
		Event.stop(event);
	},

	setSelectedNodeOpenOrGoDown: function(setOpen){
		var node = this.getSelectedNode();
		if (!node) return;
		var button = this.getButton(node);
		if (!button) return;
		var isClosed = this.isClosed(button);
		if (isClosed == setOpen) return this.toggleOpen(button, setOpen);
		var dd = Element[isClosed ? "up" : "next"](node, "dd");
		if (!this.checkElement(dd)) return;
		var toSelect = Element[isClosed ? "previous" : "down"](dd, "dt");
		if (toSelect) this.selectNode(toSelect);
	},

	selectRelative: function(direction){
		var node = this.getSelectedNode();
		if (!node) return this.selectNode(this.getFirstNode());
		var thisobj = this;
		var relative = this.getRelative(node, direction,
				function(element){ return thisobj.isVisible(element, true); },
				function(element){ return thisobj.isVisible(element, false); });
		if (relative) this.selectNode.bind(this).curry(relative).defer();
	},

	getRelative: function(node, direction, testElement, testAncestor){
		if (typeof testElement != "function") testElement = function(element){ return true; };
		if (typeof testAncestor != "function") testAncestor = function(element){ return true; };
		switch (direction){
			case "down": return this.getRelativeDown(node, testElement, testAncestor);
			case "up": return this.getRelativeUp(node, testElement, testAncestor);
			default: return null;
		}
	},

	getRelativeDown: function(node, testElement, testAncestor){
		if (!this.checkElement(node)) return null;
		var nextNode = Element.next(node);
		if (nextNode){
			if (testAncestor(nextNode)){
				var downDT = Element.down(nextNode, "dt");
				if (downDT){
					if (testElement(downDT)) return downDT;
					var result = this.getRelativeDown(downDT, testElement, testAncestor);
					if (result) return result;
				}
			}
			var result = this.getRelativeDown(nextNode, testElement, testAncestor);
			if (result) return result;
		}
		return this.getRelativeDown(Element.up(node, "dl"), testElement, testAncestor);
	},

	getRelativeUp: function(node, testElement, testAncestor){
		if (!this.checkElement(node)) return null;
		var prevDLs = Element.previousSiblings(Element.up(node, "dl"));
		while (prevDLs.length > 0){
			var dl = prevDLs.shift();
			if (testAncestor(dl)){
				var result = this.findLast(dl, testElement, testAncestor);
				if (result) return result;
			}
		}
		var upDD = Element.up(node, "dd");
		if (!this.checkElement(upDD)) return null;
		var prevNode = Element.previous(upDD, "dt");
		return testElement(prevNode) ? prevNode : this.getRelativeUp(prevNode, testElement, testAncestor);
	},

	findLast: function(dl, testElement, testAncestor){
		if (!this.checkElementMatch(dl, "dl")) return null;
		var dd = Element.down(dl, "dd");
		if (testAncestor(dd)){
			var childDLs = Element.childElements(dd);
			while (childDLs.length > 0){
				var last = this.findLast(childDLs.pop(), testElement, testAncestor);
				if (last) return last;
			}
		}
		var dt = Element.down(dl, "dt");
		return testElement(dt) ? dt : null;
	},

	isVisible: function(node, locally){
		if (!node || node.nodeType != Node.ELEMENT_NODE) return false;
		if (!Element.descendantOf(node, this.element)) return true;
		return Element.getStyle(node, "display") != "none" && (locally || this.isVisible(node.parentNode));
	},

	getFirstNode: function(){
		return Element.down(this.element, "dt");
	},

	getLastNode: function(){
		var testElement = function(){ return true; };
		return this.findLast(Element.down(this.element, "dl"), testElement, testElement);
	},

	isLeaf: function(dt){
		var dd = Element.next(dt, "dd");
		return !Element.down(dd);
	},

	updateClassNames: function(element){
		if (Element.match(element, "dl")) return this.updateClassNames(Element.down(element, "dt"));
		if (!this.checkElementMatch(element, "dt")) return;
		var dt = element;
		var dl = Element.up(dt, "dl");
		var isLeaf = this.isLeaf(dt);
		var isMiddle = Boolean(Element.next(dl, "dl"));
		[dt, dl].each(function(element){
			Element[isLeaf ? "addClassName" : "removeClassName"](element, "Leaf");
			Element.removeClassName(element, isMiddle ? "Last" : "Middle");
			Element.addClassName(element, isMiddle ? "Middle" : "Last");
		});
	},

	rollNode: function(dt){
		if (!dt) return;
		[dt].concat(Element.select(Element.next(dt, "dd"), "dt")).each(this.setClosed.bind(this));
	},

	unrollNode: function(dt){
		if (!dt) return;
		[dt].concat(Element.select(Element.next(dt, "dd"), "dt")).each(this.setOpen.bind(this));
	},

	GetChildNodes: function(dt){
		if (!dt) return [];
		var dd = Element.next(dt, "dd");
		if (!dd) return [];
		var dls = new Array();
		for (var node = dd.firstChild; node; node = node.nextSibling){
			if (node.nodeType == Node.ELEMENT_NODE && Node.localName(node).toLowerCase() == "dl")
				dls.push(node);
		}
		return dls.collect(function(dl){ return Element.down(dl, "dt"); });
	}
});

GenericTree.events = { "blur": "generic-tree:blur" };

