/* Remoting.js
 *
 * Author: Jonas Jonsson (fatbrain@sphere.se)
 * Copyright (C) 2003-2004 Jonas Jonsson
 * All Rights Reserved 2003-2004
 */

/*
	TODO:
		WebService:
			* Complete the loadWSDL function.
			* Figure out how the encoding works.

*/

/* lame ie check */
var isIE = document.all?true:false;

/* browser compatability check */
if((isIE && !window["ActiveXObject"]) || (!isIE && !(document.implementation && document.implementation.createDocument)))
{
	// Incompatable. The Remoting scripts will not run on this browser.
	alert("ERROR!\n\nYour browser can't handle the WebService script.");
}

function getXmlDocument() { return isIE?new ActiveXObject("Microsoft.XMLDOM"):document.implementation.createDocument("", "", null); }
function getXmlHttpRequest() { return typeof XMLHttpRequest == 'undefined' ? (typeof ActiveXObject == 'undefined' ? null : new ActiveXObject("Msxml2.XMLHTTP")) : new XMLHttpRequest(); };

/*
	Method															Description
	abort()															Stops the current request
	getAllResponseHeaders()							Returns complete set of headers (labels and values) as a string
	getResponseHeader("headerLabel")		Returns the string value of a single header label
	open("method", "URL"[, asyncFlag[,	Assigns destination URL, method, and other optional attributes of a pending request
		"userName"[, "password"]]])	
	send(content)												Transmits the request, optionally with postable string or DOM object data
	setRequestHeader("label", "value")	Assigns a label/value pair to the header to be sent with a request


	Property														Description
	onreadystatechange									Event handler for an event that fires at every state change
	readyState													Object status integer:
																				0 = uninitialized
																				1 = loading
																				2 = loaded
																				3 = interactive
																				4 = complete
	responseText												String version of data returned from server process
	responseXML													DOM-compatible document object of data returned from server process
	status															Numeric code returned by server, such as 404 for "Not Found" or 200 for "OK"
	statusText													String message accompanying the status code
*/

function WebServiceWorkerItem(wsUri, asyncRequest)
{
	var _this = this;
	
	asyncRequest = asyncRequest?true:false;
	
	var isPending = false;
	var fpNotify = null;
	var callObject = null;
	
	var xmlHttp = getXmlHttpRequest();


	function getElementTextNS(prefix, local, parentElem, index)
	{
		var result = parentElem.getElementsByTagName((prefix && document.all?(prefix + ":"):"") + local)[index];
		if(result)
		{
			return result.childNodes[result.childNodes.length > 1?1:0].nodeValue;
		}
		
		return "";		// no-node
	}

	function createJson()
	{
		//alert(xmlHttp.responseText) //DEBUG
		if(xmlHttp.status == 200)
		{
			if(xmlHttp.responseXML.documentElement == null)
				return null;

			var jsonString = xmlHttp.responseXML.documentElement[document.all?"text":"textContent"].toString();
			var oJson = js.deserializeJson(jsonString);
			oJson.toString = function() { return jsonString; }

			return oJson;
		}
		else
		{
			return { statusCode: xmlHttp.status, statusText: xmlHttp.statusText, toString: function() { return this.statusCode + ". " + this.statusText; } }
		}
	}

	this.getCallObject = function()
	{
		return callObject;
	}
	this.getJson = function()
	{
		return createJson();
	}
	this.IsPending = function()
	{
		return isPending;
	}
	
	this.Abort = function()
	{
		if(isPending)
		{
			xmlHttp.abort();
			
			this.EndInvoke();
		}
	}
	this.BeginInvoke = function(_callObject, _fpNotify)
	{
		if(!isPending)
		{
			isPending = true;
			fpNotify = _fpNotify;
			callObject = _callObject;
			
			makeRequest(callObject.functionName, callObject.paramList);
		}
	}
	this.EndInvoke = function()
	{
		isPending = false;
		callObject = null;
		fpNotify = null;
	}
	this.Invoke = function(functionName, paramList)
	{
		if(isPending == false)
		{
			makeRequest(functionName, paramList);
		}
	}
	
	function xmlHttp_Load()
	{
		if(asyncRequest && ((document.all && xmlHttp.readyState == 4) || (!document.all)))
			fpNotify(_this);
	}
	
	function makeRequest(functionName, paramList)
	{
		var queryString = js.createQueryString(paramList);
		
		xmlHttp.open("POST", wsUri + "/" + functionName, asyncRequest);
		if(asyncRequest)
			dom.setProperty(xmlHttp, document.all?"onreadystatechange":"onload", xmlHttp_Load, emptyFunction);
		xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
		xmlHttp.send(queryString);
	}
}

function WebService(Url, functionNames)
{
	var wsUri = Url;
	
	var callID = 0;
	
	var syncWorkItem = new WebServiceWorkerItem(wsUri, false);
	var asyncQueue = new Array();					// request queue
	var pendingRequests = new Array();		// requests currently beeing executed by a workitem
	var asyncWorkItems = new Array();			// workitems used async
	for(var i = 0; i < 3; i++)
		asyncWorkItems.Add(new WebServiceWorkerItem(wsUri, true));

	var wItem = new WebServiceWorkerItem(wsUri, false);

	function getIdleWorkItem()
	{
		for(var i = 0; i < asyncWorkItems.length; i++)
			if(!asyncWorkItems[i].IsPending())
				return asyncWorkItems[i];
		
		return null;
	}

	function webServiceFunctionNotify(workItem)
	{
		var callObject = workItem.getCallObject();

		var Json = workItem.getJson()
		
		pendingRequests.Remove(callObject);
		workItem.EndInvoke();


		if(callObject.fpNotify && typeof(callObject.fpNotify) == "function")
			callObject.fpNotify(Json);
		
		ExecuteQueue();
	}
	
	function ExecuteQueue()
	{
		if(asyncQueue.length > 0)
		{
			var workItem = getIdleWorkItem();
			if(workItem)
			{
				var callObject = asyncQueue.Dequeue();
				callObject.workItem = workItem;
				
				pendingRequests.Add(callObject);

				workItem.BeginInvoke(callObject, webServiceFunctionNotify);
				
				if(asyncQueue.length > 0)
					ExecuteQueue();
			}
		}
	}
	
	function webServiceCall(functionName, paramList, fpNotify)
	{
		if(fpNotify && typeof(fpNotify) == "function")	// async
		{
			asyncQueue.Enqueue({ callID: callID, functionName: functionName, paramList: paramList, fpNotify: fpNotify, workItem: null });
			ExecuteQueue();
			
			return callID++;
		}
		else																						// sync
		{
			syncWorkItem.Invoke(functionName, paramList);
			return syncWorkItem.getJson();
		}
	}
	
	this.Abort = function(callID)
	{
		for(var i = 0; i < asyncQueue.length; i++)
			if(asyncQueue[i].callID == callID)
			{
				asyncQueue.RemoveAt(i);
				return;
			}

		for(var i = 0; i < pendingRequests.length; i++)
		{
			if(pendingRequests[i].callID == callID)
			{
				pendingRequests[i].workItem.Abort();
				pendingRequests.RemoveAt(i);
				
				ExecuteQueue();
				
				return;
			}
		}
	}
	
	// Constructor //
	function webServiceFunction(webService, functionName)
	{
		webService[functionName] = function(paramList, fpNotify)
		{
			return webServiceCall(functionName, paramList, fpNotify);
		}
	}
	
	for(var i = 0; i < functionNames.length; i++)
		new webServiceFunction(this, functionNames[i]);
}

