// Version: 1.20
// Date: 2007-01-25
// Author: CrazyDave
// Modified by Marc Fowler <marc.fowler@defraction.net>
// Website: http://www.clanccc.co.uk/moo/nested.html
var Nested = new Class({
	Implements: [Events, Options],
	options: {
		childTag: 'li',
		ghost: true,
		childStep: 30, // attempts to become a child if the mouse is moved this number of pixels right
		handleClass: null, 
		onStart: Class.empty,
		onComplete: Class.empty,
		collapse: false, // true/false
		collapseLevel : 3,
		collapseClass: 'nCollapse', // Class added to collapsed items
		expandKey: 'shift', // control | shift
		lock: null, // parent || depth || class || all
		lockClass: 'unlocked'
	},
 
	initialize: function(list, options) {
		this.setOptions(options);
		if (!this.options.expandKey.match(/^(control|shift)$/)) {
			this.options.expandKey = 'shift';
		}
		this.list = $(list);
//		this.options.parentTag = this.list.nodeName;
		this.options.parentTag = this.list.get('tag');
		this.bound = {};
		this.bound.start = this.start.bind(this);
		this.list.addEvent('mousedown', this.bound.start);
		if (this.options.collapse) {
			this.bound.collapse = this.collapse.bind(this);
			this.list.addEvent('click', this.bound.collapse);
//			alert(this.options.collapseLevel);
			this.list.getElements(this.options.childTag).each(function(el) {
//				alert(this.getDepth(el));
				if ((el.getElement(this.options.parentTag)) && (this.getDepth(el) >= this.options.collapseLevel)) {
					el.getElement(this.options.parentTag).setStyle('display', 'none');
					el.addClass(this.options.collapseClass);
				}
/*
				if (el.getElement(this.options.parentTag)) {
					if (this.getDepth(el) >= this.options.collapseLevel) {
						el.getElement(this.options.parentTag).slide('hide');
						el.getElement(this.options.parentTag).setStyle('display', 'none');
						el.addClass(this.options.collapseClass);
					} else {
						el.getElement(this.options.parentTag).slide('show');
						el.getElement(this.options.parentTag).setStyle('display', 'block');
					}
				}
*/
			},this);

		}
		if (this.options.initialize) this.options.initialize.call(this);
		if (this.options.handleClass) this.list.getElements('.'+this.options.handleClass).setStyle('cursor','move');
	},
 
	start: function(event) {
		var el = $(event.target);
		if (this.options.handleClass) {
//			while (el.nodeName != this.options.childTag && !el.hasClass(this.options.handleClass) && el != this.list) {
			while (el.get('tag') != this.options.childTag && !el.hasClass(this.options.handleClass) && el != this.list) {
				el = el.getParent();
			}
			if (!el.hasClass(this.options.handleClass)) return true;
		} 
//		while (el.nodeName != this.options.childTag && el != this.list) {
		while (el.get('tag') != this.options.childTag && el != this.list) {
//			el = el.parentNode;
			el = el.getParent();
		}
//		if (el.nodeName != this.options.childTag) return true;
		if (el.get('tag') != this.options.childTag) return true;
		el = $(el);
		if (this.options.lock == 'all') return;
		if (this.options.lock == 'class' && !el.hasClass(this.options.lockClass)) return;
		if (this.options.ghost) { // Create the ghost
			this.ghost = el.clone().setStyles({
				'list-style-type': 'none',
				'opacity': 0.5,
				'position': 'absolute',
				'visibility': 'hidden',
				'top': event.page.y+'px',
				'left': (event.page.x+10)+'px'
			}).inject(document.body, 'inside');
		}
		el.depth = this.getDepth(el);
		el.moved = false;
		this.bound.movement = this.movement.bind(this, el);
		this.bound.end = this.end.bind(this, el);
		this.list.removeEvent('mousedown', this.bound.start);
		this.list.addEvent('mousedown', this.bound.end);
		this.list.addEvent('mousemove', this.bound.movement);
		document.addEvent('mouseup', this.bound.end);
//		if(Browser.ie) { // IE fix to stop selection of text when dragging
	//		this.bound.stop = this.stop.bind(this);
		//	$(document.body).addEvent('drag', this.bound.stop).addEvent('selectstart', this.bound.stop);
//		}
		this.fireEvent('onStart', el);
		event.stop();
	},
 
	collapse: function(event) {
		var el = $(event.target);
		if (this.options.handleClass) {
//			while (el.nodeName != this.options.childTag && !el.hasClass(this.options.handleClass) && el != this.list) {
			while (el.get('tag') != this.options.childTag && !el.hasClass(this.options.handleClass) && el != this.list) {
				el = el.getParent();
			}
			if (!el.hasClass(this.options.handleClass)) return true;
		} 
//		while (el.nodeName != this.options.childTag && el != this.list) {
		while (el.get('tag') != this.options.childTag && el != this.list) {
//			el = el.parentNode;
			el = el.getParent();
		}
		if (el == this.list) return;
		el = $(el);
		if (!el.moved) {
			var sub = el.getElement(this.options.parentTag);
			if (sub) {
/*
				if (sub.getStyle('display') == 'none') {
					sub.setStyle('display', 'block');
					new Fx.Slide(sub, {
						duration: 200,
						mode: 'vertical',
						onComplete: function(){
							el.removeClass(this.options.collapseClass);
							el.getElement('div').setStyle('height','');
						}.bind(this)
					}).toggle();
				} else {
					new Fx.Slide(sub, {
						duration: 200,
						mode: 'vertical',
						onComplete: function(){
							el.addClass(this.options.collapseClass);
							sub.setStyle('display', 'none');
						}.bind(this)
					}).toggle();
				}
*/
				if (sub.getStyle('display') == 'none') {
					sub.setStyle('display', 'block');
					el.removeClass(this.options.collapseClass);
				} else {
					sub.setStyle('display', 'none');
					el.addClass(this.options.collapseClass);
				}
			}
		}
		event.stop();
	},
 
	stop: function(event) {
		event.stop();
		return false;
	},
 
	getDepth: function(el, add) {
		var counter = (add) ? 1 : 0;
		while (el != this.list) {
//			if (el.nodeName == this.options.parentTag) counter += 1;
			if (el.get('tag') == this.options.parentTag) counter += 1;
//			el = el.parentNode;
			el = el.getParent();
		}
		return counter;
	},
 
	movement: function(el, event) {
		var dir, over, check, items;
		var dest, move, prev, prevParent;
		var abort = false;
		var sub, depth;
		if (this.options.ghost && el.moved) { // Position the ghost
			this.ghost.setStyles({
				'position': 'absolute',
				'visibility': 'visible',
				'top': event.page.y+'px',
				'left': (event.page.x+10)+'px'
			});
		}
		over = event.target;
//		alert(over.get('tag'));
//		while (over.nodeName != this.options.childTag && over != this.list) {
		while (over.get('tag') != this.options.childTag && over != this.list && over.getParent()) {
//			over = over.parentNode;
			over = over.getParent();
		}
		if (over == this.list) return;
		if (!over.getParent()) return; // Fix IE target above list
/*
		if (event[this.options.expandKey] && over != el && over.hasClass(this.options.collapseClass)) {
			check = over.getElement(this.options.parentTag);//
			check.setStyle('display', 'block');
			new Fx.Slide(check, {
				duration: 200,
				mode: 'vertical',
				onComplete: function(){
					over.removeClass(this.options.collapseClass);
					over.getElement('div').setStyle('height','');
				}.bind(this)
			}).slideIn();
		}
*/
		if (event[this.options.expandKey] && over != el && over.hasClass(this.options.collapseClass)) {
			check = over.getElement(this.options.parentTag);//
			over.removeClass(this.options.collapseClass);
			check.setStyle('display', 'block');
		} 
		// Check if it's actually inline with a child element of the event firer
		orig = over;
		if (el != over) {
			items = over.getElements(this.options.childTag);
			items.each(function(item) {
				if(event.page.y > item.getTop() && item.offsetHeight > 0) over = item;
			});
		}
		// Make sure we end up with a childTag element
//		if (over.nodeName != this.options.childTag) return;
		if (over.get('tag') != this.options.childTag) return;
 
		// store the previous parent 'ol' to remove it if a move makes it empty
		prevParent = el.getParent();
		dir = (event.page.y < el.getTop()) ? 'up' : 'down';
		move = 'before';
		dest = el;
 
		if (el != over) {
			check = over;
			while (check != null && check != el) {
//				check = check.parentNode;
				check = check.getParent();
			} // Make sure we're not trying to move something below itself
			if (check == el) return;
			if (dir == 'up') {
				move = 'before'; dest = over;
			} else {
				sub = over.getElement(this.options.childTag);//
				if (sub && sub.offsetHeight > 0) {
					move = 'before'; dest = sub;
				} else {
					move = 'after'; dest = over;
				}
			}
		}
 
		// Check if we're trying to go deeper -->>
		prev = (move == 'before') ? dest.getPrevious() : dest;
		if (prev) {
			move = 'after';
			dest = prev;
			check = dest.getElement(this.options.parentTag);
			while (check && event.page.x > check.getLeft() && check.offsetHeight > 0) {
				dest = check.getLast();
				check = dest.getElement(this.options.parentTag);
			}
			if (!check && event.page.x > dest.getLeft()+this.options.childStep) {
				move = 'inside';
			}
		}
 
		last = dest.getParent().getLast();
		while (((move == 'after' && last == dest) || last == el) && dest.getParent() != this.list && event.page.x < dest.getLeft()) {
			move = 'after';
//			dest = $(dest.parentNode.parentNode);
			dest = $(dest.getParent().getParent());
			last = dest.getParent().getLast();
		}
 
		abort = false;
		if (move != '') {
			abort += (dest == el);
			abort += (move == 'after' && dest.getNext() == el);
			abort += (move == 'before' && dest.getPrevious() == el);
			abort += (this.options.lock == 'depth' && el.depth != this.getDepth(dest, (move == 'inside')));
//			abort += (this.options.lock == 'parent' && (move == 'inside' || dest.parentNode != el.parentNode));
			abort += (this.options.lock == 'parent' && (move == 'inside' || dest.getParent() != el.getParent()));
			abort += (dest.offsetHeight == 0);
			sub = over.getElement(this.options.parentTag);
			sub = (sub) ? sub.getTop() : 0;
			sub = (sub > 0) ? sub-over.getTop() : over.offsetHeight;
			abort += (event.page.y < (sub-el.offsetHeight)+over.getTop());
			depth = this.getDepth(dest, (move == 'inside'));
			depth += (el.getElements(this.options.childTag).length == 0) ? 0 : 1; // we assume that if el has children it has depth of one without checking deeper
			abort += (depth > 1);
			if (!abort) {
				if (move == 'inside') dest = new Element(this.options.parentTag).inject(dest,'inside');
				el.inject(dest, move);
				el.moved = true;
				if (!prevParent.getFirst()) prevParent.destroy();
			}
		}
		event.stop();
	},
 
	detach: function() {
		this.list.removeEvent('mousedown', this.start.bind(this));
		if (this.options.collapse) this.list.removeEvent('click', this.bound.collapse);
	},
 
	serialize: function(listEl) {
		var serial = [];
		var kids;
		if (!listEl) listEl = this.list;
		$$(listEl.childNodes).each(function(node, i) {
			kids = node.getElement(this.options.parentTag);
			serial[i] = {
				id: node.id,
				title: (node.title) ? node.title : "",
				children: (kids) ? this.serialize(kids) : []
			};
		}.bind(this));
		return serial;
	},
 
	end: function(el) {
		if (this.options.ghost) this.ghost.destroy();
		this.list.removeEvent('mousemove', this.bound.movement);
		document.removeEvent('mouseup', this.bound.end);
		this.list.removeEvent('mousedown', this.bound.end);
		this.list.addEvent('mousedown', this.bound.start);
		this.fireEvent('onComplete', el);
//		if(Browser.ie) $(document.body).removeEvent('drag', this.bound.stop).removeEvent('selectstart', this.bound.stop);
	}
});
