/*------------------------------------------------------------------------------
* SoftIdiom (c) copyright 2007 Soft Idiom. All rights reserved.
*
* This module provides functions to make SOAP calls to 4DSite Designer web
* sites.
* 
* TODO
*
* 1.03 09/01/2009
* - Fixed bug to decode /n encoding.
* 1.02 30/05/2007
* - Added replaceNode function.
* 1.01 24/05/2007
* - Added Record handling to properties.
* 1.00 26/04/2007
* - First version.
------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------
* SOAP
------------------------------------------------------------------------------*/
var si4d_soap = {
  version:    1.03,
  callIDs:    null,
  serverURL:  null,
  
  /*****************************************************************************
  * Initialise the object.
  * Parameters:
  *   None.
  * Returns:
  *   None.
  ******************************************************************************/
  init: function() {
    this.callIDs = new Object();
  },
  /*****************************************************************************
  * Set the server URL.
  * Parameters:
  *   serverURL - the server URL.
  * Returns:
  *   None.
  ******************************************************************************/
  setServerURL: function(serverURL) {
    this.serverURL = serverURL;
  },
  /*****************************************************************************
  * Call the 4DSite component web service to execute the specified component.
  * Parameters:
  *   serviceURL - the SOAP service URL.
  *   nodeID - the id of the nod that is to be replaced
  *   component - the name of the component.
  *   inProperties - an object holding the input properties for the component.
  *   outProperties - an object holding the required output properties from the 
  *     component. If not defined then 'output' property is used.
  *   requestProperties - an object holding the request properties for the 
  *     component.
  *   inOptions - an object holding additional input properties.
  *   outOptions - an object holding additional output properties.
  *   requestOptions - an object holding additional request properties.
  * Returns:
  *   None.
  ******************************************************************************/
  replaceNode: function(serviceURL, nodeID, component, inProperties, outProperties, requestProperties, inOptions, outOptions, requestOptions) {
    try {
      for(var property in inOptions){ 
        inProperties[property] = inOptions[property]; 
      }
      for(var property in outOptions){ 
        outProperties[property] = outOptions[property]; 
      }
      for(var property in requestOptions){ 
        requestProperties[property] = requestOptions[property]; 
      }
      this.setServerURL(serviceURL);
      var id = this.callComponentService(component, inProperties, outProperties, requestProperties);
      var output = this.collectComponentOutput(id);
      var node = document.getElementById(nodeID);
      if(node != null){ node.innerHTML = output; }
      else{ alert('ajax node not found: '+nodeID); }
    }catch(e){
      alert(e);
    }
  },
  /*****************************************************************************
  * Call the 4DSite component web service to execute the specified component.
  * Parameters:
  *   component - the name of the component.
  *   inProperties - an object holding the input properties for the component.
  *   outProperties - an object holding the required output properties from the 
  *     component. If not defined then 'output' property is used.
  *   requestProperties - an object holding the request properties for the 
  *     component.
  * Returns:
  *   An identifier string for this call or 0 if there is an error.
  ******************************************************************************/
  callComponentService: function(component, inProperties, outProperties, requestProperties) {
    if(this.serverURL == null){ return 0; }
    // build the SOAP message.
    var body = 
      "<?xml version='1.0' ?>"+
      "<env:Envelope xmlns:env='http://www.w3.org/2003/05/soap-envelope'>"+
      "<env:Header>"+
      "</env:Header>"+
      "<env:Body>"+
      "<m:SIService xmlns:m='http://softidiom.com/SIRequest'>"+
      "<m:service name='"+component+"' type='component' />"+
      "<m:inputProperties>";
    for(var property in inProperties){
      var name = property;
      var type = (inProperties[property].type != null) ? inProperties[property].type: "string";
      var value = inProperties[property].value;
      var i = type.indexOf("_array");
      if (i > 0) {
        body += "<m:"+name+" type='"+type+"'>";
        var elementType = type.substring(0, i);
        for(var j = 0; j < value.length; j++) {
          var xmlElementValue = (elementType == "record") ? this._buildRecord(value[j]): this._encodeXML(value[j]);
          body += "<m:element type='"+elementType+"' index='"+j+"'>"+xmlElementValue+"</m:element>";
        }
        body += "</m:"+name+">";
      }
      else {
        var xmlValue = (type == "record") ? this._buildRecord(value): this._encodeXML(value);
        body += "<m:"+name+" type='"+type+"'>"+xmlValue+"</m:"+name+">";
      }
    }
    body += "</m:inputProperties>";
    if(outProperties != null){
      body += "<m:outputProperties>";
      for(var property in outProperties){
        body += "<m:"+property+" />";
      }
      body += "</m:outputProperties>";
    }
    if(outProperties != null){
      body += "<m:requestProperties>";
      for(var property in requestProperties){
        var name = property;
        var value = requestProperties[property];
        body += "<m:"+name+">"+this._encodeXML(value)+"</m:"+name+">";
      }
      body += "</m:requestProperties>";
    }
    body +=
      "</m:SIService>"+
      "</env:Body>"+
      "</env:Envelope>";
    
    // send the message.
    var id = this._createID();
    var options = {
      url:          this.serverURL,
      method:       "POST",
      asynchronous: false,
      contentType:  "text/xml; charset=utf-8",
      encoding:     "UTF-8",
      body:         body
    };
    this.callIDs[id] = si4d_ajax.request(options);
    
    return id;
  },
  /*****************************************************************************
  * Return the specified output from the call to the component 
  * service.
  * Parameters:
  *   callID - the call identifier string.
  *   output - the name of the output.
  * Result:
  *   The specified component output or the empty string.
  ******************************************************************************/
  collectComponentOutput: function(callID, output) {
    if(output == null){ output = "output"; }
    output = "m:"+output;
    var response = this.callIDs[callID];
    var xmlObj = response.responseXML;
    var outputNode = this._getNodeByName(xmlObj, output);
    return this._getNodeValue(outputNode);
  },

  //============================================================================
  // The following functions are private to this object.
  
  /*****************************************************************************
  * Return the named element node from the supplied element node.
  * This function checks the child nodes of the supplied element node looking
  * for the named node. This function calls itself recursively on any child 
  * nodes that are themselves element nodes.
  * Parameters:
  *   node - the element node to search.
  *   nodeName - the name of the node.
  * Returns:
  *   The node or null if not found.
  ******************************************************************************/
  _getNodeByName: function(node, nodeName) {
    for(var i = 0; i  < node.childNodes.length; i++) {
      var childNode = node.childNodes[i];
      if(childNode.nodeType == 1) {
        if(childNode.nodeName == nodeName){ return childNode; }
        var returnNode = this._getNodeByName(childNode, nodeName);
        if(returnNode != null){ return returnNode; }
      }
    }
    return null;
  },
  /*****************************************************************************
  * Return the value of the supplied element node.
  * Parameters:
  *   node - the element node to search.
  * Returns:
  *   The value or the empty string if no value.
  ******************************************************************************/
  _getNodeValue: function(node) {
    var value = "";
    for(var i = 0; i  < node.childNodes.length; i++) {
      var childNode = node.childNodes[i];
      if(childNode.nodeType == 3) { value += childNode.nodeValue; }
    }
    value = value.replace(/__SOFT_fslash_n_IDIOM__/g, '/n');
    return value;
  },
  /*****************************************************************************
  * Return a unique object ID.
  * Parameters:
  *   None.
  * Returns:
  *   The unique id. Puts the id the collection of ids.
  ******************************************************************************/
  _createID: function() {
    var date = new Date();
    var id = date.getTime();
    while(this.callIDs[id] != null){
      // already got this id, so try again.
      var date = new Date();
      id = date.getTime();
    }
    this.callIDs[id] = new Object();
    return id;
  },
  /*****************************************************************************
  * Return a record value as XML.
  * Parameters:
  *   value - the value.
  * Returns:
  *   The XML string.
  ******************************************************************************/
  _buildRecord: function(value) {
    var xml = "";
    for (var property in value) {
      var xmlValue = this._encodeXML(value[property]);
      xml += "<m:"+property+" type='string'>"+xmlValue+"</m:"+property+">";
    }
    return xml;
  },
  /*****************************************************************************
  * Return a string encoded for XML.
  * Parameters:
  *   string - the string.
  * Returns:
  *   The encoded string.
  ******************************************************************************/
  _encodeXML: function(string) {
    if (string.length == 0) { return string; }
    try {
      var strObj = new String(string);
      strObj = strObj.replace(/&/g, "&amp;");
      strObj = strObj.replace(/</g, "&lt;");
      strObj = strObj.replace(/>/g, "&gt;");
    }
    catch(e){
      alert(e);
    }
    return strObj;
  }
}

//-----------------------------------------------------------------------------
// initialize the Soap object.
si4d_soap.init();


