function DragonDrop() {
	
	/* Object being dragged */
	this.dragging = null;

	/* Z-order management */
	this.firstZ = 1000;
	this.nextZ = 0;
	this.objArr = new Array();
	
	/* Array of swappable objects */
	this.swappableArr = new Array();
	
	/* this function registers an object as draggable
	 * the first parameter is the object to be dragged.  This is the only required parameter.
	 * the second parameter is the optional controller for the object, which must be a child.
	 * For example, in a window, you would grab the title bar, but the whole window is dragged.
	 * The last parameter is an array of optional parameters, which are fairly obvious and listed below.
	 */
	this.makeDraggable = function(obj, childController, params) {
				
		/* optional offset coordinates upon registration with draggable class */
		if (!params['offsetTop']) params['offsetTop'] = false;
		if (!params['offsetLeft']) params['offsetLeft'] = false;
		
		/* restrict to horizontal axis or vertical axis? */
		if (!params['vertical']) params['vertical'] = false;
		if (!params['horizontal']) params['horizontal'] = false;
		
		/* default cursors */
		if (params['vertical'] && !params['cursor']) params['cursor'] = 'n-resize';
		if (params['horizontal'] && !params['cursor']) params['cursor'] = 'w-resize';
		if (params['disable_drag'] && !params['cursor']) params['cursor'] = 'default';
		if (!params['cursor']) params['cursor'] = 'move';
		
		/* make semi-transparent while dragging? */
		if (!params['dragTransparency'] || params['dragTransparency'] == 1) params['dragTransparency'] = false;
		
		/* register as swappable? */
		if (!params['swappable']) params['swappable'] = false;
				
				
		var coord = DD.getCenteredCoordinates(obj);
		var top = coord.top;
		var left = coord.left;
		
		obj.appendTo('body');
		
		if (params['offsetTop']) {
			top = params['offsetTop'];
		}
		if (params['offsetLeft']) {
			left = params['offsetLeft'];
		}
		
		/* if object is absolute and no offset is given, it will be centered on the user's screen */
		if (obj.css('position') == 'absolute') {
			obj.css('top', top).css('left', left);
		}
		else {
			obj.css('top', 0).css('left', 0);
		}
		
		
		/* Establish z-indexing for management later on */
		obj.css('z-index', DD.firstZ + DD.nextZ);
		DD.objArr[DD.nextZ] = obj;
		DD.nextZ++;
		
		
		/* use child controller, if applicable. */
		var dragObj;
		if (!childController) {
			dragObj = obj;
		}
		else {
			dragObj = $('#'+childController);
		}
		dragObj.attr('controlParent', obj.attr('id'));
		
		
		/* assign some misc properties.
		 */
		dragObj.css('cursor', params['cursor']);
		obj.attr('vertical', params['vertical']);
		obj.attr('horizontal', params['horizontal']);
		obj.attr('disable_drag', params['disable_drag']);
		obj.attr('dragTransparency', params['dragTransparency']);
		
		/* register as swappable, if applicable */
		//obj.setAttribute('swappable', params['swappable']);
		//if (params['swappable']) DD.swappableArr.push(obj);
		
		
		/* capture events */
		dragObj.mousedown(function() {
			DD.drag($(this).attr('controlParent'));
		});
		document.onmouseup = function() {
			DD.stop();
		}
		
		/* register absolute position */
		//DD.getAbsolutePosition(obj);
		
	}
	
	
	/* This function is used to create new swappable objects on the fly.
	 * It uses an array of parameters, which have default values.
	 */
	this.createDraggable = function(params) {
		
		/* Type of object: window or box. 
		 * color: blue, white, yellow, or green.
		 * width & height: duh.
		 * title: if window, give it a title.
		 * overflow: how to handle overflow on each axis
		 */
		if (!params['type']) params['type'] = 'window';
		if (!params['color']) params['color'] = 'white';
		if (!params['width']) params['width'] = 300;
		if (!params['height']) params['height'] = 200;
		if (!params['title']) params['title'] = 'Message from myYearbook:';
		if (!params['overflow-x']) params['overflow-x'] = 'hidden';
		if (!params['overflow-y']) params['overflow-y'] = 'hidden';
		if (params['curtain']) params['disable_drag'] = true;
		
		if (params['type'] == 'window') {
			obj = DD.createWindow(params['width'], params['height'], params['color'], params['title'], params['curtain']);
		}
		else if (params['type'] == 'box') {
			obj = DD.createBox(params['width'], params['height'], params['color']);
		}
		
		DD.makeDraggable(obj, obj.attr('title_id'), params);
		
		if (params['curtain'] == true && !document.getElementById('DD_curtain')) {
			DD.makeCurtain();
		}
		if(params['content'])	$('#'+obj.attr('content_id')).html(params['content']);
		return obj;
		
	}
	

	/* This function creates a new window object.
	 */
	this.win_cnt = 0;
	this.createWindow = function(width, height, color, title, curtain) {
		
		id = 'dd_win'+DD.win_cnt;
		DD.win_cnt++;
		
		var win = $('<div />').attr('id', id)
		var win_title = $('<div />').attr('id', id+'title');
		var win_close = $('<div />').attr('id', id+'close');
		var win_content = $('<div />').attr('id', id+'content');
		
		win.width(width);
		win.height(height);
		win_title.width(width-6);
		win_title.height(20);
		win_close.width(26);
		win_close.height(23);
		
		win_title.css('text-align', 'left');
		
		win.addClass('DD_Win').addClass('DD_Win_'+color);
		win_title.addClass('DD_Win_title').addClass('DD_Win_title_'+color);
		win_close.addClass('DD_Win_close');
		win_content.addClass('DD_Win_content');
	
		if (title.length > 40) title = title.substring(0,40) + '...';
		win_title.html( title );
		win_title.css('overflow', 'hidden');
				
		win_close.html('<a href="#" onClick="DD.destroy(\''+id+'\'); return false;"><img src="'+IMAGE_URL+'/dragon_drop/'+color+'_x.gif"></a>');
		
		win.attr('title_id', win_title.attr('id'));
		win.attr('close_id', win_close.attr('id'));
		win.attr('content_id', win_content.attr('id'));
		
		win_close.appendTo(win_title);
		win_title.appendTo(win);
		win_content.appendTo(win);

		return win;
	
	}
	

	/* This function creates a new box object
	 */
	this.box_cnt = 0;
	this.createBox = function(width, height, color) {
	
		box = document.createElement('div');

		id = 'box'+DD.box_cnt;
		DD.box_cnt++;

		box.id = id;
		
		box.style.position = 'absolute';

		box.style.width = width+'px';
		box.style.height = height+'px';

		box.className = 'DD_Box';

		document.body.appendChild(box);
		
		return box;

	}
	
	
	/* Remove an object from the document
	 */
	this.destroy = function(id, keepcurtain) {
		$('#'+id).remove();
		if (!keepcurtain) {
			$('#DD_curtain').remove();
			$('html').css('overflow', 'auto');
		}
	}
	
	/* make a curtain for the screen, if requested
	 */
	this.makeCurtain = function() {
		
		var curH = document.documentElement.clientHeight || '100%';
		var offsetX = document.documentElement.scrollLeft || document.body.scrollLeft;
		var offsetY = document.documentElement.scrollTop || document.body.scrollTop;
		$('html').css('overflow', 'hidden');
		var cur = $('<div />').attr('id', 'DD_curtain').addClass('DD_curtain').css({ top: offsetY, left: offsetX, width: '100%', height: curH, 'z-index': ( this.firstZ - 1 ) }).appendTo('body');
		window.scroll(offsetX, offsetY);
		
	}
	

	/* this is called from an object's onmousedown event, and registers the drag object
	 */
	this.drag = function(targ) {
		
		targ = $('#'+targ);
		
		if (targ.attr('dragTransparency')) {
			DD.setTransparency(targ.attr('id'), targ.attr('dragTransparency'));
		}
		
		/* Establish new z-order */
		var temp_arr = new Array();
		var temp_cnt = 0;
		for (i=0; i<DD.objArr.length; i++) {
			//Make a new array excluding the target object
			if (i != targ.css('z-index') - DD.firstZ) {
				DD.objArr[i].css('z-index', temp_cnt + DD.firstZ);
				temp_arr[temp_cnt] = DD.objArr[i];
				temp_cnt++;
			}
		}
		targ.css('z-index', temp_cnt + DD.firstZ);
		temp_arr[temp_cnt] = targ;
		DD.objArr = temp_arr;
				
		targ.css('MozUserSelect', 'none');	
		document.onselectstart = function() {
			return false;
		}
		
		/* if swappable, register location from which the object was picked up.
		 * unlike jersey trash, which does not get picked up
		 */
		//if (targ.getAttribute('swappable')) {
		//	DD.empty_x = parseInt(targ.style.left);
		//	DD.empty_y = parseInt(targ.style.top);
		//}
		
		/* finally, register object as the one being dragged. */
		DD.dragging = targ;

	}
	

	/* Stop dragging function (onmouseup) 
	 */
	this.stop = function() {
		
		if (DD.dragging!=null) {
			DD.dragging.css('MozUserSelect', '');	
			document.onselectstart = null;
			
			if (DD.dragging.attr('dragTransparency')) {
				DD.setTransparency(DD.dragging.attr('id'), 100);
			}
		}
		
		DD.dragging = null;

	}
	
	/* keep track of mouse position, and drag object if registered
	 */
	this.x = 0;
	this.y = 0;
	this.prev_x = 0;
	this.prev_y = 0;
	this.captureMouseXY = function(e) {
		
		if ($.browser.msie) {
			DD.x = event.clientX + document.body.scrollLeft;
			DD.y = event.clientY + document.body.scrollTop;
		}
		else {
			DD.x = e.pageX;
			DD.y = e.pageY;
		}
		
		/* If there's an object being dragged...
		 */
		if (DD.dragging && (DD.dragging.attr('disable_drag')!='true' && DD.dragging.attr('disable_drag')!=true)) {
			
			diff_x = DD.x - DD.prev_x;
			diff_y = DD.y - DD.prev_y;
			
			new_x = parseInt(DD.dragging.css('left')) + diff_x;
			new_y = parseInt(DD.dragging.css('top')) + diff_y;
			
			
			/* if the curtain is visible, restrict dragging to visible area */
			if ($('#DD_curtain')) {
				winW = window.innerWidth || document.documentElement.clientWidth;
				winH = window.innerHeight || document.documentElement.clientHeight;
				offsetY = document.documentElement.scrollTop || document.body.scrollTop;
				winH = winH + offsetY;
				
				w = parseInt(DD.dragging.css('width'));
				h = parseInt(DD.dragging.css('height'));
				x = parseInt(DD.dragging.css('left'));
				y = parseInt(DD.dragging.css('top'));
				
				if (0 > new_x) {
					new_x = 0;
				}
				if (winW < (new_x + w)) {
					new_x = winW - w;
				}
				if (0 > new_y) {
					new_y = 0;
				}
				if (winH < (new_y + h)) {
					new_y = winH - h;
				}
			}
			/* end curtain restrict */


			/* if not restricted to vertical, we can move horizontally.
			 */
			if (DD.dragging.attr('vertical') == 'false' || !DD.dragging.attr('vertical')) {
				DD.dragging.css('left', new_x);
			}
			
			/* if not restricted to horizontal, we can move vertically.
			 */
			if (DD.dragging.attr('horizontal') == 'false' || !DD.dragging.attr('horizontal')) {
				DD.dragging.css('top', new_y);
			}

			/* perform swap if:
			 * -registered as swappable
			 * -object found below (collision)
			 * -object below is registered as swappable
			 */
			/*if (DD.dragging.getAttribute('swappable')=='true' && (below = DD.getElementBelow(DD.dragging))) {
				if (below.getAttribute('swappable')) {
					var empty_x = parseInt(below.style.left);
					var empty_y = parseInt(below.style.top);
					below.style.left = DD.empty_x+'px';
					below.style.top = DD.empty_y+'px';
					DD.empty_x = empty_x;
					DD.empty_y = empty_y;
					DD.getAbsolutePosition(below);
				}
			}
			*/
		}

		DD.prev_x = DD.x;
		DD.prev_y = DD.y;

	}
	

	/* function that organizes the z-indexes for all registered objects */
	this.organizeZ = function() {
		
		for (i=0; i<DD.nextZ; i++) {
			try {
				DD.objArr[i].style.zIndex = DD.firstZ + i;
			}
			catch (e) {}
		}

	}
	
	/* given an object, this function will register attributes
	 * for absolute top, left, bottom, and right positions, regardless of CSS position property.
	 */
	this.getAbsolutePosition = function(obj) {
		
		var tempObj = obj;
		var curtop = curleft = curbottom = curright = 0;
		if (tempObj.offsetParent) {
			curleft = tempObj.offsetLeft
			curtop = tempObj.offsetTop
			while (tempObj = tempObj.offsetParent) {
				curleft += tempObj.offsetLeft
				curtop += tempObj.offsetTop
			}
		}
		curbottom = curtop+parseInt(obj.style.height);
		curright = curleft+parseInt(obj.style.width);

		obj.setAttribute('abs_top', Number(curtop));
		obj.setAttribute('abs_left', Number(curleft));
		obj.setAttribute('abs_bottom', Number(curbottom));
		obj.setAttribute('abs_right', Number(curright));
	}
	
	/* This function looks for a collision with another registered object
	 */
	this.pad = 5;
	this.getElementBelow = function(obj) {

		DD.getAbsolutePosition(obj);
		for (i=0; i<DD.objArr.length; i++) {
			if (DD.objArr[i] != obj) {
				var l = (	( Number(obj.getAttribute('abs_left')) >= Number(DD.objArr[i].getAttribute('abs_left')) )	&& 	( Number(obj.getAttribute('abs_left')) <= Number(DD.objArr[i].getAttribute('abs_right')) )	);
				var t = (	( Number(obj.getAttribute('abs_top'))  >= Number(DD.objArr[i].getAttribute('abs_top')) )	&& 	( Number(obj.getAttribute('abs_top')) <= Number(DD.objArr[i].getAttribute('abs_bottom')) )	);
				//var r;
				//var b;
				//DD.printDebug('l='+l+', t='+t+', r='+r+', b='+b);
				if (l && t) {
					//return false;
					return DD.objArr[i];
					break;
					break;
				}
			}

		}
		return false;
	}

	this.printDebug = function(str) {
		document.getElementById('debug').value = str;
	}
	
	this.setTransparency = function(id, tr) {		
		$('#'+id).css('-moz-opacity', tr).css('filter', 'alpha(opacity='+(tr * 100)+')');
	}
	
	
	
	this.getCenteredCoordinates = function(obj) {
		winW = document.documentElement.clientWidth || document.body.offsetWidth;
        winH = document.documentElement.clientHeight || document.body.offsetHeight;
        offsetY = document.documentElement.scrollTop || document.body.scrollTop;
		
		var top = (winH/2) - (parseInt(obj.css('height'))/2) + offsetY;
		var left = (winW/2) - (parseInt(obj.css('width'))/2);
		
		return { top:top, left:left };
	}
	
	
	this.alertBox = function(msg_str, custom_params) {
		
		if ( !custom_params ) custom_params = { curtain:true, height:150 };
		if ( !custom_params.height ) custom_params.height = 150;
		
		var alert_box = DD.createDraggable( custom_params );
		$('#'+alert_box.attr('content_id')).css('font-size', '14px').html(msg_str + '<a href="#" onClick="DD.destroy(\'' + alert_box.attr('id') + '\'); return false;"><img src="'+IMAGE_URL+'/btn_closewindow.gif" /></a>');
		
	}
	
	
}

/* call new class object */
var DD = new DragonDrop();

/* begin capturing mouse movement event */
document.onmousemove = DD.captureMouseXY;
