"use strict";

//! {"category": "libraries"}

exports.ChatService = class {
	static handleMessages(serviceClass) {
		let self = this;
		let sessions = {};

		core.register("onMessage", function(sender, options) {
			let service = sessions[options.name];
			if (!service) {
				service = new serviceClass(options);
				sessions[options.name] = service;
			} else {
				service._service.addCallback(options.callback);
			}
			return service._service.makeInterface(service);
		});
	}

	constructor(callback) {
		this._callbacks = [callback];
		this._conversations = {};
		this._state = null;
		this._maxHistory = 64;
	}

	makeInterface(service) {
		let self = this;
		return {
			sendMessage: service.sendMessage.bind(service),
			disconnect: service.disconnect.bind(service),

			getConversations: self.getConversations.bind(self),
			getHistory: self.getHistory.bind(self),
			getParticipants: self.getParticipants.bind(self),
		};
	}

	addCallback(callback) {
		if (this._callbacks.indexOf(callback) == -1) {
			this._callbacks.push(callback);
		}
	}

	_invokeCallback(message) {
		let self = this;
		for (let i = self._callbacks.length - 1; i >= 0; i--) {
			let callback = self._callbacks[i];
			try {
				callback(message).catch(function(error) {
					self._callbacks.splice(i, 1);

					// XXX: Send it to the other connections?
					print(error);
				});
			} catch (error) {
				self._callbacks.splice(i, 1);

				// XXX: Send it to the other connections?
				print(error);
			}
		}
	}

	_getConversation(conversation) {
		if (!this._conversations[conversation]) {
			this._conversations[conversation] = {history: [], participants: []};
		}
		return this._conversations[conversation];
	}

	_pushMessage(conversation, message) {
		let history = this._getConversation(conversation || "").history;
		history.push(message);
		if (history.length > this._maxHistory) {
			history.splice(0, history.length - this._maxHistory);
		}
	}

	notifyMessageReceived(conversation, message) {
		let fullMessage = {action: "message", conversation: conversation || "", message: message};
		this._pushMessage(conversation, fullMessage);
		this._invokeCallback(fullMessage);
	}

	notifyPresenceChanged(conversation, user, state) {
		let leaving = state == "unavailable";
		let participants = this._getConversation(conversation).participants;
		let index = participants.indexOf(user);
		let different = true;
		if (leaving) {
			participants.splice(index, 1);
		} else if (index == -1) {
			participants.push(user);
		} else {
			different = false;
		}
		let message = {
			action: "presence",
			conversation: conversation,
			user: user,
			presence: state,
		};
		if (different) {
			this._pushMessage(conversation, message);
		}
		this._invokeCallback(message);
	}

	notifyParticipantList(conversation, participants) {
		let current = this._getConversation(conversation).participants;
		for (let i in current) {
			if (!participants[i]) {
				this.notifyPresenceChanged(conversation, i, "unavailable");
			}
		}
		for (let i in participants) {
			this.notifyPresenceChanged(conversation, i, participants[i]);
		}
	}

	notifyStateChanged(state) {
		this._state = state;
		this._invokeCallback({action: state});
	}

	reportError(error) {
		this._invokeCallback({
			action: "error",
			error: error,
		}).catch(function(error) {
			print(error);
		});
	}

	isConversation(conversation) {
		return this._conversations[conversation] != null;
	}

	getConversations() {
		return Object.keys(this._conversations);
	}

	getHistory(conversation) {
		let result;
		if (this._conversations[conversation]) {
			result = this._conversations[conversation].history;
		}
		return result;
	}

	getParticipants(conversation) {
		let result;
		if (this._conversations[conversation]) {
			result = this._conversations[conversation].participants;
		}
		return result;
	}
}