
Object.addProperties = function(obj, properties) {
   
   for (property in properties) 
     {
	obj[property] = properties[property];
     }
   
   return obj;
}

Object.createObject = function() 
{
   return function() 
     {
	this.initialize.apply(this, arguments);
     }
}

Function.prototype.bindEvent = function(object)
{
   var __me = this;
   return function(event) 
     {
	return __me.call(object, event || window.event);
     }
}

Function.prototype.bindObject = function()
{
   var __me = this, args = new Array(arguments), object = args.shift();
   return function() 
     {
	return __me.apply(object, args.concat(new Array(arguments)));
     }
}


/* Arrays */

Object.addProperties(Array.prototype, {
   
   indexOf: function(data) 
     {
	for (var i=0; i < this.length; i++) 
	  {
	     if (this[i] == data) return i;
	  }
	
	return -1;
     }
   ,     
     inArray: function(data) 
       {
	  if (this.indexOf(data) > -1) 
	    {
	       return true;
	    }
	  else
	    {
	       return false;
	    }
       }
   ,     
     remove: function(data)
       {
	  var i = this.indexOf(data);
	  
	  if (i > -1) 
	    {
	       this.splice(i, 1);
	    }
	  
	  return i;
       }
   ,
     implode: function(seperator)
       {
	  var values = new String(this.toString());
	  
	  return values.replace(/,/g, seperator);
       }
   
});

var Dimension = function(w, h) 
{
   this.width = w;
   this.height = h;
}

Dimension.prototype = 
{
   width: 0,
   height: 0
}

var Position = function(posX, posY)
{
   this.x = posX;
   this.y = posY;
}

Position.prototype =
{
   x: 0,
   y: 0
}
		     
/* DOM */

var DOM_ELEMENT_NODE = 1;
var DOM_ATTRIBUTE_NODE = 2;
var DOM_TEXT_NODE = 3;
var DOM_CDATA_SECTION_NODE = 4;
var DOM_ENTITY_REFERENCE_NODE = 5;
var DOM_ENTITY_NODE = 6;
var DOM_PROCESSING_INSTRUCTION_NODE = 7;
var DOM_COMMENT_NODE = 8;
var DOM_DOCUMENT_NODE = 9;
var DOM_DOCUMENT_TYPE_NODE = 10;
var DOM_DOCUMENT_FRAGMENT_NODE = 11;
var DOM_NOTATION_NODE = 12;

var DOMElement = new Object();
		     
Object.addProperties(DOMElement, {
   
   buVersion: 0.5,
     
     getClasses: function()
       {
	  var classes = new Array();
	  
	  classes = String(this.className).split(' ');
	  
	  return classes;
       }
   ,
     hasClass: function(className)
       {
	  var classes = this.getClasses();
	  
	  return classes.inArray(className);
       }
   ,
     addClass: function(className)
       {
	  if (!this.hasClass(className)) 
	    {
	       var classes = this.getClasses();
	       
	       classes.push(className);
	       
	       this.className = classes.implode(' ');
	    }
       }
   ,
     removeClass: function(className)
       {
	  if (this.hasClass(className)) 
	    {
	       var classes = this.getClasses();
	       
	       classes.remove(className);
	       
	       this.className = classes.implode(' ');
	    }
       }
   ,
     destroy: function()
       {
	  this.parentNode.removeChild(this);
       }
   ,
    isDisplayed: function()
       {
	  return this.style.display != "none";
       }
   ,
     hide: function()
       {
	  this.style.display = "none";
       }
   ,
     display: function()
       {
	  if (arguments.length == 1) 
	    {
	       this.style.display = arguments[0];
	    }
	   else 
	    {
	       this.style.display = '';
	    }
       }
   ,
     toggleDisplay: function()
       {
	  if (arguments.length == 1) 
	    {
	       if (this.isDisplayed()) 
		 {
		    this.hide();
		 }
	       else 
		 {
		    this.display(arguments[0]);
		 }
	    }
	  else 
	    {
	       if (this.isDisplayed()) 
		 {
		    this.hide();
		 }
	       else
		 {
		    this.display();
		 }
	    }
       }
   ,
     getDimensions: function()
       {
	  //return {w: this.clientWidth, h:this.clientHeight};
	  return {w: this.offsetWidth, h:this.offsetHeight};
       }
   ,
     getPosition: function()
       {
	  var xPos = 0;
	  var yPos = 0;
	  
	  /* todo: object detection! ugh */
	  if (window.navigator.appName == "Netscape")
	    {
	       var element = this;
	       
	       while (element) 
		 {
		    xPos += element.offsetLeft;
		    yPos += element.offsetTop;
		    element = element.offsetParent;
		 }
	       
	       element = this;
	       
	       while((element) && (element != document.body) && (element != document.documentElement)) 
		 {
		    if (element.scrollLeft) 
		      {
			 xPos -= element.scrollLeft;
		      }
		    
		    if (element.scrollTop)
		      {
			 yPos -= element.scrollTop;
		      }
		    
		    element = element.parentNode;
		 }
	       
	    }
	  else 
	    {
	       var element = this;
	       
	       while (element) 
		 {
		    xPos += element.offsetLeft - element.scrollLeft;
		    yPos += element.offsetTop - element.scrollTop;
		    
		    element = element.offsetParent;
		 }	       
	    }
	  
	  return { x:xPos, y:yPos };
       }
   ,
     pointInside: function(x, y)
       {
	  var d = this.getDimensions();
	  var p = this.getPosition();
	  
	  //debug("DOMElement::pointInside(" + x + "," + y + ") [" + p.x + "," + p.y + "," + (p.x + d.w) + "," + (p.y + d.h) + "]");
	  
	  if (((x >= p.x) && (x <= p.x + d.w)) && ((y >= p.y) && (y <= p.y + d.h))) 
	    {
	       return true;
	    }
	  else 
	    {
	       return false;
	    }
	  
       }
   ,
     makeAbsolute: function()
       {
	  if (this.style.position == "absolute") return;
	  
	  this.deltaX = window.pageXOffset 
	    || document.documentElement.scrollLeft
	    || document.body.scrollLeft
	    || 0;
	  
	  this.deltaY = window.pageYOffet
	    || document.documentElement.scrollTop
	    || document.body.scrollTop
	    || 0;
	  
	  var top = this.offsetTop || 0;
	  var left = this.offsetLeft || 0;
	  
	  try 
	    {
	       var parent = this.offsetParent;
	       
	       while (parent) 
		 {
		    if ((parent.style.position == "absolute") || (parent.style.position == "relative")) 
		      {
			 break;
		      }
		    
		    top += parent.offsetTop || 0;
		    left += parent.offsetLeft || 0;
		    
		    parent = parent.offsetParent;
		 }
	    }
	  catch (e) 
	    {
	    }
	  
	  var width = this.clientWidth;
	  var height = this.clientHeight;
	  
	  this._originalLeft = left - parseFloat(this.style.left || 0);
	  this._originalTop = top - parseFloat(this.style.top || 0);
	  this._originalWidth = this.style.width;
	  this._originalHeight = this.style.height;
	  
	  this.style.position = "absolute";
	  this.style.top = top + "px";
	  this.style.left = left + "px";
	  //this.style.width = width + "px";
	  //this.style.height = height + "px";
       }
   ,
     absolutelyPosition: function(x, y)
       {
	  this.makeAbsolute();
	  this.style.top = y + "px";
	  this.style.left = x + "px";
	  
	  var content = new String(this.innerHTML);
	  //this.innerHTML = content.replace(/\d+x\d+/, x + "x" + y);
       }
   ,
     getOpacity: function()
       {
	  var opacity = 1;
	  
	  var cssOpacity = this.style.opacity;
	  
	  var filterOpacity = new String(this.style.filter);

	  if (cssOpacity != undefined) 
	    {
	       opacity = parseFloat(cssOpacity);
	       if (isNaN(opacity)) opacity = 1;
	    } 
	  else if (filterOpacity.length > 0) 
	    {
	       var match = filterOpacity.match(/alpha\(opacity:(\d+)\)/);

	       if (match[1]) 
		 {
		    opacity = parseInt(match[1])/100;
		 }
	       
	    }
	  	  
	  return opacity;
       }
   ,
     setOpacity: function(opacity)
       {
	  if (opacity > 1)
	    opacity = 1;
	  
	  if (opacity < 0)
	    opacity = 0;
	  	  
	  var percent = Math.round(opacity * 100);
	  
	  this.style.filter = "alpha(opacity:" + percent + ")";
	  this.style.opacity = opacity;
								    
       }
   ,
     decreaseOpacity: function(percent)
       {
	  var opacityChange = percent/100;
	  
	  var opacity = this.getOpacity() - opacityChange;
	  
	  if (opacity > 1)
	    opacity = 1;
	  
	  if (opacity < 0)
	    opacity = 0;
	  
	  this.setOpacity(opacity);
       }
   ,
     increaseOpacity: function(percent)
       {
	  var opacityChange = percent/100;
	  
	  var opacity = this.getOpacity() + opacityChange;
	  
	  if (opacity > 1)
	    opacity = 1;
	  
	  if (opacity < 0)
	    opacity = 0;
	  
	  this.setOpacity(opacity);
       }
   ,
     throb: function(count)
       {
	  if (count == undefined) count = 1;
	  
	  var throbOut = function(element, callback)
	    {
	       return function()
		 {
		    element.fade(50, null, null, callback);
		 }
	    }
	  
	  var throbIn = function(element, callback)
	    {
	       return function()
		 {
		    element.fade(100, null, null, callback);
		 }
	    }
	  
	  var throbAgain = function(element, count)
	    {
	       return function()
		 {
		    element.throb(count);
		 }
	    }
	  
	  count--;
	  
	  var rethrob = null;
	  
	  if (count > 0) 
	    {
	       rethrob = throbAgain(this, count);
	    }
	  
	  var throb = throbOut(this, throbIn(this, rethrob));
	  	  
	  setTimeout(throb, 0);
	  
       }
   ,
     fade: function(percent, steps, duration, callback)
       {
	  this.fadeOpacity = percent/100;
	  this.fadeSteps = steps ? steps : 18;
	  this.fadeDuration = duration ? duration : this.fadeSteps * 10;
	  this.fadeCallback = callback;
	  this.fading = true;
	  
	  var fadeCall = this._fade(this);
	  
	  setTimeout(fadeCall, 0);
	  
       }
   ,
     _fade: function(element)
       {
	  return function()
	    {
	       var opacity = element.getOpacity();
	       
	       if (element.fadeSteps <= 0) 
		 {
		    element.fading = true;
		    element.fadeSteps = null;
		    element.fadeOpacity = null;
		    element.fadeDuration = null;
		    
		    if (element.fadeTimer) clearTimeout(element.fadeTimer);
		    
		    if (element.fadeCallback) setTimeout(element.fadeCallback, 0);
		    
		    return;
		 }
	       
	       if (element.fadeTimer) clearTimeout(element.fadeTimer);
	       
	       var duration = Math.round(element.fadeDuration/element.fadeSteps);
	       
	       var opacityChange = (element.fadeSteps > 0) ? ((element.fadeOpacity - opacity)/element.fadeSteps) : 0;
	       
	       opacity = opacity + opacityChange;
	       
	       //debug("Setting opacity to " + opacity + "(" + element._opacity + " + " + opacityChange + ")");
	       element.setOpacity(opacity);

	       element.fadeSteps--;		 
	       element.fadeDuration -= duration;
	       
	       var callback = element._fade(element);
	       
	       element.fadeTimer = setTimeout(callback, duration);

	    };	  
       }
   ,
     slide: function(x, y, steps, duration, callback)
       {
	  this.sliding = true;
	  this.slideSteps = steps ? steps : 18;
	  this.slideDuration = duration ? duration : this.slideSteps * 10;
	  this.slideX = x;
	  this.slideY = y;
	  this.slideCallback = callback;
	  	  
	  var slideCall = this._slide(this);
	  
	  this.slideTimer = setTimeout(slideCall, 0);
       }
   ,
     _slide: function(element)
       {
	  return function() 
	    {
	       
	       var pos = element.getPosition();
	       
	       if (element.slideSteps <= 0)
		 {
		    element.sliding = false;
		    element.slideSteps = null;
		    element.slideDuration = null;
		    element.slideX = null;
		    element.slideY = null;
		    
		    if (element.slideTimer) clearTimeout(element.slideTimer);
		    
		    if (element.slideCallback) 
		      {
			 setTimeout(element.slideCallback, 0);
		      }		    
		    
		    return;
		 }
	       
	       if (element.slideTimer) clearTimeout(element.slideTimer);
	       
	       var duration = Math.round(element.slideDuration/element.slideSteps);
	       
	       var x = parseInt(pos.x + ((element.slideX - pos.x)/element.slideSteps));
	       var y = parseInt(pos.y + ((element.slideY - pos.y)/element.slideSteps));
	       
	       //debug("Sliding; step: " + element.slideSteps + "; duration: " + element.slideDuration + "; current: " + pos.x + "x" + pos.y + "; to: " + x + "x" + y + "; target: " + element.slideX + "x" + element.slideY);
	       
	       element.absolutelyPosition(x, y);
	       
	       pos = element.getPosition();
	       
	       //debug("After slide step, postion is " + pos.x + "x" + pos.y);

	       element.slideSteps--;
	       element.slideDuration -= duration;

	       var callback = element._slide(element);
	       
	       element.slideTimer = setTimeout(callback, duration);
	    }
	  
       }
   ,
     addEvent: function(eventName, handler)
       {
   	  if (this.attachEvent) 
   	    {
   	       this['e' + eventName + handler] = handler;
	       
	       function makeHandler(element, eventName, handler) 
		 {
		    return function()
		      {
			 element['e' + eventName + handler](window.event);
		      }
		 }
	       
	       
   	       this[eventName + handler] = makeHandler(this, eventName, handler);
	       
   	       this.attachEvent("on" + eventName, this[eventName + handler]);
   	    }
   	  else if (this.addEventListener)
   	    {
   	       this.addEventListener(eventName, handler, false);
   	    }
   	   else 
   	    {
   	       /* should older browsers be supported? */
   	    }
       }
   ,
     removeEvent: function(eventName, handler)
       {
   	  if (this.detachEvent) 
   	    {
   	       this.detachEvent("on" + eventName, this[eventName + handler]);
   	       this[eventName + handler] = null;
   	    }
   	   else if (this.removeEventListener) 
   	    {
   	       this.removeEventListener(eventName, handler, false);
   	    }
   	  else 
   	    {
   	       /* todo? */
   	    }
       }
   ,
     getElementsByClass: function(className, tagName)
       {
   	  var elements = new Array();
	  
   	  if (tagName == null) 
   	    {
   	       tagName = '*';
   	    }
	  
   	  var children = this.getElementsByTagName(tagName);
	  
   	  for (var i=0; i < children.length; i++) 
   	    {
	       Object.addProperties(children[i], DOMElement);
   	       if (children[i].hasClass(className)) 
   		 {
   		    elements.push(children[i]);
   		 }
   	    }
	  
   	  return elements;
       }
   ,
     insertAfter: function(newElement, referenceElement)
       {
   	  if (referenceElement.nextSibling) 
   	    {
   	       this.insertBefore(newElement, referenceElement.nextSibling);
   	    }
   	  else 
   	    {
   	       this.appendChild(newElement);
   	    }
       }
});
		     
document._getElementById = document.getElementById;
		     
document.getElementById = function(id) 
  {
     var element = document._getElementById(id);
     
     if (id) 
       {
	  Object.addProperties(element, DOMElement);
	  	  
       }
     
     return element;
  }
		     
document._createElement = document.createElement;

document.createElement = function(type)
{
   var element = document._createElement(type);
   
   if (element)
     {
	Object.addProperties(element, DOMElement);
     }
   
   return element;
}

document.getElementsByClass = function(className, tagName)
{
   var elements = new Array();
   
   if (tagName == null) 
     {
	tagName = '*';
     }
   
   var children = document.getElementsByTagName(tagName);
   
   for (var i=0; i < children.length; i++) 
     {
	if (children[i].hasClass(className)) 
	  {
	     elements.push(children[i]);
	  }
     }
   
   return elements;
}

document.getBody = function()
{
   var body = document.body;
   
   Object.addProperties(body, DOMElement);
   
   return body;
}


function $() 
{
   var elements = new Array();
   
   for (var i=0; i < arguments.length; i++) 
     {
	var element = arguments[i];
	
	if (typeof(element) == 'string') 
	  {
	     element = document.getElementById(element);
	  }
	
	if (element.buVersion == undefined) 
	  {
	     Object.addProperties(element, DOMElement);
	  }
	
	if (arguments.length == 1) 
	  {
	     return element;
	  }
		
	elements.push(element);
     }
   
   return elements;
}

var DragGovernor = Object.createObject();

DragGovernor.prototype = 
{
   initialize: function()
     {
	this.elements = new Array();
	this.targets = new Array();
	this.dragging = null;
	this.activeTargets = false;
	this.onMouseDown = this.handleMouseDown.bindEvent(this);
	this.onMouseMove = this.handleMouseMove.bindEvent(this);
	this.onMouseOut = this.handleMouseOut.bindEvent(this);
	this.onMouseUp = this.handleMouseUp.bindEvent(this);
     }
   ,
     registerElement: function(draggableElement)
       {
	  this.elements.push(draggableElement);
	  this.addHandlers(draggableElement);
       }
   ,
     registerTarget: function(targetElement)
       {
	  this.targets.push(targetElement);
       }
   ,
     addHandlers: function(draggableElement)
       {
	  draggableElement.element.addEvent("mousedown", this.onMouseDown);
       }
   ,
     isDragging: function()
       {
	  return (this.dragging == null) ? false : true;
       }
   ,
     addTargetHandlers: function(draggableElement)
       {
	  for (var i=0; i < this.targets.length; i++) 
	    {
	       if (!this.targets[i].acceptsElement(draggableElement)) 
		 continue;

	       //this.targets[i].element.addEvent("mouseover", this.targets[i].onMouseOver);
	       //this.targets[i].element.addEvent("mouseout", this.targets[i].onMouseOut);
	       this.targets[i].active = true;
	       this.activeTargets = true;
	       
	       //debug("Added mouseover handler to target " + this.targets[i].element.id);
	       
	    }
       }
   ,
     removeTargetHandlers: function(draggableElement)
       {
	  for (var i=0; i < this.targets.length; i++) 
	    {
	       if (!this.targets[i].acceptsElement(draggableElement)) 
		 continue;

	       //this.targets[i].element.removeEvent("mouseover", this.targets[i].onMouseOver);
	       //this.targets[i].element.removeEvent("mouseout", this.targets[i].onMouseOut);
	       this.targets[i].active = false;
	       this.targets[i].deactivate();
	    }
	  
	  this.activeTargets = false;
       }
   ,
     startDrag: function(evt, draggableElement)
       {
	  /* todo: save original position */
	  this.startPositionXY = draggableElement.element.getPosition();
	  this.startPosition = draggableElement.element.style.position;
	  this.startDimensions = draggableElement.element.getDimensions();

	  this.startX = evt.screenX - this.startPositionXY.x;
	  this.startY = evt.screenY - this.startPositionXY.y;

	  //draggableElement.element.addEvent("mousemove", this.onMouseMove);
	  var body = document.getBody();
	  
	  body.addEvent("mouseup", this.onMouseUp);
	  body.addEvent("mousemove", this.onMouseMove);
	  
	  this.addTargetHandlers(draggableElement);

	  draggableElement.element.addClass("dragging");
	  
	  debug("Drag started at " + this.startX + "x" + this.startY + "; Element originally at " + this.startPositionXY.x + "x" + this.startPositionXY.y + " and positioned " + this.startPosition);
	  
	  this.stopEventPropagation(evt);
       }
   ,
     cancelDrag: function(evt)
       {
	  debug("cancelDrag");
	  if (!this.isDragging()) return;
	  	  
	  var afterSlide = function(draggableElement)
	    {
	       return (function() 
		 {
		    draggableElement.onCancel();
		 }
		       );
	    }
	  
	  var callback = afterSlide(this.dragging);
	  	  
	  this.dragging.element.slide(this.startPositionXY.x, this.startPositionXY.y, null, null, callback);
	  
	  this.stopDrag(evt);

       }
   ,
     stopDrag: function(evt)
       {
	  if (!this.isDragging()) return;
	  
	  this.dragging.element.removeClass("dragging");
	  
	  this.removeTargetHandlers(this.dragging);
	  
	  this.dragging = null;
	  
	  var body = document.getBody();
	  
	  body.removeEvent("mouseup", this.onMouseUp);
	  body.removeEvent("mousemove", this.onMouseMove);
	  
       }
   ,
     handleMouseDown: function(evt)
       {
	  if (!evt) evt = window.event;
	  
	  var target = evt.target ? evt.target : evt.srcElement;
	  
	  if (String(target.tagName).toLowerCase() == "a") 
	    {
	       debug("Stopping event propagation for mousedown event on anchor");
	       this.stopEventPropagation(evt);
	    }
	  	  
	  var draggableElement = this.getDraggableElement(target);
	  
	  if (draggableElement) 
	    {
	       this.dragging = draggableElement;
	       
	       this.startDrag(evt, draggableElement);
	    }
	  
       }
   ,
     getDraggableElement: function(element)
       {
	  var draggableElement = null;
	  
	  while (element) 
	    {	       
	       if (element.id) 
		 {
		    var id = element.id;
		    
		    for (var i=0; i < this.elements.length; i++) 
		      {
			 if (this.elements[i].element.id == id) 
			   {
			      draggableElement = this.elements[i];
			   }
		      }
		 }
	       
	       if (draggableElement) 
		 {
		    element = null;
		 }
	       else 
		 {
		    element = element.parentNode;
		 }
	    }
	  
	  
	  return draggableElement;
       }
   ,
     deactivateAllTargets: function()
       {
	  for (var i=0; i < this.targets.length; i++) 
	    {
	       this.targets[i].deactivate();
	    }
	  
       }
   ,
     checkTargets: function(evt)
       {
	  if (!this.activeTargets)
	    return;
	  
	  for (var i=0; i < this.targets.length; i++) 
	    {
	       if (!this.targets[i].active)
		 continue;
	       
	       var d = this.targets[i].element.getDimensions();
	       var p = this.targets[i].element.getPosition();
	       
	       //debug("pointInside(" + evt.clientX + "," + evt.clientY + ") [" + p.x + "," + p.y + "," + (p.x + d.w) + "," + (p.y + d.h) + "]");

	       if (this.targets[i].element.pointInside(evt.clientX, evt.clientY)) 
		 {
		    //debug("pointInside(" + evt.clientX + "," + evt.clientY + ") [" + p.x + "," + p.y + "," + (p.x + d.w) + "," + (p.y + d.h) + "]");
		    //this.deactivateAllTargets();
		    this.targets[i].activate(evt, this.dragging);
		 }
	       else
		 {
		    this.targets[i].deactivate(evt);
		 }
		   
	    }
	  
       }
   ,
     handleMouseMove: function(evt)
       {
	  if (!this.isDragging()) 
	    {
	       debug("handleMouseMove: not dragging");
	       return;
	    }
	  
	  window.status = "handleMouseMove: " + evt.clientX + "x" + evt.clientY;
	  
	  this.updateDrag(evt);
	  
	  this.stopEventPropagation(evt);
       }
   ,
     handleMouseOut: function(evt)
       {
	  if (!this.isDragging()) return;
	  return;	  
	  this.cancelDrag(evt);
       }
   ,
     handleMouseUp: function(evt)
       {
	  if (!this.isDragging()) return;
		  
	  var dropZone = this.isDropZone(evt);
	    
	  if (dropZone)
	    {
	       dropZone.drop(this.dragging, evt);
	       this.stopDrag(evt);
	    }
	  else 
	    {
	       /* todo : return to orig */
	       this.stopEventPropagation(evt);
	       
	       this.cancelDrag(evt);
	       
	    }
	  
	  
       }
   ,
     isDropZone: function(evt)
       {
	  if (!this.activeTargets)
	    return false;
	  
	  var inZone = false;
	  
	  for (var i=0; i < this.targets.length; i++) 
	    {
	       if (!this.targets[i].active)
		 continue;

	       if (this.targets[i].element.pointInside(evt.clientX, evt.clientY)) 
		 {
		    inZone = this.targets[i];
		    break;
		 }
	    }
	  
	  return inZone;
       }
   ,
     updateDrag: function(evt)
       {
	  if (!this.isDragging()) return;
	  
	  var x = evt.screenX - this.startX + (evt.offsetX ? document.body.scrollLeft : 0);
	  var y = evt.screenY - this.startY + (evt.offsetY ? document.body.scrollTop : 0);
	  
	  var d = this.dragging.element.getDimensions();
	  
	  var body = document.getBody();
	  
	  //window.status = "body dimensions: " + body.offsetWidth + "x" + body.offsetHeight;
	  
	  if (x < 0) x = 0;
	  if (y < 0) y = 0;
	  
	  if (x + d.w >= body.offsetWidth) x = body.offsetWidth - d.w;
	  if (y + d.h >= body.offsetHeight) y = body.offsetHeight - d.h;

	  this.dragging.element.absolutelyPosition(x, y);
	  
	  this.checkTargets(evt);

       }
   ,
     stopEventPropagation: function(evt)
       {
	  if (evt.stopPropagation)
	    evt.stopPropagation();
	  
	  if (evt.cancelBubble != undefined)
	    evt.cancelBubble = true;
	  
	  if (evt.preventDefault)
	    evt.preventDefault();
	  
	  evt.returnValue = false;
	  
	  return false;
       }
   
   
}

var DraggableElement = Object.createObject();

DraggableElement.prototype = 
{
   initialize: function(element) 
     {
	this.element = $(element);
	this.active = false;
	this.defaultPosition = this.element.style.position;
	this.position = this.element.getPosition();
     }
   ,
     onCancel: function(evt)
       {
	  this.element.style.position = this.defaultPosition;
       }
   ,
     onDrop: function(evt)
       {
	  debug("DraggableElement::onDrop");
	  this.element.style.position = this.defaultPosition;
       }
   
}

var DROPTARGET_APPEND = 1;
var DROPTARGET_INSERT = 2;
var DROPTARGET_DROP = 1;
var DROPTARGET_HOVER = 2;
var DROPTARGET_HOVEROUT = 3;

var DropTarget = Object.createObject();

DropTarget.prototype =
{
   initialize: function(element, behaviour)
     {
	this.element = $(element);
	this.onMouseOver = this.handleMouseOver.bindEvent(this);
	this.onMouseOut = this.handleMouseOut.bindEvent(this);
	this.hovering = false;
	this.active = false;
	this.behaviour = behaviour ? behaviour : DROPTARGET_APPEND;
     }
   ,
     handleMouseOver: function(evt)
       {
	  //debug("DropTarget::handleMouseOver");
	  this.activate(evt);
       }
   ,
     handleMouseOut: function(evt)
       {
	  this.deactivate(evt);
       }
   ,
     acceptsElement: function(element)
       {
	  return true;
       }
   ,
     activate: function(evt, draggableElement)
       {
	  //debug("Activating drop target: " + this.element.id);
	  
	  this.hovering = true;

	  this.element.addClass("dropHover");
	  
	  switch (this.behaviour) 
	    {
	     case DROPTARGET_APPEND:
	       this.hoverAppend(evt, draggableElement);
	       break;
	       
	     case DROPTARGET_INSERT:
	       this.hoverInsert(evt, draggableElement);
	       break;
	       
	     case (typeof(this.behaviour) == "object"):
	       this.behaviour(DROPTARGET_HOVER, evt, draggableElement);
	       break;
	       
	     default:
	       this.hoverAppend(evt, draggableElement);
	       break;
	    }
	  
       }
   ,
     hoverAppend: function(evt, draggableElement)
       {
	  var potentials = this.element.getElementsByClass("dropTargetPotential");
	  
	  if (potentials.length != 0) return;
	  
	  var ghost = draggableElement.element.cloneNode(true);
	  Object.addProperties(ghost, DOMElement);
	  
	  ghost.addClass("dropTargetPotential");
	  ghost.removeClass("dragging");
	  ghost.id = null;
	  ghost.style.position = draggableElement.defaultPosition;
	  
	  this.element.appendChild(ghost);
	  
	  ghost.setOpacity(.5);
       }
   ,
     hoverOutAppend: function(evt)
       {
	  var potentials = this.element.getElementsByClass("dropTargetPotential");
	  
	  for (var i=0; i < potentials.length; i++) 
	    {
	       potentials[i].parentNode.removeChild(potentials[i]);
	    }
	  
       }
   ,
     hoverInsert: function(evt, draggableElement)
       {
	  this.hoverOutInsert(evt); /* remove any existing ghosts */
	  //debug("hoverInsert");
	  
	  /* var ghost = draggableElement.element.cloneNode(true); */
	  /* Object.addProperties(ghost, DOMElement); */
	  
	  /* ghost.addClass("dropTargetPotential"); */
	  /* ghost.removeClass("dragging"); */
	  /* ghost.id = null; */
	  /* ghost.style.position = draggableElement.defaultPosition; */

	  var d = this.element.getDimensions();
	  
	  var ghost = document.createElement("div");
	  ghost.addClass("dropTargetPotential");
	  ghost.makeAbsolute();
	  ghost.style.height = "2px";
	  ghost.style.background = "#000000";
	  ghost.style.width = d.w + "px";
	  
	  
	  for (var i=0; i < this.element.childNodes.length; i++) 
	    {
	       var child = this.element.childNodes[i];
	       
	       if (child.nodeType != DOM_ELEMENT_NODE) continue;
	       
	       if (child == draggableElement.element) continue;
	       
	       Object.addProperties(child, DOMElement);
	       	       
	       var d = child.getDimensions();
	       var p = child.getPosition();
	       
	       if (child.pointInside(evt.clientX, evt.clientY)) 
		 {
		    var parent = child.parentNode;
		    Object.addProperties(parent, DOMElement);
		    
		    if (evt.clientY > ((d.h/2) + p.y)) 
		      {
			 ghost.style.left = p.x + "px";
			 ghost.style.top = (p.y + d.h + 1) + "px";
			 parent.insertAfter(ghost, child);
		      }
		     else 
		      {
			 ghost.style.left = p.x + "px";
			 ghost.style.top = (p.y - 1) + "px";
			 parent.insertBefore(ghost, child);
		      }
		    
		    
		 }
	       
	    }
	  
       }
   ,
     hoverOutInsert: function(evt)
       {
	  this.hoverOutAppend(evt);
       }
   ,
     deactivate: function(evt)
       {
	  if (!this.hovering) return;

	  //debug("Deactivating drop target: " + this.element.id);
	  
	  this.hovering = false;
	  
	  this.element.removeClass("dropHover");
	  
	  switch (this.behaviour)
	    {
	       case DROPTARGET_APPEND:
	       this.hoverOutAppend(evt);
	       break;
	       
	     case DROPTARGET_INSERT:
	       this.hoverOutInsert(evt);
	       break;
	       
	     case (typeof(this.behaviour) == "object"):
	       this.behaviour(DROPTARGET_HOVEROUT, evt);
	       break;
	       
	     default:
	       this.hoverOutAppend(evt);
	       break;
	    }
	  
       }
   ,
     stopEventPropagation: function(evt)
       {
	  if (evt.stopPropagation)
	    evt.stopPropagation();
	  
	  if (evt.cancelBubble != undefined)
	    evt.cancelBubble = true;
	  
	  if (evt.preventDefault)
	    evt.preventDefault();
	  
	  evt.returnValue = false;
	  
	  return false;
       }
   ,
     drop: function(draggableElement, evt)
       {
	  draggableElement.onDrop(evt);
	  
	  switch (this.behaviour) 
	    {
	     case DROPTARGET_APPEND:
	       this.append(draggableElement, evt);
	       break;
	       
	     case DROPTARGET_INSERT:
	       this.insert(draggableElement, evt);
	       break;
	       
	     case (typeof(this.behaviour) == "object"):
	       this.behaviour(DROPTARGET_DROP, evt, draggableElement);
	       break;
	       
	     default:
	       break;
	    }
	  
       }
   ,
     append: function(draggableElement, evt)
       {
	  this.element.appendChild(draggableElement.element.parentNode.removeChild(draggableElement.element));
       }
   ,
     insert: function(draggableElement, evt)
       {
	  var inserted = false;
	  
	  for (var i=0; i < this.element.childNodes.length; i++) 
	    {
	       var child = this.element.childNodes[i];
	       
	       if (child.nodeType != DOM_ELEMENT_NODE) continue;
	       
	       Object.addProperties(child, DOMElement);
	       
	       var d = child.getDimensions();
	       var p = child.getPosition();
	       
	       debug("pointInside(" + evt.clientX + "," + evt.clientY + ") [" + p.x + "," + p.y + "," + (p.x + d.w) + "," + (p.y + d.h) + "]");
	       
	       if (child.pointInside(evt.clientX, evt.clientY)) 
		 {
		    var parent = child.parentNode;
		    Object.addProperties(parent, DOMElement);
		    
		    if (evt.clientY > ((d.h/2) + p.y)) 
		      {
			 parent.insertAfter(draggableElement.element.parentNode.removeChild(draggableElement.element), child);
		      }
		     else 
		      {
			 parent.insertBefore(draggableElement.element.parentNode.removeChild(draggableElement.element), child);
		      }
		    
		    inserted = true;
		    
		 }
	       
	    }
	  
	  if (!inserted) this.append(draggableElement, evt);
	  
	  alert(this.element.innerHTML);
       }
   
}
