ToolMan.tmDragFactory = {
	createSimpleGroup : function(element, handle) {
		handle = handle ? handle : element
		var group = this.createGroup(element)
		group.setHandle(handle)
		group.transparentDrag()
		group.onTopWhileDragging()
		return group
	},

	createGroup : function(element) {
		var group = new tmToolManDragGroup(this, element)

		var position = ToolMan.css().readStyle(element, 'position')
		if (position == 'static') {
			element.style["position"] = 'relative'
		} else if (position == 'absolute') {
			/* for Safari 1.2 */
			ToolMan.coordinates().topLeftOffset(element).reposition(element)
		}

		// TODO: only if ToolMan.isDebugging()
		group.register('draginit', this.tmShowDragEventStatus)
		group.register('dragmove', this.tmShowDragEventStatus)
		group.register('dragend', this.tmShowDragEventStatus)

		return group
	},

	tmShowDragEventStatus : function(dragEvent) {
	},

	constraints : function() {
		return this.tmConstraintFactory
	},

	tmCreateEvent : function(type, event, group) {
		return new tmToolManDragEvent(type, event, group)
	}
}

function tmToolManDragGroup(factory, element) {
	this.factory = factory
	this.element = element
	this.tmHandle = null
	this.tmThresholdDistance = 0
	this.tmTransforms = new Array()
	// TODO: refactor into a helper object, move into events.js
	this.tmListeners = new Array()
	this.tmListeners['draginit'] = new Array()
	this.tmListeners['dragstart'] = new Array()
	this.tmListeners['dragmove'] = new Array()
	this.tmListeners['dragend'] = new Array()
}

tmToolManDragGroup.prototype = {
	/*
	 * TODO:
	 *   - unregister(type, func)
	 *   - move custom event listener stuff into Event library
	 *   - keyboard nudging of "selected" group
	 */

	setHandle : function(handle) {
		var events = ToolMan.events()

		handle.toolManDragGroup = this
		events.register(handle, 'mousedown', this.tmDragInit)
		handle.onmousedown = function() { return false }

		if (this.element != handle)
			events.unregister(this.element, 'mousedown', this.tmDragInit)
	},

	register : function(type, func) {
		this.tmListeners[type].push(func)
	},

	addTransform : function(transformFunc) {
		this.tmTransforms.push(transformFunc)
	},

	verticalOnly : function() {
		this.addTransform(this.factory.constraints().vertical())
	},

	horizontalOnly : function() {
		this.addTransform(this.factory.constraints().horizontal())
	},

	setThreshold : function(thresholdDistance) {
		this.tmThresholdDistance = thresholdDistance
	},

	transparentDrag : function(opacity) {
		var opacity = typeof(opacity) != "undefined" ? opacity : 0.75;
		var originalOpacity = ToolMan.css().readStyle(this.element, "opacity")
		var originalBackgroundColor = ToolMan.css().readStyle(this.element, "background-color")

		this.register('dragstart', function(dragEvent) {
			var element = dragEvent.group.element
			element.style.opacity = opacity
			element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')'
			element.style.backgroundColor = "#e4cfd9"
		})
		this.register('dragend', function(dragEvent) {
			var element = dragEvent.group.element
			element.style.opacity = originalOpacity
			element.style.filter = 'alpha(opacity=100)'
			element.style.backgroundColor = originalBackgroundColor;
		})
	},

	onTopWhileDragging : function(zIndex) {
		var zIndex = typeof(zIndex) != "undefined" ? zIndex : 100000;
		var originalZIndex = ToolMan.css().readStyle(this.element, "z-index")

		this.register('dragstart', function(dragEvent) {
			dragEvent.group.element.style.zIndex = zIndex
		})
		this.register('dragend', function(dragEvent) {
			dragEvent.group.element.style.zIndex = originalZIndex
		})
	},

	tmDragInit : function(event) {
		event = ToolMan.events().fix(event)
		var group = document.toolManDragGroup = this.toolManDragGroup
		var dragEvent = group.factory.tmCreateEvent('draginit', event, group)

		group.tmIsThresholdExceeded = false
		group.tmInitialMouseOffset = dragEvent.mouseOffset
		group.tmGrabOffset = dragEvent.mouseOffset.minus(dragEvent.topLeftOffset)
		ToolMan.events().register(document, 'mousemove', group.tmDrag)
		document.onmousemove = function() { return false }
		ToolMan.events().register(document, 'mouseup', group.tmDragEnd)

		group.tmNotifyListeners(dragEvent)
	},

	tmDrag : function(event) {
		event = ToolMan.events().fix(event)
		var coordinates = ToolMan.coordinates()
		var group = this.toolManDragGroup
		if (!group) return
		var dragEvent = group.factory.tmCreateEvent('dragmove', event, group)

		var newTopLeftOffset = dragEvent.mouseOffset.minus(group.tmGrabOffset)

		// TODO: replace with DragThreshold object
		if (!group.tmIsThresholdExceeded) {
			var distance = 
					dragEvent.mouseOffset.distance(group.tmInitialMouseOffset)
			if (distance < group.tmThresholdDistance) return
			group.tmIsThresholdExceeded = true
			group.tmNotifyListeners(
					group.factory.tmCreateEvent('dragstart', event, group))
		}

		for (var i=0; i<group.tmTransforms.length; i++) {
			var myTransform = group.tmTransforms[i];
			if(myTransform)
				newTopLeftOffset = myTransform(newTopLeftOffset, dragEvent);
		}

		var dragDelta = newTopLeftOffset.minus(dragEvent.topLeftOffset)
		var newTopLeftPosition = dragEvent.topLeftPosition.plus(dragDelta)
		newTopLeftPosition.reposition(group.element)
		dragEvent.transformedMouseOffset = newTopLeftOffset.plus(group.tmGrabOffset)

		group.tmNotifyListeners(dragEvent)

		var errorDelta = newTopLeftOffset.minus(coordinates.topLeftOffset(group.element))
		if (errorDelta.x != 0 || errorDelta.y != 0) {
			coordinates.topLeftPosition(group.element).plus(errorDelta).reposition(group.element)
		}
	},

	tmDragEnd : function(event) {
		event = ToolMan.events().fix(event)
		var group = this.toolManDragGroup
		if (group != null)
		{
		    var dragEvent = group.factory.tmCreateEvent('dragend', event, group)

		    group.tmNotifyListeners(dragEvent)

		    this.toolManDragGroup = null
		    ToolMan.events().unregister(document, 'mousemove', group.tmDrag)
		    document.onmousemove = null
		    ToolMan.events().unregister(document, 'mouseup', group.tmDragEnd)
		}
	},

	tmNotifyListeners : function(dragEvent) {
		var listeners = this.tmListeners[dragEvent.type]
		for (var i =0; i<listeners.length; i++) {
			if(listeners[i])
				listeners[i](dragEvent);
		}
	}
}

function tmToolManDragEvent(type, event, group) {
	this.type = type
	this.group = group
	this.mousePosition = ToolMan.coordinates().mousePosition(event)
	this.mouseOffset = ToolMan.coordinates().mouseOffset(event)
	this.transformedMouseOffset = this.mouseOffset
	this.topLeftPosition = ToolMan.coordinates().topLeftPosition(group.element)
	this.topLeftOffset = ToolMan.coordinates().topLeftOffset(group.element)
}

tmToolManDragEvent.prototype = {
	toString : function() {
		return "mouse: " + this.mousePosition + this.mouseOffset + "    " +
				"xmouse: " + this.transformedMouseOffset + "    " +
				"left,top: " + this.topLeftPosition + this.topLeftOffset
	}
}

ToolMan.tmDragFactory.tmConstraintFactory = {
	vertical : function() {
		return function(coordinate, dragEvent) {
			var x = dragEvent.topLeftOffset.x
			return coordinate.x != x
					? coordinate.factory.create(x, coordinate.y) 
					: coordinate
		}
	},

	horizontal : function() {
		return function(coordinate, dragEvent) {
			var y = dragEvent.topLeftOffset.y
			return coordinate.y != y
					? coordinate.factory.create(coordinate.x, y) 
					: coordinate
		}
	}
}
