/**
 * Blackboard class, manage the blackboard app, the CanvasCapture and the WebRTC streaming
 * 
 * @package JCHAT::BLACKBOARD::components::com_jchat
 * @subpackage js
 * @author Joomla! Extensions Store
 * @copyright (C)2014 Joomla! Extensions Store
 * @license GNU/GPLv2 http://www.gnu.org/licenses/gpl-2.0.html
 */
//'use strict';
(function($) {
	var BlackBoard = function(identifier, options) {
		/**
		 * This bind object
		 * 
		 * @access private
		 * @var Object
		 */
		var bindContext = this;
		
		/**
		 * Main container tooltip for this peer user video chat session
		 * 
		 * @access private
		 * @var Object jQuery
		 */
		var $tooltipContainer;
		
		/**
		 * Store the full support state for WebRTC API
		 * 
		 * @access private
		 * @var Boolean
		 */
		var supportFullWebRTC;
		
		/**
		 * Ringing effect time interval
		 * 
		 * @access private
		 * @var Object
		 */
		var ringingInterval;
		
		/**
		 * Log status messages on debug enabled
		 * 
		 * @access private
		 * @var Boolean
		 */
		var debugLogging = options.debugEnabled;
		
		/**
		 * Log status messages on debug enabled
		 * 
		 * @access private
		 * @var Boolean
		 */
		var jsonLiveSite = options.jsonLiveSite;

		/**
		 * RTCPeerConnection object
		 * 
		 * @access private
		 * @var RTCPeerConnection
		 */
		var peerConnection = null;

		/**
		 * Peer candidates array
		 * 
		 * @access private
		 * @var Array
		 */
		var peerCandidates = new Array();
		
		/**
		 * Local peer stream retrieved to send to remote peer
		 * 
		 * @access private
		 * @var Stream
		 */
		var localGotStream;

		/**
		 * Candidates counter
		 * 
		 * @access private
		 * @var int
		 */
		var candidatesCounter = 0;

		/**
		 * STUN and TURN servers configurations ICE Framework requires them for
		 * NAT traversal, if devices are behind a NAT router TURN server is used
		 * as a fallback to relay video and audio stream
		 * 
		 * @access private
		 * @var Object
		 */
		var servers = {};
		
		/**
		 * Start call timeout, avoid to have ringing forever waiting answer
		 * 
		 * access private
		 * @var object
		 */
		var startCallTimeout = null;
		
		/**
		 * End call timeout, avoid to have infinite calls if other user close browser or connection is lost
		 * 
		 * access private
		 * @var object
		 */
		var endCallTimeout = null;
		
		/**
		 * The RTC Messages timeout
		 * 
		 * access private
		 * @var object
		 */
		var RTCMessagesTimeout = null;
		
		/**
		 * The other peer identifier, needs to be passed server side to store target informations
		 * 
		 * @access private
		 * @var String
		 */
		var remotePeer = null;
		
		/**
		 * Received sdp message, can be offer or answer
		 * 
		 * @access private
		 * @var Object
		 */
		var receivedSdpMessage = null;
		
		/**
		 * Received ICE candidates, can be offer candidates or answer candidates
		 * 
		 * @access private
		 * @var Object
		 */
		var receivedICECandidates = null;

		/**
		 * The call status, based on both peer connection
		 * 
		 * @access private
		 * @var Boolean
		 */
		var callStatus = false;
		
		/**
		 * Promise of signaling channel resolved
		 * 
		 * @access private
		 * @var Boolean
		 */
		var promiseResolved = false;
		
		/**
		 * RTCPeerConnection main object
		 * 
		 * @access public
		 * @var Object
		 */
		this.remoteVideo = null;

		/**
		 * Caller role
		 * 
		 * @access public
		 * @var Object
		 */
		this.caller = false;
		
		/**
		 * Callee role
		 * 
		 * @access public
		 * @var Object
		 */
		this.callee = false;
		
		/**
		 * RTCPeerConnection main object
		 * 
		 * @access public
		 * @var Object
		 */
		window.RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection;
		
		/**
		 * Canvas private variables
		 */
		var canvas;
		var context;
		var canvasWidth = 490;
		var canvasHeight = 220;
		var padding = 25;
		var lineWidth = 8;
		var colorPurple = "#cb3594";
		var colorGreen = "#659b41";
		var colorYellow = "#ffcf33";
		var colorBrown = "#986928";
		var crayonImage = new Image();
		var markerImage = new Image();
		var eraserImage = new Image();
		var crayonBackgroundImage = new Image();
		var markerBackgroundImage = new Image();
		var eraserBackgroundImage = new Image();
		var crayonTextureImage = new Image();
		var clickX = new Array();
		var clickY = new Array();
		var clickColor = new Array();
		var clickTool = new Array();
		var clickSize = new Array();
		var clickDrag = new Array();
		var paint = false;
		var curColor = colorPurple;
		var curTool = "marker";
		var curSize = "normal";
		var mediumStartX = 18;
		var mediumStartY = 19;
		var mediumImageWidth = 93;
		var mediumImageHeight = 46;
		var doubledImageHeight = 65;
		var drawingAreaX = 111;
		var drawingAreaY = 11;
		var drawingAreaWidth = 267;
		var drawingAreaHeight = 200;
		var toolHotspotStartY = 23;
		var toolHotspotHeight = 38;
		var sizeHotspotStartY = 157;
		var sizeHotspotHeight = 36;
		var sizeHotspotWidthObject = new Object();
		sizeHotspotWidthObject.huge = 39;
		sizeHotspotWidthObject.large = 25;
		sizeHotspotWidthObject.normal = 18;
		sizeHotspotWidthObject.small = 16;
		var totalLoadResources = 8;
		var curLoadResNum = 0;
		var XDisplacementKonstant = 2;
		var YDisplacementKonstant = 16;
		
		/**
		 * Show user messages both info messages and exceptions by WebRTC framework
		 * 
		 * @access private
		 * @return Void
		 */
		var showRTCMessages = function(containerClass, iconClass, innerClass, translationText, hasClose) {
			// Blackboard popup needs to be opened and initialized
			if(!$tooltipContainer) {
				return;
			}
			
			// Check if close is required
			var closer = '';
			if(hasClose) {
				closer = '<div class="' + hasClose + '"></div>';
				// Manage close tooltip
				$(document).on('click.jchatwebrtcblackboard', 'div.' + hasClose, function(jqEvent){
					$('div.' + containerClass).fadeOut(500, function(){
						$('div.jchat_exceptions').remove();
					});
				});
				
				// Clear current timeout if any
				if(typeof RTCMessagesTimeout == 'number') {
					clearTimeout(RTCMessagesTimeout);
				}
				
				// Ensure an autoclose after 8 seconds
				RTCMessagesTimeout = setTimeout(function(){
					$('div.' + hasClose).trigger('click.jchatwebrtcblackboard');
				}, 8000);
			}
			
			// Show calling user interface
			$('div.jchat_infouser_webrtc, div.jchat_exceptions', $tooltipContainer).remove();
			$tooltipContainer.append('<div class="' + containerClass + '">' +
					  					'<div class=" ' + iconClass + '"></div>' +
					  				 	'<div class=" ' + innerClass + '">' + translationText + '</div>' + 
					  				 	closer +
					  				'</div>');
			
			// Log status
			if (debugLogging) {
				console.log(translationText);
			}
			
		};
		
		/**
		 * Create a new peerConnection object, valid and needed in both cases caller or callee
		 * 
		 * @access private
		 * @return Void
		 */
		var createPeerConnection = function() {
			// Now create a new peer connection object
			try {
				// New peer connection
				peerConnection = new RTCPeerConnection(servers);
				
				// Record ICE candidates callback
				peerConnection.onicecandidate = gotIceCandidate;
				// Ensure to post always ice candidates if the user agent doesn't throw the complete event
				checkIceCandidatesSent();
				
				// Add the HTML canvas media stream
				peerConnection.addStream(localGotStream);
				
				// Registered callback when remote stream is ready
				var peerConnectionRemoteStreamCallback = function(event) {
					gotRemoteStream(event);
				};
				if('ontrack' in peerConnection) {
					peerConnection.ontrack = peerConnectionRemoteStreamCallback;
				} else {
					peerConnection.onaddstream = peerConnectionRemoteStreamCallback;
				}
				
				// Monitor connection state estabilished
				peerConnection.oniceconnectionstatechange = function() {
					// Check if valid peerConnection object
					if(peerConnection) {
						// Log status
						if (debugLogging) {
							console.log('Connection Blackboard: ' + peerConnection.iceConnectionState);
						}
						
						// Connection completed, show call started message
						if(peerConnection.iceConnectionState == 'completed') {
							showRTCMessages('jchat_infouser_webrtc', 'jchat_icon_ok', 'jchat_tooltip_innermsg', jchat_session_started, 'jchat_info_closer');
						}
						
						// Connection completed, show call started message
						if(peerConnection.iceConnectionState == 'connected') {
							showRTCMessages('jchat_infouser_webrtc', 'jchat_icon_ok', 'jchat_tooltip_innermsg', jchat_connection_active, 'jchat_info_closer');
						}
						
						// Connection disconnected, something went wrong on net or other peer closed browser?
						if(peerConnection.iceConnectionState == 'disconnected') {
							// Log status
							if (debugLogging) {
								console.log(peerConnection.iceConnectionState);
							}
							// Clear current timeout if any
							if(typeof endCallTimeout == 'number') {
								clearTimeout(endCallTimeout);
							}
							endCallTimeout = setTimeout(function(){
								// Check again if peerConnection still exists, a user could have hanged up!
								if(peerConnection) {
									if(peerConnection.iceConnectionState == 'disconnected') {
										showRTCMessages('jchat_infouser_webrtc', 'jchat_icon_ok', 'jchat_tooltip_innermsg', jchat_call_disconnected, 'jchat_info_closer');
										// End call at this stage
										$('.jchat_end_sharing', $tooltipContainer).trigger('click', [jchat_call_disconnected]);
									}
								}
							}, options.endCallTimeout);
						}
						
						// Connection failed, ICE candidates are probably not suitable for remote peer to peer
						if(peerConnection.iceConnectionState == 'failed') {
							showRTCMessages('jchat_infouser_webrtc', 'jchat_icon_ok', 'jchat_tooltip_innermsg', jchat_connection_failed, 'jchat_info_closer');
						}
					}
				};
			} catch(exception) {
				// Reset buttons
				resetButtons();
				
				showRTCMessages('jchat_exceptions', 'jchat_icon_error', 'jchat_tooltip_error', jchat_error_creating_connection, 'jchat_exceptions_closer');

				// Log status
				if (debugLogging) {
					console.log(exception.message);
				}
				return;
			}
			
			// If peerConnection object is still not created return immediately, something went wrong
			if(!peerConnection) {
				// Reset buttons
				resetButtons();
				
				showRTCMessages('jchat_exceptions', 'jchat_icon_error', 'jchat_tooltip_error', jchat_error_creating_connection, 'jchat_exceptions_closer');
				return;
			}
		};
		
		// Handler to be called as soon as the remote stream becomes available
		function gotRemoteStream(event) {
			if(bindContext.caller) {
				return;
			}
			// Check if other peer video stream track is found on the MediaStream object
			var videoStreamTrack = event.streams ? event.streams[0].getVideoTracks()[0] : event.stream.getVideoTracks()[0];
			
			if(videoStreamTrack) {
				$('canvas[id=canvas_' + identifier + ']').hide();
				
				var eventStream = event.streams ? event.streams[0] : event.stream;
				var remoteVideoElement = createVideoTag('video_' + identifier, 490, 224, eventStream);
				$('canvas[id=canvas_' + identifier + ']').after(remoteVideoElement);
				
				// Setup the video Fullscreen API feature
				$('#video_' + identifier).on('click.jchatwebrtcblackboard', function(jqEvent){
					this.requestFullScreen = this.webkitRequestFullScreen || 
											 this.mozRequestFullScreen || 
											 this.requestFullScreen;
					this.requestFullScreen();
				}).css('cursor', 'pointer');
				
				// Activate the blackboard tab
				$('span.jchat_blackboard_tab').addClass('active').text(jchat_remote_blackboard);

				// Log status
				if (debugLogging) {
					console.log('Remote Blackboard video stream got');
				}
			}
		};
		
		/**
		 * The SDP description has been retrieved, now set on local peer connection object
		 * and send to the remote peer using the signaling channel
		 * 
		 * @access private
		 * @return Void
		 */
		var gotSdpDescription = function(sdp) {
			// Caller or callee?
			var SDPType = bindContext.caller ? 'offer' : 'answer';
			
			// Set the local sdp description for the local peer
			peerConnection.setLocalDescription(sdp).then(function(){
					// Log status
					if (debugLogging) {
						console.log('SDP Blackboard ' + SDPType + ' correctly generated');
					}
				}, function (){
					// Log status
					if (debugLogging) {
						console.log('SDP Blackboard ' + SDPType + ' error');
					}
			});
			
			// Log status
			if (debugLogging) {
				console.log('SDP Blackboard ' + SDPType + ' created');
			}
			
			// Now use the signaling channel to send the sdp description to remote peer
			signalingChannel('sdp', sdp);
		};
		
		/**
		 * The ICE candidate has been retrieved, now set on local peer connection object
		 * and send to the remote peer using the signaling channel when completed all candidates
		 * 
		 * @access private
		 * @return Void
		 */
		var gotIceCandidate = function(event) {
			// Check if a new candidate is available, otherwise candidates are finished, so send using signaling channel
			if(event.candidate) {
				if (event.candidate) {
					peerCandidates[candidatesCounter] = event.candidate;
				}
				candidatesCounter++;
				
				// Still gathering candidates
				peerConnection.iceCandidatesSent = false;
				
				// Log status
				if (debugLogging) {
					console.log('Event candidate Blackboard found: ' + event.candidate.candidate);
				}
			} else if(!event.candidate || peerConnection.iceGatheringState === 'complete') {
				// Normal send if not already sent by the polyfill
				if(!peerConnection.iceCandidatesSent) {
					signalingChannel('icecandidate', peerCandidates);
					peerConnection.iceCandidatesSent = true;
					
					// Log status
					if (debugLogging) {
						console.log('icecandidates Blackboard sent successfully using standard complete event');
					}
				}
			}
		};
		
		/**
		 * Polyfill for the browser error not reaching the 'completed' ice candidates state or not trigger null final candidate
		 * 
		 * @access private
		 * @return Void
		 */
		var checkIceCandidatesSent = function() {
			setTimeout(function(){
				if(peerConnection) {
					if(!peerConnection.iceCandidatesSent) {
						signalingChannel('icecandidate', peerCandidates);
						peerConnection.iceCandidatesSent = true;

						// Log status
						if (debugLogging) {
							console.log('icecandidates Blackboard sent successfully using polyfill');
						}
					}
				}
			}, 1000);
		};
		
		/**
		 * Implement the client server signaling channel storing the description messages and ICE candidates
		 * 
		 * @access private
		 * @return Void
		 */
		var signalingChannel = function(dataType, data) {
			// Ajax post params
			var postData = {
				task : 'blackboard.saveEntity',
				peer2 : remotePeer,
				caller : (bindContext.caller ? 1 : 0)
			};
			
			// Check the data type to send
			switch(dataType) {
				case 'sdp':
					$.extend(postData, {'sdp' : JSON.stringify(data)});
					break;
					
				case 'icecandidate':
					$.extend(postData, {'icecandidate' : JSON.stringify(data)});
					break;
			}
			
			// Signaling channel between peers
			var signalingChannelPromise = $.Deferred(function(defer) {
				$.ajax({
					type : "POST",
					url : jsonLiveSite,
					dataType : 'json',
					context : this,
					data : postData
				}).done(function(response, textStatus, jqXHR) {
					if(!response.storing.status) {
						// Error found
						defer.reject(response.storing.exception_message, textStatus, response.storing);
						return false;
					}
					
					// Check response all went well
					defer.resolve(response.storing);
				}).fail(function(jqXHR, textStatus, errorThrown) {
					// Error found
					var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1);
					defer.reject('-' + genericStatus + '- ' + errorThrown, null, {});
				});
			}).promise();

			signalingChannelPromise.then(function(responseData) {
				// Do stuff
				promiseResolved = true;
				
				// Log status
				if (debugLogging) {
					console.log(dataType + ' Blackboard sent succesfully using signaling channel');
				}
			}, function(errorText, error, exception) {
				// Close if opened
				if(peerConnection) {
					// Show user exception, only the first!
					if(exception.usermessage) {
						showRTCMessages('jchat_exceptions', 'jchat_icon_error', 'jchat_tooltip_error', errorText, 'jchat_exceptions_closer');
					} else {
						showRTCMessages('jchat_exceptions', 'jchat_icon_error', 'jchat_tooltip_error', jchat_error_creating_connection, 'jchat_exceptions_closer');
					}
					
					peerConnection.close();
					peerConnection = null;
					peerCandidates = new Array();
					candidatesCounter = 0;
				}
				
				// Reset caller
				bindContext.caller = false;
				bindContext.callee = false;
				BlackBoard.starterSession = null;
				BlackBoard.receiverSession = null;
				promiseResolved = false;
				
				// Reset buttons
				resetButtons();
				
				// Log status
				if (debugLogging) {
					console.log(dataType + ' Blackboard error using signaling channel: ' + errorText);
				}
			});
		};
		
		/**
		 * Set the received messaging data while the peer is listening for incoming call/answer
		 * 
		 * @access public
		 * @param Object data
		 * @param Object realtimeOptions
		 * @return Void
		 */
		this.setListeningData = function(data, realtimeOptions) {
			// Ensure getUserMedia and peer connection object are available
			if(!supportFullWebRTC) {
				// Log status
				if (debugLogging) {
					console.log('No Blackboard WebRTC support detected for this device browser');
				}
				return false;
			}
			
			// Set/refresh call status
			callStatus = data.call_status;
			
			// Set incoming received SDP / ICE data if any, both for an offer or
			// answer
			if(data.sdp && data.icecandidate) {
				// Store SDP message
				receivedSdpMessage = data.sdp;
				
				// Store ICE candidates
				receivedICECandidates = data.icecandidate;
				
				// If this is the callee, turn the button to accept call and start ringing
				if(!this.caller) {
					// Set now as callee
					this.callee = true;
					BlackBoard.receiverSession = identifier;
					
					// Play ringing sound incoming call
					JChatNotifications.playAcceptWebrtcCall();
					
					// Manipulate tab title if not focused
					if(!realtimeOptions.tabFocused) {
                    	$('title').text(jchat_newvideocall_tab);
                    }
					
					// Start now
					if($('div.jchat_blackboardtooltip[data-userid=' +  data.peer1 + ']:not(:hidden)').length) {
						this.setCalleeRingingButton();
					} else {
						// Open the popup for the target user, this ensure it's opened
						$('#jchat_userlist_' + data.peer1).trigger('click');
						
						// Show the incoming call popover
						$('.jchat_trigger_blackboard', '#jchat_user_' + data.peer1 + '_popup').attr('data-text', jchat_trigger_blackboard_ringing).trigger('mouseover');
						
						// Bring in front the callee popup
						$('div[id^=jchat_user_][id!=jchat_user_' + data.peer1 + '_popup]').css('z-index', 10002);
						$('div[id=jchat_user_' + data.peer1 + '_popup], #jchat_trigger_blackboard_tooltip').css('z-index', 10005);
					}
					
					// Log status
					if (debugLogging) {
						console.log('New Blackboard incoming share session arrived');
					}
				} else {
					// Stop button ringing
					$('.jchat_start_accept_sharing', $tooltipContainer).removeClass('jchat_ringing').addClass('jchat_disabled');
					if(ringingInterval) {
						clearInterval(ringingInterval);
						ringingInterval = null;
					}
					
					// Stop sounds waiting answer
					resetSounds();
					
					// Complete the session exchange from the caller perspective and manage callee SDP answer
					acceptShare.call(this);
					
					// Show user notice
					showRTCMessages('jchat_infouser_webrtc', 'jchat_icon_ok', 'jchat_tooltip_innermsg', jchat_session_starting, 'jchat_info_closer');

					// Log status
					if (debugLogging) {
						console.log('Exchanged Blackboard SDP/ICE and session started between 2 peer');
					}
				}
			} else {
				// Log status
				if (debugLogging) {
					console.log('No Blackboard SDP/ICE data received through signaling channel');
				}
			}
			
			// Check if the caller has had a declined call from other peer
			if(promiseResolved && this.caller && !data.caller_peer_state) {
				// Stop button ringing and reset
				resetButtons();
				resetTooltips();
				
				this.caller = false;
				BlackBoard.starterSession = null;
				promiseResolved = false;
				
				// Close if opened
				if(peerConnection) {
					peerConnection.close();
					peerConnection = null;
					peerCandidates = new Array();
					candidatesCounter = 0;
				}
				
				// Show user notice
				showRTCMessages('jchat_infouser_webrtc', 'jchat_icon_ok', 'jchat_tooltip_innermsg', jchat_connection_closed, 'jchat_info_closer');
				
				// Log status
				if (debugLogging) {
					console.log('Connection Blackboard closed from callee');
				}
			}
			
			// Check if the caller has ended the call
			if(this.callee && !callStatus) {
				// Enable again the start call button
				resetButtons();
				resetTooltips();
				
				// Stop ringing sound
				resetSounds();
				
				this.callee = false;
				BlackBoard.receiverSession = null;
				promiseResolved = false;
				
				// Manipulate tab title if not focused
				if(!realtimeOptions.tabFocused) {
                	$('title').text(realtimeOptions.tabTitle);
                }
				
				// Close if opened
				if(peerConnection) {
					peerConnection.close();
					peerConnection = null;
					peerCandidates = new Array();
					candidatesCounter = 0;
				}
				
				// Show user notice
				showRTCMessages('jchat_infouser_webrtc', 'jchat_icon_ok', 'jchat_tooltip_innermsg', jchat_connection_closed, 'jchat_info_closer');
				
				// Log status
				if (debugLogging) {
					console.log('Connection Blackboard closed from caller');
				}
			}
		};
		
		/**
		 * Used for debugging purpouse
		 * 
		 * @access private
		 * @return Void
		 */
		var onSignalingError = function(error) {
			// Reset all
			resetButtons();
			resetTooltips();
			
			// Throw a user message exceptions
			showRTCMessages('jchat_exceptions', 'jchat_icon_error', 'jchat_tooltip_error', jchat_session_error, 'jchat_exceptions_closer');
			
			// Log status
			if (debugLogging) {
				console.log('Failed to create signaling Blackboard message : ' + error.name);
			}
		};
		
		/**
		 * Reset the call buttons to original state: start call/and call
		 * 
		 * @access private
		 * @param Boolean noResetOtherPeers
		 * @return Void
		 */
		var resetButtons = function(noResetOtherPeers) {
			// Text reset
			$('.jchat_start_accept_sharing span.text', $tooltipContainer).text(jchat_start_sharing);
			$('.jchat_end_sharing span.text', $tooltipContainer).text(jchat_end_sharing);
			
			// Styles reset
			$('.jchat_start_accept_sharing', $tooltipContainer).removeClass('jchat_ringing jchat_disabled');
			$('.jchat_end_sharing', $tooltipContainer).addClass('jchat_disabled');

			// Restore canvas and remove remote video streaming
			$('canvas[id=canvas_' + identifier + ']').next('video').remove();
			$('canvas[id=canvas_' + identifier + ']').show();
			
			// Reset all state start buttons
			if(!noResetOtherPeers) {
				$('span.jchat_start_accept_sharing').removeClass('jchat_disabled');
			}
			
			// Reset the blackboard tab
			$('span.jchat_blackboard_tab').removeClass('active').text(jchat_local_blackboard);
			
			// Reset the incoming call popover
			$('.jchat_trigger_blackboard', '#jchat_user_' + identifier + '_popup').removeAttr('data-text');
			
			// Reset interval state if any
			if(ringingInterval) {
				clearInterval(ringingInterval);
				ringingInterval = null;
			}
		};
		
		/**
		 * Reset the popups tooltip and triggers state
		 * 
		 * @access private
		 * @return Void
		 */
		var resetTooltips = function() {
			// A popup tooltip is found as open
			if($('#jchat_trigger_blackboard_tooltip').length) {
				$('#jchat_trigger_blackboard_tooltip').remove();
			}
		};
		
		/**
		 * Start a sharing session
		 * 
		 * @access private
		 * @return Void
		 */
		var startShare = function() {
			// Check if a valid local stream has got
			if(!localGotStream) {
				showRTCMessages('jchat_exceptions', 'jchat_icon_error', 'jchat_tooltip_error', jchat_missing_local_blackabord_stream, 'jchat_exceptions_closer');
				return;
			}
			
			// Create a new peerConnection object and initialize it
			createPeerConnection();
			
			// Ensure object is valid
			if(!peerConnection) {
				return;
			}
			
			// Start a new call process, register callback that is involved during sdp creation and signaling channel send store
			peerConnection.createOffer().then(gotSdpDescription, onSignalingError);
			
			// Show calling user interface
			showRTCMessages('jchat_infouser_webrtc', 'jchat_async_loader', 'jchat_tooltip_innermsg', jchat_connecting);
			
			// Set buttons and interval for the caller
			bindContext.setCallerRingingButton();
			
			// Set a timeout for the started call if no answer by other peer
			startCallTimeout = setTimeout(function(){
				// End call at this stage
				$('.jchat_end_call', $tooltipContainer).trigger('click', [jchat_blackboard_noanswer]);
			}, options.startCallTimeout);
			
			// Log status
			if (debugLogging) {
				console.log('New Blackboard session started');
			}
		};
		
		/**
		 * Accept an incoming call
		 * 
		 * @access private
		 * @return Void
		 */
		var acceptShare = function() {
			// Create a new peerConnection object and initialize it if not already exists
			if(!peerConnection) {
				createPeerConnection();
			}
			
			// Check if accept call is by callee, in this case set and create an sdp answer
			if (this.callee) { // SDP Offer
				// Correct sdp desc received
        		if(receivedSdpMessage) {
        			peerConnection.setRemoteDescription(new RTCSessionDescription(JSON.parse(receivedSdpMessage))).then(function(){
        				// Log status
        				if (debugLogging) {
        					console.log('Set remote Blackboard description by callee');
        				}
        			}, function(error){
        				// Log status
        				if (debugLogging) {
        					console.log('Error during set remote Blackboard description:' + error);
        				}
        			});
        		}
        		// Now create the sdp answer
        		peerConnection.createAnswer().then(gotSdpDescription, onSignalingError);
        		
        		// Ensure call/end button are reset
        		resetButtons(true);
        		// Invert status after call started
				$('.jchat_start_accept_sharing', $tooltipContainer).addClass('jchat_disabled');
				$('.jchat_end_sharing', $tooltipContainer).removeClass('jchat_disabled');
				
				// Show user notice
				showRTCMessages('jchat_infouser_webrtc', 'jchat_icon_ok', 'jchat_tooltip_innermsg', jchat_session_starting, 'jchat_info_closer');
				
				// Log status
				if (debugLogging) {
					console.log('Exchanged Blackboard SDP/ICE and session started between 2 peer');
				}
        	} else if (this.caller) { // SDP Answer
        		if(receivedSdpMessage) {
        			peerConnection.setRemoteDescription(new RTCSessionDescription(JSON.parse(receivedSdpMessage))).then(function(){
        				// Start redrawing the canvas to update video to the remote callee
    					setTimeout(function(){
    						redraw();
    					}, 1000)
        				
        				// Log status
        				if (debugLogging) {
        					console.log('Set remote Blackboard description by caller');
        				}
        			}, function(error){
        				// Log status
        				if (debugLogging) {
        					console.log('Error during set remote Blackboard description:' + error);
        				}
        			});
        		}
        	} 

			// In both cases ice candidates have to be set, caller or callee
        	if(receivedICECandidates) {
        		var candidates = JSON.parse(receivedICECandidates);
        		$.each(candidates, function(index, candidate){
        			var rtcCandidate = new RTCIceCandidate(candidate);
        			try{
        				peerConnection.addIceCandidate(rtcCandidate);
        			} catch(exception) {
        				// Log status
        				if (debugLogging) {
        					console.log(exception.message);
        				}
        			}
        		});
        	}
		};
		
		/**
		 * End the current call
		 * 
		 * @access private
		 * @param String details
		 * @return Void
		 */
		var endShare = function(details) {
			// Get the end call reason desc
			var closedDetails = details ? details : jchat_connection_closed;
			
			// Close the peer connection
			try {
				if(peerConnection) {
					// Show calling user interface
					showRTCMessages('jchat_infouser_webrtc', 'jchat_async_loader', 'jchat_tooltip_innermsg', jchat_closing_connection);
				}
				
				// Close if opened
				peerConnection.close();
				peerConnection = null;
				peerCandidates = new Array();
				candidatesCounter = 0;
			} catch(exception) {
				// Log status
				if (debugLogging) {
					console.log(exception.message);
				}
				
				// Log status
				if (debugLogging) {
					console.log('Session/connection Blackboard ended or refused');
				}
			}
			
			// Delete session on server
			var deleteSessionPromise = $.Deferred(function(defer) {
				$.ajax({
					type : "POST",
					url : jsonLiveSite,
					dataType : 'json',
					context : this,
					data : {task : 'blackboard.deleteEntity', ids : remotePeer}
				}).done(function(response, textStatus, jqXHR) {
					if(!response.storing.status) {
						// Error found
						defer.reject(response.storing.exception_message, textStatus);
						return false;
					}
					
					// Check response all went well
					defer.resolve();
				}).fail(function(jqXHR, textStatus, errorThrown) {
					// Error found
					var genericStatus = textStatus[0].toUpperCase() + textStatus.slice(1);
					defer.reject('-' + genericStatus + '- ' + errorThrown);
				});
			}).promise();

			deleteSessionPromise.then(function(responseData) {
				// Show user notice
				showRTCMessages('jchat_infouser_webrtc', 'jchat_icon_ok', 'jchat_tooltip_innermsg', closedDetails, 'jchat_info_closer');
				
				// Log status
				if (debugLogging) {
					console.log('Session Blackboard ended, connection closed and session deleted on server');
				}
			}, function(errorText, error) {
				// Show user notice
				showRTCMessages('jchat_exceptions', 'jchat_icon_error', 'jchat_tooltip_error', jchat_connection_close_error, 'jchat_exceptions_closer');
				
				// Log status
				if (debugLogging) {
					console.log('Error deleting Blackboard session on server: ' + errorText);
				}
			}).always(function(){
				// Stop button ringing
				resetButtons();
			});
		};
		
		/**
		 * Set ringing button when starting calls from caller perspective
		 * 
		 * @access public
		 * @return Void
		 */
		this.setCallerRingingButton = function() {
			// Videochat popup needs to be opened and initialized
			if(!$tooltipContainer) {
				return;
			}
			
			// Setup for buttons
			$('.jchat_start_accept_sharing', $tooltipContainer).toggleClass('jchat_ringing');
			$('.jchat_end_sharing', $tooltipContainer).removeClass('jchat_disabled');
			
			// Disable all other buttons
			var $tooltipButtonsContext = $('div.jchat_blackboardtooltip[data-userid!=' + identifier + ']');
			$('span.jchat_start_accept_sharing', $tooltipButtonsContext).addClass('jchat_disabled');
			$('span.jchat_end_sharing', $tooltipButtonsContext).addClass('jchat_disabled');
			
			// Start ringing effect and clear time if already started
			if(ringingInterval) {
				clearInterval(ringingInterval);
			}
			// Start now
			ringingInterval = setInterval(function(){
				$('.jchat_start_accept_sharing', $tooltipContainer).toggleClass('jchat_ringing');
			}, 500);
		};
		
		/**
		 * Set ringing button when incoming calls
		 * 
		 * @access public
		 * @return Void
		 */
		this.setCalleeRingingButton = function() {
			// Videochat popup needs to be opened and initialized
			if(!$tooltipContainer) {
				return;
			}
			
			// Setup for buttons
			$('.jchat_start_accept_sharing span.text', $tooltipContainer).text(jchat_accept_sharing);
			$('.jchat_end_sharing span.text', $tooltipContainer).text(jchat_decline_sharing);
			$('.jchat_end_sharing', $tooltipContainer).removeClass('jchat_disabled');
			
			// Disable all other buttons
			var $tooltipButtonsContext = $('div.jchat_blackboardtooltip[data-userid!=' + identifier + ']');
			$('span.jchat_start_accept_sharing', $tooltipButtonsContext).addClass('jchat_disabled');
			$('span.jchat_end_sharing', $tooltipButtonsContext).addClass('jchat_disabled');

			// Start ringing effect and clear time if already started
			if(ringingInterval) {
				clearInterval(ringingInterval);
			}
			ringingInterval = setInterval(function(){
				$('.jchat_start_accept_sharing', $tooltipContainer).toggleClass('jchat_ringing');
			}, 500);
			
			// Show user notice
			showRTCMessages('jchat_infouser_webrtc', 'jchat_icon_ok', 'jchat_tooltip_innermsg', jchat_blackboard_request, 'jchat_info_closer');
		};
		
		/**
		 * Register user events for interface controls
		 * 
		 * @access private
		 * @return Void
		 */
		var registerEvents = function() {
			// Start call handler
			$('.jchat_start_accept_sharing', $tooltipContainer).on('click.jchatwebrtcblackboard', {scope: this}, function(jqEvent){
				// Check if button is in disabled state
				if($(this).hasClass('jchat_disabled')) {
					return false;
				}
				
				// Check callee state and post data to server using signaling channel
				if(!jqEvent.data.scope.callee) {
					// Set caller true, this peer is starting a new call caller = true
					jqEvent.data.scope.caller = true;
					BlackBoard.starterSession = identifier;
					
					// Start new call
					startShare();
				} else {
					// Accept incoming call, this peer is the callee
					acceptShare.call(jqEvent.data.scope);
				}
			});
			
			// End call handler
			$('.jchat_end_sharing', $tooltipContainer).on('click.jchatwebrtcblackboard', {scope: this}, function(jqEvent, details){
				// Check if button is in disabled state
				if($(this).hasClass('jchat_disabled')) {
					return false;
				}
				
				// Reset state of roles
				jqEvent.data.scope.caller = false;
				jqEvent.data.scope.callee = false;
				BlackBoard.starterSession = null;
				BlackBoard.receiverSession = null;
				promiseResolved = false;
				
				// End the call and flush the signaling channel
				endShare(details);
			});
			
			// Upload canvas handler
			$('.image_uploader', $tooltipContainer).on('change.jchatwebrtcblackboard', function(jqEvent){
				var dataIdentifier = $(this).data('identifier');
				var targetCanvas = $('canvas[id=canvas_' + dataIdentifier + ']');
				var ctx = targetCanvas.get(0).getContext('2d');
				
				var reader = new FileReader();
				reader.onload = function(event){
			        var img = new Image();
			        img.onload = function(){
			            ctx.drawImage(img, 111, 11, 267, 200);
			            redraw();
			        }
			        img.src = event.target.result;
			        ctx.canvasimage = img;
			    }
			    reader.readAsDataURL(jqEvent.target.files[0]);     
			});
		};
		
		/**
		 * Reset the sounds if any looped both caller and callee
		 * 
		 * @access private
		 * @return Void
		 */
		var resetSounds = function() {
			// Clear current timeout if any
			if(typeof startCallTimeout == 'number') {
				clearTimeout(startCallTimeout);
			}
		};
		
		/**
		 * Create a video tag element based on input params
		 * 
		 * @access private
		 * @return Void
		 */
		var createVideoTag = function(id, width, height, video_source) {
			  var videoTag = document.createElement('video');
			  if (video_source) {
				  videoTag.srcObject = video_source;
			  }
			  videoTag.id = id;
			  videoTag.width = width;
			  videoTag.height = height;
			  videoTag.className = "w3-round w3-border w3-card-4";
			  videoTag.autoplay = true;
			  if (debugLogging) {
				  console.log("VideoTag " + id + " created");
			  }
			  
			  return videoTag;
		};
		
		/**
		 * Calls the redraw function after all neccessary resources are loaded.
		 * 
		 * @access private
		 * @return Void
		 */
		var resourceLoaded = function() {
			if(++curLoadResNum >= totalLoadResources){
				redraw();
			}
		};

		/**
		 * Adds a point to the drawing array.
		 * 
		 * @param x
		 * @param y
		 * @param dragging
		 * 
		 * @access private
		 * @return Void
		 */
		var addClick = function(x, y, dragging) {
			clickX.push(x);
			clickY.push(y);
			clickTool.push(curTool);
			clickColor.push(curColor);
			clickSize.push(curSize);
			clickDrag.push(dragging);
		};

		/**
		 * Clears the canvas.
		 * 
		 * @access private
		 * @return Void
		 */
		var clearCanvas = function() {
			context.clearRect(0, 0, canvasWidth, canvasHeight);
		};

		/**
		 * Redraws the canvas.
		 * 
		 * @access private
		 * @return Void
		 */
		var redraw = function() {
			// Make sure required resources are loaded before redrawing
			if(curLoadResNum < totalLoadResources){ return; }
			
			clearCanvas();
			
			var locX;
			var locY;
			if(curTool == "crayon")
			{
				// Draw the crayon tool background
				context.drawImage(crayonBackgroundImage, 0, 0, canvasWidth, canvasHeight);
				
				// Purple
				locX = (curColor == colorPurple) ? 18 : 52;
				locY = 19;
				
				context.beginPath();
				context.moveTo(locX + 41, locY + 11);
				context.lineTo(locX + 41, locY + 35);
				context.lineTo(locX + 29, locY + 35);
				context.lineTo(locX + 29, locY + 33);
				context.lineTo(locX + 11, locY + 27);
				context.lineTo(locX + 11, locY + 19);
				context.lineTo(locX + 29, locY + 13);
				context.lineTo(locX + 29, locY + 11);
				context.lineTo(locX + 41, locY + 11);
				context.closePath();
				context.fillStyle = colorPurple;
				context.fill();	

				if(curColor == colorPurple){
					context.drawImage(crayonImage, locX, locY, mediumImageWidth, mediumImageHeight);
				}else{
					context.drawImage(crayonImage, 0, 0, 59, mediumImageHeight, locX, locY, 59, mediumImageHeight);
				}
				
				// Green
				locX = (curColor == colorGreen) ? 18 : 52;
				locY += 46;
				
				context.beginPath();
				context.moveTo(locX + 41, locY + 11);
				context.lineTo(locX + 41, locY + 35);
				context.lineTo(locX + 29, locY + 35);
				context.lineTo(locX + 29, locY + 33);
				context.lineTo(locX + 11, locY + 27);
				context.lineTo(locX + 11, locY + 19);
				context.lineTo(locX + 29, locY + 13);
				context.lineTo(locX + 29, locY + 11);
				context.lineTo(locX + 41, locY + 11);
				context.closePath();
				context.fillStyle = colorGreen;
				context.fill();	

				if(curColor == colorGreen){
					context.drawImage(crayonImage, locX, locY, mediumImageWidth, mediumImageHeight);
				}else{
					context.drawImage(crayonImage, 0, 0, 59, mediumImageHeight, locX, locY, 59, mediumImageHeight);
				}
				
				// Yellow
				locX = (curColor == colorYellow) ? 18 : 52;
				locY += 46;
				
				context.beginPath();
				context.moveTo(locX + 41, locY + 11);
				context.lineTo(locX + 41, locY + 35);
				context.lineTo(locX + 29, locY + 35);
				context.lineTo(locX + 29, locY + 33);
				context.lineTo(locX + 11, locY + 27);
				context.lineTo(locX + 11, locY + 19);
				context.lineTo(locX + 29, locY + 13);
				context.lineTo(locX + 29, locY + 11);
				context.lineTo(locX + 41, locY + 11);
				context.closePath();
				context.fillStyle = colorYellow;
				context.fill();	

				if(curColor == colorYellow){
					context.drawImage(crayonImage, locX, locY, mediumImageWidth, mediumImageHeight);
				}else{
					context.drawImage(crayonImage, 0, 0, 59, mediumImageHeight, locX, locY, 59, mediumImageHeight);
				}
				
				// Yellow
				locX = (curColor == colorBrown) ? 18 : 52;
				locY += 46;
				
				context.beginPath();
				context.moveTo(locX + 41, locY + 11);
				context.lineTo(locX + 41, locY + 35);
				context.lineTo(locX + 29, locY + 35);
				context.lineTo(locX + 29, locY + 33);
				context.lineTo(locX + 11, locY + 27);
				context.lineTo(locX + 11, locY + 19);
				context.lineTo(locX + 29, locY + 13);
				context.lineTo(locX + 29, locY + 11);
				context.lineTo(locX + 41, locY + 11);
				context.closePath();
				context.fillStyle = colorBrown;
				context.fill();	

				if(curColor == colorBrown){
					context.drawImage(crayonImage, locX, locY, mediumImageWidth, mediumImageHeight);
				}else{
					context.drawImage(crayonImage, 0, 0, 59, mediumImageHeight, locX, locY, 59, mediumImageHeight);
				}
			}
			else if(curTool == "marker")
			{
				// Draw the marker tool background
				context.drawImage(markerBackgroundImage, 0, 0, canvasWidth, canvasHeight);
				
				// Purple
				locX = (curColor == colorPurple) ? 18 : 52;
				locY = 19;
				
				context.beginPath();
				context.moveTo(locX + 10, locY + 24);
				context.lineTo(locX + 10, locY + 24);
				context.lineTo(locX + 22, locY + 16);
				context.lineTo(locX + 22, locY + 31);
				context.closePath();
				context.fillStyle = colorPurple;
				context.fill();	

				if(curColor == colorPurple){
					context.drawImage(markerImage, locX, locY, mediumImageWidth, mediumImageHeight);
				}else{
					context.drawImage(markerImage, 0, 0, 59, mediumImageHeight, locX, locY, 59, mediumImageHeight);
				}
				
				// Green
				locX = (curColor == colorGreen) ? 18 : 52;
				locY += 46;
				
				context.beginPath();
				context.moveTo(locX + 10, locY + 24);
				context.lineTo(locX + 10, locY + 24);
				context.lineTo(locX + 22, locY + 16);
				context.lineTo(locX + 22, locY + 31);
				context.closePath();
				context.fillStyle = colorGreen;
				context.fill();	

				if(curColor == colorGreen){
					context.drawImage(markerImage, locX, locY, mediumImageWidth, mediumImageHeight);
				}else{
					context.drawImage(markerImage, 0, 0, 59, mediumImageHeight, locX, locY, 59, mediumImageHeight);
				}
				
				// Yellow
				locX = (curColor == colorYellow) ? 18 : 52;
				locY += 46;
				
				context.beginPath();
				context.moveTo(locX + 10, locY + 24);
				context.lineTo(locX + 10, locY + 24);
				context.lineTo(locX + 22, locY + 16);
				context.lineTo(locX + 22, locY + 31);
				context.closePath();
				context.fillStyle = colorYellow;
				context.fill();	

				if(curColor == colorYellow){
					context.drawImage(markerImage, locX, locY, mediumImageWidth, mediumImageHeight);
				}else{
					context.drawImage(markerImage, 0, 0, 59, mediumImageHeight, locX, locY, 59, mediumImageHeight);
				}
				
				// Yellow
				locX = (curColor == colorBrown) ? 18 : 52;
				locY += 46;
				
				context.beginPath();
				context.moveTo(locX + 10, locY + 24);
				context.lineTo(locX + 10, locY + 24);
				context.lineTo(locX + 22, locY + 16);
				context.lineTo(locX + 22, locY + 31);
				context.closePath();
				context.fillStyle = colorBrown;
				context.fill();	

				if(curColor == colorBrown){
					context.drawImage(markerImage, locX, locY, mediumImageWidth, mediumImageHeight);
				}else{
					context.drawImage(markerImage, 0, 0, 59, mediumImageHeight, locX, locY, 59, mediumImageHeight);
				}
			}
			else if(curTool == "eraser")
			{
				context.drawImage(eraserBackgroundImage, 0, 0, canvasWidth, canvasHeight);
				context.drawImage(eraserImage, 18, 19, 93, 100);	
			}else{
				alert("Error: Current Tool is undefined");
			}
			
			if(curSize == "small"){
				locX = 467;
			}else if(curSize == "normal"){
				locX = 450;
			}else if(curSize == "large"){
				locX = 428;
			}else if(curSize == "huge"){
				locX = 399;
			}
			locY = 189;
			context.beginPath();
			context.rect(locX, locY, 2, 12);
			context.closePath();
			context.fillStyle = '#333333';
			context.fill();	
			
			// Keep the drawing in the drawing area
			context.save();
			context.beginPath();
			context.rect(drawingAreaX, drawingAreaY, drawingAreaWidth, drawingAreaHeight);
			context.clip();
				
			// Redraw at this stage the uploaded canvas image if any
			if(context.canvasimage) {
        		context.drawImage(context.canvasimage, 111, 11, 267, 200);
        	}
			
			var radius;
			var i = 0;
			for(; i < clickX.length; i++)
			{		
				if(clickSize[i] == "small"){
					radius = 2;
				}else if(clickSize[i] == "normal"){
					radius = 5;
				}else if(clickSize[i] == "large"){
					radius = 10;
				}else if(clickSize[i] == "huge"){
					radius = 20;
				}else{
					alert("Error: Radius is zero for click " + i);
					radius = 0;	
				}
				
				context.beginPath();
				if(clickDrag[i] && i){
					context.moveTo(clickX[i-1], clickY[i-1]);
				}else{
					context.moveTo(clickX[i], clickY[i]);
				}
				context.lineTo(clickX[i], clickY[i]);
				context.closePath();
				
				if(clickTool[i] == "eraser"){
					// context.globalCompositeOperation = "destination-out"; //
					// To erase instead of draw over with white
					context.strokeStyle = 'white';
				}else{
					// context.globalCompositeOperation = "source-over"; // To
					// erase instead of draw over with white
					context.strokeStyle = clickColor[i];
				}
				context.lineJoin = "round";
				context.lineWidth = radius;
				context.stroke();
				
			}
			// context.globalCompositeOperation = "source-over";// To erase
			// instead of draw over with white
			context.restore();
			
			// Overlay a crayon texture (if the current tool is crayon)
			if(curTool == "crayon"){
				context.globalAlpha = 0.4; // No IE support
				context.drawImage(crayonTextureImage, 0, 0, canvasWidth, canvasHeight);
			}
			context.globalAlpha = 1; // No IE support
		};
		
		/**
		 * Creates a canvas element, loads images, adds events, and draws the canvas for the first time.
		 * 
		 * @access public
		 * @return Void
		 */
		this.prepareCanvas = function() {
			// Create the canvas (Neccessary for IE because it doesn't know what a canvas element is)
			var canvasDivWrappedset = $('div.jchat_blackboardtooltip[data-userid=' + identifier + ']');
			var canvasDiv = canvasDivWrappedset.get(0);
			var canvasIdentifier = 'canvas_' + identifier;
			canvas = document.createElement('canvas');
			canvas.setAttribute('class', 'jchat_canvas_blackboard');
			canvas.setAttribute('width', canvasWidth);
			canvas.setAttribute('height', canvasHeight);
			canvas.setAttribute('id', canvasIdentifier);
			$(canvas).css('touch-action', 'none');
			canvasDiv.appendChild(canvas);
			
			// Append share controls
			var disabledButtonState = '';
			if(BlackBoard.starterSession) {
				if(BlackBoard.starterSession != identifier) {
					disabledButtonState = ' jchat_disabled'; 
				}
			}
			if(BlackBoard.receiverSession) {
				if(BlackBoard.receiverSession != identifier) {
					disabledButtonState = ' jchat_disabled'; 
				}
			}
			canvasDivWrappedset.prepend('<span class="jchat_blackboard_tab">' + jchat_local_blackboard + '</span>');
			canvasDivWrappedset.append('<div class="jchat_webrtc_clearer"/>');
			canvasDivWrappedset.append('<span class="jchat_start_accept_sharing' + disabledButtonState + '"><span class="text">' + jchat_start_sharing + '</span></span>');
			canvasDivWrappedset.append('<span class="jchat_end_sharing jchat_disabled"><span class="text">'+ jchat_end_sharing + '</span></span>');
			canvasDivWrappedset.append('<input type="file" class="image_uploader" data-identifier="' + identifier + '" id="file_' + identifier + '"/><label class="image_uploader" for="file_' + identifier + '">' + jchat_blackboard_image_upload + '</label>');
			
			if(typeof G_vmlCanvasManager != 'undefined') {
				canvas = G_vmlCanvasManager.initElement(canvas);
			}
			context = canvas.getContext("2d"); // Grab the 2d canvas context
			// Note: The above code is a workaround for IE 8 and lower.
			// Otherwise we could have used:
			// context = document.getElementById('canvas').getContext("2d");
			
			// Load images
			// -----------
			crayonImage.onload = function() { resourceLoaded(); };
			crayonImage.src = jchat_livesite + "components/com_jchat/images/blackboard/crayon-outline.png";
			
			markerImage.onload = function() { resourceLoaded(); };
			markerImage.src = jchat_livesite + "components/com_jchat/images/blackboard/marker-outline.png";
			
			eraserImage.onload = function() { resourceLoaded(); };
			eraserImage.src = jchat_livesite + "components/com_jchat/images/blackboard/eraser-outline.png";	
			
			crayonBackgroundImage.onload = function() { resourceLoaded(); };
			crayonBackgroundImage.src = jchat_livesite + "components/com_jchat/images/blackboard/crayon-background.png";
			
			markerBackgroundImage.onload = function() { resourceLoaded(); };
			markerBackgroundImage.src = jchat_livesite + "components/com_jchat/images/blackboard/marker-background.png";
			
			eraserBackgroundImage.onload = function() { resourceLoaded(); };
			eraserBackgroundImage.src = jchat_livesite + "components/com_jchat/images/blackboard/eraser-background.png";

			crayonTextureImage.onload = function() { resourceLoaded(); };
			crayonTextureImage.src = jchat_livesite + "components/com_jchat/images/blackboard/crayon-texture.png";
			
			resourceLoaded(); 

			// Add mouse events
			$('#' + canvasIdentifier).on('mousedown pointerdown', function(e)
			{
				// Mouse down location
				var mouseX = e.pageX - $(this).parent().offset().left - XDisplacementKonstant;
				var mouseY = e.pageY - $(this).parent().offset().top - YDisplacementKonstant;
				
				if(mouseX < drawingAreaX) // Left of the drawing area
				{
					if(mouseX > mediumStartX)
					{
						if(curTool == 'eraser') {
							// Delete drawing only
							if(mouseY > mediumStartY && mouseY < mediumStartY + mediumImageHeight){
								if(curTool == 'eraser') {
									clearCanvas();
									clickX = new Array();
									clickY = new Array();
									clickColor = new Array();
									clickTool = new Array();
									clickSize = new Array();
									clickDrag = new Array();
									paint = false;
								}
							}else if(mouseY > mediumStartY + doubledImageHeight && mouseY < mediumStartY + mediumImageHeight * 2) {
								// Delete all
								if(curTool == 'eraser') {
									clearCanvas();
									clickX = new Array();
									clickY = new Array();
									clickColor = new Array();
									clickTool = new Array();
									clickSize = new Array();
									clickDrag = new Array();
									paint = false;
									context.canvasimage = false;
								}
							}
						} else {
							if(mouseY > mediumStartY && mouseY < mediumStartY + mediumImageHeight){
								curColor = colorPurple;
							}else if(mouseY > mediumStartY + mediumImageHeight && mouseY < mediumStartY + mediumImageHeight * 2){
								curColor = colorGreen;
							}else if(mouseY > mediumStartY + mediumImageHeight * 2 && mouseY < mediumStartY + mediumImageHeight * 3){
								curColor = colorYellow;
							}else if(mouseY > mediumStartY + mediumImageHeight * 3 && mouseY < mediumStartY + mediumImageHeight * 4){
								curColor = colorBrown;
							}
						}
					}
				}
				else if(mouseX > drawingAreaX + drawingAreaWidth) // Right of
																	// the
																	// drawing
																	// area
				{
					if(mouseY > toolHotspotStartY)
					{
						if(mouseY > sizeHotspotStartY)
						{
							var sizeHotspotStartX = drawingAreaX + drawingAreaWidth;
							if(mouseY < sizeHotspotStartY + sizeHotspotHeight && mouseX > sizeHotspotStartX)
							{
								if(mouseX < sizeHotspotStartX + sizeHotspotWidthObject.huge){
									curSize = "huge";
								}else if(mouseX < sizeHotspotStartX + sizeHotspotWidthObject.large + sizeHotspotWidthObject.huge){
									curSize = "large";
								}else if(mouseX < sizeHotspotStartX + sizeHotspotWidthObject.normal + sizeHotspotWidthObject.large + sizeHotspotWidthObject.huge){
									curSize = "normal";
								}else if(mouseX < sizeHotspotStartX + sizeHotspotWidthObject.small + sizeHotspotWidthObject.normal + sizeHotspotWidthObject.large + sizeHotspotWidthObject.huge){
									curSize = "small";						
								}
							}
						}
						else
						{
							if(mouseY < toolHotspotStartY + toolHotspotHeight){
								curTool = "marker";
							}else if(mouseY < toolHotspotStartY + toolHotspotHeight * 2){
								curTool = "crayon";
							}else if(mouseY < toolHotspotStartY + toolHotspotHeight * 3){
								curTool = "eraser";
							}
						}
					}
				}
				else if(mouseY > drawingAreaY && mouseY < drawingAreaY + drawingAreaHeight)
				{
					// Mouse click location on drawing area
				}
				paint = true;
				addClick(mouseX, mouseY, false);
				redraw();
			});
			
			$('#' + canvasIdentifier).on('mousemove pointermove', function(e){
				if(paint==true){
					addClick(e.pageX - $(this).parent().offset().left - XDisplacementKonstant, e.pageY - $(this).parent().offset().top - YDisplacementKonstant, true);
					redraw();
				}
			});
			
			$('#' + canvasIdentifier).on('mouseup pointerup', function(e){
				paint = false;
			  	redraw();
			});
			
			$('#' + canvasIdentifier).on('mouseleave pointerleave', function(e){
				paint = false;
			});
			
			// Create a MediaStream out of the <video> tag.
			remotePeer = identifier;
			$tooltipContainer = $('.jchat_blackboardtooltip[data-userid=' + identifier + ']');
			localGotStream = document.getElementById(canvasIdentifier).captureStream(25);
			
			// Register user interface events
			registerEvents.call(this);
		};
		
		/**
		 * Function dummy constructor
		 * 
		 * @access private
		 * @param String
		 *            contextSelector
		 * @method <<IIFE>>
		 * @return Void
		 */
		(function __construct() {
			// Set ICE servers
			servers = {
					"iceServers" : options.iceServers
			}
			
			// Init status on load
			var isFirefox = false;
			if (navigator.mozGetUserMedia) {
				window.RTCSessionDescription = window.RTCSessionDescription || window.mozRTCSessionDescription;
				window.RTCIceCandidate = window.RTCIceCandidate || window.mozRTCIceCandidate;
				isFirefox = true;
			}
			
			// Set local vars
			debugLogging = options.debugEnabled;
			jsonLiveSite = options.jsonLiveSite;
			
			// Set sound status
			JChatNotifications.setAudioStatus(options.audiostatus);
			
			// Detect and store full support for WebRTC features
			supportFullWebRTC = !!navigator.mediaDevices && !!window.RTCPeerConnection;
		}).call(this);
	}
	
	/**
	 * Show the fallback layout when no WebRTC support is detected
	 * 
	 * @access public static
	 * @return Void
	 */
	BlackBoard.showFallback = function($container) {
		// Define disclaimer
		$container.append('<div class="jchat_webrtc_nosupport jchat_webrtc_blackboard_nosupport">' + jchat_webrtc_blackboard_nosupport + '</div>');

		// Define browsers source array
		var browsersArray = [ {
			browserName : 'chrome',
			support : 'jchat_support_ok',
			description : jchat_chrome_webrtc_blackboard_support
		}, {
			browserName : 'firefox',
			support : 'jchat_support_ok',
			description : jchat_firefox_webrtc_blackboard_support
		}, {
			browserName : 'opera',
			support : 'jchat_support_ok',
			description : jchat_opera_webrtc_blackboard_support
		} ];
		
		// Now build the UX for browsers
		$.each(browsersArray, function(index, browser){
			var browserImage = 'components/com_jchat/images/default/' + browser.browserName + '_48x48.png';
			
			$container.append(	'<div class="jchat_browser_row">' +
									'<img src="' + jchat_livesite + browserImage + '" alt="browsers"/>' +
									'<span class="' + browser.support + '">' + browser.description + '</span>' +
								'</div>');
		});
	};
	
	/**
	 * Static global caller state
	 * 
	 * @access public static
	 */
	BlackBoard.starterSession = null;
	
	/**
	 * Static global callee state
	 * 
	 * @access public static
	 */
	BlackBoard.receiverSession = null;
	
	// Make it global to instantiate as plugin
	window.JChatBlackBoard = BlackBoard;
})(jQuery);