/**
 *  Copyright 2018 Avaya Inc. All Rights Reserved.
 *
 * Usage of this source is bound to the terms described in
 * licences/License.txt
 *
 * Avaya - Confidential & Proprietary. Use pursuant to your signed agreement or
 * Avaya Policy
 *
 * Defines the client end of the WebSocket chat.
 */

var webChat = {

    users : {},
    chatBotName : '',

    guid : null,
    ak : null,
    webOnHoldComfortGroups : null,
    webOnHoldURLs : null,

    onHoldUrlInterval : null,
    onHoldComfortInterval : null,
    pingInterval : null,

    // isTyping variable currently not used, Planned for future release
    isTyping : false,
    typeOut : null,
    activeAgentTypeOut : null,
    passiveAgentTypeOut : null,
    supervisorTypeOut : null,
    usersTable : null,
    lastIsTypingSent : 0,
    timeBetweenMsgs : 10000,

    initCalled : false,
    autoOpen : false,
    sendClientTime : true,

    timeouts : [],

    // holds details such as names, etc.
    custDetails : {},

    customFields: [],

    // custom data for chatbot
    customData: {},

    workflowType : '',
    routePointIdentifier : 'Default',

    // CoBrowse related.
    coBrowseEnabled : true,
    coBrowseReady : false,
    coBrowseIframe : null,
    coBrowseKey : '',
    isPagePushKey : false,
    coBrowseOceanaPath : 'cobrowse-oceana/js/',
    coBrowseSDKPath : 'cobrowse-oceana/js/libs/',
    hideElements : [ 'chatPanelHidden', 'configPanel' ],
    joinedAgents : {},


    /**
     * Add a new custom field. Must be unique
     * @param {String} title - must be at most 50 characters, and must not be blank.
     * @param {String} value - must be at most 255 characters.
     */
    addCustomFields: function(title, value) {
        'use strict';

        if (avayaGlobal.isStringEmpty(title)) {
            console.error("WebChat: Cannot have a custom field with an empty title!");
            return;
        }

        if (value.length > 255) {
            console.error("WebChat: Custom fields must not exceed 255 characters in length!");
            return;
        }

        // required because IE doesn't support the Array.prototype.find() method
        var contained = false;
        for (var i = 0; i < webChat.customFields.length; i++) {
            var field = webChat.customFields[i];
            if (field.title === title) {
                contained = true;
                break;
            }
        }
        if (!contained) {
            webChat.customFields.push({"title" : title, "value" : value});
        }
    },

    clearCustomFields: function() {
        'use strict';
        webChat.customFields = [];
    },

    removeCustomField: function(title){
        'use strict';
        var index = -1;
        for (var i = 0; i < webChat.customFields.length; i++) {
            var field = webChat.customFields[i];
            console.log(title, field);
            if (field.title === title) {
                index = i;
                break;
            }
        }
        if (index > -1) {
            webChat.customFields.splice(index, 1);
        }
    },

    /**
     * Append a specified link to the chat transcript.
     *
     * @param url
     * @param urlDestination
     * @param openAutomatically -
     *                false for Web-On-Hold URLs, but URLs from an agent may be
     *                changed
     * @param parentElement -
     *                the parent element
     */
    appendLink : function(url, urlDestination, openAutomatically, parentElement) {
        'use strict';
        var link = document.createElement('a');
        link.href = url;
        link.text = url;
        link.target = ('_' + urlDestination);
        parentElement.appendChild(link);

        if (openAutomatically) {
            window.open(link);
        }
    },

    /**
     * Log the user into the chat.
     */
    chatLogin : function() {

        'use strict';
        var wantsEmail = chatUI.getRequestTranscript();
        var topic = chatUI.getTopic();
        var leaseTime = chatConfig.leaseTime;
        console.debug('WebChat: Chat attributes are ' + JSON.stringify(chatLogon.attributes));

        // if the user didn't specify a valid email address, they can't
        // receive a transcript.
        if (avayaGlobal.isStringEmpty(webChat.custDetails.email)) {
            wantsEmail = false;
        }
        var calledParty = window.location.href;
        var msg;
        var custDetails = webChat.custDetails;

        if (!chatConfig.previouslyConnected) {
			if (webChat.customFields.length > 10){
				var customFieldsMessage = 'The limit of custom fields is 10. These fields were not added:';
				for (var i = 10; i < webChat.customFields.length; i++) {
					customFieldsMessage = customFieldsMessage.concat('\n', webChat.customFields[i].title);
				}
				webChat.customFields.splice(10, webChat.customFields.length-10);
				webChat.writeResponse(customFieldsMessage, chatConfig.writeResponseClassSystem);
			}

            msg = {
                'apiVersion' : '1.0',
                'type' : 'request',
                'body' : {
                    'method' : 'requestChat',
                    'deviceType' : navigator.userAgent,
                    'routePointIdentifier' : webChat.routePointIdentifier,
                    'workFlowType' : webChat.workflowType,
                    'requestTranscript' : wantsEmail,
                    'workRequestId' : avayaGlobal.getSessionStorage('contextId'),
                    'calledParty' : calledParty,
                    'leaseTime' : leaseTime,
                    'intrinsics' : {
                        'channelAttribute' : 'Chat',
                        'textDirection' : document.dir.toUpperCase(),
                        'attributes' : chatLogon.attributes,
                        'email' : custDetails.email,
                        'name' : custDetails.firstName,
                        'lastName' : custDetails.lastName,
                        'country' : custDetails.phone.country,
                        'area' : custDetails.phone.area,
                        'phoneNumber' : custDetails.phone.number,
                        'topic' : topic,
                        'customFields' : webChat.customFields,
                        'zoneId': this.sendClientTime ? Intl.DateTimeFormat().resolvedOptions().timeZone : null
                    },
                    'priority' : ewt.getPriority(),
                    'customData': webChat.customData
                }
            };
            webChat.writeResponse(chatConfig.openingChatText, chatConfig.writeResponseClassSystem);
        } else {
            // check if this is continuing after a page refresh or just a network glitch
            // if this is a page refresh, then we will request the full transcript
            var isAfterRefresh = custDetails.isContinuingAfterRefresh;
            console.log("Is this after a refresh?", isAfterRefresh);

            msg = {
                'apiVersion' : '1.0',
                'type' : 'request',
                'body' : {
                    'method' : 'renewChat',
                    'guid' : webChat.guid,
                    'authenticationKey' : webChat.ak,
                    'requestFullTranscript' : isAfterRefresh
                }
            };
        }
        chatSocket.sendMessage(msg);
    },

    /**
     * Clears all timeouts on the page.
     */
    clearAllTimeouts : function() {
        'use strict';
        for (var i = 0; i < webChat.timeouts.length; i++) {
            clearTimeout(webChat.timeouts[i]);
        }
    },

    /**
     * Clears the customer's typing timeout.
     *
     * @param obj
     */
    clearTypingTimeout : function(obj) {
        'use strict';
        if (obj) {
            clearTimeout(obj);
        }
    },

    /**
     * Handles notification messages.
     *
     * @param message
     */
    handleNotification : function(message) { // NOSONAR: too complex
        // for Sonar, but cannot be reduced further
        'use strict';
        var body = message.body, method = body.method;
        if (method === chatConfig.jsonMethodRequestChat) {
            webChat.notifyRequestChat(body);
        } else if (method === chatConfig.jsonMethodRouteCancel) {
            webChat.notifyRouteCancel();
        } else if (method === chatConfig.jsonMethodRequestNewParticipant) {
            webChat.notifyNewParticipant(body);
        } else if (method === chatConfig.jsonMethodRequestIsTyping) {
            webChat.notifyIsTyping(body);
        } else if (method === chatConfig.jsonMethodRequestNewMessage) {
            webChat.notifyNewMessage(body);
        } else if (method === chatConfig.jsonMethodRequestCloseConversation) {
            webChat.notifyCloseConversation();
        } else if (method === chatConfig.jsonMethodRequestParticipantLeave) {
            webChat.notifyParticipantLeave(body);
        } else if (method === chatConfig.jsonMethodRequestNewPushMessage) {
            webChat.notifyNewPagePushMessage(body);
        } else if (method === chatConfig.jsonMethodRequestNewCoBrowseSessionKeyMessage) {
            webChat.notifyNewCoBrowseSessionKeyMessage(body);
        } else if (method === chatConfig.jsonMethodPing) {
            // do nothing with pings. They just confirm that the
            // WebSocket is open.
        } else if (method === chatConfig.jsonMethodFileTransfer) {
            webChat.notifyFileTransfer(body);
        } else if (method === chatConfig.jsonMethodWebOnHoldMessage) {
            webChat.notifyWebOnHold(body);
        } else {
            throw new TypeError('Received notification with unknown method: '.concat(method));
        }
    },

    /**
     * Initialise the chat.
     * @param {Boolean} disableControls - defines whether or not to disable the controls. If true, the user will not be able to type a message
     */
    initChat : function(disableControls, custDetails) {
        'use strict';

        // if the chat has already opened, don't bother opening it
        if (webChat.initCalled) {
            return;
        }

        if (disableControls === undefined) {
            disableControls = true;
        }

        webChat.custDetails = custDetails;
        chatUI.disableControls(disableControls);
        chatSocket.openSocket();
        webChat.initCalled = true;
    },

    /**
     * Notify the user that the chat is closed.
     *
     * @param body
     */
    notifyCloseConversation : function() {
        'use strict';
        // Server will close the websocket
        chatSocket.manualClose = false;
        chatConfig.dontRetryConnection = true;
        avayaGlobal.clearSessionStorage();
    },

    /**
     * Reset the chat.
     */
    resetChat : function() {
        'use strict';
        console.info('WebChat: Resetting chat');
        chatUI.resetChatUI();
        webChat.updateUsers();
        webChat.clearAllTimeouts();
        webChat.ak = null;
        webChat.guid = null;
        webChat.lastIsTypingSent = 0;
        chatSocket.resetWebSocket();
        chatConfig.dontRetryConnection = false;
    },

    /**
     * Notify the user that an agent's typing status has changed.
     *
     * @param body
     */
    notifyIsTyping : function(body) {
        'use strict';
        var isAgentTyping = body.isTyping;

        if (isAgentTyping === true) {
            var agent = webChat.users[body.agentId];
            agent.isTyping = isAgentTyping;
            webChat.updateTypingCell(agent, true);

            var agentTypeOut;
            if (agent.type === 'active_participant') {
                agentTypeOut = chatConfig.activeAgentTypeOut;
            } else if (agent.type === 'passive_participant') {
                agentTypeOut = chatConfig.passiveAgentTypeOut;
            } else {
                agentTypeOut = chatConfig.supervisorTypeOut;
            }

            if (agentTypeOut !== undefined) {
                webChat.clearTypingTimer(agentTypeOut);
            }

            agentTypeOut = setTimeout(function() {
                if (webChat.users !== undefined) {
                    agent.isTyping = false;
                    webChat.updateTypingCell(agent, false);
                }
            }, chatConfig.agentTypingTimeout);
            webChat.timeouts.push(agentTypeOut);
        }
    },

    /**
     * Notify the user of a new chat message.
     *
     * @param body
     */
    notifyNewMessage : function(body) {
        'use strict';
        var date = new Date(body.timestamp);
        var senderType = body.senderType.toLowerCase();
        var dateMessage = body.displayName + ' (' + date.toLocaleTimeString() + ')';

        if (senderType === "customer") {
            // this is likely to be caused by customer refreshing page
            webChat.writeResponse(dateMessage, chatConfig.writeResponseClassDate);
            webChat.writeResponse(body.message, chatConfig.writeResponseClassSent);
        } else {
            webChat.writeResponse(dateMessage, chatConfig.writeResponseClassAgentDate);
            var isBot = senderType === "bot";
            var chatMessageClass = (isBot ? chatConfig.writeResponseClassChatbot : chatConfig.writeResponseClassResponse);
            if ((body.type !== "widget")) {
                webChat.writeResponse(body.message, chatMessageClass);
            } else {
                webChat.createWidgets(body);
            }
        }

    },

    /**
     * Notify the user of a new page push message
     *
     * @param body
     */
    notifyNewPagePushMessage : function(body) {
        'use strict';
        var sentDate = new Date(body.timestamp), url = body.pagePushURL, name = body.displayName;

        // Check for Co-Browse enabled.
        var sessionKey = avayaGlobal.getParameterByName('key', body.pagePushURL);
        var dateMessage;

        if (sessionKey !== null) {
            console.debug('WebChat/CoBrowse: Received Co-Browse page push request for session ' + sessionKey +
                    '.');
            webChat.coBrowseKey = sessionKey;
            webChat.isPagePushKey = true;
            coBrowseUI.showCoBrowseIframe(url);

            // show the Co-Browsing notification
            dateMessage = 'System: (' + new Date().toLocaleTimeString() + ')';
            webChat.writeResponse(dateMessage, chatConfig.writeResponseClassSystem);
            webChat.writeResponse(chatConfig.coBrowseSessionStartedText, chatConfig.writeResponseClassSystem);
        } else {
            // show the Page-Push notification
            dateMessage = name + ' (' + sentDate.toLocaleTimeString() + ')';
            var systemMessage;
            var chatMessageClass;
            var senderType = body.senderType.toLowerCase();
            if (senderType === "bot") {
                systemMessage = chatConfig.pagePushMessageTextBot.concat(url);
                chatMessageClass = chatConfig.writeResponseClassChatbot;
            } else {
                systemMessage = chatConfig.pagePushMessageTextAgent.concat(url);
                chatMessageClass = chatConfig.writeResponseClassAgentDate;
            }
            webChat.writeResponse(dateMessage, chatMessageClass);
            webChat.writeResponse(systemMessage, chatConfig.writeResponseClassSystem);
        }
    },

    postCoBrowseDialogCleanUp : function() {
        'use strict';

        var contentWindow = webChat.coBrowseIframe.contentWindow;
        var iFramedCoBrowse = contentWindow.coBrowse;
        var iFramedCoBrowseUI = contentWindow.coBrowseUI;

        if (iFramedCoBrowse && iFramedCoBrowseUI) {

            iFramedCoBrowseUI.closeHangingDialogs();

        } else {
            coBrowseUI.closeHangingDialogs();
        }

    },

    /**
     * Notify the user of a new co-browse session key message
     *
     * @param body
     */
    notifyNewCoBrowseSessionKeyMessage : function(body) {
        'use strict';
        webChat.postCoBrowseDialogCleanUp();
        // TODO: check format of the key.
        var coBrowseKey = body.sessionKey;

        // makes sure that i-frame is closed before key-push load
        webChat.cleanUpPagePushCoBrowse();

	// show the Co-Browsing notification
        var dateMessage = 'System: (' + new Date().toLocaleTimeString() + ')';
        webChat.writeResponse(dateMessage, chatConfig.writeResponseClassSystem);
        webChat.writeResponse(chatConfig.coBrowseSessionStartedText, chatConfig.writeResponseClassSystem);

        webChat.joinKeyPushCoBrowse(coBrowseKey);
    },

    /**
     * Notify the user that an agent has joined the chat.
     *
     * @param body
     */
    notifyNewParticipant: function (body) {
        'use strict';

        // enable the controls now in case there are any missing fields in the request
        chatUI.disableControls(false);
        webChat.stopOnHoldMessages();

        var id = body.agentId;
        var role = body.role;
        var agents = body.participants;
        // assign chatbot name here. The UI will then use whatever name was assigned in AutomationController.
        if (id === 'AvayaAutomatedResource') {
            webChat.chatBotName = body.displayName;
        }

        var roles = webChat.joinedAgents[id];

        // Save all agent/supervisor states
        if (roles === undefined) {
            for (var i = 0; i < agents.length; i++) {
                roles = [];
                roles.push(agents[i].type);
                webChat.joinedAgents[agents[i].id] = roles;
            }
        } else {
            roles.push(role);
            webChat.joinedAgents[id] = roles;
        }

        // Notify UI about Agent/Supervisor join if it's configured in ChatUI configuration panel
        if (webChat.checkAgentVisibility(id, role)) {
            webChat.writeResponse(chatConfig.agentJoinedMessage, chatConfig.writeResponseClassSystem);
        }

        webChat.updateUsers(agents);
        
        if (body.webOnHoldComfortGroup !== undefined && body.webOnHoldComfortGroup.length > 0) {
            webChat.webOnHoldComfortGroups = body.webOnHoldComfortGroup[0];
        }
    },

    checkAgentVisibility : function(id, role) {
        'use strict';
        // check if notifications are allowed/required
        if (webChat.isBotVisible(id)) {
            return true;
        }
        
        if(webChat.isAgentActive(role)){
            return true;
        }
        
        // if notifications are allowed/required, display them
        var isVisible = webChat.isSupervisorVisible(role);
        if (isVisible) {
            return true;
        }
        return false;
    },
    
    isAgentActive : function (role) {
        "use strict";
        return role === 'active_participant';
    },
    
    isBotVisible : function (id) {
        "use strict";
        var announceBot = (id === 'AvayaAutomatedResource' && !chatConfig.suppressChatbotPresence);
        return announceBot;
    },
    
    isSupervisorVisible : function (role) {
        "use strict";
        var announceObserve = (role === 'supervisor_observe' && chatConfig.notifyOfObserve);
        var announceCoach = (role === 'supervisor_coach' && chatConfig.notifyOfCoach);
        var announceBarge = (role === 'supervisor_barge' && chatConfig.notifyOfBarge);
        return announceObserve || announceCoach || announceBarge || (role === 'passive_participant' && chatConfig.notifyOfBarge); 
    },

    /**
     * For every role stored in joinedAgents for the specified agentId it writes an agent leave notification
     * to the UI if combination of Agent/Role is marked as visible in ChatUI configuration panel
     * @param id - agentId
     * @param currentRole - the first role in the list of roles which should not be currently removed.
     */
    notifyConsecutiveAgentLeave: function (id, currentRole) {
        "use strict";
        var roles = webChat.joinedAgents[id];
        var role = roles[0];
        while (role !== undefined && role !== currentRole) {
            if (webChat.checkAgentVisibility(id, role)) {
                webChat.writeResponse(chatConfig.agentLeftMessage, chatConfig.writeResponseClassSystem);
            }
            roles.shift();
            role = roles[0];
        }
    },


    /**
     * Notify the user that an agent has left the chat.
     *
     * @param body
     */
    notifyParticipantLeave : function(body) {
        'use strict';

        var id = body.agentId;
        var agents = body.participants;

        // workaround to make sure Chrome closes on a two-node cluster
        if (body.endChatFlag) {
            chatConfig.dontRetryConnection = true;
        }

        if (agents.length !== 0) {
            var savedAgentRoles = webChat.joinedAgents[id];
        }
        var leaveReason = body.leaveReason.toLowerCase();
        console.log(leaveReason);
        
        // if there are no users, check if this is a transfer.
        if (agents.length === 0) {
            console.info('WebChat: Only the customer remains in the room.');
            chatUI.disableControls(true);
            webChat.startOnHoldMessages();

            if (leaveReason === 'transfer') {
                webChat.writeResponse(chatConfig.transferNotificationText, chatConfig.writeResponseClassSystem);
            } else if (leaveReason === 'requeue') {
                webChat.writeResponse(chatConfig.requeueNotificationText, chatConfig.writeResponseClassSystem);
            } else if (leaveReason === 'escalate' && !chatConfig.suppressChatbotPresence) {
                webChat.writeResponse(chatConfig.chatbotTransferNotification, chatConfig.writeResponseClassSystem);
            } else if (leaveReason === 'transfer_to_agent') {
                webChat.writeResponse(chatConfig.transferToUserText, chatConfig.writeResponseClassSystem);
            } else if (webChat.joinedAgents[id] !== undefined) {
                webChat.notifyConsecutiveAgentLeave(id);
                delete webChat.joinedAgents[id];
            }

        } else {
            if (savedAgentRoles !== undefined) {
                var otherParticipant;
                for (var i = 0; i < agents.length; i++) {
                    if (agents[i].id !== id) {
                        otherParticipant = agents[i].id;
                    }
                }
                var savedRolesOtherParticipant = webChat.joinedAgents[otherParticipant];
                if (savedAgentRoles.includes('supervisor_observe') || savedAgentRoles.includes('supervisor_coach')) {
                    // Notify customer with all supervisor roles leave, except the last one - barging supervisor
                    webChat.notifyConsecutiveAgentLeave(id, 'supervisor_barge')
                } else if (agents.length === 1 && (savedAgentRoles.includes('passive_participant')
                        || (savedRolesOtherParticipant !== undefined && savedRolesOtherParticipant.includes('supervisor_barge')))) {
                    webChat.writeResponse(chatConfig.transferNotificationText, chatConfig.writeResponseClassSystem);
                    savedAgentRoles.shift();
                }
            }
            if (savedAgentRoles !== undefined && savedAgentRoles.length === 0) {
                delete webChat.joinedAgents[id];
            }
        }
        webChat.updateUsers(agents);
    },

    /**
     * Notifies the user that they have entered the chat.
     *
     * @param body
     */
    notifyRequestChat : function(body) {
        'use strict';

        webChat.guid = body.guid;
        webChat.custDetails.displayName = body.intrinsics.name;
        webChat.ak = body.authenticationKey;

        var wrid = body.workRequestId;
        console.info("WebChat: workRequestId is " + wrid + ", contactUUID/chatroom key is " + webChat.ak + ". Valid email? " + body.wasEmailValid);

        if (chatSocket.retries > 0) {
            chatSocket.resetConnectionAttempts();
        }

        // Send up the Co-Browse status of this page.
        var coBrowseEnabled = typeof coBrowse !== 'undefined' && webChat.coBrowseEnabled;
        webChat.sendCoBrowseStatus(coBrowseEnabled);

        // if the customer has already been connected, don't play the on
        // hold messages
        if (!chatConfig.previouslyConnected) {
            webChat.writeResponse(chatConfig.chatOpenedText, chatConfig.writeResponseClassSystem);
            chatConfig.previouslyConnected = true;

            if (!body.isEmailValid) {
                webChat.writeResponse(chatConfig.invalidEmailAddressText, chatConfig.writeResponseClassSystem);
                webChat.custDetails.email = '';
            }

            webChat.webOnHoldComfortGroups = body.webOnHoldComfortGroups[0];
            webChat.webOnHoldURLs = body.webOnHoldURLs[0];
            webChat.startOnHoldMessages();
        } else {
            webChat.writeResponse(chatConfig.successfulReconnectionText, chatConfig.writeResponseClassSystem);
        }
    },

    /**
     * Notifies the user that no agent could be found to route the chat to.
     */
    notifyRouteCancel : function() {
        'use strict';
        webChat.writeResponse(chatConfig.routeCancelText, chatConfig.writeResponseClassSystem);
        chatConfig.dontRetryConnection = true;
    },

    /**
     * Notifies the user of a new file transfer.
     */
    notifyFileTransfer : function(body) {
        'use strict';
        console.info('WebChat: Notifying of file transfer');
        var agentname = body.agentName;
        var uuid = body.uuid;
        var wrid = body.workRequestId;
        var url = links.getFileDownloadUrl(uuid, wrid);
        var filename = body.name;
        var timestamp = new Date().toLocaleString();
        var dateMessage = agentname + ' (' + timestamp + ')';
        webChat.writeResponse(dateMessage, chatConfig.writeResponseClassAgentDate);

        var message = chatConfig.fileTransferMessageText;
        message = message.replace('{0}', agentname);
        message = message.replace('{1}', filename);
        message = message.replace('{2}', timestamp);
        message = message.replace('{3}', url);
        webChat.writeResponse(message, chatConfig.writeResponseClassResponse);
    },

    /**
     * Notifies the user that a video or voice chat is being opened.
     *
     * @param body
     */
    notifyVideoOrVoice : function(body) {
        'use strict';
        console.debug('WebChat: ' + body);
    },

    /**
     * Called when the customer starts typing.
     * The current behavious is that pressing Enter (keycode 13) will send the chat message.
     * @param event
     */
    onType : function(event) {
        'use strict';
        if (event.keyCode === 13) {
            webChat.sendChatMessage();
        } else {
            webChat.startTypingTimer();
        }
    },

    /**
     * Play the Web-On-Hold messages inside a specific array.
     *
     * @param array
     *                of Web-On-Hold comfort messages or URLs.
     */
    playOnHoldMessage : function(array) {
        'use strict';
        var currentMsg;

        // if this has a urls array, it's a WebOnHold URL
        // otherwise, it's a comfort message
        if((array.sentMessages >=chatConfig.maxWebOnHoldMessages) && (chatConfig.serverWebOnHold)) {
            webChat.stopOnHoldMessages();
        }
        if (array.urls !== undefined) {
            currentMsg = array.urls[array.currentSequence];
            var msgText = array.description + ': ' + currentMsg.url;
            if(chatConfig.serverWebOnHold) {
                webChat.sendWebOnHold(array.currentSequence, 'url');
            } else {
                webChat.writeResponse(msgText, chatConfig.writeResponseClassSystem);
            }
        } else {
            currentMsg = array.messages[array.currentSequence];
            if(chatConfig.serverWebOnHold) {
                webChat.sendWebOnHold(array.currentSequence, 'comfort');
            } else {
                webChat.writeResponse(currentMsg.message, chatConfig.writeResponseClassSystem);
            }
        }

        array.currentSequence++;
        array.sentMessages++;
        if ((array.numberOfMessages !== undefined && array.currentSequence >= array.numberOfMessages) ||
                (array.urls !== undefined && array.currentSequence >= array.urls.length)) {
            array.currentSequence = 0;
        }

    },

    /**
     * Sends a close chat request to the server.
     */
    quitChat : function() {
        'use strict';
        // Prevent reconnect attempts if customer clicks 'Close' while chat is
        // reconnecting
        chatConfig.dontRetryConnection = true;
        chatSocket.manualClose = true;
        webChat.clearAllTimeouts();
        webChat.joinedAgents = {};
        if (webSocket !== null && webSocket.readyState === webSocket.OPEN) {
            var closeRequest = {
                'apiVersion' : '1.0',
                'type' : 'request',
                'body' : {
                    'method' : 'closeConversation'
                }
            };
            webChat.writeResponse(chatConfig.closeRequestText, chatConfig.writeResponseClassSent);
            chatSocket.sendMessage(closeRequest);
        }
    },

    /**
     * Sends a chat message to the server. If the message box is empty, nothing
     * is sent.
     */
    sendChatMessage : function() {
        'use strict';
        var text = chatUI.getChatMessage();

        if (!avayaGlobal.isStringEmpty(text)) {

            // add the timestamp message, then the chat.
            var timestamp = new Date().toLocaleTimeString();
            var dateMessage = webChat.custDetails.displayName + ' (' + timestamp + ')';
            webChat.writeResponse(dateMessage, chatConfig.writeResponseClassDate);
            webChat.writeResponse(text, chatConfig.writeResponseClassSent);

            var message = {
                'apiVersion' : '1.0',
                'type' : 'request',
                'body' : {
                    'method' : 'newMessage',
                    'message' : text,
                    'type': 'text',
                    'data': {
                        'message': text
                    },
                    'customData': webChat.customData
                }
            };
            chatSocket.sendMessage(message);
            chatUI.clearMessageInput();
        }
    },

    /**
     * Sends a widget response to the server. If the message box is empty, nothing
     * is sent.
     */
    sendWidgetMessage: function () {
        'use strict';

        var timestamp = new Date().toLocaleTimeString();
        var custName = webChat.custDetails.displayName;
        var dateMessage = custName + ' (' + timestamp + ')';
        webChat.writeResponse(dateMessage, chatConfig.writeResponseClassDate);
        webChat.writeResponse(this.getAttribute('value'), chatConfig.writeResponseClassSent);

        var message = {
            'apiVersion': '1.0',
            'type': 'request',
            'body': {
                'method': 'newMessage',
                'message': this.getAttribute('value'),
                'type': 'customResponse',
                'data': {
                    "message": this.getAttribute('value'),
                    "correlationId": this.getAttribute('data-correlationId')
                },
                'customData': webChat.customData
            }
        };

        chatSocket.sendMessage(message);

    },

    /**
     * Lets the agents know that the customer is typing.
     *
     * @param isUserTyping
     */
    sendIsTyping : function(isUserTyping) {
        'use strict';
        var isTypingMessage = {
            'apiVersion' : '1.0',
            'type' : 'request',
            'body' : {
                'method' : 'isTyping',
                'isTyping' : isUserTyping
            }
        };

        // update lastisTypingSent timestamp
        webChat.lastIsTypingSent = Date.now();

        chatSocket.sendMessage(isTypingMessage);
    },

    sendWebOnHold : function (currentSeq, type) {
        'use strict';
        var webOnHoldMessage = {
            'apiVersion' : '1.0',
            'type' : 'request',
            'body' : {
                'method' : 'webOnHoldMessage',
                'type' : type,
                'sequence' : currentSeq
            }
        };
        chatSocket.sendMessage(webOnHoldMessage);
    },

    /**
     * Lets the agent know if the customer's client is enabled for Co-Browse.
     *
     * @param isEnabled
     */
    sendCoBrowseStatus : function(isEnabled) {
        'use strict';
        var coBrowseStatusMessage = {
            'apiVersion' : '1.0',
            'type' : 'request',
            'body' : {
                'method' : 'coBrowseStatus',
                'isEnabled' : isEnabled
            }
        };

        chatSocket.sendMessage(coBrowseStatusMessage);
    },

    /**
     * Ping the WebSocket to see if it is still open on both ends. JavaScript
     * doesn't have an API for this, so this is a workaround.
     */
    sendPing : function() {
        'use strict';
        var ping = {
            'apiVersion' : '1.0',
            'type' : 'request',
            'body' : {
                'method' : 'ping'
            }
        };
        chatSocket.sendMessage(ping);
    },

    /**
     * Start playing the on hold messages, if either the messages or the URLs
     * are defined. The intervals are defined in the OCP Admin.
     */
    startOnHoldMessages : function() {
        'use strict';
        console.info('WebChat: Starting the On Hold messages');
        var onHoldUrlsDefined = (webChat.webOnHoldURLs !== null && webChat.webOnHoldURLs.urls.length > 0);
        var onHoldMessagesDefined = (webChat.webOnHoldComfortGroups !== null && webChat.webOnHoldComfortGroups.messages.length > 0);

        if (!onHoldUrlsDefined && !onHoldMessagesDefined) {
            console.warn('WebChat: On Hold messages are not defined!');
        }

        if (onHoldMessagesDefined) {
            // sort the WebOnHoldComfortGroups according to sequence
            var newMessages = webChat.webOnHoldComfortGroups.messages.sort(function(a, b) {
                return (a.sequence - b.sequence);
            });
            webChat.webOnHoldComfortGroups.messages = newMessages;
            webChat.webOnHoldComfortGroups.currentSequence = 0;
            webChat.webOnHoldComfortGroups.sentMessages = 0;

            webChat.onHoldComfortInterval = setInterval(function() {
                webChat.playOnHoldMessage(webChat.webOnHoldComfortGroups);
            }, webChat.webOnHoldComfortGroups.delay * 1000);
            webChat.timeouts.push(webChat.onHoldComfortInterval);
        }

        if (onHoldUrlsDefined) {
            webChat.webOnHoldURLs.currentSequence = 0;
            webChat.webOnHoldURLs.sentMessages = 0;

            webChat.onHoldUrlInterval = setInterval(function() {
                webChat.playOnHoldMessage(webChat.webOnHoldURLs);
            }, webChat.webOnHoldURLs.holdTime * 1000);
            webChat.timeouts.push(webChat.onHoldUrlInterval);
        }

    },

    stopOnHoldMessages : function() {
        'use strict';
        console.info('Web On Hold: Stopping messages');
        clearInterval(webChat.onHoldUrlInterval);
        clearInterval(webChat.onHoldComfortInterval);
    },

    /**
     * Start the customer's typing timer.
     */
    startTypingTimer : function() {
        'use strict';
        var isTypingTimer = Date.now();
        var timerExpiryTime = webChat.lastIsTypingSent + webChat.timeBetweenMsgs;

        if (isTypingTimer >= timerExpiryTime) {
            webChat.sendIsTyping(true);
        }

    },

    /**
     * Update the typing image for a specific agent.
     *
     * @param agent
     * @param isTyping
     */
    updateTypingCell : function(agent, isTyping) {
        'use strict';
        var imageSrc;
        if (agent.agentType === 'active_participant' || agent.agentType === 'passive_participant') {
            imageSrc = isTyping === true ? chatConfig.agentTypingImage : chatConfig.agentImage;
        } else {
            imageSrc = isTyping === true ? chatConfig.supervisorTypingImage : chatConfig.supervisorImage;
        }

        // find which index this agent is. If the index is -1 (likely due to timing issues with a single agent),
        // default to 0
        var index = Object.keys(webChat.users).indexOf(agent);
        if (index < 0) {
            index = 0;
        }
        chatUI.updateUserImage(index, imageSrc, "", isTyping === true ? agent.displayName.concat(' is typing') : agent.displayName);
    },

    /**
     * Update the users section when an agent leaves or joins.
     *
     * @param agents
     */
    updateUsers : function(agents) {
        'use strict';
        webChat.users = {};
        chatUI.clearUsers();

        if (agents !== undefined) {
            for (var i = 0; i < agents.length; i++) {
                var agent = agents[i];
                if (webChat.checkAgentVisibility(agent.id, agent.type)) {
                    console.debug('WebChat: Adding agent with id ' + agent.id + ' and name ' + agent.name);

                    var src = (agent.type.substring(0, 10) === 'supervisor') ? chatConfig.supervisorImage
                            : chatConfig.agentImage;
                    var className = '';
                    chatUI.updateUserImage(i, src, className,agent.name);

                    webChat.users[agent.id] = {
                        displayName : agent.name,
                        isTyping : false,
                        agentType : agent.type
                    };
                    avayaGlobal.setSessionStorage("users", JSON
                        .stringify(webChat.users));
                }
            }
        }
    },

    /**
     * split text string on white-space and add white-space
     * after each entry to maintain message structure
     *
     *
     * @param text
     */
    splitText : function(text) {
        'use strict';
        var textArray = text.split(" ");
        var output = [];
        for (var i = 0; i < textArray.length; i++) {
            output.push(textArray[i]);
            output.push(" ");
        }

        return output;

    },

    /**
     * Writes the specified text out to the transcript box and includes an
     * autoscroll mechanism.
     *
     * @param text
     * @param msgClass
     */
    writeResponse : function(text, msgClass) {
        'use strict';
        var paragraph = document.createElement('p');
        paragraph.className = msgClass;

        // get message split by white-space
        var textArray = webChat.splitText(text);

        // append each element of the message as a string or a link
        for (var i = 0; i < textArray.length; i++) {

            var span = document.createElement('span');
            // check that URLs have at least one character in the path else display as text
            // have to use "indexOf === 0", as IE does not support the startsWith method
            if ((textArray[i].indexOf("http://") === 0 && textArray[i].length > 7) ||
                    (textArray[i].indexOf("https://") === 0 && textArray[i].length > 8)) {

                webChat.appendLink(textArray[i], 'blank', false, span);
            }
            //if http(s) appears without a qualifing '://', it will be displayed as normal text
            else {
                span.textContent = textArray[i];
            }

            paragraph.appendChild(span);
        }
        chatUI.appendParagraph(paragraph);

    },

    /* This function is used to plot widgets */
    createWidgets: function (body) {
        'use strict';
        var data = body.data;
        if (data.widgetType === undefined) {
            console.warn("Widget type unknown!");
            webChat.writeResponse(chatConfig.unknownWidgetText, chatConfig.writeResponseClassChatbot);
            return;
        }
        // different widgets use different fields for the helper text
        webChat.writeResponse(body.data.text || body.message, chatConfig.writeResponseClassChatbot);
        var type = data.widgetType.split('|');

        if (type[0].toLowerCase() === (widgetConfig.selector).toLowerCase()) {
            webChat.createSelectorElement(data, type[1]);
        }
        else if (type[0].toLowerCase() === (widgetConfig.click).toLowerCase()) {
            webChat.createClickElement(data, type[1]);
        }
        else {
            console.warn("This type of widget (" + type + ") is not supported");
            webChat.writeResponse(chatConfig.unknownWidgetText, chatConfig.writeResponseClassChatbot);
        }

    },

    createSelectorElement: function (data, type) {
        'use strict';
        var divElement = document.createElement('div');
        divElement.className = chatConfig.writeResponseClassResponse;

        if (type.toLowerCase() === (widgetConfig.radio).toLowerCase()) {
            webChat.createRadioElement(data, divElement);
        }

        divElement.append(document.createElement("br"));
        chatUI.appendParagraph(divElement);
    },

    createClickElement: function (data, type) {
        'use strict';
        var divElement = document.createElement('div');
        divElement.className = chatConfig.writeResponseClassResponse;

        if (type.toLowerCase() === (widgetConfig.button).toLowerCase()) {
            webChat.createButtonElement(data, divElement);
        }

        divElement.append(document.createElement("br"));
        chatUI.appendParagraph(divElement);

    },

    createRadioElement: function (data, divElement) {
        'use strict';
        for (var item in data.selectItems ) {
            var radioInput = document.createElement("label");
            var radioElement = document.createElement("input");
            radioElement.type = "radio";
            radioElement.value = data.selectItems[item].product;
            radioElement.id =data.selectItems[item].id;
            radioElement.name = data.selectorType;
            radioElement.setAttribute("data-correlationId", data.correlationId);
            radioElement.addEventListener('click', webChat.sendWidgetMessage);
            radioInput.append(radioElement);
            divElement.append(radioInput);
            var spanElement = document.createElement('span');
            var textElement = document.createTextNode(data.selectItems[item].product);
            spanElement.append(textElement);
            spanElement.setAttribute("class", "radio-widget");

            divElement.append(spanElement);
            divElement.append(document.createElement("br"));
        }

    },

    createButtonElement: function (data, divElement) {
        'use strict';
	    for (var item in data.selectItems )  {
            var btnElement = document.createElement("BUTTON");
            btnElement.setAttribute("class", "button-widget");
            btnElement.setAttribute("id",data.selectItems[item].id);
            btnElement.setAttribute("value", data.selectItems[item].product);
            btnElement.setAttribute("data-correlationId", data.correlationId);
            btnElement.addEventListener('click', webChat.sendWidgetMessage);
            btnElement.append(document.createTextNode(data.selectItems[item].product));
            divElement.append(btnElement);
        }
    },

    /**
     * Utility function to set the workflow type for routing.
     *
     * @param newWorkflowType
     */
    setWorkflowType : function(newWorkflowType) {
        'use strict';
        webChat.workflowType = newWorkflowType;
    },

    /**
     * Gather the required chat elements
     */
    gatherChatElements : function() {
        'use strict';
        var find = avayaGlobal.getEl;
        webChat.coBrowseIframe = find('cobrowse');
    },

    ////////////////////////////////////////////////////////////////////////////
    // Subscribed Co-Browse iFrame Events
    ////////////////////////////////////////////////////////////////////////////

    onLoadCoBrowseIframe : function() {
        'use strict';

        var contentWindow = webChat.coBrowseIframe.contentWindow;
        var iFramedCoBrowse = contentWindow.coBrowse;
        var iFramedCoBrowseUI = contentWindow.coBrowseUI;

        if (iFramedCoBrowse !== undefined && iFramedCoBrowseUI !== undefined) {
            iFramedCoBrowseUI.addListener(webChat);
            iFramedCoBrowse.init(webChat.coBrowseSDKPath, console, [ webChat,
                    iFramedCoBrowseUI ], links.coBrowseHost);

            contentWindow.onbeforeunload = webChat.onBeforeUnloadCoBrowseIframe;
        }
    },

    onBeforeUnloadCoBrowseIframe : function() {
        'use strict';

        var contentWindow = webChat.coBrowseIframe.contentWindow;
        var iFramedCoBrowse = contentWindow.coBrowse;
        var iFramedCoBrowseUI = contentWindow.coBrowseUI;

        if (iFramedCoBrowse !== undefined && iFramedCoBrowseUI !== undefined) {
            iFramedCoBrowseUI.closeHangingDialogs();
        }
    },

    ////////////////////////////////////////////////////////////////////////////
    // Subscribed Co-Browse Events
    ////////////////////////////////////////////////////////////////////////////

    onCoBrowseReady : function(source) {
        'use strict';

        if (webChat.isPagePushKey) {
            // Came from the iFrame handle it differently.
            if (webChat.coBrowseKey) {
                webChat.joinPagePushCoBrowse();
            }
        } else {
            // Came from the Co-Browse on this page.
            webChat.coBrowseReady = true;
            coBrowseUI.showCoBrowseLinkDiv();
        }
    },

    onCoBrowseSessionClose : function(source, title, text) {
        'use strict';

        webChat.coBrowseKey = '';
        if (webChat.initCalled) {

            // we don't have a timestamp or display name for this,
            // so use the local time and claim it's done by the system.
            var date = new Date();
            webChat.writeResponse('System ('+ date.toLocaleTimeString() + ')', chatConfig.writeResponseClassSystem);
            webChat.writeResponse(chatConfig.coBrowseSessionFinishedText, chatConfig.writeResponseClassSystem);
        }
    },

    onCoBrowseStopSuccess : function(source) {
        'use strict';

        if (webChat.initCalled) {

            // we don't have a timestamp or display name for this,
            // so use the local time and claim it's done by the system.
            var date = new Date();
            webChat.writeResponse('System ('+ date.toLocaleTimeString() + ')', chatConfig.writeResponseClassSystem);
            webChat.writeResponse(chatConfig.coBrowseSessionFinishedText, chatConfig.writeResponseClassSystem);
        }
    },

    // Subscribed coBrowseUI events.
    onCoBrowseUIJoinRequest : function(source, name, coBrowseKey) {
        'use strict';

        var hiddenElements = webChat.hideElements.concat(coBrowseUI.hiddenElements);
        coBrowse.joinSession(name, coBrowseKey, hiddenElements);
        coBrowseUI.hideCoBrowseLinkDiv();
    },

    onCoBrowseUIJoinFailure : function(source) {
        'use strict';

        if (webChat.coBrowseKey) {
            if (webChat.isPagePushKey) {
                webChat.cleanUpPagePushCoBrowse();
            }
        }
    },

    onCoBrowseUICleanUp : function(source) {
        'use strict';

        if (webChat.isPagePushKey) {
            webChat.cleanUpPagePushCoBrowse();
        }

        coBrowseUI.showCoBrowseLinkDiv();
    },

    // CoBrowse related functions.
    initCoBrowse : function() {
        'use strict';

        coBrowse.init(webChat.coBrowseSDKPath, console, [ webChat, coBrowseUI ],
                links.coBrowseHost);
    },

    joinPagePushCoBrowse : function() {
        'use strict';

        // Hide join dialog for other CoBrowse on this page.
        coBrowseUI.closeDialog(coBrowseUI.proactiveJoinDialogId);
        coBrowseUI.hideCoBrowseLinkDiv();

        var iFramedCoBrowse = webChat.coBrowseIframe.contentWindow.coBrowse;
        if (iFramedCoBrowse) {
            var hiddenElements = webChat.hideElements.concat(coBrowseUI.hiddenElements);
            iFramedCoBrowse.joinSession(webChat.custDetails.displayName, webChat.coBrowseKey, hiddenElements);
        } else {
            // TODO: Handle error.
        }
    },

    joinKeyPushCoBrowse : function(coBrowseKey) {
        'use strict';

        var hiddenElements = webChat.hideElements.concat(coBrowseUI.hiddenElements);
        coBrowse.joinSession(webChat.custDetails.displayName, coBrowseKey, hiddenElements);
        coBrowseUI.hideCoBrowseLinkDiv();
    },

    cleanUpPagePushCoBrowse : function() {
        'use strict';

        webChat.isPagePushKey = false;
        coBrowseUI.hideCoBrowseIframe();
    },

    /**
     * Call this to set up webChat.
     */
    setupWebChat: function() {
        'use strict';

        // check if this browser supports required features
        avayaGlobal.detectBrowserSupport();
        links.setupSecurity();

        webChat.gatherChatElements();

        // set up the configuration panel. Remove this in production
        chatConfigPanel.setup();


        // set up the UI
        chatUI.setup();

        // Set Co-Browsing iFrame onLoad handler. Comment out if not using Co-Browsing.
        webChat.coBrowseIframe.onload = webChat.onLoadCoBrowseIframe;

        // Setup Co-Browsing instance on this page. Comment out if not using Co-Browsing
        coBrowseUI.addListener(webChat);
        webChat.initCoBrowse();

        // check if there was a chat in progress before reloading
        chatSocket.reloadAfterRefresh();

        ewt.requestEwt();
        chatLogon.saveAttributeCount();

        // If chat is in progress, prevent user from accidentally closing the page.
        // Can't override default message due to security restrictions
        // so the value returned here doesn't really matter.
        window.onbeforeunload = function() {
            // Some browsers appear to have issues around closing the CoBrowse session during page refresh
            // This appears to be low-level browser and/or network issues, so the best we can do is prevent the user from accidentally closing it
            var inCobrowse = coBrowse.getSessionItem(coBrowse.inCoBrowse);
            if (webChat.initCalled || inCobrowse === "true") {
                chatSocket.setupRefresh();
                return "You're about to end your session, are you sure?";
            }
        };

        window.onunload = function() {
            if (coBrowse !== 'undefined') {
                // reset the session details. This would normally be handled by a successful call to the logout method,
                // but some browsers have issues with the timing of this
                coBrowse.setSessionItem(coBrowse.inCoBrowse, "false");
                coBrowse.stopSession();
                return "Ending session";
            }
        };
    },
    notifyWebOnHold: function(body) {
        'use strict';
        webChat.writeResponse(body.message, chatConfig.writeResponseClassSystem);
    }
};
