/**
 * (./) xmlHttpRequest.js, v0.1, 27/04/2007 21:14
 * (by) cousot stephane @ http://www.ubaa.net/
 * (cc) some right reserved
 *
 * Javascript object to communicate with server-side scripting languages and transfer XML or other
 * data to and from a web server using HTTP.
 *
 * 		usage:	new XHR( [callback_function,[options]] );
 *		------
 *		properties :
 *				- method	// the method use to send the request
 *				- url		// the target CGI address
 *				- async		// working asynchronous or not		--> TRUE by default
 *				- as_xml	// response as XML document or not	--> FALSE by default
 *				- id		// to store useful property
 *
 *		functions :
 *		(see the code for more details about each function)
 *				- options( method, url, async );		// set the open() request arguments
 *				- send( querystring );					// send a request
 *				- abort();								// stop the current request
 *				- timeout( ms );						// set the request timeout (default 1 min)
 *				- callback( function );					// set the callback method for sucess
 *				- loader( function );					// set the callback method for progress
 *				- error( function, long_message );		// set the callback method for errors
 *				- headers( name, value );				// set a new header
 *				- xml( boolean );						// set to response XML document (or not)
 *
 * This Object is released under a Creative Commons Licence BY
 * ‹ http://creativecommons.org/licenses/by/3.0/ ›
 */
 

 
/**
 * Manages the communication between JS and the other languages. To define the default otions, pass
 * your own values as Object { property_name: value, property_name: value, … } like the code below :
 *
 *		{ method: 'POST', url: 'treatment.php', async: true, as_xml: false }
 * 
 * @param Function	[ the function to be call on success ]
 * @param Object	[ the default options used to send the request ]
 */
function XHR( func, opts ) {

	// set the default timeout value a bit less than 1 min for overriding
	// the xmlHttpRequest timeout
	var DEFAULT_TIMEOUT_VALUE = 58000;
	
	
	// define public properties
	this.method		= opts ? opts['method'] : null;						// the requets method used
	this.url		= opts ? opts['url'] : null;						// the target CGI address
	this.async		= opts&&opts['async'] ? opts['async'] : true;		// working asynchronous
	this.as_xml		= opts&&opts['as_xml'] ? opts['as_xml'] : false;	// require XML document
	this.id			= null;
	
	// register public methods
	this.send		= send;
	this.options	= options;
	this.headers	= headers;
	this.callback	= setCallback;
	this.loader		= setLoader;
	this.error		= setError;
	this.timeout	= delay;
	this.abort		= abort;
	this.xml		= xml;
	
	
	// -- private object --
	var __xhr		= transport();				// the XMLHttpRequest Object
	var __callback	= func;						// the callback method call on success
	var __loader	= null;						// the callback method call on loading
	var __error		= null;						// the callback method call on error
	var __errlong	= false;					// require long error message (if possible) or not
	var __tId		= null;						// the timeout ID
	var __tMs		= DEFAULT_TIMEOUT_VALUE;	// the timeout delay
	var __exception	= null;						// customized Error
	var __xml		= this.as_xml;				// require XML document
	var __headers	= {							// the headers list
						names:  new Array(),
						values: new Array()
					  };	
	
	
	
	/**
	 * Returns the XMLHttpRequest Object if found, null otherwise.
	 * TODO :  Try.these()
	 *
	 * @retrun Object
	 */
	function transport() {
	
		try { return new XMLHttpRequest(); } catch(e) {	// mozilla, safari,…
		try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch(e) {	// msie > 5
		try { return new ActiveXObject("Microsoft.XMLHTTP"); } catch(e) {;}}}	// msie 5
		
		return null;
	}
	
	/**
	 * Set your receive handler to be called for the next (and probably the current) response.
	 * On request success, the specified function will be automatically called by passing two 
	 * arguments : the response for the first argument and a boolean value corresponding to an XML
	 * document type (or not) for the second (this should reflect the as_xml object property).
	 *
	 *		(ie) function onComplete( response[, asxml] ) { //… }
	 *
	 * @param Function	[ the function to be call on success ]
	 */
	function setCallback( func ) {
		__callback = func;
	}
	
	/**
	 * Set your loading handler to be called on progress.
	 * while loading the request, for each step, the specified function will be automatically called
	 * and must have one argument reflecting the current status number :
	 *
	 *		0 = uninitialized
	 *		1 = loading 
	 *		2 = loaded
	 *		3 = interactive (meanings you can work with the document, get the headers, etc…)
	 *
	 * @param Function	[ the function to be call while loading ]
	 */
	function setLoader( func ) {
		__loader = func;
	}
	
	/**
	 * Set your error handler to be called on error. If null is given all error are send to the
	 * default alert window. Only error message is passed to your own handler. 
	 *
	 * @param Function	[ the function to be call while loading ]
	 * @param boolean	[ TRUE for long error message, FALSE by default ]
	 */
	function setError( func, as_long ) {
		__error		= func;
		__errlong	= as_long!=null;
	}
	
	/**
	 * Set the current (and the next) request timeout. If a timeout occured
	 *
	 * @param Function	[ the function to be call while loading ]
	 * @param boolean	[ TRUE for long error message, FALSE by default ]
	 */
	function setError( func, as_long ) {
		__error		= func;
		__errlong	= as_long!=null;
	}
	
	/**
	 * Set the response type.
	 *
	 * @param boolean	TRUE for XML document, FALSE otherwise. 
	 */
	function xml( bVal ) {
		this.as_xml = __xml = bVal;
	}
	
	/** 
	 * Set the XMLHttpRequest open() method arguments.
	 * All this properties are public, they can be set individually with the following code :
	 *		myXHR.url = "treatment.php";
	 *
	 * User agents must at least support the following list of methods.
	 * (see http://ietf.org/rfc/rfc2616 for more details ):
	 *
	 *	- 'GET' ( by default)
	 *	- 'POST'
	 *	- 'HEAD'
	 *	- 'PUT'
	 *	- 'DELETE'
	 *	- 'OPTIONS'
	 *
	 * @param String	[ the method use for opening the request ]
	 * @param Striing	[ the target url ]
	 * @param boolean	[ process asynchronous or not, set to TRUE by default ]
	 * @param boolean	[ require XML response (or not), set to FALSE by default ]
	 */
	function options( method, url, async ) {
		if ( method!=null )	this.method = method;
		if ( url!=null )	this.url	= url;
		if ( async!=null )	this.async	= async;
	}
	
	/** 
	 * Define the XMLHttpRequest request headers. For more details, see the document at
	 * http://www.w3.org/TR/XMLHttpRequest/#dfn-setrequestheader
	 *
	 * 		for a POST method use :
	 *		headers( 'Content-Type', 'application/x-www-form-urlencoded' );
	 *
	 *	note: if 
	 *
	 * @param String	the header name or null to clear the current list
	 * @param String	[ the header value ]
	 */
	function headers( name, value ) {
		if ( name==null ) {
			__headers.names  = new Array();
			__headers.values = new Array();
		}
		else if ( __headers.names.toString().indexOf(name)==-1 ) {
			// old school : Array.push() doesn't work in IE 5
			__headers.names[__headers.names.length] = name;		// __headers.names.push( name );
			__headers.values[__headers.values.length] = value;	// __headers.values.push( value );
		}
	}
	
	/**
	 *	Add the required header to this request.
	 */
	function addHeaders() {
		try {
			for( i=0; i<__headers.names.length; i++ ) 
				__xhr.setRequestHeader( __headers.names[i], __headers.values[i] );
		}
		catch(e) { error( "Error while setting headers!", e); }
	}
	
	/**
	 * Set the current timeout value or reset the value to the default timeout value if the argument
	 * is null.
	 *
	 * @param int	[ the new timeout value in millisecond ]
	 */
	function delay( ms ) {
		__tMs = ms!=null ? ms : DEFAULT_TIMEOUT_VALUE;
	}
	
	/**
	 * Abort the request and set the error message
	 */
	function timeout() {
		__exception = new Error( "Timeout" );
		abort();
	}
	
	/**
	 * Abort the current process
	 */
	function abort() {
		try {
			if ( __tId!=null ) clearTimeout( __tId );
			if ( __exception==null ) __exception = new Error( "abort" );
			__xhr.abort();
		}
		catch(e) { error("abort failed!", e); }
	}
	
	/**
	 * Send a request via XMLHttpRequest and call the appropriate handlers on success/error.
	 * TODO : ? implement open(method, url, async, user, password)
	 *
	 * @param String	[ the query string to be send ]
	 * @return boolean
	 */
	function send( qs ) {
			
		// errors
		if ( __xhr==null )
			return error( 'This browser does not support XML request.' );
		if ( this.method==null || this.url==null)
			return error( 'You should (at least) set the request method type and the target URL.' );
		
		// for XML result
		if ( this.__xml && __xhr.overrideMimeType )
			__xhr.overrideMimeType( "text/xml" );
		
		// POST required headers for working properly
		if ( this.method.toLowerCase()=="post" && __headers.names.length==0 )
			headers( 'Content-Type', 'application/x-www-form-urlencoded' );
		
		// make the request
		try {

			/* 
				BUG :
					make an error __xhr.readyState could not be retrieved
					probably while aborting previous handler
			*/
			//abort(); // <-- close current request and stop the timer
			
			__xhr.open( this.method, this.url, this.async );
			addHeaders();
			__xhr.onreadystatechange = handler;
			__xhr.send( qs );
			
			__tId = setTimeout( timeout, __tMs );
		}
		catch(e) { error("Failed to open request!", e); }
		
		return true;
	}
	
	
	/**
	 * The process handler : 
	 * check for the request progression and call the appropriate functions.
	 * SHOULD BE COMPLETE
	 */
	function handler() {
		
		try {
			switch( __xhr.readyState ) {
			// uninitialized
			case 0 :
			// loading 
			case 1 :
			// loaded
			case 2 :
			// interactive
			case 3 :
				if ( __loader!=null ) __loader( __xhr.readyState );
				break;
			// complete
			case 4 :
				switch( __xhr.status ) {
					// ok
					case undefined :
					case 200 :
						try {
							__callback( __xml ? __xhr.responseXML : __xhr.responseText, __xml );
						}
						catch(e){;}
						break;
					// not found
					case 404 :
						error( "Not Found: The requested URL was not found on this server." );
						break;
					// forbidden
					case 403 :
						error( "Forbidden: you don't have permission to access on this server." );
						break;
					// internal server error
					case 500 :
						error( "Internal Server Error: The server encountered an internal error or"+
							   "misconfiguration and was unable to complete your request." );
						break;
					// otherwise
					default : error( "Error loading document!" );
				}
				break;
			}
		}
		catch(e) {
			if ( __exception==null ) error( "Request failed!", e );
			else
				if ( __exception.message!="abort" ) error( __exception.message );
		}
		finally {
			// stop the timer
			clearTimeout( __tId );
			__exception = null;
		}
	}
	
	/**
	 * The error handler : output the error message as an Alert (by default) or call the appropriete
	 * callback method.s By convention this function alway return FALSE.
	 *
	 * @param String	the error message
	 * @param Object	[ the error object ]
	 * @return boolean
	 */
	function error( msg, e ) {
		if ( __errlong && e!=null ) msg += "\n\n"+e.message;
		if ( __error==null ) alert( msg );
		else __error( msg );
		return false;
	}
}
