/*
This file provides base functionality to implement the XMLHTTPRequests easily.

*/

/*
There are three objects that can be used:

ssbsGetXML	 	- retrieve the XML
ssbsGetOptions	- retrieve an option list into a <select> form field
ssbsTransformWithXSLT	-retrieve the XML and transform it and (optionally) write it to an element.

Each has a basic set of methods that are inherited from ssbsXMLRequestBase
which should never be used


*/



/*
Set up the Request "Registry"
*/
	oRegistry = new reg();

/*
class: ssbsXMLRequestBase

Parameters:

	strName					Name to associate this request with
	XMLreq,					the XMLHTTPRequest object that is currently running
	XMLUrl,					the url that is being retrieved
	resultEle,					the HTML element whose innerHTML will be modified
	funcs						the CallBackFunctions Class to use fire at the various request stages (loading, loaded, interactive, complete)

Methods
	transform
	setXMLUrl
	getXMLUrl
	setName
	getName
	setResultEle
	getResultEle
	getLoading
	getLoaded
	getInteractive
	getComplete

*/

	function ssbsXMLRequestBase()
	{
		this._type = "ssbsXMLRequestBase";

		this.strName 						= null;

		this.XMLreq 						= null;		//XMLHTTP Request Object
		this.XMLUrl 						= null;
		this._postData						=null;
		this._XMLDOM 					= null;


		this.funcs 							= null;		// call back function object



		this.hasXML = function()
		{
			return (this._XMLDOM !== null);
		}

		this.setXMLUrl = function(url) { this.XMLUrl = url; }
		this.getXMLUrl = function () { return this.XMLUrl; }

		this.setName = function(strName) { this.strName = strName;}
		this.getName = function() { return this.strName;}

		this.setResultEle = function(ele) { this.resultEle = ele;}
		this.getResultEle = function() { return this.resultEle;}

		this.setXMLReq = function(req) {this.XMLreq = req;}
		this.getXMLReq = function() {return this.XMLreq; }

		this.hasPostData = function()
		{
			return (this._postData !== null);
		}

		this.setPostData = function(postData) { this._postData = postData; }
		this.getPostData = function () { return this._postData; }
		this.getPostDataSize = function() { return this.getPostData().length; }

		/* add the responseXML from the XMLReq object as the internal XML DOM */
		this.addAsXMLDOM = function()
		{
			this._XMLDOM = this.getXMLReq().responseXML;
			this.XMLreq = null;
		}

		this.setCallBackFunctions = function(fn) {this.funcs = fn;}
		/* Retrieve the CallBack Object.
			If none were specified, return the default progress call back
			if the func parameter is set to null, return false (don't process and callbacks)
		*/
		this.getCallBackFunctions = function()
		{
			if (this.funcs === null) return false;
			if (this.funcs === false)
			{
				this.funcs = new DefaultProgressCallBack();
			}

			return this.funcs;
		}

		/*
		Use this method to override the existing call backs
		This can be used to create an alternative complete function:
			function newComplete(ssbsRequest)
			{ handleDefaultComplete(ssbsRequest); alert("Done!"); }
		*/

		this.setFunction = function(strFnName, strCallBack)
		{
			if (this.getCallBackFunctions())
			{
				if (strFnName == "loadingFunction" ||
					strFnName == "loadedFunction" ||
					strFnName == "interactiveFunction" ||
					strFnName == "completeFunction")
					{
						eval ("this.getCallBackFunctions()." + strFnName + " = strCallBack");
					}
			}
		}
		this.getLoading = function()
		{
			if (this.getCallBackFunctions())
			{
				return this.getCallBackFunctions().loadingFunction;
			}

			return false;
		}

		this.setLoading = function(strCallBackName) {
			this.setFunction("loadingFunction", strCallBackName);
		}

		this.getLoaded = function()
		{
			if (this.getCallBackFunctions())
			{
				return this.getCallBackFunctions().loadedFunction;
			}

			return false;
		}

		this.setInteractive = function(strCallBackName) {
			this.setFunction("interactiveFunction", strCallBackName);
		}

		this.getInteractive = function()
		{
			if (this.getCallBackFunctions())
			{
				return this.getCallBackFunctions().interactiveFunction;
			}

			return false;
		}

		this.setComplete = function(strCallBackName) {
			this.setFunction("completeFunction", strCallBackName);
		}

		this.getComplete = function()
		{
			if (this.funcs)
			{
				return this.funcs.completeFunction;
			}

			return false;
		}

		this.preserveCallBacks = function()
		{
			this._funcs = this.getCallBackFunctions();
			this.setCallBackFunctions(null);
		}

		this.restoreCallBacks = function()
		{
			this.funcs = this._funcs;
			this._funcs = null;
		}

		// blnBlocking set to true will stop this request from being executed.

		this.execute = function(blnBlocking)
		{
			if (oRegistry == "undefined") return false;

			// if this request is already in progress.
			if (blnBlocking && (oRegistry.getAll().length > 0))
			{
				arrReqs = oRegistry.getAll();
				for (var i in arrReqs)
				{
					if (oRegistry.getAll()[i] != null)
					{
						if (oRegistry.getAll()[i].getName)
						{
							if (oRegistry.get(i).getName() == this.getName()) return false;
						}
					}
				}
			}

			// store the XML Request Object
			this.setXMLReq(getNewXMLHttpRequest());

			// add the request to the request registry
			key = oRegistry.add(this);
			// open the connection.
			openConnection(oRegistry.get(key), this.getXMLUrl());
			return key;
		}
	}

	function ssbsTransformWithXSLT()
	{
		this._type = "ssbsTransformWithXSLT";

		this.XSLSuccessUrl 				= null;
		this.XSLFailureUrl 				= null;

		this.resultEle 						= null;		// element to put the results into
		this.progresEle 					= null;		// element to update with the status (optional)

		this._XSLDOM 						= null;
		this.transformedXMLDOM	= null;			//stores the transformed data



		this.transform = function()
		{
			try
			{
				var processor = new XSLTProcessor();
				processor.importStylesheet(this._XSLDOM);

				this.transformedXMLDOM = processor.transformToDocument(this._XMLDOM);
			} catch(e)
			{
			//	addStatus(e.message);
			}
		}


		/* add the responseXML from the XMLReq object as the internal XSL DOM */
		this.addAsXSLDOM = function()
		{
			this._XSLDOM = this.XMLreq.responseXML;
			this.XMLreq = null;
		}

		this.setXSLSuccessUrl = function(url) { this.XSLSuccessUrl = url; }
		this.getXSLSuccessUrl = function () { return this.XSLSuccessUrl; }

		this.setXSLFailureUrl = function(url) { this.XSLFailureUrl = url; }
		this.getXSLFailureUrl = function () { return this.XSLFailureUrl; }

		this.setProgressEle = function(ele) { this.progressEle = ele;}
		this.getProgressEle = function() { return this.progressEle;}
		this.hasProgressEle = function() { return (this.progressEle == null); }

	}

	ssbsTransformWithXSLT.prototype = new ssbsXMLRequestBase();

/*
parameters:
	XSLSuccessUrl		the xslt url to retrieve if the response is valid with data
	XSLFailureUrl,			the xslt url to retrieve if the response is valid and an error

methods:
	hasXML
	addAsXMLDOM
	addAsXSLDOM
	setXSLSuccessUrl
	getXSLSuccessUrl
	setXSLFailureUrl
	getXSLFailureUrl

*/
	function ssbsGetOptions()
	{
		this._type = "ssbsGetOptions";
		this.setCallBackFunctions(new DefaultProgressSelectCallBack());

		this.setSelectEle = function(ele)
		{
			if (ele.type.substring(0,6) == "select")
			{
				this.selectEle = ele;
			}
		}
		this.getSelectEle = function() { return this.selectEle;}
		this.hasSelectEle = function() { return (this.selectEle == null); }

		this.selectEle 					= null;		// element to update with the status (optional)

	}
	ssbsGetOptions.prototype = new ssbsXMLRequestBase();


	function ssbsGetXML()
	{
		this._type = "ssbsGetXML";
	}

	ssbsGetXML.prototype = new ssbsXMLRequestBase();




	function handleXMLResponse(ssbsObj)
	{
		//okay, now we need to decide what to do with the object!
		if (ssbsObj._type == "ssbsTransformWithXSLT")
		{
			if (ssbsObj.hasXML())
			{
				ssbsObj.addAsXSLDOM();
				ssbsObj.transform();

				if (ssbsObj.getResultEle())
				{
					if (ssbsObj.getResultEle().style.display == "none") ssbsObj.getResultEle().style.display = "block";
					ssbsObj.getResultEle().innerHTML = (new XMLSerializer()).serializeToString(ssbsObj.transformedXMLDOM);
				}
				//TODO: raise an error
				ssbsObj.restoreCallBacks();
			}
			else // okay, set up a new XSL request
			{
				ssbsObj.addAsXMLDOM();
				ssbsObj.preserveCallBacks();
				url = (isError(ssbsObj._XMLDOM)) ? ssbsObj.getXSLFailureUrl(): ssbsObj.getXSLSuccessUrl();

				ssbsObj.XMLreq = getNewXMLHttpRequest();
				ssbsObj.setXMLUrl(url);
				// add the request to the request array (registry)
				key = oRegistry.add(ssbsObj);
				openConnection(oRegistry.get(key), ssbsObj.getXMLUrl());

			}

		}
		else if (ssbsObj._type =="ssbsGetOptions")
		{
			//take the select box and clear it!
			while(ssbsObj.getSelectEle().options.length > 0) {
				ssbsObj.getSelectEle().options[0] = null;
			}

			try
			{
				ResXMLDOM = ssbsObj.getXMLReq().responseXML;

				//get the options list
				opt = ResXMLDOM.getElementsByTagName("ssbsOption")[0];
				counter=0;

				for (n = 0; n < opt.childNodes.length;n++)
				{
					itm = opt.childNodes[n];
					if (itm.nodeType == "1") 		// element node, get it's children!
					{

						if (itm.nodeName == "ssbsOptionItem")
						{
							selected = (itm.getAttribute("selected")) ? true : false;

							//add a new option to the select!
							ssbsObj.getSelectEle().options[counter++] = new Option(
										itm.getAttribute("desc"),
										itm.getAttribute("id"),  false, selected);
						}
					}
				}
			}
			catch(e)
			{
				//silently ignore errors
			}

		}
		else if (ssbsObj._type =="ssbsGetXML")
		{
			// do nothing and rely on a custom XML handler to manipulate the resultant data
			return (new XMLSerializer()).serializeToString(ssbsObj.getXMLReq().responseXML);

		}
		else	// unknown type
		{
			throw new TypeError();
		}
		return false;
	}

	function isError(req)
	{
		try
		{
			return (req.getElementsByTagName('ssbsFault')[0]);
		}
		catch(e) { }
		return true;
	}
/*
decides how to handle the XMLRequest State Change.
*/
	function XMLRequestController()
	{
		arrReqs = oRegistry.getAll();

		//for (var i=0; i < arrReqs.length; i++)
		for (var i in arrReqs)
		{
			if (oRegistry.get(i))
			{
				if (oRegistry.get(i).XMLreq)
				{
					if ((oRegistry.get(i).XMLreq.readyState == 1) && (oRegistry.get(i).getLoading()))
					{
						try {
							eval(oRegistry.get(i).getLoading() + "(oRegistry.get(i))" );
						} catch (e) {}

					}

					if ((oRegistry.get(i).XMLreq.readyState == 2) && (oRegistry.get(i).getLoaded()))
					{
						try {
							eval(oRegistry.get(i).getLoaded() + "(oRegistry.get(i))" );
						} catch (e) {}

					}

					if ((oRegistry.get(i).XMLreq.readyState == 3) && (oRegistry.get(i).getInteractive()))
					{
						try {
							eval(oRegistry.get(i).getInteractive() + "(oRegistry.get(i))" );
						} catch (e) {}

					}

					if (oRegistry.get(i).XMLreq.readyState == 4) {
						if (oRegistry.get(i).XMLreq.status == 200 || oRegistry.get(i).XMLreq.status == 304) {
							// 200 OK
							// get response info here before splicing - see below on creating an xml object
							req = oRegistry.get(i);
							handleXMLResponse(req);
							oRegistry.remove(i);

							if (req.getComplete())
							{
								try {
									eval(req.getComplete() + "(req)" );
								} catch (e) {
									alert(e.message);
								}
							}

						} else {
							// error
							oRegistry.remove(i);
						}
					}
				}
			}
		}
	}


	/* **********************************************************************
		Call back functions and default progress handlers
	   ********************************************************************** */

	/*
		Class: 		CallBackFunctions()
	*/
	function CallBackFunctions(strLoading, strLoaded, strInteractive, strComplete)
	{
		this.loadingFunction = strLoading;
		this.loadedFunction = strLoaded;
		this.interactiveFunction = strInteractive;
		this.completeFunction = strComplete;
	}

	// profide a set of default handers for the progress element
	function DefaultProgressCallBack()
	{
		return new CallBackFunctions(
			"handleDefaultLoading",
			"handleDefaultLoaded",
			"handleDefaultInteractive",
			"handleDefaultComplete");
	}

	function DefaultProgressSelectCallBack()
	{
		return new CallBackFunctions(
			"handleDefaultSelectLoading",
			"handleDefaultSelectLoaded",
			"handleDefaultSelectInteractive",
			"handleDefaultSelectComplete");
	}

	/* **********************************************************************
		Default progress handlers
	********************************************************************** */

	function handleDefaultLoading(req)
	{
		if (req.getProgressEle())
		{
			handleXMLProgressMessage(req.getProgressEle(), "Loading ...");
		}
	}

	function handleDefaultLoaded(req)
	{
		if (req.getProgressEle())
		{
			handleXMLProgressMessage(req.getProgressEle(), "Loaded ...");
		}
	}

	function handleDefaultInteractive(req)
	{
		if (req.getProgressEle())
		{
			handleXMLProgressMessage(req.getProgressEle(), "Interactive ...");
		}
	}

	function handleDefaultComplete(req)
	{
		if (req.getProgressEle())
		{
			handleXMLProgressMessage(req.getProgressEle(), "Complete ...");
		}
	}

	function handleDefaultSelectLoading(req)
	{
		if (req.getSelectEle())
		{
			handleXMLProgressSelectMessage(req.getSelectEle(), "Loading ...");
		}
	}

	function handleDefaultSelectLoaded(req)
	{
		if (req.getSelectEle())
		{
			handleXMLProgressSelectMessage(req.getSelectEle(), "Loaded ...");
		}
	}

	function handleDefaultSelectInteractive(req)
	{
		if (req.getSelectEle())
		{
			handleXMLProgressSelectMessage(req.getSelectEle(), "Interactive ...");
		}
	}

	function handleDefaultSelectComplete(req)
	{
		if (req.getSelectEle())
		{
			handleXMLProgressSelectMessage(req.getSelectEle(), "Complete ...");
		}
	}

	/* **********************************************************************
		Update the status
 	********************************************************************** */
 	function handleXMLProgressMessage(ele, str)
	{
		try {
			ele.innerHTML = str;
		} catch(e) {}
	}

	function handleXMLProgressSelectMessage(ele, str)
	{
		try {
				while(ele.options.length > 0) {
					ele.options[0] = null;
				}
				ele.options[0] = new Option(str,'',false,true);
			} catch(e) {}
	}

	// set up the XMLHttp request Object
	// TODO: allow the XMLHander to sent 'loaded' messages After being added to the registry
	// IE 'open()' AFTER adding to registry.

	function getNewXMLHttpRequest()
	{
		var req = new XMLHttpRequest();
		req.onreadystatechange = XMLRequestController ;
		return req;
	}

	function openConnection(req, url)
	{
		try
		{
			if (req.hasPostData())
			{
				req.getXMLReq().open("POST", url,  true);
				req.getXMLReq().setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
				req.getXMLReq().setRequestHeader("Content-length", req.getPostDataSize() );
				req.getXMLReq().setRequestHeader("Connection", "close");
				req.getXMLReq().send(req.getPostData());
			} else
			{
				req.getXMLReq().open("GET", url,  true);
				req.getXMLReq().send(null);
			}
		} catch (e)
		{alert(e.message); }
		return req;
	}

	/* ***************************************************************************
	Create a simple REGISTRY to store the various XML request objects as they are created
	***************************************************************************	*/
	function reg()
	{
		this._reg = Array();

		// Allow adding an object to the registry
		this.add = function(obj)
		{
			return ( this._reg.push(obj) -1);
		}
		// allow removing items from the array.
		// This method preserves the array index.
		this.remove= function(idx)
		{
			try{
				this._reg[idx] = null;
			} catch(e) {}	// silently ignore if the index doesn't exist.
			return false;
		}
		// returns the contents of the registry
		this.getAll = function()
		{
			return this._reg;
		}

		// retrieve a specific index. If the index doesnot exist (previously removed?)
		// then this method silently failes and returns FALSE
		this.get = function(idx)
		{
			try
			{
				return this._reg[idx];
			} catch(e)	// silently end if the idx does not exist.
			{}
			return false;
		}
	}

	function getTransInProgress()
	{
		return "Transaction in progress.\n Please wait ...";
	}

