
if(typeof Diddl == 'undefined') Diddl = {}

Diddl.Portal = Class.create();
Object.extend(Diddl.Portal.prototype, {

	options: {// default options
		accept: 'portlet',       // classname of porlets
		handle: 'portlet-handle', // classname of porlets handle
		columnHoverClassName: 'portal-column-hover'
	},
	_columns: [],
	_portlets: [],

	initialize: function(options) {
		this.options = Object.extend(this.options, options);

		Draggables.addObserver({
			//onDrag  : this._onDraggablesDrag.bind(this),
			onStart : this._onDraggablesStart.bind(this),
			onEnd   : this._onDraggablesEnd.bind(this)
		});

	},

	addColumn: function (element, options) {
		this._columns.push($(element));
		
		$(element).addClassName('portal-column');

		Droppables.add(element, {
			accept   : this.options.accept,
			onHover  : this._onDroppablesHover.bind(this),
			onDrop   : this._onDroppablesDrop.bind(this),
			overlap  : 'vertical',
			hoverclass : this.options.columnHoverClassName
		});
		$(element).undoPositioned();

		$(element).setStyle({minHeight: '3em'});
		if (Prototype.Browser.IE && navigator.userAgent.indexOf('6.0') >= 0) {
			$(element).setStyle({height: '3em'});
		}

	},

	addPortlet: function (element, options) {

		if (! options) options = {};
		options.handle = options.handle || this.options.handle;

		this._portlets.push(element);

		new Draggable(element,{
			//scroll      : window?
			revert      : this._revert.bind(this),
			reverteffect: this._reverteffect.bind(this),
			handle      : options.handle,
			starteffect : null,
			endEffect   : null
		});

		var handle;
		if(options.handle && Object.isString(options.handle))
			handle = element.down('.'+options.handle, 0);
		if(! handle) handle = $(options.handle);
		if(! handle) handle = element;
		if(handle && handle.setStyle) handle.setStyle({cursor: 'move'});
	},


	serializePositions: function (toJSON) {
		//@return string JSON : {'id col1':['id pot1', 'id pot1', ...], 'id col2':['id pot3', 'id pot4', ...]}
		var positions = {};

		var portletIds = [];
		this._portlets.each(function (portlet) {
			portlet = $(portlet);
			if (! portlet) return;
			if (! portlet.id) return;
			if (portletIds.indexOf(portlet.id) !== -1) return;
			portletIds.push($(portlet).id);
		});

		this._columns.each(function (column) {
			column = $(column);
			if (! column) return;
			if (! column.id) return;
			
			column.childElements(column).each(function (portlet) {
				if (! portlet.id) return;
				if (portletIds.indexOf(portlet.id) === '-1') return;

				if (typeof positions[column.id] === 'undefined') {
					positions[column.id] = [];
				}

				positions[column.id].push(portlet.id);
				portletIds.splice(portletIds.indexOf(portlet.id), 1);
			});
		});

		if (portletIds.length > 0) {
			positions._notAssigned = portletIds;
		}

		if (toJSON) return Object.toJSON(positions);
		return positions;
	},

	applyPositions: function (positions) {
		//@param object positions: {'id col1':['id pot1', 'id pot1', ...], 'id col2':['id pot3', 'id pot4', ...]}
		//    OR string positions: JSON...
		
		if (typeof positions === 'string') {
			try {
				positions = positions.evalJSON(true);
			} catch (e) {
				//pass
			}
		}
		
		if (typeof positions !== 'object') return;

		var portletIds = [];
		this._portlets.each(function (portlet) {
			portlet = $(portlet);
			if (! portlet) return;
			if (! portlet.id) return;
			portletIds.push($(portlet).id);
		});


		this._columns.each(function (column) {
			column = $(column);
			if (column && column.id && Object.isArray(positions[column.id])) {

				// iterate in reverse order...
				for (var i=positions[column.id].length-1; i>=0; i--) {
					// skip if not managed portlet
					if (portletIds.indexOf(positions[column.id][i]) === '-1') continue;

					var portlet = $(positions[column.id][i]);
					if (! portlet) continue;
						
					if (column.firstChild) {
						column.insertBefore(portlet, column.firstChild);
					} else {
						column.appendChild(portlet);
					}
					
				}

			}
		});

		
	},

	_revert: function (element) {
		window.setTimeout(this._hidePlaceHolder.bind(this), 300);
		return true
	},

	_sleepRevertEffectOnce: false,
	_reverteffect: function (element, top_offset, left_offset) {
		if (this._sleepRevertEffectOnce) {
			// position now!
			this._hidePlaceHolder(); 
			element.setStyle({
				'top': 0,
				'left': 0
			});
			this._sleepRevertEffectOnce = false;
			return;
		}

		// default revert effect (but twice faster...)
		new Effect.Move(element, { 
			x: -left_offset, 
			y: -top_offset, 
			duration: Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.01,
			queue: {scope:'_draggable', position:'end'}
		});
	},

	/*
	_onDraggablesDrag: function (eventName, draggable, event) {
		//console.debug('onDraggablesDrag');
	},
	*/

	_onDraggablesStart: function (eventName, draggable, event) {
		$(document.body).addClassName('portal-show-columns');
		//console.debug('onDraggablesStart');
	},

	_onDraggablesEnd: function (eventName, draggable, event) {
		$(document.body).removeClassName('portal-show-columns');
		//console.debug('onDraggablesEnd');
	},

	

	_onDroppablesDrop: function (element, dropon, event) {
		//console.debug("dropped " + element.id + " on "+ dropon.id + "\n");
		if (this._placeHolder && typeof this._placeHolder.parentNode !== 'undefined') {
			this._placeHolder.parentNode.insertBefore(element, this._placeHolder);
			this._sleepRevertEffectOnce = true;
			this.onAfterDrop(element);
		}
	},

	onAfterDrop: function (element) {
		// pass
	},


	/*
	//@todo reimplement safer/customized Position.within/Position.overlap
	_elementOverlap: function (elm1, elm2) {
		//console.debug('_elementOverlap: ', elm1, elm2);
		//console.debug('elm1.cumulativeOffset(): ', elm1.cumulativeOffset());
		//console.debug('elm2.cumulativeOffset(): ', elm2.cumulativeOffset());

	},
	*/

	_onDroppablesHoverTimer: null,
	_clearOnDroppablesHoverTimer: function () {
		if (this._onDroppablesHoverTimer) {
			window.clearTimeout(this._onDroppablesHoverTimer);
			this._onDroppablesHoverTimer = null;
		}
	},
	_onDroppablesHover: function (element, dropon, overlap) {
		//console.debug(element.id + " is hover "+ dropon.id + " with overlap "+ overlap +"\n");

		// avoid too many events handling...
		if (this._onDroppablesHoverTimer) return;
		this._onDroppablesHoverTimer = window.setTimeout(this._clearOnDroppablesHoverTimer.bind(this), 20);

		var offset = element.cumulativeOffset();
		var x = offset[0];
		var y = offset[1];

		// search overlapping with childrens...
		var droponChilds = dropon.childElements();
		var target = '';
		var found = false;
		var overlapsPlaceHolder = false;

		//console.debug('droponChilds: ', droponChilds);

		for (var i = 0, len = droponChilds.length; i < len; i++) {
			target = droponChilds[i];
			
			//console.debug('target: ', target);
			//console.debug('this._elementOverlap(element, target): ', this._elementOverlap(element, target));

			if (target == element) continue; // skip itself !!!

			//console.debug('x, y: ', x, ', ', y);

			if (Position.within(target, x, y)) {
				var overlap = Position.overlap( 'vertical', target);
				//console.debug('overlap: ', overlap);
				
				// overlaps with _placeHolder ?
				if (target == this._placeHolder) {
					overlapsPlaceHolder = true;
					found = true;
					//console.debug(element.id +' overlaps with PlaceHolder =>'+overlap);
					break;
				}

				//console.debug(element.id +' overlaps with the column child '+ target.id +' =>'+overlap);

				if (overlap > 0.5) {
					this._showPlaceHolder(target, 'before', element.getDimensions());
				} else if (target.previous() != this._placeHolder) {
					this._showPlaceHolder(target, 'after', element.getDimensions());
				}
				found = true;
				break;
			}
		}

		// overlap the column itself
		if (! found) {
			if (Position.within(dropon, x, y)) {
				//
				var overlap = Position.overlap( 'vertical', dropon);
				//console.debug(element.id +' overlaps with the column '+ dropon.id);

				if (overlap > 0.5) {
					this._showPlaceHolder(dropon, 'top', element.getDimensions());
				} else {
					this._showPlaceHolder(dropon, 'bottom', element.getDimensions());
				}

			}
		}
	},


	_placeHolder: null,
	_showPlaceHolder: function(target, position, dimensions) {

		if (! this._placeHolder) {
			this._placeHolder = new Element('div');
			this._placeHolder.className = 'portal-placeholder';
		}

		if (dimensions) {
			this._placeHolder.setStyle({
				//width  : dimensions.width + 'px',
				height : dimensions.height + 'px'
			});
		}

		//@todo fix gecko rendering ??????
		//this._placeHolder.style.visibility = "hidden";

		switch (position) {
		case 'before':
			target.parentNode.insertBefore(this._placeHolder, target);
			break;
		case 'top':
			var firstChildElm = target.firstDescendant();
			if (firstChildElm) {
				try {
					target.parentNode.insertBefore(this._placeHolder, firstChildElm);
				} catch (e) {
					//@todo FIXME Why does it not work in some cases ???
					//  seems to be "bottom of firstChildElm" case...
					//console.debug('insertBefore ERROR');
					//console.debug(this._placeHolder);
					//console.debug(firstChildElm);
				}
			} else {
				target.parentNode.appendChild(this._placeHolder);
			}
			break;
		case 'bottom':
			target.appendChild(this._placeHolder);
			break;
		case 'after':
			var nextElm = target.next();
			if (nextElm) {
				target.parentNode.insertBefore(this._placeHolder, nextElm);
			} else {
				target.parentNode.appendChild(this._placeHolder);
			}
			break;
		}

		this._placeHolder.show();

	},

	_hidePlaceHolder: function() {
		//@todo use a fade effect ???
		if (this._placeHolder) this._placeHolder.hide();
	}

});
