Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.Request
 * @extends Ext.util.Observable
 * 
 * Request object for asynchronous communication with the server. The request object
 * automatically serialises single or multiple action requests into corresponding
 * JSON and provides a callback system to allow asynchronous handling of the
 * response from the server.
 * <p>
 * The request object supports global events surrounding the sending of the data
 * to the server. For handling the responses it communicates directly with the
 * {@link Zarafa.core.ResponseRouter ResponseRouter} on which events are provided
 * surrounding the receiving of the Responses (as well as handling of the Notifications).
 * <p>
 * You shouldn't have to instantiate this class yourself as a global instance
 * can be obtained from the globally available {@link Zarafa.core.Container container}
 * object.
 * <p>
 * The structure of data that will be used for communication will be like
 * <pre><code>
 * zarafa : {
 *	module_name : {
 *		module_id : {
 *			action_type : {
 *				// data
 *			}
 *		}
 *	}
 * }
 * </code></pre>
 */
Zarafa.core.Request = Ext.extend(Ext.util.Observable, (function() {
	/**
	 * True if the conection has been {@link #paralyze}. This will
	 * prevent any requests from being send to the server, or any responses
	 * from being processed.
	 * @property
	 * @type Boolean
	 * @private
	 */
	var paralyzed = false;

	/**
	 * True if the Webapp has been {@link #interrupt}. This will
	 * queue all requests which will be send to the server, until the connection
	 * is restored again.
	 * @property
	 * @type Boolean
	 * @private
	 */
	var interrupted = false;

	/**
	 * The current base number for the generating new requestsIds
	 * using the {@link #getRequestId} function.
	 * @property
	 * @type Number
	 * @private
	 */
	var requestIdBase = 0;

	/**
	 * The current JSON object containing all requests which
	 * should be send to the server on the next call to
	 * {@link #send}. Will be reset on {@link #reset}.
	 * @property
	 * @type Object
	 * @private
	 */
	var zarafaTag;

	/**
	 * The flag to indicate if zarafaTag holds JSON data.
	 * This influences if the contents of zarafaTag will be encoded or not.
	 * @property
	 * @type Boolean
	 * @private
	 */
	var hasJson = false;

	/**
	 * The flag to indicate if zarafaTag holds RAW data.
	 * This influences if the contents of zarafaTag will be encoded or not.
	 * @property
	 * @type Boolean
	 * @private
	 */
	var hasData = false;

	/**
	 * The list of request ids as generated by {@link #getRequestId} of all
	 * requests which are added after a {@link #reset} and before {@link #send}.
	 * These will be merged into {@link #activeRequests} on {@link #send}.
	 * @property
	 * @type Array
	 * @private
	 */
	var queuedRequests = [];

	/**
	 * Key-value list of request ids and the corresponding {@link XMLHttpRequest} objects
	 * which was used to send the request to the server. These requests are all pending
	 * on the PHP side. These will be cleared as soon as the response has been received.
	 * As long as a request is listed here, it can be canceled using {@link #cancelActiveRequest}.
	 * @property
	 * @type Array
	 * @private
	 */
	var activeRequests = {};

	/**
	 * The list of {@link #XMLHttpRequest} objects which are queued while the
	 * {@link #interrupted connection is interrupted}. These will be dequeued
	 * later when the connection is restored.
	 * @property
	 * @type Array
	 * @private
	 */
	var queuedInterruptedHttpRequests = [];

	/**
	 * The unique id for subsystem which will be used to differentiate between two sessions of same user
	 * using same browser but in different tabs.
	 * @property
	 * @type String
	 * @private
	 */
	var subSystemId;

	return {
		// public variables
		/**
		 * @cfg {String} defaultUrl
		 * The url used to send the requests to. defaults to kopano.php.
		 */
		defaultUrl : 'kopano.php',

		/**
		 * @cfg {Object} defaultHeaders
		 * The default headers to be applied to the request. defaults to
		 *	'Content-Type' => 'application/json; charset=utf-8;'
		 */
		defaultHeaders : undefined,

		/**
		 * @cfg {String} subSystemPrefix string that will be used to generate session filename which
		 * will be used to store session data in php. current timestamp will be appended to this string
		 * so different tabs opened in same browser will have different session data. So every request object
		 * corresponds to one unique subSystem string id (accuracy upto miliseconds).
		 */
		subSystemPrefix : 'webapp',

		/**
		 * @cfg {Object} requestHeaders
		 * Headers that should be sent with every request.
		 */
		requestHeaders : undefined,

		/**
		 * @constructor
		 * @param {Object} config Configuration object
		 */
		constructor: function(config)
		{
			config = config || {};

			Ext.apply(config, {
				// Apply here instead of class prototype, to prevent
				// accidental sharing of object between all instances.
				defaultHeaders : {
					'Content-Type' : 'application/json; charset=utf-8;'
				},
				requestHeaders : {}
			});

			Ext.apply(this, config);

			this.addEvents(
				/**
				 * @event connectionparalyzed
				 * Fired when the window is about to be unloaded. At this moment
				 * WebApp will shutdown and start dropping all communication with the PHP
				 * @param {Zarafa.core.Request} request 
				 * @param {Zarafa.core.data.ParalyzeReason} reason The reason to paralyze the WebApp
				 */
				'connectionparalyzed',
				/**
				 * @event connectioninterrupted
				 * Fired when the connection to the server has been lost, but is likely
				 * to be restored soon. WebApp will start pausing all communication with
				 * the PHP until the connection has been restored.
				 * @param {Zarafa.core.Request} request 
				 * @param {Zarafa.core.PingService} service The ping service which will
				 * be used to ping for the connection restoration.
				 */
				'connectioninterrupted',
				/**
				 * @event connectionrestored
				 * Fired when the connection to the server has been restored. This will
				 * resend any requests to the PHP which were scheduled while the connection
				 * was lost.
				 * @param {Zarafa.core.Request} request 
				 */
				'connectionrestored',
				/**
				 * @event beforesend
				 * Fires before the XmlHttpRequest sends a request to the server.
				 * Return false to not send request to server.
				 * @param {Zarafa.core.Request} request 
				 * @param {window.XMLHttpRequest} xmlHttpRequest XmlHttpRequest object
				 */
				'beforesend',
				/**
				 * @event aftersend
				 * Fires after the XmlHttpRequest sent a request to the server.
				 * @param {Zarafa.core.Request} request 
				 * @param {window.XMLHttpRequest} xmlHttpRequest XmlHttpRequest object
				 */
				'aftersend'
			);

			Zarafa.core.Request.superclass.constructor.call(this, config);

			// Initialize all private variables
			this.initialize();
		},

		/**
		 * Initialize all private variables of the Request object
		 * @private
		 */
		initialize : function()
		{
			paralyzed = false;
			interrupted = false;
			requestIdBase = 0;
			zarafaTag = undefined;
			hasJson = false;
			hasData = false;
			queuedRequests = [];
			activeRequests = {};
			queuedInterruptedHttpRequests = [];
			subSystemId = this.subSystemPrefix + '_' + new Date().getTime();
		},

		/**
		 * Set the {@link #paralyzed} property which will prevent any requests from
		 * being send out to the server. This will fire the {@link #connectionparalyzed} event.
		 * @param {Zarafa.core.data.ParalyzeReason} reason The reason to paralyze the WebApp
		 */
		paralyze : function(reason)
		{
			if (this.isParalyzed()) {
				return;
			}

			paralyzed = true;
			this.fireEvent('connectionparalyzed', this, reason);
		},

		/**
		 * @return {Boolean} True if the connection is {@link #paralyzed}.
		 */
		isParalyzed : function()
		{
			return paralyzed;
		},

		/**
		 * Set the {@link #interrupted} property which will pause all requests to be send out
		 * to the server. These will be send when the connection is {@link #restore restored}.
		 * @private
		 */
		interrupt : function()
		{
			if (this.isInterrupted() || this.isParalyzed()) {
				return;
			}

			interrupted = true;

			// Instantiate the PingService for polling the
			// server for the recovery of the connection.
			var service = new Zarafa.core.PingService({
				url : this.defaultUrl,
				headers : this.defaultHeaders
			});

			// Add event handler which will restore interaction with
			// the PHP server when the link has been restored.
			service.on('restored', this.restore, this);

			// Add some event handlers which will stop the PingService
			// as soon as the connection is restored, or if the WebApp
			// is being paralyzed. We defer the connectionrestored by 1 ms,
			// so we force the 'stop' event from the service to be fired after
			// all 'restored' event handlers from the service have been handled.
			this.on('connectionparalyzed', service.stop, service);
			this.on('connectionrestored', service.stop, service, { delay : 1 });

			// Fire the event, allow interested parties to register
			// for events on the service before we start it.
			this.fireEvent('connectioninterrupted', this, service);

			// start the service
			service.start();
		},

		/**
		 * Clear the {@link #interrupted} property which will restore all requests which were
		 * paused while the connection was interrupted.
		 * @param {Zarafa.core.PingService} service This object
		 * @param {Object} response The response as send by the server
		 * @private
		 */
		restore : function(service, response)
		{
			if (!this.isInterrupted()) {
				return;
			}

			// If the response object indicates that our session
			// is no longer active, we must paralyze the WebApp
			// as no further requests can be send to the server.
			if (response && response.active === false) {
				this.paralyze(Zarafa.core.data.ParalyzeReason.SESSION_EXPIRED);
				return;
			} else {
				interrupted = false;
				this.fireEvent('connectionrestored', this);

				// If there was a HTTP request queued, we should
				// send the first one now. The next one will be
				// send when the first one has responded. (This prevents
				// a DDOS if the connection was lost for a long time,
				// and many clients are connected to the server).
				if (this.hasQueuedHttpRequests()) {
					this.dequeueHttpRequest();
				}
			}
		},

		/**
		 * @return {Boolean} True if the connection is {@link #interrupted}.
		 */
		isInterrupted : function()
		{
			return interrupted;
		},

		/**
		 * Sets request headers that will be sent with every request made through {@link Zarafa.core.Request} object.
		 * @param {String} key header key.
		 * @param {String} value header value.
		 */
		setRequestHeader: function(key, value)
		{
			this.requestHeaders[key] = value;
		},

		/**
		 * Generates a new unique Request ID, which can be used to match
		 * the request with a response.
		 * @param {String} prefix (optional) The prefix for the unique request id.
		 * @return {String} request ID
		 */
		getRequestId : function(prefix)
		{
			return (prefix || 'z-gen') + (++requestIdBase);
		},

		/**
		 * Function will be used to cancel a previously generated request which is waiting for the server request.
		 * When doing multiple requests we need to make sure that previous requests are cancelled otherwise data 
		 * can be overwritten by previous requests. Special requirement was that we can't do {@link window.XMLHttpRequest#abort}
		 * as it will abort the whole request and discard data that was added to response by server side, which is not
		 * good when server has added any notification data to response (like new mail), so we will need to overwrite
		 * responseHandler of this particular request with {@link Zarafa.core.data.DummyResponseHandler}, which will
		 * have empty functions for discarding response came from server and notifications will be handled normally.
		 * @param {String} requestId The request id.
		 */
		cancelActiveRequest : function(requestId)
		{
			var responseRouter = container.getResponseRouter();

			// If the request is still queued, we have an opportunity to
			// prevent the entire request. However this is only possible
			// if no other request was queued, as then we can cancel all the
			// pending data. If that is not the case, we have to register the
			// DummyResponseHandler
			if (queuedRequests.indexOf(requestId) >= 0) {
				if (queuedRequests.length === 1) {
					// Reset environment
					this.reset();
					responseRouter.removeRequestResponseHandler(requestId);
				} else {
					responseRouter.addRequestResponseHandler(requestId, new Zarafa.core.data.DummyResponseHandler());
				}
				return;
			}

			// If there is a link interruption, then the request might still be queued.
			// This means we can interrupt the request before it is being send to the
			// server.
			if (this.hasQueuedHttpRequests()) {
				// Search through the list of pending HTTP requests
				// to find the one which holds our cancelled request id.
				for (var i = 0; i < queuedInterruptedHttpRequests.length; i++) {
					var xhrObj = queuedInterruptedHttpRequests[i];
					var index = xhrObj.queuedRequests.indexOf(requestId);

					if (index >= 0) {
						// We found the request, but now comes the problem.
						// We can only cancel the entire HTTPRequest if this
						// was the only request on the object. Because we don't
						// know which part of the JSON corresponds to this request.
						if (xhrObj.queuedRequests.length === 1) {
							queuedInterruptedHttpRequests.splice(i, 1);
							responseRouter.removeRequestResponseHandler(requestId);
							return;
						}

						// If we can't cancel the request, we can break
						// the loop and just change the response handler.
						break;
					}
				}

				// We didn't find the request to which the request corresponds,
				// or we couldn't cancel the entire request. In either case,
				// we have to register the DummyResponseHandler.
				responseRouter.addRequestResponseHandler(requestId, new Zarafa.core.data.DummyResponseHandler());
				return;
			}

			// If the request has been send out to the server, we cannot cancel
			// it and thus we will have to register the DummyResponseHandler to
			// prevent the response from being processed. However we can mark
			// the corresponding xmlHttpRequest so we will not retry it if all
			// requests have been cancelled.
			var xhrObj = activeRequests[requestId];
			if (Ext.isDefined(xhrObj)) {
				// Only prevent the retry if all requests attached
				// to the xmlHttpRequest have been cancelled.
				if (xhrObj.queuedRequests.length === 1) {
					xhrObj.preventRetry = true;
				}
				responseRouter.addRequestResponseHandler(requestId, new Zarafa.core.data.DummyResponseHandler());
				return;
			}
		},

		/**
		 * Queue a request to {@link #queuedRequests}. This can be dequeued
		 * in {@link #dequeueRequests}.
		 * @param {String} The request to be queued
		 * @private
		 */
		queueRequest : function(request)
		{
			queuedRequests.push(request);
		},

		/**
		 * Obtain and dequeue any requests from {@link #queuedRequests}. Use
		 * {@link #activateRequests} to add them the the {@link #activeRequests} array
		 * once the request has been send out.
		 * @return {Array} The array of previously queued requests
		 * @private
		 */
		dequeueRequests : function()
		{
			var requests = queuedRequests;
			queuedRequests = [];
			return requests;
		},

		/**
		 * Move all requests into {@link #activeRequests}. The provided requests
		 * should previously have been obtained through {@link #dequeueRequests}.
		 * @param {Array} requests The list of requests to activate.
		 * @private
		 */
		activateRequests : function(requests, xmlHttpRequest)
		{
			for (var i = 0; i < requests.length; i++) {
				activeRequests[requests[i]] = xmlHttpRequest;
			}
		},

		/**
		 * Remove all given requests from the {@link #activeRequests} queue.
		 * @param {Array} requests The list of requests to complete
		 * @private
		 */
		completeRequests : function(requests)
		{
			for (var i = 0, len = requests.length; i < len; i++) {
				delete activeRequests[requests[i]];
			}
		},

		/**
		 * Prepare a {@link XMLHttpRequest} object which will be used to transmit data to the server for processing.
		 * it also registers a callback function for onreadystatechange event that will be called when server sends response.
		 * @param {Mixed} requestData data that will be send using this request object, for XML request it
		 * will be XML document or else it will be JSON string.
		 * @param {Boolean} encoded True of the requestData is encoded, and must be decoded to be used.
		 * @param {String} url (optional) url to post to. Defaults to {@link #defaultUrl}
		 * @param {Object} headers (optional) headers to apply to the request. Defaults to {@link #defaultHeaders}
		 * @return {XMLHttpRequest} The initialized XMLHttpRequest 
		 * @private
		 */
		prepareHttpRequest : function(requestData, encoded, url, headers)
		{
			var xmlHttpRequest = new XMLHttpRequest();

			if (!url) {
				url = this.defaultUrl;
				url = Ext.urlAppend(url, 'subsystem=' + subSystemId);
			}

			xmlHttpRequest.open('POST', url, true);
			xmlHttpRequest.requestUrl = url;

			// Apply header from argument, or apply the default headers
			headers = Ext.apply({}, this.requestHeaders, headers || this.defaultHeaders);
			for (var key in headers) {
				xmlHttpRequest.setRequestHeader(key, headers[key]);
			}
			xmlHttpRequest.requestHeaders = headers;

			// Store requestData into the xmlHttpRequest, when the request failed,
			// we can still determine report the failure back to the requestee.
			xmlHttpRequest.requestData = requestData;
			xmlHttpRequest.requestDataEncoded = encoded;
			xmlHttpRequest.queuedRequests = this.dequeueRequests();

			return xmlHttpRequest;
		},

		/**
		 * Clone a {@link XMLHttpRequest} instance which was prepared by {@link #prepareHttpRequest} and
		 * has previously been attempted to be {@link #sendHttpRequest send out} but failed due to a broken
		 * connection. This will clone the instance, so the request can be retried when the connection
		 * returns.
		 * @param {XMLHttpRequest} xmlHttpRequest The request object to clone
		 * @return {XMLHttpRequest} The cloned XMLhttpRequest object
		 * @private
		 */
		cloneRequest : function(xmlHttpRequest)
		{
			var cloneRequest = new XMLHttpRequest();
			cloneRequest.open('POST', xmlHttpRequest.requestUrl, true);
			cloneRequest.requestUrl = xmlHttpRequest.requestUrl;

			var headers = xmlHttpRequest.requestHeaders;
			for (var key in headers) {
				cloneRequest.setRequestHeader(key, headers[key]);
			}
			cloneRequest.requestHeaders = headers;

			cloneRequest.requestData = xmlHttpRequest.requestData;
			cloneRequest.requestDataEncoded = xmlHttpRequest.requestDataEncoded;
			cloneRequest.queuedRequests = xmlHttpRequest.queuedRequests;

			return cloneRequest;
		},

		/**
		 * Send out a {@link XMLHttpRequest} which was initialized by {@link #prepareHttpRequest}. This will
		 * perform the final step for transmitting the request, by firing the {@link #beforesend} event,
		 * and {@link #activeRequests activating the queued requests}.
		 * @param {XMLHttpRequest} xmlHttpRequest The request object
		 * @private
		 */
		sendHttpRequest : function(xmlHttpRequest)
		{
			var requestData = xmlHttpRequest.requestData;
			var requests = xmlHttpRequest.queuedRequests;

			// When the connection paralyzed, we cannot handle any incoming data anymore.
			// So all outgoing requests will be dropped.
			if (this.isParalyzed()) {
				return;
			}

			if (this.fireEvent('beforesend', this, xmlHttpRequest) === false) {
				return;
			}

			// Register the onready StateChange event handler
			xmlHttpRequest.onreadystatechange = this.stateChange.createDelegate(this, [xmlHttpRequest]);

			// Move the queued requests to the active list.
			this.activateRequests(requests, xmlHttpRequest);

			// send request
			xmlHttpRequest.send(requestData);

			this.fireEvent('aftersend', this, xmlHttpRequest);
		},

		/**
		 * {@link #queuedInterruptedHttpRequests Queue} a {@link XMLHttpRequest} which was initialized by
		 * {@link #prepareHttpRequest}. This will keep the request instance until it can be send
		 * to the server at a {@link #dequeueHttpRequest later time}.
		 * @param {XMLHttpRequest} xmlHttpRequest The request object
		 * @private
		 */
		queueHttpRequest : function(xmlHttpRequest)
		{
			queuedInterruptedHttpRequests.push(xmlHttpRequest);
		},

		/**
		 * Check if there are queued {@link #queuedInterruptedHttpRequests HTTP requests}
		 * @return {Boolean} True if there are queued HTTP requests
		 * @private
		 */
		hasQueuedHttpRequests : function()
		{
			return !Ext.isEmpty(queuedInterruptedHttpRequests);
		},

		/**
		 * Dequeue a {@link #queuedInterruptedHttpRequests HTTP request} and {@link #sendHttpRequest send it} to the server
		 * @private
		 */
		dequeueHttpRequest : function()
		{
			var xmlHttpRequest = queuedInterruptedHttpRequests.shift();
			if (xmlHttpRequest) {
				this.sendHttpRequest(xmlHttpRequest);
			}
		},

		/**
		 * Called by XMLHttpRequest when a response from the server is coming in. When the entire response has been
		 * completed it calls {@link Zarafa.core.ResponseRouter#receive receive}} which handles the rest of the process.
		 * @param {Object} xmlHttpRequest The raw HTTP request object that is used for communication.
		 * @private
		 */
		stateChange : function(xmlHttpRequest)
		{
			var requestIds = xmlHttpRequest.queuedRequests;
			var responseRouter = container.getResponseRouter();
			var response;

			// When the connection is paralyzed, we cannot handle any incoming data anymore.
			// All incoming responses from the server will be dropped.
			if (this.isParalyzed()) {
				return;
			}

			// The readyState can be 4 values:
			//  0 - Object is created, but not initialized
			//  1 - Request has been opened, but send() has not been called yet
			//  2 - send() has been called, no data available yet
			//  3 - Some data has been received, responseText nor responseBody are available
			//  4 - All data has been received
			//
			// readyState 0 - 3 can be completely ignored by us, as they are only updates
			// about the current progress. Only on readyState 4, should we continue and
			// start checking for the response status.
			if (xmlHttpRequest.readyState != 4) {
				return;
			}

			// The transaction is complete, all requests must now be cleared from the list.
			// Note that when we got a HTTP error, the requests are still removed, since
			// the HTTP request is no longer pending.
			this.completeRequests(requestIds);

			// HTTP request must have succeeded
			switch (xmlHttpRequest.status) {
				case 401: /* Unauthorized */
					// Indicate that the user is no longer logged in, and
					// he must re-authenticate. This must be done through the
					// normal logon page, so here we just paralyze the Request.
					// The exact reason for the paralyzation can be found in the
					// headers.
					var reason = xmlHttpRequest.getResponseHeader('X-Zarafa-Hresult');
					if (reason === 'MAPI_E_INVALID_WORKSTATION_ACCOUNT') {
						this.paralyze(Zarafa.core.data.ParalyzeReason.SESSION_INVALID);
					} else {
						this.paralyze(Zarafa.core.data.ParalyzeReason.SESSION_EXPIRED);
					}
					return;
				case 500: /* Internal Server Error */
					// The connection is present, if there still are queued
					// HTTP requests, we can send the next one right now.
					if (this.hasQueuedHttpRequests()) {
						this.dequeueHttpRequest();
					}

					// Indicate that the request failed
					// inside the server and exit the function.
					this.receiveFailure(xmlHttpRequest);
					return;
				case 200: /* OK */
					// The connection is present, if there still are queued
					// HTTP requests, we can send the next one right now.
					if (this.hasQueuedHttpRequests()) {
						this.dequeueHttpRequest();
					}
					break;
				default: /* Connection errors */
					// Interrupt the connection
					this.interrupt();

					// Clone the XMLHttpRequest and queue it
					// for when the connection is restored.
					if (xmlHttpRequest.preventRetry !== true) {
						var clone = this.cloneRequest(xmlHttpRequest);
						this.queueHttpRequest(clone);
					}
					return;
			}

			// Depending on the response type, convert it into a data Object.
			if (xmlHttpRequest.responseText) {
				// JSON response
				response = Ext.decode(xmlHttpRequest.responseText);
			} else {
				// XML response is not supported
				this.receiveFailure(xmlHttpRequest);
				return;
			}

			// Check for empty response, sometimes the PHP server doesn't bother with
			// responding with any data.
			if (Ext.isEmpty(response) || Ext.isEmpty(response.zarafa)) {
				this.receiveFailure(xmlHttpRequest);
				return;
			}

			responseRouter.receive(response);
		},

		/**
		 * Called when the {@link XMLHttpRequest} object indicates a problem
		 * in the returned data from the server. This will call
		 * {@link Zarafa.core.ResponseRouter#receiveFailure receiveFailure} on
		 * the {@link Zarafa.core.ResponseRouter ResponseRouter}.
		 * @param {XMLHttpRequest} xmlHttpRequest The xmlHttpRequest which contains the problem
		 * @private
		 */
		receiveFailure : function(xmlHttpRequest)
		{
			var responseRouter = container.getResponseRouter();
			var requestData = xmlHttpRequest.requestData;
			if (xmlHttpRequest.requestDataEncoded) {
				requestData = Ext.decode(requestData);
			}
			responseRouter.receiveFailure(requestData, xmlHttpRequest);
		},
	
		/**
		 * Resets the {@link Zarafa.core.Request Request} object and prepares it for
		 * accepting new calls to {@link #addRequest} or {@link #singleRequest}.
		 */
		reset : function()
		{
			queuedRequests = [];

			zarafaTag = {
				'zarafa' : {}
			};
			hasJson = false;
			hasData = false;
		},

		/**
		 * Adds a single request to the Request object. This is a combination of a module name, id and action.
		 * The parameters objected will be encoded and added into the action tag.
		 * The callbacks are used when the responds for this specific request has returned.
		 * @param {String} moduleName name of the module to communicate with (i.e. 'addressbooklistmodule')
		 * @param {String} actionType action to perform (i.e. 'list' or 'globaladdressbook')
		 * @param {Object} actionData data that will included in the action tag (i.e. { restriction : { name : 'piet' } })
		 * @param {Zarafa.core.data.AbstractResponseHandler} responseHandler The response handler which must be
		 * used for handling the responds for this specific request.
		 * @return {String} The unique Request ID which was assigned to this request.
		 */
		addRequest : function(moduleName, actionType, actionData, responseHandler)
		{
			if (Ext.isEmpty(zarafaTag)) {
				throw 'Request object not initialised. Call reset() first';
			}
			if (hasData) {
				throw 'Request object initialized with RAW data';
			}

			var requestId = this.getRequestId(moduleName);
			this.queueRequest(requestId);

			if (Ext.isDefined(responseHandler)) {
				container.getResponseRouter().addRequestResponseHandler(requestId, responseHandler);
			}

			actionData = Ext.value(actionData, {});

			// create new module tag if not present
			if (!Ext.isDefined(zarafaTag.zarafa[moduleName])) {
				zarafaTag.zarafa[moduleName] = {};
			}

			// create new module id tag if not present
			if (!Ext.isDefined(zarafaTag.zarafa[moduleName][requestId])) {
				zarafaTag.zarafa[moduleName][requestId] = {};
			}

			// add action data
			zarafaTag.zarafa[moduleName][requestId][actionType] = actionData;

			// JSON data was added
			hasJson = true;

			return requestId;
		},

		/**
		 * Adds a single request to the Request object. Opposed to {@link #addRequest} this will be raw data.
		 * The paramenters object will be placed to the action tag.
		 * The callbacks are used when the responds for this specific request has returned.
		 * @param {String} moduleName name of the module to communicate with (i.e. 'addressbooklistmodule')
		 * @param {String} actionType action to perform (i.e. 'list' or 'globaladdressbook')
		 * @param {Object} actionData data that will included in the action tag
		 * @param {Zarafa.core.data.AbstractResponseHandler} responseHandler The response handler which must be
		 * used for handling the responds for this specific request.
		 * @return {String} The unique Request ID which was assigned to this request.
		 */
		addDataRequest : function(moduleName, actionType, actionData, responseHandler)
		{
			if (Ext.isEmpty(zarafaTag)) {
				throw 'Request object not initialised. Call reset() first';
			}
			if (hasJson) {
				throw 'Request object intitialized with JSON data';
			}
			if (hasData) {
				throw 'Request object already contains RAW data';
			}

			var requestId = this.getRequestId(moduleName);
			this.queueRequest(requestId);

			if (Ext.isDefined(responseHandler)) {
				container.getResponseRouter().addRequestResponseHandler(requestId, responseHandler);
			}

			// add action data
			zarafaTag = actionData;

			// Raw data was added
			hasData = true;

			return requestId;
		},

		/**
		 * Calls {@link #prepareHttpRequest} to actually create the request using passed data,
		 * and calls {@link #sendHttpRequest} to send request.
		 * @param {String} url (optional) url to post to. Defaults to {@link #defaultUrl}
		 * @param {Object} headers (optional) headers to apply to the request. Defaults to {@link #defaultHeaders}
		 */
		send : function(url, headers)
		{
			if (Ext.isEmpty(zarafaTag)) {
				throw 'Request object not initialised. Call reset() first';
			}

			if (Ext.isEmpty(queuedRequests)) {
				throw 'No requests have been added. Use addRequest()';
			}

			if (!this.isParalyzed()) {
				var xmlHttpRequest = this.prepareHttpRequest(hasJson ? Ext.encode(zarafaTag) : zarafaTag, hasJson, url, headers);
				if (this.isInterrupted() || this.hasQueuedHttpRequests()) {
					this.queueHttpRequest(xmlHttpRequest);
				} else {
					this.sendHttpRequest(xmlHttpRequest);
				}
			}

			// All data has been send, reset the zarafaTag to
			// prevent sending the same request twice.
			zarafaTag = undefined;
			hasJson = false;
			hasData = false;
		},

		/**
		 * Convenience method for performing a single JSON request to the server.
		 * It calls {@link #reset reset()}, {@link #addRequest addRequest()}, and {@link #send send()} in turn.
		 * @param {String} moduleName name of the module to communicate with (i.e. 'addressbooklistmodule')
		 * @param {String} actionType action to perform (i.e. 'list' or 'globaladdressbook')
		 * @param {Object} actionData data that will included in the action tag (i.e. { restriction : { name : 'piet' } })
		 * @param {Zarafa.core.data.AbstractResponseHandler} responseHandler The response handler which must be
		 * used for handling the responds for this specific request.
		 * @return {String} The unique request ID which was assigned to this transaction object.
		 */
		singleRequest : function(moduleName, actionType, actionData, responseHandler)
		{
			var requestId;

			this.reset();
			requestId = this.addRequest(moduleName, actionType, actionData, responseHandler);
			this.send();

			return requestId;
		}
	};
})());