/**
 * @copyright 2021, 2024 Double Bastion LLC <www.doublebastion.com>
 *
 * @author Double Bastion LLC
 *
 * @license GNU AGPL version 3 or any later version
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
 *
 * You should have received a copy of the GNU Affero General Public
 * License along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 *
 * This is a modified version of the original file "app.js".
 *
 * Below is the copyright notice of ctxSip phone (https://github.com/collecttix/ctxSip)
 * which also applies to the original "app.js" file, as part of ctxSip phone:
 *
 *
 *  The MIT License (MIT)
 *
 *  Copyright (c) 2014 Collecttix
 *
 *  Permission is hereby granted, free of charge, to any person obtaining a copy
 *  of this software and associated documentation files (the "Software"), to deal
 *  in the Software without restriction, including without limitation the rights
 *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *  copies of the Software, and to permit persons to whom the Software is
 *  furnished to do so, subject to the following conditions:
 *
 *  The above copyright notice and this permission notice shall be included in
 *  all copies or substantial portions of the Software.
 *
 *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *  THE SOFTWARE.
 *
 */


/* globals SIP, user, moment, Stopwatch */

$(document).ready(function() {

    // Show system notifications on incoming calls
    function incomingCallNote() {
       var noticeOptions = { body : "New incoming call !!!", icon : "images/sip_trip_phone_logo.svg" }
       var callNotification = new Notification("SIP Trip Phone incoming call", noticeOptions);
       callNotification.onclick = function (event) { callNotification.close(); return; }

       if (document.visibilityState == "visible") {
           return;
       } else { setTimeout(incomingCallNote, 8000); }
    }

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

    function decryptAES(token, key) {
        var code = CryptoJS.AES.decrypt(token, key);
        var dectoken = code.toString(CryptoJS.enc.Utf8);
        return dectoken;
    }

    var mediaRecorder = null;
    var fetchedTkn = localStorage.getItem('sipUserToken') || null;
    var kfcook = document.cookie.match('(^|;)\\s*crsptknks\\s*=\\s*([^;]+)')?.pop() || '';
    var userSIPToken = decryptAES(fetchedTkn, kfcook);

    localStorage.removeItem('sipUserToken');
    document.cookie = 'crsptknks=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; samesite=strict; secure';

    var user = JSON.parse(localStorage.getItem('SIPCreds'));
    var traceornot = (user.Tracesipmsg == 1)? true : false;

    // Add the 'available phone numbers' drop-down list
    var stpVoiceNmbrs = user.Voicenumbers;
    var stpVoiceNbOpt = '';
    var stpVoiceNmbrsArr = [];

    if (stpVoiceNmbrs != '') {

	stpVoiceNmbrsArr = stpVoiceNmbrs.split(",");
	var stpDefNmbrFdb = user.Defaultvoicenumber;

	for (var v = 0; v < stpVoiceNmbrsArr.length; v++) {
             stpVoiceNbOpt += '<option value="'+ stpVoiceNmbrsArr[v] +'" class="stpSlctFrmNmbrs">'+ stpVoiceNmbrsArr[v] +'</option>';
	}
    }
    stpVoiceNbOpt += '<option value="'+ user.User +'" class="stpSlctFrmNmbrs">'+ user.User +'</option>';

    $("#fromNumber").append(stpVoiceNbOpt);

    if (stpDefNmbrFdb != '' && stpDefNmbrFdb != null) {
        $("#fromNumber").val(stpDefNmbrFdb);
    } else {
        if (stpVoiceNmbrsArr.length > 0) {
            $("#fromNumber").val(stpVoiceNmbrsArr[0]);
        } else { $("#fromNumber").val(user.User); }
    }

    // Adjust height of initial screen, call history list and width of dial pad
    $("#sip-splash").css({ "height" : window.innerHeight - 111 });
    $("#sip-logitems").css({ "height" : window.innerHeight - 141 });

    $(window).resize(function() {
      $("#sip-splash").css({ "height" : window.innerHeight - 111 });
      $("#sip-logitems").css({ "height" : window.innerHeight - 141 });
    });

    var calcWidth = parseInt(window.innerWidth - 30) + "px";
    var calcHeight = parseInt(window.innerHeight - 111) + "px";
    $("#sip-dialpad").css({ "width" : calcWidth, "height" : calcHeight });

    var calcMargin =  parseInt(($("#sip-dialpad").height() - $("#dialpadWrap").height()) / 2)  + "px auto";
    $("#dialpadWrap").css("margin", calcMargin);

    // Get the contacts with phone numbers and the Nextcloud users with phone numbers (that were not set up as private)
    var contactsData = JSON.parse(localStorage.getItem('NCContacts'));

    // Sort contacts alphabetically by first name
    contactsData.sort(function(a, b) {
        if (a.contact_name < b.contact_name) return -1;
        if (a.contact_name > b.contact_name) return 1;
        return 0;
    });

    // First list regular contacts, then Nextcloud users
    contactsData.sort(function(c, d) {
        if (c.contact_type == "regular_contact" && d.contact_type == "ncuser_contact") return -1;
        if (c.contact_type == "ncuser_contact" && d.contact_type == "regular_contact") return 1;
        return 0;
    });

    // Create the drop-down list containing the contacts with phone numbers and the Nextcloud users with phone numbers (that were not set up as private)
    var contactsTbl = "<div id='contactsTable'><table id='cntctsTbl'>";
    contactsTbl += "<tr id='searchCntct'><td colspan='3'><div id='searchSpace'><i class='fa fa-search'></i><input type='text' id='txtFindContact' autocomplete='none' placeholder='Search contact by name or role' title='Search contact by name or by role within the organization'></div></td></tr>";

    if (contactsData.length > 0) {

        var regContactNb = 0;
        var ncUserNb = 0;

        for (var c = 0; c < contactsData.length; c++) {

             var ctctsNmbr = '';
             for (var p = 0; p < contactsData[c]['contact_numbers'].length; p++) {
                  if (contactsData[c]['contact_numbers'][p][0].indexOf("VOICE/CELL") > -1) {
                      var ctPhType = "<i class='fa fa-phone phnmbrtype' title='"+ contactsData[c]['contact_numbers'][p][0] +"' style='font-size:16px'></i>";
                  } else if (contactsData[c]['contact_numbers'][p][0].indexOf("VOICE") > -1) {
                      var ctPhType = "<i class='fa fa-volume-control-phone phnmbrtype' title='"+ contactsData[c]['contact_numbers'][p][0] +"' style='font-size:17px'></i>";
                  } else if (contactsData[c]['contact_numbers'][p][0].indexOf("CELL") > -1) {
                      var ctPhType = "<i class='fa fa-mobile phnmbrtype' title='"+ contactsData[c]['contact_numbers'][p][0] +"' style='font-size:18px'></i>";
                  } else if (contactsData[c]['contact_numbers'][p][0].indexOf("CAR") > -1) {
                      var ctPhType = "<i class='fa fa-car phnmbrtype' title='"+ contactsData[c]['contact_numbers'][p][0] +"' style='font-size:15px'></i>";
                  }
                  ctctsNmbr += "<div class='phnmbrRow'><div class='phnbType'>"+ ctPhType +"</div><div id='indPhnmbr-"+ c +"-"+ p +"' class='phnbNumber' title='Click to call'>"+ contactsData[c]['contact_numbers'][p][1] +"</div></div>";
             }
             if (contactsData[c].contact_type == "regular_contact") {
                 var cntctTypeInd = "<div class='typeContact' data-tpcontact='regular_contact'><img src='images/contacts_green.svg' class='regularContact' title='Regular contact'></div>";
                 regContactNb++;
             } else if (contactsData[c].contact_type == "ncuser_contact") {
                 var cntctTypeInd = "<div class='typeContact' data-tpcontact='ncuser_contact'><img src='images/nextcloud_logo_small.svg' class='ncuserContact' title='Nextcloud user'></div>";
                 ncUserNb++;
             }
             if (c % 2 == 1) { var backgroundClr = "darkBackground" } else { var backgroundClr = ""; }

             contactsTbl += "<tr id='ctRowNb-"+ c +"' class='contactRow "+ backgroundClr +"'><td id='contactName'><div class='cntctName'>"+ contactsData[c].contact_name +"</div><div class='cntctRole'>"+ contactsData[c].contact_role +"</div></td><td class='contactType'>"+ cntctTypeInd +"</td><td class='contactNumber'>"+ ctctsNmbr +"</td></tr>";
        }

        var totalCntctNb = regContactNb + ncUserNb;
        contactsTbl += "<tr id='totCtDisp'><td colspan='3'><div id='totalDisplayed'><img src='images/contacts_green.svg' id='regCntctsTot' title='Regular contacts'><span id='totDspRegCts'>"+ regContactNb +"</span><img src='images/nextcloud_logo_small.svg' id='ncUsersTot' title='Nextcloud users'><span id='totDspNcUsr'>"+ ncUserNb +"</span><span style='display:inline-block;margin-left:12px;'>Total:<span id='totDspCntcts'>"+ totalCntctNb +"</span></span></div></td></tr>";
    } else {
        contactsTbl += "<tr><td colspan='3' class='noPhoneNmbrs'>Here you will see all the contacts with phone numbers from your Contacts app and all the Nextcloud users with phone numbers that were not set up as 'Private'.<br><br>For the moment there are no Nextcloud contacts and Nextcloud users with available phone numbers to display.</td></tr>";
    }

    contactsTbl += "</table></div>";

    $("#wndSpace").append(contactsTbl);

    var nameWidth = parseInt(window.innerWidth * 0.40) + "px";
    $("#contactName").width(nameWidth);

    // Show/hide the contacts' phone numbers
    function showContactsNmbrs() {

            $("#contactsUsersList").unbind("click");
	    $("#contactsUsersList").click(function() {

	       if ($("#contactsTable").is(":visible")) {
		   $("#contactsTable").css("display", "none");

		   $("#ctctsUsrsLst").removeClass("contUsrsLstRed");
		   $("#ctctsUsrsLst").addClass("contUsrsLst");
		   if (localStorage.getItem('sipCalls')) {
		       $('#sip-splash').css("display", "none");
		       $('#sip-log').css("display", "block");
		   } else {
		       $('#sip-splash').css("display", "block");
		       $('#sip-log').css("display", "none");
		   }
		   $("#contactsUsersList").prop("title", "Show list of contacts and Nextcloud users with available phone numbers");

	       } else {
		   $("#contactsTable").css("display", "block");

		   $("#ctctsUsrsLst").removeClass("contUsrsLst");
		   $("#ctctsUsrsLst").addClass("contUsrsLstRed");
		   $('#sip-splash').css("display", "none");
		   $('#sip-log').css("display", "none");
		   $("#contactsUsersList").prop("title", "Hide list of contacts and Nextcloud users with available phone numbers");
	       }
               $("#dropDownButton").prop("title", "Show keypad");
	    });
    }

    // Call a phone number on the contacts list when clicking on it
    $('[id^="indPhnmbr-"]').click(function() {
       var targetNmbr = $(this).text();
       $("#numDisplay").val(targetNmbr);
       ctxSip.sipCall(targetNmbr);
    });

    // Search contact by name and role
    $("#txtFindContact").on('keyup', function() {

       var srchd = $("#txtFindContact").val();
       for (var b = 0; b < contactsData.length; b++) {
            if (srchd && srchd.length > 0) {
                if (($("#ctRowNb-"+ b +" .cntctName").text().toLowerCase().indexOf(srchd.toLowerCase()) == -1) && 
                    ($("#ctRowNb-"+ b +" .cntctRole").text().toLowerCase().indexOf(srchd.toLowerCase()) == -1)) {

                     $("#ctRowNb-"+ b +"").hide();

                } else { $("#ctRowNb-"+ b +"").show(); }

            } else { $(".contactRow").each(function() { $(this).show(); }); }
       }

       // Update the totals row
       var totDspReg = 0;
       var totDspNcusr = 0;
       for (var t = 0; t < contactsData.length; t++) {
            if ($("#ctRowNb-"+ t +"").is(":visible") && ($("#ctRowNb-"+ t +" .typeContact").data("tpcontact") == "regular_contact")) { totDspReg++; } else if 
               ($("#ctRowNb-"+ t +"").is(":visible") && ($("#ctRowNb-"+ t +" .typeContact").data("tpcontact") == "ncuser_contact")){ totDspNcusr++; }
       }
       $("#totDspRegCts").text(totDspReg);
       $("#totDspNcUsr").text(totDspNcusr);
       $("#totDspCntcts").text(parseInt(totDspReg + totDspNcusr));
    });

    // Format call duration
    function formatDuration(millsec) {

	    var secs = Math.round(millsec/1000);
	    var hours = Math.floor(secs / 3600);

	    var divisor_for_minutes = secs % 3600;
	    var minutes = Math.floor(divisor_for_minutes / 60);

	    var divisor_for_seconds = divisor_for_minutes % 60;
	    var seconds = Math.ceil(divisor_for_seconds);

	    var finduration = (hours > 0)? hours +" hr " : "";
	    finduration += (minutes > 0)? minutes +" min " : "";
	    finduration += seconds +" sec";

	    return finduration;
    }


    if (user.Stun) {

        var configComp = {
            media : {
                remote : {
                    video : document.getElementById('outputVideo'),
                    audio : document.getElementById('outputAudio')
                }
            },
            peerConnectionOptions : { 
                rtcConfiguration : {
                    iceServers : [{ urls : "stun:"+ user.Stun }]
                } 
            },
            password : userSIPToken,
            authorizationUser : user.User,
            displayName : user.Display,
            uri : user.User +'@'+ user.Realm,
            transportOptions : {
                wsServers : user.WSServer,
                traceSip : traceornot,
                log : { level : 3 }
            },
            registerExpires : 9999999
        };

    } else {

        var configComp = {
            media : {
                remote : {
                    video : document.getElementById('outputVideo'),
                    audio : document.getElementById('outputAudio')
                }
            },
            password : userSIPToken,
            authorizationUser : user.User,
            displayName : user.Display,
            uri : user.User +'@'+ user.Realm,
            transportOptions : {
                wsServers : user.WSServer,
                traceSip : traceornot,
                log : { level : 3 }
            },
            registerExpires : 9999999
        };
    }

    var ctxSip = {

        config : configComp, 
        ringtone : document.getElementById('ringtone'),
        ringbacktone : document.getElementById('ringbacktone'),
        recordingwarn : document.getElementById('warnUser'),
        Sessions : [],
        callTimers : {},
        callActiveID : null,
        callVolume : 1,
        Stream : null,

        /**
         * Parses a SIP uri and returns a formatted US phone number.
         *
         * @param  {string} phone number or uri to format
         * @return {string} formatted number
         */
        formatPhone : function(phone) {

            var num;

            if (phone.indexOf('@')) {
                num = phone.split('@')[0];
            } else {
                num = phone;
            }

            num = num.toString().replace(/[^0-9\+]/g, '');

            return num;
        },

        // Sound methods
        startRingTone : function() {
            try { ctxSip.ringtone.play(); } catch (e) { }
        },

        stopRingTone : function() {
            try { ctxSip.ringtone.pause(); } catch (e) { }
        },

        startRingbackTone : function() {
            try { ctxSip.ringbacktone.play(); } catch (e) { }
        },

        stopRingbackTone : function() {
            try { ctxSip.ringbacktone.pause(); } catch (e) { }
        },

        // Generate a random string to set an ID for a call
        getUniqueID : function() {
            return Math.random().toString(36).substr(2, 9);
        },

        newSession : function(newSess) {

            var remoteUsr = newSess.remoteIdentity.uri.user;

            if (remoteUsr.length < 6) {
                var remoteUriUser = remoteUsr; 
            } else { 
                if (remoteUsr.slice(0,1) != "+") { 
                    var remoteUriUser = "+"+ remoteUsr;
                } else {
                    var remoteUriUser = remoteUsr;
                }
            }
            newSess.displayName = newSess.remoteIdentity.displayName || remoteUriUser;
            newSess.ctxid = ctxSip.getUniqueID();

            var status;

            if (newSess.direction === 'incoming') {

                status = "Incoming: "+ newSess.displayName;
                ctxSip.startRingTone();
                incomingCallNote();
                changePageTitle();
            } else {
                status = "Trying: "+ newSess.displayName;
                ctxSip.startRingbackTone();
            }

            ctxSip.logCall(newSess, 'ringing');

            ctxSip.setCallSessionStatus(status);

            // Event callbacks
         
	    newSess.on('trackAdded', function() {

		var pc = newSess.sessionDescriptionHandler.peerConnection;

		// Get remote audio track (local audio is set up 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 = document.getElementById('outputAudio');
		remoteAudio.srcObject = remoteStream;
		remoteAudio.onloadedmetadata = function(e) {
		    remoteAudio.play();
		}
                ctxSip.Sessions[newSess.ctxid].isMuted = false;
	    });

            newSess.on('progress',function(e) {
                if (e.direction === 'outgoing') {
                    ctxSip.setCallSessionStatus('Calling...');
                }
            });

            newSess.on('connecting',function(e) {
                if (e.direction === 'outgoing') {
                    ctxSip.setCallSessionStatus('Connecting...');
                }
            });

            newSess.on('accepted',function(e) {
                if (ctxSip.callActiveID && ctxSip.callActiveID !== newSess.ctxid) {
                    ctxSip.phoneHoldButtonPressed(ctxSip.callActiveID);
                }

                ctxSip.stopRingbackTone();
                ctxSip.stopRingTone();
                ctxSip.setCallSessionStatus('Answered');
                ctxSip.logCall(newSess, 'answered');
                ctxSip.callActiveID = newSess.ctxid;
            });

            newSess.on('hold', function(e) {
                ctxSip.callActiveID = null;
                ctxSip.logCall(newSess, 'holding');
                ctxSip.setCallSessionStatus("Holding");
            });

            newSess.on('unhold', function(e) {
                ctxSip.logCall(newSess, 'resumed');
                ctxSip.callActiveID = newSess.ctxid;
                ctxSip.setCallSessionStatus("Answered");
            });

            newSess.on('cancel', function(e) {
                ctxSip.stopRingTone();
                ctxSip.stopRingbackTone();
                ctxSip.setCallSessionStatus("Canceled");
                if (this.direction === 'outgoing') {
                    ctxSip.callActiveID = null;
                    newSess = null;
                    ctxSip.logCall(this, 'ended');
                }
            });

            newSess.on('bye', function(e) {
                ctxSip.stopRingTone();
                ctxSip.stopRingbackTone();
                ctxSip.setCallSessionStatus("");
                ctxSip.logCall(newSess, 'ended');
                ctxSip.callActiveID = null;
                newSess = null;
            });

            newSess.on('failed',function(e) {
                ctxSip.stopRingTone();
                ctxSip.stopRingbackTone();
                ctxSip.setCallSessionStatus('Terminated');
            });

            newSess.on('rejected',function(e) {
                ctxSip.stopRingTone();
                ctxSip.stopRingbackTone();
                ctxSip.setCallSessionStatus('Rejected');
                ctxSip.callActiveID = null;
                ctxSip.logCall(this, 'ended');
                newSess = null;
            });
            
            ctxSip.Sessions[newSess.ctxid] = newSess;

            // Hide contacts list
            $("#contactsTable").css("display", "none");
            $("#ctctsUsrsLst").removeClass("contUsrsLstRed");
            $("#ctctsUsrsLst").addClass("contUsrsLst");
        },

        // getUserMedia failed or device wasn't present
        getUserMediaFailure : function(e) {
            window.console.error('getUserMedia failed: ', e);
            ctxSip.setError(true, 'Media Error.', 'You must allow access to your microphone. Check the address bar.', true);
        },

        getUserMediaSuccess : function(stream) {
             ctxSip.Stream = stream;
        },

        /**
         * Sets the UI call status field
         *
         * @param {string} status
         */
        setCallSessionStatus : function(status) {
            $('#txtCallStatus').html(status);
        },

        /**
         * Sets the UI connection status field
         *
         * @param {string} status
         */
        setStatus : function(status) {
            $("#txtRegStatus").html('<i class="fa fa-signal"></i> '+ status);
        },

        /**
         * Logs a call to localStorage
         *
         * @param  {object} session
         * @param  {string} status Enum 'ringing', 'answered', 'ended', 'holding', 'resumed'
         */
        logCall : function(session, status) {

            var log = {
                    clid : session.displayName,
                    uri : session.remoteIdentity.uri.toString(),
                    id : session.ctxid,
                    time : new Date().getTime()
            };
            var calllog = JSON.parse(localStorage.getItem('sipCalls'));

            if (!calllog) { calllog = {}; }

            if (!calllog.hasOwnProperty(session.ctxid)) {
                calllog[log.id] = {
                    id : log.id,
                    clid : log.clid,
                    uri : log.uri,
                    start : log.time,
                    flow : session.direction
                };
            }

            if (status === 'answered') {
                calllog[log.id].start = log.time;
            }

            if (status === 'ended') {
                calllog[log.id].stop = log.time;
            }

            if (status === 'ended' && calllog[log.id].status === 'ringing') {
                calllog[log.id].status = 'missed';
            } else {
                calllog[log.id].status = status;
            }

            localStorage.setItem('sipCalls', JSON.stringify(calllog));
            ctxSip.logShow();
        },

        /**
         * Adds a UI item to the call log
         *
         * @param  {object} item log item
         */
        logItem : function(item) {

            var callActive = (item.status !== 'ended' && item.status !== 'missed');
            var callLength = (item.status !== 'ended')? '<span id="'+ item.id +'"></span>' : formatDuration(moment.duration(item.stop - item.start));

            var callClass = '';
            var callIcon = '';
            var callTitle = '';

            switch (item.status) {
                case 'ringing':
                    callClass = 'list-group-item-success';
                    callIcon = 'fa-bell';
                    break;

                case 'missed':
                    callClass = 'list-group-item-danger';
                    if (item.flow === "incoming") { callIcon = 'incomingCall'; callTitle = 'Incoming call'; }
                    if (item.flow === "outgoing") { callIcon = 'outgoingCall'; callTitle = 'Outgoing call'; }
                    break;

                case 'holding':
                    callClass = 'list-group-item-warning';
                    callIcon = 'fa-pause';
                    break;

                case 'answered': ;
                case 'resumed':
                    callClass = 'list-group-item-info';
                    callIcon = 'fa-phone-square';
                    break;

                case 'ended':
                    if (item.flow === "incoming") { callIcon = 'incomingCall'; callTitle = 'Incoming call'; }
                    if (item.flow === "outgoing") { callIcon = 'outgoingCall'; callTitle = 'Outgoing call'; }
                    break;
            }

            var i = '<div class="list-group-item sip-logitem clearfix '+ callClass +'" data-sessionid="'+ item.id +'">';
            i += '<div class="clearfix"><div class="pull-left">';
            i += '<i class="fa fa-fw '+ callIcon +'" title="'+ callTitle +'"></i><div class="listPhNb" data-uri="'+ item.uri +'" title="Double click to call">'+ ctxSip.formatPhone(item.uri) +'</div><div class="callTimeMmt" title="Call time">'+ moment(item.start).format('YYYY/MM/DD HH:mm:ss') +'</div></div>';
            i += '<div class="pull-right text-right" title="Target number/Caller"><em>'+ item.clid +'</em><div class="callDuration" title="Call duration">'+ callLength +'</div></div></div>';

            if (callActive) {
                i += '<div class="btn-group btn-group-xs pull-right">';
                if (item.status === 'ringing' && item.flow === 'incoming') {
                    i += '<button class="btn btn-xs btn-success btnCall" title="Call"><i class="fa fa-phone"></i></button>';
                } else {
                    i += '<button id="hold-'+ item.id +'" class="btn btn-xs btn-primary btnHoldResume" title="Hold"><i class="fa fa-pause"></i></button>';
                    i += '<button class="btn btn-xs btn-info btnTransfer" title="Transfer"><i class="fa fa-random"></i></button>';
                    i += '<button id="mute-'+ item.id +'" class="btn btn-xs btn-warning btnMute" title="Mute"><i class="fa fa-fw fa-microphone"></i></button>';
                    i += '<button id="rec-'+ item.id +'" class="btn btn-xs btnRecord" title="Start recording"><i class="fa fa-fw fa-dot-circle-o"></i></button>';
                }
                i += '<button class="btn btn-xs btn-danger btnHangUp" title="Hangup"><i class="fa fa-stop"></i></button>';
                i += '</div>';
            }
            i += '</div>';

            $('#sip-logitems').append(i);

            // Start call timer on answer
            if (item.status === 'answered') {
                var tEle = document.getElementById(item.id);
                ctxSip.callTimers[item.id] = new Stopwatch(tEle);
                ctxSip.callTimers[item.id].start();
            }

            if (callActive && item.status !== 'ringing') {
                ctxSip.callTimers[item.id].start({ startTime : item.start });
            }

            $('#sip-logitems').scrollTop(0);
        },

        /**
         * Updates the call log UI
         */
        logShow : function() {

            var calllog = JSON.parse(localStorage.getItem('sipCalls'));
            var x = [];

            if (calllog !== null) {

                $('#sip-splash').css("display", "none");
                $('#sip-log').css("display", "block");

                // Empty existing logs
                $('#sip-logitems').empty();

                // JS doesn't guarantee property order so, create an array with the start time as the key and sort by that

                // Add start time to array
                $.each(calllog, function(k,v) {
                    x.push(v);
                });

                // Sort descending
                x.sort(function(a, b) {
                    return b.start - a.start;
                });

                $.each(x, function(k, v) {
                    ctxSip.logItem(v);
                });

            } else {
                $('#sip-splash').css("display", "block");
                $('#sip-log').css("display", "none");
            }

            showContactsNmbrs();
        },

        /**
         * Remove log items from localStorage and update the UI
         */
        logClear : function() {

            localStorage.removeItem('sipCalls');
            ctxSip.logShow();
        },

        sipCall : function(target) {

            try {

	        // Get the name of the SIP provider and send it to Asterisk, so that it knows what provider to use for outbound calls
	        var callerNumber = $("#fromNumber").val();

                if (callerNumber.indexOf(":") >= 0) {
	            var shortProv = callerNumber.split(":");
	            var sipProvider = shortProv[0].replace(" ", "");
                    var callFromNumber = shortProv[1].replace(" ", "");
                } else {
	            var sipProvider = 'n/a';
                    var callFromNumber = callerNumber;
                }

                var s = ctxSip.phone.invite(target, {
                    media : {
                        remote : {
                            video : document.getElementById('outputVideo'),
                            audio : document.getElementById('outputAudio')
                        }
                    },
                    extraHeaders : [ 'X-SipProvider: '+ sipProvider , 'X-CallFromNumber: '+ callFromNumber ],
                    sessionDescriptionHandlerOptions : {
                        constraints : { audio : true, video : false },
                        rtcConfiguration : { RTCConstraints : { "optional" : [{ 'DtlsSrtpKeyAgreement' : 'true'} ]}, stream : ctxSip.Stream }
                       
                    }
                });
                s.direction = 'outgoing';
                ctxSip.newSession(s);

                s.on('trackAdded', function() {

			var pc = s.sessionDescriptionHandler.peerConnection;

			// Get remote audio track (local audio is set up 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 = document.getElementById('outputAudio');
			remoteAudio.srcObject = remoteStream;
			remoteAudio.onloadedmetadata = function(e) {
			    remoteAudio.play();
			}
	        });

            } catch(e) {
                throw(e);
            }
        },

        sipTransfer : function(sessionid) {

            var s = ctxSip.Sessions[sessionid],
            target = window.prompt('Enter destination number', '');
            if (target) {
                ctxSip.setCallSessionStatus('<i>Transfering the call...</i>');
            }
            s.refer(target);
            showContactsNmbrs();
        },

        sipHangUp : function(sessionid) {

            var s = ctxSip.Sessions[sessionid];

            if (!s) {
                return;
            } else if (s.startTime) {
                s.bye();
            } else if (s.reject) {
                s.reject();
            } else if (s.cancel) {
                s.cancel();
            }

            $("#numDisplay").val("");
            showContactsNmbrs();
        },

        sipSendDTMF : function(digit) {

            var a = ctxSip.callActiveID;
            if (a) {
                var s = ctxSip.Sessions[a];
                s.dtmf(digit);
            }
        },

        phoneCallButtonPressed : function(sessionid) {

            var s = ctxSip.Sessions[sessionid],
            target = $("#numDisplay").val();

            if (!s) {

                $("#numDisplay").val("");
                ctxSip.sipCall(target);

            } else if (s.accept && !s.startTime) {

                s.accept({
                    media : {
                        remote : {
                            video : document.getElementById('outputVideo'),
                            audio : document.getElementById('outputAudio')
                        }
                    },
                    sessionDescriptionHandlerOptions : {
                        constraints : { audio : true, video : false },
                        rtcConfiguration : { RTCConstraints : { "optional" : [{ 'DtlsSrtpKeyAgreement' : 'true'} ]}, stream : ctxSip.Stream }
                       
                    }
                });
            }

            // Hide contacts list
            $("#contactsTable").css("display", "none");
            $("#ctctsUsrsLst").removeClass("contUsrsLstRed");
            $("#ctctsUsrsLst").addClass("contUsrsLst");
        },

        // Record audio call
        phoneRecordButtonPressed : function (recbtnid) {

            var recBtn = document.getElementById(recbtnid);

            if (mediaRecorder === null) {

                var sessionid = recbtnid.replace("rec-", "");
                var crtsess = ctxSip.Sessions[sessionid];
                var pc = crtsess.sessionDescriptionHandler.peerConnection;
                var audioTracks = [];

		pc.getSenders().forEach(function (rtpSender) {
		    if (rtpSender.track && rtpSender.track.kind == "audio") {
			console.log("Adding sender audio track to record: ", rtpSender.track.label);
                        audioTracks.push(rtpSender.track);
		    }
	        });

		pc.getReceivers().forEach(function (rtpReceiver) {
		    if (rtpReceiver.track && rtpReceiver.track.kind == "audio") {
			console.log("Adding receiver audio track to record: ", rtpReceiver.track.label);
                        audioTracks.push(rtpReceiver.track);
		    }
	        });

                // Combine the audio tracks into one track
	        const ac = new AudioContext();
	        const sources = audioTracks.map(function(t) { return ac.createMediaStreamSource(new MediaStream([t])); });
	        const dest = ac.createMediaStreamDestination();
                sources.forEach(function(s) { s.connect(dest); });

                var mediaStream = new MediaStream(dest.stream);
	        mediaRecorder = new MediaRecorder(mediaStream);
                var recordedChunks = [];

                mediaRecorder.addEventListener("dataavailable", function (event) {
                    recordedChunks.push(event.data);
                });

                mediaRecorder.addEventListener("stop", function() {

		    const rawblob = new Blob(recordedChunks, { type: 'audio/webm' });
		    let duration = recordedChunks.length * 200;

		    // Add duration to the media blob, so that the final webm/ogx file becomes seekable
		    ysFixWebmDuration(rawblob, duration, function(fixedBlob) {

			let filename = window.prompt('Enter the name of the audio file:');
			let downloadLink = document.createElement('a');
			downloadLink.href = URL.createObjectURL(fixedBlob);
			downloadLink.download = `${filename}.webm`;

			document.body.appendChild(downloadLink);
			downloadLink.click();
			URL.revokeObjectURL(fixedBlob);
			document.body.removeChild(downloadLink);

		    });
                });

                confirmAlert("In some countries it is a legal requirement to warn the interlocutor before recording a conversation. Do you want to play a short audio warning at the beginning of the recording, to inform the interlocutor that (s)he will be recorded ?");

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

                   // Start recording in chunks of 200 milliseconds
                   mediaRecorder.start(200);

                   // Warn the interlocutor about the recording
                   ctxSip.recordingwarn.play();
		   $("#alertMsgOverlay").remove();
		   $("#alertConfMessage").remove();
                });
                $("#confirmCancel").click(function() {
                   mediaRecorder.start(200);
		   $("#alertMsgOverlay").remove();
		   $("#alertConfMessage").remove();
                });
                recBtn.title = "Stop recording";
                recBtn.classList.add("changeRecBtn");

            } else {

                recBtn.classList.remove("changeRecBtn");
                recBtn.title = "Start recording";

                // Stop recording
                mediaRecorder.stop();
                mediaRecorder = null;
	    }
        },

	phoneMuteButtonPressed : function (sessionid) {

            var sess = ctxSip.Sessions[sessionid];
            var pc = sess.sessionDescriptionHandler.peerConnection;

            if (sess.isMuted) {
                sess.isMuted = false;
		pc.getSenders().forEach(function(rtpSender) {
                   if (rtpSender.track && rtpSender.track.kind == "audio") {
                       rtpSender.track.enabled = true;
                   }
		});
		$("#txtCallStatus").text("Answered");
		$("#mute-"+ sessionid +"").prop("title", "Mute").css("background-color", "#f0ad4e");
            } else {
                sess.isMuted = true;
		pc.getSenders().forEach(function(rtpSender) {
                   if (rtpSender.track && rtpSender.track.kind == "audio") {
                       rtpSender.track.enabled = false;
                   }
		});
		$("#txtCallStatus").text("Muted");
		$("#mute-"+ sessionid +"").prop("title", "Unmute").css("background-color", "#c47e1a");
            }
        },

        phoneHoldButtonPressed : function(sessionid) {

		var ses = ctxSip.Sessions[sessionid];
		var direction = ses.sessionDescriptionHandler.getDirection();

		if (direction === 'sendrecv') {
			console.log('Call is on hold');
			ses.hold();
			ctxSip.logCall(ses, 'holding');
			$("#txtCallStatus").text("Holding");
			$("#hold-"+ sessionid +"").prop("title", "Unhold").css("background-color", "#20496c");

		} else {
			console.log('Call is not on hold');
			ses.unhold();
			ctxSip.logCall(ses, 'resumed');
			$("#txtCallStatus").text("Answered");
			$("#hold-"+ sessionid +"").prop("title", "Hold").css("background-color", "#337ab7");
		}
        },

        setError : function(err, title, msg, closable) {

            // Show modal if err === true
            if (err === true) {
                $("#mdlError p").html(msg);
                $("#mdlError").modal('show');

                if (closable) {
                    var b = '<button type="button" class="close" data-dismiss="modal">&times;</button>';
                    $("#mdlError .modal-header").find('button').remove();
                    $("#mdlError .modal-header").prepend(b);
                    $("#mdlError .modal-title").html(title);
                    $("#mdlError").modal({ keyboard : true });
                } else {
                    $("#mdlError .modal-header").find('button').remove();
                    $("#mdlError .modal-title").html(title);
                    $("#mdlError").modal({ keyboard : false });
                }
            } else {
                $('#numDisplay').removeProp('disabled');
                $("#mdlError").modal('hide');
            }
        },

        /**
         * Test for an audio call capable browser and return an error on failure
         */
        hasWebRTC : function() {

            if (navigator.mediaDevices.getUserMedia) {
                return true;
            } else if (navigator.webkitGetUserMedia) {
                return true;
            } else if (navigator.mozGetUserMedia) {
                return true;
            } else {
                ctxSip.setError(true, 'Unsupported Browser.', 'Your browser does not support the features required for this phone.');
                window.console.error("WebRTC support not found");
                return false;
            }
        }
    };

    ctxSip.hasWebRTC();

    ctxSip.phone = new SIP.UA(ctxSip.config);

    ctxSip.phone.on('connected', function(e) {
        ctxSip.setStatus("Connected");
        ctxSip.setError(false);
        userSIPToken = '';
    });

    ctxSip.phone.on('disconnected', function(e) {
        ctxSip.setStatus("Disconnected");

        // Disable phone
        ctxSip.setError(true, 'Websocket Disconnected.', 'An Error occurred connecting to the websocket.');

        userSIPToken = '';
    });

    ctxSip.phone.on('registered', function(e) {

        navigator.mediaDevices.getUserMedia({ audio : true, video : false }).then(function(stream) {
		ctxSip.setError(false);
		var closeEditorWarning = function() {
		    return 'If you close this window, you will not be able to make or receive calls from your browser.';
		};

		var closePhone = function() {
		    // Stop the phone on unload
		    localStorage.removeItem('ctxPhone');
		    ctxSip.phone.stop();
		};

		window.onbeforeunload = closeEditorWarning;
		window.onunload = closePhone;

		// This key is set to prevent multiple windows
		localStorage.setItem('ctxPhone', 'true');

		$("#mldError").modal('hide');
		ctxSip.setStatus("Ready");
        });

        userSIPToken = '';
    });

    ctxSip.phone.on('registrationFailed', function(e) {
        console.log(e)
        ctxSip.setError(true, 'Registration Error.', 'An Error occurred registering your phone. Check your settings.');
        ctxSip.setStatus("Error: Registration Failed");
        userSIPToken = '';
    });

    ctxSip.phone.on('unregistered', function(e) {
        ctxSip.setError(true, 'Registration Error.', 'An Error occurred registering your phone. Please wait while we try to connect it again.');
        ctxSip.setStatus("Error: Registration Failed");
        userSIPToken = '';
    });

    ctxSip.phone.on('invite', function (incomingSession) {
        var s = incomingSession;
        s.direction = 'incoming';
        ctxSip.newSession(s);
        showContactsNmbrs();
    });

    $('#numDisplay').keypress(function(e) {
        // If 'Enter' is pressed, dial
        if (e.which === 13) {
            ctxSip.phoneCallButtonPressed();
        }
    });

    $('#phoneIcon').click(function(event) {
        // If 'Enter' is pressed, dial
        event.preventDefault();
        ctxSip.phoneCallButtonPressed();
    });

    $(".digit").click(function(event) {

       if (event.shiftKey) {

           clck++;
           event.preventDefault();
           var num = $('#numDisplay').val();
           var dig;
           var diginit = $(this).data('digit').toString().split(',');
           var elct = diginit.length;

           dig = diginit[clck%elct];
           var numsec = num.slice(0,-1);
           $('#numDisplay').val(numsec+dig);
           ctxSip.sipSendDTMF(dig);

       } else {
           event.preventDefault();
           var num = $('#numDisplay').val();
           var dig;
           var diginit = $(this).data('digit').toString().split(',');

           dig = diginit[0];
           clck = 0;
           $('#numDisplay').val(num+dig);
           ctxSip.sipSendDTMF(dig);
       }

       $("#deleteInput").css("display", "block");
       return false;
    });

    // Show the backspace icon
    $("#numDisplay").on("focus", function() { $("#deleteInput").css("display", "block"); });

    $("#deleteInput").click(function() {
       var entNmbr = $('#numDisplay').val().slice(0, -1);
       $('#numDisplay').val(entNmbr);
    });

    // Hide the backspace icon
    $("#sip-splash,#sip-log,#selectFromNmbr,#contactsTable").click(function() { $("#deleteInput").css("display", "none"); });

    // Change keypad title
    $("#dropDownButton").click(function() {
       $("#deleteInput").css("display", "none");
       if ($("#sip-dialpad").is(":visible")) { $("#dropDownButton").prop("title", "Show keypad"); } else { $("#dropDownButton").prop("title", "Hide keypad"); }
    });
    $("#sip-dialpad").click(function() { $("#dropDownButton").prop("title", "Show keypad"); });

    $('#phoneUI .dropdown-menu').click(function(e) {
        e.preventDefault();
    });

    $('#phoneUI').delegate('.btnCall', 'click', function(event) {
        ctxSip.phoneCallButtonPressed();
        // Close the dropdown
        return true;
    });

    $('.sipLogClear').click(function(event) {
        event.preventDefault();
        ctxSip.logClear();
        showContactsNmbrs();
    });

    $('#sip-logitems').delegate('.sip-logitem .btnCall', 'click', function(event) {
        var sessionid = $(this).closest('.sip-logitem').data('sessionid');
        ctxSip.phoneCallButtonPressed(sessionid);
        return false;
    });

    $('#sip-logitems').delegate('.sip-logitem .btnHoldResume', 'click', function(event) {
        var sessionid = $(this).closest('.sip-logitem').data('sessionid');
        ctxSip.phoneHoldButtonPressed(sessionid);
        return false;
    });

    $('#sip-logitems').delegate('.sip-logitem .btnHangUp', 'click', function(event) {
        var sessionid = $(this).closest('.sip-logitem').data('sessionid');
        ctxSip.sipHangUp(sessionid);
        return false;
    });

    $('#sip-logitems').delegate('.sip-logitem .btnTransfer', 'click', function(event) {
        var sessionid = $(this).closest('.sip-logitem').data('sessionid');
        ctxSip.sipTransfer(sessionid);
        return false;
    });

    $('#sip-logitems').delegate('.sip-logitem .btnMute', 'click', function(event) {
        var sessionid = $(this).closest('.sip-logitem').data('sessionid');
        ctxSip.phoneMuteButtonPressed(sessionid);
        return false;
    });

    $('#sip-logitems').delegate('.sip-logitem .btnRecord', 'click', function(event) {
        ctxSip.phoneRecordButtonPressed(this.id);
        return false;
    });

    $('#sip-logitems').delegate('.listPhNb', 'dblclick', function(event) {
        event.preventDefault();

        var uricalled = $(this).data('uri');
        var uriproc = uricalled.split(":");
        var uripronb = uriproc[1].split("@");
        var uri = uripronb[0];
        $('#numDisplay').val(uri);
        ctxSip.phoneCallButtonPressed();
    });

    $('#sldVolume').on('change', function() {

            var v = $(this).val() / 100,
            btn = $('#btnVol'),
            icon = $('#btnVol').find('i'),
            active = ctxSip.callActiveID;

        // Set the object and media stream volumes
        if (ctxSip.Sessions[active]) {
            ctxSip.Sessions[active].player.volume = v;
            ctxSip.callVolume = v;
        }

        // Set the others
        $('audio').each(function() {
            $(this).get()[0].volume = v;
        });

        if (v < 0.1) {
            btn.removeClass(function (index, css) {
                   return (css.match (/(^|\s)btn\S+/g) || []).join(' ');
                })
                .addClass('btn btn-sm btn-danger');
            icon.removeClass().addClass('fa fa-fw fa-volume-off');
        } else if (v < 0.8) {
            btn.removeClass(function (index, css) {
                   return (css.match (/(^|\s)btn\S+/g) || []).join(' ');
               }).addClass('btn btn-sm btn-info');
            icon.removeClass().addClass('fa fa-fw fa-volume-down');
        } else {
            btn.removeClass(function (index, css) {
                   return (css.match (/(^|\s)btn\S+/g) || []).join(' ');
               }).addClass('btn btn-sm btn-primary');
            icon.removeClass().addClass('fa fa-fw fa-volume-up');
        }
        return false;
    });

    // Hide the splash and show the call log after 1 second
    setTimeout(function() {
        ctxSip.logShow();
    }, 1000);


    /**
     * Stopwatch object used for call timers
     *
     * @param {dom element} elem
     * @param {[object]} options
     */
    var Stopwatch = function(elem, options) {

        // Private functions
        function createTimer() {
            return document.createElement("span");
        }

        var timer = createTimer(),
            offset,
            clock,
            interval;

        // Default options
        options = options || {};
        options.delay = options.delay || 1000;
        options.startTime = options.startTime || Date.now();

        // Append elements
        elem.appendChild(timer);

        function start() {
            if (!interval) {
                offset = options.startTime;
                interval = setInterval(update, options.delay);
            }
        }

        function stop() {
            if (interval) {
                clearInterval(interval);
                interval = null;
            }
        }

        function reset() {
            clock = 0;
            render();
        }

        function update() {
            clock += delta();
            render();
        }

        function render() {
            timer.innerHTML = moment(clock).format('mm:ss');
        }

        function delta() {
            var now = Date.now();
            var d = now - offset;

            offset = now;
            return d;
        }

        // Initialize
        reset();

        // Public API
        this.start = start;
        this.stop  = stop;
    };


    function confirmAlert(alertText) {

        $("#alertMsgOverlay").remove();
        $("#alertConfMessage").remove();

        let alertwndcf = "<div id='alertConfMessage'>";
        alertwndcf += "<a id='closeAlertWndcf' title='Close this window'></a>";
        alertwndcf += "<div id='alertTextWrap'>"+ alertText +"</div>";
        alertwndcf += "<div id='cancelOkWrap'><input type='submit' id='confirmCancel' value='Record without warning'>";
        alertwndcf += "<input type='submit' id='confirmOk' value='Record with warning'></div>";
        alertwndcf += "</div>";

        $("#flContainer").append("<div id='alertMsgOverlay'></div>");
        $("#flContainer").append(alertwndcf);

        let topDist = parseInt((window.innerHeight / 2).toFixed(2) - 152) +"px";
        let leftDist = parseInt((window.innerWidth / 2).toFixed(2) - 150) +"px";
        $("#alertConfMessage").css({ "top" : topDist, "left" : leftDist });

        $("#closeAlertWndcf").click(function() { $("#alertMsgOverlay").remove(); $("#alertConfMessage").remove(); });
        $("#alertMsgOverlay").click(function() { $("#alertMsgOverlay").remove(); $("#alertConfMessage").remove(); });
    }

    $(window).on('beforeunload', function() {
        localStorage.removeItem('SIPCreds');
        localStorage.removeItem('NCContacts');
    });
});
