// http://www.devx.com/webdev/Article/28695/1954?pf=true
 
// constants
var HTTP_STATUS_OK = 200;
var READYSTATE_COMPLETED = 4;
var NODE_TYPE_ELEMENT = 1;
var HTML_RESP_NAME = 'htmlResponse';  // When we expect a HTML response rather than XML (only one at a time);

var isFirefox = !(navigator.userAgent.toLowerCase().indexOf("firefox") == -1);

/**
 * This is a quick class to get around JavaScript's poor support for associative arrays. Specifically, 
 * without this class, properties of the object would be included as keys in the associative array. This
 * class ensures that we deal only with array elements we added to the array and not any properties of the
 * container we're using. See Side Bar 1.
 */
function _RCHashtable()
{
   this.data = new Object();
   this.keys = new Array();
   this.indices = new Array();
} 

function _RCHashtable_getKey(raw)
{
   // python style naming convention, to avoid conflicts with actual attributes (see Side Bar 1).
   return '__'+ raw +'__';
} 


function _RCHashtable_get(nam)
{
   var key = this.getKey(nam);
   // retreive value if exists, else null
   var val = (this.data[key]) ? this.data[key] : null;
   return val;
} 

function _RCHashtable_findKey(val)	// val is the index of indices where the key should be stored
{
	if (val != 0 && (!val || isNaN(val) || val < 0)) return null;
	var key = (this.indices[val]) ? this.indices[val] : null;
	return key;
} 

function _RCHashtable_put(nam, val)
{
   // if missing arg
   if (!nam) return false;
   
   var key = this.getKey(nam);
   
   // if key doesn't already exist add to keys array.
   var exists = true;
   if (!this.data[key]) {
      exists = false;
      this.keys[this.keys.length] = key;
   }
   
   // return old value if set, or else null
   var oldval = exists ? this.data[key] : null;
   
   this.data[key] = val;
    
   return oldval;
} 

function _RCHashtable_putInd (nam, val) { //nam is for the key, val is the index in aHtml
   // if missing arg
   if (!nam) return false;
   if (val != 0 && (!val || isNaN(val) || val < 0))	return false;

   this.indices[val] = nam;
   
   return true;
}

function _RCHashtable_keys() {
   // return a copy of the array, otherwise the user of this class could accidentally
   // modify our array, since we&#8217;d be passing them a reference.
   return keys.slice(0);
} 


function _RCHashtable_containsKey(nam) {
   // get() returns null if not found or if found and is null. In both conditions, we want to return false.
   // Otherwise, we want to return true.
   return (this.get(nam) != null);
} 

// creating the class this way is more memory efficient if we are using multiple instances of this data 
// structure. We&#8217;re only using one, so there&#8217;s really  no memory gain. However, I generally use this method
// with data structures for consistency&#8217;s sake.
_RCHashtable.prototype.getKey = _RCHashtable_getKey;
_RCHashtable.prototype.get = _RCHashtable_get;
_RCHashtable.prototype.findKey = _RCHashtable_findKey;
_RCHashtable.prototype.put = _RCHashtable_put;
_RCHashtable.prototype.putInd= _RCHashtable_putInd;
_RCHashtable.prototype.keys = _RCHashtable_keys;
_RCHashtable.prototype.containsKey = _RCHashtable_containsKey;




/**
 * This is the class that deals with remote requests. This implementation uses XMLHttpRequest, but the 
 * idea is that you could swap implementations without changing any code that uses this class.
*/

function RemoteConnection(recurseOnChildren) {
   // array where we&#8217;ll store our request objects
	this.aRequests = new Array();
	this.aRequests[0] = null;
	this.aHtmlRequests = new Array();
	this.aHtmlRequests[0] = null;
	
   this.hRespHandlers = new _RCHashtable();	// our container to map elements to handling functions
   this.isWildcardSet = false;	// flag to track whether a wildcard handler has been set.
   // whether or not to recurse on children of a matched subtree that has already been passed
   // to an appropriate handler function. The code is such because it is an optional parameter and might 
   // not be defined (but if it is defined and isn&#8217;t false then we also know uts value. So we just assign 
   //  it literally.
   
   this.recurseOnChildren = recurseOnChildren ? true : false;
   
   this.request = function(url, method, requestxml, async, html_flg)  {
        // set defaults omitted optional arguments
        if (!method) method = "GET";
        if (!requestxml) requestxml = null;
        
        // request object
        var req = null;
        
        // look for an empty spot in requests array due to a deleted request.
        // default spot is at end
        var openIndex; 
		if (html_flg == 0) {
			openIndex = this.aRequests.length;
			for (var i=0; i < this.aRequests.length; i++) {
				if (this.aRequests[i] == null) {
				   openIndex = i;
				   break;
				} // end if
			}
		 } else {
			openIndex = this.aHtmlRequests.length;
			for (var i=0; i < this.aHtmlRequests.length; i++) {
				if (this.aHtmlRequests[i] == null) {
				   openIndex = i;
				   break;
				} // end if
			}
		 }

          // now make the request, if possible
         if (window.XMLHttpRequest)
         {
            // this might look odd, but it is necessary because &#8216;this&#8217; in event handlers refers to the owner of the 
            // fired event. See: www.quirksmode.org/js/this.html
            var self = this;
            req = new XMLHttpRequest();
			//if (req.overrideMimeType)
			//					req.overrideMimeType('text/xml');
			//		 }            
            if (html_flg == 0) {
            	req.onreadystatechange = function() { self.handleXML() };
				// add the element to the array before doing anything that will fire readyStateChange event. 
				// If we didn&#8217;t do this now, we could be getting event firings from request objects that we 
				// can&#8217;t find in our requsts array, when we  go to handle the readyStateChange.
				this.aRequests[openIndex] = req;
            } else {
            	req.onreadystatechange = function() { self.handleHTML() };
            	this.aHtmlRequests[openIndex] = req;
            	this.hRespHandlers.putInd(HTML_RESP_NAME + html_flg, openIndex); // Store a link between the index where the request is stored and the key in hRespHandlers
            }           
            req.open(method, url, async);
            if (method == "POST")
				req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
			//if (req.readyState > 0 && req.readyState < 4) req.abort(); // kill if 1,2,3
            req.send(requestxml);
         }
         else if (window.ActiveXObject)
         {
            req = new ActiveXObject("Microsoft.XMLHTTP");
            if (req)
            {
               // this might look odd, but it is necessary because &#8216;this&#8217; in event handlers refers to the owner of the 
               // fired event. See: www.quirksmode.org/js/this.html
               var self = this;
				if (html_flg == 0) {
					req.onreadystatechange = function() { self.handleXML() };
					// add the element to the array before doing anything that will fire readyStateChange event. 
					// If we didn&#8217;t do this now, we could be getting event firings from request objects that we 
					// can&#8217;t find in our requsts array, when we  go to handle the readyStateChange.
					this.aRequests[openIndex] = req;
				} else {
					req.onreadystatechange = function() { self.handleHTML() };
					this.aHtmlRequests[openIndex] = req;
					this.hRespHandlers.putInd(HTML_RESP_NAME + html_flg, openIndex); // Store a link between the index where the request is stored and the key in hRespHandlers
				}
				req.open(method, url, async);
				if (method == "POST")
					req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
					
				//if (req.readyState > 0 && req.readyState < 4) req.abort(); // kill if 1,2,3
				req.send(requestxml);
            }
            else
            {
                return false; // indicate an error
            } 
         }
         else
         {
            // no support
            return false; // indicate an error
         } 
            
         return true; // indicate no errors
      }; // end method request
    
    
   this.handleXML = function() {
         // cycle through request objects to see if any are ready with a response. we keep looping even after we find one, 
         // because it might not be the one that fired the event (there could be multiple that are ready).

         for (var i=0; i < this.aRequests.length; i++) {
            // if state is "complete"
            if (this.aRequests[i] != null && this.aRequests[i].readyState == READYSTATE_COMPLETED) {   
				if (isFirefox) {
					try {
						if (this.aRequests[i].status ==  HTTP_STATUS_OK) {
						  // pass this off to the xml parser
						  this.parseResponse(this.aRequests[i].responseXML);
						  // remove object. this is  important because Opera sometimes refires the readyStateChange event.
						  // Plus, this might not be the one that fired the event, and this method might be running 
						  // twice at the same time (setting this to null is about as &#8216;thread safe&#8217; as we can get).
						  this.aRequests[i] = null;
						} 

					} catch (e) {}
				} else {
					if (this.aRequests[i].status ==  HTTP_STATUS_OK) {
					  // pass this off to the xml parser
					  this.parseResponse(this.aRequests[i].responseXML);
					  // remove object. this is  important because Opera sometimes refires the readyStateChange event.
					  // Plus, this might not be the one that fired the event, and this method might be running 
					  // twice at the same time (setting this to null is about as &#8216;thread safe&#8217; as we can get).
					  this.aRequests[i] = null;
					}
				}
            } 
         }
      }; 
      
      //NS_ERROR_NOT_AVAILABLE
      
	this.handleHTML = function() {
		for (var i=0; i < this.aHtmlRequests.length; i++) {
            // if state is "complete"
            if (this.aHtmlRequests[i] != null && this.aHtmlRequests[i].readyState == READYSTATE_COMPLETED) {               
				if (isFirefox) {
					try {

						if (this.aHtmlRequests[i].status ==  HTTP_STATUS_OK) {
							// retrieve the handler.
							// First, get the key:
							var obj = this.aHtmlRequests[i].responseText;
							this.aHtmlRequests[i] = null;
							var key = this.hRespHandlers.findKey(i);
							var funcHandler = this.hRespHandlers.get(key);

							// remove object. this is  important because Opera sometimes refires the readyStateChange event.
							// Plus, this might not be the one that fired the event, and this method might be running 
							// twice at the same time (setting this to null is about as &#8216;thread safe&#8217; as we can get).
							if (funcHandler)
								funcHandler(obj);
						} 
					} catch (e) {  }
				}  else {
					if (this.aHtmlRequests[i].status ==  HTTP_STATUS_OK) {
						// retrieve the handler.
						// First, get the key:
						var obj = this.aHtmlRequests[i].responseText;
						this.aHtmlRequests[i] = null;
						var key = this.hRespHandlers.findKey(i);
						var funcHandler = this.hRespHandlers.get(key);

						// remove object. this is  important because Opera sometimes refires the readyStateChange event.
						// Plus, this might not be the one that fired the event, and this method might be running 
						// twice at the same time (setting this to null is about as &#8216;thread safe&#8217; as we can get).
						if (funcHandler)
							funcHandler(obj);
					} 				
				}
			}
		}
	} 


   this.setRespHandler = function(sElementName, funcHandler, html_flg) { //html_flg - integer, and 0 if XML
         // flip flag if wildcard
         if (sElementName == '*') this.isWildcardSet = true;
		if (html_flg != 0)	sElementName = HTML_RESP_NAME + html_flg;
        
         // add the element handler to the hashtable
         return this.hRespHandlers.put(sElementName, funcHandler);
      }; 
    
    
   this.parseResponse = function(oNode) {
            if (!oNode) return;
            
            // var root = oNode.getElementsByTagName('test').item(0);   
         	// base case (oNode is a leaf element)
         if (!oNode.hasChildNodes()) return;
         
         // else... recurse through children
         var children = oNode.childNodes;
         for (var i=0; i < children.length; i++){
             // all nodes (element, attribute, text, etc.) are returned as children, but we only want to act on children that 
             // are element nodes
             if (children[i].nodeType == NODE_TYPE_ELEMENT) {
                // check to see if a handler exists specifically for this element
                var elementName = children[i].nodeName;
                if ( this.hRespHandlers.containsKey(elementName) ) {
                   // if so, fire handler, and pass subtree starting with the node of interest
                
                   // retreive the handler
                   var funcHandler = this.hRespHandlers.get(elementName);
                   // fire the handler and pass the subtree as its argument
                   funcHandler(children[i]);
                           
                   // a match was found. conditionally recurse on the subtree. Conditionally, because this subtree
                   // has already been handed off to one function.
                   if (this.recurseOnChildren) 
                      this.parseResponse(children[i]);
                }
                else if (this.isWildcardSet) {
                   // retreive the handler
                   var funcHandler = this.hRespHandlers.get('*');
                   // fire the handler and pass the subtree as its argument
                   funcHandler(children[i]);
                   
                   // a match was found. conditionally recurse on the subtree. Conditionally, because this subtree
                   // has already been handed off to one function.
                   if (this.recurseOnChildren)  this.parseResponse(children[i]);
                }
                else {
                   // not match yet found on this subtree. keep digging.
                   this.parseResponse(children[i]);
                }
                
             } 
         } 
      }; 
    
    

} // end class RemoteConnection



// http://www.theurer.cc/blog/2005/12/15/web-services-json-dump-your-proxy/ - modified it a lot!!
// Create javascript

function JSONscriptRequest() {
	// Static script ID counter
	this.scriptCounter = 0;
	this.scriptObj = new Array();
	this.headLoc = document.getElementsByTagName("head")[0];
}

// buildScriptTag method
JSONscriptRequest.prototype.buildScriptTag = function (fullUrl) {
	// Create the script tag
	var scObj = document.createElement("script");
	var scCnt = this.scriptCounter;

  	// Add script object attributes
	scObj.setAttribute("type", "text/javascript");
	scObj.setAttribute("src", fullUrl + '?noCacheIE=' + (new Date()).getTime());

	this.scriptObj[scCnt] = scObj;	
	this.scriptCounter++;
	this.headLoc.appendChild(this.scriptObj[scCnt]);
	

	return scCnt;
}

// removeScriptTag method
JSONscriptRequest.prototype.removeScriptTag = function (scCnt) {
	if (scCnt >= this.scriptCounter || !(this.scriptObj[scCnt]) || this.scriptObj[scCnt] == null)	return this.scriptCounter - 1;
	// Destroy the script tag
	this.headLoc.removeChild(this.scriptObj[scCnt]); 
	this.scriptObj[scCnt] = null;
	if (scCnt == this.scriptCounter - 1)	this.scriptCounter--;
	return this.scriptCounter - 1;
}

JSONscriptRequest.prototype.removeLastScriptTag = function () {
	if (this.scriptCounter == 0)	return 0;
	this.scriptCounter--;
	this.headLoc.removeChild(this.scriptObj[this.scriptCounter]);
	this.scriptObj[this.scriptCounter] = null;
	return this.scriptCounter - 1;
}


