/*- ========================================================================================
Functions to implement "encapsulated" XMLHTTP requests.

        modified by Daniel Hellerstein (danielh@ers.usda.gov)
	using base code of Mike Chambers (mesh@adobe.com)

  svy_CreateXMLHTTP is the "Constructor for the class". It creates "request instances".

       A request instance will send a request to a server, and send the  server's response to  a user supplied "callback" function

  When you instantiate a request instance with this constructor (using svy_CreateXMLHTTP constructor function) you can provide a variable:
             dataURL - the url to request
  This can be changed after instantiation by setting the .dataURL variable.

  Example:

     toget ='/info/mystuff.txt' ;
     GetIt = new svy_CreateXMLHTTP(toget);

  To actually tell the instance to send a request, use the instance's .load() function.
  .load() can take 2 arguments:
        .load('other info',nDo)
      other info: used to identify or explain this request, it is NOT sent to the server
      nDo       : number of times to repeat this request. If _interval_id is not set, this not used (though its value may be returned, after decrementing)

  Example:
     GetIt.load('Retrieving mystuff ',10);

  If you invoke an instance while a prior invocation is still busy, it will refuse to run. To "force" it to "load" (kill the prior request,
  and then send a request)  set the attribute ._checkbusy=false (for the instance) -- see the details below.

  In addition to the arguments that you can specify at instantion and with .load(), there are a number of other attributes that can
  be assigned to the request instance. These can be used to control minor features (such as status display). They can also be
  used to save variables that the callback function can use (see below for the details)

                        --------------------

 The  user-supplied callback (.onDRAW)

  When a response is recieved from the server, or a timeout occurs, the callback function (that the user must supply) is called.
  Four  arguments are provided to the call back function:
       otherInfo,daResults,rcode,status,nth
   where ...
        otherinfo : the  "other info", either set in the ._otherinfo attribute or provided in the first argument to .load()
        daResults : content returned by the server.
        rcode     : status of return (the server's return code)
        status    : a "did the request work" status word.
        nth       : the "nth" repetition. If _ndo not set (see below), this is always equal to 1

   Some of these values depend on which of the 3 kinds of responses occurs: success, timeout, and failure

     If success (server provided a response within the specified time period):
        daResults  : content returned by the server.
                    daResults will be a text string if ._isText attribute is 'true'. Otherwise, an XML object (a node) is returned.
        rcode: The response code (almost always 200, but might be 0)
        astatus : 'OK'
        nth: Which request this is (non-1 only if _ndo is set to something other than 1)

     If a timeout occurred:
        daResults :  'timeout due to server non-response '
        rcode     :   the ._timeout attribute
        status    : 'TIMEOUT'

    If a non-200 response code from server (a failure):
       daResults : 'undefined status'
       rcode     :  non-200 response code returned by server
       status    : 'ERROR'

    Example of a call back function:
      function myCallback(otherinfo,dares,rcode,astatus,nth) {
          var aa=document.getElementById("finals") ;
          if (astatus=='OK') {
                aa.innerHTML=aa.innerHTML + ' \n For ' + otherinfo + ': ' + nth + ' : response= '+ dares);
          } else {
                aa.innerHTML=aa.innerHTML + ' \n Problem  in ' + otherinfo + ': '  + nth + ' : error message= '+ dares);
          }

                       --------------------
Examples:

  Creating an instance (using the svy_CreateXMLHTTP constructor)
       var ml = new svy_CreateXMLHTTP("/mysite/mydoc.html") ;
   or,
       var ml = new svy_CreateXMLHTTP("") ;
       m1._dataURL ='/mysite/mydoc.html';

 Setting the user-supplied "callback" function
      ml.onDraw=myCallback ;               // The "myCallback" function will be called and given the response (otherinfo, daResponse, rcode, status)

 Actually making a request
      ml.load('Help me',6) ;            // 'help me' the "otherInfo", 6 is the "numnber of times to repeat this request"
                                        // the 6 is ignored if you don't use setInterval to run a repetitive sequence

  Running a repetitive sequence
      ml.load('my sequence',6) ;            // 'my sequence' is the "otherInfo", 6 is the "numnber of times to repeat this request"
      m1._interval_id=setInterval("m1.load()",5000) ;      // 5000 is a 5 second pause between repetitions (note that NO arguments are used in the load() )
  This will do the xml request, and then (after waiting 5 seconds), repeat the request. Thus, the only thing you would change on the
  m1._interval_id line is the "5000" (the millisecons to wait between requests)

 Killing a repetitive sequence of requests
     To stop a repetitive sequence of request, use the .kill() function (of the request instance object).
    Example:  m1.kill()

  Other stuff
      m1._showstatus=true      // show statusinfo on the window.status line

  See below for syntax on other attributes. For example, you can use POST instead of GET for making the request.


- ========================================================================================  */

function svy_CreateXMLHTTP(dataURL) {                   // THIS IS THE CONSTRUCTOR  (note use of prototypes above to specify attributes
  if (arguments.length>0 && typeof dataURL != 'undefined') {this._dataURL = dataURL };
}

// ====== Attributes of the request object ==================

// Create default values for several attributes for request objects. They can be overridden when an instance is constructed
// That is, after creating a request object with svy_CreateXMLHTTP, you can explicilty set its attributes using  xxx._attributeName=whatever

// ====  These attributes are user controllable

// the method of the request: GET or POST -- by default, use GET.
svy_CreateXMLHTTP.prototype._method = 'GET';

// The request sent to the server if _method='POST. It is the "body" of a POST request.
// However, this is NOT used if a ? appears in the _dataURL. In this case (a ? in the _dataURL), 
//  the stuff after the ? is used as the "body" of the POST request
svy_CreateXMLHTTP.prototype._postRequest = '';

// default timeout (time to wait for server to respond to a request). NOT the same as "pause between repetitive requests"
svy_CreateXMLHTTP.prototype._timeout=8000  ;

// set to true to show status info in status line
svy_CreateXMLHTTP.prototype._showstatus = false ;

// set to TRUE to suppress a "load" if a request is currently active (HIGHLY recommended if the server will be under any kind of load)
svy_CreateXMLHTTP.prototype._checkbusy= true ;

// Flag indicating what type of content will be returned from server. If True, text content. Otherwise, xml content.
svy_CreateXMLHTTP.prototype._isText=true  ;

//  ?xxx can be  added to all requests (to break caches), and nocache headers set. To suppress this (and possibly get cached content), set =0. To use headers only to bust cache, set to 2
svy_CreateXMLHTTP.prototype._cacheBuster=1 ;

// holder for pointer to setInterval. When several repetitions are desired, this argument MUST recieve the value returned by a setInterval
// (it is used by clearInterval after _ndo repetitions). See above for an example.
svy_CreateXMLHTTP.prototype._interval_id=null  ;

// ====  Optional, user controlled variables

// Optional variables (for storing information about this request). These are also set by the user -- they are never set by these functions.
// Thus, the  names should not be taken too literally (they can be used for whatever purpose you, the programmer, desire)
// They can be used by the callback function to customize what the callback does.

//  Sugggested use: a string to use when saving response to an internal cache. This cache can be used to resolve future requests that are identified by this name
// Typically: '' or 0 means "no save"; otherwise, use the name of the file being  retreived.
svy_CreateXMLHTTP.prototype._cacheName= '' ;

svy_CreateXMLHTTP.prototype._counter=0 ;                // a counter variable?
svy_CreateXMLHTTP.prototype._ID=0 ;                  // an identifier?
svy_CreateXMLHTTP.prototype._test='' ;             // a test variable?
svy_CreateXMLHTTP.prototype._status='' ;          // a status variable?
svy_CreateXMLHTTP.prototype._comment='' ;       // a comment message?
svy_CreateXMLHTTP.prototype._summary='' ;       // a  summary of this request?

svy_CreateXMLHTTP.prototype._variable='' ;       // and several more ...
svy_CreateXMLHTTP.prototype._variable1='' ;
svy_CreateXMLHTTP.prototype._variable2='' ;
svy_CreateXMLHTTP.prototype._variable3='' ;
svy_CreateXMLHTTP.prototype._variable4='' ;
svy_CreateXMLHTTP.prototype._variable5='' ;
svy_CreateXMLHTTP.prototype._variable6='' ;
svy_CreateXMLHTTP.prototype._variable7='' ;


//  ===== These attributes are use internally, and should not be set by the user.

//var to hold an instance of the XMLHTTPRequest object. This is used internally.
svy_CreateXMLHTTP.prototype._request = null;

// flag that this request is in use (i.e.; the "thread is busy")
svy_CreateXMLHTTP.prototype._inuse = false ;

// flag that response has been recieved, and callback function has been called
svy_CreateXMLHTTP.prototype._gotResponse = false ;

// holder for max_wait timeout id (used for wait between repeated requests, set by clearTimeout )
svy_CreateXMLHTTP.prototype._timeoutid=null  ;

// flag that this request is in a wait-between-repetitive-requests timeout (i.e.; the "thread will be aborted when timeout is over")
svy_CreateXMLHTTP.prototype._intimeout = false ;

// flag set if an abort occurred
svy_CreateXMLHTTP.prototype._aborted=false  ;


//  ===== These attributes are use internally, and are typically set as functions of prodedures (rather than explicitily set by the user)

//  === The request sent to the server. Typically this is set using the first argument to the construtor. i.e.: new svy_CreateXMLHTTP('xxx')
svy_CreateXMLHTTP.prototype._dataURL = "";

// Descriptive info on this request. Typicall this is set using first argument of  .load(). As noted above, this is sent to the callback function
svy_CreateXMLHTTP.prototype._otherinfo = " ";

// The number of remaining times to repeat this request -- typically is set as the 2nd argument of .load()
// This will be decremented after a request has been recieved, so this changes as requests are completed.
// _ndo  is used in conjunction with a setInterval -- see above description of repetitive requests
//     In other words, if _interval_id is not used (in particular, is not equal to a setInterval function call)
//     _ndo is not used.
svy_CreateXMLHTTP.prototype._ndo=1 ;

// ====== End of attributes of the request object ==================

/**************** Public APIs **********************/



/* ======
 load is used to send a request to the server, which results in a call to the
 callback function (onDraw). Note use of the timeout.
 arg1 is "otherinfo" -- used to identify/explain this request
 arg2 is "nrepeats" -- the number of times to make this request

arg1 should at least equal ''
arg2 is optional (if not specified, 1 request is made immediately)

=== */

svy_CreateXMLHTTP.prototype.load = function(arg1,arg2)  {

	//set the var so we can scope the callback
	var _this = this;

        if (this._inuse )  {            // if this object is already making a request, either refuse or kill outstanding request
           if (this._checkbusy) {               // checkbusy is true. Then do NOT make another request (but do decrement repetition counter)
                if (this._showstatus) window.status='Busy, ' + this._ndo + ',  can not make request for ' + this._otherinfo ;
                this._ndo-- ;
                if (this._ndo<1 && this._interval_id)  {
                    ff=this._interval_id ;
                    clearInterval(ff) ;
                }
               return true ;                    // in use and checkbusy -- DONT MAKE A REQUEST (let the current one finish)
           }                     // checkbusy is true -- DO NOT make another request if this requester is in use
           if (this._showstatus) {                      // note: the "prior" request is killed below. 
              window.status='Busy, but will abort prior request and issue a new  for ' + this._otherinfo ;  // overrides this instances prior request-processing
           }                            // checkbuxy 
        } else {                // NOT in use 
            if (this._showstatus) window.status='Making a request for ' +  this._otherinfo ; 
        }                               // _inuse 

        //  clear any stuff from pending requests (that this object is undertaking) 
       if (this._intimeout) { clearTimeout(this._timeoutid) ; this._intimeout=false } ; // if timeout active, clear it

        var  t_inuse=this._inuse, L_req=this._request ;
        if (t_inuse && L_req) {L_req.abort() };      // if object in use -- kill it
        if (this._request) delete this._request ;               // cleanup
  
	// cleanup is done ... Now get a new XMLHTTPRequest and make the request (with everything refering to within object functions etc

	this._request = this._getXMLHTTPRequest();             // note we do NOT reuse xmlhttprequest "variables" (hence the delete above)
        this._inuse=true ;
        this._gotResponse=false ;
        this._aborted=false ;

        if (arguments.length>0) this._otherinfo=arg1;                              // arg1 is ONLY set on first call
        if (arguments.length>1) this._ndo=arg2;                                 // arg2 is ONLY set on first call
        if (isNaN(this._ndo)) this._ndo=1 ;                     
       
	//callback will be an anonymous function that calls back into our class
	//this allows the call back in which we handle the response (_onData())
	// to have the correct scope.
        // floob is "which repetition" (of a sequence of repeated requests) this is. If _ndo is never set, this will be 1

        var floob=this._ndo ;
        this._request.onreadystatechange = function(){_this._onData(floob)};


        if (this._method.toUpperCase()=='GET') {
            this._request.open("GET",this._generateDataUrl(0), true);  // gets this._dataURL 
             if (this._cacheBuster!==0) {
                this._request.setRequestHeader("Pragma", "no-cache");
                this._request.setRequestHeader("Cache-Control", "no-cache");
                this._request.setRequestHeader("Connection", "close");
             }
            this._request.send(null);
            if (this._showstatus) {window.status='GET , ' +    _this._ndo + ', request for ' +    _this._otherinfo };
        } else       {                           // post
            var daurl=this._generateDataUrl(1) ;
            var isq=daurl.indexOf('?');
            if (isq<0) {                                // no ? in url. Check _postRequest.
                var daurl1=daurl  ;
                var daurl2=this._postRequest ; 
            } else {                                    // convert GET format to POST format
               var daurl1=daurl.substr(0,isq)       ;
               var daurl2=daurl.substr(isq+1);
            }  
            this._request.open("POST",daurl1, true);  // gets this._dataURL 

             if (this._cacheBuster!==0) {
                this._request.setRequestHeader("Pragma", "no-cache");
                this._request.setRequestHeader("Cache-Control", "no-cache");
             }

            this._request.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            this._request.setRequestHeader("Content-length", daurl2.length);
            this._request.setRequestHeader("Connection", "close");

// suppress caching 
//Pragma: no-cache Cache-Control: no-cache  
            this._request.send(daurl2);
        }                               // get post 
 
        function doTimeout(){   // call this when timeout period is over. Note that this is instance specific (hence the use of _this rather than this)
               var  t_inuse=_this._inuse, L_req=_this._request, t_intimeout=_this._intimeout,  t_timeout=_this._timeout, ainfo=_this._otherinfo  ;
               var t_gotResponse=_this._gotResponse ;
               var t_do=_this._ndo ;
               if (t_inuse && L_req &&  t_intimeout  && !t_gotResponse) {      // object still exists, and is in use, but no response recieved
                  _this._inuse=false;
                  _this._intimeout=false;
                  _this._aborted=true ; this._aborted=true;
                  if (_this._showstatus) {window.status='Timeout for ' + _this._otherinfo };
                  L_req.abort();
 		 _this.onDraw(ainfo,'timeout due to server non-response',t_timeout,'TIMEOUT',t_do);
               }
               return 0 ;
         };             // dotimeout

      if (this._timeout>0) {                                    // set a timeout (after timeout seconds, requset is killed and a "TIMEOUT" is sent to callback fucntion
          this._timeoutid=setTimeout(doTimeout,this._timeout) ; this._intimeout=true 
       } ; // kill request timeout

        this._ndo-- ;
        if (this._ndo<1 && this._interval_id) {                 // no more repetitions to do, and we are actually doing them? Then clearInterval
               ff=this._interval_id ;
               clearInterval(ff) ; 
         }   
         return 1 ;

}


// ========  abort this request, and its sequence (if a repetitive sequence, using _interval_id, was specified)
svy_CreateXMLHTTP.prototype.kill= function()  {
   if (this._intimeout) { clearTimeout(this._timeoutid) ; this._intimeout=false } ; // if timeout active, clear it
   if (this._ndo>0 && this._interval_id) { clearTimeout(this._interval_id) ; this._ndo=1 } ; // clear interval if one exists
   var  t_inuse=this._inuse, L_req=this._request ;
   if (t_inuse && L_req) {L_req.abort() };      // if object in use -- kill it
   this._inuse=false ; this._aborted=true ;
   if (this._request) delete this._request ;               // cleanup
   if (this._showstatus) {window.status='Timeout for ' + this._otherinfo };
}


// PRIVATE callback for when the data is loaded from the server. It will call a user defined callback function (loaded into this instances onDraw variable.

svy_CreateXMLHTTP.prototype._onData = function(arf) {                           // arf is the "repetition number"
       if (this._aborted)  {          // Caution: if aborted, call onDraw with abort message -- reading request info can cause trouble
         delete this._request; 		//clean up
         return true ;
       }

	if(this._request.readyState == 4) {                            // otherwise, do nothing
  	      if(typeof this.onDraw == 'undefined') {
                    alert(' No callback function for ' + this._otherinfo) ;
   	             delete this._request; 		//clean up
                    this._inuse=false ;
                    return 1 ;
              }
              this._gotResponse=true ;

              if (typeof(this)=='object' && typeof(this._request)=='object') {                // legit return
                 try {
                   var tmessage='ok' ;
                   var tstatus=this._request.status ;
                 } catch(e) {
                    var tmessage=e.name+'='+e.message;
                    var tstatus=-1;
                 }
                 if (tstatus == "200" || tstatus=="0"){
                     if (this._isText) {
                          this.onDraw(this._otherinfo,this._request.responseText,tstatus,'OK',arf);
                     } else {                        // xml?
                        this.onDraw(this._otherinfo,this._request.responseXML,tstatus,'OK',arf);
                     }
                  } else {        // call the callback, indicating an error
                    if (tstatus==-1) {   
                         if (E2_writeStatus) E2_writeStatus('XmlRequest warning: no status returned: '+tmessage )
                         this.onDraw(this._otherinfo,'bad object: no status ',0,'ERROR',arf);         
                    } else {
                         this.onDraw(this._otherinfo,'undefined status '+tstatus,tstatus,'ERROR',arf);
                    }
                  }
              } else {                                // bad obhect
                  this.onDraw(this._otherinfo,'bad object ',0,'ERROR',arf);         
              }                                         // this is an object
              delete this._request; 		//clean up
              this._inuse=false ;
	}
}


// PRIVATE return the URL from which the data will be loaded, with cache buster added
svy_CreateXMLHTTP.prototype._generateDataUrl = function(ispost) {
   var aurl=this._dataURL;
   if (this._cacheBuster!==1 || ispost==1) { return aurl }  ; // suppress cache buster (the ?xxx stuff)... 

   var mytime= new Date().getTime();
   var aurl1=(aurl.indexOf('?')<0) ? aurl+'?'+mytime : aurl+'&='+mytime ;
   return aurl1 ;
}

//PRIVATE -- returns an XMLHTTPRequest instance (based on browser)
svy_CreateXMLHTTP.prototype._getXMLHTTPRequest = function() {
	var xmlHttp;
        if (typeof(XMLHttpRequest)=='undefined') {
	try
	{
		xmlHttp = new ActiveXObject("Msxml2.XMLHttp");
	}
	catch(e)
	{
		try
		{
			xmlHttp = new ActiveXObject("Microsoft.XMLHttp");
		}
		catch(e2)
		{
		}
	}
        }
	if(typeof(xmlHttp) == 'undefined' && (typeof XMLHttpRequest != 'undefined'))
	{
		xmlHttp = new XMLHttpRequest();
	}

	return xmlHttp;
}


/* ============= End of svy_CreateXMLHTTP generic functions =========== */

