// 
/*
  PACDragnDrop - simple single mode drag&drop for elements.
  Class PACDrag is for draggables, class PACDrop is for droppables.
  Both can be used standalone or as interface, but i would recommend

  The standalone-approach:
  ...
  this.pacdrag = PACDrag.MakeDraggable(anElement, someOptions); // make anElement draggable
  ...
  this.pacdrag.unmakeDraggable(); // for anElement free all drag&drop-related resources


  The interface-approach:
  ...
  MyClass.implements(new PACDrag); // enhance MyClass with class PACDrag
  myObject = new MyClass();
  myObject.makeDraggable(anElement, someOptions); // make anElement draggable
  ...
  myObject.unmakeDraggable(); // for anElement free all drag&drop-related resources


  Even this works:
  this.pacdrag = new PACDrag().makeDraggable(anElement, someOptions);

  The same patterns works for PACDrop.


  To make an element draggable pass the element and some options.

  The options passed to make an element draggable must contain 3 callbacks:
  makeHandle - a factory for the 'handle' to drag
  makeGhost  - a factory for the 'ghost' to be temporary injected into a droppable
  showSource - a function that for arguments true/false hides/shows the dragged element

  A callback 'onDrop' can be passed with the options to make an element draggable.
  If the draggable droppes onto a droppable it will be called with the options passed
  to create the draggable as first argument, the options passed to create the droppable
  as second argument and the drop-index as third argument.

  An element 'anchor' can be passed with the options to make an element draggable.
  It will be used as the element to grab to drag the entire draggable. By default
  the draggable element itself will be used for this.

  An array of strings 'keys' can be passed with the options to make an element draggable.
  If the options passed to make an element droppable hold a string 'lock' the array
  must contain this string to get access to the the droppable.


  To make an element droppable pass the element. Droppables have no mandatory options.

  A callback 'onDrop' can be passed with the options to make an element droppable.
  If a draggable droppes onto the droppable it will be called with the same arguments
  like for the 'onDrop'-callback for draggables.

  A string 'lock' can be passed with the options to make an element droppable.
  Any draggable then must contain this string in the array 'keys' passed with its
  options.


  PACDrag-objects fire 'drag'-, drop'- and 'dropped'-events. Don't mismatch them
  with the 'onDrag'- and 'onDropp(ed)'-events of PACItem.
  The 'drag'- and the 'drop'-events are mainly for internal use in PACDragnDrop.
  As payload they carry a xy-position and a the PACDrag-object that fires the event.
  The 'dropped'-event is intended to notify the app to trigger possible backend-ops,
  etc. and will only be fired if the draggable drops onto a explicit droppable.
  'dropped'-events carry 1) the options passed to create the dropped draggable, 2) the
  options passed to create the droppable and 3) the drop-index.

  Please note: i misuse the options passed to create draggables and droppables and passed
  back with callbacks and events as an internal transporter for some internal stuff. Its
  just convenient. To avoid collisions simply don't set / ignore entries with keys starting
  with an underscore.

  For a real-live example see PACView as draggable and PACTab as droppable.
 */




/*
Class: PACDrag
	The PACDragnDrop class for draggables, compelement of PACDrop.
See also:
	PACDrop
*/
PACDrag = new Class({

	/*
	Method: makeDraggable
		Makes an element draggable.
	Parameters:
		el      - an element or its css-ID; the element to drag
		options - object; an options table, for entries see above
	Returns: this
	See also:
		unmakeDraggable
	*/
	makeDraggable: function(el, options){
		PACDragLib.initialize(); // some general onetime initializations
		return PACDragLib.makeDraggable.apply(this, arguments);
	},

	/*
	Method: unmakeDraggable
		Reverts makeDraggable.
	Returns: this
	See also:
		makeDraggable
	*/
	unmakeDraggable: function(){
		return PACDragLib.unmakeDraggable.apply(this, arguments);
	}
});

/*
Function: PACDrag.MakeDraggable
	A convenience function:
	Makes an element draggable, returns the created PACDrag-object.
Parameters:
	el      - an element or its css-ID; the element to drag
	options - object; an options table, for entries see above
Returns: PACDrag object
See also:
	PACDrag::unmakeDraggable
*/
PACDrag.MakeDraggable = function(el, options){
	return new PACDrag().makeDraggable(el, options);
};


/*  !!! Rest of the file: DragnDrop internal-only stuff you shouldn't need to know. !!! */


/* PACDragLib. Internal only. All the attributes and functions for pacdraggables. */
PACDragLib = {

	/* all draggables use this to fire their events. */
	eventClient: new PACEvents(),
	options: { 
		dragThreshold: 15
	},

	/*
	Function: initialize. Internal only. Some onetime initializations for PACDragLib. */
	initialize: function(){
		// 'this' is the PACDragLib

		if(!PACDrag.libInitialized){
			PACDrag.libInitialized = true;
			
			// make div where to inject handles to drag
			this.dragspace = $("dragspace");
			if(!this.dragspace){
				this.dragspace = new Element("div", {"id": "dragspace"});
				this.dragspace.injectInside(document.body);
			}
			this.dragspace.setStyles({
				"position": "absolute",
				"left":     0,
				"top":      0,
				"width":    0,
				"height":   0
			});
		}
	},


	/* Function: makeDraggable.	Internal only. Initialization of draggables. */
	makeDraggable: function(el, options){
		// 'this' is a PACDrag-object
		el = $(el);

		// i store all dragndrop-related stuff into separate tables
		// to avoid any namespace-collisions and to ease freeing resources
		this.dragOptions = $merge(PACDragLib.options, options);
		this.dragOptions.element = el;
		//this.dragOptions.pacdrag = this;

		if(!this.dragOptions.makeHandle){
			// make default handle-factory
			this.dragOptions.makeHandle = el.clone.bind(el);
		}

		// make listeners to add and to remove for dragging
		this.dragOptions._onDragpending = PACDragLib.onDragpending.bind(this);
		this.dragOptions._onDrag = PACDragLib.onDrag.bind(this);
		this.dragOptions._onDrop = PACDragLib.onDrop.bind(this);

		// add dragStart-listener to draggable
		(this.dragOptions.anchor ? this.dragOptions.anchor : this.dragOptions.element).addEvent(
			"mousedown", this.dragOptions._onDragpending);
			
		return this;
	},


	/*
	  Function: unmakeDraggable
	  	Internal only. Reverts makeDraggable.
	 */
	unmakeDraggable: function(){
		// 'this' is a PACDrag object

		// remove dragStart-listener from draggable
		(this.dragOptions.anchor ? this.dragOptions.anchor : this.dragOptions.element).removeEvent(
			"mousedown", this.dragOptions._onDragpending);

		// free all resources
		delete this.dragOptions;
		
		return this;
	},


	/*
	  Function: onDragpending
	  	Internal only. A mouseDown-listener to start dragging.
	 */
	onDragpending: function(evt){
		// 'this' is a PACDrag object

		// convert event and prevent UI selection
		evt = new Event(evt).preventDefault();

		// save drag-start position
		var co;
		if (this.dragOptions.overflown) {
			co = this.dragOptions.element.getCoordinates(this.dragOptions.overflown);
		} else {
			var co = this.dragOptions.parentElement ? this.dragOptions.element.getCoordinates([this.dragOptions.parentElement]) : this.dragOptions.element.getCoordinates();
		}
		this.dragOptions._xOffs = evt.page.x - co.left;
		this.dragOptions._yOffs = evt.page.y - co.top;
		this.dragOptions._clientX = evt.page.x; //org. Mouse position to check threshold for dragging
		this.dragOptions._clientY = evt.page.y;
		
		// add dragging-listeners
		document.addEvent("mousemove", this.dragOptions._onDrag);
		document.addEvent("mouseup",   this.dragOptions._onDrop);
	},


	/* Function: onDrag. Internal only. A mouseMove-listener for dragging. */
	onDrag: function(evt){
		// 'this' is a PACDrag object

		// convert event and prevent UI selection
		evt = new Event(evt).preventDefault();

		//as long as we did not move out of the threshold box: do nothing
		if((Math.abs(this.dragOptions._clientX - evt.page.x) <= this.dragOptions.dragThreshold) && 
	   		(Math.abs(this.dragOptions._clientY - evt.page.y) <= this.dragOptions.dragThreshold)){
			return;
		}
		
		if(!this.dragOptions._dragStarted){
			// dragging just started
			this.dragOptions._dragStarted = true;

			// make handle to drag
			this.dragOptions.handle = this.dragOptions.makeHandle();
			// set some styles only once to be set for dragging
			// set styles for positioning the handle
			// inject handle
			this.dragOptions.handle.setStyles({
				"z-index":  999999,
				"position": "absolute",
				"left": evt.page.x - this.dragOptions._xOffs,
				"top":  evt.page.y - this.dragOptions._yOffs
			}).injectInside(PACDragLib.dragspace);
		}

		else{
			// continue dragging
			// set styles for positioning the handle
			this.dragOptions.handle.setStyles({
				"left": evt.page.x - this.dragOptions._xOffs,
				"top":  evt.page.y - this.dragOptions._yOffs
			});
		}

		// fire drag-event
		var handleCoordinates = this.dragOptions.handle.getCoordinates();
		PACDragLib.eventClient.fireEvent("drag", [evt.page, this, handleCoordinates]);
	},


	/* Function: onDrop. Internal only. A mouseUp-listener to stop dragging. */
	onDrop: function(evt){
		// 'this' is a PACDrag object
		
		// convert event
		evt = new Event(evt);

		// remove dragging-listeners
		document.removeEvent("mousemove", this.dragOptions._onDrag);
		document.removeEvent("mouseup",   this.dragOptions._onDrop);
		
		// del drag-start position
		delete this.dragOptions._xOffs;
		delete this.dragOptions._yOffs;
		
		if(this.dragOptions._dragStarted){
			// stopped dragging
			delete this.dragOptions._dragStarted;
	
			var handleCoordinates = this.dragOptions.handle.getCoordinates();
			// remove handle
			this.dragOptions.handle.remove();
			delete this.dragOptions.handle;

			// fire drop-event
			PACDragLib.eventClient.fireEvent("drop", [evt.page, this, handleCoordinates]);
		} else {
			//additional stuff to behave as if the user has clicked here... 
		}
	}
};

