Backbone.Trait.TreeStruct = {
	parent: null,
	firstChild: null, lastChild: null,
	prevSibling: null, nextSibling: null,
	
	
	initialize: function() {
		this.children = [];
	},
	
	addChild: function(child) {
		if (child.parent) {
			child.parent.removeChild(child);
		}
		
		child.parent = this;
		
		if (this.children.length == 0) {
			this.firstChild = child;
			this.lastChild = child;
		}
		else {
			this.lastChild.nextSibling = child;
			child.prevSibling = this.lastChild;
			this.lastChild = child;
		}
		
		this.children.push(child);
	},
	
	removeChild: function(child) {
		var i = _.indexOf(this.children, child),
			prev = child.prevSibling, next = child.nextSibling;
		
		if (i == -1) {
			throw "Child not found";
		}
		
		if (this.firstChild == child) {
			this.firstChild = next;
		}
		
		if (this.lastChild == child) {
			this.lastChild = prev;
		}
		
		prev && (prev.nextSibling = next);
		next && (next.prevSibling = prev);
		
		child.parent = child.nextSibling = child.prevSibling = null;
		
		this.children.splice(i, 1);
	}
}

Backbone.Trait.selectable = {
	selectedMulti: true,
	
	select: function(item) {
		var $item = $(item).addClass('selected');
	}
}

Backbone.Trait.DragDrop = {
	// a list of all Drag and Drop areas
	areaList: [],
	// a list of properties used when working with an area and its elements
	// one will be chosen at runtime depending on the orientation
	areaProps: {
		horizontal: {pagePos: 'pageX', position: 'left', markerSize: 'height', outSize: 'offsetWidth', outSizeSec: 'offsetHeight'},
		vertical: {pagePos: 'pageY', position: 'top', markerSize: 'width', outSize: 'offsetHeight', outSizeSec: 'offsetWidth'}
	},
	
	initialize: function(area) {
		_.bindAll(this, 'move', 'drop', 'scroll');
		
		if (!this.dragType) {
			return;
		}
		
		// get the area element of the dragDrop
		// if no custom area element was passed, use the main element
		this.areaEl = ((area && area.addClass) ? area[0] : area) || this.el;
		this.$areaEl = (area && area.addClass) ? area : $(this.areaEl);
		
		this.$areaEl
			.addClass('drag-drop-area')
			// prevent text selection on DnD areas
			.mousedown(function(e) {
				if (!$.browser.msie && e.which === 1) {
					e.preventDefault();
				}
			})
			.bind('selectstart', function(e) {
				e.preventDefault();
			});
		
		// store the ID of this DnD area
		this.areaEl.setAttribute('data-drag-drop', this.areaList.length);
		this.areaList.push(this);
		
		// create the drop marker (shows where the row will be dropped)
		this.dropMarker = document.createElement('div');
		this.dropMarker.className = 'drop-marker' + (this.areaOrientation ? ' ' + this.areaOrientation : '');
		
		// select the correct area properties
		if (this.areaOrientation) {
			this.areaProps = this.areaProps[this.areaOrientation];
		}
	},
	
	drag: function(e) {
		e.preventDefault();
		
		// dragging should only start on a left click
		if (e.which != 1) {
			return;
		}
		
		// if no custom target was set, use the event element
		this.dragElement = this.dragElement || e.currentTarget;
		this.$dragElement = $(this.dragElement);
		this.$dragElement.addClass('dragging');
		
		this.prev = {x: e.pageX, y: e.pageY};
		
		// execute the custom drag start function, if it returns false then exit
		//if (this.drag && this.drag(e) === false) {
		//	return;
		//}
		
		// start listening for mousemove/mouseup
		$(document)
			.bind('mousemove.dragDrop', this.move)
			.bind('mouseup.dragDrop', this.drop);
		
		this.$areaEl && this.$areaEl.bind('scroll', this.scroll);
		
		this.move(e);
	},
	
	move: function(e) {
		this.moveEvent = e;
		
		this.prev.x = e.pageX
		this.prev.y = e.pageY;
		
		this.prevOverArea = this.overArea;
	},
	
	drop: function() {
		// remove the drop marker
		if (this.dropMarker && this.dropMarker.parentNode) {
			this.dropMarker.parentNode.removeChild(this.dropMarker);
		}
		
		$(document).unbind('.dragDrop');
		this.$areaEl && this.$areaEl.unbind('scroll', this.scroll);
		
		this.overArea = this.dragElement = this.under = null;
	},
	
	scroll: function() {
		
	},
	
	threshold: function(e, limit, callback) {
		var self = this, eOrig = e,
			x = e.pageX, y = e.pageY;
		
		$(document)
			.bind('mousemove.dragDropThreshold', function(e) {
				if (Math.abs(x - e.pageX) >= limit || Math.abs(y - e.pageY) >= limit) {
					$(this).unbind('.dragDropThreshold');
					
					callback.call(self, eOrig);
				}
			})
			.bind('mouseup.dragDropThreshold', function(e) {
				$(this).unbind('.dragDropThreshold');
			});
	},
	
	updateDrop: function() {
		var e = this.moveEvent, props = this.areaProps,
			over, overArea, overAreaEl, overAreaOffsets;
		
		// hide the marker while the the element under the cursor is found
		this.dropMarker.style.display = 'none';
		over = document.elementFromPoint(e.clientX, e.clientY);
		
		if (!over) {
			/* Todo: What needs to be unset here? */
			return;
		}
		
		// check if the cursor is over an empty space in a drop area
		if (over.hasAttribute('data-drag-drop')) {
			overAreaEl = over;
			overArea = this.getArea(overAreaEl);
			overAreaOffsets = overArea.$areaEl.offset();
			
			// if this area is vertical, check that the cursor isn't over the scrollbar
			// do this by checking for an element at the same y but at the left side of the area
			if (overArea.areaOrientation == 'vertical') {
				/* Potential Error: offset.left is not bound to the viewport but clientY is */
				over = document.elementFromPoint(overAreaOffsets.left, e.clientY);
			}
		}
		
		// get the item and area under the mouse
		if (over != overAreaEl) {
			while (over.parentNode && over.parentNode.hasAttribute) {
				if (over.parentNode.hasAttribute('data-drag-drop')) {
					overAreaEl = over.parentNode;
					overArea = this.getArea(overAreaEl);
					
					break;
				}
				
				over = over.parentNode;
			}			
		}
		
		if (!overArea) {
			/* Todo: What needs to be unset here? */
			this.overArea = null;
			return;
		}
		
		var offsets = $.fn.offset.call([over]), last,
			sizeA = over[props.outSize], sizeB = over[props.outSizeSec];
		
		if (this.over != over) {
			this.over = over;
			this.$over = $(over);
			
			if (this.overArea != overArea) {
				if (overArea.dragType == this.dragType) {
					this.validDrop = true;
				}
				else {
					return this.reset();
				}
				
				// add the marker to the new area
				overArea.areaEl.appendChild(this.dropMarker);
				// set its secondary size
				this.dropMarker.style[props.markerSize] = sizeB + 'px';
				
				this.overArea = overArea;
			}
		}
		
		this.dropMarker.style.display = 'block';
		
		if (over == overAreaEl) {
			var list = overAreaEl.children || overArea.$areaEl.children().get();
			// if the area does have children, use the last child as the reference
			if (list.length > 1) {
				var last = this.over = list[list.length - 2];
				this.$over = $(this.over);
			}
			else {
				this.ref = 0;
				this.dropMarker.style[props.position] = (overArea.$areaEl.position()[props.position] - (this.dropMarker[props.outSize] / 2)) + 'px';
				return;
			}
			
		}
		
		var positions = this.$over.position();
		var markerOffset = positions[props.position] - (this.dropMarker[props.outSize] / 2);
		
		// check if the cursor is on the top/left side of an element
		if (!last && e[props.pagePos] - offsets[props.position] < sizeA / 2) {
			this.dropMarker.style[props.position] = markerOffset + 'px';
			this.ref = over;
		}
		else {
			var next = (this.over.nextElementSibling !== undefined) ? this.over.nextElementSibling : this.$over.next()[0];
			
			this.dropMarker.style[props.position] = (markerOffset + this.over[props.outSize]) + 'px';
			this.ref = (next != this.dropMarker) ? next : 0;
		}
	},
	
	insertDrop: function() {
		var dropArea = this.overArea, dropAreaEl,
			selected = this.selected || [this.dragElement];
		
		if (dropArea && this.ref != this.dragElement) {
			dropAreaEl = dropArea.areaEl;
			dragChildren = $_(this.areaEl).children().get();
			
			if (this.ref !== 0) {
				var dropChildren = $_(dropAreaEl).children().get(),
					dropIndex = _.indexOf(dropChildren, this.ref);
			}
			
			for (var i = 0, target; (target = selected[i]); i++) {
				console.log(dragChildren, this.target);
				var index = _.indexOf(dragChildren, target),
					onSelf = (dropArea == this && dropIndex > index);
				
				if (this.dragValues) {
					for (var j = 0, valueName; (valueName = this.dragValues[j]); j++) {
						var value = this[valueName].splice(index, 1)[0];
							//newIndex = (onSelf) ? dropIndex - 1 : dropIndex;
						
						if (this.ref !== 0) {
							var newIndex = (dropArea == this && dropIndex > index) ? dropIndex - 1 : dropIndex;
							dropArea[valueName].splice(newIndex, 0, value);
						} else {
							dropArea[valueName].push(value);
						}
					}
				}
				
				dragChildren.splice(index, 1);
				/* dragChildren needs to update when dropped on self */
				if (this.ref !== 0) {
					dropAreaEl.insertBefore(target, this.ref);
					//if ()
				} else {
					dropAreaEl.appendChild(target);
				}
				/*if (this.dragValues) {
					var index = _.indexOf(dragChldren, this.target);
					for (var j = 0, values; (values = this.dragValues[j]); j++) {
						var value = this[values].splice(index, 1)[0];
						
						if (this.ref !== 0) {
							
						} else {
						dropArea.data.splice(dropIndex, 0, value);
						}
					}
					
					dragChldren.splice(index, 1);
				}
				//console.log(dropArea, target);
				if (this.ref !== 0) {
					for (var j = 0, valueName; (valueName = this.dragValues[j]); j++)
					if (this.data) {
						dropArea.data.splice(dropIndex, 0, value);
					}
					
					dropAreaEl.insertBefore(target, this.ref);
				} else {
					if (this.data) {
						dropArea.data.push(value);
					}
					dropAreaEl.appendChild(target);
				}*/
			}
		}
	},
	
	getArea: function(areaEl) {
		var id = areaEl.getAttribute('data-drag-drop');
		return this.areaList[id];
	}
	/*,
	
		areas: [],
		
		// values that should be set to null
		temp: ['area', 'ref', 'under'],
		
		initialize: function(options) {
			var area;
			
			if (options) {
				area = options.area;
				this.data = options.data;
				this.selected = options.selected;
			}
			// if no custom area element was passed, use the main element
			this.areaEl = (area && area.addClass ? area[0] : area) || this.parent.el;
			
			$(this.areaEl)
				.mousedown(function(e) {
					if (!$.browser.msie && e.which === 1) {
						e.preventDefault();
					}
				})
				.bind('selectstart', function(e) {
					e.preventDefault();
				});
			
			// add the dragDrop class and bind the ID to the main element
			this.areaEl.className += ' drag-drop-area';
			this.areaEl.setAttribute('data-drag-drop', this.areas.length);
			
			this.areas.push(this);
			
			// create the drop marker (shows where the row will be dropped)
			this.marker = document.createElement('div');
			this.marker.className = 'drag-marker';
			
			if (this.orientation) {
				this.props = this.props[this.orientation];
			}
			
			_.bindAll(this, 'begin', '_move', '_drop', '_scroll');
		},
		
		begin: function(e) {
			if (e.which != 1) {
				return;
			}
			
			e.preventDefault();
			
			// if no custom target was set, use the event element
			this.target = this.target || e.currentTarget;
			this.$target = $(this.target);
			
			this.initial = {x: e.pageX, y: e.pageY};
			this.prev = {x: e.pageX, y: e.pageY};
			this.offset = {};
			
			var offsets = this.$target.offset();
			this.layer = {
				x: e.pageX - offsets.left,
				y: e.pageY - offsets.top
			};
			
			if (this.drag && this.drag(e) === false) {
				return;
			}
			
			$(document)
				.bind('mousemove.dragDrop', this._move)
				.bind('mouseup.dragDrop', this._drop);
			
			this.parent.$el.bind('scroll', this._scroll);
			
			this._move(e);
		},
		
		_move: function(e) {
			this.event = e;
			this.offset.x = e.pageX - this.layer.x;
			this.offset.y = e.pageY - this.layer.y;
			
			if (this.move) {
				this.move(this, e);
			}
			
			this.prev.x = e.pageX
			this.prev.y = e.pageY;
			this.prevArea = this.area;
		},
		
		_drop: function(e) {
			// remove the drop marker
			this.marker.parentNode && this.marker.parentNode.removeChild(this.marker);
			
			$(document).unbind('.dragDrop');
			
			this.parent.$el.unbind('scroll', this._scroll);
			
			this.drop && this.drop(e);
			
			this.area = this.target = this.under = null;
		},
		
		_scroll: function() {
			this._move(this.event);
		},
		
		checkDrop: function() {
			var e = this.event, props = this.props,
				under, area, areaEl, areaOffsets;
			
			// hide the marker while the the element under the cursor is found
			this.marker.style.display = 'none';
			under = document.elementFromPoint(e.clientX, e.clientY), area;
			
			if (!under) {
				return this.reset();
			}
			
			// check if the cursor is over an empty space in a drop area
			if (under.hasAttribute('data-drag-drop')) {
				areaEl = under;
				area = this.getArea(areaEl);
				areaOffsets = $.fn.offset.call([under]);
				// if this area is vertical, check that the cursor isn't over the scrollbar
				// do this by checking for an element at the same y but at the left side of the area
				if (area.orientation == 'vertical') {
					var areaLeft = areaOffsets.left;
					under = document.elementFromPoint(areaLeft, e.clientY);
				}
			}
			
			if (under != areaEl) {
				while (under.parentNode && under.parentNode.hasAttribute) {
					if (under.parentNode.hasAttribute('data-drag-drop')) {
						areaEl = under.parentNode;
						break;
					}
					under = under.parentNode;
				}			
			}
			
			if (!areaEl) {
				return this.reset();
			}
			
			var offsets = $.fn.offset.call([under]), last,
				sizeA = under[props.outSize], sizeB = under[props.outSizeSec];
			
			if (this.under != under) {
				this.under = under;
				this.$under = $(under);
				
				area = area || this.getArea(areaEl);
				
				if (this.area != area) {
					if (area.type == this.type) {
						this.validDrop = true;
					}
					else {
						return this.reset();
					}
					
					// add the marker to the new area
					area.areaEl.appendChild(this.marker);
					// set it's secondary size
					this.marker.style[props.markerSize] = sizeB + 'px';
					
					this.area = area;
				}
			}
			
			this.marker.style.display = 'block';
			
			if (under == areaEl) {
				var list = areaEl.children || $(areaEl).children().get();
				if (list.length > 1) {
					var last = this.under = list[list.length - 2];
					this.$under = $(this.under);
				} else {
					this.ref = 0;
					this.marker.style[props.position] = ($(areaEl).position()[props.position] - (this.marker[props.outSize] / 2)) + 'px';
					return;
				}
				
			}
			
			var positions = this.$under.position();
			var markerOffset = positions[props.position] - (this.marker[props.outSize] / 2);
			// check if the cursor is on the top/left side of an element
			if (!last && e[props.pagePos] - offsets[props.position] < sizeA / 2) {
				this.marker.style[props.position] = markerOffset + 'px';
				this.ref = under;
			}
			else {
				var next = (this.under.nextElementSibling !== undefined) ? this.under.nextElementSibling : this.$under.next()[0];
				
				this.marker.style[props.position] = (markerOffset + this.under[props.outSize]) + 'px';
				this.ref = (next != this.marker) ? next : 0;
			}
		},
		
		insert: function() {
			var dropArea = this.area, dropAreaEl,
				selected = this.selected || [this.target];
			
			if (dropArea && this.ref != this.target) {
				dropAreaEl = dropArea.areaEl;
				dragChldren = $(this.areaEl).children().get();
				
				if (this.ref !== 0) {
					var dropChildren = $(dropAreaEl).children().get(),
						dropIndex = _.indexOf(dropChildren, this.ref);
				}
				
				for (var i = 0, target; (target = selected[i]); i++) {
					if (this.data) {
						var index = _.indexOf(dragChldren, this.target),
							value = this.data.splice(index, 1)[0];
						
						dragChldren.splice(index, 1);
					}
					
					if (this.ref !== 0) {
						if (this.data) {
							dropArea.data.splice(dropIndex, 0, value);
						}
						
						dropAreaEl.insertBefore(target, this.ref);
					} else {
						if (this.data) {
							dropArea.data.push(value);
						}
						dropAreaEl.appendChild(target);
					}
				}
				
				var realOrder = [];
				$(this.areaEl).children().each(function() {
					var num = parseInt(this.innerHTML.replace('Row_', ''));
					realOrder.push(num);
				});
				console.log(realOrder);
			}
		},
		
		reset: function() {
			for (var i = 0, propName; (propName = this.temp[i]); i++) {
				this[propName] = null;
			}
			
			if (this.marker.parentNode) {
				this.marker.parentNode.removeChild(this.marker);
			}
		},
		
		getArea: function(el) {
			var id = el.getAttribute('data-drag-drop');
			return this.areas[id];
		}
	}*/
}
