'use strict';

import {decodeJanusDisplay, wait} from "./utils";
import _emitter_ from "../modules/wt-emitter";

// SYNCHRONISATION MODULES
import syncHlsJs from "../modules/sync-modules/sync-hls";
import syncVodHlsJs from "../modules/sync-modules/sync-hls-vod";
import syncVodNativeHls from "../modules/sync-modules/sync-native-hls-vod";
import syncNativeHls from "../modules/sync-modules/sync-native-hls";
import syncShakaDash from "../modules/sync-modules/sync-shaka-dash";
import syncVodShakaDash from "../modules/sync-modules/sync-shaka-dash-vod";
import syncDashJs from "../modules/sync-modules/sync-dash";
import syncVodDashJs from "../modules/sync-modules/sync-dash-vod";
import syncDoris from "../modules/sync-modules/sync-doris";
import syncDisabled from "../modules/sync-modules/sync-disabled";
import syncDaznDash from "../modules/sync-modules/sync-dazn-dash";
import syncDazn from "../modules/sync-modules/sync-dazn";
import syncModule from "../modules/sync-modules/sync-module";

let roomSession = function ({roomId, pinHash, role, options = {}}, room, wt) {

	let primaryRoomId = roomId;
	let {simulcast = null, simulcastSettings = null, enableDtx = null, subscriptionRules = null} = options;
	let publicCustomEvents = ['changePlayerSource', 'chatMessage', 'userUpdate', 'reconnecting', 'connecting', 'remoteMuted', 'scaling'];

	const addEvents = (events) => {
		publicCustomEvents = [...publicCustomEvents, ...events];
	};
	const removeEvents = (events) => {
		publicCustomEvents = publicCustomEvents.filter(ev => events.indexOf(ev) === -1);
	};

	const emitter = _emitter_();
	let alpTimeoutId = null;
	let abortController = null;
	let ___; // return object

	room.on('addLocalParticipant', () => {

		// TODO: this doesnt seem to be fixable until we switch to different type of messaging
		// At some random case we don't get message back if we don't wait

		clearTimeout(alpTimeoutId);
		alpTimeoutId = setTimeout(() => {
			___.__requestMuteStatus();
		}, 2000);
	});

	room.on('requestMuteStatus', () => {
		___.__requestMuteStatus();
	});

	room.on('localMuted', ({type, value, source, mid}) => {
		___.sendSystemMessage('remote_muted', {type, value, source, mid})
	});
	room.on('data', (data) => {
		___.__parseDataEvents(data);
	});
	room.on('dataMessage', (data) => {
		___.__parseDataMessageEvents(data);
	})

	room.on('error', (e) => {
		if(e?.type === 'error') {
			___.disconnect(true);
		}
	});

	return (___ = {

		syncModule: null,
		playerInterface: null,

		get userId() {
			return room.userId;
		},
		get roomId() {
			return roomId;
		},
		get sessionId () {
			return room.sessionId;
		},
		get constructId () {
			return room.constructId;
		},

		get role() {
			return role;
		},

		// needs to be called before connect or it doesn't have any effect
		changeUserRole: function (newRole) {
			role = newRole;
		},

		kill: function () {
			clearTimeout(alpTimeoutId);
			this.detachPlayer();
			return room.destroy()
				.finally(() => {
					this.$clear();
					return true;
				});
		},

		destroy: function() {
			return wt.room.destroySession(room.constructId)
		},

		connect: function ({reactooRoomId = null} = {}) {
			emitter.emit('connecting', true);
			clearTimeout(alpTimeoutId);
			abortController = new AbortController();
			return wt.room.__joinRoom({
				roomId: reactooRoomId || primaryRoomId,
				pinHash,
				role
			}, abortController?.signal)
				.then(roomData => {

					// Happens when we reroute user to a different room
					if(roomData?.data?.reactooRoomId !== roomId) {
						roomId = roomData.data.reactooRoomId;
						emitter.emit('changeRoomId', roomId);
					}

					room.setRoomType(roomData.data.roomType);

					const roomSettingSimulcastEnabled = !!roomData?.data?.simulcast;
					const roomSettingSimulcastConfig = roomData?.data?.simulcast||{};

					return room.connect(
						roomData.data.roomId,
						roomData.data.pin,
						roomData.data.href,
						roomData.data.protocol,
						roomData.data.iceServers,
						roomData.data.accessToken,
						roomData.data.display,
						roomData.data.userId,
						roomData.data.webrtcVersion,
						(roomData.data.bitrate ? parseInt(roomData.data.bitrate) : 0),
						roomData.data.recordingFilename,
						simulcast !== null ? simulcast : roomSettingSimulcastEnabled,
						simulcastSettings !== null ? simulcastSettings : roomSettingSimulcastConfig,
						enableDtx !== null ? enableDtx : roomData?.data?.enableDtx,
					)
				})
				.finally(() => {
					emitter.emit('connecting', false);
				})
		},

		disconnect: function (dontWaitForResponses) {
			clearTimeout(alpTimeoutId);
			abortController?.abort?.();
			return room.disconnect(dontWaitForResponses);
		},

		//TODO: refactor restart method
		restart: function ({isObserver = false, reactooRoomId = null} = {}) {

			emitter.emit('reconnecting', true);
			room.isRestarting = true;

			let wasPublished = room.isPublished;
			let streams = room.getLocalStreams();

			return this.disconnect()
				.then(() => wait(1000)) //TODO: remove 1000ms wait by waiting for proper events from janus
				.then(() => this.connect({reactooRoomId}))
				.then(() => {
					if(streams.length) {
						return streams.reduce((promise, stream) => {
							return promise.then(() => {
								return this.publishLocal(stream.stream, stream.source);
							});
						}, Promise.resolve())

					} else if (wasPublished) {
						return this.publishLocal(null);
					} else {
						return Promise.resolve()
					}
				})
				.then(() => {
					room.isRestarting = false;
					emitter.emit('reconnecting', false);
					return 1;
				})
				.catch((error) => {
					room.isRestarting = false;
					emitter.emit('reconnecting', false);
					emitter.emit('error', {type: 'error', id: 26, message: 'reconnecting failed', data: error});
					return Promise.reject(0);
				});

		},

		__parseDataMessageEvents: function(msg = {}) {
			emitter.emit('dataMessage', msg);
		},

		__parseDataEvents: function (msg = {}) {

			if (msg.videoroom === 'message') {
				if (msg.action === 'pending_shutdown' || msg.action === 'shutting_down') {
					emitter.emit('scaling');
					this.restart(); // current videoroom was reassigned to a different WebRTC instance
				} else if (msg.action === 'force_restart') {
					this.restart();
				} else if (msg.action === 'user_update_displayname' || msg.action === 'user_update_avatar' || msg.action === 'user_update_customattributes' || msg.action === 'user_update_privateattributes') {
					emitter.emit('userUpdate', msg.text);
				} else if (msg.action === 'bitrate_changed') {
					this.setBitrateCap(msg.text);
				} else if (msg.user_action === 'chat_message') {
					emitter.emit('chatMessage', msg);
				} else if (msg.user_action === 'remote_muted') {
					const fromUserId = decodeJanusDisplay(msg.from)?.userId;
					const ownUserId = decodeJanusDisplay(room.userId)?.userId;
					if (fromUserId !== ownUserId) {
						emitter.emit('remoteMuted', {role: decodeJanusDisplay(msg.from)?.role, userId: decodeJanusDisplay(msg.from)?.userId, ...(msg.text && JSON.parse(msg.text))})
					}
				} else if (msg.user_action === 'remote_muted_request') {
					const fromUserId = decodeJanusDisplay(msg.from)?.userId;
					const ownUserId = decodeJanusDisplay(room.userId)?.userId;
					if (fromUserId !== ownUserId) {
						this.__sendMuteStatus();
					}
				} else if(msg.user_action === 'customData') {
					emitter.emit('customData', msg);
				}
			}
		},

		// DAZN interface
		renderPlayer: function (playerWrapper, fullscreenElement, roomId) {
			try {
				this.syncModule = syncDazn({room, wt, roomSession: this, emitter});
				this.playerInterface = wt.__privates.playerFactory(playerWrapper, fullscreenElement, this.syncModule.getHandlers(), {roomId});
				this.syncModule.initialize({playerInterface: this.playerInterface});
				return true;
			} catch (e) {
				return false;
			}
		},

		attachPlayer: function (type, inputs) {

			this.detachPlayer();

			if(type === 'universal') {
				this.syncModule = syncModule({room, emitter});
				if (this.syncModule.__events) {
					addEvents(this.syncModule.__events);
				}
				this.syncModule.initialize(inputs);
			}

			// Everything below is for legacy player and will be removed in the future

			else if (type === 'hlsjs') {
				this.syncModule = syncHlsJs({room, wt, roomSession: this, emitter});
				if (this.syncModule.__events) {
					addEvents(this.syncModule.__events);
				}
				this.syncModule.initialize(inputs);
			}
			else if (type === 'hlsjs-vod') {
				this.syncModule = syncVodHlsJs({room, wt, roomSession: this, emitter});
				if (this.syncModule.__events) {
					addEvents(this.syncModule.__events);
				}
				this.syncModule.initialize(inputs);
			}
			else if (type === 'hlsnative-vod') {
				this.syncModule = syncVodNativeHls({room, wt, roomSession: this, emitter});
				if (this.syncModule.__events) {
					addEvents(this.syncModule.__events);
				}
				this.syncModule.initialize(inputs);
			}
			else if (type === 'hlsnative') {
				this.syncModule = syncNativeHls({room, wt, roomSession: this, emitter});
				if (this.syncModule.__events) {
					addEvents(this.syncModule.__events);
				}
				this.syncModule.initialize(inputs);
			} else if (type === 'shaka-dash-vod') {
				this.syncModule = syncVodShakaDash({room, wt, roomSession: this, emitter});
				if (this.syncModule.__events) {
					addEvents(this.syncModule.__events);
				}
				this.syncModule.initialize(inputs);
			} else if (type === 'shaka-dash') {
				this.syncModule = syncShakaDash({room, wt, roomSession: this, emitter});
				if (this.syncModule.__events) {
					addEvents(this.syncModule.__events);
				}
				this.syncModule.initialize(inputs);
			} else if (type === 'dashjs') {
				this.syncModule = syncDashJs({room, wt, roomSession: this, emitter});
				if (this.syncModule.__events) {
					addEvents(this.syncModule.__events);
				}
				this.syncModule.initialize(inputs);
			} else if (type === 'dashjs-vod') {
				this.syncModule = syncVodDashJs({room, wt, roomSession: this, emitter});
				if (this.syncModule.__events) {
					addEvents(this.syncModule.__events);
				}
				this.syncModule.initialize(inputs);
			} else if (type === 'doris') {
				this.syncModule = syncDoris({room, wt, roomSession: this, emitter});
				if (this.syncModule.__events) {
					addEvents(this.syncModule.__events);
				}
				this.syncModule.initialize(inputs);
			}
			else if (type === 'dazn-dash') {
				this.syncModule = syncDaznDash({room, wt, roomSession: this, emitter});
				if (this.syncModule.__events) {
					addEvents(this.syncModule.__events);
				}
				this.syncModule.initialize(inputs);
			}
			else if (type === 'disabled') {
				this.syncModule = syncDisabled({room, wt, roomSession: this, emitter});
				if (this.syncModule.__events) {
					addEvents(this.syncModule.__events);
				}
				this.syncModule.initialize(inputs);
			} else {
				room._log('Synchronisation type not recognised');
			}
		},

		detachPlayer: function () {
			if (this.syncModule) {
				this.playerInterface = null;
				this.syncModule.destroy();
				if (this.syncModule.__events) {
					removeEvents(this.syncModule.__events);
				}
				this.syncModule = null;
			}
		},

		publishLocal: function (stream = null, source) {
			return room.publishLocal(stream, source)
		},

		unpublishLocal: () => {
			return room.unpublishLocal();
		},

		toggleAudio: (value, source) => {
			return room.toggleAudio(value, source);
		},

		toggleVideo: (value, source) => {
			return room.toggleVideo(value, source);
		},

		setRestrictSubscribeToUserIds: (userIds = []) => {
			return room.setRestrictSubscribeToUserIds(userIds);
		},

		setTalkIntercomChannels: (groups) => {
			return room.setTalkIntercomChannels(groups);
		},

		getTalkIntercomChannels: () => {
			return room._talkIntercomChannels;
		},

		getUserTalkIntercomChannels: (userId) => {
			return room.getUserTalkIntercomChannels(userId);
		},

		setListenIntercomChannels: (groups) => {
			return room.setListenIntercomChannels(groups);
		},

		getListenIntercomChannels: () => {
			return room._listenIntercomChannels;
		},

		requestKeyFrame: (handleId, mid) => {
			return room.requestKeyFrame(handleId, mid);
		},

		selectSubStream: (id, substream, source, mid) => {
			return room.selectSubStream(id, substream, 2, source, mid);
		},

		setBitrateCap: (bitrate) => {
			if(role === 'host') {
				return;
			}
			return room.sendMessage(room.handleId, {
				"body": {
					"request": "configure",
					"bitrate": parseInt(bitrate)
				}
			}).catch(() => null)
		},


		sendSystemMessage: (action, value = {}, to, set_master) => {
			return room.sendMessage(room.handleId, {
				body: {
					action,
					request: "message",
					room: room.roomId,
					timestamp: new Date().getTime(),
					text: JSON.stringify(value),
					...(to && Array.isArray(to) ? {tos: to} : {to}),
					...(set_master && {set_master})
				}
			});
		},

		sendChatMessage: (text, to) => {
			return room.sendMessage(room.handleId, {
				body: {
					action: "chat_message",
					request: "message",
					room: room.roomId,
					timestamp: new Date().getTime(),
					text,
					...(to && Array.isArray(to) ? {tos: to} : {to}),
				}
			});
		},

		sendMessageViaDataChannel: (label, data) => {
			room.sendMessageViaDataChannel(label, data);
		},

		__requestMuteStatus: function () {
			this.sendSystemMessage('remote_muted_request');
		},

		__sendMuteStatus: function () {

			if(room.getMyUserRole() !== 'participant') {
				return;
			}

			for(const val of room.isMuted) {
				this.sendSystemMessage('remote_muted', {...val});
			}
		},

		$on: (key, callback, that) => {
			emitter.on(key, callback, that || this);
			room.on(key, callback, that || this);
		},

		$off: (key, callback, that) => {
			emitter.on(key, callback, that || this);
			room.on(key, callback, that || this);
		},

		$clear: function () {
			room.clear();
			emitter.clear();
		}
	})
};

export default roomSession;
