Ext.namespace('Zarafa.core');

/**
 * @class Zarafa.core.ResponseRouter
 * @extends Ext.util.Observable
 *
 * The router for Responses to requests made by the {@link Zarafa.core.Request Request} object.
 * Each response is delivered to its destination {@link Ext.data.Store store} through the
 * {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler}.
 * Upon recieving a response, the ResponseRouter will determine if it is a response to
 * a direct request from the {@link Zarafa.core.Request Request Object} or a notification
 * generated by the PHP-side.
 * If the response came from a request from the {@link Zarafa.core.Request Request Object} a
 * {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} will have been registered
 * by the {@link Ext.data.DataProxy Proxy} which made the request. If the
 * {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} is not available the
 * response is considered a Notification, in which case the
 * {@link Zarafa.core.data.NotificationResolver NotificationResolver} is used to generate
 * a special {@link Zarafa.core.data.AbstractNotificationResponseHandler ResponseHandler} which can
 * update the {@link Ext.data.Store stores} which contain the updated {@link Ext.data.Record records}.
 */
Zarafa.core.ResponseRouter = Ext.extend(Ext.util.Observable, {
	/**
	 * The collection of {@link Zarafa.core.data.AbstractResponseHandler ResponseHandlers}
	 * stored by using the moduleid of the outgoing request.
	 * @property
	 * @type Object
	 */
	responseHandlers : undefined,

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

		Ext.applyIf(config, {
			responseHandlers : {}
		});

		this.addEvents(
			/**
			 * @event beforereceive
			 * Main event which is triggered when data has been received from
			 * the PHP server, and is about to be processed by the router.
			 * @param {Object} data The data which was received by the router.
			 */
			'beforereceive',
			/**
			 * @event afterreceive
			 * Main event which is triggered when the data which has been received from
			 * the PHP server has been processed.
			 * @param {Object} data The data which was received by the router.
			 */
			'afterreceive',
			/**
			 * @event receiveexception
			 * Main event which is triggered when a Request has failed, or the response
			 * doesn't contain sufficient data to handle it.
			 * @param {Object} requestdata The request data which was send to the server.
			 * @param {Object} xmlHttpRequest The raw browser response object.
			 */
			'receiveexception',
			/**
			 * @event response
			 * Main event which is triggered when a response has been received
			 * from the PHP server.
			 * @param {String} module The module name for this response
			 * @param {String} id The module id for this reponse
			 * @param {Object} response The response which was received.
			 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the response was received
			 * @return {Boolean} False to cancel the handling of the response by ResponseHandlers
			 */
			'response'
		);

		Ext.apply(this, config);

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

	/**
	 * Register a {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} to the
	 * Response Router. This handler will be used to handle the Response for the request
	 * with the given identifier.
	 * @param {String} id The unique request identifier on which the ResponseHandler
	 * must be registerdd.
	 * @param {Zarafa.core.data.AbstractResponseHandler} handler The ResponseHandler
	 * which must be registered for the given id.
	 */
	addRequestResponseHandler : function(id, handler)
	{
		this.responseHandlers[id] = handler;
	},

	/**
	 * Get a {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} from
	 * the {@link #responseHandlers} which has been registerd for the given module identifier.
	 * This will automatically deregister the handler to prevent it being used twice.
	 * @param {String} id The unique request identifier on which the ResponseHandler
	 * could be registered.
	 * @return {Zarafa.core.data.AbstractResponseHandler} The registered response handler.
	 * @private
	 */
	getRequestResponseHandler : function(id)
	{
		var handler = this.responseHandlers[id];
		this.removeRequestResponseHandler(id);
		return handler;
	},

	/**
	 * Removes a {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} from the
	 * {@link Zarafa.core.ResponsRouter ResponsRouter} to prevent it being called twice.
	 * @param {String} id The unique request identifier of the ResponseHandler which will be
	 * deregistered from {@link Zarafa.core.ResponsRouter ResponsRouter}.
	 */
	removeRequestResponseHandler : function(id)
	{
		delete this.responseHandlers[id];
	},

	/**
	 * Get a {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler} from
	 * the {@link Zarafa.core.data.NotificationResolver NotificationResolver}. This
	 * will construct a special {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler}
	 * which is dedicated to handling this specific notification.
	 * @param {String} module The module from which the notification originated.
	 * @param {Object} data The response data which was send as notification, this is used
	 * to determine which {@link Ext.data.Store stores} are affected by this notification.
	 * @private
	 */
	getNotificationResponseHandler : function(module, data)
	{
		return container.getNotificationResolver().getHandlerForResponse(module, data);
	},

	/**
	 * This will have a Response from the PHP server and will process it
	 * by delivering the Response over the configured route to the destination.
	 *
	 * @param {Object} data The data object which was received and
	 * must be processed.
	 */
	receive : function(data)
	{
		this.fireEvent('beforereceive', data);

		this.processResponse(data);

		this.fireEvent('afterreceive', data);
	},

	/**
	 * This will report a Receive failure which can be triggered when
	 * the request failed with HTTP-error (e.g. 404) or when the response data
	 * was incomplete. This will go through all
	 * {@link Zarafa.core.data.AbstractResponseHandlers ResponseHandlers} which
	 * are affected by this error to report the error using:
	 * {@link Zarafa.core.data.AbstractResponseHandlers#responseFailure responseFailure}.
	 * @param {Object} requestdata The request data which was send to the server.
	 * @param {Object} xmlHttpRequest The raw browser response object.
	 */
	receiveFailure : function(requestData, xmlHttpRequest)
	{
		this.fireEvent('receiveexception', requestData, xmlHttpRequest);

		// Without requestData we cannot report the request failure
		// back to the requestee.
		if (!Ext.isObject(requestData)) {
			return;
		}

		// Find all registered ResponseHandlers which are affected by this
		// failure and propogate the responseFailure to them, 
		Ext.iterate(requestData.zarafa, function(moduleName, modules) {
			Ext.iterate(modules, function(moduleId, moduleData) {
				var handler = this.getRequestResponseHandler(moduleId);
				if (!Ext.isEmpty(handler)) {
					handler.responseFailure(xmlHttpRequest);
				}
			}, this);
		}, this);
	},

	/**
	 * Resolve all response data into a collection of {@link Zarafa.core.data.AbstractResponseHandlers}
	 * which will be in charge of handling all responsed. The responsehandlers will be returned into
	 * an array which is sorted on priority, meaning that the response handlers should be called in
	 * the order in which they are listed in the array.
	 * @param {Object} data The data from the response
	 * @return {Array} Array of objects containing the data
	 * @private
	 */
	resolveResponseHandlers : function(data)
	{
		var responses = [];
		var notifications = [];

		// Iterate over all modules and ids, and obtain the corresponding
		// ResponseHandlers. We separate the RequestResponses from the notifications.
		Ext.iterate(data, function(moduleName, modules) {
			// iterate over module ids
			Ext.iterate(modules, function(moduleId, moduleData) {
				var handler = {
					moduleName : moduleName,
					moduleId : moduleId,
					moduleData : moduleData
				};

				// Check if a RequestResponse Handler is registered for this moduleId
				handler.handler = this.getRequestResponseHandler(moduleId);
				if (!Ext.isEmpty(handler.handler)) {
					responses.push(handler);
					return;
				}

				// No RequestResponse was available, this is a notification
				handler.handler = this.getNotificationResponseHandler(moduleName, moduleData);
				if (!Ext.isEmpty(handler.handler)) {
					notifications.push(handler);
					return;
				}
			}, this);
		}, this);

		// Return the objects as a single array, the RequestResponses have highest priority,
		// followed by the notifications.
		return responses.concat(notifications);
	},

	/**
	 * Perform a transaction through the {@link Zarafa.core.data.AbstractResponseHandler ResponseHandler}.
	 *
	 * @param {Zarafa.core.data.AbstractResponseHandler} handler The handler which should be used
	 * for handling the response.
	 * @param {String} moduleName The name of the module from which the response was received
	 * @param {String} moduleId The unique id which is used to correlate the response to a request
	 * @param {Object} moduleData The data which was provided for the given moduleName/moduleId
	 * @param {Number} timestamp The {@link Date#getTime timestamp} on which the response was received
	 * @private
	 */
	handleResponse : function(handler, moduleName, moduleId, moduleData, timestamp)
	{
		var success = true;

		// Begin the Response transaction. When the transaction cannot
		// be started, we bail out immediately.
		if (handler.start(moduleName, moduleId, moduleData, timestamp) === false) {
			return;
		}

		if (Ext.isObject(moduleData)) {
			// Iterate over each action, and start handling them with
			// the corresponding actionData. If one of the handlers indicate
			// failure, we only change the 'success' status, but continue
			// with the other handlers. The 'success' status itself will
			// be used when the transaction is being completed.
			Ext.iterate(moduleData, function(actionType, actionData) {
				if (handler.handle(actionType, actionData) === false) {
					success = false;
				}
			}, this);
		}

		// Complete the transaction.
		handler.done(success);
	},

	/**
	 * Processes a response from the server. the data is examined for
	 * any error tags and call error listeners.
	 * @param {Object} jsonData A JSON object containing server response.
	 * @private
	 */
	processResponse : function(jsonData)
	{
		// check for errors, these are global errors which can be generated from kopano.php
		// file, module level errors will be handled by module callback functions.
		if (!Ext.isEmpty(jsonData.zarafa.error)) {
			// Fire the exception event on the DataProxy like this, as the response cannot be matched to a specific proxy.
			Ext.data.DataProxy.fireEvent('exception', Ext.data.DataProxy, 'remote', null, null, jsonData.zarafa, null);
			return;
		}

		// Create the timestamp which is used as receive date for the current response
		var timestamp = new Date().getTime();

		// when all's fine, unpack the server response and obtain the responseHandlers
		var handlers = this.resolveResponseHandlers(jsonData.zarafa);

		for (var i = 0, len = handlers.length; i < len; i++) {
			var handler = handlers[i];
			var moduleName = handler.moduleName;
			var moduleId = handler.moduleId;
			var moduleData = handler.moduleData;
			var responseHandler = handler.handler;

			if (this.fireEvent('response', moduleName, moduleId, moduleData, timestamp) !== false) {
				this.handleResponse(responseHandler, moduleName, moduleId, moduleData, timestamp);
			}
		}
	}
});