/**
 * Main client websocket
 * 
 * @author Pierre HUBERT
 */


class WsMessage {
	constructor(info) {
		this.title = info.title
		this.id = info.id
		this.data = info.data
	}

	get hasId() {
		return this.id && this.id.length > 0
	}
}

let requests = {};
let reqCounter = 0;

class UserWebSocket {

	/**
	 * Connect to server
	 */
	static async Connect() {

		try {
			await this.Disconnect();

			console.log("Connect to websocket");

			// Generate an access token
			const token = (await api("ws/token", null, true)).token;

			// Determine websocket URL
			const url = ComunicWeb.__config.apiURL.replace("http", "ws") + "ws?token=" + token

			// Connect to websocket
			this.ws = new WebSocket(url);
			
			// Wait for connection
			this.ws.addEventListener("open", () => {
				console.info("Connected to websocket!");
				SendEvent("wsOpen")
			})
			this.ws.addEventListener("error", (e) => this.Error(e))
			this.ws.addEventListener("close", (e) => this.Closed(e));

			// Handle incoming messages
			this.ws.addEventListener("message", (e) => {
				this.ProcessMessage(new WsMessage(JSON.parse(e.data)));
			})

		} catch(e) {
			this.Error(e);
		}
	}

	/**
	 * Wait for the socket to be connected (if not already)
	 */
	static WaitForConnected() {
		return new Promise((res, err) => {

			// Check if we are already connected
			if(this.ws.readyState == WebSocket.OPEN) {
				res();
				return;
			}

			this.ws.addEventListener("open", () => res());
		});
	}

	/**
	 * Get current connection status with the server 
	 */
	static get IsConnected() {
		return this.hasOwnProperty("ws") 
			&& this.ws.readyState == WebSocket.OPEN;
	}

	/**
	 * Handles websocket errors
	 */
	static async Error(e) {
		console.error(e)
		notify("Could not connect to websocket ! Try to refresh the page...", "danger");
	}

	/**
	 * When we get disconnected from the websocket
	 */
	static async Closed(e) {
		console.error("WS closed", e)

		// Notify the application
		SendEvent("wsClosed");

		// Reset requests queue
		requests = {};
		
		// Check if the server was gracefully stopped
		if(!this.hasOwnProperty("ws"))
			return;

		const num_seconds = ComunicWeb.__config.productionMode ? 5 : 0.8;

		notify("Disconnected from the server, page will be reloaded in "+num_seconds+" seconds !", "danger");

		setTimeout(() => {
			ComunicWeb.common.system.reset();
		}, num_seconds*1000);
	}

	/**
	 * Disconnect from server
	 */
	static async Disconnect() {
		console.log("Disconnect from websocket");

		// Disconnect, if reuired
		if(this.hasOwnProperty("ws")) {
			if(this.ws.readyState == WebSocket.OPEN)
				this.ws.close()
			delete this.ws
		}
	}

	/**
	 * Send a request to the server through the socket
	 * 
	 * @param {String} title The title of the request
	 * @param {any} data Information associated to the request
	 */
	static SendRequest(title, data) {
		// Send request
		return new Promise((res, err) => {
			if(!this.hasOwnProperty("ws") || this.ws.readyState != WebSocket.OPEN)
				throw new Error("WebSocket is not open!");

			// Determine unique request ID
			const req_id = "r-"+reqCounter++;

			// Send the message
			console.info("WS request", req_id, title, data);
			this.ws.send(JSON.stringify(new WsMessage({
				id: req_id,
				title: title,
				data: data
			})))

			// Add promise information to the queue
			requests[req_id] = {
				res: res,
				err: err
			};
		})

		
	}

	/**
	 * Process an incoming message
	 * 
	 * @param {WsMessage} msg The incoming message
	 */
	static async ProcessMessage(msg) {
		
		console.info("WS remote message", msg);

		// Check if the message is not associated if any request
		if(!msg.hasId)
			this.ProcessDetachedMessage(msg)
		
		else
			this.ProcessResponse(msg);

	}

	/**
	 * Process detached message
	 * @param {WsMessage} msg Incoming message
	 */
	static async ProcessDetachedMessage(msg) {

		switch(msg.title) {

			case "number_notifs":
				SendEvent("newNumberNotifs", msg.data)
				break;

			case "number_unread_conversations":
				SendEvent("newNumberUnreadConvs", msg.data)
				break;
			
			case "new_conv_message":
				SendEvent("newConvMessage", msg.data);
				break;
			
			case "updated_conv_message":
				SendEvent("updatedConvMessage", msg.data);
				break;
			
			case "deleted_conv_message":
				SendEvent("deletedConvMessage", msg.data.id);
				break;
			
			case "new_comment":
				SendEvent("new_comment", msg.data);
				break;

			case "comment_updated":
				SendEvent("commentUpdated", msg.data);
				break;
			
			case "comment_deleted":
				SendEvent("commentDeleted", msg.data);
				break;

			case "user_joined_call":
				SendEvent("userJoinedCall", msg.data);
				break;

			case "user_left_call":
				SendEvent("userLeftCall", msg.data);
				break;

			case "new_call_signal":
				SendEvent("newCallSignal", msg.data);
				break;
			
			case "call_peer_ready":
				SendEvent("callPeerReady", msg.data);
				break;
			
			case "call_peer_interrupted_streaming":
				SendEvent("callPeerInterruptedStreaming", msg.data);
				break;
			
			case "call_closed":
				SendEvent("callClosed", msg.data);
				break;


			default:
				console.error("WS Unspported kind of message!", msg);
				break;
		}

	}

	/**
	 * Process response message
	 * 
	 * @param {WsMessage} msg The message
	 */
	static ProcessResponse(msg) {

		// Check for attached request
		if(!requests.hasOwnProperty(msg.id)) {
			console.error("WS error: received unattended message! ", msg)
			return;
		}

		const queue = requests[msg.id];
		delete requests[msg.id];
		
		// Check for error
		if(msg.title !== "success") {
			console.error("WS error", msg.data);
			queue.err(msg)
			return;
		}
		
		// It is a success
		queue.res(msg.data);
	}
}


// Register some events
document.addEventListener("incognitoStatusChanged", (e) => {
	if(UserWebSocket.IsConnected)
		ws("$main/set_incognito", {enable: e.detail.enabled})
})