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.lbSend('POST', 'Accounts/login', params,
			this._onOpen.bind(this, callback, remember));
	}

	/*
	 * Called when open operation is done.
	 */
	,_onOpen: function(callback, remember, json, error) {
		if (json) {
			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.lbSend('POST', 'Accounts/logout', null,
			this._onClose.bind(this, callback));
		this._closeClient();
	}

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

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

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

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

	/**
	 * Ends the user supplanting and restores the last login.
	 **/
	,supplantEnd: function() {
		this.lbSend('POST', 'Accounts/logout');
		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);

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

		this._addRequest();
	}

	,sendFormData: function(formData, callback) {
		var request = new XMLHttpRequest();
		request.open('POST', '', true);
		if (this.token)
			request.setRequestHeader('Authorization', this.token);
		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) {
		var request = new XMLHttpRequest();
		request.open(method, url, true);
		request.setRequestHeader('Content-Type',
			'application/x-www-form-urlencoded');
		if (this.token)
			request.setRequestHeader('Authorization', this.token);
		request.onreadystatechange =
			this._onStateChange.bind(this, request, callback);
		request.send(Vn.Url.makeUri(params));

		this._addRequest();
	}

	,lbSend: function(method, url, params, callback) {
		var request = new XMLHttpRequest();
		request.open(method, `api/${url}`, true);
		request.setRequestHeader('Content-Type',
			'application/json;charset=utf-8');
		if (this.token)
			request.setRequestHeader('Authorization', this.token);
		request.onreadystatechange =
			this._onStateChange.bind(this, request, callback);
		request.send(params && JSON.stringify(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 (err) {
				console.warn(err);
			}
				
			if (contentType != 'application/json') {
				var ex = new JsonException();
				ex.message = request.statusText;
				ex.code = request.status;
				throw ex;
			}

			var json;
			var jsData;

			if (request.responseText)
				json = JSON.parse(request.responseText);
			if (json)
				jsData = json.data || json;

			if (request.status >= 200 && request.status < 300) {
				data = jsData;
			} else {
				var exception = jsData.exception;
				var error = jsData.error;
			
				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;
				} else if (error) {
					var ex = new Error();
					ex.name = error.name;
					ex.message = error.message;
					ex.code = error.code;
					ex.statusCode = request.status;
				}

				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);
		}
	}
});