/**
 *  Copyright (C) 2022, 2024  Double Bastion LLC
 *
 *  This file is part of Roundpin, which is licensed under the
 *  GNU Affero General Public License Version 3.0. The license terms
 *  are detailed in the "LICENSE.txt" file located in the root directory.
 *
 *  This is a substantially modified version of the original file
 *  "phone.js", first modified in 2020.
 *
 *  The original "phone.js" file was written by Conrad de Wet.
 *  We thank Conrad de Wet for his work and we list below
 *  the copyright notice of the original "phone.js" file:

/*
-------------------------------------------------------------
 Copyright (c) 2020  - Conrad de Wet - All Rights Reserved.
=============================================================
File: phone.js
License: GNU Affero General Public License v3.0
Version: 0.1.0
Owner: Conrad de Wet
Date: April 2020
Git: https://github.com/InnovateAsterisk/Browser-Phone
*/


// Global Settings
// ===============

// Lanaguage Packs
// ===============

const availableLang = { "en": "English", "de": "German", "fr": "French", "es": "Spanish", "it": "Italian", "pt": "Portuguese" };

// User Settings & Defaults
// ========================
let wssServer = null;
let profileUserID = null;
let profileUser = null;
let profileName = null;
let WebSocketPort = null;
let ServerPath = null;
let SipUsername = null;
let SipPassword = null;
let StunServer = null;
let rcBasicAuthUser = '';
let rcBasicAuthPass = '';
let RoundcubeUser = '';
let RoundcubePass = '';
let TransportConnectionTimeout = 15;      // The timeout in seconds for the initial connection to make on the web socket port
let TransportReconnectionAttempts = 99;   // The number of times to attempt to reconnect to a WebSocket when the connection drops.
let TransportReconnectionTimeout = 15;    // The time in seconds to wait between WebSocket reconnection attempts.
let userAgentStr = "Roundpin";            // Set this to whatever you want.
let hostingPrefix = '';                   // Use if hosting is not on root directory (Eg: on /phone/ or /static/)
let RegisterExpires = 600;                // Registration expiry time (in seconds)
let WssInTransport = true;                // Set the transport parameter to wss when used in SIP URIs. (Required for Asterisk as it doesnt support Path)
let IpInContact = true;                   // Set a random IP address as the host value in the Contact header field and Via sent-by parameter. (Suggested for Asterisk)
let IceStunCheckTimeout = 500;            // Set amount of time in milliseconds to wait for the ICE/STUN server
let StartVideoFullScreen = false;         // Starts a vdeo call in the full screen (browser screen, not dektop)
let AutoGainControl = true;               // Attempts to adjust the microphone volume to a good audio level. (OS may be better at this)
let EchoCancellation = true;              // Attemots to remove echo over the line.
let NoiseSuppression = true;              // Attempts to clear the call qulity of noise.
let MirrorVideo = "rotateY(180deg)";      // Displays the self-preview in normal or mirror view, to better present the preview. 
let maxFrameRate = 30;                    // Suggests a frame rate to your webcam if possible.
let videoHeight = 720;                    // Suggests a video height (and therefore picture quality) to your webcam.
let videoAspectRatio = 1.33;              // Suggests an aspect ratio (1:1 | 4:3 | 16:9) to your webcam.
let NotificationsActive = false;
let ShowVconfUsernamesActive = false;
let DisplayVconfUserActive = false;
let PosterJpegQuality = 0.6;              // The image quality of the Video Poster images
let VideoResampleSize = "HD";             // The resample size (height) to re-render video that gets presented (sent). (SD = ???x360 | HD = ???x720 | FHD = ???x1080)
let RecordingVideoSize = "HD";            // The size/quality of the video track in the recodings (SD = 640x360 | HD = 1280x720 | FHD = 1920x1080)
let RecordingVideoFps = 12;               // The Frame Per Second of the Video Track recording
let RecordingLayout = "them-pnp";         // The Layout of the Recording Video Track (side-by-side | us-pnp | them-pnp | us-only | them-only)
let DidLength = 4;                        // If the phone number length is greater than this number, the number is a "contact", otherwise it's an "extension".
let DisplayDateFormat = "YYYY-MM-DD";     // The display format for all dates. https://momentjs.com/docs/#/displaying/
let DisplayTimeFormat = "h:mm:ss A";      // The display format for all times. https://momentjs.com/docs/#/displaying/
let currentStatus = getDbItem("MyCurrentStatus", "");

// System variables
// ================
let localDB = window.localStorage;
let userAgent = null;
let BlfSubs = [];
let CanvasCollection = [];
let Buddies = [];
let contactsObj = { TotalRows: 0, DataCollection: [] };
let streamObj = {};
let imgagesObj = {};
let profilePictureBsf = null;
let extUserExtension = [];
let extUserExtensionPass = [];
let vidConferenceExtension = [];
let vidConferenceTag = [];
let confAccessLink = [];
let isReRegister = false;
let selectedBuddy = null;
let selectedLine = null;
let HasVideoDevice = false;
let HasAudioDevice = false;
let HasSpeakerDevice = false;
let AudioinputDevices = [];
let VideoinputDevices = [];
let SpeakerDevices = [];
let Lines = [];
let lang = {};
let audioBlobs = {};
let newLineNumber = 0;
let videoAudioCheck = 0;
let RCLoginCheck = 0;
let decSipPass = '';
let currentChatPrivKey = '';
let sendFileCheck = 0;
let sendRecFileCheck = 0;
let upFileName = '';
let upFileNameTCPM = {};
let upRecFileName = '';
let sendFileChatErr = '';
let sendRecFileChatErr = '';
let pubKeyCheck = 0;
let splitMessage = {};
let winTextConfCheck = {};
let winVideoConf = null;
let winMsgToGrps = 0;
let pictureUp = false;
let ebpictureUp = false;
let textExtTagObj = {};
let textExtGroupsObj = {};
let audioExtTagObj = {};
let audioExtGroupsObj = {};
let videoExtTagObj = {};
let videoExtGroupsObj = {};
let docIsVisible = true;
let notifyPermCheck = true;
let tcSessions = {};
let sendFileCheckTC = 0;
let sendFileCheckTCPM = {};
let sendFileChatErrTC = '';
let sendFileChatErrTCPM = {};
let messageWindowStateTC = {};
let messageWindowStateTCPM = {};
let messageWindowStateMgr = 'closed';
let splitMessageTC = {};
let splitMessageTCPM = {};
let tconfPrivKey = {};
let sendFileNoChck = 0;
let textConfNmb = 0;
let textConfNmbPM = 0;
let settingsMicrophoneStream = null;
let settingsMicrophoneStreamTrack = null;
let settingsMicrophoneSoundMeter = null;
let settingsVideoStream = null;
let settingsVideoStreamTrack = null;
let localVStream = null;
let localAStream = null;
let allGroups = [];
let allGroupsAndIds = {};
let roundpinUsers = {};
let bannedUsers = {};
let loadGroups = '';
let phoneNumbersFdb = {};
let faxsettingsFdb = {};

// Utilities
// =========
function uID(){
    return Date.now()+Math.floor(Math.random()*10000).toString(16).toUpperCase();
}
function utcDateNow(){
    return moment().utc().format("YYYY-MM-DD HH:mm:ss UTC");
}
function getDbItem(itemIndex, defaultValue){
    if (window.localStorage.getItem(itemIndex) != null && window.localStorage.getItem(itemIndex) != 'null') { 
        return window.localStorage.getItem(itemIndex); 
    } else { return defaultValue; }
}
function getAudioSrcID(){
    var id = localDB.getItem("AudioSrcId");
    return (id != null)? id : "default";
}
function getAudioOutputID(){
    var id = localDB.getItem("AudioOutputId");
    return (id != null)? id : "default";
}
function getVideoSrcID(){
    var id = localDB.getItem("VideoSrcId");
    return (id != null)? id : "default";
}
function getRingerOutputID(){
    var id = localDB.getItem("RingOutputId");
    return (id != null)? id : "default";
}
function formatDuration(seconds){
    var sec = Math.floor(parseFloat(seconds));
    if(sec < 0){
        return sec;
    }
    else if(sec >= 0 && sec < 60){
         return sec + " " + ((sec != 1) ? lang.seconds_plural : lang.second_single);
    } 
    else if(sec >= 60 && sec < 60 * 60){ // greater then a minute and less then an hour
        var duration = moment.duration(sec, 'seconds');
        return duration.minutes() + " "+ ((duration.minutes() != 1) ? lang.minutes_plural: lang.minute_single) +" " + duration.seconds() +" "+ ((duration.seconds() != 1) ? lang.seconds_plural : lang.second_single);
    } 
    else if(sec >= 60 * 60 && sec < 24 * 60 * 60){ // greater than an hour and less then a day
        var duration = moment.duration(sec, 'seconds');
        return duration.hours() + " "+ ((duration.hours() != 1) ? lang.hours_plural : lang.hour_single) +" " + duration.minutes() + " "+ ((duration.minutes() != 1) ? lang.minutes_plural: lang.minute_single) +" " + duration.seconds() +" "+ ((duration.seconds() != 1) ? lang.seconds_plural : lang.second_single);
    } 
    //  Otherwise.. this is just too long
}
function formatShortDuration(seconds) {
    var sec = Math.floor(parseFloat(seconds));
    if(sec < 0){
        return sec;
    } 
    else if(sec >= 0 && sec < 60){
        return "00:"+ ((sec > 9)? sec : "0"+sec );
    } 
    else if(sec >= 60 && sec < 60 * 60){ // greater then a minute and less then an hour
        var duration = moment.duration(sec, 'seconds');
        return ((duration.minutes() > 9)? duration.minutes() : "0"+duration.minutes()) + ":" + ((duration.seconds() > 9)? duration.seconds() : "0"+duration.seconds());
    } 
    else if(sec >= 60 * 60 && sec < 24 * 60 * 60){ // greater than an hour and less then a day
        var duration = moment.duration(sec, 'seconds');
        return ((duration.hours() > 9)? duration.hours() : "0"+duration.hours())  + ":" + ((duration.minutes() > 9)? duration.minutes() : "0"+duration.minutes())  + ":" + ((duration.seconds() > 9)? duration.seconds() : "0"+duration.seconds());
    } 
    //  Otherwise.. this is just too long
}
function formatBytes(bytes, decimals) {
    if (bytes === 0) return "0 "+ lang.bytes;
    var k = 1024;
    var dm = (decimals && decimals >= 0)? decimals : 2;
    var sizes = [lang.bytes, lang.kb, lang.mb, lang.gb, lang.tb, lang.pb, lang.eb, lang.zb, lang.yb];
    var i = Math.floor(Math.log(bytes) / Math.log(k));
    return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i];
}
function UserLocale(){
    var language = window.navigator.userLanguage || window.navigator.language; // "en", "en-US", "fr", "fr-FR", "es-ES", etc.

    langtag = language.split('-');
    if(langtag.length == 1){
        return ""; 
    } 
    else if(langtag.length == 2) {
        return langtag[1].toLowerCase();  // en-US => us
    }
    else if(langtag.length >= 3) {
        return langtag[1].toLowerCase();  // en-US => us
    }
}

function getFilter(filter, keyword) {
    if(filter.indexOf(",", filter.indexOf(keyword +": ") + keyword.length + 2) != -1) {
        return filter.substring(filter.indexOf(keyword +": ") + keyword.length + 2, filter.indexOf(",", filter.indexOf(keyword +": ") + keyword.length + 2));
    }
    else {
        return filter.substring(filter.indexOf(keyword +": ") + keyword.length + 2);
    }
}
function base64toBlob(base64Data, contentType) {
    if (base64Data.indexOf("," != -1)) base64Data = base64Data.split(",")[1]; // [data:image/png;base64] , [...]
    var byteCharacters = atob(base64Data);
    var slicesCount = Math.ceil(byteCharacters.length / 1024);
    var byteArrays = new Array(slicesCount);
    for (var s = 0; s < slicesCount; ++s) {
        var begin = s * 1024;
        var end = Math.min(begin + 1024, byteCharacters.length);
        var bytes = new Array(end - begin);
        for (var offset = begin, i = 0; offset < end; ++i, ++offset) {
            bytes[i] = byteCharacters[offset].charCodeAt(0);
        }
        byteArrays[s] = new Uint8Array(bytes);
    }
    return new Blob(byteArrays, { type: contentType });
}
function MakeDataArray(defaultValue, count) {
    var rtnArray = new Array(count);
    for(var i = 0; i < rtnArray.length; i++) {
        rtnArray[i] = defaultValue;
    }
    return rtnArray;
}
// Generate a random alphanumeric string, of variable length
function randString(length) {
      var allchars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
      var result = '';
      for (var i = length; i > 0; --i) { result += allchars[Math.floor(Math.random() * allchars.length)]; }
      return result;
}
// Save account configuration data to the database
function saveConfToSqldb() {

    wssServer = $("#Configure_Account_wssServer").val();
    WebSocketPort = parseInt($("#Configure_Account_WebSocketPort").val());
    ServerPath = $("#Configure_Account_ServerPath").val();
    profileName = $("#Configure_Account_profileName").val();
    SipUsername = $("#Configure_Account_SipUsername").val();
    SipPassword = $("#Configure_Account_SipPassword").val();

    var STUNServer = $("#Configure_Account_StunServer").val();
    var AUDIOOutputId = $("#playbackSrc").val();
    var VIDEOSrcId = $("#previewVideoSrc").val();
    var VIDEOHeight = $("input[name=Settings_Quality]:checked").val(); 
    var FRAMERate = parseInt($("input[name=Settings_FrameRate]:checked").val());
    var ASPECTRatio = $("input[name=Settings_AspectRatio]:checked").val();
    var VIDEOOrientation = $("input[name=Settings_Oriteation]:checked").val();
    var RINGtone = $("#ringingTone").val();
    var AUDIOSrcId = $("#microphoneSrc").val();
    var AUTOGainControl = ($("#Settings_AutoGainControl").is(':checked'))? "1" : "0";
    var ECHOCancellation = ($("#Settings_EchoCancellation").is(':checked'))? "1" : "0";
    var NOISESuppression = ($("#Settings_NoiseSuppression").is(':checked'))? "1" : "0";
    var RINGOutputId = $("#ringDevice").val();
    var VideoConfWindowWidth = $("#Video_Conf_Window_Width").val();
    var PROFILEpicture = profilePictureBsf;
    var CHOSENLanguage = $("#languageSelect").val();
    var NOTIFYCheck = ($("#Settings_Notifications").is(":checked"))? 1 : 0;
    var SHOWVCONFUsersCheck = ($("#Settings_ShowVconfNames").is(":checked"))? 1 : 0;
    var DISPLAYVCONFUserCheck = ($("#Settings_DisplayVconfUser").is(":checked"))? 1 : 0;
    var EMAILIntegration = ($("#emailIntegration").is(":checked"))? 1 : 0;
    var RCDomain = $("#RoundcubeDomain").val();
    var RCbasicauthuser = $("#rcBasicAuthUser").val();
    var RCbasicauthpass = $("#rcBasicAuthPass").val();
    var RCuser = $("#RoundcubeUser").val();
    var RCpassword = $("#RoundcubePass").val();
    var VOICENmbrs = $("#voiceNumbers").val();
    var VOICEDefNmbrs = $("#voiceDefaultNumber").val();
    var SMSNmbrs = $("#smsNumbers").val();
    var SMSDefNmbrs = $("#smsDefaultNumber").val();
    var TELSecretkey = $("#telnyxsecretkey").val();
    var TELFaxappid = $("#telnyxfaxappid").val();
    var TELRecwhurl = $("#telnyxrecwhurl").val();
    var PHAXApikey = $("#phaxapikey").val();
    var PHAXApisecret = $("#phaxapisecret").val();
    var PHAXWebhooktoken = $("#phaxwebhooktoken").val();
    var PHAXReceiveurl = $("#phaxreceiveurl").val();
    var FAXNotification = ($("#faxnotification").is(":checked"))? 1 : 0;
    var FAXnotifyemail = $("#faxnotifyemailaddr").val();
    var FAXNmbrs = $("#faxNumbers").val();
    var FAXDefNmbrs = $("#faxDefaultNumber").val();
    var allslctdGroupsLd = "";

    $("#AddContactGroupsLd .slctdGroupTextlg").each(function() { allslctdGroupsLd += "|"+ $(this).text(); });
    if (allslctdGroupsLd != "") { var slctdGroupsLd = allslctdGroupsLd +"|"; } else { var slctdGroupsLd = ""; }

    if (userName != '') {

       if (wssServer != '' && WebSocketPort != '' && ServerPath != '' && SipUsername != '' && SipPassword != '') {

          $.ajax({
             type: "POST",
             url: "src/save-update-settings.php",
             dataType: "JSON",
             data: {
                     username: userName,
                     wss_server: wssServer,
                     web_socket_port: WebSocketPort,
                     server_path: ServerPath,
                     profile_name: profileName,
                     sip_username: SipUsername,
                     sip_password: SipPassword,
                     stun_server: STUNServer,
                     audio_output_id: AUDIOOutputId,
                     video_src_id: VIDEOSrcId,
                     video_height: VIDEOHeight,
                     frame_rate: FRAMERate,
                     aspect_ratio: ASPECTRatio,
                     video_orientation: VIDEOOrientation,
                     ringtone: RINGtone,
                     audio_src_id: AUDIOSrcId,
                     auto_gain_control: AUTOGainControl,
                     echo_cancellation: ECHOCancellation,
                     noise_suppression: NOISESuppression,
                     ring_output_id: RINGOutputId,
                     video_conf_window_width: VideoConfWindowWidth,
                     profile_picture: PROFILEpicture,
                     language: CHOSENLanguage,
                     notifications: NOTIFYCheck,
                     show_vconf_usernames: SHOWVCONFUsersCheck,
                     display_vconf_user: DISPLAYVCONFUserCheck,
                     use_email: EMAILIntegration,
                     rcdomain: RCDomain,
                     rcbasicauthuser: RCbasicauthuser,
                     rcbasicauthpass: RCbasicauthpass,
                     rcuser: RCuser,
                     rcpassword: RCpassword,
		     voicenmb: VOICENmbrs,
		     voicedefnmb: VOICEDefNmbrs,
		     smsnmb: SMSNmbrs,
		     smsdefnmb: SMSDefNmbrs,

		     telsecretkey: TELSecretkey,
		     telfaxappid: TELFaxappid,
		     telrecwhurl: TELRecwhurl,

                     phaxapikey: PHAXApikey,
                     phaxapisecret: PHAXApisecret,
                     phaxwebhooktoken: PHAXWebhooktoken,
                     phaxreceiveurl: PHAXReceiveurl,
                     faxnotification: FAXNotification,
                     faxnotifyemail: FAXnotifyemail,

		     faxnmb: FAXNmbrs,
		     faxdefnmb: FAXDefNmbrs,
                     load_groups: slctdGroupsLd,
                     s_ajax_call: validateSToken
             },
             success: function(response) {
                               if (response.resultupdata == 'success' && response.resultuppicture == 'success') { window.location.reload(); } 
                               if (response.resultupdata != 'success') { alert("There where problems while saving some settings. The individual query results are as follows: " + response.indivresults); window.location.reload(); }
                               if (response.resultuppicture != 'success') { alert("There where issues while updating the profile picture."); window.location.reload(); } 
                      },
             error: function(response) {
                             alert("An error occurred while attempting to save the account configuration data!");
             }
          });

       } else { alert("Fields: 'WebSocket Domain', 'WebSocket Port', 'WebSocket Path', 'SIP Username' and 'SIP Password' are required ! Please fill in all these fields !"); }

    } else { alert("An error occurred while attempting to save the data!"); }
}
// Get account configuration data from the database
function getConfFromSqldb() {

          $.ajax({
             type: "POST",
             url: "src/get-settings.php",
             dataType: "JSON",
             data: {
                     username: userName,
                     s_ajax_call: validateSToken
             },
             success: function(response) {

                        // Get configuration data from the database
                        if (response.datafromdb.wss_server == null || response.datafromdb.web_socket_port == null || response.datafromdb.server_path == null ||
                            response.datafromdb.sip_username == null || response.datafromdb.sip_password == null) {

                            ConfigureExtensionWindow();

                        } else {

                                if (response.datafromdb.stun_server == null || typeof response.datafromdb.stun_server == 'undefined') {
                                    var stunData = null;
                                } else { var stunData = response.datafromdb.stun_server; }

                                profileUserID = uID();
		                localDB.setItem("profileUserID", profileUserID);
				localDB.setItem("userdbid", response.datafromdb.id);
				localDB.setItem("userrole", response.datafromdb.userrole);
                                wssServer = response.datafromdb.wss_server;
				localDB.setItem("wssServer", wssServer);
                                WebSocketPort = response.datafromdb.web_socket_port;
				localDB.setItem("WebSocketPort", WebSocketPort);
                                ServerPath = response.datafromdb.server_path;
				localDB.setItem("ServerPath", ServerPath);
                                profileUser = response.datafromdb.sip_username;
				localDB.setItem("profileUser", profileUser);
                                profileName = response.datafromdb.profile_name;
				localDB.setItem("profileName", profileName);
                                SipUsername = response.datafromdb.sip_username;
				localDB.setItem("SipUsername", SipUsername);
                                SipPassword = response.datafromdb.sip_password;
                                StunServer = stunData;
				localDB.setItem("StunServer", StunServer);

				localDB.setItem("AudioOutputId", response.datafromdb.audio_output_id);
				localDB.setItem("VideoSrcId", response.datafromdb.video_src_id);
                                videoHeight = response.datafromdb.video_height;
				localDB.setItem("VideoHeight", videoHeight);
                                maxFrameRate = response.datafromdb.frame_rate;
				localDB.setItem("FrameRate", maxFrameRate);
                                videoAspectRatio = response.datafromdb.aspect_ratio;
				localDB.setItem("AspectRatio", videoAspectRatio);
                                MirrorVideo = response.datafromdb.video_orientation;
				localDB.setItem("VideoOrientation", MirrorVideo);
				localDB.setItem("Ringtone", response.datafromdb.ringtone);
				localDB.setItem("AudioSrcId", response.datafromdb.audio_src_id);
				localDB.setItem("AutoGainControl", response.datafromdb.auto_gain_control);
				localDB.setItem("EchoCancellation", response.datafromdb.echo_cancellation);
				localDB.setItem("NoiseSuppression", response.datafromdb.noise_suppression);
				localDB.setItem("RingOutputId", response.datafromdb.ring_output_id);
                                localDB.setItem("VidConfWindowWidth", response.datafromdb.video_conf_window_width);
				profilePictureBsf = response.datafromdb.profile_picture;
				localDB.setItem("chosenLanguage", response.datafromdb.language);
                                NotificationsActive = response.datafromdb.notifications;
				localDB.setItem("Notifications", NotificationsActive);
				localDB.setItem("ShowVconfUsernames", response.datafromdb.show_vconf_usernames);
				localDB.setItem("DisplayVconfUser", response.datafromdb.display_vconf_user);
                                localDB.setItem("useRoundcube", response.datafromdb.use_email);
                                localDB.setItem("rcDomain", (response.datafromdb.rcdomain)? response.datafromdb.rcdomain : "");
                                rcBasicAuthUser = response.datafromdb.rcbasicauthuser;
                                rcBasicAuthPass = response.datafromdb.rcbasicauthpass;
                                RoundcubeUser = response.datafromdb.rcuser;
                                RoundcubePass = response.datafromdb.rcpassword;
                                loadGroups = response.datafromdb.load_groups;
                                localDB.setItem("dateAdded", response.datafromdb.date_added);
                                localDB.setItem("dateModified", response.datafromdb.date_modified);
                                phoneNumbersFdb['voice_numbers'] = (response.datafromdb.voice_numbers)? response.datafromdb.voice_numbers : "";
                                phoneNumbersFdb['default_voice_number'] = (response.datafromdb.default_voice_number)? response.datafromdb.default_voice_number : "";
                                phoneNumbersFdb['sms_numbers'] = (response.datafromdb.sms_numbers)? response.datafromdb.sms_numbers : "";
                                phoneNumbersFdb['default_sms_number'] = (response.datafromdb.default_sms_number)? response.datafromdb.default_sms_number : "";
                                phoneNumbersFdb['fax_numbers'] = (response.datafromdb.fax_numbers)? response.datafromdb.fax_numbers : "";
                                phoneNumbersFdb['default_fax_number'] = (response.datafromdb.default_fax_number)? response.datafromdb.default_fax_number : "";
                                faxsettingsFdb['tel_secret_key'] = (response.datafromdb.tel_secret_key)? response.datafromdb.tel_secret_key : "";
                                faxsettingsFdb['tel_fax_app_id'] = (response.datafromdb.tel_fax_app_id)? response.datafromdb.tel_fax_app_id : "";
                                faxsettingsFdb['tel_recwh_url'] = (response.datafromdb.tel_recwh_url)? response.datafromdb.tel_recwh_url : "";
                                faxsettingsFdb['phax_api_key'] = (response.datafromdb.phax_api_key)? response.datafromdb.phax_api_key : "";
                                faxsettingsFdb['phax_api_secret'] = (response.datafromdb.phax_api_secret)? response.datafromdb.phax_api_secret : "";
                                faxsettingsFdb['phax_webhook_token'] = (response.datafromdb.phax_webhook_token)? response.datafromdb.phax_webhook_token : "";
                                faxsettingsFdb['phax_receive_url'] = (response.datafromdb.phax_receive_url)? response.datafromdb.phax_receive_url : "";
                                faxsettingsFdb['get_notification'] = (response.datafromdb.get_notification)? response.datafromdb.get_notification : "";
                                faxsettingsFdb['notification_email'] = (response.datafromdb.notification_email)? response.datafromdb.notification_email : "";


                                var txtcnfarr = response.textconferences;

			        for (var t = 0; t < txtcnfarr.length; t++) {
                                     var crttxtexten = txtcnfarr[t].text_conf_extension;
                                     var crtxttag = txtcnfarr[t].text_conf_label;
                                     var crtxtgroups = txtcnfarr[t].limit_to_groups;

                                     textExtTagObj[crttxtexten] = crtxttag;
                                     textExtGroupsObj[crttxtexten] = crtxtgroups;
                                     winTextConfCheck[crttxtexten] = 0;
                                }

                                var audiocnfarr = response.audioconferences;

			        for (var t = 0; t < audiocnfarr.length; t++) {
                                     var crtaudioexten = audiocnfarr[t].audio_conf_extension;
                                     var craudiotag = audiocnfarr[t].audio_conf_label;
                                     var craudiogroups = audiocnfarr[t].limit_to_groups;

                                     audioExtTagObj[crtaudioexten] = craudiotag;
                                     audioExtGroupsObj[crtaudioexten] = craudiogroups;
                                }

                                var videocnfarr = response.videoconferences;

			        for (var t = 0; t < videocnfarr.length; t++) {
                                     var crtvideoexten = videocnfarr[t].video_conf_extension;
                                     var crvideotag = videocnfarr[t].video_conf_label;
                                     var crvideogroups = videocnfarr[t].limit_to_groups;

                                     videoExtTagObj[crtvideoexten] = crvideotag;
                                     videoExtGroupsObj[crtvideoexten] = crvideogroups;
                                }

                                localDB.setItem('vConfOpenCheck', 'unset');

		                Register();

                                InitUi();

                        }
                      },
             error: function(response) {
                           alert("An error occurred while attempting to retrieve account configuration data from the database!");
             }
          });
}

// Save contact data to the database
function saveContactToSQLDB(newPerson) {

    if (newPerson.length != 0) {
             $.ajax({
                type: "POST",
                url: "src/save-contact.php",
                dataType: "JSON",
                data: {
                     username: userName,
                     contact_name: newPerson[0],
                     contact_desc: newPerson[1],
                     contact_long_desc: newPerson[2],
                     address_street: newPerson[3],
                     address_zip: newPerson[4],
                     address_town: newPerson[5],
                     address_country: newPerson[6],
                     address_state: newPerson[7],
                     extension_number: newPerson[8],
                     contact_mobile: newPerson[9],
                     contact_num1: newPerson[10],
                     contact_num2: newPerson[11],
                     contact_fax: newPerson[12],
                     contact_email: newPerson[13],
                     groups: newPerson[14],
                     date_added: newPerson[15],
                     date_modified: newPerson[16],
                     s_ajax_call: validateSToken
                },
                success: function(response) {
                         if (response.result != 'success') { alert(response.result); }
                },
                error: function(response) {
                         alert("An error occurred while attempting to save the contact to the database!");
                }
             });
    } else { alert("An error occurred while attempting to save the data!"); }
}

// Update contact data in the database
function updateContactToSQLDB(newPerson) {

    if (newPerson.length != 0) {

        if ((newPerson[0] != '' && newPerson[0] != null && typeof newPerson[0] != 'undefined') && (newPerson[16] == null || newPerson[16] == '' || typeof newPerson[16] == 'undefined')) {
             saveContactToSQLDB(newPerson);
        } else if ((newPerson[0] != '' && newPerson[0] != null && typeof newPerson[0] != 'undefined') && (newPerson[16] != null && newPerson[16] != '' && typeof newPerson[16] != 'undefined')) { 

            $.ajax({
                type: "POST",
                url: "src/update-contact.php",
                dataType: "JSON",
                data: {
                     contact_name: newPerson[0],
                     contact_desc: newPerson[1],
                     contact_long_desc: newPerson[2],
                     address_street: newPerson[3],
                     address_zip: newPerson[4],
                     address_town: newPerson[5],
                     address_country: newPerson[6],
                     address_state: newPerson[7],
                     extension_number: newPerson[8],
                     contact_mobile: newPerson[9],
                     contact_num1: newPerson[10],
                     contact_num2: newPerson[11],
                     contact_fax: newPerson[12],
                     contact_email: newPerson[13],
                     groups: newPerson[14],
                     date_modified: newPerson[15],
                     contactDBID: newPerson[16],
                     s_ajax_call: validateSToken
                },
                success: function(response) {
                         if (response.result != 'success') { alert(response.result); }
                },
                error: function(response) {
                         alert("An error occurred while attempting to update the data!");
                }
            });
        } else { alert("An error occurred while trying to save the data!"); }
    } else { alert("An error occurred while attempting to save the data!"); }
}
// Save contact picture data to the database
function saveContactPicToSQLDB(newPic) {

    if (newPic.length != 0) {
        $.ajax({
             type: "POST",
             url: "src/save-update-contact-picture.php",
             dataType: "JSON",
             data: {
                     username: userName,
                     contact_name: newPic[0],
                     profile_picture_c: newPic[1],
                     s_ajax_call: validateSToken
             },
             success: function(response) {
                      },
             error: function(response) {
                          alert("An error occurred while attempting to save contact picture!");
             }
        });
    } else { alert("An error occurred while attempting to save contact picture!"); }
}
// Get contact data from the database
function getContactsAndUsersDb(specGroups) {

          $.ajax({
             type: "POST",
             url: "src/get-contacts-and-users.php",
             dataType: "JSON",
             data: {
                     username: userName,
                     specifygroups: specGroups,
                     s_ajax_call: validateSToken
             },
             success: function(contactsfromdb) {

                        // Get contacts from the database
                        $.each(contactsfromdb.contactsinfo, function(ind, contactrow) {
			       var id = uID();
			       var dateNow = utcDateNow();

                               if (contactsfromdb.contactsinfo[ind].extension_number == '' || contactsfromdb.contactsinfo[ind].extension_number == null) {
 
                                  contactsObj.DataCollection.push(
					{
					    Type: "contact",
					    LastActivity: dateNow,
					    ExtensionNumber: "",
					    MobileNumber: contactsfromdb.contactsinfo[ind].contact_mobile,
					    ContactNumber1: contactsfromdb.contactsinfo[ind].contact_num1,
					    ContactNumber2: contactsfromdb.contactsinfo[ind].contact_num2,
					    uID: null,
					    cID: id,
					    DisplayName: contactsfromdb.contactsinfo[ind].contact_name,
					    Position: "",
					    Description: contactsfromdb.contactsinfo[ind].contact_desc,
					    LongDescription: contactsfromdb.contactsinfo[ind].contact_long_desc,
                                            AddressStreet: contactsfromdb.contactsinfo[ind].address_street,
                                            AddressZip: contactsfromdb.contactsinfo[ind].address_zip,
                                            AddressTown: contactsfromdb.contactsinfo[ind].address_town,
                                            AddressCountry: contactsfromdb.contactsinfo[ind].address_country,
                                            AddressState: contactsfromdb.contactsinfo[ind].address_state,
                                            Fax: contactsfromdb.contactsinfo[ind].contact_fax,
					    Email: contactsfromdb.contactsinfo[ind].contact_email,
                                            Groups: contactsfromdb.contactsinfo[ind].groups,
                                            DateAdded: contactsfromdb.contactsinfo[ind].date_added,
                                            DateModified: contactsfromdb.contactsinfo[ind].date_modified,
					    MemberCount: 0
					}
				    );

                                    if (contactsfromdb.contactsinfo[ind].profile_picture_c != '' && contactsfromdb.contactsinfo[ind].profile_picture_c != null) {
                                        imgagesObj["img-"+ id +"-contact"] = contactsfromdb.contactsinfo[ind].profile_picture_c;
                                    }

			            var buddyObj = new Buddy("contact", id, contactsfromdb.contactsinfo[ind].contact_name, "", contactsfromdb.contactsinfo[ind].contact_mobile, contactsfromdb.contactsinfo[ind].contact_num1, contactsfromdb.contactsinfo[ind].contact_num2, dateNow, contactsfromdb.contactsinfo[ind].contact_desc, contactsfromdb.contactsinfo[ind].contact_fax, contactsfromdb.contactsinfo[ind].contact_email, contactsfromdb.contactsinfo[ind].groups);
			            AddBuddy(buddyObj, true, false, false);

                               } else {

                                  contactsObj.DataCollection.push(
					{
					    Type: "extension",
					    LastActivity: dateNow,
					    ExtensionNumber: contactsfromdb.contactsinfo[ind].extension_number,
					    MobileNumber: contactsfromdb.contactsinfo[ind].contact_mobile,
					    ContactNumber1: contactsfromdb.contactsinfo[ind].contact_num1,
					    ContactNumber2: contactsfromdb.contactsinfo[ind].contact_num2,
					    uID: id,
					    cID: null,
					    DisplayName: contactsfromdb.contactsinfo[ind].contact_name,
					    Position: contactsfromdb.contactsinfo[ind].contact_desc,
					    Description: "",
					    LongDescription: contactsfromdb.contactsinfo[ind].contact_long_desc,
                                            AddressStreet: contactsfromdb.contactsinfo[ind].address_street,
                                            AddressZip: contactsfromdb.contactsinfo[ind].address_zip,
                                            AddressTown: contactsfromdb.contactsinfo[ind].address_town,
                                            AddressCountry: contactsfromdb.contactsinfo[ind].address_country,
                                            AddressState: contactsfromdb.contactsinfo[ind].address_state,
                                            Fax: contactsfromdb.contactsinfo[ind].contact_fax,
					    Email: contactsfromdb.contactsinfo[ind].contact_email,
                                            Groups: contactsfromdb.contactsinfo[ind].groups,
                                            DateAdded: contactsfromdb.contactsinfo[ind].date_added,
                                            DateModified: contactsfromdb.contactsinfo[ind].date_modified,
					    MemberCount: 0
					}
				    );

                                    if (contactsfromdb.contactsinfo[ind].profile_picture_c != '' && contactsfromdb.contactsinfo[ind].profile_picture_c != null) {
                                        imgagesObj["img-"+ id +"-extension"] = contactsfromdb.contactsinfo[ind].profile_picture_c;
                                    }

			            var buddyObj = new Buddy("extension", id, contactsfromdb.contactsinfo[ind].contact_name, contactsfromdb.contactsinfo[ind].extension_number, contactsfromdb.contactsinfo[ind].contact_mobile, contactsfromdb.contactsinfo[ind].contact_num1, contactsfromdb.contactsinfo[ind].contact_num2, dateNow, contactsfromdb.contactsinfo[ind].contact_desc, contactsfromdb.contactsinfo[ind].contact_fax, contactsfromdb.contactsinfo[ind].contact_email, contactsfromdb.contactsinfo[ind].groups);
			            AddBuddy(buddyObj, true, false, true);
                               } 
                        });

                        contactsObj.TotalRows = contactsObj.DataCollection.length;

                        allGroups = [];
                        var allGroupsArr = contactsfromdb.groups;
                        for (var r = 0; r < allGroupsArr.length; r++) {
                             Object.keys(allGroupsArr[r]).forEach(function(key, index) {
                                    allGroups.push(key);
                                    allGroupsAndIds[key] = allGroupsArr[r][key];
                             });
                        }

                        allGroups = allGroups.sort();

                        roundpinUsers = contactsfromdb.users;
                        bannedUsers = contactsfromdb.bannedusers;

		        // Add the 'select groups to load' drop-down list
		        var selGroupToLoad = "<select id='selectGroupToLoad'>";
		        selGroupToLoad += "<option value=''>Load only contacts in this group:</option>";
		           for (var grp = 0; grp < allGroups.length; grp++) {
			        selGroupToLoad += "<option value='"+ allGroups[grp] +"'>"+ allGroups[grp] +"</option>"; 
		           }
                        selGroupToLoad += "<option value='withoutgroups'>--- Contacts without groups ---</option>";
                        selGroupToLoad += "<option value='allcntcts'>--- All contacts ---</option>";
		        selGroupToLoad += "</select>";

		        $("#slctdGroupsToLoad").append(selGroupToLoad);

                        if ($("#selectGroupToLoad").is(":visible")) { $("#selectGroupToLoad").val('allcntcts'); }

		        $("#selectGroupToLoad").change(function() {
		           var selGroupToLd = $("#selectGroupToLoad").val();
		           if (selGroupToLd != '' && selGroupToLd != 'allcntcts' && selGroupToLd != 'withoutgroups') {
			       Buddies = [];
			       getContactsAndUsersByGroup(selGroupToLd);
                           } else if (selGroupToLd == 'allcntcts' || selGroupToLd == '') {
                               $("#selectGroupToLoad").remove();
                               Buddies = [];
                               getContactsAndUsersDb("all");
                           } else if (selGroupToLd == 'withoutgroups') {
                               Buddies = [];
                               getContactsAndUsersByGroup("nogroups");
                           }
		        });

             },
             error: function(contactsfromdb) {
                           alert("An error occurred while attempting to retrieve contacts data from the database!");
             }
          });
}

// Get the contacts from a specific group from the database
function getContactsAndUsersByGroup(getGroup) {

          $.ajax({
             type: "POST",
             url: "src/get-contacts-and-users-by-group.php",
             dataType: "JSON",
             data: {
                     username: userName,
                     selectedgroup: getGroup,
                     s_ajax_call: validateSToken
             },
             success: function(contactsfromdb) {

                        // Get contacts from the database
                        if (contactsfromdb.contactsinfo != '') {

                            $.each(contactsfromdb.contactsinfo, function(ind, contactrow) {

			       var id = uID();
			       var dateNow = utcDateNow();

                               if (contactsfromdb.contactsinfo[ind].extension_number == '' || contactsfromdb.contactsinfo[ind].extension_number == null) {

                                   contactsObj.DataCollection.push(
					{
					    Type: "contact",
					    LastActivity: dateNow,
					    ExtensionNumber: "",
					    MobileNumber: contactsfromdb.contactsinfo[ind].contact_mobile,
					    ContactNumber1: contactsfromdb.contactsinfo[ind].contact_num1,
					    ContactNumber2: contactsfromdb.contactsinfo[ind].contact_num2,
					    uID: null,
					    cID: id,
					    DisplayName: contactsfromdb.contactsinfo[ind].contact_name,
					    Position: "",
					    Description: contactsfromdb.contactsinfo[ind].contact_desc,
					    LongDescription: contactsfromdb.contactsinfo[ind].contact_long_desc,
                                            AddressStreet: contactsfromdb.contactsinfo[ind].address_street,
                                            AddressZip: contactsfromdb.contactsinfo[ind].address_zip,
                                            AddressTown: contactsfromdb.contactsinfo[ind].address_town,
                                            AddressCountry: contactsfromdb.contactsinfo[ind].address_country,
                                            AddressState: contactsfromdb.contactsinfo[ind].address_state,
                                            Fax: contactsfromdb.contactsinfo[ind].contact_fax,
					    Email: contactsfromdb.contactsinfo[ind].contact_email,
                                            Groups: contactsfromdb.contactsinfo[ind].groups,
                                            DateAdded: contactsfromdb.contactsinfo[ind].date_added,
                                            DateModified: contactsfromdb.contactsinfo[ind].date_modified,
					    MemberCount: 0
					}
				   );

                                   if (contactsfromdb.contactsinfo[ind].profile_picture_c != '' && contactsfromdb.contactsinfo[ind].profile_picture_c != null) {
                                       imgagesObj["img-"+ id +"-contact"] = contactsfromdb.contactsinfo[ind].profile_picture_c;
                                   }

			           var buddyObj = new Buddy("contact", id, contactsfromdb.contactsinfo[ind].contact_name, "", contactsfromdb.contactsinfo[ind].contact_mobile, contactsfromdb.contactsinfo[ind].contact_num1, contactsfromdb.contactsinfo[ind].contact_num2, dateNow, contactsfromdb.contactsinfo[ind].contact_desc, contactsfromdb.contactsinfo[ind].contact_fax, contactsfromdb.contactsinfo[ind].contact_email, contactsfromdb.contactsinfo[ind].groups);
			           AddBuddy(buddyObj, true, false, false);

                               } else {

                                   contactsObj.DataCollection.push(
					{
					    Type: "extension",
					    LastActivity: dateNow,
					    ExtensionNumber: contactsfromdb.contactsinfo[ind].extension_number,
					    MobileNumber: contactsfromdb.contactsinfo[ind].contact_mobile,
					    ContactNumber1: contactsfromdb.contactsinfo[ind].contact_num1,
					    ContactNumber2: contactsfromdb.contactsinfo[ind].contact_num2,
					    uID: id,
					    cID: null,
					    DisplayName: contactsfromdb.contactsinfo[ind].contact_name,
					    Position: contactsfromdb.contactsinfo[ind].contact_desc,
					    Description: "",
					    LongDescription: contactsfromdb.contactsinfo[ind].contact_long_desc,
                                            AddressStreet: contactsfromdb.contactsinfo[ind].address_street,
                                            AddressZip: contactsfromdb.contactsinfo[ind].address_zip,
                                            AddressTown: contactsfromdb.contactsinfo[ind].address_town,
                                            AddressCountry: contactsfromdb.contactsinfo[ind].address_country,
                                            AddressState: contactsfromdb.contactsinfo[ind].address_state,
                                            Fax: contactsfromdb.contactsinfo[ind].contact_fax,
					    Email: contactsfromdb.contactsinfo[ind].contact_email,
                                            Groups: contactsfromdb.contactsinfo[ind].groups,
                                            DateAdded: contactsfromdb.contactsinfo[ind].date_added,
                                            DateModified: contactsfromdb.contactsinfo[ind].date_modified,
					    MemberCount: 0
					}
				   );

                                   if (contactsfromdb.contactsinfo[ind].profile_picture_c != '' && contactsfromdb.contactsinfo[ind].profile_picture_c != null) {
                                       imgagesObj["img-"+ id +"-extension"] = contactsfromdb.contactsinfo[ind].profile_picture_c;
                                   }

			           var buddyObj = new Buddy("extension", id, contactsfromdb.contactsinfo[ind].contact_name, contactsfromdb.contactsinfo[ind].extension_number, contactsfromdb.contactsinfo[ind].contact_mobile, contactsfromdb.contactsinfo[ind].contact_num1, contactsfromdb.contactsinfo[ind].contact_num2, dateNow, contactsfromdb.contactsinfo[ind].contact_desc, contactsfromdb.contactsinfo[ind].contact_fax, contactsfromdb.contactsinfo[ind].contact_email, contactsfromdb.contactsinfo[ind].groups);
			           AddBuddy(buddyObj, true, false, true);
                               } 
                            });

                            contactsObj.TotalRows = contactsObj.DataCollection.length;

                        } else {

                            contactsObj.DataCollection = [];
                            contactsObj.TotalRows = 0;
                            PopulateBuddyList();

                        }
             },
             error: function(contactsfromdb) {
                             alert("An error occurred while attempting to retrieve contacts data from the database!");
             }
          });
}

// Get external users data from the database
function getExternalUserConfDB() {

         $.ajax({
             type: "POST",
             url: "src/get-external-users-conf.php",
             dataType: "JSON",
             data: {
                     username: userName, 
                     s_ajax_call: validateSToken
             },
             success: function(extdatafromdb) {

                        var extdatafromdbstr = JSON.stringify(extdatafromdb);

                        if (extdatafromdbstr.length > 0) {

                            localDB.setItem("externalUserConfElem", extdatafromdb.length);

                            for (var t = 0; t < extdatafromdb.length; t++) {

				 extUserExtension.push(extdatafromdb[t]['exten_for_external']);
				 extUserExtensionPass.push(extdatafromdb[t]['exten_for_ext_pass']);
				 vidConferenceExtension.push(extdatafromdb[t]['conf_extension']);
				 vidConferenceTag.push(extdatafromdb[t]['conf_tag']);
				 confAccessLink.push(extdatafromdb[t]['conf_access_link']);
                            }
                        }
                      },
             error: function(extdatafromdb) {
                           alert("An error occurred while attempting to retrieve external users configuration data from the database!");
             }
         });
}

// Check if there are any links for external access associated with the current username and propose to remove them
function checkExternalLinks() {

    $.jeegoopopup.close();

    var delExtConfLinks = "<div class=NoSelect>";
    delExtConfLinks += "<div id=windowCtrls><img id=closeImg src=images/3_close.svg title=Close /></div>";
    delExtConfLinks += "<div class='UiText' id='ConfirmMessageText'>"+ lang.confirm_del_external_links +"</div>";
    delExtConfLinks += "</div>";

    $.jeegoopopup.open({
                html: delExtConfLinks,
                width: '378',
                height: '250',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                contentClass: 'ConfDelExtConfData',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $("#jg_popup_b").append("<div id=ConfDelExtCnfInfo><button id=ConfirmDelEcdButton>Remove links and exit</button><button id=ConfirmExitButton>Keep links and exit</button></div>");

    $("#ConfirmDelEcdButton").click(function () {

             $.ajax({
                 type: "POST",
                 url: "src/remove-links-for-external-access.php",
                 dataType: "JSON",
                 data: {
                        username: userName,
                        s_ajax_call: validateSToken
                       },
                 success: function(response) {
                            alert("All the links for external access to conferences associated with your username have been successfully removed from the database!");

		            Unregister();
			    console.log("Signing Out ...");
			    localStorage.clear();
			    if (winVideoConf != null) {
			        winVideoConf.close();
			    }
			    window.open('https://' + window.location.host + '/src/logout.php', '_self');
                 },
                 error: function(response) {
                            alert("An error occurred while trying to remove the data from the database!");

		            Unregister();
			    console.log("Signing Out ...");
			    localStorage.clear();
			    if (winVideoConf != null) {
			        winVideoConf.close();
			    }
			    window.open('https://' + window.location.host + '/src/logout.php', '_self');
                 }
             });

    });

    $("#ConfirmExitButton").click(function () {

        $("#windowCtrls").empty();
        $("#jg_popup_b").empty();
        $.jeegoopopup.close();

                 Unregister();
		 console.log("Signing Out ...");
		 localStorage.clear();
		 if (winVideoConf != null) {
		     winVideoConf.close();
		 }
		 window.open('https://' + window.location.host + '/src/logout.php', '_self');
    });

    $(window).resize(function() {
       $.jeegoopopup.width('378').height('250');
       $.jeegoopopup.center();
    });

    $("#closeImg").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); $("#windowCtrls").empty(); });
    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); $("#windowCtrls").empty(); $("#jg_popup_b").empty(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); $("#jg_popup_b").empty(); } });
}

// Remove contact from the database
function deleteBuddyFromSqldb(buddyDisplayName) {

      $.ajax({
             type: "POST",
             url: "src/remove-contact.php",
             dataType: "JSON",
             data: {
                     username: userName,
                     contact_name: buddyDisplayName,
                     s_ajax_call: validateSToken
             },
             success: function(delresult) {
             },
             error: function(delresult) {
                        alert("An error occurred while attempting to remove the contact from the database!");
             }
      });
}

// Save new Roundpin user password
function saveNewUserPassword() {

      var currentPassword = $("#Current_User_Password").val();
      var newPassword = $("#New_User_Password").val();
      var repeatPassword = $("#Repeat_New_User_Password").val();

      if (currentPassword == '' || newPassword == '' || repeatPassword == '') { alert("Please fill in all the fields!"); return; }

      // Verify if the new password meets constraints
      if (/^((?=.*\d)(?=.*[a-z])(?=.*\W).{10,})$/.test(newPassword)) {

          if (repeatPassword == newPassword) {
		  $.ajax({
		     type: "POST",
		     url: "src/save-new-user-password.php",
		     dataType: "JSON",
		     data: {
		             username: userName,
		             current_password: currentPassword,
                             new_password: newPassword,
		             s_ajax_call: validateSToken
		     },
		     success: function(passchangemessage) {
                                       alert(passchangemessage);
		     },
		     error: function(passchangemessage) {
		                alert("An error occurred while attempting to change the user password!");
		     }
		  });
          } else { alert("The passwords entered in the new password fields don't match!"); }

      } else {
          alert("The new password does not meet the requirements (to be at least 10 characters long, to contain at least one letter, at least one digit and at least one special character). Please choose a different password ! ");
        }
}

// Save new Roundpin user email
function saveNewUserEmail() {

      var currentEmail = $("#Current_User_Email").val();
      var newEmail = $("#New_User_Email").val();
      var repeatEmail = $("#Repeat_New_User_Email").val();

      if (currentEmail == '' || newEmail == '' || repeatEmail == '') { alert("Please fill in all the fields!"); return; }

      // Verify if the new email is a valid email address
      if (/^[A-Za-z0-9\_\.\-\~\%\+\!\?\&\*\^\=\#\$\{\}\|\/]+@[A-Za-z0-9\.\-]+\.[A-Za-z0-9\-]{2,63}$/.test(newEmail)) {

          if (repeatEmail == newEmail) {
		  $.ajax({
		     type: "POST",
		     url: "src/save-new-user-email.php",
		     dataType: "JSON",
		     data: {
		             username: userName,
		             current_email: currentEmail,
                             new_email: newEmail,
		             s_ajax_call: validateSToken
		     },
		     success: function(emailchangemessage) {
                                       alert(emailchangemessage);
		     },
		     error: function(emailchangemessage) {
		                alert("An error occurred while attempting to change the user email address!");
		     }
		  });
          } else { alert("The email addresses entered in the new email fields don't match!"); }

      } else {
            alert("The new email address is not a valid email address. Please enter a valid email address!");
        }
}

// Close Roundpin user account
function closeUserAccount() {

      closeVideoAudio();

      ConfirmDelAccount(lang.confirm_close_account, lang.close_roundpin_user_account, function() {

		  $.ajax({
		     type: "POST",
		     url: "src/close-user-account.php",
		     dataType: "JSON",
		     data: {
		             username: userName,
		             s_ajax_call: validateSToken
		     },
		     success: function(closeaccountmessage) {
                                       alert(closeaccountmessage);
                                       SignOut();
		     },
		     error: function(closeaccountmessage) {
		                alert("An error occurred while attempting to close your user account!");
		     }
		  });
      });
}

// Launch text conference
function LaunchTextConference(crTextConfExt, crTextConfTag) {

      $.jeegoopopup.close()

      if (winTextConfCheck[crTextConfExt] == 0) {

                ++textConfNmb;

                // Start a new conference session
                var tcOptions = { sessionDescriptionHandlerOptions: { constraints: { audio: true, video: false } } };
                var newTCSession = userAgent.invite("sip:"+ crTextConfExt +"@"+ wssServer, tcOptions);
                tcSessions[crTextConfExt] = newTCSession;

		var chathtml = "<div id='publicTextMessages-"+ crTextConfExt +"' class='publicTextCnfMessage'>";
		chathtml += "<div id='windowCtrlsTC-"+ crTextConfExt +"' class='windowCtrlsTC'><img id='closeImg-"+ crTextConfExt +"' class='closeImgTextCnf' src='images/3_close.svg' title='Close'><img id='maximizeImg-"+ crTextConfExt +"' class='maximizeImgTextCnf' src='images/2_maximize.svg' title='Maximize'><img id='restoreImg-"+ crTextConfExt +"' class='restoreImgTextCnf' src='images/4_restore.svg' title='Restore'><img id='minimizeImg-"+ crTextConfExt +"' class='minimizeImgTextCnf' src='images/1_minimize.svg' title='Minimize'><div id='publicTCMsgLabel-"+ crTextConfExt +"' class='textConfMinLabel' title='"+ lang.text_conference_room +"'>"+ lang.room +": <span style='color:#379b77;'>"+ crTextConfTag + "</span> ("+ crTextConfExt +") </div></div>";
		chathtml += "<div id='dragPubMessageWindow-"+ crTextConfExt +"' class='dragPubMessage'><i class='fa fa-ellipsis-h' title='Drag Button' style='display:block;position:relative;width:40px;margin:0px auto;color:#d0d0d0;font-size:17px;cursor:grab;'></i></div>";
		chathtml += "<div id='pubMTopBar-"+ crTextConfExt +"' style='display:block;margin:16px auto 10px auto;text-align: center;'><span style='display:block;font-size:16px;font-weight:600;color:#224573;user-select: none;'>"+ lang.text_conference_room +": <span style='color:#379b77;'>"+ crTextConfTag +"</span> ("+ crTextConfExt +")</span></div>";
                chathtml += "<div class='txtCnfTblAll'><div id='txtCnfTableUaT-"+ crTextConfExt +"' class='txtCnfTableUaT'><table cellspacing='0' cellpadding='0' id='tcTableUsersText-"+ crTextConfExt +"' class='tcTableUsersText'><tr><td><div id='tConfUsers-"+ crTextConfExt +"' class='tConfUsersList'></div></td><td><div id='receiveTextMessage-"+ crTextConfExt +"' class='receiveTextMessage'><div id='downloadTextConfHst-"+ crTextConfExt +"' class='downTextConfHst' title='Download Text Conversation'><i class='fa fa-download'></i></div></div></td></tr></table></div>";  
		chathtml += "<div id='emojiMenu-"+ crTextConfExt +"' class='EmojiMenuTC' style='display:none'></div>";
		chathtml += "<div id='sendTMessage-"+ crTextConfExt +"' class='sendMsgTConf'><table cellspacing='0' cellpadding='0' id='sendMessageTbl-"+ crTextConfExt +"' class='sendMessageTbl'><tr><td style='height: 100%;width: 100%;'><textarea id='sendTextMessage-"+ crTextConfExt +"' class='chatMessageTC' placeholder='"+ lang.type_your_message_here +"'></textarea></td>";
		chathtml += "<td class='textCnfBoxBtns'><button id='sendTextChatBtn-"+ crTextConfExt +"' class='roundButtonsSpecTC' title='Send Message'><i class='fa fa-paper-plane-o'></i></button>";
		chathtml += "<button id='selectEmoticon-"+ crTextConfExt +"' class='roundButtonsSpecTC' title='Select Emoticon'><i class='fa fa-smile-o'></i></button>";
		chathtml += "<button id='sendFileTC-"+ crTextConfExt +"' class='roundButtonsSpecTC' title='Send File'><i class='fa fa-file-text-o'></i></button></td></tr></table></div></div>";
		chathtml += "</div>";

		$("#Phone").append(chathtml);
		$("#publicTextMessages-"+ crTextConfExt +"").css("display", "block");
		winTextConfCheck[crTextConfExt] = 1;

		$("#restoreImg-"+ crTextConfExt +"").hide();
		$("#sendTextMessage-"+ crTextConfExt +"").focus();
		messageWindowStateTC[crTextConfExt] = 'restored';

		// Bring window forward when clicking on it
		$("#publicTextMessages-"+ crTextConfExt +"").click(function(e) {
                   if (!$(e.target).hasClass('tcPartListItem') && !$(e.target).hasClass('recMessageNm')) {
                       $(".textMessagesTCPM").each(function() { $(this).css("z-index", 19); });
                   }
                   $(".publicTextCnfMessage").each(function() { $(this).css("z-index", 19); });
                   $(this).css("z-index", 21);
		});

		// Make the window draggable
		function makePubWndDraggable(e) {

		       window.pmdragging = {};
		       pmdragging.pageX0 = e.pageX;
		       pmdragging.pageY0 = e.pageY;
		       pmdragging.elem = $("#publicTextMessages-"+ crTextConfExt +"");
		       pmdragging.offset0 = $("#publicTextMessages-"+ crTextConfExt +"").offset();

		       function handle_dragging(e) {
			   var left = pmdragging.offset0.left + (e.pageX - pmdragging.pageX0);
			   var top = pmdragging.offset0.top + (e.pageY - pmdragging.pageY0);
			   $(pmdragging.elem).offset({top: top, left: left});
		       }

		       function handle_mouseup(e) {
			   $('body').off('mousemove', handle_dragging).off('mouseup', handle_mouseup);
		       }

		       $('body').on('mouseup', handle_mouseup).on('mousemove', handle_dragging);
		}

		$("#dragPubMessageWindow-"+ crTextConfExt +"").mousedown(makePubWndDraggable);

		// Open private message window when clicking on username in the list of conference participants
		$("#tConfUsers-"+ crTextConfExt +"").on("click", ".tcPartListItem", function() {

		   var tcMessageDesExt = $(this).attr("name");
		   var tcMessageDesDName = $(this).attr("tag");
		   if (messageWindowStateTCPM[crTextConfExt +"-"+ tcMessageDesExt] == 'minimized' || messageWindowStateTCPM[crTextConfExt +"-"+ tcMessageDesExt] == 'restored') {
		       alert("The private messaging window for this conference participant is already open!");
		   } else {
                       openTCPrivateMsgWindow(tcMessageDesDName, tcMessageDesExt, crTextConfExt, crTextConfTag);
		   }
		});

		// Open private message window when clicking on username in received message
		$("#receiveTextMessage-"+ crTextConfExt +"").on("click", ".recMessageNm", function() {

		   var msgTCDestText = $(this).text();
		   var crntExtenfirsttc = msgTCDestText.split(" (");
                   var messageDestTc = crntExtenfirsttc[0];
		   var crntTgtExtenTc = crntExtenfirsttc[1].replace("):", "");
		   if (messageWindowStateTCPM[crTextConfExt +"-"+ crntTgtExtenTc] == 'minimized' || messageWindowStateTCPM[crTextConfExt +"-"+ crntTgtExtenTc] == 'restored') {
		       alert("The private messaging window for this conference participant is already open!");
		   } else {
                       openTCPrivateMsgWindow(messageDestTc, crntTgtExtenTc, crTextConfExt, crTextConfTag);
		   }
		});

		// Select emoticon
		$("#selectEmoticon-"+ crTextConfExt +"").click(function() {

		    var messageContainer = $("#emojiMenu-"+ crTextConfExt +"");
		    var textarea = $("#sendTextMessage-"+ crTextConfExt +"");

		    if (messageContainer.is(":visible")) { messageContainer.hide(); } else { messageContainer.show(); }

		    var menuBar = $("<div>");
		    menuBar.prop("class", "emojiButton");
		    var emojis = ["😀","😁","😂","😃","😄","😅","😆","😊","😦","😉","😊","😋","😌","😍","😎","😏","😐","😑","😒","😓","😔","😕","😖","😗","😘","😙","😚","😛","😜","😝","😞","😟","😠","😡","😢","😣","😤","😥","😦","😧","😨","😩","😪","😫","😬","😭","😮","😯","😰","😱","😲","😳","😴","😵","😶","😷","🙁","🙂","🙃","🙄","🤐","🤑","🤒","🤓","🤔","🤕","🤠","🤡","🤢","🤣","🤤","🤥","🤧","🤨","🤩","🤪","🤫","🤬","🤭","🤯","🤗","🧐"];
		    $.each(emojis, function(i,e){
			       var emoji = $("<button>");
			       emoji.html(e);
			       emoji.on('click', function() {
				    var i = textarea.prop('selectionStart');
				    var v = textarea.val();
				    textarea.val(v.substring(0, i) + " " + $(this).html() + v.substring(i, v.length) + " ");

				    messageContainer.hide();
				    textarea.focus();
			       });
			       menuBar.append(emoji);
		    });

		    $("#sendTextMessage-"+ crTextConfExt +",#receiveTextMessage-"+ crTextConfExt +",#tConfUsers-"+ crTextConfExt +"").on('click', function(){
		       messageContainer.hide();
		    });

		    messageContainer.empty();
		    messageContainer.append(menuBar);
		});

                // Show the 'Download Text Conversation' button
                $("#receiveTextMessage-"+ crTextConfExt +"").mouseenter(function() {
                   $("#downloadTextConfHst-"+ crTextConfExt +"").css("display", "block");
                });
                $("#receiveTextMessage-"+ crTextConfExt +"").mouseleave(function() {
                   $("#downloadTextConfHst-"+ crTextConfExt +"").css("display", "none");
                });

                // Download chat history
                $("#downloadTextConfHst-"+ crTextConfExt +"").click(function() { DownloadHistoryTC(crTextConfExt, crTextConfTag); });

		// Generate a RSA key pair for the text conference extension if it hasn't been already generated by a participant who entered the conference earlier
		var crypto = new JSEncrypt({default_key_size: 1024});
		crypto.getKey();
		var crTConfChatPubKey = crypto.getPublicKey();
		var crTConfChatPrivKey = crypto.getPrivateKey();

		$.ajax({
		     type: "POST",
		     url: "src/save-text-conf-keys.php",
		     dataType: "JSON",
		     data: {
			     currenttconfext: crTextConfExt,
			     currenttconfpubkey: crTConfChatPubKey,
		             crnttconfprivkey: crTConfChatPrivKey,
			     s_ajax_call: validateSToken
		     },
		     success: function() {
		     },
		     error: function() {
				alert("An error occurred while trying to save the text conference public key!");
		     }
		});

		// Send message to all the text conference participants
		function SendPubChatMessage() {

		        var textToSend = $("#sendTextMessage-"+ crTextConfExt +"").val().trim();
                        var textToSendFormatted = ReformatMessage(textToSend);
		        var targetUser = '';

			// When a file is sent
			if (sendFileCheckTC == 1) {

                           $.when($("#publicTextMessages-"+ crTextConfExt +"").append("<span id='sendFileLoaderTC-"+ crTextConfExt +"' class='sendFileLoaderTC'></span>")).then(function() {

		             sendFileChatErrTC = '';

		             $("#sendFileFormChatTC-"+ crTextConfExt +"").on('submit', function(ev) {

			           ev.preventDefault();

				   $.ajax({
					type: 'POST',
					url: 'src/text-chat-textconf-upload-file.php',
					data: new FormData(this),
					dataType: "JSON",
					contentType: false,
					cache: false,
					processData:false,
					success: function(respdata) { 

						    if (respdata.error != '') {

		                                        sendFileChatErrTC = respdata.error;
							$("#sendFileFormChatTC-"+ crTextConfExt +"").remove();
							$("#sendFileLoaderTC-"+ crTextConfExt +"").remove();

							alert("Error: " + respdata.error);

						    } else {

						        // Encrypt the message
						        var genCnfPassphr = Date.now() + Math.floor(Math.random()*10000).toString(6).toUpperCase();
						        var confAESKey = String(generateAESKey(genCnfPassphr));
						        var encryptedConfMes = encryptAES(textToSend, confAESKey);

						        // Split the encrypted message into smaller pieces to fit the 1024 byte limit for SIP messages, which is hard-coded in Asterisk
						        var messagepretcnfarr = splitString(encryptedConfMes, 850);

						        var genMessageId = Date.now() + Math.floor(Math.random()*10000).toString(16).toUpperCase();
						        var messageIdent = genMessageId + SipUsername;

						        // Get the public key of the current text conference extension
						        $.ajax({
						          type: "POST",
						          url: "src/get-text-conf-pub-key.php",
						          dataType: "JSON",
						          data: {
							         crntconfext: crTextConfExt,
							         s_ajax_call: validateSToken
						          },
						          success: function(result) {

								if (result.resmessage == 'success') {

								    var destExtChatRSAPubKey = "`" + result.chatkey.join("") + "`";

								    // Encrypt the AES key with RSA
								    var newtcnfencrypt = new JSEncrypt();
								    newtcnfencrypt.setPublicKey(destExtChatRSAPubKey);
								    var enctcnfmIdAesKey = newtcnfencrypt.encrypt(confAESKey);

								    // Add the encrypted AES key as the first element of the array of message pieces
								    messagepretcnfarr.unshift(enctcnfmIdAesKey);

								    // Send the message pieces
								    for (var k = 0; k < messagepretcnfarr.length; k++) {

									 if (k == 0) {
									     // Send the RSA-encrypted AES key as the first piece of the message
									     var messagePiece = messageIdent + "|" + messagepretcnfarr.length + "|" + parseInt(k) + "||" + messagepretcnfarr[k];	
									 } else if (sendFileCheckTC == 1 && sendFileChatErrTC == '') {
									     var messagePiece = messageIdent + "|" + messagepretcnfarr.length + "|" + parseInt(k) + "|" + upFileNameTC + "|" + messagepretcnfarr[k];
									 } else if (sendFileCheckTC == 1 && sendFileChatErrTC != '') {
									     $("#sendFileFormChatTC").remove();
									     sendFileChatErrTC = '';
									     sendFileCheckTC = 0;
									     return;
									 }

									 var msg = {
									   'From': profileName +'|'+ SipUsername,
									   'To': crTextConfExt,
				                                           'Body': messagePiece
									 };

									 var msgBody = JSON.stringify(msg);
									 var extraHeaders = [ 'Content-Type: application/x-asterisk-confbridge-chat-main+json' ];

						                         // Send the text message
				                                         try {

				                                            let TCSess = tcSessions[crTextConfExt];
				                                            TCSess.sendRequest(SIP.C.MESSAGE, {
									       extraHeaders,
									       body: msgBody
									    });

				                                         } catch (error) {

				                                                 if (String(error).indexOf("this.dialog is undefined") != -1) {
				                                                     alert("Your connection to this text conference room has timed out. To continue, close and reopen the text conference window.");
                                                                                     return;
				                                                 } else {
				                                                     alert("An error occurred while sending the message. Your connection to this text conference may have problems.");
                                                                                     return;
				                                                 }
				                                         }
                                                                    }

								    $("#sendTextMessage-"+ crTextConfExt +"").val("");
								    var currentTime = new Date().toLocaleTimeString();

								    // Append the sent message
								    if (textToSend != '') {
								        $("#receiveTextMessage-"+ crTextConfExt +"").append("<div class='textMessageSent textMessageTConf'><span class='sentMessageNm'>"+ profileName +" ("+ SipUsername +"):</span><div class='smTConfcont'>"+ textToSendFormatted +"</div><div class='messageDate'>"+ currentTime +"<i class='fa fa-check' style='display:inline-block;margin-left:8px;color:#03cba0' title='No errors on message sending'></i></div></div>"); 
								    }

			                                            // Append the 'Download file' section
								    let currentflTime = new Date().toLocaleTimeString();
								    $("#receiveTextMessage-"+ crTextConfExt +"").append("<div class='textMessageSent textMessageTConf'><span class='sentMessageNm'>"+ profileName +" ("+ SipUsername +"):</span><div class='smTConfcont'> Download file: <a href='src/download-sent-chat-file.php?s_ajax_call="+ validateSToken +"&destSipUser="+ crTextConfExt +"&sentFlNm="+ upFileNameTC +"' target='_blank'>"+ upFileNameTC +"</a></div><div class='messageDate'>"+ currentTime +"<i class='fa fa-check' style='display:inline-block;margin-left:8px;color:#03cba0' title='No errors on message sending'></i></div></div>");

								    $("#sendFileFormChatTC-"+ crTextConfExt +"").remove();
			                                            $(".receiveTextMessage").scrollTop($(".receiveTextMessage")[0].scrollHeight);

								    $("#sendFileLoaderTC-"+ crTextConfExt +"").remove();
                                                                    sendFileCheckTC = 0;

								} else { alert('An error occurred while retrieving the public key for text chat!'); }

							  },
							  error: function(result) { alert('An error occurred while attempting to retrieve the public key for text chat!'); }

							});

		                                    }

					},
					error: function(respdata) {
						    $("#sendFileFormChatTC-"+ crTextConfExt +"").remove();
						    $("#sendFileLoaderTC-" + crTextConfExt +"").remove();
						    alert("An error occurred while sending the file!");
					}
				   });

		             });

		             $("#sendFileFormChatTC-"+ crTextConfExt +"").submit();

                           });

			} else {

			        // Encrypt the message
			        var genCnfPassphr = Date.now() + Math.floor(Math.random()*10000).toString(6).toUpperCase();
			        var confAESKey = String(generateAESKey(genCnfPassphr));
			        var encryptedConfMes = encryptAES(textToSend, confAESKey);

			        // Split the encrypted message into smaller pieces to fit the 1024 byte limit for SIP messages, which is hard-coded in Asterisk
			        var messagepretcnfarr = splitString(encryptedConfMes, 850);

			        var genMessageId = Date.now() + Math.floor(Math.random()*10000).toString(16).toUpperCase();
			        var messageIdent = genMessageId + SipUsername;

			        // Get the public key of the current text conference extension
			        $.ajax({
			          type: "POST",
			          url: "src/get-text-conf-pub-key.php",
			          dataType: "JSON",
			          data: {
				         crntconfext: crTextConfExt,
				         s_ajax_call: validateSToken
			          },
			          success: function(result) {

					if (result.resmessage == 'success') {

					    var destExtChatRSAPubKey = "`" + result.chatkey.join("") + "`";

					    // Encrypt the AES key with RSA
					    var newtcnfencrypt = new JSEncrypt();
					    newtcnfencrypt.setPublicKey(destExtChatRSAPubKey);
					    var enctcnfmIdAesKey = newtcnfencrypt.encrypt(confAESKey);

					    // Add the encrypted AES key as the first element of the array of message pieces
					    messagepretcnfarr.unshift(enctcnfmIdAesKey);

					    // Send the message pieces
					    for (var k = 0; k < messagepretcnfarr.length; k++) {

						 if (k == 0) {
						     // Send the RSA-encrypted AES key as the first piece of the message
						     var messagePiece = messageIdent + "|" + messagepretcnfarr.length + "|" + parseInt(k) + "||" + messagepretcnfarr[k];	
						 } else if (sendFileCheckTC == 0 && messagepretcnfarr[k] != '') {
						     var messagePiece = messageIdent + "|" + messagepretcnfarr.length + "|" + parseInt(k) + "||" + messagepretcnfarr[k];
                                                 }

					         var msg = {
						    'From': profileName +'|'+ SipUsername,
						    'To': crTextConfExt,
						    'Body': messagePiece
					         };

					         var msgBody = JSON.stringify(msg);
					         var extraHeaders = [ 'Content-Type: application/x-asterisk-confbridge-chat-main+json' ];

					         // Send the text message
				                 try {

				                    let TCSess = tcSessions[crTextConfExt];
				                    TCSess.sendRequest(SIP.C.MESSAGE, {
						       extraHeaders,
						       body: msgBody
						    });

				                 } catch (error) {

				                      if (String(error).indexOf("this.dialog is undefined") != -1) {
				                          alert("Your connection to this text conference room has timed out. To continue, close and reopen the text conference window.");
				                          return;
				                      } else {
				                          alert("An error occurred while sending the message. Your connection to this text conference may have problems.");
				                          return;
				                      }
				                 }
                                            }

				            $("#sendTextMessage-"+ crTextConfExt +"").val("");
				            var currentTime = new Date().toLocaleTimeString();

				            // Append the sent message
				            if (textToSend != '') {
				                $("#receiveTextMessage-"+ crTextConfExt +"").append("<div class='textMessageSent textMessageTConf'><span class='sentMessageNm'>"+ profileName +" ("+ SipUsername +"):</span><div class='smTConfcont'>"+ textToSendFormatted +"</div><div class='messageDate'>"+ currentTime +"<i class='fa fa-check' style='display:inline-block;margin-left:8px;color:#03cba0' title='No errors on message sending'></i></div></div>"); 
				            }

		                            $(".receiveTextMessage").scrollTop($(".receiveTextMessage")[0].scrollHeight);
				            $("#sendFileLoaderTC-"+ crTextConfExt +"").remove();

					} else { alert('An error occurred while retrieving the public key for text chat!'); }
				  },
				  error: function(result) { alert('An error occurred while attempting to retrieve the public key for text chat!'); }

				});

		        }

		}

		// Send the message when clicking on the 'Send Message' button
		$("#sendTextChatBtn-"+ crTextConfExt +"").click(function() { SendPubChatMessage(); });

		// Send the message when pressing Ctrl + Enter
		$("#sendTextMessage-"+ crTextConfExt +"").keydown(function() {
		    var keycode = (event.keyCode ? event.keyCode : event.which);
		    if (keycode == '13') {
			if (event.ctrlKey) {
			    SendPubChatMessage();
			    return false;
			}
		    }
		});

		// Select the file to send
		$("#sendFileTC-"+ crTextConfExt +"").click(function() {

		      $("#selectedFileTC-"+ crTextConfExt +"").val('');
		      $("#upFileTC-"+ crTextConfExt +"").empty();

		      var uploadfiletc = '<form id="sendFileFormChatTC-'+ crTextConfExt +'" class="sendFileFormTextCnf" enctype="multipart/form-data">';
		      uploadfiletc += '<input type="hidden" name="MAX_FILE_SIZE" value="786432000" />';
		      uploadfiletc += '<input type="hidden" name="tcextension" value="'+ crTextConfExt +'" />';
		      uploadfiletc += '<input type="hidden" name="s_ajax_call" value="'+ validateSToken +'" />';
		      uploadfiletc += '<label for="selectedFileTC-'+ crTextConfExt +'" class="customBrowseBtnTC">Select File</label>';
		      uploadfiletc += '<span id="upFileTC-'+ crTextConfExt +'" class="upFileTextCnf"></span>';
		      uploadfiletc += '<input type="file" id="selectedFileTC-'+ crTextConfExt +'" name="uploadedFileTC" />';
		      uploadfiletc += '<input type="submit" id="submitFileChatTC-'+ crTextConfExt +'" value="Send File" style="visibility:hidden"/>';
		      uploadfiletc += '</form>';


		      if ($("#sendFileFormChatTC-"+ crTextConfExt +"").is(":visible")) {
			  $("#sendFileFormChatTC-"+ crTextConfExt +"").remove();
			  sendFileCheckTC = 0;
		      } else {
			  $("#sendTextMessage-"+ crTextConfExt +"").before(uploadfiletc);
			  $("#sendFileFormChatTC-"+ crTextConfExt +"").css("display", "block");

			  $("#selectedFileTC-"+ crTextConfExt +"").bind("change", function() {
			     upFileNameTC = $(this).val().split('\\').pop();
			     if (/^[a-zA-Z0-9\-\_\.]+$/.test(upFileNameTC)) {
                                 $("#upFileTC-"+ crTextConfExt +"").css("display", "inline-block");
				 $("#upFileTC-"+ crTextConfExt +"").html(upFileNameTC);
		                 sendFileCheckTC = 1;
			     } else { 
				   $("#sendFileFormChatTC-"+ crTextConfExt +"").remove();
				   sendFileCheckTC = 0;
				   alert("The name of the uploaded file is not valid. The name must contain only alphanumeric characters, hyphens, underscores and dots.");
			     }
			  });
		      }

		});


		// Position the window
                var maxWidth = parseInt($(window).width());
                var maxHeight = parseInt($(window).height());
                var wTop = 0;
		if (textConfNmb > 1) { wTop = parseInt((maxHeight / 2) - 324 + (textConfNmb * 10) ); } else { wTop = parseInt((maxHeight / 2) - 324); }
                var wLeft = 0;
                if (textConfNmb > 1) { wLeft = parseInt( (maxWidth / 2) - 390 + (textConfNmb * 10) ); } else { wLeft = parseInt( (maxWidth / 2) - 390 ); }
                var widthCalc = maxWidth - 351;
                var widthRecMessage = maxWidth - 232;
                var widthSendCalc = maxWidth - 46;
                var tcCalcHeight = maxHeight - 242;

		$("#publicTCMsgLabel-"+ crTextConfExt +"").hide();


		if (maxWidth < 800 || maxHeight < 645) {
                    messageWindowStateTC[crTextConfExt] = 'maximized';
		    $("#publicTextMessages-"+ crTextConfExt +"").css({ "width": maxWidth, "height": maxHeight });
                    $(".tConfUsersList,.receiveTextMessage").css("height", "510px");
                    $(".chatMessageTC,.chatMessageTCPM").css("height", "131px");
                    $(".tConfUsersList").css("width", "188px");
                    $(".receiveTextMessage").css("width", widthRecMessage);
                    $(".sendMsgTConf").css("width", widthSendCalc);
                    $(".textCnfBoxBtns").css("margin", "0px");
		} else { 
		    $("#publicTextMessages-"+ crTextConfExt +"").css({ "width": 800, "height": 645, "top": wTop, "left": wLeft });
                    $("#restoreImg-"+ crTextConfExt +",#publicTCMsgLabel-"+ crTextConfExt +"").hide();
                    $("#maximizeImg-"+ crTextConfExt +",#pubMTopBar-"+ crTextConfExt +",#minimizeImg-"+ crTextConfExt +",#receiveTextMessage-"+ crTextConfExt +",#sendTMessage-"+ crTextConfExt +",#tConfUsers-"+ crTextConfExt + "").show();
                    $(".tConfUsersList,.receiveTextMessage").css("height", "451px");
                    $(".chatMessageTC,.chatMessageTCPM").css("height", "112px");
                    $(".tConfUsersList").css("width", "188px");
                    $(".receiveTextMessage").css("width", "559px");
                    $(".sendMsgTConf").css({ "width" : "752px", "height" : "114px" });
                    $(".textCnfBoxBtns").css("margin", "0px");
		}

		// Handle window position when resizing browser window
		$(window).resize(function() {

                  let maxWidth = parseInt($(window).width());
                  let maxHeight = parseInt($(window).height());
                  let wTop = 0;
		  if (textConfNmb > 1) { wTop = parseInt((maxHeight / 2) - 324 + (textConfNmb * 10) ); } else { wTop = parseInt((maxHeight / 2) - 324); }
                  let wLeft = 0;
                  if (textConfNmb > 1) { wLeft = parseInt( (maxWidth / 2) - 390 - (textConfNmb * 10) ); } else { wLeft = parseInt( (maxWidth / 2) - 390 ); }
                  let widthRecMessage = maxWidth - 232;
                  let widthSendCalc = maxWidth - 46;

		  if (maxWidth < 800 || maxHeight < 645) { 
                      messageWindowStateTC[crTextConfExt] = 'maximized'; 
                      $("#publicTextMessages-"+ crTextConfExt +"").css({ "width": maxWidth, "height": maxHeight, "top": "0", "left": "0" });
                      $("#maximizeImg-"+ crTextConfExt +",#publicTCMsgLabel-"+ crTextConfExt +"").hide();
                      $("#minimizeImg-"+ crTextConfExt +",#pubMTopBar-"+ crTextConfExt +",#restoreImg-"+ crTextConfExt +",#receiveTextMessage-"+ crTextConfExt +",#sendTMessage-"+ crTextConfExt +",#tConfUsers-"+ crTextConfExt +"").show();
                      $(".tConfUsersList,.receiveTextMessage").css("height", "510px");
                      $(".chatMessageTC,.chatMessageTCPM").css("height", "131px");
                      $(".tConfUsersList").css("width", "188px");
                      $(".receiveTextMessage").css("width", widthRecMessage);
                      $(".sendMsgTConf").css("width", widthSendCalc);
                      $(".textCnfBoxBtns").css("margin", "0px");
		  } else { 
                      $("#publicTextMessages-"+ crTextConfExt +"").css({ "width": 800, "height": 645, "top": wTop, "left": wLeft });
                      $("#restoreImg-"+ crTextConfExt +",#publicTCMsgLabel-"+ crTextConfExt +"").hide();
                      $("#maximizeImg-"+ crTextConfExt +",#pubMTopBar-"+ crTextConfExt +",#minimizeImg-"+ crTextConfExt +",#receiveTextMessage-"+ crTextConfExt +",#sendTMessage-"+ crTextConfExt +",#tConfUsers-"+ crTextConfExt + "").show();
                      $(".tConfUsersList,.receiveTextMessage").css("height", "451px");
                      $(".chatMessageTC,.chatMessageTCPM").css("height", "112px");
                      $(".tConfUsersList").css("width", "188px");
                      $(".receiveTextMessage").css("width", "559px");
                      $(".sendMsgTConf").css({ "width" : "752px", "height" : "114px" });
                      $(".textCnfBoxBtns").css("margin", "0px");
		  }

		});

		// Handle clicking on window control buttons
                $("#maximizeImg-"+ crTextConfExt +"").click(function() {
                   $("#publicTextMessages-"+ crTextConfExt +"").css({ "width": maxWidth, "height": maxHeight, "top": "0", "left": "0" });
                   $("#maximizeImg-"+ crTextConfExt +",#publicTCMsgLabel-"+ crTextConfExt +"").hide();
                   $("#minimizeImg-"+ crTextConfExt +",#pubMTopBar-"+ crTextConfExt +",#restoreImg-"+ crTextConfExt +",#receiveTextMessage-"+ crTextConfExt +",#sendTMessage-"+ crTextConfExt +",#tConfUsers-"+ crTextConfExt +"").show();
                   $(".receiveTextMessage").css("width", widthCalc);
                   $(".tConfUsersList,.receiveTextMessage").css("height", tcCalcHeight);
                   $(".tConfUsersList").css("width", "300px");
                   $(".chatMessageTC,.chatMessageTCPM").css("height", "131px");
                   $(".sendMsgTConf").css({ "width" : widthSendCalc, "height" : "140px" });
                   $(".textCnfBoxBtns").css("margin", "12px 4px");
                   $("#recTCPubMessage-"+ crTextConfExt +"").remove();
                   $("#receiveTextMessage-"+ crTextConfExt +"").scrollTop($("#receiveTextMessage-"+ crTextConfExt +"")[0].scrollHeight);
                });

		$("#minimizeImg-"+ crTextConfExt +"").click(function() { 
                   messageWindowStateTC[crTextConfExt] = 'minimized'; 
                   $("#publicTextMessages-"+ crTextConfExt +"").css({ "display": "block", "position": "fixed", "width": 280, "height": 40, "inset": "auto auto 1px 3px" });
                   $("#minimizeImg-"+ crTextConfExt +",#pubMTopBar-"+ crTextConfExt +",#receiveTextMessage-"+ crTextConfExt +",#sendTMessage-"+ crTextConfExt +",#tConfUsers-"+ crTextConfExt + "").hide();
                   $("#maximizeImg-"+ crTextConfExt +",#restoreImg-"+ crTextConfExt +"").show();
                   $("#publicTCMsgLabel-"+ crTextConfExt +"").css("display", "block");
                });

                $("#restoreImg-"+ crTextConfExt +"").click(function() {
		   if (maxWidth < 800 || maxHeight < 645) { 
                      messageWindowStateTC[crTextConfExt] = 'maximized'; 
                      $("#publicTextMessages-"+ crTextConfExt +"").css({ "width": maxWidth, "height": maxHeight, "top": "0", "left": "0" });
                      $("#maximizeImg-"+ crTextConfExt +",#publicTCMsgLabel-"+ crTextConfExt +"").hide();
                      $("#minimizeImg-"+ crTextConfExt +",#pubMTopBar-"+ crTextConfExt +",#restoreImg-"+ crTextConfExt +",#receiveTextMessage-"+ crTextConfExt +",#sendTMessage-"+ crTextConfExt +",#tConfUsers-"+ crTextConfExt +"").show();
                      $(".tConfUsersList,.receiveTextMessage").css("height", "510px");
                      $(".chatMessageTC,.chatMessageTCPM").css("height", "131px");
                      $(".tConfUsersList").css("width", "188px");
                      $(".receiveTextMessage").css("width", widthRecMessage);
                      $(".sendMsgTConf").css("width", widthSendCalc);
                      $(".textCnfBoxBtns").css("margin", "0px");
                      $("#recTCPubMessage-"+ crTextConfExt +"").remove();
                      $("#receiveTextMessage-"+ crTextConfExt +"").scrollTop($("#receiveTextMessage-"+ crTextConfExt +"")[0].scrollHeight);
		   } else {
                      messageWindowStateTC[crTextConfExt] = 'restored';
                      $("#publicTextMessages-"+ crTextConfExt +"").css({ "width": 800, "height": 645, "top": wTop, "left": wLeft });
                      $("#restoreImg-"+ crTextConfExt +",#publicTCMsgLabel-"+ crTextConfExt +"").hide();
                      $("#maximizeImg-"+ crTextConfExt +",#pubMTopBar-"+ crTextConfExt +",#minimizeImg-"+ crTextConfExt +",#receiveTextMessage-"+ crTextConfExt +",#sendTMessage-"+ crTextConfExt +",#tConfUsers-"+ crTextConfExt + "").show();
                      $(".tConfUsersList,.receiveTextMessage").css("height", "451px");
                      $(".chatMessageTC,.chatMessageTCPM").css("height", "112px");
                      $(".tConfUsersList").css("width", "188px");
                      $(".receiveTextMessage").css("width", "559px");
                      $(".sendMsgTConf").css({ "width" : "752px", "height" : "114px" });
                      $(".textCnfBoxBtns").css("margin", "0px");
                      $("#recTCPubMessage-"+ crTextConfExt +"").remove();
                      $("#receiveTextMessage-"+ crTextConfExt +"").scrollTop($("#receiveTextMessage-"+ crTextConfExt +"")[0].scrollHeight);
		   }
                });

		$("#closeImg-"+ crTextConfExt +"").click(function() {
                   winTextConfCheck[crTextConfExt] = 0;
                   messageWindowStateTC[crTextConfExt] = 'closed';
                   $("#publicTextMessages-"+ crTextConfExt +"").remove();
                   $("#recTCPubMessage-"+ crTextConfExt +"").remove();

                   // Hang up when closing the text conference window
                   var TCSess = tcSessions[crTextConfExt]; TCSess.bye(); delete tcSessions[crTextConfExt];
                   --textConfNmb;
                });

		// Get the private key of the current text conference extension, since it may have been created previously by another conference participant
		$.ajax({
		  type: "POST",
		  url: "src/get-text-conf-priv-key.php",
		  dataType: "JSON",
		  data: {
		     crnttconfext: crTextConfExt,
		     s_ajax_call: validateSToken
		  },
		  success: function(result) {

		        if (result.resmessage == 'success') {
		            tconfPrivKey[crTextConfExt] = "`" + result.chatprivkey.join("") + "`";
		        } else {
		            alert('An error occurred while retrieving the private key for text conference!');
		        }
		  },
		  error: function(result) {
		            alert('An error occurred while attempting to retrieve the private key for text conference!');
		  }
		});

                $("#openTConfLoader").remove();

      } else { alert("This text conference has already been launched!"); $("#openTConfLoader").remove(); }
}

// Open a private (text conference) message window
function openTCPrivateMsgWindow(msgDestName, msgDestExt, msgDestConfExt, msgDestConfTag) {

                                   sendFileCheckTCPM[msgDestConfExt +"-"+ msgDestExt] = 0;
                                   upFileNameTCPM[msgDestConfExt +"-"+ msgDestExt] = '';
                                   ++textConfNmbPM;

				   var privchathtmltc = "<div id='pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"' class='textMessagesTCPM'>";
                                   privchathtmltc += "<div id='windowCtrlsTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='windowCtrlsTCPM'><img id='closeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='closeImgTCPM' src='../images/3_close.svg' title='Close'><img id='maximizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='maximizeImgTCPM' src='../images/2_maximize.svg' title='Maximize'><img id='restoreImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='restoreImgTCPM' src='../images/4_restore.svg' title='Restore'><img id='minimizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='minimizeImgTCPM' src='../images/1_minimize.svg' title='Minimize'><img id='privateMessageImg-"+ msgDestConfExt +"-"+ msgDestExt +"' class='privateMessageImgTC' src='../images/PrivateMessage.svg' title='Private Message'></div>"; 
                                   privchathtmltc += "<div id='topDragArea-"+ msgDestConfExt +"-"+ msgDestExt +"' style='display:block;position:absolute;width:100%;height:10px;top:0px;text-align:center;z-index:20;'><i class='fa fa-ellipsis-h' title='Drag Button' style='display:block;position:relative;margin:-1px auto;color:#d0d0d0;font-size:17px;cursor:grab;'></i></div>";
				   privchathtmltc += "<div id='pmTopBar-"+ msgDestConfExt +"-"+ msgDestExt +"' class='pmTopBar'><span class='privTConfMsgUpTitle'>"+ lang.text_conference_room +": <span style='color:#248d8d;'>"+ msgDestConfTag +"</span> ("+ msgDestConfExt +")</span><span class='privTConfMsgTitle'>Private Text Messaging - <span style='color:#484c94'>"+ msgDestName + " ("+ msgDestExt +")</span></span></div>";
				   privchathtmltc += "<div id='receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='receiveTextMessageTCPM'><div id='downloadTConfHst-"+ msgDestConfExt +"-"+ msgDestExt +"' class='downloadTConfHstTCPM' title='Download Text Conversation'><i class='fa fa-download'></i></div></div>";
                                   privchathtmltc += "<div id='emojiMenuTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='emojiMenuTCPM'></div>";
                                   privchathtmltc += "<div id='sendTMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='sendTMessageTCPM'><table cellspacing='0' cellpadding='0' class='sendMessageTblPM'><tr><td style='height: 100%;width: 100%;'><textarea id='sendTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='chatMessageTCPM' placeholder='Type the message here, then click the paper plane button or press Ctrl + Enter to send it.'></textarea></td>";
                                   privchathtmltc += "<td style='display:block;width:36px;margin:0px;'><button id='sendTextChatBtnPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='roundButtonsSpec' title='Send Message'><i class='fa fa-paper-plane-o' aria-hidden='true'></i></button>";
                                   privchathtmltc += "<button id='selectEmoticonPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='roundButtonsSpec' title='Select Emoticon'><i class='fa fa-smile-o'></i></button>";
                                   privchathtmltc += "<button id='sendFileTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='roundButtonsSpec' title='Send File'><i class='fa fa-file-text-o'></i></button></td></tr></table></div>";
				   privchathtmltc += "</div>";

                                   $("#Phone").append(privchathtmltc);
                                   $("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").css("display", "block");

                                   $("#restoreImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();
				   $("#sendTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").focus();
                                   messageWindowStateTCPM[msgDestConfExt +"-"+ msgDestExt] = 'restored';
                                   $("#publicTextMessages").css("z-index", 18);

                                   // Bring window forward when clicking on it
                                   $('[id^="pmtextMessage-"]').click(function() { 
                                      $(".textMessagesTCPM").each(function() { $(this).css("z-index", 19); });
                                      $(".publicTextCnfMessage").each(function() { $(this).css("z-index", 18); });
                                      $(this).css("z-index", 22);
                                   });

                                   // Make the window draggable
				   function makeDraggable(e){

				       window.pmdragging = {};
				       pmdragging.pageX0 = e.pageX;
				       pmdragging.pageY0 = e.pageY;
                                       pmdragging.elem = $("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"");
                                       pmdragging.offset0 = $("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").offset();

				       function handle_dragging(e) {
					   var left = pmdragging.offset0.left + (e.pageX - pmdragging.pageX0);
					   var top = pmdragging.offset0.top + (e.pageY - pmdragging.pageY0);
					   $(pmdragging.elem).offset({top: top, left: left});
				       }

				       function handle_mouseup(e) {
					   $('body').off('mousemove', handle_dragging).off('mouseup', handle_mouseup);
				       }

				       $('body').on('mouseup', handle_mouseup).on('mousemove', handle_dragging);
				   }

                                   $("#topDragArea-"+ msgDestConfExt +"-"+ msgDestExt +"").mousedown(makeDraggable);

                                   // Show emoji menu
                                   $("#selectEmoticonPM-"+ msgDestConfExt +"-"+ msgDestExt +"").click(function() {

				       var messageContainerPM = $("#emojiMenuTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"");
				       var textareaPM = $("#sendTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"");

				       if (messageContainerPM.is(":visible")) { messageContainerPM.hide(); } else { messageContainerPM.show(); }

				       var menuBarPM = $("<div>");
				       menuBarPM.prop("class", "emojiButton")
				       var emojisPM = ["😀","😁","😂","😃","😄","😅","😆","😊","😦","😉","😊","😋","😌","😍","😎","😏","😐","😑","😒","😓","😔","😕","😖","😗","😘","😙","😚","😛","😜","😝","😞","😟","😠","😡","😢","😣","😤","😥","😦","😧","😨","😩","😪","😫","😬","😭","😮","😯","😰","😱","😲","😳","😴","😵","😶","😷","🙁","🙂","🙃","🙄","🤐","🤑","🤒","🤓","🤔","🤕","🤠","🤡","🤢","🤣","🤤","🤥","🤧","🤨","🤩","🤪","🤫","🤬","🤭","🤯","🤗","🧐"];
				       $.each(emojisPM, function(i,e){
					       var emojiPM = $("<button>");
					       emojiPM.html(e);
					       emojiPM.on('click', function() {
						    var i = textareaPM.prop('selectionStart');
						    var v = textareaPM.val();
						    textareaPM.val(v.substring(0, i) + " " + $(this).html() + v.substring(i, v.length) + " ");

						    messageContainerPM.hide();
						    textareaPM.focus();
					       });
					       menuBarPM.append(emojiPM);
				       });

				       $("#sendTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +",#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").on('click', function() {
				          messageContainerPM.hide();
				       });

				       messageContainerPM.empty();
				       messageContainerPM.append(menuBarPM);
                                   });

				   // Show the 'Download Text Conversation' button
				   $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").mouseenter(function() {
				      $("#downloadTConfHst-"+ msgDestConfExt +"-"+ msgDestExt +"").css("display", "block");
				   });
				   $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").mouseleave(function() {
				      $("#downloadTConfHst-"+ msgDestConfExt +"-"+ msgDestExt +"").css("display", "none");
				   });

				   // Download chat history
				   $("#downloadTConfHst-"+ msgDestConfExt +"-"+ msgDestExt +"").click(function() { DownloadHistoryTCPM(msgDestConfExt, msgDestConfTag, msgDestExt); });


				   function sendPrivMessageTC(targetUserTCPM, textToSendTCPM) {

                                       var textToSendTCPMformatted = ReformatMessage(textToSendTCPM);

			               // When a file is sent
                                       if (sendFileCheckTCPM[msgDestConfExt +"-"+ msgDestExt] == 1) {

                                           $.when($("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").append("<span id='sendFileLoaderTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"' class='sendFileLoaderTC'></span>")).then(function() {

                                             sendFileChatErrTCPM[msgDestConfExt +"-"+ msgDestExt] = '';

                                             $("#sendFileFormChatTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").on('submit', function(ev) {

				                   ev.preventDefault();

                                                   $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").after("<span id='sendFileLoaderTC'></span>");

						   $.ajax({
							type: 'POST',
							url: 'src/text-chat-textconf-upload-file-pm.php',
							data: new FormData(this),
							dataType: "JSON",
							contentType: false,
							cache: false,
							processData: false,
							success: function(respdata) { 

								   if (respdata.error != '') {

                                                                       sendFileChatErrTCPM[msgDestConfExt +"-"+ msgDestExt] = respdata.error;
								       $("#sendFileFormChatTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
								       $("#sendFileLoaderTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
								       sendFileCheckTCPM[msgDestConfExt +"-"+ msgDestExt] = 0;
								       alert("Error: " + respdata.error);

								   } else {

								       // Encrypt the message
								       var genCnfPassphr = Date.now() + Math.floor(Math.random()*10000).toString(6).toUpperCase();
								       var confAESKey = String(generateAESKey(genCnfPassphr));
								       var encryptedConfMes = encryptAES(textToSendTCPM, confAESKey);

								       // Split the encrypted message into smaller pieces to fit the 1024 byte limit for SIP messages, which is hard-coded in Asterisk
								       var messageprevcnfarrpm = [];
								       var messageprevcnfarrpm = splitString(encryptedConfMes, 850);

								       var genMessageIdpm = Date.now() + Math.floor(Math.random()*10000).toString(16).toUpperCase();
								       var messageIdentpm = genMessageIdpm + msgDestConfExt +"-"+ msgDestExt;

								       // Get the public key of the other conference participant
								       $.ajax({
									 type: "POST",
									 url: "src/get-text-chat-pub-key.php",
									 dataType: "JSON",
									 data: { 
									     recsendsipuser: msgDestExt,
									     s_ajax_call: validateSToken
									 },
									 success: function(result) {

										    if (result.resmessage == 'success') {

											var destExtRSAPubKey = "`" + result.chatkey.join("") + "`";

											// Encrypt the AES key with RSA
											var newvcnfencryptpm = new JSEncrypt();
											newvcnfencryptpm.setPublicKey(destExtRSAPubKey);
											var encvcnfmIdAesKeypm = newvcnfencryptpm.encrypt(confAESKey);

											// Add the encrypted AES key as the first element of the array of message pieces
											messageprevcnfarrpm.unshift(encvcnfmIdAesKeypm);

                                                                                        let privMsErr = 0;

											// Send the message pieces
											for (var k = 0; k < messageprevcnfarrpm.length; k++) {

											     if (k == 0) {
												 // Send the RSA-encrypted AES key as the first piece of the message
												 var messagePiecePM = messageIdentpm + "|" + messageprevcnfarrpm.length + "|" + parseInt(k) + "||" + messageprevcnfarrpm[k];	
											     } else if (sendFileChatErrTCPM[msgDestConfExt +"-"+ msgDestExt] == '') {
												 var messagePiecePM = messageIdentpm + "|" + messageprevcnfarrpm.length + "|" + parseInt(k) + "|" + upFileNameTCPM[msgDestConfExt +"-"+ msgDestExt] + "|" + messageprevcnfarrpm[k];
											     } else if (sendFileChatErrTCPM[msgDestConfExt +"-"+ msgDestExt] != '') {
												 $("#sendFileFormChatTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
												 sendFileChatErrTCPM[msgDestConfExt +"-"+ msgDestExt] = '';
												 sendFileCheckTCPM[msgDestConfExt +"-"+ msgDestExt] = 0;
												 return;
											     }

					                                                     let messageincfrObj = { "From": profileName, "Body": messagePiecePM };
					                                                     let messageincfr = JSON.stringify(messageincfrObj);

											     try { userAgent.message(targetUserTCPM, messageincfr, { extraHeaders: [ 'X-TextConfPrivMessage: '+ profileName +'|'+ SipUsername +'|'+ msgDestConfExt ] }); } catch(er) { privMsErr++; }
								                        }

										        if (textToSendTCPM.trim() != '' && sendFileChatErrTCPM[msgDestConfExt +"-"+ msgDestExt] == '') {

                                                                                            if (privMsErr == 0) {
											        let currentTimePM = new Date().toLocaleTimeString();
											        $("#sendTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").val("");
											        $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").append("<div class='textMessageSentTCPM textMessageTConfPM'><span class='sentMessageNm'>" + profileName + " (" + SipUsername + "):</span><div class='srMsgTConfcont'>" + textToSendTCPMformatted + "</div><div class='messageDate'>" + currentTimePM + "<i class='fa fa-check' style='display:inline-block;margin-left:8px;color:#03cba0' title='No errors on message sending'></i></div></div>"); 

											        // Append the 'Download file' section
											        let currentflTimePM = new Date().toLocaleTimeString();
											        $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").append("<div class='textMessageSentTCPM textMessageTConfPM'><span class='sentMessageNm'>"+ profileName +" ("+ SipUsername +"):</span><div class='srMsgTConfcont'>Download file: <a href='src/download-sent-chat-file.php?s_ajax_call="+ validateSToken +"&destSipUser="+ msgDestExt +"&sentFlNm="+ upFileNameTCPM[msgDestConfExt +"-"+ msgDestExt] +"' target='_blank'>"+ upFileNameTCPM[msgDestConfExt +"-"+ msgDestExt] +"</a></div><div class='messageDate'>"+ currentflTimePM +"<i class='fa fa-check' style='display:inline-block;margin-left:8px;color:#03cba0' title='No errors on message sending'></i></div></div>");

											        $("#sendFileFormChatTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
                                                                                            }

										        } else if (textToSendTCPM.trim() == '' && sendFileChatErrTCPM[msgDestConfExt +"-"+ msgDestExt] == '') {

											    // Append the 'Download file' section
											    let currentflTimePM = new Date().toLocaleTimeString();
											    $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").append("<div class='textMessageSentTCPM textMessageTConfPM'><span class='sentMessageNm'>"+ profileName +" ("+ SipUsername +"):</span><div class='srMsgTConfcont'>Download file: <a href='src/download-sent-chat-file.php?s_ajax_call="+ validateSToken +"&destSipUser="+ msgDestExt +"&sentFlNm="+ upFileNameTCPM[msgDestConfExt +"-"+ msgDestExt] +"' target='_blank'>"+ upFileNameTCPM[msgDestConfExt +"-"+ msgDestExt] +"</a></div><div class='messageDate'>"+ currentflTimePM +"<i class='fa fa-check' style='display:inline-block;margin-left:8px;color:#03cba0' title='No errors on message sending'></i></div></div>");

											    $("#sendFileFormChatTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
										        }

											$("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").scrollTop($("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"")[0].scrollHeight);
										        $("#sendFileLoaderTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
											
										    } else {
											alert('An error occurred while retrieving the public key for text chat!');
										    }

										    sendFileCheckTCPM[msgDestConfExt +"-"+ msgDestExt] = 0;

									 },
									 error: function(result) {
								                        alert('An error occurred while attempting to retrieve the public key for text chat!');
									 }
								       });

                                                                       $("#sendFileFormChatTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
                                                                       $("#sendFileLoaderTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
                                                                   }

							},
							error: function(respdata) {
								    $("#sendFileFormChatTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
								    $("#sendFileLoaderTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
								    alert("An error occurred while sending the file!");
							}  
						   });

                                             });

                                             $("#sendFileFormChatTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").submit();

                                           });

                                       } else {

					       // Encrypt the message
					       var genCnfPassphr = Date.now() + Math.floor(Math.random()*10000).toString(6).toUpperCase();
					       var confAESKey = String(generateAESKey(genCnfPassphr));
					       var encryptedConfMes = encryptAES(textToSendTCPM, confAESKey);

					       // Split the encrypted message into smaller pieces to fit the 1024 byte limit for SIP messages, which is hard-coded in Asterisk
					       var messageprevcnfarrpm = [];
					       var messageprevcnfarrpm = splitString(encryptedConfMes, 850);

					       var genMessageIdpm = Date.now() + Math.floor(Math.random()*10000).toString(16).toUpperCase();
					       var messageIdentpm = genMessageIdpm + msgDestConfExt +"-"+ msgDestExt;

					       // Get the public key of the other conference participant
					       $.ajax({
						 type: "POST",
						 url: "src/get-text-chat-pub-key.php",
						 dataType: "JSON",
						 data: { 
						     recsendsipuser: msgDestExt,
						     s_ajax_call: validateSToken
						 },
						 success: function(result) {

							    if (result.resmessage == 'success') {

								var destExtRSAPubKey = "`" + result.chatkey.join("") + "`";

								// Encrypt the AES key with RSA
								var newvcnfencryptpm = new JSEncrypt();
								newvcnfencryptpm.setPublicKey(destExtRSAPubKey);
								var encvcnfmIdAesKeypm = newvcnfencryptpm.encrypt(confAESKey);

								// Add the encrypted AES key as the first element of the array of message pieces
								messageprevcnfarrpm.unshift(encvcnfmIdAesKeypm);

								// Send the message pieces
                                                                if (textToSendTCPM.trim() != '') {

                                                                    let privMsErrWfTC = 0;

								    for (var k = 0; k < messageprevcnfarrpm.length; k++) {

								         if (k == 0) {
									     // Send the RSA-encrypted AES key as the first piece of the message
									     var messagePiecePM = messageIdentpm + "|" + messageprevcnfarrpm.length + "|" + parseInt(k) + "||" + messageprevcnfarrpm[k];	
								         } else if (messageprevcnfarrpm[k] != '') {
									     var messagePiecePM = messageIdentpm + "|" + messageprevcnfarrpm.length + "|" + parseInt(k) + "||" + messageprevcnfarrpm[k];
								         }

					                                 let messageincfrObj = { "From": profileName, "Body": messagePiecePM };
					                                 let messageincfr = JSON.stringify(messageincfrObj);
                                                                         try { userAgent.message(targetUserTCPM, messageincfr, { extraHeaders: [ 'X-TextConfPrivMessage: '+ profileName +'|'+ SipUsername +'|'+ msgDestConfExt ] }); } catch(ersm) { privMsErrWfTC++; alert("Error when sending message: "+ ersm); }
		                                                    }

                                                                    if (privMsErrWfTC == 0) {
                                                                        let currentTimePM = new Date().toLocaleTimeString();
                                                                        $("#sendTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").val("");
                                                                        $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").append("<div class='textMessageSentTCPM textMessageTConfPM'><span class='sentMessageNm'>" + profileName + " (" + SipUsername + "):</span><div class='srMsgTConfcont'>" + textToSendTCPMformatted + "</div><div class='messageDate'>" + currentTimePM + "<i class='fa fa-check' style='display:inline-block;margin-left:8px;color:#03cba0' title='No errors when sending message'></i></div></div>"); 
                                                                        $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").scrollTop($("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"")[0].scrollHeight);
                                                                    }
                                                                }
								
							    } else {
								alert('An error occurred while retrieving the public key for text chat!');
							    }
						 },
						 error: function(result) {
		                                                alert('An error occurred while attempting to retrieve the public key for text chat!');
						 }
					       });

                                       }

				   }

                                   // Send private message by clicking the "Send Message" button
				   $("#sendTextChatBtnPM-"+ msgDestConfExt +"-"+ msgDestExt +"").click(function() {

                                      var textToSendTCPM = $("#sendTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").val();
                                      var targetUserTCPM = msgDestExt +"@"+ wssServer +"";

                                      sendPrivMessageTC(targetUserTCPM, textToSendTCPM);
                                   });

                                   // Send private message by pressing Ctrl + Enter
                                   $("#sendTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").keydown(function() {
				       var keycode = (event.keyCode ? event.keyCode : event.which);
				       if (keycode == '13') {
				           if (event.ctrlKey) {

                                               var textToSendTCPM = $("#sendTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").val();
                                               var targetUserTCPM = msgDestExt +"@"+ wssServer +"";

                                               sendPrivMessageTC(targetUserTCPM, textToSendTCPM);
				           }
				       }
                                   });


                                   // Select the file to send
                                   $("#sendFileTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").click(function() {

				      $("#selectedFileTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").val('');
				      $("#upFileTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").empty();

				      var uploadfiletcpm = '<form id="sendFileFormChatTCPM-'+ msgDestConfExt +"-"+ msgDestExt +'" class="fileSendTCPM" enctype="multipart/form-data">';
				      uploadfiletcpm += '<input type="hidden" name="MAX_FILE_SIZE" value="786432000" />';
				      uploadfiletcpm += '<input type="hidden" name="tcextensionpm" value="'+ msgDestExt +'" />';
				      uploadfiletcpm += '<input type="hidden" name="s_ajax_call" value="'+ validateSToken +'" />';
				      uploadfiletcpm += '<label for="selectedFileTCPM-'+ msgDestConfExt +"-"+ msgDestExt +'" class="customBrowseButtonTCPM">Select File</label>';
				      uploadfiletcpm += '<span id="upFileTCPM-'+ msgDestConfExt +"-"+ msgDestExt +'" class="uploadedFlTCPM" ></span>';
				      uploadfiletcpm += '<input type="file" id="selectedFileTCPM-'+ msgDestConfExt +"-"+ msgDestExt +'" name="uploadedFileTCPM" />';
				      uploadfiletcpm += '<input type="submit" id="submitFileChatTCPM-'+ msgDestConfExt +"-"+ msgDestExt +'" value="Send File" style="visibility:hidden"/>';
				      uploadfiletcpm += '</form>';


				      if ($("#sendFileFormChatTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").is(":visible")) {
					  $("#sendFileFormChatTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
					  sendFileCheckTCPM[msgDestConfExt +"-"+ msgDestExt] = 0;
				      } else {
                                          sendFileCheckTCPM[msgDestConfExt +"-"+ msgDestExt] = 1;
					  $("#sendTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").before(uploadfiletcpm);
					  $("#sendFileFormChatTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").css("display", "block");

					  $("#selectedFileTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").bind("change", function() {
					     upFileNameTCPM[msgDestConfExt +"-"+ msgDestExt] = $(this).val().split('\\').pop();
					     if (/^[a-zA-Z0-9\-\_\.]+$/.test(upFileNameTCPM[msgDestConfExt +"-"+ msgDestExt])) {
                                                 $("#upFileTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").css("display", "inline-block");
						 $("#upFileTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").html(upFileNameTCPM[msgDestConfExt +"-"+ msgDestExt]);
					     } else {
						 $("#sendFileFormChatTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
						 sendFileCheckTCPM[msgDestConfExt +"-"+ msgDestExt] = 0;
						 alert("The name of the selected file is not valid!");
					     }
					  });
				      }

                                   });


                                   // Set window position
			           var maxHeightPm = parseInt(0.88 * $(window).height());
			           var maxWidthPm = parseInt(maxHeightPm * 1.24);

                                   var pmwTop = 0;
                                   var pmwLeft = 0;

		                   if (textConfNmbPM > 1) { pmwTop = parseInt(($(window).height() / 2) - 305) + (textConfNmbPM * 10); } else { pmwTop = parseInt(($(window).height() / 2) - 305); }
                                   if (textConfNmbPM > 1) { pmwLeft = parseInt(($(window).width() / 2) - 245) + (textConfNmbPM * 10); } else { pmwLeft = parseInt(($(window).width() / 2) - 245); }

                                   $("#privateMessageImg-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();

			           if (maxWidthPm < 610 || maxHeightPm < 490) { 
			               $("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").css({ "width": maxWidthPm, "height": maxHeightPm, "top": pmwTop, "left": pmwLeft });
			               $("#maximizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();
                                       $("#restoreImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();
                                   } else {
			               $("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").css({ "width": 610, "height": 490, "top": pmwTop, "left": pmwLeft });
                                       $("#minimizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
			               $("#maximizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                       $("#restoreImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();
			           }

			           $(window).resize(function() {

			             let maxHeightPm = parseInt(0.88 * $(window).height());
			             let maxWidthPm = parseInt(maxHeightPm * 1.24);
                                     let pmwTop = 0;
                                     let pmwLeft = 0;

		                     if (textConfNmbPM > 1) { pmwTop = parseInt(($(window).height() / 2) - 305) + (textConfNmbPM * 10); } else { pmwTop = parseInt(($(window).height() / 2) - 305); }
                                     if (textConfNmbPM > 1) { pmwLeft = parseInt(($(window).width() / 2) - 245) + (textConfNmbPM * 10); } else { pmwLeft = parseInt(($(window).width() / 2) - 245); }

                                     $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                     $("#sendTMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                     $("#pmTopBar-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                     $("#privateMessageImg-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();

			             if (maxWidthPm < 610 || maxHeightPm < 490) { 
				         $("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").css({ "width": maxWidthPm, "height": maxHeightPm, "top": pmwTop, "left": pmwLeft });
				         $("#maximizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();
                                         $("#restoreImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();
			             } else { 
				         $("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").css({ "width": 610, "height": 490, "top": pmwTop, "left": pmwLeft });
                                         $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").css("height", "58%");
       				         $("#minimizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
				         $("#maximizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                         $("#restoreImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();
			             }

			           });

			           $("#maximizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").click(function() { 
                                      messageWindowStateTCPM[msgDestConfExt +"-"+ msgDestExt] = 'maximized';
                                      $("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").css({ "width": maxWidthPm, "height": maxHeightPm, "top": pmwTop - 40, "left": pmwLeft - 40});
                                      $("#maximizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();
                                      $("#minimizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                      $("#restoreImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                      $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").css("height", "67%");
                                      $("#recPrivMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
                                      $("#pmTopBar-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                      $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                      $("#sendTMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                      $("#privateMessageImg-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();
                                      $("#topDragArea-"+ msgDestConfExt +"-"+ msgDestExt +"").css("top", "0px");
                                      $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").scrollTop($("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"")[0].scrollHeight);
                                      $("#recTCPrivMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").remove(); 
                                   });

                                   $("#minimizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").click(function() {
                                      messageWindowStateTCPM[msgDestConfExt +"-"+ msgDestExt] = 'minimized';
                                      $("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").css({ "display": "block", "position": "fixed", "width": "218px", "height": "31px", "inset": "auto auto 2px 342px" });
                                      $("#minimizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +",#pmTopBar-"+ msgDestConfExt +"-"+ msgDestExt +",#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +",#sendTMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();
                                      $("#maximizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +",#restoreImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +",#privateMessageImg-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                      $("#windowCtrlsTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").css("margin", "2px 2px 0px 0px");
                                      $("#topDragArea-"+ msgDestConfExt +"-"+ msgDestExt +"").css("top", "-3px");
                                   });

                                   $("#restoreImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").click(function() {
                                      messageWindowStateTCPM[msgDestConfExt +"-"+ msgDestExt] = 'restored';
                                      $("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").css({ "width": 610, "height": 490, "top": pmwTop, "left": pmwLeft });
                                      $("#restoreImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();
                                      $("#maximizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                      $("#minimizeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                      $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").css("height", "58%");
                                      $("#recPrivMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
                                      $("#pmTopBar-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                      $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                      $("#sendTMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").show();
                                      $("#privateMessageImg-"+ msgDestConfExt +"-"+ msgDestExt +"").hide();
                                      $("#topDragArea-"+ msgDestConfExt +"-"+ msgDestExt +"").css("top", "0px");
                                      $("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").scrollTop($("#receiveTextMessageTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"")[0].scrollHeight);
                                      $("#recTCPrivMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
                                   });

			           $("#closeImgTCPM-"+ msgDestConfExt +"-"+ msgDestExt +"").click(function() {
                                      messageWindowStateTCPM[msgDestConfExt +"-"+ msgDestExt] = 'closed';
                                      $("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").remove();
                                      $("#recPrivMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").remove(); 
                                      --textConfNmbPM;
                                   });

			           $(window).on('keydown', function(event) { if (event.key == "Escape") { messageWindowStateTCPM[msgDestConfExt +"-"+ msgDestExt] = 'closed'; $("#pmtextMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").remove(); $("#recPrivMessage-"+ msgDestConfExt +"-"+ msgDestExt +"").remove(); } });

}

// Launch video conference
function LaunchVideoConference(crvidcnfext, crvidcnftag) {

     if (localDB.getItem('vConfOpenCheck') == 'closed' || localDB.getItem('vConfOpenCheck') == 'unset') {
	    winVideoConf = window.open('https://' + window.location.host + '/videoconference/index.php?extensionvidconf='+ crvidcnfext +'&tagvideoconf='+ crvidcnftag +'');

            // Send 'On the phone' status message to all the other extensions
            for (let b = 0; b < Buddies.length; b++) {
                 let crBuddyObj = Buddies[b];
                 if (crBuddyObj.type == "extension") {
                     userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'On the phone', { extraHeaders: [ 'X-CustomPresence: on_the_phone' ] });
                 }
            }
     } else { alert("The video conference has already been launched."); }

}

// Generate a new text chat key pair
function generateChatRSAKeys(currentSIPusername) {

          var crypto = new JSEncrypt({default_key_size: 1024});
          crypto.getKey();
          var currentChatPubKey = crypto.getPublicKey();
          currentChatPrivKey = crypto.getPrivateKey();

	  $.ajax({
	     type: "POST",
	     url: "src/save-text-chat-pub-key.php",
	     dataType: "JSON",
	     data: {
	             currentextension: currentSIPusername,
                     currentchatpubkey: currentChatPubKey,
	             s_ajax_call: validateSToken
	     },
	     success: function() {
	     },
	     error: function() {
	                alert("An error occurred while trying to save the new text chat public key!");
	     }
	  });
}

// Remove the files received during text chat, from the 'uploads' directory of the current user
function removeTextChatUploads(currentSIPUser) {

          let userRoleFDB = getDbItem("userrole", "");
          let extensionVideoConfPre = '';
          Object.keys(videoExtTagObj).forEach(function(key, index) {
                 extensionVideoConfPre += key +",";
          });
          let extensionVideoConf = extensionVideoConfPre.slice(0, -1);

          let extensionTextConfPre = '';
          Object.keys(textExtTagObj).forEach(function(key, index) {
                 extensionTextConfPre += key +",";
          });
          let extensionTextConf = extensionTextConfPre.slice(0, -1);

	  $.ajax({
	     type: "POST",
	     url: "src/remove-text-chat-uploaded-files.php",
	     dataType: "JSON",
	     data: {
		     sipusername: currentSIPUser,
                     sipuserrole: userRoleFDB,
                     vidconfextension: extensionVideoConf,
                     textconfextension: extensionTextConf,
		     s_ajax_call: validateSToken
	     },
	     success: function(resresult) {
                          if (resresult.note != 'success') {
                              alert("An error occurred while trying to empty the 'uploads' directory!");
                          }
             },
             error: function(resresult) {
                              alert("An error occurred while attempting to empty the 'uploads' directory!");
             }
          });
}

// Send the pressence status to all the other extensions
function sendReadyStatus() {           
            for (let b = 0; b < Buddies.length; b++) {
                 let crBuddyObj = Buddies[b];
                 if (crBuddyObj.type == "extension") {
                     userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Ready', { extraHeaders: [ 'X-CustomPresence: ready' ] });
                 }
            }
}
function sendCustomStateStatus(cstmStNm) {
            for (let b = 0; b < Buddies.length; b++) {
                 let crBuddyObj = Buddies[b];
                 if (crBuddyObj.type == "extension") {
                     userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Ready', { extraHeaders: [ 'X-CustomPresence: '+ cstmStNm ] });
                 }
            }
}
function sendOnThePhoneStatus() {           
            for (let b = 0; b < Buddies.length; b++) {
                 let crBuddyObj = Buddies[b];
                 if (crBuddyObj.type == "extension") {
                     userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'On the phone', { extraHeaders: [ 'X-CustomPresence: on_the_phone' ] });
                 }
            }
}
function sendAwayStatus() {           
            for (let b = 0; b < Buddies.length; b++) {
                 let crBuddyObj = Buddies[b];
                 if (crBuddyObj.type == "extension") {
                     userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Away', { extraHeaders: [ 'X-CustomPresence: away' ] });
                 }
            }
}
function sendDoNotDisturbStatus() {           
            for (let b = 0; b < Buddies.length; b++) {
                 let crBuddyObj = Buddies[b];
                 if (crBuddyObj.type == "extension") {
                     userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Do not disturb', { extraHeaders: [ 'X-CustomPresence: do_not_disturb' ] });
                 }
            }
}

function sendAutoAnswerStatus() {           
            for (let b = 0; b < Buddies.length; b++) {
                 let crBuddyObj = Buddies[b];
                 if (crBuddyObj.type == "extension") {
                     userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Autoanswer', { extraHeaders: [ 'X-CustomPresence: auto_answer' ] });
                 }
            }
}

function sendUnavailableStatus() {        
            for (let b = 0; b < Buddies.length; b++) {
                 let crBuddyObj = Buddies[b];
                 if (crBuddyObj.type == "extension") {
                     userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Unavailable', { extraHeaders: [ 'X-CustomPresence: unavailable' ] });
                 }
            }
}

// Show SMS window
function ShowSMSWindow() {

}

// Show Fax window
function ShowFaxWindow() {

      $(".streamSelected").each(function() { $(this).css("display", "none"); });
      $("#emailInLogoSection").css("color", "#626262");

      if ($("#sendFaxFrame").is(":visible")) {

          $("#sendFaxFrame").css("display", "none");
          $("#faxInLogoSection").css("color", "#626262");

      } else {

          if ($(window).innerWidth() >= 920) {
              $("#sendFaxFrame").css({ "display" : "block", "height" : $(window).innerHeight() - 12 +"px", "width" : "auto" });
          } else {
              $("#sendFaxFrame").css("display", "none");
          }

          $("#sendFaxFrame").empty();
          $("#faxInLogoSection").css("color", "#0ca366");
      }

      var faxbarcont = '<div id="srfax_vbar">';

      faxbarcont += '<div id="currentbaldiv"><div id="balanceText">Balance:</div><select id="selectPrvBalance"><option value="">Select</option><option value="telnyx">Telnyx</option><option value="phaxio">Phaxio</option></select><div id="currentbalance"></div><div class="balancetooltip"><div id="balancetipInfo">i</div><div class="balancetipText">Select a provider from the drop-down list to see the current balance. The balance will be rounded to 3 decimal places and the currency will be that of the respective account. Telnyx fax prices can be seen <a href="https://telnyx.com/pricing/fax" rel="noreferrer noopener" target="_blank" style="color:#1b60ba">here</a> and Phaxio prices <a href="https://www.phaxio.com/pricing/" rel="noreferrer noopener" target="_blank" style="color:#1b60ba">here</a>. After you send a fax, the balance will reflect the charge after a short delay.</div></div></div>';
      faxbarcont += '<div id="faxnumbersdiv">';
      faxbarcont += '<div id="pickid">Set ID:</div>';
      faxbarcont += '<select id="currentfaxnmbrs"></select>';
      faxbarcont += '<div class="fortooltipinit"><div id="infosetid">i</div>';
      faxbarcont += '<span class="calleridtooltip">The Caller ID is the "From" number that the receiver sees on the fax that you send. You can set as Caller ID any of your fax numbers. Unlike Telnyx, Phaxio also allows setting a blank Caller ID.</span></div></div>';
      faxbarcont += '<div  class="pfheader">Enter the recipient\'s number as:</div>';
      faxbarcont += '<div class="faxnbformat"><a id="countryCallCode" href="https://en.wikipedia.org/wiki/List_of_country_calling_codes#Alphabetical_order" target="_blank" rel="noreferrer noopener">CountryCallingCode</a><font style="color:#aa2e4b">FaxNumber</font></div>';
      faxbarcont += '<div class="fortooltip"><div id="infonumber">i</div><span class="fortooltiptext">';
      faxbarcont += 'For example, to send a fax to the German fax number 1212121212, enter 491212121212, where 49 is the country calling code of Germany. (For US the calling code is 1, for UK 44, for France 33, etc.) The <a href="https://en.wikipedia.org/wiki/List_of_country_calling_codes#Alphabetical_order" target="_blank" rel="noreferrer noopener" style="color:#1b60ba">country calling code</a> must be always included, even if the fax is sent locally. You don\'t need to add the "+" sign or "00" in front of the number, because the "+" sign will be added automatically. Telnyx allows sending one fax to one phone number at a time. Phaxio allows sending one fax to up to 15 numbers at once. If you use Phaxio, to send a fax to multiple fax numbers add the numbers one after the other separated by a comma, like this: 497777777777,442222222222</span></div>';
      faxbarcont += '<div id="faxtodiv"><input type="text" id="faxto" name="faxto" /></div>';
      faxbarcont += '<div class="pfheader">Choose the document(s) that you want to send as fax:</div><div class="fortooltipsec"><div id="infoformat">i</div><span class="fortooltiptextsec">';
      faxbarcont += 'For Telnyx, the supported format is pdf and you can send one file at a time. The pdf file cannot exceed 50 MB in size or 350 pages. For Phaxio, the supported formats are: pdf, odt, jpeg/jpg, png, txt, tiff/tif, html, doc, docx. With Phaxio you can send up to 20 files with a cumulative size of up to 20 MB and up to 200 pages, in one fax call. (docx and doc files can be sent as fax with Phaxio but cannot be previewed in the adjoining preview pane; these files should be previewed outside Roundpin before sending them.)</span></div>';
      faxbarcont += '<div id="filestotsize">Total files size:<div id="filessizetext"></div></div>';
      faxbarcont += '<div id="fileuploadednm" class="fileuploadstl"></div>';
      faxbarcont += '<div class="uploadfiledv">';
      faxbarcont += '<form id="upldFileToFax" enctype="multipart/form-data">';
      faxbarcont += '<div id="uploadFile">Upload file</div>';
      faxbarcont += '<input type="hidden" name="MAX_FILE_SIZE" value="786432000" />';
      faxbarcont += '<input type="hidden" name="username" value="'+ userName +'" />';
      faxbarcont += '<input type="hidden" name="s_ajax_call" value="'+ validateSToken +'" />';
      faxbarcont += '<input type="file" id="uploadfileforfax" class="fileupload" name="uploadedtofax" />';
      faxbarcont += '<label for="uploadfileforfax" id="uploadfileforfax"><i class="fa fa-upload" id="uploadFileToFax"></i></label>';
      faxbarcont += '<input type="submit" id="submitFileForFax" style="visibility:hidden" />';
      faxbarcont += '</form></div><div id="pf_upload_msg"></div>';
      faxbarcont += '<div id="pf_choose_msg"></div>';
      faxbarcont += '<input type="submit" id="submitfax" value="Send Fax" />';
      faxbarcont += '<div id="pf_submit_msg"></div>';
      faxbarcont += '<div id="dirbuttons">';
      faxbarcont += '<button id="receivedfaxdir" class="openfaxfolder">Received Faxes</button>';
      faxbarcont += '<button id="sentfaxdir" class="openfaxfolder">Sent Faxes</button>';
      faxbarcont += '<button id="receivedfaileddir" class="openfaxfolder">Failed Received Faxes</button>';
      faxbarcont += '<button id="sentfaileddir" class="openfaxfolder">Failed Sent Faxes</button></div></div>';
      faxbarcont += '</div>';
      faxbarcont += '<div id="sendFaxContent"><i id="centeredFaxIc" class="fa fa-fax"></i></div>';

      $("#sendFaxFrame").append(faxbarcont);

      // Show the available balance for the selected Fax provider
      $("#selectPrvBalance").change(function() {

        if ($(this).val() == "telnyx") {

            $('#currentbalance').append("<span id='getBalanceLoader'></span>");

            $.ajax({
                 type: "POST",
                 url: 'src/get-telnyx-balance.php',
                 dataType: "JSON",
                 data: {
                        username: userName,
                        s_ajax_call: validateSToken
                 },
                 success: function(respdata) {

                                   $("#getBalanceLoader").remove();
                                   if (respdata.messagetosend == "success") {
                                       $("#currentbalance").html("<div>"+ respdata.telnyxbalance +"</div>");
                                   } else { alert("Error while trying to get Telnyx balance!"); }
                 },
                 error: function() {
                                   $("#getBalanceLoader").remove();
                                   alert("Error while attempting to get Telnyx balance!");
                 }
            });

        } else if ($(this).val() == "phaxio") {

            $('#currentbalance').append("<span id='getBalanceLoader'></span>");

            $.ajax({
                 type: "POST",
                 url: 'src/get-phaxio-balance.php',
                 dataType: "JSON",
                 data: {
                        username: userName,
                        s_ajax_call: validateSToken
                 },
                 success: function(respdata) {

                                   $("#getBalanceLoader").remove();
                                   if (respdata.messagetosend == "success") {
                                       $("#currentbalance").html("<div>"+ respdata.phaxiobalance +"</div>");
                                   } else { alert("Error while trying to get Phaxio balance!"); }
                 },
                 error: function() {
                                   $("#getBalanceLoader").remove();
                                   alert("Error while attempting to get Phaxio balance!");
                 }
            });
        }

      });

      // Show the available fax numbers in the drop-down Caller ID list
      var allFaxNmbrs = phoneNumbersFdb['fax_numbers'];

      if (allFaxNmbrs != '') {

          var allFaxNmbrsArr = allFaxNmbrs.split(",");
          var defFaxNmbrFdb = phoneNumbersFdb['default_fax_number'];
          var allFaxNmbrsOpt = '';

          for (var f = 0; f < allFaxNmbrsArr.length; f++) {
               if ((f == 0 && defFaxNmbrFdb == '') || (defFaxNmbrFdb != '' && defFaxNmbrFdb == allFaxNmbrsArr[f])) {
                   allFaxNmbrsOpt += '<option value="'+ allFaxNmbrsArr[f] +'" selected="selected">'+ allFaxNmbrsArr[f] +'</option>';
               } else {
                   allFaxNmbrsOpt += '<option value="'+ allFaxNmbrsArr[f] +'">'+ allFaxNmbrsArr[f] +'</option>';
               }
          }
          allFaxNmbrsOpt += '<option value="phaxioblank">Ph: Blank Caller ID</option>';

          $("#currentfaxnmbrs").append(allFaxNmbrsOpt);
      }

      $("#sendFaxContent").css("width", $("#sendFaxFrame").width() - 336 +"px");
      $("#srfax_vbar").css("height", $("#sendFaxFrame").height() - 36 +"px");

      // Hide the fax window when clicking on a contact on the left panel
      $(".buddy,.buddySelected").click(function() { $("#sendFaxFrame").css("display", "none"); $("#faxInLogoSection").css("color", "#626262"); });


      let readtxtfile = [];
      let readhtmlfile = [];
      let readjpg = [];
      let readpng = [];
      let n = 1;
      let upfilestofaxarr = [];

      // Upload files to be faxed
      $("#uploadfileforfax").change(function(e) {
         $("#pf_upload_msg").append("<div>Uploading ...</div>");
         $('#sendFaxContent').append("<span id='sendFileLoader'></span>");
         $('#filestotsize').show();
         $("#submitFileForFax").click();
      });

      $("#upldFileToFax").on('submit', function(e) {

             e.preventDefault();

	     var currentflsize = $('#uploadfileforfax')[0].files[0].size;

	     var fileup = $('#uploadfileforfax').val();
	     var fileuptrim = fileup.split('\\').pop();

	     if ($.inArray(fileuptrim, upfilestofaxarr) != -1) {
		 var duplicatescheck = 1;
	     } else var duplicatescheck = 0;

	     var extension = fileup.replace(/^.*\./, '');

	     if (extension == fileup) {
		 extension = "";
	     } else {
		 extension = extension.toLowerCase();
	     }

	     var validExtensions = ["pdf", "doc", "docx", "tif", "tiff", "jpg", "jpeg", "odt", "txt", "html", "png"];

	     if ($.inArray(extension, validExtensions) != -1) {

	       if (duplicatescheck == 0) {

		 if (currentflsize < 20971520) {

		    if (n < 21) {

		       upfilestofaxarr.push(fileuptrim);

		       $.ajax({
		          type: 'POST',
		          url: 'src/upload-file-to-fax.php',
		          data: new FormData(this),
		          dataType: "JSON",
		          contentType: false,
		          cache: false,
		          processData:false,
		          success: function(respdata) {

		                   $("#sendFileLoader").remove();
		                   $("#pf_upload_msg").empty();

		                   if (respdata.error != '') { 
		                       alert("Error: " + respdata.error);
		                   }

				   let totalflsize = respdata.totalflsize;

				   $('#filessizetext').text(totalflsize);

				   // Check if the total file size is over 20 MB
				   let rectotfilesz = parseFloat(totalflsize.replace(" MB", ""));

				   if (rectotfilesz > 20.00) {
				       $('#filestotsize').css('color', '#ba3555');
				       alert("Error ! You can't send files having a total size of more than 20 MB in one fax call ! Please consider removing files so that the total files size would drop to or below 20 MB");
				       $('#submitfax').attr("disabled", true);
				   } else {
				       $('#submitfax').attr("disabled", false);
				       $('#filestotsize').css('color', '#1eb16a');
				   }


				   $('#fileuploadednm').append("<div class='indgenflcls'><div id='"+n+"indfilediv' class='indivflcls'>" + n + ") " + fileuptrim + "</div><div class='indupfldl' title='Remove this file'><i class='fa fa-times'></i></div></div>");
				   n++;

				   // Preview the file when you click on its name
                                   $(".indivflcls").each(function() {

				        $(this).on("click", function(eventclck) {

				           let faxfilenameinit = $(this).text();
                                           let faxfilename = faxfilenameinit.split(" ")[1];

				           let baseUrlnc = 'fax/temp_files/'+ userName +'/'+ faxfilename;
				           let extenlst = faxfilename.replace(/^.*\./, '');

				           if (extenlst == faxfilename) {
				               extenlst = "";
				           } else {
				               extenlst = extenlst.toLowerCase();
				           }

				           if (extenlst == 'pdf') {

				                  var iframeprv = $('<iframe id="docviewernc" style="display:block;position:relative;top:0;left:0;z-index:10;" src="' + baseUrlnc + '" sandbox="allow-scripts allow-same-origin allow-popups allow-modals"></iframe>');
                                                  iframeprv.css({ "width" : $("#sendFaxFrame").width() - $("#srfax_vbar").width() - 64, "height" : $("#sendFaxFrame").height() });
				                  $("#sendFaxContent").empty();
				                  $("#sendFaxContent").append(iframeprv);
				           } else if (extenlst == 'txt') {
				                  $("#sendFaxContent").empty();
				                  $("#sendFaxContent").append(readtxtfile[faxfilename]);
				           } else if (extenlst == 'html') {
				                  $("#sendFaxContent").empty();
				                  $("#sendFaxContent").append(readhtmlfile[faxfilename]);
				           } else if (extenlst == 'jpg' || extenlst == 'jpeg') {
				                  $("#sendFaxContent").empty();
				                  $("#sendFaxContent").append('<img style="width:100%;height:100%" id="uploadedjpg" src="#" />');
				                  $("#uploadedjpg").attr('src', readjpg[faxfilename]);
				           } else if (extenlst == 'png') {
				                  $("#sendFaxContent").empty();
				                  $("#sendFaxContent").append('<img style="width:100%;height:100%" id="uploadedpng" src="#" />');
				                  $("#uploadedpng").attr('src', readpng[faxfilename]);
				           } else if (extenlst == 'odt') {
				                  $("#sendFaxContent").empty();
				                  function odfInit() {
				                     var odfelementnc = document.getElementById("sendFaxContent");
				                     var odfcanvas = new odf.OdfCanvas(odfelementnc);
				                     odfcanvas.load(baseUrlnc);
				                  }
				                  window.setTimeout(odfInit, 0);
				           } else if (extenlst == 'tif' || extenlst == 'tiff') {
				                  let tiffile = 'fax/temp_files/'+ userName +'/'+ faxfilename;
				                  let xhr = new XMLHttpRequest();
				                  xhr.responseType = 'arraybuffer';
				                  xhr.open('GET', tiffile);
				                  xhr.onload = function (evnt) {
				                      let tiff = new Tiff({buffer: xhr.response});
				                      let tifcanvas = tiff.toCanvas();
				                      $("#sendFaxContent").empty();
				                      $("#sendFaxContent").append(tifcanvas);
				                  };
				                  xhr.send();
				           }

                                           $("#sendFaxContent").css("width", $("#sendFaxFrame").width() - $("#srfax_vbar").width() - 64 +"px");

				           $("#sendFaxContent").show();
				        });
				   });

				   // Remove uploaded files
				   $(".indupfldl").last().click(function() {

				           let removedfilenameinit = $(this).closest(".indgenflcls").find(".indivflcls").text();
				           let removedfilesplit = removedfilenameinit.split(") ");
				           let removedflnb = parseInt(removedfilesplit[0]);
                                           let removedflname = removedfilesplit[1];

				           $(this).closest(".indgenflcls").find(".indivflcls").remove();
				           $(this).remove();

				           upfilestofaxarr = $.grep(upfilestofaxarr, function(value) {
				                             return value != removedflname;
				           });

				           $.ajax({
				                   type: "POST",
		                                   url: 'src/remove-uploaded-file-for-fax.php',
                                                   dataType: "JSON",
				                   data: {
                                                          username: userName,
                                                          removedfilename: removedflname,
                                                          s_ajax_call: validateSToken
                                                   },
				                   success: function(respdata) {

                                                      if (respdata.result != 'success') { alert("Error while removing the file!"); }

                                                      let totalflsize = respdata.totalflsize;
				                      $('#filessizetext').text(totalflsize);

				                      // Check file size
				                      let rectotfilesz = parseFloat(totalflsize.replace(" MB", ""));

				                      if (rectotfilesz > 20.00) {
				                          $('#filestotsize').css('color', '#ba3555');
				                             alert("Error ! You can't send files having a total size of more than 20 MB in one fax call ! Please consider removing files so that the total files size would drop to or below 20 MB");
				                          $('#submitfax').attr("disabled", true);
				                      } else {
				                          $('#submitfax').attr("disabled", false);
				                          $('#filestotsize').css('color', '#1eb16a');
				                      }

				                      // Rewrite order numbers for files that follow
				                      --n;
				                      $("div:visible[id*='indfilediv']").each(function() {

				                          let getdivtext = $(this).text();
				                          let splitdivtext = getdivtext.split(") ");
				                          let currentnbvalue = parseInt(splitdivtext[0]);
				                          let newnbvalue = currentnbvalue - 1;

				                          if (currentnbvalue > removedflnb) {
				                              let replacedstr = newnbvalue + ") " + splitdivtext[1];
				                              $(this).text(replacedstr);
				                          }
				                      });
				                   },
				                   error: function() {
				                              alert('Error while attempting to remove the file!');
				                   }
				           });

		                           $("#sendFileLoader").remove();
				           $("#sendFaxContent").empty();
				   });

				   // Preview the uploaded file
		                   let faxfilename = $('#uploadfileforfax')[0].files[0].name;

		                   let baseUrlprv = 'fax/temp_files/'+ userName +'/'+ faxfilename;

				   if (extension == 'pdf') {
				              let iframepdf = $('<iframe id="docviewerprv" style="display:block;position:relative;top:0;left:0;z-index:9;" src="' + baseUrlprv + '" sandbox="allow-scripts allow-same-origin allow-popups allow-modals"></iframe>');
                                              iframepdf.css({ "width" : $("#sendFaxFrame").width() - $("#srfax_vbar").width() - 64, "height" : $("#sendFaxFrame").height() });
				              $("#sendFaxContent").empty();
				              $("#sendFaxContent").append(iframepdf);
				   } else if (extension == 'txt') {
				              let reader = new FileReader();
		                              reader.readAsText($('#uploadfileforfax')[0].files[0]);
				              reader.onload = function(e) {
				                  readtxtfile[faxfilename] = e.target.result.replace(/(?:\r\n|\r|\n)/g, '<br>');
				                  $("#sendFaxContent").empty();
				                  $("#sendFaxContent").append(readtxtfile[faxfilename]);
				              };
				   } else if (extension == 'html') {
				              let reader = new FileReader();
		                              reader.readAsText($('#uploadfileforfax')[0].files[0]);
				              reader.onload = function(e) {
				                  readhtmlfile[faxfilename] = e.target.result.replace(/(?:\r\n|\r|\n)/g, '<br>');
				                  $("#sendFaxContent").empty();
				                  $("#sendFaxContent").append(readhtmlfile[faxfilename]);
				              };
				   } else if (extension == 'jpg' || extension == 'jpeg') {
				              $("#sendFaxContent").empty();
				              $("#sendFaxContent").append('<img style="width:100%;height:100%" id="uploadedjpg" src="#" />');

				              let reader = new FileReader();
				              reader.onload = function (e) {
				                  readjpg[faxfilename] = e.target.result;
				                  $("#uploadedjpg").attr('src', readjpg[faxfilename]);
				              }
		                              reader.readAsDataURL($('#uploadfileforfax')[0].files[0]);
				   } else if (extension == 'png') {
				              $("#sendFaxContent").empty();
				              $("#sendFaxContent").append('<img style="width:100%;height:100%" id="uploadedpng" src="#" />');

				              let reader = new FileReader();
				              reader.onload = function (e) {
				                  readpng[faxfilename] = e.target.result;
				                  $("#uploadedpng").attr('src', readpng[faxfilename]);
				              }
		                              reader.readAsDataURL($('#uploadfileforfax')[0].files[0]);
				   } else if (extension == 'odt') {
				              $("#sendFaxContent").empty();
				              function odfInit() {
				                 var odfelement = document.getElementById("sendFaxContent");
				                 var odfcanvas = new odf.OdfCanvas(odfelement);
				                 odfcanvas.load(baseUrlprv);
				              }
				              window.setTimeout(odfInit, 0);
				   } else if (extension == 'tif' || extension == 'tiff') {
				              let tiffile = 'fax/temp_files/'+ userName +'/'+ faxfilename;
				              let xhr = new XMLHttpRequest();
				              xhr.responseType = 'arraybuffer';
				              xhr.open('GET', tiffile);
				              xhr.onload = function (e) {
				                  let tiff = new Tiff({buffer: xhr.response});
				                  let tifcanvas = tiff.toCanvas();
				                  $("#sendFaxContent").empty();
				                  $("#sendFaxContent").append(tifcanvas);
				              };
				              xhr.send();
				   }

                                   $("#sendFaxContent").css("width", $("#sendFaxFrame").width() - $("#srfax_vbar").width() - 64 +"px");

				   $("#sendFileLoader").remove();
				   $('#pf_upload_msg').hide();
		         },
		         error: function() {
		                    $("#sendFileLoader").remove();
		                    $('#pf_upload_msg').hide();
		                    alert("An error occurred while uploading the file!");
		         }          
		       });


		    } else {
		          alert("Error ! You can't send as fax more than 20 files in one fax call !");
                          $("#sendFileLoader").remove();
		          $('#pf_upload_msg').hide();
		    }
		 } else {
		      alert("Error ! You can't send files having a total size of more than 20 MB in one fax call !");
                      $("#sendFileLoader").remove();
		      $('#pf_upload_msg').hide();
		 }
	       } else {
		    alert("Error ! There is already a file named '"+fileuptrim+"' in the 'Pax_Fax/temp_files' folder !");
		    $('#pf_upload_msg').hide();
                    $("#sendFileLoader").remove();
	       }

	     } else {
		  alert('The file type is not supported ! Supported formats are: pdf, doc, docx, tiff/tif, jpeg/jpg, odt, txt, html, png.');
		  $('#pf_upload_msg').hide();
                  $("#sendFileLoader").remove();
	     }

   });

}

// Show Email window
function ShowEmailWindow() {

   if (getDbItem("useRoundcube", "") == 1) {

      $("#sendFaxFrame").css("display", "none");
      $("#faxInLogoSection").css("color", "#626262");
      $("#roundcubeFrame").remove();
      $("#rightContent").show();
      $(".streamSelected").each(function() { $(this).css("display", "none"); });
      $("#rightContent").append('<iframe id="roundcubeFrame" name="displayFrame"></iframe>');

      var rcDomain = '';
      var rcBasicAuthUser = '';
      var rcBasicAuthPass = '';
      var rcUsername = '';
      var rcPasswd = '';

      $.ajax({
             type: "POST",
             url: "src/get-email-info.php",
             dataType: "JSON",
             data: {
                     username: userName,
                     s_ajax_call: validateSToken
             },
             success: function(datafromdb) {

                               rcDomain = datafromdb.rcdomain;
                               rcBasicAuthUser = encodeURIComponent(datafromdb.rcbasicauthuser);
                               rcBasicAuthPass = encodeURIComponent(datafromdb.rcbasicauthpass);
                               rcUsername = datafromdb.rcuser;
                               rcPasswd = datafromdb.rcpassword;

			       if (rcBasicAuthUser != '' && rcBasicAuthPass != '') { 
				   var actionURL = "https://"+ rcBasicAuthUser +":"+ rcBasicAuthPass +"@"+ rcDomain +"/"; 
			       } else { var actionURL = "https://"+ rcDomain +"/"; }

			       var form = '<form id="rcForm" method="POST" action="'+ actionURL +'" target="displayFrame">'; 
			       form += '<input type="hidden" name="_action" value="login" />';
			       form += '<input type="hidden" name="_task" value="login" />';
			       form += '<input type="hidden" name="_autologin" value="1" />';
			       form += '<input name="_user" value="'+ rcUsername +'" type="text" />';
			       form += '<input name="_pass" value="'+ rcPasswd +'" type="password" />';
			       form += '<input id="submitButton" type="submit" value="Login" />';
			       form += '</form>';

			       $("#roundcubeFrame").append(form);

			       if (RCLoginCheck == 0) {
				   $("#submitButton").click();
				   RCLoginCheck = 1;
			       } else { $("#roundcubeFrame").attr("src", actionURL); }

                               rcBasicAuthPass = '';
             },
             error: function(datafromdb) {
                             alert("An error occurred while trying to retrieve email related data from the database!");
             }
      });

      if ($("#roundcubeFrame").is(":visible")) { $("#emailInLogoSection").css("color", "#0ca366"); } else { $("#emailInLogoSection").css("color", "#626262"); }

   } else { alert("Email integration is not enabled ! You can enable Roundcube email integration by clicking on the 'Settings' wheel on the left panel > 'Settings' > 'Email' > 'Enable Roundcube email integration'"); }
}

// Shrink the left panel
function CollapseLeftPanel() {
    if ($(window).innerWidth() >= 920) {
        if ($("#leftContent").hasClass("shrinkLeftContent")) {
            $("#leftContent").removeClass("shrinkLeftContent");

            $("#sendFaxFrame").css("left", "320px");
            $("#sendFaxContent").css("width", $("#sendFaxContent").width() - 280 +"px");
            $("#centeredFaxIc").css({ "left" : "50%", "margin-left" : "-55px" });

            $("#rightContent").removeClass("widenRightContent");
            $("#shrinkLeftPanel").attr("title", lang.collapse_left_panel);
            $("#collapseLeftPanel").mouseenter(function() { $(this).css("background-image", "url('images/collapse-dark.svg')") });
            $("#collapseLeftPanel").mouseleave(function() { $(this).css("background-image", "url('images/collapse.svg')") });
        } else {
            $("#leftContent").addClass("shrinkLeftContent");

            $("#sendFaxFrame").css("left", "40px");
            $("#sendFaxContent").css("width", $("#sendFaxContent").width() + 280 +"px");
            $("#centeredFaxIc").css({ "left" : "50%", "margin-left" : "-55px" });

            $("#rightContent").addClass("widenRightContent");
            $("#shrinkLeftPanel").attr("title", lang.expand_left_panel);
            $("#collapseLeftPanel").mouseenter(function() { $(this).css("background-image", "url('images/collapse-reverse-dark.svg')") });
            $("#collapseLeftPanel").mouseleave(function() { $(this).css("background-image", "url('images/collapse-reverse-dark.svg')") });
        }
    }
}

// Show the 'About' window
function ShowAboutWindow() {

   $.jeegoopopup.close();

   var aboutHtml = '<div>';
   aboutHtml += '<div id="windowCtrls"><img id="minimizeImg" src="images/1_minimize.svg" title="Restore" /><img id="maximizeImg" src="images/2_maximize.svg" title="Maximize" /><img id="closeImg" src="images/3_close.svg" title="Close" /></div>';
   aboutHtml += '<div class="UiWindowField scroller">';
   aboutHtml += '<div><img id="AboutLogoImg" src="images/login-logo.svg"/></div>';
   aboutHtml += '<div id="aboutPopup">'+lang.about_text+'</div>';
   aboutHtml += '</div></div>';

   $.jeegoopopup.open({
                title: 'About Roundpin',
                html: aboutHtml,
                width: '640',
                height: '500',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
   });

   $("#jg_popup_b").append('<button id="ok_button">'+ lang.ok +'</button>');

   var maxWidth = $(window).width() - 12;
   var maxHeight = $(window).height() - 88;

   if (maxWidth < 656 || maxHeight < 500) { 
       $.jeegoopopup.width(maxWidth).height(maxHeight);
       $.jeegoopopup.center();
       $("#maximizeImg").hide();
       $("#minimizeImg").hide();
   } else { 
       $.jeegoopopup.width(640).height(500);
       $.jeegoopopup.center();
       $("#minimizeImg").hide();
       $("#maximizeImg").show();
   }

   $(window).resize(function() {
       maxWidth = $(window).width() - 12;
       maxHeight = $(window).height() - 88;

       $.jeegoopopup.center();
       if (maxWidth < 656 || maxHeight < 500) { 
           $.jeegoopopup.width(maxWidth).height(maxHeight);
           $.jeegoopopup.center();
           $("#maximizeImg").hide();
           $("#minimizeImg").hide();
       } else { 
           $.jeegoopopup.width(640).height(500);
           $.jeegoopopup.center();
           $("#minimizeImg").hide();
           $("#maximizeImg").show();
       }
   });

   
   $("#minimizeImg").click(function() { $.jeegoopopup.width(640).height(500); $.jeegoopopup.center(); $("#maximizeImg").show(); $("#minimizeImg").hide(); });
   $("#maximizeImg").click(function() { $.jeegoopopup.width(maxWidth).height(maxHeight); $.jeegoopopup.center(); $("#minimizeImg").show(); $("#maximizeImg").hide(); });

   $("#closeImg").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); });
   $("#ok_button").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); });
   $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); });
   $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); $("#jg_popup_b").empty(); } });
}

// Change page title on incoming calls
function changePageTitle() {
    if ($(document).attr("title") == "Roundpin") { $(document).prop("title", "New call !!!"); } else { $(document).prop("title", "Roundpin"); }
    if (document.hasFocus()) {
        $(document).prop("title", "Roundpin");
        return;
    } else { setTimeout(changePageTitle, 460); }
}

// Window and Document Events
// ==========================
$(window).on("beforeunload", function() {
    Unregister();
});

$(window).on("resize", function() {
    UpdateUI();
});

// User Interface
// ==============
function UpdateUI() {

    if ($(window).outerWidth() < 920) {
        // Narrow Layout
        if (selectedBuddy == null & selectedLine == null) {
            // Nobody Selected
            $("#rightContent").hide();

            $("#leftContent").css("width", "100%");
            $("#leftContent").show();
        } else {
            $("#rightContent").css("margin-left", "0px");
            $("#rightContent").show();

            $("#leftContent").hide();

            if(selectedBuddy != null) updateScroll(selectedBuddy.identity);
        }
    } else {
        // Wide Screen Layout
        if (selectedBuddy == null & selectedLine == null) {
            $("#leftContent").css("width", "320px");
            $("#rightContent").css("margin-left", "0px");
            $("#leftContent").show();
            $("#rightContent").hide();
        }
        else{
            $("#leftContent").css("width", "320px");
            $("#rightContent").css("margin-left", "320px");
            $("#leftContent").show();
            $("#rightContent").show();

            if(selectedBuddy != null) updateScroll(selectedBuddy.identity);
        }
    }

    for (var l = 0; l<Lines.length; l++) {
        updateLineScroll(Lines[l].LineNumber);
    }
}

// UI Windows
// ==========
function AddSomeoneWindow() {

    $("#userMenu").hide();
    $.jeegoopopup.close();

    var html = "<div id='AddNewContact'>";

    html += "<div id='windowCtrls'><img id='minimizeImg' src='images/1_minimize.svg' title='Restore' /><img id='maximizeImg' src='images/2_maximize.svg' title='Maximize' /><img id='closeImg' src='images/3_close.svg' title='Close' /></div>";

    html += "<div class='UiWindowField scroller' style='height:500px;overflow-x:hidden;'>";

    html += "<div class=UiText>"+ lang.display_name +":</div>";
    html += "<div><input id=AddSomeone_Name class=UiInputText type=text placeholder='"+ lang.eg_display_name +"'></div>";

    html += "<div class='UiText'>"+ lang.title_description +":</div>";
    html += "<div><input id='AddSomeone_Desc' class='UiInputText' type='text' placeholder='"+ lang.eg_general_manager +"'></div>";

    html += "<div class='UiText'>Long Description:</div>";
    html += "<div><input id='AddSomeoneLongDesc' class='UiInputText' type='text' placeholder='Eg: Marketing specialist, contact person for Global Beavers Inc.'></div>";

    html += "<div class='UiText'>Address - Street:</div>";
    html += "<div><input id='addressStreet' class='UiInputText' type='text' placeholder='Eg: Spring Street 157'></div>";

    html += "<div class='UiText'>Address - Zip:</div>";
    html += "<div><input id='addressZip' class='UiInputText' type='text' placeholder='Eg: 77294'></div>";

    html += "<div class='UiText'>Address - Town:</div>";
    html += "<div><input id='addressTown' class='UiInputText' type='text' placeholder='Eg: Houston'></div>";

    html += "<div class='UiText'>Address - Country:</div>";
    html += "<div><input id='addressCountry' class='UiInputText' type='text' placeholder='Eg: United States of America'></div>";

    html += "<div class='UiText'>Address - State/Province:</div>";
    html += "<div><input id='addressState' class='UiInputText' type='text' placeholder='Eg: Texas'></div>";

    html += "<div class=UiText>"+ lang.internal_subscribe_extension +":</div>";

    html += "<div><input id=AddSomeone_Exten class=UiInputText type=text placeholder='"+ lang.eg_internal_subscribe_extension +"'></div>";

    html += "<div class=UiText>"+ lang.mobile_number +":</div>";
    html += "<div><input id=AddSomeone_Mobile class=UiInputText type=text placeholder='"+ lang.eg_mobile_number +"'></div>";

    html += "<div class=UiText>"+ lang.contact_number_1 +":</div>";

    html += "<div><input id=AddSomeone_Num1 class=UiInputText type=text placeholder='"+ lang.eg_contact_number_1 +"'></div>";

    html += "<div class=UiText>"+ lang.contact_number_2 +":</div>";
    html += "<div><input id=AddSomeone_Num2 class=UiInputText type=text placeholder='"+ lang.eg_contact_number_2 +"'></div>";

    html += "<div class='UiText'>Fax Number:</div>";
    html += "<div><input id='AddSomeone_Fax' class='UiInputText' type='text' placeholder='Eg: +492576543059'></div>";

    html += "<div class='UiText'>"+ lang.email +":</div>";
    html += "<div><input id='AddSomeone_Email' class='UiInputText' type='text' placeholder='"+ lang.eg_email +"'></div>";

    html += "<div class='UiText'>Add new contact to the following groups:</div>";
    html += "<div><select id='AddContactGroupsOpt' class='contactGroupsSelect'>";
    html += "<option value=''>Click to choose one or more groups from the list</option>";
             for (var g = 0; g < allGroups.length; g++) {
                  html += "<option value='"+ allGroups[g] +"'>"+ allGroups[g] +"</option>"; 
             }
    html += "</select></div>";
    html += "<div id='AddContactGroups' class='addGroupsToContact'></div>";

    html += "<br><br></div></div>"

    $.jeegoopopup.open({
                title: 'Add Contact',
                html: html,
                width: '640',
                height: '500',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                contentClass: 'addContactPopup',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $("#jg_popup_b").append("<div id=bottomButtons><button id=save_button>Save</button><button id=cancel_button>Cancel</button></div>");


    // Add new contact to groups
    $("#AddContactGroupsOpt").change(function() {
       var selectedGroup = $("#AddContactGroupsOpt").val();
       if (selectedGroup != '') {
           var slctCheck = 0;
           $(".slctdGroupText").each(function() {
             if ($(this).text() == selectedGroup) { slctCheck++; }
           });
           if (slctCheck == 0) { 
               $("#AddContactGroups").append("<div class='slctdGroup'><div class='slctdGroupText'>"+ selectedGroup +"</div><div class='removeGroup' title='Remove this group'><i class='fa fa-times'></i></div></div>"); 
               // Remove group from list
               $(".removeGroup").last().click(function() { $(this).closest(".slctdGroup").remove(); });
           }
       }
    });


    // Save new contact
    $("#save_button").click(function() {

      var currentASName = $("#AddSomeone_Name").val();

      if (currentASName != null && currentASName.trim() !== '') {

        if (/^[A-Za-z0-9\s\-\'\.\(\)]+$/.test(currentASName)) {

	   var currentDesc = $("#AddSomeone_Desc").val();
	   if (currentDesc != null && currentDesc.trim() !== '') {
	       if (/^[A-Za-z0-9\s\-\_\.\,\'\"\(\)\!\?\@\%\&\*]+$/.test(currentDesc)) { var finCurrentDesc = currentDesc; } else { 
		   var finCurrentDesc = ''; alert('The title/description that you entered is not valid!'); }
	   } else { var finCurrentDesc = ''; }

	   var currentLgDesc = $("#AddSomeoneLongDesc").val();
	   if (currentLgDesc != null && currentLgDesc.trim() !== '') {
	       if (/^[A-Za-z0-9\s\-\_\.\,\'\"\(\)\!\?\@\%\&\*]+$/.test(currentLgDesc)) { var finCurrentLgDesc = currentLgDesc; } else { 
		   var finCurrentLgDesc = ''; alert('The long description that you entered is not valid!'); }
	   } else { var finCurrentLgDesc = ''; }

	   var addressStreet = $("#addressStreet").val();
	   if (addressStreet != null && addressStreet.trim() !== '') {
	       if (/^[A-Za-z0-9\s\-\_\.\,\'\"\(\)\#\%\&]+$/.test(currentLgDesc)) { var finaddressStreet = addressStreet; } else { 
		   var finaddressStreet = ''; alert('The street address that you entered is not valid!'); }
	   } else { var finaddressStreet = ''; }

	   var addressZip = $("#addressZip").val();
	   if (addressZip != null && addressZip.trim() !== '') {
	       if (/^[A-Za-z0-9\s\-\_\.\(\)\#\&]+$/.test(addressZip)) { var finaddressZip = addressZip; } else { 
		   var finaddressZip = ''; alert('The zip code that you entered is not valid!'); }
	   } else { var finaddressZip = ''; }

	   var addressTown = $("#addressTown").val();
	   if (addressTown != null && addressTown.trim() !== '') {
	       if (/^[A-Za-z0-9\s\-\_\.\,\(\)\&]+$/.test(addressTown)) { var finaddressTown = addressTown; } else { 
		   var finaddressTown = ''; alert('The town that you entered is not valid!'); }
	   } else { var finaddressTown = ''; }

	   var addressCountry = $("#addressCountry").val();
	   if (addressCountry != null && addressCountry.trim() !== '') {
	       if (/^[A-Za-z0-9\s\-\.\,\(\)\&]+$/.test(addressCountry)) { var finaddressCountry = addressCountry; } else { 
		   var finaddressCountry = ''; alert('The country that you entered is not valid!'); }
	   } else { var finaddressCountry = ''; }

	   var addressState = $("#addressState").val();
	   if (addressState != null && addressState.trim() !== '') {
	       if (/^[A-Za-z0-9\s\-\.\,\(\)\&]+$/.test(addressState)) { var finaddressState = addressState; } else { 
		   var finaddressState = ''; alert('The state/province that you entered is not valid!'); }
	   } else { var finaddressState = ''; }

	   var currentExtension = $("#AddSomeone_Exten").val();
	   if (currentExtension != null && currentExtension.trim() !== '') {
	       if (/^[a-zA-Z0-9\*\#]+$/.test(currentExtension)) { var finCurrentExtension = currentExtension; } else {
		   var finCurrentExtension = ''; alert("The extension that you entered in the 'Extension (Internal)' field is not valid. The extension should contain only numbers, letters, asterisks and number signs and should have less than 51 characters."); }
	   } else { var finCurrentExtension = ''; }

	   var currentMobile = $("#AddSomeone_Mobile").val();
	   if (currentMobile != null && currentMobile.trim() !== '') {
	       if (/^[0-9\s\+\-\.\#]+$/.test(currentMobile)) { var finCurrentMobile = currentMobile; } else {
		   var finCurrentMobile = ''; alert("The phone number that you entered in the 'Mobile Number' field is not valid! The only allowed characters are: digits, spaces, plus signs, hyphens, dots and number signs."); }
	   } else { var finCurrentMobile = ''; }

	   var currentNum1 = $("#AddSomeone_Num1").val();
	   if (currentNum1 != null && currentNum1.trim() !== '') {
	       if (/^[0-9\s\+\-\.\#]+$/.test(currentNum1)) { var finCurrentNum1 = currentNum1; } else {
		   var finCurrentNum1 = ''; alert("The phone number that you entered in the 'Contact Number 1' field is not valid! The only allowed characters are: digits, spaces, plus signs, hyphens, dots and number signs."); }
	   } else { var finCurrentNum1 = ''; }

	   var currentNum2 = $("#AddSomeone_Num2").val();
	   if (currentNum2 != null && currentNum2.trim() !== '') {
	       if (/^[0-9\s\+\-\.\#]+$/.test(currentNum2)) { var finCurrentNum2 = currentNum2; } else {
		   var finCurrentNum2 = ''; alert("The phone number that you entered in the 'Contact Number 2' field is not valid! The only allowed characters are: digits, spaces, plus signs, hyphens, dots and number signs."); }
           } else { var finCurrentNum2 = ''; }

	   var currentFax = $("#AddSomeone_Fax").val();
	   if (currentFax != null && currentFax.trim() !== '') {
	       if (/^[0-9\s\+\-\.\#]+$/.test(currentFax)) { var finCurrentFax = currentFax; } else {
		   var finCurrentFax = ''; alert("The phone number that you entered in the 'Fax Number' field is not valid! The only allowed characters are: digits, spaces, plus signs, hyphens, dots and number signs."); }
           } else { var finCurrentFax = ''; }

	   var currentEmail = $("#AddSomeone_Email").val();
	   if (currentEmail != null && currentEmail.trim() !== '') {
	       if (/^[A-Za-z0-9\_\.\-\~\%\+\!\?\&\*\^\=\#\$\{\}\|\/]+@[A-Za-z0-9\.\-]+\.[A-Za-z0-9\-]{2,63}$/.test(currentEmail)) { var finCurrentEmail = currentEmail; } else {
		   var finCurrentEmail = ''; alert("The email that you entered is not a valid email address!"); }
	   } else { var finCurrentEmail = ''; }

           var allSlctGroups = "";
           $("#AddContactGroups .slctdGroupText").each(function() { allSlctGroups += "|"+ $(this).text(); });
           if (allSlctGroups != "") { var finSlctGroupspre = allSlctGroups +"|"; } else { var finSlctGroupspre = ""; }

	   if (finSlctGroupspre.trim() !== '') {
	       if (/^[a-zA-Z0-9\s\-\|]+$/.test(finSlctGroupspre)) { var finSlctGroups = finSlctGroupspre; } else { 
		   var finSlctGroups = ''; alert('The group that you entered is not valid!'); }
	   } else { var finSlctGroups = ''; }

           // Add Contact / Extension
           if (finCurrentExtension == '') {
               // Add Regular Contact
               var id = uID();
               var dateNow = utcDateNow();
               var crntDate = moment().format('YYYY-MM-DD HH:mm:ss');

               contactsObj.DataCollection.push(
                   {
                    Type: "contact",
                    LastActivity: dateNow,
                    ExtensionNumber: "",
                    MobileNumber: finCurrentMobile,
                    ContactNumber1: finCurrentNum1,
                    ContactNumber2: finCurrentNum2,
                    uID: null,
                    cID: id,
                    DisplayName: currentASName,
                    Position: "",
                    Description: finCurrentDesc,
                    LongDescription: finCurrentLgDesc,
                    AddressStreet: finaddressStreet,
                    AddressZip: finaddressZip,
                    AddressTown: finaddressTown,
                    AddressCountry: finaddressCountry,
                    AddressState: finaddressState,
                    Fax: finCurrentFax,
                    Email: finCurrentEmail,
                    Groups: finSlctGroups,
                    DateAdded: crntDate,
                    DateModified: crntDate,
                    MemberCount: 0
                   }
               );
               var newPerson = [];
               newPerson = [currentASName, finCurrentDesc, finCurrentLgDesc, finaddressStreet, finaddressZip, finaddressTown, finaddressCountry, finaddressState, "", finCurrentMobile, finCurrentNum1, finCurrentNum2, finCurrentFax, finCurrentEmail, finSlctGroups, crntDate, crntDate];
               saveContactToSQLDB(newPerson);

               var buddyObj = new Buddy("contact", id, currentASName, "", finCurrentMobile, finCurrentNum1, finCurrentNum2, dateNow, finCurrentDesc, finCurrentFax, finCurrentEmail, finSlctGroups);
               AddBuddy(buddyObj, false, false, false);

           } else {
               // Add Extension
               var id = uID();
               var dateNow = utcDateNow();
               var crntDate = moment().format('YYYY-MM-DD HH:mm:ss');

               contactsObj.DataCollection.push(
                   {
                    Type: "extension",
                    LastActivity: dateNow,
                    ExtensionNumber: finCurrentExtension,
                    MobileNumber: finCurrentMobile,
                    ContactNumber1: finCurrentNum1,
                    ContactNumber2: finCurrentNum2,
                    uID: id,
                    cID: null,
                    DisplayName: currentASName,
                    Position: finCurrentDesc,
                    Description: "",
                    LongDescription: finCurrentLgDesc,
                    AddressStreet: finaddressStreet,
                    AddressZip: finaddressZip,
                    AddressTown: finaddressTown,
                    AddressCountry: finaddressCountry,
                    AddressState: finaddressState,
                    Fax: finCurrentFax,
                    Email: finCurrentEmail,
                    Groups: finSlctGroups,
                    DateAdded: crntDate,
                    DateModified: crntDate,
                    MemberCount: 0
                   }
               );

               var newPerson = [];

               newPerson = [currentASName, finCurrentDesc, finCurrentLgDesc, finaddressStreet, finaddressZip, finaddressTown, finaddressCountry, finaddressState, finCurrentExtension, finCurrentMobile, finCurrentNum1, finCurrentNum2, finCurrentFax, finCurrentEmail, finSlctGroups, crntDate, crntDate];
               saveContactToSQLDB(newPerson);

               var buddyObj = new Buddy("extension", id, currentASName, finCurrentExtension, finCurrentMobile, finCurrentNum1, finCurrentNum2, dateNow, finCurrentDesc, finCurrentFax, finCurrentEmail, finSlctGroups);
               AddBuddy(buddyObj, false, false, true);

           }
           // Update Size:
           contactsObj.TotalRows = contactsObj.DataCollection.length;

           // Save To Local DB
           UpdateBuddyList();

           $.jeegoopopup.close();
           $("#jg_popup_b").empty();

        } else { alert('The display name that you entered is not valid. The display name should contain only alphanumeric characters, spaces, plus signs, single quotation marks, hyphens, square and round brackets !'); }

      } else { alert("'Display Name' cannot be empty!"); }
       
   });

   var maxWidth = $(window).width() - 12;
   var maxHeight = $(window).height() - 110;

   if (maxWidth < 656 || maxHeight < 500) { 
       $.jeegoopopup.width(maxWidth).height(maxHeight);
       $.jeegoopopup.center();
       $("#maximizeImg").hide();
       $("#minimizeImg").hide();
   } else { 
       $.jeegoopopup.width(640).height(500);
       $.jeegoopopup.center();
       $("#minimizeImg").hide();
       $("#maximizeImg").show();
   }

   $(window).resize(function() {
       maxWidth = $(window).width() - 16;
       maxHeight = $(window).height() - 110;
       $.jeegoopopup.center();
       if (maxWidth < 656 || maxHeight < 500) { 
           $.jeegoopopup.width(maxWidth).height(maxHeight);
           $.jeegoopopup.center();
           $("#maximizeImg").hide();
           $("#minimizeImg").hide();
       } else { 
           $.jeegoopopup.width(640).height(500);
           $.jeegoopopup.center();
           $("#minimizeImg").hide();
           $("#maximizeImg").show();
       }
   });

   $("#minimizeImg").click(function() { $.jeegoopopup.width(640).height(500); $.jeegoopopup.center(); $("#maximizeImg").show(); $("#minimizeImg").hide(); });
   $("#maximizeImg").click(function() { $.jeegoopopup.width(maxWidth).height(maxHeight); $.jeegoopopup.center(); $("#minimizeImg").show(); $("#maximizeImg").hide(); });

   $("#closeImg").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); });
   $("#cancel_button").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); });
   $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); });
   $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); $("#jg_popup_b").empty(); } });
}

// Send text message to all the contacts in a group
function SendMessageToGroups() {

   if (winMsgToGrps == 0) {

       $.jeegoopopup.close();

       var msgtogrps = "<div id='msgToGroups'>";
       msgtogrps += "<div id='windowCtrlsMsggrp'><img id='closeImgmgr' class='closeImgTextCnf' src='images/3_close.svg' title='Close'><img id='maximizeImgmgr' class='maximizeImgTextCnf' src='images/2_maximize.svg' title='Maximize'><img id='restoreImgmgr' class='restoreImgTextCnf' src='images/4_restore.svg' title='Restore'><img id='minimizeImgmgr' class='minimizeImgTextCnf' src='images/1_minimize.svg' title='Minimize'><div id='sendMsgToGrpsLabel' title='Send Message to Groups'>Send Message to Groups</div></div>";
       msgtogrps += "<div id='dragWindowmgr' class='dragPubMessage'><i class='fa fa-ellipsis-h' title='Drag Button' style='display:block;position:relative;width:40px;margin:0px auto;color:#d0d0d0;font-size:17px;cursor:grab;'></i></div>";
       msgtogrps += "<div id='msgToGroupsTopBar' style='display:block;margin:18px auto 2px auto;text-align: center;'><span style='display:block;font-size:16px;font-weight:600;color:#224573;user-select: none;'>Send Message to Groups</span></div>";
       msgtogrps += "<div id='txtToGroupsSend'>";

       msgtogrps += "<div class='UiText'>Select the group(s) to which you want to send the message:</div>";
       msgtogrps += "<div><select id='addContactGrpsmgr'>";
       msgtogrps += "<option value=''>Click to choose one or more groups from the list</option>";
	         for (var g = 0; g < allGroups.length; g++) {
	              msgtogrps += "<option value='"+ allGroups[g] +"'>"+ allGroups[g] +"</option>"; 
	         }
       msgtogrps += "<option value='withoutgroupsmgr'>--- Contacts without groups ---</option>";
       msgtogrps += "<option value='allcontactsmgr'>--- All contacts ---</option>";
       msgtogrps += "</select></div>";
       msgtogrps += "<div id='addContactGroupsMgr'></div>";

       msgtogrps += "<div class='sendIMsgToGroups'><input type='checkbox' id='sendInstMsgGrps'><label for='sendInstMsgGrps'><i class='fa fa-comments-o' style='margin: 0px 6px'></i>Send the message as <font style='color:#018f5a !important;font-weight:600;'>instant message</font> to all the extensions that are <b>currently loaded and online</b>, from the selected groups.</label></div>";
       msgtogrps += "<div class='sendIMsgToGroups'><input type='checkbox' id='sendEmailToGrps'><label for='sendEmailToGrps'><i class='fa fa-envelope-o' style='margin: 0px 6px'></i>Send the message as <font style='color:#018f5a !important;font-weight:600;'>email</font> to all the extensions that are <b>not currently loaded and online</b>, from the selected groups, and to all the regular contacts with email address, from the selected groups.<div class='sendMsgToGrpTip'><i id='sendMsgToGrpInfo' class='fa fa-info'></i><div class='sendMsgToGrpText'>To send a message as email, the 'sendmail' utility ('/usr/sbin/sendmail' on Debian Linux) has to be present on the server. 'sendmail' is a binary which gets installed when Postfix is installed. Also, the web server user (www-data) has to be allowed to send emails from command line (by specifying 'authorized_submit_users = www-data' in '/etc/postfix/main.cf' for Postfix on a Debian based Linux distribution). The receiver will see the message as coming from the email address that you used to sign up for Roundpin.</div></div></label></div>";
       msgtogrps += "<div class='sendIMsgToGroups'><input type='checkbox' id='sendSMSToGrps'><label for='sendSMSToGrps'><i class='fa fa-commenting-o' style='margin: 0px 6px'></i>Send the message as <font style='color:#018f5a !important;font-weight:600;'>SMS</font> to all the extensions that are <b>not currently loaded and online</b>, from the selected groups, and to all the regular contacts with mobile numbers, from the selected groups.<div class='sendSMSToGrpTip'><i id='sendSMSToGrpInfo' class='fa fa-info'></i><div class='sendSMSToGrpText'>If you check this option, the default phone number for outgoing SMS will be used for sending the SMS messages, and every message will be charged as a regular SMS message. If the message is longer than 160 characters, it will be split by the SMS provider into smaller pieces and reassembled at the destination. Each piece will be charged as a separate SMS.</div></div></label></div>";
       msgtogrps += "<div id='showTextMessagemgr'></div>";  
       msgtogrps += "<div id='emojiMenumgr' class='EmojiMenumgr' style='display:none'></div>";
       msgtogrps += "<div id='msgtoGrpTxtSend'><table id='sendMessageTblmgr' class='sendMessageTbl' cellspacing='0' cellpadding='0'><tr><td style='width:100%;height:100%;'><textarea id='sendTextMessagemgr' placeholder='"+ lang.type_your_message_here +"'></textarea></td>";
       msgtogrps += "<td id='textToGrpsBoxBtns'><button id='sendTextChatBtnmgr' class='roundButtonsSpec' title='Send Message'><i class='fa fa-paper-plane-o'></i></button>";
       msgtogrps += "<button id='selectEmoticonmgr' class='roundButtonsSpec' title='Select Emoticon'><i class='fa fa-smile-o'></i></button>";
       msgtogrps += "</td></tr></table></div></div>";
       msgtogrps += "</div>";

       $("#Phone").append(msgtogrps);
       $("#msgToGroups").css("display", "block");
       $("#restoreImgmgr").hide();
       winMsgToGrps = 1;


       // Make the window draggable
       function makeMgrWndDraggable(e) {

           window.pmdragging = {};
           pmdragging.pageX0 = e.pageX;
           pmdragging.pageY0 = e.pageY;
           pmdragging.elem = $("#msgToGroups");
           pmdragging.offset0 = $("#msgToGroups").offset();

           function handle_dragging(e) {
	       var left = pmdragging.offset0.left + (e.pageX - pmdragging.pageX0);
	       var top = pmdragging.offset0.top + (e.pageY - pmdragging.pageY0);
	       $(pmdragging.elem).offset({top: top, left: left});
           }

           function handle_mouseup(e) {
	       $('body').off('mousemove', handle_dragging).off('mouseup', handle_mouseup);
           }

           $('body').on('mouseup', handle_mouseup).on('mousemove', handle_dragging);
       }

       $("#dragWindowmgr").mousedown(makeMgrWndDraggable);


       // Add new group to the list of groups to which you can send messages
       $("#addContactGrpsmgr").on("change", function() {

          var selectedGrpmgr = $("#addContactGrpsmgr").val();

          if (selectedGrpmgr != '') {

              if (selectedGrpmgr == 'withoutgroupsmgr') {
                  $("#addContactGroupsMgr").empty();
	          $("#addContactGroupsMgr").append("<div id='slctdGroupmgrbl'><div style='display:inline;position:relative;'>Contacts without groups</div><span class='removeGroup' title='Remove this group'><i class='fa fa-times'></i></span></div>"); 

	          $(".removeGroup").last().click(function() { $("#slctdGroupmgrbl").remove(); });
              } else if (selectedGrpmgr == 'allcontactsmgr') {
                  $("#addContactGroupsMgr").empty();
	          $("#addContactGroupsMgr").append("<div id='slctdGroupmgrgr'><div style='display:inline;position:relative;'>All contacts</div><span class='removeGroup' title='Remove this group'><i class='fa fa-times'></i></span></div>"); 

	          $(".removeGroup").last().click(function() { $("#slctdGroupmgrgr").remove(); });
              } else {
	          var selCheckmgr = 0;
	          $(".slctdGroupTextmsgr").each(function() {
	            if ($(this).text() == selectedGrpmgr) { selCheckmgr++; }
	          });
	          if (selCheckmgr == 0) {
                      $("#slctdGroupmgrbl,#slctdGroupmgrgr").remove();
	              $("#addContactGroupsMgr").append("<div class='slctdGroupmsgtg'><div class='slctdGroupTextmsgr'>"+ selectedGrpmgr +"</div><span class='removeGroup' title='Remove this group'><i class='fa fa-times'></i></span></div>"); 
	              // Remove group from list
	              $(".removeGroup").last().click(function() { $(this).closest(".slctdGroupmsgtg").remove(); });
	          }
              }
          }
       });


       // Select emoticon
       $("#selectEmoticonmgr").click(function() {

          var messageContainermgr = $("#emojiMenumgr");
          var textareamgr = $("#sendTextMessagemgr");

          if (messageContainermgr.is(":visible")) { messageContainermgr.hide(); } else { messageContainermgr.show(); }

          var menuBarmgr = $("<div>");
          menuBarmgr.prop("class", "emojiButton");
          var emojis = ["😀","😁","😂","😃","😄","😅","😆","😊","😦","😉","😊","😋","😌","😍","😎","😏","😐","😑","😒","😓","😔","😕","😖","😗","😘","😙","😚","😛","😜","😝","😞","😟","😠","😡","😢","😣","😤","😥","😦","😧","😨","😩","😪","😫","😬","😭","😮","😯","😰","😱","😲","😳","😴","😵","😶","😷","🙁","🙂","🙃","🙄","🤐","🤑","🤒","🤓","🤔","🤕","🤠","🤡","🤢","🤣","🤤","🤥","🤧","🤨","🤩","🤪","🤫","🤬","🤭","🤯","🤗","🧐"];
          $.each(emojis, function(i,e){
	       var emoji = $("<button>");
	       emoji.html(e);
	       emoji.on('click', function() {
		    var i = textareamgr.prop('selectionStart');
		    var v = textareamgr.val();
		    textareamgr.val(v.substring(0, i) + " " + $(this).html() + v.substring(i, v.length) + " ");

		    messageContainermgr.hide();
		    textareamgr.focus();
	       });
	       menuBarmgr.append(emoji);
          });

          $("#sendTextMessagemgr,#showTextMessagemgr").on('click', function(){
	     messageContainermgr.hide();
          });

          messageContainermgr.empty();
          messageContainermgr.append(menuBarmgr);
       });


       // Send message to groups as SMS
       function sendMsgAsSMSToGroups(smsNumber, textToSendAsSms) {

       }

       // Send message to groups
       function sendMsgToSelectedGroups() {

          var smtgSelectedOpt = $("#addContactGrpsmgr").val();

          var smtgSelectedgArr = [];
          $("#addContactGroupsMgr .slctdGroupTextmsgr").each(function() { smtgSelectedgArr.push($(this).text()); });

          var txtToSendGrps = $("#sendTextMessagemgr").val().trim();

          if (txtToSendGrps != '') {

              if (smtgSelectedgArr.length > 0 || smtgSelectedOpt == 'allcontactsmgr' || smtgSelectedOpt == 'withoutgroupsmgr') {

                  if ($("#sendInstMsgGrps").is(":checked") || $("#sendEmailToGrps").is(":checked") || $("#sendSMSToGrps").is(":checked")) {

                      $("#showTextMessagemgr").append("<span id='sendFileLoadermgr'></span>");

                      var txtToSendGrpsFmt = ReformatMessage(txtToSendGrps);

                      for (var b = 0; b < Buddies.length; b++) {

                           var crntBuddyGrps = Buddies[b].Groups;
                           if (crntBuddyGrps != '' && crntBuddyGrps != null && crntBuddyGrps != 'null') {
                               var crctctGroupsArr = crntBuddyGrps.split("|");
                               crctctGroupsArr.shift(); crctctGroupsArr.pop();
                           } else { var crctctGroupsArr = []; }

                           if (Buddies[b].type == "extension") {

                               let foundGrChck = 0;
                               for (let j = 0; j < crctctGroupsArr.length; j++) {
                                    if ($.inArray(crctctGroupsArr[j], smtgSelectedgArr) !== -1) { foundGrChck++; }
                               }

                               if (foundGrChck != 0 || smtgSelectedOpt == 'allcontactsmgr' || (smtgSelectedOpt == 'withoutgroupsmgr' && crctctGroupsArr.length == 0)) {

                                   if ($.inArray(Buddies[b].presence, ["Unavailable", "Unknown", "Not online", "Do not disturb"]) == -1 && $("#sendInstMsgGrps").is(":checked")) {
                                       SendChatMessage(Buddies[b].identity, txtToSendGrps);
                                   }

                                   if ($.inArray(Buddies[b].presence, ["Unavailable", "Unknown", "Not online", "Do not disturb"]) !== -1 && $("#sendEmailToGrps").is(":checked")) {
                                       if (Buddies[b].Email != '' && Buddies[b].Email != null && Buddies[b].Email != 'null') { sendMsgAsEmailToGroups(Buddies[b].Email, txtToSendGrpsFmt); }
                                   }

                                   if ($.inArray(Buddies[b].presence, ["Unavailable", "Unknown", "Not online", "Do not disturb"]) !== -1 && $("#sendSMSToGrps").is(":checked")) {
                                       if (Buddies[b].MobileNumber != '' && Buddies[b].MobileNumber != null && Buddies[b].MobileNumber != 'null') { sendMsgAsSMSToGroups(Buddies[b].MobileNumber, txtToSendGrpsFmt); }
                                   }
                               }

                           } else if (Buddies[b].type == "contact") {

                               let foundGrChckct = 0;
                               for (let c = 0; c < crctctGroupsArr.length; c++) {
                                    if ($.inArray(crctctGroupsArr[c], smtgSelectedgArr) !== -1) { foundGrChckct++; }
                               }

                               if (foundGrChckct != 0 || smtgSelectedOpt == 'allcontactsmgr' || (smtgSelectedOpt == 'withoutgroupsmgr' && crctctGroupsArr.length == 0)) {

                                   if ($("#sendEmailToGrps").is(":checked")) {
                                       if (Buddies[b].Email != '' && Buddies[b].Email != null && Buddies[b].Email != 'null') { sendMsgAsEmailToGroups(Buddies[b].Email, txtToSendGrpsFmt); }
                                   }

                                   if ($("#sendSMSToGrps").is(":checked")) {
                                       if (Buddies[b].MobileNumber != '' && Buddies[b].MobileNumber != null && Buddies[b].MobileNumber != 'null') { sendMsgAsSMSToGroups(Buddies[b].MobileNumber, txtToSendGrpsFmt); }
                                   }
                               }
                           }
                      }

                      $("#sendTextMessagemgr").val("");
	              var crntTimemgr = new Date().toLocaleTimeString();

                      // Status of the sent message
                      if ($("#sendInstMsgGrps").is(":checked")) {

                          var msgMultiIm = "<i class='fa fa-comments-o' style='display:inline-block;margin:0px 0px 0px 10px;color:#1c5592;font-size:13px;'></i><i class='fa fa-check mgrImSentStatus' title='No errors when sending the instant messages.'></i>";

                      } else { var msgMultiIm = ""; }

                      if ($("#sendEmailToGrps").is(":checked")) {

                          var msgMultiEmail = "<i class='fa fa-envelope-o' style='display:inline-block;margin:0px 0px 0px 10px;color:#1c5592;font-size:13px;'></i><i class='fa fa-check mgrEmSentStatus' title='No errors when sending the emails.'></i>";

                      } else { var msgMultiEmail = ""; }

                      if ($("#sendSMSToGrps").is(":checked")) {

                          var msgMultiSMS = "<i class='fa fa-commenting-o' style='display:inline-block;margin:0px 0px 0px 10px;color:#1c5592;font-size:13px;'></i><i class='fa fa-check mgrSmSentStatus' title='No errors when sending the SMS messages.'></i>";

                      } else { var msgMultiSMS = ""; }

                      var msgMultiStatus = msgMultiIm + msgMultiEmail + msgMultiSMS;

	              // Append the sent message
                      $("#showTextMessagemgr").append("<div class='textMessageSent'><span class='sentMessageNm'>"+ profileName +" ("+ SipUsername +"):</span><div style='display:inline;margin-left:4px;word-break:keep-all;white-space:pre-wrap;user-select:text;cursor:text;'>"+ txtToSendGrps +"</div><div class='messageDate'>"+ crntTimemgr +" "+ msgMultiStatus +"</div></div>"); 

                      $("#showTextMessagemgr").scrollTop($("#showTextMessagemgr")[0].scrollHeight);
                      $("#sendFileLoadermgr").remove();

                  } else { alert("Please check at least one checkbox!"); }

              } else { alert("Please select one or more groups from the drop-down list at the top of this window!"); }

          } else { alert("You cannot send an empty message!"); }

       }

       // Send the message when clicking on the 'Send Message' button
       $("#sendTextChatBtnmgr").click(function() {
           sendMsgToSelectedGroups();
       });

       // Send the message when pressing Ctrl + Enter
       $("#sendTextMessagemgr").keydown(function() {
	  var keycodemgr = (event.keyCode ? event.keyCode : event.which);
	  if (keycodemgr == '13') {
	      if (event.ctrlKey) {
                  sendMsgToSelectedGroups();
		  return false;
	      }
          }
       });

       $(".chatHistory,.chatMessage,.buddy,.buddySelected,.profileContainer,#logoSection").on('click', function() { $("#minimizeImgmgr").click(); });

       // Position the window
       var maxWidthmgr = parseInt($(window).width());
       var maxHeightmgr = parseInt($(window).height());

       var wTopmgr = parseInt((maxHeightmgr / 2) - 324);
       var wLeftmgr = parseInt((maxWidthmgr / 2) - 390);

       var widthRecMessagemgr = maxWidthmgr - 30;
       var widthSendCalcmgr = maxWidthmgr - 60;
       var heightCalcmgr = maxHeightmgr - 482;

       $("#msgToGroups").css({ "width": 800, "height": 647, "top": wTopmgr, "left": wLeftmgr });

       $("#sendMsgToGrpsLabel").hide();


       if (maxWidthmgr < 800 || maxHeightmgr < 647) {
           messageWindowStateMgr = 'maximized';
	   $("#msgToGroups").css({ "width": maxWidthmgr, "height": maxHeightmgr });
           $("#showTextMessagemgr").css({ "width" : widthRecMessagemgr, "height" : "160px" });
           $("#msgtoGrpTxtSend").css("width", widthSendCalcmgr);
       } else { 
	   $("#msgToGroups").css({ "width": 800, "height": 647, "top": wTopmgr, "left": wLeftmgr });
           $("#restoreImgmgr,#sendMsgToGrpsLabel").hide();
           $("#maximizeImgmgr,#msgToGroupsTopBar,#minimizeImgmgr,#showTextMessagemgr,#msgtoGrpTxtSend").show();
           $("#showTextMessagemgr").css({ "width" : "100%", "height" : "160px" });
           $("#msgtoGrpTxtSend").css({ "width" : "100%", "height" : "100px" });
       }

       // Handle window position when resizing browser window
       $(window).resize(function() {

          let maxWidthmgr = parseInt($(window).width());
          let maxHeightmgr = parseInt($(window).height());

          let wTopmgr = parseInt((maxHeightmgr / 2) - 324);
          let wLeftmgr = parseInt((maxWidthmgr / 2) - 390);

          let widthRecMessagemgr = maxWidthmgr - 60;
          let widthSendCalcmgr = maxWidthmgr - 60;


	  if (maxWidthmgr < 800 || maxHeightmgr < 647) { 
              messageWindowStateMgr = 'maximized'; 
              $("#msgToGroups").css({ "width": maxWidthmgr, "height": maxHeightmgr, "top": "0", "left": "0" });
              $("#maximizeImgmgr,#sendMsgToGrpsLabel").hide();
              $("#minimizeImgmgr,#maximizeImgmgr,#msgToGroupsTopBar,#txtToGroupsSend").show();
              $("#showTextMessagemgr").css({ "width" : widthRecMessagemgr, "height" : "160px" });
              $("#msgtoGrpTxtSend").css("width", widthSendCalcmgr);
              $("#textToGrpsBoxBtns").css("padding", "13px 6px");
	  } else { 
              $("#msgToGroups").css({ "width": 800, "height": 647, "top": wTopmgr, "left": wLeftmgr });
              $("#restoreImgmgr,#sendMsgToGrpsLabel").hide();
              $("#minimizeImgmgr,#maximizeImgmgr,#msgToGroupsTopBar,#txtToGroupsSend").show();
              $("#showTextMessagemgr").css({ "width" : "100%", "height" : "160px" });
              $("#msgtoGrpTxtSend").css({ "width" : "100%", "height" : "100px" });
              $("#textToGrpsBoxBtns").css("padding", "13px 6px");
	  }

       });

       // Handle clicking on window control buttons
       $("#maximizeImgmgr").click(function() {
           $("#msgToGroups").css({ "width": maxWidthmgr, "height": maxHeightmgr, "top": "0", "left": "0" });
           $("#maximizeImgmgr,#sendMsgToGrpsLabel").hide();
           $("#minimizeImgmgr,#restoreImgmgr,#msgToGroupsTopBar,#txtToGroupsSend").show();
           $("#showTextMessagemgr").css({ "width" : "100%", "height": heightCalcmgr });
           $("#msgtoGrpTxtSend").css({ "width" : widthSendCalcmgr, "height" : "100px" });
           $("#textToGrpsBoxBtns").css("padding", "12px 4px");
           $("#showTextMessagemgr").scrollTop($("#showTextMessagemgr")[0].scrollHeight);
       });

       $("#minimizeImgmgr").click(function() {
           messageWindowStateMgr = 'minimized';
           $("#msgToGroups").css({ "display": "block", "position": "fixed", "width": 300, "height": 40, "inset": "auto auto 1px 300px" });
           $("#minimizeImgmgr,#msgToGroupsTopBar,#txtToGroupsSend").hide();
           $("#maximizeImgmgr,#restoreImgmgr").show();
           $("#sendMsgToGrpsLabel").css("display", "block");
           $("windowCtrlsMsggrp").css("margin", "10px 10px 0px 0px");
       });

       $("#restoreImgmgr").click(function() {
	   if (maxWidthmgr < 800 || maxHeightmgr < 647) {
               messageWindowStateMgr = 'maximized'; 
               $("#msgToGroups").css({ "width": maxWidthmgr, "height": maxHeightmgr, "top": "0", "left": "0" });
               $("#maximizeImgmgr,#sendMsgToGrpsLabel").hide();
               $("#minimizeImgmgr,#maximizeImgmgr,#msgToGroupsTopBar,#txtToGroupsSend").show();
               $("#showTextMessagemgr").css({ "width" : widthRecMessagemgr, "height" : "160px" });
               $("#msgtoGrpTxtSend").css("width", widthSendCalcmgr);
               $("#textToGrpsBoxBtns").css("padding", "13px 6px");
               $("#showTextMessagemgr").scrollTop($("#showTextMessagemgr")[0].scrollHeight);
	   } else {
               messageWindowStateMgr = 'restored';
               $("#msgToGroups").css({ "width": 800, "height": 647, "top": wTopmgr, "left": wLeftmgr });
               $("#restoreImgmgr,#sendMsgToGrpsLabel").hide();
               $("#minimizeImgmgr,#maximizeImgmgr,#msgToGroupsTopBar,#txtToGroupsSend").show();
               $("#showTextMessagemgr").css({ "width" : "100%", "height" : "160px" });
               $("#msgtoGrpTxtSend").css({ "width" : "100%", "height" : "100px" });
               $("#textToGrpsBoxBtns").css("padding", "13px 6px");
               $("#showTextMessagemgr").scrollTop($("#showTextMessagemgr")[0].scrollHeight);
	   }
       });

       $("#closeImgmgr").click(function() {
           winMsgToGrps = 0;
           messageWindowStateMgr = 'closed';
           $("#msgToGroups").remove();
       });

   } else { alert("The 'Send Message to Groups' window has already been opened!"); }

}

function closeVideoAudio() {

        var localVideo = $("#local-video-preview").get(0);
        try{
            var tracks = localVideo.srcObject.getTracks();
            tracks.forEach(function(track) {
                track.stop();
            });
            localVideo.srcObject = null;
        }
        catch(e){}

        try{
            var tracks = window.SettingsMicrophoneStream.getTracks();
            tracks.forEach(function(track) {
                track.stop();
            });
        }
        catch(e){}
        window.SettingsMicrophoneStream = null;

        try{
            var soundMeter = window.SettingsMicrophoneSoundMeter;
            soundMeter.stop();
        }
        catch(e){}
        window.SettingsMicrophoneSoundMeter = null;

        try{
            window.SettingsOutputAudio.pause();
        }
        catch(e){}
        window.SettingsOutputAudio = null;

        try{
            var tracks = window.SettingsOutputStream.getTracks();
            tracks.forEach(function(track) {
                track.stop();
            });
        }
        catch(e){}
        window.SettingsOutputStream = null;

        try{
            var soundMeter = window.SettingsOutputStreamMeter;
            soundMeter.stop();
        }
        catch(e){}
        window.SettingsOutputStreamMeter = null;

        return true;
}
function ConfigureExtensionWindow() {

    $("#settingsCMenu").hide();

    $.jeegoopopup.close();

    var configWindow = "<div id=\"mainConfWindow\">";
    configWindow += "<div id='windowCtrls'><img id='minimizeImg' src='images/1_minimize.svg' title='Restore' /><img id='maximizeImg' src='images/2_maximize.svg' title='Maximize' /><img id='closeImg' src='images/3_close.svg' title='Close' /></div>"; 
    configWindow += "<div id=\"mainRightConf\">";
    configWindow += "<div id=\"rightMainConfWindow\">";

    configWindow +=  "<div id=\"AccountHtml\" class=\"settingsSubSection\" style=\"display:block;\">";
    configWindow += "<div class=UiText>"+ lang.asterisk_server_address +":</div>";
    configWindow += "<div><input id=Configure_Account_wssServer class=UiInputText type=text placeholder='"+ lang.eg_asterisk_server_address +"' value='"+ wssServer +"'></div>";
    configWindow += "<div class=UiText>"+ lang.websocket_port +":</div>";
    configWindow += "<div><input id=Configure_Account_WebSocketPort class=UiInputText type=text placeholder='"+ lang.eg_websocket_port +"' value='"+ WebSocketPort +"'></div>";
    configWindow += "<div class=UiText>"+ lang.websocket_path +":</div>";
    configWindow += "<div><input id=Configure_Account_ServerPath class=UiInputText type=text placeholder='"+ lang.eg_websocket_path +"' value='"+ ServerPath +"'></div>";
    configWindow += "<div class=UiText>"+ lang.display_name +":</div>";

    var escapedDisplayName = 'value="'+ profileName.replace("'","\'") +'"';
    configWindow += "<div><input id=Configure_Account_profileName class=UiInputText type=text placeholder='"+ lang.eg_display_name +"' "+ escapedDisplayName +"></div>";
    configWindow += "<div class=UiText>"+ lang.sip_username +":</div>";
    configWindow += "<div><input id=Configure_Account_SipUsername class=UiInputText type=text placeholder='"+ lang.eg_sip_username +"' value='"+ SipUsername +"'></div>";
    configWindow += "<div class=UiText>"+ lang.sip_password +":</div>";
    configWindow += "<div><input id=Configure_Account_SipPassword class=UiInputText type=password placeholder='"+ lang.eg_sip_password +"' value='"+ SipPassword +"'></div>";
    configWindow += "<div class=UiText>"+ lang.stun_server +":</div>";
    configWindow += "<div><input id=Configure_Account_StunServer class=UiInputText type=text placeholder='Eg: 123.123.123.123:8443' value='"+ StunServer +"'></div>";
    configWindow += "<br><br></div>";

    configWindow += "<div id=\"AudioVideoHtml\" class=\"settingsSubSection\" style=\"display:none;\">";
    configWindow += "<div class=UiText>"+ lang.speaker +":</div>";
    configWindow += "<div style='text-align:center'><select id='playbackSrc' style='width:100%'></select></div>";
    configWindow += "<div class='Settings_VolumeOutput_Container'><div id='Settings_SpeakerOutput' class='Settings_VolumeOutput'></div></div>";
    configWindow += "<div><button class='on_white' id='preview_output_play' title='Play sample audio file'><i class='fa fa-play'></i></button><button class='on_white' id='preview_output_pause' title='Pause'><i class='fa fa-pause'></i></button></div>";

    configWindow += "<br><div class='UiText'>"+ lang.ring_device +":</div>";
    configWindow += "<div style='text-align:center'><select id='ringDevice' style='width:100%'></select></div>";
    configWindow += "<div class='Settings_VolumeOutput_Container'><div id='Settings_RingerOutput' class='Settings_VolumeOutput'></div></div>";
    configWindow += "<div><button class='on_white' id='preview_ringer_play' title='Play sample ringtone'><i class='fa fa-play'></i></button></div>";

    configWindow += "<br><div class='UiText'>"+ lang.ringtone +":</div>";
    configWindow += "<div style='text-align:left'><select id='ringingTone' style='width:100%'><option value='nm-simple.ogg'>Nm Simple</option><option value='nm-kawa.ogg'>Nm Kawa</option><option value='origring.ogg'>Orig Ring</option><option value='marimba.ogg'>Marimba</option><option value='pop.ogg'>Pop</option><option value='trilla.ogg'>Trilla</option><option value='digitalphone.ogg'>Digital Phone</option><option value='nm-justadream.ogg'>Nm Just a Dream</option><option value='fntring.ogg'>Fnt Ring</option><option value='bellphonering.ogg'>Bell Phone Ring</option><option value='trippycats.ogg'>Trippy Cats</option><option value='classic.ogg'>Classic</option></select></div>";
    configWindow += "<div><button class='on_white' id='preview_ringtone' title='Play selected ringtone'><i class='fa fa-play'></i></button></div>";

    configWindow += "<br><div class='UiText'>"+ lang.microphone +":</div>";
    configWindow += "<div style='text-align:center'><select id='microphoneSrc' style='width:100%'></select></div>";
    configWindow += "<div class=Settings_VolumeOutput_Container><div id=Settings_MicrophoneOutput class=Settings_VolumeOutput></div></div>";
    configWindow += "<br><br><div><input type=checkbox id=Settings_AutoGainControl><label for=Settings_AutoGainControl> "+ lang.auto_gain_control +"</label></div>";
    configWindow += "<div><input type=checkbox id=Settings_EchoCancellation><label for=Settings_EchoCancellation> "+ lang.echo_cancellation +"</label></div>";
    configWindow += "<div><input type=checkbox id=Settings_NoiseSuppression><label for=Settings_NoiseSuppression> "+ lang.noise_suppression +"</label></div>";
    configWindow += "<br><div class=UiText>"+ lang.camera +":</div>";
    configWindow += "<div style=\"text-align:center\"><select id=previewVideoSrc style=\"width:100%\"></select></div>";
    configWindow += "<br><div class=UiText>"+ lang.frame_rate +":</div>"
    configWindow += "<div class=pill-nav>";
    configWindow += "<input name=Settings_FrameRate id=r40 type=radio value=\"2\"><label class=radio_pill for=r40>2</label>";
    configWindow += "<input name=Settings_FrameRate id=r41 type=radio value=\"5\"><label class=radio_pill for=r41>5</label>";
    configWindow += "<input name=Settings_FrameRate id=r42 type=radio value=\"10\"><label class=radio_pill for=r42>10</label>";
    configWindow += "<input name=Settings_FrameRate id=r43 type=radio value=\"15\"><label class=radio_pill for=r43>15</label>";
    configWindow += "<input name=Settings_FrameRate id=r44 type=radio value=\"20\"><label class=radio_pill for=r44>20</label>";
    configWindow += "<input name=Settings_FrameRate id=r45 type=radio value=\"25\"><label class=radio_pill for=r45>25</label>";
    configWindow += "<input name=Settings_FrameRate id=r46 type=radio value=\"30\"><label class=radio_pill for=r46>30</label>";
    configWindow += "<input name=Settings_FrameRate id=r47 type=radio value=\"\"><label class=radio_pill for=r47><i class=\"fa fa-trash\"></i></label>";
    configWindow += "</div>";
    configWindow += "<br><br><div class=UiText>"+ lang.quality +":</div>";
    configWindow += "<div class=pill-nav>";
    configWindow += "<input name=Settings_Quality id=r30 type=radio value=\"160\"><label class=radio_pill for=r30><i class=\"fa fa-video-camera\" style=\"transform: scale(0.4)\"></i> HQVGA</label>";
    configWindow += "<input name=Settings_Quality id=r31 type=radio value=\"240\"><label class=radio_pill for=r31><i class=\"fa fa-video-camera\" style=\"transform: scale(0.6)\"></i> QVGA</label>";
    configWindow += "<input name=Settings_Quality id=r32 type=radio value=\"480\"><label class=radio_pill for=r32><i class=\"fa fa-video-camera\" style=\"transform: scale(0.8)\"></i> VGA</label>";
    configWindow += "<input name=Settings_Quality id=r33 type=radio value=\"720\"><label class=radio_pill for=r33><i class=\"fa fa-video-camera\" style=\"transform: scale(1)\"></i> HD</label>";
    configWindow += "<input name=Settings_Quality id=r34 type=radio value=\"\"><label class=radio_pill for=r34><i class=\"fa fa-trash\"></i></label>";
    configWindow += "</div>";
    configWindow += "<br><br><div class=UiText>"+ lang.image_orientation +":</div>";
    configWindow += "<div class=pill-nav>";
    configWindow += "<input name=Settings_Oriteation id=r20 type=radio value=\"rotateY(0deg)\"><label class=radio_pill for=r20><i class=\"fa fa-address-card\" style=\"transform: rotateY(0deg)\"></i> Normal</label>";
    configWindow += "<input name=Settings_Oriteation id=r21 type=radio value=\"rotateY(180deg)\"><label class=radio_pill for=r21><i class=\"fa fa-address-card\" style=\"transform: rotateY(180deg)\"></i> Mirror</label>";
    configWindow += "</div>";
    configWindow += "<br><br><div class=UiText>"+ lang.aspect_ratio +":</div>";
    configWindow += "<div class=pill-nav>";
    configWindow += "<input name=Settings_AspectRatio id=r10 type=radio value=\"1\"><label class=radio_pill for=r10><i class=\"fa fa-square-o\" style=\"transform: scaleX(1); margin-left: 7px; margin-right: 7px\"></i> 1:1</label>";
    configWindow += "<input name=Settings_AspectRatio id=r11 type=radio value=\"1.33\"><label class=radio_pill for=r11><i class=\"fa fa-square-o\" style=\"transform: scaleX(1.33); margin-left: 5px; margin-right: 5px;\"></i> 4:3</label>";
    configWindow += "<input name=Settings_AspectRatio id=r12 type=radio value=\"1.77\"><label class=radio_pill for=r12><i class=\"fa fa-square-o\" style=\"transform: scaleX(1.77); margin-right: 3px;\"></i> 16:9</label>";
    configWindow += "<input name=Settings_AspectRatio id=r13 type=radio value=\"\"><label class=radio_pill for=r13><i class=\"fa fa-trash\"></i></label>";
    configWindow += "</div>";
    configWindow += "<br><br><div class=UiText>"+ lang.preview +":</div>";
    configWindow += "<div style=\"text-align:center; margin-top:10px\"><video id=\"local-video-preview\" class=\"previewVideo\"></video></div>";

    configWindow += "<br><br><div class='UiText'>Voice phone number(s):<div class='voicePhNoTip'><i id='voicePhNoInfo' class='fa fa-info'></i><div class='voicePhNoText'>Enter all the phone numbers that you have configured in Asterisk and/or your SIP provider(s), for making voice phone calls. Each number must be preceded by the <a href='https://en.wikipedia.org/wiki/List_of_country_calling_codes#Alphabetical_order' target='_blank' rel='noreferrer noopener' style='color:#1b60ba'>country calling code</a>, the 'plus' sign and the 2 letter abbreviation of your SIP provider's name. For example, if you use the US phone number 3076543210 rented from Telnyx, and the UK phone number 7654321012 rented from Localphone, you should enter:<br> Tx: +13076543210,Ln: +447654321012 <br>where Tx stands for Telnyx, Ln stands for Localphone, 1 is the country calling code for US and 44 is the country calling code for UK. The 2 letter abbreviation for Telnyx is Tx , for Localphone is Ln , for Flowroute is Fl , for Twilio is Tw and for Vonage is Vn . This list of phone numbers will allow you to set any of them as the 'From' number, when making phone calls. If you configure a number for both voice calls and SMS, you should enter it both here and in the 'Phone number(s) for SMS' field, in the 'SMS' section.</div></div></div>";
    configWindow += "<div><input id='voiceNumbers' class='UiInputText' type='text' placeholder='Eg:  Tx: +15676543210,Ln: +491234567891,Fl: +347865432101' value='"+ phoneNumbersFdb['voice_numbers'] +"'></div>";

    configWindow += "<br><div class='UiText'>Default voice phone number for outgoing calls:<div class='vdefPhNoTip'><i id='vdefPhNoInfo' class='fa fa-info'></i><div class='vdefPhNoText'>Enter the phone number configured for voice calls in Asterisk and/or your SIP provider, that you want to use as the default 'From' number when making phone calls. If you set a number here and you make a phone call without choosing a 'From' number from the drop-down list on the dial pad, the number from this field is automatically chosen as the 'From' number and the receiver sees the call as coming from this number. If you leave this field empty, the first phone number from the numbers listed in the 'Voice phone number(s)' field from above, is used as the 'From' number for outbound phone calls. The phone number must be preceded by the <a href='https://en.wikipedia.org/wiki/List_of_country_calling_codes#Alphabetical_order' target='_blank' rel='noreferrer noopener' style='color:#1b60ba'>country calling code</a>, the 'plus' sign and the 2 letter abbreviation of your SIP provider's name. For example, if you want to set as default number for outbound voice calls the US number 3076543210 rented from Telnyx, you have to enter Tx: +13076543210 . The 2 letter abbreviation for Telnyx is Tx , for Localphone is Ln , for Flowroute is Fl , for Twilio is Tw and for Vonage is Vn .</div></div></div>";
    configWindow += "<div><input id='voiceDefaultNumber' class='UiInputText' type='text' placeholder='Eg:  Tx: +15676543210' value='"+ phoneNumbersFdb['default_voice_number'] +"'></div>";

    configWindow += "</div>";



    configWindow += "<div id='UsersAndGroups' class='settingsSubSection' style='display:none'>";
    configWindow += "<div class='UiText'>Groups:<div class='groupsListTip'><i id='groupsListInfo' class='fa fa-info'></i><div class='groupsListInfoText'>Superadmins can remove any groups, Admins can remove only the groups created by them and Regular Users cannot remove groups.</div></div></div>";
    configWindow += "<div id='cnfwGroupsList'>";

    var cusrrl = getDbItem("userrole", "");
    var crntUsrDbId = getDbItem("userdbid", "");

    for (var g = 0; g < allGroups.length; g++) {

         if (cusrrl == 'regular_user' || cusrrl == 'admin') {
             if (crntUsrDbId == allGroupsAndIds[allGroups[g]]) {
                 configWindow += "<div class='grpListRow'><div class='grpListDspl'>"+ allGroups[g] +"</div><div class='rmGrpFrmDtbs'><i class='fa fa-times' title='Remove this group completely from the database.'></i></div></div>";
             } else {
                 configWindow += "<div style='display:block;margin:6px 10px;'>"+ allGroups[g] +"</div>"; 
             }
         } else if (cusrrl == 'superadmin') {
                 configWindow += "<div class='grpListRow'><div class='grpListDspl'>"+ allGroups[g] +"</div><div class='rmGrpFrmDtbs'><i class='fa fa-times' title='Remove this group completely from the database.'></i></div></div>";
         }
    }

    configWindow += "</div>";

    configWindow += "<div class='UiText'>Create New Group:</div>";

    if (cusrrl == 'admin' || cusrrl == "superadmin") {
        configWindow += "<div><input id='cnfwAddNewGroup' class='UiInputText' style='font-size:15px !important' type='text' placeholder='Enter the name of the new group.' value=''></div>";
        configWindow += "<div><input id='cnfSaveGroup' class='saveCnfBtn' type='button' value='Create Group'></div>";
    } else {
        configWindow += "<div style='display:block;color:#3b3b3b;margin-top:6px;'>Only users with the role of Admin or Superadmin can create groups.</div>";
    }

    configWindow += "<br><br><div class='UiText'>Assign roles and groups to Roundpin users:</div>";

    if (cusrrl == 'admin' || cusrrl == "superadmin") {

        configWindow += "<table id='roundpinUsersTbl'><tr class='thRuTableRow'><td>Display Name</td><td>Extension</td><td>Role</td><td>Groups</td><td>Add Groups</td><td>Save</td></tr>";

        for (var u = 0; u < roundpinUsers.length; u++) {

             var usrRolesLst = ['superadmin', 'admin', 'regular_user'];
             var itmIndx = usrRolesLst.indexOf(roundpinUsers[u].userrole);
             if (itmIndx !== -1) { usrRolesLst.splice(itmIndx, 1); }

             var selectUsrRole = "<select class='selectUserRole'>";
             selectUsrRole += "<option value='"+ roundpinUsers[u].userrole +"'>"+ roundpinUsers[u].userrole +"</option>";
             for (var r = 0; r < usrRolesLst.length; r++) {
                  selectUsrRole += "<option value='"+ usrRolesLst[r] +"'>"+ usrRolesLst[r] +"</option>";
             }
             selectUsrRole += "</select>";

             var procGroupsInit = roundpinUsers[u].user_groups;
             var procGroupsArrPre = procGroupsInit.split("|");
             procGroupsArrPre.shift(); procGroupsArrPre.pop();
             var procGroupsArr = procGroupsArrPre.sort();
             var procGroups = "<div id='grpsOfUserList-"+ u +"' class='grpsOfUserList'>";
             procGroups += "<div id='groupsOfUserTh-"+ u +"'><div class='groupsOfUser grofusrLabel' style='color:#03366c'>Show Groups</div></div>";
             for (var p = 0; p < procGroupsArr.length; p++) {
                  procGroups += "<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ procGroupsArr[p] +"</div><div id='grpsOfUserRw-"+ u +"' class='rmvGrpFromUser'><i class='fa fa-times' title='Remove this group from this list.'></i></div></div>";
             }
             procGroups += "</div>";

             var procAddGroups = "<div id='addGrpsToUserList-"+ u +"' class='addGrpsToUserList'>";
             procAddGroups += "<div id='addGroupsToUserTh-"+ u +"'><div class='grofusrAddLabel'><i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to this Roundpin user.'></i></div></div>";

             var grpsDiffArr = allGroups.filter( function(x) { return procGroupsArr.indexOf(x) == -1; });
             for (var v = 0; v < grpsDiffArr.length; v++) {
                  procAddGroups += "<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ grpsDiffArr[v] +"</div><div id='addGrpsToUserRw-"+ u +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups to which this user belongs.'></i></div></div>";
             }
             procAddGroups += "</div>";

             configWindow += "<tr class='btnRuTableRow'><td class='lightGreenBckgrnd'>"+ roundpinUsers[u].profile_name +"</td><td class='lightBlueBckgrnd crUsrextension'>"+ roundpinUsers[u].sip_username +"</td><td class='lightGreenBckgrnd'>"+ selectUsrRole +"</td><td class='lightBlueBckgrnd'>"+ procGroups +"</td><td class='lightGreenBckgrnd'>"+ procAddGroups +"</td><td class='lightBlueBckgrnd'><input type='submit' class='saveCnfBtn' value='Save' title='Save this row.'/></td></tr>"; 
        }

        configWindow += "</table>";

    } else {
        configWindow += "<div style='display:block;color:#3b3b3b;margin-top:6px;'>Only users with the role of Admin or Superadmin can see the list with all Roundpin users and can change users' roles and associated groups.</div>";
    }



    configWindow += "<br><br><div class='UiText'>Users banned from video conferences:</div>";

    if (cusrrl == 'admin' || cusrrl == "superadmin") {

        if (bannedUsers.length != 0) {

            configWindow += "<table id='bannedUsersTbl'><tr class='thBanTableRow'><td>Display Name</td><td>User Extension</td><td>Conference Label</td><td>Conference Extension</td><td title='The day when the ban will be removed (the ban will be removed at the beginning of the specified day, which means at 00:00:00).'>Banned Until</td><td>Unban</td><td>Save</td></tr>";

            for (var b = 0; b < bannedUsers.length; b++) {
                 var banDateFdb = bannedUsers[b].banned_until;
                 var banDateFdbDay = banDateFdb.split(" ");
                 configWindow += "<tr class='btnBanTableRow'><td class='lightGreenBckgrnd bannedProfileName'>"+ bannedUsers[b].banned_profilename +"</td><td class='lightBlueBckgrnd crUsrextension'>"+ bannedUsers[b].banned_sipusername +"</td><td class='lightBlueBckgrnd bnfrvConfLabel'>"+ bannedUsers[b].conf_label +"</td><td class='lightGreenBckgrnd bfvConfExtension'>"+ bannedUsers[b].conf_extension +"</td><td class='lightGreenBckgrnd'><input id='pickNewBanTime-"+ b +"' class='pickBanTime' type='text' placeholder='New date' value='"+ banDateFdbDay[0] +"' title='Enter the day when the ban will be removed (the ban will be removed at the beginning of the chosen day, which means at 00:00:00).'></td><td class='lightBlueBckgrnd'><i class='fa fa-times unbanUsr' title='Unban this user'></i></td><td class='lightGreenBckgrnd'><input type='submit' class='saveCnfBtn' value='Save' title='Save new ban date.'/></td></tr>"; 
            }
            configWindow += "</table>";

        } else {
             configWindow += "<div style='display:block;margin:10px 0px 0px;color:#3b3b3b;font-size:15px;'>There are no banned users at this moment.</div>";
        }
    } else {
        configWindow += "<div style='display:block;color:#3b3b3b;margin-top:6px;'>Only Admins and Superadmins can see the list of users banned from video conferences, and can modify the ban time or unban them.</div>";
    }


    configWindow += "<br><br><div class='UiText'>When logging in, load only the contacts in the following group(s):</div>";
    configWindow += "<div><select id='AddContactGroupsOptLd' class='contactGroupsSelect'>";
    configWindow += "<option value=''>Click to choose one or more groups from the list</option>";
             for (var g = 0; g < allGroups.length; g++) {
                  configWindow += "<option value='"+ allGroups[g] +"'>"+ allGroups[g] +"</option>"; 
             }
    configWindow += "</select></div>";
    configWindow += "<div id='AddContactGroupsLd' class='loadContactGroups'></div>";

    configWindow += "<br></div>";


    configWindow += "<div id='ContactsHtml' class='settingsSubSection' style='display:none;'>";
    configWindow += "<div class='UiTextMod'>Export contacts:</div>";
    configWindow += "<div><select id='selectExportType' class='contactGroupsSelect'>";
    configWindow += "<option value=''>Click to choose the type of export</option>";
    configWindow += "<option value='exportall'>Export all contacts</option>";
    configWindow += "<option value='exportbygroup'>Export contacts by group</option>";
    configWindow += "<option value='exportbydate'>Export contacts by date</option>";
    configWindow += "</select></div>";

    configWindow += "<div id='exportAllContacts'>";
            var selectCSVDelimiter = "<select id='csvDelimiterList'>";
            selectCSVDelimiter += "<option value='\\'>Backslash (\\)</option>";
            selectCSVDelimiter += "<option value='^'>Caret (^)</option>";
            selectCSVDelimiter += "<option value='`'>Backtick (`)</option>";
            selectCSVDelimiter += "<option value='$'>Dollar sign ($)</option>";
            selectCSVDelimiter += "<option value='!'>Exclamation mark (!)</option>";
            selectCSVDelimiter += "<option value='?'>Question mark (?)</option>";
            selectCSVDelimiter += "<option value='%'>Percent sign (%)</option>";
            selectCSVDelimiter += "<option value=';'>Semicolon (;)</option>";
            selectCSVDelimiter += "<option value=','>Comma (,)</option>";
            selectCSVDelimiter += "</select>";
    configWindow += "<div>The CSV file that will be generated will contain values delimited by the following character:<div class='expCharTipAll'><i id='expCharInfoAll' class='fa fa-info'></i><div class='expCharTextAll'>It's recommended to choose as delimiter a character that doesn't occur inside any contact data field, such as the backslash (\\), the caret (^) or the backtick (`). It must be noted that every 'Profile Picture' field in the database that is not empty contains a comma (,) and a semicolon (;).</div></div>"+ selectCSVDelimiter +"</div>";
    configWindow += "<div><input id='expAllContactsBtn' class='saveCnfBtn' type='button' value='Export All Contacts'></div>";
    configWindow += "</div>";


    configWindow += "<div id='exportCntctsByGroup'>";
    configWindow += "<div><select id='contactGrpsToExport'>";
    configWindow += "<option value=''>Click to choose one or more groups from the list</option>";
             for (var g = 0; g < allGroups.length; g++) {
                  configWindow += "<option value='"+ allGroups[g] +"'>"+ allGroups[g] +"</option>"; 
             }
    configWindow += "</select></div>";
    configWindow += "<div id='contactGrpsExportList' class='exportContactGrps'></div>";

            var selectCSVDelimitergr = "<select id='csvDelimiterListgr'>";
            selectCSVDelimitergr += "<option value='\\'>Backslash (\\)</option>";
            selectCSVDelimitergr += "<option value='^'>Caret (^)</option>";
            selectCSVDelimitergr += "<option value='`'>Backtick (`)</option>";
            selectCSVDelimitergr += "<option value='$'>Dollar sign ($)</option>";
            selectCSVDelimitergr += "<option value='!'>Exclamation mark (!)</option>";
            selectCSVDelimitergr += "<option value='?'>Question mark (?)</option>";
            selectCSVDelimitergr += "<option value='%'>Percent sign (%)</option>";
            selectCSVDelimitergr += "<option value=';'>Semicolon (;)</option>";
            selectCSVDelimitergr += "<option value=','>Comma (,)</option>";
            selectCSVDelimitergr += "</select>";
    configWindow += "<div>The CSV file that will be generated will contain values delimited by the following character:<div class='expCharTipGr'><i id='expCharInfoGr' class='fa fa-info'></i><div class='expCharTextGr'>It's recommended to choose as delimiter a character that doesn't occur inside any contact data field, such as the backslash (\\), the caret (^) or the backtick (`). It must be noted that every 'Profile Picture' field in the database that is not empty contains a comma (,) and a semicolon (;).</div></div>"+ selectCSVDelimitergr +"</div>";
    configWindow += "<div><input id='expContactsByGroupBtn' class='saveCnfBtn' type='button' value='Export Contacts From Selected Groups'></div>";
    configWindow += "</div>";


    configWindow += "<div id='exportCntctsByDate'>";
    configWindow += "<div>Export the contacts added to Roundpin between the following 2 dates, including the selected dates (to export the contacts added since forever up to a specific date, leave the start date empty):</div>";
    configWindow += "<input id='pickDateForExpst' type='text' placeholder='Pick a start date' value=''>";
    configWindow += "<input id='pickDateForExpfn' type='text' placeholder='Pick an end date' value=''>";

            var selectCSVDelimiterdt = "<select id='csvDelimiterListdt'>";
            selectCSVDelimiterdt += "<option value='\\'>Backslash (\\)</option>";
            selectCSVDelimiterdt += "<option value='^'>Caret (^)</option>";
            selectCSVDelimiterdt += "<option value='`'>Backtick (`)</option>";
            selectCSVDelimiterdt += "<option value='$'>Dollar sign ($)</option>";
            selectCSVDelimiterdt += "<option value='!'>Exclamation mark (!)</option>";
            selectCSVDelimiterdt += "<option value='?'>Question mark (?)</option>";
            selectCSVDelimiterdt += "<option value='%'>Percent sign (%)</option>";
            selectCSVDelimiterdt += "<option value=';'>Semicolon (;)</option>";
            selectCSVDelimiterdt += "<option value=','>Comma (,)</option>";
            selectCSVDelimiterdt += "</select>";
    configWindow += "<div>The CSV file that will be generated will contain values delimited by the following character:<div class='expCharTipDt'><i id='expCharInfoDt' class='fa fa-info'></i><div class='expCharTextDt'>It's recommended to choose as delimiter a character that doesn't occur inside any contact data field, such as the backslash (\\), the caret (^) or the backtick (`). It must be noted that every 'Profile Picture' field in the database that is not empty contains a comma (,) and a semicolon (;).</div></div>"+ selectCSVDelimiterdt +"</div>";
    configWindow += "<div><input id='expContactsByDateBtn' class='saveCnfBtn' type='button' value='Export All Contacts Added Between Selected Dates'></div>";
    configWindow += "</div>";

    configWindow += "<br><br><div class='UiTextMod'>Import contacts:</div>";
    configWindow += "<div id='importContacts'>";
    configWindow += "<div>Enter the character used to delimit the values in the CSV file that will be imported:<div class='importCntsTip'><i id='importCntsInfo' class='fa fa-info'></i><div class='importCntsText'>The character used to delimit the values must not occur inside the values themselves, otherwise the importing process will not succeed.</div></div></div>";
    configWindow += "<input id='impCDelimiter' class='delimitAndRowImport' type='text' placeholder='Enter the character' />";
    configWindow += "<div>Enter the order number of the first row that must be imported from the CSV file:<div class='firstRowNbTip'><i id='firstRowInfo' class='fa fa-info'></i><div class='firstRowText'>If row number 1 of the CSV file contains data that you want to import, enter 1. If row number 1 of the CSV file is a header which contains column names, then the first row of data from the CSV file that must be imported will be row 2, so, enter 2. If the CSV file starts with 3 empty rows, and the 4th row contains the column names, you should enter 5, etc.</div></div></div>";
    configWindow += "<input id='impCFirstRow' class='delimitAndRowImport' type='text' placeholder='Enter the number' />";
    configWindow += "<div>Next to each field from below, enter the order number of the corresponding value in the CSV file that will be imported:<div class='fieldNbTip'><i id='fieldNbInfo' class='fa fa-info'></i><div class='fieldNbText'>For example, if the rows in the CSV file contain the 'Display Name' in the 3rd position from left to right, the 'Title / Description' in the 2nd position, the 'Long Description' in the 6th position, etc., you should enter 3 next to 'Display Name', 2 next to 'Title / Description', 6 next to 'Long Description', etc. Apart from the Display Name field, you can leave any field empty, if the CSV file doesn't contain data that can be inserted in that field.</div></div></div>";
    configWindow += "<table id='CntctsImportMatch'>";
    configWindow += "<tr class='lightGreenBckgrnd'><td class='nameCtImportMatch'>Display Name</td><td><input id='displaynameImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightBlueBckgrnd'><td class='nameCtImportMatch'>Title / Description</td><td><input id='descriptionImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightGreenBckgrnd'><td class='nameCtImportMatch'>Long Description</td><td><input id='longdescImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightBlueBckgrnd'><td class='nameCtImportMatch'>Address - Street</td><td><input id='streetImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightGreenBckgrnd'><td class='nameCtImportMatch'>Address - Zip</td><td><input id='zipImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightBlueBckgrnd'><td class='nameCtImportMatch'>Address - Town</td><td><input id='townImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightGreenBckgrnd'><td class='nameCtImportMatch'>Address - Country</td><td><input id='countryImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightBlueBckgrnd'><td class='nameCtImportMatch'>Address - State/Province</td><td><input id='stateImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightGreenBckgrnd'><td class='nameCtImportMatch'>Extension</td><td><input id='extensionImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightBlueBckgrnd'><td class='nameCtImportMatch'>Mobile Number</td><td><input id='mobileImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightGreenBckgrnd'><td class='nameCtImportMatch'>Contact Number 1</td><td><input id='number1Imp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightBlueBckgrnd'><td class='nameCtImportMatch'>Contact Number 2</td><td><input id='number2Imp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightGreenBckgrnd'><td class='nameCtImportMatch'>Fax Number</td><td><input id='faxnumberImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightBlueBckgrnd'><td class='nameCtImportMatch'>Email</td><td><input id='emailImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightGreenBckgrnd'><td class='nameCtImportMatch'>Profile Picture</td><td><input id='pictureImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "<tr class='lightBlueBckgrnd'><td class='nameCtImportMatch'>Groups</td><td><input id='groupsImp' class='nbCntctsImportMatch' type='text' placeholder='Enter the number' /></td></tr>";
    configWindow += "</table>";

    configWindow += "<table id='sendFileCImpTbl'><tr><td><form id='sendFileCImp' enctype='multipart/form-data'>";
    configWindow += "<label for='selectedFileCImp' id='slctFlCImportBtn' class='custBrowseBttnSfl'>Select File</label>";
    configWindow += "<input type='file' id='selectedFileCImp' name='uploadedFileCImp' />";
    configWindow += "</form></td>";
    configWindow += "<td><span id='upFileCImp'></span></td></tr></table>";
    configWindow += "<input id='impContactsBtn' class='saveCnfBtn' type='button' value='Import Contacts From Selected File Into Database'>";
    configWindow += "</div>";

    configWindow += "<br><br><div class='UiTextMod'>Remove contacts:</div>";
    configWindow += "<div><select id='selectRemoveCType' class='contactGroupsSelect'>";
    configWindow += "<option value=''>Click to choose the type of removal</option>";
    configWindow += "<option value='removeall'>Remove all contacts</option>";
    configWindow += "<option value='removebygroup'>Remove contacts by group</option>";
    configWindow += "<option value='removebydate'>Remove contacts by date</option>";
    configWindow += "</select></div>";


    configWindow += "<div id='removeAllContacts'>";
    configWindow += "<div><input id='removeAllCntctsBtn' class='saveCnfBtn' type='button' value='Remove All Contacts'></div>";
    configWindow += "</div>";


    configWindow += "<div id='removeCntctsByGroup'>";
    configWindow += "<div><select id='contactGrpsToRemove'>";
    configWindow += "<option value=''>Click to choose one or more groups from the list</option>";
             for (var g = 0; g < allGroups.length; g++) {
                  configWindow += "<option value='"+ allGroups[g] +"'>"+ allGroups[g] +"</option>"; 
             }
    configWindow += "</select></div>";
    configWindow += "<div id='contactGrpsRemoveList' class='removeContactGrps'></div>";

    configWindow += "<div><input id='removeCntctsByGrpBtn' class='saveCnfBtn' type='button' value='Remove All Contacts From Selected Groups'></div>";
    configWindow += "</div>";


    configWindow += "<div id='removeCntctsByDate'>";
    configWindow += "<div>Remove the contacts added to Roundpin between the following 2 dates, including the selected dates (to remove the contacts added since forever up to a specific date, leave the start date empty):</div>";
    configWindow += "<input id='pickDateForDelst' type='text' placeholder='Pick a start date' value=''>";
    configWindow += "<input id='pickDateForDelfn' type='text' placeholder='Pick an end date' value=''>";

    configWindow += "<div><input id='removeCntctsByDateBtn' class='saveCnfBtn' type='button' value='Remove All Contacts Added Between Selected Dates'></div>";
    configWindow += "</div>";



    configWindow += "<br></div>";


    configWindow += "<div id='ConferencesHtml' class='settingsSubSection' style='display:none;'>";
    configWindow += "<div class='UiText'>"+ lang.text_conference_extensions +":</div>";

    configWindow += "<table id='txtCnfExtns'>";
    configWindow += "<tr class='tConfthTableRow'><td class='tcHeaderTitle'>Text Conference Extension</td><td class='tcHeaderTitle'>Text Conference Room Label</td><td class='tcHeaderTitle'>Limit Access to these Groups</td><td class='tcHeaderTitle'>Add Groups</td><td></td><td></td></tr>";

    if (!$.isEmptyObject(textExtTagObj)) {
        Object.keys(textExtTagObj).forEach(function(key, u) {

		    var tcExtGroups = "<div id='grpsOfUserListtc-"+ u +"' class='grpsOfUserList'>";
		    tcExtGroups += "<div id='groupsOfUserThtc-"+ u +"'><div class='groupsOfUser grofusrLabel' style='color:#03366c'>Show Groups</div></div>";

                    var tcExtGroupsArr = textExtGroupsObj[key].split("|");
                    tcExtGroupsArr.shift(); tcExtGroupsArr.pop();

		    for (var p = 0; p < tcExtGroupsArr.length; p++) {
		         tcExtGroups += "<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ tcExtGroupsArr[p] +"</div><div id='grpsOfUserRwtc-"+ p +"-"+ u +"' class='rmvGrpFromUser'><i class='fa fa-times' title='Remove this group from this list.'></i></div></div>";
		    }
		    tcExtGroups += "</div>";


		    var tcAddGroups = "<div id='addGrpsToUserListtc-"+ u +"' class='addGrpsToUserList'>";
		    tcAddGroups += "<div id='addGroupsToUserThtc-"+ u +"'><div class='grofusrAddLabel'><i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i></div></div>";

                    var grpsDiffArr = allGroups.filter(function(x) { return tcExtGroupsArr.indexOf(x) == -1; });
		    for (var v = 0; v < grpsDiffArr.length; v++) {
			 tcAddGroups += "<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ grpsDiffArr[v] +"</div><div id='addGrpsToUserRwtc-"+ v +"-"+ u +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups of users allowed to access the conference.'></i></div></div>";
		    }
		    tcAddGroups += "</div>";

	            configWindow += "<tr class='tcbtnTableRow'><td><input type='text' class='txtConfExtension' value='"+ key +"' disabled='disabled' /></td><td><input type='text' class='tConferenceTag' value='"+ textExtTagObj[key] +"' disabled='disabled' /></td><td>"+ tcExtGroups +"</td><td>"+ tcAddGroups +"</td><td><span class='tcDeleteExtRow' title='Delete conference settings from the database.'>X</span></td><td><input type='submit' class='saveCnfBtn' value='Edit' title='Edit this row.' /></td></tr>";
        });
    }

    configWindow += "</table>";
    configWindow += "<button id='addTextConfExt'>Add Extension</button>";


    configWindow += "<div class='UiText'>"+ lang.audio_conference_extensions +":</div>";
    configWindow += "<table id='audioCnfExtns'>";
    configWindow += "<tr class='aConfthTableRow'><td class='acHeaderTitle'>Audio Conference Extension</td><td class='acHeaderTitle'>Audio Conference Room Label</td><td class='acHeaderTitle'>Limit Access to these Groups</td><td class='acHeaderTitle'>Add Groups</td><td></td><td></td></tr>";

    if (!$.isEmptyObject(audioExtTagObj)) {
        Object.keys(audioExtTagObj).forEach(function(key, u) {
//	            configWindow += "<tr class='acbtnTableRow'><td><input type='text' class='audioConfExtension' name='aConfExtension' value='"+ key +"' disabled='disabled' /></td><td><input type='text' class='aConferenceTag' name='aConfRoomLabel' value='"+ audioExtTagObj[key] +"' disabled='disabled' /></td><td><span class='acDeleteExtRow' title='Delete extension data from the database.'>X</span></td><td><input type='submit' class='saveCnfBtn' value='Edit' title='Edit this row.' /></td></tr>";

		    var acExtGroups = "<div id='grpsOfUserListac-"+ u +"' class='grpsOfUserList'>";
		    acExtGroups += "<div id='groupsOfUserThac-"+ u +"'><div class='groupsOfUser grofusrLabel' style='color:#03366c'>Show Groups</div></div>";

                    var acExtGroupsArr = audioExtGroupsObj[key].split("|");
                    acExtGroupsArr.shift(); acExtGroupsArr.pop();

		    for (var p = 0; p < acExtGroupsArr.length; p++) {
		         acExtGroups += "<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ acExtGroupsArr[p] +"</div><div id='grpsOfUserRwac-"+ p +"-"+ u +"' class='rmvGrpFromUser'><i class='fa fa-times' title='Remove this group from this list.'></i></div></div>";
		    }
		    acExtGroups += "</div>";


		    var acAddGroups = "<div id='addGrpsToUserListac-"+ u +"' class='addGrpsToUserList'>";
		    acAddGroups += "<div id='addGroupsToUserThac-"+ u +"'><div class='grofusrAddLabel'><i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i></div></div>";

                    var grpsDiffArr = allGroups.filter(function(x) { return acExtGroupsArr.indexOf(x) == -1; });
		    for (var v = 0; v < grpsDiffArr.length; v++) {
			 acAddGroups += "<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ grpsDiffArr[v] +"</div><div id='addGrpsToUserRwac-"+ v +"-"+ u +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups of users allowed to access the conference.'></i></div></div>";
		    }
		    acAddGroups += "</div>";

	            configWindow += "<tr class='acbtnTableRow'><td><input type='text' class='audioConfExtension' value='"+ key +"' disabled='disabled' /></td><td><input type='text' class='aConferenceTag' value='"+ audioExtTagObj[key] +"' disabled='disabled' /></td><td>"+ acExtGroups +"</td><td>"+ acAddGroups +"</td><td><span class='acDeleteExtRow' title='Delete conference settings from the database.'>X</span></td><td><input type='submit' class='saveCnfBtn' value='Edit' title='Edit this row.' /></td></tr>";

        });
    }

    configWindow += "</table>";
    configWindow += "<button id='addAudioConfExt'>Add Extension</button>";


    configWindow += "<div class='UiText'>"+ lang.video_conference_extensions +":</div>";
    configWindow += "<table id='videoCnfExtns'>";
    configWindow += "<tr class='vConfthTableRow'><td class='vcHeaderTitle'>Video Conference Extension</td><td class='vcHeaderTitle'>Video Conference Room Label</td><td class='vcHeaderTitle'>Limit Access to these Groups</td><td class='vcHeaderTitle'>Add Groups</td><td></td><td></td></tr>";

    if (!$.isEmptyObject(videoExtTagObj)) {
        Object.keys(videoExtTagObj).forEach(function(key, u) {

		    var vcExtGroups = "<div id='grpsOfUserListvc-"+ u +"' class='grpsOfUserList'>";
		    vcExtGroups += "<div id='groupsOfUserThvc-"+ u +"'><div class='groupsOfUser grofusrLabel' style='color:#03366c'>Show Groups</div></div>";

                    var vcExtGroupsArr = videoExtGroupsObj[key].split("|");
                    vcExtGroupsArr.shift(); vcExtGroupsArr.pop();

		    for (var p = 0; p < vcExtGroupsArr.length; p++) {
		         vcExtGroups += "<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ vcExtGroupsArr[p] +"</div><div id='grpsOfUserRwvc-"+ p +"-"+ u +"' class='rmvGrpFromUser'><i class='fa fa-times' title='Remove this group from this list.'></i></div></div>";
		    }
		    vcExtGroups += "</div>";


		    var vcAddGroups = "<div id='addGrpsToUserListvc-"+ u +"' class='addGrpsToUserList'>";
		    vcAddGroups += "<div id='addGroupsToUserThvc-"+ u +"'><div class='grofusrAddLabel'><i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i></div></div>";

                    var grpsDiffArr = allGroups.filter(function(x) { return vcExtGroupsArr.indexOf(x) == -1; });
		    for (var v = 0; v < grpsDiffArr.length; v++) {
			 vcAddGroups += "<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ grpsDiffArr[v] +"</div><div id='addGrpsToUserRwvc-"+ v +"-"+ u +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups of users allowed to access the conference.'></i></div></div>";
		    }
		    vcAddGroups += "</div>";

	            configWindow += "<tr class='vcbtnTableRow'><td><input type='text' class='videoConfExtension' value='"+ key +"' disabled='disabled' /></td><td><input type='text' class='vConferenceTag' value='"+ videoExtTagObj[key] +"' disabled='disabled' /></td><td>"+ vcExtGroups +"</td><td>"+ vcAddGroups +"</td><td><span class='vcDeleteExtRow' title='Delete conference settings from the database.'>X</span></td><td><input type='submit' class='saveCnfBtn' value='Edit' title='Edit this row.' /></td></tr>";

        });
    }

    configWindow += "</table>";
    configWindow += "<button id='addVideoConfExt'>Add Extension</button>";


    configWindow += "<div class=UiText>"+ lang.video_conference_window_width +":</div>";
    configWindow += "<div><input id=Video_Conf_Window_Width class=UiInputText type=text placeholder='"+ lang.video_conf_window_width_explanation +"' value='"+ getDbItem("VidConfWindowWidth", "") +"'></div>";

    configWindow += "<div id='confTableSection'>"+ lang.external_conf_users +"</div>";

    if (cusrrl == 'admin' || cusrrl == "superadmin") {

        configWindow += "<table id='vidConfExternalTable'>";
        configWindow += "<tr class='thTableRow'><td class='euHeaderTitle'>User Extension</td><td class='euHeaderTitle'>SIP Password</td><td class='euHeaderTitle'>Conference Extension</td><td class='euHeaderTitle'>Room Label</td><td class='euHeaderTitle'>Link</td><td></td><td></td><td></td></tr>";

        for (var cqt = 0; cqt < getDbItem("externalUserConfElem", ""); cqt++) {
	     configWindow += "<tr class='btnTableRow'><td><input type='text' class='extConfExtension' value='"+ extUserExtension[cqt] +"' disabled='disabled' /></td><td><input type='password' class='extConfExtensionPass' value='"+ extUserExtensionPass[cqt] +"' disabled='disabled' /></td><td><input type='text' class='vidConferenceExtension' value='"+ vidConferenceExtension[cqt] +"' disabled='disabled' /></td><td><input type='text' class='vidConferenceTag' value='"+ vidConferenceTag[cqt] +"' /></td><td><input type='text' class='extConfExtensionLink' value='"+ confAccessLink[cqt] +"' /></td><td><span class='copyToClipboard'><i class='fa fa-clipboard' title='Copy link to clipboard.'></i></span></td><td><span class='deleteExtRow' title='Delete extension data from the database.'>X</span></td><td><input type='submit' class='saveCnfBtn' value='Edit' title='Edit this row.' /></td></tr>";
        }

	configWindow += "<tr id='emptyExtRow' class='btnTableRow'><td><input type='text' class='extConfExtension' placeholder='Eg: 779' /></td><td><input type='password' class='extConfExtensionPass' placeholder='Eg: d5?W?9q?8rg*R9!eFrVth?9' /></td><td><input type='text' class='vidConferenceExtension' placeholder='Eg: 910' /></td><td><input type='text' class='vidConferenceTag' placeholder='Eg: Sales' /></td><td><input type='text' class='extConfExtensionLink' placeholder='Generated on Save' title='Generated after you click Save' disabled='disabled' /></td><td><span class='copyToClipboard'><i class='fa fa-clipboard' title='Copy link to clipboard.'></i></span></td><td><span class='deleteExtRow deleteExtRowDisabled' title='Delete extension data from the database.'>X</span></td><td><input type='submit' class='saveCnfBtn' value='Save' title='Save this row.' /></td></tr>";
        configWindow += "</table>";
	configWindow += "<button id='add_New_External_User'>Add External User</button>";

    } else {
        configWindow += "<div style='display:block;color:#3b3b3b;margin-top:6px;'>Only users with the role of Admin or Superadmin can configure external users of video conferences.</div>";
    }

    configWindow += "</div>";


    configWindow += "<div id='AppearanceHtml' class='settingsSubSection' style='display:none'>";
    configWindow += "<div id=ImageCanvas style='width:155px;height:155px;'></div>";
    configWindow += "<label for=fileUploader class=customBrowseButton style=\"position: absolute; left: 200px; top: 108px;\">Select File</label>";
    configWindow += "<div><input id=fileUploader type=file></div>";
    configWindow += "<div style='margin-top:50px'></div>";
    configWindow += "</div>";

    configWindow += "<div id='NotificationsHtml' class='settingsSubSection' style='display:none'>";
    configWindow += "<div class='UiText'>"+ lang.language +":</div>";
    configWindow += "<div style='text-align:left'><select id='languageSelect' style='width:100%'></select></div><br>";
    configWindow += "<div class='UiText'>"+ lang.notifications +":</div>";
    configWindow += "<div id='notificationsCheck'><input type='checkbox' id='Settings_Notifications'><label for='Settings_Notifications'> "+ lang.enable_onscreen_notifications +"</label></div>";
    configWindow += "<div class=UiText>"+ lang.show_video_conf_usernames +":</div>";
    configWindow += "<div id='showVcunCheck'><input type=checkbox id=Settings_ShowVconfNames><label for=Settings_ShowVconfNames> "+ lang.enable_show_video_conf_usernames +"</label></div>";
    configWindow += "<div class=UiText>"+ lang.display_video_conf_user +":</div>";
    configWindow += "<div id='displayVcuCheck'><input type=checkbox id=Settings_DisplayVconfUser><label for=Settings_DisplayVconfUser> "+ lang.enable_display_video_conf_user +"</label></div>";
    configWindow += "</div>";


    configWindow += "<div id='SMSHtml' class='settingsSubSection' style='display:none'>";

    configWindow += "<div class='UiText'>Phone number(s) for SMS:<div class='smsPhNoTip'><i id='smsPhNoInfo' class='fa fa-info'></i><div class='smsPhNoText'>Enter all the phone numbers that you have configured for SMS sending in your online account(s) with your SMS provider(s). Each number must be preceded by the <a href='https://en.wikipedia.org/wiki/List_of_country_calling_codes#Alphabetical_order' target='_blank' rel='noreferrer noopener' style='color:#1b60ba'>country calling code</a>, the 'plus' sign and the 2 letter abbreviation of your SIP provider's name. For example, if you use the US phone number 3076543210 rented from Telnyx, and the UK phone number 7654321012 rented from Plivo, you should enter:<br> Tx: +13076543210,Pl: +447654321012 <br>where Tx stands for Telnyx, Pl stands for Plivo, 1 is the country calling code for US and 44 is the country calling code for UK. The 2 letter abbreviation for Telnyx is Tx , for Plivo is Pl , for Flowroute is Fl and for Twilio is Tw . This list of phone numbers will allow you to set any of them as the 'From' number, when sending SMS/MMS messages. If you configure a number for both voice calls and SMS, you should enter it both here and in the 'Voice phone number(s)' field, in the 'Audio & Video' section.</div></div></div>";
    configWindow += "<div><input id='smsNumbers' class='UiInputText' type='text' placeholder='Eg:  Tx: +15676543210,Pl: +491234567891,Fl: +347865432101' value='"+ phoneNumbersFdb['sms_numbers'] +"'></div>";

    configWindow += "<br><div class='UiText'>Default SMS phone number for outgoing SMS:<div class='smsdefPhNoTip'><i id='smsdefPhNoInfo' class='fa fa-info'></i><div class='smsdefPhNoText'>Enter the phone number configured for SMS sending in your online account with your SMS provider, that you want to use as the default number for sending SMS/MMS messages. If you set a number here, when you will send an SMS/MMS message to a mobile number, the receiver will see the message as coming from this number. If you leave this field empty, the first phone number from the numbers listed in the 'Phone number(s) for SMS' field from above, will be used as default number for sending SMS/MMS messages. The phone number must be preceded by the <a href='https://en.wikipedia.org/wiki/List_of_country_calling_codes#Alphabetical_order' target='_blank' rel='noreferrer noopener' style='color:#1b60ba'>country calling code</a>, the 'plus' sign and the 2 letter abbreviation of your SIP provider's name. For example, if you want to set as default number for outbound SMS/MMS messages the US number 3076543210 rented from Telnyx, you should enter Tx: +13076543210 . The 2 letter abbreviation for Telnyx is Tx , for Plivo is Pl , for Flowroute is Fl and for Twilio is Tw .</div></div></div>";
    configWindow += "<div><input id='smsDefaultNumber' class='UiInputText' type='text' placeholder='Tx: +15676543210' value='"+ phoneNumbersFdb['default_sms_number'] +"'></div>";
    configWindow += "</div>";


    configWindow += "<div id='FAXHtml' class='settingsSubSection' style='display:none'>";

    configWindow += "<div class='UiTextMod'>Enter the settings for the Fax provider(s) that you are using:<div class='faxTitleTip'><i id='faxTitleInfo' class='fa fa-info'></i><div class='faxTitleText'>As a Fax provider you can use <a href='https://telnyx.com/sign-up' target='_blank' rel='noreferrer noopener' style='color:#1b60ba'>Telnyx</a> or <a href='https://console.phaxio.com/sf_guard_users/sign_up' target='_blank' rel='noreferrer noopener' style='color:#1b60ba'>Phaxio</a> or both of them.</div></div></div>";

    configWindow += "<br><div style='display:block;margin-bottom:16px;font-size:15px;color:#193b66;font-weight:600;'>Telnyx Settings</div>";

    configWindow += "<div class='UiText'><font style='color:#038f6e;font-weight:600;'>Telnyx</font> API Secret Key:<div class='telnyxKeyTip'><i id='telnyxKeyInfo' class='fa fa-info'></i><div class='telnyxKeyText'>Log in to Telnyx. On the \'Home\' page, in the \'API Keys\' section from the upper right corner, click on \'Manage Keys\'. If you already have an API key, just copy it by clicking the \'Copy to clipboard\' icon. Otherwise, to create an API key, click the \'Create API Key\' button, on the pop up message click \'Create\', then copy the key and enter it in this field.</div></div></div>";
    configWindow += "<div><input id='telnyxsecretkey' class='UiInputText' type='password' placeholder='Eg: d6f172ce92e4f2 ...' value='"+ faxsettingsFdb['tel_secret_key'] +"'></div>";

    configWindow += "<div class='UiText' style='margin-bottom:12px!important'><font style='color:#038f6e;font-weight:600;'>Telnyx</font>  webhook URL:<div class='telnyxWhUrlTip'><i id='telnyxWhUrlInfo' class='fa fa-info'></i><div class='telnyxWhUrlText'>This URL will be used by Telnyx to send status updates for outgoing and incoming faxes. First generate the URL by pressing the button from below, then copy it in your Telnyx account as follows: while logged in to your Telnyx account, click \'Voice\', \'Programmable Voice\', then click the \'Fax API Applications\' tab on the upper bar. If you have already created a Fax API Application, just click on its name and enter the URL that you have just generated in the field \'Send a webhook to the URL\' and click \'Save\'. If you haven't created a Fax API Application, to create one, click on the \'Add new App\' button, then enter a name for the new app in the \'App Name\' field, enter the URL that you have just generated in the field \'Send a webhook to the URL\', and enter the same URL in the field \'If the primary URL fails, send webhook to\'. Under \'RTCP Capture\' check \'No\', then click the \'Save\' button. After you enter all the needed settings, don\'t forget to also click the \'Save\' button on this window, to save all the settings to the database.</div></div></div>";
    configWindow += "<input type='button' id='generateTelRecWhUrl' class='saveCnfBtn' value='Generate new webhook URL'/>";
    configWindow += "<div><input type='text' id='telnyxrecwhurl' class='UiInputText' style='display:inline-block;width:92%!important;' value='"+ faxsettingsFdb['tel_recwh_url'] +"'></div>";
    configWindow += "<span id='copyToClipTel' style='display:inline-block;position:relative;float:right;right:0;top:-40px;font-size:16px;color:#6d82b3;cursor:pointer;' title='Copy to clipboard'><i class='fa fa-clipboard'></i></span>";

    configWindow += "<div class='UiText'><font style='color:#038f6e;font-weight:600;'>Telnyx</font> Fax app ID:<div class='telnyxAppIdTip'><i id='telnyxAppIdInfo' class='fa fa-info'></i><div class='telnyxAppIdText'>While logged in to your Telnyx account, click \'Voice\', \'Programmable Voice\', then click the \'Fax API Applications\' tab on the upper bar. Click on the name of the Fax API Application created earlier, copy the ID under \'App ID\' and enter it in this field.</div></div></div>";
    configWindow += "<div><input id='telnyxfaxappid' class='UiInputText' type='password' placeholder='Eg: 52862419575432 ...' value='"+ faxsettingsFdb['tel_fax_app_id'] +"'></div>";


    configWindow += "<br><div style='display:block;margin-bottom:16px;font-size:15px;color:#193b66;font-weight:600;'>Phaxio Settings</div>";

    configWindow += "<div class='UiText'><font style='color:#038f6e;font-weight:600;'>Phaxio</font> API Key:<div class='phaxKeyTip'><i id='phaxKeyInfo' class='fa fa-info'></i><div class='phaxKeyText'>Log in to your Phaxio account, click \'API Keys\' on the left panel, then, under \'Live Keys\' you will find the \'API Key\'. Enter the \'API Key\' in this field.</div></div></div>";
    configWindow += "<div><input id='phaxapikey' class='UiInputText' type='password' placeholder='Eg: d6f172ce92e4f2 ...' value='"+ faxsettingsFdb['phax_api_key'] +"'></div>";

    configWindow += "<div class='UiText'><font style='color:#038f6e;font-weight:600;'>Phaxio</font> API Secret:<div class='phaxSecretTip'><i id='phaxSecretInfo' class='fa fa-info'></i><div class='phaxSecretText'>While logged in to your Phaxio account, click \'API Keys\' on the left panel, then, under \'Live Keys\' you will find the \'API Secret\'. Enter the \'API Secret\' in this field.</div></div></div>";
    configWindow += "<div><input id='phaxapisecret' class='UiInputText' type='password' placeholder='Eg: 8af589b8ad9579 ...' value='"+ faxsettingsFdb['phax_api_secret'] +"'></div>";

    configWindow += "<div class='UiText'><font style='color:#038f6e;font-weight:600;'>Phaxio</font> Webhook Token:<div class='phaxWhtokenTip'><i id='phaxWhtokenInfo' class='fa fa-info'></i><div class='phaxWhtokenText'>While logged in to your Phaxio account, click \'Webhooks\' on the left panel, then copy the token found under \'Webhook Token\' and enter it in this field.</div></div></div>";
    configWindow += "<div><input id='phaxwebhooktoken' class='UiInputText' type='password' placeholder='Eg: b6d90b72ab6cce450025abd46f4d697' value='"+ faxsettingsFdb['phax_webhook_token'] +"'></div>";

    configWindow += "<div class='UiText' style='margin-bottom:12px!important'><font style='color:#038f6e;font-weight:600;'>Phaxio</font> callback URL for incoming faxes:<div class='phaxRecurlTip'><i id='phaxRecurlInfo' class='fa fa-info'></i><div class='phaxRecurlText'>Phaxio will send the incoming faxes to this URL. First generate the URL by pressing the button from below, then copy it in your Phaxio account as follows: while logged in to your Phaxio account, click on \'Phone Numbers\' on the left panel, then click on the wheel icon on the row of the fax number that you want to configure, then enter the URL that you have just generated in the \'Callback URL\' field and click \'Save\'. After you enter all the needed settings, don\'t forget to also click the \'Save\' button on this window, to save all the settings to the database.</div></div></div>";
    configWindow += "<input type='button' id='generatePhaxRecWhUrl' class='saveCnfBtn' value='Generate new callback URL'/>";
    configWindow += "<div><input type='text' id='phaxreceiveurl' class='UiInputText' style='display:inline-block;width:92%!important;' value='"+ faxsettingsFdb['phax_receive_url'] +"'></div>";
    configWindow += "<span id='copyToClipPhax' style='display:inline-block;position:relative;float:right;right:0;top:-40px;font-size:16px;color:#6d82b3;cursor:pointer;' title='Copy to clipboard'><i class='fa fa-clipboard'></i></span>";

    configWindow += "<br><div class='UiText'>Fax notifications:</div>";
    configWindow += "<div style='display:block;margin:10px 0px 25px 0px;'><input type='checkbox' id='faxnotification'><label for='faxnotification'>Display an onscreen notification when a new fax is received.</label></div>";

    configWindow += "<div class='UiText'>Send a notification to the email address from below, when a new fax is received:</div>";
    configWindow += "<div><input id='faxnotifyemailaddr' class='UiInputText' type='text' placeholder='Eg: john.doe@example.com' value='"+ faxsettingsFdb['notification_email'] +"'></div>";


    configWindow += "<br><div class='UiText'>Phone number(s) for Fax:<div class='faxPhNoTip'><i id='faxPhNoInfo' class='fa fa-info'></i><div class='faxPhNoText'>Enter all the phone numbers that you have configured for fax sending in your online account(s) with your fax provider(s). Each number must be preceded by the <a href='https://en.wikipedia.org/wiki/List_of_country_calling_codes#Alphabetical_order' target='_blank' rel='noreferrer noopener' style='color:#1b60ba'>country calling code</a>, the 'plus' sign and the 2 letter abbreviation of your fax provider's name. For example, if you use the US phone number 3076543210 rented from Phaxio, and the UK phone number 7654321012 rented from Telnyx, you should enter:<br> Ph: +13076543210,Tx: +447654321012 <br>where Ph stands for Phaxio, Tx stands for Telnyx, 1 is the country calling code for US and 44 is the country calling code for UK. The 2 letter abbreviation for Telnyx is Tx and for Phaxio is Ph . This list of phone numbers will allow you to set any of them as the 'From' number, when sending faxes.</div></div></div>";
    configWindow += "<div><input id='faxNumbers' class='UiInputText' type='text' placeholder='Eg:  Tx: +15676543210,Ph: +491234567891,Tw: +347865432101' value='"+ phoneNumbersFdb['fax_numbers'] +"'></div>";

    configWindow += "<div class='UiText'>Default Fax phone number for outgoing faxes:<div class='faxdefPhNoTip'><i id='faxdefPhNoInfo' class='fa fa-info'></i><div class='faxdefPhNoText'>Enter the phone number configured for fax sending in your online account with your fax provider, that you want to use as the default number for sending faxes. If you set a number here, when you will send a fax to a phone number, the receiver will see the fax as coming from this number. If you leave this field empty, the first phone number from the numbers listed in the 'Phone number(s) for Fax' field from above, will be used as default number for sending faxes. The phone number must be preceded by the <a href='https://en.wikipedia.org/wiki/List_of_country_calling_codes#Alphabetical_order' target='_blank' rel='noreferrer noopener' style='color:#1b60ba'>country calling code</a>, the 'plus' sign and the 2 letter abbreviation of your fax provider's name. For example, if you want to set as default number for outbound faxes the US number 3076543210 rented from Telnyx, you should enter Tx: +13076543210 . The 2 letter abbreviation for Telnyx is Tx and for Phaxio is Ph .</div></div></div>";
    configWindow += "<div><input id='faxDefaultNumber' class='UiInputText' type='text' placeholder='Eg:  Tx: +15676543210' value='"+ phoneNumbersFdb['default_fax_number'] +"'></div>";
    configWindow += "</div>";



    configWindow += "<div id='RoundcubeEmailHtml' class='settingsSubSection' style='display:none'>";
    configWindow += "<div class=UiText>"+ lang.email_integration +":</div>";
    configWindow += "<div id='enableRCcheck'><input id='emailIntegration' type='checkbox'><label for='emailIntegration'> "+ lang.enable_roundcube_integration +"</label></div>";
    configWindow += "<div class=UiText>"+ lang.roundcube_domain +":</div>";
    configWindow += "<div><input id=RoundcubeDomain class=UiInputText type=text placeholder='Roundcube domain (Eg: mail.example.com).' value='"+ getDbItem("rcDomain", "") +"'></div>";
    configWindow += "<div class=UiText>"+ lang.roundcube_user +":</div>";
    configWindow += "<div><input id=RoundcubeUser class=UiInputText type=text placeholder='Roundcube login user (Eg: john.doe@example.com or john_doe).' value='"+ RoundcubeUser +"'></div>";
    configWindow += "<div class=UiText>"+ lang.roundcube_password +":</div>";
    configWindow += "<div><input id=RoundcubePass class=UiInputText type=password placeholder='Roundcube login password.' value='"+ RoundcubePass +"'></div>";
    configWindow += "<div class=UiText>"+ lang.rc_basic_auth_user +":<div class='rcBasicAuthTip'><i id='rcBasicAuthInfo' class='fa fa-info'></i><div class='rcBasicAuthText'>Fill in this field only if basic authentication is enabled for Roundcube. Loading Roundcube with basic authentication works in Firefox but not in Chrome and similar browsers (Opera, Vivaldi, etc.).</div></div></div>";
    configWindow += "<div><input id=rcBasicAuthUser class=UiInputText type=text placeholder='If you have a Roundcube basic auth user, enter it here.' value='"+ rcBasicAuthUser +"'></div>";
    configWindow += "<div class=UiText>"+ lang.rc_basic_auth_password +":<div class='rcpwBasicAuthTip'><i id='rcpwBasicAuthInfo' class='fa fa-info'></i><div class='rcpwBasicAuthText'>Fill in this field only if basic authentication is enabled for Roundcube. Loading Roundcube with basic authentication works in Firefox but not in Chrome and similar browsers (Opera, Vivaldi, etc.).</div></div></div>";
    configWindow += "<div><input id=rcBasicAuthPass class=UiInputText type=password placeholder='If you have a Roundcube basic auth password, enter it here.' value='"+ rcBasicAuthPass +"'></div>";

    configWindow += "<br><br></div>";

    configWindow += "<div id='ChangePasswordHtml' class='settingsSubSection' style='display:none'>";
    configWindow += "<div class=UiText>"+ lang.current_user_password +":</div>";
    configWindow += "<div><input id=Current_User_Password class=UiInputText type=password placeholder='Enter your current Roundpin user password.' value=''></div>";
    configWindow += "<div class=UiText>"+ lang.new_user_password +":</div>";
    configWindow += "<div><input id=New_User_Password class=UiInputText type=password placeholder='Enter your new Roundpin user password.' value=''></div>";
    configWindow += "<div class=UiText>"+ lang.repeat_new_user_password +":</div>";
    configWindow += "<div><input id=Repeat_New_User_Password class=UiInputText type=password placeholder='Enter your new Roundpin user password again.' value=''></div><br>";
    configWindow += "<div><input id=Save_New_User_Password type=button value='Save New Password' onclick='saveNewUserPassword()' ></div>";
    configWindow += "<br><br></div>";

    configWindow += "<div id='ChangeEmailHtml' class='settingsSubSection' style='display:none'>";
    configWindow += "<div class=UiText>"+ lang.current_user_email +":</div>";
    configWindow += "<div><input id=Current_User_Email class=UiInputText type=text placeholder='Enter your current Roundpin email address.' value=''></div>";
    configWindow += "<div class=UiText>"+ lang.new_user_email +":</div>";
    configWindow += "<div><input id=New_User_Email class=UiInputText type=text placeholder='Enter your new email address.' value=''></div>";
    configWindow += "<div class=UiText>"+ lang.repeat_new_user_email +":</div>";
    configWindow += "<div><input id=Repeat_New_User_Email class=UiInputText type=text placeholder='Enter your new email address again.' value=''></div><br>";
    configWindow += "<div><input id=Save_New_User_Email type=button value='Save New Email' onclick='saveNewUserEmail()' ></div>";
    configWindow += "<br><br></div>";

    configWindow += "<div id='AccountDetailsHtml' class='settingsSubSection' style='display:none;'>";
    configWindow += "<table><tr><td><div class='UiText'>"+ lang.logged_in_as +"</div></td><td><div><span style='display:inline-block;color:#5e1d70;padding-left:10px;font-size:15px;font-weight:500;font-family:Inter,sans-serif;margin-top:8px;word-break:break-word;'>"+ userName +"</span></div></td></tr>";

    var crntUsrRole = getDbItem("userrole", "");
    if (crntUsrRole == 'regular_user') {
        var crntUsrRoleFin = 'Regular User';
        var roleInfoText = "As a Regular User, you can use all the functionalities of Roundpin, but you cannot change the user role or groups of Roundpin users, you cannot create or remove groups, you cannot change the settings of text/audio/video conferences and you cannot eject participants from video conferences.";
    } else if (crntUsrRole == 'admin') {
        var crntUsrRoleFin = 'Admin';
        var roleInfoText = "As an Admin, you can use all the functionalities of Roundpin, you can change the user role of a Regular User from Regular User to Admin but not to Superadmin, you cannot change the user role of other Admins or Superadmins, you can change the groups for Regular Users but not for Admins or Superadmins, you can create groups and you can remove the groups created by you, you can configure external users of video conferences, you can change the settings of the text/audio/video conferences that you added, but not the settings of the conferences added by others and you can eject Regular Users from video conferences but you cannot eject Admins or Superadmins from video conferences.";
    } else if (crntUsrRole == 'superadmin') {
        var crntUsrRoleFin = 'Superadmin';
        var roleInfoText = "As a Superadmin you have the highest level of privileges: you can use all the functionalities of Roundpin, you can change the user role and groups of any user, including yourself, you can create groups and you can remove any group, you can configure external users of video conferences, you can change the settings of all the text/audio/video conferences and you can eject Regular Users, Admins and even Superadmins from video conferences.";
    }

    configWindow += "<tr><td><div class='UiText'>Your role is:</div></td><td><div><span style='display:inline-block;color:#5e1d70;padding-left:10px;font-size:15px;font-weight:500;font-family:Inter,sans-serif;margin-top:8px;word-break:break-word;'>"+ crntUsrRoleFin +"</span><div class='userRoleTip'><i id='userRoleInfo' class='fa fa-info'></i><div class='userRoleText'>"+ roleInfoText +"</div></div></div></td></tr>";
    configWindow += "<tr><td><div class='UiText'>Account created on:</div></td><td><div><span style='display:inline-block;color:#52a390;padding-left:10px;font-size:14px;font-weight:500;font-family:Inter,sans-serif;margin-top:8px;word-break:break-word;'>"+ getDbItem("dateAdded", "") +"</span></div></td></tr>";
    configWindow += "<tr><td><div class='UiText'>Last modified on:</div></td><td><div><span style='display:inline-block;color:#52a390;padding-left:10px;font-size:14px;font-weight:500;font-family:Inter,sans-serif;margin-top:8px;word-break:break-word;'>"+ getDbItem("dateModified", "") +"</span></div></td></tr></table>";

    configWindow += "<br><div class='UiText'>"+ lang.if_you_want_to_close_account +":";
    configWindow += "<input id='Close_User_Account' type='button' value='Close User Account' onclick='closeUserAccount()' ></div>";
    configWindow += "</div>";

    configWindow += "</div></div></div>";

    var settingsSections = "<table id=leftPanelSettings cellspacing=14 cellpadding=0 style=\"width:184px;margin-left:8px;margin-top:14px;font-size:15px;\">";
    settingsSections += "<tr id=ConnectionSettingsRow><td class=SettingsSection>"+ lang.connection_settings +"</td></tr>";
    settingsSections += "<tr id=AudioAndVideoRow><td class=SettingsSection>"+ lang.audio_and_video +"</td></tr>";
    settingsSections += "<tr id=UsersGroupsRow><td class=SettingsSection>"+ lang.groups_and_users +"</td></tr>";
    settingsSections += "<tr id=ContactsRow><td class=SettingsSection>"+ lang.contacts +"</td></tr>";
    settingsSections += "<tr id=ConferencesRow><td class=SettingsSection>"+ lang.conferences +"</td></tr>";
    settingsSections += "<tr id=ProfilePictureRow><td class=SettingsSection>"+ lang.profile_picture +"</td></tr>";
    settingsSections += "<tr id=NotificationsRow><td class=SettingsSection>"+ lang.display +"</td></tr>";
    settingsSections += "<tr id=SMSRow><td class=SettingsSection>"+ lang.sms +"</td></tr>";
    settingsSections += "<tr id=FAXRow><td class=SettingsSection>"+ lang.fax +"</td></tr>";
    settingsSections += "<tr id=RoundcubeEmailRow><td class=SettingsSection>"+ lang.email +"</td></tr>";
    settingsSections += "<tr id=ChangePasswordRow><td class=SettingsSection>"+ lang.change_password +"</td></tr>";
    settingsSections += "<tr id=ChangeEmailRow><td class=SettingsSection>"+ lang.change_email +"</td></tr>";
    settingsSections += "<tr id=AccountDetailsRow><td class=SettingsSection>"+ lang.account +"</td></tr></table>";

    $.jeegoopopup.open({
                title: '<span id=settingsTitle>Settings</span>',
                html: configWindow,
                width: '520',
                height: '500',
                center: true,
//                scrolling: 'no',
                scrolling: 'yes',
                skinClass: 'jg_popup_basic',
                contentClass: 'configPopup',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $("#jg_popup_b").append('<div id="bottomButtonsConf"><button id="save_button_conf">Save</button><button id="cancel_button_conf">Cancel</button></div>');
    $("#jg_popup_l").append(settingsSections);

    if (getDbItem("useRoundcube", "") == 1) { $("#emailIntegration").prop("checked", true); } else { $("#emailIntegration").prop("checked", false); }
    if (getDbItem("ShowVconfUsernames", "") == 1) { $("#Settings_ShowVconfNames").prop("checked", true); } else { $("#Settings_ShowVconfNames").prop("checked", false); }
    if (getDbItem("DisplayVconfUser", "") == 1) { $("#Settings_DisplayVconfUser").prop("checked", true); } else { $("#Settings_DisplayVconfUser").prop("checked", false); }
    if (faxsettingsFdb['get_notification'] == 1) { $("#faxnotification").prop("checked", true); } else { $("#faxnotification").prop("checked", false); }

    //Generate the webhook URL for Telnyx faxes
    $("#generateTelRecWhUrl").on("click", function() {
       let telrandname = "telnyxfaxwh-"+ randString(55);
       let telnyxwhurlrec = window.location.origin + "/src/"+ telrandname +".php";

       $("#telnyxrecwhurl").val(telnyxwhurlrec);
    });

    // Copy the webhook URL for Telnyx faxes to clipboard
    $("#copyToClipTel").on("click", function() {
       let $tempElm = $("<input>");
       $("body").append($tempElm);
       $tempElm.val($("#telnyxrecwhurl").val()).select();
       document.execCommand("Copy");
       $tempElm.remove();
       alert("The URL has been copied to your clipboard!");
    });


    //Generate the callback URL for Phaxio faxes
    $("#generatePhaxRecWhUrl").on("click", function() {
       let phaxrandname = "phaxiofaxwh-"+ randString(55);
       let phaxiowhurlrec = window.location.origin + "/src/"+ phaxrandname +".php";

       $("#phaxreceiveurl").val(phaxiowhurlrec);
    });

    // Copy the webhook URL for Telnyx faxes to clipboard
    $("#copyToClipPhax").on("click", function() {
       let $tempElm = $("<input>");
       $("body").append($tempElm);
       $tempElm.val($("#phaxreceiveurl").val()).select();
       document.execCommand("Copy");
       $tempElm.remove();
       alert("The URL has been copied to your clipboard!");
    });


    $("#ConnectionSettingsRow td").addClass("selectedSettingsSection");

    $("#ConnectionSettingsRow").click(function() {
        $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
        $("#AccountHtml").css("display", "block");
        $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
        $("#ConnectionSettingsRow td").addClass("selectedSettingsSection");
    });

    $("#ContactsRow").click(function() {
        $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
        $("#ContactsHtml").css("display", "block");
        $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
        $("#ContactsRow td").addClass("selectedSettingsSection");
    });

    $("#ConferencesRow").click(function() {
        $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
        $("#ConferencesHtml").css("display", "block");
        $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
        $("#ConferencesRow td").addClass("selectedSettingsSection");
    });

    $("#UsersGroupsRow").click(function() {
        $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
        $("#UsersAndGroups").css("display", "block");
        $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
        $("#UsersGroupsRow td").addClass("selectedSettingsSection");
    });

    $("#ProfilePictureRow").click(function() {
        $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
        $("#AppearanceHtml").css("display", "block");
        $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
        $("#ProfilePictureRow td").addClass("selectedSettingsSection");
    });

    $("#NotificationsRow").click(function() {
        $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
        $("#NotificationsHtml").css("display", "block");
        $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
        $("#NotificationsRow td").addClass("selectedSettingsSection");
    });

    $("#SMSRow").click(function() {
        $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
        $("#SMSHtml").css("display", "block");
        $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
        $("#SMSRow td").addClass("selectedSettingsSection");
    });

    $("#FAXRow").click(function() {
        $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
        $("#FAXHtml").css("display", "block");
        $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
        $("#FAXRow td").addClass("selectedSettingsSection");
    });

    $("#RoundcubeEmailRow").click(function() {
        $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
        $("#RoundcubeEmailHtml").css("display", "block");
        $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
        $("#RoundcubeEmailRow td").addClass("selectedSettingsSection");
    });

    $("#ChangePasswordRow").click(function() {
        $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
        $("#ChangePasswordHtml").css("display", "block");
        $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
        $("#ChangePasswordRow td").addClass("selectedSettingsSection");
    });

    $("#ChangeEmailRow").click(function() {
        $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
        $("#ChangeEmailHtml").css("display", "block");
        $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
        $("#ChangeEmailRow td").addClass("selectedSettingsSection");
    });

    $("#AccountDetailsRow").click(function() {
        $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
        $("#AccountDetailsHtml").css("display", "block");
        $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
        $("#AccountDetailsRow td").addClass("selectedSettingsSection");
    });

    var maxWidth = $(window).width() - 192;
    var maxHeight = $(window).height() - 98;

    if (maxWidth < 520 || maxHeight < 500) { 
       $.jeegoopopup.width(maxWidth).height(maxHeight);
       $.jeegoopopup.center();
       $("#maximizeImg").hide();
       $("#minimizeImg").hide();
    } else { 
       $.jeegoopopup.width(520).height(500);
       $.jeegoopopup.center();
       $("#minimizeImg").hide();
       $("#maximizeImg").show();
    }

    $(window).resize(function() {
       maxWidth = $(window).width() - 192;
       maxHeight = $(window).height() - 98;
       $.jeegoopopup.center();
       if (maxWidth < 520 || maxHeight < 500) { 
           $.jeegoopopup.width(maxWidth).height(maxHeight);
           $.jeegoopopup.center();
           $("#maximizeImg").hide();
           $("#minimizeImg").hide();
       } else { 
           $.jeegoopopup.width(520).height(500);
           $.jeegoopopup.center();
           $("#minimizeImg").hide();
           $("#maximizeImg").show();
       }
    });

    $("#minimizeImg").click(function() { $.jeegoopopup.width(520).height(500); $.jeegoopopup.center(); $("#maximizeImg").show(); $("#minimizeImg").hide(); });
    $("#maximizeImg").click(function() { $.jeegoopopup.width(maxWidth).height(maxHeight); $.jeegoopopup.center(); $("#minimizeImg").show(); $("#maximizeImg").hide(); });


    // Output
    var selectAudioScr = $("#playbackSrc");

    var playButton = $("#preview_output_play");

    var pauseButton = $("#preview_output_pause");

    var playRingButton = $("#preview_ringer_play");

    // Microphone
    var selectMicScr = $("#microphoneSrc");
    $("#Settings_AutoGainControl").prop("checked", AutoGainControl);
    $("#Settings_EchoCancellation").prop("checked", EchoCancellation);
    $("#Settings_NoiseSuppression").prop("checked", NoiseSuppression);

    // Webcam
    var selectVideoScr = $("#previewVideoSrc");

    // Orientation
    var OrientationSel = $("input[name=Settings_Oriteation]");
    OrientationSel.each(function(){
        if(this.value == MirrorVideo) $(this).prop("checked", true);
    });
    $("#local-video-preview").css("transform", MirrorVideo);

    // Frame Rate
    var frameRateSel = $("input[name=Settings_FrameRate]");
    frameRateSel.each(function(){
        if(this.value == maxFrameRate) $(this).prop("checked", true);
    });

    // Quality
    var QualitySel = $("input[name=Settings_Quality]");
    QualitySel.each(function(){
        if(this.value == videoHeight) $(this).prop("checked", true);
    });

    // Aspect Ratio
    var AspectRatioSel = $("input[name=Settings_AspectRatio]");
    AspectRatioSel.each(function(){
        if(this.value == videoAspectRatio) $(this).prop("checked", true);
    });

    // Ring Device
    var selectRingDevice = $("#ringDevice");

    // Ringtone
    var dbRingtone = getDbItem("Ringtone", "");

    if (dbRingtone == 'null' || dbRingtone == '') { 
        $("#ringingTone").val("nm-simple.ogg").prop("selected", true);
        localDB.setItem("Ringtone", "nm-simple.ogg");

        var oReq = new XMLHttpRequest();
        oReq.open("GET", audioBlobs.selectedRingtone.url, true);
        oReq.responseType = "blob";
        oReq.onload = function(oEvent) {
             var reader = new FileReader();
             reader.readAsDataURL(oReq.response);
             reader.onload = function() {
                 audioBlobs.selectedRingtone.blob = reader.result;
             }
        }
        oReq.send();

    } else { $("#ringingTone").val(dbRingtone); }

    $("#ringingTone").change(function() {
       var selectedRTn = $("#ringingTone").val();
       localDB.setItem("Ringtone", selectedRTn);
       audioBlobs.selectedRingtone = { file : selectedRTn, url : hostingPrefix +"sounds/ringtones/"+ selectedRTn.slice(0, -4) +"/"+ selectedRTn +"" };

       var oReq = new XMLHttpRequest();
       oReq.open("GET", audioBlobs.selectedRingtone.url, true);
       oReq.responseType = "blob";
       oReq.onload = function(oEvent) {
            var reader = new FileReader();
            reader.readAsDataURL(oReq.response);
            reader.onload = function() {
                audioBlobs.selectedRingtone.blob = reader.result;
            }
       }
       oReq.send();
    });

    // Handle Aspect Ratio Change
    AspectRatioSel.change(function(){
        console.log("Call to change Aspect Ratio ("+ this.value +")");

        var localVideo = $("#local-video-preview").get(0);
        localVideo.muted = true;
        localVideo.playsinline = true;
        localVideo.autoplay = true;

        var tracks = localVideo.srcObject.getTracks();
        tracks.forEach(function(track) {
            track.stop();
        });

        var constraints = {
            audio: false,
            video: {
                deviceId: (selectVideoScr.val() != "default")? { exact: selectVideoScr.val() } : "default"
            }
        }
        if($("input[name=Settings_FrameRate]:checked").val() != ""){
            constraints.video.frameRate = $("input[name=Settings_FrameRate]:checked").val();
        }
        if($("input[name=Settings_Quality]:checked").val() != ""){
            constraints.video.height = $("input[name=Settings_Quality]:checked").val();
        }
        if(this.value != ""){
            constraints.video.aspectRatio = this.value;
        }        
        console.log("Constraints:", constraints);
        var localStream = new MediaStream();
        if(navigator.mediaDevices){
            navigator.mediaDevices.getUserMedia(constraints).then(function(newStream){
                var videoTrack = newStream.getVideoTracks()[0];
                localStream.addTrack(videoTrack);
                localVideo.srcObject = localStream;
                localVideo.onloadedmetadata = function(e) {
                    localVideo.play();
                }
            }).catch(function(e){
                console.error(e);
                AlertConfigExtWindow(lang.alert_error_user_media, lang.error);
            });
        }
    });

    // Handle Video Height Change
    QualitySel.change(function(){    
        console.log("Call to change Video Height ("+ this.value +")");

        var localVideo = $("#local-video-preview").get(0);
        localVideo.muted = true;
        localVideo.playsinline = true;
        localVideo.autoplay = true;

        var tracks = localVideo.srcObject.getTracks();
        tracks.forEach(function(track) {
            track.stop();
        });

        var constraints = {
            audio: false,
            video: {
                deviceId: (selectVideoScr.val() != "default")? { exact: selectVideoScr.val() } : "default" ,
            }
        }
        if($("input[name=Settings_FrameRate]:checked").val() != ""){
            constraints.video.frameRate = $("input[name=Settings_FrameRate]:checked").val();
        }
        if(this.value){
            constraints.video.height = this.value;
        }
        if($("input[name=Settings_AspectRatio]:checked").val() != ""){
            constraints.video.aspectRatio = $("input[name=Settings_AspectRatio]:checked").val();
        } 
        console.log("Constraints:", constraints);
        var localStream = new MediaStream();
        if(navigator.mediaDevices){
            navigator.mediaDevices.getUserMedia(constraints).then(function(newStream){
                var videoTrack = newStream.getVideoTracks()[0];
                localStream.addTrack(videoTrack);
                localVideo.srcObject = localStream;
                localVideo.onloadedmetadata = function(e) {
                    localVideo.play();
                }
            }).catch(function(e){
                console.error(e);
                AlertConfigExtWindow(lang.alert_error_user_media, lang.error);
            });
        }
    });    

    // Handle Frame Rate Change 
    frameRateSel.change(function(){
        console.log("Call to change Frame Rate ("+ this.value +")");

        var localVideo = $("#local-video-preview").get(0);
        localVideo.muted = true;
        localVideo.playsinline = true;
        localVideo.autoplay = true;

        var tracks = localVideo.srcObject.getTracks();
        tracks.forEach(function(track) {
            track.stop();
        });

        var constraints = {
            audio: false,
            video: {
                deviceId: (selectVideoScr.val() != "default")? { exact: selectVideoScr.val() } : "default" ,
            }
        }
        if(this.value != ""){
            constraints.video.frameRate = this.value;
        }
        if($("input[name=Settings_Quality]:checked").val() != ""){
            constraints.video.height = $("input[name=Settings_Quality]:checked").val();
        }
        if($("input[name=Settings_AspectRatio]:checked").val() != ""){
            constraints.video.aspectRatio = $("input[name=Settings_AspectRatio]:checked").val();
        } 
        console.log("Constraints:", constraints);
        var localStream = new MediaStream();
        if(navigator.mediaDevices){
            navigator.mediaDevices.getUserMedia(constraints).then(function(newStream){
                var videoTrack = newStream.getVideoTracks()[0];
                localStream.addTrack(videoTrack);
                localVideo.srcObject = localStream;
                localVideo.onloadedmetadata = function(e) {
                    localVideo.play();
                }
            }).catch(function(e){
                console.error(e);
                AlertConfigExtWindow(lang.alert_error_user_media, lang.error);
            });
        }
    });

    // Handle Audio Source changes (Microphone)
    selectMicScr.change(function(){
        console.log("Call to change Microphone ("+ this.value +")");

        // Change and update visual preview
        try {
            var tracks = window.SettingsMicrophoneStream.getTracks();
            tracks.forEach(function(track) {
                track.stop();
            });
            window.SettingsMicrophoneStream = null;
        }
        catch(e){}

        try{
            soundMeter = window.SettingsMicrophoneSoundMeter;
            soundMeter.stop();
            window.SettingsMicrophoneSoundMeter = null;
        }
        catch(e){}

        // Get Microphone
        var constraints = { 
            audio: {
                deviceId: { exact: this.value }
            }, 
            video: false 
        }
        var localMicrophoneStream = new MediaStream();
        navigator.mediaDevices.getUserMedia(constraints).then(function(mediaStream){
            var audioTrack = mediaStream.getAudioTracks()[0];
            if(audioTrack != null){
                // Display Microphone Levels
                localMicrophoneStream.addTrack(audioTrack);
                window.SettingsMicrophoneStream = localMicrophoneStream;
                window.SettingsMicrophoneSoundMeter = MeterSettingsOutput(localMicrophoneStream, "Settings_MicrophoneOutput", "width", 50);
            }
        }).catch(function(e){
            console.log("Failed to getUserMedia", e);
        });
    });

    // Handle output change (speaker)
    selectAudioScr.change(function() {
        console.log("Call to change Speaker ("+ this.value +")");

        var audioObj = window.SettingsOutputAudio;
        if (audioObj != null) {
            if (typeof audioObj.sinkId !== 'undefined') {
                audioObj.setSinkId(this.value).then(function() {
                    console.log("sinkId applied to audioObj:", this.value);
                }).catch(function(e){
                    console.warn("Failed to apply setSinkId.", e);
                });
            }
        }
    });

    // Play sound to test output device
    playButton.click(function(){

        try{
            window.SettingsOutputAudio.pause();
        } 
        catch(e){}
        window.SettingsOutputAudio = null;

        try{
            var tracks = window.SettingsOutputStream.getTracks();
            tracks.forEach(function(track) {
                track.stop();
            });
        }
        catch(e){}
        window.SettingsOutputStream = null;

        try{
            var soundMeter = window.SettingsOutputStreamMeter;
            soundMeter.stop();
        }
        catch(e){}
        window.SettingsOutputStreamMeter = null;

        // Load Sample
        console.log("Audio:", audioBlobs.speaker_test.url);
        var audioObj = new Audio(audioBlobs.speaker_test.blob);
        audioObj.preload = "auto";
        audioObj.onplay = function(){
            var outputStream = new MediaStream();
            if (typeof audioObj.captureStream !== 'undefined') {
                outputStream = audioObj.captureStream();
            } 
            else if (typeof audioObj.mozCaptureStream !== 'undefined') {
                return;
            }
            else if (typeof audioObj.webkitCaptureStream !== 'undefined') {
                outputStream = audioObj.webkitCaptureStream();
            }
            else {
                console.warn("Cannot display Audio Levels")
                return;
            }

            // Display Speaker Levels
            window.SettingsOutputStream = outputStream;
            window.SettingsOutputStreamMeter = MeterSettingsOutput(outputStream, "Settings_SpeakerOutput", "width", 50);
        }
        audioObj.oncanplaythrough = function(e) {
            if (typeof audioObj.sinkId !== 'undefined') {
                audioObj.setSinkId(selectAudioScr.val()).then(function() {
                    console.log("Set sinkId to:", selectAudioScr.val());
                }).catch(function(e){
                    console.warn("Failed to apply setSinkId.", e);
                });
            }
            // Play
            audioObj.play().then(function(){
            // Audio Is Playing
            }).catch(function(e){
                console.warn("Unable to play audio file", e);
            });
            console.log("Playing sample audio file... ");
        }

        window.SettingsOutputAudio = audioObj;
    });

    // Pause sound played to test output device
    pauseButton.click(function() {
       if (window.SettingsOutputAudio.paused) {
           window.SettingsOutputAudio.play();
       } else { 
           window.SettingsOutputAudio.pause();
         }
    });


    // Play sound to test ring device
    playRingButton.click(function(){

        try{
            window.SettingsRingerAudio.pause();
        } 
        catch(e){}
        window.SettingsRingerAudio = null;

        try{
            var tracks = window.SettingsRingerStream.getTracks();
            tracks.forEach(function(track) {
                track.stop();
            });
        }
        catch(e){}
        window.SettingsRingerStream = null;

        try{
            var soundMeter = window.SettingsRingerStreamMeter;
            soundMeter.stop();
        }
        catch(e){}
        window.SettingsRingerStreamMeter = null;

        // Load Sample
        console.log("Audio:", audioBlobs.defaultRingtone.url);
        var audioObj = new Audio(audioBlobs.defaultRingtone.blob);
        audioObj.preload = "auto";
        audioObj.onplay = function(){
            var outputStream = new MediaStream();
            if (typeof audioObj.captureStream !== 'undefined') {
                outputStream = audioObj.captureStream();
            } 
            else if (typeof audioObj.mozCaptureStream !== 'undefined') {
                return;
                // BUG: mozCaptureStream() in Firefox does not work the same way as captureStream()
                // the actual sound does not play out to the speakers... its as if the mozCaptureStream
                // removes the stream from the <audio> object.
            }
            else if (typeof audioObj.webkitCaptureStream !== 'undefined') {
                outputStream = audioObj.webkitCaptureStream();
            }
            else {
                console.warn("Cannot display Audio Levels")
                return;
            }
            // Monitor Output
            window.SettingsRingerStream = outputStream;
            window.SettingsRingerStreamMeter = MeterSettingsOutput(outputStream, "Settings_RingerOutput", "width", 50);
        }
        audioObj.oncanplaythrough = function(e) {
            if (typeof audioObj.sinkId !== 'undefined') {
                audioObj.setSinkId(selectRingDevice.val()).then(function() {
                    console.log("Set sinkId to:", selectRingDevice.val());
                }).catch(function(e){
                    console.warn("Failed to apply setSinkId.", e);
                });
            }
            // Play
            audioObj.play().then(function(){
            // Audio Is Playing
            }).catch(function(e){
                console.warn("Unable to play audio file", e);
            });
            console.log("Playing sample audio file... ");
        }

        window.SettingsRingerAudio = audioObj;
    });


    // Play sound to test ringtone
    $("#preview_ringtone").click(function() {

       console.log("Audio:", audioBlobs.selectedRingtone.url);
       var audioObj = new Audio(audioBlobs.selectedRingtone.blob);
       audioObj.preload = "auto";

       // Play
       audioObj.play().then(function() {
       // Audio is playing
       }).catch(function(e) {
           console.warn("Unable to play audio file", e);
       });
       console.log("Playing ringtone... ");

    });


    // Change Video Image
    OrientationSel.change(function(){
        console.log("Call to change Orientation ("+ this.value +")");
        $("#local-video-preview").css("transform", this.value);
    });

    // Handle video input change (WebCam)
    selectVideoScr.change(function(){
        console.log("Call to change WebCam ("+ this.value +")");

        var localVideo = $("#local-video-preview").get(0);
        localVideo.muted = true;
        localVideo.playsinline = true;
        localVideo.autoplay = true;

        var tracks = localVideo.srcObject.getTracks();
        tracks.forEach(function(track) {
            track.stop();
        });

        var constraints = {
            audio: false,
            video: {
                deviceId: (this.value != "default")? { exact: this.value } : "default"
            }
        }
        if($("input[name=Settings_FrameRate]:checked").val() != ""){
            constraints.video.frameRate = $("input[name=Settings_FrameRate]:checked").val();
        }
        if($("input[name=Settings_Quality]:checked").val() != ""){
            constraints.video.height = $("input[name=Settings_Quality]:checked").val();
        }
        if($("input[name=Settings_AspectRatio]:checked").val() != ""){
            constraints.video.aspectRatio = $("input[name=Settings_AspectRatio]:checked").val();
        } 
        console.log("Constraints:", constraints);
        var localStream = new MediaStream();
        if(navigator.mediaDevices){
            navigator.mediaDevices.getUserMedia(constraints).then(function(newStream){
                var videoTrack = newStream.getVideoTracks()[0];
                localStream.addTrack(videoTrack);
                localVideo.srcObject = localStream;
                localVideo.onloadedmetadata = function(e) {
                    localVideo.play();
                }
            }).catch(function(e){
                console.error(e);
                AlertConfigExtWindow(lang.alert_error_user_media, lang.error);
            });
        }
    });

    $("#AudioAndVideoRow").click(function() {

       $(".settingsSubSection").each(function() { $(this).css("display", "none"); });
       $("#AudioVideoHtml").css("display", "block");
       $(".SettingsSection").each(function() { $(this).removeClass("selectedSettingsSection"); });
       $("#AudioAndVideoRow td").addClass("selectedSettingsSection");

       if (videoAudioCheck == 0) {

            videoAudioCheck = 1;

	    // Note: Only works over HTTPS or via localhost!!
	    var localVideo = $("#local-video-preview").get(0);
	    localVideo.muted = true;
	    localVideo.playsinline = true;
	    localVideo.autoplay = true;

	    var localVideoStream = new MediaStream();
	    var localMicrophoneStream = new MediaStream();


            navigator.mediaDevices.enumerateDevices().then(function(deviceInfos) {

		    var savedVideoDevice = getVideoSrcID();
		    var videoDeviceFound = false;
	    
		    var savedAudioDevice = getAudioSrcID();
		    var audioDeviceFound = false;
	    
		    var MicrophoneFound = false;
		    var SpeakerFound = false;
		    var VideoFound = false;
	    
		    for (var i = 0; i < deviceInfos.length; ++i) {

		         console.log("Found Device ("+ deviceInfos[i].kind +"): ", deviceInfos[i].label);
	    
		         // Check Devices
		         if (deviceInfos[i].kind === "audioinput") {
		             MicrophoneFound = true;
		             if (savedAudioDevice != "default" && deviceInfos[i].deviceId == savedAudioDevice) {
		                 audioDeviceFound = true;
		             }
		         } else if (deviceInfos[i].kind === "audiooutput") {
		                    SpeakerFound = true;
		         } else if (deviceInfos[i].kind === "videoinput") {
		                    VideoFound = true;
		                    if (savedVideoDevice != "default" && deviceInfos[i].deviceId == savedVideoDevice) {
		                        videoDeviceFound = true;
		                    }
		         }
		    }
	    
		    var constraints = {
		        audio: MicrophoneFound,
		        video: VideoFound
		    }
	    
		    if (MicrophoneFound) {
		        constraints.audio = { deviceId: "default" }
		        if(audioDeviceFound) constraints.audio.deviceId = { exact: savedAudioDevice }
		    }

	            if (VideoFound) {
	                constraints.video = { deviceId: "default" }
	                if(videoDeviceFound) constraints.video.deviceId = { exact: savedVideoDevice }
	            }
	            // Additional
	            if ($("input[name=Settings_FrameRate]:checked").val() != "") {
	                constraints.video.frameRate = $("input[name=Settings_FrameRate]:checked").val();
	            }
	            if ($("input[name=Settings_Quality]:checked").val() != "") {
	                constraints.video.height = $("input[name=Settings_Quality]:checked").val();
	            }
	            if ($("input[name=Settings_AspectRatio]:checked").val() != "") {
	                constraints.video.aspectRatio = $("input[name=Settings_AspectRatio]:checked").val();
	            }

		    console.log("Get User Media", constraints);

                    var nwconstraints = { audio: true, video: true };

		    navigator.mediaDevices.getUserMedia(nwconstraints).then(function(mediaStream) {

                        // Handle Audio
                        settingsMicrophoneStreamTrack = (mediaStream.getAudioTracks().length >= 1)? mediaStream.getAudioTracks()[0] : null ;
                        if (MicrophoneFound && settingsMicrophoneStreamTrack != null) {
                            settingsMicrophoneStream = new MediaStream();
                            settingsMicrophoneStream.addTrack(settingsMicrophoneStreamTrack);
                            // Display Micrphone Levels
                            // window.SettingsMicrophoneStream = settingsMicrophoneStream;
                            settingsMicrophoneSoundMeter = MeterSettingsOutput(settingsMicrophoneStream, "Settings_MicrophoneOutput", "width", 50);
                        } else {
                            console.warn("No microphone devices found. Calling will not be possible.")
                        }

                        // Display Output Levels
                        $("#Settings_SpeakerOutput").css("width", "0%");
                        $("#Settings_RingerOutput").css("width", "0%");

		        // Handle Video
		        var videoTrack = (mediaStream.getVideoTracks().length >= 1)? mediaStream.getVideoTracks()[0] : null;

                        settingsVideoStreamTrack = (mediaStream.getVideoTracks().length >= 1)? mediaStream.getVideoTracks()[0] : null;

                        if(VideoFound && settingsVideoStreamTrack != null){
                            settingsVideoStream = new MediaStream();
                            settingsVideoStream.addTrack(settingsVideoStreamTrack);
                            // Display Preview Video
                            localVideo.srcObject = settingsVideoStream;
                            localVideo.onloadedmetadata = function(e) {
                                localVideo.play();
                            }

		        } else {
		            console.warn("No video / webcam devices found. Video calling will not be possible.")
		        }

		       return navigator.mediaDevices.enumerateDevices();

		    }).then(function(deviceInfos) {

		        for (var i = 0; i < deviceInfos.length; ++i) {

		            console.log("Found Device ("+ deviceInfos[i].kind +") Again: ", deviceInfos[i].label, deviceInfos[i].deviceId);

		            var deviceInfo = deviceInfos[i];
		            var devideId = deviceInfo.deviceId;
		            var DisplayName = deviceInfo.label;

		            if (DisplayName.indexOf("(") > 0) DisplayName = DisplayName.substring(0,DisplayName.indexOf("("));

		            var option = $('<option/>');
		            option.prop("value", devideId);

		            if (deviceInfo.kind === "audioinput") {
		                option.text((DisplayName != "")? DisplayName : "Microphone");
		                if (getAudioSrcID() == devideId) option.prop("selected", true);
		                selectMicScr.append(option);
		            } else if (deviceInfo.kind === "audiooutput") {
		                option.text((DisplayName != "")? DisplayName : "Speaker");
		                if (getAudioOutputID() == devideId) option.prop("selected", true);
		                selectAudioScr.append(option);
		                selectRingDevice.append(option.clone());
		            } else if (deviceInfo.kind === "videoinput") {
		                if(getVideoSrcID() == devideId) option.prop("selected", true);
		                option.text((DisplayName != "")? DisplayName : "Webcam");
		                selectVideoScr.append(option);
		            }
		        }

		        // Add "Default" option to the 'Camera' drop-down list
		        if (selectVideoScr.children('option').length > 0) {
		            var option = $('<option/>');
		            option.prop("value", "default");
		            if (getVideoSrcID() == "default" || getVideoSrcID() == "" || getVideoSrcID() == null) { option.prop("selected", true); }
		            option.text("Default");
		            selectVideoScr.append(option);
		        }

		    }).catch (function(e) {
		            console.error(e);
		            AlertConfigExtWindow(lang.alert_error_user_media, lang.error);
		    });

            }).catch(function(err) {
                 alert("Error getting media devices!");
                 console.log("Error getting media devices: "+ err); 
            });
        }
    });



    // Remove groups from the database
    $("#cnfwGroupsList").on("click", ".rmGrpFrmDtbs", function() {

       var groupToRemove = $(this).closest(".grpListRow").find(".grpListDspl").text();

       if (confirm("Are you sure you want to remove the group '"+ groupToRemove +"' completely from the database ? If you click 'OK', the group will be removed completely from all the database records, including from the groups associated with each Roundpin user and with each contact of any Roundpin user.")) {

           $.ajax({
             type: "POST",
             url: "src/remove-group.php",
             dataType: "JSON",
             data: {
                    username: userName,
                    grouptoremove: groupToRemove,
                    s_ajax_call: validateSToken
             },
             success: function(response) {

                           if (response.result == 'success') {
                               alert('The group has been successfully removed from all the database records. You can refresh the page to see the changes.');
                           } else { alert(response.result); }

             },
             error: function(response) {
                             alert("An error occurred while trying to remove the group from the database !");
             }
           });
       }
    });

    // Create new group
    $("#cnfSaveGroup").click(function() {

       var newGrpName = $("#cnfwAddNewGroup").val();

       if (/^[a-zA-Z0-9\s\-]+$/.test(newGrpName) && newGrpName.length < 151) {

           $.ajax({
              type: "POST",
              url: "src/save-new-group.php",
              dataType: "JSON",
              data: {
                  username: userName,
                  newgroup: newGrpName,
                  s_ajax_call: validateSToken
              },
              success: function(response) {

                           if (response.result == 'success') {
                               alert("The group has been successfully saved to the database. You can refresh the page to see the new group in the list of groups.");
                           } else { alert(response.result); }

              },
              error: function(response) {
                           alert("An error occurred while trying to save the group to the database!");
              }
           });

       } else { alert("The group name should contain only letters, numbers, spaces and hyphens and should have less than 151 characters."); }
    });

    // Show/Hide the list of groups for Roundpin users
    $('[id^="groupsOfUserTh-"]').click(function() {

      var foundgoulst = $(this).closest(".grpsOfUserList").find(".groupsOfUserRow");
      if (foundgoulst.length > 0) {
          foundgoulst.each(function() { 
            if ($(this).is(":visible")) {
                $(this).css("display", "none");
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups"); 
            } else { 
                $(this).css("display", "block");
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups"); 
            } 
          });
      } else {
          if ($(this).closest(".grpsOfUserList").find(".grofusrLabel").text() == "Hide Groups") {
              $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups");
          } else {
              $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups");
          }
      }
    });

    // Remove group from groups list
    function removeGrpFrmUsrGrpList(rwgrpusrtxt, rwgrpnmbr) {

       $("#addGrpsToUserList-"+ rwgrpnmbr +"").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;'>Hide Groups</span>");
       $("#addGrpsToUserList-"+ rwgrpnmbr +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ rwgrpusrtxt +"</div><div id='addGrpsToUserRw-"+ rwgrpnmbr +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups to which this user belongs.'></i></div></div>");

       $("#addGrpsToUserList-"+ rwgrpnmbr +"").find(".groupsOfUserRow").each(function() { 
          $(this).css("display", "block");
       });

       $('[id^="addGrpsToUserRw-"]').unbind("click");
       $('[id^="addGrpsToUserRw-"]').click(function() {
          var rwgrpusrtxtrm = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var rwgrpnmbrrm = $(this).attr("id").split("-")[1];
          addGrpToUsrGrpList(rwgrpusrtxtrm, rwgrpnmbrrm);
          $(this).closest(".groupsOfUserRow").remove();
       });
    }

    $('[id^="grpsOfUserRw-"]').click(function() {

       var rwgrpusrtxt = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
       var rwgrpnmbr = $(this).attr("id").split("-")[1];

       $(this).closest(".groupsOfUserRow").remove();

       removeGrpFrmUsrGrpList(rwgrpusrtxt, rwgrpnmbr);
    });

    // Show/Hide the list of groups that can be added to the current user
    $('[id^="addGroupsToUserTh-"]').click(function() {

      var foundadgrulst = $(this).closest(".addGrpsToUserList").find(".groupsOfUserRow");
      if (foundadgrulst.length > 0) {
          foundadgrulst.each(function() { 
            if ($(this).is(":visible")) {
                $(this).css("display", "none");
                $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to this Roundpin user.'></i>"); 
            } else { 
                $(this).css("display", "block");
                $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
            } 
          });
      } else {
          if ($(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").text() == "Hide Groups") {
              $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to this Roundpin user.'></i>");
          } else {
              $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>");
          }
      }
    });

    // Add group to groups list
    function addGrpToUsrGrpList(clsgrpusrtxt, clsgrpusrnmbr) {

       $("#grpsOfUserList-"+ clsgrpusrnmbr +"").find(".grofusrLabel").html("<span style='display:block;text-align:left!important;'>Hide Groups</span>");
       $("#grpsOfUserList-"+ clsgrpusrnmbr +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ clsgrpusrtxt +"</div><div id='grpsOfUserRw-"+ clsgrpusrnmbr +"' class='rmvGrpFromUser'><i class='fa fa-times' title='Remove this group from this list.'></i></div></div>");

       $("#grpsOfUserList-"+ clsgrpusrnmbr +"").find('.groupsOfUserRow').each(function() { 
          $(this).css("display", "block");
       });

       $('[id^="grpsOfUserRw-"]').unbind("click");
       $('[id^="grpsOfUserRw-"]').click(function() {
          var rwgrpusrtxtadd = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var rwgrpnmbradd = $(this).attr("id").split("-")[1];
          removeGrpFrmUsrGrpList(rwgrpusrtxtadd, rwgrpnmbradd);
          $(this).closest(".groupsOfUserRow").remove();
       });
    }

    $('[id^="addGrpsToUserRw-"]').click(function() {

       var clsgrpusrtxt = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
       var clsgrpusrnmbr = $(this).attr("id").split("-")[1];

       $(this).closest(".groupsOfUserRow").remove();

       addGrpToUsrGrpList(clsgrpusrtxt, clsgrpusrnmbr);
    });


    // Save user role and associated groups
    $("#roundpinUsersTbl").on("click", ".saveCnfBtn", function() {

       if (confirm("Do you really want to save the specified role and groups for this user to the database ?")) {

           var slctdUsrRl = $(this).closest(".btnRuTableRow").find(".selectUserRole").val();

           var slctdUsrGrpsPre = '';
           $(this).closest(".btnRuTableRow").find(".grpsOfUserList").find(".groupsOfUserRow").find(".groupsOfUser").each(function() { slctdUsrGrpsPre += "|"+ $(this).text(); });
           var slctdUsrGrps = slctdUsrGrpsPre +"|";
           var thsUsrExt = $(this).closest(".btnRuTableRow").find(".crUsrextension").text();

           if (!(/^[a-zA-Z0-9]+$/.test(thsUsrExt) && thsUsrExt.length < 51)) {
               alert("The user's extension should contain only numbers and letters and should have less than 51 characters.");
               return;
           }

           if (!(/^[a-zA-Z0-9\@\_\.\-]+$/.test(userName) && userName.length < 151)) {
               alert("The username of the person who performs the update should contain only upper-case and lower-case letters, numbers, underscores (_), dots (.), at signs (@) and hyphens (-) and should have less than 151 characters.");
               return;
           }

           if ($.inArray(slctdUsrRl, ["regular_user", "admin", "superadmin"]) == -1) {
               alert("The user's role can only be 'regular_user', 'admin' or 'superadmin'.");
               return;
           }

           if (!(/^[a-zA-Z0-9\s\-\|]+$/.test(slctdUsrGrps) && slctdUsrGrps.length < 901)) {
               alert("The groups associated with a user can only contain letters, numbers, hyphens and spaces and should have less than 901 characters in total.");
               return;
           }


           $.ajax({
             type: "POST",
             url: "src/save-update-user-role-and-groups.php",
             dataType: "JSON",
             data: {
                    sip_username: thsUsrExt,
                    username: userName,
                    newrole: slctdUsrRl,
                    usergroups: slctdUsrGrps,
                    s_ajax_call: validateSToken
             },
             success: function(response) {

                           if (response.result == 'success') {
                               alert('The data has been successfully saved to the database. You can refresh the page to see the changes.');
                           } else { alert(response.result); }

             },
             error: function(response) {
                             alert("An error occurred while trying to save user data to the database !");
             }
           });

       }
    });


    // Initialize the date picker for editing the ban date
    window.DatePickerX.setDefaults({ format: 'yyyy-mm-dd' });

    for (var b = 0; b < bannedUsers.length; b++) {
         document.getElementById("pickNewBanTime-"+ b).DatePickerX.init();
    }


    // Save new ban time for user banned from video conference
    $("#bannedUsersTbl").on("click", ".saveCnfBtn", function() {

       var nwBanTime = $(this).closest(".btnBanTableRow").find(".pickBanTime").val();
       var bannedPrflName = $(this).closest(".btnBanTableRow").find(".bannedProfileName").text();
       var thbanUsrExt = $(this).closest(".btnBanTableRow").find(".crUsrextension").text();
       var bfvConfLabel = $(this).closest(".btnBanTableRow").find(".bnfrvConfLabel").text();
       var vconfbanExt = $(this).closest(".btnBanTableRow").find(".bfvConfExtension").text();

       if (confirm("The user "+ bannedPrflName +" with extension "+ thbanUsrExt +" will be banned from the video conference '"+ bfvConfLabel +"' (extension "+ vconfbanExt +") until "+ nwBanTime +" 00:00:00 (which means that the ban will be removed in the first second of the specified day). Do you really want to save the specified date for this user to the database ?")) {

           if (!(/^[a-zA-Z0-9\*\#]+$/.test(vconfbanExt) && vconfbanExt.length < 51)) {
               alert("The user extension should contain only numbers, letters, asterisks and number signs and should have less than 51 characters.");
               return;
           }

           if (!(/^[a-zA-Z0-9\*\#]+$/.test(thbanUsrExt) && thbanUsrExt.length < 51)) {
               alert("The conference extension should contain only numbers, letters, asterisks and number signs and should have less than 51 characters.");
               return;
           }

           if (!(/^[0-9\-]+$/.test(nwBanTime) && nwBanTime.length < 13)) {
               alert("The ban date should contain only digits and hyphens and should have less than 13 characters");
               return;
           }

           if (!(/^[a-zA-Z0-9\@\_\.\-]+$/.test(userName) && userName.length < 151)) {
               alert("The username of the person who performs the update should contain only upper-case and lower-case letters, numbers, underscores (_), dots (.), at signs (@) and hyphens (-) and should have less than 151 characters.");
               return;
           }

           $.ajax({
             type: "POST",
             url: "src/save-new-vconf-ban-time.php",
             dataType: "JSON",
             data: {
                    username: userName,
                    bannedsipusername: thbanUsrExt,
                    confextension: vconfbanExt,
                    nwbandate: nwBanTime,
                    s_ajax_call: validateSToken
             },
             success: function(response) {

                           if (response.result == 'success') {
                               alert('The ban date has been successfully saved to the database. You can refresh the page to see the changes.');
                           } else { alert(response.result); }

             },
             error: function(response) {
                             alert("An error occurred while trying to save the ban date to the database !");
             }
           });

       }
    });


    // Remove ban for users banned from video conferences
    $("#bannedUsersTbl").on("click", ".unbanUsr", function() {

       var bannedPrflNndl = $(this).closest(".btnBanTableRow").find(".bannedProfileName").text();
       var thbanUsrExtdl = $(this).closest(".btnBanTableRow").find(".crUsrextension").text();
       var bfvConfLabeldl = $(this).closest(".btnBanTableRow").find(".bnfrvConfLabel").text();
       var vconfbanExtdl = $(this).closest(".btnBanTableRow").find(".bfvConfExtension").text();

       if (confirm("The ban for user "+ bannedPrflNndl +" with extension "+ thbanUsrExtdl +" from the video conference '"+ bfvConfLabeldl +"' (extension "+ vconfbanExtdl +") will be completely removed. Do you really want to remove this ban from the database ?")) {

           if (!(/^[a-zA-Z0-9\*\#]+$/.test(vconfbanExtdl) && vconfbanExtdl.length < 51)) {
               alert("The user extension should contain only numbers, letters, asterisks and number signs and should have less than 51 characters.");
               return;
           }

           if (!(/^[a-zA-Z0-9\*\#]+$/.test(thbanUsrExtdl) && thbanUsrExtdl.length < 51)) {
               alert("The conference extension should contain only numbers, letters, asterisks and number signs and should have less than 51 characters.");
               return;
           }

           if (!(/^[a-zA-Z0-9\@\_\.\-]+$/.test(userName) && userName.length < 151)) {
               alert("The username of the person who performs the update should contain only upper-case and lower-case letters, numbers, underscores (_), dots (.), at signs (@) and hyphens (-) and should have less than 151 characters.");
               return;
           }

           $.ajax({
             type: "POST",
             url: "src/remove-vconf-user-ban.php",
             dataType: "JSON",
             data: {
                    username: userName,
                    bannedsipusername: thbanUsrExtdl,
                    confextension: vconfbanExtdl,
                    s_ajax_call: validateSToken
             },
             success: function(response) {

                           if (response.result == 'success') {
                               alert('The ban has been successfully removed from the database. You can refresh the page to see the changes.');
                           } else { alert(response.result); }

             },
             error: function(response) {
                             alert("An error occurred while trying to remove the ban data from the database !");
             }
           });
       }
    });


    // Show groups that are to be loaded when logging in
    if (loadGroups != null && loadGroups.length > 0) {
        var crGroupsArrLd = loadGroups.split("|");
        crGroupsArrLd.shift(); crGroupsArrLd.pop();
        for (var r = 0; r < crGroupsArrLd.length; r++) {
             $("#AddContactGroupsLd").append("<div class='slctdGrouplg'><div class='slctdGroupTextlg'>"+ crGroupsArrLd[r] +"</div><div class='removeGrouplg' title='Remove this group'><i class='fa fa-times'></i></div></div>");
             // Remove group from list
             $(".removeGrouplg").last().click(function() { 
                $(this).closest(".slctdGrouplg").remove();
             });
        }
    }

    // Add new group to the list of groups whose contacts are to be loaded when logging in
    $("#AddContactGroupsOptLd").on("change", function() {
       var selectedGroup = $("#AddContactGroupsOptLd").val();
       if (selectedGroup != '') {
           var slctChecklg = 0;
           $(".slctdGroupTextlg").each(function() {
             if ($(this).text() == selectedGroup) { slctChecklg++; }
           });
           if (slctChecklg == 0) { 
               $("#AddContactGroupsLd").append("<div class='slctdGrouplg'><div class='slctdGroupTextlg'>"+ selectedGroup +"</div><span class='removeGrouplg' title='Remove this group'><i class='fa fa-times'></i></span></div>"); 
               // Remove group from list
               $(".removeGrouplg").last().click(function() { $(this).closest(".slctdGrouplg").remove(); });
           }
       }
    });




    // Select contacts export type
    $("#selectExportType").change(function() {
       if ($(this).val() == 'exportall') { 
           $("#exportAllContacts").css("display", "block");
           $("#exportCntctsByGroup,#exportCntctsByDate").css("display", "none"); 
       } else if ($(this).val() == 'exportbygroup') {
           $("#exportCntctsByGroup").css("display", "block");
           $("#exportAllContacts,#exportCntctsByDate").css("display", "none");
       } else if ($(this).val() == 'exportbydate') {
           $("#exportCntctsByDate").css("display", "block");
           $("#exportAllContacts,#exportCntctsByGroup").css("display", "none");
       }
    });


    // Export all contacts of current Roundpin user
    $("#expAllContactsBtn").click(function() {

       var csvDelimiter = $("#csvDelimiterList").val();

       $.ajax({
             type: "POST",
             url: "src/export-all-contacts.php",
             dataType: "JSON",
             data: {
                    username: userName,
                    csvdelimiter: csvDelimiter,
                    s_ajax_call: validateSToken
             },
             success: function(contactsfromdb) {

                               let cntctsExpMessage = contactsfromdb.messagetoconvey;

                               if (cntctsExpMessage == 'delimiterfound') {

                                   alert("The character that you have selected as delimiter was found inside the content of a field of the database. Please choose a different character to delimit the values and try exporting the contacts again.");

                               } else {

                                   let allContactsArr = contactsfromdb.contactsdata;

                                   if (allContactsArr.length > 0) {

//                                       let allExpContactsTxt = 'contact_name'+ csvDelimiter +'contact_desc'+ csvDelimiter +'contact_long_desc'+ csvDelimiter +'address_street'+ csvDelimiter +'address_zip'+ csvDelimiter +'address_town'+ csvDelimiter +'address_country'+ csvDelimiter +'address_state'+ csvDelimiter +'extension_number'+ csvDelimiter +'contact_mobile'+ csvDelimiter +'contact_num1'+ csvDelimiter +'contact_num2'+ csvDelimiter +'contact_fax'+ csvDelimiter +'contact_email'+ csvDelimiter +'profile_picture_c'+ csvDelimiter +'groups'+ csvDelimiter +'date_added'+ csvDelimiter +'date_modified'+'\n';
                                       let allExpContactsTxt = 'Sep='+ csvDelimiter +'\n';
                                       allExpContactsTxt += 'contact_name'+ csvDelimiter +'contact_desc'+ csvDelimiter +'contact_long_desc'+ csvDelimiter +'address_street'+ csvDelimiter +'address_zip'+ csvDelimiter +'address_town'+ csvDelimiter +'address_country'+ csvDelimiter +'address_state'+ csvDelimiter +'extension_number'+ csvDelimiter +'contact_mobile'+ csvDelimiter +'contact_num1'+ csvDelimiter +'contact_num2'+ csvDelimiter +'contact_fax'+ csvDelimiter +'contact_email'+ csvDelimiter +'profile_picture_c'+ csvDelimiter +'groups'+ csvDelimiter +'date_added'+ csvDelimiter +'date_modified'+'\n';

                                       for (let l = 0; l < allContactsArr.length; l++) {
                                            let objTextConc = '';
                                            Object.keys(allContactsArr[l]).forEach(function(key, ind) {
                                                   if (allContactsArr[l][key] != null) {
                                                       objTextConc += allContactsArr[l][key] + csvDelimiter;
                                                   } else { objTextConc += '' + csvDelimiter; }
                                            });
                                            let objTextConcfin = objTextConc.slice(0, -1) + '\n';
                                            allExpContactsTxt += objTextConcfin;
                                       }

			               let prDate = moment().format("YYYY-MM-DD_HH-mm-ss");
			               let downecFilename = "All_Roundpin_contacts_for_user_"+ userName +"_exported_on_"+ prDate +".csv";
			               let downeclink = document.createElement('a');
			               let mimeType = 'text/csv';

			               downeclink.setAttribute('download', downecFilename);
			               downeclink.setAttribute('href', 'data:' + mimeType + ';charset=utf-8,' + encodeURIComponent(allExpContactsTxt));
			               downeclink.click();
                                   }
                               }

             },
             error: function(contactsfromdb) {
                             alert("An error occurred while trying to get contacts data from the database !");
             }
       });       
    });


    // Add new group to the list of groups whose contacts are to be exported
    $("#contactGrpsToExport").on("change", function() {
       var selectedGroupegr = $("#contactGrpsToExport").val();
       if (selectedGroupegr != '') {
           var sctCheck = 0;
           $(".slctdGroupTextgr").each(function() {
             if ($(this).text() == selectedGroupegr) { sctCheck++; }
           });
           if (sctCheck == 0) { 
               $("#contactGrpsExportList").append("<div class='slctdGroupebgr'><div class='slctdGroupTextgr'>"+ selectedGroupegr +"</div><span class='removeGroupex' title='Remove this group'><i class='fa fa-times'></i></span></div>"); 
               // Remove group from list
               $(".removeGroupex").last().click(function() { $(this).closest(".slctdGroupebgr").remove(); });
           }
       }
    });


    // Export contacts from selected groups
    $("#expContactsByGroupBtn").click(function() {

       var csvDelimitergr = $("#csvDelimiterListgr").val();

       var allslctdGroupsLdgr = "";
       $("#contactGrpsExportList .slctdGroupTextgr").each(function() { allslctdGroupsLdgr += "|"+ $(this).text(); });
       if (allslctdGroupsLdgr != "") { var slctdGroupsLdgr = allslctdGroupsLdgr +"|"; } else { var slctdGroupsLdgr = ""; }

       if (slctdGroupsLdgr.trim() !== "") {

           var allgroupslstpre = slctdGroupsLdgr.replace(/\|/g, '_').replace(/ /g, '-');
           if (allgroupslstpre.length > 140) { var allgroupslst = allgroupslstpre.slice(0, 139) + "..."; } else { var allgroupslst = allgroupslstpre; }

           $.ajax({
             type: "POST",
             url: "src/export-contacts-by-groups.php",
             dataType: "JSON",
             data: {
                    username: userName,
                    csvdelimiter: csvDelimitergr,
                    selectedgroups: slctdGroupsLdgr, 
                    s_ajax_call: validateSToken
             },
             success: function(contactsfromdb) {

                               let cntctsExpMessage = contactsfromdb.messagetoconvey;

                               if (cntctsExpMessage == 'delimiterfound') {

                                   alert("The character that you have selected as delimiter was found inside the content of a field of the database. Please choose a different character to delimit the values and try exporting the contacts again.");

                               } else {

                                   let allContactsArr = contactsfromdb.contactsdata;

                                   if (allContactsArr.length > 0) {

//                                       let allExpContactsTxt = 'contact_name'+ csvDelimitergr +'contact_desc'+ csvDelimitergr +'contact_long_desc'+ csvDelimitergr +'address_street'+ csvDelimitergr +'address_zip'+ csvDelimitergr +'address_town'+ csvDelimitergr +'address_country'+ csvDelimitergr +'address_state'+ csvDelimitergr +'extension_number'+ csvDelimitergr +'contact_mobile'+ csvDelimitergr +'contact_num1'+ csvDelimitergr +'contact_num2'+ csvDelimitergr +'contact_fax'+ csvDelimitergr +'contact_email'+ csvDelimitergr +'profile_picture_c'+ csvDelimitergr +'groups'+ csvDelimitergr +'date_added'+ csvDelimitergr +'date_modified'+'\n';
                                       let allExpContactsTxt = 'Sep='+ csvDelimitergr +'\n';
                                       allExpContactsTxt += 'contact_name'+ csvDelimitergr +'contact_desc'+ csvDelimitergr +'contact_long_desc'+ csvDelimitergr +'address_street'+ csvDelimitergr +'address_zip'+ csvDelimitergr +'address_town'+ csvDelimitergr +'address_country'+ csvDelimitergr +'address_state'+ csvDelimitergr +'extension_number'+ csvDelimitergr +'contact_mobile'+ csvDelimitergr +'contact_num1'+ csvDelimitergr +'contact_num2'+ csvDelimitergr +'contact_fax'+ csvDelimitergr +'contact_email'+ csvDelimitergr +'profile_picture_c'+ csvDelimitergr +'groups'+ csvDelimitergr +'date_added'+ csvDelimitergr +'date_modified'+'\n';

                                       for (let l = 0; l < allContactsArr.length; l++) {
                                            let objTextConc = '';
                                            Object.keys(allContactsArr[l]).forEach(function(key, ind) {
                                                   if (allContactsArr[l][key] != null) {
                                                       objTextConc += allContactsArr[l][key] + csvDelimitergr;
                                                   } else { objTextConc += '' + csvDelimitergr; } 
                                            });
                                            let objTextConcfin = objTextConc.slice(0, -1) + '\n';
                                            allExpContactsTxt += objTextConcfin;
                                       }

			               let prDate = moment().format("YYYY-MM-DD_HH-mm-ss");
			               let downecFilename = "Roundpin_contacts_for_user_"+ userName +"_from_groups"+ allgroupslst +"exported_on_"+ prDate +".csv";
			               let downeclink = document.createElement('a');
			               let mimeType = 'text/csv';

			               downeclink.setAttribute('download', downecFilename);
			               downeclink.setAttribute('href', 'data:' + mimeType + ';charset=utf-8,' + encodeURIComponent(allExpContactsTxt));
			               downeclink.click();
                                   }
                               }

             },
             error: function(contactsfromdb) {
                             alert("An error occurred while trying to get contacts data from the database !");
             }
           });

       } else { alert("You must select at least one group."); }
   
    });


    // Date picker for exporting contacts
    document.getElementById('pickDateForExpst').DatePickerX.init();
    document.getElementById('pickDateForExpfn').DatePickerX.init();


    // Export the contacts added between the specified dates
    $("#expContactsByDateBtn").click(function() {

       var csvDelimiterdt = $("#csvDelimiterListdt").val();

       var expstartdate = $("#pickDateForExpst").val();
       var expenddate = $("#pickDateForExpfn").val();

       var expstartcmp = new Date(expstartdate);
       var expendcmp = new Date(expenddate);

       if (expstartdate.trim() !== "" && expenddate.trim() !== "" && expstartcmp > expendcmp) { 
           alert("The start date is older than the end date. Choose a start date that precedes the end date.");
           return; 
       }

       if (expenddate.trim() !== "") {

           $.ajax({
             type: "POST",
             url: "src/export-contacts-by-date.php",
             dataType: "JSON",
             data: {
                    username: userName,
                    csvdelimiter: csvDelimiterdt,
                    expstartdate: expstartdate,
                    expenddate: expenddate,
                    s_ajax_call: validateSToken
             },
             success: function(contactsfromdb) {

                               let cntctsExpMessage = contactsfromdb.messagetoconvey;

                               if (cntctsExpMessage == 'delimiterfound') {

                                   alert("The character that you have selected as delimiter was found inside the content of a field of the database. Please choose a different character to delimit the values and try exporting the contacts again.");

                               } else {

                                   let allContactsArr = contactsfromdb.contactsdata;

                                   if (allContactsArr.length > 0) {

//                                       let allExpContactsTxt = 'contact_name'+ csvDelimiterdt +'contact_desc'+ csvDelimiterdt +'contact_long_desc'+ csvDelimiterdt +'address_street'+ csvDelimiterdt +'address_zip'+ csvDelimiterdt +'address_town'+ csvDelimiterdt +'address_country'+ csvDelimiterdt +'address_state'+ csvDelimiterdt +'extension_number'+ csvDelimiterdt +'contact_mobile'+ csvDelimiterdt +'contact_num1'+ csvDelimiterdt +'contact_num2'+ csvDelimiterdt +'contact_fax'+ csvDelimiterdt +'contact_email'+ csvDelimiterdt +'profile_picture_c'+ csvDelimiterdt +'groups'+ csvDelimiterdt +'date_added'+ csvDelimiterdt +'date_modified'+'\n';
                                       let allExpContactsTxt = 'Sep='+ csvDelimiterdt +'\n';
                                       allExpContactsTxt += 'contact_name'+ csvDelimiterdt +'contact_desc'+ csvDelimiterdt +'contact_long_desc'+ csvDelimiterdt +'address_street'+ csvDelimiterdt +'address_zip'+ csvDelimiterdt +'address_town'+ csvDelimiterdt +'address_country'+ csvDelimiterdt +'address_state'+ csvDelimiterdt +'extension_number'+ csvDelimiterdt +'contact_mobile'+ csvDelimiterdt +'contact_num1'+ csvDelimiterdt +'contact_num2'+ csvDelimiterdt +'contact_fax'+ csvDelimiterdt +'contact_email'+ csvDelimiterdt +'profile_picture_c'+ csvDelimiterdt +'groups'+ csvDelimiterdt +'date_added'+ csvDelimiterdt +'date_modified'+'\n';

                                       for (let l = 0; l < allContactsArr.length; l++) {
                                            let objTextConc = '';
                                            Object.keys(allContactsArr[l]).forEach(function(key, ind) {
                                                   if (allContactsArr[l][key] != null) {
                                                       objTextConc += allContactsArr[l][key] + csvDelimiterdt;
                                                   } else { objTextConc += '' + csvDelimiterdt; }
                                            });
                                            let objTextConcfin = objTextConc.slice(0, -1) + '\n';
                                            allExpContactsTxt += objTextConcfin;
                                       }

			               let prDate = moment().format("YYYY-MM-DD_HH-mm-ss");
			               let downecFilename = "Roundpin_contacts_for_user_"+ userName +"_added_between_"+ expstartdate +"_and_"+ expenddate +"_exported_on_"+ prDate +".csv";
			               let downeclink = document.createElement('a');
			               let mimeType = 'text/csv';

			               downeclink.setAttribute('download', downecFilename);
			               downeclink.setAttribute('href', 'data:' + mimeType + ';charset=utf-8,' + encodeURIComponent(allExpContactsTxt));
			               downeclink.click();

                                   } else { alert("No contacts were added between the specified dates."); }
                               }

             },
             error: function(contactsfromdb) {
                             alert("An error occurred while trying to get contacts data from the database !");
             }
           });

       } else { alert("You must enter at least the end date."); }
   
    });


    // Import contacts from file
    $("#selectedFileCImp").bind("change", function() {

        upFileNameCImp = $(this).val().split('\\').pop();

        // Verify the name of the file selected for importing contacts
        if (/^[a-zA-Z0-9\-\_\.]+$/.test(upFileNameCImp)) {

            $("#upFileCImp").html(upFileNameCImp);

            var impCntctsDelimiter = $("#impCDelimiter").val();
            if (!(/^[A-Za-z0-9\_\.\,\-\~\`\%\+\!\?\&\*\^\=\#\$\@\(\)\[\]\<\>\{\}\\\|\/]+$/.test(impCntctsDelimiter))) { alert("You must enter the character used to delimit values in the CSV file that will be imported!"); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }

            var impCFrstRow = $("#impCFirstRow").val();
            if (!(/^[0-9]+$/.test(impCFrstRow))) { alert("You must enter the order number of the first row that must be imported from the CSV file!"); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }

            var impCFrstRowPr = impCFrstRow - 1;

            var displaynameImpv = $("#displaynameImp").val();
            var descriptionImpv = $("#descriptionImp").val();
            var longdescImpv = $("#longdescImp").val();
            var streetImpv = $("#streetImp").val();
            var zipImpv = $("#zipImp").val();
            var townImpv = $("#townImp").val();
            var countryImpv = $("#countryImp").val();
            var stateImpv = $("#stateImp").val();
            var extensionImpv = $("#extensionImp").val();
            var mobileImpv = $("#mobileImp").val();
            var number1Impv = $("#number1Imp").val();
            var number2Impv = $("#number2Imp").val();
            var faxnumberImpv = $("#faxnumberImp").val();
            var emailImpv = $("#emailImp").val();
            var pictureImpv = $("#pictureImp").val();
            var groupsImpv = $("#groupsImp").val();

            var flreader = new FileReader();
            flreader.readAsText(this.files[0]);

            var contactsfromfile = [];
            flreader.onload = function() {

                let resulttext = flreader.result;
                let resulttxtarr = resulttext.split(/\r?\n/);

                for (let c = impCFrstRowPr; c < resulttxtarr.length; c++) {

                     let crntCntctRow = resulttxtarr[c];
                     if (crntCntctRow) {

                         let crntCntctArr = crntCntctRow.split(impCntctsDelimiter);
                         let ctctarrpr1 = (typeof crntCntctArr[displaynameImpv - 1] != 'undefined')? crntCntctArr[displaynameImpv - 1] : '';
                         let ctctarrpr2 = (typeof crntCntctArr[descriptionImpv - 1] != 'undefined')? crntCntctArr[descriptionImpv - 1] : '';
                         let ctctarrpr3 = (typeof crntCntctArr[longdescImpv - 1] != 'undefined')? crntCntctArr[longdescImpv - 1] : '';
                         let ctctarrpr4 = (typeof crntCntctArr[streetImpv - 1] != 'undefined')? crntCntctArr[streetImpv - 1] : '';
                         let ctctarrpr5 = (typeof crntCntctArr[zipImpv - 1] != 'undefined')? crntCntctArr[zipImpv - 1] : '';
                         let ctctarrpr6 = (typeof crntCntctArr[townImpv - 1] != 'undefined')? crntCntctArr[townImpv - 1] : '';
                         let ctctarrpr7 = (typeof crntCntctArr[countryImpv - 1] != 'undefined')? crntCntctArr[countryImpv - 1] : '';
                         let ctctarrpr8 = (typeof crntCntctArr[stateImpv - 1] != 'undefined')? crntCntctArr[stateImpv - 1] : '';
                         let ctctarrpr9 = (typeof crntCntctArr[extensionImpv - 1] != 'undefined')? crntCntctArr[extensionImpv - 1] : '';
                         let ctctarrpr10 = (typeof crntCntctArr[mobileImpv - 1] != 'undefined')? crntCntctArr[mobileImpv - 1] : '';
                         let ctctarrpr11 = (typeof crntCntctArr[number1Impv - 1] != 'undefined')? crntCntctArr[number1Impv - 1] : '';
                         let ctctarrpr12 = (typeof crntCntctArr[number2Impv - 1] != 'undefined')? crntCntctArr[number2Impv - 1] : '';
                         let ctctarrpr13 = (typeof crntCntctArr[faxnumberImpv - 1] != 'undefined')? crntCntctArr[faxnumberImpv - 1] : '';
                         let ctctarrpr14 = (typeof crntCntctArr[emailImpv - 1] != 'undefined')? crntCntctArr[emailImpv - 1] : '';
                         let ctctarrpr15 = (typeof crntCntctArr[pictureImpv - 1] != 'undefined')? crntCntctArr[pictureImpv - 1] : '';
                         let ctctarrpr16 = (typeof crntCntctArr[groupsImpv - 1] != 'undefined')? crntCntctArr[groupsImpv - 1] : '';

                         if (!(/^$|^[A-Za-z0-9\s\.\-\'\(\)]+$/.test(ctctarrpr1))) { alert("Error! The 'Display Name' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[A-Za-z0-9\s\-\_\.\,\'\"\(\)\!\?\@\%\&\*]+$/.test(ctctarrpr2))) { alert("Error! The 'Title / Description' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[A-Za-z0-9\s\-\_\.\,\'\"\(\)\!\?\@\%\&\*]+$/.test(ctctarrpr3))) { alert("Error! The 'Long Description' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[A-Za-z0-9\s\-\_\.\,\'\"\(\)\#\%\&]+$/.test(ctctarrpr4))) { alert("Error! The 'Address - Street' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[A-Za-z0-9\s\-\_\.\(\)\#\&]+$/.test(ctctarrpr5))) { alert("Error! The 'Address - Zip' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[A-Za-z0-9\s\-\_\.\,\(\)\&]+$/.test(ctctarrpr6))) { alert("Error! The 'Address - Town' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[A-Za-z0-9\s\-\.\,\(\)\&]+$/.test(ctctarrpr7))) { alert("Error! The 'Address - Country' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[A-Za-z0-9\s\-\.\,\(\)\&]+$/.test(ctctarrpr8))) { alert("Error! The 'Address - State/Province' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[A-Za-z0-9\*\#]+$/.test(ctctarrpr9))) { alert("Error! The 'Extension (Internal)' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[0-9\s\+\-\.\#]+$/.test(ctctarrpr10))) { alert("Error! The 'Mobile Number' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[0-9\s\+\-\.\#]+$/.test(ctctarrpr11))) { alert("Error! The 'Contact Number 1' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[0-9\s\+\-\.\#]+$/.test(ctctarrpr12))) { alert("Error! The 'Contact Number 2' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[0-9\s\+\-\.\#]+$/.test(ctctarrpr13))) { alert("Error! The 'Fax Number' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[A-Za-z0-9\_\.\-\~\%\+\!\?\&\*\^\=\#\$\{\}\|\/]+@[A-Za-z0-9\.\-]+\.[A-Za-z0-9\-]{2,63}$/.test(ctctarrpr14))) { alert("Error! The 'Email' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[A-Za-z0-9\+\/\=\;\:\,]+$/.test(ctctarrpr15))) { alert("Error! The 'Profile Picture' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }
                         if (!(/^$|^[A-Za-z0-9\s\-\|]+$/.test(ctctarrpr16))) { alert("Error! The 'Groups' field contains characters that are not allowed."); $("#selectedFileCImp").val(""); $("#upFileCImp").empty(); return; }

                         let crntCntctArrPrc = [ctctarrpr1, ctctarrpr2, ctctarrpr3, ctctarrpr4, ctctarrpr5, ctctarrpr6, ctctarrpr7, ctctarrpr8, ctctarrpr9, ctctarrpr10, ctctarrpr11, ctctarrpr12, ctctarrpr13, ctctarrpr14, ctctarrpr15, ctctarrpr16];
                         contactsfromfile.push(crntCntctArrPrc);
                     }
                }
            }

            $("#impContactsBtn").click(function() {

               if (displaynameImpv.trim() == "") { alert("The Display Name cannot be empty!"); return; }

               $("#slctFlCImportBtn").append("<span id='sendFileLoader'></span>");

               // Import the contacts into the database
               $.ajax({
                   type: "POST",
                   url: "src/import-contacts-from-file.php",
                   dataType: "JSON",
                   data: {
                          username: userName,
                          contactsdata: JSON.stringify(contactsfromfile),
                          s_ajax_call: validateSToken
                   },
                   success: function(response) {
                                     $("#sendFileLoader").remove();
                                     $("#upFileCImp").empty();

                                     if (response.restrict != "") { alert(response.restrict); }
                                     if (response.reindex != "") { alert(response.reindex); }
                                     if (response.insert != "") { alert(response.insert); }
                                     if (response.duplicate != "") { alert(response.duplicate); }
                                     if (response.restrict == "" && response.reindex == "" && response.insert == "" && response.duplicate == "") { alert("The contacts have been successfully imported into the database! You can refresh the page to see the results."); }
                   },
                   error: function(response) {
                                   alert("An error occurred while attempting to import the contacts into the database!");
                   }
               });      
            });

        } else {
            $("#upFileCImp").empty();
            alert("The name of the selected file is not valid!");
        }
    });


    // Select contacts removal type
    $("#selectRemoveCType").change(function() {
       if ($(this).val() == 'removeall') { 
           $("#removeAllContacts").css("display", "block");
           $("#removeCntctsByGroup,#removeCntctsByDate").css("display", "none"); 
       } else if ($(this).val() == 'removebygroup') {
           $("#removeCntctsByGroup").css("display", "block");
           $("#removeAllContacts,#removeCntctsByDate").css("display", "none");
       } else if ($(this).val() == 'removebydate') {
           $("#removeCntctsByDate").css("display", "block");
           $("#removeAllContacts,#removeCntctsByGroup").css("display", "none");
       }
    });


    // Remove all the contacts of the current Roundpin user
    $("#removeAllCntctsBtn").click(function() {

       if (confirm("Are you sure you want to completely remove all the contacts of the user '"+ userName +"' from the database? Please consider exporting all the contacts to a CSV file before deleting them, so that you can import them later if you need them. Click 'Cancel' if you don't want to remove all the contacts right now, or 'OK' to remove all the contacts at this very moment.")) {
           $.ajax({
             type: "POST",
             url: "src/remove-all-contacts.php",
             dataType: "JSON",
             data: {
                    username: userName,
                    s_ajax_call: validateSToken
             },
             success: function(delresult) {

                               if (delresult.removeresult == "success") {
                                   alert("The contacts have been successfully removed from the database! You can refresh the page to see the result.");
                               } else {
                                   alert("Error while removing contacts data from the database!");
                               }
                               if (delresult.reindex != "success") { alert("The 'contacts' table couldn't be reindexed!"); }

             },
             error: function(delresult) {
                               alert("An error occurred while trying to remove contacts data from the database !");
             }
           });
       }
  
    });



    // Add new group to the list of groups whose contacts are to be removed
    $("#contactGrpsToRemove").on("change", function() {
       var selGroupedel = $("#contactGrpsToRemove").val();
       if (selGroupedel != '') {
           var sctCheckdel = 0;
           $(".slctdGroupTextgrrm").each(function() {
             if ($(this).text() == selGroupedel) { sctCheckdel++; }
           });
           if (sctCheckdel == 0) { 
               $("#contactGrpsRemoveList").append("<div class='slctdGroupebgrrm'><div class='slctdGroupTextgrrm'>"+ selGroupedel +"</div><span class='removeGroup' title='Remove this group'><i class='fa fa-times'></i></span></div>"); 
               // Remove group from list
               $(".removeGroup").last().click(function() { $(this).closest(".slctdGroupebgrrm").remove(); });
           }
       }
    });


    // Remove contacts from selected groups
    $("#removeCntctsByGrpBtn").click(function() {

       var allslctdGrpLdgrdel = "";
       $("#contactGrpsRemoveList .slctdGroupTextgrrm").each(function() { allslctdGrpLdgrdel += "|"+ $(this).text(); });
       if (allslctdGrpLdgrdel != "") { var slctdGroupsLdgrdel = allslctdGrpLdgrdel +"|"; } else { var slctdGroupsLdgrdel = ""; }

       if (slctdGroupsLdgrdel.trim() !== "") {

           if (confirm("Are you sure you want to completely remove all your contacts in the selected groups, from the database? Please consider exporting all the contacts to a CSV file before deleting them, so that you can import them later if you need them. Click 'Cancel' if you don't want to remove the contacts right now, or 'OK' to remove the contacts at this very moment.")) {
               $.ajax({
                 type: "POST",
                 url: "src/remove-contacts-by-groups.php",
                 dataType: "JSON",
                 data: {
                        username: userName,
                        selectedgroups: slctdGroupsLdgrdel,
                        s_ajax_call: validateSToken
                 },
                 success: function(delresult) {
                                   
                               if (delresult.removeresult == "success") {
                                   alert("The contacts have been successfully removed from the database! You can refresh the page to see the result.");
                               } else {
                                   alert("Error while removing contacts data from the database!");
                               }
                               if (delresult.reindex != "success") { alert("The 'contacts' table couldn't be reindexed!"); }

                 },
                 error: function(delresult) {
                               alert("An error occurred while trying to remove contacts data from the database !");
                 }
               });
           }

       } else { alert("You must select at least one group."); }
   
    });



    // Initialize the date picker for removing contacts
    window.DatePickerX.setDefaults({ format: 'yyyy-mm-dd' });

    document.getElementById('pickDateForDelst').DatePickerX.init();
    document.getElementById('pickDateForDelfn').DatePickerX.init();


    // Remove the contacts added between the specified dates
    $("#removeCntctsByDateBtn").click(function() {

       var delstartdate = $("#pickDateForDelst").val();
       var delenddate = $("#pickDateForDelfn").val();

       var delstartcmp = new Date(delstartdate);
       var delendcmp = new Date(delenddate);

       if (delstartdate.trim() !== "" && delenddate.trim() !== "" && delstartcmp > delendcmp) { 
           alert("The start date is older than the end date. Choose a start date that precedes the end date.");
           return; 
       }

       if (delenddate.trim() !== "") {

           if (confirm("Are you sure you want to completely remove all your contacts added to Roundpin between the selected dates, from the database? Please consider exporting all the contacts to a CSV file before deleting them, so that you can import them later if you need them. Click 'Cancel' if you don't want to remove the contacts right now, or 'OK' to remove the contacts at this very moment.")) {

               $.ajax({
                 type: "POST",
                 url: "src/remove-contacts-by-date.php",
                 dataType: "JSON",
                 data: {
                        username: userName,
                        delstartdate: delstartdate,
                        delenddate: delenddate,
                        s_ajax_call: validateSToken
                 },
                 success: function(delresult) {

                               if (delresult.removeresult == "success") {
                                   alert("The contacts have been successfully removed from the database! You can refresh the page to see the result.");
                               } else {
                                   alert("Error while removing contacts data from the database!");
                               }
                               if (delresult.reindex != "success") { alert("The 'contacts' table couldn't be reindexed!"); }

                 },
                 error: function(delresult) {
                             alert("An error occurred while trying to remove contacts data from the database !");
                 }
               });
           }

       } else { alert("You must enter at least the end date."); }
   
    });





    // Show/Hide the list of groups of users allowed to access the text conference
    $('[id^="groupsOfUserThtc-"]').click(function() {

      var foundgrpsusrlst = $(this).closest(".grpsOfUserList").find($(".groupsOfUserRow"));
      if (foundgrpsusrlst.length > 0) {
          foundgrpsusrlst.each(function() { 
            if ($(this).is(":visible")) {
                $(this).css("display", "none");
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups"); 
            } else { 
                $(this).css("display", "block");
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups"); 
            } 
          });
      } else {
          if ($(this).closest(".grpsOfUserList").find(".grofusrLabel").text() == "Hide Groups") {
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups");
          } else {
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups");
          }
      }
    });

    // Remove group of users allowed to access the text conference, from the list of groups
    function removeGrpFrmAccGrpListtc(rwgrpusrtxt, rwgrpnmbr, rwgrpnmbrsec) {

       $("#addGroupsToUserThtc-"+ rwgrpnmbrsec +"").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>");
       $("#addGrpsToUserListtc-"+ rwgrpnmbrsec +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ rwgrpusrtxt +"</div><div id='addGrpsToUserRwtc-"+ rwgrpnmbr +"-"+ rwgrpnmbrsec +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups of users allowed to access the conference.'></i></div></div>");

       $("#addGrpsToUserListtc-"+ rwgrpnmbrsec +"").find(".groupsOfUserRow").each(function() { 
          $(this).css("display", "block");
       });

       $('[id^="addGrpsToUserRwtc-"]').unbind("click");
       $('[id^="addGrpsToUserRwtc-"]').click(function() {
          var rwgrpusrtxtrm = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var rwgrpnmbrrm = $(this).attr("id").split("-")[1];
          var rwgrpnmbrrmsec = $(this).attr("id").split("-")[2];

          addGrpToAccGrpListtc(rwgrpusrtxtrm, rwgrpnmbrrm, rwgrpnmbrrmsec);
          $(this).closest(".groupsOfUserRow").remove();
       });
    }

    $('[id^="grpsOfUserRwtc-"]').click(function() {

       var rwgrpusrtxt = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
       var rwgrpnmbr = $(this).attr("id").split("-")[1];
       var rwgrpnmbrsec = $(this).attr("id").split("-")[2];

       $(this).closest(".groupsOfUserRow").remove();
       removeGrpFrmAccGrpListtc(rwgrpusrtxt, rwgrpnmbr, rwgrpnmbrsec);
    });

    // Show/Hide the list of groups that can be added
    $('[id^="addGroupsToUserThtc-"]').click(function() {

       var foundtrpsitmlst = $(this).closest(".addGrpsToUserList").find($(".groupsOfUserRow"));
       if (foundtrpsitmlst.length > 0) {
           foundtrpsitmlst.each(function() { 
             if ($(this).is(":visible")) {
                 $(this).css("display", "none");
                 $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i>"); 
             } else { 
                 $(this).css("display", "block");
                 $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
             } 
           });
       } else {
           if ($(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").text() == "Hide Groups") {
               $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i>");
           } else {
               $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
           }
       }
    });

    // Add group of users allowed to access the text conference, to the list of groups
    function addGrpToAccGrpListtc(clsgrpusrtxt, clsgrpusrnmbr, clsgrpusrnmbrsec) {

       $("#groupsOfUserThtc-"+ clsgrpusrnmbrsec +"").find(".grofusrLabel").html("<span style='color:#03366c'>Hide Groups</span>");
       $("#grpsOfUserListtc-"+ clsgrpusrnmbrsec +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ clsgrpusrtxt +"</div><div id='grpsOfUserRwtc-"+ clsgrpusrnmbr +"-"+ clsgrpusrnmbrsec +"' class='rmvGrpFromUser'><i class='fa fa-times' title='Remove this group from this list.'></i></div></div>");

       $("#grpsOfUserListtc-"+ clsgrpusrnmbrsec +"").find(".groupsOfUserRow").each(function() { 
          $(this).css("display", "block");
       });

       $('[id^="grpsOfUserRwtc-"]').unbind("click");
       $('[id^="grpsOfUserRwtc-"]').click(function() {
          var rwgrpusrtxtadd = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var rwgrpnmbradd = $(this).attr("id").split("-")[1];
          var rwgrpnmbraddsec = $(this).attr("id").split("-")[2];

          removeGrpFrmAccGrpListtc(rwgrpusrtxtadd, rwgrpnmbradd, rwgrpnmbraddsec);
          $(this).closest(".groupsOfUserRow").remove();
       });
    }

    $('[id^="addGrpsToUserRwtc-"]').click(function() {

       var clsgrpusrtxt = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
       var clsgrpusrnmbr = $(this).attr("id").split("-")[1];
       var clsgrpusrnmbrsec = $(this).attr("id").split("-")[2];

       $(this).closest(".groupsOfUserRow").remove();

       addGrpToAccGrpListtc(clsgrpusrtxt, clsgrpusrnmbr, clsgrpusrnmbrsec);
    });



    // Add new row to the list of text conferences
    $("#addTextConfExt").click(function() {

       var jkno = 0;
       for (var i in textExtTagObj) { jkno++; };

       var tcExtGroupsEmp = "<div id='grpsOfUserListtc-"+ jkno +"' class='grpsOfUserList'>";
       tcExtGroupsEmp += "<div id='groupsOfUserThtc-"+ jkno +"'><div class='groupsOfUser grofusrLabel' style='color:#03366c'>Show Groups</div></div>";
       tcExtGroupsEmp += "</div>";

       var tcAddGroupsAll = "<div id='addGrpsToUserListtc-"+ jkno +"' class='addGrpsToUserList'>";
       tcAddGroupsAll += "<div id='addGroupsToUserThtc-"+ jkno +"'><div class='grofusrAddLabel'><i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i></div></div>";

       for (var t = 0; t < allGroups.length; t++) {
            tcAddGroupsAll += "<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ allGroups[t] +"</div><div id='addGrpsToUserRwtc-"+ t +"-"+ jkno +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups of users allowed to access the conference.'></i></div></div>";
       }

       tcAddGroupsAll += "</div>";

       $("#txtCnfExtns").append("<tr class='tcbtnTableRow'><td><input type='text' class='txtConfExtension' placeholder='Eg: 710' /></td><td><input type='text' class='tConferenceTag' placeholder='Eg: Sales' /></td><td>"+ tcExtGroupsEmp +"</td><td>"+ tcAddGroupsAll +"</td><td><span class='tcDeleteExtRow deleteExtRowDisabled' title='Delete conference settings from the database.'>X</span></td><td><input type='submit' class='saveCnfBtn' value='Save' title='Save this row.' /></td></tr>");


       // Show/Hide the list of groups of users allowed to access the text conference
       $("#groupsOfUserThtc-"+ jkno +"").click(function() {

          var foundgrpsusrls = $(this).closest(".grpsOfUserList").find($(".groupsOfUserRow"));
          if (foundgrpsusrls.length > 0) {
              foundgrpsusrls.each(function() {
 	        if ($(this).is(":visible")) {
		    $(this).css("display", "none");
		    $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups"); 
	        } else {
		    $(this).css("display", "block");
		    $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups"); 
	        }
              });
          } else {
              if ($(this).closest(".grpsOfUserList").find(".grofusrLabel").text() == "Hide Groups") {
		  $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups");
              } else {
		  $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups");
              }
          }
       });

       // Remove group of users allowed to access the text conference, from groups list
       function removeGrpFrmAccGrpLstc(rwgrpusrtxt, rwgrpnmbr, rwgrpnmbrsec) {

          $("#addGroupsToUserThtc-"+ rwgrpnmbrsec +"").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>");
          $("#addGrpsToUserListtc-"+ rwgrpnmbrsec +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ rwgrpusrtxt +"</div><div id='addGrpsToUserRwtc-"+ rwgrpnmbr +"-"+ rwgrpnmbrsec +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups of users allowed to access the conference.'></i></div></div>");

          $("#addGrpsToUserListtc-"+ rwgrpnmbrsec +"").find(".groupsOfUserRow").each(function() { 
	     $(this).css("display", "block");
          });

          $('[id^="addGrpsToUserRwtc-"]').unbind("click");
          $('[id^="addGrpsToUserRwtc-"]').click(function() {
	     var rwgrpusrtxtrm = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
             var clsgrpusrnmbr = $(this).attr("id").split("-")[1];

	     addGrpToAccGrpLstc(rwgrpusrtxtrm, clsgrpusrnmbr, jkno);
	     $(this).closest(".groupsOfUserRow").remove();
          });
       }

       $("#grpsOfUserListtc-"+ jkno +"").on('click', '.rmvGrpFromUser', function() {

          var rwgrpusrtext = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var clsgrpusrnmbr = $(this).attr("id").split("-")[1];
          var clsgrpusrnmbrsec = $(this).attr("id").split("-")[2];

          $(this).closest(".groupsOfUserRow").remove();

          removeGrpFrmAccGrpLstc(rwgrpusrtext, clsgrpusrnmbr, clsgrpusrnmbrsec);
       });

       // Show/Hide the list of groups that can be added
       $("#addGroupsToUserThtc-"+ jkno +"").click(function() {

          var foundtrpsitmlst = $(this).closest(".addGrpsToUserList").find($(".groupsOfUserRow"));
          if (foundtrpsitmlst.length > 0) {
              foundtrpsitmlst.each(function() { 
                if ($(this).is(":visible")) {
                    $(this).css("display", "none");
                    $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i>"); 
                } else { 
                    $(this).css("display", "block");
                    $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
                } 
              });
          } else {
              if ($(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").text() == "Hide Groups") {
                  $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i>");
              } else {
                  $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
              }
          }
       });

       // Add group of users allowed to access the text conference, to groups list
       function addGrpToAccGrpLstc(clsgrpusrtxt, clsgrpusrnmbr, clsgrpusrnmbrsec) {

          $("#groupsOfUserThtc-"+ clsgrpusrnmbrsec +"").find(".grofusrLabel").html("<span style='color:#03366c'>Hide Groups</span>");
          $("#grpsOfUserListtc-"+ clsgrpusrnmbrsec +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ clsgrpusrtxt +"</div><div id='grpsOfUserRwtc-"+ clsgrpusrnmbr +"-"+ clsgrpusrnmbrsec +"' class='rmvGrpFromUser'><i class='fa fa-times' title='Remove this group from this list.'></i></div></div>");

          $("#grpsOfUserListtc-"+ clsgrpusrnmbrsec +"").find(".groupsOfUserRow").each(function() { 
	     $(this).css("display", "block");
          });

          $('[id^="grpsOfUserRwtc-"]').unbind("click");
          $('[id^="grpsOfUserRwtc-"]').click(function() {
	     var rwgrpusrtextadd = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
             var clsgrpusrnmbr = $(this).attr("id").split("-")[1];

	     removeGrpFrmAccGrpLstc(rwgrpusrtextadd, clsgrpusrnmbr, jkno);
	     $(this).closest(".groupsOfUserRow").remove();
          });
       }

       $("#addGrpsToUserListtc-"+ jkno +"").on('click', '.addGrpToUser', function() {

          var clsgrpusrtext = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var clsgrpusrnmbr = $(this).attr("id").split("-")[1];
          var clsgrpusrnmbrsec = $(this).attr("id").split("-")[2];

          $(this).closest(".groupsOfUserRow").remove();

          addGrpToAccGrpLstc(clsgrpusrtext, clsgrpusrnmbr, clsgrpusrnmbrsec);

       });

    });



    // Show/Hide the list of groups of users allowed to access the audio conference
    $('[id^="groupsOfUserThac-"]').click(function() {

      var foundgrpsusrlst = $(this).closest(".grpsOfUserList").find($(".groupsOfUserRow"));
      if (foundgrpsusrlst.length > 0) {
          foundgrpsusrlst.each(function() { 
            if ($(this).is(":visible")) {
                $(this).css("display", "none");
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups"); 
            } else { 
                $(this).css("display", "block");
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups"); 
            } 
          });
      } else {
          if ($(this).closest(".grpsOfUserList").find(".grofusrLabel").text() == "Hide Groups") {
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups");
          } else {
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups");
          }
      }
    });

    // Remove group of users allowed to access the audio conference, from the list of groups
    function removeGrpFrmAccGrpListac(rwgrpusrtxt, rwgrpnmbr, rwgrpnmbrsec) {

       $("#addGroupsToUserThac-"+ rwgrpnmbrsec +"").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>");
       $("#addGrpsToUserListac-"+ rwgrpnmbrsec +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ rwgrpusrtxt +"</div><div id='addGrpsToUserRwac-"+ rwgrpnmbr +"-"+ rwgrpnmbrsec +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups of users allowed to access the conference.'></i></div></div>");

       $("#addGrpsToUserListac-"+ rwgrpnmbrsec +"").find(".groupsOfUserRow").each(function() { 
          $(this).css("display", "block");
       });

       $('[id^="addGrpsToUserRwac-"]').unbind("click");
       $('[id^="addGrpsToUserRwac-"]').click(function() {
          var rwgrpusrtxtrm = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var rwgrpnmbrrm = $(this).attr("id").split("-")[1];
          var rwgrpnmbrrmsec = $(this).attr("id").split("-")[2];

          addGrpToAccGrpListac(rwgrpusrtxtrm, rwgrpnmbrrm, rwgrpnmbrrmsec);
          $(this).closest(".groupsOfUserRow").remove();
       });
    }

    $('[id^="grpsOfUserRwac-"]').click(function() {

       var rwgrpusrtxt = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
       var rwgrpnmbr = $(this).attr("id").split("-")[1];
       var rwgrpnmbrsec = $(this).attr("id").split("-")[2];

       $(this).closest(".groupsOfUserRow").remove();
       removeGrpFrmAccGrpListac(rwgrpusrtxt, rwgrpnmbr, rwgrpnmbrsec);
    });

    // Show/Hide the list of groups that can be added
    $('[id^="addGroupsToUserThac-"]').click(function() {

       var foundtrpsitmlst = $(this).closest(".addGrpsToUserList").find($(".groupsOfUserRow"));
       if (foundtrpsitmlst.length > 0) {
           foundtrpsitmlst.each(function() { 
             if ($(this).is(":visible")) {
                 $(this).css("display", "none");
                 $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i>"); 
             } else { 
                 $(this).css("display", "block");
                 $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
             } 
           });
       } else {
           if ($(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").text() == "Hide Groups") {
               $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i>");
           } else {
               $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
           }
       }
    });

    // Add group of users allowed to access the audio conference, to the list of groups
    function addGrpToAccGrpListac(clsgrpusrtxt, clsgrpusrnmbr, clsgrpusrnmbrsec) {

       $("#groupsOfUserThac-"+ clsgrpusrnmbrsec +"").find(".grofusrLabel").html("<span style='color:#03366c'>Hide Groups</span>");
       $("#grpsOfUserListac-"+ clsgrpusrnmbrsec +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ clsgrpusrtxt +"</div><div id='grpsOfUserRwac-"+ clsgrpusrnmbr +"-"+ clsgrpusrnmbrsec +"' class='rmvGrpFromUser'><i class='fa fa-times' title='Remove this group from this list.'></i></div></div>");

       $("#grpsOfUserListac-"+ clsgrpusrnmbrsec +"").find(".groupsOfUserRow").each(function() { 
          $(this).css("display", "block");
       });

       $('[id^="grpsOfUserRwac-"]').unbind("click");
       $('[id^="grpsOfUserRwac-"]').click(function() {
          var rwgrpusrtxtadd = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var rwgrpnmbradd = $(this).attr("id").split("-")[1];
          var rwgrpnmbraddsec = $(this).attr("id").split("-")[2];

          removeGrpFrmAccGrpListac(rwgrpusrtxtadd, rwgrpnmbradd, rwgrpnmbraddsec);
          $(this).closest(".groupsOfUserRow").remove();
       });
    }

    $('[id^="addGrpsToUserRwac-"]').click(function() {

       var clsgrpusrtxt = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
       var clsgrpusrnmbr = $(this).attr("id").split("-")[1];
       var clsgrpusrnmbrsec = $(this).attr("id").split("-")[2];

       $(this).closest(".groupsOfUserRow").remove();

       addGrpToAccGrpListac(clsgrpusrtxt, clsgrpusrnmbr, clsgrpusrnmbrsec);
    });



    // Add new row to the list of audio conferences
    $("#addAudioConfExt").click(function() {

       var jkno = 0;
       for (var i in audioExtTagObj) { jkno++; };

       var acExtGroupsEmp = "<div id='grpsOfUserListac-"+ jkno +"' class='grpsOfUserList'>";
       acExtGroupsEmp += "<div id='groupsOfUserThac-"+ jkno +"'><div class='groupsOfUser grofusrLabel' style='color:#03366c'>Show Groups</div></div>";
       acExtGroupsEmp += "</div>";

       var acAddGroupsAll = "<div id='addGrpsToUserListac-"+ jkno +"' class='addGrpsToUserList'>";
       acAddGroupsAll += "<div id='addGroupsToUserThac-"+ jkno +"'><div class='grofusrAddLabel'><i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i></div></div>";

       for (var t = 0; t < allGroups.length; t++) {
            acAddGroupsAll += "<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ allGroups[t] +"</div><div id='addGrpsToUserRwac-"+ t +"-"+ jkno +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups of users allowed to access the conference.'></i></div></div>";
       }

       acAddGroupsAll += "</div>";

       $("#audioCnfExtns").append("<tr class='acbtnTableRow'><td><input type='text' class='audioConfExtension' placeholder='Eg: 710' /></td><td><input type='text' class='aConferenceTag' placeholder='Eg: Sales' /></td><td>"+ acExtGroupsEmp +"</td><td>"+ acAddGroupsAll +"</td><td><span class='acDeleteExtRow deleteExtRowDisabled' title='Delete conference settings from the database.'>X</span></td><td><input type='submit' class='saveCnfBtn' value='Save' title='Save this row.' /></td></tr>");


       // Show/Hide the list of groups of users allowed to access the audio conference
       $("#groupsOfUserThac-"+ jkno +"").click(function() {

          var foundgrpsusrls = $(this).closest(".grpsOfUserList").find($(".groupsOfUserRow"));
          if (foundgrpsusrls.length > 0) {
              foundgrpsusrls.each(function() {
 	        if ($(this).is(":visible")) {
		    $(this).css("display", "none");
		    $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups"); 
	        } else {
		    $(this).css("display", "block");
		    $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups"); 
	        }
              });
          } else {
              if ($(this).closest(".grpsOfUserList").find(".grofusrLabel").text() == "Hide Groups") {
		  $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups");
              } else {
		  $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups");
              }
          }
       });

       // Remove group of users allowed to access the audio conference, from groups list
       function removeGrpFrmAccGrpLsac(rwgrpusrtxt, rwgrpnmbr, rwgrpnmbrsec) {

          $("#addGroupsToUserThac-"+ rwgrpnmbrsec +"").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>");
          $("#addGrpsToUserListac-"+ rwgrpnmbrsec +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ rwgrpusrtxt +"</div><div id='addGrpsToUserRwac-"+ rwgrpnmbr +"-"+ rwgrpnmbrsec +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups of users allowed to access the conference.'></i></div></div>");

          $("#addGrpsToUserListac-"+ rwgrpnmbrsec +"").find(".groupsOfUserRow").each(function() { 
	     $(this).css("display", "block");
          });

          $('[id^="addGrpsToUserRwac-"]').unbind("click");
          $('[id^="addGrpsToUserRwac-"]').click(function() {
	     var rwgrpusrtxtrm = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
             var clsgrpusrnmbr = $(this).attr("id").split("-")[1];

	     addGrpToAccGrpLsac(rwgrpusrtxtrm, clsgrpusrnmbr, jkno);
	     $(this).closest(".groupsOfUserRow").remove();
          });
       }

       $("#grpsOfUserListac-"+ jkno +"").on('click', '.rmvGrpFromUser', function() {

          var rwgrpusrtext = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var clsgrpusrnmbr = $(this).attr("id").split("-")[1];
          var clsgrpusrnmbrsec = $(this).attr("id").split("-")[2];

          $(this).closest(".groupsOfUserRow").remove();

          removeGrpFrmAccGrpLsac(rwgrpusrtext, clsgrpusrnmbr, clsgrpusrnmbrsec);
       });

       // Show/Hide the list of groups that can be added
       $("#addGroupsToUserThac-"+ jkno +"").click(function() {

          var foundtrpsitmlst = $(this).closest(".addGrpsToUserList").find($(".groupsOfUserRow"));
          if (foundtrpsitmlst.length > 0) {
              foundtrpsitmlst.each(function() { 
                if ($(this).is(":visible")) {
                    $(this).css("display", "none");
                    $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i>"); 
                } else { 
                    $(this).css("display", "block");
                    $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
                } 
              });
          } else {
              if ($(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").text() == "Hide Groups") {
                  $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i>");
              } else {
                  $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
              }
          }
       });

       // Add group of users allowed to access the audio conference, to groups list
       function addGrpToAccGrpLsac(clsgrpusrtxt, clsgrpusrnmbr, clsgrpusrnmbrsec) {

          $("#groupsOfUserThac-"+ clsgrpusrnmbrsec +"").find(".grofusrLabel").html("<span style='color:#03366c'>Hide Groups</span>");
          $("#grpsOfUserListac-"+ clsgrpusrnmbrsec +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ clsgrpusrtxt +"</div><div id='grpsOfUserRwac-"+ clsgrpusrnmbr +"-"+ clsgrpusrnmbrsec +"' class='rmvGrpFromUser'><i class='fa fa-times' title='Remove this group from this list.'></i></div></div>");

          $("#grpsOfUserListac-"+ clsgrpusrnmbrsec +"").find(".groupsOfUserRow").each(function() { 
	     $(this).css("display", "block");
          });

          $('[id^="grpsOfUserRwac-"]').unbind("click");
          $('[id^="grpsOfUserRwac-"]').click(function() {
	     var rwgrpusrtextadd = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
             var clsgrpusrnmbr = $(this).attr("id").split("-")[1];

	     removeGrpFrmAccGrpLsac(rwgrpusrtextadd, clsgrpusrnmbr, jkno);
	     $(this).closest(".groupsOfUserRow").remove();
          });
       }

       $("#addGrpsToUserListac-"+ jkno +"").on('click', '.addGrpToUser', function() {

          var clsgrpusrtext = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var clsgrpusrnmbr = $(this).attr("id").split("-")[1];
          var clsgrpusrnmbrsec = $(this).attr("id").split("-")[2];

          $(this).closest(".groupsOfUserRow").remove();

          addGrpToAccGrpLsac(clsgrpusrtext, clsgrpusrnmbr, clsgrpusrnmbrsec);

       });

    });





    // Show/Hide the list of groups of users allowed to access the video conference
    $('[id^="groupsOfUserThvc-"]').click(function() {

      var foundgrpsusrlst = $(this).closest(".grpsOfUserList").find($(".groupsOfUserRow"));
      if (foundgrpsusrlst.length > 0) {
          foundgrpsusrlst.each(function() { 
            if ($(this).is(":visible")) {
                $(this).css("display", "none");
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups"); 
            } else { 
                $(this).css("display", "block");
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups"); 
            } 
          });
      } else {
          if ($(this).closest(".grpsOfUserList").find(".grofusrLabel").text() == "Hide Groups") {
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups");
          } else {
                $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups");
          }
      }
    });

    // Remove group of users allowed to access the video conference, from the list of groups
    function removeGrpFrmAccGrpListvc(rwgrpusrtxt, rwgrpnmbr, rwgrpnmbrsec) {

       $("#addGroupsToUserThvc-"+ rwgrpnmbrsec +"").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>");
       $("#addGrpsToUserListvc-"+ rwgrpnmbrsec +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ rwgrpusrtxt +"</div><div id='addGrpsToUserRwvc-"+ rwgrpnmbr +"-"+ rwgrpnmbrsec +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups of users allowed to access the conference.'></i></div></div>");

       $("#addGrpsToUserListvc-"+ rwgrpnmbrsec +"").find(".groupsOfUserRow").each(function() { 
          $(this).css("display", "block");
       });

       $('[id^="addGrpsToUserRwvc-"]').unbind("click");
       $('[id^="addGrpsToUserRwvc-"]').click(function() {
          var rwgrpusrtxtrm = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var rwgrpnmbrrm = $(this).attr("id").split("-")[1];
          var rwgrpnmbrrmsec = $(this).attr("id").split("-")[2];

          addGrpToAccGrpListvc(rwgrpusrtxtrm, rwgrpnmbrrm, rwgrpnmbrrmsec);
          $(this).closest(".groupsOfUserRow").remove();
       });
    }

    $('[id^="grpsOfUserRwvc-"]').click(function() {

       var rwgrpusrtxt = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
       var rwgrpnmbr = $(this).attr("id").split("-")[1];
       var rwgrpnmbrsec = $(this).attr("id").split("-")[2];

       $(this).closest(".groupsOfUserRow").remove();
       removeGrpFrmAccGrpListvc(rwgrpusrtxt, rwgrpnmbr, rwgrpnmbrsec);
    });

    // Show/Hide the list of groups that can be added
    $('[id^="addGroupsToUserThvc-"]').click(function() {

       var foundtrpsitmlst = $(this).closest(".addGrpsToUserList").find($(".groupsOfUserRow"));
       if (foundtrpsitmlst.length > 0) {
           foundtrpsitmlst.each(function() { 
             if ($(this).is(":visible")) {
                 $(this).css("display", "none");
                 $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i>"); 
             } else { 
                 $(this).css("display", "block");
                 $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
             } 
           });
       } else {
           if ($(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").text() == "Hide Groups") {
               $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i>");
           } else {
               $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
           }
       }
    });

    // Add group of users allowed to access the video conference, to the list of groups
    function addGrpToAccGrpListvc(clsgrpusrtxt, clsgrpusrnmbr, clsgrpusrnmbrsec) {

       $("#groupsOfUserThvc-"+ clsgrpusrnmbrsec +"").find(".grofusrLabel").html("<span style='color:#03366c'>Hide Groups</span>");
       $("#grpsOfUserListvc-"+ clsgrpusrnmbrsec +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ clsgrpusrtxt +"</div><div id='grpsOfUserRwvc-"+ clsgrpusrnmbr +"-"+ clsgrpusrnmbrsec +"' class='rmvGrpFromUser'><i class='fa fa-times' title='Remove this group from this list.'></i></div></div>");

       $("#grpsOfUserListvc-"+ clsgrpusrnmbrsec +"").find(".groupsOfUserRow").each(function() { 
          $(this).css("display", "block");
       });

       $('[id^="grpsOfUserRwvc-"]').unbind("click");
       $('[id^="grpsOfUserRwvc-"]').click(function() {
          var rwgrpusrtxtadd = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var rwgrpnmbradd = $(this).attr("id").split("-")[1];
          var rwgrpnmbraddsec = $(this).attr("id").split("-")[2];

          removeGrpFrmAccGrpListvc(rwgrpusrtxtadd, rwgrpnmbradd, rwgrpnmbraddsec);
          $(this).closest(".groupsOfUserRow").remove();
       });
    }

    $('[id^="addGrpsToUserRwvc-"]').click(function() {

       var clsgrpusrtxt = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
       var clsgrpusrnmbr = $(this).attr("id").split("-")[1];
       var clsgrpusrnmbrsec = $(this).attr("id").split("-")[2];

       $(this).closest(".groupsOfUserRow").remove();

       addGrpToAccGrpListvc(clsgrpusrtxt, clsgrpusrnmbr, clsgrpusrnmbrsec);
    });



    // Add new row to the list of video conferences
    $("#addVideoConfExt").click(function() {

       var jkno = 0;
       for (var i in videoExtTagObj) { jkno++; };

       var vcExtGroupsEmp = "<div id='grpsOfUserListvc-"+ jkno +"' class='grpsOfUserList'>";
       vcExtGroupsEmp += "<div id='groupsOfUserThvc-"+ jkno +"'><div class='groupsOfUser grofusrLabel' style='color:#03366c'>Show Groups</div></div>";
       vcExtGroupsEmp += "</div>";

       var vcAddGroupsAll = "<div id='addGrpsToUserListvc-"+ jkno +"' class='addGrpsToUserList'>";
       vcAddGroupsAll += "<div id='addGroupsToUserThvc-"+ jkno +"'><div class='grofusrAddLabel'><i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i></div></div>";

       for (var t = 0; t < allGroups.length; t++) {
            vcAddGroupsAll += "<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ allGroups[t] +"</div><div id='addGrpsToUserRwvc-"+ t +"-"+ jkno +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups of users allowed to access the conference.'></i></div></div>";
       }

       vcAddGroupsAll += "</div>";

       $("#videoCnfExtns").append("<tr class='vcbtnTableRow'><td><input type='text' class='videoConfExtension' placeholder='Eg: 710' /></td><td><input type='text' class='vConferenceTag' placeholder='Eg: Sales' /></td><td>"+ vcExtGroupsEmp +"</td><td>"+ vcAddGroupsAll +"</td><td><span class='vcDeleteExtRow deleteExtRowDisabled' title='Delete conference settings from the database.'>X</span></td><td><input type='submit' class='saveCnfBtn' value='Save' title='Save this row.' /></td></tr>");


       // Show/Hide the list of groups of users allowed to access the video conference
       $("#groupsOfUserThvc-"+ jkno +"").click(function() {

          var foundgrpsusrls = $(this).closest(".grpsOfUserList").find($(".groupsOfUserRow"));
          if (foundgrpsusrls.length > 0) {
              foundgrpsusrls.each(function() {
 	        if ($(this).is(":visible")) {
		    $(this).css("display", "none");
		    $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups"); 
	        } else {
		    $(this).css("display", "block");
		    $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups"); 
	        }
              });
          } else {
              if ($(this).closest(".grpsOfUserList").find(".grofusrLabel").text() == "Hide Groups") {
		  $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Show Groups");
              } else {
		  $(this).closest(".grpsOfUserList").find(".grofusrLabel").text("Hide Groups");
              }
          }
       });

       // Remove group of users allowed to access the video conference, from groups list
       function removeGrpFrmAccGrpLsvc(rwgrpusrtxt, rwgrpnmbr, rwgrpnmbrsec) {

          $("#addGroupsToUserThvc-"+ rwgrpnmbrsec +"").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>");
          $("#addGrpsToUserListvc-"+ rwgrpnmbrsec +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ rwgrpusrtxt +"</div><div id='addGrpsToUserRwvc-"+ rwgrpnmbr +"-"+ rwgrpnmbrsec +"' class='addGrpToUser'><i class='fa fa-plus' title='Add this group to the list of groups of users allowed to access the conference.'></i></div></div>");

          $("#addGrpsToUserListvc-"+ rwgrpnmbrsec +"").find(".groupsOfUserRow").each(function() { 
	     $(this).css("display", "block");
          });

          $('[id^="addGrpsToUserRwvc-"]').unbind("click");
          $('[id^="addGrpsToUserRwvc-"]').click(function() {
	     var rwgrpusrtxtrm = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
             var clsgrpusrnmbr = $(this).attr("id").split("-")[1];

	     addGrpToAccGrpLsvc(rwgrpusrtxtrm, clsgrpusrnmbr, jkno);
	     $(this).closest(".groupsOfUserRow").remove();
          });
       }

       $("#grpsOfUserListvc-"+ jkno +"").on('click', '.rmvGrpFromUser', function() {

          var rwgrpusrtext = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var clsgrpusrnmbr = $(this).attr("id").split("-")[1];
          var clsgrpusrnmbrsec = $(this).attr("id").split("-")[2];

          $(this).closest(".groupsOfUserRow").remove();

          removeGrpFrmAccGrpLsvc(rwgrpusrtext, clsgrpusrnmbr, clsgrpusrnmbrsec);
       });

       // Show/Hide the list of groups that can be added
       $("#addGroupsToUserThvc-"+ jkno +"").click(function() {

          var foundtrpsitmlst = $(this).closest(".addGrpsToUserList").find($(".groupsOfUserRow"));
          if (foundtrpsitmlst.length > 0) {
              foundtrpsitmlst.each(function() { 
                if ($(this).is(":visible")) {
                    $(this).css("display", "none");
                    $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i>"); 
                } else { 
                    $(this).css("display", "block");
                    $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
                } 
              });
          } else {
              if ($(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").text() == "Hide Groups") {
                  $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<i class='fa fa-plus-square' style='font-size:17px' title='Add more groups to the list of groups of users allowed to access the conference.'></i>");
              } else {
                  $(this).closest(".addGrpsToUserList").find(".grofusrAddLabel").html("<span style='display:block;text-align:left!important;padding:1px 4px 2px 4px;'>Hide Groups</span>"); 
              }
          }
       });

       // Add group of users allowed to access the video conference, to groups list
       function addGrpToAccGrpLsvc(clsgrpusrtxt, clsgrpusrnmbr, clsgrpusrnmbrsec) {

          $("#groupsOfUserThvc-"+ clsgrpusrnmbrsec +"").find(".grofusrLabel").html("<span style='color:#03366c'>Hide Groups</span>");
          $("#grpsOfUserListvc-"+ clsgrpusrnmbrsec +"").append("<div class='groupsOfUserRow'><div class='groupsOfUser'>"+ clsgrpusrtxt +"</div><div id='grpsOfUserRwvc-"+ clsgrpusrnmbr +"-"+ clsgrpusrnmbrsec +"' class='rmvGrpFromUser'><i class='fa fa-times' title='Remove this group from this list.'></i></div></div>");

          $("#grpsOfUserListvc-"+ clsgrpusrnmbrsec +"").find(".groupsOfUserRow").each(function() { 
	     $(this).css("display", "block");
          });

          $('[id^="grpsOfUserRwvc-"]').unbind("click");
          $('[id^="grpsOfUserRwvc-"]').click(function() {
	     var rwgrpusrtextadd = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
             var clsgrpusrnmbr = $(this).attr("id").split("-")[1];

	     removeGrpFrmAccGrpLsvc(rwgrpusrtextadd, clsgrpusrnmbr, jkno);
	     $(this).closest(".groupsOfUserRow").remove();
          });
       }

       $("#addGrpsToUserListvc-"+ jkno +"").on('click', '.addGrpToUser', function() {

          var clsgrpusrtext = $(this).closest(".groupsOfUserRow").find(".groupsOfUser").text();
          var clsgrpusrnmbr = $(this).attr("id").split("-")[1];
          var clsgrpusrnmbrsec = $(this).attr("id").split("-")[2];

          $(this).closest(".groupsOfUserRow").remove();

          addGrpToAccGrpLsvc(clsgrpusrtext, clsgrpusrnmbr, clsgrpusrnmbrsec);

       });

    });




    // Save text conference data to the database
    $("#txtCnfExtns").on("click", ".saveCnfBtn", function() {

      if ($(this).val() == "Save") {

          var tcExtnsn = $(this).closest('tr').find('input.txtConfExtension').val();
          var tConfTag = $(this).closest('tr').find('input.tConferenceTag').val();

          var slctdAccGrps = '';
          $(this).closest(".tcbtnTableRow").find(".grpsOfUserList").find(".groupsOfUserRow").find(".groupsOfUser").each(function() { slctdAccGrps += "|"+ $(this).text(); });
          var limitToGroupstc = slctdAccGrps +"|";

          if (tcExtnsn != '' && tConfTag != '') {
            if (/^[a-zA-Z0-9\*\#]+$/.test(tcExtnsn) && tcExtnsn.length < 51) {
              if (/^[a-zA-Z0-9\s]+$/.test(tConfTag) && tConfTag.length < 151) {
                 if (/^[a-zA-Z0-9\s\-\|]+$/.test(limitToGroupstc) && limitToGroupstc.length < 1001) {

                     $.ajax({
                       type: "POST",
                       url: "src/save-update-textconf-extensions.php",
                       dataType: "JSON",
                       data: {
                          username: userName,
                          text_conf_extension: tcExtnsn,
                          text_conf_tag: tConfTag,
                          limit_to_groups: limitToGroupstc,
                          s_ajax_call: validateSToken
                       },
                       success: function(response) {

                                   if (response.result == 'The data has been successfully saved to the database !') {
                                       textExtTagObj[tcExtnsn] = tConfTag;
                                       textExtGroupsObj[tcExtnsn] = limitToGroupstc;

                                       alert("The data has been successfully saved to the database !");
                                       closeVideoAudio(); $.jeegoopopup.open({}); $.jeegoopopup.close();

                                   } else { alert(response.result); }

                       },
                       error: function(response) {
                                   alert("An error occurred while trying to save the data to the database !");
                       }
                     });

                     $(this).closest('[class="tcbtnTableRow"]').find('[class="txtConfExtension"]').attr("disabled", true);
                     $(this).closest('[class="tcbtnTableRow"]').find('[class="tConferenceTag"]').attr("disabled", true);
                     $(this).attr("value", "Edit");
                     $(this).prop("title", "Edit this row.");

                 } else { alert("The groups of users allowed to access the text conference can only contain letters, numbers, hyphens and spaces and should have less than 1001 characters in total."); }
              } else { alert("The label should contain only letters, numbers and spaces and should have less than 151 characters."); }
            } else { alert("The extension should contain only numbers, letters, asterisks and number signs and should have less than 51 characters."); }
          } else { alert("Please fill in both the \"Text Conference Extension\" and \"Text Conference Room Label\" fields!"); }

      } else {
            $(this).closest('[class="tcbtnTableRow"]').find('[class="txtConfExtension"]').attr("disabled", false);
            $(this).closest('[class="tcbtnTableRow"]').find('[class="tConferenceTag"]').attr("disabled", false);
            $(this).attr("value", "Save");
            $(this).prop("title", "Save this row.");
      }
    });


    // Delete text conference data from the database
    $("#txtCnfExtns").on("click", ".tcDeleteExtRow", function() {

      var tcExtnsndl = $(this).closest('tr').find('input.txtConfExtension').val();

      if (tcExtnsndl != '') {

         if (confirm("Do you really want to delete this row from the database ?")) {

             $.ajax({
                    type: "POST",
                    url: "src/remove-textconf-ext-data.php",
                    dataType: "JSON",
                    data: {
                           username: userName,
                           text_conf_extension: tcExtnsndl,
                           s_ajax_call: validateSToken
                          },
                    success: function(response) {

                                      if (response.result == 'success') {
                                          alert("The data has been successfully removed! You can reload the page to see the results.");
                                      } else { alert(response.result); }
                    },
                    error: function(response) {
                                      alert("An error occurred while trying to remove the data !");
                    }
             });

         }
      }
    });


    // Save audio conference data to the database
    $("#audioCnfExtns").on("click", ".saveCnfBtn", function() {

      if ($(this).val() == "Save") {

          var acExtnsn = $(this).closest('tr').find('input.audioConfExtension').val();
          var aConfTag = $(this).closest('tr').find('input.aConferenceTag').val();

          var slctdAccGrpsac = '';
          $(this).closest(".acbtnTableRow").find(".grpsOfUserList").find(".groupsOfUserRow").find(".groupsOfUser").each(function() { slctdAccGrpsac += "|"+ $(this).text(); });
          var limitToGroupsac = slctdAccGrpsac +"|";

          if (acExtnsn != '' && aConfTag != '') {
            if (/^[a-zA-Z0-9\*\#]+$/.test(acExtnsn) && acExtnsn.length < 51) {
              if (/^[a-zA-Z0-9\s]+$/.test(aConfTag) && aConfTag.length < 151) {
                if (/^[a-zA-Z0-9\s\-\|]+$/.test(limitToGroupsac) && limitToGroupsac.length < 1001) {
                     $.ajax({
                       type: "POST",
                       url: "src/save-update-audioconf-extensions.php",
                       dataType: "JSON",
                       data: {
                          username: userName,
                          audio_conf_extension: acExtnsn,
                          audio_conf_tag: aConfTag,
                          limit_to_groups: limitToGroupsac,
                          s_ajax_call: validateSToken
                       },
                       success: function(response) {

                                   if (response.result == 'The data has been successfully saved to the database !') {
                                       audioExtTagObj[acExtnsn] = aConfTag;
                                       audioExtGroupsObj[acExtnsn] = limitToGroupsac;

                                       alert("The data has been successfully saved to the database !");
                                       closeVideoAudio(); $.jeegoopopup.open({}); $.jeegoopopup.close();

                                   } else { alert(response.result); }

                       },
                       error: function(response) {
                                   alert("An error occurred while trying to save the data to the database !");
                       }
                     });

                     $(this).closest('tr').find('[class="audioConfExtension"]').attr("disabled", true);
                     $(this).closest('tr').find('[class="aConferenceTag"]').attr("disabled", true);
                     $(this).attr("value", "Edit");
                     $(this).prop("title", "Edit this row.");

                } else { alert("The groups of users allowed to access the audio conference can only contain letters, numbers, hyphens and spaces and should have less than 1001 characters in total."); }
              } else { alert("The label should contain only letters, numbers and spaces and should have less than 151 characters."); }
            } else { alert("The extension should contain only numbers, letters, asterisks and number signs and should have less than 51 characters."); }
          } else { alert("Please fill in both the \"Audio Conference Extension\" and \"Audio Conference Room Label\" fields!"); }

      } else {
            $(this).closest('tr').find('[class="audioConfExtension"]').attr("disabled", false);
            $(this).closest('tr').find('[class="aConferenceTag"]').attr("disabled", false);
            $(this).attr("value", "Save");
            $(this).prop("title", "Save this row.");
      }
    });


    // Delete audio conference data from the database
    $("#audioCnfExtns").on("click", ".acDeleteExtRow", function() {

      var acExtnsndl = $(this).closest('tr').find('input.audioConfExtension').val();

      if (acExtnsndl != '') {

         if (confirm("Do you really want to delete this row from the database ?")) {

             $.ajax({
                    type: "POST",
                    url: "src/remove-audioconf-ext-data.php",
                    dataType: "JSON",
                    data: {
                           username: userName,
                           audio_conf_extension: acExtnsndl,
                           s_ajax_call: validateSToken
                          },
                    success: function(response) {

                                      if (response.result == 'The data has been successfully removed from the database !') {
                                          alert("The data has been successfully removed! You can reload the page to see the results.");
                                      } else { alert(response.result); }
                    },
                    error: function(response) {
                                      alert("An error occurred while trying to remove the data !");
                    }
             });

         }
      }
    });


    // Save video conference data to the database
    $("#videoCnfExtns").on("click", ".saveCnfBtn", function() {

      if ($(this).val() == "Save") {

          var vcExtnsn = $(this).closest('tr').find('input.videoConfExtension').val();
          var vConfTag = $(this).closest('tr').find('input.vConferenceTag').val();

          var slctdAccGrpsvc = '';
          $(this).closest(".vcbtnTableRow").find(".grpsOfUserList").find(".groupsOfUserRow").find(".groupsOfUser").each(function() { slctdAccGrpsvc += "|"+ $(this).text(); });
          var limitToGroupsvc = slctdAccGrpsvc +"|";

          if (vcExtnsn != '' && vConfTag != '') {
            if (/^[a-zA-Z0-9\*\#]+$/.test(vcExtnsn) && vcExtnsn.length < 51) {
              if (/^[a-zA-Z0-9\s]+$/.test(vConfTag) && vConfTag.length < 151) {
                if (/^[a-zA-Z0-9\s\-\|]+$/.test(limitToGroupsvc) && limitToGroupsvc.length < 1001) {
                     $.ajax({
                       type: "POST",
                       url: "src/save-update-videoconf-extensions.php",
                       dataType: "JSON",
                       data: {
                          username: userName,
                          video_conf_extension: vcExtnsn,
                          video_conf_tag: vConfTag,
                          limit_to_groups: limitToGroupsvc,
                          s_ajax_call: validateSToken
                       },
                       success: function(response) {

                                   if (response.result == 'The data has been successfully saved to the database !') {
                                       videoExtTagObj[vcExtnsn] = vConfTag;
                                       videoExtGroupsObj[vcExtnsn] = limitToGroupsvc;

                                       alert("The data has been successfully saved to the database !");
                                       closeVideoAudio(); $.jeegoopopup.open({}); $.jeegoopopup.close();

                                   } else { alert(response.result); }

                       },
                       error: function(response) {
                                   alert("An error occurred while trying to save the data to the database !");
                       }
                     });

                     $(this).closest('tr').find('[class="videoConfExtension"]').attr("disabled", true);
                     $(this).closest('tr').find('[class="vConferenceTag"]').attr("disabled", true);
                     $(this).attr("value", "Edit");
                     $(this).prop("title", "Edit this row.");

                } else { alert("The groups of users allowed to access the video conference can only contain letters, numbers, hyphens and spaces and should have less than 1001 characters in total."); }
              } else { alert("The label should contain only letters, numbers and spaces and should have less than 151 characters."); }
            } else { alert("The extension should contain only numbers, letters, asterisks and number signs and should have less than 51 characters."); }
          } else { alert("Please fill in both the \"Video Conference Extension\" and \"Video Conference Room Label\" fields!"); }

      } else {
            $(this).closest('tr').find('[class="videoConfExtension"]').attr("disabled", false);
            $(this).closest('tr').find('[class="vConferenceTag"]').attr("disabled", false);
            $(this).attr("value", "Save");
            $(this).prop("title", "Save this row.");
      }
    });

    // Delete video conference data from the database
    $("#videoCnfExtns").on("click", ".vcDeleteExtRow", function() {

      var vcExtnsndl = $(this).closest('tr').find('input.videoConfExtension').val();

      if (vcExtnsndl != '') {

         if (confirm("Do you really want to delete this row from the database ?")) {

             $.ajax({
                    type: "POST",
                    url: "src/remove-videoconf-ext-data.php",
                    dataType: "JSON",
                    data: {
                           username: userName,
                           video_conf_extension: vcExtnsndl,
                           s_ajax_call: validateSToken
                          },
                    success: function(response) {

                                      if (response.result == 'The data has been successfully removed from the database !') {
                                          alert("The data has been successfully removed! You can reload the page to see the results.");
                                      } else { alert(response.result); }
                    },
                    error: function(response) {
                                      alert("An error occurred while trying to remove the data !");
                    }
             });

         }
      }
    });


    // Save configration data for external video conference users
    $("#vidConfExternalTable").on("click", ".saveCnfBtn", function() {

      if ($(this).val() == "Save") {

        var extUserExtension = $(this).closest('tr').find('input.extConfExtension').val();
        var extUserExtensionPass = $(this).closest('tr').find('input.extConfExtensionPass').val();
        var confExtension = $(this).closest('tr').find('input.vidConferenceExtension').val();
        var confTag = $(this).closest('tr').find('input.vidConferenceTag').val();

        if (extUserExtension != '' && extUserExtensionPass != '' && confExtension != '') {
          if (extUserExtension.length < 51 && extUserExtensionPass.length < 501 && confExtension.length < 51) {
            if (/^[a-zA-Z0-9\s]+$/.test(confTag) && confTag.length < 151) {
              // Check if the extension that has been entered is valid
              if (/^[a-zA-Z0-9\*\#]+$/.test(extUserExtension) && /^[a-zA-Z0-9\*\#]+$/.test(confExtension)) {

                $.ajax({
                   type: "POST",
                   url: "src/save-update-external-user-conf.php",
                   dataType: "JSON",
                   data: {
                          username: userName,
                          exten_for_external: extUserExtension,
                          exten_for_ext_pass: extUserExtensionPass,
                          conf_extension: confExtension,
                          conf_tag: confTag,
                          wss_server: wssServer,
                          web_sock_path: WebSocketPort +''+ ServerPath,
                          s_ajax_call: validateSToken
                       },
                   success: function(response) {

                                   if (response.result == 'The data has been successfully saved to the database !') {

                                       getExternalUserConfDB();
                                       alert("The data has been successfully saved to the database ! To see the result you can reopen this window !");

                                       closeVideoAudio(); $.jeegoopopup.open({}); $.jeegoopopup.close();

                                   } else { alert(response.result); }

                   },
                   error: function(response) {
                                   alert("An error occurred while trying to save the data to the database !");
                   }
                });

                $(this).closest('[class="btnTableRow"]').find('[class="extConfExtension"]').attr("disabled", true);
                $(this).closest('[class="btnTableRow"]').find('[class="extConfExtensionPass"]').attr("disabled", true);
                $(this).closest('[class="btnTableRow"]').find('[class="vidConferenceExtension"]').attr("disabled", true);
                $(this).closest('[class="btnTableRow"]').find('[class="vidConferenceTag"]').attr("disabled", true);
                $(this).attr("value", "Edit");
                $(this).prop("title", "Edit this row.");

              } else { alert("Extensions should contain only numbers, letters, asterisks and number signs."); }
            } else { alert("The label should contain only letters, numbers and spaces and should have less than 151 characters."); }
          } else { alert("The extension(s) and/or the SIP password don't have a reasonable length."); }
        } else { alert("Please fill in the \"Extension\", \"SIP Password\" and \"Conference Extension\" fields !"); }
      } else {
          $(this).closest('[class="btnTableRow"]').find('[class="extConfExtension"]').attr("disabled", false);
          $(this).closest('[class="btnTableRow"]').find('[class="extConfExtensionPass"]').attr("disabled", false);
          $(this).closest('[class="btnTableRow"]').find('[class="vidConferenceExtension"]').attr("disabled", false);
          $(this).closest('[class="btnTableRow"]').find('[class="vidConferenceTag"]').attr("disabled", false);
          $(this).attr("value", "Save");
          $(this).prop("title", "Save this row.");
      }
    });


    // Delete configration data for external video conference users from the database
    $("#vidConfExternalTable").on("click", ".deleteExtRow", function() {

      var vConfCrRow = $(this).closest('tr');
      var targetExtension = $(this).closest('[class="btnTableRow"]').find('[class="extConfExtension"]').val();

      if (targetExtension != '') {

         if (confirm("Do you really want to delete this row from this window and from the database ?")) {

             $.ajax({
                 type: "POST",
                 url: "src/remove-external-user-ext-data.php",
                 dataType: "JSON",
                 data: {
                        username: userName,
                        exten_for_external: targetExtension,
                        s_ajax_call: validateSToken
                       },
                 success: function(response) {

                                   if (response.result == 'The data has been successfully removed from the database !') {

                                       vConfCrRow.remove();
                                       getExternalUserConfDB();
                                       alert("The data has been permanently removed !");

                                   } else { alert(response.result); }
                 },
                 error: function(response) {
                             alert("An error occurred while trying to remove the data !");
                 }
             });

         }
      }
    });

    $(".copyToClipboard").mouseenter(function() {
        if ($(this).closest('[class="btnTableRow"]').find('[class="extConfExtensionLink"]').val() != '') {
            $(this).css("color", "#424242");
            $(this).css("cursor", "pointer");
        }
    });

    $(".copyToClipboard").mouseleave(function() {
        $(this).css("color", "#cccccc");
    });

    $(".copyToClipboard").click(function() {
        if ($(this).closest('[class="btnTableRow"]').find('[class="extConfExtensionLink"]').val() != '') {
	      var $tempEl = $("<input>");
	      $("body").append($tempEl);
	      $tempEl.val($(this).closest('[class="btnTableRow"]').find('[class="extConfExtensionLink"]').val()).select();
	      document.execCommand("Copy");
	      $tempEl.remove();
	      alert("The link has been copied to your clipboard!");
        }
    });

    var externUserData = getDbItem("externalUserConfElem", "");
    if (externUserData != 0 && externUserData != null && typeof externUserData !== 'undefined') {
        $("#emptyExtRow").hide();
    }

    $("#add_New_External_User").click(function() {
       $("#vidConfExternalTable").append("<tr class='btnTableRow'><td><input type='text' class='extConfExtension' placeholder='Eg: 779' /></td><td><input type='password' class='extConfExtensionPass' placeholder='Eg: d5?W?9q?8rg*R9!eFrVth?9' /></td><td><input type='text' class='vidConferenceExtension' placeholder='Eg: 910' /></td><td><input type='text' class='vidConferenceTag' placeholder='Eg: Sales' /></td><td><input type='text' class='extConfExtensionLink' placeholder='Generated on Save' title='Generated after you click Save' disabled='disabled' /></td><td><span class='copyToClipboard'><i class='fa fa-clipboard' title='Copy URL to clipboard.'></i></span></td><td><span class='deleteExtRow deleteExtRowDisabled' title='Delete extension data from the database.'>X</span></td><td><input type='submit' class='saveCnfBtn' value='Save' title='Save this row.' /></td></tr>");
    });







    // Profile Picture
    cropper = $("#ImageCanvas").croppie({
        viewport: { width: 150, height: 150, type: 'circle' }
    });

    // Preview Existing Image
    $("#ImageCanvas").croppie('bind', { url: getPicture("profilePicture") }).then(function() {
       $('.cr-slider').attr({ min: 0.5, max: 3 });
       $('.cr-slider').change(function() { pictureUp = true; });
    });

    // Wireup File Change
    $("#fileUploader").change(function () {
        var filesArray = $(this).prop('files');
    
        if (filesArray.length == 1) {
            var uploadId = Math.floor(Math.random() * 1000000000);
            var fileObj = filesArray[0];
            var fileName = fileObj.name;
            var fileSize = fileObj.size;
    
            if (fileSize <= 52428800) {
                console.log("Adding (" + uploadId + "): " + fileName + " of size: " + fileSize + "bytes");
                pictureUp = true;
 
                var reader = new FileReader();
                reader.Name = fileName;
                reader.UploadId = uploadId;
                reader.Size = fileSize;
                reader.onload = function (event) {
                    $("#ImageCanvas").croppie('bind', {
                        url: event.target.result
                    });
                }

                // Use onload for this
                reader.readAsDataURL(fileObj);
            }
            else {
                Alert(lang.alert_file_size, lang.error);
            }
        }
        else {
            Alert(lang.alert_single_file, lang.error);
        }
    });


    // Add options to the Language drop-down list
    Object.keys(availableLang).forEach(function(key, ind) {
           $("#languageSelect").append($("<option>", { value: key, text: availableLang[key] }));
    });

    // Get the language if it was set previously
    var sctLanguage = getDbItem("chosenLanguage", "");

    if (sctLanguage == "" || sctLanguage == null) { 
        $("#languageSelect").val("en").prop("selected", true);
        localDB.setItem("chosenLanguage", "en");
    } else { $("#languageSelect").val(sctLanguage); }

    var NotificationsCheck = $("#Settings_Notifications");
    NotificationsCheck.prop("checked", NotificationsActive);
    NotificationsCheck.change(function(){
        if(this.checked){
            if(Notification.permission != "granted"){
                if(checkNotificationPromise()){
                    Notification.requestPermission().then(function(p){
                        console.log(p);
                        HandleNotifyPermission(p);
                    });
                }
                else {
                    Notification.requestPermission(function(p){
                        console.log(p);
                        HandleNotifyPermission(p)
                    });
                }
            }
        }
    });


    $("#save_button_conf").click(function() {

        if (profileUserID == null) { profileUserID = uID(); } // For first time only

        var confAccWssServer = $("#Configure_Account_wssServer").val();
        if (/^[A-Za-z0-9\.\-]+\.[A-Za-z0-9\-]{2,63}$/.test(confAccWssServer)) { var finconfAccWssServer = confAccWssServer; } else { 
            var finconfAccWssServer = ''; alert('The WebSocket domain that you entered is not a valid domain name!'); }

        var confAccWSPort = $("#Configure_Account_WebSocketPort").val();
        if (/^[0-9]+$/.test(confAccWSPort)) { var finConfAccWSPort = confAccWSPort; } else { 
            var finConfAccWSPort = ''; alert('The web socket port that you entered is not a valid port!'); }

        var confAccServerPath = $("#Configure_Account_ServerPath").val();
        if (/^[A-Za-z0-9\/]+$/.test(confAccServerPath)) { var finConfAccServerPath = confAccServerPath; } else { 
            var finConfAccServerPath = ''; alert('The server path that you entered is not a valid server path!');}

        var confAccProfileName = $("#Configure_Account_profileName").val();
        if (/^[A-Za-z0-9\s\-\'\[\]\(\)]+$/.test(confAccProfileName)) { var finConfAccProfileName = confAccProfileName; } else { 
            var finConfAccProfileName = ''; alert('The profile name that you entered is not a valid profile name!'); }

        var confAccSipUsername = $("#Configure_Account_SipUsername").val();
        if (/^[a-zA-Z0-9\*\#]+$/.test(confAccSipUsername)) { var finConfAccSipUsername = confAccSipUsername; } else { 
            var finConfAccSipUsername = ''; alert('The SIP username that you entered is not a valid SIP username!'); }

        var confAccSipPassword = $("#Configure_Account_SipPassword").val();
        if (confAccSipPassword.length < 400) { var finConfAccSipPassword = confAccSipPassword; } else { 
            var finConfAccSipPassword = ''; alert('The SIP password that you entered is too long!'); }

        var confAccStun = $("#Configure_Account_StunServer").val();
        if (confAccStun != null && confAccStun.trim() !== '') {
            if (/^(\d+\.\d+\.\d+\.\d+:\d+)$|^([A-Za-z0-9\.\-]+\.[A-Za-z0-9\-]{2,63}:\d+)/.test(confAccStun)) { var finConfAccStun = confAccStun; } else { 
                var finConfAccStun = ''; alert('The domain or IP or port number of the STUN server is not valid!'); }
        } else { var finConfAccStun = ''; }

        if (finconfAccWssServer != null && finconfAccWssServer.trim() !== '' &&
            finConfAccWSPort != null && finConfAccWSPort.trim() !== '' &&
            finConfAccServerPath != null && finConfAccServerPath.trim() !== '' &&
            finConfAccProfileName != null && finConfAccProfileName.trim() !== '' &&
            finConfAccSipUsername != null && finConfAccSipUsername.trim() !== '' &&
            finConfAccSipPassword != null && finConfAccSipPassword.trim() !== '') {

            // OK

        } else { alert('All fields marked with an asterisk are required!'); return; }


        // Audio & Video
        localDB.setItem("AudioOutputId", $("#playbackSrc").val());
        localDB.setItem("VideoSrcId", $("#previewVideoSrc").val());
        localDB.setItem("VideoHeight", $("input[name=Settings_Quality]:checked").val());
        localDB.setItem("FrameRate", $("input[name=Settings_FrameRate]:checked").val());
        localDB.setItem("AspectRatio", $("input[name=Settings_AspectRatio]:checked").val());
        localDB.setItem("VideoOrientation", $("input[name=Settings_Oriteation]:checked").val());
        localDB.setItem("AudioSrcId", $("#microphoneSrc").val());
        localDB.setItem("AutoGainControl", ($("#Settings_AutoGainControl").is(':checked'))? "1" : "0");
        localDB.setItem("EchoCancellation", ($("#Settings_EchoCancellation").is(':checked'))? "1" : "0");
        localDB.setItem("NoiseSuppression", ($("#Settings_NoiseSuppression").is(':checked'))? "1" : "0");
        localDB.setItem("RingOutputId", $("#ringDevice").val());

        var videoConfWinWidth = $("#Video_Conf_Window_Width").val();
        if ((/^[0-9]*$/.test(videoConfWinWidth)) && Math.abs(videoConfWinWidth) <= 100) { var finVideoConfWinWidth = Math.abs(videoConfWinWidth); } else if (videoConfWinWidth != '') { 
             $("#Video_Conf_Window_Width").val(""); var finVideoConfWinWidth = ''; alert("The percent value that you entered in the 'Percent of screen width ...' field is not valid!"); }

        localDB.setItem("VidConfWindowWidth", finVideoConfWinWidth);

        localDB.setItem("Notifications", ($("#Settings_Notifications").is(":checked"))? "1" : "0");

        localDB.setItem("ShowVconfUsernames", ($("#Settings_ShowVconfNames").is(":checked"))? "1" : "0");

        localDB.setItem("DisplayVconfUser", ($("#Settings_DisplayVconfUser").is(":checked"))? "1" : "0");


        if (/^$|^[A-Za-z0-9\:\,\+\s]+$/.test($("#voiceNumbers").val())) {} else {
                alert("The voice phone numbers are not valid. They should only contain alphanumeric characters, colons (:), commas (,), plus signs (+) and spaces!");
                return;
        }

        if (/^$|^[A-Za-z0-9\:\+\s]+$/.test($("#voiceDefaultNumber").val())) {} else {
                alert("The default voice phone number for outgoing calls is not valid. It should only contain alphanumeric characters, a colon (:), a plus sign (+) and a space!");
                return;
        }

        if (/^$|^[A-Za-z0-9\:\,\+\s]+$/.test($("#smsNumbers").val())) {} else {
                alert("The phone numbers for SMS are not valid. They should only contain alphanumeric characters, colons (:), commas (,), plus signs (+) and spaces!");
                return;
        }

        if (/^$|^[A-Za-z0-9\:\+\s]+$/.test($("#smsDefaultNumber").val())) {} else {
                alert("The default SMS phone number for outgoing SMS is not valid. It should only contain alphanumeric characters, a colon (:), a plus sign (+) and a space!");
                return;
        }

        if (/^$|^[A-Za-z0-9\%]+$/.test($("#phaxapikey").val())) {} else {
                alert("The key entered in the 'Phaxio API Key' field is not valid!");
                return;
        }

        if (/^$|^[A-Za-z0-9\%]+$/.test($("#phaxapisecret").val())) {} else {
                alert("The secret string entered in the 'Phaxio API Secret' field is not valid!");
                return;
        }

        if (/^$|^[A-Za-z0-9\%]+$/.test($("#phaxwebhooktoken").val())) {} else {
                alert("The token entered in the 'Phaxio Webhook Token' field is not valid!");
                return;
        }

        if (/^$|^[A-Za-z0-9\:\/\-\?\.\=\_\%]+$/.test($("#phaxreceiveurl").val())) {} else {
                alert("The URL entered in the 'Phaxio callback URL for incoming faxes' field is not valid!");
                return;
        }

        if (/^$|^[A-Za-z0-9\_\.\-\~\%\+\!\?\&\*\^\=\#\$\{\}\|\/]+@[A-Za-z0-9\.\-]+\.[A-Za-z0-9\-]{2,63}$/.test($("#faxnotifyemailaddr").val())) {} else {
                alert("The email address entered in the fax notification email field is not valid!");
                return;
        }

        if (/^$|^[A-Za-z0-9\:\,\+\s]+$/.test($("#faxNumbers").val())) {} else {
                alert("The phone numbers for Fax are not valid. They should only contain alphanumeric characters, colons (:), commas (,), plus signs (+) and spaces!");
                return;
        }

        if (/^$|^[A-Za-z0-9\:\+\s]+$/.test($("#faxDefaultNumber").val())) {} else {
                alert("The default Fax phone number for outgoing faxes is not valid. It should only contain alphanumeric characters, a colon (:), a plus sign (+) and a space!");
                return;
        }


        if ($("#emailIntegration").is(":checked")) {

            if (/^[A-Za-z0-9\.\-]+\.[A-Za-z0-9\-]{2,63}$/.test($("#RoundcubeDomain").val())) {} else {
                $("#emailIntegration").prop("checked", false);
                $("#RoundcubeDomain").val("");
                alert("The Roundcube domain is not valid. After entering a valid Roundcube domain, please remember to check the checkbox 'Enable Roundcube email integration' again !");
                return;
            }

            if (/^[A-Za-z0-9\_\.\-\@\~\%\+\!\?\&\*\^\=\#\$\{\}\|\/]{1,300}$/.test($("#RoundcubeUser").val())) {} else {
                $("#emailIntegration").prop("checked", false);
                $("#RoundcubeUser").val("");
                alert("The Roundcube user is not valid. After entering a valid Roundcube user, please remember to check the checkbox 'Enable Roundcube email integration' again !");
                return;
            }

            if ($("#RoundcubePass").val() == '' || $("#RoundcubePass").val().length > 300) { 
                $("#emailIntegration").prop("checked", false); 
                $("#RoundcubePass").val("");
                alert("The Roundcube password is not valid. After entering a valid Roundcube password, please remember to check the checkbox 'Enable Roundcube email integration' again !");
                return;
            }

            if ($("#rcBasicAuthUser").val().length > 300) { $("#rcBasicAuthUser").val(""); alert("The Roundcube basic authentication user is not valid."); return; }

            if ($("#rcBasicAuthPass").val().length > 300) { $("#rcBasicAuthPass").val(""); alert("The Roundcube basic authentication password is not valid."); return; }
        }

        if (pictureUp == true) {

		// The profile picture can't be saved if the style of its section is 'display: none;'
		$("#AppearanceHtml").css({ 'display' : 'block', 'visibility' : 'hidden', 'margin-top' : '-228px' });


		// Convert the profile picture to base64
		$("#ImageCanvas").croppie('result', {
		    type: 'base64',
		    size: 'viewport',
		    format: 'png',
		    quality: 1,
		    circle: false
		}).then(function(base64) {
                    profilePictureBsf = base64;
		});

                pictureUp = false;

        }

        setTimeout(function() {
            saveConfToSqldb();
        }, 600);

    });

    $("#closeImg").click(function() { $("#jg_popup_b").empty(); $("#jg_popup_l").empty(); $("#windowCtrls").empty(); closeVideoAudio(); $.jeegoopopup.close(); });
    $("#cancel_button_conf").click(function() { $("#jg_popup_b").empty(); $("#jg_popup_l").empty(); $("#windowCtrls").empty(); closeVideoAudio(); $.jeegoopopup.close(); });
    $("#jg_popup_overlay").click(function() { $("#jg_popup_b").empty(); $("#jg_popup_l").empty(); $("#windowCtrls").empty(); closeVideoAudio(); $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $("#jg_popup_b").empty(); $("#jg_popup_l").empty(); $("#windowCtrls").empty(); closeVideoAudio(); $.jeegoopopup.close(); } });

}

function checkNotificationPromise() {
    try {
        Notification.requestPermission().then();
    } catch(e) {
        return false;
    }
    return true;
}

function HandleNotifyPermission(p){

    if (p == "granted") {
       // Good
    } else {
        Alert(lang.alert_notification_permission, lang.permission, function(){
            console.log("Attempting to uncheck the checkbox...");
            $("#Settings_Notifications").prop("checked", false);
        });
    }
}

function EditBuddyWindow(buddy) {

    $.jeegoopopup.close();

    var buddyObj = null;
    var itemId = -1;
    $.each(contactsObj.DataCollection, function (i, item) {
        if (item.uID == buddy || item.cID == buddy) {
            buddyObj = item;
            itemId = i;
            return false;
        }
    });

    if (buddyObj == null) {
        Alert(lang.alert_not_found, lang.error);
        return;
    }

    var html = "<div id='EditContact'>";

    html += "<div id='windowCtrls'><img id='minimizeImg' src='images/1_minimize.svg' title='Restore' /><img id='maximizeImg' src='images/2_maximize.svg' title='Maximize' /><img id='closeImg' src='images/3_close.svg' title='Close' /></div>";

    html += "<div class='UiWindowField scroller' style='height:500px;overflow-y:auto;overflow-x:hide;'>";

    html += "<div id=ImageCanvas style=\"width:155px; height:155px\"></div>";

    html += "<label for=ebFileUploader class=customBrowseButton style=\"position: relative; display: inline-block; left: 230px; top: -47px;\">Select File</label>";
    html += "<div><input type=file id=ebFileUploader /></div>";
    html += "<div style=\"margin-top: 50px\"></div>";

    html += "<div class=UiText>"+ lang.display_name +":</div>";

    html += "<div><input id=AddSomeone_Name class=UiInputText type=text placeholder='"+ lang.eg_display_name +"' value='"+ ((buddyObj.DisplayName != '' && buddyObj.DisplayName != null && buddyObj.DisplayName != "undefined")? buddyObj.DisplayName : "") +"'></div>";

    html += "<div class=UiText>"+ lang.title_description +":</div>";

    if(buddyObj.Type == "extension"){
        html += "<div><input id='AddSomeone_Desc' class='UiInputText' type='text' placeholder='"+ lang.eg_general_manager +"' value='"+ ((buddyObj.Position != '' && buddyObj.Position != null && buddyObj.Position != "undefined")? buddyObj.Position : "") +"'></div>";
    }
    else {
        html += "<div><input id='AddSomeone_Desc' class='UiInputText' type='text' placeholder='"+ lang.eg_general_manager +"' value='"+ ((buddyObj.Description != '' && buddyObj.Description != null && buddyObj.Description != "undefined")? buddyObj.Description : "") +"'></div>";
    }

    html += "<div class='UiText'>Long Description:</div>";
    html += "<div><input id='AddSomeoneLongDesc' class='UiInputText' type='text' placeholder='Eg: Marketing specialist, contact person for Global Beavers Inc.' value='"+ ((buddyObj.LongDescription != '' && buddyObj.LongDescription != null && buddyObj.LongDescription != "undefined")? buddyObj.LongDescription : "") +"'></div>";

    html += "<div class='UiText'>Address - Street:</div>";
    html += "<div><input id='addressStreet' class='UiInputText' type='text' placeholder='Eg: Spring Street 157' value='"+ ((buddyObj.AddressStreet != '' && buddyObj.AddressStreet != null && buddyObj.AddressStreet != "undefined")? buddyObj.AddressStreet : "") +"'></div>";

    html += "<div class='UiText'>Address - Zip:</div>";
    html += "<div><input id='addressZip' class='UiInputText' type='text' placeholder='Eg: 77294' value='"+ ((buddyObj.AddressZip != '' && buddyObj.AddressZip != null && buddyObj.AddressZip != "undefined")? buddyObj.AddressZip : "") +"'></div>";

    html += "<div class='UiText'>Address - Town:</div>";
    html += "<div><input id='addressTown' class='UiInputText' type='text' placeholder='Eg: Houston' value='"+ ((buddyObj.AddressTown != '' && buddyObj.AddressTown != null && buddyObj.AddressTown != "undefined")? buddyObj.AddressTown : "") +"'></div>";

    html += "<div class='UiText'>Address - Country:</div>";
    html += "<div><input id='addressCountry' class='UiInputText' type='text' placeholder='Eg: United States of America' value='"+ ((buddyObj.AddressCountry != '' && buddyObj.AddressCountry != null && buddyObj.AddressCountry != "undefined")? buddyObj.AddressCountry : "") +"'></div>";

    html += "<div class='UiText'>Address - State/Province:</div>";
    html += "<div><input id='addressState' class='UiInputText' type='text' placeholder='Eg: Texas' value='"+ ((buddyObj.AddressState != '' && buddyObj.AddressState != null && buddyObj.AddressState != "undefined")? buddyObj.AddressState : "") +"'></div>";

    html += "<div class=UiText>"+ lang.internal_subscribe_extension +":</div>";
    html += "<div><input id=AddSomeone_Exten class=UiInputText type=text placeholder='"+ lang.eg_internal_subscribe_extension +"' value='"+ ((buddyObj.ExtensionNumber != '' && buddyObj.ExtensionNumber != null && buddyObj.ExtensionNumber != "undefined")? buddyObj.ExtensionNumber : "") +"'></div>";

    html += "<div class=UiText>"+ lang.mobile_number +":</div>";
    html += "<div><input id=AddSomeone_Mobile class=UiInputText type=text placeholder='"+ lang.eg_mobile_number +"' value='"+ ((buddyObj.MobileNumber != '' && buddyObj.MobileNumber != null && buddyObj.MobileNumber != "undefined")? buddyObj.MobileNumber : "") +"'></div>";

    html += "<div class=UiText>"+ lang.contact_number_1 +":</div>";
    html += "<div><input id=AddSomeone_Num1 class=UiInputText type=text placeholder='"+ lang.eg_contact_number_1 +"' value='"+((buddyObj.ContactNumber1 != '' && buddyObj.ContactNumber1 != null && buddyObj.ContactNumber1 != "undefined")? buddyObj.ContactNumber1 : "") +"'></div>";

    html += "<div class=UiText>"+ lang.contact_number_2 +":</div>";
    html += "<div><input id=AddSomeone_Num2 class=UiInputText type=text placeholder='"+ lang.eg_contact_number_2 +"' value='"+ ((buddyObj.ContactNumber2 != '' && buddyObj.ContactNumber2 != null && buddyObj.ContactNumber2 != "undefined")? buddyObj.ContactNumber2 : "") +"'></div>";

    html += "<div class='UiText'>Fax Number:</div>";
    html += "<div><input id='AddSomeone_Fax' class='UiInputText' type='text' placeholder='Eg: +492576543059' value='"+ ((buddyObj.Fax != '' && buddyObj.Fax != null && buddyObj.Fax != "undefined")? buddyObj.Fax : "") +"'></div>";

    html += "<div class=UiText>"+ lang.email +":</div>";
    html += "<div><input id=AddSomeone_Email class=UiInputText type=text placeholder='"+ lang.email +"' value='"+ ((buddyObj.Email != '' && buddyObj.Email != null && buddyObj.Email != "undefined")? buddyObj.Email : "") +"'></div>";

    html += "<div class='UiText'>This contact is included in the following groups:</div>";
    html += "<div><select id='AddContactGroupsOpt' class='contactGroupsSelect'>";
    html += "<option value=''>Click to choose one or more groups from the list</option>";
             for (var g = 0; g < allGroups.length; g++) {
                  html += "<option value='"+ allGroups[g] +"'>"+ allGroups[g] +"</option>"; 
             }
    html += "</select></div>";
    html += "<div id='AddContactGroups' class='addGroupsToContact'></div>";

    html += "<table><tr><td><div class='UiText'>Contact created on:</div></td><td><div><span style='display:inline-block;color:#52a390;padding-left:10px;font-size:14px;font-weight:500;font-family:Inter,sans-serif;margin-top:8px;word-break:break-word;'>"+ ((buddyObj.DateAdded != '' && buddyObj.DateAdded != null)? buddyObj.DateAdded : "") +"</span></div></td></tr>";
    html += "<tr><td><div class='UiText'>Last modified on:</div></td><td><div><span style='display:inline-block;color:#52a390;padding-left:10px;font-size:14px;font-weight:500;font-family:Inter,sans-serif;margin-top:8px;word-break:break-word;'>"+ ((buddyObj.DateModified != '' && buddyObj.DateModified != null)? buddyObj.DateModified : "") +"</span></div></td></tr></table>";


    html += "<br><br></div></div>"

    $.jeegoopopup.open({
                title: 'Edit Contact',
                html: html,
                width: '640',
                height: '500',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                contentClass: 'editContactPopup',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $("#jg_popup_b").append("<div id=bottomButtons><button id=save_button>Save</button><button id=cancel_button>Cancel</button></div>");

    // Show groups associated with the current contact
    if (buddyObj.Groups) {
        var crGroupsStr = buddyObj.Groups;
        var crGroupsArr = crGroupsStr.split("|");
        crGroupsArr.shift(); crGroupsArr.pop();
        for (var r = 0; r < crGroupsArr.length; r++) {
             $("#AddContactGroups").append("<div class='slctdGroup'><div class='slctdGroupTextEd'>"+ crGroupsArr[r] +"</div><div class='removeGroup' title='Remove this group'><i class='fa fa-times'></i></div></div>");
             // Remove group from list
             $(".removeGroup").last().click(function() {
                $(this).closest(".slctdGroup").remove();
             });
        }
    }

    // Add new contact to groups
    $("#AddContactGroupsOpt").change(function() {
       var selectedGroup = $("#AddContactGroupsOpt").val();
       if (selectedGroup != '') {
           var slctCheck = 0;
           $(".slctdGroupTextEd").each(function() {
             if ($(this).text() == selectedGroup) { slctCheck++; }
           });
           if (slctCheck == 0) { 
               $("#AddContactGroups").append("<div class='slctdGroup'><div class='slctdGroupTextEd'>"+ selectedGroup +"</div><span class='removeGroup' title='Remove this group'><i class='fa fa-times'></i></span></div>"); 
               // Remove group from list
               $(".removeGroup").last().click(function() { $(this).closest(".slctdGroup").remove(); });
           }
       }
    });


    var contactDatabaseId = '';

    $.ajax({
        type: "POST",
        url: "src/get-contact-dbid.php",
        dataType: "JSON",
        data: {
            username: userName,
            contact_name: buddyObj.DisplayName,
            s_ajax_call: validateSToken
        },
        success: function(response) {
                 if (response.successorfailure == 'success') {
                     contactDatabaseId = response.cntctDatabaseID;
                 } else { alert("The contact with this Display Name hasn't been found in the database!"); }
        },
        error: function(response) {
                 // alert("Error while attempting to retrieve contact data from the database!");
        }
    });

    // DoOnLoad
    var cropper = $("#ImageCanvas").croppie({
              viewport: { width: 150, height: 150, type: 'circle' }
    });

    // Preview Existing Image
    if (buddyObj.Type == "extension") {
       $("#ImageCanvas").croppie('bind', { url: getPicture(buddyObj.uID, "extension") }).then(function() {
          $('.cr-slider').attr({ min: 0.5, max: 3 });
          $('.cr-slider').change(function() { ebpictureUp = true; });
       });
    } else if (buddyObj.Type == "contact") {
            $("#ImageCanvas").croppie('bind', { url: getPicture(buddyObj.cID, "contact") }).then(function() {
               $('.cr-slider').attr({ min: 0.5, max: 3 });
               $('.cr-slider').change(function() { ebpictureUp = true; });
            });
    }

    // Wireup File Change
    $("#ebFileUploader").change(function() {

        var filesArray = $(this).prop('files');
        
        if (filesArray.length == 1) {
            var uploadId = Math.floor(Math.random() * 1000000000);
            var fileObj = filesArray[0];
            var fileName = fileObj.name;
            var fileSize = fileObj.size;
        
            if (fileSize <= 52428800) {
                console.log("Adding (" + uploadId + "): " + fileName + " of size: " + fileSize + "bytes");
                ebpictureUp = true;
      
                var reader = new FileReader();
                reader.Name = fileName;
                reader.UploadId = uploadId;
                reader.Size = fileSize;
                reader.onload = function (event) {
                    $("#ImageCanvas").croppie('bind', {
                       url: event.target.result
                    });
                }
                reader.readAsDataURL(fileObj);
            } else {
                Alert(lang.alert_file_size, lang.error);
            }
        } else {
                Alert(lang.alert_single_file, lang.error);
        }
    });

    $("#save_button").click(function() {

      var currentCtctName = $("#AddSomeone_Name").val();

      if (currentCtctName != null && currentCtctName.trim() !== '') {

        if (/^[A-Za-z0-9\s\-\'\.\(\)]+$/.test(currentCtctName)) {

            buddyObj.LastActivity = utcDateNow();
            buddyObj.DisplayName = currentCtctName;

	    var currentDesc = $("#AddSomeone_Desc").val();
	    if (currentDesc != null && currentDesc.trim() !== '') {
	        if (/^[A-Za-z0-9\s\-\_\.\,\'\"\(\)\!\?\@\%\&\*]+$/.test(currentDesc)) { var finCurrentDesc = currentDesc; } else { 
		    var finCurrentDesc = ''; alert('The title/description that you entered is not valid!'); }
	    } else { var finCurrentDesc = ''; }

            if(buddyObj.Type == "extension") {
                buddyObj.Position = finCurrentDesc;

            } else {
                buddyObj.Description = finCurrentDesc;
            }

	    var currentLgDesc = $("#AddSomeoneLongDesc").val();
	    if (currentLgDesc != null && currentLgDesc.trim() !== '') {
	        if (/^[A-Za-z0-9\s\-\_\.\,\'\"\(\)\!\?\@\%\&\*]+$/.test(currentLgDesc)) { var finCurrentLgDesc = currentLgDesc; } else { 
		    var finCurrentLgDesc = ''; alert('The long description that you entered is not valid!'); }
	    } else { var finCurrentLgDesc = ''; }

            buddyObj.LongDescription = finCurrentLgDesc;

	    var addressStreet = $("#addressStreet").val();
	    if (addressStreet != null && addressStreet.trim() !== '') {
	        if (/^[A-Za-z0-9\s\-\_\.\,\'\"\(\)\#\%\&]+$/.test(currentLgDesc)) { var finaddressStreet = addressStreet; } else { 
		    var finaddressStreet = ''; alert('The street address that you entered is not valid!'); }
	    } else { var finaddressStreet = ''; }

            buddyObj.AddressStreet = finaddressStreet;

	    var addressZip = $("#addressZip").val();
	    if (addressZip != null && addressZip.trim() !== '') {
	        if (/^[A-Za-z0-9\s\-\_\.\(\)\#\&]+$/.test(addressZip)) { var finaddressZip = addressZip; } else { 
		    var finaddressZip = ''; alert('The zip code that you entered is not valid!'); }
	    } else { var finaddressZip = ''; }

            buddyObj.AddressZip = finaddressZip;

	    var addressTown = $("#addressTown").val();
	    if (addressTown != null && addressTown.trim() !== '') {
	        if (/^[A-Za-z0-9\s\-\_\.\,\(\)\&]+$/.test(addressTown)) { var finaddressTown = addressTown; } else { 
		    var finaddressTown = ''; alert('The town that you entered is not valid!'); }
	    } else { var finaddressTown = ''; }

            buddyObj.AddressTown = finaddressTown;

	    var addressCountry = $("#addressCountry").val();
	    if (addressCountry != null && addressCountry.trim() !== '') {
	        if (/^[A-Za-z0-9\s\-\.\,\(\)\&]+$/.test(addressCountry)) { var finaddressCountry = addressCountry; } else { 
		    var finaddressCountry = ''; alert('The country that you entered is not valid!'); }
	    } else { var finaddressCountry = ''; }

            buddyObj.AddressCountry = finaddressCountry;

	    var addressState = $("#addressState").val();
	    if (addressState != null && addressState.trim() !== '') {
	        if (/^[A-Za-z0-9\s\-\.\,\(\)\&]+$/.test(addressState)) { var finaddressState = addressState; } else { 
		    var finaddressState = ''; alert('The state/province that you entered is not valid!'); }
	    } else { var finaddressState = ''; }

            buddyObj.AddressState = finaddressState;

	    var currentExtension = $("#AddSomeone_Exten").val();
	    if (currentExtension != null && currentExtension.trim() !== '') {
	        if (/^[a-zA-Z0-9\*\#]+$/.test(currentExtension)) { var finCurrentExtension = currentExtension; } else { 
		    var finCurrentExtension = ''; alert("The extension that you entered in the 'Extension (Internal)' field is not valid. The extension should contain only numbers, letters, asterisks and number signs and should have less than 51 characters."); }
	    } else { var finCurrentExtension = ''; }

            buddyObj.ExtensionNumber = finCurrentExtension;

	    var currentMobile = $("#AddSomeone_Mobile").val();
	    if (currentMobile != null && currentMobile.trim() !== '') {
	        if (/^[0-9\s\+\-\.\#]+$/.test(currentMobile)) { var finCurrentMobile = currentMobile; } else {
		    var finCurrentMobile = ''; alert("The phone number that you entered in the 'Mobile Number' field is not valid! The only allowed characters are: digits, spaces, plus signs and pound signs."); }
	    } else { var finCurrentMobile = ''; }

            buddyObj.MobileNumber = finCurrentMobile;

	    var currentNum1 = $("#AddSomeone_Num1").val();
	    if (currentNum1 != null && currentNum1.trim() !== '') {
	        if (/^[0-9\s\+\-\.\#]+$/.test(currentNum1)) { var finCurrentNum1 = currentNum1; } else {
		    var finCurrentNum1 = ''; alert("The phone number that you entered in the 'Contact Number 1' field is not valid! The only allowed characters are: digits, spaces, plus signs and pound signs."); }
	    } else { var finCurrentNum1 = ''; }

            buddyObj.ContactNumber1 = finCurrentNum1;

	    var currentNum2 = $("#AddSomeone_Num2").val();
	    if (currentNum2 != null && currentNum2.trim() !== '') {
	        if (/^[0-9\s\+\-\.\#]+$/.test(currentNum2)) { var finCurrentNum2 = currentNum2; } else {
		    var finCurrentNum2 = ''; alert("The phone number that you entered in the 'Contact Number 2' field is not valid! The only allowed characters are: digits, spaces, plus signs and pound signs."); }
            } else { var finCurrentNum2 = ''; }

            buddyObj.ContactNumber2 = finCurrentNum2;

	    var currentFax = $("#AddSomeone_Fax").val();
	    if (currentFax != null && currentFax.trim() !== '') {
	        if (/^[0-9\s\+\-\.\#]+$/.test(currentFax)) { var finCurrentFax = currentFax; } else {
		    var finCurrentFax = ''; alert("The phone number that you entered in the 'Fax Number' field is not valid! The only allowed characters are: digits, spaces, plus signs and pound signs."); }
            } else { var finCurrentFax = ''; }

            buddyObj.Fax = finCurrentFax;

	    var currentEmail = $("#AddSomeone_Email").val();
	    if (currentEmail != null && currentEmail.trim() !== '') {
	        if (/^[A-Za-z0-9\_\.\-\~\%\+\!\?\&\*\^\=\#\$\{\}\|\/]+@[A-Za-z0-9\.\-]+\.[A-Za-z0-9\-]{2,63}$/.test(currentEmail)) { var finCurrentEmail = currentEmail; } else {
		    var finCurrentEmail = ''; alert("The email that you entered is not a valid email address!"); }
	    } else { var finCurrentEmail = ''; }

            buddyObj.Email = finCurrentEmail;

            var allslctdGroups = "";
            $("#AddContactGroups .slctdGroupTextEd").each(function() { allslctdGroups += "|"+ $(this).text(); });
            if (allslctdGroups != "") { var slctdGroupspre = allslctdGroups +"|"; } else { var slctdGroupspre = ""; }

	   if (slctdGroupspre.trim() !== '') {
	       if (/^[a-zA-Z0-9\s\-\|]+$/.test(slctdGroupspre)) { var slctdGroups = slctdGroupspre; } else { 
		   var slctdGroups = ''; alert('The group that you entered is not valid!'); }
	   } else { var slctdGroups = ''; }

            buddyObj.Groups = slctdGroups;

            if (ebpictureUp == true) {

		// Update Image
		var constraints = { 
		        type: 'base64', 
		        size: 'viewport', 
		        format: 'png', 
		        quality: 1, 
		        circle: false 
		}

		$("#ImageCanvas").croppie('result', constraints).then(function(base64) {

		   if (buddyObj.Type == "extension") {
                       imgagesObj["img-"+ buddyObj.uID +"-extension"] = base64;

		       $("#contact-"+ buddyObj.uID +"-picture-main").css("background-image", 'url('+ getPicture(buddyObj.uID, 'extension') +')');
		       $("#contact-"+ buddyObj.uID +"-presence-main").html(buddyObj.Position);

		       // Save contact picture to database
		       var newPic = [];
                       if (imgagesObj["img-"+ buddyObj.uID +"-extension"]) { 
                           var crimgfrmimgobj = imgagesObj["img-"+ buddyObj.uID +"-extension"]; 
                       } else { 
                           var crimgfrmimgobj = base64; 
                       }
		       newPic = [buddyObj.DisplayName, crimgfrmimgobj];
		       saveContactPicToSQLDB(newPic);

		   } else if (buddyObj.Type == "contact") {

                       imgagesObj["img-"+ buddyObj.cID +"-contact"] = base64;
		       $("#contact-"+ buddyObj.cID +"-picture-main").css("background-image", 'url('+ getPicture(buddyObj.cID, 'contact') +')');
		       $("#contact-"+ buddyObj.cID +"-presence-main").html(buddyObj.Description);

		       // Save contact picture to database
		       var newPic = [];
                       if (imgagesObj["img-"+ buddyObj.uID +"-extension"]) { 
                           var crimgfrmimgobjex = imgagesObj["img-"+ buddyObj.uID +"-extension"];
                       } else { 
                           var crimgfrmimgobjex = base64; 
                       }
		       newPic = [buddyObj.DisplayName, crimgfrmimgobjex];
		       saveContactPicToSQLDB(newPic);
	           }

		   // Update
		   UpdateBuddyList();
		});
                ebpictureUp = false;
            }

            // Update: 
            contactsObj.DataCollection[itemId] = buddyObj;

            // Save To DB

            var dateModify = moment().format('YYYY-MM-DD HH:mm:ss');
            var newPerson = [];

            newPerson = [currentCtctName, finCurrentDesc, finCurrentLgDesc, finaddressStreet, finaddressZip, finaddressTown, finaddressCountry, finaddressState, finCurrentExtension, finCurrentMobile, finCurrentNum1, finCurrentNum2, finCurrentFax, finCurrentEmail, slctdGroups, dateModify, contactDatabaseId];

            updateContactToSQLDB(newPerson);

            // Update the Memory Array, so that the UpdateBuddyList can make the changes
            for(var b = 0; b < Buddies.length; b++) {
                if(buddyObj.Type == "extension"){
                    if(buddyObj.uID == Buddies[b].identity){
                        Buddies[b].lastActivity = buddyObj.LastActivity;
                        Buddies[b].CallerIDName = buddyObj.DisplayName;
                        Buddies[b].Desc = buddyObj.Position;
                    }                
                }
                else if(buddyObj.Type == "contact") {
                    if(buddyObj.cID == Buddies[b].identity){
                        Buddies[b].lastActivity = buddyObj.LastActivity;
                        Buddies[b].CallerIDName = buddyObj.DisplayName;
                        Buddies[b].Desc = buddyObj.Description;
                    }                
                }
            }

            UpdateBuddyList();

            $.jeegoopopup.close();
            $("#jg_popup_b").empty();

        } else { alert('The display name that you entered is not valid. The display name should contain only alphanumeric characters, spaces, plus signs, single quotation marks, hyphens, square and round brackets !'); }

      } else { alert("'Display Name' cannot be empty!"); }

    });

    var maxWidth = $(window).width() - 16;
    var maxHeight = $(window).height() - 110;

    if (maxWidth < 656 || maxHeight < 500) { 
        $.jeegoopopup.width(maxWidth).height(maxHeight);
        $.jeegoopopup.center();
        $("#maximizeImg").hide();
        $("#minimizeImg").hide();
    } else { 
        $.jeegoopopup.width(640).height(500);
        $.jeegoopopup.center();
        $("#minimizeImg").hide();
        $("#maximizeImg").show();
    }

    $(window).resize(function() {
        maxWidth = $(window).width() - 16;
        maxHeight = $(window).height() - 110;
        $.jeegoopopup.center();
        if (maxWidth < 656 || maxHeight < 500) { 
            $.jeegoopopup.width(maxWidth).height(maxHeight);
            $.jeegoopopup.center();
            $("#maximizeImg").hide();
            $("#minimizeImg").hide();
        } else { 
            $.jeegoopopup.width(640).height(500);
            $.jeegoopopup.center();
            $("#minimizeImg").hide();
            $("#maximizeImg").show();
        }
    });

    $("#minimizeImg").click(function() { $.jeegoopopup.width(640).height(500); $.jeegoopopup.center(); $("#maximizeImg").show(); $("#minimizeImg").hide(); });
    $("#maximizeImg").click(function() { $.jeegoopopup.width(maxWidth).height(maxHeight); $.jeegoopopup.center(); $("#minimizeImg").show(); $("#maximizeImg").hide(); });

    $("#closeImg").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); });
    $("#cancel_button").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); });
    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); $("#jg_popup_b").empty(); } });
}

// Document Ready
// ==============
$(document).ready(function() {

    // Load Langauge File
    // ==================
    $.getJSON(hostingPrefix + "lang/en.json", function(data) {
        lang = data;
        var userLang = getDbItem("chosenLanguage", "");

        if (userLang != "" && userLang != null) {
            $.getJSON(hostingPrefix +"lang/"+ userLang +".json", function(altdata) {
                lang = altdata;
            }).always(function() {
                console.log("Alternate lanaguage pack loaded: ", lang);

                // Get configuration data from the database
                getConfFromSqldb();

                // Get external users data from the database
                getExternalUserConfDB();
            });
        } else {
                console.log("Lanaguage pack already loaded: ", lang);

                // Get configuration data from the database
                getConfFromSqldb();

                // Get external users data from the database
                getExternalUserConfDB();
        }
    });

    // When leaving the video conference, inform the other extensions that our presence status has changed
    document.addEventListener('visibilitychange', function() {

       if (document.hidden) { docIsVisible = false; } else { docIsVisible = true; }

       if (docIsVisible && localDB.getItem('vConfOpenCheck') == 'closed') {
           for (var b = 0; b < Buddies.length; b++) {

                var crPrStatus = 'ready';
                if (currentStatus == 'away') {
                    crPrStatus = 'away';
                } else if (currentStatus == 'do_not_disturb') { 
                    crPrStatus = 'do_not_disturb';
                } else if (currentStatus == 'custom_status') { 
                    var retCustStatus = localDB.getItem("CustomStateSetText");
                    crPrStatus = 'custom_state'+ retCustStatus;
                }

                var crBuddyObj = Buddies[b];
                if (crBuddyObj.type == "extension") {
                    userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Current Status', { extraHeaders: [ 'X-CustomPresence: '+ crPrStatus ] });
                }
           }
       }
    });

});

// Init UI
// =======
function InitUi() {

    var phone = $("#Phone");
    phone.empty();
    phone.attr("class", "pageContainer");

    // Left Section
    var leftSection = $("<div>");
    leftSection.attr("id", "leftContent");
    leftSection.attr("style", "float:left; height: 100%; width:320px");

    var leftHTML = "<table style='height:100%; width:100%;' cellspacing=0 cellpadding=0>";
    leftHTML += "<tr><td id='logoSection' style='height: 47.5px'><div id='smallLogo'><img src='images/small-logo.svg' /></div><div id='inLogoSection'><div id='smsInLogoSection' onclick='ShowSMSWindow()' title='SMS'><i class='fa fa-commenting-o'></i></div><div id='emailInLogoSection' onclick='ShowEmailWindow()' title='Email'><i class='fa fa-envelope-o'></i></div><div id='faxInLogoSection' onclick='ShowFaxWindow()' title='FAX'><i class='fa fa-fax'></i></div><div id='aboutInLogoSection' onclick='ShowAboutWindow()' title='About'><div id='aboutImg'></div></div><div id='shrinkLeftPanel' onclick='CollapseLeftPanel()' title='"+ lang.collapse_left_panel +"'><div id='collapseLeftPanel'></div></div></div></td></tr>";
    leftHTML += "<tr><td id='profileArea' class='streamSection' style='height: 81px'>";

    // Profile User
    leftHTML += "<div class=profileContainer>";
    leftHTML += "<div id='caretDownPrfl'><i class='fa fa-caret-down' title='Click to see user menu'></i></div>";
    leftHTML += "<div class=contact id=UserProfile >";
    leftHTML += "<div id='UserProfilePic' class='buddyIcon' title='Click to see user menu'></div>";
    leftHTML += "<span id=reglink class=dotOffline></span>";
    leftHTML += "<span id=dereglink class=dotOnline style=\"display:none\"><i class=\"fa fa-wifi\" style=\"line-height: 14px; text-align: center; display: block;\"></i></span>";
    leftHTML += "<span id=WebRtcFailed class=dotFailed style=\"display:none\"><i class=\"fa fa-cross\" style=\"line-height: 14px; text-align: center; display: block;\"></i></span>";
    leftHTML += "<div class='contactNameText' style='margin-right: 0px'><i class='fa fa-phone-square'></i> <span id='UserDID'>"+ profileUser +"</span> - <span id='UserCallID'>"+ profileName +"</span></div>";
    leftHTML += "<div id='regStatus' class='presenceText'>&nbsp;</div>";
    leftHTML += "</div>";
    // Search / Add Buddies
    leftHTML += "<div id='searchBoxAndIcons'>";
    leftHTML += "<span class='searchClean'><input type='text' id='txtFindBuddy' autocomplete='none' style='width: 146px; font-size: 13px !important;' title='Find Contact'></span>";
    leftHTML += "<i class='fa fa-filter' id='filterContacts' title='Load only the contacts belonging to a specific group.'></i>";
    leftHTML += "<div id='ButtonsOnSearchLine'>";
    leftHTML += "<button id='BtnFreeDial' title='"+ lang.dial_number +"'></button>";
    leftHTML += "<button id='LaunchConferences' title='"+ lang.enter_conference +"'><i class=\"fa fa-users\"></i></button>";
    leftHTML += "<button id='BtnSettings' title='"+ lang.account_settings +"'><i class=\"fa fa-cog\"></i></button>";
    leftHTML += "</div>";
    leftHTML += "</div>";
    leftHTML += "</div>";
    leftHTML += "</td></tr>";
    // Lines & Buddies
    leftHTML += "<tr><td id='contactsArea' class='streamSection'><div id='slctdGroupsToLoad'></div><div id='myContacts' class='contactArea cleanScroller'></div></td></tr>";
    leftHTML += "</table>";

    leftSection.html(leftHTML);
  
    // Right Section
    var rightSection = $("<div>");
    rightSection.attr("id", "rightContent");
    rightSection.attr("style", "margin-left: 320px; height: 100%; overflow: auto;");

    phone.append(leftSection);
    phone.append(rightSection);

    phone.append("<div id='sendFaxFrame'></div>");

    $("#UserProfilePic").css("background-image", "url('"+ getPicture("profilePicture") +"')");
    
    $("#txtFindBuddy").attr("placeholder", lang.find_someone);
    $("#txtFindBuddy").on('keyup', function(event){
        UpdateBuddyList();
    });

    $("#BtnFreeDial").on('click', function(event){
        ShowDial(this);
    });
    $("#LaunchConferences").on('click', function(event){
       ShowLaunchConfMenu(this);
    });

    $("#BtnSettings").on('click', function(event){
       ConfigureExtensionWindow();
    });

    $("#UserProfilePic,#caretDownPrfl").on('click', function(event){
        ShowMyProfileMenu(this);
    });

    $("#filterContacts").on('click', function() { 
       if ($("#slctdGroupsToLoad").is(":visible")) {
           $("#slctdGroupsToLoad").css("display", "none");
           $("#myContacts").css({ "top":"0", "padding-bottom":"0" });
           let selGrpToLd = $("#selectGroupToLoad").val();
           if (selGrpToLd == '' || selGrpToLd == 'allcntcts') {
               $(this).css("color", "#a4b5c4");
               $(this).attr("title", "Load only the contacts belonging to a specific group.");
           } else {
               $(this).css("color", "#c10303");
               $(this).attr("title", "The contacts listed below are filtered.");
           }
       } else {
           $("#slctdGroupsToLoad").css("display", "block");
           $("#myContacts").css({ "top":"37px", "padding-bottom":"37px" });
           $(this).css("color", "#08b36e");
           $(this).attr("title", "Hide the filter drop-down list.");
       }
    });

    UpdateUI();  

    PopulateBuddyList();

    // Select Last user
    if (localDB.getItem("SelectedBuddy") != null) {
        console.log("Selecting previously selected buddy...", localDB.getItem("SelectedBuddy"));
        SelectBuddy(localDB.getItem("SelectedBuddy"));
        UpdateUI();
    }

    PreloadAudioFiles();

    CreateUserAgent();

    getContactsAndUsersDb("init");

    generateChatRSAKeys(SipUsername);
    // Remove the 'uploads' directory used to temporarily store files received during text chat
    removeTextChatUploads(SipUsername);

    // Enable scrolling for status text, when hovering over it
    $(".presenceText").mouseenter(function() { $(this).css('overflow', 'auto'); });
    $(".presenceText").mouseleave(function() { $(this).css('overflow', 'hidden'); });
    let isChrome = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime);
    if (isChrome) { $(".buddy .presenceText").css("padding", "0px 4px 0px 4px !important"); $(".buddySelected .presenceText").css("padding", "0px 4px 0px 4px !important"); }

}

function PreloadAudioFiles() {
    audioBlobs.Alert = { file : "Alert.ogg", url : hostingPrefix +"sounds/Alert.ogg" };
    audioBlobs.speaker_test = { file : "Speaker_test.ogg", url : hostingPrefix +"sounds/Speaker_test.ogg" };
    audioBlobs.defaultRingtone = { file : "nm-simple.ogg", url : hostingPrefix +"sounds/ringtones/nm-simple/nm-simple.ogg" };

    var ringtonefromDb = getDbItem("Ringtone", "");
    if (ringtonefromDb != 'null' && ringtonefromDb != '') { 
        audioBlobs.Ringtone = { file : ringtonefromDb, url : hostingPrefix +"sounds/ringtones/"+ ringtonefromDb.slice(0, -4) +"/"+ ringtonefromDb +"" };
        audioBlobs.selectedRingtone = { file : ringtonefromDb, url : hostingPrefix +"sounds/ringtones/"+ ringtonefromDb.slice(0, -4) +"/"+ ringtonefromDb +"" };
    } else {
        audioBlobs.Ringtone = { file : "nm-simple.ogg", url : hostingPrefix +"sounds/ringtones/nm-simple/nm-simple.ogg" };
        audioBlobs.selectedRingtone = { file : "nm-simple.ogg", url : hostingPrefix +"sounds/ringtones/nm-simple/nm-simple.ogg" };
    }

    audioBlobs.Busy_UK = { file : "Tone_Busy-UK.ogg", url : hostingPrefix +"sounds/Tone_Busy-UK.ogg" };
    audioBlobs.Busy_US = { file : "Tone_Busy-US.ogg", url : hostingPrefix +"sounds/Tone_Busy-US.ogg" };
    audioBlobs.CallWaiting = { file : "Tone_CallWaiting.ogg", url : hostingPrefix +"sounds/Tone_CallWaiting.ogg" };
    audioBlobs.Congestion_UK = { file : "Tone_Congestion-UK.ogg", url : hostingPrefix +"sounds/Tone_Congestion-UK.ogg" };
    audioBlobs.Congestion_US = { file : "Tone_Congestion-US.ogg", url : hostingPrefix +"sounds/Tone_Congestion-US.ogg" };
    audioBlobs.EarlyMedia_Australia = { file : "Tone_EarlyMedia-Australia.ogg", url : hostingPrefix +"sounds/Tone_EarlyMedia-Australia.ogg" };
    audioBlobs.EarlyMedia_European = { file : "Tone_EarlyMedia-European.ogg", url : hostingPrefix +"sounds/Tone_EarlyMedia-European.ogg" };
    audioBlobs.EarlyMedia_Japan = { file : "Tone_EarlyMedia-Japan.ogg", url : hostingPrefix +"sounds/Tone_EarlyMedia-Japan.ogg" };
    audioBlobs.EarlyMedia_UK = { file : "Tone_EarlyMedia-UK.ogg", url : hostingPrefix +"sounds/Tone_EarlyMedia-UK.ogg" };
    audioBlobs.EarlyMedia_US = { file : "Tone_EarlyMedia-US.ogg", url : hostingPrefix +"sounds/Tone_EarlyMedia-US.ogg" };
    
    $.each(audioBlobs, function (i, item) {
        var oReq = new XMLHttpRequest();
        oReq.open("GET", item.url, true);
        oReq.responseType = "blob";
        oReq.onload = function(oEvent) {
            var reader = new FileReader();
            reader.readAsDataURL(oReq.response);
            reader.onload = function() {
                item.blob = reader.result;
            }
        }
        oReq.send();
    });
}
// Create User Agent
// =================
function CreateUserAgent() {

    $.ajax({
        type: "POST",
        url: "src/get-sippass.php",
        dataType: "JSON",
        data: {
                username: userName,
                s_ajax_call: validateSToken
        },
        success: function (sipdatafromdb) {

            decSipPass = sipdatafromdb;

	    try {
		console.log("Creating User Agent...");

		var options = {
		    displayName: profileName,
		    uri: SipUsername + "@" + wssServer,
		    transportOptions: {
			wsServers: "wss://"+ wssServer +":"+ WebSocketPort +""+ ServerPath,
			traceSip: false,
			//  traceSip: true,
			connectionTimeout: TransportConnectionTimeout,
			maxReconnectionAttempts: TransportReconnectionAttempts,
			reconnectionTimeout: TransportReconnectionTimeout,
		    },
		    sessionDescriptionHandlerFactoryOptions:{
			peerConnectionOptions :{
			    alwaysAcquireMediaFirst: true, // Better for Firefox, but seems to have no effect on other browsers
			    iceCheckingTimeout: IceStunCheckTimeout,
			    rtcConfiguration: {}
			}
		    },
		    authorizationUser: SipUsername,
		    password: decSipPass,
		    registerExpires: RegisterExpires,
		    hackWssInTransport: WssInTransport,
		    hackIpInContact: IpInContact,
		    userAgentString: userAgentStr,
		    autostart: false,
		    register: false,
		}

		decSipPass = '';

		var currentStunServer = StunServer;

		if (currentStunServer == '' || currentStunServer == null || typeof currentStunServer == 'undefined') { 
		    var IceStunServersList = '';
		} else { var IceStunServersList = '[{"urls":"stun:'+ currentStunServer +'"}]'; }

		if (IceStunServersList != "") {
		    options.sessionDescriptionHandlerFactoryOptions.peerConnectionOptions.rtcConfiguration.iceServers = JSON.parse(IceStunServersList);
		}

		userAgent = new SIP.UA(options);

		console.log("Creating User Agent... Done");

	    } catch (e) {
		console.error("Error creating User Agent: "+ e);
		$("#regStatus").html(lang.error_user_agant);
		alert(e.message);
		return;
	    }


	    // UA Register events
	    userAgent.on('registered', function () {

		if (!isReRegister) {
		    console.log("Registered!");

		    $("#reglink").hide();
		    $("#dereglink").show();
		    if (currentStatus == 'do_not_disturb') {
		        $("#dereglink").attr("class", "dotDoNotDisturb");
		    } else if (currentStatus == 'away') {
		        $("#dereglink").attr("class", "dotAway");
		    } else if (currentStatus == 'custom_status') {
		        $("#dereglink").attr("class", "dotCustom");
		    } else if (currentStatus == 'auto_answer') {
		        $("#dereglink").attr("class", "dotAutoAnswer");
		    }

		    // Start Subscribe Loop
		    SubscribeAll();

		    // Output the status
		    var customStName = lang.state_ready;

		    if (currentStatus == "ready") {
		        customStName = lang.state_ready;
		        sendReadyStatus();
		    } else if (currentStatus == "auto_answer") {
		        customStName = lang.auto_answer;
		        sendAutoAnswerStatus();
		    } else if (currentStatus == "away") {
		        customStName = lang.away;
		        sendAwayStatus();
		    } else if (currentStatus == "do_not_disturb") {
		        customStName = lang.state_do_not_disturb;
		        sendDoNotDisturbStatus();
		    } else if (currentStatus == "custom_status") {
		        customStName = localDB.getItem("CustomStateSetText");
		        sendCustomStateStatus('custom_state' + customStName);
		    } else { currentStatus = "ready"; localDB.setItem("MyCurrentStatus", "ready"); sendReadyStatus(); }

		    var regStatusAll = lang.registered +" - "+ lang.status_label +": "+ customStName +"";

		    $("#regStatus").html(regStatusAll);

		    // Custom Web hook
		    if (typeof web_hook_on_register !== 'undefined') web_hook_on_register(userAgent);
		}
		isReRegister = true;

		// Ask all extensions for their presence status
		for (let b = 0; b < Buddies.length; b++) {
		     let crBuddyObj = Buddies[b];
		     if (crBuddyObj.type == "extension") {
		         userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Status ?', { extraHeaders: [ 'X-CustomPresence: askforcustomstate' ] });
		     }
		}

	    });

	    userAgent.on('registrationFailed', function (response, cause) {
		console.log("Registration Failed: " + cause);
		$("#regStatus").html(lang.registration_failed);

		$("#reglink").show();
		$("#dereglink").hide();

		// We set this flag here so that the re-register attepts are fully completed.
		isReRegister = false;

		// Custom Web hook
		if(typeof web_hook_on_registrationFailed !== 'undefined') web_hook_on_registrationFailed(cause);
	    });
	    userAgent.on('unregistered', function () {
		console.log("Unregistered, bye!");
		$("#regStatus").html(lang.unregistered);

		$("#reglink").show();
		$("#dereglink").hide();

		// Custom Web hook
		if(typeof web_hook_on_unregistered !== 'undefined') web_hook_on_unregistered();
	    });

	    // UA transport
	    userAgent.on('transportCreated', function (transport) {
		console.log("Transport Object Created");
		
		// Transport Events
		transport.on('connected', function () {
		    console.log("Connected to Web Socket!");
		    $("#regStatus").html(lang.connected_to_web_socket);

		    $("#WebRtcFailed").hide();

		    window.setTimeout(function () {
		        Register();
		    }, 500);

		});
		transport.on('disconnected', function () {
		    console.log("Disconnected from Web Socket!");
		    $("#regStatus").html(lang.disconnected_from_web_socket);

		    // We set this flag here so that the re-register attepts are fully completed.
		    isReRegister = false;
		});
		transport.on('transportError', function () {
		    console.log("Web Socket error!");
		    $("#regStatus").html(lang.web_socket_error);

		    $("#WebRtcFailed").show();

		    // Custom Web hook
		    if(typeof web_hook_on_transportError !== 'undefined') web_hook_on_transportError(transport, userAgent);
		});
	    });

	    // Inbound Calls
	    userAgent.on("invite", function (session) {

		ReceiveCall(session);

		changePageTitle();

		// Custom Web hook
		if(typeof web_hook_on_invite !== 'undefined') web_hook_on_invite(session);
	    });

	    // Inbound Text Message
	    userAgent.on('message', function (message) {

		var custPresence = message.request.getHeader('CustomPresenceState');
		var deliveryReceipt = message.request.getHeader('DeliveryReceipt');
		var tcPrivMsgHdr = message.request.getHeader('TConfPrivMessage');
		var ctype = message.request.getHeader('Content-Type');
		var txtConfFrom = message.request.getHeader('From');


		// If it's a presence status message
		if (custPresence) {

			var custPresFrom = message.request.from;
			var custPresFromproc = custPresFrom.toString();
			var custPresFromFirst = custPresFromproc.split("<sip:");
			var custPresFromSec = custPresFromFirst[1].split("@");
			var custPresExt = custPresFromSec[0];
		        var dotClass = 'dotOnline';
		        var friendlyState = 'Ready';

		        // If an extension asks for our presence status, send them a message
		        if (custPresence =='askforcustomstate') {

		            var crCustomPresState = '';

		            if (localDB.getItem('vConfOpenCheck') == 'open') {
		                crCustomPresState = 'on_the_phone';
		            } else if (currentStatus == 'away') {
		                crCustomPresState = 'away';
		            } else if (currentStatus == 'do_not_disturb') { 
		                crCustomPresState = 'do_not_disturb';
		            } else if (currentStatus == 'auto_answer') { 
		                crCustomPresState = 'auto_answer';
		            } else if (currentStatus == 'custom_status') {
		                var retCustStatus = localDB.getItem("CustomStateSetText");
		                crCustomPresState = 'custom_state'+ retCustStatus;
		            } else { crCustomPresState = 'ready'; }

		            if (crCustomPresState != '') {

		                userAgent.message(custPresExt + '@' + wssServer, 'Custom Presence State', { extraHeaders: [ 'X-CustomPresence: '+crCustomPresState ] });
		                console.log("Sending custom presence state: "+crCustomPresState);
		            }

		        } else {

			    var buddyObj = FindBuddyByExtNo(custPresExt);

		            if (custPresence == 'do_not_disturb') { 
		                    dotClass = 'dotDoNotDisturb'; friendlyState = 'Do not disturb';

				    // Disable the text message textarea and buttons
				    $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("placeholder", lang.send_message_restrictions);
				    $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("disabled", true);
				    $(".roundButtonsSpec").prop("disabled", true);

		            } else if (custPresence == 'away') { 
		                    dotClass = 'dotAway'; friendlyState = 'Away';

				    // Enable the text message textarea and buttons
				    $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("placeholder", lang.type_your_message_here);
				    $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("disabled", false);
				    $(".roundButtonsSpec").prop("disabled", false);

		            } else if (custPresence == 'ready') {
		                    dotClass = 'dotOnline'; friendlyState = 'Ready';

				    // Enable the text message textarea and buttons
				    $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("placeholder", lang.type_your_message_here);
				    $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("disabled", false);
				    $(".roundButtonsSpec").prop("disabled", false);

		            } else if (custPresence == 'on_the_phone') {
		                    dotClass = 'dotInUse'; friendlyState = 'On the phone';

				    // Enable the text message textarea and buttons
				    $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("placeholder", lang.type_your_message_here);
				    $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("disabled", false);
				    $(".roundButtonsSpec").prop("disabled", false);

		            } else if (custPresence == 'auto_answer') {
		                    dotClass = 'dotAutoAnswer'; friendlyState = 'Auto Answer';

				    // Enable the text message textarea and buttons
				    $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("placeholder", lang.type_your_message_here);
				    $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("disabled", false);
				    $(".roundButtonsSpec").prop("disabled", false);

		            } else if (custPresence.substring(0,12) == 'custom_state') { 
		                    dotClass = 'dotCustom'; friendlyState = custPresence.slice(12);

				    // Enable the text message textarea and buttons
				    $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("placeholder", lang.type_your_message_here);
				    $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("disabled", false);
				    $(".roundButtonsSpec").prop("disabled", false);

		            } else if (custPresence == 'unavailable') { 
		                    dotClass = 'dotOffline'; friendlyState = 'Unavailable'; 
		            }

			    console.log("Setting Presence for "+ buddyObj.identity +" to "+ friendlyState);

			    $("#contact-" + buddyObj.identity + "-devstate").prop("class", dotClass);
			    $("#contact-" + buddyObj.identity + "-devstate-main").prop("class", dotClass);
			    buddyObj.devState = dotClass;
			    buddyObj.presence = friendlyState;
		            $("#contact-" + buddyObj.identity + "-presence").html(friendlyState);
			    $("#contact-" + buddyObj.identity + "-presence-main").html(friendlyState);
		        }


		// If it's a 'delivery receipt' message
		} else if (deliveryReceipt) {

		    var delrecStrmMIdArr = deliveryReceipt.split("|");
		    var delrecExt = delrecStrmMIdArr[0];
		    var delrecMid = delrecStrmMIdArr[1];
		    var drBuddyObj = FindBuddyByExtNo(delrecExt);
		    var drBuddyIdentity = drBuddyObj.identity;

		    // Modify the local message stream, to reflect that the message has been delivered
		    if (typeof streamObj[drBuddyIdentity+"-stream"] == 'undefined') {
			streamObj[drBuddyIdentity+"-stream"] = { TotalRows: 0, DataCollection: [] };
			var currentStream = streamObj[drBuddyIdentity+"-stream"];
		    } else {
			var currentStream = streamObj[drBuddyIdentity+"-stream"];
		    }

		    var deliveryState = "<i class='fa fa-check' style='color:#0099c7;font-size:13px;' title='Delivered'></i>";

		    for (var mitm in currentStream.DataCollection) {
			 if (currentStream.DataCollection[mitm].ItemId == delrecMid || currentStream.DataCollection[mitm].ItemId == delrecMid +"-attachedfile") {
			     currentStream.DataCollection[mitm]['DeliveryStatus'] = deliveryState;
			 }
		    }

		    currentStream.TotalRows = currentStream.DataCollection.length;
		    RefreshStream(drBuddyObj);


		// If it's a text conference message sent to a specific participant (private message)
		} else if (tcPrivMsgHdr) {

		    let msTCPReceivedObj = JSON.parse(message.request.body);
		    let encryptedtexttcpm = msTCPReceivedObj.Body;

		    let crTConfPrivMsg = tcPrivMsgHdr.split("|");

		    let crTConfPMName = crTConfPrivMsg[0];
		    let crTConfPMExt = crTConfPrivMsg[1];
		    let destTConfExt = crTConfPrivMsg[2];

		    let recMessageTimepm = new Date().toLocaleTimeString();

		    let newTCPrivMessage = "<div id='recTCPrivMessage-"+ destTConfExt +"-"+ crTConfPMExt +"' class='recTCPrivMessageNotify'><i class='fa fa-commenting' title='New Private Message from User: "+ crTConfPMName +" ("+ crTConfPMExt +") - Text Conference: "+ destTConfExt +"' style='color: #28b6b6;'></i></div>";

		    function decryptAEStcpm(message, key) {
			     let code = CryptoJS.AES.decrypt(message, key);
			     let decryptedMessage = code.toString(CryptoJS.enc.Utf8);
			     return decryptedMessage;
		    }

		    let originalMsgArrPM = encryptedtexttcpm.split("|");

		    if (originalMsgArrPM[3] != '' && typeof originalMsgArrPM[3] != 'undefined' && originalMsgArrPM[3] != null) {
			var fileSentDuringChatTCPM = 1;
			var recFileNameTCPM = originalMsgArrPM[3];
		    } else {
			var fileSentDuringChatTCPM = 0;
			var recFileNameTCPM = '';
		    }

		    let currentMIdPM = originalMsgArrPM[0];
		    let totalPieceNoPM = parseInt(originalMsgArrPM[1]);
		    let currentPieceNoPM = parseInt(originalMsgArrPM[2]);

		    if (encryptedtexttcpm != '') {

			// Reassemble the original message and decrypt it
			if (currentPieceNoPM == 0) {

			    if (splitMessageTCPM.hasOwnProperty(currentMIdPM)) {

				  if (currentPieceNoPM == 0) {
				      let aesPayloadpm = originalMsgArrPM[4];
				      let newdecryptpm = new JSEncrypt();
				      newdecryptpm.setPrivateKey(currentChatPrivKey);
				      let decAESKeyConpm = newdecryptpm.decrypt(aesPayloadpm);
				      splitMessageTCPM[currentMIdPM].aeskeyandiv = decAESKeyConpm;

				  } else {
				      splitMessageTCPM[currentMIdPM][currentPieceNoPM] = originalMsgArrPM[4];
				  }

			    } else {

				  if (currentPieceNoPM == 0) {
				      let aesPayloadpm = originalMsgArrPM[4];
				      let newdecryptpm = new JSEncrypt();
				      newdecryptpm.setPrivateKey(currentChatPrivKey);
				      let decAESKeyConpm = newdecryptpm.decrypt(aesPayloadpm);
				      splitMessageTCPM[currentMIdPM] = { "aeskeyandiv": decAESKeyConpm };

				  } else {
				     splitMessageTCPM[currentMIdPM] = { [currentPieceNoPM]: originalMsgArrPM[4] };
				  }

			    }

			    return;

			} else if (currentPieceNoPM < (totalPieceNoPM - 1)) {

			       if (splitMessageTCPM.hasOwnProperty(currentMIdPM)) {
				    splitMessageTCPM[currentMIdPM][currentPieceNoPM] = originalMsgArrPM[4];
			       } else {
				    splitMessageTCPM[currentMIdPM] = { [currentPieceNoPM]: originalMsgArrPM[4] };
			       }

			       return;

			} else if (currentPieceNoPM == (totalPieceNoPM - 1)) {

			       if (splitMessageTCPM.hasOwnProperty(currentMIdPM)) {
				    splitMessageTCPM[currentMIdPM][currentPieceNoPM] = originalMsgArrPM[4];
			       } else {
				    splitMessageTCPM[currentMIdPM] = { [currentPieceNoPM]: originalMsgArrPM[4] };
			       }

			       if (Object.keys(splitMessageTCPM[currentMIdPM]).length == totalPieceNoPM) {

				   let decryptAesKeypm = splitMessageTCPM[currentMIdPM]["aeskeyandiv"];

				   delete splitMessageTCPM[currentMIdPM]["aeskeyandiv"];
				   let origMessageEncpm = '';
				   let orderedMPiecespm = Object.keys(splitMessageTCPM[currentMIdPM]).sort().reduce(function(obj, key) { 
				       obj[key] = splitMessageTCPM[currentMIdPM][key]; 
				       return obj;
				   },{});

				   Object.keys(orderedMPiecespm).forEach(function(key, index) {
				       origMessageEncpm += splitMessageTCPM[currentMIdPM][key];
				   });

				   var originalMessageTCPM = decryptAEStcpm(origMessageEncpm, decryptAesKeypm);

				   delete splitMessageTCPM[currentMIdPM];

			       } else { return; }
			}

		    }

		    let origMessageFormattedpm = ReformatMessage(originalMessageTCPM);

		    if (typeof messageWindowStateTCPM[destTConfExt +"-"+ crTConfPMExt] == 'undefined' || messageWindowStateTCPM[destTConfExt +"-"+ crTConfPMExt] == 'closed' || messageWindowStateTCPM[destTConfExt +"-"+ crTConfPMExt] == 'minimized') {

		        // If the private message window hasn't been opened yet, open it, minimize it, show the 'new message' icon and append the new message
		        if ((typeof messageWindowStateTCPM[destTConfExt +"-"+ crTConfPMExt] == 'undefined' || messageWindowStateTCPM[destTConfExt +"-"+ crTConfPMExt] == 'closed') && typeof messageWindowStateTC[destTConfExt] != 'undefined' && messageWindowStateTC[destTConfExt] != 'closed') { 

		            openTCPrivateMsgWindow(crTConfPMName, crTConfPMExt, destTConfExt, textExtTagObj[destTConfExt]);
		            $("#minimizeImgTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").click();
		            if ($("#recTCPrivMessage-"+ destTConfExt +"-"+ crTConfPMExt +"").length == 0) { $("#Phone").append(newTCPrivMessage); }

		            // Append the received message
		            if (originalMessageTCPM != '') {
		                $("#receiveTextMessageTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").append("<div class='textMessageReceivedTCPM textMessageTConfPM'><span class='recMessageNm'>"+ crTConfPMName +" ("+ crTConfPMExt +"):</span><div class='srMsgTConfcont'>"+ origMessageFormattedpm +"</div><div class='messageDate'>"+ recMessageTimepm +"</div></div>");
		            }

		            // If a file is received, add the received file section
		            if (fileSentDuringChatTCPM == 1 && recFileNameTCPM != '') {

		                fileSentDuringChatTCPM = 0;
		                $("#receiveTextMessageTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").append("<div class='textMessageReceivedTCPM textMessageTConfPM'><span class='recMessageNm'>"+ crTConfPMName +" ("+ crTConfPMExt +"):</span><div class='srMsgTConfcont'> Download file: <a href='src/download-rec-chat-file.php?s_ajax_call="+ validateSToken +"&recSipUser="+ SipUsername +"&recFlNm="+ recFileNameTCPM +"' target='_blank'>"+ recFileNameTCPM +"</a></div><div class='messageDate'>"+ recMessageTimepm +"</div></div>");
		            }

		        } else if (messageWindowStateTCPM[destTConfExt +"-"+ crTConfPMExt] == 'minimized' && typeof messageWindowStateTC[destTConfExt] != 'undefined' && messageWindowStateTC[destTConfExt] != 'closed') {

		                if ($("#recTCPrivMessage-"+ destTConfExt +"-"+ crTConfPMExt +"").length == 0) { $("#Phone").append(newTCPrivMessage); }

				// Append the received message
				if (originalMessageTCPM != '') {
				    $("#receiveTextMessageTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").append("<div class='textMessageReceivedTCPM textMessageTConfPM'><span class='recMessageNm'>"+ crTConfPMName +" ("+ crTConfPMExt +"):</span><div class='srMsgTConfcont'>"+ origMessageFormattedpm +"</div><div class='messageDate'>"+ recMessageTimepm +"</div></div>");
				}

				// If a file is received, add the received file section
				if (fileSentDuringChatTCPM == 1 && recFileNameTCPM != '') {

				    fileSentDuringChatTCPM = 0;
				    $("#receiveTextMessageTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").append("<div class='textMessageReceivedTCPM textMessageTConfPM'><span class='recMessageNm'>"+ crTConfPMName +" ("+ crTConfPMExt +"):</span><div class='srMsgTConfcont'> Download file: <a href='src/download-rec-chat-file.php?s_ajax_call="+ validateSToken +"&recSipUser="+ SipUsername +"&recFlNm="+ recFileNameTCPM +"' target='_blank'>"+ recFileNameTCPM +"</a></div><div class='messageDate'>"+ recMessageTimepm +"</div></div>");
				}
		        } 

		    } else {

		        // Append the received message
		        if (originalMessageTCPM != '') {
		            $("#receiveTextMessageTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").append("<div class='textMessageReceivedTCPM textMessageTConfPM'><span class='recMessageNm'>"+ crTConfPMName +" ("+ crTConfPMExt +"):</span><div class='srMsgTConfcont'>"+ origMessageFormattedpm +"</div><div class='messageDate'>"+ recMessageTimepm +"</div></div>");
		        }

		        // If a file is received, add the received file section
		        if (fileSentDuringChatTCPM == 1 && recFileNameTCPM != '') {

		            fileSentDuringChatTCPM = 0;
		            $("#receiveTextMessageTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").append("<div class='textMessageReceivedTCPM textMessageTConfPM'><span class='recMessageNm'>"+ crTConfPMName +" ("+ crTConfPMExt +"):</span><div class='srMsgTConfcont'> Download file: <a href='src/download-rec-chat-file.php?s_ajax_call="+ validateSToken +"&recSipUser="+ SipUsername +"&recFlNm="+ recFileNameTCPM +"' target='_blank'>"+ recFileNameTCPM +"</a></div><div class='messageDate'>"+ recMessageTimepm +"</div></div>");
		        }

		    }

		    $("#receiveTextMessageTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").scrollTop($("#receiveTextMessageTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"")[0].scrollHeight);

		    $("#recTCPrivMessage-"+ destTConfExt +"-"+ crTConfPMExt +"").click(function() {

		       $(this).remove();
		       messageWindowStateTCPM[destTConfExt +"-"+ crTConfPMExt] = 'restored';

		       $("#privateMessageImg-"+ destTConfExt +"-"+ crTConfPMExt +"").hide();
		       $("#pmTopBar-"+ destTConfExt +"-"+ crTConfPMExt +"").show();
		       $("#receiveTextMessageTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").show();
		       $("#emojiMenuTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").show();
		       $("#sendTMessageTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").show();

		       let maxHeightPm = parseInt(0.88 * $(window).height());
		       let maxWidthPm = parseInt(maxHeightPm * 1.24);

		       let pmwTop = 0;
		       let pmwLeft = 0;

		       if (textConfNmbPM > 1) { pmwTop = parseInt(($(window).height() / 2) - 305) + (textConfNmbPM * 10); } else { pmwTop = parseInt(($(window).height() / 2) - 305); }
		       if (textConfNmbPM > 1) { pmwLeft = parseInt(($(window).width() / 2) - 245) + (textConfNmbPM * 10); } else { pmwLeft = parseInt(($(window).width() / 2) - 245); }

		       if (maxWidthPm < 610 || maxHeightPm < 490) {
		           messageWindowStateTCPM[destTConfExt +"-"+ crTConfPMExt] = 'maximized';
		           $("#pmtextMessage-"+ destTConfExt +"-"+ crTConfPMExt +"").css({ "width": maxWidthPm, "height": maxHeightPm, "top": pmwTop, "left": pmwLeft });
		           $("#maximizeImgTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").hide();
		           $("#restoreImgTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").hide();
		       } else {
		           $("#pmtextMessage-"+ destTConfExt +"-"+ crTConfPMExt +"").css({ "width": 610, "height": 490, "top": pmwTop, "left": pmwLeft });
		           $("#minimizeImgTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").show();
		           $("#maximizeImgTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").show();
		           $("#restoreImgTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").hide();
		       }
		       $("#receiveTextMessageTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"").scrollTop($("#receiveTextMessageTCPM-"+ destTConfExt +"-"+ crTConfPMExt +"")[0].scrollHeight);
		    });


		// If it's a text conference message sent to all participants
		} else if (ctype === 'application/x-asterisk-confbridge-chat-main+json') {

		    let msTCReceivedObj = JSON.parse(message.request.body);

		    let encryptedtexttc = msTCReceivedObj.Body;
		    let crTConfExt = msTCReceivedObj.To;
		    let crTConfFrom = msTCReceivedObj.From;
		    let crTConfFromPt = crTConfFrom.split("|");
		    let crTConfPName = crTConfFromPt[0];
		    let crTConfFromExt = crTConfFromPt[1];

		    let recMessageTime = new Date().toLocaleTimeString();

		    let newTCPubMessage = "<div id='recTCPubMessage-"+ crTConfExt +"' class='recTCPubMessageNotify'><i class='fa fa-commenting' title='New Public Message from Text Conference: "+ crTConfExt +"' style='color: #438be8;'></i></div>";

		    function decryptAEStc(message, key) {
			     let code = CryptoJS.AES.decrypt(message, key);
			     let decryptedMessage = code.toString(CryptoJS.enc.Utf8);
			     return decryptedMessage;
		    }

		    let originalMessageArr = encryptedtexttc.split("|");

		    if (originalMessageArr[3] != '' && typeof originalMessageArr[3] != 'undefined' && originalMessageArr[3] != null) {
			var fileSentDuringChatTC = 1;
			var recFileNameTC = originalMessageArr[3];
		    } else {
			var fileSentDuringChatTC = 0;
			var recFileNameTC = '';
		    }

		    let currentMId = originalMessageArr[0];
		    let totalPieceNo = parseInt(originalMessageArr[1]);
		    let currentPieceNo = parseInt(originalMessageArr[2]);

		    if (encryptedtexttc != '') {

			// Reassemble the original message and decrypt it
			if (currentPieceNo == 0) {

			    if (splitMessageTC.hasOwnProperty(currentMId)) {

				  if (currentPieceNo == 0) {
				      let aesPayload = originalMessageArr[4];
				      let newdecrypt = new JSEncrypt();
				      newdecrypt.setPrivateKey(tconfPrivKey[crTConfExt]);
				      let decAESKeyCon = newdecrypt.decrypt(aesPayload);
				      splitMessageTC[currentMId].aeskeyandiv = decAESKeyCon;

				  } else {
				      splitMessageTC[currentMId][currentPieceNo] = originalMessageArr[4];
				  }

			    } else {

				  if (currentPieceNo == 0) {
				      let aesPayload = originalMessageArr[4];
				      let newdecrypt = new JSEncrypt();
				      newdecrypt.setPrivateKey(tconfPrivKey[crTConfExt]);
				      let decAESKeyCon = newdecrypt.decrypt(aesPayload);
				      splitMessageTC[currentMId] = { "aeskeyandiv": decAESKeyCon };

				  } else {
				     splitMessageTC[currentMId] = { [currentPieceNo]: originalMessageArr[4] };
				  }

			    }

			    return;

			} else if (currentPieceNo < (totalPieceNo - 1)) {

			       if (splitMessageTC.hasOwnProperty(currentMId)) {
				    splitMessageTC[currentMId][currentPieceNo] = originalMessageArr[4];
			       } else {
				    splitMessageTC[currentMId] = { [currentPieceNo]: originalMessageArr[4] };
			       }

			       return;

			} else if (currentPieceNo == (totalPieceNo - 1)) {

			       if (splitMessageTC.hasOwnProperty(currentMId)) {
				    splitMessageTC[currentMId][currentPieceNo] = originalMessageArr[4];
			       } else {
				    splitMessageTC[currentMId] = { [currentPieceNo]: originalMessageArr[4] };
			       }

			       if (Object.keys(splitMessageTC[currentMId]).length == totalPieceNo) {

				   let decryptAesKey = splitMessageTC[currentMId]["aeskeyandiv"];

				   delete splitMessageTC[currentMId]["aeskeyandiv"];
				   let origMessageEnc = '';
				   let orderedMPieces = Object.keys(splitMessageTC[currentMId]).sort().reduce(function(obj, key) { 
				       obj[key] = splitMessageTC[currentMId][key]; 
				       return obj;
				   },{});

				   Object.keys(orderedMPieces).forEach(function(key, index) {
				       origMessageEnc += splitMessageTC[currentMId][key];
				   });

				   var originalMessageTC = decryptAEStc(origMessageEnc, decryptAesKey);

				   delete splitMessageTC[currentMId];

			       } else { return; }
			}

		    }

		    let origMessageFormatted = ReformatMessage(originalMessageTC);

		    if (messageWindowStateTC[crTConfExt] == 'minimized') {

		        // Append the received message
		        if (originalMessageTC != '') {
		            $("#receiveTextMessage-"+ crTConfExt +"").append("<div class='textMessageReceived textMessageTConf'><span class='recMessageNm'>"+ crTConfPName +" ("+ crTConfFromExt +"):</span><div class='smTConfcont'>"+ origMessageFormatted +"</div><div class='messageDate'>"+ recMessageTime +"</div></div>");
		        }

		        // If a file is received, add the received file section
		        if (fileSentDuringChatTC == 1 && recFileNameTC != '') {

		            fileSentDuringChatTC = 0;
		            $("#receiveTextMessage-"+ crTConfExt +"").append("<div class='textMessageReceived textMessageTConf'><span class='recMessageNm'>"+ crTConfPName +" ("+ crTConfFromExt +"):</span><div class='smTConfcont'> Download file: <a href='src/download-rec-chat-file.php?s_ajax_call="+ validateSToken +"&recSipUser="+ crTConfExt +"&recFlNm="+ recFileNameTC +"' target='_blank'>"+ recFileNameTC +"</a></div><div class='messageDate'>"+ recMessageTime +"</div></div>");
		        }

		        if ($("#recTCPubMessage-"+ crTConfExt +"").length == 0) { $("#Phone").append(newTCPubMessage); }

		    } else {

		        // Append the received message
		        if (originalMessageTC != '') {
		            $("#receiveTextMessage-"+ crTConfExt +"").append("<div class='textMessageReceived textMessageTConf'><span class='recMessageNm'>"+ crTConfPName +" ("+ crTConfFromExt +"):</span><div class='smTConfcont'>"+ origMessageFormatted +"</div><div class='messageDate'>"+ recMessageTime +"</div></div>");
		        }

		        // If a file is received, add the received file section
		        if (fileSentDuringChatTC == 1 && recFileNameTC != '') {

		            fileSentDuringChatTC = 0;
		            $("#receiveTextMessage-"+ crTConfExt +"").append("<div class='textMessageReceived textMessageTConf'><span class='recMessageNm'>"+ crTConfPName +" ("+ crTConfFromExt +"):</span><div class='smTConfcont'> Download file: <a href='src/download-rec-chat-file.php?s_ajax_call="+ validateSToken +"&recSipUser="+ crTConfExt +"&recFlNm="+ recFileNameTC +"' target='_blank'>"+ recFileNameTC +"</a></div><div class='messageDate'>"+ recMessageTime +"</div></div>");
		        }

		        $("#receiveTextMessage-"+ crTConfExt +"").scrollTop($("#receiveTextMessage-"+ crTConfExt +"")[0].scrollHeight);
		    }


		    $("#recTCPubMessage-"+ crTConfExt +"").click(function() { 
		       $(this).remove();
		       messageWindowStateTC[crTConfExt] = 'restored';
		       let maxWidth = parseInt($(window).width());
		       let maxHeight = parseInt($(window).height());
		       let wTop = 0;
		       if (textConfNmb > 1) { wTop = parseInt((maxHeight / 2) - 324 + (textConfNmb * 10) ); } else { wTop = parseInt((maxHeight / 2) - 324); }
		       let wLeft = 0;
		       if (textConfNmb > 1) { wLeft = parseInt( (maxWidth / 2) - 390 + (textConfNmb * 10) ); } else { wLeft = parseInt( (maxWidth / 2) - 390 ); }

		       if (maxWidth < 800 || maxHeight < 645) { 
		              messageWindowStateTC[crTConfExt] = 'maximized'; 
		              $("#publicTextMessages-"+ crTConfExt +"").css({ "width": maxWidth, "height": maxHeight, "top": "0", "left": "0" });
		              $("#maximizeImg-"+ crTConfExt +",#publicTCMsgLabel-"+ crTConfExt +"").hide();
		              $("#minimizeImg-"+ crTConfExt +",#pubMTopBar-"+ crTConfExt +",#restoreImg-"+ crTConfExt +",#receiveTextMessage-"+ crTConfExt +",#sendTMessage-"+ crTConfExt +",#tConfUsers-"+ crTConfExt +"").show();
		              $(".tConfUsersList,.receiveTextMessage").css("height", "510px");
		              $(".chatMessageTC,.chatMessageTCPM").css("height", "131px");
		              $(".tConfUsersList").css("width", "188px");
		              $(".receiveTextMessage").css("width", widthRecMessage);
		              $(".sendMsgTConf").css("width", widthSendCalc);
		              $(".textCnfBoxBtns").css("margin", "0px");
		       } else {
		              $("#publicTextMessages-"+ crTConfExt +"").css({ "width": 800, "height": 645, "top": wTop, "left": wLeft });
		              $("#restoreImg-"+ crTConfExt +",#publicTCMsgLabel-"+ crTConfExt +"").hide();
		              $("#maximizeImg-"+ crTConfExt +",#pubMTopBar-"+ crTConfExt +",#minimizeImg-"+ crTConfExt +",#receiveTextMessage-"+ crTConfExt +",#sendTMessage-"+ crTConfExt +",#tConfUsers-"+ crTConfExt + "").show();
		              $(".tConfUsersList,.receiveTextMessage").css("height", "451px");
		              $(".chatMessageTC,.chatMessageTCPM").css("height", "112px");
		              $(".tConfUsersList").css("width", "188px");
		              $(".receiveTextMessage").css("width", "559px");
		              $(".sendMsgTConf").css({ "width" : "752px", "height" : "114px" });
		              $(".textCnfBoxBtns").css("margin", "0px");
		       }
		    });


		// If it's a message that shows who is connected to a text conference
		} else if (txtConfFrom.indexOf("sip:textconfusers@") != -1) {

		    var tConfMsgRaw = message.request.body;
		    var tConfMsgArr = tConfMsgRaw.split(/\r?\n/);
		    var finTConfVal = [];
		    for (var t = 0; t < tConfMsgArr.length; t++) {
		         var crConfMsgArr = tConfMsgArr[t];
		         if (crConfMsgArr.indexOf("PJSIP/") != -1) {
		             var tConfMsgLast = crConfMsgArr.split(" ")[0];
		             var crtConfRes = tConfMsgLast.split("-");
		             if (crtConfRes.length == 2) {
		                 var crtConfResFn = crtConfRes[0].replace("PJSIP/", "");
		                 if (crtConfResFn == SipUsername) { finTConfVal.push(profileName +" ("+ SipUsername +")" ); }
				 for (var b = 0; b < Buddies.length; b++) {
				      var buddyObj = Buddies[b];
				      if (buddyObj.type == "extension" && buddyObj.ExtNo == crtConfResFn) {
		                          finTConfVal.push(buddyObj.CallerIDName +" ("+ crtConfResFn +")" );
				      }
				 }
		             } else if (crtConfRes.length > 2) {
		                 var crtConfResPre = crtConfRes.slice(0, -1);
		                 var crtConfResSec = crtConfResPre.join("-");
		                 var crtConfResFn = crtConfResSec.replace("PJSIP/", "");
		                 if (crtConfResFn == SipUsername) { finTConfVal.push(profileName +" ("+ SipUsername +")" ); }
				 for (var b = 0; b < Buddies.length; b++) {
				      var buddyObj = Buddies[b];
				      if (buddyObj.type == "extension" && buddyObj.ExtNo == crtConfResFn) {
		                          finTConfVal.push(buddyObj.CallerIDName +" ("+ crtConfResFn +")" );
				      }
				 }
		             }
		         }
		    }

		    if (finTConfVal.length > 0) {
		        var connectedPart = "<div id='textConfParticipants'>";
		        for (var p = 0; p < finTConfVal.length; p++) {
		             connectedPart += "<div class='txtConfPartLine'>"+ finTConfVal[p] +"</div>";
		        }
		        connectedPart += "</div>";

		        $("#textSubMenu").append(connectedPart);
		        var tsmHeight = parseInt($("#textSubMenu").height()) / 2;
		        var tsmWidth = parseInt($("#textSubMenu").width()) + 27;
		        $("#textConfParticipants").css({ "top" : tsmHeight, "left" : tsmWidth });
		        if ($(window).width() <= 919) { $("#textConfParticipants").css("left", "0px"); }

		    } else {
		        var connectedPart = "<div id='textConfParticipants'>";
		        connectedPart += "<div class='txtConfPartLine'>No Participants</div>";
		        connectedPart += "</div>";

		        $("#textSubMenu").append(connectedPart);
		        var tsmHeight = parseInt($("#textSubMenu").height()) / 2;
		        var tsmWidth = parseInt($("#textSubMenu").width()) + 27;
		        $("#textConfParticipants").css({ "top" : tsmHeight, "left" : tsmWidth });
		        if ($(window).width() <= 919) { $("#textConfParticipants").css("left", "0px"); }
		    }

		    $("#textSubMenu").click(function() { if ($("#textConfParticipants").is(":visible")) { $("#textConfParticipants").remove(); } });



		// If it's a message that shows who is connected to an audio conference
		} else if (txtConfFrom.indexOf("sip:audioconfusers@") != -1) {

		    var aConfMsgRaw = message.request.body;
		    var aConfMsgArr = aConfMsgRaw.split(/\r?\n/);
		    var finAConfVal = [];
		    for (var a = 0; a < aConfMsgArr.length; a++) {
		         var crAConfMsgArr = aConfMsgArr[a];
		         if (crAConfMsgArr.indexOf("PJSIP/") != -1) {
		             var aConfMsgLast = crAConfMsgArr.split(" ")[0];
		             var crAConfRes = aConfMsgLast.split("-");
		             if (crAConfRes.length == 2) {
		                 var crAConfResFn = crAConfRes[0].replace("PJSIP/", "");
		                 if (crAConfResFn == SipUsername) { finAConfVal.push(profileName +" ("+ SipUsername +")" ); }
				 for (var b = 0; b < Buddies.length; b++) {
				      var buddyObj = Buddies[b];
				      if (buddyObj.type == "extension" && buddyObj.ExtNo == crAConfResFn) {
		                          finAConfVal.push(buddyObj.CallerIDName +" ("+ crAConfResFn +")" );
				      }
				 }
		             } else if (crAConfRes.length > 2) {
		                 var crAConfResPre = crAConfRes.slice(0, -1);
		                 var crAConfResSec = crAConfResPre.join("-");
		                 var crAConfResFn = crAConfResSec.replace("PJSIP/", "");
		                 if (crAConfResFn == SipUsername) { finAConfVal.push(profileName +" ("+ SipUsername +")" ); }
				 for (var b = 0; b < Buddies.length; b++) {
				      var buddyObj = Buddies[b];
				      if (buddyObj.type == "extension" && buddyObj.ExtNo == crAConfResFn) {
		                          finAConfVal.push(buddyObj.CallerIDName +" ("+ crAConfResFn +")" );
				      }
				 }
		             }
		         }
		    }

		    if (finAConfVal.length > 0) {
		        var connectedAPart = "<div id='audioConfParticipants'>";
		        for (var p = 0; p < finAConfVal.length; p++) {
		             connectedAPart += "<div class='audioConfPartLine'>"+ finAConfVal[p] +"</div>";
		        }
		        connectedAPart += "</div>";

		        $("#audioSubMenu").append(connectedAPart);
		        var asmHeight = parseInt($("#audioSubMenu").height()) / 2;
		        var asmWidth = parseInt($("#audioSubMenu").width()) + 27;
		        $("#audioConfParticipants").css({ "top" : asmHeight, "left" : asmWidth });
		        if ($(window).width() <= 919) { $("#audioConfParticipants").css("left", "0"); }

		    } else {
		        var connectedAPart = "<div id='audioConfParticipants'>";
		        connectedAPart += "<div class='audioConfPartLine'>No Participants</div>";
		        connectedAPart += "</div>";

		        $("#audioSubMenu").append(connectedAPart);
		        var asmHeight = parseInt($("#audioSubMenu").height()) / 2;
		        var asmWidth = parseInt($("#audioSubMenu").width()) + 27;
		        $("#audioConfParticipants").css({ "top" : asmHeight, "left" : asmWidth });
		        if ($(window).width() <= 919) { $("#audioConfParticipants").css("left", "0"); }
		    }

		    $("#audioSubMenu").click(function() { if ($("#audioConfParticipants").is(":visible")) { $("#audioConfParticipants").remove(); } });



		// If it's a message that shows who is connected to a video conference
		} else if (txtConfFrom.indexOf("sip:videoconfusers@") != -1) {

		    var vConfMsgRaw = message.request.body;

		    var vConfMsgArr = vConfMsgRaw.split(/\r?\n/);
		    var finVConfVal = [];
		    for (var v = 0; v < vConfMsgArr.length; v++) {
		         var crVConfMsgArr = vConfMsgArr[v];
		         if (crVConfMsgArr.indexOf("PJSIP/") != -1) {
		             var vConfMsgLast = crVConfMsgArr.split(" ")[0];
		             var crVConfRes = vConfMsgLast.split("-");
		             if (crVConfRes.length == 2) {
		                 var crVConfResFn = crVConfRes[0].replace("PJSIP/", "");
		                 if (crVConfResFn == SipUsername +"vconf") { finVConfVal.push(profileName +" ("+ SipUsername +"vconf)" ); }
				 for (var b = 0; b < Buddies.length; b++) {
				      var buddyObj = Buddies[b];
		                      var crVConfResFnCrop = crVConfResFn.slice(0, -5);
				      if (buddyObj.type == "extension" && buddyObj.ExtNo == crVConfResFnCrop) {
		                          finVConfVal.push(buddyObj.CallerIDName +" ("+ crVConfResFn +")" );
				      }
				 }
		             } else if (crVConfRes.length > 2) {
		                 var crVConfResPre = crVConfRes.slice(0, -1);
		                 var crVConfResSec = crVConfResPre.join("-");
		                 var crVConfResFn = crVConfResSec.replace("PJSIP/", "");
		                 if (crVConfResFn == SipUsername) { finVConfVal.push(profileName +" ("+ SipUsername +")" ); }
				 for (var b = 0; b < Buddies.length; b++) {
				      var buddyObj = Buddies[b];
		                      var crVConfResFnCrop = crVConfResFn.slice(0, -5);
				      if (buddyObj.type == "extension" && buddyObj.ExtNo == crVConfResFnCrop) {
		                          finVConfVal.push(buddyObj.CallerIDName +" ("+ crVConfResFn +")" );
				      }
				 }
		             }
		         }
		    }

		    if (finVConfVal.length > 0) {
		        var connectedVPart = "<div id='videoConfParticipants'>";
		        for (var p = 0; p < finVConfVal.length; p++) {
		             connectedVPart += "<div class='videoConfPartLine'>"+ finVConfVal[p] +"</div>";
		        }
		        connectedVPart += "</div>";

		        $("#videoSubMenu").append(connectedVPart);
		        var vsmHeight = parseInt($("#videoSubMenu").height()) / 2;
		        var vsmWidth =  parseInt($("#videoSubMenu").width()) + 27;
		        $("#videoConfParticipants").css({ "top" : vsmHeight, "left" : vsmWidth });
		        if ($(window).width() <= 919) { $("#videoConfParticipants").css("left", "0"); }

		    } else {
		        var connectedVPart = "<div id='videoConfParticipants'>";
		        connectedVPart += "<div class='videoConfPartLine'>No Participants</div>";
		        connectedVPart += "</div>";

		        $("#videoSubMenu").append(connectedVPart);
		        var vsmHeight = parseInt($("#videoSubMenu").height()) / 2;
		        var vsmWidth = parseInt($("#videoSubMenu").width()) + 27;
		        $("#videoConfParticipants").css({ "top" : vsmHeight, "left" : vsmWidth });
		        if ($(window).width() <= 919) { $("#videoConfParticipants").css("left", "0"); }
		    }

		    $("#videoSubMenu").click(function() { if ($("#videoConfParticipants").is(":visible")) { $("#videoConfParticipants").remove(); } });



		// If it's a message of type 'ConfbridgeWelcome', 'ConfbridgeTalking', 'ConfbridgeJoin' or 'ConfbridgeLeave'
		} else if (ctype == 'application/x-asterisk-confbridge-event+json') {

		    var msTCBody = JSON.parse(message.request.body);
		    var msTCType = msTCBody.type;
		    var msTCBridge = msTCBody.bridge.name;
		    var msTCChannels = msTCBody.channels;

		    var tcConfPartList = "";

		    if (msTCType == 'ConfbridgeWelcome') {
		        for (var c = 0; c < msTCChannels.length; c++) {
		             var crMsTCChannels = msTCChannels[c];
		             if (crMsTCChannels.caller.number != SipUsername) {
		                 tcConfPartList += "<span class='tcPartListItem' name='"+ crMsTCChannels.caller.number +"' tag='"+ crMsTCChannels.caller.name +"'>"+ crMsTCChannels.caller.name + " (" + crMsTCChannels.caller.number + ")</span>";
		             } else { 
		                 tcConfPartList += "<span class='tcPartListItemIt'>"+ crMsTCChannels.caller.name + " (" + crMsTCChannels.caller.number + ")</span>"; 
		             }
		        }
		    }

		    $("#tConfUsers-"+ msTCBridge +"").append(tcConfPartList);

		    if (msTCType == 'ConfbridgeTalking' || msTCType == 'ConfbridgeJoin') {

		        var msTCChannelFirst = msTCChannels[0];
		        var ct = 0;
		        $("#tConfUsers-"+ msTCBridge +" .tcPartListItem").each(function() { if ($(this).attr("name") == msTCChannelFirst.caller.number) { ct++; } });

		        if (ct == 0 && msTCChannelFirst.caller.number != SipUsername) { 
		            $("#tConfUsers-"+ msTCBridge +"").append("<span class='tcPartListItem' name='"+ msTCChannelFirst.caller.number +"' tag='"+ msTCChannelFirst.caller.name +"'>"+ msTCChannelFirst.caller.name + " ("+ msTCChannelFirst.caller.number +")</span>");
		        }

		    } else if (msTCType == 'ConfbridgeLeave') {
		        var msTCChannelFirstcl = msTCChannels[0];

		        $("#tConfUsers-"+ msTCBridge +" .tcPartListItem").each(function() { if ($(this).attr("name") == msTCChannelFirstcl.caller.number) { $(this).remove(); } });
		        $("[id^='sendTextMessageTCPM-"+ msTCBridge +"-']").val("This interlocutor has left the current text conference room ("+ msTCBridge +"). You can still send a private message to this person by clicking on their name in the contacts list on the left panel and then using the text box at the bottom of the right-hand pane. It's recommended to close this window.");
		        $("[id^='sendTextMessageTCPM-"+ msTCBridge +"-']").attr("disabled", "disabled");
		        $("[id^='sendTextChatBtnPM-"+ msTCBridge +"-']").attr("disabled", "disabled");
		        $("[id^='selectEmoticonPM-"+ msTCBridge +"-']").attr("disabled", "disabled");
		        $("[id^='sendFileTCPM-"+ msTCBridge +"-']").attr("disabled", "disabled");
		    }

		// If it's a message different from the types mentioned above
		} else if (ctype != 'application/x-asterisk-confbridge-event+json') {
		    ReceiveMessage(message);
		}

		// Custom Web hook
		if(typeof web_hook_on_message !== 'undefined') web_hook_on_message(message);

	    });

	    // Start the WebService Connection loop
	    console.log("Connecting to Web Socket...");
	    $("#regStatus").html(lang.connecting_to_web_socket);
	    userAgent.start();
	    
	    // Register Buttons
	    $("#reglink").on('click', Register);

	    // WebRTC Error Page
	    $("#WebRtcFailed").on('click', function(){
		Confirm(lang.error_connecting_web_socket, lang.web_socket_error, function(){
		    window.open("https://"+ wssServer +":"+ WebSocketPort +"/httpstatus");
		}, null);
	    });

        },
        error: function(sipdatafromdb) {
                 alert("An error occurred while attempting to retrieve data from the database!");
        }
    });
}


// Registration
// ============
function Register() {

    if (userAgent == null || userAgent.isRegistered()) return;

    console.log("Sending Registration...");
    $("#regStatus").html(lang.sending_registration);
    userAgent.register();

    if (getDbItem("Notifications", "") == 1) {
          if (Notification.permission != "granted") {
              $("#Phone").click(function() {
                 if (notifyPermCheck) {
                     try { Notification.requestPermission(); } catch(e) { return; }
                     notifyPermCheck = false;
                 }
              });
          }
    }
}
function Unregister() {
    if (userAgent == null || !userAgent.isRegistered()) return;

    console.log("Unsubscribing...");
    $("#regStatus").html(lang.unsubscribing);
    try {
        UnsubscribeAll();
    } catch (e) { }

    console.log("Disconnecting...");
    $("#regStatus").html(lang.disconnecting);
    userAgent.unregister();

    isReRegister = false;
}

// Inbound Calls
// =============
function ReceiveCall(session) {

    var callerID = session.remoteIdentity.displayName;
    var did = session.remoteIdentity.uri.user;

    if ( typeof callerID == 'undefined') { callerID = ""; }
    console.log("New Incoming Call!", callerID +" ("+ did +")");

    var CurrentCalls = countSessions(session.id);
    console.log("Current Call Count:", CurrentCalls);

    var buddyObj = FindBuddyByDid(did);

    if (callerID == '' || typeof callerID == 'undefined' || callerID == null) { callerID = '+' + did; }

    // Make new contact if it's not there
    if (buddyObj == null) {
        var buddyType = (did.length > DidLength)? "contact" : "extension";
        var focusOnBuddy = (CurrentCalls == 0);
        var plusdid = '+' + did;
        buddyObj = MakeBuddy(buddyType, true, focusOnBuddy, true, callerID, plusdid);
    } else {
        // Double check that the buddy has the same caller ID as the incoming call
        // With Buddies that are contacts, eg +441234567890 <+441234567890> leave as is
        if(buddyObj.type == "extension" && buddyObj.CallerIDName != callerID){
            UpdateBuddyCalerID(buddyObj, callerID);
        }
        else if(buddyObj.type == "contact" && callerID != did && buddyObj.CallerIDName != callerID){
            UpdateBuddyCalerID(buddyObj, callerID);
        }
    }
    var buddy = buddyObj.identity;

    // Time Stamp
    window.clearInterval(session.data.callTimer);
    var startTime = moment.utc();
    session.data.callstart = startTime.format("YYYY-MM-DD HH:mm:ss UTC");
    $("#contact-" + buddy + "-timer").show();
    session.data.callTimer = window.setInterval(function(){
        var now = moment.utc();
        var duration = moment.duration(now.diff(startTime)); 
        $("#contact-" + buddy + "-timer").html(formatShortDuration(duration.asSeconds()));
    }, 1000);
    session.data.buddyId = buddy;
    session.data.calldirection = "inbound";
    session.data.terminateby = "them";
    session.data.withvideo = false;
    var videoInvite = false;
    if (session.request.body) {
        // Asterisk 13 PJ_SIP always sends m=video if endpoint has video codec,
        // even if origional invite does not specify video.
        if (session.request.body.indexOf("m=video") > -1) {
           videoInvite = true;
           if (buddyObj.type == "contact"){
                videoInvite = false;
           }
        }
    }

    // Inbound You or They Rejected
    session.on('rejected', function (response, cause) {
        console.log("Call rejected: " + cause);

        session.data.reasonCode = response.status_code
        session.data.reasonText = cause
    
        AddCallMessage(buddy, session, response.status_code, cause);

        // Custom Web hook
        if(typeof web_hook_on_terminate !== 'undefined') web_hook_on_terminate(session);
    });
    // They canceled (Gets called regardless)
    session.on('terminated', function(response, cause) {

        // Stop the ringtone
        if(session.data.rinngerObj){
            session.data.rinngerObj.pause();
            session.data.rinngerObj.removeAttribute('src');
            session.data.rinngerObj.load();
            session.data.rinngerObj = null;
        }

        $.jeegoopopup.close();
        console.log("Call terminated");

        window.clearInterval(session.data.callTimer);

        $("#contact-" + buddy + "-timer").html("");
        $("#contact-" + buddy + "-timer").hide();
        $("#contact-" + buddy + "-msg").html("");
        $("#contact-" + buddy + "-msg").hide();
        $("#contact-" + buddy + "-AnswerCall").hide();

        RefreshStream(buddyObj);
        updateScroll(buddyObj.identity);
        UpdateBuddyList();
    });

    // Start Handle Call
    if (currentStatus == 'do_not_disturb') {
        console.log("'Do not disturb' enabled, rejecting call.");
        RejectCall(buddyObj.identity);

        userAgent.message(buddyObj.ExtNo + '@' + wssServer, 'Do not disturb', { extraHeaders: [ 'X-CustomPresence: do_not_disturb' ] });

        return;
    }

    if (currentStatus == 'auto_answer') {
        if (CurrentCalls == 0) { // There are no other calls, so you can answer
            console.log("Auto Answer Call...");
            var buddyId = buddyObj.identity;
            window.setTimeout(function(){
                // If the call is with video, assume the auto answer is also
                // In order for this to work nicely, the recipient must be "ready" to accept video calls
                // In order to ensure video call compatibility (i.e. the recipient must have their web cam in, and working)
                // The NULL video should be configured
                // https://github.com/InnovateAsterisk/Browser-Phone/issues/26
                if(videoInvite) {
                    AnswerVideoCall(buddyId)
                }
                else {
                    AnswerAudioCall(buddyId);
                }
            }, 1000);

            // Select Buddy
            SelectBuddy(buddyObj.identity);
            return;
        }
        else {
            console.warn("Could not auto answer call, already on a call.");
        }
    }
    
    // Show the Answer Thingy
    $("#contact-" + buddyObj.identity + "-msg").html(lang.incoming_call_from +" " + callerID +" ("+ did +")");
    $("#contact-" + buddyObj.identity + "-msg").show();
    if(videoInvite){
        $("#contact-"+ buddyObj.identity +"-answer-video").show();
    }
    else {
        $("#contact-"+ buddyObj.identity +"-answer-video").hide();
    }
    $("#contact-" + buddyObj.identity + "-AnswerCall").show();
    updateScroll(buddyObj.identity);

    // Play Ring Tone if not on the phone
    if (CurrentCalls >= 1) {

        // Play Alert
        console.log("Audio:", audioBlobs.CallWaiting.url);
        var rinnger = new Audio(audioBlobs.CallWaiting.blob);
        rinnger.preload = "auto";
        rinnger.loop = false;
        rinnger.oncanplaythrough = function(e) {
            if (typeof rinnger.sinkId !== 'undefined' && getRingerOutputID() != "default") {
                rinnger.setSinkId(getRingerOutputID()).then(function() {
                    console.log("Set sinkId to:", getRingerOutputID());
                }).catch(function(e){
                    console.warn("Failed to apply setSinkId.", e);
                });
            }
            // If there has been no interaction with the page at all... this page will not work
            rinnger.play().then(function(){
               // Audio Is Playing
            }).catch(function(e){
                console.warn("Unable to play audio file.", e);
            }); 
        }
        session.data.rinngerObj = rinnger;
    } else {
        // Play Ringtone
        console.log("Audio:", audioBlobs.Ringtone.url);
        var rinnger = new Audio(audioBlobs.Ringtone.blob);
        rinnger.preload = "auto";
        rinnger.loop = true;
        rinnger.oncanplaythrough = function(e) {
            if (typeof rinnger.sinkId !== 'undefined' && getRingerOutputID() != "default") {
                rinnger.setSinkId(getRingerOutputID()).then(function() {
                    console.log("Set sinkId to:", getRingerOutputID());
                }).catch(function(e){
                    console.warn("Failed to apply setSinkId.", e);
                });
            }
            // If there has been no interaction with the page at all... this page will not work
            rinnger.play().then(function(){
               // Audio Is Playing
            }).catch(function(e){
                console.warn("Unable to play audio file.", e);
            });
        }
        session.data.rinngerObj = rinnger;
    }

//    $.jeegoopopup.close();
    $("#jg_popup_b").empty(); $("#jg_popup_l").empty(); $("#windowCtrls").empty(); closeVideoAudio(); $.jeegoopopup.close();

    // Show Call Answer Window
    var callerIDmod = callerID;

    if (callerID == '' || typeof callerID == 'undefined' || callerID == null) { callerIDmod = '+' + did; }

    var callAnswerHtml = '<div id="windowCtrls"><img id="closeImg" src="images/3_close.svg" title="Close" /></div>';
    callAnswerHtml += "<div class=\"UiWindowField scroller\" style=\"text-align:center\">";
    callAnswerHtml += "<div style=\"font-size: 18px; margin-top:1px\">"+ callerIDmod + "<div>";
    if(callerID != did) {
            callAnswerHtml += "<div style=\"font-size: 18px; margin-top:1px\">("+ did + ")<div>";
    }
    callAnswerHtml += "<div class=callAnswerBuddyIcon style=\"background-image: url("+ getPicture(buddyObj.identity) +"); margin-top:6px\"></div>";
    callAnswerHtml += "<div style=\"margin-top:14px\"><button onclick=\"AnswerAudioCall('"+ buddyObj.identity +"')\" class='answerButton'><i class=\"fa fa-phone\"></i>&nbsp;&nbsp;"+ lang.answer_call +"</button></div>";
    if(videoInvite) {
            callAnswerHtml += "<div style=\"margin-top:11px\"><button onclick=\"AnswerVideoCall('"+ buddyObj.identity +"')\" class='answerButtonVid'><i class=\"fa fa-video-camera\"></i>&nbsp;&nbsp;"+ lang.answer_call_with_video +"</button></div>";
    }
    callAnswerHtml += "<div style=\"margin-top:11px\"><button onclick=\"RejectCall('"+ buddyObj.identity +"')\" class=hangupButton><i class=\"fa fa-phone\"></i>&nbsp;&nbsp;"+ lang.reject_call +"</button></div>";
    callAnswerHtml += "</div>";

    $.jeegoopopup.open({
                title: lang.incoming_call,
                html: callAnswerHtml,
                width: '290',
                height: '320',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $(window).resize(function() {
       $.jeegoopopup.width('290').height('320');
       $.jeegoopopup.center();
    });

    $("#closeImg").click(function() { $.jeegoopopup.close(); });
    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); } });

    // Add a notification badge
    IncreaseMissedBadge(buddyObj.identity);

    // Show notification
    // =================
    if (getDbItem("Notifications", "") == 1) {

            function showNotification() {
                var noticeOptions = { body: lang.incoming_call_from +" " + callerID +" \""+ did +"\"", icon: getPicture(buddyObj.identity) }
                var incomingCallNotify = new Notification(lang.roundpin_incoming_call, noticeOptions);
                incomingCallNotify.onclick = function() { incomingCallNotify.close(); return; }

                if (docIsVisible) { incomingCallNotify.close(); return; } else { setTimeout(showNotification, 8000); }
            }

            showNotification();
    }
}

function AnswerAudioCall(buddy) {

    $.jeegoopopup.close();

    var buddyObj = FindBuddyByIdentity(buddy);
    if (buddyObj == null) {
        console.warn("Audio Answer failed, null buddy");
        $("#contact-" + buddy + "-msg").html(lang.call_failed);
        $("#contact-" + buddy + "-AnswerCall").hide();
        return;
    }

    var session = getSession(buddy);
    if (session == null) {
        console.warn("Audio Answer failed, null session");
        $("#contact-" + buddy + "-msg").html(lang.call_failed);
        $("#contact-" + buddy + "-AnswerCall").hide();
        return;
    }
    
    // Stop the ringtone
    if (session.data.rinngerObj) {
        session.data.rinngerObj.pause();
        session.data.rinngerObj.removeAttribute('src');
        session.data.rinngerObj.load();
        session.data.rinngerObj = null;
    }

    // Check vitals
    if (HasAudioDevice == false) {
        Alert(lang.alert_no_microphone);
        $("#contact-" + buddy + "-msg").html(lang.call_failed);
        $("#contact-" + buddy + "-AnswerCall").hide();
        return;
    }
    $("#contact-" + buddy + "-timer").html("");
    $("#contact-" + buddy + "-timer").hide();
    $("#contact-" + buddy + "-msg").html("");
    $("#contact-" + buddy + "-msg").hide();
    $("#contact-" + buddy + "-AnswerCall").hide();

    // Create a new Line and move the session over to the line
    var callerID = session.remoteIdentity.displayName;
    var did = session.remoteIdentity.uri.user;
    newLineNumber = newLineNumber + 1;
    lineObj = new Line(newLineNumber, callerID, did, buddyObj);
    lineObj.SipSession = session;
    lineObj.SipSession.data.line = lineObj.LineNumber;
    lineObj.SipSession.data.buddyId = lineObj.BuddyObj.identity;
    Lines.push(lineObj);
    AddLineHtml(lineObj);
    SelectLine(newLineNumber);
    UpdateBuddyList();

    var supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
    var spdOptions = {
        sessionDescriptionHandlerOptions: {
            constraints: {
                audio: { deviceId : "default" },
                video: false
            }
        }
    }

    // Configure Audio
    var currentAudioDevice = getAudioSrcID();
    if(currentAudioDevice != "default"){
        var confirmedAudioDevice = false;
        for (var i = 0; i < AudioinputDevices.length; ++i) {
            if(currentAudioDevice == AudioinputDevices[i].deviceId) {
                confirmedAudioDevice = true;
                break;
            }
        }

        if(confirmedAudioDevice) {
            spdOptions.sessionDescriptionHandlerOptions.constraints.audio.deviceId = { exact: currentAudioDevice }
        } else {
            console.warn("The audio device you used before is no longer available, default settings applied.");
            localDB.setItem("AudioSrcId", "default");
        }
    }

    // Add additional Constraints
    if (supportedConstraints.autoGainControl) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.autoGainControl = AutoGainControl;
    }
    if (supportedConstraints.echoCancellation) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.echoCancellation = EchoCancellation;
    }
    if (supportedConstraints.noiseSuppression) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.noiseSuppression = NoiseSuppression;
    }

    // Send Answer
    lineObj.SipSession.accept(spdOptions);
    lineObj.SipSession.data.withvideo = false;
    lineObj.SipSession.data.VideoSourceDevice = null;
    lineObj.SipSession.data.AudioSourceDevice = getAudioSrcID();
    lineObj.SipSession.data.AudioOutputDevice = getAudioOutputID();

    // Wire up UI
    wireupAudioSession(lineObj);
    $("#contact-" + buddy + "-msg").html(lang.call_in_progress);

    // Clear Answer Buttons
    $("#contact-" + buddy + "-AnswerCall").hide();

}

function AnswerVideoCall(buddy) {

    $.jeegoopopup.close();

    var buddyObj = FindBuddyByIdentity(buddy);
    if(buddyObj == null) {
        console.warn("Audio Answer failed, null buddy");
        $("#contact-" + buddy + "-msg").html(lang.call_failed);
        $("#contact-" + buddy + "-AnswerCall").hide();
        return;
    }

    var session = getSession(buddy);
    if (session == null) {
        console.warn("Video Answer failed, null session");
        $("#contact-" + buddy + "-msg").html(lang.call_failed);
        $("#contact-" + buddy + "-AnswerCall").hide();
        return;
    }

    // Stop the ringtone
    if(session.data.rinngerObj){
        session.data.rinngerObj.pause();
        session.data.rinngerObj.removeAttribute('src');
        session.data.rinngerObj.load();
        session.data.rinngerObj = null;
    }

    // Check vitals
    if(HasAudioDevice == false){
        Alert(lang.alert_no_microphone);
        $("#contact-" + buddy + "-msg").html(lang.call_failed);
        $("#contact-" + buddy + "-AnswerCall").hide();
        return;
    }
    if(HasVideoDevice == false){
        console.warn("No video devices (webcam) found, switching to audio call.");
        AnswerAudioCall(buddy);
        return;
    }
    $("#contact-" + buddy + "-timer").html("");
    $("#contact-" + buddy + "-timer").hide();
    $("#contact-" + buddy + "-msg").html("");
    $("#contact-" + buddy + "-msg").hide();
    $("#contact-" + buddy + "-AnswerCall").hide();

    // Create a new Line and move the session over to the line
    var callerID = session.remoteIdentity.displayName;
    var did = session.remoteIdentity.uri.user;
    newLineNumber = newLineNumber + 1;
    lineObj = new Line(newLineNumber, callerID, did, buddyObj);
    lineObj.SipSession = session;
    lineObj.SipSession.data.line = lineObj.LineNumber;
    lineObj.SipSession.data.buddyId = lineObj.BuddyObj.identity;
    Lines.push(lineObj);
    AddLineHtml(lineObj);
    SelectLine(newLineNumber);
    UpdateBuddyList();

    var supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
    var spdOptions = {
        sessionDescriptionHandlerOptions: {
            constraints: {
                audio: { deviceId : "default" },
                video: { deviceId : "default" }
            }
        }
    }

    // Configure Audio
    var currentAudioDevice = getAudioSrcID();
    if(currentAudioDevice != "default"){
        var confirmedAudioDevice = false;
        for (var i = 0; i < AudioinputDevices.length; ++i) {
            if(currentAudioDevice == AudioinputDevices[i].deviceId) {
                confirmedAudioDevice = true;
                break;
            }
        }
        if(confirmedAudioDevice) {
            spdOptions.sessionDescriptionHandlerOptions.constraints.audio.deviceId = { exact: currentAudioDevice }
        }
        else {
            console.warn("The audio device you used before is no longer available, default settings applied.");
            localDB.setItem("AudioSrcId", "default");
        }
    }
    // Add additional Constraints
    if(supportedConstraints.autoGainControl) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.autoGainControl = AutoGainControl;
    }
    if(supportedConstraints.echoCancellation) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.echoCancellation = EchoCancellation;
    }
    if(supportedConstraints.noiseSuppression) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.noiseSuppression = NoiseSuppression;
    }

    // Configure Video
    var currentVideoDevice = getVideoSrcID();
    if (currentVideoDevice != "default") {
        var confirmedVideoDevice = false;
        for (var i = 0; i < VideoinputDevices.length; ++i) {
            if (currentVideoDevice == VideoinputDevices[i].deviceId) {
                confirmedVideoDevice = true;
                break;
            }
        }
        if(confirmedVideoDevice){
            spdOptions.sessionDescriptionHandlerOptions.constraints.video.deviceId = { exact: currentVideoDevice }
        }
        else {
            console.warn("The video device you used before is no longer available, default settings applied.");
            localDB.setItem("VideoSrcId", "default"); // resets for later and subsequent calls
        }
    }
    // Add additional Constraints
    if(supportedConstraints.frameRate && maxFrameRate != "") {
        spdOptions.sessionDescriptionHandlerOptions.constraints.video.frameRate = maxFrameRate;
    }
    if(supportedConstraints.height && videoHeight != "") {
        spdOptions.sessionDescriptionHandlerOptions.constraints.video.height = videoHeight;
    }
    if(supportedConstraints.aspectRatio && videoAspectRatio != "") {
        spdOptions.sessionDescriptionHandlerOptions.constraints.video.aspectRatio = videoAspectRatio;
    }

    // Send Answer
    lineObj.SipSession.data.withvideo = true;
    lineObj.SipSession.data.VideoSourceDevice = getVideoSrcID();
    lineObj.SipSession.data.AudioSourceDevice = getAudioSrcID();
    lineObj.SipSession.data.AudioOutputDevice = getAudioOutputID();

    // Send Answer
    // If this fails, it must still wireup the call so we can manually close it
    $("#contact-" + buddy + "-msg").html(lang.call_in_progress);
    // Wire up UI
    wireupVideoSession(lineObj);

    try {
        lineObj.SipSession.accept(spdOptions);
        if (StartVideoFullScreen) ExpandVideoArea(lineObj.LineNumber);
        $("#contact-" + buddy + "-AnswerCall").hide();

    } catch(e) {
        console.warn("Failed to answer call", e, lineObj.SipSession);
        teardownSession(lineObj, 500, "Client Error");
    }
}

function RejectCall(buddy) {
    var session = getSession(buddy);
    if (session == null) {
        console.warn("Reject failed, null session");
        $("#contact-" + buddy + "-msg").html(lang.call_failed);
        $("#contact-" + buddy + "-AnswerCall").hide();
    }
    session.data.terminateby = "us";
    session.reject({ 
        statusCode: 486, 
        reasonPhrase: "Rejected by us"
    });
    $("#contact-" + buddy + "-msg").html(lang.call_rejected);

    var buddyObj = FindBuddyByIdentity(buddy);
    if (buddyObj.type == "extension") {

        var crntPStts = 'ready';
        if (currentStatus == 'away') {
            crntPStts = 'away';
        } else if (currentStatus == 'do_not_disturb') {
            crntPStts = 'do_not_disturb';
        } else if (currentStatus == 'custom_status') {
            var rtCustStat = localDB.getItem("CustomStateSetText");
            crntPStts = 'custom_state'+ rtCustStat;
        }

        userAgent.message(buddyObj.ExtNo + '@' + wssServer, 'Our status', { extraHeaders: [ 'X-CustomPresence: '+ crntPStts ] });
    }
}

// Session Wireup
// ==============
function wireupAudioSession(lineObj) {
    if (lineObj == null) return;

    var MessageObjId = "#line-" + lineObj.LineNumber + "-msg";
    var session = lineObj.SipSession;

    session.on('progress', function (response) {
        // Provisional 1xx
        if(response.status_code == 100){
            $(MessageObjId).html(lang.trying);
        } else if(response.status_code == 180){
            $(MessageObjId).html(lang.ringing);
            
            var soundFile = audioBlobs.EarlyMedia_European;
            if(UserLocale().indexOf("us") > -1) soundFile = audioBlobs.EarlyMedia_US;
            if(UserLocale().indexOf("gb") > -1) soundFile = audioBlobs.EarlyMedia_UK;
            if(UserLocale().indexOf("au") > -1) soundFile = audioBlobs.EarlyMedia_Australia;
            if(UserLocale().indexOf("jp") > -1) soundFile = audioBlobs.EarlyMedia_Japan;

            // Play Early Media
            console.log("Audio:", soundFile.url);
            var earlyMedia = new Audio(soundFile.blob);
            earlyMedia.preload = "auto";
            earlyMedia.loop = true;
            earlyMedia.oncanplaythrough = function(e) {
                if (typeof earlyMedia.sinkId !== 'undefined' && getAudioOutputID() != "default") {
                    earlyMedia.setSinkId(getAudioOutputID()).then(function() {
                        console.log("Set sinkId to:", getAudioOutputID());
                    }).catch(function(e){
                        console.warn("Failed to apply setSinkId.", e);
                    });
                }
                earlyMedia.play().then(function(){
                   // Audio Is Playing
                }).catch(function(e){
                    console.warn("Unable to play audio file.", e);
                }); 
            }
            session.data.earlyMedia = earlyMedia;
        } else {
            $(MessageObjId).html(response.reason_phrase + "...");
        }

        // Custom Web hook
        if(typeof web_hook_on_modify !== 'undefined') web_hook_on_modify("progress", session);
    });
    session.on('trackAdded', function () {
        var pc = session.sessionDescriptionHandler.peerConnection;

        // Gets Remote Audio Track (Local audio is setup via initial GUM)
        var remoteStream = new MediaStream();
        pc.getReceivers().forEach(function (receiver) {
            if(receiver.track && receiver.track.kind == "audio"){
               remoteStream.addTrack(receiver.track);
            } 
        });
        var remoteAudio = $("#line-" + lineObj.LineNumber + "-remoteAudio").get(0);
        remoteAudio.srcObject = remoteStream;
        remoteAudio.onloadedmetadata = function(e) {
            if (typeof remoteAudio.sinkId !== 'undefined') {
                remoteAudio.setSinkId(getAudioOutputID()).then(function(){
                    console.log("sinkId applied: "+ getAudioOutputID());
                }).catch(function(e){
                    console.warn("Error using setSinkId: ", e);
                });
            }
            remoteAudio.play();
        }

        // Custom Web hook
        if(typeof web_hook_on_modify !== 'undefined') web_hook_on_modify("trackAdded", session);
    });
    session.on('accepted', function (data) {

        if(session.data.earlyMedia){
            session.data.earlyMedia.pause();
            session.data.earlyMedia.removeAttribute('src');
            session.data.earlyMedia.load();
            session.data.earlyMedia = null;
        }

        window.clearInterval(session.data.callTimer);
        var startTime = moment.utc();
        session.data.callTimer = window.setInterval(function(){
            var now = moment.utc();
            var duration = moment.duration(now.diff(startTime)); 
            $("#line-" + lineObj.LineNumber + "-timer").html(formatShortDuration(duration.asSeconds()));
        }, 1000);

        $("#line-" + lineObj.LineNumber + "-progress").hide();
        $("#line-" + lineObj.LineNumber + "-VideoCall").hide();
        $("#line-" + lineObj.LineNumber + "-ActiveCall").show();

        // Audo Monitoring
        lineObj.LocalSoundMeter = StartLocalAudioMediaMonitoring(lineObj.LineNumber, session);
        lineObj.RemoteSoundMeter = StartRemoteAudioMediaMonitoring(lineObj.LineNumber, session);
        
        $(MessageObjId).html(lang.call_in_progress);

        updateLineScroll(lineObj.LineNumber);

        // Send the 'On the phone' status to all extensions
        sendOnThePhoneStatus();

        UpdateBuddyList();

        // Change status to 'On the phone'
        $("#contact-" + lineObj.BuddyObj.identity + "-devstate").prop("class", "dotInUse");
        $("#contact-" + lineObj.BuddyObj.identity + "-devstate-main").prop("class", "dotInUse");
        $("#contact-" + lineObj.BuddyObj.identity + "-presence").html("On the phone");
        $("#contact-" + lineObj.BuddyObj.identity + "-presence-main").html("On the phone");

        // Custom Web hook
        if(typeof web_hook_on_modify !== 'undefined') web_hook_on_modify("accepted", session);
    });
    session.on('rejected', function (response, cause) {
        // Should only apply befor answer
        $(MessageObjId).html(lang.call_rejected +": " + cause);
        console.log("Call rejected: " + cause);
        teardownSession(lineObj, response.status_code, response.reason_phrase);
    });
    session.on('failed', function (response, cause) {
        $(MessageObjId).html(lang.call_failed + ": " + cause);
        console.log("Call failed: " + cause);
        teardownSession(lineObj, 0, "Call failed");
    });
    session.on('cancel', function () {
        $(MessageObjId).html(lang.call_canceled);
        console.log("Call Canceled");
        teardownSession(lineObj, 0, "Canceled by caller");
    });
    // referRequested
    // replaced
    session.on('bye', function () {
        $(MessageObjId).html(lang.call_ended);
        console.log("Call ended, bye!");
        teardownSession(lineObj, 16, "Normal Call clearing");

        // Send the current status to all extensions
        if (currentStatus == 'ready') { sendReadyStatus();
        } else if (currentStatus == 'custom_status') { sendCustomStateStatus('custom_state' + localDB.getItem("CustomStateSetText"));
        } else if (currentStatus == 'away') { sendAwayStatus();
        } else if (currentStatus == 'do_not_disturb') { sendDoNotDisturbStatus();
        } else if (currentStatus == 'auto_answer') { sendAutoAnswerStatus();
        } else { sendReadyStatus(); }
    });
    session.on('terminated', function (message, cause) {
        $(MessageObjId).html(lang.call_ended);
        console.log("Session terminated");
        teardownSession(lineObj, 16, "Normal Call clearing");

        // Send the current status to all extensions
        if (currentStatus == 'ready') { sendReadyStatus();
        } else if (currentStatus == 'custom_status') { sendCustomStateStatus('custom_state' + localDB.getItem("CustomStateSetText"));
        } else if (currentStatus == 'away') { sendAwayStatus();
        } else if (currentStatus == 'do_not_disturb') { sendDoNotDisturbStatus();
        } else if (currentStatus == 'auto_answer') { sendAutoAnswerStatus();
        } else { sendReadyStatus(); }
    });
    session.on('reinvite', function (session) {
        console.log("Session reinvited!");
    });
    // dtmf
    session.on('directionChanged', function() {
        var direction = session.sessionDescriptionHandler.getDirection();
        console.log("Direction Change: ", direction);

        // Custom Web hook
        if(typeof web_hook_on_modify !== 'undefined') web_hook_on_modify("directionChanged", session);
    });

    $("#line-" + lineObj.LineNumber + "-btn-settings").removeAttr('disabled');
    $("#line-" + lineObj.LineNumber + "-btn-audioCall").prop('disabled','disabled');
    $("#line-" + lineObj.LineNumber + "-btn-videoCall").prop('disabled','disabled');
    $("#line-" + lineObj.LineNumber + "-btn-search").removeAttr('disabled');
    $("#line-" + lineObj.LineNumber + "-btn-remove").prop('disabled','disabled');

    $("#line-" + lineObj.LineNumber + "-progress").show();
    $("#line-" + lineObj.LineNumber + "-msg").show();

    $("#line-" + lineObj.LineNumber + "-conference").hide();

    updateLineScroll(lineObj.LineNumber);

    UpdateUI();
}

function wireupVideoSession(lineObj) {
    if (lineObj == null) return;

    var MessageObjId = "#line-" + lineObj.LineNumber + "-msg";
    var session = lineObj.SipSession;

    session.on('trackAdded', function () {
        // Gets remote tracks
        var pc = session.sessionDescriptionHandler.peerConnection;
        var remoteAudioStream = new MediaStream();
        var remoteVideoStream = new MediaStream();
        pc.getReceivers().forEach(function (receiver) {
            if(receiver.track){
                if(receiver.track.kind == "audio"){
                    remoteAudioStream.addTrack(receiver.track);
                }
                if(receiver.track.kind == "video"){
                    remoteVideoStream.addTrack(receiver.track);
                }
            }
        });

        if (remoteAudioStream.getAudioTracks().length >= 1) {
            var remoteAudio = $("#line-" + lineObj.LineNumber + "-remoteAudio").get(0);
            remoteAudio.srcObject = remoteAudioStream;
            remoteAudio.onloadedmetadata = function(e) {
               if (typeof remoteAudio.sinkId !== 'undefined') {
                   remoteAudio.setSinkId(getAudioOutputID()).then(function(){
                       console.log("sinkId applied: "+ getAudioOutputID());
                   }).catch(function(e){
                       console.warn("Error using setSinkId: ", e);
                   });
               }
               remoteAudio.play();
            }
        }

        if (remoteVideoStream.getVideoTracks().length >= 1) {
            var remoteVideo = $("#line-" + lineObj.LineNumber + "-remoteVideo").get(0);
            remoteVideo.srcObject = remoteVideoStream;
            remoteVideo.onloadedmetadata = function(e) {
                remoteVideo.play();
            }
        }

        // Note: There appears to be a bug in the peerConnection.getSenders()
        // The array returns but may or may not be fully populated by the RTCRtpSender
        // The track property appears to be null initially and then moments later populated.
        // This does not appear to be the case when originating a call, mostly when receiving a call.
        window.setTimeout(function(){
            var localVideoStream = new MediaStream();
            var pc = session.sessionDescriptionHandler.peerConnection;
            pc.getSenders().forEach(function (sender) {
                if(sender.track && sender.track.kind == "video"){
                    localVideoStream.addTrack(sender.track);
                }
            });
            var localVideo = $("#line-" + lineObj.LineNumber + "-localVideo").get(0);
            localVideo.srcObject = localVideoStream;
            localVideo.onloadedmetadata = function(e) {
                localVideo.play();
            }
        }, 1000);

        // Custom Web hook
        if(typeof web_hook_on_modify !== 'undefined') web_hook_on_modify("trackAdded", session);
    });
    session.on('progress', function (response) {
        // Provisional 1xx
        if(response.status_code == 100){
            $(MessageObjId).html(lang.trying);
        } else if(response.status_code == 180){
            $(MessageObjId).html(lang.ringing);

            var soundFile = audioBlobs.EarlyMedia_European;
            if(UserLocale().indexOf("us") > -1) soundFile = audioBlobs.EarlyMedia_US;
            if(UserLocale().indexOf("gb") > -1) soundFile = audioBlobs.EarlyMedia_UK;
            if(UserLocale().indexOf("au") > -1) soundFile = audioBlobs.EarlyMedia_Australia;
            if(UserLocale().indexOf("jp") > -1) soundFile = audioBlobs.EarlyMedia_Japan;

            // Play Early Media
            console.log("Audio:", soundFile.url);
            var earlyMedia = new Audio(soundFile.blob);
            earlyMedia.preload = "auto";
            earlyMedia.loop = true;
            earlyMedia.oncanplaythrough = function(e) {
                if (typeof earlyMedia.sinkId !== 'undefined' && getAudioOutputID() != "default") {
                    earlyMedia.setSinkId(getAudioOutputID()).then(function() {
                        console.log("Set sinkId to:", getAudioOutputID());
                    }).catch(function(e){
                        console.warn("Failed to apply setSinkId.", e);
                    });
                }
                earlyMedia.play().then(function(){
                   // Audio Is Playing
                }).catch(function(e){
                    console.warn("Unable to play audio file.", e);
                }); 
            }
            session.data.earlyMedia = earlyMedia;
        } else {
            $(MessageObjId).html(response.reason_phrase + "...");
        }

        // Custom Web hook
        if(typeof web_hook_on_modify !== 'undefined') web_hook_on_modify("progress", session);
    });
    session.on('accepted', function (data) {
        
        if(session.data.earlyMedia){
            session.data.earlyMedia.pause();
            session.data.earlyMedia.removeAttribute('src');
            session.data.earlyMedia.load();
            session.data.earlyMedia = null;
        }

        window.clearInterval(session.data.callTimer);
        $("#line-" + lineObj.LineNumber + "-timer").show();
        var startTime = moment.utc();
        session.data.callTimer = window.setInterval(function(){
            var now = moment.utc();
            var duration = moment.duration(now.diff(startTime)); 
            $("#line-" + lineObj.LineNumber + "-timer").html(formatShortDuration(duration.asSeconds()));
        }, 1000);

        $("#line-"+ lineObj.LineNumber +"-progress").hide();
        $("#line-"+ lineObj.LineNumber +"-VideoCall").show();
        $("#line-"+ lineObj.LineNumber +"-ActiveCall").show();
        $("#line-"+ lineObj.LineNumber +"-btn-Transfer").hide(); // Cannot transfer a Video Call (Yet...)
        $("#line-"+ lineObj.LineNumber +"-btn-CancelTransfer").hide();
        $("#line-"+ lineObj.LineNumber +"-Transfer").hide();

        // Default to use Camera
        $("#line-"+ lineObj.LineNumber +"-src-camera").prop("disabled", true);
        $("#line-"+ lineObj.LineNumber +"-src-canvas").prop("disabled", false);
        $("#line-"+ lineObj.LineNumber +"-src-desktop").prop("disabled", false);
        $("#line-"+ lineObj.LineNumber +"-src-video").prop("disabled", false);

        updateLineScroll(lineObj.LineNumber);

        // Start Audio Monitoring
        lineObj.LocalSoundMeter = StartLocalAudioMediaMonitoring(lineObj.LineNumber, session);
        lineObj.RemoteSoundMeter = StartRemoteAudioMediaMonitoring(lineObj.LineNumber, session);

        $(MessageObjId).html(lang.call_in_progress);

        if(StartVideoFullScreen) ExpandVideoArea(lineObj.LineNumber);

        // Change status to 'On the phone'
        $("#contact-" + lineObj.BuddyObj.identity + "-devstate").prop("class", "dotInUse");
        $("#contact-" + lineObj.BuddyObj.identity + "-devstate-main").prop("class", "dotInUse");
        $("#contact-" + lineObj.BuddyObj.identity + "-presence").html("On the phone");
        $("#contact-" + lineObj.BuddyObj.identity + "-presence-main").html("On the phone");

        // Custom Web hook
        if(typeof web_hook_on_modify !== 'undefined') web_hook_on_modify("accepted", session);
    });
    session.on('rejected', function (response, cause) {
        // Should only apply before answer
        $(MessageObjId).html(lang.call_rejected +": "+ cause);
        console.log("Call rejected: "+ cause);
        teardownSession(lineObj, response.status_code, response.reason_phrase);
    });
    session.on('failed', function (response, cause) {
        $(MessageObjId).html(lang.call_failed +": "+ cause);
        console.log("Call failed: "+ cause);
        teardownSession(lineObj, 0, "call failed");
    });
    session.on('cancel', function () {
        $(MessageObjId).html(lang.call_canceled);
        console.log("Call Canceled");
        teardownSession(lineObj, 0, "Canceled by caller");
    });
    // referRequested
    // replaced
    session.on('bye', function () {
        $(MessageObjId).html(lang.call_ended);
        console.log("Call ended, bye!");
        teardownSession(lineObj, 16, "Normal Call clearing");

        if (currentStatus == 'ready') { sendReadyStatus();
        } else if (currentStatus == 'custom_status') { sendCustomStateStatus('custom_state' + localDB.getItem("CustomStateSetText"));
        } else if (currentStatus == 'away') { sendAwayStatus();
        } else if (currentStatus == 'do_not_disturb') { sendDoNotDisturbStatus();
        } else if (currentStatus == 'auto_answer') { sendReadyStatus();
        } else { sendReadyStatus(); }
    });
    session.on('terminated', function (message, cause) {
        $(MessageObjId).html(lang.call_ended);
        console.log("Session terminated");
        teardownSession(lineObj, 16, "Normal Call clearing");

        if (currentStatus == 'ready') { sendReadyStatus();
        } else if (currentStatus == 'custom_status') { sendCustomStateStatus('custom_state' + localDB.getItem("CustomStateSetText"));
        } else if (currentStatus == 'away') { sendAwayStatus();
        } else if (currentStatus == 'do_not_disturb') { sendDoNotDisturbStatus();
        } else if (currentStatus == 'auto_answer') { sendReadyStatus();
        } else { sendReadyStatus(); }
    });
    session.on('reinvite', function (session) {
        console.log("Session reinvited!");
    });
    // dtmf
    session.on('directionChanged', function() {
        var direction = session.sessionDescriptionHandler.getDirection();
        console.log("Direction Change: ", direction);

        // Custom Web hook
        if(typeof web_hook_on_modify !== 'undefined') web_hook_on_modify("directionChanged", session);
    });

    $("#line-" + lineObj.LineNumber + "-btn-settings").removeAttr('disabled');
    $("#line-" + lineObj.LineNumber + "-btn-audioCall").prop('disabled','disabled');
    $("#line-" + lineObj.LineNumber + "-btn-videoCall").prop('disabled','disabled');
    $("#line-" + lineObj.LineNumber + "-btn-search").removeAttr('disabled');
    $("#line-" + lineObj.LineNumber + "-btn-remove").prop('disabled','disabled');

    $("#line-" + lineObj.LineNumber + "-progress").show();
    $("#line-" + lineObj.LineNumber + "-msg").show();
    
    updateLineScroll(lineObj.LineNumber);

    UpdateUI();
}
function teardownSession(lineObj, reasonCode, reasonText) {
    if(lineObj == null || lineObj.SipSession == null) return;

    var session = lineObj.SipSession;
    if(session.data.teardownComplete == true) return;
    session.data.teardownComplete = true; // Run this code only once

    session.data.reasonCode = reasonCode
    session.data.reasonText = reasonText

    // Call UI
    $.jeegoopopup.close();

    // End any child calls
    if(session.data.childsession){
        try{
            if(session.data.childsession.status == SIP.Session.C.STATUS_CONFIRMED){
                session.data.childsession.bye();
            } 
            else{
                session.data.childsession.cancel();
            }
        } catch(e){}
    }
    session.data.childsession = null;

    // Mixed Tracks
    if(session.data.AudioSourceTrack && session.data.AudioSourceTrack.kind == "audio"){
        session.data.AudioSourceTrack.stop();
        session.data.AudioSourceTrack = null;
    }
    // Stop any Early Media
    if(session.data.earlyMedia){
        session.data.earlyMedia.pause();
        session.data.earlyMedia.removeAttribute('src');
        session.data.earlyMedia.load();
        session.data.earlyMedia = null;
    }

    // Stop Recording if we are
    StopRecording(lineObj.LineNumber,true);

    // Audio Meters
    if(lineObj.LocalSoundMeter != null){
        lineObj.LocalSoundMeter.stop();
        lineObj.LocalSoundMeter = null;
    }
    if(lineObj.RemoteSoundMeter != null){
        lineObj.RemoteSoundMeter.stop();
        lineObj.RemoteSoundMeter = null;
    }

    // End timers
    window.clearInterval(session.data.videoResampleInterval);
    window.clearInterval(session.data.callTimer);

    // Add to stream
    AddCallMessage(lineObj.BuddyObj.identity, session, reasonCode, reasonText);

    // Close up the UI
    window.setTimeout(function () {
        RemoveLine(lineObj);
    }, 1000);

    UpdateBuddyList();
    UpdateUI();

    // Custom Web hook
    if(typeof web_hook_on_terminate !== 'undefined') web_hook_on_terminate(session);
}

// Mic and Speaker Levels
// ======================
function StartRemoteAudioMediaMonitoring(lineNum, session) {
    console.log("Creating RemoteAudio AudioContext on Line:" + lineNum);

    // Create local SoundMeter
    var soundMeter = new SoundMeter(session.id, lineNum);
    if(soundMeter == null){
        console.warn("AudioContext() RemoteAudio not available... it fine.");
        return null;
    }

    // Ready the getStats request
    var remoteAudioStream = new MediaStream();
    var audioReceiver = null;
    var pc = session.sessionDescriptionHandler.peerConnection;
    pc.getReceivers().forEach(function (RTCRtpReceiver) {
        if(RTCRtpReceiver.track && RTCRtpReceiver.track.kind == "audio"){
            if(audioReceiver == null) {
                remoteAudioStream.addTrack(RTCRtpReceiver.track);
                audioReceiver = RTCRtpReceiver;
            }
            else {
                console.log("Found another Track, but audioReceiver not null");
                console.log(RTCRtpReceiver);
                console.log(RTCRtpReceiver.track);
            }
        }
    });

    // Setup Charts
    var maxDataLength = 100;
    soundMeter.startTime = Date.now();
    Chart.defaults.global.defaultFontSize = 12;

    var ChatHistoryOptions = { 
        responsive: false,
        maintainAspectRatio: false,
        devicePixelRatio: 1,
        animation: false,
        scales: {
            yAxes: [{
                ticks: { beginAtZero: true } //, min: 0, max: 100
            }]
        }, 
    }

    // Receive Kilobits per second
    soundMeter.ReceiveBitRateChart = new Chart($("#line-"+ lineNum +"-AudioReceiveBitRate"), {
        type: 'line',
        data: {
            labels: MakeDataArray("", maxDataLength),
            datasets: [{
                label: lang.receive_kilobits_per_second,
                data: MakeDataArray(0, maxDataLength),
                backgroundColor: 'rgba(168, 0, 0, 0.5)',
                borderColor: 'rgba(168, 0, 0, 1)',
                borderWidth: 1,
                pointRadius: 1
            }]
        },
        options: ChatHistoryOptions
    });
    soundMeter.ReceiveBitRateChart.lastValueBytesReceived = 0;
    soundMeter.ReceiveBitRateChart.lastValueTimestamp = 0;

    // Receive Packets per second
    soundMeter.ReceivePacketRateChart = new Chart($("#line-"+ lineNum +"-AudioReceivePacketRate"), {
        type: 'line',
        data: {
            labels: MakeDataArray("", maxDataLength),
            datasets: [{
                label: lang.receive_packets_per_second,
                data: MakeDataArray(0, maxDataLength),
                backgroundColor: 'rgba(168, 0, 0, 0.5)',
                borderColor: 'rgba(168, 0, 0, 1)',
                borderWidth: 1,
                pointRadius: 1
            }]
        },
        options: ChatHistoryOptions
    });
    soundMeter.ReceivePacketRateChart.lastValuePacketReceived = 0;
    soundMeter.ReceivePacketRateChart.lastValueTimestamp = 0;

    // Receive Packet Loss
    soundMeter.ReceivePacketLossChart = new Chart($("#line-"+ lineNum +"-AudioReceivePacketLoss"), {
        type: 'line',
        data: {
            labels: MakeDataArray("", maxDataLength),
            datasets: [{
                label: lang.receive_packet_loss,
                data: MakeDataArray(0, maxDataLength),
                backgroundColor: 'rgba(168, 99, 0, 0.5)',
                borderColor: 'rgba(168, 99, 0, 1)',
                borderWidth: 1,
                pointRadius: 1
            }]
        },
        options: ChatHistoryOptions
    });
    soundMeter.ReceivePacketLossChart.lastValuePacketLoss = 0;
    soundMeter.ReceivePacketLossChart.lastValueTimestamp = 0;

    // Receive Jitter
    soundMeter.ReceiveJitterChart = new Chart($("#line-"+ lineNum +"-AudioReceiveJitter"), {
        type: 'line',
        data: {
            labels: MakeDataArray("", maxDataLength),
            datasets: [{
                label: lang.receive_jitter,
                data: MakeDataArray(0, maxDataLength),
                backgroundColor: 'rgba(0, 38, 168, 0.5)',
                borderColor: 'rgba(0, 38, 168, 1)',
                borderWidth: 1,
                pointRadius: 1
            }]
        },
        options: ChatHistoryOptions
    });

    // Receive Audio Levels
    soundMeter.ReceiveLevelsChart = new Chart($("#line-"+ lineNum +"-AudioReceiveLevels"), {
        type: 'line',
        data: {
            labels: MakeDataArray("", maxDataLength),
            datasets: [{
                label: lang.receive_audio_levels,
                data: MakeDataArray(0, maxDataLength),
                backgroundColor: 'rgba(140, 0, 168, 0.5)',
                borderColor: 'rgba(140, 0, 168, 1)',
                borderWidth: 1,
                pointRadius: 1
            }]
        },
        options: ChatHistoryOptions
    });

    // Connect to Source
    soundMeter.connectToSource(remoteAudioStream, function (e) {
        if (e != null) return;

        // Create remote SoundMeter
        console.log("SoundMeter for RemoteAudio Connected, displaying levels for Line: " + lineNum);
        soundMeter.levelsInterval = window.setInterval(function () {
            // Calculate Levels
            //value="0" max="1" high="0.25" (this seems low... )
            var level = soundMeter.instant * 4.0;
            if (level > 1) level = 1;
            var instPercent = level * 100;

            $("#line-" + lineNum + "-Speaker").css("height", instPercent.toFixed(2) +"%");
        }, 50);
        soundMeter.networkInterval = window.setInterval(function (){
            // Calculate Network Conditions
            if(audioReceiver != null) {
                audioReceiver.getStats().then(function(stats) {
                    stats.forEach(function(report){

                        var theMoment = utcDateNow();
                        var ReceiveBitRateChart = soundMeter.ReceiveBitRateChart;
                        var ReceivePacketRateChart = soundMeter.ReceivePacketRateChart;
                        var ReceivePacketLossChart = soundMeter.ReceivePacketLossChart;
                        var ReceiveJitterChart = soundMeter.ReceiveJitterChart;
                        var ReceiveLevelsChart = soundMeter.ReceiveLevelsChart;
                        var elapsedSec = Math.floor((Date.now() - soundMeter.startTime)/1000);

                        if(report.type == "inbound-rtp"){

                            if(ReceiveBitRateChart.lastValueTimestamp == 0) {
                                ReceiveBitRateChart.lastValueTimestamp = report.timestamp;
                                ReceiveBitRateChart.lastValueBytesReceived = report.bytesReceived;

                                ReceivePacketRateChart.lastValueTimestamp = report.timestamp;
                                ReceivePacketRateChart.lastValuePacketReceived = report.packetsReceived;

                                ReceivePacketLossChart.lastValueTimestamp = report.timestamp;
                                ReceivePacketLossChart.lastValuePacketLoss = report.packetsLost;

                                return;
                            }
                            // Receive Kilobits Per second
                            var kbitsPerSec = (8 * (report.bytesReceived - ReceiveBitRateChart.lastValueBytesReceived))/1000;

                            ReceiveBitRateChart.lastValueTimestamp = report.timestamp;
                            ReceiveBitRateChart.lastValueBytesReceived = report.bytesReceived;

                            soundMeter.ReceiveBitRate.push({ value: kbitsPerSec, timestamp : theMoment});
                            ReceiveBitRateChart.data.datasets[0].data.push(kbitsPerSec);
                            ReceiveBitRateChart.data.labels.push("");
                            if(ReceiveBitRateChart.data.datasets[0].data.length > maxDataLength) {
                                ReceiveBitRateChart.data.datasets[0].data.splice(0,1);
                                ReceiveBitRateChart.data.labels.splice(0,1);
                            }
                            ReceiveBitRateChart.update();

                            // Receive Packets Per Second
                            var PacketsPerSec = (report.packetsReceived - ReceivePacketRateChart.lastValuePacketReceived);

                            ReceivePacketRateChart.lastValueTimestamp = report.timestamp;
                            ReceivePacketRateChart.lastValuePacketReceived = report.packetsReceived;

                            soundMeter.ReceivePacketRate.push({ value: PacketsPerSec, timestamp : theMoment});
                            ReceivePacketRateChart.data.datasets[0].data.push(PacketsPerSec);
                            ReceivePacketRateChart.data.labels.push("");
                            if(ReceivePacketRateChart.data.datasets[0].data.length > maxDataLength) {
                                ReceivePacketRateChart.data.datasets[0].data.splice(0,1);
                                ReceivePacketRateChart.data.labels.splice(0,1);
                            }
                            ReceivePacketRateChart.update();

                            // Receive Packet Loss
                            var PacketsLost = (report.packetsLost - ReceivePacketLossChart.lastValuePacketLoss);

                            ReceivePacketLossChart.lastValueTimestamp = report.timestamp;
                            ReceivePacketLossChart.lastValuePacketLoss = report.packetsLost;

                            soundMeter.ReceivePacketLoss.push({ value: PacketsLost, timestamp : theMoment});
                            ReceivePacketLossChart.data.datasets[0].data.push(PacketsLost);
                            ReceivePacketLossChart.data.labels.push("");
                            if(ReceivePacketLossChart.data.datasets[0].data.length > maxDataLength) {
                                ReceivePacketLossChart.data.datasets[0].data.splice(0,1);
                                ReceivePacketLossChart.data.labels.splice(0,1);
                            }
                            ReceivePacketLossChart.update();

                            // Receive Jitter
                            soundMeter.ReceiveJitter.push({ value: report.jitter, timestamp : theMoment});
                            ReceiveJitterChart.data.datasets[0].data.push(report.jitter);
                            ReceiveJitterChart.data.labels.push("");
                            if(ReceiveJitterChart.data.datasets[0].data.length > maxDataLength) {
                                ReceiveJitterChart.data.datasets[0].data.splice(0,1);
                                ReceiveJitterChart.data.labels.splice(0,1);
                            }
                            ReceiveJitterChart.update();
                        }
                        if(report.type == "track") {

                            // Receive Audio Levels
                            var levelPercent = (report.audioLevel * 100);
                            soundMeter.ReceiveLevels.push({ value: levelPercent, timestamp : theMoment});
                            ReceiveLevelsChart.data.datasets[0].data.push(levelPercent);
                            ReceiveLevelsChart.data.labels.push("");
                            if(ReceiveLevelsChart.data.datasets[0].data.length > maxDataLength)
                            {
                                ReceiveLevelsChart.data.datasets[0].data.splice(0,1);
                                ReceiveLevelsChart.data.labels.splice(0,1);
                            }
                            ReceiveLevelsChart.update();
                        }
                    });
                });
            }
        } ,1000);
    });

    return soundMeter;
}
function StartLocalAudioMediaMonitoring(lineNum, session) {
    console.log("Creating LocalAudio AudioContext on line " + lineNum);

    // Create local SoundMeter
    var soundMeter = new SoundMeter(session.id, lineNum);
    if(soundMeter == null){
        console.warn("AudioContext() LocalAudio not available... its fine.")
        return null;
    }

    // Ready the getStats request
    var localAudioStream = new MediaStream();
    var audioSender = null;
    var pc = session.sessionDescriptionHandler.peerConnection;
    pc.getSenders().forEach(function (RTCRtpSender) {
        if(RTCRtpSender.track && RTCRtpSender.track.kind == "audio"){
            if(audioSender == null){
                console.log("Adding Track to Monitor: ", RTCRtpSender.track.label);
                localAudioStream.addTrack(RTCRtpSender.track);
                audioSender = RTCRtpSender;
            }
            else {
                console.log("Found another Track, but audioSender not null");
                console.log(RTCRtpSender);
                console.log(RTCRtpSender.track);
            }
        }
    });

    // Setup Charts
    var maxDataLength = 100;
    soundMeter.startTime = Date.now();
    Chart.defaults.global.defaultFontSize = 12;
    var ChatHistoryOptions = { 
        responsive: false,    
        maintainAspectRatio: false,
        devicePixelRatio: 1,
        animation: false,
        scales: {
            yAxes: [{
                ticks: { beginAtZero: true }
            }]
        }, 
    }

    // Send Kilobits Per Second
    soundMeter.SendBitRateChart = new Chart($("#line-"+ lineNum +"-AudioSendBitRate"), {
        type: 'line',
        data: {
            labels: MakeDataArray("", maxDataLength),
            datasets: [{
                label: lang.send_kilobits_per_second,
                data: MakeDataArray(0, maxDataLength),
                backgroundColor: 'rgba(0, 121, 19, 0.5)',
                borderColor: 'rgba(0, 121, 19, 1)',
                borderWidth: 1,
                pointRadius: 1
            }]
        },
        options: ChatHistoryOptions
    });
    soundMeter.SendBitRateChart.lastValueBytesSent = 0;
    soundMeter.SendBitRateChart.lastValueTimestamp = 0;

    // Send Packets Per Second
    soundMeter.SendPacketRateChart = new Chart($("#line-"+ lineNum +"-AudioSendPacketRate"), {
        type: 'line',
        data: {
            labels: MakeDataArray("", maxDataLength),
            datasets: [{
                label: lang.send_packets_per_second,
                data: MakeDataArray(0, maxDataLength),
                backgroundColor: 'rgba(0, 121, 19, 0.5)',
                borderColor: 'rgba(0, 121, 19, 1)',
                borderWidth: 1,
                pointRadius: 1
            }]
        },
        options: ChatHistoryOptions
    });
    soundMeter.SendPacketRateChart.lastValuePacketSent = 0;
    soundMeter.SendPacketRateChart.lastValueTimestamp = 0;    

    // Connect to Source
    soundMeter.connectToSource(localAudioStream, function (e) {
        if (e != null) return;

        console.log("SoundMeter for LocalAudio Connected, displaying levels for Line: " + lineNum);
        soundMeter.levelsInterval = window.setInterval(function () {
            // Calculate Levels
            //value="0" max="1" high="0.25" (this seems low... )
            var level = soundMeter.instant * 4.0;
            if (level > 1) level = 1;
            var instPercent = level * 100;
            $("#line-" + lineNum + "-Mic").css("height", instPercent.toFixed(2) +"%");
        }, 50);
        soundMeter.networkInterval = window.setInterval(function (){
            // Calculate Network Conditions
            // Sending Audio Track
            if(audioSender != null) {
                audioSender.getStats().then(function(stats) {
                    stats.forEach(function(report){

                        var theMoment = utcDateNow();
                        var SendBitRateChart = soundMeter.SendBitRateChart;
                        var SendPacketRateChart = soundMeter.SendPacketRateChart;
                        var elapsedSec = Math.floor((Date.now() - soundMeter.startTime)/1000);

                        if(report.type == "outbound-rtp"){
                            if(SendBitRateChart.lastValueTimestamp == 0) {
                                SendBitRateChart.lastValueTimestamp = report.timestamp;
                                SendBitRateChart.lastValueBytesSent = report.bytesSent;

                                SendPacketRateChart.lastValueTimestamp = report.timestamp;
                                SendPacketRateChart.lastValuePacketSent = report.packetsSent;
                                return;
                            }

                            // Send Kilobits Per second
                            var kbitsPerSec = (8 * (report.bytesSent - SendBitRateChart.lastValueBytesSent))/1000;

                            SendBitRateChart.lastValueTimestamp = report.timestamp;
                            SendBitRateChart.lastValueBytesSent = report.bytesSent;

                            soundMeter.SendBitRate.push({ value: kbitsPerSec, timestamp : theMoment});
                            SendBitRateChart.data.datasets[0].data.push(kbitsPerSec);
                            SendBitRateChart.data.labels.push("");
                            if(SendBitRateChart.data.datasets[0].data.length > maxDataLength) {
                                SendBitRateChart.data.datasets[0].data.splice(0,1);
                                SendBitRateChart.data.labels.splice(0,1);
                            }
                            SendBitRateChart.update();

                            // Send Packets Per Second
                            var PacketsPerSec = report.packetsSent - SendPacketRateChart.lastValuePacketSent;

                            SendPacketRateChart.lastValueTimestamp = report.timestamp;
                            SendPacketRateChart.lastValuePacketSent = report.packetsSent;

                            soundMeter.SendPacketRate.push({ value: PacketsPerSec, timestamp : theMoment});
                            SendPacketRateChart.data.datasets[0].data.push(PacketsPerSec);
                            SendPacketRateChart.data.labels.push("");
                            if(SendPacketRateChart.data.datasets[0].data.length > maxDataLength) {
                                SendPacketRateChart.data.datasets[0].data.splice(0,1);
                                SendPacketRateChart.data.labels.splice(0,1);
                            }
                            SendPacketRateChart.update();
                        }
                        if(report.type == "track") {
                            // Bug/security consern... this seems always to report "0"
                            // Possible reason: When applied to isolated streams, media metrics may allow an application to infer some characteristics of the isolated stream, such as if anyone is speaking (by watching the audioLevel statistic).
                            // console.log("Audio Sender: " + report.audioLevel);
                        }
                    });
                });
            }
        } ,1000);
    });

    return soundMeter;
}

// Sounds Meter Class
// ==================
class SoundMeter {
    constructor(sessionId, lineNum) {
        var audioContext = null;
        try {
            window.AudioContext = window.AudioContext || window.webkitAudioContext;
            audioContext = new AudioContext();
        }
        catch(e) {
            console.warn("AudioContext() LocalAudio not available... its fine.");
        }
        if (audioContext == null) return null;

        this.lineNum = lineNum;
        this.sessionId = sessionId;
        this.levelsInterval = null;
        this.networkInterval = null;
        this.startTime = 0;
        this.ReceiveBitRateChart = null;
        this.ReceiveBitRate = [];
        this.ReceivePacketRateChart = null;
        this.ReceivePacketRate = [];
        this.ReceivePacketLossChart = null;
        this.ReceivePacketLoss = [];
        this.ReceiveJitterChart = null;
        this.ReceiveJitter = [];
        this.ReceiveLevelsChart = null;
        this.ReceiveLevels = [];
        this.SendBitRateChart = null;
        this.SendBitRate = [];
        this.SendPacketRateChart = null;
        this.SendPacketRate = [];
        this.context = audioContext;
        this.instant = 0.0;
        this.script = audioContext.createScriptProcessor(2048, 1, 1);
        const that = this;
        this.script.onaudioprocess = function (event) {
            const input = event.inputBuffer.getChannelData(0);
            let i;
            let sum = 0.0;
            for (i = 0; i < input.length; ++i) {
                sum += input[i] * input[i];
            }
            that.instant = Math.sqrt(sum / input.length);
        }
    }
    connectToSource(stream, callback) {
        console.log("SoundMeter connecting...");
        try {
            this.mic = this.context.createMediaStreamSource(stream);
            this.mic.connect(this.script);
            // necessary to make sample run, but should not be.
            this.script.connect(this.context.destination);
            callback(null);
        }
        catch(e) {
            console.error(e); // Probably not audio track
            callback(e);
        }
    }
    stop() {
        console.log("Disconnecting SoundMeter...");
        try {
            window.clearInterval(this.levelsInterval);
            this.levelsInterval = null;
        }
        catch(e) { }
        try {
            window.clearInterval(this.networkInterval);
            this.networkInterval = null;
        }
        catch(e) { }
        this.mic.disconnect();
        this.script.disconnect();
        this.mic = null;
        this.script = null;
        try {
            this.context.close();
        }
        catch(e) { }
        this.context = null;

        // Save to IndexDb
        var lineObj = FindLineByNumber(this.lineNum);
        var QosData = {
            ReceiveBitRate: this.ReceiveBitRate,
            ReceivePacketRate: this.ReceivePacketRate,
            ReceivePacketLoss: this.ReceivePacketLoss,
            ReceiveJitter: this.ReceiveJitter,
            ReceiveLevels: this.ReceiveLevels,
            SendBitRate: this.SendBitRate,
            SendPacketRate: this.SendPacketRate,
        }
        SaveQosData(QosData, this.sessionId, lineObj.BuddyObj.identity);
    }
}
function MeterSettingsOutput(audioStream, objectId, direction, interval){
    var soundMeter = new SoundMeter(null, null);
    soundMeter.startTime = Date.now();
    soundMeter.connectToSource(audioStream, function (e) {
        if (e != null) return;

        console.log("SoundMeter Connected, displaying levels to:"+ objectId);
        soundMeter.levelsInterval = window.setInterval(function () {
            // Calculate Levels
            //value="0" max="1" high="0.25" (this seems low... )
            var level = soundMeter.instant * 4.0;
            if (level > 1) level = 1;
            var instPercent = level * 100;

            $("#"+objectId).css(direction, instPercent.toFixed(2) +"%"); // Settings_MicrophoneOutput "width" 50
        }, interval);
    });

    return soundMeter;
}

// QOS
// ===
function SaveQosData(QosData, sessionId, buddy){
    var indexedDB = window.indexedDB;
    var request = indexedDB.open("CallQosData");
    request.onerror = function(event) {
    //    console.error("IndexDB Request Error:", event);
    }
    request.onupgradeneeded = function(event) {
        console.warn("Upgrade Required for IndexDB... probably because of first time use.");
        var IDB = event.target.result;

        // Create Object Store
        if(IDB.objectStoreNames.contains("CallQos") == false){
            var objectStore = IDB.createObjectStore("CallQos", { keyPath: "uID" });
            objectStore.createIndex("sessionid", "sessionid", { unique: false });
            objectStore.createIndex("buddy", "buddy", { unique: false });
            objectStore.createIndex("QosData", "QosData", { unique: false });
        }
        else {
            console.warn("IndexDB requested upgrade, but object store was in place");
        }
    }
    request.onsuccess = function(event) {
        console.log("IndexDB connected to CallQosData");

        var IDB = event.target.result;
        if(IDB.objectStoreNames.contains("CallQos") == false){
            console.warn("IndexDB CallQosData.CallQos does not exists");
            return;
        }
        IDB.onerror = function(event) {
            console.error("IndexDB Error:", event);
        }

        // Prepare data to write
        var data = {
            uID: uID(),
            sessionid: sessionId,
            buddy: buddy,
            QosData: QosData
        }
        // Commit Transaction
        var transaction = IDB.transaction(["CallQos"], "readwrite");
        var objectStoreAdd = transaction.objectStore("CallQos").add(data);
        objectStoreAdd.onsuccess = function(event) {
            console.log("Call CallQos Sucess: ", sessionId);
        }
    }
}
function DisplayQosData(sessionId){
    var indexedDB = window.indexedDB;
    var request = indexedDB.open("CallQosData");
    request.onerror = function(event) {
    //    console.error("IndexDB Request Error:", event);
    }
    request.onupgradeneeded = function(event) {
        console.warn("Upgrade Required for IndexDB... probably because of first time use.");
    }
    request.onsuccess = function(event) {
        console.log("IndexDB connected to CallQosData");

        var IDB = event.target.result;
        if(IDB.objectStoreNames.contains("CallQos") == false){
            console.warn("IndexDB CallQosData.CallQos does not exists");
            return;
        } 

        var transaction = IDB.transaction(["CallQos"]);
        var objectStoreGet = transaction.objectStore("CallQos").index('sessionid').getAll(sessionId);
        objectStoreGet.onerror = function(event) {
            console.error("IndexDB Get Error:", event);
        }
        objectStoreGet.onsuccess = function(event) {
            if(event.target.result && event.target.result.length == 2){
                // This is the correct data

                var QosData0 = event.target.result[0].QosData;
                // ReceiveBitRate: (8) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
                // ReceiveJitter: (8) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
                // ReceiveLevels: (9) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
                // ReceivePacketLoss: (8) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
                // ReceivePacketRate: (8) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
                // SendBitRate: []
                // SendPacketRate: []
                var QosData1 = event.target.result[1].QosData;
                // ReceiveBitRate: []
                // ReceiveJitter: []
                // ReceiveLevels: []
                // ReceivePacketLoss: []
                // ReceivePacketRate: []
                // SendBitRate: (9) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]
                // SendPacketRate: (9) [{…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}, {…}]

                Chart.defaults.global.defaultFontSize = 12;

                var ChatHistoryOptions = { 
                    responsive: true,
                    maintainAspectRatio: false,
                    animation: false,
                    scales: {
                        yAxes: [{
                            ticks: { beginAtZero: true } //, min: 0, max: 100
                        }],
                        xAxes: [{
                            display: false
                        }]
                    }, 
                }

                // ReceiveBitRateChart
                var labelset = [];
                var dataset = [];
                var data = (QosData0.ReceiveBitRate.length > 0)? QosData0.ReceiveBitRate : QosData1.ReceiveBitRate;
                $.each(data, function(i,item){
                    labelset.push(moment.utc(item.timestamp.replace(" UTC", "")).local().format(DisplayDateFormat +" "+ DisplayTimeFormat));
                    dataset.push(item.value);
                });
                var ReceiveBitRateChart = new Chart($("#cdr-AudioReceiveBitRate"), {
                    type: 'line',
                    data: {
                        labels: labelset,
                        datasets: [{
                            label: lang.receive_kilobits_per_second,
                            data: dataset,
                            backgroundColor: 'rgba(168, 0, 0, 0.5)',
                            borderColor: 'rgba(168, 0, 0, 1)',
                            borderWidth: 1,
                            pointRadius: 1
                        }]
                    },
                    options: ChatHistoryOptions
                });

                // ReceivePacketRateChart
                var labelset = [];
                var dataset = [];
                var data = (QosData0.ReceivePacketRate.length > 0)? QosData0.ReceivePacketRate : QosData1.ReceivePacketRate;
                $.each(data, function(i,item){
                    labelset.push(moment.utc(item.timestamp.replace(" UTC", "")).local().format(DisplayDateFormat +" "+ DisplayTimeFormat));
                    dataset.push(item.value);
                });
                var ReceivePacketRateChart = new Chart($("#cdr-AudioReceivePacketRate"), {
                    type: 'line',
                    data: {
                        labels: labelset,
                        datasets: [{
                            label: lang.receive_packets_per_second,
                            data: dataset,
                            backgroundColor: 'rgba(168, 0, 0, 0.5)',
                            borderColor: 'rgba(168, 0, 0, 1)',
                            borderWidth: 1,
                            pointRadius: 1
                        }]
                    },
                    options: ChatHistoryOptions
                });

                // AudioReceivePacketLossChart
                var labelset = [];
                var dataset = [];
                var data = (QosData0.ReceivePacketLoss.length > 0)? QosData0.ReceivePacketLoss : QosData1.ReceivePacketLoss;
                $.each(data, function(i,item){
                    labelset.push(moment.utc(item.timestamp.replace(" UTC", "")).local().format(DisplayDateFormat +" "+ DisplayTimeFormat));
                    dataset.push(item.value);
                });
                var AudioReceivePacketLossChart = new Chart($("#cdr-AudioReceivePacketLoss"), {
                    type: 'line',
                    data: {
                        labels: labelset,
                        datasets: [{
                            label: lang.receive_packet_loss,
                            data: dataset,
                            backgroundColor: 'rgba(168, 99, 0, 0.5)',
                            borderColor: 'rgba(168, 99, 0, 1)',
                            borderWidth: 1,
                            pointRadius: 1
                        }]
                    },
                    options: ChatHistoryOptions
                });

                // AudioReceiveJitterChart
                var labelset = [];
                var dataset = [];
                var data = (QosData0.ReceiveJitter.length > 0)? QosData0.ReceiveJitter : QosData1.ReceiveJitter;
                $.each(data, function(i,item){
                    labelset.push(moment.utc(item.timestamp.replace(" UTC", "")).local().format(DisplayDateFormat +" "+ DisplayTimeFormat));
                    dataset.push(item.value);
                });
                var AudioReceiveJitterChart = new Chart($("#cdr-AudioReceiveJitter"), {
                    type: 'line',
                    data: {
                        labels: labelset,
                        datasets: [{
                            label: lang.receive_jitter,
                            data: dataset,
                            backgroundColor: 'rgba(0, 38, 168, 0.5)',
                            borderColor: 'rgba(0, 38, 168, 1)',
                            borderWidth: 1,
                            pointRadius: 1
                        }]
                    },
                    options: ChatHistoryOptions
                });
              
                // AudioReceiveLevelsChart
                var labelset = [];
                var dataset = [];
                var data = (QosData0.ReceiveLevels.length > 0)? QosData0.ReceiveLevels : QosData1.ReceiveLevels;
                $.each(data, function(i,item){
                    labelset.push(moment.utc(item.timestamp.replace(" UTC", "")).local().format(DisplayDateFormat +" "+ DisplayTimeFormat));
                    dataset.push(item.value);
                });
                var AudioReceiveLevelsChart = new Chart($("#cdr-AudioReceiveLevels"), {
                    type: 'line',
                    data: {
                        labels: labelset,
                        datasets: [{
                            label: lang.receive_audio_levels,
                            data: dataset,
                            backgroundColor: 'rgba(140, 0, 168, 0.5)',
                            borderColor: 'rgba(140, 0, 168, 1)',
                            borderWidth: 1,
                            pointRadius: 1
                        }]
                    },
                    options: ChatHistoryOptions
                });
                 
                // SendPacketRateChart
                var labelset = [];
                var dataset = [];
                var data = (QosData0.SendPacketRate.length > 0)? QosData0.SendPacketRate : QosData1.SendPacketRate;
                $.each(data, function(i,item){
                    labelset.push(moment.utc(item.timestamp.replace(" UTC", "")).local().format(DisplayDateFormat +" "+ DisplayTimeFormat));
                    dataset.push(item.value);
                });
                var SendPacketRateChart = new Chart($("#cdr-AudioSendPacketRate"), {
                    type: 'line',
                    data: {
                        labels: labelset,
                        datasets: [{
                            label: lang.send_packets_per_second,
                            data: dataset,
                            backgroundColor: 'rgba(0, 121, 19, 0.5)',
                            borderColor: 'rgba(0, 121, 19, 1)',
                            borderWidth: 1,
                            pointRadius: 1
                        }]
                    },
                    options: ChatHistoryOptions
                });

                // AudioSendBitRateChart
                var labelset = [];
                var dataset = [];
                var data = (QosData0.SendBitRate.length > 0)? QosData0.SendBitRate : QosData1.SendBitRate;
                $.each(data, function(i,item){
                    labelset.push(moment.utc(item.timestamp.replace(" UTC", "")).local().format(DisplayDateFormat +" "+ DisplayTimeFormat));
                    dataset.push(item.value);
                });
                var AudioSendBitRateChart = new Chart($("#cdr-AudioSendBitRate"), {
                    type: 'line',
                    data: {
                        labels: labelset,
                        datasets: [{
                            label: lang.send_kilobits_per_second,
                            data: dataset,
                            backgroundColor: 'rgba(0, 121, 19, 0.5)',
                            borderColor: 'rgba(0, 121, 19, 1)',
                            borderWidth: 1,
                            pointRadius: 1
                        }]
                    },
                    options: ChatHistoryOptions
                });
            } else {
                console.warn("Result not expected", event.target.result);
            }
        }
    }
}
function DeleteQosData(buddy) {

    var myStorage = {};
    myStorage.indexedDB = {};

    myStorage.indexedDB.onerror = function(e) {
	console.log(e);
    };

    var request = indexedDB.open("CallQosData");

    request.onerror = myStorage.indexedDB.onerror;

    request.onupgradeneeded = function(event) {
        console.warn("Upgrade Required for IndexDB... probably because of first time use.");
        // If this is the case, there will be no call recordings
    }
    request.onsuccess = function(event) {
        console.log("IndexDB connected to CallQosData");

        var IDB = event.target.result;
        if (IDB.objectStoreNames.contains("CallQos") == false) {
            console.warn("IndexDB CallQosData.CallQos does not exists");
            return;
        }
        IDB.onerror = function(event) {
            console.error("IndexDB Error:", event);
        }

        // Loop and Delete
        console.log("Deleting CallQosData: ", buddy);
        var transaction = IDB.transaction(["CallQos"], "readwrite");
        var objectStore = transaction.objectStore("CallQos");
        var objectStoreGet = objectStore.index('buddy').getAll(buddy);

        objectStoreGet.onerror = function(event) {
            console.error("IndexDB Get Error:", event);
        }
        objectStoreGet.onsuccess = function(event) {
            if (event.target.result && event.target.result.length > 0) {
                // There sre some rows to delete
                $.each(event.target.result, function(i, item) {
                    // console.log("Delete: ", item.uID);
                    try {
                        objectStore.delete(item.uID);
                    } catch(e) {
                        console.log("Call CallQosData Delete failed: ", e);
                    }
                });
            }
        }
    }
}

// Presence / Subscribe
// ====================

function SubscribeAll() {

    // PIDF Subscription
    var dialogOptions = { expires: 3600, extraHeaders: ['Accept: application/pidf+xml'] };

    // Start subscribe all
    console.log("Starting Subscribe of all ("+ Buddies.length +") Extension Buddies...");
    for(var b=0; b<Buddies.length; b++) {
        var buddyObj = Buddies[b];
        if(buddyObj.type == "extension") {
            console.log("SUBSCRIBE: "+ buddyObj.ExtNo +"@" + wssServer);
            var blfObj = userAgent.subscribe(buddyObj.ExtNo +"@" + wssServer, 'presence', dialogOptions); 
            blfObj.data.buddyId = buddyObj.identity;
            blfObj.on('notify', function (notification) {
                ReceiveBlf(notification);
            });
            BlfSubs.push(blfObj);
        }
    }
}

function SubscribeBuddy(buddyObj) {

    var dialogOptions = { expires: 3600, extraHeaders: ['Accept: application/pidf+xml'] };

    if(buddyObj.type == "extension") {
        console.log("SUBSCRIBE: "+ buddyObj.ExtNo +"@" + wssServer);
        var blfObj = userAgent.subscribe(buddyObj.ExtNo +"@" + wssServer, 'presence', dialogOptions);
        blfObj.data.buddyId = buddyObj.identity;
        blfObj.on('notify', function (notification) {
            ReceiveBlf(notification);
        });
        BlfSubs.push(blfObj);
    }
}
function ReceiveBlf(notification) {

    if (userAgent == null || !userAgent.isRegistered()) return;

    var ContentType = notification.request.headers["Content-Type"][0].parsed;

    if (ContentType == "application/pidf+xml") {

        var xml = $($.parseXML(notification.request.body));

        var Entity = xml.find("presence").attr("entity");
        var Contact = xml.find("presence").find("tuple").find("contact").text();
        if (SipUsername == Entity.split("@")[0].split(":")[1] || SipUsername == Contact.split("@")[0].split(":")[1]) {
            // Message is for you.
        } 
        else {
            console.warn("presence message not for you.", xml);
            return;
        }

        var buddy = xml.find("presence").find("tuple").attr("id");

        var statusObj = xml.find("presence").find("tuple").find("status");
        var availability = xml.find("presence").find("tuple").find("status").find("basic").text();
        var friendlyState = xml.find("presence").find("note").text();
        var dotClass = "dotOffline";

        if (friendlyState == "Not online") dotClass = "dotOffline";
        if (friendlyState == "Ringing") dotClass = "dotRinging";
        if (friendlyState == "On hold") dotClass = "dotOnHold";
        if (friendlyState == "Unavailable") dotClass = "dotOffline";

        // dotOnline | dotOffline | dotRinging | dotInUse | dotReady | dotOnHold
        var buddyObj = FindBuddyByExtNo(buddy);
        if (buddyObj != null) {

            if (friendlyState == "Ringing" || friendlyState == "On hold" || friendlyState == "Unavailable" || friendlyState == "Not online" || friendlyState == "Unknown") {
                console.log("Setting Presence for "+ buddyObj.identity +" to "+ friendlyState);
                $("#contact-" + buddyObj.identity + "-devstate").prop("class", dotClass);
                $("#contact-" + buddyObj.identity + "-devstate-main").prop("class", dotClass);
                buddyObj.devState = dotClass;
                buddyObj.presence = friendlyState;

                if (friendlyState == "Not online") friendlyState = lang.state_not_online;
                if (friendlyState == "Ringing") friendlyState = lang.state_ringing;
                if (friendlyState == "On hold") friendlyState = lang.state_on_hold;
                if (friendlyState == "Unavailable") friendlyState = lang.state_unavailable;
                if (friendlyState == "Unknown") friendlyState = lang.state_unknown;

                $("#contact-" + buddyObj.identity + "-presence").html(friendlyState);
                $("#contact-" + buddyObj.identity + "-presence-main").html(friendlyState);
            }
        }

    } else if (ContentType == "application/dialog-info+xml") {

        // Handle "Dialog" State

        var xml = $($.parseXML(notification.request.body));

        var ObservedUser = xml.find("dialog-info").attr("entity");
        var buddy = ObservedUser.split("@")[0].split(":")[1];

        var version = xml.find("dialog-info").attr("version");

        var DialogState = xml.find("dialog-info").attr("state");
        if (DialogState != "full") return;

        var extId = xml.find("dialog-info").find("dialog").attr("id");
        if (extId != buddy) return;

        var state = xml.find("dialog-info").find("dialog").find("state").text();
        var friendlyState = "Unknown";
        var dotClass = "dotOffline";
        if (friendlyState == "Not online") dotClass = "dotOffline";
        if (friendlyState == "Ringing") dotClass = "dotRinging";
        if (friendlyState == "On hold") dotClass = "dotOnHold";
        if (friendlyState == "Unavailable") dotClass = "dotOffline";

        // The dialog states only report devices' states.
        // dotOnline | dotOffline | dotRinging | dotInUse | dotReady | dotOnHold
        var buddyObj = FindBuddyByExtNo(buddy);

        if (buddyObj != null) {
            if (friendlyState == "Ringing" || friendlyState == "On hold" || friendlyState == "Unavailable" || friendlyState == "Not online" || friendlyState == "Unknown") {
                console.log("Setting Presence for "+ buddyObj.identity +" to "+ friendlyState);
                $("#contact-" + buddyObj.identity + "-devstate").prop("class", dotClass);
                $("#contact-" + buddyObj.identity + "-devstate-main").prop("class", dotClass);
                buddyObj.devState = dotClass;
                buddyObj.presence = friendlyState;

                if (friendlyState == "Not online") friendlyState = lang.state_not_online;
                if (friendlyState == "Ringing") friendlyState = lang.state_ringing;
                if (friendlyState == "On hold") friendlyState = lang.state_on_hold;
                if (friendlyState == "Unavailable") friendlyState = lang.state_unavailable;
                if (friendlyState == "Unknown") friendlyState = lang.state_unknown;

                $("#contact-" + buddyObj.identity + "-presence").html(friendlyState);
                $("#contact-" + buddyObj.identity + "-presence-main").html(friendlyState);
            }
        }
    }
}

function UnsubscribeAll() {
    console.log("Unsubscribing "+ BlfSubs.length + " subscriptions...");
    for (var blf = 0; blf < BlfSubs.length; blf++) {
        BlfSubs[blf].unsubscribe();
        BlfSubs[blf].close();
    }
    BlfSubs = new Array();

    for(var b=0; b<Buddies.length; b++) {
        var buddyObj = Buddies[b];
        if (buddyObj.type == "extension") {
            $("#contact-" + buddyObj.identity + "-devstate").prop("class", "dotOffline");
            $("#contact-" + buddyObj.identity + "-devstate-main").prop("class", "dotOffline");
            $("#contact-" + buddyObj.identity + "-presence").html(lang.state_unknown);
            $("#contact-" + buddyObj.identity + "-presence-main").html(lang.state_unknown);
        }
    }
}

function UnsubscribeBuddy(buddyObj) {
    if(buddyObj.type != "extension") return;

    for (var blf = 0; blf < BlfSubs.length; blf++) {
        var blfObj = BlfSubs[blf];
        if(blfObj.data.buddyId == buddyObj.identity){
        console.log("Unsubscribing:", buddyObj.identity);
            if(blfObj.dialog != null){
                blfObj.unsubscribe();
                blfObj.close();
            }
            BlfSubs.splice(blf, 1);
            break;
        }
    }
}

// Send message to groups as email
function sendMsgAsEmailToGroups(emailAddr, textToSendAsEmail) {
    $.ajax({
            type: "POST",
            url: "src/send-text-message-as-email.php",
            dataType: "JSON",
            data: {
                   username: userName,
                   roundpinhost: window.location.host,
                   sendername: profileName,
                   senderextension: SipUsername,
                   receiveremail: emailAddr,
                   texttosend: textToSendAsEmail,
                   s_ajax_call: validateSToken
            },
            success: function(response) {

                          if (response.result != 'success') {
                              var sntmgremstts = $("#showTextMessagemgr .mgrEmSentStatus").last();
                              sntmgremstts.removeClass("fa-check");
                              sntmgremstts.addClass("fa-question-circle-o");
                              sntmgremstts.attr("title", "There were problems while sending some emails.");
                              sntmgremstts.css("color", "#b82424");
                              alert("An error occurred while trying to send the message as email!");
                          }

            },
            error: function(response) {
                            var sntmgremstts = $("#showTextMessagemgr .mgrEmSentStatus").last();
                            sntmgremstts.removeClass("fa-check");
                            sntmgremstts.addClass("fa-question-circle-o");
                            sntmgremstts.attr("title", "There were problems while sending some emails.");
                            sntmgremstts.css("color", "#b82424");
                            alert("An error occurred while attempting to send the message as email!");
            }
    });
}

// Buddy: Chat / Instant Message
// ====================================
function splitString(str, size) {
    const mPieces = Math.ceil(str.length / size);
    const piecesArr = [];

    for (let i = 0, s = 0; i < mPieces; ++i, s += size) {
      piecesArr[i] = str.substr(s, size);
    }
    return piecesArr;
}
function generateAESKey(passphrase) {
    const salt = CryptoJS.lib.WordArray.random(128 / 8);
    const key256Bits = CryptoJS.PBKDF2(passphrase, salt, {
      keySize: 256 / 32,
      iterations: 100,
    });
    return key256Bits;
}
function encryptAES(message, key) {
    var message = CryptoJS.AES.encrypt(message, key);
    return message.toString();
}
function decryptAES(message, key) {
    var code = CryptoJS.AES.decrypt(message, key);
    var decryptedMessage = code.toString(CryptoJS.enc.Utf8);
    return decryptedMessage;
}

function SendChatMessage(buddy, msgtosend) {

    if (userAgent == null) return;
    if (!userAgent.isRegistered()) return;

    $.when( (sendFileCheck == 1 || sendRecFileCheck == 1) ? ($("#contact-"+ buddy +"-ChatHistory").append("<span id='sendFileLoader'></span>")) : false ).then(function() {

            if (msgtosend == '') {
	        var messageraw = $("#contact-" + buddy + "-ChatMessage").val();
            } else {
                var messageraw = msgtosend;
            }

	    messagetrim = $.trim(messageraw);

	    if (messagetrim == "" && sendFileCheck == 0 && sendRecFileCheck == 0) {
		$("#sendFileLoader").remove();
		alert(lang.alert_empty_text_message);
		return;
	    }

	    var buddyObj = FindBuddyByIdentity(buddy);

	    // If our presence status doesn't allow receiving messages, show a warning
	    var getStatusFrag = lang.status_label +": ";
	    var crStatusPre = $("#regStatus").text().split(getStatusFrag);
            var crStatusSec = crStatusPre[1].split(" - ");
            var crStatusTert = crStatusSec[0];
	    if ($.inArray(crStatusTert, [lang.state_do_not_disturb, lang.state_unavailable, lang.state_unknown, lang.state_not_online]) !== -1) {
		alert("Since your current status is '"+ crStatusTert +"', your interlocutors can receive your messages but they cannot respond. To be able to receive messages, your status must be 'Ready', 'Auto Answer', 'Away', 'Ringing', 'On the phone', 'On hold', or a custom status.");
	    }

	    // Encrypt the message
	    var genPassphr = Date.now() + Math.floor(Math.random()*10000).toString(6).toUpperCase();
	    var aesKey = String(generateAESKey(genPassphr));
	    var encryptedMes = encryptAES(messagetrim, aesKey);

	    // Split the encrypted message into smaller pieces to fit the 1024 byte limit for SIP messages, which is hard-coded in Asterisk
	    var messageprearr = [];
	    var messageprearr = splitString(encryptedMes, 900);

	    var genMessageId =  Date.now() + Math.floor(Math.random()*10000).toString(16).toUpperCase();
	    var messageIdent = genMessageId + profileUser;
	    var firstPieceCheck = [];
	    firstPieceCheck = [messageIdent, 1];
	    var lastPieceCheck = [];
	    lastPieceCheck = [messageIdent, 0];

	    // Get the public key of the extension to which we send the message
	    var chatPubKey = '';
	    $.ajax({
		     type: "POST",
		     url: "src/get-text-chat-pub-key.php",
		     dataType: "JSON",
		     data: { 
			     recsendsipuser: buddyObj.ExtNo,
		             s_ajax_call: validateSToken
		     },
		     success: function(result) {

		                if (result.resmessage == 'success') {

                                    var destExtChatRSAPubKey = "`" + result.chatkey.join("") + "`";

				    // Encrypt the AES key with RSA
				    var newencrypt = new JSEncrypt();
				    newencrypt.setPublicKey(destExtChatRSAPubKey);
				    var encmIdAesKey = newencrypt.encrypt(aesKey);

				    // Add the encrypted AES key as the first element of the array of message pieces
				    messageprearr.unshift(encmIdAesKey);

				    for (var k = 0; k < messageprearr.length; k++) {

					if (k == 0) {
					    // Send the RSA-encrypted AES key as the first piece of the message
					    var messagePiece = messageIdent + "|" + messageprearr.length + "|" + parseInt(k) + "||" + messageprearr[k];
					} else if (sendFileCheck == 0 && messageprearr[k] != '' && sendRecFileCheck == 0) {
					    var messagePiece = messageIdent + "|" + messageprearr.length + "|" + parseInt(k) + "||" + messageprearr[k];
					} else if (sendFileCheck == 1 && sendFileChatErr == '') {
					    var messagePiece = messageIdent + "|" + messageprearr.length + "|" + parseInt(k) + "|" + upFileName + "|" + messageprearr[k];
					} else if (sendFileCheck == 1 && sendFileChatErr != '') {
					    $("#sendFileFormChat").remove();
					    sendFileCheck = 0;
					    sendFileChatErr = '';
					    return;
					} else if (sendRecFileCheck == 1 && sendRecFileChatErr == '') {
					    var messagePiece = messageIdent + "|" + messageprearr.length + "|" + parseInt(k) + "|" + upRecFileName + "|" + messageprearr[k];
					} else if (sendRecFileCheck == 1 && sendRecFileChatErr != '') {
					    $("#sendRecordedAudio").remove();
					    sendRecFileCheck = 0;
					    sendRecFileChatErr = '';
					    return;
					}

					if (k == 1) { firstPieceCheck = [messageIdent, 0]; }

					if (k == (messageprearr.length - 1)) { lastPieceCheck = [messageIdent, 1]; }

					SendChatMessageProc(buddy, buddyObj, messageIdent, messagePiece, messagetrim, firstPieceCheck, lastPieceCheck);
				    }

		                } else {
		                    pubKeyCheck = 1;
                                    var sntmgrinstmstts = $("#showTextMessagemgr .mgrImSentStatus").last();
                                    sntmgrinstmstts.removeClass("fa-check");
                                    sntmgrinstmstts.addClass("fa-question-circle-o");
                                    sntmgrinstmstts.attr("title", "There were problems while sending some instant messages.");
                                    sntmgrinstmstts.css("color", "#b82424");
		                    alert('An error occurred while retrieving the public key for text chat!');
		                }

		     },
		     error: function(result) {
                            var sntmgrinstmstts = $("#showTextMessagemgr .mgrImSentStatus").last();
                            sntmgrinstmstts.removeClass("fa-check");
                            sntmgrinstmstts.addClass("fa-question-circle-o");
                            sntmgrinstmstts.attr("title", "There were problems while sending some instant messages.");
                            sntmgrinstmstts.css("color", "#b82424");
		            alert('An error occurred while attempting to retrieve the public key for text chat!');
		     }
	    });

    });
}

function SendChatMessageProc(buddy, buddyObj, messageIdent, messagePiece, messagetrim, firstPieceCheck, lastPieceCheck) {

    var messageId = messageIdent;

    if (pubKeyCheck == 1) { pubKeyCheck = 0; $("#selectedFile").val(""); $("#sendFileLoader").remove(); return; }

    sendFileChatErr = '';

    $("#sendFileFormChat").on('submit', function(ev) {
             ev.preventDefault();

             $.ajax({
                type: 'POST',
                url: 'src/text-chat-upload-file.php',
                data: new FormData(this),
                dataType: "JSON",
                contentType: false,
                cache: false,
                processData:false,
                success: function(respdata) {
                            $("#sendFileLoader").remove();
                            if (respdata.error != '') { 
                                sendFileChatErr = respdata.error;
                                $("#sendFileFormChat").remove();
                                alert("Error: " + respdata.error);
                            }
                },
                error: function(respdata) {
                            alert("An error occurred while sending the file!");
                }          
             });
    });

    if (sendFileCheck == 1 && (firstPieceCheck[0] == messageIdent && firstPieceCheck[1] == 1)) {
        $("#submitFileChat").click();
    } else if (sendRecFileCheck == 1 && (firstPieceCheck[0] == messageIdent && firstPieceCheck[1] == 1)) {
        if ($("#sendRecAudioFl").length != 0) { $("#sendRecAudioFl").click(); } else { $("#sendRecVideoFl").click(); }
    }

    if (sendFileCheck == 1 && lastPieceCheck[0] == messageIdent && lastPieceCheck[1] == 1 && sendFileChatErr != '') {
        $("#sendFileFormChat").remove();
        $("#sendFileLoader").remove();
        sendFileChatErr = '';
        sendFileCheck = 0;
        return;
    }

    if (sendRecFileCheck == 1 && lastPieceCheck[0] == messageIdent && lastPieceCheck[1] == 1 && sendRecFileChatErr != '') {
        $("#sendRecordedAudio").remove();
        $("#sendFileLoader").remove();
        sendRecFileChatErr = '';
        sendRecFileCheck = 0;
        return;
    }

    if (buddyObj.type == "extension") {

          var chatBuddy = buddyObj.ExtNo + "@" + wssServer;
          console.log("MESSAGE: "+ chatBuddy + " (extension)");

          var messageObj = userAgent.message(chatBuddy, messagePiece);

            messageObj.data.direction = "outbound";
            messageObj.data.messageId = messageId;

            messageObj.on("accepted", function (response, cause) {

                if (response.status_code == 202) {

                    console.log("Message Accepted:", messageId);

                    // Update
		    if (typeof streamObj[buddy+"-stream"] == 'undefined') {
			streamObj[buddy+"-stream"] = { TotalRows: 0, DataCollection: [] };
			var currentStream = streamObj[buddy+"-stream"];
		    } else {
			var currentStream = streamObj[buddy+"-stream"];
		    }

                    if (currentStream != null || currentStream.DataCollection != null) {
                        $.each(currentStream.DataCollection, function (i, item) {
                            if (item.ItemType == "MSG" && item.ItemId == messageId) {
                                // Found
                                item.Sent = true;
                                return false;
                            }
                        });
                        RefreshStream(buddyObj);
                    }

                } else {

                    console.warn("Message Error", response.status_code, cause);

                    var sntmgrinstmstts = $("#showTextMessagemgr .mgrImSentStatus").last();
                    sntmgrinstmstts.removeClass("fa-check");
                    sntmgrinstmstts.addClass("fa-question-circle-o");
                    sntmgrinstmstts.attr("title", "There were problems while sending some instant messages.");
                    sntmgrinstmstts.css("color", "#b82424");


                    // Update
		    if (typeof streamObj[buddy+"-stream"] == 'undefined') {
			streamObj[buddy+"-stream"] = { TotalRows: 0, DataCollection: [] };
			var currentStream = streamObj[buddy+"-stream"];
		    } else {
			var currentStream = streamObj[buddy+"-stream"];
		    }

                    if (currentStream != null || currentStream.DataCollection != null){
                        $.each(currentStream.DataCollection, function (i, item) {
                            if (item.ItemType == "MSG" && item.ItemId == messageId) {
                                // Found
                                item.Sent = false;

                                // Change the delivery status to reflect the error
                                item.DeliveryStatus = "<i class='fa fa-exclamation-circle FailedMessage' title='Sending Message Error'></i>";

                                return false;
                            }
                        });

                        RefreshStream(buddyObj);
                    }                
                }
            });

          // Custom Web hook
          if (typeof web_hook_on_message !== 'undefined') web_hook_on_message(messageObj); 
    }

    if (lastPieceCheck[0] == messageIdent && lastPieceCheck[1] == 1) {

       // Update Stream
       var DateTime = moment.utc().format("YYYY-MM-DD HH:mm:ss UTC");
       var DateTimeMil = moment.utc().valueOf();
       if (typeof streamObj[buddy+"-stream"] == 'undefined') {
	   streamObj[buddy+"-stream"] = { TotalRows: 0, DataCollection: [] };
	   var currentStream = streamObj[buddy+"-stream"];
       } else {
	   var currentStream = streamObj[buddy+"-stream"];
       }

       // Add New Message
       var newMessageJson = {
           ItemId: messageId,
           ItemType: "MSG",
           ItemDate: DateTime,
           ItemDateMil: DateTimeMil,
           SrcUserId: profileUserID,
           Src: "\""+ profileName +"\" ("+ profileUser +")",
           DstUserId: buddy,
           Dst: "",
           MessageData: messagetrim
       }

       currentStream.DataCollection.push(newMessageJson);

       // If a file selected by the user is sent, add the sent file section
       if (sendFileCheck == 1) {

           $("#sendFileFormChat").remove();
           sendFileCheck = 0;

           var newDateTime = moment.utc().format("YYYY-MM-DD HH:mm:ss UTC");
           var newDateTimeMil = parseInt(moment.utc().valueOf()) + parseInt(1);

	   var newFileMessageJson = {
	        ItemId: messageId +"-attachedfile",
	        ItemType: "FILE",
	        ItemDate: newDateTime,
                ItemDateMil: newDateTimeMil,
	        SrcUserId: profileUserID,
	        Src: "\""+ profileName +"\" ("+ profileUser +")",
	        DstUserId: buddy,
	        Dst: buddyObj.ExtNo,
                SentFileName: upFileName,
	        MessageData: "Download file"
	   }

           if (sendFileChatErr == '') {
               currentStream.DataCollection.push(newFileMessageJson);
           }

       } else { $("#sendFileFormChat").remove(); }


       // If a recorded file is sent, add the sent file section
       if (sendRecFileCheck == 1) {

           $("#sendRecordedAudio,#sendRecordedVideo").remove();
           sendRecFileCheck = 0;

           var newDateTime = moment.utc().format("YYYY-MM-DD HH:mm:ss UTC");
           var newDateTimeMil = parseInt(moment.utc().valueOf()) + parseInt(1);

	   var newFileMessageJson = {
	        ItemId: messageId +"-attachedfile",
	        ItemType: "FILE",
	        ItemDate: newDateTime,
                ItemDateMil: newDateTimeMil,
	        SrcUserId: profileUserID,
	        Src: "\""+ profileName +"\" ("+ profileUser +")",
	        DstUserId: buddy,
	        Dst: buddyObj.ExtNo,
                SentFileName: upRecFileName,
	        MessageData: "Download file"
	   }

           if (sendFileChatErr == '') {
               currentStream.DataCollection.push(newFileMessageJson);
           }

       } else { $("#sendRecordedAudio,#sendRecordedVideo").remove(); }


       currentStream.TotalRows = currentStream.DataCollection.length;
    }

    // Post Add Activity
    if (lastPieceCheck[0] == messageIdent && lastPieceCheck[1] == 1) {
        $("#sendFileLoader").remove();
        $("#contact-" + buddy + "-ChatMessage").val("");
    }
    $("#contact-" + buddy + "-emoji-menu").hide();

    if(buddyObj.recognition != null){
       buddyObj.recognition.abort();
       buddyObj.recognition = null;
    }

    sendFileNoChck = 0;

    RefreshStream(buddyObj);
}

function ReceiveMessage(message) {

    var callerID = message.remoteIdentity.displayName;
    var did = message.remoteIdentity.uri.user;

    console.log("New Incoming Message!", "\""+ callerID +"\" ("+ did +")");

    message.data.direction = "inbound";

    if (did.length > DidLength) {

        // Contacts that are not extensions cannot receive Text Messages
        console.warn("DID length greater than extensions length")
        return;
    }

    var CurrentCalls = countSessions("0");

    var buddyObj = FindBuddyByDid(did);

    // Make new contact if it's not there
    if (buddyObj == null) {

        // Add Extension
        var id = uID();
        var dateNow = utcDateNow();
        var crDate = moment().format('YYYY-MM-DD HH:mm:ss');

        contactsObj.DataCollection.push({
            Type: "extension",
            LastActivity: dateNow,
            ExtensionNumber: did,
            MobileNumber: "",
            ContactNumber1: "",
            ContactNumber2: "",
            uID: id,
            cID: null,
            DisplayName: callerID,
            Position: "",
            Description: "",
            LongDescription: "",
            Fax: "",
            Email: "",
            Groups: "",
            DateAdded: crDate,
            DateModified: crDate,
            MemberCount: 0
        });
        buddyObj = new Buddy("extension", id, callerID, did, "", "", "", dateNow, "", "", "", "");
        AddBuddy(buddyObj, true, (CurrentCalls==0), true);

        // Update Size
        contactsObj.TotalRows = contactsObj.DataCollection.length;
    }

    var encryptedtext = message.body;

    var originalMessageArr = encryptedtext.split("|");

    if (originalMessageArr[3] != '' && typeof originalMessageArr[3] != 'undefined' && originalMessageArr[3] != null) {
        var fileSentDuringChat = 1;
	var recFileName = originalMessageArr[3];
    } else {
	var fileSentDuringChat = 0;
        var recFileName = '';
    }

    var currentMId = originalMessageArr[0];
    var totalPieceNo = parseInt(originalMessageArr[1]);
    var currentPieceNo = parseInt(originalMessageArr[2]);

    // Reassemble the original message and decrypt it
    if (currentPieceNo == 0) {

       if (splitMessage.hasOwnProperty(currentMId)) {

          if (currentPieceNo == 0) {
              var aesPayload = originalMessageArr[4];
              var newdecrypt = new JSEncrypt();
              newdecrypt.setPrivateKey(currentChatPrivKey);
              var decAESKeyCon = newdecrypt.decrypt(aesPayload);
              splitMessage[currentMId].aeskeyandiv = decAESKeyCon;

          } else {
              splitMessage[currentMId][currentPieceNo] = originalMessageArr[4];
          }

       } else {

          if (currentPieceNo == 0) {
              var aesPayload = originalMessageArr[4];
              var newdecrypt = new JSEncrypt();
              newdecrypt.setPrivateKey(currentChatPrivKey);
              var decAESKeyCon = newdecrypt.decrypt(aesPayload);
              splitMessage[currentMId] = { "aeskeyandiv": decAESKeyCon };

          } else {
             splitMessage[currentMId] = { [currentPieceNo]: originalMessageArr[4] };
          }

       }

       return;

    } else if (currentPieceNo < (totalPieceNo - 1)) {

        if (splitMessage.hasOwnProperty(currentMId)) {
            splitMessage[currentMId][currentPieceNo] = originalMessageArr[4];
        } else {
	    splitMessage[currentMId] = { [currentPieceNo]: originalMessageArr[4] };
        }

        return;

    } else if (currentPieceNo == (totalPieceNo - 1)) {

        if (splitMessage.hasOwnProperty(currentMId)) {
            splitMessage[currentMId][currentPieceNo] = originalMessageArr[4];
        } else {
	    splitMessage[currentMId] = { [currentPieceNo]: originalMessageArr[4] };
        }

        if (Object.keys(splitMessage[currentMId]).length == totalPieceNo) {

            var decryptAesKey = splitMessage[currentMId]["aeskeyandiv"];

            delete splitMessage[currentMId]["aeskeyandiv"];
            var origMessageEnc = '';
            var orderedMPieces = Object.keys(splitMessage[currentMId]).sort().reduce(function(obj, key) { 
                obj[key] = splitMessage[currentMId][key]; 
                return obj;
            },{});

            Object.keys(orderedMPieces).forEach(function(key, index) {
                origMessageEnc += splitMessage[currentMId][key];
            });

            var originalMessage = decryptAES(origMessageEnc, decryptAesKey);

	    delete splitMessage[currentMId];

        } else { return; }
    }

    var messageId = currentMId;
    var DateTime = moment.utc().format("YYYY-MM-DD HH:mm:ss UTC");
    var DateTimeMil = moment.utc().valueOf();

    // Current stream
    if (typeof streamObj[buddyObj.identity+"-stream"] == 'undefined') {
	streamObj[buddyObj.identity+"-stream"] = { TotalRows: 0, DataCollection: [] };
	var currentStream = streamObj[buddyObj.identity+"-stream"];
    } else {
	var currentStream = streamObj[buddyObj.identity+"-stream"];
    }

    // Add new message
    var newMessageJson = {
        ItemId: messageId,
        ItemType: "MSG",
        ItemDate: DateTime,
        ItemDateMil: DateTimeMil,
        SrcUserId: buddyObj.identity,
        Src: "\""+ buddyObj.CallerIDName +"\" ("+ buddyObj.ExtNo +")",
        DstUserId: profileUserID,
        Dst: "",
        MessageData: originalMessage
    }

    currentStream.DataCollection.push(newMessageJson);

    // If a file is received, add the received file section
    if (fileSentDuringChat == 1 && recFileName != '') {

        fileSentDuringChat = 0;
        var newDateTime = moment.utc().format("YYYY-MM-DD HH:mm:ss UTC");
        var newDateTimeMil = parseInt(moment.utc().valueOf()) + parseInt(1);

	var newFileMessageJson = {
	        ItemId: messageId +"-attachedfile",
	        ItemType: "FILE",
	        ItemDate: newDateTime,
                ItemDateMil: newDateTimeMil,
	        SrcUserId: buddyObj.identity,
                Src: "\""+ buddyObj.CallerIDName +"\" ("+ buddyObj.ExtNo +")",
	        DstUserId: profileUserID,
	        Dst: profileUser,
                ReceivedFileName: recFileName,
	        MessageData: "Download file"
	}

        currentStream.DataCollection.push(newFileMessageJson);
    }

    currentStream.TotalRows = currentStream.DataCollection.length;

    // Update Last Activity
    // ====================
    UpdateBuddyActivity(buddyObj.identity);
    RefreshStream(buddyObj);

    // Send a delivery receipt for the current message
    userAgent.message(buddyObj.ExtNo + "@" + wssServer, 'Delivery Receipt', { extraHeaders: [ 'X-DeliveryReceipt: '+ SipUsername +'|'+ currentMId +'' ] });

    // Disable/Enable the text message textarea and buttons depending on the current status of the contact
    if ($.inArray(buddyObj.presence, ["Unavailable", "Unknown", "Not online", "Do not disturb"]) !== -1) {
        $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("placeholder", lang.send_message_restrictions);
        $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("disabled", true);
        $(".roundButtonsSpec").prop("disabled", true);
    } else { 
        $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("placeholder", lang.type_your_message_here);
        $("#contact-"+ buddyObj.identity +"-ChatMessage").prop("disabled", false);
        $(".roundButtonsSpec").prop("disabled", false);
    }

    // Handle stream not visible
    // =========================
    var streamVisible = $("#stream-"+ buddyObj.identity).is(":visible");
    if (!streamVisible) {
        // Add or Increase the Badge
        IncreaseMissedBadge(buddyObj.identity);
        if ("Notification" in window) {
            if (Notification.permission === "granted") {
                var imageUrl = getPicture(buddyObj.identity);
                var noticeOptions = { body: originalMessage.substring(0, 250), icon: imageUrl }
                var inComingChatNotification = new Notification(lang.message_from + " : " + buddyObj.CallerIDName, noticeOptions);
                inComingChatNotification.onclick = function (event) {
                    // Show Message
                    SelectBuddy(buddyObj.identity);
                }
            }
        }
        // Play Alert
        console.log("Audio:", audioBlobs.Alert.url);
        var rinnger = new Audio(audioBlobs.Alert.blob);
        rinnger.preload = "auto";
        rinnger.loop = false;
        rinnger.oncanplaythrough = function(e) {
            if (typeof rinnger.sinkId !== 'undefined' && getRingerOutputID() != "default") {
                rinnger.setSinkId(getRingerOutputID()).then(function() {
                    console.log("Set sinkId to:", getRingerOutputID());
                }).catch(function(e){
                    console.warn("Failed to apply setSinkId.", e);
                });
            }
            // If there has been no interaction with the page at all... this page will not work
            rinnger.play().then(function(){
               // Audio Is Playing
            }).catch(function(e){
                console.warn("Unable to play audio file.", e);
            });
        }
        message.data.rinngerObj = rinnger; // Will be attached to this object until it's disposed.
    } else {
        // Message window is active.
    }

}
function AddCallMessage(buddy, session, reasonCode, reasonText) {

    var CallEnd = moment.utc(); // Take Now as the Hangup Time
    var callDuration = 0;
    var totalDuration = 0;
    var ringTime = 0;

    var CallStart = moment.utc(session.data.callstart.replace(" UTC", "")); // Actual start (both inbound and outbound)
    var cmDateTimeMil = moment.utc().valueOf();
    var CallAnswer = null; // On Accept when inbound, Remote Side when Outbound
    if (session.startTime) {
        // The time when WE answered the call (May be null - no answer)
        // or
        // The time when THEY answered the call (May be null - no answer)
        CallAnswer = moment.utc(session.startTime);  // Local Time gets converted to UTC 

        callDuration = moment.duration(CallEnd.diff(CallAnswer));
        ringTime = moment.duration(CallAnswer.diff(CallStart));
    }
    totalDuration = moment.duration(CallEnd.diff(CallStart));

    console.log(session.data.reasonCode + "("+ session.data.reasonText +")")

    var srcId = "";
    var srcCallerID = "";
    var dstId = ""
    var dstCallerID = "";
    if (session.data.calldirection == "inbound") {
        srcId = buddy;
        dstId = profileUserID;
        srcCallerID = "("+ session.remoteIdentity.uri.user +") "+ session.remoteIdentity.displayName;
        dstCallerID = "("+ profileUser+") "+ profileName;
    } else if(session.data.calldirection == "outbound") {
        srcId = profileUserID;
        dstId = buddy;
        srcCallerID = "("+ profileUser+") "+ profileName;
        dstCallerID = session.remoteIdentity.uri.user;
    }

    var callDirection = session.data.calldirection;
    var withVideo = session.data.withvideo;
    var sessionId = session.id;
    var hanupBy = session.data.terminateby;

    var newMessageJson = {
        CdrId: uID(),
        ItemType: "CDR",
        ItemDate: CallStart.format("YYYY-MM-DD HH:mm:ss UTC"),
        ItemDateMil: cmDateTimeMil,
        CallAnswer: (CallAnswer)? CallAnswer.format("YYYY-MM-DD HH:mm:ss UTC") : null,
        CallEnd: CallEnd.format("YYYY-MM-DD HH:mm:ss UTC"),
        SrcUserId: srcId,
        Src: srcCallerID,
        DstUserId: dstId,
        Dst: dstCallerID,
        RingTime: (ringTime != 0)? ringTime.asSeconds() : 0,
        Billsec: (callDuration != 0)? callDuration.asSeconds() : 0,
        TotalDuration: (totalDuration != 0)? totalDuration.asSeconds() : 0,
        ReasonCode: reasonCode,
        ReasonText: reasonText,
        WithVideo: withVideo,
        SessionId: sessionId,
        CallDirection: callDirection,
        Terminate: hanupBy,
        // CRM
        MessageData: null,
        Tags: [],
        // Reporting
        Transfers: (session.data.transfer)? session.data.transfer : [],
        Mutes: (session.data.mute)? session.data.mute : [],
        Holds: (session.data.hold)? session.data.hold : [],
        Recordings: (session.data.recordings)? session.data.recordings : [],
        ConfCalls: (session.data.confcalls)? session.data.confcalls : [],
        QOS: []
    }

    console.log("New CDR", newMessageJson);
    if (typeof streamObj[buddy+"-stream"] == 'undefined') {
        streamObj[buddy+"-stream"] = { TotalRows: 0, DataCollection: [] };
        streamObj[buddy+"-stream"]["DataCollection"].push(newMessageJson);
        streamObj[buddy+"-stream"]["TotalRows"] = streamObj[buddy+"-stream"]["DataCollection"].length;
    } else {
        streamObj[buddy+"-stream"]["DataCollection"].push(newMessageJson);
        streamObj[buddy+"-stream"]["TotalRows"] = streamObj[buddy+"-stream"]["DataCollection"].length;
    }

    UpdateBuddyActivity(buddy);
}

function SendImageDataMessage(buddy, ImgDataUrl) {
    if (userAgent == null) return;
    if (!userAgent.isRegistered()) return;

    // Ajax Upload
    // ===========

    var DateTime = moment.utc().format("YYYY-MM-DD HH:mm:ss UTC");
    var formattedMessage = '<IMG class=previewImage onClick="PreviewImage(this)" src="'+ ImgDataUrl +'">';
    var messageString = "<table class=\"ourChatMessage chatMessageTable\" cellspacing=0 cellpadding=0><tr><td style=\"width: 80px\">"
        + "<div class=messageDate>" + DateTime + "</div>"
        + "</td><td>"
        + "<div class=ourChatMessageText>" + formattedMessage + "</div>"
        + "</td></tr></table>";
    $("#contact-" + buddy + "-ChatHistory").append(messageString);
    updateScroll(buddy);

    ImageEditor_Cancel(buddy);

    UpdateBuddyActivity(buddy);
}

function updateLineScroll(lineNum) {
    RefreshLineActivity(lineNum);

    var element = $("#line-"+ lineNum +"-CallDetails").get(0);
    element.scrollTop = element.scrollHeight;
}
function updateScroll(buddy) {
    var history = $("#contact-"+ buddy +"-ChatHistory");
    if(history.children().length > 0) history.children().last().get(0).scrollIntoView(false);
    history.get(0).scrollTop = history.get(0).scrollHeight;

    // Disable/Enable the text message textarea and buttons depending on the current status of the contact
    var crPresence = $("#contact-"+ buddy +"-presence").text();
    if ($.inArray(crPresence, ["Unavailable", "Unknown", "Not online", "Do not disturb"]) !== -1) { 
        $("#contact-"+ buddy +"-ChatMessage").prop("placeholder", lang.send_message_restrictions);
        $("#contact-"+ buddy +"-ChatMessage").prop("disabled", true);
        $(".roundButtonsSpec").prop("disabled", true);
    } else { 
        $("#contact-"+ buddy +"-ChatMessage").prop("placeholder", lang.type_your_message_here);
        $("#contact-"+ buddy +"-ChatMessage").prop("disabled", false);
        $(".roundButtonsSpec").prop("disabled", false);
    }

}
function PreviewImage(obj){

    $.jeegoopopup.close();
    var previewimgHtml = '<div>';
    previewimgHtml += '<div class="UiWindowField scroller">';
    previewimgHtml += '<div><img src="'+ obj.src +'"/></div>';
    previewimgHtml += '</div></div>';

    $.jeegoopopup.open({
                title: 'Preview Image',
                html: previewimgHtml,
                width: '800',
                height: '600',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });
    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); } });
}

// Missed Item Notification
// ========================
function IncreaseMissedBadge(buddy) {
    var buddyObj = FindBuddyByIdentity(buddy);
    if(buddyObj == null) return;

    // Up the Missed Count
    // ===================
    buddyObj.missed += 1;

    // Take Out
    if (!$.isEmptyObject(contactsObj)) {
        $.each(contactsObj.DataCollection, function (i, item) {
            if (item.uID == buddy || item.cID == buddy) {
                item.missed = item.missed + 1;
                return false;
            }
        });
    }

    // Update Badge
    // ============
    $("#contact-" + buddy + "-missed").text(buddyObj.missed);
    $("#contact-" + buddy + "-missed").show();
    console.log("Set Missed badge for "+ buddy +" to: "+ buddyObj.missed);
}
function UpdateBuddyActivity(buddy){
    var buddyObj = FindBuddyByIdentity(buddy);
    if(buddyObj == null) return;

    // Update Last Activity Time
    // =========================
    var timeStamp = utcDateNow();
    buddyObj.lastActivity = timeStamp;
    console.log("Last Activity is now: "+ timeStamp);

    // Take Out
    if (!$.isEmptyObject(contactsObj)) {
        $.each(contactsObj.DataCollection, function (i, item) {
            if (item.uID == buddy || item.cID == buddy) {
                item.LastActivity = timeStamp;
                return false;
            }
        });
    }

    // List Update
    // ===========
    UpdateBuddyList();
}
function ClearMissedBadge(buddy) {
    var buddyObj = FindBuddyByIdentity(buddy);
    if(buddyObj == null) return;

    buddyObj.missed = 0;

    // Take Out
    if (!$.isEmptyObject(contactsObj)) {
        $.each(contactsObj.DataCollection, function (i, item) {
            if (item.uID == buddy || item.cID == buddy) {
                item.missed = 0;
                return false;
            }
        });
    }

    $("#contact-" + buddy + "-missed").text(buddyObj.missed);
    $("#contact-" + buddy + "-missed").hide(400);
}

// Outbound Calling
// ================
function VideoCall(lineObj, dialledNumber, callerNumber) {
    if (userAgent == null) return;
    if (!userAgent.isRegistered()) return;
    if(lineObj == null) return;

    if(HasAudioDevice == false){
        Alert(lang.alert_no_microphone);
        return;
    }

    if(HasVideoDevice == false){
        console.warn("No video devices (webcam) found, switching to audio call.");
        AudioCall(lineObj, dialledNumber);
        return;
    }

    var supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
    var spdOptions = {
        sessionDescriptionHandlerOptions: {
            constraints: {
                audio: { deviceId : "default" },
                video: { deviceId : "default" }
            }
        },
        extraHeaders: [ 'X-CallerNumber: '+ callerNumber ]
    }

    // Configure Audio
    var currentAudioDevice = getAudioSrcID();
    if (currentAudioDevice != "default") {
        var confirmedAudioDevice = false;
        for (var i = 0; i < AudioinputDevices.length; ++i) {
            if(currentAudioDevice == AudioinputDevices[i].deviceId) {
                confirmedAudioDevice = true;
                break;
            }
        }
        if (confirmedAudioDevice) {
            spdOptions.sessionDescriptionHandlerOptions.constraints.audio.deviceId = { exact: currentAudioDevice }
        }
        else {
            console.warn("The audio device you used before is no longer available, default settings applied.");
            localDB.setItem("AudioSrcId", "default");
        }
    }
    // Add additional Constraints
    if(supportedConstraints.autoGainControl) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.autoGainControl = AutoGainControl;
    }
    if(supportedConstraints.echoCancellation) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.echoCancellation = EchoCancellation;
    }
    if(supportedConstraints.noiseSuppression) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.noiseSuppression = NoiseSuppression;
    }

    // Configure Video
    var currentVideoDevice = getVideoSrcID();
    if (currentVideoDevice != "default") {
        var confirmedVideoDevice = false;
        for (var i = 0; i < VideoinputDevices.length; ++i) {
            if(currentVideoDevice == VideoinputDevices[i].deviceId) {
                confirmedVideoDevice = true;
                break;
            }
        }
        if(confirmedVideoDevice){
            spdOptions.sessionDescriptionHandlerOptions.constraints.video.deviceId = { exact: currentVideoDevice }
        }
        else {
            console.warn("The video device you used before is no longer available, default settings applied.");
            localDB.setItem("VideoSrcId", "default"); // resets for later and subsequent calls
        }
    }
    // Add additional Constraints
    if(supportedConstraints.frameRate && maxFrameRate != "") {
        spdOptions.sessionDescriptionHandlerOptions.constraints.video.frameRate = maxFrameRate;
    }
    if(supportedConstraints.height && videoHeight != "") {
        spdOptions.sessionDescriptionHandlerOptions.constraints.video.height = videoHeight;
    }
    console.log(supportedConstraints)
    console.log(supportedConstraints.aspectRatio)
    console.log(videoAspectRatio)
    if(supportedConstraints.aspectRatio && videoAspectRatio != "") {
        spdOptions.sessionDescriptionHandlerOptions.constraints.video.aspectRatio = videoAspectRatio;
    }

    $("#line-" + lineObj.LineNumber + "-msg").html(lang.starting_video_call);
    $("#line-" + lineObj.LineNumber + "-timer").show();

    // Invite
    console.log("INVITE (video): " + dialledNumber + "@" + wssServer, spdOptions);
    lineObj.SipSession = userAgent.invite("sip:" + dialledNumber + "@" + wssServer, spdOptions);

    var startTime = moment.utc();
    lineObj.SipSession.data.line = lineObj.LineNumber;
    lineObj.SipSession.data.buddyId = lineObj.BuddyObj.identity;
    lineObj.SipSession.data.calldirection = "outbound";
    lineObj.SipSession.data.dst = dialledNumber;
    lineObj.SipSession.data.callstart = startTime.format("YYYY-MM-DD HH:mm:ss UTC");
    lineObj.SipSession.data.callTimer = window.setInterval(function(){
        var now = moment.utc();
        var duration = moment.duration(now.diff(startTime)); 
        $("#line-" + lineObj.LineNumber + "-timer").html(formatShortDuration(duration.asSeconds()));
    }, 1000);
    lineObj.SipSession.data.VideoSourceDevice = getVideoSrcID();
    lineObj.SipSession.data.AudioSourceDevice = getAudioSrcID();
    lineObj.SipSession.data.AudioOutputDevice = getAudioOutputID();
    lineObj.SipSession.data.terminateby = "them";
    lineObj.SipSession.data.withvideo = true;

    updateLineScroll(lineObj.LineNumber);

    // Do Necessary UI Wireup
    wireupVideoSession(lineObj);

    // Custom Web hook
    if(typeof web_hook_on_invite !== 'undefined') web_hook_on_invite(lineObj.SipSession);
}

function SendSMS(buddy, obj, event) {

}

function ComposeEmail(buddy, obj, event) {

        event.stopPropagation();
        $("#sendFaxFrame").css("display", "none");
        SelectBuddy(buddy);

        var buddyObj = FindBuddyByIdentity(buddy);

	$("#roundcubeFrame").remove();
	$("#rightContent").show();
	$(".streamSelected").each(function() { $(this).css("display", "none"); });
	$("#rightContent").append('<iframe id="roundcubeFrame" name="displayFrame"></iframe>');
        $("#emailInLogoSection").css("color", "#0ca366");
        $("#faxInLogoSection").css("color", "#626262");

	var rcDomain = '';
	var rcBasicAuthUser = '';
	var rcBasicAuthPass = '';
	var rcUsername = '';
	var rcPasswd = '';

	$.ajax({
	     type: "POST",
	     url: "src/get-email-info.php",
	     dataType: "JSON",
	     data: {
		     username: userName,
		     s_ajax_call: validateSToken
	     },
	     success: function(datafromdb) {
		           rcDomain = datafromdb.rcdomain;
		           rcBasicAuthUser = encodeURIComponent(datafromdb.rcbasicauthuser);                              
		           rcBasicAuthPass = encodeURIComponent(datafromdb.rcbasicauthpass);
		           rcUsername = datafromdb.rcuser;
		           rcPasswd = datafromdb.rcpassword;

			   if (rcBasicAuthUser != '' && rcBasicAuthPass != '') { 
			       var loginURL = "https://"+ rcBasicAuthUser +":"+ rcBasicAuthPass +"@"+ rcDomain +"/";
			       var composeURL = "https://"+ rcBasicAuthUser +":"+ rcBasicAuthPass +"@"+ rcDomain +"/?_task=mail&_action=compose&_to="+ encodeURIComponent(buddyObj.Email) +"";

			   } else { 
			       var loginURL = "https://"+ rcDomain +"/";
			       var composeURL = "https://"+ rcDomain +"/?_task=mail&_action=compose&_to="+ encodeURIComponent(buddyObj.Email) +"";
			   }

			   var form = '<form id="rcForm" method="POST" action="'+ loginURL +'" target="displayFrame">'; 
			   form += '<input type="hidden" name="_action" value="login" />';
			   form += '<input type="hidden" name="_task" value="login" />';
			   form += '<input type="hidden" name="_autologin" value="1" />';
			   form += '<input name="_user" value="'+ rcUsername +'" type="text" />';
			   form += '<input name="_pass" value="'+ rcPasswd +'" type="password" />';
			   form += '<input id="submitButton" type="submit" value="Login" />';
			   form += '</form>';

			   $("#roundcubeFrame").append(form);

		           var loginURLinit = "https://"+ rcDomain +"/";

			   if (RCLoginCheck == 0) {
			       $("#submitButton").click();
			       RCLoginCheck = 1;

			       if (rcBasicAuthUser != '' && rcBasicAuthPass != '') {

				   if (confirm('You are about to log in to the site "'+ rcDomain +'" with the username "'+ rcBasicAuthUser +'".')) {
				       $("#roundcubeFrame").attr("src", composeURL);
				   }

			       } else { setTimeout(function() { $("#roundcubeFrame").attr("src", composeURL); }, 1000); }

			   } else { $("#roundcubeFrame").attr("src", composeURL); }

                           rcBasicAuthPass = '';

	     },
	     error: function(datafromdb) {
		             alert("An error occurred while trying to retrieve data from the database!");
	     }
	});

}
function AudioCallMenu(buddy, obj){

    if (($(window).width() - event.pageX) > 54) { var leftPos = event.pageX - 120; } else { var leftPos = event.pageX - 174; }
    if (($(window).height() - event.pageY) > 140) { var topPos = event.pageY + 27; } else { var topPos = event.pageY - 80; }

    var buddyObj = FindBuddyByIdentity(buddy);

    if(buddyObj.type == "extension") {

        // Extension
        var menu = "<div id=quickCallMenu>";
        menu += "<table id=quickCallTable cellspacing=10 cellpadding=0 style=\"margin-left:auto; margin-right: auto\">";

        menu += "<tr class=quickNumDialRow><td><i class=\"fa fa-phone-square\"></i></td><td>"+ lang.call_extension + "</td><td><span class=quickNumToDial>" + buddyObj.ExtNo + "</span></td></tr>";

        if (buddyObj.MobileNumber != null && buddyObj.MobileNumber != "") {
            menu += "<tr class=quickNumDialRow><td><i class=\"fa fa-mobile\"></i></td><td>"+ lang.call_mobile + "</td><td><span class=quickNumToDial>" + buddyObj.MobileNumber + "</span></td></tr>";
        }

        if (buddyObj.ContactNumber1 != null && buddyObj.ContactNumber1 != "") {
            menu += "<tr class=quickNumDialRow><td><i class=\"fa fa-phone\"></i></td><td>"+ lang.call_number + "</td><td><span class=quickNumToDial>" + buddyObj.ContactNumber1 + "</span></td></tr>";
        }

        if (buddyObj.ContactNumber2 != null && buddyObj.ContactNumber2 != "") {
            menu += "<tr class=quickNumDialRow><td><i class=\"fa fa-phone\"></i></td><td>"+ lang.call_number + "</td><td><span class=quickNumToDial>" + buddyObj.ContactNumber2 + "</span></td></tr>";
        }

        menu += "</table>";
        menu += "</div>";

        var consoleLogContent = "Menu click AudioCall("+ buddy +", ";

    } else if (buddyObj.type == "contact") {

        // Contact
        var menu = "<div id=quickCallMenu>";
        menu += "<table id=quickCallTable cellspacing=10 cellpadding=0 style=\"margin-left:auto; margin-right: auto\">";

        if (buddyObj.MobileNumber != null && buddyObj.MobileNumber != "") {
            menu += "<tr class=quickNumDialRow><td><i class=\"fa fa-mobile\"></i></td><td>"+ lang.call_mobile + "</td><td><span class=quickNumToDial>" + buddyObj.MobileNumber + "</span></td></tr>";
        }

        if (buddyObj.ContactNumber1 != null && buddyObj.ContactNumber1 != "") {
            menu += "<tr class=quickNumDialRow><td><i class=\"fa fa-phone\"></i></td><td>"+ lang.call_number + "</td><td><span class=quickNumToDial>" + buddyObj.ContactNumber1 + "</span></td></tr>";
        }

        if (buddyObj.ContactNumber2 != null && buddyObj.ContactNumber2 != "") {
            menu += "<tr class=quickNumDialRow><td><i class=\"fa fa-phone\"></i></td><td>"+ lang.call_number + "</td><td><span class=quickNumToDial>" + buddyObj.ContactNumber2 + "</span></td></tr>";
        }

        menu += "</table>";
        menu += "</div>";

        var consoleLogContent = "Menu click AudioCall("+ buddy +", ";

    }

    $.jeegoopopup.close();

    $.jeegoopopup.open({
                html: menu,
                width: '260',
                height: 'auto',
                left: leftPos,
                top: topPos,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                overlay: true,
                opacity: 0,
                draggable: false,
                resizable: false,
                fadeIn: 0
    });

    $("#quickCallTable").on("click", ".quickNumDialRow", function() {

       $("#sendFaxFrame").css("display", "none");
       $("#faxInLogoSection").css("color", "#626262");
       var NumberToDial = $(this).closest("tr").find("span.quickNumToDial").html();
       console.log(consoleLogContent + NumberToDial +")");
       DialByLine("audio", buddy, NumberToDial);
    });

    if ($(window).width() <= 625) { $.jeegoopopup.left(10); } else { $.jeegoopopup.width('260').height('auto').left(leftPos).top(topPos); }

    $(window).resize(function() {
        $.jeegoopopup.width('260').height('auto').left(leftPos).top(topPos);
    });

    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); } });
}
function AudioCall(lineObj, dialledNumber, callerNumber) {

    if (userAgent == null) return;
    if (userAgent.isRegistered() == false) return;
    if (lineObj == null) return;

    if(HasAudioDevice == false){
        Alert(lang.alert_no_microphone);
        return;
    }

    // Get the name of the SIP provider and send it to Asterisk, so that it knows what provider to use for outbound calls
    if ($("#selectFromNbDial").length) {
        var callerNmbr = $("#selectFromNbDial").val();
    } else {
        var defaultVoiceNbr = phoneNumbersFdb['default_voice_number'];
        if (defaultVoiceNbr) {
            var callerNmbr = defaultVoiceNbr;
        } else {
            var allVoiceNbs = phoneNumbersFdb['voice_numbers'];
            if (allVoiceNbs) {
                var allVoiceNbsArr = allVoiceNbs.split(",");
                var callerNmbr = allVoiceNbsArr[0];
            }
        }
    }

    if (callerNmbr.indexOf(":") >= 0) {
        var shortProv = callerNmbr.split(":");
        var sipProvider = shortProv[0].replace(" ", "");
        var callFromNumber = shortProv[1].replace(" ", "");
    } else {
        var sipProvider = 'n/a';
        var callFromNumber = callerNmbr;
    }

    var supportedConstraints = navigator.mediaDevices.getSupportedConstraints();

    var spdOptions = {
        sessionDescriptionHandlerOptions: {
            constraints: {
                audio: { deviceId : "default" },
                video: false
            }
        },
        extraHeaders: [ 'X-CallerNumber: '+ callerNumber , 'X-SipProvider: '+ sipProvider , 'X-CallFromNumber: '+ callFromNumber ]
    }
    // Configure Audio
    var currentAudioDevice = getAudioSrcID();
    if(currentAudioDevice != "default"){
        var confirmedAudioDevice = false;
        for (var i = 0; i < AudioinputDevices.length; ++i) {
            if(currentAudioDevice == AudioinputDevices[i].deviceId) {
                confirmedAudioDevice = true;
                break;
            }
        }
        if(confirmedAudioDevice) {
            spdOptions.sessionDescriptionHandlerOptions.constraints.audio.deviceId = { exact: currentAudioDevice }
        }
        else {
            console.warn("The audio device you used before is no longer available, default settings applied.");
            localDB.setItem("AudioSrcId", "default");
        }
    }

    // Add additional Constraints
    if (supportedConstraints.autoGainControl) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.autoGainControl = AutoGainControl;
    }
    if (supportedConstraints.echoCancellation) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.echoCancellation = EchoCancellation;
    }
    if (supportedConstraints.noiseSuppression) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.noiseSuppression = NoiseSuppression;
    }

    $("#line-" + lineObj.LineNumber + "-msg").html(lang.starting_audio_call);
    $("#line-" + lineObj.LineNumber + "-timer").show();

    // Invite
    console.log("INVITE (audio): " + dialledNumber + "@" + wssServer);
    lineObj.SipSession = userAgent.invite("sip:" + dialledNumber + "@" + wssServer, spdOptions);


    var startTime = moment.utc();
    lineObj.SipSession.data.line = lineObj.LineNumber;
    lineObj.SipSession.data.buddyId = lineObj.BuddyObj.identity;
    lineObj.SipSession.data.calldirection = "outbound";
    lineObj.SipSession.data.dst = dialledNumber;
    lineObj.SipSession.data.callstart = startTime.format("YYYY-MM-DD HH:mm:ss UTC");
    lineObj.SipSession.data.callTimer = window.setInterval(function(){
        var now = moment.utc();
        var duration = moment.duration(now.diff(startTime)); 
        $("#line-" + lineObj.LineNumber + "-timer").html(formatShortDuration(duration.asSeconds()));
    }, 1000);
    lineObj.SipSession.data.VideoSourceDevice = null;
    lineObj.SipSession.data.AudioSourceDevice = getAudioSrcID();
    lineObj.SipSession.data.AudioOutputDevice = getAudioOutputID();
    lineObj.SipSession.data.terminateby = "them";
    lineObj.SipSession.data.withvideo = false;

    updateLineScroll(lineObj.LineNumber);

    // Do Necessary UI Wireup
    wireupAudioSession(lineObj);

    // Custom Web hook
    if(typeof web_hook_on_invite !== 'undefined') web_hook_on_invite(lineObj.SipSession);

}

// Sessions & During Call Activity
// ===============================
function getSession(buddy) {
    if(userAgent == null) {
        console.warn("userAgent is null");
        return;
    }
    if(userAgent.isRegistered() == false) {
        console.warn("userAgent is not registered");
        return;
    }

    var rtnSession = null;
    $.each(userAgent.sessions, function (i, session) {
        if(session.data.buddyId == buddy) {
           rtnSession = session;
           return false;
        }
    });
    return rtnSession;
}
function countSessions(id){
    var rtn = 0;
    if(userAgent == null) {
       console.warn("userAgent is null");
       return 0;
    }
    $.each(userAgent.sessions, function (i, session) {
        if(id != session.id) rtn ++;
    });
    return rtn;
}
function StartRecording(lineNum){

    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null) return;

    $("#line-"+ lineObj.LineNumber +"-btn-start-recording").hide();
    $("#line-"+ lineObj.LineNumber +"-btn-stop-recording").show();

    var session = lineObj.SipSession;
    if(session == null){
        console.warn("Could not find session");
        return;
    }

    var id = uID();

    if(!session.data.recordings) session.data.recordings = [];
    session.data.recordings.push({
        uID: id,
        startTime: utcDateNow(),
        stopTime: utcDateNow(),
    });

    if(!session.data.mediaRecorder){
        console.log("Creating call recorder...");
        var recordStream = new MediaStream();
        var pc = session.sessionDescriptionHandler.peerConnection;
        pc.getSenders().forEach(function (RTCRtpSender) {
            if(RTCRtpSender.track && RTCRtpSender.track.kind == "audio") {
                console.log("Adding sender audio track to record:", RTCRtpSender.track.label);
                recordStream.addTrack(RTCRtpSender.track);
            }
        });
        pc.getReceivers().forEach(function (RTCRtpReceiver) {
            if(RTCRtpReceiver.track && RTCRtpReceiver.track.kind == "audio") {
                console.log("Adding receiver audio track to record:", RTCRtpReceiver.track.label);
                recordStream.addTrack(RTCRtpReceiver.track);
            }
            if(session.data.withvideo){
                if(RTCRtpReceiver.track && RTCRtpReceiver.track.kind == "video") {
                    console.log("Adding receiver video track to record:", RTCRtpReceiver.track.label);
                    recordStream.addTrack(RTCRtpReceiver.track);
                }
            }
        });

        // Resample the Video Recording
        if(session.data.withvideo){
            var recordingWidth = 640;
            var recordingHeight = 360;
            var pnpVideSize = 100;
            if(RecordingVideoSize == "HD"){
                recordingWidth = 1280;
                recordingHeight = 720;
                pnpVideSize = 144;
            }
            if(RecordingVideoSize == "FHD"){
                recordingWidth = 1920;
                recordingHeight = 1080;
                pnpVideSize = 240;
            }

            // them-pnp
            var pnpVideo = $("#line-" + lineObj.LineNumber + "-localVideo").get(0);
            var mainVideo = $("#line-" + lineObj.LineNumber + "-remoteVideo").get(0);
            if(RecordingLayout == "us-pnp"){
                pnpVideo = $("#line-" + lineObj.LineNumber + "-remoteVideo").get(0);
                mainVideo = $("#line-" + lineObj.LineNumber + "-localVideo").get(0);
            }
            var recordingCanvas = $('<canvas/>').get(0);
            recordingCanvas.width = (RecordingLayout == "side-by-side")? (recordingWidth * 2) + 5: recordingWidth;
            recordingCanvas.height = recordingHeight;
            var recordingContext = recordingCanvas.getContext("2d");

            window.clearInterval(session.data.recordingRedrawInterval);
            session.data.recordingRedrawInterval = window.setInterval(function(){

                // Main Video
                var videoWidth = (mainVideo.videoWidth > 0)? mainVideo.videoWidth : recordingWidth ;
                var videoHeight = (mainVideo.videoHeight > 0)? mainVideo.videoHeight : recordingHeight ;

                if(videoWidth >= videoHeight){
                    // Landscape / Square
                    var scale = recordingWidth / videoWidth;
                    videoWidth = recordingWidth;
                    videoHeight = videoHeight * scale;
                    if(videoHeight > recordingHeight){
                        var scale = recordingHeight / videoHeight;
                        videoHeight = recordingHeight;
                        videoWidth = videoWidth * scale;
                    }
                } 
                else {
                    // Portrait
                    var scale = recordingHeight / videoHeight;
                    videoHeight = recordingHeight;
                    videoWidth = videoWidth * scale;
                }
                var offsetX = (videoWidth < recordingWidth)? (recordingWidth - videoWidth) / 2 : 0;
                var offsetY = (videoHeight < recordingHeight)? (recordingHeight - videoHeight) / 2 : 0;
                if(RecordingLayout == "side-by-side") offsetX = recordingWidth + 5 + offsetX;

                // Picture-in-Picture Video
                var pnpVideoHeight = pnpVideo.videoHeight;
                var pnpVideoWidth = pnpVideo.videoWidth;
                if(pnpVideoHeight > 0){
                    if(pnpVideoWidth >= pnpVideoHeight){
                        var scale = pnpVideSize / pnpVideoHeight;
                        pnpVideoHeight = pnpVideSize;
                        pnpVideoWidth = pnpVideoWidth * scale;
                    } 
                    else{
                        var scale = pnpVideSize / pnpVideoWidth;
                        pnpVideoWidth = pnpVideSize;
                        pnpVideoHeight = pnpVideoHeight * scale;
                    }
                }
                var pnpOffsetX = 10;
                var pnpOffsetY = 10;
                if(RecordingLayout == "side-by-side"){
                    pnpVideoWidth = pnpVideo.videoWidth;
                    pnpVideoHeight = pnpVideo.videoHeight;
                    if(pnpVideoWidth >= pnpVideoHeight){
                        // Landscape / Square
                        var scale = recordingWidth / pnpVideoWidth;
                        pnpVideoWidth = recordingWidth;
                        pnpVideoHeight = pnpVideoHeight * scale;
                        if(pnpVideoHeight > recordingHeight){
                            var scale = recordingHeight / pnpVideoHeight;
                            pnpVideoHeight = recordingHeight;
                            pnpVideoWidth = pnpVideoWidth * scale;
                        }
                    } 
                    else {
                        // Portrait
                        var scale = recordingHeight / pnpVideoHeight;
                        pnpVideoHeight = recordingHeight;
                        pnpVideoWidth = pnpVideoWidth * scale;
                    }
                    pnpOffsetX = (pnpVideoWidth < recordingWidth)? (recordingWidth - pnpVideoWidth) / 2 : 0;
                    pnpOffsetY = (pnpVideoHeight < recordingHeight)? (recordingHeight - pnpVideoHeight) / 2 : 0;
                }

                // Draw Elements
                recordingContext.fillRect(0, 0, recordingCanvas.width, recordingCanvas.height);
                if(mainVideo.videoHeight > 0){
                    recordingContext.drawImage(mainVideo, offsetX, offsetY, videoWidth, videoHeight);
                }
                if(pnpVideo.videoHeight > 0 && (RecordingLayout == "side-by-side" || RecordingLayout == "us-pnp" || RecordingLayout == "them-pnp")){
                    // Only Draw the Pnp Video when needed
                    recordingContext.drawImage(pnpVideo, pnpOffsetX, pnpOffsetY, pnpVideoWidth, pnpVideoHeight);
                }
            }, Math.floor(1000/RecordingVideoFps));
            var recordingVideoMediaStream = recordingCanvas.captureStream(RecordingVideoFps);
        }

        var mixedAudioVideoRecordStream = new MediaStream();
        mixedAudioVideoRecordStream.addTrack(MixAudioStreams(recordStream).getAudioTracks()[0]);
        if(session.data.withvideo){
           mixedAudioVideoRecordStream.addTrack(recordingVideoMediaStream.getVideoTracks()[0]);
        }

        var mediaType = "audio/webm";
        if(session.data.withvideo) mediaType = "video/webm";
        var options = {
            mimeType : mediaType
        }
        var mediaRecorder = new MediaRecorder(mixedAudioVideoRecordStream, options);
        mediaRecorder.data = {}
        mediaRecorder.data.id = ""+ id;
        mediaRecorder.data.sessionId = ""+ session.id;
        mediaRecorder.data.buddyId = ""+ lineObj.BuddyObj.identity;
        mediaRecorder.ondataavailable = function(event) {
            console.log("Got Call Recording Data: ", event.data.size +"Bytes", this.data.id, this.data.buddyId, this.data.sessionId);
            // Save the Audio/Video file
            SaveCallRecording(event.data, this.data.id, this.data.buddyId, this.data.sessionId);
        }

        console.log("Starting Call Recording", id);
        session.data.mediaRecorder = mediaRecorder;
        session.data.mediaRecorder.start(); // Safari does not support timeslice
        session.data.recordings[session.data.recordings.length-1].startTime = utcDateNow();

        $("#line-" + lineObj.LineNumber + "-msg").html(lang.call_recording_started);

        updateLineScroll(lineNum);
    }
    else if(session.data.mediaRecorder.state == "inactive") {
        session.data.mediaRecorder.data = {}
        session.data.mediaRecorder.data.id = ""+ id;
        session.data.mediaRecorder.data.sessionId = ""+ session.id;
        session.data.mediaRecorder.data.buddyId = ""+ lineObj.BuddyObj.identity;

        console.log("Starting Call Recording", id);
        session.data.mediaRecorder.start();
        session.data.recordings[session.data.recordings.length-1].startTime = utcDateNow();

        $("#line-" + lineObj.LineNumber + "-msg").html(lang.call_recording_started);

        updateLineScroll(lineNum);
    } 
    else {
        console.warn("Recorder is in an unknown state");
    }
}
function SaveCallRecording(blob, id, buddy, sessionid){
    var indexedDB = window.indexedDB;
    var request = indexedDB.open("CallRecordings");
    request.onerror = function(event) {
        console.error("IndexDB Request Error:", event);
    }
    request.onupgradeneeded = function(event) {
        console.warn("Upgrade Required for IndexDB... probably because of first time use.");
        var IDB = event.target.result;

        // Create Object Store
        if(IDB.objectStoreNames.contains("Recordings") == false){
            var objectStore = IDB.createObjectStore("Recordings", { keyPath: "uID" });
            objectStore.createIndex("sessionid", "sessionid", { unique: false });
            objectStore.createIndex("bytes", "bytes", { unique: false });
            objectStore.createIndex("type", "type", { unique: false });
            objectStore.createIndex("mediaBlob", "mediaBlob", { unique: false });
        }
        else {
            console.warn("IndexDB requested upgrade, but object store was in place");
        }
    }
    request.onsuccess = function(event) {
        console.log("IndexDB connected to CallRecordings");

        var IDB = event.target.result;
        if(IDB.objectStoreNames.contains("Recordings") == false){
            console.warn("IndexDB CallRecordings.Recordings does not exists");
            return;
        }
        IDB.onerror = function(event) {
            console.error("IndexDB Error:", event);
        }
    
        // Prepare data to write
        var data = {
            uID: id,
            sessionid: sessionid,
            bytes: blob.size,
            type: blob.type,
            mediaBlob: blob
        }
        // Commit Transaction
        var transaction = IDB.transaction(["Recordings"], "readwrite");
        var objectStoreAdd = transaction.objectStore("Recordings").add(data);
        objectStoreAdd.onsuccess = function(event) {
            console.log("Call Recording Sucess: ", id, blob.size, blob.type, buddy, sessionid);
        }
    }
}
function StopRecording(lineNum, noConfirm){
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null) return;

    var session = lineObj.SipSession;
    if(noConfirm == true){
        // Called at the end of a call
        $("#line-"+ lineObj.LineNumber +"-btn-start-recording").show();
        $("#line-"+ lineObj.LineNumber +"-btn-stop-recording").hide();

        if(session.data.mediaRecorder){
            if(session.data.mediaRecorder.state == "recording"){
                console.log("Stopping Call Recording");
                session.data.mediaRecorder.stop();
                session.data.recordings[session.data.recordings.length-1].stopTime = utcDateNow();
                window.clearInterval(session.data.recordingRedrawInterval);

                $("#line-" + lineObj.LineNumber + "-msg").html(lang.call_recording_stopped);

                updateLineScroll(lineNum);
            } 
            else{
                console.warn("Recorder is in an unknow state");
            }
        }
        return;
    } 
    else {
        // User attempts to end call recording
        Confirm(lang.confirm_stop_recording, lang.stop_recording, function(){
            $("#line-"+ lineObj.LineNumber +"-btn-start-recording").show();
            $("#line-"+ lineObj.LineNumber +"-btn-stop-recording").hide();
    
            if(session.data.mediaRecorder){
                if(session.data.mediaRecorder.state == "recording"){
                    console.log("Stopping Call Recording");
                    session.data.mediaRecorder.stop();
                    session.data.recordings[session.data.recordings.length-1].stopTime = utcDateNow();
                    window.clearInterval(session.data.recordingRedrawInterval);

                    $("#line-" + lineObj.LineNumber + "-msg").html(lang.call_recording_stopped);

                    updateLineScroll(lineNum);
                }
                else{
                    console.warn("Recorder is in an unknow state");
                }
            }
        });
    }
}
function PlayAudioCallRecording(obj, cdrId, uID){
    var container = $(obj).parent();
    container.empty();

    var audioObj = new Audio();
    audioObj.autoplay = false;
    audioObj.controls = true;

    // Make sure you are playing out via the correct device
    var sinkId = getAudioOutputID();
    if (typeof audioObj.sinkId !== 'undefined') {
        audioObj.setSinkId(sinkId).then(function(){
            console.log("sinkId applied: "+ sinkId);
        }).catch(function(e){
            console.warn("Error using setSinkId: ", e);
        });
    } else {
        console.warn("setSinkId() is not possible using this browser.")
    }

    container.append(audioObj);

    // Get Call Recording
    var indexedDB = window.indexedDB;
    var request = indexedDB.open("CallRecordings");
    request.onerror = function(event) {
        console.error("IndexDB Request Error:", event);
    }
    request.onupgradeneeded = function(event) {
        console.warn("Upgrade Required for IndexDB... probably because of first time use.");
    }
    request.onsuccess = function(event) {
        console.log("IndexDB connected to CallRecordings");

        var IDB = event.target.result;
        if(IDB.objectStoreNames.contains("Recordings") == false){
            console.warn("IndexDB CallRecordings.Recordings does not exists");
            return;
        }

        var transaction = IDB.transaction(["Recordings"]);
        var objectStoreGet = transaction.objectStore("Recordings").get(uID);
        objectStoreGet.onerror = function(event) {
            console.error("IndexDB Get Error:", event);
        }
        objectStoreGet.onsuccess = function(event) {
            $("#cdr-media-meta-size-"+ cdrId +"-"+ uID).html(" Size: "+ formatBytes(event.target.result.bytes));
            $("#cdr-media-meta-codec-"+ cdrId +"-"+ uID).html(" Codec: "+ event.target.result.type);

            // Play
            audioObj.src = window.URL.createObjectURL(event.target.result.mediaBlob);
            audioObj.oncanplaythrough = function(){
                audioObj.play().then(function(){
                    console.log("Playback started");
                }).catch(function(e){
                    console.error("Error playing back file: ", e);
                });
            }
        }
    }
}
function PlayVideoCallRecording(obj, cdrId, uID, buddy){
    var container = $(obj).parent();
    container.empty();

    var videoObj = $("<video>").get(0);
    videoObj.id = "callrecording-video-"+ cdrId;
    videoObj.autoplay = false;
    videoObj.controls = true;
    videoObj.ontimeupdate = function(event){
        $("#cdr-video-meta-width-"+ cdrId +"-"+ uID).html(lang.width + " : "+ event.target.videoWidth +"px");
        $("#cdr-video-meta-height-"+ cdrId +"-"+ uID).html(lang.height +" : "+ event.target.videoHeight +"px");
    }

    var sinkId = getAudioOutputID();
    if (typeof videoObj.sinkId !== 'undefined') {
        videoObj.setSinkId(sinkId).then(function(){
            console.log("sinkId applied: "+ sinkId);
        }).catch(function(e){
            console.warn("Error using setSinkId: ", e);
        });
    } else {
        console.warn("setSinkId() is not possible using this browser.")
    }

    container.append(videoObj);

    // Get Call Recording
    var indexedDB = window.indexedDB;
    var request = indexedDB.open("CallRecordings");
    request.onerror = function(event) {
        console.error("IndexDB Request Error:", event);
    }
    request.onupgradeneeded = function(event) {
        console.warn("Upgrade Required for IndexDB... probably because of first time use.");
    }
    request.onsuccess = function(event) {
        console.log("IndexDB connected to CallRecordings");

        var IDB = event.target.result;
        if(IDB.objectStoreNames.contains("Recordings") == false){
            console.warn("IndexDB CallRecordings.Recordings does not exists");
            return;
        } 

        var transaction = IDB.transaction(["Recordings"]);
        var objectStoreGet = transaction.objectStore("Recordings").get(uID);
        objectStoreGet.onerror = function(event) {
            console.error("IndexDB Get Error:", event);
        }
        objectStoreGet.onsuccess = function(event) {
            $("#cdr-media-meta-size-"+ cdrId +"-"+ uID).html(" Size: "+ formatBytes(event.target.result.bytes));
            $("#cdr-media-meta-codec-"+ cdrId +"-"+ uID).html(" Codec: "+ event.target.result.type);

            // Play
            videoObj.src = window.URL.createObjectURL(event.target.result.mediaBlob);
            videoObj.oncanplaythrough = function(){
                try{
                    videoObj.scrollIntoViewIfNeeded(false);
                } catch(e){}
                videoObj.play().then(function(){
                    console.log("Playback started");
                }).catch(function(e){
                    console.error("Error playing back file: ", e);
                });

                // Create a Post Image after a second
                if(buddy){
                    window.setTimeout(function(){
                        var canvas = $("<canvas>").get(0);
                        var videoWidth = videoObj.videoWidth;
                        var videoHeight = videoObj.videoHeight;
                        if(videoWidth > videoHeight){
                            // Landscape
                            if(videoHeight > 225){
                                var p = 225 / videoHeight;
                                videoHeight = 225;
                                videoWidth = videoWidth * p;
                            }
                        }
                        else {
                            // Portrait
                            if(videoHeight > 225){
                                var p = 225 / videoWidth;
                                videoWidth = 225;
                                videoHeight = videoHeight * p;
                            }
                        }
                        canvas.width = videoWidth;
                        canvas.height = videoHeight;
                        canvas.getContext('2d').drawImage(videoObj, 0, 0, videoWidth, videoHeight);  
                        canvas.toBlob(function(blob) {
                            var reader = new FileReader();
                            reader.readAsDataURL(blob);
                            reader.onloadend = function() {
                                var Poster = { width: videoWidth, height: videoHeight, posterBase64: reader.result }
                                console.log("Capturing Video Poster...");
    
                                // Update
//                                var currentStream = JSON.parse(localDB.getItem(buddy + "-stream"));
//                                var currentStream = streamObj[buddy +"-stream"];
			        if (typeof streamObj[buddy+"-stream"] == 'undefined') {
				    streamObj[buddy+"-stream"] = { TotalRows: 0, DataCollection: [] };
				    var currentStream = streamObj[buddy+"-stream"];
			        } else {
				    var currentStream = streamObj[buddy+"-stream"];
			        }
                                if (currentStream != null || currentStream.DataCollection != null) {
                                    $.each(currentStream.DataCollection, function(i, item) {
                                        if (item.ItemType == "CDR" && item.CdrId == cdrId) {
                                            // Found
                                            if(item.Recordings && item.Recordings.length >= 1){
                                                $.each(item.Recordings, function(r, recording) {
                                                    if(recording.uID == uID) recording.Poster = Poster;
                                                });
                                            }
                                            return false;
                                        }
                                    });
//                                    localDB.setItem(buddy + "-stream", JSON.stringify(currentStream));
                                    console.log("Capturing Video Poster, Done");
                                }
                            }
                        }, 'image/jpeg', PosterJpegQuality);
                    }, 1000);
                }
            }
        }
    }
}

// Stream Manipulations
// ====================
function MixAudioStreams(MultiAudioTackStream){
    // Takes in a MediaStream with any number of audio tracks and mixes them together

    var audioContext = null;
    try {
        window.AudioContext = window.AudioContext || window.webkitAudioContext;
        audioContext = new AudioContext();
    }
    catch(e){
        console.warn("AudioContext() not available, cannot record");
        return MultiAudioTackStream;
    }
    var mixedAudioStream = audioContext.createMediaStreamDestination();
    MultiAudioTackStream.getAudioTracks().forEach(function(audioTrack){
        var srcStream = new MediaStream();
        srcStream.addTrack(audioTrack);
        var streamSourceNode = audioContext.createMediaStreamSource(srcStream);
        streamSourceNode.connect(mixedAudioStream);
    });

    return mixedAudioStream.stream;
}


// Call Transfer & Conference
// ============================

function QuickFindBuddy(obj){

    $.jeegoopopup.close();

    var leftPos = obj.offsetWidth + 178;
    var topPos = obj.offsetHeight + 68;

    if ($(window).width() < 1467) {
        topPos = obj.offsetHeight + 164;
        if ($(window).width() < 918) {
            leftPos = obj.offsetWidth - 140;
            if ($(window).width() < 690) {
               leftPos = obj.offsetWidth - 70;
               topPos = obj.offsetHeight + 164;
            }
        }
    }

    var filter = obj.value;
    if(filter == "") return;

    console.log("Find Buddy: ", filter);

    Buddies.sort(function(a, b){
        if(a.CallerIDName < b.CallerIDName) return -1;
        if(a.CallerIDName > b.CallerIDName) return 1;
        return 0;
    });

    var visibleItems = 0;

    var menu = "<div id=\"quickSearchBuddy\">";
    menu += "<table id=\"quickSearchBdTable\" cellspacing=10 cellpadding=0 style=\"margin-left:auto; margin-right: auto\">";

    for (var b = 0; b < Buddies.length; b++) {
        var buddyObj = Buddies[b];

        // Perform Filter Display
        var display = false;
        if (buddyObj.CallerIDName.toLowerCase().indexOf(filter.toLowerCase()) > -1) display = true;
        if (buddyObj.ExtNo.toLowerCase().indexOf(filter.toLowerCase()) > -1) display = true;
        if (buddyObj.Desc.toLowerCase().indexOf(filter.toLowerCase()) > -1) display = true;
        if (buddyObj.MobileNumber.toLowerCase().indexOf(filter.toLowerCase()) > -1) display = true;
        if (buddyObj.ContactNumber1.toLowerCase().indexOf(filter.toLowerCase()) > -1) display = true;
        if (buddyObj.ContactNumber2.toLowerCase().indexOf(filter.toLowerCase()) > -1) display = true;
        if (display) {
            // Filtered Results
            var iconColor = "#404040";
            if (buddyObj.presence == "Unknown" || buddyObj.presence == "Not online" || buddyObj.presence == "Unavailable") iconColor = "#666666";
            if (buddyObj.presence == "Ready") iconColor = "#3fbd3f";
            if (buddyObj.presence == "On the phone" || buddyObj.presence == "Ringing" || buddyObj.presence == "On hold") iconColor = "#c99606";

            menu += "<tr class=\"quickFindBdTagRow\"><td></td><td><b>"+ buddyObj.CallerIDName +"</b></td><td></td></tr>";

            if (buddyObj.ExtNo != "") {
                menu += "<tr class=\"quickFindBdRow\"><td><i class=\"fa fa-phone-square\" style=\"color:"+ iconColor +"\"></i></td><td>"+ lang.extension +" ("+ buddyObj.presence + ")" +"</td><td><span class=\"quickNumFound\">"+ buddyObj.ExtNo +"</span></td></tr>";
            }
            if (buddyObj.MobileNumber != "") {
                menu += "<tr class=\"quickFindBdRow\"><td><i class=\"fa fa-mobile\"></i></td><td>"+ lang.mobile +"</td><td><span class=\"quickNumFound\">"+ buddyObj.MobileNumber +"</span></td></tr>";
            }

            if (buddyObj.ContactNumber1 != "") {
                menu += "<tr class=\"quickFindBdRow\"><td><i class=\"fa fa-phone\"></i></td><td>"+ lang.contact_number_1 +"</td><td><span class=\"quickNumFound\">"+ buddyObj.ContactNumber1 +"</span></td></tr>";
            }
            if (buddyObj.ContactNumber2 != "") {
                menu += "<tr class=\"quickFindBdRow\"><td><i class=\"fa fa-phone\"></i></td><td>"+ lang.contact_number_2 +"</td><td><span class=\"quickNumFound\">"+ buddyObj.ContactNumber2 +"</span></td></tr>";
            }

            visibleItems++;
        }
        if(visibleItems >= 5) break;
    }

    menu += "</table></div>";

    if(menu.length > 1) {
       $.jeegoopopup.open({
                html: menu,
//                width: 'auto',
                width: '330',
                height: 'auto',
                left: leftPos,
                top: topPos,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                overlay: true,
                opacity: 0,
                draggable: false,
                resizable: false,
                fadeIn: 0
       });

       $("#jg_popup_inner").focus();
       $("#jg_popup_inner #jg_popup_content #quickSearchBuddy #quickSearchBdTable").on("click", ".quickFindBdRow", function() {

          var quickFoundNum = $(this).closest("tr").find("span.quickNumFound").html();

          $.jeegoopopup.close();
          $(document).find("input[id*='-txt-FindTransferBuddy']").focus();
          $(document).find("input[id*='-txt-FindTransferBuddy']").val(quickFoundNum);
       });

       $(window).resize(function() {
         $.jeegoopopup.width('330').height('auto').left(leftPos).top(topPos);
       });

       $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); });
       $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); } });
    }
}

// Call Transfer
// =============
function StartTransferSession(lineNum) {

    var lineObj = FindLineByNumber(lineNum);

    $("#line-"+ lineNum +"-btn-Transfer").hide();
    $("#line-"+ lineNum +"-btn-CancelTransfer").show();

    holdSession(lineNum);
    $("#line-"+ lineNum +"-txt-FindTransferBuddy").val("");
    $("#line-"+ lineNum +"-txt-FindTransferBuddy").parent().show();

    $("#line-"+ lineNum +"-btn-blind-transfer").show();
    $("#line-"+ lineNum +"-btn-attended-transfer").show();
    $("#line-"+ lineNum +"-btn-complete-transfer").hide();
    $("#line-"+ lineNum +"-btn-cancel-transfer").hide();

    $("#line-"+ lineNum +"-transfer-status").hide();

    $("#line-"+ lineNum +"-Transfer").show();

    updateLineScroll(lineNum);

    // Change status to 'On hold'
    $("#contact-" + lineObj.BuddyObj.identity + "-devstate").prop("class", "dotOnHold");
    $("#contact-" + lineObj.BuddyObj.identity + "-devstate-main").prop("class", "dotOnHold");
    $("#contact-" + lineObj.BuddyObj.identity + "-presence").html("On hold");
    $("#contact-" + lineObj.BuddyObj.identity + "-presence-main").html("On hold");
}
function CancelTransferSession(lineNum) {

    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Null line or session");
        return;
    }
    var session = lineObj.SipSession;
    if(session.data.childsession){
        console.log("Child Transfer call detected:", session.data.childsession.status)
        try{
            if(session.data.childsession.status == SIP.Session.C.STATUS_CONFIRMED){
                session.data.childsession.bye();
            } 
            else{
                session.data.childsession.cancel();
            }
        } catch(e){}
    }

    $("#line-"+ lineNum +"-btn-Transfer").show();
    $("#line-"+ lineNum +"-btn-CancelTransfer").hide();

    unholdSession(lineNum);
    $("#line-"+ lineNum +"-Transfer").hide();

    updateLineScroll(lineNum);

    // Change status to 'On the phone'
    $("#contact-" + lineObj.BuddyObj.identity + "-devstate").prop("class", "dotInUse");
    $("#contact-" + lineObj.BuddyObj.identity + "-devstate-main").prop("class", "dotInUse");
    $("#contact-" + lineObj.BuddyObj.identity + "-presence").html("On the phone");
    $("#contact-" + lineObj.BuddyObj.identity + "-presence-main").html("On the phone");
}
function BlindTransfer(lineNum) {
    var dstNo = $("#line-"+ lineNum +"-txt-FindTransferBuddy").val().replace(/[^0-9\*\#\+]/g,'');
    if(dstNo == ""){
        console.warn("Cannot transfer, must be [0-9*+#]");
        return;
    }

    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Null line or session");
        return;
    }
    var session = lineObj.SipSession;

    if(!session.data.transfer) session.data.transfer = [];
    session.data.transfer.push({ 
        type: "Blind", 
        to: dstNo, 
        transferTime: utcDateNow(), 
        disposition: "refer",
        dispositionTime: utcDateNow(), 
        accept : {
            complete: null,
            eventTime: null,
            disposition: ""
        }
    });
    var transferid = session.data.transfer.length-1;

    var transferOptions  = { 
        receiveResponse: function doReceiveResponse(response){
            console.log("Blind transfer response: ", response.reason_phrase);

            session.data.terminateby = "refer";
            session.data.transfer[transferid].accept.disposition = response.reason_phrase;
            session.data.transfer[transferid].accept.eventTime = utcDateNow();

            $("#line-" + lineNum + "-msg").html("Call Blind Transfered (Accepted)");

            updateLineScroll(lineNum);
        }
    }
    console.log("REFER: ", dstNo + "@" + wssServer);
    session.refer("sip:" + dstNo + "@" + wssServer, transferOptions);
    $("#line-" + lineNum + "-msg").html(lang.call_blind_transfered);

    updateLineScroll(lineNum);
}
function AttendedTransfer(lineNum){
    var dstNo = $("#line-"+ lineNum +"-txt-FindTransferBuddy").val().replace(/[^0-9\*\#\+]/g,'');
    if(dstNo == ""){
        console.warn("Cannot transfer, must be [0-9*+#]");
        return;
    }
    
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Null line or session");
        return;
    }
    var session = lineObj.SipSession;

    $.jeegoopopup.close();

    $("#line-"+ lineNum +"-txt-FindTransferBuddy").parent().hide();
    $("#line-"+ lineNum +"-btn-blind-transfer").hide();
    $("#line-"+ lineNum +"-btn-attended-transfer").hide();

    $("#line-"+ lineNum +"-btn-complete-attended-transfer").hide();
    $("#line-"+ lineNum +"-btn-cancel-attended-transfer").hide();
    $("#line-"+ lineNum +"-btn-terminate-attended-transfer").hide();

    var newCallStatus = $("#line-"+ lineNum +"-transfer-status");
    newCallStatus.html(lang.connecting);
    newCallStatus.show();

    if(!session.data.transfer) session.data.transfer = [];
    session.data.transfer.push({ 
        type: "Attended", 
        to: dstNo, 
        transferTime: utcDateNow(), 
        disposition: "invite",
        dispositionTime: utcDateNow(), 
        accept : {
            complete: null,
            eventTime: null,
            disposition: ""
        }
    });
    var transferid = session.data.transfer.length-1;

    updateLineScroll(lineNum);

    // SDP options
    var supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
    var spdOptions = {
        sessionDescriptionHandlerOptions: {
            constraints: {
                audio: { deviceId : "default" },
                video: false
            }
        }
    }
    if(session.data.AudioSourceDevice != "default"){
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.deviceId = { exact: session.data.AudioSourceDevice }
    }
    // Add additional Constraints
    if(supportedConstraints.autoGainControl) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.autoGainControl = AutoGainControl;
    }
    if(supportedConstraints.echoCancellation) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.echoCancellation = EchoCancellation;
    }
    if(supportedConstraints.noiseSuppression) {
        spdOptions.sessionDescriptionHandlerOptions.constraints.audio.noiseSuppression = NoiseSuppression;
    }

    if(session.data.withvideo){
        spdOptions.sessionDescriptionHandlerOptions.constraints.video = true;
        if(session.data.VideoSourceDevice != "default"){
            spdOptions.sessionDescriptionHandlerOptions.constraints.video.deviceId = { exact: session.data.VideoSourceDevice }
        }
        // Add additional Constraints
        if(supportedConstraints.frameRate && maxFrameRate != "") {
            spdOptions.sessionDescriptionHandlerOptions.constraints.video.frameRate = maxFrameRate;
        }
        if(supportedConstraints.height && videoHeight != "") {
            spdOptions.sessionDescriptionHandlerOptions.constraints.video.height = videoHeight;
        }
        if(supportedConstraints.aspectRatio && videoAspectRatio != "") {
            spdOptions.sessionDescriptionHandlerOptions.constraints.video.aspectRatio = videoAspectRatio;
        }
    }

    // Create new call session
    console.log("INVITE: ", "sip:" + dstNo + "@" + wssServer);
    var newSession = userAgent.invite("sip:" + dstNo + "@" + wssServer, spdOptions);
    session.data.childsession = newSession;
    newSession.on('progress', function (response) {
        newCallStatus.html(lang.ringing);
        session.data.transfer[transferid].disposition = "progress";
        session.data.transfer[transferid].dispositionTime = utcDateNow();

        $("#line-" + lineNum + "-msg").html(lang.attended_transfer_call_started);
        
        var CancelAttendedTransferBtn = $("#line-"+ lineNum +"-btn-cancel-attended-transfer");
        CancelAttendedTransferBtn.off('click');
        CancelAttendedTransferBtn.on('click', function(){
            newSession.cancel();
            newCallStatus.html(lang.call_canceled);
            console.log("New call session canceled");

            session.data.transfer[transferid].accept.complete = false;
            session.data.transfer[transferid].accept.disposition = "cancel";
            session.data.transfer[transferid].accept.eventTime = utcDateNow();

            $("#line-" + lineNum + "-msg").html(lang.attended_transfer_call_canceled);

            updateLineScroll(lineNum);
        });
        CancelAttendedTransferBtn.show();

        updateLineScroll(lineNum);
    });
    newSession.on('accepted', function (response) {
        newCallStatus.html(lang.call_in_progress);
        $("#line-"+ lineNum +"-btn-cancel-attended-transfer").hide();
        session.data.transfer[transferid].disposition = "accepted";
        session.data.transfer[transferid].dispositionTime = utcDateNow();

        var CompleteTransferBtn = $("#line-"+ lineNum +"-btn-complete-attended-transfer");
        CompleteTransferBtn.off('click');
        CompleteTransferBtn.on('click', function(){
            var transferOptions  = { 
                receiveResponse: function doReceiveResponse(response){
                    console.log("Attended transfer response: ", response.reason_phrase);

                    session.data.terminateby = "refer";
                    session.data.transfer[transferid].accept.disposition = response.reason_phrase;
                    session.data.transfer[transferid].accept.eventTime = utcDateNow();

                    $("#line-" + lineNum + "-msg").html(lang.attended_transfer_complete_accepted);

                    updateLineScroll(lineNum);
                }
            }

            // Send REFER
            session.refer(newSession, transferOptions);

            newCallStatus.html(lang.attended_transfer_complete);
            console.log("Attended transfer complete");
            // Call will now teardown...

            session.data.transfer[transferid].accept.complete = true;
            session.data.transfer[transferid].accept.disposition = "refer";
            session.data.transfer[transferid].accept.eventTime = utcDateNow();

            $("#line-" + lineNum + "-msg").html(lang.attended_transfer_complete);

            updateLineScroll(lineNum);
        });
        CompleteTransferBtn.show();

        updateLineScroll(lineNum);

        var TerminateAttendedTransferBtn = $("#line-"+ lineNum +"-btn-terminate-attended-transfer");
        TerminateAttendedTransferBtn.off('click');
        TerminateAttendedTransferBtn.on('click', function(){
            newSession.bye();
            newCallStatus.html(lang.call_ended);
            console.log("New call session end");

            session.data.transfer[transferid].accept.complete = false;
            session.data.transfer[transferid].accept.disposition = "bye";
            session.data.transfer[transferid].accept.eventTime = utcDateNow();

            $("#line-" + lineNum + "-msg").html(lang.attended_transfer_call_ended);

            updateLineScroll(lineNum);
        });
        TerminateAttendedTransferBtn.show();

        updateLineScroll(lineNum);
    });
    newSession.on('trackAdded', function () {
        var pc = newSession.sessionDescriptionHandler.peerConnection;

        // Gets Remote Audio Track (Local audio is setup via initial GUM)
        var remoteStream = new MediaStream();
        pc.getReceivers().forEach(function (receiver) {
            if(receiver.track && receiver.track.kind == "audio"){
                remoteStream.addTrack(receiver.track);
            }
        });
        var remoteAudio = $("#line-" + lineNum + "-transfer-remoteAudio").get(0);
        remoteAudio.srcObject = remoteStream;
        remoteAudio.onloadedmetadata = function(e) {
            if (typeof remoteAudio.sinkId !== 'undefined') {
                remoteAudio.setSinkId(session.data.AudioOutputDevice).then(function(){
                    console.log("sinkId applied: "+ session.data.AudioOutputDevice);
                }).catch(function(e){
                    console.warn("Error using setSinkId: ", e);
                });            
            }
            remoteAudio.play();
        }
    });
    newSession.on('rejected', function (response, cause) {
        console.log("New call session rejected: ", cause);
        newCallStatus.html(lang.call_rejected);
        session.data.transfer[transferid].disposition = "rejected";
        session.data.transfer[transferid].dispositionTime = utcDateNow();

        $("#line-"+ lineNum +"-txt-FindTransferBuddy").parent().show();
        $("#line-"+ lineNum +"-btn-blind-transfer").show();
        $("#line-"+ lineNum +"-btn-attended-transfer").show();

        $("#line-"+ lineNum +"-btn-complete-attended-transfer").hide();
        $("#line-"+ lineNum +"-btn-cancel-attended-transfer").hide();
        $("#line-"+ lineNum +"-btn-terminate-attended-transfer").hide();

        $("#line-"+ lineNum +"-msg").html(lang.attended_transfer_call_rejected);

        updateLineScroll(lineNum);

        window.setTimeout(function(){
            newCallStatus.hide();
            updateLineScroll(lineNum);
        }, 1000);
    });
    newSession.on('terminated', function (response, cause) {
        console.log("New call session terminated: ", cause);
        newCallStatus.html(lang.call_ended);
        session.data.transfer[transferid].disposition = "terminated";
        session.data.transfer[transferid].dispositionTime = utcDateNow();

        $("#line-"+ lineNum +"-txt-FindTransferBuddy").parent().show();
        $("#line-"+ lineNum +"-btn-blind-transfer").show();
        $("#line-"+ lineNum +"-btn-attended-transfer").show();

        $("#line-"+ lineNum +"-btn-complete-attended-transfer").hide();
        $("#line-"+ lineNum +"-btn-cancel-attended-transfer").hide();
        $("#line-"+ lineNum +"-btn-terminate-attended-transfer").hide();

        $("#line-"+ lineNum +"-msg").html(lang.attended_transfer_call_terminated);

        updateLineScroll(lineNum);

        window.setTimeout(function(){
            newCallStatus.hide();
            updateLineScroll(lineNum);
        }, 1000);
    });
}

function cancelSession(lineNum) {
    var lineObj = FindLineByNumber(lineNum);
    if (lineObj == null || lineObj.SipSession == null) return;

    lineObj.SipSession.data.terminateby = "us";

    console.log("Cancelling session : "+ lineNum);
    lineObj.SipSession.cancel();

    $("#line-" + lineNum + "-msg").html(lang.call_canceled);

    // Ask the extension for its status
    if (lineObj.BuddyObj.ExtNo) {
        userAgent.message(lineObj.BuddyObj.ExtNo + '@' + wssServer, 'Status ?', { extraHeaders: [ 'X-CustomPresence: askforcustomstate' ] });
    }
}

function holdSession(lineNum) {
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null) return;

    console.log("Putting Call on hold: "+ lineNum);
    if(lineObj.SipSession.local_hold == false){
        lineObj.SipSession.hold();
    }
    // Log Hold
    if(!lineObj.SipSession.data.hold) lineObj.SipSession.data.hold = [];
    lineObj.SipSession.data.hold.push({ event: "hold", eventTime: utcDateNow() });

    $("#line-" + lineNum + "-btn-Hold").hide();
    $("#line-" + lineNum + "-btn-Unhold").show();
    $("#line-" + lineNum + "-msg").html(lang.call_on_hold);

    updateLineScroll(lineNum);

    // Change status to 'On hold'
    $("#contact-" + lineObj.BuddyObj.identity + "-devstate").prop("class", "dotOnHold");
    $("#contact-" + lineObj.BuddyObj.identity + "-devstate-main").prop("class", "dotOnHold");
    $("#contact-" + lineObj.BuddyObj.identity + "-presence").html("On hold");
    $("#contact-" + lineObj.BuddyObj.identity + "-presence-main").html("On hold");
}
function unholdSession(lineNum) {
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null) return;

    console.log("Taking call off hold: "+ lineNum);
    if(lineObj.SipSession.local_hold == true){
        lineObj.SipSession.unhold();
    }
    // Log Hold
    if(!lineObj.SipSession.data.hold) lineObj.SipSession.data.hold = [];
    lineObj.SipSession.data.hold.push({ event: "unhold", eventTime: utcDateNow() });

    $("#line-" + lineNum + "-msg").html(lang.call_in_progress);
    $("#line-" + lineNum + "-btn-Hold").show();
    $("#line-" + lineNum + "-btn-Unhold").hide();

    updateLineScroll(lineNum);

    // Change status to 'On the phone'
    $("#contact-" + lineObj.BuddyObj.identity + "-devstate").prop("class", "dotInUse");
    $("#contact-" + lineObj.BuddyObj.identity + "-devstate-main").prop("class", "dotInUse");
    $("#contact-" + lineObj.BuddyObj.identity + "-presence").html("On the phone");
    $("#contact-" + lineObj.BuddyObj.identity + "-presence-main").html("On the phone");
}
function endSession(lineNum) {

    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null) return;

    console.log("Ending call with: "+ lineNum);
    lineObj.SipSession.data.terminateby = "us";
    lineObj.SipSession.bye();

    $("#line-" + lineNum + "-msg").html(lang.call_ended);
    $("#line-" + lineNum + "-ActiveCall").hide();

    updateLineScroll(lineNum);

    $("#contact-" + lineObj.BuddyObj.identity + "-devstate-main").prop("class", "dotOffline");
    $("#contact-" + lineObj.BuddyObj.identity + "-presence-main").html("Unknown");

    // Ask the extension for its status
    if (lineObj.BuddyObj.ExtNo) {
        userAgent.message(lineObj.BuddyObj.ExtNo + '@' + wssServer, 'Status ?', { extraHeaders: [ 'X-CustomPresence: askforcustomstate' ] });
    }
}
function sendDTMF(lineNum, itemStr) {
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null) return;

    console.log("Sending DTMF ("+ itemStr +"): "+ lineNum);
    lineObj.SipSession.dtmf(itemStr);

    $("#line-" + lineNum + "-msg").html(lang.send_dtmf + ": "+ itemStr);

    updateLineScroll(lineNum);

    // Custom Web hook
    if(typeof web_hook_on_dtmf !== 'undefined') web_hook_on_dtmf(itemStr, lineObj.SipSession);
}
function switchVideoSource(lineNum, srcId){
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Line or Session is Null");
        return;
    }
    var session = lineObj.SipSession;

    $("#line-" + lineNum + "-msg").html(lang.switching_video_source);

    var supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
    var constraints = { 
        audio: false, 
        video: { deviceId: "default" }
    }
    if(srcId != "default"){
        constraints.video.deviceId = { exact: srcId }
    }

    // Add additional Constraints
    if(supportedConstraints.frameRate && maxFrameRate != "") {
        constraints.video.frameRate = maxFrameRate;
    }
    if(supportedConstraints.height && videoHeight != "") {
        constraints.video.height = videoHeight;
    }
    if(supportedConstraints.aspectRatio && videoAspectRatio != "") {
        constraints.video.aspectRatio = videoAspectRatio;
    }

    session.data.VideoSourceDevice = srcId;

    var pc = session.sessionDescriptionHandler.peerConnection;

    var localStream = new MediaStream();
    navigator.mediaDevices.getUserMedia(constraints).then(function(newStream){
        var newMediaTrack = newStream.getVideoTracks()[0];
        pc.getSenders().forEach(function (RTCRtpSender) {
            if(RTCRtpSender.track && RTCRtpSender.track.kind == "video") {
                console.log("Switching Video Track : "+ RTCRtpSender.track.label + " to "+ newMediaTrack.label);
                RTCRtpSender.track.stop();
                RTCRtpSender.replaceTrack(newMediaTrack);
                localStream.addTrack(newMediaTrack);
            }
        });
    }).catch(function(e){
        console.error("Error on getUserMedia", e, constraints);
    });

    // Restore Audio Stream if it was changed
    if(session.data.AudioSourceTrack && session.data.AudioSourceTrack.kind == "audio"){
        pc.getSenders().forEach(function (RTCRtpSender) {
            if(RTCRtpSender.track && RTCRtpSender.track.kind == "audio") {
                RTCRtpSender.replaceTrack(session.data.AudioSourceTrack).then(function(){
                    if(session.data.ismute){
                        RTCRtpSender.track.enabled = false;
                    }
                }).catch(function(){
                    console.error(e);
                });
                session.data.AudioSourceTrack = null;
            }
        });
    }

    // Set Preview
    console.log("Showing as preview...");
    var localVideo = $("#line-" + lineNum + "-localVideo").get(0);
    localVideo.srcObject = localStream;
    localVideo.onloadedmetadata = function(e) {
        localVideo.play();
    }
}
function SendCanvas(lineNum){
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Line or Session is Null");
        return;
    }
    var session = lineObj.SipSession;

    $("#line-" + lineNum + "-msg").html(lang.switching_to_canvas);

    // Create scratch Pad
    RemoveScratchpad(lineNum);

    var newCanvas = $('<canvas/>');
    newCanvas.prop("id", "line-" + lineNum + "-scratchpad");
    $("#line-" + lineNum + "-scratchpad-container").append(newCanvas);
    $("#line-" + lineNum + "-scratchpad").css("display", "inline-block");
    $("#line-" + lineNum + "-scratchpad").css("width", "640px"); // SD
    $("#line-" + lineNum + "-scratchpad").css("height", "360px"); // SD
    $("#line-" + lineNum + "-scratchpad").prop("width", 640); // SD
    $("#line-" + lineNum + "-scratchpad").prop("height", 360); // SD
    $("#line-" + lineNum + "-scratchpad-container").show();

    console.log("Canvas for Scratchpad created...");

    scratchpad = new fabric.Canvas("line-" + lineNum + "-scratchpad");
    scratchpad.id = "line-" + lineNum + "-scratchpad";
    scratchpad.backgroundColor = "#FFFFFF";
    scratchpad.isDrawingMode = true;
    scratchpad.renderAll();
    scratchpad.redrawIntrtval = window.setInterval(function(){
        scratchpad.renderAll();
    }, 1000);

    CanvasCollection.push(scratchpad);

    // Get The Canvas Stream
    var canvasMediaStream = $("#line-"+ lineNum +"-scratchpad").get(0).captureStream(25);
    var canvasMediaTrack = canvasMediaStream.getVideoTracks()[0];

    // Switch Tracks
    var pc = session.sessionDescriptionHandler.peerConnection;
    pc.getSenders().forEach(function (RTCRtpSender) {
        if(RTCRtpSender.track && RTCRtpSender.track.kind == "video") {
            console.log("Switching Track : "+ RTCRtpSender.track.label + " to Scratchpad Canvas");
            RTCRtpSender.track.stop();
            RTCRtpSender.replaceTrack(canvasMediaTrack);
        }
    });

    // Restore Audio Stream if it was changed
    if(session.data.AudioSourceTrack && session.data.AudioSourceTrack.kind == "audio"){
        pc.getSenders().forEach(function (RTCRtpSender) {
            if(RTCRtpSender.track && RTCRtpSender.track.kind == "audio") {
                RTCRtpSender.replaceTrack(session.data.AudioSourceTrack).then(function(){
                    if(session.data.ismute){
                        RTCRtpSender.track.enabled = false;
                    }
                }).catch(function(){
                    console.error(e);
                });
                session.data.AudioSourceTrack = null;
            }
        });
    }

    // Set Preview
    // ===========
    console.log("Showing as preview...");
    var localVideo = $("#line-" + lineNum + "-localVideo").get(0);
    localVideo.srcObject = canvasMediaStream;
    localVideo.onloadedmetadata = function(e) {
        localVideo.play();
    }
}
function SendVideo(lineNum, src){
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Line or Session is Null");
        return;
    }
    var session = lineObj.SipSession;

    $("#line-"+ lineNum +"-src-camera").prop("disabled", false);
    $("#line-"+ lineNum +"-src-canvas").prop("disabled", false);
    $("#line-"+ lineNum +"-src-desktop").prop("disabled", false);
    $("#line-"+ lineNum +"-src-video").prop("disabled", true);
    $("#line-"+ lineNum +"-src-blank").prop("disabled", false);

    $("#line-" + lineNum + "-msg").html(lang.switching_to_shared_video);

    $("#line-" + lineNum + "-scratchpad-container").hide();
    RemoveScratchpad(lineNum);
    $("#line-"+ lineNum +"-sharevideo").hide();
    $("#line-"+ lineNum +"-sharevideo").get(0).pause();
    $("#line-"+ lineNum +"-sharevideo").get(0).removeAttribute('src');
    $("#line-"+ lineNum +"-sharevideo").get(0).load();

    $("#line-"+ lineNum +"-localVideo").hide();
    $("#line-"+ lineNum +"-remoteVideo").appendTo("#line-" + lineNum + "-preview-container");

    // Create Video Object
    var newVideo = $("#line-" + lineNum + "-sharevideo");
    newVideo.prop("src", src);
    newVideo.off("loadedmetadata");
    newVideo.on("loadedmetadata", function () {
        console.log("Video can play now... ");

        // Resample Video
        var ResampleSize = 360;
        if(VideoResampleSize == "HD") ResampleSize = 720;
        if(VideoResampleSize == "FHD") ResampleSize = 1080;

        var videoObj = newVideo.get(0);
        var resampleCanvas = $('<canvas/>').get(0);

        var videoWidth = videoObj.videoWidth;
        var videoHeight = videoObj.videoHeight;
        if(videoWidth >= videoHeight){
            // Landscape / Square
            if(videoHeight > ResampleSize){
                var p = ResampleSize / videoHeight;
                videoHeight = ResampleSize;
                videoWidth = videoWidth * p;
            }
        }
        else {
            // Portrate... (phone turned on its side)
            if(videoWidth > ResampleSize){
                var p = ResampleSize / videoWidth;
                videoWidth = ResampleSize;
                videoHeight = videoHeight * p;
            }
        }

        resampleCanvas.width = videoWidth;
        resampleCanvas.height = videoHeight;
        var resampleContext = resampleCanvas.getContext("2d");

        window.clearInterval(session.data.videoResampleInterval);
        session.data.videoResampleInterval = window.setInterval(function(){
            resampleContext.drawImage(videoObj, 0, 0, videoWidth, videoHeight);
        }, 40); // 25frames per second

        // Capture the streams
        var videoMediaStream = null;
        if('captureStream' in videoObj) {
            videoMediaStream = videoObj.captureStream();
        }
        else if('mozCaptureStream' in videoObj) {
            // This doesn't really work
            // see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/captureStream
            videoMediaStream = videoObj.mozCaptureStream();
        }
        else {
            // This is not supported.
            // videoMediaStream = videoObj.webkitCaptureStream();
            console.warn("Cannot capture stream from video, this will result in no audio being transmitted.")
        }
        var resampleVideoMediaStream = resampleCanvas.captureStream(25);

        // Get the Tracks
        var videoMediaTrack = resampleVideoMediaStream.getVideoTracks()[0];

        var audioTrackFromVideo = (videoMediaStream != null )? videoMediaStream.getAudioTracks()[0] : null;

        // Switch & Merge Tracks
        var pc = session.sessionDescriptionHandler.peerConnection;
        pc.getSenders().forEach(function (RTCRtpSender) {
            if(RTCRtpSender.track && RTCRtpSender.track.kind == "video") {
                console.log("Switching Track : "+ RTCRtpSender.track.label);
                RTCRtpSender.track.stop();
                RTCRtpSender.replaceTrack(videoMediaTrack);
            }
            if(RTCRtpSender.track && RTCRtpSender.track.kind == "audio") {
                console.log("Switching to mixed Audio track on session");

                session.data.AudioSourceTrack = RTCRtpSender.track;

                var mixedAudioStream = new MediaStream();
                if(audioTrackFromVideo) mixedAudioStream.addTrack(audioTrackFromVideo);
                mixedAudioStream.addTrack(RTCRtpSender.track);
                var mixedAudioTrack = MixAudioStreams(mixedAudioStream).getAudioTracks()[0];
                mixedAudioTrack.IsMixedTrack = true;

                RTCRtpSender.replaceTrack(mixedAudioTrack);
            }
        });

        // Set Preview
        console.log("Showing as preview...");
        var localVideo = $("#line-" + lineNum + "-localVideo").get(0);
        localVideo.srcObject = videoMediaStream;
        localVideo.onloadedmetadata = function(e) {
            localVideo.play().then(function(){
                console.log("Playing Preview Video File");
            }).catch(function(e){
                console.error("Cannot play back video", e);
            });
        }
        // Play the video
        console.log("Starting Video...");
        $("#line-"+ lineNum +"-sharevideo").get(0).play();
    });

    $("#line-"+ lineNum +"-sharevideo").show();
    console.log("Video for Sharing created...");
}
function ShareScreen(lineNum){
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Line or Session is Null");
        return;
    }
    var session = lineObj.SipSession;

    $("#line-" + lineNum + "-msg").html(lang.switching_to_shared_screeen);

    var localStream = new MediaStream();
    var pc = session.sessionDescriptionHandler.peerConnection;

    if (navigator.getDisplayMedia) {
        // EDGE, legacy support
        var screenShareConstraints = { video: true, audio: false }
        navigator.getDisplayMedia(screenShareConstraints).then(function(newStream) {
            console.log("navigator.getDisplayMedia")
            var newMediaTrack = newStream.getVideoTracks()[0];
            pc.getSenders().forEach(function (RTCRtpSender) {
                if(RTCRtpSender.track && RTCRtpSender.track.kind == "video") {
                    console.log("Switching Video Track : "+ RTCRtpSender.track.label + " to Screen");
                    RTCRtpSender.track.stop();
                    RTCRtpSender.replaceTrack(newMediaTrack);
                    localStream.addTrack(newMediaTrack);
                }
            });

            // Set Preview
            // ===========
            console.log("Showing as preview...");
            var localVideo = $("#line-" + lineNum + "-localVideo").get(0);
            localVideo.srcObject = localStream;
            localVideo.onloadedmetadata = function(e) {
                localVideo.play();
            }
        }).catch(function (err) {
            console.error("Error on getUserMedia");
        });
    }
    else if (navigator.mediaDevices.getDisplayMedia) {
        // New standard
        var screenShareConstraints = { video: true, audio: false }
        navigator.mediaDevices.getDisplayMedia(screenShareConstraints).then(function(newStream) {
            console.log("navigator.mediaDevices.getDisplayMedia")
            var newMediaTrack = newStream.getVideoTracks()[0];
            pc.getSenders().forEach(function (RTCRtpSender) {
                if(RTCRtpSender.track && RTCRtpSender.track.kind == "video") {
                    console.log("Switching Video Track : "+ RTCRtpSender.track.label + " to Screen");
                    RTCRtpSender.track.stop();
                    RTCRtpSender.replaceTrack(newMediaTrack);
                    localStream.addTrack(newMediaTrack);
                }
            });

            // Set Preview
            // ===========
            console.log("Showing as preview...");
            var localVideo = $("#line-" + lineNum + "-localVideo").get(0);
            localVideo.srcObject = localStream;
            localVideo.onloadedmetadata = function(e) {
                localVideo.play();
            }
        }).catch(function (err) {
            console.error("Error on getUserMedia");
        });
    } 
    else {
        // Firefox, apparently
        var screenShareConstraints = { video: { mediaSource: 'screen' }, audio: false }
        navigator.mediaDevices.getUserMedia(screenShareConstraints).then(function(newStream) {
            console.log("navigator.mediaDevices.getUserMedia")
            var newMediaTrack = newStream.getVideoTracks()[0];
            pc.getSenders().forEach(function (RTCRtpSender) {
                if(RTCRtpSender.track && RTCRtpSender.track.kind == "video") {
                    console.log("Switching Video Track : "+ RTCRtpSender.track.label + " to Screen");
                    RTCRtpSender.track.stop();
                    RTCRtpSender.replaceTrack(newMediaTrack);
                    localStream.addTrack(newMediaTrack);
                }
            });

            // Set Preview
            console.log("Showing as preview...");
            var localVideo = $("#line-" + lineNum + "-localVideo").get(0);
            localVideo.srcObject = localStream;
            localVideo.onloadedmetadata = function(e) {
                localVideo.play();
            }
        }).catch(function (err) {
            console.error("Error on getUserMedia");
        });
    }

    // Restore Audio Stream if it was changed
    if(session.data.AudioSourceTrack && session.data.AudioSourceTrack.kind == "audio"){
        pc.getSenders().forEach(function (RTCRtpSender) {
            if(RTCRtpSender.track && RTCRtpSender.track.kind == "audio") {
                RTCRtpSender.replaceTrack(session.data.AudioSourceTrack).then(function(){
                    if(session.data.ismute){
                        RTCRtpSender.track.enabled = false;
                    }
                }).catch(function(){
                    console.error(e);
                });
                session.data.AudioSourceTrack = null;
            }
        });
    }

}
function DisableVideoStream(lineNum){
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Line or Session is Null");
        return;
    }
    var session = lineObj.SipSession;

    var pc = session.sessionDescriptionHandler.peerConnection;
    pc.getSenders().forEach(function (RTCRtpSender) {
        if(RTCRtpSender.track && RTCRtpSender.track.kind == "video") {
            console.log("Disable Video Track : "+ RTCRtpSender.track.label + "");
            RTCRtpSender.track.enabled = false; //stop();
        }
        if(RTCRtpSender.track && RTCRtpSender.track.kind == "audio") {
            if(session.data.AudioSourceTrack && session.data.AudioSourceTrack.kind == "audio"){
                RTCRtpSender.replaceTrack(session.data.AudioSourceTrack).then(function(){
                    if(session.data.ismute){
                        RTCRtpSender.track.enabled = false;
                    }
                }).catch(function(){
                    console.error(e);
                });
                session.data.AudioSourceTrack = null;
            }
        }
    });

    // Set Preview
    console.log("Showing as preview...");
    var localVideo = $("#line-" + lineNum + "-localVideo").get(0);
    localVideo.pause();
    localVideo.removeAttribute('src');
    localVideo.load();

    $("#line-" + lineNum + "-msg").html(lang.video_disabled);
}

// Phone Lines
// ===========
var Line = function(lineNumber, displayName, displayNumber, buddyObj){
    this.LineNumber = lineNumber;
    this.DisplayName = displayName;
    this.DisplayNumber = displayNumber;
    this.IsSelected = false;
    this.BuddyObj = buddyObj;
    this.SipSession = null;
    this.LocalSoundMeter = null;
    this.RemoteSoundMeter = null;
}
function ShowDial(obj) {

    var leftPos = obj.offsetWidth + 104;
    var rightPos = 0;
    var topPos = obj.offsetHeight + 131;

    if ($(window).width() <= 915) {
        leftPos = event.pageX + obj.offsetWidth - 120;
        rightPos = 0;
        topPos = event.pageY + obj.offsetHeight - 11;
    }

    var html = "<div id='mainDialPad'>";
    html += "<div id='dragMDialPad' style='display:block;position:relative;height:5px;top:-10px;color:#d0d0d0;font-size:17px;text-align:center;' title='Drag Keypad'><i class='fa fa-ellipsis-h'></i></div>";
    html += "<div style='display:block;position:relative;width:100%;text-align:center;margin:3px 0px 0px 0px;'><div style='display:block;position:relative;color:#3b3b3b;'>From:</div><div style='display:block;position:relative;'><select id='selectFromNbDial'></select></div></div>";
    html += "<div style='display:block;position:relative;width:100%;text-align:center;margin:4px 0px 0px 0px;'><div style='display:block;position:relative;color:#3b3b3b;'>To:</div><div style='display:block;position:relative;'><input id='dialText' oninput='handleDialInput(this, event)' onkeydown='dialOnkeydown(event, this)'></div></div>";
    html += "<table cellspacing='10' cellpadding='0' style='margin-left:auto;margin-right: auto'>";
    html += "<tr><td><button class=dtmfButtons onclick=\"KeyPress('1');new Audio('sounds/dtmf.ogg').play();\"><div>1</div><span>&nbsp;</span></button></td>"
    html += "<td><button class=dtmfButtons onclick=\"KeyPress('2');new Audio('sounds/dtmf.ogg').play();\"><div>2</div><span>ABC</span></button></td>"
    html += "<td><button class=dtmfButtons onclick=\"KeyPress('3');new Audio('sounds/dtmf.ogg').play();\"><div>3</div><span>DEF</span></button></td></tr>";
    html += "<tr><td><button class=dtmfButtons onclick=\"KeyPress('4');new Audio('sounds/dtmf.ogg').play();\"><div>4</div><span>GHI</span></button></td>"
    html += "<td><button class=dtmfButtons onclick=\"KeyPress('5');new Audio('sounds/dtmf.ogg').play();\"><div>5</div><span>JKL</span></button></td>"
    html += "<td><button class=dtmfButtons onclick=\"KeyPress('6');new Audio('sounds/dtmf.ogg').play();\"><div>6</div><span>MNO</span></button></td></tr>";
    html += "<tr><td><button class=dtmfButtons onclick=\"KeyPress('7');new Audio('sounds/dtmf.ogg').play();\"><div>7</div><span>PQRS</span></button></td>"
    html += "<td><button class=dtmfButtons onclick=\"KeyPress('8');new Audio('sounds/dtmf.ogg').play();\"><div>8</div><span>TUV</span></button></td>"
    html += "<td><button class=dtmfButtons onclick=\"KeyPress('9');new Audio('sounds/dtmf.ogg').play();\"><div>9</div><span>WXYZ</span></button></td></tr>";
    html += "<tr><td><button class=dtmfButtons onclick=\"KeyPress('*');new Audio('sounds/dtmf.ogg').play();\">*</button></td>"
    html += "<td><button class=dtmfButtons onclick=\"KeyPress('0');new Audio('sounds/dtmf.ogg').play();\">0</button></td>"
    html += "<td><button class=dtmfButtons onclick=\"KeyPress('#');new Audio('sounds/dtmf.ogg').play();\">#</button></td></tr>";
    html += "</table>";
    html += "<div style=\"text-align: center;\">";
    html += "<button class=\"roundButtons dialButtons\" id=dialAudio style=\"width:42px; height:42px;\" title=\""+ lang.audio_call  +"\" onclick=\"DialByLine('audio')\"><i class=\"fa fa-phone\"></i></button>";
    html += "<button class=\"roundButtons dialButtons\" id=dialVideo style=\"width:42px; height:42px; margin-left:20px\" title=\""+ lang.video_call +"\" onclick=\"DialByLine('video')\"><i class=\"fa fa-video-camera\"></i></button>";
    html += "</div></div>";

    $.jeegoopopup.open({
                html: html,
                width: '200',
                height: '360',
                left: leftPos,
                right: rightPos,
                top: topPos,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                overlay: true,
                opacity: 0,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $("#dialText").focus();

    // Show the proper phone number from the drop-down list on the main dial pad
    var allVoiceNmbrs = phoneNumbersFdb['voice_numbers'];

    var allVoiceNmbrsOpt = '';

    if (allVoiceNmbrs) {

          var allVoiceNmbrsArr = allVoiceNmbrs.split(",");
          var defVoiceNmbrFdb = phoneNumbersFdb['default_voice_number'];

          for (var v = 0; v < allVoiceNmbrsArr.length; v++) {
               if ((v == 0 && defVoiceNmbrFdb == '') || (defVoiceNmbrFdb != '' && defVoiceNmbrFdb == allVoiceNmbrsArr[v])) {
                   allVoiceNmbrsOpt += '<option value="'+ allVoiceNmbrsArr[v] +'" selected="selected" class="slctFromNmbrs">'+ allVoiceNmbrsArr[v] +'</option>';
               } else {
                   allVoiceNmbrsOpt += '<option value="'+ allVoiceNmbrsArr[v] +'" class="slctFromNmbrs">'+ allVoiceNmbrsArr[v] +'</option>';
               }
          }
    }
    allVoiceNmbrsOpt += '<option value="'+ SipUsername +'" class="slctFromNmbrs">'+'   '+ SipUsername +'</option>';
    $("#selectFromNbDial").append(allVoiceNmbrsOpt);

    if ($(window).width() <= 915) { $.jeegoopopup.right(6); } else { $.jeegoopopup.width('200').height('360').left(leftPos).top(topPos); }

    $(window).resize(function() {
        $.jeegoopopup.width('200').height('360').left(leftPos).top(topPos);
    });

    $("#jg_popup_overlay").click(function() { $("#jg_popup_b").empty(); $("#jg_popup_l").empty(); $("#windowCtrls").empty(); $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $("#jg_popup_b").empty(); $("#jg_popup_l").empty(); $("#windowCtrls").empty(); $.jeegoopopup.close(); } });
}

function handleDialInput(obj, event) {

    $("#dialText").val($("#dialText").val().replace(/[^\da-zA-Z\*\#\+]/g, "").substring(0,128));
}
function dialOnkeydown(event, obj, buddy) {
    var keycode = (event.keyCode ? event.keyCode : event.which);
    if (keycode == '13') {

        event.preventDefault();

        // Defaults to audio dial
        DialByLine('audio');

        $("#jg_popup_b").empty(); $("#jg_popup_l").empty(); $("#windowCtrls").empty(); $.jeegoopopup.close();

        return false;
    }
}
function KeyPress(num) {
    $("#dialText").val(($("#dialText").val()+num).substring(0,128));
}
function DialByLine(type, buddy, numToDial, CallerID) {

    $.jeegoopopup.close();
    $("#sendFaxFrame").css("display", "none");
    $("#faxInLogoSection").css("color", "#626262");

    if (userAgent == null || userAgent.isRegistered() == false) {
        ConfigureExtensionWindow();
        return;
    }

    var numDial = (numToDial)? numToDial : $("#dialText").val();

    numDial = numDial.replace(/[^\da-zA-Z\*\#\+]/g, "").substring(0,128);

    if (numDial.length == 0) {
        console.warn("Enter number to dial");
        return;
    }

    var callFrmNmbr = '';
    if ($("#selectFromNbDial").val()) { 
        callFrmNmbr = $("#selectFromNbDial").val().substring(5);
    } else {
        if ($("#dialText").val()) {
            if ($("#dialText").val().length > DidLength) {
                alert("You have to choose a 'From' phone number. If the 'From' drop-down list on the dial pad is empty, click on the wheel icon to go to 'Settings' > 'Audio & Video' > 'Voice phone number(s)' and enter in that field the phone numbers that you configured for voice calling in your Asterisk server and/or your SIP provider, then reload the page and check the 'From' drop-down list again.");
                return;
            }
        }
    }

    // Create a Buddy if one is not already existing
    var buddyObj = (buddy)? FindBuddyByIdentity(buddy) : FindBuddyByDid(numDial);
    if(buddyObj == null) {
        var buddyType = (numDial.length > DidLength)? "contact" : "extension";
        buddyObj = MakeBuddy(buddyType, true, false, true, (CallerID)? CallerID : numDial, numDial);
    }

    // Create a Line
    newLineNumber = newLineNumber + 1;
    lineObj = new Line(newLineNumber, buddyObj.CallerIDName, numDial, buddyObj);
    Lines.push(lineObj);
    AddLineHtml(lineObj);
    SelectLine(newLineNumber);
    UpdateBuddyList();

    // Start Call Invite
    if (type == "audio") {
        AudioCall(lineObj, numDial, callFrmNmbr);
    }
    else {
        VideoCall(lineObj, numDial, callFrmNmbr);
    }

    try{
        $("#line-" + newLineNumber).get(0).scrollIntoViewIfNeeded();
    } catch(e){}
}
function SelectLine(lineNum){

    $("#roundcubeFrame").remove();
    $("#emailInLogoSection").css("color", "#626262");
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null) return;

    var displayLineNumber = 0;
    for(var l = 0; l < Lines.length; l++) {
        if(Lines[l].LineNumber == lineObj.LineNumber) displayLineNumber = l+1;
        if(Lines[l].IsSelected == true && Lines[l].LineNumber == lineObj.LineNumber){
            // Nothing to do, you re-selected the same buddy;
            return;
        }
    }

    console.log("Selecting Line : "+ lineObj.LineNumber);

    // Can only display one thing on the right
    $(".streamSelected").each(function () {
        $(this).prop('class', 'stream');
    });
    $("#line-ui-" + lineObj.LineNumber).prop('class', 'streamSelected');

    $("#line-ui-" + lineObj.LineNumber + "-DisplayLineNo").html("<i class=\"fa fa-phone\"></i> "+ lang.line +" "+ displayLineNumber);
    $("#line-ui-" + lineObj.LineNumber + "-LineIcon").html(displayLineNumber);

    // Switch the SIP Sessions
    SwitchLines(lineObj.LineNumber);

    // Update Lines List
    for(var l = 0; l < Lines.length; l++) {
        var classStr = (Lines[l].LineNumber == lineObj.LineNumber)? "buddySelected" : "buddy";
        if(Lines[l].SipSession != null) classStr = (Lines[l].SipSession.local_hold)? "buddyActiveCallHollding" : "buddyActiveCall";

        $("#line-" + Lines[l].LineNumber).prop('class', classStr);
        Lines[l].IsSelected = (Lines[l].LineNumber == lineObj.LineNumber);
    }
    // Update Buddy List
    for(var b = 0; b < Buddies.length; b++) {
        $("#contact-" + Buddies[b].identity).prop("class", "buddy");
        Buddies[b].IsSelected = false;
    }

    // Change to Stream if in Narrow view
    UpdateUI();
}
function FindLineByNumber(lineNum) {
    for(var l = 0; l < Lines.length; l++) {
        if(Lines[l].LineNumber == lineNum) return Lines[l];
    }
    return null;
}
function AddLineHtml(lineObj) {

    var html = "<table id=\"line-ui-"+ lineObj.LineNumber +"\" class=\"stream\" cellspacing=\"5\" cellpadding=\"0\">";
    html += "<tr><td class='streamSectionRight'>";

    // Close|Return|Back Button
    html += "<div class='streamSctnRightTop'>"
    html += "<button id=\"line-"+ lineObj.LineNumber +"-btn-back\" onclick=\"CloseLine('"+ lineObj.LineNumber +"')\" class=roundButtons title=\""+ lang.back +"\"><i class=\"fa fa-chevron-left\"></i></button> ";
    html += "</div>"

    // Profile UI
    html += "<div class=contact style=\"float: left;\">";
    html += "<div id=\"line-ui-"+ lineObj.LineNumber +"-LineIcon\" class=lineIcon>"+ lineObj.LineNumber +"</div>";
    html += "<div id=\"line-ui-"+ lineObj.LineNumber +"-DisplayLineNo\" class=contactNameText><i class=\"fa fa-phone\"></i> "+ lang.line +" "+ lineObj.LineNumber +"</div>";

    html += "<div class=presenceText>"+ lineObj.DisplayName +" ("+ lineObj.DisplayNumber +")</div>";
    html += "</div>";

    // Action Buttons
    html += "<div style=\"float:right; line-height: 46px;\">";
    html += "</div>";

    // Separator --------------------------------------------------------------------------
    html += "<div style=\"clear:both; height:0px\"></div>"

    // Calling UI --------------------------------------------------------------------------
    html += "<div id=\"line-"+ lineObj.LineNumber +"-calling\">";

    // Gneral Messages
    html += "<div id=\"line-"+ lineObj.LineNumber +"-timer\" style=\"float: right; margin-top: 4px; margin-right: 10px; color: #575757; display:none;\"></div>";
    html += "<div id=\"line-"+ lineObj.LineNumber +"-msg\" class=callStatus style=\"display:none\">...</div>";

    // Dialing Out Progress
    html += "<div id=\"line-"+ lineObj.LineNumber +"-progress\" style=\"display:none; margin-top: 10px\">";
    html += "<div class=progressCall>";
    html += "<button onclick=\"cancelSession('"+ lineObj.LineNumber +"')\" class=hangupButton><i class=\"fa fa-phone\"></i>&nbsp;&nbsp;"+ lang.cancel +"</button>";
    html += "</div>";
    html += "</div>";

    // Active Call UI
    html += "<div id=\"line-"+ lineObj.LineNumber +"-ActiveCall\" style=\"display:none; margin-top: 10px;\">";

    // Group Call
    html += "<div id=\"line-"+ lineObj.LineNumber +"-conference\" style=\"display:none;\"></div>";

    // Video UI
    if(lineObj.BuddyObj.type == "extension") {
        html += "<div id=\"line-"+ lineObj.LineNumber +"-VideoCall\" class=videoCall style=\"display:none;\">";

        // Presentation
        html += "<div style=\"height:35px; line-height:35px; text-align: right\">"+ lang.present +": ";
        html += "<div class=pill-nav style=\"border-color:#333333\">";
        html += "<button id=\"line-"+ lineObj.LineNumber +"-src-camera\" onclick=\"PresentCamera('"+ lineObj.LineNumber +"')\" title=\""+ lang.camera +"\" disabled><i class=\"fa fa-video-camera\"></i></button>";
        html += "<button id=\"line-"+ lineObj.LineNumber +"-src-canvas\" onclick=\"PresentScratchpad('"+ lineObj.LineNumber +"')\" title=\""+ lang.scratchpad +"\"><i class=\"fa fa-pencil-square\"></i></button>";
        html += "<button id=\"line-"+ lineObj.LineNumber +"-src-desktop\" onclick=\"PresentScreen('"+ lineObj.LineNumber +"')\" title=\""+ lang.screen +"\"><i class=\"fa fa-desktop\"></i></button>";
        html += "<button id=\"line-"+ lineObj.LineNumber +"-src-video\" onclick=\"PresentVideo('"+ lineObj.LineNumber +"')\" title=\""+ lang.video +"\"><i class=\"fa fa-file-video-o\"></i></button>";
        html += "<button id=\"line-"+ lineObj.LineNumber +"-src-blank\" onclick=\"PresentBlank('"+ lineObj.LineNumber +"')\" title=\""+ lang.blank +"\"><i class=\"fa fa-ban\"></i></button>";
        html += "</div>";
        html += "&nbsp;<button id=\"line-"+ lineObj.LineNumber +"-expand\" onclick=\"ExpandVideoArea('"+ lineObj.LineNumber +"')\"><i class=\"fa fa-expand\"></i></button>";
        html += "<button id=\"line-"+ lineObj.LineNumber +"-restore\" onclick=\"RestoreVideoArea('"+ lineObj.LineNumber +"')\" style=\"display:none\"><i class=\"fa fa-compress\"></i></button>";
        html += "</div>";

        // Preview
        html += "<div id=\"line-"+ lineObj.LineNumber +"-preview-container\" class=PreviewContainer>";
        html += "<video id=\"line-"+ lineObj.LineNumber +"-localVideo\" muted></video>"; // Default Display
        html += "</div>";

        // Stage
        html += "<div id=\"line-"+ lineObj.LineNumber +"-stage-container\" class=StageContainer>";
        html += "<video id=\"line-"+ lineObj.LineNumber +"-remoteVideo\" muted></video>"; // Default Display
        html += "<div id=\"line-"+ lineObj.LineNumber +"-scratchpad-container\" style=\"display:none\"></div>";
        html += "<video id=\"line-"+ lineObj.LineNumber +"-sharevideo\" controls muted style=\"display:none; object-fit: contain;\"></video>";
        html += "</div>";

        html += "</div>";
    }

    // Audio Call
    html += "<div id=\"line-"+ lineObj.LineNumber +"-AudioCall\" style=\"display:none;\">";
    html += "<audio id=\"line-"+ lineObj.LineNumber+"-remoteAudio\"></audio>";
    html += "</div>";

    // In Call Buttons
    html += "<div style=\"text-align:center\">";
    html += "<div style=\"margin-top:10px\">";
    html += "<button id=\"line-"+ lineObj.LineNumber +"-btn-ShowDtmf\" onclick=\"ShowDtmfMenu(this, '"+ lineObj.LineNumber +"')\" class=\"roundButtons inCallButtons\" title=\""+ lang.show_key_pad +"\"><i class=\"fa fa-keyboard-o\"></i></button>";
    html += "<button id=\"line-"+ lineObj.LineNumber +"-btn-Mute\" onclick=\"MuteSession('"+ lineObj.LineNumber +"')\" class=\"roundButtons inCallButtons\" title=\""+ lang.mute +"\"><i class=\"fa fa-microphone-slash\"></i></button>";
    html += "<button id=\"line-"+ lineObj.LineNumber +"-btn-Unmute\" onclick=\"UnmuteSession('"+ lineObj.LineNumber +"')\" class=\"roundButtons inCallButtons\" title=\""+ lang.unmute +"\" style=\"color: red; display:none\"><i class=\"fa fa-microphone\"></i></button>";
    if (typeof MediaRecorder != "undefined") {
        // Safari: must enable in Develop > Experimental Features > MediaRecorder
        html += "<button id=\"line-"+ lineObj.LineNumber +"-btn-start-recording\" onclick=\"StartRecording('"+ lineObj.LineNumber +"')\" class=\"roundButtons inCallButtons\" title=\""+ lang.start_call_recording +"\"><i class=\"fa fa-dot-circle-o\"></i></button>";
        html += "<button id=\"line-"+ lineObj.LineNumber +"-btn-stop-recording\" onclick=\"StopRecording('"+ lineObj.LineNumber +"')\" class=\"roundButtons inCallButtons\" title=\""+ lang.stop_call_recording +"\" style=\"color: red; display:none\"><i class=\"fa fa-circle\"></i></button>";
    }
    html += "<button id=\"line-"+ lineObj.LineNumber +"-btn-Transfer\" onclick=\"StartTransferSession('"+ lineObj.LineNumber +"')\" class=\"roundButtons inCallButtons\" title=\""+ lang.transfer_call +"\"><i class=\"fa fa-reply\" style=\"transform: rotateY(180deg)\"></i></button>";
    html += "<button id=\"line-"+ lineObj.LineNumber+"-btn-CancelTransfer\" onclick=\"CancelTransferSession('"+ lineObj.LineNumber +"')\" class=\"roundButtons inCallButtons\" title=\""+ lang.cancel_transfer +"\" style=\"color: red; display:none\"><i class=\"fa fa-reply\" style=\"transform: rotateY(180deg)\"></i></button>";

    html += "<button id=\"line-"+ lineObj.LineNumber +"-btn-Hold\" onclick=\"holdSession('"+ lineObj.LineNumber +"')\" class=\"roundButtons inCallButtons\"  title=\""+ lang.hold_call +"\"><i class=\"fa fa-pause-circle\"></i></button>";
    html += "<button id=\"line-"+ lineObj.LineNumber +"-btn-Unhold\" onclick=\"unholdSession('"+ lineObj.LineNumber +"')\" class=\"roundButtons inCallButtons\" title=\""+ lang.resume_call +"\" style=\"color: red; display:none\"><i class=\"fa fa-play-circle\"></i></button>";
    html += "<button id=\"line-"+ lineObj.LineNumber +"-btn-End\" onclick=\"endSession('"+ lineObj.LineNumber +"')\" class=\"roundButtons inCallButtons hangupButton\" title=\""+ lang.end_call +"\"><i class=\"fa fa-phone\"></i></button>";
    html += "</div>";
    // Call Transfer
    html += "<div id=\"line-"+ lineObj.LineNumber +"-Transfer\" style=\"display:none\">";
    html += "<div style=\"margin-top:10px\">";
    html += "<span class=searchClean><input id=\"line-"+ lineObj.LineNumber +"-txt-FindTransferBuddy\" oninput=\"QuickFindBuddy(this,'"+ lineObj.LineNumber +"')\" type=text autocomplete=none style=\"width: 170px; color: #000000;\" autocomplete=none placeholder=\""+ lang.enter_number_or_search +"\"></span>";
    html += " <button id=\"line-"+ lineObj.LineNumber +"-btn-blind-transfer\" onclick=\"BlindTransfer('"+ lineObj.LineNumber +"')\"><i class=\"fa fa-reply\" style=\"transform: rotateY(180deg)\"></i> "+ lang.blind_transfer +"</button>"
    html += " <button id=\"line-"+ lineObj.LineNumber +"-btn-attended-transfer\" onclick=\"AttendedTransfer('"+ lineObj.LineNumber +"')\"><i class=\"fa fa-reply-all\" style=\"transform: rotateY(180deg)\"></i> "+ lang.attended_transfer +"</button>";
    html += " <button id=\"line-"+ lineObj.LineNumber +"-btn-complete-attended-transfer\" style=\"display:none\"><i class=\"fa fa-reply-all\" style=\"transform: rotateY(180deg)\"></i> "+ lang.complete_transfer +"</buuton>";
    html += " <button id=\"line-"+ lineObj.LineNumber +"-btn-cancel-attended-transfer\" style=\"display:none\"><i class=\"fa fa-phone\" style=\"transform: rotate(135deg);\"></i> "+ lang.cancel_transfer +"</buuton>";
    html += " <button id=\"line-"+ lineObj.LineNumber +"-btn-terminate-attended-transfer\" style=\"display:none\"><i class=\"fa fa-phone\" style=\"transform: rotate(135deg);\"></i> "+ lang.end_transfer_call +"</buuton>";
    html += "</div>";
    html += "<div id=\"line-"+ lineObj.LineNumber +"-transfer-status\" class=callStatus style=\"margin-top:10px; display:none\">...</div>";
    html += "<audio id=\"line-"+ lineObj.LineNumber +"-transfer-remoteAudio\" style=\"display:none\"></audio>";
    html += "</div>";

    // Monitoring
    html += "<div  id=\"line-"+ lineObj.LineNumber +"-monitoring\" style=\"margin-top:10px;margin-bottom:10px;\">";
    html += "<span style=\"vertical-align:middle;color:#9a9a9a;\"><i class=\"fa fa-microphone\"></i></span> ";
    html += "<span class=meterContainer title=\""+ lang.microphone_levels +"\">";
    html += "<span id=\"line-"+ lineObj.LineNumber +"-Mic\" class=meterLevel style=\"height:0%\"></span>";
    html += "</span> ";
    html += "<span style=\"vertical-align:middle;color:#9a9a9a;\"><i class=\"fa fa-volume-up\"></i></span> ";
    html += "<span class=meterContainer title=\""+ lang.speaker_levels +"\">";
    html += "<span id=\"line-"+ lineObj.LineNumber +"-Speaker\" class=meterLevel style=\"height:0%\"></span>";
    html += "</span> ";
    html += "<button id=\"line-"+ lineObj.LineNumber +"-btn-settings\" onclick=\"ChangeSettings('"+ lineObj.LineNumber +"', this)\"><i class=\"fa fa-cogs\"></i> "+ lang.device_settings +"</button>";
    html += "<button id=\"line-"+ lineObj.LineNumber +"-call-stats\" onclick=\"ShowCallStats('"+ lineObj.LineNumber +"', this)\"><i class=\"fa fa-area-chart\"></i> "+ lang.call_stats +"</button>";
    html += "</div>";

    html += "<div id=\"line-"+ lineObj.LineNumber +"-AudioStats\" class=\"audioStats cleanScroller\" style=\"display:none\">";
    html += "<div style=\"text-align:right\"><button onclick=\"HideCallStats('"+ lineObj.LineNumber +"', this)\"><i class=\"fa fa-times\" style=\"font-size:20px;\"></i></button></div>";
    html += "<fieldset class=audioStatsSet onclick=\"HideCallStats('"+ lineObj.LineNumber +"', this)\">";
    html += "<legend>"+ lang.send_statistics +"</legend>";
    html += "<canvas id=\"line-"+ lineObj.LineNumber +"-AudioSendBitRate\" class=audioGraph width=600 height=160 style=\"width:600px; height:160px\"></canvas>";
    html += "<canvas id=\"line-"+ lineObj.LineNumber +"-AudioSendPacketRate\" class=audioGraph width=600 height=160 style=\"width:600px; height:160px\"></canvas>";
    html += "</fieldset>";
    html += "<fieldset class=audioStatsSet onclick=\"HideCallStats('"+ lineObj.LineNumber +"', this)\">";
    html += "<legend>"+ lang.receive_statistics +"</legend>";
    html += "<canvas id=\"line-"+ lineObj.LineNumber +"-AudioReceiveBitRate\" class=audioGraph width=600 height=160 style=\"width:600px; height:160px\"></canvas>";
    html += "<canvas id=\"line-"+ lineObj.LineNumber +"-AudioReceivePacketRate\" class=audioGraph width=600 height=160 style=\"width:600px; height:160px\"></canvas>";
    html += "<canvas id=\"line-"+ lineObj.LineNumber +"-AudioReceivePacketLoss\" class=audioGraph width=600 height=160 style=\"width:600px; height:160px\"></canvas>";
    html += "<canvas id=\"line-"+ lineObj.LineNumber +"-AudioReceiveJitter\" class=audioGraph width=600 height=160 style=\"width:600px; height:160px\"></canvas>";
    html += "<canvas id=\"line-"+ lineObj.LineNumber +"-AudioReceiveLevels\" class=audioGraph width=600 height=160 style=\"width:600px; height:160px\"></canvas>";
    html += "</fieldset>";
    html += "</div>";

    html += "</div>";
    html += "</div>";
    html += "</div>";
    html += "</td></tr>";
    html += "<tr><td class=\"streamSection streamSectionBackground\">";
    
    html += "<div id=\"line-"+ lineObj.LineNumber +"-CallDetails\" class=\"chatHistory cleanScroller\">";

    // In Call Activity
    html += "</div>";

    html += "</td></tr>";
    html += "</table>";

    $("#rightContent").append(html);
}
function RemoveLine(lineObj){
    if(lineObj == null) return;

    for(var l = 0; l < Lines.length; l++) {
        if(Lines[l].LineNumber == lineObj.LineNumber) {
            Lines.splice(l,1);
            break;
        }
    }

    CloseLine(lineObj.LineNumber);
    $("#line-ui-"+ lineObj.LineNumber).remove();

    UpdateBuddyList();

    // Rather than showing nothing, go to the last buddy selected
    // Select last user
    if(localDB.getItem("SelectedBuddy") != null){
        console.log("Selecting previously selected buddy...", localDB.getItem("SelectedBuddy"));
        SelectBuddy(localDB.getItem("SelectedBuddy"));
        UpdateUI();
    }
}
function CloseLine(lineNum){
    // Lines and Buddies (Left)
    $(".buddySelected").each(function () {
        $(this).prop('class', 'buddy');
    });
    // Streams (Right)
    $(".streamSelected").each(function () {
        $(this).prop('class', 'stream');
    });

    SwitchLines(0);

    console.log("Closing Line: "+ lineNum);
    for(var l = 0; l < Lines.length; l++){
        Lines[l].IsSelected = false;
    }
    selectedLine = null;
    for(var b = 0; b < Buddies.length; b++){
        Buddies[b].IsSelected = false;
    }
    selectedBuddy = null;

    $.jeegoopopup.close();

    // Change to Stream if in Narrow view
    UpdateUI();
}
function SwitchLines(lineNum){
    $.each(userAgent.sessions, function (i, session) {
        // All the other calls, not on hold
        if(session.local_hold == false && session.data.line != lineNum) {
            console.log("Putting an active call on hold: Line: "+ session.data.line +" buddy: "+ session.data.buddyId);
            session.hold(); // Check state

            // Log Hold
            if(!session.data.hold) session.data.hold = [];
            session.data.hold.push({ event: "hold", eventTime: utcDateNow() });
        }
        $("#line-" + session.data.line + "-btn-Hold").hide();
        $("#line-" + session.data.line + "-btn-Unhold").show();
        session.data.IsCurrentCall = false;
    });

    var lineObj = FindLineByNumber(lineNum);
    if(lineObj != null && lineObj.SipSession != null) {
        var session = lineObj.SipSession;
        if(session.local_hold == true) {
            console.log("Taking call off hold:  Line: "+ lineNum +" buddy: "+ session.data.buddyId);
            session.unhold();

            // Log Hold
            if(!session.data.hold) session.data.hold = [];
            session.data.hold.push({ event: "unhold", eventTime: utcDateNow() });
        }
        $("#line-" + lineNum + "-btn-Hold").show();
        $("#line-" + lineNum + "-btn-Unhold").hide();
        session.data.IsCurrentCall = true;
    }
    selectedLine = lineNum;

    RefreshLineActivity(lineNum);
}
function RefreshLineActivity(lineNum){
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null) {
        return;
    }
    var session = lineObj.SipSession;

    $("#line-"+ lineNum +"-CallDetails").empty();

    var callDetails = [];

    var ringTime = 0;
    var CallStart = moment.utc(session.data.callstart.replace(" UTC", ""));
    var CallAnswer = null;
    if(session.startTime){
        CallAnswer = moment.utc(session.startTime);
        ringTime = moment.duration(CallAnswer.diff(CallStart));
    }
    CallStart = CallStart.format("YYYY-MM-DD HH:mm:ss UTC")
    CallAnswer = (CallAnswer)? CallAnswer.format("YYYY-MM-DD HH:mm:ss UTC") : null,
    ringTime = (ringTime != 0)? ringTime.asSeconds() : 0

    var srcCallerID = "";
    var dstCallerID = "";
    if(session.data.calldirection == "inbound") {
//        srcCallerID = "<"+ session.remoteIdentity.uri.user +"> "+ session.remoteIdentity.displayName;
        srcCallerID = "("+ session.remoteIdentity.uri.user +") "+ session.remoteIdentity.displayName;
    } 
    else if(session.data.calldirection == "outbound") {
        dstCallerID = session.remoteIdentity.uri.user;
    }

    var withVideo = (session.data.withvideo)? "("+ lang.with_video +")" : "";
    var startCallMessage = (session.data.calldirection == "inbound")? lang.you_received_a_call_from + " " + srcCallerID  +" "+ withVideo : lang.you_made_a_call_to + " " + dstCallerID +" "+ withVideo;
    callDetails.push({ 
        Message: startCallMessage,
        TimeStr : CallStart
    });
    if(CallAnswer){
        var answerCallMessage = (session.data.calldirection == "inbound")? lang.you_answered_after + " " + ringTime + " " + lang.seconds_plural : lang.they_answered_after + " " + ringTime + " " + lang.seconds_plural;
        callDetails.push({ 
            Message: answerCallMessage,
            TimeStr : CallAnswer
        });
    }

    var Transfers = (session.data.transfer)? session.data.transfer : [];
    $.each(Transfers, function(item, transfer){
        var msg = (transfer.type == "Blind")? lang.you_started_a_blind_transfer_to +" "+ transfer.to +". " : lang.you_started_an_attended_transfer_to + " "+ transfer.to +". ";
        if(transfer.accept && transfer.accept.complete == true){
            msg += lang.the_call_was_completed
        }
        else if(transfer.accept.disposition != "") {
            msg += lang.the_call_was_not_completed +" ("+ transfer.accept.disposition +")"
        }
        callDetails.push({
            Message : msg,
            TimeStr : transfer.transferTime
        });
    });
    var Mutes = (session.data.mute)? session.data.mute : []
    $.each(Mutes, function(item, mute){
        callDetails.push({
            Message : (mute.event == "mute")? lang.you_put_the_call_on_mute : lang.you_took_the_call_off_mute,
            TimeStr : mute.eventTime
        });
    });
    var Holds = (session.data.hold)? session.data.hold : []
    $.each(Holds, function(item, hold){
        callDetails.push({
            Message : (hold.event == "hold")? lang.you_put_the_call_on_hold : lang.you_took_the_call_off_hold,
            TimeStr : hold.eventTime
        });
    });
    var Recordings = (session.data.recordings)? session.data.recordings : []
    $.each(Recordings, function(item, recording){
        var msg = lang.call_is_being_recorded;
        if(recording.startTime != recording.stopTime){
            msg += "("+ lang.now_stopped +")"
        }
        callDetails.push({
            Message : msg,
            TimeStr : recording.startTime
        });
    });
    var ConfCalls = (session.data.confcalls)? session.data.confcalls : []
    $.each(ConfCalls, function(item, confCall){
        var msg = lang.you_started_a_conference_call_to +" "+ confCall.to +". ";
        if(confCall.accept && confCall.accept.complete == true){
            msg += lang.the_call_was_completed
        }
        else if(confCall.accept.disposition != "") {
            msg += lang.the_call_was_not_completed +" ("+ confCall.accept.disposition +")"
        }
        callDetails.push({
            Message : msg,
            TimeStr : confCall.startTime
        });
    });

    callDetails.sort(function(a, b){
        var aMo = moment.utc(a.TimeStr.replace(" UTC", ""));
        var bMo = moment.utc(b.TimeStr.replace(" UTC", ""));
        if (aMo.isSameOrAfter(bMo, "second")) {
            return -1;
        } else return 1;
        return 0;
    });

    $.each(callDetails, function(item, detail){
        var Time = moment.utc(detail.TimeStr.replace(" UTC", "")).local().format(DisplayTimeFormat);
        var messageString = "<table class=timelineMessage cellspacing=0 cellpadding=0><tr>"
        messageString += "<td class=timelineMessageArea>"
        messageString += "<div class=timelineMessageDate><i class=\"fa fa-circle timelineMessageDot\"></i>"+ Time +"</div>"
        messageString += "<div class=timelineMessageText>"+ detail.Message +"</div>"
        messageString += "</td>"
        messageString += "</tr></table>";
        $("#line-"+ lineNum +"-CallDetails").prepend(messageString);
    });
}

// Buddy & Contacts
// ================
var Buddy = function(type, identity, CallerIDName, ExtNo, MobileNumber, ContactNumber1, ContactNumber2, lastActivity, desc, Fax, Email, Groups) {
    this.type = type; // extension | contact
    this.identity = identity;
    this.CallerIDName = CallerIDName;
    this.Desc = desc;
    this.ExtNo = ExtNo;
    this.MobileNumber = MobileNumber;
    this.ContactNumber1 = ContactNumber1;
    this.ContactNumber2 = ContactNumber2;
    this.Fax = Fax;
    this.Email = Email;
    this.Groups = Groups;
    this.lastActivity = lastActivity; // Full Date as string eg "1208-03-21 15:34:23 UTC"
    this.devState = "dotOffline";
    this.presence = "Unknown";
    this.missed = 0;
    this.IsSelected = false;
    this.imageObjectURL = "";
}

function MakeBuddy(type, update, focus, subscribe, callerID, did) {

    var buddyObj = null;
    if(type == "contact"){
        var id = uID();
        var dateNow = utcDateNow();
        var crDate = moment().format('YYYY-MM-DD HH:mm:ss');

        contactsObj.DataCollection.push({
            Type: "contact", 
            LastActivity: dateNow,
            ExtensionNumber: "", 
            MobileNumber: "",
            ContactNumber1: did,
            ContactNumber2: "",
            uID: null,
            cID: id,
            DisplayName: callerID,
            Position: "",
            Description: "",
            LongDescription: "",
            Fax: "",
            Email: "",
            Groups: "",
            DateAdded: crDate,
            DateModified: crDate,
            MemberCount: 0
        });
        buddyObj = new Buddy("contact", id, callerID, "", "", did, "", dateNow, "", "", "", "");
        AddBuddy(buddyObj, update, focus);
    }
    else {
        var id = uID();
        var dateNow = utcDateNow();
        var crDate = moment().format('YYYY-MM-DD HH:mm:ss');

        contactsObj.DataCollection.push({
            Type: "extension",
            LastActivity: dateNow,
            ExtensionNumber: did,
            MobileNumber: "",
            ContactNumber1: "",
            ContactNumber2: "",
            uID: id,
            cID: null,
            DisplayName: callerID,
            Position: "",
            Description: "",
            LongDescription: "",
            Fax: "",
            Email: "",
            Groups: "",
            DateAdded: crDate,
            DateModified: crDate,
            MemberCount: 0
        });
        buddyObj = new Buddy("extension", id, callerID, did, "", "", "", dateNow, "", "", "", "");
        AddBuddy(buddyObj, update, focus, subscribe);
    }
    // Update Size: 
    contactsObj.TotalRows = contactsObj.DataCollection.length;

    // Return new buddy
    return buddyObj;
}
function UpdateBuddyCalerID(buddyObj, callerID) {
    buddyObj.CallerIDName = callerID;

    var buddy = buddyObj.identity;
    // Update DB
    if (!$.isEmptyObject(contactsObj)) {
        $.each(contactsObj.DataCollection, function (i, item) {
            if (item.uID == buddy || item.cID == buddy) {
                item.DisplayName = callerID;
                return false;
            }
        });
    }

    UpdateBuddyList();
}
function AddBuddy(buddyObj, update, focus, subscribe) {
    Buddies.push(buddyObj);
    if(update == true) UpdateBuddyList();
    AddBuddyMessageStream(buddyObj);
    if(focus == true) SelectBuddy(buddyObj.identity);
    if(subscribe == true) SubscribeBuddy(buddyObj);
}
function PopulateBuddyList() {
    console.log("Clearing Buddies...");
    Buddies = new Array();
    console.log("Adding Buddies...");
    if ($.isEmptyObject(contactsObj)) { return; }

    console.log("Total Buddies: " + contactsObj.TotalRows);

    $.each(contactsObj.DataCollection, function (i, item) {
        if(item.Type == "extension"){
            // extension
            var buddy = new Buddy("extension", item.uID, item.DisplayName, item.ExtensionNumber, item.MobileNumber, item.ContactNumber1, item.ContactNumber2, item.LastActivity, item.Position, item.Fax, item.Email, item.Groups);
            AddBuddy(buddy, false, false);
        }
        else if(item.Type == "contact"){
            // contact
            var buddy = new Buddy("contact", item.cID, item.DisplayName, "", item.MobileNumber, item.ContactNumber1, item.ContactNumber2, item.LastActivity, item.Description, item.Fax, item.Email, item.Groups);
            AddBuddy(buddy, false, false);
        }
    });

    // Update List (after add)
    console.log("Updating Buddy List...");
    UpdateBuddyList();
}
function UpdateBuddyList() {
    var filter = $("#txtFindBuddy").val();

    $("#myContacts").empty();

    var callCount = 0;

    for (var l = 0; l < Lines.length; l++) {

        var classStr = (Lines[l].IsSelected)? "buddySelected" : "buddy";
        if (Lines[l].SipSession != null) { classStr = (Lines[l].SipSession.local_hold)? "buddyActiveCallHollding" : "buddyActiveCall"; }

        var html = "<div id=\"line-"+ Lines[l].LineNumber +"\" class="+ classStr +" onclick=\"SelectLine('"+ Lines[l].LineNumber +"')\">";
        html += "<div class=lineIcon>"+ (l + 1) +"</div>";
        html += "<div class=contactNameText><i class=\"fa fa-phone\"></i> "+ lang.line +" "+ (l + 1) +"</div>";
        html += "<div id=\"Line-"+ Lines[l].ExtNo +"-datetime\" class=contactDate>&nbsp;</div>";
        html += "<div class=presenceText>"+ Lines[l].DisplayName +" ("+ Lines[l].DisplayNumber +")" +"</div>";
        html += "</div>";
        // SIP.Session.C.STATUS_TERMINATED
        if (Lines[l].SipSession && Lines[l].SipSession.data.earlyReject != true) {
            $("#myContacts").append(html);
            callCount ++;
        }
    }

    // Sort Buddy List
    // ===========================
    Buddies.sort(function(a, b){
        if(a.CallerIDName < b.CallerIDName) return -1;
        if(a.CallerIDName > b.CallerIDName) return 1;
        return 0;
    });

    Buddies.sort(function(c, d){
        if (c.type == "extension" && d.type == "contact") return -1;
        if (c.type == "contact" && d.type == "extension") return 1;
        return 0;
    });

    var contactNoIndex = 0;
    var extensionNoIndex = 0;

    for (var b = 0; b < Buddies.length; b++) {

        var buddyObj = Buddies[b];

        if (filter && filter.length >= 1) {
            // Perform Filter Display
            var display = false;
            if(buddyObj.CallerIDName.toLowerCase().indexOf(filter.toLowerCase()) > -1 ) display = true;
            if(buddyObj.ExtNo.toLowerCase().indexOf(filter.toLowerCase()) > -1 ) display = true;
            if(buddyObj.Desc.toLowerCase().indexOf(filter.toLowerCase()) > -1 ) display = true;
            if(!display) continue;
        }

        var today = moment.utc();
        var lastActivity = moment.utc(buddyObj.lastActivity.replace(" UTC", ""));
        var displayDateTime = "";
        if (lastActivity.isSame(today, 'day')) {
//            displayDateTime = lastActivity.local().format(DisplayTimeFormat);
            displayDateTime = lastActivity.local().format(DisplayTimeFormat);
        } 
        else {
            displayDateTime = lastActivity.local().format(DisplayDateFormat);
        }

        var classStr = (buddyObj.IsSelected)? "buddySelected" : "buddy";

        if (buddyObj.type == "extension") {

            var friendlyState = buddyObj.presence;
            if (friendlyState == "Unknown") friendlyState = lang.state_unknown;
            if (friendlyState == "Not online") friendlyState = lang.state_not_online;
            if (friendlyState == "Ready") friendlyState = lang.state_ready;
            if (friendlyState == "On the phone") friendlyState = lang.state_on_the_phone;
            if (friendlyState == "Ringing") friendlyState = lang.state_ringing;
            if (friendlyState == "On hold") friendlyState = lang.state_on_hold;
            if (friendlyState == "Unavailable") friendlyState = lang.state_unavailable;
            if (friendlyState == "Do not disturb") friendlyState = lang.state_do_not_disturb;
            if (friendlyState == "Away") friendlyState = lang.away;

            // An extension on the same system
            var html = "<div id=\"contact-"+ buddyObj.identity +"\" class="+ classStr +" onmouseenter=\"ShowBuddyDial(this, '"+ buddyObj.identity +"')\" onmouseleave=\"HideBuddyDial(this, '"+ buddyObj.identity +"')\" onclick=\"SelectBuddy('"+ buddyObj.identity +"', 'extension')\">";
            html += "<span id=\"contact-"+ buddyObj.identity +"-devstate\" class=\""+ buddyObj.devState +"\"></span>";

            html += "<div id='quickDialSection'>";

            html += "<span id=\"contact-"+ buddyObj.identity +"-audio-dial\" class=quickDial style=\"display:none\" title='"+ lang.make_audio_call +"' onclick=\"QuickDialAudio('"+ buddyObj.identity +"', this, event)\"><i class=\"fa fa-phone\"></i></span>";

            html += "<span id=\"contact-"+ buddyObj.identity +"-video-dial\" class=quickDial style=\"display:none\" title='"+ lang.make_video_call +"' onclick=\"QuickDialVideo('"+ buddyObj.identity +"', '"+ buddyObj.ExtNo +"', event)\"><i class=\"fa fa-video-camera\"></i></span>";

            if (buddyObj.MobileNumber || buddyObj.ContactNumber1 || buddyObj.ContactNumber2) {
                html += "<span id=\"contact-"+ buddyObj.identity +"-sms\" class=quickDial style=\"display:none\" title='"+ lang.send_sms +"' onclick=\"SendSMS('"+ buddyObj.identity +"', this, event)\"><i class=\"fa fa-commenting-o\" aria-hidden=\"true\"></i></span>";
            }

            if (getDbItem("useRoundcube", "") == 1 && buddyObj.Email) {
                html += "<span id=\"contact-"+ buddyObj.identity +"-email\" class=quickDial style=\"display:none\" title='"+ lang.send_email +"' onclick=\"ComposeEmail('"+ buddyObj.identity +"', this, event)\"><i class=\"fa fa-envelope-o\" aria-hidden=\"true\"></i></span>";
            }

            if (buddyObj.Fax) {
                html += "<span id='contact-"+ buddyObj.identity +"-fax' class='quickDial' style='display:none' title='"+ lang.send_fax +"' onclick=\"SendFax('"+ buddyObj.identity +"', this, event)\"><i class='fa fa-fax'></i></span>";
            }

            html += "</div>";

            if (buddyObj.missed && buddyObj.missed > 0) {
                html += "<span id=\"contact-"+ buddyObj.identity +"-missed\" class='missedNotifyer'>"+ buddyObj.missed +"</span>";
            } else {
                html += "<span id=\"contact-"+ buddyObj.identity +"-missed\" class='missedNotifyer' style=\"display:none\">"+ buddyObj.missed +"</span>";
            }
            html += "<div class=buddyIcon onclick=\"EditBuddyWindow('"+ buddyObj.identity +"')\" style=\"background-image: url('"+ getPicture(buddyObj.identity) +"')\" title=\"Edit Contact\"><i class=\"fa fa-pencil-square-o\" aria-hidden=\"true\"></i></div>";
            html += "<div class=contactNameText><i class=\"fa fa-phone-square\"></i> "+ buddyObj.ExtNo +" - "+ buddyObj.CallerIDName +"</div>";
            html += "<div id='contact-"+ buddyObj.identity +"-datetime' class='contactDate' title='"+ lang.last_activity +"'>"+ displayDateTime +"</div>";
            html += "<div id=\"contact-"+ buddyObj.identity +"-presence\" class=presenceText>"+ friendlyState +"</div>";
            html += "</div>";
            $("#myContacts").append(html);
            extensionNoIndex++;

        } else if (buddyObj.type == "contact") {

            // An Addressbook Contact
            var html = "<div id=\"contact-"+ buddyObj.identity +"\" class="+ classStr +" onmouseenter=\"ShowBuddyDial(this, '"+ buddyObj.identity +"')\" onmouseleave=\"HideBuddyDial(this, '"+ buddyObj.identity +"')\" onclick=\"SelectBuddy('"+ buddyObj.identity +"', 'contact')\">";

            html += "<div id='quickDialSection'>";

            if (buddyObj.MobileNumber || buddyObj.ContactNumber1 || buddyObj.ContactNumber2) {
                html += "<span id=\"contact-"+ buddyObj.identity +"-audio-dial\" class=quickDial style=\"display:none\" title='"+ lang.make_audio_call +"' onclick=\"QuickDialAudio('"+ buddyObj.identity +"', this, event)\"><i class=\"fa fa-phone\"></i></span>";
            }

            if (buddyObj.MobileNumber || buddyObj.ContactNumber1 || buddyObj.ContactNumber2) {
                html += "<span id=\"contact-"+ buddyObj.identity +"-sms\" class=quickDial style=\"display:none\" title='"+ lang.send_sms +"' onclick=\"SendSMS('"+ buddyObj.identity +"', this, event)\"><i class=\"fa fa-commenting-o\" aria-hidden=\"true\"></i></span>";
            }

            if (getDbItem("useRoundcube", "") == 1 && buddyObj.Email) {
                html += "<span id=\"contact-"+ buddyObj.identity +"-email\" class=quickDial style=\"display:none\" title='"+ lang.send_email +"' onclick=\"ComposeEmail('"+ buddyObj.identity +"', this, event)\"><i class=\"fa fa-envelope-o\" aria-hidden=\"true\"></i></span>";
            }

            if (buddyObj.Fax) {
                html += "<span id='contact-"+ buddyObj.identity +"-fax' class='quickDial' style='display:none' title='"+ lang.send_fax +"' onclick=\"SendFax('"+ buddyObj.identity +"', this, event)\"><i class='fa fa-fax'></i></span>";
            }

            html += "</div>";

            if (buddyObj.missed && buddyObj.missed > 0) {
                html += "<span id=\"contact-"+ buddyObj.identity +"-missed\" class='missedNotifyer'>"+ buddyObj.missed +"</span>";
            } else{
                html += "<span id=\"contact-"+ buddyObj.identity +"-missed\" class='missedNotifyer' style=\"display:none\">"+ buddyObj.missed +"</span>";
            }

            html += "<div class=buddyIcon onclick=\"EditBuddyWindow('"+ buddyObj.identity +"')\" style=\"background-image: url('"+ getPicture(buddyObj.identity,"contact") +"')\" title=\"Edit Contact\"><i class=\"fa fa-pencil-square-o\" aria-hidden=\"true\"></i></div>";
            html += "<div class=contactNameText><i class=\"fa fa-address-card\"></i> "+ buddyObj.CallerIDName +"</div>";
            html += "<div id='contact-"+ buddyObj.identity +"-datetime' class='contactDate' title='"+ lang.last_activity +"'>"+ displayDateTime +"</div>";
            html += "<div class=presenceText><i class='fa fa-tag' style='padding-right: 7px;'></i>"+ buddyObj.Desc +"</div>";
            html += "</div>";
            $("#myContacts").append(html);
            contactNoIndex++;
        }
        if ($("#contact-"+ buddyObj.identity).hasClass("buddySelected")) { $("#contact-"+ buddyObj.identity +"-missed").hide(); }
    }

    $("#myContacts").append("<div id='totalNmbrContacts'>Extensions: "+ extensionNoIndex +"&nbsp;&nbsp;&nbsp; Regular Contacts: "+ contactNoIndex +"&nbsp;&nbsp; Total: "+ parseInt(extensionNoIndex + contactNoIndex) +"</div>");

    // Enable scrolling for status text, when hovering over it
    $(".presenceText").mouseenter(function() { $(this).css('overflow', 'auto'); });
    $(".presenceText").mouseleave(function() { $(this).css('overflow', 'hidden'); });
}

function AddBuddyMessageStream(buddyObj) {
    var html = "<table id=\"stream-"+ buddyObj.identity +"\" class='stream' cellspacing=0 cellpadding=0>";
    html += "<tr><td id='contactTopSection' class='streamSection' style='height: 59.5px'>";

    // Close|Return|Back Button
    html += "<div style=\"float:left; margin:12px 0px 0px 15px; width: 38px; height:38px; line-height:38px;\">"

    html += "<button id=\"contact-"+ buddyObj.identity +"-btn-back\" onclick=\"CloseBuddy('"+ buddyObj.identity +"')\" class=roundButtons title=\""+ lang.collapse_contact +"\"><i class=\"fa fa-chevron-left\"></i></button> ";
    html += "</div>"
    
    // Profile UI
    html += "<div class=contact style=\"position: absolute; float: left; left: 57px;\" onclick=\"ShowBuddyProfileMenu('"+ buddyObj.identity +"', this, '"+ buddyObj.type +"')\">";

    if (buddyObj.type == "extension") {
        html += "<span id=\"contact-"+ buddyObj.identity +"-devstate-main\" class=\""+ buddyObj.devState +"\"></span>";
        html += "<div id=\"contact-"+ buddyObj.identity +"-picture-main\" class=buddyIcon style=\"background-image: url('"+ getPicture(buddyObj.identity) +"')\"></div>";
    }
    else if (buddyObj.type == "contact") {
        html += "<div class=buddyIcon style=\"background-image: url('"+ getPicture(buddyObj.identity,"contact") +"')\"></div>";
    }
    if(buddyObj.type == "extension") {
        html += "<div class=contactNameText style=\"margin-right: 0px;\"><i class=\"fa fa-phone-square\"></i> "+ buddyObj.ExtNo +" - "+ buddyObj.CallerIDName +"</div>";
    }
    else if (buddyObj.type == "contact") {
        html += "<div class=contactNameText style=\"margin-right: 0px;\"><i class=\"fa fa-address-card\"></i> "+ buddyObj.CallerIDName +"</div>";
    }
    if (buddyObj.type == "extension") {
        var friendlyState = buddyObj.presence;
        if (friendlyState == "Unknown") friendlyState = lang.state_unknown;
        if (friendlyState == "Not online") friendlyState = lang.state_not_online;
        if (friendlyState == "Ready") friendlyState = lang.state_ready;
        if (friendlyState == "On the phone") friendlyState = lang.state_on_the_phone;
        if (friendlyState == "Ringing") friendlyState = lang.state_ringing;
        if (friendlyState == "On hold") friendlyState = lang.state_on_hold;
        if (friendlyState == "Unavailable") friendlyState = lang.state_unavailable;
        if (friendlyState == "Do not disturb") friendlyState = lang.state_do_not_disturb;
        if (friendlyState == "Away") friendlyState = lang.away;

        html += "<div id=\"contact-"+ buddyObj.identity +"-presence-main\" class=presenceText>"+ friendlyState +"</div>";
    } else {
        html += "<div id=\"contact-"+ buddyObj.identity +"-presence-main\" class=presenceText>"+ buddyObj.Desc +"</div>";
    }
    html += "</div>";

    // Action Buttons
    html += "<div id=\"actionButtonsTop\">";
    html += "<button id=\"contact-"+ buddyObj.identity +"-btn-audioCall\" onclick=\"AudioCallMenu('"+ buddyObj.identity +"', this)\" class=roundButtons title=\""+ lang.make_audio_call +"\"><i class=\"fa fa-phone\"></i></button> ";
    if (buddyObj.type == "extension") {
        html += "<button id=\"contact-"+ buddyObj.identity +"-btn-videoCall\" onclick=\"DialByLine('video', '"+ buddyObj.identity +"', '"+ buddyObj.ExtNo +"')\" class=roundButtons title=\""+ lang.make_video_call +"\"><i class=\"fa fa-video-camera\"></i></button> ";
    }
    if (buddyObj.MobileNumber || buddyObj.ContactNumber1 || buddyObj.ContactNumber2) {
        html += "<button id=\"contact-"+ buddyObj.identity +"-sms\" onclick=\"SendSMS('"+ buddyObj.identity +"', this, event)\" class=\"roundButtons\" title='"+ lang.send_sms +"'><i class=\"fa fa-commenting-o\"></i></button> ";
    }
    if (getDbItem("useRoundcube", "") == 1 && buddyObj.Email) {
        html += "<button id=\"contact-"+ buddyObj.identity +"-email\" onclick=\"ComposeEmail('"+ buddyObj.identity +"', this, event)\" class=\"roundButtons\" title='"+ lang.send_email +"' ><i class=\"fa fa-envelope-o\"></i></button> ";
    }
    if (buddyObj.Fax) {
        html += "<button id='contact-"+ buddyObj.identity +"-fax' onclick=\"SendFax('"+ buddyObj.identity +"', this, event)\" class='roundButtons' title='"+ lang.send_fax +"'><i class='fa fa-fax'></i></button> ";
    }
    html += "<button id=\"contact-"+ buddyObj.identity +"-btn-search\" onclick=\"FindSomething('"+ buddyObj.identity +"')\" class=\"roundButtons\" title=\""+ lang.find_something +"\"><i class=\"fa fa-search\"></i></button> ";
    html += "<button id=\"contact-"+ buddyObj.identity +"-btn-download-history\" onclick=\"DownloadHistory('"+ buddyObj.identity +"')\" class=\"roundButtons\" title=\""+ lang.save_history +"\"><i class=\"fa fa-download\"></i></button> ";
    html += "<button id=\"contact-"+ buddyObj.identity +"-btn-remove\" onclick=\"RemoveBuddy('"+ buddyObj.identity +"')\" class=\"roundButtons\" title=\""+ lang.remove_contact +"\"><i class=\"fa fa-trash\"></i></button> ";
    html += "</div>";

    // Calling UI --------------------------------------------------------------------------
    html += "<div id=\"contact-"+ buddyObj.identity +"-calling\">";

    // Gneral Messages
    html += "<div id=\"contact-"+ buddyObj.identity +"-timer\" style=\"float: right; margin-top: 4px; margin-right: 10px; color: #575757; display:none;\"></div>";
    html += "<div id=\"contact-"+ buddyObj.identity +"-msg\" class=callStatus style=\"display:none\">...</div>";

    // Call Answer UI
    html += "<div id=\"contact-"+ buddyObj.identity +"-AnswerCall\" class=answerCall style=\"display:none\">";
    html += "<div>";
    html += "<button onclick=\"AnswerAudioCall('"+ buddyObj.identity +"')\" class='answerButton'><i class=\"fa fa-phone\"></i>&nbsp;&nbsp;"+ lang.answer_call +"</button> ";

    if (buddyObj.type == "extension") {
        html += "<button id=\"contact-"+ buddyObj.identity +"-answer-video\" onclick=\"AnswerVideoCall('"+ buddyObj.identity +"')\" class='answerButtonVid'><i class=\"fa fa-video-camera\"></i>&nbsp;&nbsp;"+ lang.answer_call_with_video +"</button> ";
    }
    html += "<button onclick=\"RejectCall('"+ buddyObj.identity +"')\" class=hangupButton><i class=\"fa fa-phone\"></i>&nbsp;&nbsp;"+ lang.reject_call +"</button> ";
    html += "</div>";
    html += "</div>";

    html += "</div>";

    // Search & Related Elements
    html += "<div id=\"contact-"+ buddyObj.identity +"-search\" style=\"margin-top:60px; display:none\">";
    html += "<span class=searchClean style=\"width:100%; margin-left:9px;\"><input type=text style=\"width:95%;margin-bottom:5px;\" autocomplete=none oninput=SearchStream(this,'"+ buddyObj.identity +"') placeholder=\""+ lang.find_something_in_the_message_stream +"\"></span>";

    html += "</div>";

    html += "</td></tr>";
    html += "<tr><td id='chatTextPanel' class='streamSection streamSectionBackground'>";

    html += "<div id=\"contact-"+ buddyObj.identity +"-ChatHistory\" class=\"chatHistory cleanScroller\">";

    // Previous Chat messages
    html += "</div>";

    html += "</td></tr>";

    if (buddyObj.type == "extension") {

        html += "<tr><td  id='sendChatMessageSection' class='streamSection' style='height:110px'>";

        // Send File
        html += "<div id=\"contact-"+ buddyObj.identity +"-fileShare\" style=\"display:none\">";
        html += "<input type=file multiple onchange=\"console.log(this)\" />";
        html += "</div>";

        // Send Audio Recording
        html += "<div id=\"contact-"+ buddyObj.identity +"-audio-recording\" style=\"display:none\"></div>";

        // Send Video Recording
        html += "<div id=\"contact-"+ buddyObj.identity +"-video-recording\" style=\"display:none\"></div>";

        // Emoji Menu Bar
        html += "<div id=\"contact-"+ buddyObj.identity +"-emoji-menu\" style=\"display:none\" class=\"EmojiMenu\"></div>";

        // =====================================
        // Type Area
        html += "<table class='sendMessageContainer' cellpadding='0' cellspacing='0'>";
        html += "<tr><td><textarea id='contact-"+ buddyObj.identity +"-ChatMessage' class='chatMessage' placeholder='"+ lang.type_your_message_here +"' onkeydown=\"chatOnkeydown(event, this, '"+ buddyObj.identity +"')\" onpaste=\"chatOnbeforepaste(event, this, '"+ buddyObj.identity +"')\"></textarea></td>";
        html += "<td style='width:40px;padding-top:4px;'><button onclick=\"SendChatMessage('"+ buddyObj.identity +"', '')\" class='roundButtonsSpec' title='"+ lang.send_chat_message +"'><i class='fa fa-paper-plane-o' aria-hidden='true'></i></button>";
        html += "<button onclick=\"ShowEmojiBar('"+ buddyObj.identity +"')\" class='roundButtonsSpec' title='"+ lang.select_expression + "'><i class='fa fa-smile-o'></i></button>";
        html += "<button onclick=\"SendFile('"+ buddyObj.identity +"')\" class='roundButtonsSpec' title='"+ lang.send_file + "'><i class='fa fa-file-text-o'></i></button></td></tr>";
        html += "</table>";
    }

    html += "</table>";

    $("#rightContent").append(html);

}

function RemoveBuddyMessageStream(buddyObj) {

    CloseBuddy(buddyObj.identity);

    UpdateBuddyList();

    // Remove Stream
    $("#stream-"+ buddyObj.identity).remove();
    var stream = streamObj[buddyObj.identity +"-stream"];
    delete streamObj[buddyObj.identity +"-stream"];

    // Remove Buddy
    var x = 0;
    $.each(contactsObj.DataCollection, function (i, item) {

        if (item.uID == buddyObj.identity || item.cID == buddyObj.identity) {
            x = i;
            return false;
        }
    });

    contactsObj.DataCollection.splice(x,1);
    contactsObj.TotalRows = contactsObj.DataCollection.length;

    // Remove Call Recordings
    if (stream && stream.DataCollection && stream.DataCollection.length >= 1) {
        DeleteCallRecordings(buddyObj.identity, stream);
    }
    
    // Remove QOS Data
    DeleteQosData(buddyObj.identity);
}
function DeleteCallRecordings(buddy, stream){
    var indexedDB = window.indexedDB;
    var request = indexedDB.open("CallRecordings");
    request.onerror = function(event) {
        console.error("IndexDB Request Error:", event);
    }
    request.onupgradeneeded = function(event) {
        console.warn("Upgrade Required for IndexDB... probably because of first time use.");
        // If this is the case, there will be no call recordings
    }
    request.onsuccess = function(event) {
        console.log("IndexDB connected to CallRecordings");

        var IDB = event.target.result;
        if(IDB.objectStoreNames.contains("Recordings") == false){
            console.warn("IndexDB CallRecordings.Recordings does not exists");
            return;
        }
        IDB.onerror = function(event) {
            console.error("IndexDB Error:", event);
        }

        // Loop and Delete
        $.each(stream.DataCollection, function (i, item) {
            if (item.Recordings && item.Recordings.length) {
                $.each(item.Recordings, function (i, recording) {
                    console.log("Deleting Call Recording: ", recording.uID);
                    var objectStore = IDB.transaction(["Recordings"], "readwrite").objectStore("Recordings");
                    try{
                        var deleteRequest = objectStore.delete(recording.uID);
                        deleteRequest.onsuccess = function(event) {
                            console.log("Call Recording Deleted: ", recording.uID);
                        }
                    } catch(e){
                        console.log("Call Recording Delete failed: ", e);
                    }
                });
            }
        });
    }
}

function MakeUpName(){
    var shortname = 4;
    var longName = 12;
    var letters = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"];
    var rtn = "";
    rtn += letters[Math.floor(Math.random() * letters.length)];
    for(var n=0; n<Math.floor(Math.random() * longName) + shortname; n++){
        rtn += letters[Math.floor(Math.random() * letters.length)].toLowerCase();
    }
    rtn += " ";
    rtn += letters[Math.floor(Math.random() * letters.length)];
    for(var n=0; n<Math.floor(Math.random() * longName) + shortname; n++){
        rtn += letters[Math.floor(Math.random() * letters.length)].toLowerCase();
    }
    return rtn;
}
function MakeUpNumber(){
    var numbers = ["0","1","2","3","4","5","6","7","8","9","0"];
    var rtn = "0";
    for(var n=0; n<9; n++){
        rtn += numbers[Math.floor(Math.random() * numbers.length)];
    }
    return rtn;
}
function MakeUpBuddies(int){
    for(var i=0; i<int; i++){
        var buddyObj = new Buddy("contact", uID(), MakeUpName(), "", "", MakeUpNumber(), "", utcDateNow(), "Testing", "", "", "");
        AddBuddy(buddyObj, false, false);
    }
    UpdateBuddyList();
}

function SelectBuddy(buddy) {

    $("#roundcubeFrame").remove();
    $("#emailInLogoSection").css("color", "#626262");
    $(".streamSelected").each(function() { $(this).show(); });

    var buddyObj = FindBuddyByIdentity(buddy);
    if(buddyObj == null) return;

    for(var b = 0; b < Buddies.length; b++) {
        if(Buddies[b].IsSelected == true && Buddies[b].identity == buddy){
            // Nothing to do, you re-selected the same buddy;
            return;
        }
    }

    console.log("Selecting Buddy: "+ buddy);

    selectedBuddy = buddyObj;

    // Can only display one thing on the right
    $(".streamSelected").each(function () {
        $(this).prop('class', 'stream');
    });
    $("#stream-" + buddy).prop('class', 'streamSelected');

    // Update Lines List
    for(var l = 0; l < Lines.length; l++) {
        var classStr = "buddy";
        if(Lines[l].SipSession != null) classStr = (Lines[l].SipSession.local_hold)? "buddyActiveCallHollding" : "buddyActiveCall";
        $("#line-" + Lines[l].LineNumber).prop('class', classStr);
        Lines[l].IsSelected = false;
    }

    ClearMissedBadge(buddy);
    // Update Buddy List
    for(var b = 0; b < Buddies.length; b++) {
        var classStr = (Buddies[b].identity == buddy)? "buddySelected" : "buddy";
        $("#contact-" + Buddies[b].identity).prop('class', classStr);

        $("#contact-"+ Buddies[b].identity +"-ChatHistory").empty();

        Buddies[b].IsSelected = (Buddies[b].identity == buddy);
    }

    // Change to Stream if in Narrow view
    UpdateUI();
    
    // Refresh Stream
    RefreshStream(buddyObj);

    try{
        $("#contact-" + buddy).get(0).scrollIntoViewIfNeeded();
    } catch(e){}

    // Save Selected
    localDB.setItem("SelectedBuddy", buddy);
}
function CloseBuddy(buddy){
    // Lines and Buddies (Left)
    $(".buddySelected").each(function () {
        $(this).prop('class', 'buddy');
    });
    // Streams (Right)
    $(".streamSelected").each(function () {
        $(this).prop('class', 'stream');
    });

    console.log("Closing Buddy: "+ buddy);
    for(var b = 0; b < Buddies.length; b++){
        Buddies[b].IsSelected = false;
    }
    selectedBuddy = null;
    for(var l = 0; l < Lines.length; l++){
        Lines[l].IsSelected = false;
    }
    selectedLine = null;

    // Save Selected
    localDB.setItem("SelectedBuddy", null);

    // Change to Stream if in Narrow view
    UpdateUI();
}
function DownloadHistory(buddy) {

    var buddyInfo = FindBuddyByIdentity(buddy);
    var DispNamePre = buddyInfo.CallerIDName;
    var DispName = DispNamePre.replace(" ", "_");
    var buddyExt = buddyInfo.ExtNo;
    var MobileNmbr = buddyInfo.MobileNumber;
    var ContactNmbr1 = buddyInfo.ContactNumber1;
    var ContactNmbr2 = buddyInfo.ContactNumber2;
    var presDate = moment().format("YYYY-MM-DD_HH-mm-ss");

    if (buddyExt && typeof buddyExt != 'undefined') {
        var wholeChatTextTitle = "\n" + "Roundpin History - Interlocutor: " + DispNamePre + " (extension " + buddyExt + "), saved on " + moment().format("YYYY-MM-DD HH:mm:ss") + "\n\n\n\n";
    } else if (MobileNmbr && typeof MobileNmbr != 'undefined') {
        var wholeChatTextTitle = "\n" + "Roundpin History - Interlocutor: " + DispNamePre + " (phone number: " + MobileNmbr + "), saved on " + moment().format("YYYY-MM-DD HH:mm:ss") + "\n\n\n\n";
    } else if (ContactNmbr1 && typeof ContactNmbr1 != 'undefined') {
        var wholeChatTextTitle = "\n" + "Roundpin History - Interlocutor: " + DispNamePre + " (phone number: " + ContactNmbr1 + "), saved on " + moment().format("YYYY-MM-DD HH:mm:ss") + "\n\n\n\n";
    } else if (ContactNmbr2 && typeof ContactNmbr2 != 'undefined') {
        var wholeChatTextTitle = "\n" + "Roundpin History - Interlocutor: " + DispNamePre + " (phone number: " + ContactNmbr2 + "), saved on " + moment().format("YYYY-MM-DD HH:mm:ss") + "\n\n\n\n";
    }

    var wholeChatText = "";

    $("#contact-"+ buddy +"-ChatHistory .chatMessageTable").each(function() {
       if ($(this).hasClass("theirChatMessage")) {
           if ($(this).hasClass("recFileChatDown")) {
               wholeChatText += "\n" + DispNamePre + " ("+ buddyExt +") ("+ $(this).find(".messageFlDate").text().trim() +"):\n  Download file \n  " + $(this).find(".recFileChatNm").text() + "\n\n";
           } else {
               wholeChatText += "\n" + DispNamePre + " ("+ buddyExt +") ("+ $(this).find(".messageDate").text().trim() +"):\n  " + $(this).find(".messageText").text() + "\n\n";
           }
       } else if ($(this).hasClass("ourChatMessage")) {
              if ($(this).hasClass("sentFileChatDown")) {
                  wholeChatText += "\n" + profileName + " ("+ SipUsername +") ("+ $(this).find(".messageFlDate").text().trim() +"):\n  Download file \n  " + $(this).find(".sentFileChatNm").text() + "\n\n";
              } else {
                  wholeChatText += "\n" + profileName + " ("+ SipUsername +") ("+ $(this).find(".messageDate").text().trim() +"):\n  " + $(this).find(".messageText").text() + "\n\n";
              }
       }
    });

    if (wholeChatText == "") { alert("There is no history to save ! "); return; }
    var wholeChatTextConc = wholeChatTextTitle + wholeChatText;
    var downFilename = 'Roundpin_History-' + DispName + "_" + presDate;
    var downlink = document.createElement('a');
    var mimeType = 'text/plain';

    downlink.setAttribute('download', downFilename);
    downlink.setAttribute('href', 'data:' + mimeType + ';charset=utf-8,' + encodeURIComponent(wholeChatTextConc));
    downlink.click(); 
}
function DownloadHistoryTC(crTextConfExt, crTextConfTag) {

    var presDateTC = moment().format("YYYY-MM-DD_HH-mm-ss");
    var wholeChatTextTitleTC = "\n" + "Roundpin Text Conference Chat History - Room: "+ crTextConfTag +" ("+ crTextConfExt +"), saved on " + moment().format("YYYY-MM-DD HH:mm:ss") + "\n\n\n\n";
    var wholeChatTextTC = "";

    $("#receiveTextMessage-"+ crTextConfExt +" .textMessageTConf").each(function() {
       if ($(this).find(".recMessageNm").length != 0) {
           wholeChatTextTC += "\n" + $(this).find(".recMessageNm").text().slice(0, -1) +" ("+ $(this).find(".messageDate").text() +"):\n  "+ $(this).find(".smTConfcont").text() +"\n\n";
       } else {
           wholeChatTextTC += "\n" + $(this).find(".sentMessageNm").text().slice(0, -1) +" ("+ $(this).find(".messageDate").text() +"):\n  "+ $(this).find(".smTConfcont").text() +"\n\n";
       }
    });

    if (wholeChatTextTC == "") { alert("There is no text conversation to save ! "); return; }
    var wholeChatTextConcTC = wholeChatTextTitleTC + wholeChatTextTC;
    var downFilenameTC = 'Roundpin_Text_Conference_Chat_History_Room_'+ crTextConfTag +"_"+ crTextConfExt +"_"+ presDateTC;
    var downlinkTC = document.createElement('a');
    var mimeTypeTC = 'text/plain';

    downlinkTC.setAttribute('download', downFilenameTC);
    downlinkTC.setAttribute('href', 'data:' + mimeTypeTC + ';charset=utf-8,' + encodeURIComponent(wholeChatTextConcTC));
    downlinkTC.click();
}

function DownloadHistoryTCPM(msgTConfExt, msgTConfTag, msgTdExt) {

    var presDateTCPM = moment().format("YYYY-MM-DD_HH-mm-ss");
    var wholeChatTextTitleTCPM = "\n" + "Roundpin Text Conference Private Chat History - Room: "+ msgTConfTag +" ("+ msgTConfExt +"), Interlocutor: "+ msgTdExt +", saved on " + moment().format("YYYY-MM-DD HH:mm:ss") + "\n\n\n\n";
    var wholeChatTextTCPM = "";

    $("#receiveTextMessageTCPM-"+ msgTConfExt +"-"+ msgTdExt +" .textMessageTConfPM").each(function() {
       if ($(this).find(".recMessageNm").length != 0) {
           wholeChatTextTCPM += "\n" + $(this).find(".recMessageNm").text().slice(0, -1) +" ("+ $(this).find(".messageDate").text() +"):\n  "+ $(this).find(".srMsgTConfcont").text() +"\n\n";
       } else {
           wholeChatTextTCPM += "\n" + $(this).find(".sentMessageNm").text().slice(0, -1) +" ("+ $(this).find(".messageDate").text() +"):\n  "+ $(this).find(".srMsgTConfcont").text() +"\n\n";
       }
    });

    if (wholeChatTextTCPM == "") { alert("There is no text conversation to save ! "); return; }
    var wholeChatTextConcTCPM = wholeChatTextTitleTCPM + wholeChatTextTCPM;
    var downFilenameTCPM = 'Roundpin_Text_Conference_Private_Chat_History_Room_'+ msgTConfTag +"_"+ msgTConfExt +"_user_"+ msgTdExt +"_"+ presDateTCPM;
    var downlinkTCPM = document.createElement('a');
    var mimeTypeTCPM = 'text/plain';

    downlinkTCPM.setAttribute('download', downFilenameTCPM);
    downlinkTCPM.setAttribute('href', 'data:' + mimeTypeTCPM + ';charset=utf-8,' + encodeURIComponent(wholeChatTextConcTCPM));
    downlinkTCPM.click();
}
function RemoveBuddy(buddy){

    var buddyData = FindBuddyByIdentity(buddy);
    var buddyDisplayName = buddyData.CallerIDName;

    // Check if you are on the phone, etc.
    Confirm(lang.confirm_remove_buddy, lang.remove_buddy, function(){
        for(var b = 0; b < Buddies.length; b++) {
            if(Buddies[b].identity == buddy) {
                RemoveBuddyMessageStream(Buddies[b]);
                UnsubscribeBuddy(Buddies[b])
                Buddies.splice(b, 1);
                break;
            }
        }
        // Remove contact from the database
        deleteBuddyFromSqldb(buddyDisplayName);

        UpdateBuddyList();
    });
}
function FindBuddyByDid(did){
    // Used only in Inboud
    for(var b = 0; b < Buddies.length; b++){
        if(Buddies[b].ExtNo == did || Buddies[b].MobileNumber == did || Buddies[b].ContactNumber1 == did || Buddies[b].ContactNumber2 == did) {
            return Buddies[b];
        }
    }
    return null;
}
function FindBuddyByExtNo(ExtNo){
    for(var b = 0; b < Buddies.length; b++){
        if(Buddies[b].type == "extension" && Buddies[b].ExtNo == ExtNo) return Buddies[b];
    }
    return null;
}
function FindBuddyByNumber(number){
    // Number could be: +XXXXXXXXXX
    // Any special characters must be removed prior to adding
    for(var b = 0; b < Buddies.length; b++){
        if(Buddies[b].MobileNumber == number || Buddies[b].ContactNumber1 == number || Buddies[b].ContactNumber2 == number) {
            return Buddies[b];
        }
    }
    return null;
}
function FindBuddyByIdentity(identity){
    for(var b = 0; b < Buddies.length; b++){
        if(Buddies[b].identity == identity) return Buddies[b];
    }
    return null;
}
function SearchStream(obj, buddy){
    var q = obj.value;

    var buddyObj = FindBuddyByIdentity(buddy);
    if(q == ""){
        console.log("Restore Stream");
        RefreshStream(buddyObj);
    }
    else{
        RefreshStream(buddyObj, q);
    }
}
function RefreshStream(buddyObj, filter) {
    $("#contact-" + buddyObj.identity + "-ChatHistory").empty();

    if (typeof streamObj[buddyObj.identity+"-stream"] == 'undefined') {
	streamObj[buddyObj.identity+"-stream"] = { TotalRows: 0, DataCollection: [] };
	var json = streamObj[buddyObj.identity+"-stream"];
    } else {
	var json = streamObj[buddyObj.identity+"-stream"];
    }

    if (json == null || json.DataCollection == null) return;

    // Sort DataCollection (Newest items first)
    json.DataCollection.sort(function(a, b) {
        var aMo = a.ItemDateMil;
        var bMo = b.ItemDateMil;
        if (aMo >= bMo) {
            return -1;
        } else { 
            return 1;
        }
    });

    // Filter
    if (typeof filter !== 'undefined' && filter) {

        console.log("Rows without filter ("+ filter +"): ", json.DataCollection.length);
        json.DataCollection = json.DataCollection.filter(function(item) {
            if (filter.indexOf("date: ") != -1) {
                // Apply Date Filter
                var dateFilter = getFilter(filter, "date");
                if(dateFilter != "" && item.ItemDate.indexOf(dateFilter) != -1) return true;
            }
            if (item.MessageData && item.MessageData.length > 1) {
                if(item.MessageData.toLowerCase().indexOf(filter.toLowerCase()) != -1) return true;
                if(filter.toLowerCase().indexOf(item.MessageData.toLowerCase()) != -1) return true;
            }
            if (item.ItemType == "MSG") {
                // Special search
            } 
            else if (item.ItemType == "CDR") {
                // Tag search
                if (item.Tags && item.Tags.length > 1) {
                    var tagFilter = getFilter(filter, "tag");
                    if (tagFilter != "") {
                        if (item.Tags.some(function(i) {
                            if(tagFilter.toLowerCase().indexOf(i.value.toLowerCase()) != -1) return true;
                            if(i.value.toLowerCase().indexOf(tagFilter.toLowerCase()) != -1) return true;
                            return false;
                        }) == true) return true;
                    }
                }
            }

            // return true to keep;
            return false;
        });
        console.log("Rows After Filter: ", json.DataCollection.length);
    }

    $.each(json.DataCollection, function (i, item) {

        var IsToday = moment.utc(item.ItemDate.replace(" UTC", "")).isSame(moment.utc(), "day");
        var DateTime = "  " + moment.utc(item.ItemDate.replace(" UTC", "")).local().calendar(null, { sameElse: DisplayDateFormat });
        if (IsToday) DateTime = "  " + moment.utc(item.ItemDate.replace(" UTC", "")).local().format(DisplayTimeFormat);

        var deliveryStatus = "<i class='fa fa-question-circle-o' style='color:#898989;margin-left:2px;font-size:15px;' title='Delivery Receipt Not Received'></i>";

        if (item.ItemType == "MSG") {
            // Add Chat Message
            // ===================
            var formattedMessage = ReformatMessage(item.MessageData);
            var longMessage = (formattedMessage.length > 1000);

            if (item.SrcUserId == profileUserID) {
                // You are the source (sending)
                if (formattedMessage.length != 0) {

                    if (typeof item.DeliveryStatus !== 'undefined') { deliveryStatus = item.DeliveryStatus; }

                    var messageString = "<table class='ourChatMessage chatMessageTable' name='"+ item.ItemId +"' cellspacing='0' cellpadding='0'><tr>";
                    messageString += "<td class=ourChatMessageText onmouseenter=\"ShowChatMenu(this)\" onmouseleave=\"HideChatMenu(this)\">";
                    messageString += "<span onclick=\"ShowMessgeMenu(this,'MSG','"+  item.ItemId +"', '"+ buddyObj.identity +"')\" class=chatMessageDropdown style=\"display:none\"><i class=\"fa fa-chevron-down\"></i></span>";
                    messageString += "<div id=msg-text-"+ item.ItemId +" class=messageText style=\""+ ((longMessage)? "max-height:190px; overflow:hidden" : "") +"\">" + formattedMessage + "</div>";
                    if(longMessage){
                       messageString += "<div id=msg-readmore-"+  item.ItemId +" class=messageReadMore><span onclick=\"ExpandMessage(this,'"+ item.ItemId +"', '"+ buddyObj.identity +"')\">"+ lang.read_more +"</span></div>";
                    }

                    messageString += "<div class=messageDate>" + DateTime + " <span class='SentMessage'>" + deliveryStatus +"</span></div>";
                    messageString += "</td>";
                    messageString += "</tr></table>";

                $("#contact-" + buddyObj.identity + "-ChatHistory").prepend(messageString);

                }
            } 
            else {

                // You are the destination (receiving)
                var ActualSender = "";
                if (formattedMessage.length != 0) {
                    var messageString = "<table class=\"theirChatMessage chatMessageTable\" cellspacing=0 cellpadding=0><tr>";
                    messageString += "<td class=theirChatMessageText onmouseenter=\"ShowChatMenu(this)\" onmouseleave=\"HideChatMenu(this)\">";
                    messageString += "<span onclick=\"ShowMessgeMenu(this,'MSG','"+  item.ItemId +"', '"+ buddyObj.identity +"')\" class=chatMessageDropdown style=\"display:none\"><i class=\"fa fa-chevron-down\"></i></span>";
                    messageString += "<div id=msg-text-"+ item.ItemId +" class=messageText style=\""+ ((longMessage)? "max-height:190px; overflow:hidden" : "") +"\">" + formattedMessage + "</div>";
                    if(longMessage){
                       messageString += "<div id=msg-readmore-"+  item.ItemId +" class=messageReadMore><span onclick=\"ExpandMessage(this,'"+ item.ItemId +"', '"+ buddyObj.identity +"')\">"+ lang.read_more +"</span></div>";
                    }
                    messageString += "<div class=messageDate>"+ DateTime + "</div>";
                    messageString += "</td>";
                    messageString += "</tr></table>";

                $("#contact-" + buddyObj.identity + "-ChatHistory").prepend(messageString);

                }
            }
        } 
        else if (item.ItemType == "CDR") {
            // Add CDR 
            // =======
            var iconColor = (item.Billsec > 0)? "#008050" : "#e30000";
            var formattedMessage = "";

            // Flagged
            var flag = "<span id=cdr-flagged-"+  item.CdrId +" style=\""+ ((item.Flagged)? "" : "display:none") +"\">";
            flag += "<i class=\"fa fa-flag FlagCall\"></i> ";
            flag += "</span>";

            // Comment
            var callComment = "";
            if(item.MessageData) callComment = item.MessageData;

            // Tags
            if(!item.Tags) item.Tags = [];
            var CallTags = "<ul id=cdr-tags-"+  item.CdrId +" class=tags style=\""+ ((item.Tags && item.Tags.length > 0)? "" : "display:none" ) +"\">"
            $.each(item.Tags, function (i, tag) {
                CallTags += "<li onclick=\"TagClick(this, '"+ item.CdrId +"', '"+ buddyObj.identity +"')\">"+ tag.value +"</li>";
            });
            CallTags += "<li class=tagText><input maxlength=24 type=text onkeypress=\"TagKeyPress(event, this, '"+ item.CdrId +"', '"+ buddyObj.identity +"')\" onfocus=\"TagFocus(this)\"></li>";
            CallTags += "</ul>";

            // Call Type
            var callIcon = (item.WithVideo)? "fa-video-camera" : "fa-phone";
            formattedMessage += "<i class='fa "+ callIcon +"' style='color:"+ iconColor +"; margin-right: 5px;'></i>";
            var audioVideo = (item.WithVideo)? lang.a_video_call :  lang.an_audio_call;

            // Recordings
            var recordingsHtml = "";
            if(item.Recordings && item.Recordings.length >= 1){
                $.each(item.Recordings, function (i, recording) {
                    if(recording.uID){
                        var StartTime = moment.utc(recording.startTime.replace(" UTC", "")).local();
                        var StopTime = moment.utc(recording.stopTime.replace(" UTC", "")).local();
                        var recordingDuration = moment.duration(StopTime.diff(StartTime));
                        recordingsHtml += "<div class=callRecording>";
                        if(item.WithVideo){
                            if(recording.Poster){
                                var posterWidth = recording.Poster.width;
                                var posterHeight = recording.Poster.height;
                                var posterImage = recording.Poster.posterBase64;
                                recordingsHtml += "<div><IMG src=\""+ posterImage +"\"><button onclick=\"PlayVideoCallRecording(this, '"+ item.CdrId +"', '"+ recording.uID +"')\" class=videoPoster><i class=\"fa fa-play\"></i></button></div>";
                            }
                            else {
                                recordingsHtml += "<div><button onclick=\"PlayVideoCallRecording(this, '"+ item.CdrId +"', '"+ recording.uID +"', '"+ buddyObj.identity +"')\"><i class=\"fa fa-video-camera\"></i></button></div>";
                            }
                        } 
                        else {
                            recordingsHtml += "<div><button onclick=\"PlayAudioCallRecording(this, '"+ item.CdrId +"', '"+ recording.uID +"', '"+ buddyObj.identity +"')\"><i class=\"fa fa-play\"></i></button></div>";
                        } 
                        recordingsHtml += "<div>"+ lang.started +": "+ StartTime.format(DisplayTimeFormat) +" <i class=\"fa fa-long-arrow-right\"></i> "+ lang.stopped +": "+ StopTime.format(DisplayTimeFormat) +"</div>";
                        recordingsHtml += "<div>"+ lang.recording_duration +": "+ formatShortDuration(recordingDuration.asSeconds()) +"</div>";
                        recordingsHtml += "<div>";
                        recordingsHtml += "<span id=\"cdr-video-meta-width-"+ item.CdrId +"-"+ recording.uID +"\"></span>";
                        recordingsHtml += "<span id=\"cdr-video-meta-height-"+ item.CdrId +"-"+ recording.uID +"\"></span>";
                        recordingsHtml += "<span id=\"cdr-media-meta-size-"+ item.CdrId +"-"+ recording.uID +"\"></span>";
                        recordingsHtml += "<span id=\"cdr-media-meta-codec-"+ item.CdrId +"-"+ recording.uID +"\"></span>";
                        recordingsHtml += "</div>";
                        recordingsHtml += "</div>";
                    }
                });
            }

            if (item.SrcUserId == profileUserID) {
                // (Outbound) You(profileUserID) initiated a call
                if(item.Billsec == 0) {
                    formattedMessage += "<div class='messageText'> "+ lang.you_tried_to_make +" "+ audioVideo +" ("+ item.ReasonText +").</div>";
                } 
                else {
                    formattedMessage += "<div class='messageText'> "+ lang.you_made + " "+ audioVideo +", "+ lang.and_spoke_for +" " + formatDuration(item.Billsec) + ".</div>";
                }
                var messageString = "<table class=\"ourChatMessage chatMessageTable\" cellspacing=0 cellpadding=0><tr>"
                messageString += "<td style=\"padding-right:4px;\">" + flag + "</td>"
                messageString += "<td class=ourChatMessageText onmouseenter=\"ShowChatMenu(this)\" onmouseleave=\"HideChatMenu(this)\">";
                messageString += "<span onClick=\"ShowMessgeMenu(this,'CDR','"+  item.CdrId +"', '"+ buddyObj.identity +"')\" class=chatMessageDropdown style=\"display:none\"><i class=\"fa fa-chevron-down\"></i></span>";
                messageString += "<div>" + formattedMessage + "</div>";
                messageString += "<div>" + CallTags + "</div>";
                messageString += "<div id=cdr-comment-"+  item.CdrId +" class=cdrComment>" + callComment + "</div>";
                messageString += "<div class=callRecordings>" + recordingsHtml + "</div>";
                messageString += "<div class=messageDate>" + DateTime  + "</div>";
                messageString += "</td>"
                messageString += "</tr></table>";
            } 
            else {
                // (Inbound) you(profileUserID) received a call
                if(item.Billsec == "0"){
                    formattedMessage += "<div class='messageText'> "+ lang.you_missed_a_call + " ("+ item.ReasonText +").</div>";
                } 
                else {
                    formattedMessage += "<div class='messageText'> "+ lang.you_received + " "+ audioVideo +", "+ lang.and_spoke_for +" " + formatDuration(item.Billsec) + ".</div>";
                }

                var messageString = "<table class=\"theirChatMessage chatMessageTable\" cellspacing=0 cellpadding=0><tr>";
                messageString += "<td class=theirChatMessageText onmouseenter=\"ShowChatMenu(this)\" onmouseleave=\"HideChatMenu(this)\">";
                messageString += "<span onClick=\"ShowMessgeMenu(this,'CDR','"+  item.CdrId +"', '"+ buddyObj.identity +"')\" class=chatMessageDropdown style=\"display:none\"><i class=\"fa fa-chevron-down\"></i></span>";
                messageString += "<div style=\"text-align:left\">" + formattedMessage + "</div>";
                messageString += "<div>" + CallTags + "</div>";
                messageString += "<div id=cdr-comment-"+  item.CdrId +" class=cdrComment>" + callComment + "</div>";
                messageString += "<div class=callRecordings>" + recordingsHtml + "</div>";
                messageString += "<div class=messageDate> " + DateTime + "</div>";
                messageString += "</td>";
                messageString += "<td style=\"padding-left:4px\">" + flag + "</td>";
                messageString += "</tr></table>";
            }
            // Messges are reappended here
            $("#contact-" + buddyObj.identity + "-ChatHistory").prepend(messageString);
        } 
        else if (item.ItemType == "FILE") {

            if (item.SrcUserId == profileUserID) {

                // File is sent
                if (typeof item.DeliveryStatus !== 'undefined') { deliveryStatus = item.DeliveryStatus; }

                var fileNameEnt = item.SentFileName;
                var fileExtensionArr = fileNameEnt.split(".");
                var fileExtensionPre = fileExtensionArr.reverse();
                var fileExtension = fileExtensionPre[0];

                var sentFileSection = "<table class='ourChatMessage sentFileChatDown chatMessageTable' cellspacing='0' cellpadding='0'><tr>";
                sentFileSection += "<td><div class='sentFileChatRect'><div id='"+ item.ItemId +"'style='display:block;width:400px;height:auto;padding: 5px 0px;'></div>Download file<br><a href='src/download-sent-chat-file.php?s_ajax_call="+ validateSToken +"&destSipUser="+ item.Dst +"&sentFlNm="+ item.SentFileName +"' target='_blank' class='sentFileChatNm'>"+ item.SentFileName +"</a>";
                sentFileSection += "<div class='messageFlDate'>"+ DateTime + " <span class='SentMessage'>" + deliveryStatus +"</span></div></div></td></tr></table>";

                $("#contact-" + buddyObj.identity + "-ChatHistory").prepend(sentFileSection);
                $("#sendFileLoader").remove();

                // Display the files with some well-known extensions in the browser
                if (fileExtension) {

                    if ($.inArray(fileExtension, ["jpg", "jpeg", "png", "gif", "bmp"]) !== -1) {
                        $("#"+ item.ItemId +"").html("<img preload='auto' src='textchat/"+ item.Dst +"/uploads/"+ item.SentFileName +"' width='400px' height='auto' />");
                    } else if ($.inArray(fileExtension, ["oga", "ogg", "wav", "mp3", "weba"]) !== -1) {
                        $("#"+ item.ItemId +"").html("<audio preload='auto' controls src='textchat/"+ item.Dst +"/uploads/"+ item.SentFileName +"'></audio>");
                    } else if ($.inArray(fileExtension, ["webm", "ogv", "avi", "mp4", "mpeg"]) !== -1) {
                        $("#"+ item.ItemId +"").html("<video preload='auto' controls src='textchat/"+ item.Dst +"/uploads/"+ item.SentFileName +"' width='400px'></video>");
                    }
                }

            } else {

                // File is received
                var fileNameEnt = item.ReceivedFileName;
                var fileExtensionArr = fileNameEnt.split(".");
                var fileExtensionPre = fileExtensionArr.reverse();
                var fileExtension = fileExtensionPre[0];
                var recFileSection = "<table class=\"theirChatMessage recFileChatDown chatMessageTable\" cellspacing=0 cellpadding=0><tr>";
                recFileSection += "<td><div class='recFileChatRect'><div id='"+ item.ItemId +"' style='display:block;width:400px;height:auto;padding: 5px 0px;'></div>Download file<br><a href='src/download-rec-chat-file.php?s_ajax_call="+ validateSToken +"&recSipUser="+ item.Dst +"&recFlNm="+ item.ReceivedFileName +"' target='_blank' class='recFileChatNm'>"+ item.ReceivedFileName +"</a>";
                recFileSection += "<div class='messageFlDate'>"+ DateTime + "</div></div></td></tr></table>";

                $("#contact-" + buddyObj.identity + "-ChatHistory").prepend(recFileSection);

                // Display the files with some well-known extensions in the browser
                if (fileExtension) {

                    if ($.inArray(fileExtension, ["jpg", "jpeg", "png", "gif", "bmp"]) !== -1) {
                        $("#"+ item.ItemId +"").html("<img preload='auto' src='textchat/"+ item.Dst +"/uploads/"+ item.ReceivedFileName +"' width='400px' height='auto' />");
                    } else if ($.inArray(fileExtension, ["oga", "ogg", "wav", "mp3", "weba"]) !== -1) {
                        $("#"+ item.ItemId +"").html("<audio preload='auto' controls src='textchat/"+ item.Dst +"/uploads/"+ item.ReceivedFileName +"'></audio>");
                    } else if ($.inArray(fileExtension, ["webm", "ogv", "avi", "mp4", "mpeg"]) !== -1) {
                        $("#"+ item.ItemId +"").html("<video preload='auto' controls src='textchat/"+ item.Dst +"/uploads/"+ item.ReceivedFileName +"' width='400px'></video>");
                    }
                }
            }

            updateScroll(buddyObj.identity);
        }
    });
    window.setTimeout(function() { updateScroll(buddyObj.identity); }, 300);
}
function ShowChatMenu(obj){
    $(obj).children("span").show();
}
function HideChatMenu(obj){
    $(obj).children("span").hide();
}
function ExpandMessage(obj, ItemId, buddy){
    $("#msg-text-" + ItemId).css("max-height", "");
    $("#msg-text-" + ItemId).css("overflow", "");
    $("#msg-readmore-" + ItemId).remove();

    $.jeegoopopup.close();
}
function ShowBuddyDial(obj, buddy) {
    $("#contact-"+ buddy +"-sms").show();
    $("#contact-"+ buddy +"-fax").show();
    $("#contact-"+ buddy +"-email").show();
    $("#contact-"+ buddy +"-audio-dial").show();
    $("#contact-"+ buddy +"-video-dial").show();
}
function HideBuddyDial(obj, buddy) {
    $("#contact-"+ buddy +"-sms").hide();
    $("#contact-"+ buddy +"-fax").hide();
    $("#contact-"+ buddy +"-email").hide();
    $("#contact-"+ buddy +"-audio-dial").hide();
    $("#contact-"+ buddy +"-video-dial").hide();
}
function QuickDialAudio(buddy, obj, event) {
    AudioCallMenu(buddy, obj);
    event.stopPropagation();
}
function QuickDialVideo(buddy, ExtNo, event) {
    event.stopPropagation();
    window.setTimeout(function(){
        DialByLine('video', buddy, ExtNo);
    }, 300);
}

// Sessions
// ========
function ExpandVideoArea(lineNum){
    $("#line-" + lineNum + "-ActiveCall").prop("class","FullScreenVideo");
    $("#line-" + lineNum + "-VideoCall").css("height", "calc(100% - 100px)");
    $("#line-" + lineNum + "-VideoCall").css("margin-top", "0px");

    $("#line-" + lineNum + "-preview-container").prop("class","PreviewContainer PreviewContainer_FS");
    $("#line-" + lineNum + "-stage-container").prop("class","StageContainer StageContainer_FS");

    $("#line-" + lineNum + "-restore").show();
    $("#line-" + lineNum + "-expand").hide();

    $("#line-" + lineNum + "-monitoring").hide();    
}
function RestoreVideoArea(lineNum){

    $("#line-" + lineNum + "-ActiveCall").prop("class","");
    $("#line-" + lineNum + "-VideoCall").css("height", "");
    $("#line-" + lineNum + "-VideoCall").css("margin-top", "10px");

    $("#line-" + lineNum + "-preview-container").prop("class","PreviewContainer");
    $("#line-" + lineNum + "-stage-container").prop("class","StageContainer");

    $("#line-" + lineNum + "-restore").hide();
    $("#line-" + lineNum + "-expand").show();

    $("#line-" + lineNum + "-monitoring").show();
}
function MuteSession(lineNum){
    $("#line-"+ lineNum +"-btn-Unmute").show();
    $("#line-"+ lineNum +"-btn-Mute").hide();

    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null) return;

    var session = lineObj.SipSession;
    var pc = session.sessionDescriptionHandler.peerConnection;
    pc.getSenders().forEach(function (RTCRtpSender) {
        if(RTCRtpSender.track.kind == "audio") {
            if(RTCRtpSender.track.IsMixedTrack == true){
                if(session.data.AudioSourceTrack && session.data.AudioSourceTrack.kind == "audio"){
                    console.log("Muting Audio Track : "+ session.data.AudioSourceTrack.label);
                    session.data.AudioSourceTrack.enabled = false;
                }
            }
            else {
                console.log("Muting Audio Track : "+ RTCRtpSender.track.label);
                RTCRtpSender.track.enabled = false;
            }
        }
    });

    if(!session.data.mute) session.data.mute = [];
    session.data.mute.push({ event: "mute", eventTime: utcDateNow() });
    session.data.ismute = true;

    $("#line-" + lineNum + "-msg").html(lang.call_on_mute);

    updateLineScroll(lineNum);

    // Custom Web hook
    if(typeof web_hook_on_modify !== 'undefined') web_hook_on_modify("mute", session);
}
function UnmuteSession(lineNum){
    $("#line-"+ lineNum +"-btn-Unmute").hide();
    $("#line-"+ lineNum +"-btn-Mute").show();

    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null) return;

    var session = lineObj.SipSession;
    var pc = session.sessionDescriptionHandler.peerConnection;
    pc.getSenders().forEach(function (RTCRtpSender) {
        if(RTCRtpSender.track.kind == "audio") {
            if(RTCRtpSender.track.IsMixedTrack == true){
                if(session.data.AudioSourceTrack && session.data.AudioSourceTrack.kind == "audio"){
                    console.log("Unmuting Audio Track : "+ session.data.AudioSourceTrack.label);
                    session.data.AudioSourceTrack.enabled = true;
                }
            }
            else {
                console.log("Unmuting Audio Track : "+ RTCRtpSender.track.label);
                RTCRtpSender.track.enabled = true;
            }
        }
    });

    if(!session.data.mute) session.data.mute = [];
    session.data.mute.push({ event: "unmute", eventTime: utcDateNow() });
    session.data.ismute = false;

    $("#line-" + lineNum + "-msg").html(lang.call_off_mute);

    updateLineScroll(lineNum);

    // Custom Web hook
    if(typeof web_hook_on_modify !== 'undefined') web_hook_on_modify("unmute", session);
}
function ShowDtmfMenu(obj, lineNum){

    var leftPos = event.pageX - 90;
    var topPos = event.pageY + 30;

    var html = "<div id=mainDtmfDialPad>";
    html += "<table cellspacing=10 cellpadding=0 style=\"margin-left:auto; margin-right: auto\">";
    html += "<tr><td><button class=dtmfButtons onclick=\"sendDTMF('"+ lineNum +"', '1');new Audio('sounds/dtmf.ogg').play();\"><div>1</div><span>&nbsp;</span></button></td>"
    html += "<td><button class=dtmfButtons onclick=\"sendDTMF('"+ lineNum +"', '2');new Audio('sounds/dtmf.ogg').play();\"><div>2</div><span>ABC</span></button></td>"
    html += "<td><button class=dtmfButtons onclick=\"sendDTMF('"+ lineNum +"', '3');new Audio('sounds/dtmf.ogg').play();\"><div>3</div><span>DEF</span></button></td></tr>";
    html += "<tr><td><button class=dtmfButtons onclick=\"sendDTMF('"+ lineNum +"', '4');new Audio('sounds/dtmf.ogg').play();\"><div>4</div><span>GHI</span></button></td>"
    html += "<td><button class=dtmfButtons onclick=\"sendDTMF('"+ lineNum +"', '5');new Audio('sounds/dtmf.ogg').play();\"><div>5</div><span>JKL</span></button></td>"
    html += "<td><button class=dtmfButtons onclick=\"sendDTMF('"+ lineNum +"', '6');new Audio('sounds/dtmf.ogg').play();\"><div>6</div><span>MNO</span></button></td></tr>";
    html += "<tr><td><button class=dtmfButtons onclick=\"sendDTMF('"+ lineNum +"', '7');new Audio('sounds/dtmf.ogg').play();\"><div>7</div><span>PQRS</span></button></td>"
    html += "<td><button class=dtmfButtons onclick=\"sendDTMF('"+ lineNum +"', '8');new Audio('sounds/dtmf.ogg').play();\"><div>8</div><span>TUV</span></button></td>"
    html += "<td><button class=dtmfButtons onclick=\"sendDTMF('"+ lineNum +"', '9');new Audio('sounds/dtmf.ogg').play();\"><div>9</div><span>WXYZ</span></button></td></tr>";
    html += "<tr><td><button class=dtmfButtons onclick=\"sendDTMF('"+ lineNum +"', '*');new Audio('sounds/dtmf.ogg').play();\">*</button></td>"
    html += "<td><button class=dtmfButtons onclick=\"sendDTMF('"+ lineNum +"', '0');new Audio('sounds/dtmf.ogg').play();\">0</button></td>"
    html += "<td><button class=dtmfButtons onclick=\"sendDTMF('"+ lineNum +"', '#');new Audio('sounds/dtmf.ogg').play();\">#</button></td></tr>";
    html += "</table>";
    html += "</div>";

    $.jeegoopopup.open({
                html: html,
                width: "174",
                height: "218",
                left: leftPos,
                top: topPos,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                overlay: true,
                opacity: 0,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); } });
}

// Stream Functionality
// =====================
function ShowMessgeMenu(obj, typeStr, cdrId, buddy) {

    $.jeegoopopup.close();

    var leftPos = event.pageX;
    var topPos = event.pageY;

    if (($(window).width() - event.pageX) < (obj.offsetWidth + 50)) { leftPos = event.pageX - 200; }

    var menu = "<div id=\"messageMenu\">";
    menu += "<table id=\"messageMenuTable\" cellspacing=10 cellpadding=0 style=\"margin-left:auto; margin-right: auto\">";

    // CDR's Menu
    if (typeStr == "CDR") {
        var TagState = $("#cdr-flagged-"+ cdrId).is(":visible");
        var TagText = (TagState)? lang.clear_flag : lang.flag_call;
        menu += "<tr id=\"CMDetails_1\"><td><i class=\"fa fa-external-link\"></i></td><td class=\"callDetails\">"+ lang.show_call_detail_record +"</td></tr>";
        menu += "<tr id=\"CMDetails_2\"><td><i class=\"fa fa-tags\"></i></td><td class=\"callDetails\">"+ lang.tag_call +"</td></tr>";
        menu += "<tr id=\"CMDetails_3\"><td><i class=\"fa fa-flag\"></i></td><td class=\"callDetails\">"+ TagText +"</td></tr>";
        menu += "<tr id=\"CMDetails_4\"><td><i class=\"fa fa-quote-left\"></i></td><td class=\"callDetails\">"+ lang.edit_comment +"</td></tr>";
    }
    if (typeStr == "MSG") {
        menu += "<tr id=\"CMDetails_5\"><td><i class=\"fa fa-clipboard\"></i></td><td class=\"callDetails\">"+ lang.copy_message +"</td></tr>";
        menu += "<tr id=\"CMDetails_6\"><td><i class=\"fa fa-quote-left\"></i></td><td class=\"callDetails\">"+ lang.quote_message +"</td></tr>";
    }

    menu += "</table></div>";

    $.jeegoopopup.open({
                html: menu,
                width: 'auto',
                height: 'auto',
                left: leftPos,
                top: topPos,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                overlay: true,
                opacity: 0,
                draggable: false,
                resizable: false,
                fadeIn: 0
    });

    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); } });

    // CDR messages
    $("#CMDetails_1").click(function(event) {

            var cdr = null;
	    if (typeof streamObj[buddy+"-stream"] == 'undefined') {
		streamObj[buddy+"-stream"] = { TotalRows: 0, DataCollection: [] };
		var currentStream = streamObj[buddy+"-stream"];
	    } else {
		var currentStream = streamObj[buddy+"-stream"];
	    }

            if (currentStream != null || currentStream.DataCollection != null) {
                $.each(currentStream.DataCollection, function (i, item) {
                    if (item.ItemType == "CDR" && item.CdrId == cdrId) {
                        // Found
                        cdr = item;
                        return false;
                    }
                });
            }
            if(cdr == null) return;

            var callDetails = [];

            var html = '<div id="windowCtrls"><img id="minimizeImg" src="images/1_minimize.svg" title="Restore" /><img id="maximizeImg" src="images/2_maximize.svg" title="Maximize" /><img id="closeImg" src="images/3_close.svg" title="Close" /></div>';
            html += "<div class=\"UiWindowField scroller\">";

            var CallDate = moment.utc(cdr.ItemDate.replace(" UTC", "")).local().format(DisplayDateFormat +" "+ DisplayTimeFormat);
            var CallAnswer = (cdr.CallAnswer)? moment.utc(cdr.CallAnswer.replace(" UTC", "")).local().format(DisplayDateFormat +" "+ DisplayTimeFormat) : null ;
            var ringTime = (cdr.RingTime)? cdr.RingTime : 0 ;
            var CallEnd = moment.utc(cdr.CallEnd.replace(" UTC", "")).local().format(DisplayDateFormat +" "+ DisplayTimeFormat);

            var srcCallerID = "";
            var dstCallerID = "";
            if(cdr.CallDirection == "inbound") {
                srcCallerID = cdr.Src;
            } 
            else if(cdr.CallDirection == "outbound") {
                dstCallerID = cdr.Dst;
            }
            html += "<div class=UiText><b>SIP CallID</b> : "+ cdr.SessionId +"</div>";
            html += "<div class=UiText><b>"+ lang.call_direction +"</b> : "+ cdr.CallDirection +"</div>";
            html += "<div class=UiText><b>"+ lang.call_date_and_time +"</b> : "+ CallDate +"</div>";
            html += "<div class=UiText><b>"+ lang.ring_time +"</b> : "+ formatDuration(ringTime) +" ("+ ringTime +")</div>";
            html += "<div class=UiText><b>"+ lang.talk_time +"</b> : " + formatDuration(cdr.Billsec) +" ("+ cdr.Billsec +")</div>";
            html += "<div class=UiText><b>"+ lang.call_duration +"</b> : "+ formatDuration(cdr.TotalDuration) +" ("+ cdr.TotalDuration +")</div>";
            html += "<div class=UiText><b>"+ lang.video_call +"</b> : "+ ((cdr.WithVideo)? lang.yes : lang.no) +"</div>";
            html += "<div class=UiText><b>"+ lang.flagged +"</b> : "+ ((cdr.Flagged)? "<i class=\"fa fa-flag FlagCall\"></i> " + lang.yes : lang.no)  +"</div>";
            html += "<hr>";
            html += "<h2 style=\"font-size: 16px\">"+ lang.call_tags +"</h2>";
            html += "<hr>";
            $.each(cdr.Tags, function(item, tag){
                html += "<span class=cdrTag>"+ tag.value +"</span>"
            });

            html += "<h2 style=\"font-size: 16px\">"+ lang.call_notes +"</h2>";
            html += "<hr>";
            if(cdr.MessageData){
                html += "\"" + cdr.MessageData + "\"";
            }

            html += "<h2 style=\"font-size: 16px\">"+ lang.activity_timeline +"</h2>";
            html += "<hr>";

            var withVideo = (cdr.WithVideo)? "("+ lang.with_video +")" : "";
            var startCallMessage = (cdr.CallDirection == "inbound")? lang.you_received_a_call_from + " " + srcCallerID  +" "+ withVideo : lang.you_made_a_call_to + " " + dstCallerID +" "+ withVideo;
            callDetails.push({ 
                Message: startCallMessage,
                TimeStr: cdr.ItemDate
            });
            if(CallAnswer){
                var answerCallMessage = (cdr.CallDirection == "inbound")? lang.you_answered_after + " " + ringTime + " " + lang.seconds_plural : lang.they_answered_after + " " + ringTime + " " + lang.seconds_plural;
                callDetails.push({ 
                    Message: answerCallMessage,
                    TimeStr: cdr.CallAnswer
                });
            }
            $.each(cdr.Transfers, function(item, transfer){
                var msg = (transfer.type == "Blind")? lang.you_started_a_blind_transfer_to +" "+ transfer.to +". " : lang.you_started_an_attended_transfer_to + " "+ transfer.to +". ";
                if(transfer.accept && transfer.accept.complete == true){
                    msg += lang.the_call_was_completed
                }
                else if(transfer.accept.disposition != "") {
                    msg += lang.the_call_was_not_completed +" ("+ transfer.accept.disposition +")"
                }
                callDetails.push({
                    Message : msg,
                    TimeStr : transfer.transferTime
                });
            });
            $.each(cdr.Mutes, function(item, mute){
                callDetails.push({
                    Message : (mute.event == "mute")? lang.you_put_the_call_on_mute : lang.you_took_the_call_off_mute,
                    TimeStr : mute.eventTime
                });
            });
            $.each(cdr.Holds, function(item, hold){
                callDetails.push({
                    Message : (hold.event == "hold")? lang.you_put_the_call_on_hold : lang.you_took_the_call_off_hold,
                    TimeStr : hold.eventTime
                });
            });
            $.each(cdr.ConfCalls, function(item, confCall){
                var msg = lang.you_started_a_conference_call_to +" "+ confCall.to +". ";
                if(confCall.accept && confCall.accept.complete == true){
                    msg += lang.the_call_was_completed
                }
                else if(confCall.accept.disposition != "") {
                    msg += lang.the_call_was_not_completed +" ("+ confCall.accept.disposition +")"
                }
                callDetails.push({
                    Message : msg,
                    TimeStr : confCall.startTime
                });
            });
            $.each(cdr.Recordings, function(item, recording){
                var StartTime = moment.utc(recording.startTime.replace(" UTC", "")).local();
                var StopTime = moment.utc(recording.stopTime.replace(" UTC", "")).local();
                var recordingDuration = moment.duration(StopTime.diff(StartTime));

                var msg = lang.call_is_being_recorded;
                if(recording.startTime != recording.stopTime){
                    msg += "("+ formatShortDuration(recordingDuration.asSeconds()) +")"
                }
                callDetails.push({
                    Message : msg,
                    TimeStr : recording.startTime
                });
            });
            callDetails.push({
                Message: (cdr.Terminate == "us")? "You ended the call." : "They ended the call",
                TimeStr : cdr.CallEnd
            });

            callDetails.sort(function(a, b){
                var aMo = moment.utc(a.TimeStr.replace(" UTC", ""));
                var bMo = moment.utc(b.TimeStr.replace(" UTC", ""));
                if (aMo.isSameOrAfter(bMo, "second")) {
                    return 1;
                } else return -1;
                return 0;
            });
            $.each(callDetails, function(item, detail){
                var Time = moment.utc(detail.TimeStr.replace(" UTC", "")).local().format(DisplayTimeFormat);
                var messageString = "<table class=timelineMessage cellspacing=0 cellpadding=0><tr>"
                messageString += "<td class=timelineMessageArea>"
                messageString += "<div class=timelineMessageDate style=\"color: #333333\"><i class=\"fa fa-circle timelineMessageDot\"></i>"+ Time +"</div>"
                messageString += "<div class=timelineMessageText style=\"color: #000000\">"+ detail.Message +"</div>"
                messageString += "</td>"
                messageString += "</tr></table>";
                html += messageString;
            });

            html += "<h2 style=\"font-size: 16px\">"+ lang.call_recordings +"</h2>";
            html += "<hr>";
            var recordingsHtml = "";
            $.each(cdr.Recordings, function(r, recording){
                if(recording.uID){
                    var StartTime = moment.utc(recording.startTime.replace(" UTC", "")).local();
                    var StopTime = moment.utc(recording.stopTime.replace(" UTC", "")).local();
                    var recordingDuration = moment.duration(StopTime.diff(StartTime));
                    recordingsHtml += "<div>";
                    if(cdr.WithVideo){
                        recordingsHtml += "<div><video id=\"callrecording-video-"+ recording.uID +"\" controls style=\"width: 100%\"></div>";
                    } 
                    else {
                        recordingsHtml += "<div><audio id=\"callrecording-audio-"+ recording.uID +"\" controls style=\"width: 100%\"></div>";
                    } 
                    recordingsHtml += "<div>"+ lang.started +": "+ StartTime.format(DisplayTimeFormat) +" <i class=\"fa fa-long-arrow-right\"></i> "+ lang.stopped +": "+ StopTime.format(DisplayTimeFormat) +"</div>";
                    recordingsHtml += "<div>"+ lang.recording_duration +": "+ formatShortDuration(recordingDuration.asSeconds()) +"</div>";
                    recordingsHtml += "<div><a id=\"download-"+ recording.uID +"\">"+ lang.save_as +"</a> ("+ lang.right_click_and_select_save_link_as +")</div>";
                    recordingsHtml += "</div>";
                }
            });
            html += recordingsHtml;

            html += "<h2 style=\"font-size: 16px\">"+ lang.send_statistics +"</h2>";
            html += "<hr>";

            html += "<div style=\"position: relative; margin: auto; height: 160px; width: 100%;\"><canvas id=\"cdr-AudioSendBitRate\"></canvas></div>";
            html += "<div style=\"position: relative; margin: auto; height: 160px; width: 100%;\"><canvas id=\"cdr-AudioSendPacketRate\"></canvas></div>";

            html += "<h2 style=\"font-size: 16px\">"+ lang.receive_statistics +"</h2>";
            html += "<hr>";

            html += "<div style=\"position: relative; margin: auto; height: 160px; width: 100%;\"><canvas id=\"cdr-AudioReceiveBitRate\"></canvas></div>";
            html += "<div style=\"position: relative; margin: auto; height: 160px; width: 100%;\"><canvas id=\"cdr-AudioReceivePacketRate\"></canvas></div>";
            html += "<div style=\"position: relative; margin: auto; height: 160px; width: 100%;\"><canvas id=\"cdr-AudioReceivePacketLoss\"></canvas></div>";
            html += "<div style=\"position: relative; margin: auto; height: 160px; width: 100%;\"><canvas id=\"cdr-AudioReceiveJitter\"></canvas></div>";
            html += "<div style=\"position: relative; margin: auto; height: 160px; width: 100%;\"><canvas id=\"cdr-AudioReceiveLevels\"></canvas></div>";

            html += "<br><br></div>";

            $.jeegoopopup.close();

            $.jeegoopopup.open({
                title: 'Call Statistics',
                html: html,
                width: '640',
                height: '500',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
            });

            $("#jg_popup_b").append('<button id="ok_button">'+ lang.ok +'</button>');

            // Display QOS data
            DisplayQosData(cdr.SessionId);


	    var maxWidth = $(window).width() - 12;
	    var maxHeight = $(window).height() - 88;

	    if (maxWidth < 656 || maxHeight < 500) { 
	       $.jeegoopopup.width(maxWidth).height(maxHeight);
	       $.jeegoopopup.center();
	       $("#maximizeImg").hide();
	       $("#minimizeImg").hide();
	    } else { 
	       $.jeegoopopup.width(640).height(500);
	       $.jeegoopopup.center();
	       $("#minimizeImg").hide();
	       $("#maximizeImg").show();
	    }

	    $(window).resize(function() {
	       maxWidth = $(window).width() - 12;
	       maxHeight = $(window).height() - 88;
	       $.jeegoopopup.center();
	       if (maxWidth < 656 || maxHeight < 500) { 
		   $.jeegoopopup.width(maxWidth).height(maxHeight);
		   $.jeegoopopup.center();
		   $("#maximizeImg").hide();
		   $("#minimizeImg").hide();
	       } else { 
		   $.jeegoopopup.width(640).height(500);
		   $.jeegoopopup.center();
		   $("#minimizeImg").hide();
		   $("#maximizeImg").show();
	       }
	    });

	   
	    $("#minimizeImg").click(function() { $.jeegoopopup.width(640).height(500); $.jeegoopopup.center(); $("#maximizeImg").show(); $("#minimizeImg").hide(); });
	    $("#maximizeImg").click(function() { $.jeegoopopup.width(maxWidth).height(maxHeight); $.jeegoopopup.center(); $("#minimizeImg").show(); $("#maximizeImg").hide(); });

	    $("#closeImg").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); });
	    $("#ok_button").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); });
	    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); $("#jg_popup_b").empty(); });
	    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); $("#jg_popup_b").empty(); } });


            // Queue video and audio
            $.each(cdr.Recordings, function(r, recording){
                    var mediaObj = null;
                    if(cdr.WithVideo){
                        mediaObj = $("#callrecording-video-"+ recording.uID).get(0);
                    }
                    else {
                        mediaObj = $("#callrecording-audio-"+ recording.uID).get(0);
                    }
                    var downloadURL = $("#download-"+ recording.uID);

                    // Playback device
                    var sinkId = getAudioOutputID();
                    if (typeof mediaObj.sinkId !== 'undefined') {
                        mediaObj.setSinkId(sinkId).then(function(){
                            console.log("sinkId applied: "+ sinkId);
                        }).catch(function(e){
                            console.warn("Error using setSinkId: ", e);
                        });
                    } else {
                        console.warn("setSinkId() is not possible using this browser.")
                    }

                    // Get Call Recording
                    var indexedDB = window.indexedDB;
                    var request = indexedDB.open("CallRecordings");
                    request.onerror = function(event) {
                        console.error("IndexDB Request Error:", event);
                    }
                    request.onupgradeneeded = function(event) {
                        console.warn("Upgrade Required for IndexDB... probably because of first time use.");
                    }
                    request.onsuccess = function(event) {
                        console.log("IndexDB connected to CallRecordings");

                        var IDB = event.target.result;
                        if(IDB.objectStoreNames.contains("Recordings") == false){
                            console.warn("IndexDB CallRecordings.Recordings does not exists");
                            return;
                        } 

                        var transaction = IDB.transaction(["Recordings"]);
                        var objectStoreGet = transaction.objectStore("Recordings").get(recording.uID);
                        objectStoreGet.onerror = function(event) {
                            console.error("IndexDB Get Error:", event);
                        }
                        objectStoreGet.onsuccess = function(event) {
                            var mediaBlobUrl = window.URL.createObjectURL(event.target.result.mediaBlob);
                            mediaObj.src = mediaBlobUrl;

                            // Download Link
                            if (cdr.WithVideo) {
                                downloadURL.prop("download",  "Video-Call-Recording-"+ recording.uID +".webm");
                            }
                            else {
                                downloadURL.prop("download",  "Audio-Call-Recording-"+ recording.uID +".webm");
                            }
                            downloadURL.prop("href", mediaBlobUrl);
                        }
                    }

            });

    });

    $("#CMDetails_2").click(function(event) {
            $("#cdr-tags-"+ cdrId).show();

            $.jeegoopopup.close();
    });

    $("#CMDetails_3").click(function(event) {
            // Tag / Untag Call
            var TagState = $("#cdr-flagged-"+ cdrId).is(":visible");
            if(TagState){
                console.log("Clearing Flag from: ", cdrId);
                $("#cdr-flagged-"+ cdrId).hide();

                // Update
		if (typeof streamObj[buddy+"-stream"] == 'undefined') {
		    streamObj[buddy+"-stream"] = { TotalRows: 0, DataCollection: [] };
		    var currentStream = streamObj[buddy+"-stream"];
		} else {
		    var currentStream = streamObj[buddy+"-stream"];
		}

                if(currentStream != null || currentStream.DataCollection != null){
                    $.each(currentStream.DataCollection, function (i, item) {
                        if (item.ItemType == "CDR" && item.CdrId == cdrId) {
                            // Found
                            item.Flagged = false;
                            return false;
                        }
                    });
                }
            }
            else {
                console.log("Flag Call: ", cdrId);
                $("#cdr-flagged-"+ cdrId).show();

                // Update DB
		if (typeof streamObj[buddy+"-stream"] == 'undefined') {
		    streamObj[buddy+"-stream"] = { TotalRows: 0, DataCollection: [] };
		    var currentStream = streamObj[buddy+"-stream"];
		} else {
		    var currentStream = streamObj[buddy+"-stream"];
		}

                if (currentStream != null || currentStream.DataCollection != null) {
                    $.each(currentStream.DataCollection, function (i, item) {
                        if (item.ItemType == "CDR" && item.CdrId == cdrId) {
                            // Found
                            item.Flagged = true;
                            return false;
                        }
                    });
                }
            }

            $.jeegoopopup.close();
    });

    $("#CMDetails_4").click(function(event) {

            var currentText = $("#cdr-comment-"+ cdrId).text();
            $("#cdr-comment-"+ cdrId).empty();

            var textboxObj = $("<input maxlength=500 type=text>").appendTo("#cdr-comment-"+ cdrId);
            textboxObj.on("focus", function(){
                $.jeegoopopup.close();
            });
            textboxObj.on("blur", function(){
                var newText = $(this).val();
                SaveComment(cdrId, buddy, newText);
            });
            textboxObj.keypress(function(event){
                window.setTimeout(function(){

                      $.jeegoopopup.close();

                }, 500);

                var keycode = (event.keyCode ? event.keyCode : event.which);
                if (keycode == '13') {
                    event.preventDefault();

                    var newText = $(this).val();
                    SaveComment(cdrId, buddy, newText);
                }
            });
            textboxObj.val(currentText);
            textboxObj.focus();

            $.jeegoopopup.close();
    });

    $("#CMDetails_5").click(function(event) {
        // Text Messages
            var msgtext = $("#msg-text-"+ cdrId).text();
            navigator.clipboard.writeText(msgtext).then(function(){
                console.log("Text coppied to the clipboard:", msgtext);
            }).catch(function(){
                console.error("Error writing to the clipboard:", e);
            });

            $.jeegoopopup.close();
    });

    $("#CMDetails_6").click(function(event) {

            var msgtext = $("#msg-text-"+ cdrId).text();
            msgtext = "\""+ msgtext + "\"";
            var textarea = $("#contact-"+ buddy +"-ChatMessage");
            console.log("Quote Message:", msgtext);
            textarea.val(msgtext +"\n" + textarea.val());

            $.jeegoopopup.close();
    });

}
function SaveComment(cdrId, buddy, newText){
    console.log("Setting Comment:", newText);

    $("#cdr-comment-"+ cdrId).empty();
    $("#cdr-comment-"+ cdrId).append(newText);

    // Update DB
    if (typeof streamObj[buddy+"-stream"] == 'undefined') {
        streamObj[buddy+"-stream"] = { TotalRows: 0, DataCollection: [] };
        var currentStream = streamObj[buddy+"-stream"];
    } else {
        var currentStream = streamObj[buddy+"-stream"];
    }

    if (currentStream != null || currentStream.DataCollection != null) {
        $.each(currentStream.DataCollection, function (i, item) {
            if (item.ItemType == "CDR" && item.CdrId == cdrId) {
                // Found
                item.MessageData = newText;
                return false;
            }
        });
    }
}
function TagKeyPress(event, obj, cdrId, buddy){

    $.jeegoopopup.close();

    var keycode = (event.keyCode ? event.keyCode : event.which);
    if (keycode == '13' || keycode == '44') {
        event.preventDefault();

        if ($(obj).val() == "") return;

        console.log("Adding Tag:", $(obj).val());

        $("#cdr-tags-"+ cdrId+" li:last").before("<li onclick=\"TagClick(this, '"+ cdrId +"', '"+ buddy +"')\">"+ $(obj).val() +"</li>");
        $(obj).val("");

        // Update DB
        UpdateTags(cdrId, buddy);
    }
}
function TagClick(obj, cdrId, buddy){
    window.setTimeout(function(){
       $.jeegoopopup.close();
    }, 500);

    console.log("Removing Tag:", $(obj).text());
    $(obj).remove();

    // Dpdate DB
    UpdateTags(cdrId, buddy);
}
function UpdateTags(cdrId, buddy){
    if (typeof streamObj[buddy+"-stream"] == 'undefined') {
        streamObj[buddy+"-stream"] = { TotalRows: 0, DataCollection: [] };
        var currentStream = streamObj[buddy+"-stream"];
    } else {
        var currentStream = streamObj[buddy+"-stream"];
    }

    if (currentStream != null || currentStream.DataCollection != null) {
        $.each(currentStream.DataCollection, function (i, item) {
            if (item.ItemType == "CDR" && item.CdrId == cdrId) {
                // Found
                item.Tags = [];
                $("#cdr-tags-"+ cdrId).children('li').each(function () {
                    if($(this).prop("class") != "tagText") item.Tags.push({ value: $(this).text() });
                });
                return false;
            }
        });
    }
}

function TagFocus(obj){
      $.jeegoopopup.close();
}

function sendRecordedFile(buddyObj, blobDatanw, recflname) {

     var flRecFormData = new FormData();
     flRecFormData.append('uploadedRecFile', blobDatanw, recflname);
     flRecFormData.append('s_ajax_call', validateSToken);
     flRecFormData.append('sipUser', buddyObj.ExtNo);

     sendRecFileChatErr = '';

     $.ajax({
	type: 'POST',
        url: 'src/text-chat-upload-recorded-file.php',
	data: flRecFormData,
	dataType: "JSON",
	contentType: false,
	cache: false,
	processData:false,
	success: function(respdata) {

                            $("#sendFileLoader").remove();                     
                            if (respdata.error != '') { 
                                sendRecFileChatErr = respdata.error;
                                $("#sendRecordedAudio,#sendRecordedVideo").remove();
                                alert("Error: " + respdata.error);
                            }
	},
	error: function(respdata) {
	            alert("An error occurred while sending the recorded file!");
	}          
     });

     $("#recordedAudioFl,#appendAudioFlName,#recordedVideoFl,#appendVideoFlName").remove();
}

function SendFile(buddy) {

      $('#selectedFile').val('');
      $("#upFile").empty();
      if (localVStream !== null) { localVStream.getVideoTracks()[0].stop(); }
      if (localAStream !== null) { localAStream.getAudioTracks()[0].stop(); }

      var buddyObj = FindBuddyByIdentity(buddy);
      var uploadfile = '<form id="sendFileFormChat" enctype="multipart/form-data" name="sndFileFrmMChat">';
      uploadfile += '<input type="hidden" name="MAX_FILE_SIZE" value="786432000" />';
      uploadfile += '<input type="hidden" name="sipUser" value="'+ buddyObj.ExtNo +'" />';
      uploadfile += '<input type="hidden" name="s_ajax_call" value="'+ validateSToken +'" />';
      uploadfile += '<label for="selectedFile" id="selectFlBtn-'+ buddy +'" class="custBrowseBttnSfl">Select File</label>';
      uploadfile += '<span id="upFile"></span>';
      uploadfile += '<input type="file" id="selectedFile" name="uploadedFile" />';
      uploadfile += '<input type="submit" id="submitFileChat" value="Send File" style="visibility:hidden"/>';
      uploadfile += '</form>';

      var recordedAudio = '<div id="sendRecordedAudio" class="sendRecAudioVideo">Record Audio File</div>';
      var recordedVideo = '<div id="sendRecordedVideo" class="sendRecordedVid">Record Video File</div>';

      if ($("#sendFileFormChat").is(":visible") || $("#sendRecordedAudio").is(":visible") || $("#startRecAudio").is(":visible") || $("#stopRecAudio").is(":visible") || 
          $("#appendRecAudio").is(":visible") || $("#sendRecordedVideo").is(":visible") || $("#startRecVideo").is(":visible") || $("#stopRecVideo").is(":visible") || 
          $("#appendRecVideo").is(":visible")) {

          $("#sendFileFormChat,#sendRecordedAudio,#startRecAudio,#stopRecAudio,#recordedAudioFl,#appendRecAudio,#recAudioAlert,#appendAudioFlName,#sendRecordedVideo,#startRecVideo,#stopRecVideo,#recordedVideoFl,#appendRecVideo,#recVideoAlert,#appendVideoFlName").remove();
          sendFileCheck = 0;
          sendRecFileCheck = 0;

      } else {

          $("#contact-"+ buddy +"-ChatMessage").after(uploadfile);
          $("#sendFileFormChat").css("display", "block");

          $("#selectedFile").bind("change", function() {
             upFileName = $(this).val().split('\\').pop();
             if (/^[a-zA-Z0-9\-\_\.]+$/.test(upFileName)) {
                 $("#upFile").css("display", "inline-block");
                 $("#upFile").html(upFileName);
                 sendFileCheck = 1;
             } else { 
                 $("#sendFileFormChat").remove();
                 sendFileCheck = 0;
                 alert("The name of the selected file is not valid!");
             }
          });

          $("#contact-"+ buddy +"-ChatMessage").after(recordedAudio);
          $("#sendRecordedAudio").css("display", "block");

          var tchatleft = parseInt($("#sendFileFormChat").width() - 60);
          $("#sendRecordedAudio").css("left", tchatleft);

          $("#contact-"+ buddy +"-ChatMessage").after(recordedVideo);
          $("#sendRecordedVideo").css("display", "block");

          var tchatleftra = parseInt($("#sendFileFormChat").width() + $("#sendRecordedAudio").width() - 33);
          $("#sendRecordedVideo").css("left", tchatleftra);

          $("#selectFlBtn-"+ buddy +"").click(function() {
             $("#sendRecordedAudio").remove();
             $("#sendRecordedVideo").remove();
          });


          // Choose to send a recorded audio file
          $("#sendRecordedAudio").click(function() {

             sendFileCheck = 0;

             $("#sendFileFormChat").remove();
             $("#sendRecordedVideo").remove();
             $(this).remove();

             $("#contact-"+ buddy +"-ChatMessage").after("<div id='startRecAudio' class='sendRecAudioVideo'><i class='fa fa-microphone' style='margin-right:5.5px'></i>Click to Start Audio Recording</div>");
             $("#startRecAudio").css("display", "block");         
             $("#startRecAudio").css("left", tchatleft);

	     navigator.mediaDevices.getUserMedia({ audio:true }).then(function(stream) { audioHandlerFunction(stream); });
             var audioChunks = [];
	     var blobData = null;
             var rec = null;
	     var audioflname = moment.utc().valueOf() +'.weba';
             upRecFileName = audioflname;

	     function audioHandlerFunction(stream) {

                localAStream = stream;
	        rec = new MediaRecorder(stream, { mimeType: 'audio/webm' });
		rec.ondataavailable = function(e) {
		   audioChunks.push(e.data);
		   if (rec.state == "inactive") {

		       const rawblobra = new Blob(audioChunks, { type: 'audio/webm' });
		       let durationra = audioChunks.length * 200;

		       // Add duration to the media blob, so that the final weba file becomes seekable
		       ysFixWebmDuration(rawblobra, durationra, function(fixedBlobData) {
                           blobData = fixedBlobData;
		           recordedAudioFl.src = window.URL.createObjectURL(fixedBlobData);
		           recordedAudioFl.controls = true;
		           recordedAudioFl.preload = "auto";
		           recordedAudioFl.download = true;
		           recordedAudioFl.autoplay = false;
                       });

		       stream.getTracks().forEach(function(track) {
			   track.stop();
		       });
		   }
		}
	     }


	     $("#startRecAudio").click(function() {

                       // Record in chunks of 200 milliseconds
		       rec.start(200);

                       $(this).remove();

		       $("#contact-"+ buddy +"-ChatMessage").after("<div id='stopRecAudio' class='sendRecAudioVideo'><i class='fa fa-stop-circle-o' style='margin-right:5.5px'></i>Stop Recording</div>");
		       $("#stopRecAudio").css("display", "block");
		       $("#stopRecAudio").css("left", tchatleft);

                       $("#contact-"+ buddy +"-ChatMessage").after("<div id='recAudioAlert' class='audiovidFlREC'>REC</div>");
                       var stopRecWidth = parseInt($("#stopRecAudio").width() - 70);
                       $("#recAudioAlert").css("left", tchatleft - stopRecWidth);

                       window.setInterval(function() { $("#recAudioAlert").toggle(); }, 500);

	               $("#stopRecAudio").click(function() {

                          rec.stop();

                          $(this).remove();
                          $("#recAudioAlert").remove();

                          $("#contact-"+ buddy +"-ChatMessage").after("<audio id='recordedAudioFl'></audio>");
		          $("#recordedAudioFl").css({"display":"inline-block", "position":"absolute", "bottom":"0", "left":"0"});

		          $("#contact-"+ buddy +"-ChatMessage").after("<div id='appendRecAudio' class='sendRecAudioVideo'><i class='fa fa-plus' style='margin-right:5.5px'></i>Append Recorded File to Message</div>");
		          $("#appendRecAudio").css("display", "block");

		          $("#appendRecAudio").css("left", parseInt(tchatleft + 205));

                          $("#appendRecAudio").click(function() {

                             sendRecFileCheck = 1;

                             $(this).remove();
                             $("#recordedAudioFl").remove();

		             $("#contact-"+ buddy +"-ChatMessage").after("<div id='appendAudioFlName' class='upRecFileNmSpace'></div>");
		             $("#appendAudioFlName").css("left", "4px");
                             $("#appendAudioFlName").html(audioflname);
		             $("#contact-"+ buddy +"-ChatMessage").after("<div id='sendRecAudioFl' display='none'></div>");
                             $("#sendRecVideoFl").remove();
                             $("#sendRecAudioFl").click(function() { sendRecordedFile(buddyObj, blobData, audioflname); });

                          });

                       });

	     });
          });


          // Choose to send a recorded video file
          $("#sendRecordedVideo").click(function() {

             sendFileCheck = 0;

             $("#sendFileFormChat").remove();
             $("#sendRecordedAudio").remove();
             $(this).remove();

             $("#contact-"+ buddy +"-ChatMessage").after("<div id='startRecVideo' class='sendRecordedVid'><i class='fa fa-video-camera' style='margin-right:5.5px'></i>Click to Start Video Recording</div>");
             $("#startRecVideo").css("display", "block");         
             $("#startRecVideo").css("left", tchatleftra);

	     navigator.mediaDevices.getUserMedia({ audio:true, video:true }).then(function(stream) { videoHandlerFunction(stream); });

             var videoChunks = [];
	     var blobDataVd = null;
             var recvd = null;
	     var videoflname = moment.utc().valueOf() +'.webm';
             upRecFileName = videoflname;

	     function videoHandlerFunction(stream) {

                localVStream = stream;
	        recvd = new MediaRecorder(stream, { mimeType: 'video/webm' });
		recvd.ondataavailable = function(e) {
		   videoChunks.push(e.data);
		   if (recvd.state == "inactive") {

		       const rawblobra = new Blob(videoChunks, { type: 'video/webm' });
		       let durationra = videoChunks.length * 200;

		       // Add duration to the media blob, so that the final webm file becomes seekable
		       ysFixWebmDuration(rawblobra, durationra, function(fixedblobDataVd) {
                           blobDataVd = fixedblobDataVd;
		           recordedVideoFl.src = window.URL.createObjectURL(fixedblobDataVd);
		           recordedVideoFl.controls = true;
		           recordedVideoFl.preload = "auto";
		           recordedVideoFl.download = true;
		           recordedVideoFl.autoplay = false;
                       });

		       stream.getTracks().forEach(function(track) {
			   track.stop();
		       });
		   }
		}
	     }


	     $("#startRecVideo").click(function() {

                       // Record in chunks of 200 milliseconds
		       recvd.start(200);

                       $(this).remove();

		       $("#contact-"+ buddy +"-ChatMessage").after("<div id='stopRecVideo' class='sendRecordedVid'><i class='fa fa-stop-circle-o' style='margin-right:5.5px'></i>Stop Recording</div>");
		       $("#stopRecVideo").css("display", "block");
		       $("#stopRecVideo").css("left", tchatleftra);

                       $("#contact-"+ buddy +"-ChatMessage").after("<div id='recVideoAlert' class='audiovidFlREC'>REC</div>");
                       var stopRecWidth = parseInt($("#stopRecVideo").width() - 70);
                       $("#recVideoAlert").css("left", tchatleftra - stopRecWidth);

                       window.setInterval(function() { $("#recVideoAlert").toggle(); }, 500);

	               $("#stopRecVideo").click(function() {

                          recvd.stop();

                          $(this).remove();
                          $("#recVideoAlert").remove();

                          $("#contact-"+ buddy +"-ChatMessage").after("<video id='recordedVideoFl'></video>");
		          $("#recordedVideoFl").css({"display":"inline-block", "position":"absolute", "bottom":"0", "left":"0"});

		          $("#contact-"+ buddy +"-ChatMessage").after("<div id='appendRecVideo' class='sendRecordedVid'><i class='fa fa-plus' style='margin-right:5.5px'></i>Append Recorded File to Message</div>");
		          $("#appendRecVideo").css("display", "block");

		          $("#appendRecVideo").css("left", parseInt(tchatleftra + 390));

                          $("#appendRecVideo").click(function() {

                             sendRecFileCheck = 1;

                             $(this).remove();
                             $("#recordedVideoFl").remove();

		             $("#contact-"+ buddy +"-ChatMessage").after("<div id='appendVideoFlName' class='upRecFileNmSpace'></div>");
		             $("#appendVideoFlName").css("left", "4px");
                             $("#appendVideoFlName").html(videoflname);
		             $("#contact-"+ buddy +"-ChatMessage").after("<div id='sendRecVideoFl' display='none'></div>");
                             $("#sendRecAudioFl").remove();
                             $("#sendRecVideoFl").click(function() { sendRecordedFile(buddyObj, blobDataVd, videoflname); });

                          });

                       });

	     });
          });
      }
}

function ShowEmojiBar(buddy) {

    var messageContainer = $("#contact-"+ buddy +"-emoji-menu");
    var textarea = $("#contact-"+ buddy +"-ChatMessage");

    if (messageContainer.is(":visible")) { messageContainer.hide(); } else { messageContainer.show(); }

    var menuBar = $("<div>");
    menuBar.prop("class", "emojiButton")
    var emojis = ["😀","😁","😂","😃","😄","😅","😆","😊","😦","😉","😊","😋","😌","😍","😎","😏","😐","😑","😒","😓","😔","😕","😖","😗","😘","😙","😚","😛","😜","😝","😞","😟","😠","😡","😢","😣","😤","😥","😦","😧","😨","😩","😪","😫","😬","😭","😮","😯","😰","😱","😲","😳","😴","😵","😶","😷","🙁","🙂","🙃","🙄","🤐","🤑","🤒","🤓","🤔","🤕","🤠","🤡","🤢","🤣","🤤","🤥","🤧","🤨","🤩","🤪","🤫","🤬","🤭","🤯","🤗","🧐"];
    $.each(emojis, function(i,e) {
        var emoji = $("<button>");
        emoji.html(e);

        emoji.on('click', function() {
            var i = textarea.prop('selectionStart');
            var v = textarea.val();
            textarea.val(v.substring(0, i) + " " + $(this).html() + v.substring(i, v.length) + " ");

            messageContainer.hide();
            textarea.focus();
            updateScroll(buddy);
        });
        menuBar.append(emoji);
    });

    $(".chatMessage,.chatHistory").on('click', function(){
        messageContainer.hide();
    });

    messageContainer.empty();
    messageContainer.append(menuBar);

    updateScroll(buddy);
}

// My Profile
// ==========
function ShowMyProfileMenu(obj){

    var leftPos = obj.offsetWidth - 254;
    var topPos = obj.offsetHeight + 56 ;

    var menu = "<div id='userMenu'>";
    menu += "<table id='userMenuTable' cellspacing=10 cellpadding=0 style='margin-left:auto; margin-right: auto'>";
    menu += "<tr id='userMenu_1'><td><div id='statusLabel'><i class='fa fa-circle-o' aria-hidden='true'></i>&nbsp; "+ lang.status_label +"</div><div id='customStatusText'><input type='text' id='enteredCustStText' placeholder='Eg: Reading my emails' /><button id='saveCustStatusBtn'>"+ lang.save_custom_status +"</button></div>\
             <div id='shownStatus'></div>\
             <ul id='selectStatus'>\
             <li id='readyState' class='statusListItem'><i class='fa fa-volume-control-phone'></i>&nbsp; "+ lang.state_ready + "</li>\
             <li id='awayState' class='statusListItem'><i class='fa fa-clock-o'></i>&nbsp; "+ lang.away + "</li>\
             <li id='dndState' class='statusListItem'><i class='fa fa-hand-paper-o'></i>&nbsp; "+ lang.state_do_not_disturb + "</li>\
             <li id='autoAnswerState' class='statusListItem'><i class='fa fa-phone'></i>&nbsp; "+ lang.auto_answer + "</li>\
             <li id='setCustomState' class='statusListItem'><i class='fa fa-question-circle-o'></i>&nbsp; "+ lang.set_custom_status + "</li>\
             </ul></td></tr>";
    menu += "<tr id=userMenu_2><td><i class=\"fa fa-refresh\"></i>&nbsp; "+ lang.refresh_registration + "</td></tr>";
    menu += "<tr id=userMenu_3><td><i class=\"fa fa-user-plus\"></i>&nbsp; "+ lang.add_contact + "</td></tr>";
    menu += "<tr id=userMenu_4><td><i class=\"fa fa-comments-o\" style=\"font-size:18px\"></i>&nbsp; "+ lang.send_message_to_group + "</td></tr>";
    menu += "<tr id=userMenu_7><td><i class=\"fa fa-power-off\"></i>&nbsp; "+ lang.log_out + "</td></tr>";
    menu += "</table>";
    menu += "</div>";  

    $.jeegoopopup.open({
                html: menu,
                width: 'auto',
                height: 'auto',
                left: '56',
                top: topPos,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                innerClass: 'userMenuInner',
                contentClass: 'userMenuContent',
                overlay: true,
                opacity: 0,
                draggable: false,
                resizable: false,
                fadeIn: 0
    });

    $(window).resize(function() {
      $.jeegoopopup.width('auto').height('auto').left('56').top(topPos);   
    });

    // Display the status in the user profile menu
    var custStateText = localDB.getItem("CustomStateSetText");
    var customStTxt = '';
    if (custStateText && typeof custStateText != 'undefined') { customStTxt = custStateText; }

    $("#shownStatus").show();
    if (currentStatus == 'ready') { $("#shownStatus").html("<div><i class='fa fa-volume-control-phone'></i>&nbsp; "+ lang.state_ready +"<i id='statusCaretDown' class='fa fa-caret-down'></i></div>");
    } else if (currentStatus == 'custom_status') { $("#shownStatus").html("<div><i class='fa fa-question-circle-o'></i>&nbsp; "+ customStTxt +"<i id='statusCaretDown' class='fa fa-caret-down'></i></div>");
    } else if (currentStatus == 'away') { $("#shownStatus").html("<div><i class='fa fa-clock-o'></i>&nbsp; "+ lang.away +"<i id='statusCaretDown' class='fa fa-caret-down'></i></div>");
    } else if (currentStatus == 'do_not_disturb') { $("#shownStatus").html("<div><i class='fa fa-hand-paper-o'></i>&nbsp; "+ lang.state_do_not_disturb +"<i id='statusCaretDown' class='fa fa-caret-down'></i></div>");
    } else if (currentStatus == 'auto_answer') { $("#shownStatus").html("<div><i class='fa fa-phone'></i>&nbsp; "+ lang.auto_answer +"<i id='statusCaretDown' class='fa fa-caret-down'></i></div>");
    } else { $("#shownStatus").html("<div><i class='fa fa-volume-control-phone'></i>&nbsp; "+ lang.state_ready +"<i id='statusCaretDown' class='fa fa-caret-down'></i></div>"); }

    $("#shownStatus").click(function() {

       if ($("#selectStatus").is(":visible")) {
           $("#selectStatus").css("display", "none");
       } else { $("#selectStatus").css("display", "inline"); }

       $("#selectStatus .statusListItem").click(function() {

          var selectedStatusId = $(this).attr('id');

          if (selectedStatusId == 'readyState') { ToggleCallWaiting();
          } else if (selectedStatusId == 'awayState') { ToggleAway();
          } else if (selectedStatusId == 'dndState') { ToggleDoNotDisturb();
          } else if (selectedStatusId == 'autoAnswerState') { ToggleAutoAnswer();
          } else if (selectedStatusId == 'setCustomState') { ToggleSetCustomState(); }

          $("#selectStatus").css("display", "none");
       });

    });

    $("#userMenu_2").click(function() { RefreshRegistration(); });
    $("#userMenu_3").click(function() { AddSomeoneWindow(); });
    $("#userMenu_4").click(function() { SendMessageToGroups(); });
    $("#userMenu_7").click(function() { SignOut(); });

    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); } });

    // Enable scrolling for the custom status on the profile drop-down menu, when hovering over it
    $("#shownStatus").mouseenter(function() { $(this).css('overflow', 'auto'); });
    $("#shownStatus").mouseleave(function() { $(this).css('overflow', 'hidden'); });
}

function ShowLaunchConfMenu(obj) {

    var leftPos = obj.offsetWidth + 144;
    var rightPos = 0;
    var topPos = obj.offsetHeight + 131;

    if ($(window).width() <= 919) {
        leftPos = event.pageX + obj.offsetWidth - 113;
        rightPos = 0;
        topPos = event.pageY + obj.offsetHeight - 11;
    }

    var cnfmenu = "<div id='AllConfMenu'>";
    cnfmenu += "<table id='lauchConfTable' cellspacing='0' cellpadding='0'>";
    cnfmenu += "<tr id='launchTextConfMenu'><td><i class='fa fa-comment-o'></i>&nbsp; "+ lang.text_conference + "</td></tr>";
    cnfmenu += "<tr id='launchAudioConfMenu'><td><i class='fa fa-volume-up'></i>&nbsp; "+ lang.audio_conference + "</td></tr>";
    cnfmenu += "<tr id='launchVConfMenu'><td><i class='fa fa-video-camera'></i>&nbsp; "+ lang.video_conference + "</td></tr>";
    cnfmenu += "</table>";
    cnfmenu += "</div>";

    $.jeegoopopup.open({
                html: cnfmenu,
                width: '180',
                height: '92',
                left: leftPos,
                right: rightPos,
                top: topPos,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                overlay: true,
                opacity: 0,
                draggable: false,
                resizable: false,
                fadeIn: 0
    });

    if ($(window).width() <= 919) { $.jeegoopopup.right(6); } else { $.jeegoopopup.width('180').height('92').left(leftPos).top(topPos); }

    $(window).resize(function() {
      $.jeegoopopup.width('180').height('92').left(leftPos).top(topPos);   
    });


    // Text conferences submenu
    var textsubmenu = "<div id='textSubMenu'>";
    textsubmenu += "<table id='textSubMenuTbl' cellspacing='0' cellpadding='0'>";

    Object.keys(textExtTagObj).forEach(function(key, ind) {
         textsubmenu += "<tr><td><i class='fa fa-comment-o' style='color: #289571;'></i>&nbsp; <span id='textConfSubMenu-"+ key +"' name='"+ key +"|"+ textExtTagObj[key] +"' title='Extension "+ key +"'>"+ textExtTagObj[key]  +"</span><span id='tpartnmbr-"+ key +"' class='textConfPartNo' name='"+ key +"' title='Show Connected Participants'><i class='fa fa-user-circle-o'></i></span></td></tr>";
    });

    textsubmenu += "</table>";
    textsubmenu += "</div>";

    if (!$.isEmptyObject(textExtTagObj)) {
        $("#AllConfMenu").append(textsubmenu);
    }

    $("#launchTextConfMenu").click(function() {

       $("#videoSubMenu,#audioSubMenu").hide();
       $("#textConfParticipants,#audioConfParticipants,#videoConfParticipants").remove();

       if ($("#textSubMenu").is(":visible")) {
           $("#textSubMenu").remove();
       } else {
               $("#textSubMenu").css("display", "block");

               if ($(window).width() <= 919) { $("#textSubMenu").css("left", "-144px"); }

       }

       $("#launchTextConfMenu td").css("color","#185494");
       $("#launchAudioConfMenu td").css("color","#092a4e");
       $("#launchVConfMenu td").css("color","#092a4e");

    });

    $("[id^='textConfSubMenu-']").click(function() {

       var crTextCnfNaT = $(this).attr('name').split("|");

          $.ajax({
             type: "POST",
             url: "src/get-text-conf-restrictions.php",
             dataType: "JSON",
             data: {
                     username: userName,
                     tconfextension: crTextCnfNaT[0],
                     s_ajax_call: validateSToken
             },
             success: function(response) {
                               if (response.result == 'success') {
                                   $("#leftContent").append("<span id='openTConfLoader'></span>");
                                   setTimeout(function() { LaunchTextConference(crTextCnfNaT[0], crTextCnfNaT[1]); }, 100);
                               } else { alert("The access to this text conference is restricted to some specific groups."); }
             },
             error: function(response) { alert("Error while trying to get text conference restrictions!"); }
          });
    });

    $("[id^='tpartnmbr-']").click(function() {

       // Ask for the list of text conference participants
       var crTxtCnfExtNo = $(this).attr('name');
       var tinviteOptions = { extraHeaders: ['X-TConfExtension: '+ crTxtCnfExtNo], sessionDescriptionHandlerOptions: { constraints: { audio: true, video: false } } };
       userAgent.invite("sip:textconfusers@"+ wssServer, tinviteOptions);

       if ($("#textConfParticipants").is(":visible")) { $("#textConfParticipants").remove(); }
    });


    // Audio conferences submenu
    var asubmenu = "<div id='audioSubMenu'>";
    asubmenu += "<table id='audioSubMenuTbl' cellspacing='0' cellpadding='0'>";

    Object.keys(audioExtTagObj).forEach(function(key, ind) {
         asubmenu += "<tr><td><i class='fa fa-volume-up' style='color: #289571;'></i>&nbsp; <span id='audioConfSubMenu-"+ key +"' name='"+ key +"' title='Extension "+ key +"'>"+ audioExtTagObj[key]  + "</span><span id='apartnmbr-"+ key +"' class='audioConfPartNo' name='"+ key +"' title='Show Connected Participants'><i class='fa fa-user-circle-o'></i></span></td></tr>";
    });

    asubmenu += "</table>";
    asubmenu += "</div>";

    if (!$.isEmptyObject(audioExtTagObj)) {
        $("#AllConfMenu").append(asubmenu);
    }

    $("#launchAudioConfMenu").click(function() {

       $("#videoSubMenu,#textSubMenu").hide();
       $("#textConfParticipants,#audioConfParticipants,#videoConfParticipants").remove();

       if ($("#audioSubMenu").is(":visible")) {
           $("#audioSubMenu").remove();
       } else { 
                $("#audioSubMenu").css("display", "block"); 
                if ($(window).width() <= 919) { $("#audioSubMenu").css("left", "-184px"); }
       }

       $("#launchTextConfMenu td").css("color","#092a4e");
       $("#launchAudioConfMenu td").css("color","#185494");
       $("#launchVConfMenu td").css("color","#092a4e");

    });

    $("[id^='audioConfSubMenu-']").click(function() {
       var crAudioCnf = $(this).attr('name');

       $.ajax({
             type: "POST",
             url: "src/get-audio-conf-restrictions.php",
             dataType: "JSON",
             data: {
                     username: userName,
                     aconfextension: crAudioCnf,
                     s_ajax_call: validateSToken
             },
             success: function(response) {

                               if (response.result == 'success') {

			           // Create a Buddy if one doesn't exist
			           var buddyObj = FindBuddyByDid(crAudioCnf);
			           if (buddyObj == null) {
				       buddyObj = MakeBuddy("extension", true, false, true, crAudioCnf, crAudioCnf);
			           }

			           // Create a Line
			           newLineNumber = newLineNumber + 1;
//			           lineObj = new Line(newLineNumber, buddyObj.CallerIDName, crAudioCnf, buddyObj);
			           lineObj = new Line(newLineNumber, audioExtTagObj[crAudioCnf], crAudioCnf, buddyObj);
			           Lines.push(lineObj);
			           AddLineHtml(lineObj);
			           SelectLine(newLineNumber);
			           UpdateBuddyList();

			           // Start Call Invite
			           AudioCall(lineObj, crAudioCnf);

//                                   winAudioConfCheck[crAudioCnf] = 1;

			           try { $("#line-" + newLineNumber).get(0).scrollIntoViewIfNeeded(); } catch(e){};

			           $.jeegoopopup.close();

                               } else { alert("The access to this audio conference is restricted to some specific groups."); }
             },
             error: function(response) { alert("Error while trying to get audio conference restrictions!"); }
       });
    });

    $("[id^='apartnmbr-']").click(function() {

       // Ask for the list of text conference participants
       var crAudioCnfExtNo = $(this).attr('name');
       var ainviteOptions = { extraHeaders: ['X-AConfExtension: '+ crAudioCnfExtNo], sessionDescriptionHandlerOptions: { constraints: { audio: true, video: false } } };
       userAgent.invite("sip:audioconfusers@"+ wssServer, ainviteOptions);

       if ($("#audioConfParticipants").is(":visible")) { $("#audioConfParticipants").remove(); }
    });


    // Video conferences submenu
    var vsubmenu = "<div id='videoSubMenu'>";
    vsubmenu += "<table id='videoSubMenuTbl' cellspacing='0' cellpadding='0'>";

    Object.keys(videoExtTagObj).forEach(function(key, ind) {
         vsubmenu += "<tr><td><i class='fa fa-video-camera' style='color: #289571;'></i>&nbsp; <span id='videoConfSubMenu-"+ key +"' name='"+ key +"' tag='"+ videoExtTagObj[key] +"' title='Extension "+ key +"'>"+ videoExtTagObj[key]  + "</span><span id='vpartnmbr-"+ key +"' class='videoConfPartNo' name='"+ key +"' title='Show Connected Participants'><i class='fa fa-user-circle-o'></i></span></td></tr>";
    });

    vsubmenu += "</table>";
    vsubmenu += "</div>";

    if (!$.isEmptyObject(videoExtTagObj)) {
        $("#AllConfMenu").append(vsubmenu);
    }

    $("#launchVConfMenu").click(function() {

       $("#textSubMenu,#audioSubMenu").hide();
       $("#textConfParticipants,#audioConfParticipants,#videoConfParticipants").remove();

       if ($("#videoSubMenu").is(":visible")) {
           $("#videoSubMenu").remove();
       } else { 
                $("#videoSubMenu").css("display", "block"); 
                if ($(window).width() <= 919) { $("#videoSubMenu").css("left", "-184px"); }
       }

       $("#launchTextConfMenu td").css("color","#092a4e");
       $("#launchAudioConfMenu td").css("color","#092a4e");
       $("#launchVConfMenu td").css("color","#185494");

    });

    $("[id^='videoConfSubMenu-']").click(function() {
       var crVidCnf = $(this).attr('name');
       var crVidTag = $(this).attr('tag');
       var launchvconfrstrct = 0;
       var launchvconfnotban = 0;

       $.when(
          $.ajax({
             type: "POST",
             url: "src/get-video-conf-restrictions.php",
             dataType: "JSON",
             data: {
                     username: userName,
                     vconfsipuser: SipUsername +"vconf",
                     vconfextension: crVidCnf,
                     s_ajax_call: validateSToken
             },
             success: function(response) {
                               if (response.restrict == 'success') { launchvconfrstrct = 1; }
                               if (response.notbanned == 'success') { launchvconfnotban = 1; }                              
             },
             error: function(response) { alert("Error while trying to get video conference restrictions!"); }
          })
       ).then(function() {
              if (launchvconfrstrct == 1 && launchvconfnotban == 1) { 
                  LaunchVideoConference(crVidCnf, crVidTag); 
              } else if (launchvconfrstrct == 0 && launchvconfnotban == 1) { 
                  alert("The access to this video conference is restricted to some specific groups.");
              } else if (launchvconfrstrct == 1 && launchvconfnotban == 0) {
                  alert("You are still banned from this conference.");
              } 
       });

    });

    $("[id^='vpartnmbr-']").click(function() {

       // Ask for the list of text conference participants
       var crVidCnfExtNo = $(this).attr('name');
       var vinviteOptions = { extraHeaders: ['X-VConfExtension: '+ crVidCnfExtNo], sessionDescriptionHandlerOptions: { constraints: { audio: true, video: false } } };
       userAgent.invite("sip:videoconfusers@"+ wssServer, vinviteOptions);

       if ($("#videoConfParticipants").is(":visible")) { $("#videoConfParticipants").remove(); }
    });



    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); } });
}

function RefreshRegistration() {
    Unregister();
    console.log("Unregister complete...");
    window.setTimeout(function() {
        console.log("Starting registration...");
        Register();
    }, 1000);
}

function SignOut() {

    if (getDbItem("useRoundcube", "") == 1 && RCLoginCheck == 1) {

        // Log out from Roundcube
        $("#roundcubeFrame").remove();
        $("#emailInLogoSection").css("color", "#626262");
        $("#rightContent").show();
        $(".streamSelected").each(function() { $(this).css("display", "none"); });
        $("#rightContent").append('<iframe id="rcLogoutFrame" name="logoutFrame"></iframe>');


        var logoutURL = "https://"+ getDbItem("rcDomain", "") +"/";

        var logoutform = '<form id="rcloForm" method="POST" action="'+ logoutURL +'" target="logoutFrame">'; 
        logoutform += '<input type="hidden" name="_action" value="logout" />';
        logoutform += '<input type="hidden" name="_task" value="logout" />';
        logoutform += '<input type="hidden" name="_autologout" value="1" />';
        logoutform += '<input id="submitloButton" type="submit" value="Logout" />';
        logoutform += '</form>';

        $("#rcLogoutFrame").append(logoutform);
        $("#submitloButton").click();
        RCLoginCheck == 0;
    }

    // Send the 'Unavailable' presence status to all extensions
    sendUnavailableStatus();

    window.setTimeout(function() {

       // Check if there are any configured external video conference users
       var externalLinks = getDbItem("externalUserConfElem", "");

       if (typeof externalLinks !== 'undefined' && externalLinks != null && externalLinks != 0) {

           checkExternalLinks();

       } else {
           Unregister();
           console.log("Signing Out ...");
           localStorage.clear();
           if (winVideoConf != null) {
               winVideoConf.close();
           }
           window.open('https://' + window.location.host + '/src/logout.php', '_self');
       }
    }, 100);
}

function ToggleAutoAnswer() {

            $("#shownStatus").html("<div><i class='fa fa-volume-control-phone'></i>&nbsp; "+ lang.auto_answer +"<i id='statusCaretDown' class='fa fa-caret-down'></i></div>");

            currentStatus = 'auto_answer';
            localDB.setItem("MyCurrentStatus", "auto_answer");

            for (let b = 0; b < Buddies.length; b++) {
                 let crBuddyObj = Buddies[b];
                 if (crBuddyObj.type == "extension") {
                     userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Ready', { extraHeaders: [ 'X-CustomPresence: auto_answer' ] });
                 }
            }

            $("#dereglink").attr("class", "dotAutoAnswer");

            console.log("MyCurrentStatus is: ", "auto_answer");

            var regStatusAll = lang.registered +" - "+ lang.status_label +": "+ lang.auto_answer +"";
            $("#regStatus").html(regStatusAll);
}

function ToggleCallWaiting() {

            currentStatus = 'ready';
            localDB.setItem("MyCurrentStatus", "ready");

            $("#shownStatus").html("<div><i class='fa fa-volume-control-phone'></i>&nbsp; "+ lang.state_ready +"<i id='statusCaretDown' class='fa fa-caret-down'></i></div>");

            for (let b = 0; b < Buddies.length; b++) {
                 let crBuddyObj = Buddies[b];
                 if (crBuddyObj.type == "extension") {
                     userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Ready', { extraHeaders: [ 'X-CustomPresence: ready' ] });
                 }
            }

            $("#dereglink").attr("class", "dotOnline");

            console.log("MyCurrentStatus is: ", "ready");

            var regStatusAll = lang.registered +" - "+ lang.status_label +": "+ lang.state_ready +"";
            $("#regStatus").html(regStatusAll);
}

function ToggleAway() {

            currentStatus = 'away';
            localDB.setItem("MyCurrentStatus", "away");

            $("#shownStatus").html("<div><i class='fa fa-clock-o'></i>&nbsp; "+ lang.away +"<i id='statusCaretDown' class='fa fa-caret-down'></i></div>");

            for (let b = 0; b < Buddies.length; b++) {
                 let crBuddyObj = Buddies[b];
                 if (crBuddyObj.type == "extension") {
                     userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Away', { extraHeaders: [ 'X-CustomPresence: away' ] });
                 }
            }

            $("#dereglink").attr("class", "dotAway");

            console.log("MyCurrentStatus is: ", "away");

            var regStatusAll = lang.registered +" - "+ lang.status_label +": "+ lang.away +"";

            $("#regStatus").html(regStatusAll);
}

function ToggleDoNotDisturb() {

            currentStatus = 'do_not_disturb';
            localDB.setItem("MyCurrentStatus", "do_not_disturb");

            $("#shownStatus").html("<div><i class='fa fa-hand-paper-o'></i>&nbsp; "+ lang.state_do_not_disturb +"<i id='statusCaretDown' class='fa fa-caret-down'></i></div>");

            for (let b = 0; b < Buddies.length; b++) {
                 let crBuddyObj = Buddies[b];
                 if (crBuddyObj.type == "extension") {
                     userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Do not disturb', { extraHeaders: [ 'X-CustomPresence: do_not_disturb' ] });
                 }
            }

            $("#dereglink").attr("class", "dotDoNotDisturb");

            console.log("MyCurrentStatus is: ", "do_not_disturb");

            var regStatusAll = lang.registered +" - "+ lang.status_label +": "+ lang.state_do_not_disturb +"";
            $("#regStatus").html(regStatusAll);

}

function ToggleSetCustomState() {

    $("#shownStatus").hide();

        $("#customStatusText").css("display", "inline");
        $("#statusCaretDown").hide();
        $("#enteredCustStText").focus();

        $("#saveCustStatusBtn").click(function() {

           currentStatus = 'custom_status';
           localDB.setItem("MyCurrentStatus", "custom_status");

           var enteredTextStatus = $("#enteredCustStText").val();

           if (enteredTextStatus.length > 0) {
             if (/^[a-zA-Z0-9\s\-\_\.\,\:\;\?\!\@\#\$\%\&\/\+\=\'\"]+$/.test(enteredTextStatus)) {
               if (enteredTextStatus.length <= 475) {

		   localDB.setItem("CustomStateSetText", enteredTextStatus);

		   $("#shownStatus").show();
		   $("#statusCaretDown").show();
		   $("#shownStatus").html("<div><i class='fa fa-question-circle-o'></i>&nbsp; "+ enteredTextStatus +"<i id='statusCaretDown' class='fa fa-caret-down'></i></div>");
		   $("#customStatusText").css("display", "none");

		   $("#dereglink").attr("class", "dotCustom");

		   var regStatusAll = lang.registered +" - "+ lang.status_label +": "+ enteredTextStatus +"";
		   $("#regStatus").html(regStatusAll);

		   for (let b = 0; b < Buddies.length; b++) {
		        let crBuddyObj = Buddies[b];
		        if (crBuddyObj.type == "extension") {
		            userAgent.message(crBuddyObj.ExtNo + '@' + wssServer, 'Custom State', { extraHeaders: [ 'X-CustomPresence: custom_state'+ enteredTextStatus ] });
		        }
		   }

                   console.log("MyCurrentStatus is: ", "custom_status");

               } else { alert("The custom status text cannot exceed 475 characters."); }
             } else { alert("The text that you entered contains characters that are not allowed. Allowed characters are: a-z, A-Z, 0-9, space, comma, -, _, ., :, ;, ?, !, @, #, $, %, &, /, +, =, ', \""); }
           } else { alert("The custom status cannot be left empty."); }

        });
}

function ShowBuddyProfileMenu(buddy, obj, typeStr) {

    $.jeegoopopup.close();

    leftPos = event.pageX - 60;
    topPos = event.pageY + 45;

    var buddyObj = FindBuddyByIdentity(buddy);

    if (typeStr == "extension") {

        var html = "<div style='width:200px;cursor:pointer' onclick=\"EditBuddyWindow('"+ buddy +"')\">";
        html += "<div class='buddyProfilePic' style=\"background-image:url('"+ getPicture(buddy, "extension") +"')\"></div>";
        html += "<div id='ProfileInfo'><i class='fa fa-spinner fa-spin'></i></div>"
        html += "</div>";

        $.jeegoopopup.open({
                html: html,
                width: '200',
                height: 'auto',
                left: leftPos,
                top: topPos,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                contentClass: 'showContactDetails',
                overlay: true,
                opacity: 0,
                draggable: true,
                resizable: false,
                fadeIn: 0
        });
      
        // Done
        $("#ProfileInfo").html("");

        $("#ProfileInfo").append("<div class=ProfileTextLarge style='margin:15px 0px 4px 0px'>"+ buddyObj.CallerIDName +"</div>");
        $("#ProfileInfo").append("<div class=ProfileTextMedium>"+ buddyObj.Desc +"</div>");

        $("#ProfileInfo").append("<div class=ProfileTextSmall style=\"margin-top:15px\">"+ lang.extension_number +":</div>");
        $("#ProfileInfo").append("<div class=ProfileTextMedium>"+ buddyObj.ExtNo +" </div>");

        if (buddyObj.Email != '' && buddyObj.Email != null && typeof buddyObj.Email != "undefined") {
            $("#ProfileInfo").append("<div class=ProfileTextSmall style=\"margin-top:15px\">"+ lang.email +":</div>");
            $("#ProfileInfo").append("<div class=ProfileTextMedium>"+ buddyObj.Email +" </div>");
        }
        if (buddyObj.MobileNumber != '' && buddyObj.MobileNumber != null && typeof buddyObj.MobileNumber != "undefined") {
            $("#ProfileInfo").append("<div class=ProfileTextSmall style=\"margin-top:15px\">"+ lang.mobile +":</div>");
            $("#ProfileInfo").append("<div class=ProfileTextMedium>"+ buddyObj.MobileNumber +" </div>");
        }
        if (buddyObj.ContactNumber1 != '' && buddyObj.ContactNumber1 != null && typeof buddyObj.ContactNumber1 != "undefined") {
            $("#ProfileInfo").append("<div class=ProfileTextSmall style=\"margin-top:15px\">"+ lang.alternative_contact +":</div>");
            $("#ProfileInfo").append("<div class=ProfileTextMedium>"+ buddyObj.ContactNumber1 +" </div>");
        }
        if (buddyObj.ContactNumber2 != '' && buddyObj.ContactNumber2 != null && typeof buddyObj.ContactNumber2 != "undefined") {
            $("#ProfileInfo").append("<div class=ProfileTextSmall style=\"margin-top:15px\">"+ lang.alternative_contact +":</div>");
            $("#ProfileInfo").append("<div class=ProfileTextMedium>"+ buddyObj.ContactNumber2 +" </div>");
        }

    } else if (typeStr == "contact") {

        var html = "<div style=\"width:200px; cursor:pointer\" onclick=\"EditBuddyWindow('"+ buddy +"')\">";
        html += "<div class=\"buddyProfilePic\" style=\"background-image:url('"+ getPicture(buddy, "contact") +"')\"></div>";
        html += "<div id='ProfileInfo'><i class=\"fa fa-spinner fa-spin\"></i></div>"
        html += "</div>";

        $.jeegoopopup.open({
                html: html,
                width: '200',
                height: 'auto',
                left: leftPos,
                top: topPos,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                contentClass: 'showContactDetails',
                overlay: true,
                opacity: 0,
                draggable: true,
                resizable: false,
                fadeIn: 0
        });

        $("#ProfileInfo").html("");
        $("#ProfileInfo").append("<div class=ProfileTextLarge style='margin:15px 0px 4px 0px'>"+ buddyObj.CallerIDName +"</div>");
        $("#ProfileInfo").append("<div class=ProfileTextMedium>"+ buddyObj.Desc +"</div>");

        if(buddyObj.Email != '' && buddyObj.Email != null && typeof buddyObj.Email != "undefined"){
            $("#ProfileInfo").append("<div class=ProfileTextSmall style=\"margin-top:15px\">"+ lang.email +":</div>");
            $("#ProfileInfo").append("<div class=ProfileTextMedium>"+ buddyObj.Email +" </div>");
        }
        if(buddyObj.MobileNumber != '' && buddyObj.MobileNumber != null && typeof buddyObj.MobileNumber != "undefined"){
            $("#ProfileInfo").append("<div class=ProfileTextSmall style=\"margin-top:15px\">"+ lang.mobile +":</div>");
            $("#ProfileInfo").append("<div class=ProfileTextMedium>"+ buddyObj.MobileNumber +" </div>");
        }
        if(buddyObj.ContactNumber1 != '' && buddyObj.ContactNumber1 != null && typeof buddyObj.ContactNumber1 != "undefined"){
            $("#ProfileInfo").append("<div class=ProfileTextSmall style=\"margin-top:15px\">"+ lang.alternative_contact +":</div>");
            $("#ProfileInfo").append("<div class=ProfileTextMedium>"+ buddyObj.ContactNumber1 +" </div>");
        }
        if(buddyObj.ContactNumber2 != '' && buddyObj.ContactNumber2 != null && typeof buddyObj.ContactNumber2 != "undefined"){
            $("#ProfileInfo").append("<div class=ProfileTextSmall style=\"margin-top:15px\">"+ lang.alternative_contact +":</div>");
            $("#ProfileInfo").append("<div class=ProfileTextMedium>"+ buddyObj.ContactNumber2 +" </div>");
        }

    }

    $(window).resize(function() {
       $.jeegoopopup.width('200').height('auto').left(leftPos).top(topPos);
    });

    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); } });
}

// Device and Settings
// ===================
function ChangeSettings(lineNum, obj){

    var leftPos = event.pageX - 138;
    var topPos = event.pageY + 28;

    if (($(window).height() - event.pageY) < 300) { topPos = event.pageY - 170; }

    // Check if you are in a call
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null) return;
    var session = lineObj.SipSession;

    var html = "<div id=DeviceSelector style=\"width:250px\"></div>";

    $.jeegoopopup.open({
                html: html,
                width: "auto",
                height: "auto",
                left: leftPos,
                top: topPos,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                contentClass: 'callSettingsContent',
                overlay: true,
                opacity: 0,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    var audioSelect = $('<select/>');
    audioSelect.prop("id", "audioSrcSelect");
    audioSelect.css("width", "100%");

    var videoSelect = $('<select/>');
    videoSelect.prop("id", "videoSrcSelect");
    videoSelect.css("width", "100%");

    var speakerSelect = $('<select/>');
    speakerSelect.prop("id", "audioOutputSelect");
    speakerSelect.css("width", "100%");

    var ringerSelect = $('<select/>');
    ringerSelect.prop("id", "ringerSelect");
    ringerSelect.css("width", "100%");

    // Handle Audio Source changes (Microphone)
    audioSelect.change(function() {

        console.log("Call to change Microphone: ", this.value);

        // First Stop Recording the call
        var mustRestartRecording = false;
        if(session.data.mediaRecorder && session.data.mediaRecorder.state == "recording"){
            StopRecording(lineNum, true);
            mustRestartRecording = true;
        }

        // Stop Monitoring
        if(lineObj.LocalSoundMeter) lineObj.LocalSoundMeter.stop();

        // Save Setting
        session.data.AudioSourceDevice = this.value;

        var constraints = {
            audio: {
                deviceId: (this.value != "default")? { exact: this.value } : "default"
            },
            video: false
        }
        navigator.mediaDevices.getUserMedia(constraints).then(function(newStream){
            // Assume that since we are selecting from a dropdown, this is possible
            var newMediaTrack = newStream.getAudioTracks()[0];
            var pc = session.sessionDescriptionHandler.peerConnection;
            pc.getSenders().forEach(function (RTCRtpSender) {
                if(RTCRtpSender.track && RTCRtpSender.track.kind == "audio") {
                    console.log("Switching Audio Track : "+ RTCRtpSender.track.label + " to "+ newMediaTrack.label);
                    RTCRtpSender.track.stop(); // Must stop, or this mic will stay in use
                    RTCRtpSender.replaceTrack(newMediaTrack).then(function(){
                        // Start recording again
                        if(mustRestartRecording) StartRecording(lineNum);
                        // Monitor audio stream
                        lineObj.LocalSoundMeter = StartLocalAudioMediaMonitoring(lineNum, session);
                    }).catch(function(e){
                        console.error("Error replacing track: ", e);
                    });
                }
            });
        }).catch(function(e){
            console.error("Error on getUserMedia");
        });
    });

    // Handle output change (speaker)
    speakerSelect.change(function() {
        console.log("Call to change Speaker: ", this.value);

        // Save Setting
        session.data.AudioOutputDevice = this.value;

        // Also change the sinkId
        // ======================
        var sinkId = this.value;
        console.log("Attempting to set Audio Output SinkID for line "+ lineNum +" [" + sinkId + "]");

        // Remote audio
        var element = $("#line-"+ lineNum +"-remoteAudio").get(0);
        if (element) {
            if (typeof element.sinkId !== 'undefined') {
                element.setSinkId(sinkId).then(function(){
                    console.log("sinkId applied: "+ sinkId);
                }).catch(function(e){
                    console.warn("Error using setSinkId: ", e);
                });
            } else {
                console.warn("setSinkId() is not possible using this browser.")
            }
        }
    });

    // Handle video input change (WebCam)
    videoSelect.change(function(){
        console.log("Call to change WebCam");

        switchVideoSource(lineNum, this.value);
    });

    // Load Devices
    if(!navigator.mediaDevices) {
        console.warn("navigator.mediaDevices not possible.");
        return;
    }

    for (var i = 0; i < AudioinputDevices.length; ++i) {
        var deviceInfo = AudioinputDevices[i];
        var devideId = deviceInfo.deviceId;
        var DisplayName = (deviceInfo.label)? deviceInfo.label : "";
        if(DisplayName.indexOf("(") > 0) DisplayName = DisplayName.substring(0,DisplayName.indexOf("("));

        // Create Option
        var option = $('<option/>');
        option.prop("value", devideId);
        option.text((DisplayName != "")? DisplayName : "Microphone");
        if(session.data.AudioSourceDevice == devideId) option.prop("selected", true);
        audioSelect.append(option);
    }
    for (var i = 0; i < VideoinputDevices.length; ++i) {
        var deviceInfo = VideoinputDevices[i];
        var devideId = deviceInfo.deviceId;
        var DisplayName = (deviceInfo.label)? deviceInfo.label : "";
        if(DisplayName.indexOf("(") > 0) DisplayName = DisplayName.substring(0,DisplayName.indexOf("("));

        // Create Option
        var option = $('<option/>');
        option.prop("value", devideId);
        option.text((DisplayName != "")? DisplayName : "Webcam");
        if(session.data.VideoSourceDevice == devideId) option.prop("selected", true);
        videoSelect.append(option);
    }
    if(HasSpeakerDevice){
        for (var i = 0; i < SpeakerDevices.length; ++i) {
            var deviceInfo = SpeakerDevices[i];
            var devideId = deviceInfo.deviceId;
            var DisplayName = (deviceInfo.label)? deviceInfo.label : "";
            if(DisplayName.indexOf("(") > 0) DisplayName = DisplayName.substring(0,DisplayName.indexOf("("));

            // Create Option
            var option = $('<option/>');
            option.prop("value", devideId);
            option.text((DisplayName != "")? DisplayName : "Speaker"); 
            if(session.data.AudioOutputDevice == devideId) option.prop("selected", true);
            speakerSelect.append(option);
        }
    }

    // Mic Serttings
    $("#DeviceSelector").append("<div class=callSettingsDvs>"+ lang.microphone +": </div>");
    $("#DeviceSelector").append(audioSelect);
    
    // Speaker
    if(HasSpeakerDevice){
        $("#DeviceSelector").append("<div class=callSettingsDvs>"+ lang.speaker +": </div>");
        $("#DeviceSelector").append(speakerSelect);
    }
    // Camera
    if(session.data.withvideo == true){
        $("#DeviceSelector").append("<div class=callSettingsDvs>"+ lang.camera +": </div>");
        $("#DeviceSelector").append(videoSelect);
    }

    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); } });
}

// Media Presentation
// ==================
function PresentCamera(lineNum){
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Line or Session is Null.");
        return;
    }
    var session = lineObj.SipSession;

    $("#line-"+ lineNum +"-src-camera").prop("disabled", true);
    $("#line-"+ lineNum +"-src-canvas").prop("disabled", false);
    $("#line-"+ lineNum +"-src-desktop").prop("disabled", false);
    $("#line-"+ lineNum +"-src-video").prop("disabled", false);
    $("#line-"+ lineNum +"-src-blank").prop("disabled", false);

    $("#line-"+ lineNum + "-scratchpad-container").hide();
    RemoveScratchpad(lineNum);
    $("#line-"+ lineNum +"-sharevideo").hide();
    $("#line-"+ lineNum +"-sharevideo").get(0).pause();
    $("#line-"+ lineNum +"-sharevideo").get(0).removeAttribute('src');
    $("#line-"+ lineNum +"-sharevideo").get(0).load();
    window.clearInterval(session.data.videoResampleInterval);

    $("#line-"+ lineNum + "-localVideo").show();
    $("#line-"+ lineNum + "-remoteVideo").appendTo("#line-"+ lineNum + "-stage-container");

    switchVideoSource(lineNum, session.data.VideoSourceDevice);
}
function PresentScreen(lineNum){
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Line or Session is Null.");
        return;
    }
    var session = lineObj.SipSession;

    $("#line-"+ lineNum +"-src-camera").prop("disabled", false);
    $("#line-"+ lineNum +"-src-canvas").prop("disabled", false);
    $("#line-"+ lineNum +"-src-desktop").prop("disabled", true);
    $("#line-"+ lineNum +"-src-video").prop("disabled", false);
    $("#line-"+ lineNum +"-src-blank").prop("disabled", false);

    $("#line-"+ lineNum + "-scratchpad-container").hide();
    RemoveScratchpad(lineNum);
    $("#line-"+ lineNum +"-sharevideo").hide();
    $("#line-"+ lineNum +"-sharevideo").get(0).pause();
    $("#line-"+ lineNum +"-sharevideo").get(0).removeAttribute('src');
    $("#line-"+ lineNum +"-sharevideo").get(0).load();
    window.clearInterval(session.data.videoResampleInterval);

    $("#line-"+ lineNum + "-localVideo").hide();
    $("#line-"+ lineNum + "-remoteVideo").appendTo("#line-"+ lineNum + "-stage-container");

    ShareScreen(lineNum);
}
function PresentScratchpad(lineNum){
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Line or Session is Null.");
        return;
    }
    var session = lineObj.SipSession;

    $("#line-"+ lineNum +"-src-camera").prop("disabled", false);
    $("#line-"+ lineNum +"-src-canvas").prop("disabled", true);
    $("#line-"+ lineNum +"-src-desktop").prop("disabled", false);
    $("#line-"+ lineNum +"-src-video").prop("disabled", false);
    $("#line-"+ lineNum +"-src-blank").prop("disabled", false);

    $("#line-"+ lineNum + "-scratchpad-container").hide();
    RemoveScratchpad(lineNum);
    $("#line-"+ lineNum +"-sharevideo").hide();
    $("#line-"+ lineNum +"-sharevideo").get(0).pause();
    $("#line-"+ lineNum +"-sharevideo").get(0).removeAttribute('src');
    $("#line-"+ lineNum +"-sharevideo").get(0).load();
    window.clearInterval(session.data.videoResampleInterval);

    $("#line-"+ lineNum + "-localVideo").hide();
    $("#line-"+ lineNum + "-remoteVideo").appendTo("#line-"+ lineNum + "-preview-container");

    SendCanvas(lineNum);
}
function PresentVideo(lineNum){

    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Line or Session is Null.");
        return;
    }
    var session = lineObj.SipSession;

    $.jeegoopopup.close();

    var presentVideoHtml = "<div>";
    presentVideoHtml += '<div id="windowCtrls"><img id="closeImg" src="images/3_close.svg" title="Close" /></div>';
    presentVideoHtml += "<label for=SelectVideoToSend class=customBrowseButton style=\"display: block; margin: 26px auto;\">Select File</label>";
    presentVideoHtml += "<div class=\"UiWindowField\"><input type=file  accept=\"video/*\" id=SelectVideoToSend></div>";
    presentVideoHtml += "</div>";

    $.jeegoopopup.open({
                html: presentVideoHtml,
                width: '180',
                height: '80',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                overlay: true,
                opacity: 0,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $("#SelectVideoToSend").on('change', function(event){
            var input = event.target;
            if (input.files.length >= 1) {

                $.jeegoopopup.close();

                // Send Video (Can only send one file)
                SendVideo(lineNum, URL.createObjectURL(input.files[0]));
            }
            else {
                console.warn("Please select a file to present.");
            }
    });

    $("#closeImg").click(function() { $.jeegoopopup.close(); });
    $("#jg_popup_overlay").click(function() { $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $.jeegoopopup.close(); } });
}
function PresentBlank(lineNum){
    var lineObj = FindLineByNumber(lineNum);
    if(lineObj == null || lineObj.SipSession == null){
        console.warn("Line or Session is Null.");
        return;
    }
    var session = lineObj.SipSession;

    $("#line-"+ lineNum +"-src-camera").prop("disabled", false);
    $("#line-"+ lineNum +"-src-canvas").prop("disabled", false);
    $("#line-"+ lineNum +"-src-desktop").prop("disabled", false);
    $("#line-"+ lineNum +"-src-video").prop("disabled", false);
    $("#line-"+ lineNum +"-src-blank").prop("disabled", true);
    
    $("#line-"+ lineNum + "-scratchpad-container").hide();
    RemoveScratchpad(lineNum);
    $("#line-"+ lineNum +"-sharevideo").hide();
    $("#line-"+ lineNum +"-sharevideo").get(0).pause();
    $("#line-"+ lineNum +"-sharevideo").get(0).removeAttribute('src');
    $("#line-"+ lineNum +"-sharevideo").get(0).load();
    window.clearInterval(session.data.videoResampleInterval);

    $("#line-"+ lineNum + "-localVideo").hide();
    $("#line-"+ lineNum + "-remoteVideo").appendTo("#line-"+ lineNum + "-stage-container");

    DisableVideoStream(lineNum);
}
function RemoveScratchpad(lineNum){
    var scratchpad = GetCanvas("line-" + lineNum + "-scratchpad");
    if(scratchpad != null){
        window.clearInterval(scratchpad.redrawIntrtval);

        RemoveCanvas("line-" + lineNum + "-scratchpad");
        $("#line-"+ lineNum + "-scratchpad-container").empty();

        scratchpad = null;
    }
}

// Call Statistics
// ===============
function ShowCallStats(lineNum, obj){
    console.log("Show Call Stats");
    $("#line-"+ lineNum +"-AudioStats").show(300);
}
function HideCallStats(lineNum, obj){
    console.log("Hide Call Stats");
    $("#line-"+ lineNum +"-AudioStats").hide(300);
}

// Chatting
// ========
function chatOnbeforepaste(event, obj, buddy){
    console.log("Handle paste, checking for Images...");
    var items = (event.clipboardData || event.originalEvent.clipboardData).items;

    // Find pasted image among pasted items
    var preventDefault = false;
    for (var i = 0; i < items.length; i++) {
        if (items[i].type.indexOf("image") === 0) {
            console.log("Image found! Opening image editor...");

            var blob = items[i].getAsFile();

            // Read the image in
            var reader = new FileReader();
            reader.onload = function (event) {

                // Image has loaded, open Image Preview Editor
                // ===========================================
                console.log("Image loaded... setting placeholder...");
                var placeholderImage = new Image();
                placeholderImage.onload = function () {

                    console.log("Placeholder loaded... CreateImageEditor...");

                    CreateImageEditor(buddy, placeholderImage);
                }
                placeholderImage.src = event.target.result;
            }
            reader.readAsDataURL(blob);

            preventDefault = true;
            continue;
        }
    }

    // Pevent default if you found an image
    if (preventDefault) event.preventDefault();
}
function chatOnkeydown(event, obj, buddy) {

    var keycode = (event.keyCode ? event.keyCode : event.which);
    if (keycode == '13') {
        if (event.ctrlKey) {
            SendChatMessage(buddy, '');
            return false;
        }
    }
}

function ReformatMessage(str) {
    var msg = str;
    // Simple tex=>HTML 
    msg = msg.replace(/</gi, "&lt;");
    msg = msg.replace(/>/gi, "&gt;");
    msg = msg.replace(/\n/gi, "<br>");
    // Emojy
    // Skype: :) :( :D :O ;) ;( (:| :| :P :$ :^) |-) |-( :x ]:)
    // (cool) (hearteyes) (stareyes) (like) (unamused) (cwl) (xd) (pensive) (weary) (hysterical) (flushed) (sweatgrinning) (disappointed) (loudlycrying) (shivering) (expressionless) (relieved) (inlove) (kiss) (yawn) (puke) (doh) (angry) (wasntme) (worry) (confused) (veryconfused) (mm) (nerd) (rainbowsmile) (devil) (angel) (envy) (makeup) (think) (rofl) (happy) (smirk) (nod) (shake) (waiting) (emo) (donttalk) (idea) (talk) (swear) (headbang) (learn) (headphones) (morningafter) (selfie) (shock) (ttm) (dream)
    msg = msg.replace(/(:\)|:\-\)|:o\))/g, String.fromCodePoint(0x1F642));     // :) :-) :o)
    msg = msg.replace(/(:\(|:\-\(|:o\()/g, String.fromCodePoint(0x1F641));     // :( :-( :o(
    msg = msg.replace(/(;\)|;\-\)|;o\))/g, String.fromCodePoint(0x1F609));     // ;) ;-) ;o)
    msg = msg.replace(/(:'\(|:'\-\()/g, String.fromCodePoint(0x1F62A));        // :'( :'‑(
    msg = msg.replace(/(:'\(|:'\-\()/g, String.fromCodePoint(0x1F602));        // :') :'‑)
    msg = msg.replace(/(:\$)/g, String.fromCodePoint(0x1F633));                // :$
    msg = msg.replace(/(>:\()/g, String.fromCodePoint(0x1F623));               // >:(
    msg = msg.replace(/(:\×)/g, String.fromCodePoint(0x1F618));                // :×
    msg = msg.replace(/(:\O|:\‑O)/g, String.fromCodePoint(0x1F632));             // :O :‑O
    msg = msg.replace(/(:P|:\-P|:p|:\-p)/g, String.fromCodePoint(0x1F61B));      // :P :-P :p :-p
    msg = msg.replace(/(;P|;\-P|;p|;\-p)/g, String.fromCodePoint(0x1F61C));      // ;P ;-P ;p ;-p
    msg = msg.replace(/(:D|:\-D)/g, String.fromCodePoint(0x1F60D));             // :D :-D
    msg = msg.replace(/(\(like\))/g, String.fromCodePoint(0x1F44D));           // (like)

    return msg;
}
function getPicture(buddy, typestr) {

    if (buddy == "profilePicture") {
        // Special handling for profile image
        var dbImg = profilePictureBsf;
        if (dbImg == null) {
            return hostingPrefix + "images/default.png";
        }
        else {
            return dbImg; 
        }
    }

    typestr = (typestr)? typestr : "extension";

    var buddyObj = FindBuddyByIdentity(buddy);
    if (buddyObj.imageObjectURL != '') {
        // Use Cache
        return buddyObj.imageObjectURL;
    }

    var dbImg = imgagesObj["img-"+ buddy +"-"+ typestr];

    if (dbImg == null) {
        return hostingPrefix + "images/default.png";
    } else {
        buddyObj.imageObjectURL = URL.createObjectURL(base64toBlob(dbImg, 'image/png'));
        return buddyObj.imageObjectURL;
    }
}

// Image Editor
// ============
function CreateImageEditor(buddy, placeholderImage){
    // Show Interface
    // ==============
    console.log("Setting Up ImageEditor...");
    if ($("#contact-" + buddy + "-imagePastePreview").is(":visible")) {
        console.log("Resetting ImageEditor...");
        $("#contact-" + buddy + "-imagePastePreview").empty();
        RemoveCanvas("contact-" + buddy + "-imageCanvas")
    } else {
        $("#contact-" + buddy + "-imagePastePreview").show();
    }
    // Create UI
    // =========
    var toolBarDiv = $('<div/>');
    toolBarDiv.css("margin-bottom", "5px")
    toolBarDiv.append('<button class="toolBarButtons" title="Select" onclick="ImageEditor_Select(\''+ buddy +'\')"><i class="fa fa-mouse-pointer"></i></button>');
    toolBarDiv.append('&nbsp;|&nbsp;');
    toolBarDiv.append('<button class="toolBarButtons" title="Draw" onclick="ImageEditor_FreedrawPen(\''+ buddy +'\')"><i class="fa fa-pencil"></i></button>');
    toolBarDiv.append('<button class="toolBarButtons" title="Paint" onclick="ImageEditor_FreedrawPaint(\''+ buddy +'\')"><i class="fa fa-paint-brush"></i></button>');
    toolBarDiv.append('&nbsp;|&nbsp;');
    toolBarDiv.append('<button class="toolBarButtons" title="Select Line Color" onclick="ImageEditor_SetectLineColor(\''+ buddy +'\')"><i class="fa fa-pencil-square-o" style="color:rgb(255, 0, 0)"></i></button>');
    toolBarDiv.append('<button class="toolBarButtons" title="Select Fill Color" onclick="ImageEditor_SetectFillColor(\''+ buddy +'\')"><i class="fa fa-pencil-square" style="color:rgb(255, 0, 0)"></i></button>');
    toolBarDiv.append('&nbsp;|&nbsp;');
    toolBarDiv.append('<button class="toolBarButtons" title="Add Circle" onclick="ImageEditor_AddCircle(\''+ buddy +'\')"><i class="fa fa-circle"></i></button>');
    toolBarDiv.append('<button class="toolBarButtons" title="Add Rectangle" onclick="ImageEditor_AddRectangle(\''+ buddy +'\')"><i class="fa fa-stop"></i></button>');
    toolBarDiv.append('<button class="toolBarButtons" title="Add Triangle" onclick="ImageEditor_AddTriangle(\''+ buddy +'\')"><i class="fa fa-play"></i></button>');
    toolBarDiv.append('<button class="toolBarButtons" title="Add Emoji" onclick="ImageEditor_SetectEmoji(\''+ buddy +'\')"><i class="fa fa-smile-o"></i></button>');
    toolBarDiv.append('<button class="toolBarButtons" title="Add Text" onclick="ImageEditor_AddText(\''+ buddy +'\')"><i class="fa fa-font"></i></button>');
    toolBarDiv.append('<button class="toolBarButtons" title="Delete Selected Items" onclick="ImageEditor_Clear(\''+ buddy +'\')"><i class="fa fa-times"></i></button>');
    toolBarDiv.append('<button class="toolBarButtons" title="Clear All" onclick="ImageEditor_ClearAll(\''+ buddy +'\')"><i class="fa fa-trash"></i></button>');
    toolBarDiv.append('&nbsp;|&nbsp;');
    toolBarDiv.append('<button class="toolBarButtons" title="Pan" onclick="ImageEditor_Pan(\''+ buddy +'\')"><i class="fa fa-hand-paper-o"></i></button>');
    toolBarDiv.append('<button class="toolBarButtons" title="Zoom In" onclick="ImageEditor_ZoomIn(\''+ buddy +'\')"><i class="fa fa-search-plus"></i></button>');
    toolBarDiv.append('<button class="toolBarButtons" title="Zoom Out" onclick="ImageEditor_ZoomOut(\''+ buddy +'\')"><i class="fa fa-search-minus"></i></button>');
    toolBarDiv.append('<button class="toolBarButtons" title="Reset Pan & Zoom" onclick="ImageEditor_ResetZoom(\''+ buddy +'\')"><i class="fa fa-search" aria-hidden="true"></i></button>');
    toolBarDiv.append('&nbsp;|&nbsp;');
    toolBarDiv.append('<button class="toolBarButtons" title="Cancel" onclick="ImageEditor_Cancel(\''+ buddy +'\')"><i class="fa fa-times-circle"></i></button>');
    toolBarDiv.append('<button class="toolBarButtons" title="Send" onclick="ImageEditor_Send(\''+ buddy +'\')"><i class="fa fa-paper-plane"></i></button>');
    $("#contact-" + buddy + "-imagePastePreview").append(toolBarDiv);

    // Create the canvas
    // =================
    var newCanvas = $('<canvas/>');
    newCanvas.prop("id", "contact-" + buddy + "-imageCanvas");
    newCanvas.css("border", "1px solid #CCCCCC");
    $("#contact-" + buddy + "-imagePastePreview").append(newCanvas);
    console.log("Canvas for ImageEditor created...");

    var imgWidth = placeholderImage.width;
    var imgHeight = placeholderImage.height;
    var maxWidth = $("#contact-" + buddy + "-imagePastePreview").width()-2; // for the border
    var maxHeight = 480;
    $("#contact-" + buddy + "-imageCanvas").prop("width", maxWidth);
    $("#contact-" + buddy + "-imageCanvas").prop("height", maxHeight);

    // Handle Initial Zoom
    var zoomToFitImage = 1;
    var zoomWidth = 1;
    var zoomHeight = 1;
    if (imgWidth > maxWidth || imgHeight > maxHeight)
    {
        if(imgWidth > maxWidth)
        {
            zoomWidth = (maxWidth / imgWidth);
        }
        if(imgHeight > maxHeight)
        {
            zoomHeight = (maxHeight / imgHeight);
            console.log("Scale to fit height: "+ zoomHeight);
        }
        zoomToFitImage = Math.min(zoomWidth, zoomHeight) // need the smallest because less is more zoom.
        console.log("Scale down to fit: "+ zoomToFitImage);

        // Shape the canvas to fit the image and the new zoom
        imgWidth = imgWidth * zoomToFitImage;
        imgHeight = imgHeight * zoomToFitImage;
        console.log("resizing canvas to fit new image size...");
        $("#contact-" + buddy + "-imageCanvas").prop("width", imgWidth);
        $("#contact-" + buddy + "-imageCanvas").prop("height", imgHeight);
    }
    else {
        console.log("Image is able to fit, resizing canvas...");
        $("#contact-" + buddy + "-imageCanvas").prop("width", imgWidth);
        $("#contact-" + buddy + "-imageCanvas").prop("height", imgHeight);
    }

    // Fabric Canvas API
    // =================
    console.log("Creating fabric API...");
    var canvas = new fabric.Canvas("contact-" + buddy + "-imageCanvas");
    canvas.id = "contact-" + buddy + "-imageCanvas";
    canvas.ToolSelected = "None";
    canvas.PenColour = "rgb(255, 0, 0)";
    canvas.PenWidth = 2;
    canvas.PaintColour = "rgba(227, 230, 3, 0.6)";
    canvas.PaintWidth = 10;
    canvas.FillColour = "rgb(255, 0, 0)";
    canvas.isDrawingMode = false;

    canvas.selectionColor = 'rgba(112,179,233,0.25)';
    canvas.selectionBorderColor = 'rgba(112,179,233, 0.8)';
    canvas.selectionLineWidth = 1;

    // Zoom to fit Width or Height
    // ===========================
    canvas.setZoom(zoomToFitImage);

    // Canvas Events
    // =============
    canvas.on('mouse:down', function(opt) {
        var evt = opt.e;

        if (this.ToolSelected == "Pan") {
            this.isDragging = true;
            this.selection = false;
            this.lastPosX = evt.clientX;
            this.lastPosY = evt.clientY;
        }
        // Make nicer grab handles
        if (opt.target != null){
            if (evt.altKey === true)
            {
                opt.target.lockMovementX = true;
            }
            if(evt.shiftKey === true)
            {
                opt.target.lockMovementY = true;
            }
            opt.target.set({
                transparentCorners: false,
                borderColor: 'rgba(112,179,233, 0.4)',
                cornerColor: 'rgba(112,179,233, 0.8)',
                cornerSize: 6
            });
        }
    });
    canvas.on('mouse:move', function(opt) {
        if (this.isDragging) {
            var e = opt.e;
            this.viewportTransform[4] += e.clientX - this.lastPosX;
            this.viewportTransform[5] += e.clientY - this.lastPosY;
            this.requestRenderAll();
            this.lastPosX = e.clientX;
            this.lastPosY = e.clientY;
        }
    });
    canvas.on('mouse:up', function(opt) {
        this.isDragging = false;
        this.selection = true;
        if(opt.target != null){
            opt.target.lockMovementX = false;
            opt.target.lockMovementY = false;
        }
    });
    canvas.on('mouse:wheel', function(opt) {
        var delta = opt.e.deltaY;
        var pointer = canvas.getPointer(opt.e);
        var zoom = canvas.getZoom();
        zoom = zoom + delta/200;
        if (zoom > 10) zoom = 10;
        if (zoom < 0.1) zoom = 0.1;
        canvas.zoomToPoint({ x: opt.e.offsetX, y: opt.e.offsetY }, zoom);
        opt.e.preventDefault();
        opt.e.stopPropagation();
    });

    // Add Image
    // ==========
    canvas.backgroundImage = new fabric.Image(placeholderImage);

    CanvasCollection.push(canvas);

    // Add Key Press Events
    // ====================
    $("#contact-" + buddy + "-imagePastePreview").keydown(function(evt) {
        evt = evt || window.event;
        var key = evt.keyCode;
        console.log("Key press on Image Editor ("+ buddy +"): "+ key);

        // Delete Key
        if (key == 46) ImageEditor_Clear(buddy);
    });

    console.log("ImageEditor: "+ canvas.id +" created");

    ImageEditor_FreedrawPen(buddy);
}
function GetCanvas(canvasId){
    for(var c = 0; c < CanvasCollection.length; c++){
        try {
            if(CanvasCollection[c].id == canvasId) return CanvasCollection[c];
        } catch(e) {
            console.warn("CanvasCollection.id not available");
        }
    }
    return null;
}
function RemoveCanvas(canvasId){
    for(var c = 0; c < CanvasCollection.length; c++){
        try{
            if(CanvasCollection[c].id == canvasId) {
                console.log("Found Old Canvas, Disposing...");

                CanvasCollection[c].clear()
                CanvasCollection[c].dispose();

                CanvasCollection[c].id = "--deleted--";

                console.log("CanvasCollection.splice("+ c +", 1)");
                CanvasCollection.splice(c, 1);
                break;
            }
        }
        catch(e){ }
    }
    console.log("There are "+ CanvasCollection.length +" canvas now.");
}
var ImageEditor_Select = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        canvas.ToolSelected = "none";
        canvas.isDrawingMode = false;
        return true;
    }
    return false;
}
var ImageEditor_FreedrawPen = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        canvas.freeDrawingBrush.color = canvas.PenColour;
        canvas.freeDrawingBrush.width = canvas.PenWidth;
        canvas.ToolSelected = "Draw";
        canvas.isDrawingMode = true;
        console.log(canvas)
        return true;
    }
    return false;
}
var ImageEditor_FreedrawPaint = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        canvas.freeDrawingBrush.color = canvas.PaintColour;
        canvas.freeDrawingBrush.width = canvas.PaintWidth;
        canvas.ToolSelected = "Paint";
        canvas.isDrawingMode = true;
        return true;
    }
    return false;
}
var ImageEditor_Pan = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        canvas.ToolSelected = "Pan";
        canvas.isDrawingMode = false;
        return true;
    }
    return false;
}
var ImageEditor_ResetZoom = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        canvas.setZoom(1);
        canvas.setViewportTransform([1,0,0,1,0,0]);
        return true;
    } 
    return false;
}
var ImageEditor_ZoomIn = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        var zoom = canvas.getZoom();
        zoom = zoom + 0.5;
        if (zoom > 10) zoom = 10;
        if (zoom < 0.1) zoom = 0.1;

        var point = new fabric.Point(canvas.getWidth() / 2, canvas.getHeight() / 2);
        var center = fabric.util.transformPoint(point, canvas.viewportTransform);

        canvas.zoomToPoint(point, zoom);

        return true;
    }
    return false;
}
var ImageEditor_ZoomOut = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        var zoom = canvas.getZoom();
        zoom = zoom - 0.5;
        if (zoom > 10) zoom = 10;
        if (zoom < 0.1) zoom = 0.1;

        var point = new fabric.Point(canvas.getWidth() / 2, canvas.getHeight() / 2);
        var center = fabric.util.transformPoint(point, canvas.viewportTransform);

        canvas.zoomToPoint(point, zoom);

        return true;
    }
    return false;
}
var ImageEditor_AddCircle = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        canvas.ToolSelected = "none";
        canvas.isDrawingMode = false;
        var circle = new fabric.Circle({
            radius: 20, fill: canvas.FillColour
        })
        canvas.add(circle);
        canvas.centerObject(circle);
        canvas.setActiveObject(circle);
        return true;
    }
    return false;
}
var ImageEditor_AddRectangle = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        canvas.ToolSelected = "none";
        canvas.isDrawingMode = false;
        var rectangle = new fabric.Rect({ 
            width: 40, height: 40, fill: canvas.FillColour
        })
        canvas.add(rectangle);
        canvas.centerObject(rectangle);
        canvas.setActiveObject(rectangle);
        return true;
    }
    return false;
}
var ImageEditor_AddTriangle = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        canvas.ToolSelected = "none";
        canvas.isDrawingMode = false;
        var triangle = new fabric.Triangle({
            width: 40, height: 40, fill: canvas.FillColour
        })
        canvas.add(triangle);
        canvas.centerObject(triangle);
        canvas.setActiveObject(triangle);
        return true;
    }
    return false;
}
var ImageEditor_AddEmoji = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        canvas.ToolSelected = "none";
        canvas.isDrawingMode = false;
        var text = new fabric.Text(String.fromCodePoint(0x1F642), { fontSize : 24 });
        canvas.add(text);
        canvas.centerObject(text);
        canvas.setActiveObject(text);
        return true;
    }
    return false;
}
var ImageEditor_AddText = function (buddy, textString){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        canvas.ToolSelected = "none";
        canvas.isDrawingMode = false;
        var text = new fabric.IText(textString, { fill: canvas.FillColour, fontFamily: 'arial', fontSize : 18 });
        canvas.add(text);
        canvas.centerObject(text);
        canvas.setActiveObject(text);
        return true;
    }
    return false;
}
var ImageEditor_Clear = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        canvas.ToolSelected = "none";
        canvas.isDrawingMode = false;

        var activeObjects = canvas.getActiveObjects();
        for (var i=0; i<activeObjects.length; i++){
            canvas.remove(activeObjects[i]);
        }
        canvas.discardActiveObject();

        return true;
    }
    return false;
}
var ImageEditor_ClearAll = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        var savedBgImage = canvas.backgroundImage;

        canvas.ToolSelected = "none";
        canvas.isDrawingMode = false;
        canvas.clear();

        canvas.backgroundImage = savedBgImage;
        return true;
    }
    return false;
}
var ImageEditor_Cancel = function (buddy){
    console.log("Removing ImageEditor...");

    $("#contact-" + buddy + "-imagePastePreview").empty();
    RemoveCanvas("contact-" + buddy + "-imageCanvas");
    $("#contact-" + buddy + "-imagePastePreview").hide();
}
var ImageEditor_Send = function (buddy){
    var canvas = GetCanvas("contact-" + buddy + "-imageCanvas");
    if(canvas != null) {
        var imgData = canvas.toDataURL({ format: 'png' });
        SendImageDataMessage(buddy, imgData);
        return true;
    }
    return false;
}

// Find something in the message stream
// ====================================
function FindSomething(buddy) {
    $("#contact-" + buddy + "-search").toggle();
    if($("#contact-" + buddy + "-search").is(":visible") == false) {
        RefreshStream(FindBuddyByIdentity(buddy));
    }
    updateScroll(buddy);
}

function preventDefault(e) {
    e.preventDefault();
    e.stopPropagation();
}

// UI Elements
// ===========
function Alert(messageStr, TitleStr, onOk) {

    $("#jg_popup_l").empty();
    $("#jg_popup_b").empty();
    $("#windowCtrls").empty();
    $.jeegoopopup.close();

    var html = "<div class=NoSelect>";
    html += '<div id="windowCtrls"><img id="closeImg" src="images/3_close.svg" title="Close" /></div>';
    html += "<div class=UiText style=\"padding: 0px 20px 8px 20px;\" id=AllertMessageText>" + messageStr + "</div>";
    html += "</div>"

    $.jeegoopopup.open({
                title: TitleStr,
                html: html,
                width: '260',
                height: '150',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                contentClass: 'confirmDelContact',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $("#jg_popup_b").append("<div id=bottomButtons><button id=AlertOkButton style=\"width:80px\">"+ lang.ok +"</button></div>");

    $("#AlertOkButton").click(function () {
        console.log("Alert OK clicked");
        if (onOk) onOk();
    });

    $(window).resize(function() {
       $.jeegoopopup.width('260').height('150');
       $.jeegoopopup.center();
    });

    $("#closeImg").click(function() { $("#jg_popup_b").empty(); $.jeegoopopup.close();});
    $("#AlertOkButton").click(function() { $("#jg_popup_b").empty(); $.jeegoopopup.close();});
    $("#jg_popup_overlay").click(function() { $("#jg_popup_b").empty(); $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $("#jg_popup_b").empty(); $.jeegoopopup.close();} });

}
function AlertConfigExtWindow(messageStr, TitleStr) {

    $("#jg_popup_l").empty(); 
    $("#jg_popup_b").empty(); 
    $("#windowCtrls").empty(); 
    $.jeegoopopup.close();

    videoAudioCheck = 1;

    var html = "<div class=NoSelect>";
    html += '<div id="windowCtrls"><img id="closeImg" src="images/3_close.svg" title="Close" /></div>';
    html += "<div class=UiText style=\"padding: 10px\" id='AllertMessageText'>" + messageStr + "</div>";
    html += "</div>"

    $.jeegoopopup.open({
                title: TitleStr,
                html: html,
                width: '260',
                height: '80',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                contentClass: 'confirmDelContact',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $("#jg_popup_b").append("<div id=bottomButtons><button id=AlertOkButton style=\"width:80px\">"+ lang.ok +"</button></div>");

    $(window).resize(function() {
       $.jeegoopopup.width('260').height('80');
       $.jeegoopopup.center();
    });

    $("#closeImg").click(function() { $("#jg_popup_b").empty(); $("#windowCtrls").empty(); $.jeegoopopup.close(); ConfigureExtensionWindow(); });
    $("#AlertOkButton").click(function() { $("#jg_popup_b").empty(); $("#windowCtrls").empty(); $.jeegoopopup.close(); ConfigureExtensionWindow(); });
}
function Confirm(messageStr, TitleStr, onOk, onCancel) {

    $("#jg_popup_l").empty();
    $("#jg_popup_b").empty();
    $("#windowCtrls").empty();
    $.jeegoopopup.close();

    var html = "<div class=NoSelect>";
    html += '<div id="windowCtrls"><img id="closeImg" src="images/3_close.svg" title="Close" /></div>';
    html += "<div class=UiText style=\"padding: 10px\" id='ConfirmMessageText'>" + messageStr + "</div>";
    html += "</div>";

    $.jeegoopopup.open({
                html: html,
                width: '260',
//                height: '200',
                height: 'auto',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                contentClass: 'confirmDelContact',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $("#jg_popup_b").append("<div id=ConfDelContact><button id=ConfirmOkButton>"+ lang.ok +"</button><button id=ConfirmCancelButton>"+ lang.cancel +"</button></div>");

    $("#ConfirmOkButton").click(function () {
        console.log("Confirm OK clicked");
        if (onOk) onOk();
        $.jeegoopopup.close();
        $("#jg_popup_b").empty();
    });
    $("#ConfirmCancelButton").click(function () {
        console.log("Confirm Cancel clicked");
        if (onCancel) onCancel();
        $.jeegoopopup.close();
        $("#jg_popup_b").empty();
    });

    $(window).resize(function() {
       $.jeegoopopup.width('260').height('auto');
       $.jeegoopopup.center();
    });

    $("#closeImg").click(function() { $("#jg_popup_b").empty(); $.jeegoopopup.close(); });
    $("#jg_popup_overlay").click(function() { $("#jg_popup_b").empty(); $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $("#jg_popup_b").empty(); $.jeegoopopup.close(); } });
}
function ConfirmDelAccount(messageStr, TitleStr, onOk, onCancel) {

    $("#jg_popup_l").empty();
    $("#jg_popup_b").empty();
    $("#windowCtrls").empty();
    $.jeegoopopup.close();

    var html = "<div class=NoSelect>";
    html += "<div id=windowCtrls><img id=closeImg src=images/3_close.svg title=Close /></div>";
    html += "<div class=UiText id='ConfirmMessageText'>" + messageStr + "</div>";
    html += "</div>";

    $.jeegoopopup.open({
                html: html,
                width: '260',
                height: '150',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                contentClass: 'ConfCloseAccount',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $("#jg_popup_b").append("<div id=ConfCloseAccount><button id=ConfirmOkButton>"+ lang.close_user_account +"</button><button id=ConfirmCancelButton>"+ lang.cancel +"</button></div>");

    $("#ConfirmOkButton").click(function () {
        console.log("Confirm OK clicked");
        if (onOk) onOk();
    });
    $("#ConfirmCancelButton").click(function () {
        console.log("Confirm Cancel clicked");
        if (onCancel) onCancel();

        $("#windowCtrls").empty();
        $("#jg_popup_b").empty();
        $.jeegoopopup.close();
        ConfigureExtensionWindow();
    });

    $(window).resize(function() {
       $.jeegoopopup.width('260').height('150');
       $.jeegoopopup.center();
    });

    $("#closeImg").click(function() { $("#jg_popup_b").empty(); $("#windowCtrls").empty(); $.jeegoopopup.close(); ConfigureExtensionWindow(); });
}
function Prompt(messageStr, TitleStr, FieldText, defaultValue, dataType, placeholderText, onOk, onCancel) {

    $("#jg_popup_l").empty();
    $("#jg_popup_b").empty();
    $("#windowCtrls").empty();
    $.jeegoopopup.close();

    var html = "<div class=NoSelect>";
    html += '<div id="windowCtrls"><img id="closeImg" src="images/3_close.svg" title="Close" /></div>';
    html += "<div class=UiText style=\"padding: 10px\" id=PromptMessageText>";
    html += messageStr;
    html += "<div style=\"margin-top:10px\">" + FieldText + " : </div>";
    html += "<div style=\"margin-top:5px\"><INPUT id=PromptValueField type=" + dataType + " value=\"" + defaultValue + "\" placeholder=\"" + placeholderText + "\" style=\"width:98%\"></div>"
    html += "</div>";
    html += "</div>";

    $.jeegoopopup.open({
                html: html,
                width: '260',
                height: '80',
                center: true,
                scrolling: 'no',
                skinClass: 'jg_popup_basic',
                contentClass: 'confirmDelContact',
                overlay: true,
                opacity: 50,
                draggable: true,
                resizable: false,
                fadeIn: 0
    });

    $("#jg_popup_b").append("<div id=bottomButtons><button id=PromptOkButton style=\"width:80px\">"+ lang.ok +"</button>&nbsp;<button id=PromptCancelButton class=UiButton style=\"width:80px\">"+ lang.cancel +"</button></div>");

    $("#PromptOkButton").click(function () {
        console.log("Prompt OK clicked, with value: " + $("#PromptValueField").val());
        if (onOk) onOk($("#PromptValueField").val());
        $.jeegoopopup.close(); 
        $("#jg_popup_b").empty();
    });

    $("#PromptCancelButton").click(function () {
        console.log("Prompt Cancel clicked");
        if (onCancel) onCancel();
        $.jeegoopopup.close(); 
        $("#jg_popup_b").empty();
    });

    $(window).resize(function() {
       $.jeegoopopup.width('260').height('80');
       $.jeegoopopup.center();
    });

    $("#closeImg").click(function() { $("#jg_popup_b").empty(); $.jeegoopopup.close(); });
    $("#jg_popup_overlay").click(function() { $("#jg_popup_b").empty(); $.jeegoopopup.close(); });
    $(window).on('keydown', function(event) { if (event.key == "Escape") { $("#jg_popup_b").empty(); $.jeegoopopup.close(); } });
}

// Device Detection
// ================
function DetectDevices(){
    navigator.mediaDevices.enumerateDevices().then(function(deviceInfos){
        // deviceInfos will not have a populated lable unless to accept the permission
        // during getUserMedia. This normally happens at startup/setup
        // so, from then on these devices will be with lables.
        HasVideoDevice = false;
        HasAudioDevice = false;
        HasSpeakerDevice = false; // Safari and Firefox don't have these
        AudioinputDevices = [];
        VideoinputDevices = [];
        SpeakerDevices = [];
        for (var i = 0; i < deviceInfos.length; ++i) {
            if (deviceInfos[i].kind === "audioinput") {
                HasAudioDevice = true;
                AudioinputDevices.push(deviceInfos[i]);
            } 
            else if (deviceInfos[i].kind === "audiooutput") {
                HasSpeakerDevice = true;
                SpeakerDevices.push(deviceInfos[i]);
            }
            else if (deviceInfos[i].kind === "videoinput") {
                HasVideoDevice = true;
                VideoinputDevices.push(deviceInfos[i]);
            }
        }

    }).catch(function(e){
        console.error("Error enumerating devices", e);
    });
}
DetectDevices();
window.setInterval(function(){
    DetectDevices();
}, 10000);

// STATUS_NULL: 0
// STATUS_INVITE_SENT: 1
// STATUS_1XX_RECEIVED: 2
// STATUS_INVITE_RECEIVED: 3
// STATUS_WAITING_FOR_ANSWER: 4
// STATUS_ANSWERED: 5
// STATUS_WAITING_FOR_PRACK: 6
// STATUS_WAITING_FOR_ACK: 7
// STATUS_CANCELED: 8
// STATUS_TERMINATED: 9
// STATUS_ANSWERED_WAITING_FOR_PRACK: 10
// STATUS_EARLY_MEDIA: 11
// STATUS_CONFIRMED: 12
// STATUS_DONOTDISTURB: 13

// =======================================
// End Of File
