// The module manager handles gathering all of the data from each
// of the modules in the page and submitting them (hidden-form POST
// or AJAX) to the server.

var ModuleManager = {};
ModuleManager.Manager = Class.create();
ModuleManager.Manager.prototype = {
	// constants
	debug: false,
	POST: 'post',
	GET: 'get',	
	type: 'post',
	beforeErrorHtml: '*',
	afterErrorHtml: '<br/>',
	globalParams: $H({}),
	cancelledWindowModules: $H({}),
	windowLoadedModuleMap: $H(),
	trackWindowModuleLoad: false,
	// fields
	modules: $H({}),
	_ALL_MODULES: '_ALL_MODULES',
	SUBMITTED_MODULES_PARAM: 'vwmSubmittedModules',
	ACTION_PARAM: 'vwmAction',
	MODULE_ID_PARAM: 'vwmModuleId',
	METHOD_NAME_PARAM: 'vwmMethodName',
	VALIDATION_MODULE_IDS_PARAM: 'vwmValidationModIds',	
	SAVE_SESSION_MODS: 'vwmSaveSessionModules',		
	DEFAULT_WAIT_TIME: 100,
	
	// public methods
	baseInitialize: function(url) {
		this.url = url;
	},
	initialize: function(url) {
		this.baseInitialize(url);
	},
	setType: function(type) {
		if( type != this.POST && type != this.GET) {
			this._badType(type);
		} else {
			this.type = type;
		}
	},
	setGlobalParams: function(globalParams) {
		this.globalParams = new $H(globalParams);
	},
	setErrorDivId: function(id) {
		this.errorDivId = id;
		this.errorDivMap = null;
	},
	setErrorDivMap: function(map) {
		this.errorDivMap = new $H(map);
	},
	setBeforeErrorHtml: function(html) {
		this.beforeErrorHtml = html;
	},
	setAfterErrorHtml: function(html) {
		this.afterErrorHtml = html;
	},
	setDebug: function(debug) {
		this._debug = debug;
		Logger.setDebug(debug);
	},
	call: function(moduleIds, callModuleId, methodName, options) {
		// current options are onComplete, requestParams, wait, form
		moduleIds = this._buildModuleIds(moduleIds);			
		this._call(moduleIds, callModuleId, methodName, false, options);
	},
	staticCall: function(callModuleId, methodName, options) {
		options = options || new $H();
		// don't validate anything
		options['validationModuleIds'] = new $A();
		this._call(new $A(), callModuleId, methodName, true, options);		
	},
	submit: function(moduleIds, options) {
		moduleIds = this._buildModuleIds(moduleIds);
		if( this.type == this.POST || this.type == this.GET) {
			this._submitPostOrGet(moduleIds, options);
		} else {
			this._badType();
		}
	},
	validate: function(moduleIds, options) {
		moduleIds = this._buildModuleIds(moduleIds);
		this._logDebug('<b>validate:</b>'+moduleIds);
		this._sendAjax('validateAjax', moduleIds, options);
	},
	// saves all modules that are loaded on the server with the VWMController
	saveAll: function(options) {	
		this._logDebug('<b>saveAll</b>');		
		this.save(null, options);		
	},
	save: function(moduleIds, options) {
		moduleIds = this._buildModuleIds(moduleIds);
		this._logDebug('<b>save:</b>'+moduleIds);
		this._sendAjax('saveAjax', moduleIds, options);
	},	
	// clear all modules that are registered with the VWMController	
	clearAll: function(options) {	
		this.clearModule(this._ALL_MODULES, options);
	},
	// clears both client and server for the specified modules
	clearModules: function(moduleIds, options) {
		this._logDebug('<b>clearModule:</b>'+moduleIds);
		this.clearValues(moduleIds);
		this.clearSession(moduleIds, options);
	},
	// clears specified modules' session state on the server
	clearSession: function(moduleIds, options) {
		this._logDebug('<b>clearSession:</b>'+moduleIds);		
		moduleIds = this._buildModuleIds(moduleIds);
		options = options || $H();
		options['ignoreResponse'] = true;
		this._sendAjax('clearSessionModules', moduleIds, options, false);
	},
	// clears specified modules' state on the page
	clearValues: function(moduleIds) {
		this._logDebug('<b>clearValues:</b>'+moduleIds);				
		if(moduleIds == this._ALL_MODULES) moduleIds == null;
		moduleIds = this._buildModuleIds(moduleIds);
		this.modules.each( (function(moduleEntry) { 
			if( moduleEntry.value != null ) {
				if( moduleIds.indexOf(moduleEntry.key) > -1 ) {
					moduleEntry.value.clearValues();
				}
			}
		}).bind(this) );
		this._clearErrorDiv();
	},
	registerModule: function(module) {
		this._logDebug('<b>registerModule:</b>'+module.id);
		this.modules[module.id] = module;
		if( this.trackWindowModuleLoad ) {
			// note that this module was loaded
			var winLoadedModules = this.windowLoadedModuleMap[this.trackWindowModuleLoadParentId];
			if(winLoadedModules == null) {
				winLoadedModules = $A();
				this.windowLoadedModuleMap[this.trackWindowModuleLoadParentId] = winLoadedModules;
			}
			winLoadedModules.push(module.id);
		}
	},
	unregisterModule: function(moduleId, options) {
		this._logDebug('<b>unregisterModule:</b>'+moduleId);
		options = options || {};
		if( options['clearSession'] == null || options['clearSession'] ) {
			this.clearSession(moduleId);
		}
		delete this.modules[moduleId];		
	},
	openModuleWindow: function(moduleId, params, windowOptions) {
		this._openModuleWindow(moduleId, params, windowOptions);			
	},
	closeModuleWindow: function(moduleId, options) {
		this._closeModuleWindow(moduleId, options);
	},
	cancelModuleWindow: function(moduleId, options) {
		this._cancelModuleWindow(moduleId, options);
	},	
	openModuleDiv: function(moduleId, divId, params, divOptions) {
		this._logDebug('<b>openModuleDiv</b>:'+moduleId);
		var options = divOptions || {};
		params = new $H(params);
		params[this.ACTION_PARAM] = 'load';
		params[this.MODULE_ID_PARAM] = moduleId;
		params['rnd'] = Math.random();
		params = params.merge(this.globalParams);
		var paramStr = params.toQueryString();
		this._logDebug('<b>&nbsp;&nbsp;&nbsp;paramStr</b>:' + paramStr);
		var effect = options['effect'] || function(options) {Element.show(divId)};
		new Ajax.Updater(divId, this.url, { evalScripts: true, parameters: paramStr, method: 'post', onComplete:
				function() { effect(divId, options); }
		 });
	},
	updateModuleDiv: function(moduleId, divId, replacementUrl, params) {
		this._logDebug('<b>updateModuleDiv</b>:'+moduleId);
		//var options = divOptions || {};
		params = new $H(params);
		params[this.MODULE_ID_PARAM] = moduleId;
		params['rnd'] = Math.random();
		params = params.merge(this.globalParams);
		var paramStr = params.toQueryString();
		//var effect = options['effect'] || function(options) {Element.show(divId)};
		new Ajax.Updater(divId, replacementUrl, { evalScripts: true, parameters: paramStr, method: 'post'});
	
	},
	// this will send the user to the view page for the specified module
	gotoModuleView: function(moduleId, options) {
		options = options || new $H();
		var delay = options['delay'] || 0;
		var formDiv = this._createHiddenForm('load', new $A());
		this._createHiddenTextInput(this.MODULE_ID_PARAM, moduleId);
		document.body.appendChild(formDiv);
		// do a timeout so we don't forward too fast after another AJAX action
		setTimeout( (function() { 
			// submit the form
			this._form.method = 'GET';
			this._form.submit('submit');
		}).bind(this), delay);
	},
	hasErrors: function() {
		return( this._errors != null && this._errors.hasErrors() );
	},
	moduleHasErrors: function(moduleId) {
		return( this._errors && this._errors[moduleId] && this._errors[moduleId].hasErrors() );
	},
	// reutrns the module window for this module if one is open
	getModuleWindow: function(moduleId) {
		var win = Windows.getWindow(moduleId);
		return win;
	},
	
	//
	// 'Protected' methods (for override if necessary)
	//
	
	buildErrorsDisplay: function(errors) {
		var errorsHtml = '';
		errors.each( (function(errorEntry) { 
			var errText = errorEntry.value;
			errorsHtml += this.beforeErrorHtml;
			errorsHtml += errText+ this.afterErrorHtml;
		}).bind(this) );
		return errorsHtml;
	},
	
	// div and window extensions
	constructWindow: function(winId, options) {
		var win = new Window(winId, options);
		return win;
	},
	
	//
	// Private methods
	//
	
	_buildModuleIds: function(moduleIds) {
		if( moduleIds == this._ALL_MODULES ) return new $A();
		if(typeof moduleIds == 'string') {
			var modIds = new $A();
			modIds.push(moduleIds);
			return modIds;
		}
		if(moduleIds != null && moduleIds.length > 0 ) {
			return moduleIds;
		} else {
			var allModIds = $A();
			this.modules.each( function(moduleEntry) { 
				if( moduleEntry.key != null ) {
					allModIds.push(moduleEntry.key);
				}
			});
			return allModIds;
		}
	},
	_getErrors: function(moduleIds) {
		var errors = new $H();
		this.modules.each( (function(moduleEntry) { 
			if( moduleEntry.value != null ) {
				if( moduleIds.indexOf(moduleEntry.value.moduleId) > -1 ) {
					var errMap = moduleEntry.value.getErrorMap();
					if( errMap != null )  {
						// copy the errors over into the main map
						Object.extend(errors, errMap);
					}
				}
			}
		}).bind(this) );
		return errors;
	},
	_clearErrorDiv: function() {
		if( this.errorDivId ) {		
			if( this._checkForErrorDiv(this.errorDivId) )
				Element.update(this.errorDivId, '');
				Element.hide(this.errorDivId);
		} 
		if( this.errorDivMap ) {
			this.errorDivMap.each( (function(moduleErrorDivEntry) {
				this._logDebug(moduleErrorDivEntry.value);
				if( this._checkForErrorDiv(moduleErrorDivEntry.value) ) {
					Element.update(moduleErrorDivEntry.value, '');
					Element.hide(moduleErrorDivEntry.value);
				}
			}).bind(this) );
		}
		// clear any window divs as well
		this.windowLoadedModuleMap.each( (function(entry) {
			var moduleId = entry.key;

			var winErrDiv = $(moduleId+'_vwmWindowErrors');

			if( winErrDiv != null ) {
				Element.update(winErrDiv, '');
				Element.hide(winErrDiv);
			}
		}).bind(this) );
		
	},	
	_checkForErrorDiv: function(divId) {
		if( divId == null ) {
			alert('Error div is null');
			return false;
		}
		if( $(divId) == null ) {
			alert('Error div '+divId+' does not exist in the DOM');	
			return false;
		}
		return true;
	},
	_call: function(moduleIds, callModuleId, methodName, isStatic, options) {
		options = new $H(options);
		this._logDebug('<b>call:</b> callModule='+callModuleId+', method='+methodName);
		var params = options['requestParams'] || new $H();
		// add methodName param
		params[this.MODULE_ID_PARAM] = callModuleId;
		params[this.METHOD_NAME_PARAM] = methodName;
		if( options['validationModuleIds'] ) {
			var ids = options['validationModuleIds'];
			if( ids instanceof String ) {
				var id = ids;
				ids = {};
				ids.push(id);
			}
			params[this.VALIDATION_MODULE_IDS_PARAM] = ids.join(',');
		}
		options['requestParams'] = params;
		var action = isStatic ? 'staticCallAjax' : 'callAjax';
		this._sendAjax(action, moduleIds, options);
		
	},	
	_sendAjax: function(action, moduleIds, options, sendModuleInfo) {
		var formDiv = this._createHiddenForm(action, moduleIds, sendModuleInfo);
		var params = Form.serialize(this._form);
		options = options || {};
		var onComplete = options['onComplete'];
		var waitTime = options['wait'];
		if( onComplete == null ) onComplete = function() {};
		var onCompleteAJAX = function(transport, object) { 
			this._handleAjaxResponse(transport, object, options);
			this._logDebug('Calling onComplete '+onComplete);
			if(waitTime && waitTime > 0) {
				setTimeout((function() {
						onComplete(this._response);
				}).bind(this));
			} else {
				onComplete(this._response);
			}
		}.bind(this);
		
		var otherParams = options['requestParams'];
		if( otherParams != null ) {
			otherParams = new $H(otherParams);
			params = params + '&' + otherParams.toQueryString();
		}
		// put this in to allow for a form submission on a remote method call.
		var form = options['form'];
		if( form != null ) {
			params = params + '&' + Form.serialize(form);
		}
		
		if( options['saveSessionMods'] != null ) {
			params[this.SAVE_SESSION_MODS] = options['saveSessionMods'];
		}
		this._logDebug("<b>sendAjax:</b> url="+this.url+"?"+params);
		new Ajax.Request(this.url, 
			{  
				parameters: params, 
				onComplete: onCompleteAJAX, 
				onException: (function(t,e) {
					this._handleAjaxException(t,e);
				}).bind(this) 
			} 
		);
	},
	_handleAjaxException: function(updater, e) {
		this._logDebug('An Exception occurred in the AJAX call: '+e);
		alert('An Exception occurred in the AJAX call: '+e);
	},
	_handleAjaxResponse: function(transport, object, options) {
		this._logDebug("<b>ajaxResponse:</b> "+transport.responseText);
		if(options['ignoreResponse'] == true) return;
		// display the content that was received from the server
		try {
			if(transport.responseText != null) {
				eval(transport.responseText);
			}
		} catch(e) {
			alert('Error evaluating AJAX response: '+e);	
			this._logDebug('<b>Error:</b> evaluating AJAX response: '+e);	
		}		
		 
		this._clearErrorDiv();
			
		var allErrorsMap = $H();
		if( transport.responseText != null && transport.responseText.indexOf('_vwmAllModuleErrors') != -1 ) {		
			this._setErrors(_vwmAllModuleErrors);
		} else {
			this._setErrors( {
				hasErrors: function() {
					return false;
				}
			});
		}
		var errors = this._errors;
		if( transport.responseText.indexOf('_vwmCallResponse') != -1 ) {
			this._response = _vwmCallResponse;
		} else {
			this._response = null;
		}

		this._logDebug('<b>ajaxResponse:</b> hasErrors = '+errors.hasErrors()+', responseText='+transport.responseText);
		// the var _vwmAllModuleErrors will have been set by the AJAX response evaluation
		if(errors.hasErrors()) {
			// iterate thru the modules 			

			errors.moduleErrors.each( (function(modErrEntry) {
				var moduleId = modErrEntry.key;
				var moduleErrors = modErrEntry.value;				
				var module = this.modules[moduleId];
				if( module != null ) {				
					var allModErrors = this._mergeModuleErrors(moduleErrors);
					this._logDebug('<b>validationResults:</b> module='+moduleId+' fieldErrors = '+allModErrors.inspect().escapeHTML());
					module.validationResults(allModErrors);
				}
				
				this._renderErrors(moduleId, moduleErrors);
			}).bind(this));
		}
	},
	_setErrors: function(errors) {
		this._errors = errors;
	},
	_renderErrors: function(moduleId, errors) {
		var allModErrors = this._mergeModuleErrors(errors);
		
		// we need to check all of the windowLoadedModule lists and see if they contain the
		// moduleId. If so, the key for that list is the name of the parent module, as these
		// lists are tracked by parent moduleId.
		
		var parentWindowModuleId = this._findParentWindowModuleId(moduleId);
		if( parentWindowModuleId != null ) {
			moduleId = parentWindowModuleId;
		}

		if( this._isOpenModuleWindow(moduleId)) {
			// render the errors in the window's div as well as the main div
			this._renderDivErrors(allModErrors, moduleId+'_vwmWindowErrors', false);
		} 
		if( this.errorDivId != null ) {
			this._renderDivErrors(allModErrors, this.errorDivId, true);
		}
		var hasModErrorsDiv = (this.errorDivMap != null); 
		if( hasModErrorsDiv ) {
			var divId = this.errorDivMap[moduleId];
			if( divId != null ) {
				this._renderDivErrors(allModErrors, divId, !hasModErrorsDiv);
			}
		}
	},		
	_findParentWindowModuleId: function(moduleId) { 
		// each map entry is windowParent=>list of child modules
		this.windowLoadedModuleMap.each( (function(entry) {
			var parentId = entry.key;
			var childList = entry.value;
			if( childList.indexOf(moduleId) != -1 ) return parentId;
		}).bind(this) );
		
		return null;
	},
	_mergeModuleErrors: function(moduleErrors) {
		var allModErrors = new $H();
		// first iterate thru the global errors and add them
		var i = 0;
		moduleErrors.globalErrors.each( (function(globalError) {
			allModErrors['globalError'+i] = globalError;
		}).bind(this));
		Object.extend(allModErrors, moduleErrors.fieldErrors);
		
		return allModErrors;
	},
	_renderDivErrors: function(errors, divId, append) {
		this._logDebug('<b>renderDivErrors:</b> divId= '+divId);
		if( divId != null ) {
			this._checkForErrorDiv(divId);
			if(errors.keys().length > 0) {
				var errorsDisplay = this.buildErrorsDisplay(errors);
				if( append ) {
					errorsDisplay = $(divId).innerHTML + errorsDisplay;
				}
				Element.update(divId, errorsDisplay);
				Element.show(divId);
				return;
			} else if(!append){
				Element.hide(divId);
			}
		}
	},		
	_submitPostOrGet: function(moduleIds, options) {
		var formDiv = this._createHiddenForm('submit', moduleIds, options);
		document.body.appendChild(formDiv);
		this._form.submit('submit');
	},
	_createHiddenForm: function(action, moduleIds, options, gatherModuleInfo) {
		options = options || {};
		var formId = options['formId'];
		
		var formDiv = document.createElement("div");
		formDiv.id = formId || 'moduleManagerFormDiv';
		// hide it 
		formDiv.style.display = '';
		this._form = $(formId) || document.createElement("form");
		this._form.method = this.type;
		this._form.action = this.url;
		
		var givenModuleIdsFound = false;
		if(moduleIds == null || moduleIds.length == 0) {
			givenModuleIdsFound = true;
		}
		this.modules.each( (function(moduleEntry) {
			if( moduleEntry.value != null ) {
				var module = moduleEntry.value;
				var valueMap = new $H();
				if( moduleIds.indexOf(module.getId()) > -1 ) {
					givenModuleIdsFound = true;
					if( gatherModuleInfo == null || gatherModuleInfo != false) {
						try {
							if(typeof(module.getAutoValueMap) != 'undefined') {
								Object.extend(valueMap, module.getAutoValueMap());
							}
							Object.extend(valueMap, module.getValueMap());
						} catch(e) {
							alert('Error getting values for '+module.id+': '+e);	
							this._logDebug('<b>Error:</b> getting values for '+module.id+': '+e);
						}
						this._logDebug('<b>Values for '+module.id+':</b> '+valueMap.inspect().escapeHTML());
						this._addDataToHiddenForm(valueMap, module.getId());
					}
					this._createHiddenTextInput(this.SUBMITTED_MODULES_PARAM, module.getId());
				}
			}
		}).bind(this) );

		if(!givenModuleIdsFound) {
			alert('The given moduleId(s):\n\n' +  moduleIds.inspect() + '\n\nwere not found in the list of known modules');
		}
		formDiv.appendChild(this._form);
		
		// add in any global parameters
		this._addDataToHiddenForm(this.globalParams);
		
		this._createHiddenTextInput(this.ACTION_PARAM,action);
		
		this._logDebug("<b>Hidden form:</b> "+Form.serialize(this._form));
		return formDiv;		
	},
	_addDataToHiddenForm: function(valueMap, prefix) {

		if(!valueMap.keys || valueMap.keys().length == 0) {
			return false;
		}
		valueMap.keys().each( (function(key) { 
			var paramName = key;
			if( prefix != null ) {
				paramName = prefix+'_'+key;
			} 
			var value = valueMap[key];
			if( value instanceof Array ){
				// it's an array
				value.each( (function(singleVal) {
					this._createHiddenTextInput(paramName, singleVal);					
				}).bind(this));
			} else {
				// just assume it's a string
				this._createHiddenTextInput(paramName, value); 
			}
		}).bind(this) );

		return true;
	},
	_createHiddenTextInput: function(name, value) {
        var hiddenField = document.createElement("input");
        hiddenField.type = "hidden";
        hiddenField.name = name;
        hiddenField.value = value;
	    this._form.appendChild(hiddenField);
	},
	_badType: function() {
		alert('ModuleManager: '+this.type+' is not a valid type.');
	},
	_isOpenModuleWindow: function(moduleId) {
		var win = Windows.getWindow(moduleId);
		if(win == null ) { 
			return false;
		} else {
			return !win.wasClosed;
		}
	},
	_openModuleWindow: function(moduleId, params, windowOptions) {
		var options = windowOptions || {};
		
		var win = Windows.getWindow(moduleId);
		this._logDebug('win='+win);
		if( win != null ) {
			this._logDebug('win.wasCancelled='+win.wasCancelled);		
		}

		if( win == null || win.wasCancelled ) { 
			// check to see if this was an already cancelled module window
			var cancelledModule = this.cancelledWindowModules[moduleId];
	
			if( cancelledModule != null ) {
				this._logDebug('<b>openModuleWindow</b>: reusing old cancelled window');
				// re-register it now
				this.registerModule(cancelledModule);
			} else {
				Windows.unregister(win);
				win = null;
			}
		}
		
		var showAsModal = options['modal'];
		if(!showAsModal || showAsModal == 'false') {
			showAsModal = false;
		} else {
			showAsModal = true;
		}
		
		var minimumTopPosition = options['minimumTopPosition'];
		
		var withoutWrapper = ( options['loadWithoutWrapper'] != null && options['loadWithoutWrapper'] == true );		

		params = new $H(params);
		params[this.ACTION_PARAM] = withoutWrapper ? 'load' : 'loadWindow';
		params[this.MODULE_ID_PARAM] = moduleId;
		params['rnd'] = Math.random();	
		params = params.merge(this.globalParams);	
		var paramStr = params.toQueryString();
		var refreshFromServer = (options['refreshFromServer'] == true);
		if( win == null ) {
			this._logDebug('<b>openModuleWindow</b>: opening new window');						
			var windowConstructor = options['windowConstructor'];
			var iframe = ( options['iframe'] != null && options['iframe'] == true );
			if( iframe ) {
				options['url'] = this.url+'?'+paramStr;
			}
			if( windowConstructor == null ) {
				// default to our Window
				windowConstructor = (function(winId, winOptions) {
					return this.constructWindow(winId, winOptions);
				}).bind(this);
			}
			win = windowConstructor(moduleId, options);
			this._logDebug('<b>openModuleWindow:</b>'+this.url+'?'+paramStr);

			if( !iframe ) {
				refreshFromServer = true;
			}
		}

		if(refreshFromServer) {		
			new Ajax.Updater(win.getContent(), this.url, { 
				evalScripts: true, 
				parameters: paramStr,
				method: 'post'
			} );
		}
		
		// these will track, in windowLoadedModuleIds, which modules
		// are loaded during the window load process so that we can
		// use the list to submit them in the end.
		this.trackWindowModuleLoad = true;
		this.trackWindowModuleLoadParentId = moduleId;
		this.windowLoadedModuleMap = new $H();
		if( win.resetWindow == true ) {
			new Ajax.Updater(win.getContent(), this.url, { 
				evalScripts: true, 
				parameters: paramStr,
				method: 'post',
				onComplete: (function() { 
					this.trackWindowModuleLoad = false;
					this.trackWindowModuleLoadParentId = null;
				 } )
			} );
		}
		
		win.wasCancelled = false;
		win.wasClosed = false;		
		win.resetWindow = options['resetWindow'];
		win.clearSession = options['clearSession'];		
		win.onClose = options['onClose'];
		win.onCancel = options['onCancel'];
		if( win.onClose == null ) {
			 win.onClose = function() { return true;};				
		}
		win.onClose.bind(this);
		if( win.onCancel == null ) {
			 win.onCancel = function() { return true;};				
		}
		win.onCancel.bind(this);
		
		if(minimumTopPosition && typeof(minimumTopPosition) != 'undefined') {
			win.showCenter(showAsModal, minimumTopPosition);
		} else {
			win.showCenter(showAsModal);
		}
	},
	_closeModuleWindow: function(moduleId, options) {	
		this._logDebug('<b>closeModuleWindow</b>:'+moduleId);
		options = options || {};
		
		var win = this.getModuleWindow(moduleId);
		if(options['resetWindow'] != null && options['resetWindow'] == true ) {
			if( win != null ) {
				win.resetWindow = true;
			}
		}
		
		if( options['validate'] != null && options['validate'] == false ) {
			if( win != null ) {
				win.onCancel();
				if( options['callOnClose'] == null || options['callOnClose'] != false ) {
					var cont = win.onClose();
					if( cont == null || cont != false) {
						win.wasClosed = true;
						win.hide();
					}
				} else {
					win.wasClosed = true;
					win.hide();
				}
			}
			return;
		}

		// validate the module(s) first
		var nestedModuleIds = options['nestedModuleIds'];
		if( nestedModuleIds == null ) {
			nestedModuleIds = $A();
			nestedModuleIds.push(moduleId);
		}
		this.validate(nestedModuleIds,	{ onComplete: (function() {
			if(!this.hasErrors()) {
				if( win != null ) {
					// callOnClose
					if( options['callOnClose'] == null || options['callOnClose'] != false ) {
						// on close can return false and it will keep the window open
						var cont = win.onClose();
						if( cont == null || cont != false) {
							win.wasClosed = true;
							win.hide();
						}
					} else {
						win.wasClosed = true;
						win.hide();
					}
				}
			}
		}).bind(this) } );
	},
	_cancelModuleWindow: function(moduleId, options) {
		options = options || {};
		this._logDebug('<b>cancelModuleWindow</b>:'+moduleId);
		var win = Windows.getWindow(moduleId);		
		win.wasCancelled = true;
		Windows.close(moduleId);
		var resetWindow = win.resetWindow || options['resetWindow'] == null || options['resetWindow'] == false;
		var clearSession = win.clearSession;
		if( clearSession != null ) {
			// pass it on to unregister
			options['clearSession'] = clearSession;
		}
		
		if( resetWindow ){
			this._logDebug('<b>cancelModuleWindow</b>:saving the cancelled window');
			this.cancelledWindowModules[moduleId] = this.modules[moduleId];
		}
		this.unregisterModule(moduleId, options);

		this._callOnComplete(options);				
	},
	_callOnComplete: function(options) {
		var onComplete = options['onComplete'];
		if( onComplete != null ) {
			setTimeout( (function() { 
				onComplete();
			}).bind(this), 100);
		}
	},	
	_logDebug : function(text) {
		if(!this._debug) return;
		Logger.logDebug(text);
	},
	clearDebug : function() {
		Logger.clearDebug();
	},
	getModule: function(moduleId) {
		return this.modules[moduleId];
	}
};

// defines the "abstract" methods that should be implemented by the 
// concrete module
ModuleManager.Module = Class.create();
ModuleManager.Module.prototype = {
	initialize: function(id) {
		this.id = id;
	},
	getId: function() {
		return this.id;
	},
	clearValues: function() {},
	getValueMap: function() {},	
	setValueMap: function(valueMap) {},
	getValue: function() {},
	setValue: function(name,value) {},
	validationResults: function(errorMap) {},
	getErrorMap: function() {}
};