/*
	HTTP-JSON-Request

	Last modification 18.06.2010 by Jens Duttke
	
	Tested in FF2, FF3.5, IE6, IE7, IE8, OP9.6, SF4b, SF5, iPhone

	url:
		/soap/kfz/?gw=search_form&allManufacturers=1&allModels=1&mkey=1-9370-199994&manufacturer=31&model=&xsl=../../../userdata/15130/layout/js/xml2json.xsl&preventCache=1253530372240

	callback:
		function (jsObj) { ... }

	errCallback (optional):
		function (e) { ... }

	options (optional):
		debug: true/false (default: true)
		parse: true/false (default: false)
		async: true/false (default: true)
		cache: true/false (default: false)
		postData: queryString (default: null)
		langId: string (default: de)
*/

var _httpRequestCache = {};

function httpRequest(url, callback, errCallback, options)
{
	if (typeof url !== "string")			{ return error("InvalidURL"); }
	if (typeof callback !== "function")		{ return error("NoCallbackSpecified"); }

	if (options === undefined) { options = {}; }
	if (typeof options.debug !== 'boolean')	{ options.debug = true; }
	if (typeof options.parse !== 'boolean')	{ options.parse = true; }
	if (typeof options.async !== 'boolean')	{ options.async = true; }
	if (typeof options.cache !== 'boolean')	{ options.cache = false; }
	if (typeof options.postData !== 'string')	{ options.postData = null; }
	if (typeof options.langId !== 'string')	{ options.langId = 'de'; }

	var xmlHttp = null;

	if (options.cache && options.postData === null)
	{
		log("ProcessCachedResponse");
		xmlHttp = { 'responseText':_httpRequestCache[url.replace(/preventCache=[0-9]+/, '')] };
		if (xmlHttp.responseText !== undefined)
		{
			processResponse();
			log("Done");
			return function () { return true; };
		}
	}

	// function: Firefox, Opera; object: Safari, IE7+
	if ((typeof XMLHttpRequest === "function") || (typeof XMLHttpRequest === "object"))
	{
		log("InitXmlHttpRequestObject");
		try
		{
			xmlHttp = new XMLHttpRequest();
		}
		catch (e)
		{
			return error("InitXmlHttpRequestObject", e);
		}
	}
	// IE6
	else if (typeof ActiveXObject === 'function')
	{
		log("InitMsXmlHttpComponent");
		var progIDs = ["MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"];
		for (var i = 0; i < progIDs.length; i++)
		{
			try
			{
				xmlHttp = new ActiveXObject(progIDs[i]);
				break;
			}
			catch (e) {}
		}
		if (!xmlHttp)
		{
			return error("InitMsXmlHttpComponent");
		}
	}
	else
	{
		return error("NoXmlHttpComponentAvailable");
	}

	try
	{
		if (xmlHttp.overrideMimeType === "function")
		{
			xmlHttp.overrideMimeType("text/plain");
			// xmlHttp.overrideMimeType("application/json");
		}
	}
	catch (e) {}

	try
	{
		log("InitOnReadyStateChange");
		xmlHttp.onreadystatechange = function()
		{
			if (!options.async) { return; }

			log("ReadyStateChangedTo" + ['Unsent', 'Opened', 'HeadersReceived', 'Loading', 'Done'][xmlHttp.readyState], (xmlHttp.readyState > 1 && typeof xmlHttp.status === 'number' ? xmlHttp.status : undefined), (xmlHttp.readyState > 1 && typeof xmlHttp.responseText === 'string' ? xmlHttp.responseText.length : undefined));

			if (xmlHttp.readyState === 4)
			{
				switch (xmlHttp.status)
				{
					case 0:	// Network error or Access to local files
						if (!xmlHttp.responseText)
						{
							error("NetworkError");
							break;
						}

					case 200:
						if (processResponse() && options.cache)
						{
							/*
								Cache will be written after the data were successfully processed,
								to make sure the server returned valid data.
							*/
							_httpRequestCache[url.replace(/preventCache=[0-9]+/, '')] = xmlHttp.responseText;
						}
						break;

					// IE specific status codes
					case 12029:
					case 12030:
					case 12031:
					case 12152:
					case 12159:
						httpRequest(url, callback, errCallback, options);
						break;

					default:
						if (xmlHttp.status)
						{
							error("UnsupportedStatusCode", null, { statusCode:xmlHttp.status, statusText:xmlHttp.statusText });
						}
						else
						{
							error("StatusCodeNotAvaiable", null, { statusText:xmlHttp.statusText });
						}
				}

				xmlHttp.onreadystatechange = new Function();	// Fix memory leak in IE
				xmlHttp = null;
				log("Done");
			}
		};
	}
	catch (e)
	{
		return error("InitOnReadyStateChange", e);
	}

	log("OpeningXmlHttpRequest");
	if (options.postData === null)
	{
		try { xmlHttp.open('GET', url, options.async); } catch (e) { return error("OpeningXmlHttpRequest", e); }
	}
	else
	{
		try { xmlHttp.open('POST', url, options.async); } catch (e) { return error("OpeningXmlHttpRequest", e); }

		log("SettingRequestHeader");
		try { xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); } catch (e) { return error("SettingRequestHeader", e); }
	}
/*
	Not allowed
	log("SettingRequestHeader");
	try { xmlHttp.setRequestHeader("Connection", "close"); } catch (e) { return error("SettingRequestHeader", e); }
*/
	log("SendingRequest");
	try { xmlHttp.send(options.postData); } catch (e) { return error("SendingRequest", e); }

	log("WaitingForResponse");

	if (!options.async)
	{
		processResponse();
		log("Done");
	}

	return abort;

	function abort()
	{
		try
		{
			if (xmlHttp !== null && typeof xmlHttp.abort === 'function')
			{
				xmlHttp.onreadystatechange = new Function();	// Fix memory leak in IE
				log("AbortingRequest");
				xmlHttp.abort();
				xmlHttp = null;
			}
			else if (options.debug)
			{
				return error("AbortingNotPossible", e);
			}
		}
		catch (e)
		{
			return error("AbortingRequest", e);
		}

		return true;
	}

	function processResponse()
	{
		if (options.parse)
		{
			log("ParsingJsonNative");
			try
			{
				if ((typeof JSON === "object") && (typeof JSON.parse === "function"))
				{
					var jsObj = JSON.parse(xmlHttp.responseText);
				}
				else
				{

					throw new TypeError('JSON.parse');
				}
			}
			catch (e)
			{
				log("ParsingJsonEval");
				try
				{
					var jsObj = eval("(" + xmlHttp.responseText + ")");
				}
				catch (e)
				{
					return error("ServerReturnedInvalidData", e, { responseText:xmlHttp.responseText });
				}
			}
		}
		else
		{
			try
			{
				var jsObj = xmlHttp.responseText;
			}
			catch (e)
			{
				return error("ServerReturnedInvalidData", e);
			}
		}

		log("ProcessResponse");
		try
		{
			callback(jsObj);
		}
		catch (e)
		{
			return error("DataProcessingFailed", e);
		}
		log("ResponseProcessed");

		return true;
	}

	/* Helper functions */

	function log(msgId)
	{
		if (options.debug)
		{
			var msg;
			if (/^[A-Za-z0-9]+$/.test)
			{

				switch (msgId)
				{
					case "ProcessCachedResponse":
						switch (options.langId)
						{
							case 'de':	msg = "Die zwischengespeicherte Server-Antwort wird verarbeitet..."; break;
							default:		msg = "Processing cached server response..."; break;
						}
					case "InitXmlHttpRequestObject":
						switch (options.langId)
						{
							case 'de':	msg = "Initialisiere XMLHttpRequest-Objekt..."; break;
							default:		msg = "Initializing XMLHttpRequest object..."; break;
						}
						break;
					case "InitMsXmlHttpComponent":
						switch (options.langId)
						{
							case 'de':	msg = "Initialisiere Microsoft XML HTTP-Komponente..."; break;
							default:		msg = "Initializing Microsoft XML HTTP component..."; break;
						}
						break;
					case "InitOnReadyStateChange":
						switch (options.langId)
						{
							case 'de':	msg = "Richte JSON-Verarbeitungsfunktion ein..."; break;
							default:		msg = "Setting JSON processing function..."; break;
						}
						break;
					case "ReadyStateChangedToUnsent":
						switch (options.langId)
						{
							case 'de':	msg = "Anfragestatus ist zu 'unsent' gewechselt..."; break;
							default:		msg = "Ready state changed to 'unsent'..."; break;
						}
						break;
					case "ReadyStateChangedToOpened":
						switch (options.langId)
						{
							case 'de':	msg = "Anfragestatus ist zu 'opened' gewechselt..."; break;
							default:		msg = "Ready state changed to 'opened'..."; break;
						}
						break;
					case "ReadyStateChangedToHeadersReceived":
						switch (options.langId)
						{
							case 'de':	msg = "Anfragestatus ist zu 'headers_received'" + (arguments[1] ? " (Status: " + arguments[1] + ")" : "") + " gewechselt..."; break;
							default:		msg = "Ready state changed to 'headers_received'" + (arguments[1] ? " (status: " + arguments[1] + ")" : "") + "..."; break;
						}
						break;
					case "ReadyStateChangedToLoading":
						switch (options.langId)
						{
							case 'de':	msg = "Anfragestatus ist zu 'loading'" + (arguments[1] || arguments[2] ? " (" + (arguments[1] ? "Status: " + arguments[1] : "") + (arguments[1] && arguments[2] ? "; " : "") + (arguments[2] ? arguments[2] + " Bytes übertragen": "") + ")": "") + " gewechselt..."; break;
							default:		msg = "Ready state changed to 'loading'" + (arguments[1] || arguments[2] ? " (" + (arguments[1] ? "status: " + arguments[1] : "") + (arguments[1] && arguments[2] ? "; " : "") + (arguments[2] ? arguments[2] + " bytes transfered": "") + ")": "") + "..."; break;
						}
						break;
					case "ReadyStateChangedToDone":
						switch (options.langId)
						{
							case 'de':	msg = "Anfragestatus ist zu 'done'" + (arguments[1] || arguments[2] ? " (" + (arguments[1] ? "Status: " + arguments[1] : "") + (arguments[1] && arguments[2] ? "; " : "") + (arguments[2] ? arguments[2] + " Bytes übertragen": "") + ")": "") + " gewechselt..."; break;
							default:		msg = "Ready state changed to 'done'" + (arguments[1] || arguments[2] ? " (" + (arguments[1] ? "status: " + arguments[1] : "") + (arguments[1] && arguments[2] ? "; " : "") + (arguments[2] ? arguments[2] + " bytes transfered": "") + ")": "") + "..."; break;
						}
						break;
					case "ParsingJsonNative":
						switch (options.langId)
						{
							case 'de':	msg = "Anasyle der JSON-Antwortdaten (Nativ)..."; break;
							default:		msg = "Parsing the JSON response (native)..."; break;
						}
						break;
					case "ParsingJsonEval":
						switch (options.langId)
						{
							case 'de':	msg = "Analyse der JSON-Antwortdaten (eval())..."; break;
							default:		msg = "Parsing the JSON response (eval())..."; break;
						}
						break;
					case "ProcessResponse":
						switch (options.langId)
						{
							case 'de':	msg = "Die Server-Antwort wird verarbeitet..."; break;
							default:		msg = "Processing server response..."; break;
						}
						break;
					case "ResponseProcessed":
						switch (options.langId)
						{
							case 'de':	msg = "Die Server-Antwort wurde erfolgreich verarbeitet..."; break;
							default:		msg = "Server response successful processed..."; break;
						}
						break;
					case "OpenXmlHttpRequest":
						switch (options.langId)
						{
							case 'de':	msg = "Öffnen der XML-Http-Anfrage..."; break;
							default:		msg = "Opening the XML Http request..."; break;
						}
						break;
					case "SettingRequestHeader":
						switch (options.langId)
						{
							case 'de':	msg = "Einstellen des XML-Http-Anfrage-Headers..."; break;
							default:		msg = "Setting the XML Http request header..."; break;
						}
						break;
					case "SendingRequest":
						switch (options.langId)
						{
							case 'de':	msg = "Server-Anfrage wird gesendet..."; break;
							default:		msg = "Sending the server request..."; break;
						}
						break;
					case "WaitingForResponse":
						switch (options.langId)
						{
							case 'de':	msg = "Warte auf die Server-Antwort..."; break;
							default:		msg = "Waiting for the server response..."; break;
						}
						break;
					case "AbortingRequest":
						switch (options.langId)
						{
							case 'de':	msg = "Server-Anfrage wird abgebrochen..."; break;
							default:		msg = "Aborting the server request..."; break;
						}
						break;
					case "Done":
						msg = ""; break;
					default:
						msg = msgId;
				}
			}
			else
			{
				msg = msgId;
			}

			window.status = msg;
		}
	}

	function error(msgId, e, infosObj)
	{
		var errObj = { "url":url, "id":msgId };

		switch (msgId)
		{
			case "InvalidURL":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Ungültige URL."; break;
					default:		errObj["msg"] = "Invalid URL."; break;
				}
				break;
			case "NoCallbackSpecified":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Keine Callback-Funktion spezifiziert."; break;
					default:		errObj["msg"] = "No callback function specified."; break;
				}
				break;
			case "InitXmlHttpRequestObject":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Fehler während der Initialisierung des XMLHttpRequest-Objekts."; break;
					default:		errObj["msg"] = "Error while initializing XMLHttpRequest object."; break;
				}
				break;
			case "InitMsXmlHttpComponent":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Keine XML-HTTP-Komponente gefunden."; break;
					default:		errObj["msg"] = "No XML HTTP component found."; break;
				}
				break;
			case "NoXmlHttpComponentAvailable":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Keine XML-HTTP-Komponente verfügbar."; break;
					default:		errObj["msg"] = "No XML HTTP component available."; break;
				}
				break;
			case "ServerReturnedInvalidData":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Der Server hat ungültige Daten zurückgeliefert."; break;
					default:		errObj["msg"] = "Server returned invalid data."; break;
				}
				break;
			case "DataProcessingFailed":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Fehler beim Verarbeiten der Server-Antwort."; break;
					default:		errObj["msg"] = "Error while processing the server response."; break;
				}
				break;
			case "NetworkError":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Netzwerkfehler."; break;
					default:		errObj["msg"] = "Network error."; break;
				}
				break;
			case "UnsupportedStatusCode":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Der Server hat einen nicht unterstützten Status-Code zurückgeliefert."; break;
					default:		errObj["msg"] = "Server returned unsupported status code."; break;
				}
				break;
			case "StatusCodeNotAvaiable":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Kein Status-Code deklariert."; break;
					default:		errObj["msg"] = "Status code not declared."; break;
				}
				break;
			case "InitOnReadyStateChange":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Fehler während der Initialisierung von onReadyStateChange."; break;
					default:		errObj["msg"] = "Error while initializing onReadyStateChange."; break;
				}
				break;
			case "OpeningXmlHttpRequest":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Fehler beim Öffnen einer neuen Server-Anfrage."; break;
					default:		errObj["msg"] = "Error opening a new server request."; break;
				}
				break;
			case "SettingRequestHeader":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Fehler beim Setzen des Anfrage-Headers."; break;
					default:		errObj["msg"] = "Error while setting the request header."; break;
				}
				break;
			case "SendingRequest":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Fehler beim Senden der Server-Anfrage."; break;
					default:		errObj["msg"] = "Error sending the server request."; break;
				}
				break;
			case "AbortingNotPossible":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Abbrechen nicht möglich."; break;
					default:		errObj["msg"] = "Aborting not possible."; break;
				}
				break;
			case "AbortingRequest":
				switch (options.langId)
				{
					case 'de':	errObj["msg"] = "Fehler beim Abbrechen der Server-Anfrage."; break;
					default:		errObj["msg"] = "Error while aborting the server request."; break;
				}
				break;
			default:
				errObj["msg"] = msgId;
		}

		if (e && e.name)		{ errObj["constructor"] = e.name; }
		if (e && e.message)	{ errObj["sys_msg"] = e.message; }

		if (infosObj)
		{
			for (i in infosObj)
			{
				errObj[i] = infosObj[i];
			}
		}

		log(errObj["msg"]);
		if (typeof errCallback === 'function')
		{
			try
			{
				errCallback(errObj);
			}
			catch (e) {}
		}

		return false;
	}
}

function jsonArray(node, index)
{
	if (Object.prototype.toString.call(node) === '[object Array]' && index >= 0 && index < node.length)
	{
		return node[index];
	}
	return (index == 0 ? node : null);
}

function jsonArrayLength(node)
{
	return (Object.prototype.toString.call(node) === '[object Array]' ? node.length : (typeof node === 'undefined' ? 0 : 1));
}