jQuery.tableDnD = {
	
	currentTable : null,
	dragObject: null,
	mouseOffset: null,
	oldY: 0,
	order: new Array(),
	
	build: function(options) {
        options = options || {};

		this.each(function() {
			this.tableDnDConfig = {
				onDragElement: options.onDragElement,
				onDragStyle: options.onDragStyle,
				onDropStyle: options.onDropStyle,
				onDragClass: options.onDragClass ? options.onDragClass : "tDnD_whileDrag",
				onDrop: options.onDrop,
				onDragStart: options.onDragStart,
				scrollAmount: options.scrollAmount ? options.scrollAmount : 5
			};
			jQuery.tableDnD.makeDraggable(this);
			
		});

		jQuery(document)
			.bind('mousemove', jQuery.tableDnD.mousemove)
			.bind('mouseup', jQuery.tableDnD.mouseup);
	
		return this;
	},

	makeDraggable: function(table) {
		var rows = table.rows; //getElementsByTagName("tr")
		var config = table.tableDnDConfig;
		
		for (var i=0; i<rows.length; i++) {

			var nodrag = $(rows[i]).hasClass("nodrag");
			if (! nodrag) { //There is no NoDnD attribute on rows I want to drag
	
				jQuery( config.onDragElement || rows[i] ).mousedown(function(ev) {						
					currentTr = $(this).parents('tr').get(0) || this;
					//alert(currentTr.nodeName);
	
					jQuery.tableDnD.dragObject = currentTr;
					jQuery.tableDnD.currentTable = table;
					jQuery.tableDnD.mouseOffset = jQuery.tableDnD.getMouseOffset(currentTr, ev);
					if (config.onDragStart) {
						config.onDragStart(table, currentTr);
					}
					return false;
				}).css("cursor", "move"); // Store the tableDnD object
			}
		}
	},
	
	mouseCoords: function(ev){
		if(ev.pageX || ev.pageY){
			return {x:ev.pageX, y:ev.pageY};
		}
		return {
			x:ev.clientX + document.body.scrollLeft - document.body.clientLeft,
			y:ev.clientY + document.body.scrollTop  - document.body.clientTop
		};
	},
	
	getMouseOffset: function(target, ev) {
		ev = ev || window.event;
	
		var docPos    = this.getPosition(target);
		var mousePos  = this.mouseCoords(ev);
		return {x:mousePos.x - docPos.x, y:mousePos.y - docPos.y};
	},
	
	getPosition: function(e){
		var left = 0;
		var top  = 0;
		if (e.offsetHeight == 0) {
			e = e.firstChild; // a table cell
		}
	
		while (e.offsetParent){
			left += e.offsetLeft;
			top  += e.offsetTop;
			e     = e.offsetParent;
		}
	
		left += e.offsetLeft;
		top  += e.offsetTop;
	
		return {x:left, y:top};
	},
	
	mousemove: function(ev) {
		if (jQuery.tableDnD.dragObject == null) {
			return;
		}
	
		var dragObj = jQuery(jQuery.tableDnD.dragObject);		
		var config = jQuery.tableDnD.currentTable.tableDnDConfig;
		var mousePos = jQuery.tableDnD.mouseCoords(ev);
		var y = mousePos.y - jQuery.tableDnD.mouseOffset.y;
		var yOffset = window.pageYOffset;
		if (document.all) {
			if (typeof document.compatMode != 'undefined' &&
				 document.compatMode != 'BackCompat') {
			   yOffset = document.documentElement.scrollTop;
			}
			else if (typeof document.body != 'undefined') {
			   yOffset=document.body.scrollTop;
			}
	
		}
			
		if (mousePos.y-yOffset < config.scrollAmount) {
			window.scrollBy(0, -config.scrollAmount);
		} else {
			var windowHeight = window.innerHeight ? window.innerHeight
				: document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight;
			if (windowHeight-(mousePos.y-yOffset) < config.scrollAmount) {
				window.scrollBy(0, config.scrollAmount);
			}
		}
	
	
		if (y != jQuery.tableDnD.oldY) {
			var movingDown = y > jQuery.tableDnD.oldY;
			// update the old value
			jQuery.tableDnD.oldY = y;
			if (config.onDragClass) {
				dragObj.addClass(config.onDragClass);
			} else {
				dragObj.css(config.onDragStyle);
			}
			var currentRow = jQuery.tableDnD.findDropTargetRow(dragObj, y);
			if (currentRow) {
				if (movingDown && jQuery.tableDnD.dragObject != currentRow) {
					jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow.nextSibling);
				} else if (! movingDown && jQuery.tableDnD.dragObject != currentRow) {
					jQuery.tableDnD.dragObject.parentNode.insertBefore(jQuery.tableDnD.dragObject, currentRow);
				}
			}
		}
	
		return false;
	},
	
	findDropTargetRow: function(draggedRow, y) {
		var rows = jQuery.tableDnD.currentTable.rows;
		for (var i=0; i<rows.length; i++) {
			var row = rows[i];
			var rowY    = this.getPosition(row).y;
			var rowHeight = parseInt(row.offsetHeight)/2;
			if (row.offsetHeight == 0) {
				rowY = this.getPosition(row.firstChild).y;
				rowHeight = parseInt(row.firstChild.offsetHeight)/2;
			}
			if ((y > rowY - rowHeight) && (y < (rowY + rowHeight))) {
				if (row == draggedRow) {return null;}
				var config = jQuery.tableDnD.currentTable.tableDnDConfig;
				if (config.onAllowDrop) {
					if (config.onAllowDrop(draggedRow, row)) {
						return row;
					} else {
						return null;
					}
				} else {
					var nodrop = $(row).hasClass("nodrop");
					if (! nodrop) {
						return row;
					} else {
						return null;
					}
				}
				return row;
			}
		}
		return null;
	},
	
	mouseup: function(e) {
		if (jQuery.tableDnD.currentTable && jQuery.tableDnD.dragObject) {
			var droppedRow = jQuery.tableDnD.dragObject;
			var config = jQuery.tableDnD.currentTable.tableDnDConfig;
			if (config.onDragClass) {
				jQuery(droppedRow).removeClass(config.onDragClass);
			} else {
				jQuery(droppedRow).css(config.onDropStyle);
			}
			jQuery.tableDnD.dragObject   = null;
			if (config.onDrop) {
				config.onDrop(jQuery.tableDnD.currentTable, droppedRow);
			}
			jQuery.tableDnD.currentTable = null; // let go of the table too
		}
	},
	
	serialize: function( sAttr, sEl ) {
	   if (jQuery.tableDnD.currentTable) {
			var result = new Array();				
			var rows = jQuery.tableDnD.currentTable.rows;
			for (var i=0; i<rows.length; i++) {
				sEl ? fixsEl = $(sEl, rows[i]) : fixsEl = $(rows[i]);
				result[i] = fixsEl.attr(sAttr);
				}
			return result;
		} 
	}
}

jQuery.fn.extend(
	{
		tableDnD : jQuery.tableDnD.build
	}
);