var Cookie = require('libresignage/util/Cookie');
var Util = require('libresignage/util/Util');
var APIError = require('libresignage/api/APIError');
var APIEndpoints = require('libresignage/api/APIEndpoints');
var APISession = require('libresignage/api/APISession');
var Assert = require('assert');

/**
* A class for interfacing with the LibreSignage HTTP API.
*
* @memberof module:libresignage/api
* @alias APIInterface
*/
class APIInterface {
	/**
	* Create a new APIInterface object.
	*
	* The hostname 'host' is only required when using this library in a
	* non-browser context, ie. in node.js. In that case you'll also need
	* to install the 'xmlhttprequest' node.js module included in the
	* LibreSignage devDependencies.
	*
	* Remember to call APIInterface.init() after constructing
	* the object.
	*
	* @constructor
	*
	* @param {string|null|undefined} host The hostname of the API
	*                                     (see description).
	*/
	constructor(host) {
		this.host = host;
		this.session = null;
		this.version = null;
		this.limits = null;

		// Load the XMLHttpRequest module for standalone use.
		if (typeof XMLHttpRequest === 'undefined') {
			Assert.ok(
				this.host != null && this.host != '',
				'Hostname required when using APIInterafce standalone.'
			);
			try {
				global.XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest;
			} catch (e) {
				throw new Error(
					'XMLHttpRequest node.js module required when using ' +
					'APIInterface standalone.'
				);
			}
		}
	}

	/**
	* Call an API endpoint.
	*
	* @param {object} endpoint An endpoint from the APIEndpoints.js file.
	* @param {*}      data     The data to send to the endpoint.
	*
	* @return {Promise} A promise that will resolve with the API response or
	*                   reject with an APIError object.
	*/
	async call(endpoint, data) {
		return new Promise((resolve, reject) => {
			let req = new XMLHttpRequest();
			let conf = {
				'ctype': null,
				'data': null,
				'url': `${this.host ? this.host + '/' : ''}` + `${endpoint.uri}`
			};

			req.addEventListener("load", function() {
				if (this.status !== 200) { reject(new APIError(this)); }
				switch(this.getResponseHeader('Content-Type')) {
					case 'application/json':
						if (this.responseText !== '') {
							resolve(JSON.parse(this.responseText));
						} else {
							resolve(null);
						}
						break;
					default:
						resolve(this.responseText);
						break;
				}
			});
			req.addEventListener("error", function() {
				reject(new APIError(this));
			});
			req.addEventListener("aborted", function() {
				reject(new APIError(this));
			});

			if (endpoint.method === 'POST') {
				conf.ctype = endpoint.req_mime;
				switch(endpoint.req_mime) {
					case 'application/json':
						conf.data = JSON.stringify(data);
						break;
					case 'application/x-www-form-urlencoded':
						conf.data = data;
					case 'multipart/form-data':
						conf.ctype = null; // Let the browser decide.
						conf.data = data;
						break;
					default:
						reject(new Error(`Invalid MIME ${endpoint.req_mime}`));
						break;
				}
			} else if (endpoint.method === 'GET') {
				conf.url += `?${Util.querify(data)}`
			} else {
				reject(new Error(`Invalid endpoint method ${endpoint.method}.`));
			}

			req.open(endpoint.method, conf.url);

			if (conf.ctype !== null) {
				req.setRequestHeader('Content-Type', conf.ctype);
			}

			if (endpoint.auth) {
				if (this.session === null) { reject(new Error('Not logged in.')) }
				req.setRequestHeader('Auth-Token', this.session.get_token());
			}
			req.send(conf.data);
		});
	}

	/**
	* Log in to the API.
	*
	* @param {string} user      The username to use.
	* @param {string} pass      The password to use.
	* @param {bool}   permanent Whether to create a permanent session.
	*/
	async login(user, pass, permanent) {
		this.session = new APISession(this);
		try {
			await this.session.login(user, pass, permanent);
		} catch (e) {
			this.session = null;
			throw e;
		}
	}

	/**
	* Log out from the API.
	*/
	async logout() {
		await this.session.logout();
		this.session = null;
	}

	/**
	* Initialize the API object. You must call this function
	* after constructing the object and before calling any
	* other APIInterface function.
	*/
	async init() {
		let resp = null;
		let token = null;

		resp = await this.call(APIEndpoints.SERVER_LIMITS, null);
		this.limits = resp.limits;

		resp = await this.call(APIEndpoints.VER_INFO, null);
		this.version = {
			ls: resp.ls,
			api: resp.api
		};

		// Attempt to reload web sessions.
		if (
			!this.is_standalone()
			&& (token = Cookie.get('session_token')) != null
		) {
			this.session = new APISession(this);
			try {
				resp = await this.session.reload(token);
			} catch (e) {
				Cookie.remove({'session_token': '', path: '/'});
				this.session = null;
				return;
			}
			if (resp === false) { this.session = null; }
		}
	}

	/**
	* Check whether and APIInterface is running in a standalone context.
	*
	* @return {boolean} True if running in standalone context, false otherwise.
	*/
	is_standalone() { return typeof document === 'undefined'; }

	/**
	* Get the session object of and APIInterface.
	*
	* @return {APISession|null} The APISession object or null if the APIInterface
	*                          is not logged in.
	*/
	get_session() { return this.session; }

	/**
	* Get the version of the host LibreSignage instance.
	*
	* @return {string} The LibreSignage version string.
	*/
	get_ver() { return this.version; }
}
module.exports = APIInterface;
