/**
 * Copyright © Volker Schukai and all contributing authors, {{copyRightYear}}. All rights reserved.
 * Node module: @schukai/monster
 *
 * This source code is licensed under the GNU Affero General Public License version 3 (AGPLv3).
 * The full text of the license can be found at: https://www.gnu.org/licenses/agpl-3.0.en.html
 *
 * For those who do not wish to adhere to the AGPLv3, a commercial license is available.
 * Acquiring a commercial license allows you to use this software without complying with the AGPLv3 terms.
 * For more information about purchasing a commercial license, please contact Volker Schukai.
 *
 * SPDX-License-Identifier: AGPL-3.0
 */

import { internalSymbol, instanceSymbol } from "../../../constants.mjs";
import { isString, isObject } from "../../../types/is.mjs";
import { WebConnect as NetWebConnect } from "../../../net/webconnect.mjs";
import { Message } from "../../../net/webconnect/message.mjs";
import { Server } from "../server.mjs";

export { WebConnect };

/**
 * @private
 * @type {symbol}
 *
 * hint: this name is used in the tests. if you want to change it, please change it in the tests as well.
 */
const webConnectSymbol = Symbol("connection");

/**
 * The RestAPI is a class that enables a REST API server.
 *
 * @fragments /fragments/libraries/data/datasource/server/webconnect/
 *
 * @externalExample ../../../../example/data/datasource/server/webconnect.mjs
 * @example /examples/libraries/data/datasource/server/webconnect/simple/ Configure a realtime datasource bridge
 * @example /examples/libraries/data/datasource/server/webconnect/message-queue/ Read queued realtime messages through the datasource API
 * @example /examples/libraries/data/datasource/server/webconnect/transformed-read/ Apply read mapping to incoming socket payloads
 * @example /examples/libraries/data/datasource/server/webconnect/write-envelope/ Prepare outgoing socket writes with sheathing options
 * @license AGPLv3
 * @since 3.1.0
 * @copyright Volker Schukai
 * @summary A realtime datasource that bridges WebConnect messages into the datasource API.
 */
class WebConnect extends Server {
	/**
	 *
	 * @param {Object} [options] options contains definitions for the datasource.
	 */
	constructor(options) {
		super();

		if (isString(options)) {
			options = { url: options };
		}

		if (!isObject(options)) options = {};
		this.setOptions(options);
		this[webConnectSymbol] = new NetWebConnect({
			url: this.getOption("url"),
			connection: {
				timeout: this.getOption("connection.timeout"),
				reconnect: {
					timeout: this.getOption("connection.reconnect.timeout"),
					attempts: this.getOption("connection.reconnect.attempts"),
					enabled: this.getOption("connection.reconnect.enabled"),
				},
			},
		});
	}

	/**
	 *
	 * @return {Promise}
	 */
	connect() {
		return this[webConnectSymbol].connect();
	}

	/**
	 * @return {boolean}
	 */
	isConnected() {
		return this[webConnectSymbol].isConnected();
	}

	/**
	 * This method is called by the `instanceof` operator.
	 * @return {symbol}
	 */
	static get [instanceSymbol]() {
		return Symbol.for("@schukai/monster/data/datasource/server/webconnect");
	}

	/**
	 * @property {string} url=undefined Defines the resource that you wish to fetch.
	 * @property {Object} connection
	 * @property {Object} connection.timeout=5000 Defines the timeout for the connection.
	 * @property {Number} connection.reconnect.timeout The timeout in milliseconds for the reconnect.
	 * @property {Number} connection.reconnect.attempts The maximum number of reconnects.
	 * @property {Bool} connection.reconnect.enabled If the reconnect is enabled.
	 * @property {Object} write={} Options
	 * @property {Object} write.mapping the mapping is applied before writing.
	 * @property {String} write.mapping.transformer Transformer to select the appropriate entries
	 * @property {Monster.Data.Datasource~exampleCallback[]} write.mapping.callback with the help of the callback, the structures can be adjusted before writing.
	 * @property {Object} write.sheathing
	 * @property {Object} write.sheathing.object Object to be wrapped
	 * @property {string} write.sheathing.path Path to the data
	 * @property {Object} read={} Options
	 * @property {String} read.path Path to data
	 * @property {Object} read.mapping the mapping is applied after reading.
	 * @property {String} read.mapping.transformer Transformer to select the appropriate entries
	 * @property {Monster.Data.Datasource~exampleCallback[]} read.mapping.callback with the help of the callback, the structures can be adjusted after reading.
	 */
	get defaults() {
		return Object.assign({}, super.defaults, {
			url: undefined,
			write: {
				mapping: {
					transformer: undefined,
					callbacks: {},
				},
				sheathing: {
					object: undefined,
					path: undefined,
				},
			},
			read: {
				mapping: {
					transformer: undefined,
					callbacks: {},
				},
				path: undefined,
			},
			connection: {
				timeout: 5000,
				reconnect: {
					timeout: 1000,
					attempts: 1,
					enabled: false,
				},
			},
		});
	}

	/**
	 * This method closes the connection.
	 *
	 * @return {Promise}
	 */
	close() {
		return this[webConnectSymbol].close();
	}

	/**
	 * @return {Promise}
	 */
	read() {
		return new Promise((resolve, reject) => {
			while (this[webConnectSymbol].dataReceived() === true) {
				let obj = this[webConnectSymbol].poll();
				if (!isObject(obj)) {
					reject(new Error("The received data is not an object."));
					return;
				}

				if (!(obj instanceof Message)) {
					reject(new Error("The received data is not a Message."));
					return;
				}

				obj = obj.getData();
				obj = this.transformServerPayload.call(this, obj);
				this.set(obj);
			}

			resolve(this.get());
		});
	}

	/**
	 * @return {Promise}
	 */
	write() {
		const obj = this.prepareServerPayload(this.get());
		return this[webConnectSymbol].send(obj);
	}

	/**
	 * @return {RestAPI}
	 */
	getClone() {
		return new WebConnect(this[internalSymbol].getRealSubject()["options"]);
	}
}
