/*
 * Author: Rodney Rehm
 * Web: http://www.rodneyrehm.de
 * Get tabManager at http://www.rodneyrehm.de/tools/remotecommunicator
 *
 * Version / Date: 21. June 2006
 *
 * Published under the Open Source BSD-License
 * http://www.opensource.org/licenses/bsd-license.php
 */

/**
 * generic communicator for communication with remote host
 * only urls from the same host can be accessed (remember that Cross-Site-Scripting)
 * @param url url open connection to
 * @param data data to send when connection opened
 **/
function remoteCommunicator(url, data)
{
	/** set some default values **/
	this.GET_REQUEST	= 0;
	this.POST_REQUEST	= 1;
	this.HEAD_REQUEST	= 2;

	var verboseErrors = false;				/* output error-messages */
	var ignoreBrowserCache = false;			/* ignore chaching */
	var req = getConnection();				/* http-request object */
	var asynchronousConnection = true;		/* don't wait for response? */
	var requestHeaders = new Array();		/* collect headers to send */

	var connectionState0 = function (){ }	/* executed when state == 0 */
	var connectionState1 = function (){ }	/* executed when state == 1 */
	var connectionState2 = function (){ }	/* executed when state == 2 */
	var connectionState3 = function (){ }	/* executed when state == 3 */
	var connectionState4 = function (){ }	/* executed when state == 4 */

	/**
	 * set handler called when state is changed to
	 * @access public
	 * @param state readyState of connection (0 - 4)
	 * @param func function object to set as handler
	 **/
	this.setStateHandler = function(state, func)
	{
		switch(state)
		{
			case 0: connectionState0 = func;
			case 1: connectionState1 = func;
			case 2: connectionState2 = func;
			case 3: connectionState3 = func;
			case 4: connectionState4 = func;
		}
	}

	/**
	 * verbose connection-errors
	 * @access public
	 * @param display true when errors should be exposed, false on ignore
	 **/
	this.setVerboseErrors = function(verbose)
	{
		verboseErrors = verbose;
	}

	/**
	 * set ignore browser cache to have timestamp automatically appended to url
	 * @access public
	 * @param display true when cache should be ignored
	 **/
	this.setIgnoreBrowserCache = function(ignore)
	{
		ignoreBrowserCache = ignore;
	}

	/**
	 * set Connection type to synchronous
	 * @param sync true for synchronous, false for asynchronous
	 **/
	this.setSynchronous = function(sync)
	{
		synchronousConnection = !sync;
	}

	/**
	 * add a parameter to current data-string
	 * @access public
	 * @param name name of the parameter
	 * @param value value of the parameter
	 * @param encode
	 **/
	this.addParameter = function(name, value, encode)
	{
		if(data) data += '&';
		if(encode)
			data += urlencode(name) + '=' + urlencode(value);
		else
			data += name + '=' + value;
	}

	/**
	 * set a request-header to send to remote host
	 * @access public
	 * @param name name of the header
	 * @param value value of the header
	 **/
	this.addRequestHeader = function(name, value)
	{
		requestHeaders[requestHeaders.length] = new Array(name, value);
	}

	/**
	 * encode chars correctly (e.g. for use with querystrings) (taken from molily - selfhtml.org)
	 * @access public
	 * @param str string to encode
	 * @return (string) encoded string
	 **/
	this.urlencode = function(str)
	{
		var code = "";
		for (var i = 0; i < str.length; i++)
		{
			if (str.charAt(i) == " ")
			{
				code += "+";
			}
			else if (str.charAt(i) == "+")
			{
				code += "%2B";
			}
			else if (str.charCodeAt(i) > 127)
			{
				code += encodeURI(str.charAt(i));
			}
			else
			{
				code += escape(str.charAt(i));
			}
		}
		return code;
	}

	/**
	 * Start communication with remote host
	 * @param method requestMethod to use (e.g. rc.POST_REQUEST)
	 **/
	this.execute = function(method)
	{
		if(!method) method = this.GET_REQUEST;

		if(data)
		{
			var apSign = '&';							/* append timestamp to an existing querystring */
			if(url.indexOf('?') == -1) apSign = '?'; 	/* timestamp is the complete querystring */
			url = url + apSign + data;					/* append data to url on GET-Requests */
		}

		if(ignoreBrowserCache)
		{
			/*
			 * Browsers love their caches. to make sure we don't get cached data
			 * (even the corresponding http-headers don't ensure this reliably)
			 * we simply append a timestamp to our URL, to make each requested url
			 * unique and get around those stupid caches
			 */
			var dObj = new Date();
			var apSign = '&';							/* append timestamp to an existing querystring */
			if(url.indexOf('?') == -1) apSign = '?'; 	/* timestamp is the complete querystring */
			url = url + apSign + 'curTimeStmp=' + dObj.getTime();
		}

		switch( method )
		{
			case this.HEAD_REQUEST:
				req.open("HEAD", url, asynchronousConnection);
				for(var i=0; i<requestHeaders.length;i++) req.setRequestHeader(requestHeaders[i][0], requestHeaders[i][1]);
				req.send(null);
			break;

			case this.GET_REQUEST:
					req.open("GET", url, asynchronousConnection);
					for(var i=0; i<requestHeaders.length;i++) req.setRequestHeader(requestHeaders[i][0], requestHeaders[i][1]);
					req.send(null);
			break;

			case this.POST_REQUEST:
				/*
				 * since we are using the POST-method here we can't send our data as a
				 * querystring, we need to send it using the send() method.
				 * we also like to send appropriate headers along with our request
				 */
				var index = url.indexOf('?')
				if(index > -1)
				{
					data += '&' + url.substr(index+1);
					url = url.substring(0,index);
				}

				req.open("POST", url, true);
				var ctSent = false;
				for(var i=0; i<requestHeaders.length;i++)
				{
					req.setRequestHeader(requestHeaders[i][0], requestHeaders[i][1]);
					if(requestHeaders[i][0].toLower() == 'content-type')
						ctSent = true;
				}
				if(!ctSent) req.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
				req.setRequestHeader( 'Content-length', data.length );
				req.send(data);
			break;
		}

	}

	/**
	 * get system-dependend connection object
	 * @access private
	 * @return Object XMLHttpRequest
	 **/
	function getConnection()
	{
		var connection;

		// branch for mozilla-compatible
		if (window.XMLHttpRequest)
		{
			connection = new XMLHttpRequest();
		}

		// branch for IE/Windows ActiveX version
		else if (window.ActiveXObject)
		{
			connection = new ActiveXObject("Microsoft.XMLHTTP");
		}

		// no xmlHTTPrequest object found
		else
		{
			alert('HTTP-Requests are not possible with this browser!' +
				  '\nPlease upgrade to a newer Version.');
		}
		return connection;
	}

	/**
	 * This function is called automatically after each change in current state of communication.
	 * here we decide what to do with corrupt transmissions, and like.
	 * @access private
	 **/
	req.onreadystatechange = function()
	{
		/*
		 * There are 5 steps in a http-request, after each step the
		 * onreadystatechange function is called:
		 * status 0 UNINITIALIZED open() has not been called yet.
		 * status 1 LOADING send() has not been called yet.
		 * status 2 LOADED send() has been called, headers and status are available.
		 * status 3 INTERACTIVE Downloading, responseText holds the partial data.
		 * status 4 COMPLETED Finished with all operations.
		 */

		switch( req.readyState )
		{
			case 0: connectionState0(); break;
			case 1: connectionState1(); break;
			case 2: connectionState2(); break;
			case 3: connectionState3(); break;
			case 4:
				if(req.status != 200 && verboseErrors)
				{
					var errorWindow = window.open('about:blank','errorWindow',
						"resizable=yes,scrollbars=auto,status=yes,toolbar=yes,location=no,menubar=yes,width=500,height=500");

					errorWindow.document.write( req.responseText );
					errorWindow.focus();
				}

				/*
				 * for easy usage we return the responseText or responseXML directly
				 */
				if(req.getResponseHeader('Content-Type') == 'text/xml')
					connectionState4(req.responseXML.documentElement, req);
				else
					connectionState4(req.responseText, req);
			break;
		}
	}
}

/*
 * example usage:
 * var rc = new remoteCommunicator('/start/', "data=1");
 * 	rc.addParameter('param', 1);
 * 	rc.addRequestHeader('X-Test', 'blubber');
 * 	rc.addRequestHeader('X-Blubber', 'bla');
 * 	rc.setIgnoreBrowserCache(true);
 * 	rc.setStateHandler(4, function(response, req){ alert( typeof(response) ); });
 * 	rc.execute(rc.GET_REQUEST);
 */


