"use strict";

function isProperty(obj, key)
{
	return (obj.hasOwnProperty(key) && typeof obj[key] !== "function");
}
/**
 * [public] constructor of the AJAX object.
 *
 * @param (string) url          Request URL
 * @param (string) typ          Request Type (GET, POST, etc.)
 */
function Ajax(url, typ) 
{
	this.id    = "ajax";
	this.url   = url;
	this.async = true; 

	var key = typ || "POST";
	key     = key.toUpperCase();
	if (this.availableTypes.hasOwnProperty(key)) {
		this.type   = key;
		this.header = this.availableTypes[key];
	}
	this.result = null; // default response value
}
/**
 * common default settings for the HTTP Request Types.
 */
Ajax.prototype.availableTypes = { 
	HEAD    : {  // return only response headers 
		"Content-Length" : 0 
	},
	OPTIONS : {  // get allowed request methods
		"Content-Length" : 0 
	},
	TRACE   : null, 
	GET     : {
		"Cache-Control" : "no-cache"
	}, 
	POST    : { 
		"Content-Type"  : "application/x-www-form-urlencoded", 
		"Cache-Control" : "no-cache",
		"Connection"    : "close"
	}, 
	PUT     : { 
		"Cache-Control" : "no-cache"
	}, 
	DELETE  : null 
};
/**
 * [private] checks, if the transfer type is allowed to use async mode.
 * background: catching errors thrown in an event is not possible for
 * all browsers (some support onerror, some not). esp. HEAD requests
 * are not guaranteed to return HTTP 200.
 *
 * @return (bool)               transfer type allows only syncronous calls.
 */
Ajax.prototype.silent = function ()
{
	const sync = ["HEAD", "OPTIONS", "TRACE", "DELETE"];
	var silent = (-1 !== sync.indexOf(this.type));
	if (silent) {
		this.async = false;
	}
	return silent;
}
/**
 * [public] add an XHR Request Header to be sent.
 *
 * @param (string) name         Header name
 * @param (string) content      Header value
 * @return (int)                XHR.readyState
 */
Ajax.prototype.addRequestHeader = function (name, content) 
{
	this.header[name] = content;
	return this.XHR.readyState;
};
/**
 * [public] create an XMLHttpRequest object, including IE.
 *
 * @param (string) id           XHR.requestid
 * @return (Ajax)               self instance for chaining
 * @throws (Error)              XHR creation failed
 */
Ajax.prototype.create = function (id) 
{
	try {
		this.XHR = new XMLHttpRequest();
	} 
	catch (e) {
		try {
			this.XHR = new ActiveXObject("Microsoft.XMLHTTP");
		} 
		catch (f) {
			throw new Error("Kann keine XMLHTTP-Instanz erzeugen.");
		}
	}
	this.XHR.requestid = id || this.id;
	return this;
};
/**
 * [public] send an AJAX request using the passed values.
 *
 * @param (object) qry          object holding the key-value pairs to submit 
 * @return (void)
 * @throws (Error)              failed to create XHR object
 */
Ajax.prototype.submit = function (qry) 
{
	// check for response handler
	if (typeof this.answer !== "function") {
		throw new Error("Keine Response-Funktion definiert.");
	}
	var hd, params   = null,
	    url          = this.url, // don't overwrite initial URL
	    query_string = this.queryString(qry); // create URL encoded query string
	    
	// create XHR object if not done yet
	if (!this.XHR) {
		this.create();
	}
	// always do certain requests in sync mode
	this.silent();
	// treat GET/POST requests
	if ("POST" !== this.type && "PUT" !== this.type) {
		// data in the header
		url += (this.url.indexOf("?") > 1 ? "&" : "?") + query_string;
	}
	else {
		// data in the body
		this.addRequestHeader("Content-Length", query_string.length);
		params = query_string;
	}
	// open request
	this.XHR.open(this.type, url, this.async);
	// set headers
	for (hd in this.header) {
		if (isProperty(this.header, hd)) {
			this.XHR.setRequestHeader(hd, this.header[hd]);
		}
	}
	// asynchronous handler must be declared before send()
	if (this.async) {
		this.asyncHandler();
		this.XHR.send(params);
	}
	// synchronous handler must be declared after send()
	else {
		this.XHR.send(params);
		this.syncHandler();
	}
};
/**
 * [private] prepare request string.
 *
 * @param (object) qry          object holding the key-value pairs to submit 
 * @return (string)             request string
 */
Ajax.prototype.queryString = function (qry)
{
	var key, str = "";
	for (key in qry) {
		if (isProperty(qry, key)) {
			str += "&" + encodeURIComponent(key) +  "=" + encodeURIComponent(qry[key]);
		}
	}
	return str.substring(1);
}
/**
 * [private] handler for asynchronous AJAX requests.
 *
 * @return (void)
 * @throws (Error)              AJAX request failed
 */
Ajax.prototype.asyncHandler = function () 
{
	this.XHR.onerror = function (evt) {
		alert("Fehler " + evt.target.status + " trat während des AJAX-Requests auf.");
	};
	this.XHR.ontimeout = function (evt) {
		alert("Fehler " + evt.srcElement.status + " trat während des AJAX-Requests auf.");
	};
	var me = this;
	this.XHR.onreadystatechange = function (evt) 
	{
		if (4 === this.readyState) {
			if (200 === this.status) {
				me.processResponse(this);
			}
			else if (!me.silent()) {
				// not every UA catches Errors from Events
				alert("Fehler: " + this.status + " - " + this.statusText);
				throw new Error("Fehler: " + this.XHR.status + " - " + this.XHR.statusText);
			}
		}
	};
};
/**
 * [private] handler for synchronous AJAX requests.
 *
 * @return (void)
 * @throws (Error)              AJAX request failed
 */
Ajax.prototype.syncHandler = function () 
{
	var success = (-1 === this.url.indexOf("file://")) ? 200 : 0;
	if (this.XHR.status === success) {
		this.processResponse(this.XHR);
	}
	else {
		throw new Error("Fehler: " + this.XHR.status + " - " + this.XHR.statusText);
	}
};
/**
 * [private] pass the response as text or XML to the post processor 
 * depending on the submitted content type.
 *
 * @param (XMLHttpRequest) xhr  XHR object, due to different scope for 
 *                              synchronous and asynchronous handlers.
 * @return (void)
 */
Ajax.prototype.processResponse = function (xhr) 
{
	var resp;
	if (-1 === xhr.getResponseHeader("Content-Type").indexOf("xml")) {
		resp = xhr.responseText;
	} 
	else {
		resp = xhr.responseXML;
	}
	this.answer(resp, xhr.requestid);
};
/**
 * [private] current state & status message of the XHR object.
 *
 * @return (string)             current state & status of the XHR object
 */
Ajax.prototype.toString = function () 
{
	const state = ["UNSENT", "OPENED", "HEADERS_RECEIVED", "LOADING", "DONE"];
	return state[this.XHR.readyState] + " - " + this.XHR.statusText;
};
/**
 * [public] cancel AJAX operation.
 *
 * @return (string)             state & status of the XHR object before abort()
 */
Ajax.prototype.cancel = function () 
{
	var status = this.toString();
	this.XHR.abort();
	return status;
};
/**
 * [public] default response processor. overwrite with your own implementation.
 *
 * @param (mixed) result        AJAX response (string or Document)
 * @param (mixed) id            XMLHttpRequest.requestid
 */
Ajax.prototype.answer = function (result, id)
{
	this.result = result;
};

/*
common Request Headers:
 - Host
 - User-Agent
 - Accept
 - Accept-Language
 - Accept-Encoding
 - Accept-Charset
 - Keep-Alive
 - Connection
 - Referer
 - Content-Length
 - Content-Type
 - Cache-Control
*/
