// gc_exception class
function gc_exception (name, message, moreInfo) {
	this.name = name;
	this.message = message;
	this.moreInfo = moreInfo;
}

// make the exception convert to a readable string when used as
// a string (e.g. by the error console)
gc_exception.prototype.toString = function () {
	var temp = new Array ();

	if (this.name != undefined)
		temp[temp.length] = 'Exception: ' + this.name;
	else
		temp[temp.length] = 'Exception: Unknown';

	if (this.message != undefined)
		temp[temp.length] = 'Message: ' + this.message;

	if (this.moreInfo != undefined)
		temp[temp.length] = 'Details: ' + this.moreInfo;

	return temp.join ("\n"); 
}

// remove an element from an array given the value of the element you'd like to remove
Array.prototype.remove = function (value) {
	for (count = 0; count < this.length; count++) {
		if (this[count] == value)
			this.splice(count, 1);
	}
}

var MyUtils = {
	findFirstAncestor: function (element, elementType) {
		var ancestors = element.ancestors ();
		for (var count = 0; count < ancestors.length; count++) {
			if (elementType.toLowerCase () == '')
				return ancestors[count];
			if (ancestors[count].nodeName.toLowerCase () == elementType.toLowerCase ())
				return ancestors[count];
		}
		return false;
	},

	findFirstDescendant: function (element, elementType) {
		var descendants = element.descendants ();
// use this instead??
//		var descendants = $$("#" + this.domId + ' .formMessage');
		for (var count = 0; count < descendants.length; count++) {
			if (elementType.toLowerCase () == '')
				return descendants[count];
			if (descendants[count].nodeName.toLowerCase () == elementType.toLowerCase ())
				return descendants[count];
		}
		return false;
	},

	add_hidden_field_inputs: function (element, extraParameters) {
		var extraParameters = clone (extraParameters);

		// quicker way
		if (element.id != '')
			var tempElements = $$("#" + this.domId + ' .input');
		// longer way but doesn't need to identify the element by id
		else
			var tempElements = element.descendants ();
		for (var count = 0; count < tempElements.length; count++) {
			if (tempElements[count].nodeName.toLowerCase () == 'input') {
				var name = tempElements[count].readAttribute ("name");
				if (extraParameters[name] != undefined) {
					if (extraParameters[name] !== null)
						tempElements[count].value = extraParameters[name];
					else
						tempElements[count].remove ();
				}
				delete (extraParameters[name]);
			}
		}
		for (fieldName in extraParameters) {
			if (extraParameters[fieldName] !== null)
				element.insert ({top: new Element ("input", {name: fieldName, value: extraParameters[fieldName], type: "hidden" })});
		}

		return element;
	}
}

Element.addMethods (MyUtils);















var class_environmentManager = Class.create ({

	popoversByDomId: new Object (),
	popoversByUniqueId: new Object (),
	popoversByGivenName: new Object (),
	popoverOrder: new Array (),

	contentBlocksByDomId: new Object (),
	contentBlocksByUniqueId: new Object (),

	usePageDimmerFade: true,	// fade the page dimmer in and out?
	usePopoverEffects: true,	// use popover show/hide effects?

	// constructor
	initialize: function () {
		this.popoverOrder = new Array (0);
		return true;
	},



	// fade the page dimmer in and out?
	use_page_dimmer_fader: function (usePageDimmerFade) {
		this.usePageDimmerFade = usePageDimmerFade;
		return true;
	},

	// use popover show/hide effects?
	use_popover_effects: function (usePopoverEffects) {
		this.usePopoverEffects = usePopoverEffects;
		return true;
	},



	// create a new popover
	create_popover: function (givenName, popoverShellCssClass, popoverShellClassName) {

//		if (this.popoversByDomId[givenName] != undefined)
//			return this.popoversByDomId[givenName];

		// generate a unique identifier for this new popover if it wasn't specified
		var domId;
		var uniqueId;
		do {
			uniqueId = Math.round ((Math.random () * 10000000000) + 1);	// unique identifier for this popover
			domId = 'popover_' + uniqueId;
		}
		while ((this.popoversByDomId[domId] != undefined) || ($(domId) !== null));

		// create the new popover
		var popover = new class_popover (domId, uniqueId, givenName, popoverShellCssClass, popoverShellClassName);

		// record this popover for reference
		this.popoversByDomId[domId] = popover;
		this.popoversByUniqueId[uniqueId] = popover;
		if (givenName != undefined)
			this.popoversByGivenName[givenName] = popover;

		this.popoverOrder.push (domId);

		return popover;
	},

	// get an existing popover
	get_popover: function (givenName) {
		if (this.popoversByGivenName[givenName] != undefined)
			return this.popoversByGivenName[givenName];

		if (this.popoversByUniqueId[givenName] != undefined)
			return this.popoversByUniqueId[givenName];

		if (this.popoversByDomId[givenName] != undefined)
			return this.popoversByDomId[givenName];
		return null;
	},

	focus_popover: function (givenName) {
		var popover = this.get_popover (givenName);

		// see if the one we need is already on the top (which is quite likely)
		var topPopover = this.popoverOrder.pop ();
		this.popoverOrder.push (topPopover);

		// if not
		if ((topPopover != popover.domId) || ($(popover.domId).getStyle ('zIndex') < 5000)) {

			// then take out the one we need from wherever it is
			this.popoverOrder.remove (popover.domId);
			// and put it on the top
			this.popoverOrder.push (popover.domId);

			// order the zIndex of the whole set of popovers accordingly
			for (count = 0; count < this.popoverOrder.length; count++)
				$(this.get_popover (this.popoverOrder[count]).domId).setStyle ({zIndex: 5000 + count});
		}
		return true;
	},








	// create a new content block
	create_content_block: function (domId, uniqueId) {

//		if (this.contentBlocksByDomId[givenName] != undefined)
//			return this.contentBlocksByDomId[givenName];

		// generate a unique identifier for this new content block
		if (uniqueId == '') {
			do {
				uniqueId = Math.round ((Math.random () * 10000000000) + 1);	// unique identifier for this content block
			}
			while (this.contentBlocksByDomId[domId] != undefined);
		}

		// create the new content block
		var contentBlock = new class_contentBlock (domId, uniqueId);

		// record this content block for reference
		this.contentBlocksByDomId[domId] = contentBlock;
		if (uniqueId != undefined)
			this.contentBlocksByUniqueId[uniqueId] = contentBlock;

		return contentBlock;
	},

	// get an existing content block
	get_content_block: function (uniqueId) {
		if (this.contentBlocksByUniqueId[uniqueId] != undefined)
			return this.contentBlocksByUniqueId[uniqueId];

		if (this.contentBlocksByDomId[uniqueId] != undefined)
			return this.contentBlocksByDomId[uniqueId];
		return null;
	},









	//
	disable_page: function () {

	
		if ($("pageDimmer") == undefined) {
/**
			var element_div = document.createElement ("div");
			element_div.setAttribute ("id", "pageDimmer");
			element_div.style.position = "absolute";
			element_div.style.left = "0px";
			element_div.style.top = "0px";
			element_div.style.zIndex = 50;
			element_div.style.display = "none";
			element_div.style.backgroundColor = "black";
			element_div.style.opacity = "0";
			element_div.style.filter = "alpha(opacity=0)";
			$("coreBase").appendChild (element_div);
/**/
			$("coreBase").appendChild (new Element ("div", { id: "pageDimmer", style: "position: absolute; left: 0px; top: 0px; z-index: 4999; display: none; background-color: black; opacity: 0; filter: alpha(opacity=0);"}));
		}
		var dimensions = document.viewport.getDimensions ();
		var offsets = document.viewport.getScrollOffsets ();
		$("pageDimmer").setStyle ( {width: (dimensions.width + offsets.left) + "px", height: (dimensions.height + offsets.top) + "px"});

		// fade the dimmer?
		if (this.usePageDimmerFade)
			new Effect.Appear ($("pageDimmer"), { duration: 0.75, to: 0.4 });
		// or simply show it
		else {
			$("pageDimmer").setStyle ({opacity: 0.4, filter: 'alpha(opacity=40)'});
			$("pageDimmer").show ();
		}
	},

	//
	enable_page: function () {
		if ($("pageDimmer") != null) {
			// fade the dimmer?
			if (this.usePageDimmerFade)
				new Effect.Fade ($("pageDimmer"), { duration: 0.25, to: 0 });
			// or simply hide it
			else {
				$("pageDimmer").setStyle ({opacity: 0, filter: 'alpha(opacity=0)'});
				$("pageDimmer").hide ();
			}
		}
	}
});

environmentManager = new class_environmentManager ();










var class_popover = Class.create ({

	// reference details
	domId: null,
	uniqueId: null,
	givenName: null,

	// effect details
	showEffectType: "Appear",
	showEffectOptions: { duration: 0.3, to: 1 },
	hideEffectType: "DropOut",
	hideEffectOptions: { duration: 0.5 },

	// position info
	x: 0,
	y: 0,
	attachedToElement: null,
	attachedToElementPositionInfo: null,
	screenPositionInfo: null,
	positionNeatlyOnScreen: true,	// should this popover be re-positioned if it appears off the screen?
	disablePage: true,	// should the page be disabled when this popover is being shown?

	// status flags
	closing: false,
	shownBefore: false,
	formHasChanged: false,
	resetContentUponShow: true,
	confirmClose: false,

	// state of the popover details
	observedElements: new Object (),
	draggableElements: new Object (),
	closeElements: new Object (),

	// sub-objects
	contentBlock: null,
	popoverShell: null,





	// constructor
	initialize: function (domId, uniqueId, givenName, popoverShellCssClass, popoverShellClassName) {

		// re-define these Arrays and Objects at the beginning here because they seem to bleed between objects of this class otherwise
		this.observedElements = new Object ();
		this.draggableElements = new Object ();
		this.closeElements = new Object ();

		this.domId = domId;
		this.uniqueId = uniqueId;
		this.givenName = givenName;

		if (popoverShellClassName == undefined)
			popoverShellClassName = 'class_popoverShell';
		if (popoverShellCssClass != undefined)
			eval ("this.popoverShell = new " + popoverShellClassName + " (\"" + popoverShellCssClass + "\");");
		else
			eval ("this.popoverShell = new " + popoverShellClassName + " ();");

		this.contentBlock = environmentManager.create_content_block (this.convert_dom_id (this.popoverShell.get_content_dom_id ()), "");
		this.contentBlock.set_popover (this);

		// create the hidden div that this popover object will use
		// create <html><body><div id="coreBase"><div id="this.domId">
		$("coreBase").appendChild (new Element ("div", {id: this.domId}).addClassName ("popoverElement").setStyle ({display: "none"}));	// create the div and attach it to the dom
		return true;
	},

	// fetch this popover's content block object
	get_content_block_object: function () {
		return this.contentBlock;
	},

	// fetch this popover's shell object
	get_shell_object: function () {
		return this.popoverShell;
	},






	// add this popover's unique identifier to the given id
	convert_dom_id: function (domId) {
		if (this.uniqueId)
			return domId + "_" + this.uniqueId;
		return domId;
	},
	// remove this popover's unique identifier from the given id
	unconvert_dom_id: function (domId) {
		if (domId.indexOf ("_" + this.uniqueId, domId.length - ("_" + this.uniqueId).length) >= 0)
			return domId.substr (0, domId.length - ("_" + this.uniqueId).length);
		return domId;
	},
	// add this popover's unique identifier to the id attributes in the given html
	convert_html_dom_ids: function (html) {
		var newDiv = new Element ("div").update (html);	// create the div and set it's innerHTML to the html given
		var tempElements = newDiv.descendants ();
		for (var count = 0; count < tempElements.length; count++) {
			var value = tempElements[count].readAttribute ("id");
			if ((value != "") && (value != null))
				tempElements[count].writeAttribute ({ id: this.convert_dom_id (value) });
			var value = tempElements[count].readAttribute ("for");
			if ((value != "") && (value != null))
				tempElements[count].writeAttribute ({ "for": this.convert_dom_id (value) });
			// account for fckeditor iframe
			if (tempElements[count].nodeName.toLowerCase () == "iframe") {
				var value = tempElements[count].readAttribute ("src");
				eval ("var temp = function (tempValue) { return tempValue + \"" + this.convert_dom_id ("") + "\" };");
				value = value.replace (/InstanceName=([^&])+/, temp);
				tempElements[count].writeAttribute ("src", value);
			}
		}
//		alert (newDiv.innerHTML);
		return newDiv.innerHTML;
	},





	// define the position that the popover should take when it show() is called
	set_position: function (x, y) {	this.x = x;	this.y = y;	return this;	},
	set_x: function (x) {	this.x = x;	return this;	},
	set_y: function (y) {	this.y = y;	return this;	},
	attach_to_element: function (attachedToElement, attachedToElementPositionInfo) {	this.attachedToElement = attachedToElement;	this.attachedToElementPositionInfo = attachedToElementPositionInfo;	return this;	},
	position_on_screen: function (screenPositionInfo) {	this.screenPositionInfo = screenPositionInfo;	return this;	},
	// should this popover be re-positioned if it appears off the screen?
	set_position_neatly_on_screen: function (positionNeatlyOnScreen) {	this.positionNeatlyOnScreen = positionNeatlyOnScreen;	return this;	},
	set_disable_page: function (disablePage) {	this.disablePage = disablePage;	return this;	},



	// don't or do reset this popover object's current content
	reset_content_upon_show: function () {		this.resetContentUponShow = true;	return this;	},
	dont_reset_content_upon_show: function () { this.resetContentUponShow = false;	return this;	},

	// specify details about the html contained inside this popover
	define_draggable_element: function (elementId) {		this.draggableElements[elementId] = elementId;		this.observedElements[elementId] = elementId;	return this;	},
	define_close_element: function (elementId) {			this.closeElements[elementId] = elementId;			this.observedElements[elementId] = elementId;	return this;	},
	// should the user be asked to confirm when closing this popover?
	set_confirm_close_on: function () {			this.confirmClose = true;		return this;	},
	set_confirm_close_off: function () {		this.confirmClose = false;		return this;	},
	set_confirm_close_on_change: function () {	this.confirmClose = 'onChange';	return this;	},








	// show the popover
	show: function () {

		// disable the rest of the page
		if (this.disablePage)
			environmentManager.disable_page ();

		popoverDomElement = $(this.domId);	// the popover dom element

		this.closing = false;
		this.contentBlock.set_ok_to_observe_forms (false);







		// stop observations for all elements
		for (elementId in this.observedElements) {
			if ($(this.convert_dom_id (elementId)) != null)
				$(this.convert_dom_id (elementId)).stopObserving ();
		}

		// set the popover's content
		if ((this.resetContentUponShow) || (!this.shownBefore)) {

			popoverDomElement.update (this.convert_html_dom_ids (this.popoverShell.generate_popover_shell_html ()));
			this.popoverShell.define_popover_shell_functional_elements (this);

			this.contentBlock.render_content_html ();
		}

		// observe the elements this popover needs to
		this.observe_elements ();

		// bring this popover to the front
		environmentManager.focus_popover (this.domId);











		// position the popover in relation to another element on the page
		if ($(this.attachedToElement) !== null) {
			var temp = geometry.determine_relative_element_position ($(this.attachedToElement), popoverDomElement, this.attachedToElementPositionInfo, this.positionNeatlyOnScreen);
			popoverDomElement.setStyle ({ left: temp.x + "px", top: temp.y + "px" });
//			Element.clonePosition (popoverDomElement, $(this.attachedToElement), {setLeft: true, setTop: true, setWidth: false, setHeight: false});
		}
		// or see if it should be positioned on the screen in a particular place
		else if (this.screenPositionInfo !== null) {
			// new element's details
			var newElementDimensions = geometry.get_element_dimensions (popoverDomElement);
			var viewportDimensions = document.viewport.getDimensions ();
			var viewportScrollOffsets = document.viewport.getScrollOffsets ();

			var temp = geometry.determine_relative_pixel_position (viewportScrollOffsets.left, viewportScrollOffsets.top, viewportDimensions.width, viewportDimensions.height, newElementDimensions.width, newElementDimensions.height, this.screenPositionInfo, this.positionNeatlyOnScreen);
			popoverDomElement.setStyle ({ left: temp.x + "px", top: temp.y + "px" });
		}
		// or use a hard-coded x, y postion
		else
			popoverDomElement.setStyle ({ left: this.x + "px", top: this.y + "px" });







		timer.cancel_delayed_action ("closePopover_" + popoverDomElement.id);						// make sure the element won't be hidden in the time being

		// use effects to show the popover?
		if (environmentManager.usePopoverEffects) {
			var queue = Effect.Queues.get (popoverDomElement.id).each (function (e) { e.cancel() });	// cancel any existing core-effects

			// determine the effects to use when showing this popover
			var effectOptions = clone (this.showEffectOptions);
			if (effectOptions.queue == undefined)
				effectOptions.queue = {	position: "end", scope: popoverDomElement.id }

			new Effect[this.showEffectType] (popoverDomElement, effectOptions);	// start the show effect

			// enable observation of this popover again soon
			timer.start_delayed_action ("environmentManager.get_content_block (\"" + this.contentBlock.domId + "\").set_ok_to_observe_forms (true);", 500, "observeFormsWait_" + this.domId);
		}
		// or simply show the popover
		else {
			popoverDomElement.show ();

			// observe this popover straight away
			environmentManager.get_content_block (this.contentBlock.domId).set_ok_to_observe_forms (true);
		}





		// get the content object to request it's content via ajax if need be
		if ((this.resetContentUponShow) || (!this.shownBefore))
			this.contentBlock.send_ajax_request_if_required (this);











		
		this.shownBefore = true;
		return true;
	},

	// hide the popover
	hide: function () {

		this.closing = true;
//		this.okToObserveForms = false;

		if ((this.confirmClose) && (this.contentBlock.has_a_form_changed ())) {
//			alert ('confirm to close goes here');
//			return false;
		}




		popoverDomElement = $(this.domId)

		// use effects to show the popover?
		if (environmentManager.usePopoverEffects) {
			var effectOptions = clone (this.hideEffectOptions);
			if (effectOptions.queue == undefined)
				effectOptions.queue = {	position: "end", scope: popoverDomElement.id }

			timer.start_delayed_action ("new Effect." + this.hideEffectType + " (popoverDomElement, " + $H(effectOptions).toJSON() + ")",
				25,	// 25 ms
				"closePopover_" + popoverDomElement.id);
		}
		// or simply hide the popover
		else
			popoverDomElement.hide ();





		// enable the rest of the page
		if (this.disablePage)
			environmentManager.enable_page ();

		return true;
	},

	hide_delayed: function (delay) {
		timer.start_delayed_action ("environmentManager.get_popover (\"" + this.domId + "\").hide ();",
				delay,	// delay in ms
				"closePopover_" + popoverDomElement.id);
	},

	position_neatly_on_screen_if_required: function () {
		if (this.positionNeatlyOnScreen)
			geometry.position_element_neatly_on_screen (this.domId);
		return true;
	},

	observe_elements: function () {

		// stop observations for all elements
		for (var elementId in this.observedElements) {
			// stop observing an element that this popover defined
			if ($(this.convert_dom_id (elementId)) != null)
				$(this.convert_dom_id (elementId)).stopObserving ();
			// or if that fails stop observing an element that this popover's content block defined
			else if ($(this.contentBlock.convert_dom_id (elementId)) != null)
				$(this.contentBlock.convert_dom_id (elementId)).stopObserving ();
		}


		// focus
		// watch for when this object is clicked (and bring it to the front + make it full opacity)
		// (because clicking and dragging prematurely can cause a popover to be stuck with only partial opacity)
//		eval ("var temp = function () { environmentManager.get_popover (\"" + this.domId + "\").focus (); }");
//		$(this.domId).observe ("mouseover", temp);



		// close elements of this popover
		var dragPrevention = new Array();
		for (var elementId in this.closeElements) {

			// try converting the element id first,  this will pick up elements that this popover defined
			var tempElement = null;
			if ($(this.convert_dom_id (elementId)) != null)
				tempElement = $(this.convert_dom_id (elementId));
			// or try getting the content block to convert the id,  this will pick up elements defined by the content block (eg. a close button)
			else if ($(this.contentBlock.convert_dom_id (elementId)) != null)
				tempElement = $(this.contentBlock.convert_dom_id (elementId));

			if (tempElement) {
				eval ("var temp = function () { environmentManager.get_popover (\"" + this.domId + "\").hide (); }");
				tempElement.observe ("click", temp);
				dragPrevention[dragPrevention.length] = '#' + tempElement.id;
			}
		}

		// draggable elements of this popover
		for (var elementId in this.draggableElements) {
			// try converting the element id first,  this will pick up elements that this popover defined
			var tempElement = null;
			if ($(this.convert_dom_id (elementId)) != null)
				tempElement = $(this.convert_dom_id (elementId));
			// or try getting the content block to convert the id,  this will pick up elements defined by the content block (eg. a close button)
			else if ($(this.contentBlock.convert_dom_id (elementId)) != null)
				tempElement = $(this.contentBlock.convert_dom_id (elementId));

			if (tempElement) {
//				new Draggable (this.domId, { handle: tempElement.id, zindex: 9000 });
				$(tempElement).addClassName ("draggable");
//				alert ('jQuery("#" + this.domId).draggable ({ handle: "#" + tempElement.id, cancel: "' + dragPrevention.join (',') + '", opacity: 0.90 });');
				jQuery("#" + this.domId).draggable ({ handle: "#" + tempElement.id, cancel: dragPrevention.join (','), opacity: 0.90, zIndex: 9000 });
			}
		}
/**
		// observe extra elements that the caller wants observed,
		// so they can perform custom actions
		for (var count = 0; count < this.watchForAction.length; count++) {
			if ($(this.convert_dom_id (this.watchForAction[count].elementId)) != null)
				eval ("$(\"" + this.convert_dom_id (this.watchForAction[count].elementId) + "\").observe (\"" + this.watchForAction[count].action + "\", function () { " + this.watchForAction[count].functionToCall + " (); } );");
		}
/**/

		return this;
	}

});












var class_popoverShell = Class.create ({

	width: undefined,
	useTitleBar: undefined,
	useTitle: undefined,
	title: undefined,
	addCloseX: undefined,
	popoverCssClass: undefined,



	// constructor
	initialize: function (popoverCssClass) {
		if (popoverCssClass == undefined)
			popoverCssClass = 'popoverType1';
		this.popoverCssClass = popoverCssClass;
		return true;
	},

	set_width: function (width) {
		this.width = width;
		return this;
	},

	set_use_title_bar: function (useTitleBar) {
		this.useTitleBar = useTitleBar;
		return this;
	},

	set_use_title: function (useTitle) {
		this.useTitle = useTitle;
		return this;
	},

	set_title: function (title) {
		this.title = title;
		return this;
	},

	set_add_close_x: function (addCloseX) {
		this.addCloseX = addCloseX;
		return this;
	},

	get_content_dom_id: function () {
		return 'popover_contentBlock';
	},

	generate_popover_shell_html: function () {

		var width = clone (this.width);
		var useTitleBar = clone (this.useTitleBar);
		var useTitle = clone (this.useTitle);
		var title = clone (this.title);
		var addCloseX = clone (this.addCloseX);

		if (width === undefined)
			width = 'auto';
		if (useTitleBar === undefined)
			useTitleBar = true;
		if (useTitle === undefined)
			useTitle = true;
		if (title === undefined)
			title = '';
		if (addCloseX === undefined)
			addCloseX = true;

		var html = '';
		html = html + '<div class="' + this.popoverCssClass + '" style="width: ' + width + ';"><div class="padding">';

		if (useTitleBar)
			html = html + '<div id="popover_titleBlock" class="titleBlock">';
		if (useTitle)
			html = html + '<h2 id="popover_title">' + title + '</h2>';
		if (addCloseX)
			html = html + '<div id="popover_close" title="Close" class="close"></div>';
		if (useTitleBar)
			html = html + '</div>';

		html = html + '<div id="' + this.get_content_dom_id () + '" class="contentBlock"></div>';
		html = html + '</div></div>';

		return html;
	},

	define_popover_shell_functional_elements: function (popoverObject) {

		var addCloseX = clone (this.addCloseX);
		var useTitleBar = clone (this.useTitleBar);

		if (addCloseX === undefined)
			addCloseX = true;
		if (useTitleBar === undefined)
			useTitleBar = true;

		if (useTitleBar)
			popoverObject.define_draggable_element ("popover_titleBlock");
		if (addCloseX)
			popoverObject.define_close_element ("popover_close");
		return true;
	}

});












var class_contentBlock = Class.create ({

	// reference details
	domId: null,
	uniqueId: null,
	ifhKey: null,

	// 
	popover: null,

	// details about where to get the content from initially
	htmlContent: null,
	sourceDomId: null,
	sourceUrl: null,
	requestParameters: null,
	requestMethod: null,

	// state of the content details
	observedElements: new Object (),
	ajaxForms: new Array (),
	back1PageElements: new Object (),
	backToStartElements: new Object (),
	defaultFocusElement: null,
	okToObserveForms: false,
	formHasChanged: false,
	messagesShown: new Object (),


	// constructor
	initialize: function (domId, uniqueId) {
		// re-define these Arrays and Objects at the beginning here because they seem to bleed between objects of this class otherwise
		this.observedElements = new Object;
		this.ajaxForms = new Array;
		this.back1PageElements = new Object;
		this.backToStartElements = new Object;
		this.messagesShown = new Object ();

		this.domId = domId;
		this.uniqueId = uniqueId;

		return true;
	},


	// add this content block's unique identifier to the given id
	convert_dom_id: function (domId) {
		if (this.uniqueId)
			return domId + "_" + this.uniqueId;
		return domId;
	},
	// remove this popover's unique identifier from the given id
	unconvert_dom_id: function (domId) {
		if (domId.indexOf ("_" + this.uniqueId, domId.length - ("_" + this.uniqueId).length) >= 0)
			return domId.substr (0, domId.length - ("_" + this.uniqueId).length);
		return domId;
	},
	// add this popover's unique identifier to the id attributes in the given html
	convert_html_dom_ids: function (html) {
		var newDiv = new Element ("div").update (html);	// create the div and set it's innerHTML to the html given
		var tempElements = newDiv.descendants ();
		for (var count = 0; count < tempElements.length; count++) {
			var value = tempElements[count].readAttribute ("id");
			if ((value != "") && (value != null))
				tempElements[count].writeAttribute ({ id: this.convert_dom_id (value) });
			var value = tempElements[count].readAttribute ("for");
			if ((value != "") && (value != null))
				tempElements[count].writeAttribute ({ "for": this.convert_dom_id (value) });
			// account for fckeditor iframe
			if (tempElements[count].nodeName.toLowerCase () == "iframe") {
				var value = tempElements[count].readAttribute ("src");
				eval ("var temp = function (tempValue) { return tempValue + \"" + this.convert_dom_id ("") + "\" };");
				value = value.replace (/InstanceName=([^&])+/, temp);
				tempElements[count].writeAttribute ("src", value);
			}
		}
//		alert (newDiv.innerHTML);
		return newDiv.innerHTML;
	},









	// record this content block's popover (not required)
	// this is used when sending ajax requests so that the server side script can identify the popover (and close it for instance)
	set_popover: function (popover) {
		this.popover = popover;
	},







	// define things about the content
	define_ajax_form: function (formElementId, url, parameters, method) {

		// make sure this form isn't observed again a second time
		for (count = 0; count < this.ajaxForms.length; count++) {
			if (this.ajaxForms[count].formElementId == formElementId)
				this.ajaxForms.splice (count, 1);
		}

		if (typeof (parameters) != "object")
			parameters = new Object ();

		if (method != undefined)
			method = method.toLowerCase ();
		if (method != 'get')
			method = 'post';

		this.ajaxForms.push ({formElementId: formElementId, url: url, parameters: parameters, 'method': method });
		this.observedElements[formElementId] = formElementId;
		return this;
	},
	define_back_one_page_element: function (elementId) {			this.back1PageElements[elementId] = elementId;		this.observedElements[elementId] = elementId;	return this;	},
	define_back_to_start_element: function (elementId) {			this.backToStartElements[elementId] = elementId;	this.observedElements[elementId] = elementId;	return this;	},
	define_default_focus_element: function (defaultFocusElement) {	this.defaultFocusElement = defaultFocusElement;		return this;	},

	set_ok_to_observe_forms: function (okToObserveForms) {	this.okToObserveForms = okToObserveForms;	return this;	},
	has_a_form_changed: function () {	return this.formHasChanged;	},

	update_ajax_form_ifh_key: function (ifhKey) {
		for (var count = 0; count < this.ajaxForms.length; count++)
			this.ajaxForms[count].parameters.ifhKey = ifhKey;
		this.ifhKey = ifhKey;
		return this;
	},







	// record when the form has changed
	set_form_has_changed: function (formHasChanged, internalCall) {
		// only make the change to the status if it's been enabled (ie. it is disabled when the popover is opening and closing)
		// or if it's called manually (ie. without the second parameter set)
		if ((internalCall) && (!this.okToObserveForms))
			return false;
		this.formHasChanged = formHasChanged;
		return true;
	},














	// specify how to initially obtain the content to use
	set_content_html: function (htmlContent) {			this.reset_content_sources ();	this.htmlContent = htmlContent;	return this;	},
	set_content_source_dom_id: function (sourceDomId) {	this.reset_content_sources ();	this.sourceDomId = sourceDomId;	return this;	},
	set_content_source_url: function (sourceUrl, requestParameters, requestMethod) {
		this.reset_content_sources ();

		if (typeof (requestParameters) != "object")
			requestParameters = new Object ();

		if (requestMethod != undefined)
			requestMethod = requestMethod.toLowerCase ();
		if (requestMethod != 'get')
			requestMethod = 'post';

		this.sourceUrl = sourceUrl;
		this.requestParameters = requestParameters;
		this.requestMethod = requestMethod;
		return this;
	},
	reset_content_sources: function () {
		this.htmlContent = null;
		this.sourceDomId = null;
		this.sourceUrl = null;
		this.cachedUrlHtml = null;
		return this;
	},







	// render the content into the dom
	render_content_html: function () {

		// specific content
		var content;
		if (this.htmlContent !== null)
			content = this.htmlContent;

		// copy the content from another element on the page
		else if ((this.sourceDomId !== null) && ($(this.sourceDomId) != null))
			content = $(this.sourceDomId).innerHTML;

		// content to be replaced with the response from an ajax request
		else if (this.sourceUrl !== null)
			content = "<div class=\"pleaseWaitBlock\">Please wait</div>";

		// change the content
		if ($(this.domId))
			$(this.domId).update (this.convert_html_dom_ids (content));

		return true;
	},

	observe_elements: function () {

		// stop observations for all elements
		for (var elementId in this.observedElements) {
			if ($(this.convert_dom_id (elementId)) != null)
				$(this.convert_dom_id (elementId)).stopObserving ();
		}



		// back one page elements
		for (var elementId in this.back1PageElements) {
			if ($(this.convert_dom_id (elementId)) != null)
				eval ("$(\"" + this.convert_dom_id (elementId) + "\").observe (\"click\", function () { var formDomId = $(\"" + this.convert_dom_id (elementId) + "\").findFirstAncestor ('form'); if (formDomId) { formDomId.add_hidden_field_inputs ( { formAction: \"navigateBack1\" } ).fire (\"custom:submit\"); } } );");
		}

		// back to start elements
		for (var elementId in this.backToStartElements) {
			if ($(this.convert_dom_id (elementId)) != null)
				eval ("$(\"" + this.convert_dom_id (elementId) + "\").observe (\"click\", function () { var formDomId = $(\"" + this.convert_dom_id (elementId) + "\").findFirstAncestor ('form'); if (formDomId) { formDomId.add_hidden_field_inputs ( { formAction: \"navigateStart\" } ).fire (\"custom:submit\"); } } );");
		}

		// ajax form submits
		for (count = 0; count < this.ajaxForms.length; count++) {
			if ($(this.convert_dom_id (this.ajaxForms[count].formElementId)) != null) {
				eval ("$(\"" + this.convert_dom_id (this.ajaxForms[count].formElementId) + "\").observe (\"custom:submit\", function (event) { var temp = function () { environmentManager.get_content_block (\"" + this.domId + "\").ajax_submit_form (\"" + this.ajaxForms[count].formElementId + "\", \"" + this.ajaxForms[count].url + "\", " + Object.toJSON(this.ajaxForms[count].parameters) + ", \"" + this.ajaxForms[count].method + "\"); }; temp.defer (); } );");
				eval ("$(\"" + this.convert_dom_id (this.ajaxForms[count].formElementId) + "\").observe (\"submit\", function (event) { event.stop (); $(\"" + this.convert_dom_id (this.ajaxForms[count].formElementId) + "\").add_hidden_field_inputs ( { \"formAction\": \"submit\" } ).fire (\"custom:submit\"); } );");
			}
		}



/**
		// watch for form changes?
		var tempElements = $$("#" + this.domId + ' form');
		for (var count = 0; count < tempElements.length; count++) {
			var domId = tempElements[count].readAttribute ("id");
			if ((domId != "") && (domId != null)) {
				// track when this form changes
				eval ("var temp = function () { environmentManager.get_content_block (\"" + this.domId + "\").set_form_has_changed (true, true); }");
				new Form.Observer (domId, 0.25, temp);
			}
		}
/**
		// watch for form changes?
		var tempElements = $(this.domId).descendants ();
		for (var count = 0; count < tempElements.length; count++) {
			var domId = tempElements[count].readAttribute ("id");
			if ((tempElements[count].nodeName.toLowerCase () == "form") && (domId != "") && (domId != null)) {
				// track when this form changes
				eval ("var temp = function () { environmentManager.get_content_block (\"" + this.domId + "\").set_form_has_changed (true, true); }");
				new Form.Observer (domId, 0.25, temp);
			}
		}
/**/
		return this;
	},







	// send an ajax request to the server to fetch it's content (if required)
	send_ajax_request_if_required: function () {

		if (this.sourceUrl !== null) {

			// do anything required before sending the ajax request (like show "please wait" images)
			this.before_send_ajax_request ();

			// the ajax request is required,  send the request
			var requestParameters = clone (this.requestParameters);
			requestParameters.contentBlockDomId = this.domId;
			if (this.popover)
				requestParameters.popoverUniqueId = this.popover.uniqueId;
			ajaxManager.request (this.sourceUrl, { 'method': this.requestMethod, parameters: requestParameters }, "updateContentBlock_" + this.domId);
			return true;
		}
		else {
			this.before_process_ajax_response ();
			this.after_process_ajax_response ();
		}
		return true;
	},
	// submit a form in this content block to the given url via ajax
	ajax_submit_form: function (formElementId, url, extraParameters, method) {

		// find out if this form has any "file" inputs (if so then the form needs to be submitted via a hidden iframe instead of ajax
		// look for any <input type="file".. > fields
		var hasFileInput = false;
/**/
//		var descendants = $(this.convert_dom_id (formElementId)).descendants ();
		var descendants = $$("#" + this.convert_dom_id (formElementId) + ' input');
		for (var count = 0; count < descendants.length; count++) {
			if (descendants[count].readAttribute ("type").toLowerCase () == "file")
				hasFileInput = true;
		}
/**/

		// do anything required before sending the ajax request (like show "please wait" images)
		this.before_send_ajax_request ();


		// if needed submit the form via a hidden iframe (for uploading files)
		if (hasFileInput) {
			var popoverUniqueId;
			if (this.popover)
				popoverUniqueId = this.popover.uniqueId
			return formManager.submit_form (this.convert_dom_id (formElementId), this.domId, popoverUniqueId, url, extraParameters, method);
		}
		// or submit it via ajax
		else {
			var requestParameters = $(this.convert_dom_id (formElementId)).serialize (true);
			for (fieldName in extraParameters)
				requestParameters[fieldName] = extraParameters[fieldName]
			requestParameters.contentBlockDomId = this.domId;
			if (this.popover)
				requestParameters.popoverUniqueId = this.popover.uniqueId;
//			alert (var_dump (requestParameters));
			ajaxManager.request (url, { 'method': method, parameters: requestParameters }, "submitForm" + this.domId);
			return true;
		}
	},
	// run at the beginning of an ajax response
	before_send_ajax_request: function () {
/**/
		// show any "please wait" images
//		var descendants = $(this.domId).descendants ();
		var descendants = $$("#" + this.domId + ' .pleaseWaitBlock');
		for (var count = 0; count < descendants.length; count++) {
//			jQuery("#" + descendants[count].id).effect ("pulsate", { times: 1 }, 800);
//			descendants[count].show ();
//			Effect.Appear (descendants[count], { duration: 0.4, to: 1 });
			$(descendants[count]).show ();
		}
/**/
		return true;
	},
	// run at the beginning of an ajax response
	before_process_ajax_response: function () {

		// reset the list of error messages shown
		this.messagesShown = new Object ();

		return true;
	},
	// run at the end of an ajax response
	after_process_ajax_response: function () {
/**
		eval ("var temp = function () {"
			+ "	var contentBlock = environmentManager.get_content_block ('" + this.domId + "');"
			+ "	var tempDomId = contentBlock.unconvert_dom_id (this.id);"
			+ "	if (tempDomId.indexOf (\'formMessage_\', 0) === 0)"
			+ "		tempDomId = tempDomId.substr (\'formMessage_\'.length, tempDomId.length - \'formMessage\'.length);"	// remove the "formMessage_" prefix to find out the field id
			+ "	if (contentBlock.messagesShown[tempDomId] == undefined)"
			+ "		hide_form_message (contentBlock.convert_dom_id (tempDomId), false);"
			+ "};"
		);

		// loop through the messages in the popover
		// and hide any ones showing that the ajax response didn't just show
		var descendants = jQuery ('#' + this.domId).find ('.formMessage').map (temp);

/**/
//		var descendants = $(this.domId).descendants ();
		var descendants = $$("#" + this.domId + ' .formMessage');
		for (var count = 0; count < descendants.length; count++) {
			var tempDomId = this.unconvert_dom_id (descendants[count].id);
			if (tempDomId.indexOf ("formMessage_", 0) === 0)
				tempDomId = tempDomId.substr ("formMessage_".length, tempDomId.length - "formMessage_".length);	// remove the "formMessage_" prefix to find out the field id
			if (this.messagesShown[tempDomId] == undefined)
				hide_form_message (this.convert_dom_id (tempDomId), false);
		}
/**/

/**
		var descendants = jQuery ('#' + this.domId).find ('.pleaseWaitBlock').map (function () {
			jQuery (this).hide ();
		});
/**/
		// hide any "please wait" images
//		var descendants = $(this.domId).descendants ();
		var descendants = $$("#" + this.domId + ' .pleaseWaitBlock');
		for (var count = 0; count < descendants.length; count++)
			descendants[count].hide ();
/**/
		if (this.popover)
			this.popover.position_neatly_on_screen_if_required ();

		return true;
	},






	// observe a link that will jump the user back to a previous page in the ifh
	observe_step_jump_link: function (domId, url, pageId) {
		eval ("var temp = function (event) { "
			+ "event.stop (); "
			+ "var contentBlock = environmentManager.get_content_block ('" + this.domId + "'); "
			+ "if (contentBlock)"
			+ "	contentBlock.jump_to_ifh_page (url, pageId); "
			+ "}");
		$(domId).observe ("click", temp);
		return true;
	},

	// show the user a previous page in the ifh
	jump_to_ifh_page: function (url, pageId) {
		var requestParameters = new Object ();
		requestParameters.contentBlockDomId = this.domId;
		if (this.popover)
			requestParameters.popoverUniqueId = this.popover.uniqueId;
		requestParameters.ifhKey = this.ifhKey;
		requestParameters.ifhPage = pageId;
		requestParameters.formAction = 'render';	// re-render this page's content

//		alert (var_dump (requestParameters));
		ajaxManager.request (url, { 'method': 'post', parameters: requestParameters }, "submitForm" + this.domId);
		return true;
	},





	// show a message on the page,  and also remember that a message was shown
	show_form_message: function (elementId, message) {
		this.messagesShown[elementId] = true;
		show_form_message (this.convert_dom_id (elementId), message, false);
	},

	focus_default_element: function () {
		if (this.defaultFocusElement) {
			eval ("var temp = function () { scroll_to_focus (\"" + this.convert_dom_id (this.defaultFocusElement) + "\");}");
			temp.defer ();
//			timer.start_delayed_action ("scroll_to_focus (\"" + this.convert_dom_id (this.defaultFocusElement) + "\");", 500, "focusWait_" + this.domId);
		}
		return this;
	}

});











var timer = {

	// storage place for internal info about delayed actions
	delayedActionTimers: new Object (),

	// start a delayed action
	start_delayed_action: function (actions, delayMS, timeoutName) {
		if (timeoutName != undefined) {
//			alert (timeoutName);
			this.cancel_delayed_action (timeoutName);
			if (delayMS > 0)
				this.delayedActionTimers[timeoutName] = setTimeout (actions, delayMS);
			else
				eval_js (actions);
		}
		else
			setTimeout (actions, delayMS);
		return true;
	},

	// cancel a delayed action
	cancel_delayed_action: function (timeoutName) {
		if (this.delayedActionTimers[timeoutName] != undefined)
			clearTimeout (this.delayedActionTimers[timeoutName]);
		return true;
	}
};

var geometry = {

	// find out an element's width and height,  accounting for the fact that it may be hidden
	get_element_dimensions: function (element) {

		// adjust based on the source element's dimensions
		if ($(element).visible ())
			var temp = $(element).getDimensions ();
		else {
			// force the $(element)'s width to be determined properly,
			// because prototype doesn't handle the width of divs with borders correctly when the object is hidden
			$(element).show ();
			var temp = $(element).getDimensions ();
			$(element).hide ();
		}

		return {width: temp.width, height: temp.height};
	},

	// find out an element's position
	get_element_position: function (element) {

		// adjust based on the source element's dimensions
		if ($(element).visible ())
			var temp = Position.cumulativeOffset ($(element));
		else {
			// force the $(element)'s width to be determined properly,
			// because prototype doesn't handle the width of divs with borders correctly when the object is hidden
			$(element).show ();
			var temp = Position.cumulativeOffset ($(element));
			$(element).hide ();
		}

		return {left: temp.left, top: temp.top};
	},

	// positionInfo =
	// {	xSourcePoint: left, middle, right
	//		ySourcePoint: top, middle, bottom
	//		xDestPoint: left, middle, right
	//		yDestPoint: top, middle, bottom
	//		xPixelOffset: integer
	//		yPixelOffset: integer
	//		xSourcePercentOffset: integer
	//		ySourcePercentOffset: integer
	//		xDestPercentOffset: integer
	//		yDestPercentOffset: integer }
	determine_relative_element_position: function (existingElement, newElement, positionInfo, positionNeatlyOnScreen) {

		// find the element in the dom
		existingElement = $(existingElement);
		if (existingElement == null)
			throw new gc_exception ('invalidElementId', 'element not found', 'element "' + existingElement + '" not found');

		// find the element in the dom
		newElement = $(newElement);
		if (newElement == null)
			throw new gc_exception ('invalidElementId', 'element not found', 'element "' + newElement + '" not found');

		if (positionInfo == undefined)
			positionInfo = new Object ();

		if (positionNeatlyOnScreen == undefined)
			positionNeatlyOnScreen = true;


		// existing element's details
		var existingElementPosition = existingElement.cumulativeOffset ();
		var existingElementDimensions = this.get_element_dimensions (existingElement);
/**
not needed
		var left = existingElementPosition.left;
		var top = existingElementPosition.top;
not needed
		// adjust for parent elements that are absolutely/relatively positioned
		var parentElement = $(existingElement.offsetParent);
		while (parentElement != null) {
			if ((parentElement.getStyle ("position") == "relative") || (parentElement.getStyle ("position") == "absolute")) {
				var temp = Position.cumulativeOffset (parentElement);
				left -= temp[0];
				top -= temp[1];
				parentElement = null;
			}
			else
				parentElement = $(parentElement.offsetParent);
		}
/**/
		// new element's details
		var newElementDimensions = this.get_element_dimensions (newElement);

		return this.determine_relative_pixel_position (existingElementPosition.left, existingElementPosition.top, existingElementDimensions.width, existingElementDimensions.height, newElementDimensions.width, newElementDimensions.height, positionInfo, positionNeatlyOnScreen);
	},


	// positionInfo =
	// {	xSourcePoint: left, middle, right
	//		ySourcePoint: top, middle, bottom
	//		xDestPoint: left, middle, right
	//		yDestPoint: top, middle, bottom
	//		xPixelOffset: integer
	//		yPixelOffset: integer
	//		xSourcePercentOffset: integer
	//		ySourcePercentOffset: integer
	//		xDestPercentOffset: integer
	//		yDestPercentOffset: integer }
	determine_relative_pixel_position: function (existingElementX, existingElementY, existingElementWidth, existingElementHeight, newElementWidth, newElementHeight, positionInfo, positionNeatlyOnScreen) {

		if (positionInfo == undefined)
			positionInfo = new Object ();

		if (positionNeatlyOnScreen == undefined)
			positionNeatlyOnScreen = true;


		var destX = existingElementX;
		var destY = existingElementY;



		// work out the new position relative to the source position
		if (positionInfo.xSourcePoint == 'middle')
			destX = destX + Math.round (existingElementWidth / 2);
		else if (positionInfo.xSourcePoint == 'right')
			destX = destX + existingElementWidth;

		if (positionInfo.ySourcePoint == 'middle')
			destY = destY + Math.round (existingElementHeight / 2);
		else if (positionInfo.ySourcePoint == 'bottom')
			destY = destY + existingElementHeight;



		if (positionInfo.xDestPoint == 'middle')
			destX = destX - Math.round (newElementWidth / 2);
		else if (positionInfo.xDestPoint == 'right')
			destX = destX - newElementWidth;

		if (positionInfo.yDestPoint == 'middle')
			destY = destY - Math.round (newElementHeight / 2);
		else if (positionInfo.yDestPoint == 'bottom')
			destY = destY - newElementHeight;


		// adjust the relative position via the given offsets
		if (positionInfo.xPixelOffset != undefined)
			destX = destX + positionInfo.xPixelOffset;
		if (positionInfo.yPixelOffset != undefined)
			destY = destY + positionInfo.yPixelOffset;

		if (positionInfo.xSourcePercentOffset != undefined)
			destX = destX + Math.round ((positionInfo.xSourcePercentOffset / 100) * existingElementWidth);
		if (positionInfo.ySourcePercentOffset != undefined)
			destY = destY + Math.round ((positionInfo.ySourcePercentOffset / 100) * existingElementHeight);

		if (positionInfo.xDestPercentOffset != undefined)
			destX = destX + Math.round ((positionInfo.xDestPercentOffset / 100) * newElementWidth);
		if (positionInfo.yDestPercentOffset != undefined)
			destY = destY + Math.round ((positionInfo.yDestPercentOffset / 100) * newElementHeight);



		// make sure the new coordinates are within the page
		if (positionNeatlyOnScreen) {

			var temp = this.position_pixel_position_neatly_on_screen (destX, destY, newElementWidth, newElementHeight);
			destX = temp.x;
			destY = temp.y;
		}

		if (destX < 0)
			destX = 0;
		if (destY < 0)
			destY = 0;

		return {x: destX, y: destY};
	},


	position_element_neatly_on_screen: function (newElement) {

		// existing element's details
		var newElementPosition = this.get_element_position ($(newElement));
		var newElementDimensions = this.get_element_dimensions ($(newElement));

		var temp = this.position_pixel_position_neatly_on_screen (newElementPosition.left, newElementPosition.top, newElementDimensions.width, newElementDimensions.height);
		$(newElement).setStyle ({ left: temp.x + "px", top: temp.y + "px" });
	},


	position_pixel_position_neatly_on_screen: function (newElementX, newElementY, newElementWidth, newElementHeight) {

		var destX = parseInt (newElementX);
		var destY = parseInt (newElementY);

		var viewportDimensions = document.viewport.getDimensions ();
		var viewportScrollOffsets = document.viewport.getScrollOffsets ();

		if (parseInt (destX) + newElementWidth > viewportScrollOffsets.left + viewportDimensions.width)
			destX = viewportScrollOffsets.left + viewportDimensions.width - newElementWidth;
		if (parseInt (destY) + newElementHeight > viewportScrollOffsets.top + viewportDimensions.height)
			destY = viewportScrollOffsets.top + viewportDimensions.height - newElementHeight;

		if (destX < 0)
			destX = 0;
		if (destY < 0)
			destY = 0;

		return {x: destX, y: destY};
	},


	// find the offset of the viewable page in relation to the very top and left of the document
	// see for more info: http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
	find_viewable_page_offset: function () {
		var x = 0, y = 0;

		// DOM compliant
		if ((document.body) && ((document.body.scrollLeft) || (document.body.scrollTop))) {
			x = document.body.scrollLeft;
			y = document.body.scrollTop;
		}
		// netscape compliant
		else if (typeof (window.pageYOffset) == 'number') {
			x = window.pageXOffset;
			y = window.pageYOffset;
		}
		// IE6 standards compliant mode
		else if ((document.documentElement) && ((document.documentElement.scrollLeft) || (document.documentElement.scrollTop))) {
			x = document.documentElement.scrollLeft;
			y = document.documentElement.scrollTop;
		}

		return {x: parseInt (x), y: parseInt (y)};
	}
}



// alert the contents of an object
var var_dump = function (object) {
	var temp = new Array ();
	for (var index in object)
		temp[temp.length] = index + ': "' + object[index] + '"';
	return temp.join ("\n");
}






var ajaxManager = {
	ajaxRequesters: new Array (),	// a storage array of ajax requesters
	identifier: undefined,

	use_requester: function (identifier) {
		this.identifier = identifier;
	},

	// start an ajax request,  but store a reference to this new ajax request
	// instance so it can be replaced by a new one later if need be
	request: function (url, sendOptions) {

		if (sendOptions == undefined)
			sendOptions = new Object ();

		if (sendOptions.onSuccess == undefined) {
			if (typeof (this.identifier) == 'string')
				eval ('sendOptions.onSuccess = function (transport)'
				+ '{	ajaxManager.process_ajax_response (transport, \'' + this.identifier + '\'); }');
			else
				eval ('sendOptions.onSuccess = function (transport)'
				+ '{	ajaxManager.process_ajax_response (transport, ' + this.identifier + '); }');
				
		}

		// create the new request
		var newAjaxHandler = new Ajax.Request (url, sendOptions);

		// if the user has specified a name for this ajax handler then..
		if (this.identifier != undefined) {

			// cancel the original http request
			if (this.ajaxRequesters[this.identifier] != undefined)
				try { this.ajaxRequesters[this.identifier].transport.abort (); } catch (e) {};

			// store this new request
			this.ajaxRequesters[this.identifier] = newAjaxHandler;
		}

		// forget the current identifier so that other anonymous requests can be made straight away without stopping the above request
		this.identifier = undefined;

		return true;
	},


	// process an xml ajax response,  and carry out it's instructions
	process_ajax_response: function (transport, identifier) {

		// remove the ajax requester object so it can be garbage collected
		this.ajaxRequesters[identifier] = undefined;

/**
		var response = transport.responseText || "no response text";
		alert("Suceeess! \n\n" + response);	
/**/
		if (transport.responseText.length == 0)
			return true;

		var xmlDoc = transport.responseXML;
		// alert the developer of a problem with the xml
//alert (transport.responseText);
//alert (xmlDoc.childNodes[0].nodeName);
//alert (xmlDoc.childNodes.length + " " + (xmlDoc.childNodes[0].nodeName == 'parsererror'));


		if ((xmlDoc == null) || (xmlDoc.childNodes.length == 0) || (xmlDoc.childNodes[0].nodeName == 'parsererror')) {
//				document.writeln ('<strong>Warning: invalid xml ajax response:</strong><br />\n<a href="' + this.uri + '">' + this.uri + '</a><br />\n' + this.httpRequestObject.responseText);
//				document.writeln ('<strong>Warning: invalid xml ajax response:</strong><br />\n' + transport.responseText);
			alert ('Warning: invalid xml ajax response:\n' + transport.responseText);
			return false;
		}

		this.process_xml (xmlDoc);
		return true;
	},

	process_xml: function (xmlDoc) {

		// find the root node
		for (var count = 0; count < xmlDoc.childNodes.length; count++) {

			if (xmlDoc.childNodes[count].nodeName == 'root') {
				var rootNode = xmlDoc.childNodes[count];

				// loop through the nodes and carry out their instructions
				for (var count2 = 0; count2 < rootNode.childNodes.length; count2++) {
					var currentNode = rootNode.childNodes[count2];

					switch (currentNode.nodeName) {
						case 'updateElement' :
							if ($(currentNode.getAttribute ('domId')) != null) {
								if (currentNode.hasChildNodes ())
									$(currentNode.getAttribute ('domId')).update (currentNode.firstChild.data);
								else
									$(currentNode.getAttribute ('domId')).update ('');
							}
							break;
						case 'showElement' :
							if ($(currentNode.getAttribute ('domId')) != null)
								$(currentNode.getAttribute ('domId')).show ();
							break;
						case 'hideElement' :
							if ($(currentNode.getAttribute ('domId')) != null)
								$(currentNode.getAttribute ('domId')).hide ();
							break;
						case 'enableElement' :
							if ($(currentNode.getAttribute ('domId')) != null)
								$(currentNode.getAttribute ('domId')).disabled = false;
							break;
						case 'disableElement' :
							if ($(currentNode.getAttribute ('domId')) != null)
								$(currentNode.getAttribute ('domId')).disabled = true;
							break;
						case 'setCookie' :
							cookie.set (currentNode.getAttribute ('name'), currentNode.firstChild.data, currentNode.getAttribute ('expireSeconds'), currentNode.getAttribute ('path'), currentNode.getAttribute ('domain'), currentNode.getAttribute ('secure'));
							break;
						case 'removeCookie' :
							cookie.remove (currentNode.getAttribute ('name'), currentNode.getAttribute ('path'), currentNode.getAttribute ('domain'));
							break;
						case 'alert' :
							alert (currentNode.firstChild.data);
							break;
						case 'redirect' :
							document.location.href = currentNode.firstChild.data;
							break;
						case 'eval' :
							if (currentNode.firstChild.data != '')
								eval_js (currentNode.firstChild.data);
							break;
						case 'reload' :
							window.location.reload ();
							break;
					}
				}
			}
		}
		return true;
	}
}

// eval the given javascript,
// called from the above process_ajax_response when eval'ing some js from an ajax response
// and also called from the timer object
// so that the code is not running in the scope of the ajaxManager object
var eval_js = function (code) {
	eval (code);
}







// submit a form via a hidden iframe (to facilitate background uploads of files)
var class_formManager = Class.create ({
	customIdentifier: "formManager",
	uniqueId: null,

	// constructor
	initialize: function () {
		// unique identifier
		this.uniqueId = Math.round ((Math.random () * 10000000000) + 1);
	},

	// 
	get_base_iframe_id: function () {
		return this.customIdentifier + "_iframe";
	},

	// 
	get_base_form_id: function () {
		return this.customIdentifier + "_form";
	},

	// formDomId: the domId of the original form to submit
	// contentBlockDomId: an string used to uniquely identify this instance of a form submission
	// popoverUniqueId: an string used to uniquely identify this instance of a form submission
	// fieldSuffix: this fieldSuffix is stripped out from fieldnames (eg. the fieldnames used in popover forms)
	// url: the url to submit to
	// extraParameters: extra hidden fields to add to the form
	// method: get/post
	submit_form: function (formDomId, contentBlockDomId, popoverUniqueId, url, extraParameters, method) {

		// work out where to put the iframe
		var newIFrameDomId = this.get_base_iframe_id () + "_" + contentBlockDomId;
		if ($(newIFrameDomId) != null)
			$(newIFrameDomId).remove ();

		// attach the iframe to the dom
//		$("coreBase").appendChild (new Element ("iframe", {id: newIFrameDomId, name: newIFrameDomId, src: "about:blank", style: "display: block", onLoad: "alert ('rah');" } ));
		$("coreBase").insert ( {bottom: "<iframe src=\"about:blank\" style=\"display: none;\" id=\"" + newIFrameDomId + "\" name=\"" + newIFrameDomId + "\" onLoad=\"formManager.load_finished (this.id);\"></iframe>" } );







/**
		// make a copy of the form
		var tempHtml = $(formDomId).up ().innerHTML;
alert (tempHtml);
		// create the div and put the form + it's siblings into it
		var newDiv = new Element ("div").update (tempHtml);
		// remove all the other form's siblings out of the div
		var tempElements = newDiv.childElements ();
		var formElement = null;
		for (var count = 0; count < tempElements.length; count++) {
			var value = tempElements[count].readAttribute ("id");
			if (value == formDomId)
				formElement = tempElements[count];
			else
				tempElements[count].remove ();
		}
//alert (formElement.innerHTML);
		// strip out all ids from the form + inputs etc
		var tempElements = newDiv.descendants ();
		for (var count = 0; count < tempElements.length; count++) {
			tempElements[count].writeAttribute ("id", null);
			tempElements[count].writeAttribute ("for", null);
		}
		formElement.setStyle ({display: "block"});

		// add the extra attributes to the form
		extraParameters.httpSource = 'iframe';
		extraParameters.contentBlockDomId = contentBlockDomId;
		if (popoverUniqueId)
			extraParameters.popoverUniqueId = popoverUniqueId;
		if (typeof (extraParameters) == 'object') {
			for (fieldName in extraParameters)
				formElement.insert ({top: new Element ("input", {name: fieldName, value: extraParameters[fieldName], type: "hidden" })});
		}

		// make the id different to the source form's id
		formDomId = formDomId + "_temp";
		formElement.writeAttribute ("id", formDomId);

		if ($(formDomId) !== null)
			$(formDomId).remove ();


		// attach this copy of the form to the dom
		$("coreBase").appendChild (formElement);



alert (formDomId);
/**/

		extraParameters.httpSource = 'iframe';
		extraParameters.contentBlockDomId = contentBlockDomId;
		if (popoverUniqueId)
			extraParameters.popoverUniqueId = popoverUniqueId;
		$(formDomId).add_hidden_field_inputs (extraParameters);







		// direct the form to the iframe and submit it
		$(formDomId).writeAttribute ("action", url);
		$(formDomId).writeAttribute ("target", newIFrameDomId);
		$(formDomId).writeAttribute ("method", method);
		$(formDomId).writeAttribute ("enctype", "multipart/form-data");		// firefox
		$(formDomId).writeAttribute ("encoding", "multipart/form-data");	// internet explorer
		$(formDomId).submit ();

	},

	load_finished: function (iframeDomId) {

		var iFrame = $(iframeDomId);
		if (iFrame.contentDocument)
			var d = iFrame.contentDocument;
		else if (iFrame.contentWindow)
			var d = iFrame.contentWindow.document;
		else
			var d = window.frames[iframeDomId].document;

		if (d.location.href == "about:blank")
			return;



		// the xml response can be placed inside an html page,  inside the value attribute of an element called "xmlResponse" (eg. <textarea id="xmlResponse">xml goes here</textarea>
		// this is to get around ie6's xml limitation when handling pure text/xml responses
		if (d.getElementById ('xmlResponse') !== null) {
			var xmlDoc = null;
			try {	//Internet Explorer
				xmlDoc = new ActiveXObject ("Microsoft.XMLDOM");
				xmlDoc.async = "false";
				xmlDoc.loadXML (d.getElementById ('xmlResponse').value);
			}
			catch (e) {
				var xmlDoc = null;
				try {	// Firefox, Mozilla, Opera, etc.
					parser = new DOMParser ();
					xmlDoc = parser.parseFromString (d.getElementById ('xmlResponse').value, "text/xml");
				}
				catch (e) {
					var xmlDoc = null;
				}
			}
		}
		// or handle the pure text/xml response (note: this does not work in ie6)
		else {
			var xmlDoc = null;
			try {	//Internet Explorer
				xmlDoc = new ActiveXObject ("Microsoft.XMLDOM");
				xmlDoc.async = "false";
				xmlDoc.loadXML (d);
			}
			catch (e) {
				var xmlDoc = null;
				try {	// Firefox, Mozilla, Opera, etc.
					xmlDoc = d;
				}
				catch (e) {
					var xmlDoc = null;
				}
			}
		}


/**
		var xmlDoc = null;
		try {	//Internet Explorer

			testXml = d.childNodes[0].innerText;

			alert (testXml);
			while ((testXml.length > 0) && (testXml.substr (0, 6) != '<root>')) {
				testXml = testXml.substring (1);
			}
			alert (testXml);


			xmlDoc = new ActiveXObject ("Microsoft.XMLDOM");
			xmlDoc.async = "false";
//			xmlDoc2 = new ActiveXObject ("Microsoft.XMLDOM");
//			xmlDoc2.async = "false";
//			xmlDoc.loadXML (d.body.innerHTML);
//			xmlDoc.loadXML (d.childNodes[0].innerText);
			xmlDoc.loadXML (testXml);

			this.blah (xmlDoc);
alert (xmlDoc.xml);
alert ('fin');
		}
		catch (e) {

			var xmlDoc = null;
			try {	// Firefox, Mozilla, Opera, etc.

//				xmlDoc = document.implementation.createDocument ("", "", null);
//				xmlDoc.async = "false";

//				parser = new DOMParser ();
//				xmlDoc = parser.parseFromString (d.body.innerHTML, "text/xml");
//				xmlDoc = parser.parseFromString (d.getElementById ('response').value, "text/xml");
				xmlDoc = $(iframeDomId).contentDocument;
			}
			catch (e) {
				var xmlDoc = null;
			}
		}
/**/




		// handle the xml response like an ajax xml response
		if (xmlDoc != null)
			return ajaxManager.process_xml (xmlDoc);
	}
/**
	blah: function (xmlPiece) {
		alert (typeof (xmlPiece.childNodes))
//		alert (xmlPiece.childNodes.length);
//		alert (xmlPiece.childNodes[0].xml);
		if (xmlPiece.childNodes.length > 0) {
			for (var count = 0; count < xmlPiece.childNodes.length; count++) {
				if (!this.blah (xmlPiece.childNodes[count])) {
//					alert ('"' + xmlPiece.childNodes[count].xml + '" IS bad');
//					xmlPiece.childNodes[count] ();
//					xmlDoc2.cloneNode (xmlPiece.childNodes[count]);
				}
//				alert (this.blah (xmlPiece.childNodes[length]));
			}
			return true;
		}
		else {
//			for (count = 0; count < xmlPiece.xml.length; count++)
//				alert ('"' + xmlPiece.xml.charCodeAt (count) + '"');
//			alert ('"' + xmlPiece.xml + '"');
			if ((xmlPiece.xml == "\r\n -") || (xmlPiece.xml.length == 4)) {
				return false;
			}
			return true;
		}
	}
/**/
});

formManager = new class_formManager ();





// cookie addition to prototype.  adapted from:
// http://wiki.script.aculo.us/scriptaculous/show/Cookie
// http://gorondowtl.sourceforge.net/wiki/Cookie
var cookie = {
	// set the value of a cookie
	set: function (name, value, expireSeconds, path, domain, secure) {

		var expires = '';
		if (expireSeconds != undefined) {
			var d = new Date ();
//				d.setTime (d.getTime () + (86400000 * parseFloat (daysToExpire)));
			d.setTime (d.getTime () + parseFloat (expireSeconds));
			expires = d.toGMTString ();
		}
//			return (document.cookie = escape (name) + '=' + escape (value || '') + expire + ((path) ? "; path=" + path : ""));
		return document.cookie = escape (name) + '=' + escape (value || '')
								+ (expires ? "; expires=" + d.toGMTString () : "")
								+ (path ? "; path=" + path : "")
								+ (domain ? "; domain=" + domain : "")
								+ (secure ? "; secure" : "");
	},
	// fetch the value of a cookie
	get: function (name) {
		var cookie = document.cookie.match (new RegExp ('(^|;)\\s*' + escape (name) + '=([^;\\s]*)'));
		return (cookie ? unescape (cookie[2]) : null);
	},
	// remove a cookie
	remove: function (name, path, domain, secure) {
		var cookie = ((cookie.get (name)) || (true));
		cookie.set (name, '', -1, path, domain, secure);
		return cookie;
	},
	// find out if javascript is allowed to access cookies
	accept: function () {
		if (typeof navigator.cookieEnabled == 'boolean')
			return navigator.cookieEnabled;
		cookie.set ('_test_cookie_abcdef_', '1');
		var cookieWasSet = (cookie.remove ('_test_cookie_abcdef_') === '1');
		cookie.remove ('_test_cookie_abcdef_');
		return cookieWasSet;
	}
}



// deep clone of a javascript object (or array,  variable, etc)
function clone (myObj) {
	if (typeof (myObj) != 'object')
		return myObj;
	if (myObj == null)
		return myObj;

	var myNewObj = new Object ();

	for (var i in myObj)
		myNewObj[i] = clone (myObj[i]);

	return myNewObj;
}











function escapeHTML (s,escapeSpacing,tabSize,escapeLineBreaks) {

	// default values
	if (escapeSpacing == null)
		escapeSpacing = false;
	if (tabSize == null)
		tabSize = 4;
	if (escapeLineBreaks == null)
		escapeLineBreaks = true;

	s = s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\"/g,'&quot;'); //html entities. // & must be replaced first.
	if(escapeSpacing){
		var spaces = '';
		for(var i=0;i<tabSize;i++)
			spaces += ' ';

		s = s.replace(/\t/g,spaces).replace(/\r\n/g,'\n');  //tab to spaces. \r\n to \n
//		md.log('s after tab2space',s)

		var ss = [];
		var s3 = [];
//		md.log('[]',[]);
//		md.log('new Array',new Array());
//		md.log('ss first',ss);
//		md.log('s3 first',s3);
//		md.log('s3.length',ss.length);
		for(var i=0, n=s.length, indent=false; i<n; i++){ //indent and double spaces to &nbsp;
			var prev = (i > 0)?s.charAt(i-1):'\n';
			var next = (i + 1 < n)?s.charAt(i+1):'\n';
			var cur = s.charAt(i);
			
			if(cur=='\n' && next==' ')
				indent = true; //cur space is an indent.
			else if(indent && cur!=' ')
				indent = false;
			
			ss.push((cur==' ' && (prev==' ' || next==' ' || indent)) ? '&nbsp;' : cur)
		}
//		md.log('ss',ss);
		s = ss.join('');
	}
	
	if(escapeLineBreaks)
		s = s.replace(/\r\n/g,'\n').replace(/\n/g,'<br>\n');
	
	return s;
}





































function show_form_message (elementId, message, focusField) {
	// update the message element's content,
	// set it to not be displayed
	// slide it in (or flash it if it's already showing)
//	jQuery("#formMessage_" + elementId + ":visible").html (message).effect ("pulsate", { times: 1 }, 800);
//	jQuery("#formMessage_" + elementId + ":hidden").html (message).toggle("slide", { direction: "left" }, 800);
	jQuery("#formMessage_" + elementId + ":visible").html (message).effect ("pulsate", { times: 1 }, 800);
	jQuery("#formMessage_" + elementId + ":hidden").html (message).show ("blind");
//	jQuery("#" + elementId).parent ().siblings (".formMessage").html (message).css ({display: "none"}).toggle("slide", { direction: "left" }, 800);
	// focus the input field if necessary
	if (focusField)
		scroll_to_focus (elementId);
	return true;
}

function hide_form_message (elementId, focusField) {
	// slide the message element out if it is currently being displayed
//	jQuery("#formMessage_" + elementId + "").toggle("slide", { direction: "left" }, 800);
//	jQuery("#formMessage_" + elementId + ":visible").toggle("slide", { direction: "left" }, 800);
	jQuery("#formMessage_" + elementId + ":visible").hide ("blind");
//	if (jQuery("#formMessage_" + elementId).css ("display") != "none")
//		jQuery("#formMessage_" + elementId).toggle("slide", { direction: "left" }, 800);
	// focus the input field if necessary
	if (focusField)
		scroll_to_focus (elementId);
	return true;
}

function scroll_to_focus (elementId) {
	if ($(elementId) != null) {

		var relativePos = $(elementId).viewportOffset ();
		var dimensions = document.viewport.getDimensions ();

		// just focus the element because it's on the screen already
		if ((relativePos.top >= 70) && (relativePos.top < dimensions.height))
			$(elementId).focus ();
		// scroll to the element
		else {
			Effect.ScrollTo (elementId, {offset: -70});
			timer.start_delayed_action ("$(\"" + elementId + "\").focus ();", 1000);
		}
	}
}

function hide_form_messages (formId) {
	// hide any message elements (containing class "formMessage") in the given form if they are currently being shown
	jQuery("#"+ formId + " .formMessage:visible").hide ("blind");
	return true;
}



function show_message (domId, message) {
	// update the element's content,
	// set it to not be displayed
	// slide it in
	jQuery("#" + domId + ":hidden").html (message).show ("blind");
	return true;
}

function hide_message (domId) {
	// slide the message element out if it is currently being displayed
//	jQuery("#formMessage_" + elementId + "").toggle("slide", { direction: "left" }, 800);
	jQuery("#" + domId + ":visible").hide ("blind");
	return true;
}

function replace_content (domId, content, copyCssClass) {

	if (copyCssClass === undefined)
		copyCssClass = true;

	// slide the message element out if it is currently being displayed
//	jQuery("#forMessage_" + elementId + "").toggle("slide", { direction: "left" }, 800);
	if ($(domId) != null) {
		// take the attributes from the existing element
		var cssClass = $(domId).readAttribute ("class");
		$(domId).replace (content);
		if ((copyCssClass) && (cssClass))
			$(domId).writeAttribute ( {"class": cssClass} );
		highlight_content (domId);
		return true;
	}
	return false;
}

function highlight_content (domId) {
	if ($(domId) != null) {
		$(domId).setStyle ( {backgroundColor: ""} );	// reset the background color to what it should normally be
		jQuery("#" + domId).effect ("highlight", { }, 800);
		jQuery("#" + domId).effect ("highlight", { }, 800);
		jQuery("#" + domId).effect ("highlight", { }, 800);
//		$(domId).highlight ();
		return true;
	}
	return false;
}

















Element.addMethods ('iframe', {
    document: function (element) {
        element = $(element);
        if (element.contentWindow)
            return element.contentWindow.document;
        else if (element.contentDocument)
            return element.contentDocument;
        else
            return null;
    },
    $: function (element, frameElement) { 
        element = $(element);
        var frameDocument = element.document();
        if (arguments.length > 2) {
            for (var i = 1, frameElements = [], length = arguments.length; i < length; i++)
                frameElements.push(element.$(arguments[i]));
            return frameElements;
        }
        if (Object.isString(frameElement))
            frameElement = frameDocument.getElementById(frameElement);
        return frameElement || element;
    }
});

html_entity_decode = function (str) {
	var ta = document.createElement ("textarea");
	ta.innerHTML = str.replace (/</g,"&lt;").replace (/>/g,"&gt;");
	return ta.value;
}

replace_html = function (el, html) {
	var oldEl = typeof el === "string" ? document.getElementById(el) : el;
	/*@cc_on // Pure innerHTML is slightly faster in IE
		oldEl.innerHTML = html;
		return oldEl;
	@*/
	var newEl = oldEl.cloneNode(false);
	newEl.innerHTML = html;
	oldEl.parentNode.replaceChild(newEl, oldEl);
	/* Since we just removed the old element from the DOM, return a reference
	to the new element, which can be used to restore variable references. */
	return newEl;
};