

/*
Class: 	Ev
	I have created this namespace for functions - but we should put classes in here eventually
*/
var Ev	= {
	version		: 0.1
};


function ArrayOverride(arr1, arr2) {
	for(p in arr2) {
		arr1[p] = arr2[p];
	};
	return arr1;
}



// REMOVE THIS??
if((typeof isset)=='undefined') {
    function isset(variable){
    	return (typeof variable)!='undefined';
    }    
}

// utilities
if(typeof(getCookie)=='undefined') {
    function getCookie(name) {
	Cookie.get(name);
	/*
    	var nameEQ = name + "=";
    	var ca = document.cookie.split(';');
    	for(var i=0;i < ca.length;i++) {
    		var c = ca[i];
    		while (c.charAt(0)==' ') c = c.substring(1,c.length);
    		if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
    	}
    	return null;*/
    }
}

/*
//if(typeof(setCookie)!='undefined') {
//	setCookie = null;
//}

//if(typeof(setCookie)=='undefined' || setCookie==null) {
    //function setCookie(cookieName, cookieValue) {
    //    document.cookie = cookieName + '='+cookieValue+'; expires=Thu, 2 Aug 2099 20:47:11 UTC; path=/';
    //}
    function setCookie(name, value) {
	Cookie.set(name, value, 999999);
    }
//}

setCookie = function(name, value) {
//	alert(name + ' / ' + value);
	Cookie.set(name, value, 999999);
}
*/



var Cookie = {
	set: function(name,value,seconds){
		if(seconds){
			d = new Date();
			d.setTime(d.getTime() + (seconds * 1000));
			expiry = '; expires=' + d.toGMTString();
		}else
			expiry = '';
		document.cookie = name + "=" + value + expiry + "; path=/";
	},
	get: function(name){
		nameEQ = name + "=";
		ca = document.cookie.split(';');
		for(i = 0; i < ca.length; i++){
			c = ca[i];
			while(c.charAt(0) == ' ')
				c = c.substring(1,c.length);
			if(c.indexOf(nameEQ) == 0)
				return c.substring(nameEQ.length,c.length);
		}
		return null;
	},
	unset: function(name){
		Cookie.set(name,'',-1);
	}
}

var setCookie = function(name, value) {
	Cookie.set(name, value, 99999);
};

var getCookie = function(name) {
	return Cookie.get(name);
}; 


/**
 * Loads JavaScriptFile and evaluates it. Use this to include JavaScript files at any point.
 */
function JSLoad(JavaScriptFile, options) {
	try {
		if(!options) var options = {};
	
		if(!options.onComplete) options.onComplete = function() { log("EMPTTY FUNCTION"); }
		//'/s/AssetRendering/edit/AssetEditing.js'
		new Ajax.Request(JavaScriptFile , { 
			onComplete : function(r) { 
				try {
					top.eval(r.responseText);
				} catch(e) {
					logError(e, 'Error including file: "' + JavaScriptFile + '" ');
				}
			}
		});
	} catch(e) {
		logError(e, 'Error including file: "' + JavaScriptFile + '" ');
	}
}

String.prototype.replaceAll=function(s1, s2) {return this.split(s1).join(s2)}



function EvRefreshElementById(id, message) {
	return;
	if(!$(id)) {
		throw new Error("Cannot update non-existant element: '"+id+"'");
	}
	if(typeof message=='undefined') {
		message = 'Reloading page - please wait';
	}
	//     ** Dim the page **
	top._refreshOverlay = new OverlayPage(
		'<div style="background-color : black; color : white; font-family : verdana, arial, sans-serif; font-size : 12pt;"><p>'+message+'<br /><img src="/s/vendor/icons/loading.gif" /></div>'
	, {
		backgroundColor : '#c0c0c0',
		opacity : 0.9
	});

	new Ajax.Request(
		document.location,  {
			onComplete : function(r) {
				try {
					var n = Builder.node('DIV');
					n.innerHTML = r.responseText;
					//alert(document.getElementsBySelector);
					// We have to do all this rubbish because IE is special
					/*                      n.id = 'tmp-node-remove';
					n.style.display = 'none';
					document.body.appendChild(n);*/

					//var m = $('tmp-node-remove');
					alert('test1');
					var b = n.getElementsBySelector('#dh-page-wrapper');
					// alert(b); alert(b[0]);
					//      $('dh-page-wrapper').innerHTML = b[0].innerHTML;
					alert('test2');
					$('dh-page-wrapper').replaceChild(b[0], $('dh-page-wrapper'));
					//              m.remove();
					//              n.remove();

				} catch(e) {
					alert('Problem updating page: ' + e.message);
				}

				try {
					setTimeout(function() {
						top._refreshOverlay.fadeDestroy();
						}, 500);
					} catch(e) {
						alert('Problem removing overlay: ' + e.message);
					}
				}
			}
		);
//     ** Bring up the page (after a second) **
}


// generic node searching function. TODO: Move to generic 'element helpers' or 'DOM helpers' or similar file.
function findAncestor(startNode, callback) {
	var searchLimit = 20;
	var notFound = true;
	var testNode = startNode;
	while(notFound && searchLimit>0) {
		if(callback(testNode)) {
			return testNode;
		}
		testNode = testNode.parentNode;
		searchLimit--;
	}
	return false;
}

/*
Class: 	OverlayPage
	Overlays the page with some content (like light-box??)
	The offset is positioned relative to the *window*, not the page, so the user should see the overlay appear in 
	the same place on the screen no matter where it's called from.

Usage:
>	new OverlayContent('overlay html content', { options... });
or
>	new OverlayContent('overlay html content', { options... });

Options:
	zIndex			- z-index of the overlay. Everything the overlay builds will be higher than this
	backgroundColor	- background colour
	onComplete		- callback. Called when overlay is done.
	beforeDestroy	- callback.
	onDestory		- callback.
	ClassName		- classname used for the overlay
	opacity			- Opacity of the overlay
	draggable		- if false, overlay contents will not be draggable. Default=true.
	allowClickAway	- If true, clicking on the background destroys the entire overlay. Otherwise, the overlay is modal. (Should rename?)
*/
var OverlayPage = new Class({
	//target		: null,
	overlayContent	: 'No overlay set',
	options 	: {
		zIndex	: 10,
		backgroundColor : 'transparent',//'#c0c0c0;',
		opacity	: 0.6,
		onComplete	: function() {},
		beforeDestroy : function() {},
		onDestroy	: function() {},
		ClassName	: 'OverlayContent',
		draggable	: true,
		allowClickAway	: false,	// If true, clicking on the background destroys the entire overlay
		backgroundClickCloses : false
	},
	
	overlayNode	: null,

	/**
	 *
	 */	
	initialize : function(overlayContent, options) {
		this.overlayContent = overlayContent;
		this.setOptions(options);

		// make the overlay wrapper
		var overlay = document.createElement('DIV');
		overlay.id = 'overlayId';

		//this.cloneSize(this.target, overlay);
		/*overlay.setStyle({
			height : document.body.getDimensions().height + 'px',
			width : document.body.getDimensions().width + 'px',
			position : 'absolute',
			backgroundColor : 'none',
			zIndex : this.options.zIndex
		});
		overlay.setOpacity(1);
		overlay.addClassName(this.options.ClassName);*/
		overlay.style.height = Element.getSize(document.body).y + 'px';
		overlay.style.width = Element.getSize(document.body).x + 'px';
		overlay.style.position = 'absolute';
		overlay.style.backgroundColor = 'transparent';
		overlay.style.zIndex = this.options.zIndex;
		overlay.style.opacity = 1;
		
		// make the overlay background 
		var background = document.createElement('DIV');
		/*background.setStyle({
			height : document.body.getDimensions().height + 'px',
			width : document.body.getDimensions().width + 'px',
			position : 'absolute',
			backgroundColor : this.options.backgroundColor,
			zIndex : this.options.zIndex + 3
		});*/
		// background.setOpacity(this.options.opacity);
		// background.setOpacity(0);
		background.style.height = Element.getSize(document.body).y + 'px';
		background.style.width = Element.getSize(document.body).x + 'px';
		background.style.position = 'absolute';
		background.style.backgroundColor = this.options.backgroundColor;// 'transparent';
		background.style.zIndex = this.options.zIndex + 3;
		background.style.opacity = 0;
		
		if(this.options['backgroundClickCloses']) {
			/*new Event.observe(
				background,
				'click',
				function (evt) {
					var n = Event.element(evt);
					OverlayContent.destroyOverlay(n);
				}
			);*/
		}
		
		// for the overlay itself
		var foreground = document.createElement('DIV');
		foreground.style.marginLeft = '25%';
		// This makes the offset relative to the scroll and size of window

		// Workaround for IE -- should push this into a single function 
		var windowHeight = (window.innerHeight) ? window.innerHeight : document.documentElement.clientHeight;
		var pageYOffset = window.pageYOffset ? window.pageYOffset : document.body.scrollTop;
		var topOffset = (((100/800)*windowHeight) + pageYOffset);
		foreground.style.marginTop = (topOffset + 'px');
		
		foreground.style.position = 'absolute';
		foreground.style.zIndex = (this.options.zIndex + 5);
		foreground.innerHTML = overlayContent;
		//	foreground.setOpacity(0);
		//	background.setOpacity(0);
		foreground.style.opacity = 0;
		background.style.opacity = 0;
		overlay.appendChild(background);
		overlay.appendChild(foreground);
		
		try{	
			// target.parentNode.insertBefore(overlay, target);
			document.body.insertBefore(overlay, document.body.firstChild);
			//new Effect.Appear(foreground, { duration : 0.5 });
			foreground.morph({
				opacity : 1
			});
			background.morph({
				opacity : this.options.opacity
			});
			//new Effect.Opacity(background, { from : 0, to : this.options.opacity, duration : 0.5 });
		} catch(e) {
			logError(e, 'Failed to create overlay');
		}
		
		this.onComplete = this.options.onComplete;
		
		overlay.OverlayContent = this;
		this.overlayNode = overlay;
		
		if(this.options.draggable) {
			new Draggable(foreground);
		}
		//removeOverlay = this.destroy;
		if(overlayContent.activate) overlayContent.activate(this);
	},
	
	destroy : function() {
		this.options.beforeDestroy();
		// this.overlayNode.remove();
		$(this.overlayNode.id).remove();
		this.options.onDestroy();
	},
	
	fadeDestroy : function() {
		this.overlayNode.tween({
			opacity : [this.options.opacity, 0]
		});
		
		top._destroyOverlayPage = this;
		setTimeout(function() {
			top._destroyOverlayPage.destroy();
			top._destroyOverlayPage = null;
		}, 550);
	},
	
	setOptions : function(options) {
		for(p in options) {
			this.options[p] = options[p];
		}
	}
});


// TODO: Move overlay stuff to a 'visual widgets'file. 
/*
Class: 	OverlayContent
	Overlays a particular node with some HTML 
todo:
	add ability to resize the overlay or underlay nodes so they're the same size.
Usage:
>	new OverlayContent('someId', 'overlay html content', { options... });
or
>	new OverlayContent(someNode, 'overlay html content', { options... });

Options:
	zIndex			- z-index of the overlay. Everything the overlay builds will be higher than this
	backgroundColor	- background colour
	onComplete		- callback. Called when overlay is done.
	beforeDestroy	- callback.
	onDestory		- callback.
	ClassName		- classname used for the overlay
	opacity			- Opacity of the overlay
	destroyOnMouseout - if true, mouse out triggers destroying the overlay
*/
var OverlayContent = new Class({
	
	/*
	Constructor: initialize
		See usage, above.
	*/	
	initialize : function(target, overlayContent, options) {
		//if((typeof target)=='string') target = $(target);
		//log('setting up with: ' + target + '/' + overlayContent);
		this.target		= null;
		this.overlayContent	= 'No overlay set';
		this.options 	= Object.extend({
			zIndex	: 10,
			backgroundColor : 'blue',
			onComplete	: function() {},
			beforeDestroy : function() {},
			onDestroy	: function() {},
			ClassName	: 'OverlayContent',
			opacity 	: 0.4,
			destroyOnMouseout	: false,
			width		: null
			// TODO: Add matchHeight option
		}, options);

		this.overlayNode = null;

		this.target = $(target);
		
		if(this.target==null) {
			throw new Error("this.target is undefined - possibly trying to put overlay on non-existant node: " + target);
		}
		
		this.overlayContent = overlayContent;
		this.setOptions(options);
		
		this._removalInProgress	= false;
		
		// make the overlay wrapper
		var overlay = document.createElement('DIV');
		overlay.id = 'overlayId';
		
		// BUG: Overlay problem - use Position.clone if overlay is sibling of targetNode; use this.cloneSize if overlay
		// is child of targetNode
		this.cloneSize(this.target, overlay);
		
		// TODO: The clone() function here seems to fix the overlay moving around the page - but I'm not 100% sure why. Test in some other browsers.
		/*try {
			clone =  function(source, target) {
			    source = $(source);
			    target = $(target);
			    target.style.position = 'absolute';
			    // var offsets = Position.realOffset(source);
			    var offsets = Position.cumulativeOffset(source);
			    target.style.top    = offsets[1] + 'px';
			    target.style.left   = offsets[0] + 'px';
			    target.style.width  = source.offsetWidth + 'px';
			    target.style.height = source.offsetHeight + 'px';
			  }
			//Position.clone(target, overlay);
			clone(target, overlay);
		} catch(e) {
			logError(e, 'asdasdsa');
		}***/
		
		/*overlay.setStyle({
			position 	: 'absolute',
			backgroundColor : 'none',
			zIndex : this.options.zIndex,
			height : 'auto'
		});*/
		// TODO: Make a nice setStyle function to make this look nicer.
		overlay.style.position = 'absolute';
		overlay.style.backgroundColor = 'transparent';
		overlay.style.zIndex = this.options.zIndex;
		overlay.style.height = 'auto';
		overlay.opacity = 0;
		//overlay.setOpacity(1);
		// set above overlay.setOpacity(0);
		// MOVED overlay.addClassName(this.options.ClassName);
		
		// make the overlay background 
		var background = document.createElement('DIV');
		this.cloneSize(this.target, background);
		if(this.options.width!=null) {
			background.style.width = (this.options.width + 'px');
		}
		/*background.setStyle({
			position	: 'absolute',
			backgroundColor	: this.options.backgroundColor,
			zIndex	: this.options.zIndex + 3
		});*/
		background.style.position = 'absolute';
		background.style.backgroundColor = this.options.backgroundColor;
		background.style.zIndex = this.options.zIndex + 3;
		
		//MOVED	background.setOpacity(this.options.opacity);
		
		// for the overlay itself
		var foreground = document.createElement('DIV');
		//this.cloneSize(this.target, foreground);
		this.cloneWidth(this.target, foreground);
		if(this.options.width!=null) {
			foreground.style.width = (this.options.width + 'px');
		}
		/*foreground.setStyle({
			position	: 'absolute',
			zIndex	: this.options.zIndex + 5
		});*/
		foreground.style.position = 'absolute';
		foreground.style.zIndex = this.options.zIndex + 5;

		foreground.innerHTML = overlayContent;

		overlay.appendChild(background);
		overlay.appendChild(foreground);

		//log(target);
		try{
			// BUG: targetNode isn't draggable if overlay is actually on TOP 	
			target.insertBefore(overlay, target.firstChild);
			//target.parentNode.insertBefore(overlay, target);
			new Effect.Appear(overlay, { duration : 0.3 });
		} catch(e) {
			logError(e, 'Failed to create overlay');
		}
		
		/*if(this.target.getHeight() < foreground.getHeight()) {
			this.target.setStyle({height : foreground.getHeight() + 'px'});
		}*/
		var dimensions = Element.getSize(foreground);
		if(this.target.getHeight() < dimensions.y) {
			this.target.style.height = dimensions.y + 'px';
		}
		
		this.onComplete = this.options.onComplete;
		
		overlay.OverlayContent = this;
		
		this.overlayNode = overlay;
		//removeOverlay = this.destroy;
		
		// BEGIN: Destroy / trigger mouse out
		
		if(this.options.destroyOnMouseout) {
			var _this = this;
			Event.observe(
				overlay,
				'mouseout', 
				function(evt) {
					var ancestors;
					if((typeof evt.relatedTarget)!='undefined') {
						aL = evt.relatedTarget.ancestors();
					} else {
						aL = evt.fromElement;	//IE FIX
					}
					var l = aL.length;
					var i = 0;
					var foundOverlay	= false;
					//log('Checking');
					for(i=0;i<l;i++) {
						//log(' = ' + aL[i]);
						if((typeof aL[i].OverlayContent)!='undefined') {
							foundOverlay = true;
						}
					}
					if(!foundOverlay) {
						_this.destroy();
					}
				}
				);
		}
		// END: //
		
		//// *** MOVED TO HERE *** \\\\
		//background.setOpacity(this.options.opacity);
		background.style.opacity = this.options.opacity;
		overlay.addClass(this.options.ClassName);
	},
	
	// Remove the overlay. If the overlay isn't there any more, we do nothing (No errors thrown).
	destroy : function() {
		try {
			this.options.beforeDestroy();
			// this.target.setStyle({height : 'auto'});
			// Changed for browser compat. Was: this.overlayNode.remove();
			$(this.overlayNode).remove(); // This was: $(this.overlayNode.id) - but this caused errors.
			this.options.onDestroy();
		} catch(e) {
			logError(e, 'Problem removing overlay - this usually means the overhas already been removed.', logger.DETAIL);
		}
	},
	
	setOptions : function(options) {
		for(p in options) {
			this.options[p] = options[p];
		}
	},
	
	cloneSize : function(sourceNode, targetNode) {
		targetNode.style.width=sourceNode.getSize().y + 'px';
		targetNode.style.height=sourceNode.getSize().x + 'px';
	},
	
	cloneWidth : function(sourceNode, targetNode) {
		targetNode.style.width=sourceNode.getSize().y + 'px';
	}
});



/*
Function: OverlayContent.destroyOverlay
	Statically called function which find the nearest overlay wrapping "node" and destroys it
*/
OverlayContent.destroyOverlay = function(startNode) {
	var n = findAncestor(startNode, function(testNode) {
		if((typeof testNode.OverlayContent)!='undefined') {
			return true;
		} else {
			return false;
		}
	});
	try {
		n.OverlayContent.destroy();
	} catch(e) {
		logError(e, 'Failed to remove overlay');
	}
}


/*
Class: 	EditableText
	Makes text editable by letting the user click and then replacing the text with a text input

Usage:
>	new EditableText(targetNode, {...options...});

Options:
	onSubmit	- callback. Called when the user clicks 'ok' when they've finished editing the text.
*/
var EditableText = {};
EditableText = Class.create();
EditableText.prototype = {
	
	initialize : function(id, options) {
		
		this.id = id;
		this.targetNode = $(id);
		$(this.id)._EditableText = this;
		
		this.options = Object.extend({
			onSubmit	: function(result) {
				alert('nothing for ' + result);
			},
			editValue	: ''
		}, options);
		//if(options['onSubmit']) {
		//	this.options['onSubmit'] = options['onSubmit'];
		//}
		
		this.editing = false;
		
		var _this = this;
		Event.observe(this.id, 'click', function() {
			if(!_this.editing) {
				_this.editing = true;
				_this.loadEditForm();
			}
		});
		$(id).setStyle({
			cursor : 'pointer'
		});
	},
	
	
	loadEditForm : function() {
		var value = this.options.editValue;//'';//$(this.id).innerHTML;
		var okid = 'editabletext_'+this.id;
		var inid = 'editabletext_input_'+this.id;
		$(this.id).innerHTML = '<input id="'+inid+'" type="text" value="'+value+'" size="13" /><div id="'+okid+'" style="display : inline;">Ok</div>';
		var _this = this;
		Event.observe(okid, 'click', function() {
			var os = _this.options['onSubmit'];
			//os($(inid).value);
			os.call(_this, ($(inid).value));
		});
	},
	
	// TODO: IMPLEMENT THIS:: Restores the editable text
	restore : function() { },
	
	onSubmit : function(value) {
		
	}
}


/*
Class: 	StickyDraggable
	Makes the target node draggable and keeps it where it was dragged to when the page refreshes.
	
Usage:
>	new StickyDraggable(targetNode, {options})

Options:
	(None implemented)
	
TODO:
	Bug: offset isn't quite right. Is within approx 30px.
 */
var StickyDraggable = {};
StickyDraggable = Class.create();

/*
Constant: StickyDraggable.WINDOW

Constant: StickyDraggable.PAGE
*/
StickyDraggable.WINDOW	= 1;
StickyDraggable.PAGE	= 2;

//
StickyDraggable.c = 0;
StickyDraggable.isDragging	= false;
StickyDraggable.singleton	= null;
StickyDraggable.lastDocOffsets	= [];
StickyDraggable.updateTimeout	= 200;
StickyDraggable.updatePosition	= function() {
	try {
//		StickyDraggable.enableUpdatePosition = false;
		if(!StickyDraggable.isDragging) {
			
			if(!$(StickyDraggable.singleton.stickyNode)) {
				setTimeout(StickyDraggable.updatePosition, 1000);
				return;
			}
			
			var offsets = document.viewport.getScrollOffsets();
			
			if(StickyDraggable.lastDocOffsets[1]!=offsets[1]) {
				var topchange	= offsets[1] - StickyDraggable.lastDocOffsets[1];
				new Effect.MoveBy(
					StickyDraggable.singleton.stickyNode,
					(topchange), // y
					0, // x
					{
						mode : 'relative',
						duration : .3
					});
			}
			StickyDraggable.lastDocOffsets = offsets;
		}
		
		
		setTimeout(StickyDraggable.updatePosition, StickyDraggable.updateTimeout);
	} catch(e) {
		alert(e.message);
		setTimeout(StickyDraggable.updatePosition, StickyDraggable.updateTimeout);
	}
}

StickyDraggable.prototype = {
	
	initialize : function(id, options) {
		node = $(id);
		if(node._isStickyDraggable) {
			return false;
		}
		
		this.options = Object.extend({
			relativeTo	: StickyDraggable.WINDOW
		}, options);
		
		//alert(this.options.relativeTo);
		
		this.draggable = null;
		
		this.offsetLeft		= getCookie('sticky-left');
		this.offsetTop		= getCookie('sticky-top');
		
		
		var nodeCopy = Object.clone(node);
		//node.remove();
		
		var stickyNode = document.createElement('DIV');
		stickyNode.id = 'stickydraggable'; // This assumes there's only 1 node on the page
		stickyNode.style.position = 'absolute';
		stickyNode.style.zIndex = 1000;
		try {
			document.body.insertBefore(stickyNode, document.body.firstChild);
		} catch(e) {alert(e.message);}
		
		stickyNode.appendChild(node);
		
		stickyNode.StickDraggable = this;
		this.stickyNode = stickyNode;
		
		new Effect.MoveBy(
			stickyNode,
			getCookie('sticky-top'),
			getCookie('sticky-left'),
			{
				mode : 'relative',
				duration : 0.1
			});
		//nodeCopy.innerHTML = 'x/y = ' + getCookie('sticky-left') + '/' +getCookie('sticky-top');
		
		
		var _this = this;
		ddd = this.draggable = new Draggable(stickyNode, {
			onEnd : function() {
				try {
					var off = Position.cumulativeOffset(stickyNode);
					ddd = off;
					
					// Returns [x, y] == [left, right]
					var docOff = document.viewport.getScrollOffsets();
					
					// Make sure position is relative to window, not the page
					_this.offsetLeft = (off.left - docOff[0]);
					_this.offsetTop	= (off.top - docOff[1]);
					
					setCookie('sticky-left', (_this.offsetLeft));
					setCookie('sticky-top', (_this.offsetTop));
					
					//alert( 'x/y = ' + off.left + '/' + off.top );
					StickyDraggable.isDragging = false;
				} catch(e) {
					alert(e.message);
				}
			},
			onStart : function() {
				//StickyDraggable.enableUpdatePosition = false;
				StickyDraggable.isDragging = true;
			},
			zindex : 1000
		});
		node._isStickyDraggable = true;
		nodeCopy._isStickyDraggable = true
		
		// 
		if(this.options.relativeTo==StickyDraggable.WINDOW) {
			StickyDraggable.singleton = this;
			StickyDraggable.updatePosition();
		}
//		this.updatePosition();
		/*setTimeout(this.updatePositionfunction() {
			var off = document.viewport.getScrollOffsets();
			alert(off[1]);
		}, 500); */
	},
	
	restoreFromCookie: function() {
		//alert(getCookie('sticky-top') + ' / ' +  getCookie('sticky-left'));
		var _this = this;
		new Effect.MoveBy(
			_this.stickyNode,
			_this.offsetTop,//getCookie('sticky-top'),
			_this.offsetLeft,//getCookie('sticky-left'),
			{
				mode : 'absolute',
				duration : 0.1
			});
	}
}




/*
Class: 	SortableList
	Makes lists (li tags) sortable and editable. Keeps the sorted items in a hidden HTML field with name=htmlFieldName
	
Parameters:
	targetNode	 	- id or node
	htmlFieldname	- HTML field name to keep the sorted items in
	options			- see below
	
Options:
	editable		- boolean. If true, each item can be clicked to edit.
	addItems		- boolean. if true, user can add items to the list.
	deleteItems		- boolean. If true, user can remove items from the list. Default: true.
	false			- boolena. If true, the values are stored in a single field in CSV format.
	
Usage:
>	new SortableList(targetListNode, 'html_field')
*/
var SortableList = {};
SortableList = Class.create();
SortableList.prototype = {

	initialize : function(id, htmlFieldName, options) {
		this.targetNode = $(id);
		this.htmlFieldName = htmlFieldName;
		this.options = Object.extend({
			editable : true,
			addItems : false,
			deleteItems : true,
			csv	: false
		}, options);
		
		
		// Make the hidden fields etc
		this._hiddenFieldNode = Builder.node(
			'div', 
			{ id : 'hidden-fields' },
			'Hidden fields' );
		this.targetNode.parentNode.insertBefore(this._hiddenFieldNode, this.targetNode);
		
		this.initializeChildNodes();
		
		this.makeSortable();
		
		if(options.addItems) {
			this.createAddItemButton();
		}
		
		this.serializeToHiddenFields();
	},
	
	createAddItemButton : function() {
		var addButton = Builder.node('DIV',{
			 'class' : 'sortableItem'
		}, 'Add item');
		
		// We need to position the 'add' button directly below the list, but *not* in the list
		this.targetNode.parentNode.appendChild(
			addButton, this.targetNode
			);
		
		var _this = this;
		new EditableText(addButton, {
			onSubmit : function(result) {
				_this.addItem(result);
			}
		});
	},
	
	newItemCount : 0,
	addItem : function(newItemValue, newItemId) {
		this.newItemCount++
		if(!newItemId) newItemId = 'NEW_' + (this.newItemCount);
		var newItem = Builder.node('LI',{
			id : newItemId,
			'class' : 'sortableItem'
		}, newItemValue);
		
		this.targetNode.appendChild(newItem);
		this.initializeNode(newItem);
		new Effect.Highlight(newItem);
		this.makeSortable();
		this.serializeToHiddenFields();
	},
	
	// Add a handle to the item. Put the text in a wrapper. Make it editable
	initializeChildNodes : function() {
		var children = $(this.targetNode).childNodes;
		if(typeof children=='undefined') {
			alert('Failed to serialize sortable.');
		}
		var _this = this;
		for(var i=0;i<children.length;i++) {
			var cNode = children[i];
			this.initializeNode(cNode);
		}
	},
	
	initializeNode : function(node) {
		var _this = this;
		
		node._SortableList = this;
		
		node._originalValue = node.innerHTML;
		node.innerHTML = '';
		
		if(this.options.deleteItems) {
			node.innerHTML = '<a class="delete" title="Delete item" style="margin-right : 10px;" href="#" onClick="var p = this.parentNode; this.parentNode.remove(); p._SortableList.serializeToHiddenFields(); return false;"><img src="/s/vendor/icons/bin.jpg" alt="Delete item" title="Delete item" /></a>DELETE THIS &nbsp;&nbsp;';
		}
		
		node.innerHTML += '<span class="handle" style="margin-right : 10px;"><img src="/s/vendor/icons/arrows.jpg" alt="Drag item up or down" title="Drag item up or down" /></span>&nbsp;&nbsp;'+
			'<span class="sortable-value">' + node._originalValue + '</span>';
		
		if(this.options.editable) {
			var l = node.getElementsByClassName('sortable-value');
			var editableNode = l[0];
			
			//new EditableText( node, {
			new EditableText( editableNode, {
				onSubmit : function(value) {
					this.targetNode.innerHTML = value;
					this.targetNode._originalValue = value;
					//_this.initializeNode(node);
					_this.makeSortable();
					_this.serializeToHiddenFields();
				},
				editValue : this.getValueOfListItem(node)
			});
		}
	},
	
	makeSortable : function() {
		var _this = this;
		
		Sortable.create($(this.targetNode), {
			onUpdate : function() {
				try {
					_this.serializeToHiddenFields();
				} catch(e) {
					alert(e.message);
				}
			},
			handle : 'handle'
		});
	},
	
	// copied the id/innerHTML values into name/values of hidden fields
	serializeToHiddenFields : function() {
		this._hiddenFieldNode.innerHTML = 'setting up';
		var children = $(this.targetNode).childNodes;
		if(typeof children=='undefined') {
			alert('Failed to serialize sortable.');
		}		
		this._hiddenFieldNode.innerHTML = '';//'l='+children.length+Math.random();
		if(this.options.csv) {
			
			// ** New method - store in CSV
			var first = true;
			var str = '';
			for(var i=0;i<children.length;i++) {
				var fldName = children[i].id;
				fldName = fldName.replace(/value_/, "");
				
				if(!first) {
					str += ',';
				} else {
					first = false;
				}
				
				str += fldName;
				
			}
			
			var n = Builder.node(
				'input', {
					type : 'hidden',
					name : this.htmlFieldName,
					value : str
				} );
			this._hiddenFieldNode.appendChild(n);
			
		} else {
			// ** Standard method -- stored in an Array
			for(var i=0;i<children.length;i++) {
			
				var fldName = children[i].id;
				fldName = fldName.replace(/value_/, "");
				//alert(fldName);
				var n = Builder.node(
					'input', {
						type : 'hidden',
						name : this.htmlFieldName + '['+fldName+']',
						value : this.getValueOfListItem(children[i])
					} );
				this._hiddenFieldNode.appendChild(n);
			}
		}
	},
	
	getValueOfListItem : function(node) {
		var nL = node.getElementsByClassName('sortable-value');
		labelNode = nL[0];
		return labelNode.innerHTML;//+'*VALUE*';
	}
}

/*
Class: 	SimpleModal
	Simple modal forms. 
	This requires ExtJS.
	
	Note: [DF] I created SimpleModal because we need to load in content over AJAX and have it evaluated. Extjs didn't seem to do this natively.



Usage:
	new SimpleModal('this is some content...', {...options...});
	
Options:
	title	- title of the modal
	height	- integer
	width 	- integer
	scroll	- boolean
*/
SimpleModal = {}
SimpleModal = new Class();//Class.create();

/*
Function: SimpleModal.close()
	Call this to close the most recently openned modal
*/
SimpleModal.close = function() {
//alert(SimpleModal.openModals);
/*
	if(SimpleModal.openModals<=1) {
		setTimeout(function() {
alert('fixing');
document.body.style.overflow = 'scroll';
document.body.style.height = 'auto';
}, 1000);
	}
*/	
	try {
		var s = SimpleModal.openModals.pop();
		s.close();
	}catch(e) {
		alert('SimpleModal.close: ' + e.message);
	}
}

/*
Function: SimpleModal.reload
	Reloads the top-most modal
*/
SimpleModal.reloadCaller = function() {
//	var s = SimpleModal.openModals[SimpleModal.openModals.length-2]);
}


SimpleModal.openModals	= [];

SimpleModal.prototype = {
	
	initialize : function(HTML, options) {



function getViewportDimensions() {
  var myWidth = 0, myHeight = 0;
  if( typeof( window.innerWidth ) == 'number' ) {
    //Non-IE
    myWidth = window.innerWidth;
    myHeight = window.innerHeight;
  } else if( document.documentElement && ( document.documentElement.clientWidth || document.documentElement.clientHeight ) ) {
    //IE 6+ in 'standards compliant mode'
    myWidth = document.documentElement.clientWidth;
    myHeight = document.documentElement.clientHeight;
  } else if( document.body && ( document.body.clientWidth || document.body.clientHeight ) ) {
    //IE 4 compatible
    myWidth = document.body.clientWidth;
    myHeight = document.body.clientHeight;
  }
  return {
    height : myHeight,
    width : myWidth
  };
//  window.alert( 'Width = ' + myWidth );
//  window.alert( 'Height = ' + myHeight );
}
//alertSize();		
var windim = getViewportDimensions();

	/*
		document.body.style.overflow = 'hidden';
//		if(!SimpleModal._windim) SimpleModal._windim = windim;
		document.body.style.height = ((windim['height']-10) + 'px')
		*/
//alert(document.body.getDimensions());
		/*
		Property: isClosed
			Set to true once the modal is removed. Ensures we don't try to remove 'this' from .openModals
		*/
		this.isClosed = false;
		
		this.options = ArrayOverride({
			onClose : function() {},
			title	: 'Dialog title',
			height	: 300,
			width 	: 300,
			scroll	: true,
			resizable:true,
			// Not a very nice way of doing this -- should replace??
			reloadCallerOnClose	: false
		}, options);
		
		var n = new Element('DIV', {
		//	'style'	: 'clear : both;',
			'class' : 'x-hidden'
		});
		document.body.appendChild(n);
		
		this._wrapperId	= 'simplemodal-' + (SimpleModal.openModals.length+1);
		
		
		HTML = '<div id="' + this._wrapperId + '">' + HTML + '</div>';
		
		//n.id = 'dialogWrapper';
		n.innerHTML = '<div class="x-window-header">'+this.options.title+'</div><div id="dialogInner" class="x-window-body"">'+HTML+'</div>';
		//'<img src="/s/vendor/icons/loading.gif" alt="Loading" /></div>';
		
		this.dialogInner	= n;
		
		// TODO: Doesn't work -- can't get the modal to resize.
		//var dim	= this.dialogInner.getDimensions();
		
		this.dialog = new Ext.Window({
				applyTo : this.dialogInner,
				
				//title : this.options.title,
				shim 	: false,
				width	: this.options.width,
				height	: this.options.height,
				
				minHeight:300,
				minimizable : false,
				shadow	: false,
				modal	: true,
				overflow:'auto',
				autoScroll: (this.options.scroll),
				bufferResize : true,
				resizable : this.options.resizable,
				
				plain	: true
			});
 		
		// Callback for onClose
		var _this = this;
		this.dialog.addListener('close', function() {
			// Let onClose callback run first
			_this.options.onClose();
			
			// Remove 'this' from the openModals
			if(!_this.isClosed) {
				SimpleModal.openModals.pop();
				_this.isClosed = true;
			}
			
			/*
			if(SimpleModal.openModals<=1) {
				document.body.style.overflow = 'scroll';
				document.body.style.height = 'auto';
			}*/
				
			// If we need to refresh the caller
			/* *** Shouldn't call this, because 
			if(_this.options.reloadCallerOnClose) {
				var s = SimpleModal.openModals[SimpleModal.openModals.length-1];
				if(s) {
					s.loadFromURL();
				}
			}*/
		});
		this.dialog.show();
		
		var pos = this.dialog.getPosition();
		var y = pos[1];
		if(y<30) {
			y = 30;
			this.dialog.setPosition(pos[0], y);
		}
		
		this.dialog.setPosition(pos[0], 100);
		// load from url if required
		if(this.options.url) {
			this.loadFromURL();
		}
		this.dialog.setPosition(pos[0], 100);
		SimpleModal.openModals.push(this);
	},
	
	/*
	Function: loadFromURL
		Loads the url (options.url) into the modal. Replaces whatever HTML the modal was created with.
	*/
	loadFromURL	: function() {
		if(typeof this.options.url=='undefined') {
			return;
		}
		try {
			new Request.HTML({
				update	: $(this._wrapperId),
				url 	: this.options.url,
				onSuccess : function() { }
			}).send();
			
		} catch(e) {
			logError(e, ('Error loading from URL to ' + this._wrapperId));
		}
	},
	
	/*
	Function : resize 
		Resize the dialog based on the content of the div we setup at the beginning
	*/
	resize : function() {
		var d = $(this._wrapperId).getSize();
		if(d.y>100) {
			this.dialog.setHeight((d.y+40));
		} else {
		//	alert('NOT resizing...');
		}
	},
	
	/*
	Function: close
		Close the dialog. 
	*/
	close	: function() {
		if(this.options.reloadCallerOnClose) {
			// We expect that 'this' has already been removed from .openModals so get the top-most modal
			var s = SimpleModal.openModals[SimpleModal.openModals.length-1];
			if(s) {
				s.loadFromURL();
			}
		} else {
			//
		}
		
		this.isClosed = true;
		
		this.dialog.close();
	}
};





/*
Class: 	ToggleNodes
	Quick way to toggle between two divs. Useful for allowing standard / advanced views of something.

Usage:
>	var toggler = new ToggleNodes(nodeA, nodeB, {
>		onSwitchToA : function() {
>			// callback
>		},
>		onSwitchToB : function() {
>			// callback
>		},
>		togglerNode	: node
>	});
>	toggler.toggle();
>	toggler.toggle(); // ...etc...

Parameters:
	nodeA	- node to toggle
	nodeB	- node to toggle
	options	- options
	
Options:
	onSwitchToA	- callback. Called when A is selected
	onSwitchToB	- callback. Called when B is selected.
	toggler	- node. Click the node will toggle between A and B
	togglerSelectA	- HTML. Shown when clicking on the toggler node would select A
	togglerSelectB	- HTML. Shown when clicking on the toggler node would select B
	onStyle		- style used for shown node
	offStyle	- style used for hidden node
	start		- defaults to 'A'. Set to 'B' to start on B
*/
ToggleNodes = {}
ToggleNodes = Class.create();
ToggleNodes.prototype	= {	
	initialize : function(nodeA, nodeB, options) {
		this.options = Object.extend({
			onSwitchToA : function() {},
			onSwitchToB : function() {},
			toggler	: null,
			togglerSelectA : '',
			togglerSelectB : '',
			
			onStyle	: {
				display : 'block'
			},
			offStyle : {
				display : 'none'
			},
			start : 'A'
		}, options);
		
		this.nodeA = $(nodeA);
		this.nodeB = $(nodeB);
		
		if(this.options.start=='B') {
			this.toggleState	= false; // 
			this.nodeA.setStyle(this.options.offStyle);
		} else {
			this.toggleState	= true; // 
			this.nodeB.setStyle(this.options.offStyle);
		}
		
		if(this.options.toggler!=null) {
			var _this = this;
			var togglerNode = $(this.options.toggler);
			new Event.observe(togglerNode, 'click', function() {
				_this.toggle();
				// togglerNode.blur();
			});
		}
		
	},
	
	/*
	Function: 	toggle
		Toggle between A and B
	*/
	toggle 	: function() {
		
		var curToggleState = this.toggleState; // so we can switch now and won't die if exceptions are thrown
		
		this.toggleState = !this.toggleState;
		
		if(curToggleState==true) {
			this.nodeA.setStyle(this.options.offStyle);
			this.nodeB.setStyle(this.options.onStyle);
			this.options.onSwitchToB();
		} else {
			this.nodeA.setStyle(this.options.onStyle);
			this.nodeB.setStyle(this.options.offStyle);
			this.options.onSwitchToA();
		}
		
		if(this.options.toggler!=null) {
			if(curToggleState==true) {
				if(this.options.togglerSelectA)
					$(this.options.toggler).innerHTML = this.options.togglerSelectA;
			} else {
				if(this.options.togglerSelectB)
					$(this.options.toggler).innerHTML = this.options.togglerSelectB;
			}
		}
	},
	
	
	
	/*
	Function: updateTogglerNode
		Update the toggler node
	*/
	updateTogglerNode	: function() {
		
	}
};



/*
Class: 	Accordion
	...

	Accordion goes through all child nodes of the target, turning them into items in the accordion.
	

Usage:
> 	new Accordion($('involve_ev_right_col'),
>	{
>		itemClass : 'obtcaform-clickable',
>		itemHeadingClass : 'csc-header',
>		itemBodyClass : 'tx-evdatarender-pi1'
>		});

Parameters:
	targetnode	- node to toggle
	options		- ...
	
Options:
	itemClass			- class that each item in the accordion will be wrapped in
	itemHeadingClass 	- class that each heading will have
	itemBodyClass		- class that each item's body will have.
*/
/*Accordion = {}
Accordion = Class.create();
Accordion.prototype	= {*/
Accordion = new Class();


Accordion.getElementsByClassNames = function(classNames, parentElement) {
  /*if (typeof Prototype.BrowserFeatures.XPath!='undefined') {
	var q=".//*[0";
	for(var i = 0, length = classNames.length ; i<length ; i++)
    		q = q+" or contains(concat(' ', @class, ' '), ' " + classNames[i] + " ') ";
	q = q+" ]";
	  
    return document._getElementsByXPath(q, parentElement);
  } else {
	*/
    var children = ($(parentElement) || document.body).getElementsByTagName('*');
    var elements = [], child;
    for (var i = 0, length = children.length; i < length; i++) {
      child = children[i];
	  if (Element.hasClassFromList(child, classNames)){
	       	elements.push(Element.extend(child));
      }
    }
    return elements;
  //}
}

Accordion.itemCounter	= 0;

Accordion.prototype 	= {
	
	initialize : function(targetNode, options){
		this.options = ArrayOverride({
			//applyToTag			: 'DIV',
			itemClass			: 'a-item',
			itemHeadingClass 	: 'heading',
			itemBodyClass		: 'body'
		}, options);
		
		this.targetNode	 = $(targetNode);
		
		this.childItems	= [];
		
		//var cL = this.targetNode.getElementsByClassName(this.options.itemClass);
		//var cL = Accordion.getElementsByClassNames(this.targetNode, this.options.itemClass);
		var cL = $$('.'+this.options.itemClass);
		
		var isFirst = true; // For some reason, we get the nodes in reverse order.
		
		//for (var i = cL.length - 1; i >= 0; i--){
		for (var i=0; i < cL.length; i++) {
			
			var curNode = cL[i];
			
			Accordion.itemCounter++;
			if(curNode.id=='') {
				curNode.id = 'accordion-' + Accordion.itemCounter;
			}
			
			//alert('curNode.id = ' + curNode.id);
			// find heading
			//alert('curNode.id = ' + curNode.id);
			
			//var hL = curNode.getElementsByClassName(this.options.itemHeadingClass);
			//var hL = Accordion.getElementsByClassNames(curNode, this.options.itemHeadingClass);
			var hL = $$('#'+curNode.id+' .'+this.options.itemHeadingClass);
			
			// find body
			//var bL = curNode.getElementsByClassName(this.options.itemBodyClass);
			//var bL = Accordion.getElementsByClassNames(curNode, this.options.itemBodyClass);
			var bL = $$('#'+curNode.id+' .'+this.options.itemBodyClass);
			
			if(hL.length==0 || bL.length==0) {
				log('Error: cannot find body or heading for this item');
				//return; // can't work without headings
			} else {
				
				var heading = hL[0];
				var body = bL[0];
				
				heading._targetBody	= body;
				
				body.setStyles({
					'overflow' : 'hidden'
				});
				
				try {
					if(isFirst) {
						isFirst = false;
						try {
							body._originalHeight = body.getStyle('height');
							//body.setStyles({ display : 'block' });
	//					body.setStyle({ display : 'none' });
						//new Effect.BlindDown(body, { duration : 0.5 });//, transition: Effect.Transitions.linear });
							//body.morph({
							//	
							//});
				
						} catch(e) { alert('FX error ' + e.message);} 
						body._isShown = true;
					} else {
						body._originalHeight = body.getStyle('height');
	//					body.setStyles({
	//						display	: 'none'
	//					});
						body.morph({
							'height' : '0px'
						});
					
						body._isShown = false;
					}
				
					this.childItems.push({
						heading	: heading,
						body	: body
					});
				
				} catch(e) {
				//	alert(e.message);
				}
				
				var _this = this;
				
				heading.addEvent('click', 
					function(evt) {
						try {
							
///						_this.closeAll();
						
						var n = evt.target;//Event.element(evt);
						n = findAncestor(n, function(node) {
							if(typeof node._targetBody=='undefined') {
								return false;
							} else {
								return true;
							}
						});
						if(n._targetBody._isShown) { return; }

						 _this.closeAll();
						// TODO: Allow 'show' effect to be changable
						//Effect.BlindDown(n._targetBody, { duration : 0.3, transition: Effect.Transitions.linear });
						
						n._targetBody.morph({
							'height' : n._targetBody._originalHeight
						});
						
						// Add transition: open item item *** 
						
						/*n._targetBody.setStyle({
							display : 'block'
						});*/
						n._targetBody._isShown = true;
					} catch(e) {
						alert(e.message);
					}
				});
			}
			
		};
		
		return;
		
	},
	
	/*
	Function: closeAll
		Closes all 
	*/
	closeAll	: function() {
		try {
			for (var i = this.childItems.length - 1; i >= 0; i--){
				// TODO: Allow 'hide' effect to be changable
				/*this.childItems[i].body.setStyle({
					display : 'none'
				});*/
				if(this.childItems[i].body._isShown) {
					//Effect.SlideUp(this.childItems[i].body, {});
					//Effect.BlindUp(this.childItems[i].body, { duration : 0.5, transition: Effect.Transitions.linear });
					
					// Add transition: collapse item *** 
					
					this.childItems[i].body.setStyles({ 'height' : '0px' });
				}
				this.childItems[i].body._isShown=false;
			};
		} catch(e) {
			alert(e.message);
		}
	}
	
}


/*
Class: InputBehaviours
	Simple API for adding things to form inputs
*/
var InputBehaviours = {
	
	/*
	Function: EvBehaviours.init
		init all behaviours.
	*/
	init	: function() {
		
		//for (var i = InputBehaviours.b.length - 1; i >= 0; i--){
		
		for(cssPath in InputBehaviours.b) {
			var nodeList = $$(cssPath);
			for (var i=0; i < nodeList.length; i++) {
				//if(typeof n._BehaviourAttached=='undefined') {
				
				var n = nodeList[i];
				if(typeof n._BehaviourAttached=='undefined') {
				InputBehaviours.b[cssPath](n);
				n._BehaviourAttached = true;
				}
				//}
			};
		}
	},
	
	/*
	Property: InputBehaviours.b
		Collection of initialization functions for the behaviours
	*/
	b	: {
		'.form-html' : function(n) {
			Ext.QuickTips.init();
			new Ext.form.HtmlEditor({
				applyTo	: n
			});
		},

		'.form-removable' : function(n) {
		//	n.innerHTML = 'REMOVABLE';
			try {
			var rm = Builder.node('DIV');
			rm.innerHTML = '<img alt="Remove" src="/s/vendor/icons/bin.jpg" title="Remove" />';
			n.insertBefore(rm, n.firstChild);
			Event.observe(rm, 'click', function() {
				n.remove();
			});
			} catch(e) {
				alert(e.message);
			}
		},

		'.form-addable' : function(n) {

/*
1. lastValue = find form-last-value **( xpath?? )
2. remove the form-last-value field
3. add the [add] link
4. on click: add new row with the new id
*/
try {
			var m = Builder.node('DIV');
			m.setStyle({
				height : '25px'
			});
			m.innerHTML = '<div href="#" style="margin-left : 10px; margin-top : 5px; color : red;">Add</div>';

			// find the id
			var nL = n.getElementsByClassName('form-last-value');
			var inp = nL[0];
			var lastId = inp.value;

			// find the id
                        var nL = n.getElementsByClassName('form-last-guid');
			var inp = nL[0];
			var guidPrefix = inp.value;
			
			var cloneNode = n;
			var nL = cloneNode.getElementsByClassName('form-last-value');
			var rmNode = nL[0];
			cloneNode.removeChild(rmNode);
			
			var nL = cloneNode.getElementsByClassName('form-last-guid');
			var rmNode = nL[0];
			cloneNode.removeChild(rmNode);

			m._guidPrefix = guidPrefix;
			m._cloneThis = cloneNode;
			m._counterStart = m._counter = parseInt(lastId);
			
			n.parentNode.replaceChild(m, n);

Event.observe(m, 'click', function() {
try {
	var id1 = m._counterStart;
	m._counter++;
	var id2 = m._counter;
//alert(m._guidPrefix);	
	var newHTML = m._cloneThis.innerHTML;
	newHTML = newHTML.replaceAll('['+id1+']', '['+id2+']');

	newHTML = newHTML.replaceAll(m._guidPrefix + id1, m._guidPrefix + id2);

	var newNode = Builder.node('DIV');
	newNode.innerHTML = newHTML;

	m.parentNode.insertBefore(newNode, m);

	newNode.addClassName('form-removable');

	setTimeout('InputBehaviours.init();', 100);
	new Effect.Highlight(newNode);

//	lid++;
} catch(e) {
	alert('ERROR: ' + e.message);
}
});

} catch(e) {
//	alert(e.message):
}



		},
	         '.form-date'    : function(n) {
                         new Ext.form.DateField({
                                applyTo : n
                         });
                 }
	}
};

/*
Class: Rocker
	Simple rocker switch

Usage:
>	new Rocker($('rocker'), {
>		on 	: '>>>',
>		off	:'<<<',
>		callback : function() {
>			//...
>		}
>	});


*/		
Rocker = new Class();
Rocker.ON	= 1;
Rocker.OFF	= -1;
Rocker.count= 0;
Rocker.prototype	= {
		
	initialize : function(targetNode, options) {
		this.targetNode = $(targetNode);
		this.position = Rocker.OFF;
		
		this.options = ArrayOverride({
			value	: null,
			off 	: 'Off',
			on 		: 'On',
			offStyle: {
				/*backgroundColor : 'red'*/
				//marginLeft	: 0
			},
			onStyle : {
				/*backgroundColor : 'blue'*/
				//marginLeft : 27
			},
			callback:function() {
			//	alert('Toggle!');
			}
		}, options);
		
		this.togglers = {};
		this.togglers[Rocker.ON] =  options.on;
		this.togglers[Rocker.OFF] = options.off;
		
		this.rocker_id = 'rocker-id-' + Rocker.count;
		//var sw = Builder.node('DIV', {
		var sw = new Element('DIV', {
			style : 'width 10px;',
			id	  : this.rocker_id
		}, '!!'); 
		this.targetNode.appendChild(sw);
		
		var _this = this;
		
		//Event.observe($(this.targetNode), 'click', function() {
		$(this.targetNode).addEvent('click', function() {
			_this.toggle();
		});
		
		if(this.options.value==Rocker.ON) {
			this.position = this.options.value;
			$(this.rocker_id).innerHTML = this.togglers[this.position];
			//$(this.targetNode).setStyles(this.options.onStyle);
			
			/*
			var n = $('rocker-id-0');

			var effect = new Fx.Morph(n);

			effect.start({
			 'marginLeft' : [0,37]
			});*/
			
			// $(this.targetNode).tween(this.options.onStyle, {
			// 				duration : 0.5
			// 			});
			//
			
			var moveby = 27 * this.position;
			$(this.rocker_id).setStyles({
				marginLeft : moveby
			});
			
			/*Effect.MoveBy($(this.rocker_id),
				0,
				moveby,
				{ duration : 0.2 } );
			*/
		} else
		if(this.options.value==Rocker.OFF) {
			this.position = this.options.value;
			$(this.rocker_id).innerHTML = this.togglers[this.position];
			$(this.targetNode).setStyles(this.options.offStyle);
		}
	},
	
	setPosition : function(rockerPosition) {
		if(rockerPosition==this.position) return;
		
		if(rockerPosition==Rocker.ON) {
			this.position = rockerPosition;
			$(this.rocker_id).innerHTML = this.togglers[this.position];
			
			$(this.targetNode).setStyles(this.options.onStyle);
			
			/*Effect.MoveBy($(this.rocker_id),
				0,
				moveby,
				{ duration : 0.2 } );
			*/
		} else
		if(rockerPosition==Rocker.OFF) {
			this.position = rockerPosition;
			$(this.rocker_id).innerHTML = this.togglers[this.position];
			$(this.targetNode).setStyles(this.options.offStyle);
		}
		
		var moveby = 27 * this.position;
		try {
			var effect = new Fx.Morph($(this.rocker_id), {
				
			});
		
			if(this.position==1) {
				effect.start({
					'marginLeft' : [0,27]
				});
			} else {
				effect.start({
					'marginLeft' : [27,0]
				});
			}
		} catch(e) {alert(e.message);}
		
	},

	toggle : function() {
		try {
			this.position = -1 * this.position;
			var moveby = 27 * this.position;
		
			var bgColor;
			if(this.position==1) {
				$(this.targetNode).setStyles(this.options.onStyle);
			} else 
			if(this.position==-1) {
				$(this.targetNode).setStyles(this.options.offStyle);
			}
			
			try {
				var effect = new Fx.Morph($(this.rocker_id), {
					
				});
			
				if(this.position==1) {
					effect.start({
						'marginLeft' : [0,27]
					});
				} else {
					effect.start({
						'marginLeft' : [27,0]
					});
				}
			} catch(e) {alert(e.message);}
			
			this.options.callback(this.position);
			
			var rocker = $(this.rocker_id)
			var updateInner = this.togglers[this.position];
			setTimeout(function() {
				$(rocker).innerHTML = updateInner;
			}, 400);
			
		} catch(e) {
			alert(e.message);
		}
	}
}


evDD = new Class({
	
	initialize : function(targetNode, options) {
		this.targetNode = $(targetNode);
		
		/*this.options = Object.extend({
			menuWidth : 80,
			items : []
		}, options);
		*/
		this.options = ArrayOverride({
			menuWidth : 80,
			items : [],
			selected_item : null
		}, options);
		
		// set up observe on target node
		var _this = this;
		try {
			//Event.observe(_this.targetNode,'click', function(e) {
			$(_this.targetNode).addEvent('click', function(e) {
				_this.showMenu();
				Event.stop(e);
			});
			
			//Event.observe(document, 'click', function() {
			$(document).addEvent('click', function(e) {
				try {
					_this.hideMenu();
				} catch(e) {}
			});
			
		} catch(e) {
			alert(e.message);
		}
	},
	menuNode : null,
	
	showMenu : function() {
		//this.targetNode
		log('showing...');
		if(this.menuNode!==null) {
			$(this.menuNode).setStyles({
				display : 'block'
			});
			return;
		}
		
		this.menuNode = new Element("DIV", {'class' : 'inv-dd-menu-wrapper'}); //Builder.node("DIV", {'class' : 'inv-dd-menu-wrapper'});
		
		try {
			this.menuNode.setStyles({
				width : (this.options.menuWidth + 'px')
			});
		} catch(e) {
			this.menuNode.style.width = (this.options.menuWidth + 'px');
		}
		this.targetNode.appendChild(this.menuNode);
		
		// this.options.items
		//top.lookatme = this;
		
		var i = 0;
		var addHTML = '';
		for(i=0;i<this.options.items.length;i++) {
			//var item = Builder.node('DIV', {
			var item = new Element('DIV', {
				'class' : 'inv-add-menu-item-wrapper'
			});
			
			var selected_class = '';
			if(this.options.selected_item==this.options.items[i].text) {
				selected_class = ' inv-add-menu-item-content-selected ';
			}
			
			item.innerHTML = '<div class="inv-add-menu-item-content'+selected_class+'"><a href="#" class="inv-add-menu-link" onclick="return false;">'+this.options.items[i].text+'</a></div>';
			// DF. I put the callback inside the menu node because of a scope problem passing 'callback' to the event, below.
			item._callback = this.options.items[i].callback;
			///alert(item._callback);
			
			this.menuNode.appendChild(item);
			
			var _this = this;
			//Event.observe(item, 'click', function(e) {
			$(item).addEvent('click', function(e) {
				try {
					top.thise = e;
					
					var cb = findAncestor(e.target, function(elmnt) {
						return (typeof elmnt._callback!='undefined');
					});
					
					_this.hideMenu();
					//Event.stop(e);
					e.stopPropagation();
					
					cb._callback();
					//alert(cb._callback);
					
				} catch(e) { alert(e.message); }
				
			});
		}
	},
	
	hideMenu : function() {
	
		try {
			if(this.menuNode!==null) {
				$(this.menuNode).setStyles({
					display : 'none'
				});
			}
		} catch(e) { alert(e.message); }
	}
	
});


/*
Class: 	UndoDropdown
	
*/
UndoDropdown = {}
UndoDropdown = Class.create();

UndoDropdown.prototype = {
	
	initialize : function(target, options) {
		
		target = $(target);
		
		options = Object.extend({
			undos : [{
					title	: 'undo 1',
					description : 'Description 1',
					callback : function() {
						alert('Undo 1')
					}
				},{
					title	: 'undo 2',
					description : 'Description 2',
					callback : function() {
						alert('Undo 2')
					}
				},{
					title	: 'undo 1',
					description : 'Description 1',
					callback : function() {
						alert('Undo 1')
					}
				},{
					title	: 'undo 2',
					description : 'Description 2',
					callback : function() {
						alert('Undo 2')
					}
				}
			]
		}, options);
		
		this.dd = Builder.node('DIV', {
			'class'  : 'inv-dd-menu-wrapper'
		});
		target.appendChild(this.dd);
		
		this.headerNode = Builder.node('DIV', {
			style : 'background-color : #404040; color : white; display : none;'// [DF - this isn't finsihed. It should show the last change]
		}, 'testing...');
		this.dd.appendChild(this.headerNode);
		
		this.undoList = Builder.node('DIV', {
			'class'  : 'inv-dd-menu-wrapper',
			'style'	 : 'width : 200px; display:none;'
		});
		this.dd.appendChild(this.undoList);
		
		for (var i=0; i < options.undos.length; i++) {
			this.addItemToList(
				options.undos[i].title, 
				options.undos[i].description,
				options.undos[i].callback
			);
		};
		
		var _this = this;
		try {
			Event.observe(target,'click', function(e) {
				_this.showMenu();
				Event.stop(e);
			});
		
			Event.observe(document, 'click', function() {
				_this.hideMenu();
			});
			
		} catch(e) {
			alert(e.message);
		}
	},
	
	showMenu : function() {
		// unhide
	//	this.undoList.setStyle({'display : block'});
	this.undoList.setStyle({'display' : 'block'});
	},
	
	hideMenu : function() {
		//hide
		this.undoList.setStyle({'display' : 'none'});
	},
	
	listCounter : 0,
	
	addItemToList : function(title, description, callback) {
		this.listCounter++;
		var bgClass = (this.listCounter%2==0) ? 'UndoDropdown-even' : 'UndoDropdown-odd';
		
		var undoLink = '<div class="inv-undo-link"><img src="/s/vendor/icons/involve/undo-on.png" /></div>';
		
		var uitem = Builder.node('DIV', {
			'class' : bgClass + ' inv-add-menu-item-wrapper'
		});
		uitem.innerHTML = 
			'<div class="inv-add-menu-item-content">' + 
			'<div style="float : left; width : 130px;"><a style="font-weight : bold;">'+title+'</a><br />' + 
			description + 
			'</div>' + 
			undoLink + 
			'<div style="clear :both;">&nbsp;</div></div>';
		
		//this.undoList.appendChild(uitem);
		if(this.undoList.firstChild) {
			this.undoList.insertBefore(uitem, this.undoList.firstChild);
		} else {
			this.undoList.appendChild(uitem);
		}
		
		// setup callback
		var cb = callback;
		Event.observe(
			uitem,
			'click',
			callback
			);
		
		this.setHeaderItem(title);
	},
	
	setHeaderItem : function(title) {
		this.headerNode.innerHTML = title;
	}
	
};
/*

	TODO:
		Make items editable 	(these show as change-objects as well)
		
		Style it a bit.
		
		Allow 'drop into' as well as 'drop after'
		
		Bug: Can't drop on items created after a given item (init order)

		Need ability to add pages (just drag this!)
		
	Docs:
		When a drag-drop is performence, the *action* is recorded and sent back over JSON
		
		Menu can be serialized, but there's no real point - we're only interested in the changes
	
	
	section-title
		[drop before first page / at top of section]
		
		first page
		
		[drop before second page / after first]
		
		second page
		
		[drop after second page]

*/

var SiteMap = new Class({
	
	target	: null,
	
	initialize : function(target, data) {
		this.target = $(target);
		
		this.sections	= [];
		
		this.target.addClass('sitemap');
		
		for(sectionPageId in data) {
			try {
				var s = new SiteMap.Section(this, sectionPageId, data[sectionPageId]);
				this.sections.push(s);
			} catch(e) {
				alert(e.message);
			}
		}
	},
	
	_initialize : function(target) {
		this.target = $(target);
		
		var l = $$('.sm-label');
		
		for (var i=0; i < l.length; i++) {
			try {
				new SiteMap.Page(l[i], this.target);
			} catch(e) {
				alert('Page error: ' + e.message);
				return;
			}
		};
		
	}
	
});


SiteMap.Section	= new Class({
	
	/*
	data is expected to be:
		{
			title	: "page title",
			
			children: {
				"..."
			}
		}
		
		Params:
			sitemap	: sitemap object
			id		: the section's page id
			data	: JSON data for the page and it's children
		
	*/
	initialize	: function(sitemap, id, data) {
		this.sitemap	= sitemap;
		this.id			= id;
		this.data		= data;
		
		this.makeHTML();
	},
	
	makeHTML	: function() {
		this.sectionNode = new Element('DIV', {
			'class'	: 'sm-section'
		});
		
		this.pageNode = new Element('DIV', {
			'class' : 'sm-label sm-section'
		});
		this.pageNode.innerHTML	= this.data['title'];
		
		this.pageNode.inject(this.sectionNode);
		
		this.sectionNode.inject(this.sitemap.target);
		
		this.children = [];
		if(typeof this.data['children']!='undefined') {
			var prev = null;
			for(c in this.data['children']) {
				
				var p = new SiteMap.Page(this.sitemap, this, c, this.data['children'][c]);
				if(prev!=null) {
					p.setPreviousPage(prev);
				}
				this.children.push(p);
				
				var prev = p;
			}
		}
		
		this.initDragging();
	},
	
	initDragging : function() {
		var _this = this;
		
		// make the drop target
		var dropnode = new Element('div', {
			'class'	: 'sm-droppable'
		});
		dropnode._sitemapPage	= this;
		dropnode.innerHTML = '&nbsp;';
		dropnode.injectAfter(this.pageNode);
		
		// Setup drag behaviour
		new Drag.Move(this.pageNode, {
			//handle	: $(handle),
			droppables: $$('.sm-droppable'),
			
			onStart : function() {
				$(_this.pageNode).addClass('sm-drag');
			},
			
			onComplete : function() {
				try {
					$(_this.pageNode).removeClass('sm-drag');
					// revert
					$(_this.pageNode).morph({ left : 0, top	: 0 });
				} catch(e) {
					logError(e, 'Problem dropping item');
				}
			},
			
			onDrop: function(element, droppable){
				$(_this.pageNode).removeClass('sm-drag');
				if (droppable)  {
					$(droppable).removeClass('sm-drop-active');
				}
			},
			
			onEnter: function(element, droppable){ $(droppable).addClass('sm-drop-active'); },
			onLeave: function(element, droppable){ $(droppable).removeClass('sm-drop-active'); }
		});
	}
});

SiteMap.Page	= new Class({
	
	initialize : function(sitemap, section, id, data) {
		this.sitemap	= sitemap;
		this.section	= section;
		this.id			= id;
		this.data		= data;
		
		this.prevPage	= null;
		
		this.makeHTML();
	},
	
	makeHTML : function() {
		//this.section.sectionNode.innerHTML = '!!';
		
		this.pageWrapper	= new Element('DIV', { });
		
		this.pageNode	 = new Element('DIV', {
			'class'	: 'sm-label'
		});
		
		this.pageNode.innerHTML = this.data.title;
		
		this.pageNode.inject(this.pageWrapper);
		
		this.pageWrapper.inject(this.section.sectionNode);
		
		this.initDragging();
	},
	
	setPreviousPage	: function(page) {
		this.prevPage	= page;
	},
	
	initDragging : function() {
		var _this = this;
		
		// make the drop target
		var dropnode = new Element('div', {
			'class'	: 'sm-droppable'
		});
		dropnode._sitemapPage	= this;
		dropnode.innerHTML = '&nbsp;';
		dropnode.injectAfter(this.pageNode);
		
		// Setup drag behaviour
		new Drag.Move(this.pageNode, {
			//handle	: $(handle),
			//container	: $(sitemapContainer),
			
			droppables: $$('.sm-droppable'),
			
			onStart : function() {
				//_this.dragStart();
				$(_this.pageNode).addClass('sm-drag');
			},
			
			onComplete : function(droppable) {
				try {
					
					//_this.dragEnd();
					$(_this.pageNode).removeClass('sm-drag');
					
					if(typeof droppable=='undefined') {
						// revert
						$(_this.pageNode).morph({
							left	: 0,
							top		: 0
						});
					} else {
						$(_this.pageNode).setStyles({
							left	: 0,
							top		: 0
						});
					}
				} catch(e) {
					logError(e, 'Problem dropping item');
				}
			},
			
			onDrop: function(element, droppable){
				$(_this.pageNode).removeClass('sm-drag');
				
				if (!droppable)  {
					//log('drop - give up ' + droppable)
					
				} else {
				//	log('drop on ' + droppable)
				//	$(droppable)._editableContent.pasteContentTo();
					$(droppable).removeClass('sm-drop-active');
					_this.moveToAfter($(droppable)._sitemapPage);
				}
			},
			
			onEnter: function(element, droppable){
				$(droppable).addClass('sm-drop-active');
			},

			onLeave: function(element, droppable){
				$(droppable).removeClass('sm-drop-active');
			}
		});
	},
	
	
	/*
		Records the movement of a page in the sitemap
	*/
	moveToAfter : function(pageObj) {
		new SiteMap.Movement( this.sitemap, this, pageObj, this.prevPage );
		
		var pw = this.pageWrapper;
	//	this.pageWrapper.destroy();
		
		this.pageWrapper.injectAfter(pageObj.pageWrapper);
	}
	
});

/**
	Records a change in the sitemap.
	This is used to send the updates over AJAX - so they can be stacked up and sent as one thing.
**/
SiteMap.Movement	= new Class({
	initialize : function(sitemap, pageMoved, pageMoveTo, pageMoveFrom) {
		this.sitemap	= sitemap;
		this.pageMoved	= pageMoved;
		this.pageMoveTo	= pageMoveTo;
		this.pageMoveFrom = pageMoveFrom;
		
		SiteMap.Movement.stack.push(this);
	}
});
SiteMap.Movement.stack	= [];

// This is experimental and most just not working
SiteMap.Movement.undo = function() {
	var m = SiteMap.Movement.stack.pop();
	try {

	m.pageMoved.pageWrapper.injectAfter(
	    m.pageMoveFrom.pageWrapper
	);
	} catch(e) {
	alert(e.message);
	}
}

// For saving over AJAX.
SiteMap.Movement.serialize	= function() {
	var ret	= [];
	for (var i=0; i < SiteMap.Movement.stack.length; i++) {
		ret.push({
			page	: SiteMap.Movement.stack[i].pageMoved.id,
			after	: SiteMap.Movement.stack[i].pageMoveTo.id
		});
	};
	return ret;
}