/**
 * This file contains SortableListItem 
 * and SortableList classes.
 * @ 2003-2004 Garrett Smith
 * Requires:
 *  - drag.js
 */ 

 
//----------------------------------------------------------------------------
// Document load function.
(function() {
	function initLists() {
		var lists = getElementsWithClass(document.body, "ul", "sortable-list");
		for(var i = 0, len = lists.length; i < len; i++) {
			var items = lists[i].childNodes;
			for(var j = 0, len2 = items.length; j < len2; j++) {
				var item = items[j];
				if(item.tagName) {
					Listener.add(item, "onmousedown", enableDrag);
				}
			}
		}
		function enableDrag() { SortableListItem.getInstance(this); };
	};
	if(document.addEventListener && !(ua.safari))
		document.addEventListener("DOMContentLoaded", initLists, false);
	else Listener.add(window, "onload", initLists);
})();
//----------------------------------------------------------------------------

SortableList = function(el) {
	this.el = el;
	if(typeof this.el.style.MozUserSelect == "string")
		this.el.style.MozUserSelect = "none";
	var span = document.getElementById(el.id + "Fields");
	if(!span) {
		span = document.createElement("span");
		span.id = el.id + "Fields";
		var toplist = findAncestorWithClass(el, "sortable-list") || el;
		document.getElementById( toplist.id +"Form" ).appendChild(span);
	}
	this.elements = document.getElementById(this.el.id+"Fields").getElementsByTagName("input");
	this.id = el.id;		
	
	// Mac IE returns "undefined" for clientHeight on list elements...
	if(ua.macIE) 
		setGetContainerHeight(this);
		
};

SortableList.instances = {};
SortableList.getInstance = function(el) {
	if(!el.id) el.id = "SortableList" + Math.round(Math.random() * 9e9);
	return SortableList.instances[el.id] || (SortableList.instances[el.id] = new SortableList(el));
};

/** SortableListItem is 
 *  a dragObj decorator.
 */
 SortableListItem = function(el) {

	this.el = el;
	this.el.oncontextmenu = function() {
		return false;
	};
	
	this.listItem = DragObj.getInstance(el, DragObj.constraints.VERT);
	this.listItem.isRel = true; // Safari can't automatically find this.
	this.listItem.keepInContainer = true;
	
	var list = findAncestorWithClass(el, "sortable-list");
	this.sortableList = SortableList.getInstance(list);
			
	// Drastically improves dragging for long lists in Mozilla and IE.
	// (Safari is fast with ondragover).
	this.listItem.isOnDragOverSet = false;
	this.addListeners();
};

SortableListItem.instances =  {};

SortableListItem.getInstance = function(el) { 
	return SortableListItem.instances[el.id] 
		|| (SortableListItem.instances[el.id] = new SortableListItem(el));
};

function isBelowElement(a, b){
	return (getOffsetTop(a) + a.offsetHeight >= getOffsetTop(b) + b.offsetHeight);
}

SortableList.toggleNestedList = function(e) {
	var style = this.getElementsByTagName("ul")[0].style;
	style.display = (style.display == "none" ? "block" : "none");
};

SortableListItem.listItemDragged = function(e) {
	var isAbove = isAboveVisArea(this.el, 3);
	var padding = ua.winIE ? 3 : 8;
	
	var isMoveUp = this.y < this.origY();
	var isMoveDown = this.y > this.origY();
	
	 // IE has problems with scrollIntoView();
	if(ua.ie || (!this.el.scrollIntoView)) { 
		if(isAbove && isMoveUp) {
			scrollBy(0, -this.el.offsetHeight);
		}
		if(isBelowVisArea(this.el, padding) && isMoveDown) {
			scrollBy(0, this.el.offsetHeight);
		}
	}
	else if(this.el.scrollIntoView) {
		if(isAbove && isMoveUp)
			this.el.scrollIntoView(isAbove);
		else if(isBelowVisArea(this.el, padding) && isMoveDown)
			this.el.scrollIntoView(isAbove);
	}
};

/** When the listItem is at the top or bottom of the list, 
 *  scroll the viewport so that the sortableList's respective edge 
 *  (top or bottom) can be viewed.
 */
SortableListItem.listItemStopped = function() {

	if(this.isDragStopped) return;
	
	var dy = this.el.offsetHeight * .5;
	
	var isTopViewable = getOffsetTop(this.container) - getScrollTop() >= dy;
	
	if(!isTopViewable && this.el.offsetTop < 1 && getScrollTop() > 0)
		scrollBy(0, -dy);
									
	else {
		var isBottomViewable = getOffsetTop(this.container) 
								+ this.container.offsetHeight + dy 
								- getScrollTop() <= getViewportHeight();
		if(!isBottomViewable && this.el.offsetTop + this.el.offsetHeight > this.container.clientHeight - dy)	
			scrollBy(0, dy);
	}

};

SortableListItem.prototype = {

	
	addListeners : function() {

		this.listItem.onfocus = function(e) {
			addClassConditionally(this.el, "focusedItem");
			SortableList.focusedElement = this.el;
		};
		
		this.listItem.onblur = function(e) {
               //alert(2);
			removeClass(this.el, "activeDrag");
			removeClass(this.el, "focusedItem");
			SortableList.focusedElement = null;
		};

		this.listItem.ondragstart = function(e) {
               SortableListItem.getInstance(this.el).dropped = false;
			addClassConditionally(this.el, "activeDrag");
		};

		


			
		// for use in closure.
		var items = this.sortableList.items();
		var sortableList = this.sortableList;
		
		for(var i = 0, len = items.length; i < len; i++) {
			if(!items[i].id) 
				items[i].id = "SortableItem" + Math.round(Math.random() * 9e9);
			if(this.el != items[i]) {
				var dt = this.listItem.addDropTarget(items[i]);
				dt.ondragover.isSet = false;
				dt.ondrop = function(e, dO) {
					SortableListItem.getInstance(dO.el).dropped = true;
					sortableList.resort(dO, this);
					sortableList.updateForm();
				};
			}
		}
		this.listItem.ondragend = function(e) {
			removeClass(this.el, "activeDrag");
			if(SortableListItem.getInstance(this.el).dropped) return;
			this.el.style.visibility = 'hidden';
			
			// Mac IE offsetTop returns clientTop in css1 mode.
			var macIEAdj = (ua.macIE ? parseInt(getStyle(this.el, "border-top-width"))||1 : 0);
			
			var isIndexChanged = false;
			if(this.el.offsetTop < 1 + macIEAdj) {
				sortableList.el.insertBefore(this.el, sortableList.item(0));
				this.el.style.top = 0;
				sortableList.updateForm();
				isIndexChanged = true;
				
			}
			else {
				var last = sortableList.items()[sortableList.items().length - 1];
				if(this.el != last) {
					var bottom = last.offsetTop + last.offsetHeight;
					if(this.el.offsetTop + this.el.offsetHeight >= bottom) {
						sortableList.el.appendChild(this.el);
						this.el.style.top = 0;
						sortableList.updateForm();
						isIndexChanged = true;
					}
				}
			}
			this.el.style.visibility = 'visible';
			if(!isIndexChanged)
				this.animateBack();
			removeClass(this.el, "activeDrag");
			
			if( !isIndexChanged && getElement("fichier-"+this.el.id) ){
  			  getElement("fichier-"+this.el.id).click();
  	          }  


//			alert(this.el.id);
		};

		var ul = this.el.getElementsByTagName("ul");
		//if(ul)
		//	Listener.add(this.el, "ondblclick", SortableList.toggleNestedList);
		Listener.add(this.listItem, "ondrag", SortableListItem.listItemDragged);
		Listener.add(this.listItem, "ondragstop", SortableListItem.listItemStopped);

	},
	
	yPosOverlap : function() {
		return getOffsetTop(this.el) - (getViewportHeight() + getScrollTop() - 15);
	},
	
	yNegOverlap : function() { return getOffsetTop(this.el) - getScrollTop(); }

};


// Mac IE returns "undefined" for clientHeight on list elements,
// so we instead get the distance to the last listItem's bottom edge.
function setGetContainerHeight(sortableList) {
	// Get a reference to the sortableList through a reference to an SLI.
	
	var tagName = sortableList.el.tagName.toLowerCase();
	if(tagName == "ul" || tagName == "ol") {
		DragObj.prototype.getContainerHeight = function() {
		
			var items = sortableList.items();
			var lastItem = items[ items.length-1 ]
			return lastItem.offsetTop + lastItem.offsetHeight 
					+ (parseInt(getStyle(lastItem, "margin-bottom"))||0)
		};
	}
}

SortableList.prototype = {
	
	id : "",
	form : null,
	elements : null,
	
	items : function() { return getChildElements(this.el); },
	item : function(i) { return this.items()[i]; },
	
	inspectList : function() {
		var lis = getChildElements(this.el);
		var len = lis.length;
		var values = new Array(len);
		for(var i = 0; i < len; i++) {
			var li = lis[i];
			values[i] = lis[i].id;
			var ul = li.getElementsByTagName("ul")[0];
			if(ul)
				values[++i] = "["+SortableList.getInstance(ul).inspectList()+"]";
		}
		return values;
	},
	
	resort : function(listItem, dropTarget) {

		listItem.dropped = true;
		
		var isIndexChanged = false;
		// If the element is on the top half of the drop target, insert it before.
		// otherwise, append it.
		if(listItem.el.offsetTop + listItem.el.offsetHeight >= dropTarget.el.offsetTop + dropTarget.el.offsetHeight) {
			var next = findNextSiblingElement(dropTarget.el);
			if(listItem.el != next) {
				listItem.el.style.visibility = 'hidden';
				this.el.insertBefore(listItem.el, next);
				listItem.el.style.top = dropTarget.el.style.top = 0;
				listItem.el.style.visibility = 'visible';
   				isIndexChanged = true;
			}
		}
		else {
			listItem.el.style.visibility = 'hidden';
			this.el.insertBefore(listItem.el, dropTarget.el);
			listItem.el.style.top = dropTarget.el.style.top = 0;
			listItem.el.style.visibility = 'visible';
			isIndexChanged = true;
 		}
		
		if(!isIndexChanged)
			listItem.animateBack(0, 0);
		else 
			listItem.dropped = true;
	},
	
	updateForm : function(e) {
		var items = this.items();
		var inputs = document.getElementById(this.el.id+"Fields").getElementsByTagName("input");
		for(var i = 0, len = items.length; i < len; i++) {
			if(!this.elements[items[i].id])
				this.makeInput(items[i]);
				
			try {
			// Safari 1.2 does not keep live namedNodeMap, so this.elements will not include the new item!
			inputs[items[i].id].value = i;
			}
			catch(e) {
			
			}
		}
	},
	
	getHiddenInput : function(name) {
		if(!SortableListItem.hiddenInput) {
			SortableListItem.hiddenInput = document.createElement("input");
			SortableListItem.hiddenInput.type = "hidden";
		}
		return SortableListItem.hiddenInput.cloneNode(false);
	},
	
	makeInput : function(el) {
		var list = findAncestorWithClass(el, "sortable-list");
		var span = document.getElementById(list.id + "Fields");
		var input = this.getHiddenInput();
		input.name = el.id;
		span.appendChild(input); 
		input.value = span.getElementsByTagName("input").length-1;
	}
};

SortableList.keyPressed = function(e) {

	if(SortableList.focusedElement == null)
		return;
	
	if(!e)
		e = window.event;
	
	var sibling;
	
	var isUpArrow = e.keyCode == 38;
	var isDownArrow = e.keyCode == 40;
	if(isUpArrow) { // up arrow key.
	
		sibling = findPreviousSiblingElement(SortableList.focusedElement, "li");
	}
	else if(isDownArrow) { // down arrow key.
	
		sibling = findNextSiblingElement(SortableList.focusedElement, "li");
		
	}
	if(sibling != null) {

		if(e.altKey && !ua.moz)
			preventKbdScroll();
		else if(ua.safari) { // no alt key, have to scroll manually.
			var dy = SortableList.focusedElement.offsetHeight;
			scrollBy(0, (isUpArrow ? -dy : dy));
		}
		
		var sli = SortableListItem.getInstance(sibling);
		
		if(e.ctrlKey) {
			if(isUpArrow)
				sli.sortableList.el.insertBefore(SortableList.focusedElement, sibling);
			else if(isDownArrow)
				sli.sortableList.el.insertBefore(sibling, SortableList.focusedElement);
		}
		else {
			if(SortableList.focusedElement) 
				SortableListItem.getInstance(SortableList.focusedElement).listItem.setFocus(false);
				
			sli.listItem.setFocus(true);
			alert(2);
			SortableList.focusedElement = sli.listItem.el;
			
			if(ua.safari) 
				preventKbdScroll();
		}
	}
	
	if(SortableList.focusedElement.scrollIntoView) {
		var isAbove = isAboveVisArea(SortableList.focusedElement);
		if(isAbove || isBelowVisArea(SortableList.focusedElement)) 
			SortableList.focusedElement.scrollIntoView(isAbove);
	}
};

// Mac IE fires keydown repeatedly onkeypress (keydown mimics keypress).
// Safari doesn't handle keyPress well here.
if(ua.moz) 
	Listener.add(document, "onkeypress", SortableList.keyPressed);
else // IE (any) doesn't recognize up or down arrows for keypres.
	Listener.add(document, "onkeydown", SortableList.keyPressed);

// Not quite working in Safari, because we can't capture the 
// altKey before the element is scrolled.
// For Safari, call this in the listItem focus.
function preventKbdScroll() {
	
	if(!preventKbdScroll.dmmy) {
		
		var tagName =  ua.safari ? "input" : "a";
		preventKbdScroll.dmmy = document.createElement(tagName);
		if(!ua.safari)
			preventKbdScroll.dmmy.href = "#";
		
		var sty = preventKbdScroll.dmmy.style;
		if(ua.safari)
	 		sty.visibility = "hidden";
		// We must position the dummy element to prevent 
		// the browser from scrolling to it.
 		var parent = document.forms[0] || document.body;
		parent.appendChild(preventKbdScroll.dmmy);
		preventKbdScroll.parentTop = getOffsetTop(parent);
		
		if(ua.safari)
			document.addEventListener("mouseup", function() { 
				preventKbdScroll.dmmy.focus(); }, true);
	}
	// Put it in middle of the screen.
	preventKbdScroll.dmmy.style.marginTop = 
		(-preventKbdScroll.parentTop + (getViewportHeight()/2) + getScrollTop()) + "px";
	preventKbdScroll.dmmy.focus();
}

SortableList.focusedElement = null;

function isAboveVisArea(el, adj) { return getOffsetTop(el) - (adj || 0) < getScrollTop(); }
function isBelowVisArea(el, adj) {
	return getOffsetTop(el) + el.offsetHeight + (adj || 0) > getScrollTop() 
		+ (window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight);
}

//---------------------------------------------------------------------------

function findPreviousSiblingElement(el) {
	if(!el) alert(findPreviousSiblingElement.caller)
	for(var ps = el.previousSibling; ps != null; ps = ps.previousSibling)
		if(ps.tagName) 
			return ps;
		return null;
}

function findNextSiblingElement(el) {
	for(var ns = el.nextSibling; ns != null; ns = ns.nextSibling)
		if(ns.tagName) 
			return ns;
		return null;
}

(function /*regexFunctions*/ () {
	var STRING_TRIM_EXP = /^\s+|\s+$/g;
	String.prototype.trim = function(){ return this.replace(STRING_TRIM_EXP,""); };
	var WS_MULT_EXP = /\s\s+/g;
	String.prototype.normalize = function() { return this.trim().replace(WS_MULT_EXP, " "); };
	
	var TokenizedExps = { };
	function getTokenizedExp(token, flag){
		return (TokenizedExps[token] || (TokenizedExps[token] = new RegExp("(^|\\s)"+token+"($|\\s)", flag)));
	}
	
	hasToken = function(s, token){
		return getTokenizedExp(token,"").test(s);
	}
	removeClass = function(el, klass) {
		el.className = el.className.replace(getTokenizedExp(klass, "g")," ").normalize();
	}
	addClassConditionally = function(el, klass) {
		if(!getTokenizedExp(klass).test(el.className))
			el.className += " " + klass;
	}
/**
 * Returns an Array of elements with the specified tagName and className.
 */
	getElementsWithClass = function(parent, tagName, klass){
		var returnedCollection = [];
	
		var exp = getTokenizedExp(klass,"");
		var collection = (tagName == "*" && parent.all) ?
			parent.all : parent.getElementsByTagName(tagName);

		for(var i = 0, counter = 0, len = collection.length; i < len; i++){
			
			if(exp.test(collection[i].className))
				returnedCollection[counter++] = collection[i];
		}
		return returnedCollection;
	}

	findAncestorWithClass = function(el, klass) {
		if(el == null)
			return null;
		var exp = getTokenizedExp(klass,"");
		for(var parent = el.parentNode; parent != null;){
		
			if( exp.test(parent.className) )
				return parent;
				
			parent = parent.parentNode;
		}
		return null;
	};
	
})();

function getChildElements(el) {	
	var ret = [];
	var cn = el.childNodes;
	for(var i = 0, len = cn.length; i < len; i++) {
		if(cn[i].tagName)
			ret[ret.length] = cn[i];
	}
	return ret;
}

function getViewportHeight() {
	if(window.innerHeight)
		return window.innerHeight;
		
	// IE treats the root node is the initial containing block.
	if(typeof window.document.documentElement.clientHeight=="number")
		return window.document.documentElement.clientHeight;
	return window.document.body.clientHeight;
}
