/**
 * Tree
 * 
 * @version 0.3.3, 2010-08-12
 * @author Gregor Kofler
 *
 * @param {Object} config object
 *		container:		{Object} DOM element tree will be appended to
 *		tree:			{Object} (nested) UL element(s) which will be turned into tree
 *		branches:		{Array} initial branches
 *		checkBoxes:		{Boolean¦"last"} show and handle checkboxes, "last" displays checkboxes only on lowest level
 *		leafNodeDefault:{Boolean} use leafnodes when subtree is undefined
 *		expandTo:		{Number|"all"} number of levels shown upon default
 *
 * properties of branches
 * 
 * String	key
 * String	elements
 * Array	branches
 * bool		disabled
 * 
 * events served:
 * branchClick
 * beforeNodeClick
 * afterNodeClick
 * afterCheckBoxClick
 * expandTree
 * collapseTree
 * 
 * @todo handling of "disabled" property
 */

/*global document, vxJS*/

vxJS.widget.tree = function(config) {

	if(!config) { config = {}; }

	var tree, allSubTrees = [], that = {}, checkBoxes = config.checkBoxes || false, activeBranch;

	/**
	 * Tree object
	 */
	var Tree = function(level, ul) {
		this.elem = ul || "ul".create();
		vxJS.dom.addClassName(this.elem, "vxJS_tree");
		this.level = level || 0;
		this.branches = [];
	};

	Tree.prototype = {
		findBranchByElem: function(e) {
			var b, i, s;

			for(i = this.branches.length; i--;) {
				b = this.branches[i];
				if(b.elem == e){
					return b;
				}
				if(b.subtree && (s = b.subtree.findBranchByElem(e))) {
					return s;
				}
			}
		},

		addBranch: function(data) {
			var b = new Branch(this, data);
			this.branches.push(b);
			this.last = b;
			b.pos = this.branches.length - 1;
		},

		addBranches: function(b) {
			var i = 0, l = b.length;

			while(i < l) {
				this.addBranch(b[i++]);
			}
		},

		setParentCheckBox: function() {
			var i, b, disabled = 0, ticked = 0, semi, p = this.parent;
			if(!p || checkBoxes === "last") {
				return;
			}

			for(i = this.branches.length; i--;) {
				b = this.branches[i];
				if (b.disabled) {
					++disabled;
				}
				ticked += b.cbState == 1 ? 1 : 0;
				semi = semi || b.cbState == 2;
			}

			p.setCheckBox(
				semi ? 2 : ( !ticked ? 0 : (ticked == this.branches.length ? 1 : 2)),
				p.disabled || disabled === this.branches.length
			);

			if(this.parent.tree) {
				this.parent.tree.setParentCheckBox();
			}
		},

		expand: function() {
			this.elem.style.display = "";
		},

		collapse: function() {
			this.elem.style.display = "none";
		},

		expandToLevel: function(lvl) {
			var i = this.branches.length, t;
			
			if(!lvl) {
				lvl = 0;
			}
			if(this.parent) {
				this.parent[lvl !== "all" && this.level > lvl ? "hideSubTree" : "showSubTree"]();
			}
			while(i--) {
				t = this.branches[i].subtree;
				if(t) {
					t.expandToLevel(lvl);
				}
			}
		},

		render: function() {
			var i, l = this.branches.length, p = this.parent, b, c, n;

			if(checkBoxes === "last" && l && p) {
				if((c = p.cbElem) && (n = c.parentNode)) {
					n.removeChild(c);
				}
			}

			this.onlyLeaves = true;

			for(i = 0; i < l; ++i) {
				b = this.branches[i];
				b.render();
				if(b.subtree) {
					b.subtree.render();
					this.onlyLeaves = false;
				}
				this.elem.appendChild(b.elem);
			}

			if(p) {
				if(this.elem && (n = this.elem.parentNode)) {
					n.removeChild(this.elem);
				}
				p.elem.appendChild(this.elem);
			}

			if(this.onlyLeaves) {
				this.setParentCheckBox();
			}
		}
	};

	/**
	 * Branch object
	 */
	var Branch = function(tree, data) {
		var p;

		this.elem = "li".create();
		this.tree = tree;

		for(p in data) {
			if(p == "branches") {
				if(data.branches.length) {
					this.appendSubTree(data.branches);
				}
				else {
					this.terminates = true;
				}
			}
			else if(data.hasOwnProperty(p)) {
				this[p] = data[p];
			}
		}
	};

	Branch.prototype = {
		appendSubTree: function(b) {
			this.subtree = new Tree(this.tree.level + 1, null);
			this.subtree.parent = this;
			if(b) {
				this.subtree.addBranches(b);
			}
			this.elem.appendChild(this.subtree.elem);
			allSubTrees.push(this.subtree);
		},
		
		removeSubTree: function() {
			var e;

			if(!this.subtree) {
				return;
			}
			this.hideSubTree();
			e = this.subtree.elem;
			e.parentNode.removeChild(e);
			delete this.subtree;
			this.renderNode();
		},

		toggleSubTree: function() {
			var s = this.subtree;

			if(!s || s.elem.style.display == "none") {
				this.showSubTree();
			}
			else {
				this.hideSubTree();
			}
		},

		hideSubTree: function() {
			var s = this.subtree;
			vxJS.event.serve(that, "collapseTree", { branch: this });
			if(s && s.elem.style.display != "none") {
				s.collapse();
			}
			this.renderNode();
		},

		showSubTree: function() {
			var s = this.subtree;
			vxJS.event.serve(that, "expandTree", { branch: this });
			if(s && s.elem.style.display == "none") {
				s.expand();
			}
			this.renderNode();
		},

		propagateCheckBox: function() {
			if(!this.subtree || !this.subtree.branches.length) {
				return;
			}
			var b = this.subtree.branches, i = b.length, s = this.cbState;

			while(i--) {
				b[i].setCheckBox(s, b[i].disabled);
				b[i].propagateCheckBox();
			}
		},

		setCheckBox: function(state, disabled) {
			this.cbState = state;
			this.disabled = !!disabled;
			this.renderCheckBox();
		},

		toggleCheckBox: function() {
			this.cbState = !this.cbState ? 1 : 0;
			this.renderCheckBox();
		},
		
		renderCheckBox: function() {
			var cn;

			switch (+this.cbState) {
				case 1:
					cn = "checked";
					break;
				case 0:
					cn = "unChecked";
					break;
				case 2:
					cn = "partChecked";
			}
			if(!this.cbElem) {
				this.cbElem = "span".create();
			}
			this.cbElem.className = cn + " __check__" + (this.disabled ? " disabled" : ""); 
		},

		renderNode: function() {
			var cn, s = this.subtree;

			if(this.terminates || typeof s == "undefined" && config.leafNodeDefault) {
				cn = "leafNode";
			}
			else if(typeof s == "undefined") {
				cn = "subTreeCollapsed __node__";
			}
			else if(s.branches.length > 0) {
				if(s.elem.style.display == "none") {
					cn = "subTreeCollapsed __node__";
				}
				else {
					cn = "subTreeExpanded __node__";
				}
			}
			else {
				cn = "leafNode";
			}
			if(!this.nodeElem) {
				this.nodeElem = "span".create();
			}
			this.nodeElem.className = cn;
		},

		render: function() {
			var	li = this.elem;

			li.className = this.tree.last === this ? "lastBranch" : "";

			this.renderNode();
			li.appendChild(this.nodeElem);

			if(checkBoxes == "last" && this.nodeElem.className.indexOf("leafNode") != -1 || checkBoxes == true) {
				if(typeof this.cbState === "undefined") {
					if(this.tree.parent && this.tree.parent.cbState && this.tree.parent.cbState !== 2) {
						this.cbState = this.tree.parent.cbState;
					}
					else {
						this.cbState = 0;
					}
				}
				this.renderCheckBox();
				li.appendChild(this.cbElem);
			}

			this.label = "div".setProp("class", "__label__").create(vxJS.dom.parse(this.elements));
			li.appendChild(this.label);
			this.elem = li;

			return li;
		}
	};

	var importUl = function(ul) {
		var li, c, frag, b, prop, rex = /(?:^|\s)__([a-z][a-z0-9]*)__([a-z0-9]+)/ig, branches = [];

		while((li = ul.childNodes[0])) {
			if(li.nodeType == 1 && li.nodeName.toLowerCase() == "li") {
				b = {};

				if(li.className) {
					while((prop = rex.exec(li.className))) {
						b[prop[1]] = prop[2];
					}
				}

				frag = document.createDocumentFragment();

				while((c = li.childNodes[0])) {
					if(c.nodeType == 1 && c.nodeName.toLowerCase() == "ul") {
						b.branches = arguments.callee(c);
						c.parentNode.removeChild(c);
					}
					else {
						frag.appendChild(c);
					}
				}

				b.elements = [ { fragment: frag } ];
				branches.push(b);
			}
			ul.removeChild(li);
		}
		return branches;
	};

	var handleClick = function() {
		var c, li, b;

		if(/disabled/.test(this.className)) { return; }

		if((c = this.className.match(/(?:\s|^)((?:__node__)|(?:__check__)|(?:__label__))(?:\s|$)/i))) {
			
			li = vxJS.dom.getParentElement(this, "li");

			if(!(b = tree.findBranchByElem(li))) {
				return;
			}

			activeBranch = b;
			vxJS.event.serve(that, "branchClick", { branch: b});

			switch(c[1]) {
				case "__node__":
					vxJS.event.serve(that, "beforeNodeClick", { branch: b});
					b.toggleSubTree();
					vxJS.event.serve(that, "afterNodeClick", { branch: b});
					break;
				case "__check__":
					vxJS.event.serve(that, "beforeCheckBoxClick", { branch: b});
					b.toggleCheckBox();
					b.propagateCheckBox();
					b.tree.setParentCheckBox();
					vxJS.event.serve(that, "afterCheckBoxClick", { branch: b});
					break;
			}
		}
	};

	if(config.tree && config.tree.nodeName && config.tree.nodeName.toLowerCase() == "ul") {
		tree = new Tree(null, config.tree);
		tree.addBranches(importUl(config.tree));
	}
	else {
		tree = new Tree();
		tree.addBranches(config.branches || []);
	}
	tree.expandToLevel(config.expandTo);
	tree.render();

	if(config.container) {
		config.container.appendChild(tree.elem);
	}
	vxJS.event.addListener(tree.elem, "click", handleClick);

	that.element = tree.element;

	that.getCheckedLeaves = (function() {
		var branches;

		var cbRecursion = function(t) {
			var l = t.branches.length, b;
			while(l--) {
				b = t.branches[l];
				if(b.subtree) {
					arguments.callee(b.subtree);
				}
				else if(b.cbElem && b.cbState == 1) {
					branches.push(b);
				}
			}
		};

		return function() {
			if(checkBoxes) {
				branches = [];
				cbRecursion(tree);
				return branches;
			}
		};
	})();

	that.getBranch = function(key, prop) {
		var l = tree.branches.length, b;

		while(l--) {
			b = tree.branches[l];
			if(b[key] == prop) {
				return b;
			}
			if((b = arguments.callee(key, prop))) {
				return b;
			}
		}
	};

	that.customExpandTo = function(cb) {
		var traces = [], track = [], trace, i, l;

		var scan = function(t) {
			var b = t.branches, l = b.length, alreadyExpanding;

			while(l--) {
				if(!alreadyExpanding && cb.apply(b[l])) {
					traces.push(track.concat([]));
					alreadyExpanding = true;
				}
				if(b[l].subtree) {
					track.push(b[l]);
					arguments.callee(b[l].subtree);
				}
			}
			track.pop();
		};

		scan(tree);

		while((trace = traces.pop())) {
			for(i = 0, l = trace.length; i < l; ++i) {
				trace[i].showSubTree();
			}
		}
	};

	that.expandToLevel = function(lvl) {
		tree.expandToLevel(lvl);
	};

	that.getActiveBranch = function() {
		return activeBranch;
	};

	return that;
};