/*
 * © 2006 Craig Nuttall - All rights reserved.  Released under the GNU Public License.
 */

/*

not sure if this is still a problem with dynamic script tags
	NOTE: there's a known problem when suppling a URL that contains a character such as %0D (carriage return); it's up to the 
		developer to escape this properly

*/

/*

i think some tweaking is in order
need document that i can't think of a decent "is_aware" test
	+ what about just seeing if i can make a tag and not die?
		+ not great b/c doesn't really show it connecting to anything remote
			+ but i guess that's not a big deal

see if the note about carriage return is legit for DS also

*/



/*----------------------------------------------------------------------------\
|                      GLOBALS (a necessary evil)
\----------------------------------------------------------------------------*/

// maintain an array of connections in play solely for the purpose of keeping setInterval happy b/c it can't operate by reference in FF (string only)
//	we'll also use this maintaining multiple instances
var cows_ajax_connections ;





/*----------------------------------------------------------------------------\
|                      REQUEST QUEUE DEFINITION
\----------------------------------------------------------------------------*/

function request_queue_node( url, no_display)
{
/*

we can do stats on time from request made to time finish; and also of time just in the process queue (so we can know when things are getting backed up)

*/
	this.connection_url = url ;
	this.no_display = no_display ;
	this.request_sent = false ;
} ;



/*----------------------------------------------------------------------------\
|                      WEB SERVICE DEFINITION
\----------------------------------------------------------------------------*/

// the definition
function cows_ajax( params)
{
	// init the comm array if not done so already
	if (!cows_ajax_connections)
	{
		cows_ajax_connections = new Array() ;
	}

	// constants
	this.version = '5.0' ;
	this.INTERVAL_PERIOD = 1000 ;					// strongly suggested that you increase this to 1000 if debugging!!
	this.polling_debug_throttle = 100 ;				// show a debug message after every this many polling iterations
	this.TIMEOUT_PERIOD = 20000 ;					// if a connection goes on for longer than this many milliseconds, then timeout
													//   the default is 20 seconds


	///
	/// params - these were sent to us in the JSON object at instantiation time
	///

	// url we're connecting to
	this.server_url = (params.server_url) ? params.server_url : '' ;

	// define what the expected helper functions external to the class will be
	this.success_handler = this.determine_handler( params.success_handler, 'success') ;
	this.failure_handler = this.determine_handler( params.failure_handler, 'failure') ;
	this.display_processing_message = params.display_processing_message ;
	this.debug_handler = params.debug_handler ;


	// initialize the communication gateway
	this.script_object = null ;
	this.request_queue = new Array() ;
	this.connection_id = -1 ;
	this.polling_debug_count = 0 ;
	this.polling_debug_first = true ;
	this.interval = null ;
	
// can't put a debug call in here b/c the page might not be fully loaded yet and something like a debug div may not exist yet
//	this.debug('web service object created') ;

};


// make sure required handlers are defined, or at least set to 'none'; throw an error if they are not defined
cows_ajax.prototype.determine_handler=function( param_handler_setting, type)
{
	if (param_handler_setting)
	{
		return (param_handler_setting == 'none') ? null : param_handler_setting ;
	}
	// neither a custom or global handler is defined, so set to null
	else
	{
		throw new Error("ERROR: undefined " + type + " handler function.  Connection not established") ;
	}
};



/*----------------------------------------------------------------------------\
|                      TESTING COWS AJAX AWARENESS
\----------------------------------------------------------------------------*/


// test to see if this stuff will fly - this function is called by the MAIN app, not the cows_ajax object
cows_ajax.prototype.check_cows_ajax_aware=function( success_command, fail_command)
{
	/*
		TO DO: NEED A REAL TEST FOR THE DYNAMIC SCRIPT FUNCTIONALITY:
			1) would be nice to avoid connecting to remote url
			2) would be ideal if we could create a dynamic one-liner and have that comm back
			3) it could be fine to not have a test at all and just have good failure handling especially for
				the first communication
			4) i really do like the idea of an awareness check
			
		this was much more important when we had the iframe implementation... but now.... not so much!
	*/
};







/*----------------------------------------------------------------------------\
|                      MAKING THE CONNECTION
\----------------------------------------------------------------------------*/


// dynamically create the script object with the specified source
cows_ajax.prototype.create_script_object=function( source)
{
	// do a quick sanity check
	if (this.script_object != null)
	{
		this.debug('WARNING: attempting to create a script object when the current object was not reinitialized.') ;
	}

	// create the script object pointing to the desired source
	this.debug('create script source=' + source) ;
	this.script_object = document.createElement('SCRIPT') ;
	this.script_object.src = source ;
	this.script_object.type = 'text/javascript' ;
	var head=document.getElementsByTagName('HEAD')[0] ;
	head.appendChild( this.script_object);
}


// clean up after ourselves
cows_ajax.prototype.remove_script_object=function()
{
	this.script_object.parentNode.removeChild(this.script_object) ;
	this.script_object = null ;
}


// create the initial tag that will be communicating with the server
cows_ajax.prototype.connect=function( params)
{
	this.debug('connect') ;

	// assign our object to a global var so that setInterval can reference it... setInterval param can't take reference, only a string
	this.connection_id = cows_ajax_connections.length ;
	cows_ajax_connections.push( this) ;

	
	// create the comm script and make the connection
	var source = this.build_connection_url( params) ;
	this.debug('attempting contact') ;
	this.create_script_object( source) ;

	// start polling to establish queue for further communications
	this.server_timeout_time = this.now() + this.TIMEOUT_PERIOD ;
	this.start_polling() ;
};


// build the url for the script tag
//   this function was FAR more important with iframe but now really just appends an id and adds to the queue
cows_ajax.prototype.build_connection_url=function( params)
{

//IS THIS NOTE STILL ACCURATE? - meh, someone will let me know ;-)
	/// NOTE: there's a known problem when suppling a URL that contains a character such as %0D (carriage return); it's up to the 
	///		developer to escape this properly

	this.debug('build_connection_url') ;

	query_param = params.query_param ;
	no_display = (params.no_display) ? params.no_display : false ;

	// add the query param to the server url
	var url = this.server_url + query_param + '&connection_id=' + this.connection_id ;
	
	this.debug('built url: [' + url + ']') ;

	this.request_queue[this.request_queue.length] = new request_queue_node( url, no_display) ;

	// return the connection url
	return url ;
};



/*----------------------------------------------------------------------------\
|                      MAKE REQUESTS AND HANDLE RESPONSES
\----------------------------------------------------------------------------*/

// called by the application to put a request into the queue
cows_ajax.prototype.send=function( params)
{
	this.debug('send') ;

	this.build_connection_url( params) ;

	// if we had stopped polling then start it back up again
	this.start_polling() ;
};


// doesn't worry about polling and calls make_request immediately
//	also dangerous b/c it doesn't check to see if we already have an outstanding request
cows_ajax.prototype.send_immediate=function( params)
{
	this.debug('send_immediate') ;

	this.build_connection_url( params) ;

//would need to make use of this.request_sent if polling was active or else we would probably get a dupe
	// polling be damned, make the request
	this.make_request() ;
};


// communicate with server; this function is called by the polling function when it's been determined safe to make a request
cows_ajax.prototype.make_request=function()
{
	this.debug('make_request') ;

	// perform a sanity check to make sure there is a request in the queue
	if (this.request_queue.length <= 0)
	{
		this.debug('WARNING: attempted to make request without anything in the request queue!')
		return ;
	}
	
	
	// if a display handler was defined, turn it on
	if ((this.display_processing_message) && (!this.request_queue[0].no_display))
	{
		this.display_processing_message('on');
	}
	
	// get the object and build the url
	var connection_code = this.request_queue[0].connection_url ;

	// clear the previous reference to the callback function
	parent.cows_ajax_response = null ;

	// call the ball
	this.debug('--------------------------------') ;
	this.create_script_object( connection_code) ;
	this.debug('make_request - request made') ;
	this.debug('--------------------------------') ;


	// record the time it will be when we should throw a timeout
	this.server_timeout_time = this.now() + this.TIMEOUT_PERIOD ;
};


// called by the server when the call is completed
cows_ajax.prototype.response_received=function()
{
	this.debug('response_received') ;

	// turn off any processing que
	if (this.display_processing_message)
	{
		this.display_processing_message('off') ;
	}

	// pop off the request we just made so we can free up the next one
	this.request_queue.shift() ;

	// reset the server timeout so we don't trigger it
	this.server_timeout_time = null

	// remove the script tag
	this.remove_script_object() ;

	// all done, so if there was some graphical display we're supposed to do, then go for it
	if (this.success_handler)
	{
		this.success_handler() ;
	}
};




/*----------------------------------------------------------------------------\
|                      POLLING
\----------------------------------------------------------------------------*/

// kick-off setInterval and poll until told to do otherwise
cows_ajax.prototype.start_polling=function()
{
	this.debug('start_polling') ;

	// if not already set, then kick off the interval
	if (this.interval == null)
	{
		this.debug('polling (re)started') ;
		this.interval=window.setInterval( 'cows_ajax_connections[' + this.connection_id + '].poll()', this.INTERVAL_PERIOD);
	}
};


// this is the poll function that will be run each interval
cows_ajax.prototype.poll=function()
{

	// display our debug message, but don't overwhelm the debugger with messages
	if (this.polling_debug_count == this.polling_debug_throttle)
	{
		this.debug('poll [' + this.now() + '] (x' + this.polling_debug_count + ')') ;
		this.polling_debug_count = 0 ;
	}
	// print a message the first time through
	else if ( this.polling_debug_first)
	{
		this.debug('poll [' + this.now() + ']') ;
		this.polling_debug_first = false ;
		// reset so we can catch our breath
		this.polling_debug_count = 0 ;
	}
	// increment the count
	this.polling_debug_count++ ;


	// handle a response timeout
	if (this.server_timeout_time)
	{
		// we timed out
		if (this.server_timeout_time<=this.now())
		{
			this.stop_polling();
			if (this.failure_handler)
			{
				this.failure_handler('Server Timed Out');
			}
		}
		// success!
		else if (parent.cows_ajax_response != null)
		{
			parent.cows_ajax_response() ;
		}
	}
	// if there's nothing currently being sent, then fire off the next request
	else if ( this.request_queue.length > 0)
	{
		this.make_request() ;
	}
};




/*----------------------------------------------------------------------------\
|                      STOPPING AND DISCONNECTING
\----------------------------------------------------------------------------*/

// purge any request that were in the queue; yup they're gone for good so the app better have a good reason for getting to this point
cows_ajax.prototype.flush_queue=function()
{
	this.debug('flush_queue') ;

	this.request_queue.length = 0 ;	
};


// stop polling
cows_ajax.prototype.stop_polling=function()
{
	this.debug('stop') ;

	this.server_timeout_time = null ;
	this.flush_queue() ;

	// clear the interval so we no longer call the polling function
	if (this.interval)
	{
		window.clearInterval( this.interval);
	}
	this.interval = null ;
};


// stop polling and remove iframe from DOM tree
cows_ajax.prototype.disconnect=function( remove_iframe)
{
	this.debug('disconnect') ;

	this.stop_polling() ;

	// script tag should already been cleaned up, so don't bother removing it
};



/*----------------------------------------------------------------------------\
|                      UTILS
\----------------------------------------------------------------------------*/

// get the current time stamp
cows_ajax.prototype.now=function()
{
	return (new Date()).getTime();
};


// call the function that handles debug messages if one was defined
cows_ajax.prototype.debug=function(message)
{
	if (this.debug_handler)
	{
		this.debug_handler(message) ;
	}
};

