var Object = require ('./object');
var JsonException = require ('./json-exception');

/**
 * Handler for JSON rest connections.
 **/
module.exports = new Class
({
	Extends: Object

	,_connected: false
	,_requestsCount: 0
	,token: null

	/**
	 * Initilizes the connection object.
	 **/
	,initialize: function ()
	{
		this.parent ();
		this.fetchToken ();
	}

	,fetchToken: function ()
	{
		var token = null;
	
		if (sessionStorage.getItem ('vnToken'))
			token = sessionStorage.getItem ('vnToken');
		if (localStorage.getItem ('vnToken'))
			token = localStorage.getItem ('vnToken');
			
		this.token = token;
	}
	
	,clearToken: function ()
	{
		this.token = null;
		localStorage.removeItem ('vnToken');
		sessionStorage.removeItem ('vnToken');
	}

	/**
	 * Opens the connection to the REST service.
	 *
	 * @param {String} user The user name
	 * @param {String} password The user password
	 * @param {Boolean} remember Specifies if the user should be remembered
	 * @param {Function} openCallback The function to call when operation is done
	 **/
	,open: function (user, pass, remember, callback)
	{
		if (user !== null && user !== undefined)
		{
			var params = {
				 'user': user
				,'password': pass
				,'remember': remember
			};
		}
		else
			var params = null;

		this.send ('core/login', params,
			this._onOpen.bind (this, callback, remember));
	}

	/*
	 * Called when open operation is done.
	 */
	,_onOpen: function (callback, remember, json, error)
	{
		if (json && json.login)
		{
			this._connected = true;
			this.token = json.token;
			
			var storage = remember ? localStorage : sessionStorage;
			storage.setItem ('vnToken', this.token);

			this.signalEmit ('openned');
		}
		else	
			this._closeClient ();

		if (callback)
			callback (this, this._connected, error);
	}

	/**
	 * Closes the connection to the REST service.
	 *
	 * @param {Function} callback The function to call when operation is done
	 **/
	,close: function (callback)
	{
		this._closeClient ();
		this.send ('core/logout', null,
			this._onClose.bind (this, callback));
	}

	/*
	 * Called when close operation is done.
	 */
	,_onClose: function (callback, json, error)
	{
		this.signalEmit ('closed');

		if (callback)
			callback (this, json === true, error);
	}
	
	,_closeClient: function ()
	{
		this._connected = false;
		this.clearToken ();
	}

	/**
	 * Suppants another user.
	 *
	 * @param {String} user The user name
	 * @param {Function} callback The callback function
	 **/
	,supplantUser: function (user, callback)
	{
		var params = {'supplantUser': user};
		this.send ('core/supplant', params,
			this._onUserSupplant.bind (this, callback));
	}
	
	,_onUserSupplant: function (callback, json, error)
	{
		if (json)
			this.token = json;

		if (callback)
			callback (json != null);
	}

	/**
	 * Ends the user supplanting and restores the last login.
	 **/
	,supplantEnd: function ()
	{
		this.fetchToken ();
	}

	/**
	 * Executes the specified REST service with the given params and calls
	 * the callback when response is received.
	 *
	 * @param {String} restService The service path
	 * @param {Map} params The params to pass to the service
	 * @param {Function} callback The response callback
	 **/
	,send: function (restService, params, callback)
	{
		if (!params)
			params = {};
	
		params['srv'] = 'json:'+ restService;
		
		this.sendWithUrl (params, callback, 'post', '.');
	}
	
	,sendForm: function (form, callback)
	{
		var params = {};
		var elements = form.elements;

		for (var i = 0; i < elements.length; i++)
		if (elements[i].name)
			params[elements[i].name] = elements[i].value;

		this.sendWithUrl (params, callback, 'post', form.action);
	}
	
	,sendFormMultipart: function (form, callback)
	{	
		var formData = new FormData (form);

		if (this.token)
			formData.append ('token', this.token);
	
		var request = new XMLHttpRequest ();
		request.open ('post', form.action, true);
		request.onreadystatechange =
			this._onStateChange.bind (this, request, callback);
		request.send (formData);

		this._addRequest ();
	}

	/*
	 * Called when REST response is received.
	 */
	,sendWithUrl: function (params, callback, method, url)
	{
		if (this.token)
			params['token'] = this.token;

		var request = new XMLHttpRequest ();
		request.open (method, url, true);
		request.setRequestHeader ('Content-Type',
			'application/x-www-form-urlencoded');
		request.onreadystatechange =
			this._onStateChange.bind (this, request, callback);
		request.send (Vn.Url.makeUri (params));

		this._addRequest ();
	}

	,_addRequest: function ()
	{
		this._requestsCount++;
		
		if (this._requestsCount === 1)
			this.signalEmit ('loading-changed', true);
	}
	
	,_onStateChange: function (request, callback)
	{
		if (request.readyState !== 4)
			return;

		this._requestsCount--;
		
		if (this._requestsCount === 0)
			this.signalEmit ('loading-changed', false);

		var data = null;
		var error = null;

		try {		
			if (request.status == 0)
			{
				var ex = new JsonException ();
				ex.message = _('The server does not respond, please check your Internet connection');
				throw ex;
			}
		
			var contentType = null;
			
			try {
				contentType = request
					.getResponseHeader ('Content-Type')
					.split (';')[0]
					.trim ();
			}
			catch (e) {}
				
			if (contentType != 'application/json')
			{
				var ex = new JsonException ();
				ex.message = request.statusText;
				ex.code = request.status;
				throw ex;
			}
		
			var json = JSON.parse (request.responseText);
			var jsData = json.data;
			//var jsWarns = json.warnings;
	
			if (request.status == 200)
			{
				data = jsData;
			}
			else
			{
				var exception = jsData.exception;
			
				if (exception)
					exception = exception
						.replace (/\\/g, '.')
						.replace (/Exception$/, '')
						.replace (/^Vn\.Web\./, '');

				var ex = new JsonException ();
				ex.exception = exception;
				ex.message = jsData.message;
				ex.code = jsData.code;
				ex.file = jsData.file;
				ex.line = jsData.line;
				ex.trace = jsData.trace;
				throw ex;
			}
		}
		catch (e)
		{
			data = null;
			error = e;
		}

		if (callback)
		try {
			callback (data, error);
			error = null;
		}
		catch (e)
		{
			error = e;
		}
		
		if (error)
		{
			if (error.exception == 'SessionExpired')
				this.clearToken ();
		
			this.signalEmit ('error', error);
		}
	}
});