/* Copyright (c) 2005 Tim Taylor Consulting (see LICENSE.txt) */

ToolMan._dragsortFactory = {

	/** 
	 * Iterates over a list's items, making them sortable, applying
	 * optional functions to each item.
	 *
	 * example: makeListSortable(myList, myFunc1, myFunc2, ... , myFuncN)
	 */
	makeListSortable : function(list) {
	
		var helpers = ToolMan.helpers()
		var coordinates = ToolMan.coordinates()
		
		var sortableList = new this._SortableList(this, list)
		
		var items = list.getElementsByTagName("li")
		for (var i = 0; i < items.length; i++)
			this._makeItemSortable(sortableList, items[i])
		
		for (var i = 1, n = arguments.length; i < n; i++)
			helpers.map(items, arguments[i])
			
		return sortableList
	},
	
	makeListSortableCond : function(list, test) {
	
		var helpers = ToolMan.helpers()
		var coordinates = ToolMan.coordinates()
		
		var sortableList = new this._SortableList(this, list)
		
		var items = list.getElementsByTagName("li")
		for (var i = 0; i < items.length; i++)
			if (test(items[i])) this._makeItemSortable(sortableList, items[i])
		
		for (var i = 2, n = arguments.length; i < n; i++)
			helpers.map(items, arguments[i])
			
		return sortableList
	},
	
	_makeItemSortable : function(sortableList, item) {
		
		var dragGroup = this.makeSortable(item)
		dragGroup.sortableList = sortableList
		dragGroup.setThreshold(4)
		var min, max
		
		dragGroup.addTransform(function(coordinate, dragEvent) {
		
			var sortableList = dragEvent.group.sortableList
			for (var i=0; i<sortableList._transforms.length; i++) {
				coordinate = sortableList._transforms[i](coordinate, dragEvent)
			}
			
			if (sortableList._set) {
				return coordinate;
			}else{
				return coordinate.constrainTo(min, max)
			}
		})
		
		var coordinates = ToolMan.coordinates()
		var list = item.parentNode;
		dragGroup.register('dragstart', function() {
			var items = list.getElementsByTagName("li")
			if (items.length==0) {
				min = coordinates.topLeftOffset(list)
				max = coordinates.bottomRightOffset(list)
			}else{
				min = max = coordinates.topLeftOffset(items[0])
				for (var i = 1, n = items.length; i < n; i++) {
					var offset = coordinates.topLeftOffset(items[i])
					min = min.min(offset)
					max = max.max(offset)
				}
			}
			if (list._onlyConstrainX) {
				min.y -= 1000;
				max.y += 1000;
			}
		})
	},
	
	makeSortable : function(item) {
		var group = ToolMan.drag().createSimpleGroup(item, item._handle ? item._handle : null)
		group.register('dragstart', this._onDragStart)
		group.register('dragmove', this._onDragMove)
		group.register('dragend', this._onDragEnd)
		return group
	},

	_onDragStart : function(dragEvent) {
	
		var coordinates = ToolMan.coordinates()
		var group = dragEvent.group
		var sortableList = group.sortableList
		var factory = sortableList.factory
		var item = group.element
		
		factory._originalParent = item.parentNode
		factory._originalNextSibling = item.nextSibling
		factory._correct = coordinates.origin()
		factory._ghost = coordinates.origin()
		
		var mouseOffset = dragEvent.mouseOffset
		var itemTopLeft = coordinates.topLeftOffset(item)
		var itemBottomRight = coordinates.bottomRightOffset(item)
		
		if (mouseOffset.x < itemTopLeft.x || mouseOffset.x > itemBottomRight.x) {
			factory._ghost.x = itemTopLeft.x - mouseOffset.x + item.offsetWidth/2
		}
		
		if (mouseOffset.y < itemTopLeft.y || mouseOffset.y > itemBottomRight.y) {
			factory._ghost.y = itemTopLeft.y - mouseOffset.y + item.offsetHeight/2
		}
		
		sortableList._notifyListeners('itemdragstart', dragEvent)
		sortableList._notifyListeners('itemover', dragEvent)
	},

	_onDragMove : function(dragEvent) {
	
		var helpers = ToolMan.helpers()
		var coordinates = ToolMan.coordinates()

		var group = dragEvent.group
		var sortableList = group.sortableList
		var set = sortableList._set
		var item = group.element
		var factory = sortableList.factory
		var xmouse = dragEvent.transformedMouseOffset
		var moveTo = null
		var itemTopLeft = coordinates.topLeftOffset(item)
		
		if (set) {
			
			var lists = set._sortableLists
			var newList = null
			
			for (var i=0; i<lists.length; i++) {
				var list = lists[i]
				if (list._target) {
					if (xmouse.inside(list._target)) {
						newList = list
						break
					}
				}else{
					if (xmouse.inside(list.element)) {
						newList = list
						break
					}
				}
			}
			
			if (newList) {
				if (newList.element!=item.parentNode && newList._reorderable && (!newList.maxItems || newList.element.getElementsByTagName('li').length < newList.maxItems)) {

					sortableList._notifyListeners('itemout', dragEvent)
					dragEvent.duplicateItem = factory._duplicateItem
					sortableList._notifyListeners('itemexit', dragEvent)
					dragEvent.duplicateItem = null
	
					item.parentNode.removeChild(item)
					newList.element.appendChild(item)
					
					group.sortableList = newList
					newList._notifyListeners('itemover', dragEvent)
					
					factory._correct.x = dragEvent.transformedMouseOffset.x - itemTopLeft.x - item.offsetWidth/2
					factory._correct.y = dragEvent.transformedMouseOffset.y - itemTopLeft.y - item.offsetHeight/2
					
					newList._notifyListeners('itementer', dragEvent)
					
					factory._ghost = null
					
				}else{
					if (newList.element==item.parentNode && group._outside) {
						sortableList._notifyListeners('itemover', dragEvent)
						group._outside = false
					}
				}
			}else{
			
				sortableList._notifyListeners('itemout', dragEvent)
				group._outside = true	
				
			}
		}
		
		if (!sortableList._reorderable) return
		
		if (factory._ghost) xmouse.plusEqual(factory._ghost)

		var previous = helpers.previousItem(item, item.nodeName)
		while (previous != null) {
			var bottomRight = coordinates.bottomRightOffset(previous)
			if (xmouse.y <= bottomRight.y && xmouse.x <= bottomRight.x) {
				moveTo = previous
			}
			previous = helpers.previousItem(previous, item.nodeName)
		}
		if (moveTo != null) {
			helpers.moveBefore(item, moveTo)
			return
		}

		var next = helpers.nextItem(item, item.nodeName)
		while (next != null) {
			var topLeft = coordinates.topLeftOffset(next)
			if (topLeft.y <= xmouse.y && topLeft.x <= xmouse.x) {
				moveTo = next
			}
			next = helpers.nextItem(next, item.nodeName)
		}
		if (moveTo != null) {
			helpers.moveBefore(item, helpers.nextItem(moveTo, item.nodeName))
			return
		}
		
	},

	_onDragEnd : function(dragEvent) {
	
		var coordinates = ToolMan.coordinates()
		var group = dragEvent.group
		var sortableList = group.sortableList
		var factory = sortableList.factory
		var item = group.element
		var outside = false
		
		if (sortableList._set && factory._originalParent != item.parentNode) {
		
			if (sortableList._target) {
				outside = !dragEvent.mouseOffset.inside(sortableList._target)
			}else{
				outside = !dragEvent.mouseOffset.inside(item.parentNode)
			}
				
			if (outside) {
				item.parentNode.removeChild(item)
				if (factory._originalParent)
					factory._originalParent.insertBefore(item, factory._originalNextSibling)
			}
		}
		
		coordinates.origin().reposition(item)
		factory._correct = null
		factory._ghost = null
		
		sortableList._notifyListeners('itemdragend', dragEvent)
		if ((group._copied || factory._originalParent && (factory._originalParent != item.parentNode || factory._originalNextSibling != item.nextSibling)) && !outside) {
			sortableList._notifyListeners('itemdrop', dragEvent)
		}
		
		if (outside) {
			sortableList._notifyListeners('itemexit', dragEvent);
		}
	},
	
	
	
	
	
	
	createListSet : function() {
		return new this._SortableListSet(this)
	},
	
	_SortableList : function(factory, list) {
		this.factory = factory
		this.element = list
		this._target = null
		this._set = null
		this._reorderable = true
		this.maxItems = null
		this._transforms = new Array()
		this._listeners = new Array()
		this._listeners['itementer'] = new Array()
		this._listeners['itemexit'] = new Array()
		this._listeners['itemover'] = new Array()
		this._listeners['itemout'] = new Array()
		this._listeners['itemdragstart'] = new Array()
		this._listeners['itemdragend'] = new Array()
		this._listeners['itemdrop'] = new Array()
	},
	
	_SortableListSet : function(factory) {
		this.factory = factory
		this._sortableLists = new Array()
	},
	
	_correct : null,
	_ghost : null,
	_originalParent : null,
	_originalNextSibling : null,
	
	_duplicateItem : function() {
		var events = ToolMan.events()
		var group = this.group
		var factory = group.factory
		var item = group.element
		var c = item.cloneNode(true)
		events.unregister(c, 'mousedown', group._dragInit)
		events.unregister(c, 'mousemove', group._drag)
		events.unregister(c, 'mouseup', group._dragEnd)
		group.element = c
		group._listeners['dragend'][0](this)
		group._listeners['dragend'][1](this)
		group.element = item
		group.sortableList.factory._originalParent = null
		group._copied = true;
		ToolMan.coordinates().origin().reposition(c)
		item.parentNode.insertBefore(c, item)
		ToolMan.dragsort()._makeItemSortable(group.sortableList, c)
		return c
	}
	
}


ToolMan._dragsortFactory._SortableList.prototype = {
	
	setTarget : function(element) {
		this._target = element
	},
	
	register : function(type, func) {
		this._listeners[type].push(func)
	},
	
	addItemTransform : function(transformFunc) {
		this._transforms.push(transformFunc)
	},
	
	setReorderable : function(reorderable) {
		this._reorderable = reorderable
	},
	
	setMaxItems : function(max) {
		this.maxItems = max;
	},
	
	_notifyListeners : function(type, dragEvent) {
		var listeners = this._listeners[type]
		for (var i=0; i<listeners.length; i++) {
			listeners[i](dragEvent)
		}
	}
	
}

ToolMan._dragsortFactory._SortableListSet.prototype = {

	add : function(sortableList) {
		sortableList._set = this
		this._sortableLists.push(sortableList)
	}
	
}
