//This code is based on a singleton pattern developed by Douglas Crockford (commonly referred
//to as the 'module pattern').
//This pattern keeps your code out of the global namespace, provides publically addressable
//API methods, and supports protected or 'private' data and methods.
//
//     Usage Overview:
//     * Import the RIA scripts into all relevant html pages.
//     * Call the public web service api methods that are defined in the methods
//       module to initiate a web service call.
//     * Perform any action on the response via the callback _RESPONSE method.

//Setup the module's namespace (typically, the module namespace will be your domain name)
if (!JMT) {
	var JMT = {};
}

JMT.util = function(){
	//private variables and methods:   //can be accessed only from within JMT.util
	var pub;
	var errors = [];
	var doneSessionTimeOut = false;
	// Possible Error Codes and Messages:
	errors[1] = 'This platform does not support XML HTTP communications.';
	errors[2] = 'The security on this platform prevented the sending of the request.'; // (details will contain an error message)
	errors[3] = 'An HTTP error occured during the sending of the request.'; // (details will contain the HTTP error number)
	errors[4] = 'The returned message was not in SOAP format.';// (details will contain the returned message (if any) as a string)
	errors[5] = 'The service returned an error message.';// (details will contain a SOAP fault message either as an XML document or string)
	var defaultHandler = function (errorCode, errorString, details){
		// if you want an exception handler to ignore the error, just have no code here
		if (errorCode == 1 ){
			alert(errorString);
		} else if (errorCode <= 4) {
			alert(errorString + '\n\n' + details);
		} else if (errorCode == 5){
			if (doneSessionTimeOut) {
				return;
			}
			if (typeof(details) == 'string'){
				alert('The web service returned an error message:\n\n' + details);
				return;
			} 
			// The fault code is a SOAP fault code concerning who caused the error
			var faultCode = details.getElementsByTagName('faultcode')[0].firstChild.nodeValue;
			// The fault string contains the error message
			// In a general case this is what a user application would want to deal with
			var faultString = details.getElementsByTagName('faultstring')[0].firstChild.nodeValue;
			// The fault details is an application specific container for error information
			var faultDetails = details.getElementsByTagName('detail')[0];
			if (faultDetails !== null){
				// If the provider is a Jade provider, it will contain the following:
				try {
					var jadeErrorCode = faultDetails.getElementsByTagName('errorCode')[0].firstChild.nodeValue;
					//var jadeErrorText = faultDetails.getElementsByTagName('errorText')[0].firstChild.nodeValue;
					var jadeErrorItem = faultDetails.getElementsByTagName('errorItem')[0].firstChild.nodeValue;
					if(jadeErrorItem) {
					// The provider is Jade
					//alert('The Jade service provider returned an error message.\n\nCode: ' + jadeErrorCode + '\nText: '+ jadeErrorText+'\nItem: '+jadeErrorItem);
						alert('The Jade service provider returned an error message.\n\n' + jadeErrorItem);
					}
					else {
						var jadeErrorText = faultDetails.getElementsByTagName('errorText')[0].firstChild.nodeValue;
						alert('The Jade service provider returned an error message.\n\n' + jadeErrorText);
					}
					if(jadeErrorCode === '11007' || jadeErrorCode === '79987') {
						doneSessionTimeOut = true;
						JMT.startup.doSessionTimeOut();
					}
					return;
				} 
				catch (e){}
			} 
			alert(errorString + '\n\n' + faultString);
		} 
	};
	var DOM_PROG_ID = null;
	var pickRecentProgID = function (idList){
		//the following snippet is from the Sarissa framework (http://sarissa.sourceforge.net)
		var o2Store = null;
		for(var i=0; i < idList.length && o2Store === null; i++){
			try{
				var oDoc = new ActiveXObject(idList[i]);
				o2Store = idList[i];
			}catch (objException){
				// trap; try next progID
			} 
		} 
		return o2Store;
	};
	var IE_XMLHTTP_PROG = pickRecentProgID(["Msxml2.XMLHTTP.6.0", "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"]);
	var stringToXMLDom = function(str,contentType){
		if(typeof(DOMParser) == 'undefined') {
			DOMParser = function() {};
			DOMParser.prototype.parseFromString = function(str, contentType) {
				//the following snippet is from the Sarissa framework (http://sarissa.sourceforge.net)
				var xmldata;
				if(typeof(ActiveXObject) != 'undefined') {
					if(!DOM_PROG_ID){
						DOM_PROG_ID = pickRecentProgID(['Msxml2.DOMDocument.6.0', 'Msxml2.DOMDocument.3.0', 'MSXML2.DOMDocument', 'MSXML.DOMDocument', 'Microsoft.XMLDOM']);
					} 
					xmldata = new ActiveXObject(DOM_PROG_ID);
					xmldata.async = false;
					xmldata.loadXML(str);
					return xmldata;
				} else if(typeof(XMLHttpRequest) != 'undefined') {
					xmldata = new XMLHttpRequest();
					if(!contentType) {
						contentType = 'application/xml';
					} 
					xmldata.open('GET', 'data:' + contentType + ';charset=utf-8,' + encodeURIComponent(str), false);
					if(xmldata.overrideMimeType) {
						xmldata.overrideMimeType(contentType);
					} 
					xmldata.send(null);
					return xmldata.responseXML;
				} 
			}; 
		} else {
			return new DOMParser().parseFromString(str,contentType);
		} 
	};
	var createXMLHttpObject = function(){
		if (window.XMLHttpRequest) {
			// Firefox, Opera, Safari, Chrome
			return new XMLHttpRequest();
		} else if (IE_XMLHTTP_PROG !== null){
			// Internet Explorer
			return new ActiveXObject(IE_XMLHTTP_PROG);
		} 
		alert('This web browser does not appear to be capable of performing XMLHTTP requests.');
	};
	var utilGetSOAPHeader = function(sessionHandler, thenamespace){
		//retrieve the SOAP wrapper details
		var header = '<?xml version="1.0" encoding="utf-8"?>\n<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://tempuri.org/" xmlns:' + thenamespace + '="http://tempuri.org/">';
		if (sessionHandler.sessionID) {
			header = header + '\n<soap:Header><JadeSessionHeader xmlns="urn:JADEWebServices/' + thenamespace + '/"><sessionId>' + sessionHandler.sessionID + '</sessionId></JadeSessionHeader></soap:Header>';
		} 
		header = header + '\n<soap:Body> ';
		return header;
	}; 
	var utilGetSOAPFooter = function(){
		return '\n</soap:Body></soap:Envelope>';
	}; 
	var utilEscape = function(sXml){
		//this code is used when serializing an object to an XML representation.
		//based on code from the Sarissa framework (http://sarissa.sourceforge.net/doc/)
		return sXml.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&apos;');
	};
	var utilMakeNamespace = function(thenamespace,str){
		//append the default web service thenamespace to the supplied string, returning the result
		if (thenamespace !== ''){
			return thenamespace + ':' + str;
		} 
		return str;
	}; 
	var utilXmlize = function(anyObject, objectName, thenamespace, indentSpace){
		//this code will serialize an object to an XML representation (including namespacing).
		//based on code from the Sarissa framework (http://sarissa.sourceforge.net/doc/)
		indentSpace = indentSpace?indentSpace:'';
		objectName = utilMakeNamespace(thenamespace,objectName);
		var isArrayItem = anyObject instanceof Array;
		var s = '\n' + indentSpace + '<' + objectName;
		if ('undefined' !== typeof anyObject && null !== anyObject) {
			for (var attribute in anyObject._attributes) {
				if (!(anyObject._attributes[attribute] instanceof Function)) {
					s += ' ' + attribute + '="' + anyObject._attributes[attribute] + '"';
				} 
			} 
		} 
		s += '>';
		var containsSubElements = false;
		if ('undefined' !== typeof anyObject && null !== anyObject){
			if(!(anyObject instanceof Object) || anyObject instanceof Number || anyObject instanceof String || anyObject instanceof Boolean || anyObject instanceof Date){
				s += utilEscape(''+anyObject);
			}else{
				for(var name in anyObject){
					if (!(anyObject[name] instanceof Function) && '_attributes' !== name) {
						containsSubElements = true;
						s += utilXmlize(anyObject[name], (isArrayItem ? 'array-item' : name), thenamespace, indentSpace + '\t');
					} 
				} 
			} 
		} 
		s += (containsSubElements ? '\n' + indentSpace : '') + '</' + objectName + '>';
		return s;
	};
	var utilRemoveWhitespace = function(xml) {
		//this code will remove empty whitespace nodes from the supplied xml
		var loopIndex;
		for (loopIndex=0;loopIndex<xml.childNodes.length;loopIndex++) {
			var currentNode=xml.childNodes[loopIndex];
				if (currentNode.nodeType==1) { 
					utilRemoveWhitespace(currentNode);
				} 
			if (/^\s+$/.test(currentNode.nodeValue) && (currentNode.nodeType==3)) {
				xml.removeChild(xml.childNodes[loopIndex--]); 
			} 
		} 
	}; 
	var sendXHR = function(soapfragment,resultElement, sessionHandler, endpoint,thenamespace,callback, preprocessorOverride, exceptionhandlerOverride, invocationArguments){
		function invokeExceptionHandler(errorCode, text, details){
			// check if they have overridden exception handling behaviour on this particular call
			if (exceptionhandlerOverride === false){
				// do nothing
			} else if ('function' === typeof exceptionhandlerOverride){
				exceptionhandlerOverride(errorCode, text, details, invocationArguments);				
			} else if ('function' === typeof pub.exceptionHandler){
				pub.exceptionHandler(errorCode,text,details,invocationArguments);
			} else if (pub.exceptionHandler === false){
				// do nothing
			} else if (null === pub.exceptionHandler) {
				defaultHandler(errorCode,text,details);
			} 
		} 
		function invokePreprocessor(payload){
			var retVal = false;
			// invoke preprocessing if its set up
			if (preprocessorOverride === false){
				// do nothing
			} else if ('function' === typeof preprocessorOverride){
				retVal = preprocessorOverride(payload, invocationArguments);
			} else if ('function' === typeof pub.preprocessor){
				retVal = pub.preprocessor(payload,invocationArguments);
			} 
			return retVal; // returning true prevents the normal callback from being processed
		} 
		//create an XMLHttpRequest object to communicate with the web service
		var xhr = createXMLHttpObject();
		if (!xhr) {invokeExceptionHandler(1,errors[1],null);}
		try {
			xhr.open('POST', endpoint, true);
		} 
		catch (err) {
			invokeExceptionHandler(2, errors[2], err);
			return;
		} 
		xhr.onreadystatechange = function(){
			if (xhr.readyState != 4) {return;}
			if (xhr.status != 200 && xhr.status != 304){
				pub.incomingSoap = xhr.responseText;
				if (xhr.status != 500) {
					invokeExceptionHandler(3, errors[3], xhr.status);
				} else {
					// the HTTP 500 response indicates a SOAP error fault is being returned
					invokeExceptionHandler(5, errors[5], xhr.responseXML || xhr.responseText);
				} 
				return;
			} 
			pub.incomingSoap = xhr.responseText;
			//clean the responseXML of empty whitespace nodes (makes manipulation easier)
			if (xhr.responseXML === null) {
				invokeExceptionHandler(4,errors[4], xhr.responseText);
				return;
			} 
			utilRemoveWhitespace(xhr.responseXML);

			//take the response payload that we are interested in out of the soap message...
			var payload  = xhr.responseXML.getElementsByTagName(resultElement)[0];
			//...grab appropriate session information if it exists...
			try {
				sessionHandler.sessionID = xhr.responseXML.getElementsByTagName("JadeSessionHeader")[0].getElementsByTagName("sessionId")[0].firstChild.nodeValue;
			} catch (e) {
				sessionHandler.sessionID = null;
				// We don't have a valid session. So invoke the sessionTimeout callback instead
				// of the normal callback, if there is one.
				if ('function' === typeof sessionHandler.onSessionTimeout) {
					sessionHandler.onSessionTimeout(payload);
					return;
				} 
			} 
			//...and process using the registered callback method
			if (!invokePreprocessor(payload)){
				// Both the preprocessor and callback are passed parameters:
				// xmldata - XML DOM structure
				// invocationArguments - an array containing mixed types of the arguments passed to the createRequest function when the call was initiated
				if(callback && 'function' === typeof callback) {
					callback(payload, invocationArguments);
				}
			} 
		}; 
		//make the request - track and test the requests onreadystatechange event to know when the request has
		//completed and received a response
		xhr.setRequestHeader( 'Content-Type','text/xml');
		var soapMessage = utilGetSOAPHeader(sessionHandler, thenamespace) + soapfragment + utilGetSOAPFooter();
		pub.outgoingSoap = soapMessage; // make the soap message being sent public
		pub.incomingSoap = '';
		xhr.send(soapMessage);
	}; 


	pub = {
		//the following section returns publicly accessable properties and methods
		//public variables can be accessed as this.myPublicProperty from within public methods
		//public methods can be accessed as pub.publicMethodName

		incomingSoap: '', // This will contain a copy of the most recently received soap message
		outgoingSoap: '', // This will contain a copy of the most recently sent soap message
		preprocessor: null, /* If you set this to be a function, it will be called when all webservices return
		prior to the normal callback function being invoked. Return true from your preprocossing function
		if you want to prevent the normal callback function being subsequently invoked. */		
		exceptionHandler : null, /* This specifies the exception handler to be used if there is a problem
		with the soap message. Either pass a function to set up a new handler,
		null to return to the default handler or false to set no handler at all (errors will be ignored).*/

		convertTextToXMLDOM: function(text) {
			//user may have encoded xml tags into the response text, turn into xml dom
			var xml = stringToXMLDom(text,'text/xml');
			utilRemoveWhitespace(xml);
			return xml;
		}, 

		xmlSerialize: function(node){
			//serialize an XML Document or element and return it as a string
			if (typeof XMLSerializer != 'undefined') {
				return new XMLSerializer().serializeToString(node);
			} else if (node.xml) {
				return node.xml;
			} else {
				return 'utilXMLSerialize is not supported or cannot serialize the node';
			} 
		}, 

		convertObjectToXml : function(anyObject, objectName){
			return utilXmlize(anyObject, objectName,'JMT');
		},

		textToHtml: function(text){
			return utilEscape(text);
		},

		createRequest: function(methodName, paramObject, sessionHandler, endpoint, thenamespace, callback, preprocessorOverride, exceptionhandlerOverride){
			// convert the arguments given to this function into a real array.
			var invocationArguments = Array.prototype.slice.call(arguments);
			//create a thenamespaced soapfragment for the requested webservice method call...
			var soapfragment  = utilXmlize(paramObject, methodName, thenamespace);
			var resultElement = methodName + 'Result';
			//...make the call
			sendXHR(soapfragment,resultElement,sessionHandler,endpoint,thenamespace,callback, preprocessorOverride, exceptionhandlerOverride, invocationArguments);
		}, 
		
		getDoneSessionTimeOut : function(){
			return doneSessionTimeOut;
		},
		
		setDoneSessionTimeOut : function(isDone){
			doneSessionTimeOut = isDone;
		}
	};
	return pub;
}(); // the parens here cause the anonymous function to execute and return

