var Droppables={
	drops: [],
	remove: function(element) { this.drops=this.drops.reject(function(d) { return d.element==$(element) }); },
	add: function(element) { element=$(element); var options=Object.extend({ greedy: true,hoverclass: null },arguments[1] || {}); if(options.containment) { options._containers=[]; var containment=options.containment; if((typeof containment=='object') &&  (containment.constructor==Array)) { containment.each( function(c) { options._containers.push($(c)) }); } else { options._containers.push($(containment)); } } if(options.accept) options.accept=[options.accept].flatten(); Element.makePositioned(element); options.element=element; this.drops.push(options); },
	isContained: function(element,drop) { var parentNode=element.parentNode; return drop._containers.detect(function(c) { return parentNode==c }); },
	isAffected: function(point,element,drop) { return ( (drop.element!=element) && ((!drop._containers) || this.isContained(element,drop)) && ((!drop.accept) || (Element.classNames(element).detect( function(v) { return drop.accept.include(v) } ) )) && Position.within(drop.element,point[0],point[1]) ); },
	deactivate: function(drop) { if(drop.hoverclass) Element.removeClassName(drop.element,drop.hoverclass); this.last_active=null; },
	activate: function(drop) { if(drop.hoverclass) Element.addClassName(drop.element,drop.hoverclass); this.last_active=drop; },
	show: function(point,element) { if(!this.drops.length) return; if(this.last_active) this.deactivate(this.last_active); this.drops.each( function(drop) { if(Droppables.isAffected(point,element,drop)) { if(drop.onHover) drop.onHover(element,drop.element,Position.overlap(drop.overlap,drop.element)); if(drop.greedy) { Droppables.activate(drop); throw $break; } } }); },
	fire: function(event,element) { if(!this.last_active) return; Position.prepare(); if (this.isAffected([Event.pointerX(event),Event.pointerY(event)],element,this.last_active)) if (this.last_active.onDrop)  this.last_active.onDrop(element,this.last_active.element,event); },
	reset: function() { if(this.last_active) this.deactivate(this.last_active); }
}

var Draggables={
	drags: [],
	observers: [],
	register: function(draggable) { if(this.drags.length==0) { this.eventMouseUp=this.endDrag.bindAsEventListener(this); this.eventMouseMove=this.updateDrag.bindAsEventListener(this); this.eventKeypress=this.keyPress.bindAsEventListener(this); Event.observe(document,"mouseup",this.eventMouseUp); Event.observe(document,"mousemove",this.eventMouseMove); Event.observe(document,"keypress",this.eventKeypress); } this.drags.push(draggable); },
	unregister: function(draggable) { this.drags=this.drags.reject(function(d) { return d==draggable }); if(this.drags.length==0) { Event.stopObserving(document,"mouseup",this.eventMouseUp); Event.stopObserving(document,"mousemove",this.eventMouseMove); Event.stopObserving(document,"keypress",this.eventKeypress); } },
  	activate: function(draggable) { window.focus(); this.activeDraggable=draggable; },
	deactivate: function(draggbale) { this.activeDraggable=null; },
	updateDrag: function(event) { if(!this.activeDraggable) return; var pointer=[Event.pointerX(event),Event.pointerY(event)]; if(this._lastPointer && (this._lastPointer.inspect()==pointer.inspect())) return; this._lastPointer=pointer; this.activeDraggable.updateDrag(event,pointer); },
	endDrag: function(event) { if(!this.activeDraggable) return; this._lastPointer=null; this.activeDraggable.endDrag(event); this.activeDraggable=null; },
	keyPress: function(event) { if(this.activeDraggable) this.activeDraggable.keyPress(event); },
	addObserver: function(observer) { this.observers.push(observer); this._cacheObserverCallbacks(); },
	removeObserver: function(element) { this.observers=this.observers.reject( function(o) { return o.element==element }); this._cacheObserverCallbacks(); },
	notify: function(eventName,draggable,event) { if(this[eventName+'Count']>0) this.observers.each( function(o) { if(o[eventName]) o[eventName](eventName,draggable,event); }); },
	_cacheObserverCallbacks: function() { ['onStart','onEnd','onDrag'].each( function(eventName) { Draggables[eventName+'Count']=Draggables.observers.select( function(o) { return o[eventName]; } ).length; }); }
}

/*--------------------------------------------------------------------------*/

var Draggable=Class.create();
Draggable.prototype={
	initialize: function(element) { var options=Object.extend({ handle: false, zindex: 1000, revert: false, snap: false },arguments[1] || {}); this.element=$(element); if(options.handle && (typeof options.handle=='string')) this.handle=Element.childrenWithClassName(this.element,options.handle)[0]; if(!this.handle) this.handle=$(options.handle); if(!this.handle) this.handle=this.element; Element.makePositioned(this.element); this.delta=this.currentDelta(); this.options=options; this.dragging=false; this.eventMouseDown=this.initDrag.bindAsEventListener(this); Event.observe(this.handle,"mousedown",this.eventMouseDown); Draggables.register(this); },
	destroy: function() { Event.stopObserving(this.handle,"mousedown",this.eventMouseDown); Draggables.unregister(this); },
	currentDelta: function() { return([parseInt(Element.getStyle(this.element,'left') || '0'), parseInt(Element.getStyle(this.element,'top') || '0')]); },
	initDrag: function(event) { if(Event.isLeftClick(event)) { var src=Event.element(event); if(src.tagName && (src.tagName=='INPUT' || src.tagName=='SELECT' || src.tagName=='BUTTON' || src.tagName=='TEXTAREA')) return; if(this.element._revert) { this.element._revert.cancel(); this.element._revert=null; } var pointer=[Event.pointerX(event),Event.pointerY(event)]; var pos=Position.cumulativeOffset(this.element); this.offset=[0,1].map( function(i) { return (pointer[i]-pos[i]) }); Draggables.activate(this); Event.stop(event); } },
	startDrag: function(event) { this.dragging=true; if(this.options.zindex) { this.originalZ=parseInt(Element.getStyle(this.element,'z-index') || 0); this.element.style.zIndex=this.options.zindex; } if(this.options.ghosting) { this._clone=this.element.cloneNode(true); Position.absolutize(this.element); this.element.parentNode.insertBefore(this._clone,this.element); } Draggables.notify('onStart',this,event); },
	updateDrag: function(event,pointer) { if(!this.dragging) this.startDrag(event); Position.prepare(); Droppables.show(pointer,this.element); Draggables.notify('onDrag',this,event); this.draw(pointer); if(this.options.change) this.options.change(this); if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); Event.stop(event); },
	finishDrag: function(event,success) { this.dragging=false; if(this.options.ghosting) { Position.relativize(this.element); Element.remove(this._clone); this._clone=null; } if(success) Droppables.fire(event,this.element); Draggables.notify('onEnd',this,event); var revert=this.options.revert; if(revert && typeof revert=='function') revert=revert(this.element); var d=this.currentDelta(); this.delta=d; if(this.options.zindex) this.element.style.zIndex=this.originalZ; Draggables.deactivate(this); Droppables.reset(); },
	keyPress: function(event) { if(!event.keyCode==Event.KEY_ESC) return; this.finishDrag(event,false); Event.stop(event); },
	endDrag: function(event) { if(!this.dragging) return; this.finishDrag(event,true); Event.stop(event); },
	draw: function(point) { var pos=Position.cumulativeOffset(this.element); var d=this.currentDelta(); pos[0]-=d[0]; pos[1]-=d[1]; var p=[0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this)); if(this.options.snap) { if(typeof this.options.snap=='function') { p=this.options.snap(p[0],p[1]); } else { if(this.options.snap instanceof Array) { p=p.map( function(v,i) { return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) } else { p=p.map( function(v) { return Math.round(v/this.options.snap)*this.options.snap }.bind(this)) } }} var style=this.element.style; if((!this.options.constraint) || (this.options.constraint=='horizontal') || (this.options.constraint=='all')) style.left=p[0]+"px"; if((!this.options.constraint) || (this.options.constraint=='vertical') || (this.options.constraint=='all')) style.top =p[1]+"px"; if(style.visibility=="hidden") style.visibility=""; }
}
