Element.Storage = {

	get: function(uid){
		return (this[uid] = this[uid] || {});
	}

};

Element.implement({
	retrieve: function(property, dflt){
		var storage = Element.Storage.get(this.uid);
		var prop = storage[property];
		if ($defined(dflt) && !$defined(prop)) prop = storage[property] = dflt;
		return $pick(prop);
	},
	store: function(property, value){
		var storage = Element.Storage.get(this.uid);
		storage[property] = value;
		return this;
	},
	isVisible: function() {
		return this.getStyle('display') != 'none';
	},
	toggle: function() {
		return this[this.isVisible() ? 'hide' : 'show']();
	},
	hide: function() {
		var d;
		try {
			//IE fails here if the element is not in the dom
			d = this.getStyle('display');
		} catch(e){}
		this.store('originalDisplay', d||'block'); 
		this.originalDisplay = d || 'block';
		this.setStyle('display','none');
		//wfx.log("hide");
		return this;
	},
	show: function(display) {
		var orig = this.originalDisplay;//this.retrieve('originalDisplay');//?this.retrieve('originalDisplay'):this.get('originalDisplay');
		if(orig == "none") orig = "block";
		this.setStyle('display',(display || orig || 'block'));
		return this;
	},
	invisible: function() {
		this.style.visibility = 'hidden';
		return this;
	},
	visible: function() {
		this.style.visibility = 'visible';
		return this;
	},
	swapClass: function(remove, add) {
		return this.removeClass(remove).addClass(add);
	}
});

Element.implement({
	getInnerSize: function(){
		//wfx.log("border:"+this.getStyle('border').toInt());
		var b = this.getStyle('border').toInt() * 2;
		return {
			'scroll': {'x': this.scrollLeft, 'y': this.scrollTop},
			'size': {'x': this.offsetWidth-b, 'y': this.offsetHeight-b},
			'scrollSize': {'x': this.scrollWidth, 'y': this.scrollHeight}
		};
	},
	addElement: function(type, properties){
		//wfx.log("addElement");
		var e = new Element(type, properties);
		//if($defined(properties)) e.setProperties(properties);
		this.appendChild(e);
		return e;
	},
	setPosition: function(pos) {
		//wfx.log("setPosition: "+pos.x+", "+pos.y);
		this.setStyles({left:pos.x, top:pos.y});
	}
	
});

Drag.MoveCopy = new Class({

	Extends: Drag.Move,

	options: {
		ghostOriginal:true,
		hideOriginal:false,
		deleteOriginal:false,
		onDrop: Class.empty,
		isFirstDrag:true
	},
	
	initialize: function(el, options) {
		//wfx.log("Drag.MoveCopy.init");
		this.original = $(el);
		this.copy();
		this.parent(this.element, options);
		this.element.dispose();
		this.element = 0;
		this.addEvent('onSnap', this.startDrag.bind(this));
		this.addEvent('enter', this.onEnter.bind(this));
	},
	attach: function() {
		//wfx.log("Drag.MoveCopy.attach");
		this.original.addEvent('mousedown', this.bound.start);
		return this;
	},
	detach: function() {
		//wfx.log("Drag.MoveCopy.detach");
		this.original.removeEvent('mousedown', this.bound.start);
		return this;
	},
	copy: function() {
		//wfx.log("Drag.MoveCopy.copy");
		if(this.element) this.element.dispose();
		this.element = this.original.clone().setStyles({position:'fixed', top:0, left:0, zIndex:1000}).setOpacity(0);
		document.body.appendChild(this.element);
	},
	hide: function(event) {
		//wfx.log("Drag.MoveCopy.hide");
	},
	onEnter: function(el, droppable) {
		//wfx.log("Drag.MoveCopy.onEnter");
		//wfx.log("droppable:"+droppable.id);
		droppable.fireEvent('enter', [el, this]);
	},
	start: function(event) {
		//wfx.log("Drag.MoveCopy.start");
		
		this.originalStyle = this.original.getProperty('style');

		// make a fresh copy of the element for each drag
		this.copy();
		var p = this.original.getPosition();
		this.element.setOpacity(0);
		this.element.setStyles({'top': p.y, 'left': p.x});

		this.parent(event);
	},
	startDrag: function() {
		//wfx.log("Drag.MoveCopy.startDrag");
		
		this.element.setOpacity(0.5);
		
		if(this.options.hideOriginal) {
			//this.original.setStyle('display', 'none');
		}
		else
		if(this.options.ghostOriginal) {
			this.original.setOpacity(0.5);
		}
		else {
			this.original.setOpacity(0);
		}
	},
	
	stop: function() {
		//wfx.log("Drag.MoveCopy.stop");
		
		// reset the original style so we don't amass a ton of useless styles from the drag
		this.original.setProperty('style', this.originalStyle);
		
		this.parent();
		//this.detach();

		// clear the drag copy (a droppable will have already had a chance to copy it)
		this.element.dispose();
		this.element = 0;
	}

});

Drag.Droppable = new Class({

	options: {
		copy:false,
		animate:true,
		makeSortable:true,
		childCallback:0
	},
	initialize: function(el, options) {
		this.setOptions(options);
		this.element = $(el);		
		//wfx.log("Drag.Droppable.initialize:"+this.element.getProperty('id'));

		this.dragoverBind = this.dragover.bindWithEvent(this);
		// use bind so the functions are called like class members, with 'this' refering to the class
		this.element.addEvents({
			'enter':this.enter.bind(this),
			'leave':this.leave.bind(this),
			'drop':this.drop.bind(this)
		});
		
		if(this.options.makeSortable || this.options.childCallback) {
			this.element.getChildren().each( function(c) {
				//wfx.log('child:'+c.id);
				if(this.options.makeSortable) c.makeDraggableCopy({droppables:[el], deleteOriginal:1, hideOriginal:1});
				if(this.options.childCallback) this.options.childCallback(c);
			}, this);
		}
		if(this.options.initialize) this.options.initialize.call(this);
	},
	animateIn: function(drag, skip) {
		if(this.options.animate && !skip) {
			//drag.previewFx.stop();
			drag.previewFx.start({
				'width': [0, drag.element.getInnerSize().size.x],
				'opacity': [0, 0.25]
			});
		}
		else {
			drag.preview.setStyles({
				'width': drag.element.getInnerSize().size.x,
				'opacity': 0.25
			});
		}
	},
	animateOut: function(drag) {
		if(this.options.animate) {
			//drag.previewFx.stop();
			drag.previewFx.start({
				'width': [drag.preview.getInnerSize().size.x, 0],
				'opacity': [0.25, 0]
			});
		}
	},
	createPreview: function(drag) {
		//wfx.log("createPreview");
		if(this.element.hasChild(drag.original)) {
			drag.preview = drag.original;
			drag.preview.canDelete = false;
		}
		else {
			if(drag.options.hideOriginal) {
				drag.original.setStyle('display', 'none');
			}
			drag.preview = drag.element.clone().setStyles({position:'relative', top:0, left:0, zIndex:100});
			drag.preview.canDelete = true;
		}
		//drag.preview = drag.element.clone().setStyles({position:'relative', top:0, left:0, zIndex:100});
		
		if(this.options.animate) {
			drag.previewFx = new Fx.Morph(drag.preview, {
				duration:500,
				transition: Fx.Transitions.Quart.easeInOut
			});
			drag.preview.setOpacity(0).setStyle('width', 0);
			//drag.options.isFirstDrag = false;
		}
		else {
			drag.preview.setOpacity(0.25);
		}
		if(this.element.hasChild(drag.original)) {
			drag.preview.injectBefore(drag.original);
		}
		else {
			this.element.appendChild(drag.preview);
		}
		if(drag.preview.canDelete) {
			this.animateIn(drag);
		}
		else {
			//wfx.log("first drag");
			drag.preview.setStyles({
				'width': drag.element.getInnerSize().size.x,
				'opacity': 0.25
			});
		//	drag.preview.index = 
		//	drag.options.isFirstDrag = false;
		//	drag.previewFx.complete();
		}
	},
	destroyPreview: function(drag) {
		//wfx.log("destroyPreview");
		if(drag.preview.canDelete) {
			drag.preview.dispose();
		}
	},
	dragover: function(event, drag) {
	// test the coordinates to see which child the drag is over
		var over;
		//if(!event) {
		//	//wfx.log("NO EVENT!");
		//	return;
		//}
		//for(p in event) {
		//	//wfx.log("event.p:"+p);
		//}
		for(var x=0; x<this.children.length; x++) {
			if(	this.children[x].left <= event.pageX && this.children[x].right >= event.pageX &&
				this.children[x].top <= event.pageY && this.children[x].bottom >= event.pageY) 
			{
				over = x;
				break;
			}
		}
		
		if($defined(over)) {
			var m = this.children[over].left + ((this.children[over].right - this.children[over].left) / 2);
			var changed = false;
			var prevIndex = drag.preview.index;
			if(drag.preview.index != over) {
				if(event.pageX < m) {
					if(drag.preview.brother != this.children[over].element || !drag.preview.before) {
						drag.preview.before = true;
						drag.preview.injectBefore(this.children[over].element);
						if(drag.preview.index < over) drag.preview.index = over - 1;
						else drag.preview.index = over;
						changed = true;
					}
				}
				else {
					if(drag.preview.brother != this.children[over].element || drag.preview.before) {
						drag.preview.before = false;
						drag.preview.injectAfter(this.children[over].element);
						if(drag.preview.index > over) drag.preview.index = over + 1;
						else drag.preview.index = over;
						changed = true;
					}
				}
			}
			if(changed && prevIndex != drag.preview.index && !drag.options.isFirstDrag) {
				//wfx.log("index:"+drag.preview.index);
				this.animateIn(drag);
				drag.preview.brother = this.children[over].element;
				this.mapChildren();
			}
			drag.options.isFirstDrag = false;
		}
	},
	mapChildren: function() {
	// Precalculate the child coordinates of the drop container
		this.children = this.element.getChildren().map( function(c) {
			var v = c.getCoordinates();
			v.element = c;
			return v;
		}, this);
	},
	enter: function(e, drag) { 
		//wfx.log(this.element.getProperty('id')+'.enter'); 

		var create = true;
		if($chk(drag.preview)) {
		// Make sure drag we're not over our own ghost
			if(drag.checkAgainst(drag.preview)) {
				create = false;
			}
		}
		if(create) {
		// create a temp node previewing the drag destination
			this.dragoverBind = this.dragover.bindWithEvent(this, drag);
			document.addListener('mousemove', this.dragoverBind);
			this.createPreview(drag);
		}
		this.mapChildren();
	},
	leave: function(e, drag) { 
		//wfx.log(this.element.getProperty('id')+'.leave'); 
		document.removeListener('mousemove', this.dragoverBind);
		
		if($chk(drag.preview)) {
			if(!drag.checkAgainst(drag.preview)) {
				this.destroyPreview(drag);
			}
		}
	},
	drop: function(e, drag) { 
		//wfx.log(this.element.getProperty('id')+'.drop'); 
		
		document.removeListener('mousemove', this.dragoverBind);

		if(this.options.animate) drag.previewFx.complete();
		drag.preview.setProperty('style', drag.originalStyle);
		drag.preview.removeEvents('onSnap');
		drag.preview.removeEvents('mousedown');
		drag.preview.makeDraggableCopy({droppables:drag.options.droppables, deleteOriginal:1, hideOriginal:1});
		
		drag.fireEvent('onDrop', drag.preview);

		if(drag.options.deleteOriginal) {
			if(drag.original != drag.preview) {
				drag.original.dispose();
			}
		}
	}
});
Drag.Droppable.implement(new Events, new Options);

Element.implement({
	makeDraggableCopy: function(options) {
		return new Drag.MoveCopy(this, options);
	},
	makeDroppable: function(options) {
		return new Drag.Droppable(this, options);
	}
});
/* */