/**
 * Single call window management
 * 
 * @author Pierre HUBERT
 */

ComunicWeb.components.calls.callWindow = {

	/**
	 * Initialize a call
	 * 
	 * @param {Object} info Information about the call to initialize
	 */
	initCall: function(info){

		/**
		 * Initialize call object
		 */
		var call = {
			info: info,

			/**
			 * @type {String}
			 */
			localPeerID: undefined,

			/**
			 * @type {Boolean}
			 */
			open: true,

			/**
			 * @type {Boolean}
			 */
			stopped: false,

			window: {},
			streams: {},

			/**
			 * @type {MediaStream}
			 */
			localStream: undefined,

			/**
			 * @type {HTMLVideoElement}
			 */
			localStreamVideo: undefined,

			/**
			 * @type {SignalExchangerClient}
			 */
			signalClient: undefined
		};



		/**
		 * Initialize utilities
		 */

		
		/**
		 * Get member information based on call ID
		 * 
		 * @param {String} id The ID of the peer to process
		 * @return Information about the peer / empty object
		 * if no peer found
		 */
		call.getMemberByCallID = function(id){
			var memberInfo = undefined;
			call.info.members.forEach(function(member){

				if(member.user_call_id == id)
					memberInfo = member;

			});
			return memberInfo;
		}

		/**
		 * Check out whether local stream audio is enabled or not
		 * 
		 * @return {Boolean} TRUE if local stream is enabled / FALSE else
		 */
		call.isLocalAudioEnabled = function(){
			
			//Default behaviour : local stream not enabled
			if(!call.localStream)
				return true;
			
			return call.localStream.getAudioTracks()[0].enabled;

		}

		/**
		 * Set audio mute status of local stream
		 * 
		 * @param {Boolean} enabled New enabled status
		 */
		call.setLocalAudioEnabled = function(enabled){
			if(call.localStream)
				call.localStream.getAudioTracks()[0].enabled = enabled;
		}


		/**
		 * Check if the local video is enabled or not
		 * 
		 * @return {Boolean} TRUE to mute video / FALSE else
		 */
		call.isLocalVideoEnabled = function(){
			
			//Default behaviour : video not enabled
			if(!call.localStream)
				return true;
			
			return call.localStream.getVideoTracks()[0].enabled;
		}

		/**
		 * Update mute status of local video stream
		 * 
		 * @param {Boolean} enabled New mute status
		 */
		call.setLocalVideoEnabled = function(enabled){
			if(call.localStream)
				call.localStream.getVideoTracks()[0].enabled = enabled;
		}

		/**
		 * Set local stream video visibility
		 * 
		 * @param {Boolean} visible TRUE to make it visible / FALSE else
		 */
		call.setLocalStreamVisibility = function(visible){
			if(call.localStreamVideo)
				call.localStreamVideo.style.display = visible ? "block" : "none";
		}

		/**
		 * Get local stream visibility
		 * 
		 * @return {Boolean} TRUE if local stream is visible / FALSE else
		 */
		call.isLocalStreamVisible = function(){
			if(!call.localStreamVideo)
				return true;
			
			return call.localStreamVideo.style.display !== "none";
		}


		/**
		 * We have to begin to draw conversation UI
		 */
		var callContainer = createElem2({
			appendTo: byId("callsTarget") ? byId("callsTarget") : byId("wrapper"), //If call target is not found, add call in page wrapper
			type: "div",
			class: "call-window" 
		});
		call.window.container = callContainer;


		//Add toolbar
		call.window.toolbar = createElem2({
			appendTo: callContainer,
			type: "div",
			class: "call-toolbar",
			innerHTML: "<i class='fa fa-phone'>"
		});

		//Call title
		call.window.title = createElem2({
			appendTo: call.window.toolbar,
			type: "div",
			class: "call-title",
			innerHTML: "Loading..."
		});

		/**
		 * Update the title of the call
		 */
		call.setTitle = function(title){
			call.window.title.innerHTML = title;
		}

		//Add close button
		call.window.closeButton = createElem2({
			appendTo: call.window.toolbar,
			type: "button",
			class: "btn btn-box-tool close-btn",
			innerHTML: "<i class='fa fa-times'></i>"
		});

		//Make close button lives
		call.close = function(){

			//Avoid to call this several times
			if(call.stopped)
				return;

			call.open = false;
			call.stopped = true;
			callContainer.remove();

			//Close sockets connections too
			ComunicWeb.components.calls.callWindow.stop(call);
		}

		call.window.closeButton.addEventListener("click", function(){
			
			//Check if the call is in full screen mode
			if(IsFullScreen())
				RequestFullScreen(null)
			else
				call.close();
			
		});


		//Get information about related conversation to get the name of the call
		ComunicWeb.components.conversations.utils.getNameForID(info.conversation_id, function(name){

			if(!name)
				return notify("Could not get information about related conversation!", "danger");
			
			call.setTitle(name);

		});


		//Call box body
		call.window.body = createElem2({
			appendTo: callContainer,
			type: "div",
			class: "call-window-body"
		});


		//Call videos target
		call.window.videosTarget = createElem2({
			appendTo: call.window.body,
			type: "div",
			class: "streams-target"
		});






		/**
		 * Create loading message area
		 */
		call.window.loadingMessageContainer = createElem2({
			insertBefore: call.window.body.firstChild,
			type: "div",
			class: "loading-message-container",
			innerHTML: "<i class='fa fa-clock-o'></i>"
		});

		call.window.loadingMessageContent = createElem2({
			appendTo: call.window.loadingMessageContainer,
			type: "div",
			class: "message",
			innerHTML: "Loading..."
		});

		/**
		 * Set loading message visiblity
		 * 
		 * @param {Boolean} visible TRUE to make it visible / FALSE else
		 */
		call.setLoadingMessageVisibility = function(visible){
			call.window.loadingMessageContainer.style.display = visible ? "flex" : "none";
		}

		/**
		 * Update call loading message
		 * 
		 * @param {String} message The new message to show to the
		 * users
		 */
		call.setLoadingMessage = function(message){
			call.setLoadingMessageVisibility(true);
			call.window.loadingMessageContent.innerHTML = message;
		}




		//Call footer
		call.window.footer = createElem2({
			appendTo: callContainer,
			type: "div",
			class: "call-footer"
		});

		/**
		 * This function is used to toggle selection state
		 * of one of the call toolbar button
		 * 
		 * @param {HTMLElement} btn Target button
		 * @param {Boolean} selected Selection state of the button
		 */
		var togglButtonSelectedStatus = function(btn, selected){

			if(!selected){
				while(btn.className.includes(" selected"))
					btn.className = btn.className.replace(" selected", "");
			}

			else if(selected && !btn.className.includes(" selected"))
				btn.className += " selected";
		}

		var buttonsList = [

			//Show current user camera
			{
				icon: "fa-eye",
				selected: true,
				onclick: function(btn){
					call.setLocalStreamVisibility(!call.isLocalStreamVisible());
					togglButtonSelectedStatus(btn, call.isLocalStreamVisible());
				}
			},

			//Mute button
			{
				icon: "fa-microphone",
				selected: true,
				onclick: function(btn){
					call.setLocalAudioEnabled(!call.isLocalAudioEnabled());
					togglButtonSelectedStatus(btn, call.isLocalAudioEnabled());
				}
			},

			//Hang up button
			{
				icon: "fa-phone",
				class: "hang-up-button",
				selected: false,
				onclick: function(){
					call.close();
				}
			},

			//Stop video button
			{
				icon: "fa-video-camera",
				selected: true,
				onclick: function(btn){
					call.setLocalVideoEnabled(!call.isLocalVideoEnabled());
					togglButtonSelectedStatus(btn, call.isLocalVideoEnabled());
					console.log(call);
				}
			},


			//Full screen button
			{
				icon: "fa-expand",
				selected: false,
				onclick: function(btn){
					RequestFullScreen(callContainer);
					setTimeout(function(){
						togglButtonSelectedStatus(btn, IsFullScreen());
					}, 1000);
				}
			}
		];

		//Add buttons
		buttonsList.forEach(function(button){

			var buttonEl = createElem2({
				appendTo: call.window.footer,
				type: "div",
				class: "call-option-button",
				innerHTML: "<i class='fa " + button.icon + "'></i>"
			});

			//Add button optionnal class
			if(button.class)
				buttonEl.className += " " + button.class;

			buttonEl.addEventListener("click", function(){
				button.onclick(buttonEl);
			});

			togglButtonSelectedStatus(buttonEl, button.selected);

		});



		/**
		 * Make the call window draggable
		 */

		function checkWindowMinPosition(){

			if(window.innerHeight < callContainer.style.top.replace("px", ""))
				callContainer.style.top = "0px";
			
			if(window.innerWidth < callContainer.style.left.replace("px", ""))
				callContainer.style.left = "0px";

			if(callContainer.style.left.replace("px", "") < 0)
				callContainer.style.left = "0px";

			if(callContainer.style.top.replace("px", "") < 49)
				callContainer.style.top = "50px";
		}

		//Enable dragging
		{
			var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;

			call.window.title.onmousedown = function(e){
				e = e || window.event;
				e.preventDefault();

				//Check if the window is currently in full screen mode
				if(IsFullScreen())
					return; 

				//get the mouse cursor position at startup
				pos3 = e.clientX;
				pos4 = e.clientY;
				document.onmouseup = closeDragElement;
				document.onmousemove = elementDrag;
			}

			function elementDrag(e){
				e = e || window.event;
				e.preventDefault();

				//Calculate new cursor position
				pos1 = pos3 - e.clientX;
				pos2 = pos4 - e.clientY;
				pos3 = e.clientX;
				pos4 = e.clientY;

				//Set element new position
				callContainer.style.top = (callContainer.offsetTop - pos2) + "px";
				callContainer.style.left = (callContainer.offsetLeft - pos1) + "px";

				checkWindowMinPosition();
			}

			function closeDragElement(){

				//Stop moving when mouse button is released
				document.onmouseup = null;
				document.onmousemove = null;
			}
		}

		window.addEventListener("resize", function(){
			checkWindowMinPosition();
		});




		//Load user media
		call.setLoadingMessage("Waiting for your microphone and camera...");

		ComunicWeb.components.calls.userMedia.get().then(function(stream){

			//Check if connection has already been closed
			if(!call.open)
				return;

			call.localStream = stream;

			//Initialize signaling server connection
			ComunicWeb.components.calls.callWindow.initializeConnectionToSignalingServer(call);

			//Add local stream to the list of visible stream
			call.localStreamVideo = ComunicWeb.components.calls.callWindow.addVideoStream(call, true, stream);

			//Mark as connecting
			call.setLoadingMessage("Connecting...");

			return true;

		}).catch(function(e){
			console.error("Get user media error: ", e);
			call.setLoadingMessageVisibility(false);
			return notify("Could not get your microphone and camera!", "danger");
		});

		/**
		 * Start to automaticaly refresh information about the call
		 */
		var interval = setInterval(function(){

			//Check if call is not visible anymore
			if(!callContainer.isConnected){
				call.close();
				return;
			}

			if(!call.open)
				return clearInterval(interval);

			ComunicWeb.components.calls.callWindow.refreshInfo(call);

		}, 4000);

	},


	/**
	 * Initialize connection to signaling server
	 * 
	 * @param {Object} call Information about the call
	 */
	initializeConnectionToSignalingServer: function(call) {

		//Get current user call ID
		call.info.members.forEach(function(member){

			if(member.userID == userID())
				call.localPeerID = member.user_call_id;
		});


		//Create client instance and connect to server
		var config = ComunicWeb.components.calls.controller.getConfig();
		call.signalClient = new SignalExchangerClient(
			config.signal_server_name,
			config.signal_server_port,
			call.localPeerID,
			config.is_signal_server_secure
		);


		/**
		 * Error when connecting to signaling server
		 */
		call.signalClient.onError = function(){
			call.setLoadingMessage("Could not connect to signaling server!");
			call.open = false;
		};

		/**
		 * Connection to signaling server is not supposed to close
		 */
		call.signalClient.onClosed = function(){
			call.setLoadingMessage("Connection to signaling server closed!");
			call.open = false;
		}

		/**
		 * A remote peer sent a ready notice
		 */
		call.signalClient.onReadyMessage = function(peerID){
			ComunicWeb.components.calls.callWindow.readyToInitialize(call, peerID);
		}

		/**
		 * A remote peer sent a signal
		 */
		call.signalClient.onSignal = function(signal, peerID){
			ComunicWeb.components.calls.callWindow.receivedSignal(call, peerID, signal);
		}

	},

	/**
	 * Refresh at a regular interval information about the call
	 * 
	 * @param {Object} call Call Root object
	 */
	refreshInfo: function(call){

		ComunicWeb.components.calls.interface.getInfo(call.info.id, function(result){

			if(result.error)
				return notify("Could not get information about the call!", "danger");

			call.info = result;

			ComunicWeb.components.calls.callWindow.gotNewCallInfo(call);
		});

	},

	/**
	 * This method get called each time information about the call
	 * are refreshed on the server
	 * 
	 * @param {Object} call Information about the call
	 */
	gotNewCallInfo: function(call) {

		//Check if we are connected to signaling server and we have got local
		//streams
		if(!call.signalClient || !call.signalClient.isConnected() || !call.localStream)
			return;

		//Check if all other members rejected call
		var allDisconnected = ComunicWeb.components.calls.utils.hasEveryoneLeft(call.info);

		//Check if all call peer rejected the call
		if(allDisconnected){
			call.setLoadingMessage("Conversation terminated.");

			setTimeout(function(){
				call.close();
			}, 5000);

			return;
		}

		//Process the connection to each accepted member
		call.info.members.forEach(function(member){

			//Ignores all not accepted connection
			if(member.userID == userID() || member.status !== "accepted")
				return;
			
			//Check if we have not peer information
			if(!call.streams.hasOwnProperty("peer-" + member.userID)){

				//If the ID of the current user is bigger than the remote
				//peer user ID, we wait a ready signal from him
				if(member.userID < userID())
					return;

				//Else we have to create peer
				ComunicWeb.components.calls.callWindow.createPeerConnection(call, member, false);
			}

		});
	
	},

	/**
	 * Create a peer connection
	 * 
	 * @param {Object} call Information about the peer
	 * @param {Object} member Information about the member
	 * @param {Boolean} isInitiator Specify whether current user is the
	 * initiator of the connection or not
	 */
	createPeerConnection: function(call, member, isInitiator){

		var peerConnection = {
			peer: undefined
		};
		call.streams["peer-" + member.userID] = peerConnection;

		//Get call configuration
		var config = ComunicWeb.components.calls.controller.getConfig();

		//Create peer
		var peer = new SimplePeer({ 
			initiator: isInitiator,
			stream: call.localStream,
			trickle: false,
			config: {
				'iceServers': [
					{ urls: config.stun_server },
					{"url": config.turn_server, 
										"credential": config.turn_username,
										"username": config.turn_password}
				]
			}
		});
		peerConnection.peer = peer;
	
		//Add a function to remove connection
		peerConnection.removePeerConnection = function(){
			peer.destroy();
			delete call.streams["peer-" + member.userID];

			if(peerConnection.video)
				peerConnection.video.remove();
		}


		peer.on("error", function(err){
			console.error("Peer error !", err, member);
			peerConnection.removePeerConnection();
		});

		peer.on("signal", function(data){
			console.log('SIGNAL', JSON.stringify(data));
			call.signalClient.sendSignal(member.user_call_id, JSON.stringify(data));
		});

		peer.on("message", function(message){
			console.log("Message from remote peer: " + message);
		});


		peer.on("close", function(){
			peerConnection.removePeerConnection();
		});

		peer.on("stream", function(stream){
			ComunicWeb.components.calls.callWindow.streamAvailable(call, member, stream);
		});

		
		//If this peer does not initialize connection, inform other peer we are ready
		if(!isInitiator)
			call.signalClient.sendReadyMessage(member.user_call_id);
	},


	/**
	 * This method is called when a remote peers notify it is ready to
	 * establish connection
	 * 
	 * @param {Object} call Information about the call
	 * @param {String} peerID Remote peer ID
	 */
	readyToInitialize: function(call, peerID){

		var member = call.getMemberByCallID(peerID);
		if(member == undefined)
			return;
		
		//It the user with the smallest ID who send the ready message
		//else it would mess everything up
		if(member.userID > userID())
			return;

		this.createPeerConnection(call, member, true);
	},

	/**
	 * This method is called when we received a remote signal
	 * 
	 * @param {Object} call Information about the call
	 * @param {String} peerID Remote peer ID
	 * @param {String} signal Received signal
	 */
	receivedSignal: function(call, peerID, signal){

		console.log("Received signal from " + peerID, signal);

		var member = call.getMemberByCallID(peerID);
		if(member == undefined)
			return;

		//Check we have got peer information
		if(!call.streams.hasOwnProperty("peer-" + member.userID))
			return;

		call.streams["peer-" + member.userID].peer.signal(JSON.parse(signal));
	},

	/**
	 * This method is called when a remote stream becomes available
	 * 
	 * @param {Object} call Information about remote call
	 * @param {String} member Information about target member
	 * @param {MediaStream} stream Remote stream available
	 */
	streamAvailable: function(call, member, stream){
		
		call.setLoadingMessageVisibility(false);

		call.streams["peer-" + member.userID].stream = stream;

		call.streams["peer-" + member.userID].video = this.addVideoStream(call, false, stream);

	},

	/**
	 * Create and set a video object for a stream
	 * 
	 * @param {Object} call Target call
	 * @param {Boolean} muted Specify whether audio should be muted
	 * or not
	 * @param {MediaStream} stream Target stream
	 * @return {HTMLVideoElement} Generated video element
	 */
	addVideoStream: function(call, muted, stream){

		/**
		 * @type {HTMLVideoElement}
		 */
		var video = createElem2({
			appendTo: call.window.videosTarget,
			type: "video"
		});

		video.muted = muted;

		//Set target video object and play it
		video.srcObject = stream;
		video.play();

		return video;
	},

	/**
	 * Stop the ongoing call
	 * 
	 * @param {Object} call Information about the call
	 */
	stop: function(call){

		//Remove the call from the opened list
		ComunicWeb.components.calls.currentList.removeCallFromList(call.info.id);

		//Close the connection to the server
		if(call.signalClient && call.signalClient.isConnected())
			call.signalClient.close();

		//Close all socket connections
		for (var key in call.streams) {
			if (call.streams.hasOwnProperty(key)) {
				var element = call.streams[key];
				element.removePeerConnection();
			}
		}

		//Close local stream
		if(call.localStream){
			call.localStream.getTracks().forEach(function(track){
				track.stop();
			});
		}

		//Notify server
		ComunicWeb.components.calls.interface.hangUp(call.info.id, function(){});
	}
}