// requires /js/generictoolkits
// requires /js/date.js
// requires /js/xmlw3cdom.js

var XMLHTTP_DEBUG = false;
var XMLHTTP_RETURN_NULL_ON_ERROR = true;
var VOID_ID = -1;

function getHTTPObject()
{
	var xmlhttp;
	/*@cc_on
		@if (@_jscript_version >= 5)
		try
		{
			xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch (e)
		{
			try
			{
				xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch (e2)
			{
				xmlhttp = false;
			}
		}
		@else
			xmlhttp = false;
		@end
	@*/
	if (!xmlhttp && typeof XMLHttpRequest != 'undefined')
	{
		try
		{
			xmlhttp = new XMLHttpRequest();
		}
		catch (e)
		{
			xmlhttp = false;
		}
	}
	return xmlhttp;
}


/*
 * Class to retrieve a scalar from a WebService, through a POST request
 * Usage: your onScalarRetrievedCallback is called when the scalar is received from the WS.
 * If needed, you may override this.VirtualGetQueryString to provide a querystring for
 * the webservice
 */
function PostScalarImporter(webServiceUrl, webMethodName, recordTagName, onScalarRetrievedCallback)
{
	this.WebServiceUrl = webServiceUrl;
	this.WebMethodName = webMethodName;
	
	this.RecordTagName = recordTagName;
	this.onScalarRetrievedCallback = onScalarRetrievedCallback;
	this.http = null;
}

PostScalarImporter.prototype.VirtualGetQueryString = function()
{
	return "";
}

PostScalarImporter.prototype.OnAfterLoad = function() {}

PostScalarImporter.prototype.SendRequest = function()
{
	if(this.http == null) this.http = getHTTPObject();
	//
	// do a POST request, not a true SOAP request	
	//
	this.http.open("POST", this.WebServiceUrl + '/' + this.WebMethodName , true);
	this.http.onreadystatechange = this.CreateReadyStateChangeFunction.bind2(this);
	this.http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 

	this.http.send(this.VirtualGetQueryString());
}

PostScalarImporter.prototype.CreateReadyStateChangeFunction = function()
{
	if(this.http.readyState != 4) return;
	
	var nodeRecord = this.ExtractPayload(this.http.responseXML);
	this.onScalarRetrievedCallback(Xml2Object(nodeRecord));
	this.OnAfterLoad();
}

PostScalarImporter.prototype.ExtractPayload = function(xmlResponse)
{
	try
	{
		var xmlNode = xmlResponse.firstChild;
		while(xmlNode != null)
		{
			if(xmlNode.nodeName == this.RecordTagName)
			{
				return xmlNode;
			}
			xmlNode = xmlNode.nextSibling;
		}
	}
	catch(e)
	{
		if(XMLHTTP_DEBUG)
		{
			alert("error occured: " + e.message + "\n" +
			      "xmlresponse: " + xmlResponse + "\n" +
			      "http.textresponse: " + this.http.responseText + "\n" +
			      "http.status: " + this.http.status + " " + this.http.statusText);
		}
		else
		{
			if(!XMLHTTP_RETURN_NULL_ON_ERROR) throw "Ophalen van gegevens is mislukt: " + this.http.responseText;
			return null;
		}
	}
	if(!XMLHTTP_RETURN_NULL_ON_ERROR) throw "Ophalen van gegevens is mislukt. Geen data ontvangen.";
	return null;
}

/*
 * END OF PostScalarImporter class
 */



/*
 * Class to import arrays of objects (serialized in XML), through a POST request
 * to a WebService
 * Usage: your functionTransferXmlNodeToDomain is called for every toplevel domain object found in the xml
 * structure. If needed, you may override this.VirtualGetQueryString to provide a querystring for
 * the webservice
 */
function PostArrayImporter(webServiceUrl, webMethodName, recordTagName, functionTransferXmlNodeToDomain)
{
	this.WebServiceUrl = webServiceUrl;
	this.WebMethodName = webMethodName;
	
	this.RecordTagName = recordTagName;
	this.TransferXmlNodeToDomain = functionTransferXmlNodeToDomain;
	this.http = null;
}


PostArrayImporter.prototype.VirtualGetQueryString = function()
{
	return "";
}

PostArrayImporter.prototype.OnAfterLoad = function(numberOfRecords) {}

PostArrayImporter.prototype.ArrayOfRecordsTagName = function()
{
	return "ArrayOf" + this.RecordTagName;
}

PostArrayImporter.prototype.SendRequest = function()
{
	if(this.http == null) this.http = getHTTPObject();
	//
	// do a POST request, not a true SOAP request	
	//
	this.http.open("POST", this.WebServiceUrl + '/' + this.WebMethodName , true);
	this.http.onreadystatechange = this.CreateReadyStateChangeFunction.bind2(this);
	this.http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); 

	this.http.send(this.VirtualGetQueryString());
}

PostArrayImporter.prototype.CreateReadyStateChangeFunction = function()
{
	if(this.http.readyState != 4) return;

	var arrayOfRecords = this.ExtractPayload(this.http.responseXML);
	var recordCount = this.TransferRecordsToModel(arrayOfRecords);
	this.OnAfterLoad(recordCount);
}

PostArrayImporter.prototype.ExtractPayload = function(xmlResponse)
{
	try
	{
		var xmlNode = xmlResponse.firstChild;
		while(xmlNode != null)
		{
			if(xmlNode.nodeName.toLowerCase() == this.ArrayOfRecordsTagName().toLowerCase())
			{
				return xmlNode;
			}
			xmlNode = xmlNode.nextSibling;
		}
	}
	catch(e)
	{
		if(XMLHTTP_DEBUG)
		{
			alert("error occured: " + e.message + "\n" +
			      "xmlresponse: " + xmlResponse + "\n" +
			      "http.textresponse: " + this.http.responseText + "\n" +
			      "http.status: " + this.http.status + " " + this.http.statusText);
		}
		else
		{
			if(!XMLHTTP_RETURN_NULL_ON_ERROR) throw "Ophalen van gegevens is mislukt: " + this.http.responseText;
			return null;
		}
	}
	if(!XMLHTTP_RETURN_NULL_ON_ERROR) throw "Ophalen van gegevens is mislukt.";
	return null;
}

/// Extrac payload nodes and transfer them to the domainmodel
PostArrayImporter.prototype.TransferRecordsToModel = function(xmlArrayOfRecords)
{
	var payLoadNodes = this._ExtractPayloadNodes(xmlArrayOfRecords);
	var payLoadNodesLength = payLoadNodes.length;

	for(var i=0; i<payLoadNodesLength; i++)
	{
			this.TransferXmlNodeToDomain(Xml2Object(payLoadNodes[i]), i, payLoadNodesLength);
	}

	return payLoadNodesLength;
}

/// Compare xmlNodeNames to RecordTagNames to strip out any whitespace nodes etc.
PostArrayImporter.prototype._ExtractPayloadNodes = function(xmlArrayOfRecords)
{
	var payLoadNodes = new Array();
	for(var i=0; i<xmlArrayOfRecords.childNodes.length; i++)
	{
		if(xmlArrayOfRecords.childNodes[i].nodeName == this.RecordTagName)
		{
			payLoadNodes.push(xmlArrayOfRecords.childNodes[i]);
		}
	}
	return payLoadNodes;
}
/*
 * END OF PostArrayImporter class
 */



/*
 * Class to export a record via SOAP
 */
function SoapScalarExporter(webServiceUrl, webMethodName, webMethodNamespace, recordTagName, funcGetXmlRecord)
{
	this.WebServiceUrl = webServiceUrl;
	this.WebMethodName = webMethodName;
	this.WebMethodNamespace = webMethodNamespace;

	this.RecordTagName = recordTagName;
	this.FuncGetXmlRecord = funcGetXmlRecord;
	this.http = null;
}

SoapScalarExporter.prototype.SendRequest = function()
{
	var record = this.FuncGetXmlRecord();
	if(record == null) return;

	if(this.http == null) this.http = getHTTPObject();
	this.http.open("POST", this.WebServiceUrl, false); // false = synchronous operation
	this.http.setRequestHeader('Content-Type', 'text/xml');
	this.http.setRequestHeader('SOAPAction', '"' + this.WebMethodNamespace + this.WebMethodName + '"');

	this.http.send(this._SoapRequest(record));
}

SoapScalarExporter.prototype._SoapRequest = function(xmlRecord)
{
	var parser = new DOMImplementation();
	var xmlSoapBody = '<?xml version="1.0" encoding="utf-8"?>' +
		'<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/">' +
		'<soap:Body></soap:Body>' +
		'</soap:Envelope>';

	var domDoc = parser.loadXML(xmlSoapBody);
	
	var customPropertyFilter = FilterNullIntValue;
	var node = Object2Xml(domDoc, this.RecordTagName, xmlRecord, customPropertyFilter);

	var webMethodNode = this._CreateWebMethodNode(domDoc);	
	webMethodNode.appendChild(node);
	
	return domDoc.getXML();
}
function FilterHtml(str)
{
	return escape(FilterNullIntValue(str));
}

SoapScalarExporter.prototype._CreateWebMethodNode = function(domDocument)
{
	var node = domDocument.createElement(this.WebMethodName);
	node.setAttribute("xmlns", this.WebMethodNamespace);

	var docRoot = domDocument.getDocumentElement();
	var soapBody = docRoot.getElementsByTagName('soap:Body').item(0);
	soapBody.appendChild(node);
	
	return node;
}
/*
 * END OF SoapScalarExporter class
 */

/*
 * Class to export arrays of records via SOAP
 */
function SoapArrayExporter(webServiceUrl, webMethodName, webMethodNamespace, arrayOfRecordsTagName, functionGetArrayOfXmlRecords)
{
	this.WebServiceUrl = webServiceUrl;
	this.WebMethodName = webMethodName;
	this.WebMethodNamespace = webMethodNamespace;

	this.ArrayOfRecordsTagName = arrayOfRecordsTagName;
	this.GetArrayOfXmlRecords = functionGetArrayOfXmlRecords;
	this.http = null;
}

SoapArrayExporter.prototype.SendRequest = function()
{
	var arrayOfXmlRecords = this.GetArrayOfXmlRecords();
	if(arrayOfXmlRecords.length == 0) return;

	if(this.http == null) this.http = getHTTPObject();
	this.http.open("POST", this.WebServiceUrl, false); // false = synchronous operation
	this.http.setRequestHeader('Content-Type', 'text/xml');
	this.http.setRequestHeader('SOAPAction', '"' + this.WebMethodNamespace + this.WebMethodName + '"');

	this.http.send(this._SoapRequest(arrayOfXmlRecords));
}

SoapArrayExporter.prototype._SoapRequest = function(arrayOfXmlRecords)
{
	var parser = new DOMImplementation();
	var xmlSoapBody = '<?xml version="1.0" encoding="utf-8"?>' +
		'<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/">' +
		'<soap:Body></soap:Body>' +
		'</soap:Envelope>';

	var domDoc = parser.loadXML(xmlSoapBody);
	
	var customPropertyFilter = FilterNullIntValue;
	var node = Object2Xml(domDoc, this.ArrayOfRecordsTagName, arrayOfXmlRecords, customPropertyFilter);

	var webMethodNode = this._CreateWebMethodNode(domDoc);	
	webMethodNode.appendChild(node);
	
	return domDoc.getXML();
}
SoapArrayExporter.prototype._CreateWebMethodNode = function(domDocument)
{
	var node = domDocument.createElement(this.WebMethodName);
	node.setAttribute("xmlns", this.WebMethodNamespace);

	var docRoot = domDocument.getDocumentElement();
	var soapBody = docRoot.getElementsByTagName('soap:Body').item(0);
	soapBody.appendChild(node);
	
	return node;
}
/*
 * END OF SoapArrayExporter class
 */



/*
 * converts an xml node to an object with named properties
 */
function Xml2Object(obj)
{
	//
	// note this function does not handle duplicate node names at all
	// (this could be implemented with an array property)
	// 
	if(obj == null) return null;
	
	if(HasOnlyTextChild(obj))
	{
		return obj.firstChild.data;
	}
	
	var result = new Object();
	var childNode = obj.firstChild;

	while(childNode != null)
	{
		if(IsNodeTypeElement(childNode))
		{
			if(HasOnlyTextChild(childNode))
			{
//				result[childNode.nodeName] = childNode.firstChild.data;
				_Xml2Object_AddProperty(result, childNode.nodeName, childNode.firstChild.data);
			}
			else if (childNode.childNodes.length == 0)
			{
				_Xml2Object_AddProperty(result, childNode.nodeName, "");
			}
			else
			{
				_Xml2Object_AddProperty(result, childNode.nodeName, Xml2Object(childNode));
				//result[childNode.nodeName] = Xml2Object(childNode);
			}
		}
	
		childNode = childNode.nextSibling;
	}
	return result;
}

function _Xml2Object_AddProperty(obj, property, node)
{
	if(obj[property] == null)
	{
		obj[property] = node;
		return;
	}
	if(!IsArray(obj[property]))
	{
		var tmp = obj[property];
		obj[property] = new Array();
		obj[property].push(tmp);
	}
	obj[property].push(node);
}

/*
 * Recursively converts a javascript variable (primitive/object/array) to an XML element. Contains several nifty features:
 *  - you must specify a function funcValueFilter which will filter primitive values to your pleasure.
 *    your function should accept one argument and return one value.
 *    function(x) { return x; } would be a sensible start
 *  - if you assign the property XML_TAG_NAME of your object, the value of that property will be used
 *    to name the object.
 *  - if you assign the property XML_ITEM_TAG_NAME of an array object, the value of that property will
 *    be used to name the array elements (unless they have their XML_TAG_NAME property set).
 */
function Object2Xml(domDocument, objectTagName, objContents, funcValueFilter)
{
	var tagName = (IsObject(objContents) && objContents.XML_TAG_NAME) ? objContents.XML_TAG_NAME : objectTagName;
	var xmlNode = domDocument.createElement(tagName);
	
	if(IsArray(objContents))
	{
		var tagName = (objContents.XML_ITEM_TAG_NAME) ? objContents.XML_ITEM_TAG_NAME : objectTagName;
		for(var i=0; i<objContents.length; i++)
		{
			var arrayElement = objContents[i];
			xmlNode.appendChild(Object2Xml(domDocument, tagName, arrayElement, funcValueFilter));
		}
		return xmlNode;
	}
	if(IsDate(objContents))
	{
		xmlNode.appendChild(domDocument.createTextNode(Date2XmlFormat(objContents)));
		return xmlNode;
	}

	if(IsObject(objContents))
	{
		for(var prop in objContents)
		{
			if(prop == "XML_TAG_NAME") continue;
			if(IsFunction(objContents[prop])) continue;
			xmlNode.appendChild(Object2Xml(domDocument, prop, objContents[prop], funcValueFilter));
		}
		return xmlNode;	
	}

	//
	// if we reach here, we assume that objContents contains plain text
	//
	var data = (objContents==null) ? "" : objContents;

	data = funcValueFilter(data);
	xmlNode.appendChild(domDocument.createTextNode(data));
	return xmlNode;
}


function HasOnlyTextChild(xmlNode)
{
	return (xmlNode.childNodes.length == 1 && IsNodeTypeText(xmlNode.firstChild));
}

/*
 * Determines the node type for an xml node
 */
function IsNodeTypeElement(xmlNode)
{
	// xmlNode.nodeType == 1 -> nodeType ELEMENT
	return (xmlNode.nodeType == 1);
}
function IsNodeTypeText(xmlNode)
{
	// xmlNode.nodeType == 3 -> nodeType TEXT
	return (xmlNode.nodeType == 3);
}



/*
 * in C# an integer is a value type. Value types cannot be null. We use the special value VOID_ID
 * to convey null int values. An alternative is to wrap integers in full blown objects (new Integer(int)).
 * that's too much of a hassle
 */
function FilterIntNullValue(intValue)
{
	return (intValue == VOID_ID) ? "" : intValue;
}

function FilterNullIntValue(nullableValue)
{
	return (nullableValue == null || nullableValue == "") ? VOID_ID : nullableValue;
}


/*****************************************************************************/
/*
 * ObjectTypeToolkit
 */
function IsVoid(aThing)
{
	return(typeof(aThing) == "undefined");
}
function IsDate(anObject)
{
	return IsObject(anObject) && (anObject.constructor == Date);
}
function IsArray(anObject)
{
	return IsObject(anObject) && (anObject.constructor == Array);
}
function IsObject(anObject)
{
	return (anObject != null) && (typeof(anObject) == "object");
}
function IsFunction(aThing)
{
	return typeof(aThing) == "function";
}
