(function($){
	var mptt = function(selector, options) {
		var obj = this;
		this.settings = {
			key: 'index'
		};

		$.extend(this.settings, options);
	}

	mptt.prototype.getObj = function() {
		return this.obj;
	}

	mptt.prototype.getKey = function() {
		return this.settings.key;
	}

	// finds the left most node of a given depth, optionally filtered by a parent node
	mptt.prototype.findLeftMostNode = function(parent_idx, depth) {
		var lft = 0;

		// If parent_idx not defined, search for nodes with empty parent.
		if(typeof(parent_idx) == 'undefined') {
			parent_idx = '';
		}
		var parent = this.findByIdx(parent_idx);
		var leftMostNode = parent;

		// Default depth to search on same row as idx.
		if(typeof(depth) == 'undefined') {
			depth = 1;
		}
		var nodes = this.findByDepth(depth);

		// if the parent's been set and it exists, only take child nodes.
		if(parent.length == 1) {
			nodes = $.grep(nodes, function(node, i) {
				if($(node).attr('parent') == parent_idx) {
					return true;
				} else {
					return false;
				}
			});
		}

		$.each(nodes, function() {
			var this_lft = parseInt($(this).attr("lft"));
			if(this_lft <= lft+1) {
				lft = this_lft;
				leftMostNode = this;
			}
		});

		return leftMostNode;
	}


	// finds the right most node of a given depth, optionally filtered by a parent node
	mptt.prototype.findRightMostNode = function(parent_idx, depth) {
		var rgt = 0;

		// If parent_idx not defined, search for nodes with empty parent.
		if(typeof(parent_idx) == 'undefined') {
			parent_idx = '';
		}
		var parent = this.findByIdx(parent_idx);
		var rightMostNode = parent;

		// Default depth to search on same row as idx.
		if(typeof(depth) == 'undefined') {
			depth = 1;
		}
		var nodes = this.findByDepth(depth);

		// if the parent's been set and it exists, only take child nodes.
		if(parent.length == 1) {
			nodes = $.grep(nodes, function(node, i) {
				if($(node).attr('parent') == parent_idx) {
					return true;
				} else {
					return false;
				}
			});
		}

		$.each(nodes, function() {
			var this_rgt = parseInt($(this).attr("rgt"));
			if(this_rgt >= rgt-1) {
				rgt = this_rgt;
				rightMostNode = this;
			}
		});

		return rightMostNode;
	}

	/* Inserts a node after the node with given idx. */
	mptt.prototype.insertAfter = function(idx, callback) {
		var node = $("tr[idx="+idx+"].node", this.obj);
		var lft = parseInt($(node).attr("lft"));
		var rgt = parseInt($(node).attr("rgt"));

		$("tr.node", this.obj).each(function() {
			// Increase the rgt of every node to the right by 2.
			if(parseInt($(this).attr("rgt")) > rgt) {
				var after_rgt = parseInt($(this).attr("rgt"))+2;
				$(this).attr("rgt", after_rgt);
			}

			// Increase the lft of every node to the left by 2.
			if(parseInt($(this).attr("lft")) > rgt) {
				var after_lft = parseInt($(this).attr("lft"))+2;
				$(this).attr("lft", after_lft);
			}
		});

		// call the callback function with the new node's lft and rgt values
		callback.call(this, parseInt(rgt)+1, parseInt(rgt)+2);

		return this;
	}

	/* Inserts a node as the child of the node with given idx. */
	mptt.prototype.insertChild = function(idx, callback) {
		var node = $("tr[idx="+idx+"].node", this.obj);

		/* if the parent already has children insert after the last child,
		 * else create the node's first child.  */
		var childCount = $("tr[parent="+idx+"].node", this.obj).length;
		if(childCount > 0) {
			var lastChild = this.findByRgt(parseInt($(node).attr('rgt'))-1);
			return this.insertAfter($(lastChild).attr("idx"), callback);
		} else {
			var lft = parseInt($(node).attr("lft"));

			$("tr.node", this.obj).each(function() {
				// Increase the rgt of every node to the right by 2.
				var this_rgt = parseInt($(this).attr("rgt"));
				if(this_rgt > lft) {
					var after_rgt = this_rgt+2;
					$(this).attr("rgt", after_rgt);
				}

				// Increase the lft of every node to the left by 2.
				var this_lft = parseInt($(this).attr("lft"));
				if(this_lft > lft) {
					var after_lft = this_lft+2;
					$(this).attr("lft", after_lft);
				}
			});

			// call the callback function with the new node's lft and rgt values
			callback.call(this, parseInt(lft)+1, parseInt(lft)+2);

			return this;
		}
	}

	mptt.prototype.deleteNode = function(idx, callback) {
		var node = this.findByIdx(idx);
		var lft = parseInt($(node).attr("lft"));
		var rgt = parseInt($(node).attr("rgt"));
		var width = (rgt - lft) + 1;

		// call the delete callback on this node and each of this node's children.
		this.findAll().each(function() {
			var this_lft = parseInt($(this).attr("lft"));
			if((this_lft >= lft) && (this_lft <= rgt)) {
				callback.call(this, this);
			}
		});

		// update nodes to the right
		this.findAll().each(function() {
			var this_rgt = parseInt($(this).attr("rgt"));
			if(this_rgt > rgt) {
				$(this).attr("rgt", this_rgt - width);
			}

			var this_lft = parseInt($(this).attr("lft"));
			if(this_lft > rgt) {
				$(this).attr("lft", this_lft - width);
			}
		});
	}

	mptt.prototype.findByIdx = function(idx) {
		return $("tr[idx="+idx+"].node", this.obj);
	}

	mptt.prototype.findByLft = function(lft) {
		return $("tr[lft="+lft+"].node", this.obj);
	}

	mptt.prototype.findByRgt = function(rgt) {
		return $("tr[rgt="+rgt+"].node", this.obj);
	}

	mptt.prototype.findByParent = function(parent) {
		return $("tr[parent="+parent+"].node", this.obj);
	}

	mptt.prototype.findAll = function() {
		return $("tr.node", this.obj);
	}

	mptt.prototype.findByDepth = function(depth) {
		var nodes = new Array();
		var tree = this;

		$.each(this.findAll(), function(i, node) {
			if(tree.getDepth($(node).attr('idx')) == depth) {
				nodes.push(node);
			}
		});

		return $($.makeArray(nodes));
	}

	mptt.prototype.getDepth = function(idx) {
		var node = this.findByIdx(idx);

		if(node.length == 0) {
			// node not found
			return 0;
		}
		if($(node).attr('parent') == '') {
			// root node
			return 1;
		} else {
			return 1+this.getDepth($(node).attr('parent'));
		}
	}

	mptt.prototype.debug = function(title, obj) {
		if (window.console && window.console.log) {
			window.console.log(title);
			if(typeof(obj) != 'undefined')
				window.console.log(obj);
		}
	};

	$.extend({
		mptt: mptt
	});

	$.fn.extend({
		mptt: function(options) {
			var mptt = new $.mptt(this, options);
			return this;
		}
	});
})(jQuery);

