function ttsHiliteModule(window) {

    var window = window,
        document = window.document;

    function getXPath(element) {

        var xpath = '';
        for (; element && element.nodeType == 1; element = element.parentNode) {
            /* jQuery code: var id = $(element.parentNode).children(element.tagName).index(element) + 1; */
            /* calculate the index of occurance of the "element" w.r.t the parentNode */
            var checkPrevSiblingsOfElement = element.previousSibling;
            var id = 1;

            while (checkPrevSiblingsOfElement != null) { // It becomes null when it runs  out of previous nodes
                if (checkPrevSiblingsOfElement.nodeType === 1) { // Execute only if previousNode is an ELEMENT_NODE (especially omit text nodes)
                    if (checkPrevSiblingsOfElement.tagName.toLowerCase() === element.tagName.toLowerCase()) {
                        id++;
                    }
                }
                checkPrevSiblingsOfElement = checkPrevSiblingsOfElement.previousSibling;
            }

            id > 1 ? (id = '[' + id + ']') : (id = '');
            xpath = '/' + element.tagName.toLowerCase() + id + xpath;
        }
        return xpath;
    }

    function applyHighlight(doc, serializedStr, className) {

        var rangeParams = base64.decode(serializedStr).split(",");
        var startOffset = parseInt(rangeParams[2]);
        var endOffset = parseInt(rangeParams[3]);

        var startContainerRelation = rangeParams[0].split("::")[1];
        var endContainerRelation = rangeParams[1].split("::")[1];

        var startContainer, endContainer;

        if (startContainerRelation !== "null") {
            startContainer = rangeParams[0].split("::")[1] === "prevSib" ? Hilite.getElementFromXPath(doc, rangeParams[0].split("::")[0]).nextSibling : Hilite.getElementFromXPath(doc, rangeParams[0].split("::")[0]).childNodes[0];

            if (startContainer.nodeType === 1 && startContainer.className.split(" ")[0] === "highlight") {
                startContainer = startContainer.childNodes[0];
            }

            //step 1. check if offsets are available in the textNode
            if (startContainer.length <= startOffset) {
                var shiftedStart = Hilite.shiftContainerAndOffset(startContainer, startOffset);
                startContainer = shiftedStart.shiftedContainer;
                startOffset = shiftedStart.shiftedOffset;
            }
        } else {
            startContainer = Hilite.getElementFromXPath(doc, rangeParams[0].split("::")[0]);
        }

        if (endContainerRelation !== "null") {

            endContainer = rangeParams[1].split("::")[1] === "prevSib" ? Hilite.getElementFromXPath(doc, rangeParams[1].split("::")[0]).nextSibling : Hilite.getElementFromXPath(doc, rangeParams[1].split("::")[0]).childNodes[0];

            if (endContainer.nodeType === 1 && endContainer.className.split(" ")[0] === "highlight") {
                endContainer = endContainer.childNodes[0];
            }

            //step 1. check if offsets are available in the textNode
            if (endContainer.length < endOffset) {
                var shiftedEnd = Hilite.shiftContainerAndOffset(endContainer, endOffset);
                endContainer = shiftedEnd.shiftedContainer;
                endOffset = shiftedEnd.shiftedOffset;
            }
        } else {
            endContainer = Hilite.getElementFromXPath(doc, rangeParams[1].split("::")[0]);
        }


        var rangeObj = doc.createRange();
        rangeObj.setStart(startContainer, startOffset);
        rangeObj.setEnd(endContainer, endOffset);

        getNodesInRange(rangeObj, doc, className);
    }

    function getNodesInRange(rangeObj, doc, className) {

        var xpaths = []; /* NR - array of XPATH addresses */
        //this.removeClasses = []; /* Merged classes, needs to be removed */
        var range = rangeObj,
            nodes = [],
            /* Collection of text nodes to be wrapped */
            mergeClasses = [],
            /* NR - Elements are objects {"className": classHighlight[1], "element": node, "position": position} */
            newStartContainer = false,
            newEndContainer = false,
            mergeClassesSeen = []; /* To maintain uniqueness of merged classes */

        /* When documnet body gets selected */
        if (navigator.userAgent.toLowerCase().indexOf("ipad") > -1 || navigator.userAgent.toLowerCase().indexOf("iphone") > -1) {
            if (range.startContainer === doc.body || range.endContainer === doc.body) {
                return "invalid: <body> selected";
            }
        }


        /* start container or end container is null logic */
        if (range.startContainer === null && range.endContainer === null) {
            return "invalid: (SC,EC)=NULL";
        } else if (range.startContainer === null) {
            range.startContainer = range.endContainer;
        } else if (range.endContainer === null) {
            range.endContainer = range.startContainer;
        }

        /* fix for firefox - when the selected region has endContainer before the startContainer (or) has "\n\t" text nodes*/
        var nodesBeforeStartContainer = [];
        for (var node = doc.getElementsByTagName('body')[0], i = 0; node; node = Hilite.getNextNode(node)) {
            if (node === range.startContainer) {
                break;
            } else {
                nodesBeforeStartContainer[i] = node;
                i++;
            }
        }
        if (Hilite.arrayContainsObject(nodesBeforeStartContainer, range.endContainer)) {
            range.setEnd(range.startContainer, range.endOffset);
        }

        if (range.endContainer.nodeType === 3) {
            var tempEndNode = range.endContainer.nodeValue.replace(/[\t\n\r]/g, "");
            if (tempEndNode.length === 0) {
                //range.setEnd(range.startContainer, range.endOffset);
                range.setEnd(range.startContainer, range.startContainer.length);
            }
        }
        /* end of fix */



        // populate nodes[] & mergeClasses[]
        for (var node = range.startContainer, position = 0; node; node = Hilite.getNextNode(node)) {

            if (node.nodeType === 3) { /* text nodes which does not belong to SVG <text> */

                /* filter out text nodes with escape sequences */
                var nodeLength = node.nodeValue.replace(/[ \t\n\r]/g, "");
                if (nodeLength.length === 0) {
                    continue;
                }

                // push textNodes not wrapped in nodes with "highlight" class to nodes[]
                /* if the text is child node of <text> of <svg>, type: svg */
                if (node.parentNode.nodeName === "text")
                    nodes.push({
                        "type": "svgText",
                        "element": node,
                        "position": position++
                    });
                else
                    nodes.push({
                        "type": "default",
                        "element": node,
                        "position": position++
                    });
            }

            // if the nodes in selection contain the "highlight" class then its a merge operation
            if (node == range.endContainer)
                break;
        }



        //Here the selection is a subset of the existing selection
        if (nodes.length === 0) {
            if (mergeClasses.length !== 0) {
                if (Hilite.merge === true) { //return the selection if it is the duplicate of an existing, before returning
                    return savedSelection;
                } else {
                    return;
                }
            } else {
                return "invalid: no text";
            }
        }

        //1. If range.startContainer == range.endContainer, then slection made within the same Element
        // also in case of merge highlight, this condition would fail
        //if(range.startContainer == range.endContainer){or
        if (nodes.length === 1) {

            var startOffset, endOffset, xpathValues, xpath;

            if (range.startContainer.previousSibling) {
                //check for "highlight" class
                //var classHighlight =  range.startContainer.previousSibling.getAttribute('class').split(" ");
                var classHighlight = (range.startContainer.previousSibling.getAttribute('class') !== null) ?
                    range.startContainer.previousSibling.getAttribute('class').split(" ") :
                    [null];
                if (classHighlight[0] === 'highlight') {


                    xpathValues = range.startContainer.previousSibling.getAttribute("data-offset");
                    xpathValues = base64.decode(xpathValues).split("|");
                    startOffset = range.startOffset + parseInt(xpathValues[1]);
                    endOffset = range.endOffset + parseInt(xpathValues[1]);
                    relation = xpathValues[2];
                    xpath = xpathValues[3];

                } else {

                    startOffset = range.startOffset;
                    endOffset = range.endOffset;
                    relation = "prevSib";
                    xpath = getXPath(range.startContainer.previousSibling);

                }
            } else {

                startOffset = range.startOffset;
                endOffset = range.endOffset;
                relation = "parentNode";
                xpath = getXPath(range.startContainer.parentNode);
            }

            var dataOffset = startOffset + "|" + endOffset + "|" + relation + "|" + xpath;

            if (window.getSelection) {

                if (nodes[0].type === "svgText") {
                    var newNode = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
                    newNode.setAttribute('class', 'highlight ' + className);
                } else {
                    var newNode = doc.createElement(Hilite.hiliteTag);
                    newNode.className = newNode.className + "highlight " + className;
                }

                // Split the text
                var text1 = nodes[0].element.nodeValue.substr(0, range.startOffset);
                var text2 = nodes[0].element.nodeValue.substr(range.startOffset, range.endOffset - range.startOffset);
                var text3 = nodes[0].element.nodeValue.substr(range.endOffset);

                newNode.appendChild(doc.createTextNode(text2));
                newNode.setAttribute('data-offset', base64.encode(dataOffset));

                //range.deleteContents();
                //range.insertNode(newNode);

                // Replace with existing text
                nodes[0].element.parentNode.replaceChild(newNode, nodes[0].element);

                // Put text1 before newNode
                if (text2.length > 0) {
                    newNode.parentNode.insertBefore(doc.createTextNode(text1), newNode);
                }

                // Put text3 before newNode
                if (text3.length > 0) {
                    if (newNode.nextSibling) {
                        newNode.parentNode.insertBefore(doc.createTextNode(text3), newNode.nextSibling);
                    } else {
                        newNode.parentNode.appendChild(doc.createTextNode(text3));
                    }

                }
            } else if (window.document.selection) {
                var rangeIE = doc.selection.createRange();
                rangeIE.pasteHTML("<span class='highlight " + className + "' data-offset=" + base64.encode(dataOffset) + ">" + rangeIE.text + "</span");
            }

            xpaths[nodes[0].position] = base64.encode(dataOffset);
        }

        //2. If start.parentNode != end.parentNode, then slection made across multiple Elements
        else {

            // a. extract all the nodes within the selection(in case of merge only the un-highlighted nodes) - already extracted in nodes[]

            // b. apply highlight to all the selected text nodes # except <textNode>\n</textNode>

            for (var i = 0; i < nodes.length; i++) {
                if (nodes[i].element.nodeType == 3) {

                    //alert(nodes[i].nodeValue.charCodeAt(0));
                    //To eliminate textNode's with a newline nodeValue
                    //if((nodes[i].nodeValue === "\t\t\n\t\t") || (nodes[i].length <= 0))
                    //if((nodes[i].nodeValue.indexOf("\t\t\n\t\t") !== -1) || (nodes[i].length <= 0))
                    //if((nodes[i].nodeValue === "\t\t\n\t\t") || (nodes[i].length <= 0) || (nodes[i].nodeValue === "\n\t\t\n\t\t") || (nodes[i].nodeValue === "\n\t\t"))
                    //var tempNode = nodes[i].nodeValue.replace(/[\t\n\r]/g, "");
                    //if(tempNode.length === 0){
                    //continue;
                    //}


                    if (i == 0) { // 1st node - begin from startOffset
                        var startOffset = range.startOffset,
                            xpathValues, relation, xpath;
                        var parent = nodes[i].element.parentNode;
                        // Split the text
                        var text1 = nodes[i].element.nodeValue.substr(0, startOffset);
                        var text2 = nodes[i].element.nodeValue.substr(startOffset);

                        //############################Check for existing SPANS(.highlight) #############################

                        if (nodes[i].element.previousSibling) {


                            //check for "highlight" in previous node
                            //var classHighlight =  nodes[i].element.previousSibling.getAttribute('class').split(" ");
                            var classHighlight = ((typeof nodes[i].element.previousSibling.getAttribute === 'function') && (nodes[i].element.previousSibling.getAttribute('class') !== null)) ?
                                nodes[i].element.previousSibling.getAttribute('class').split(" ") :
                                [null];
                            if (classHighlight[0] === 'highlight') {

                                xpathValues = nodes[i].element.previousSibling.getAttribute("data-offset");
                                xpathValues = base64.decode(xpathValues).split("|");
                                startOffset = range.startOffset + parseInt(xpathValues[1]);
                                endOffset = parseInt(xpathValues[1]) + nodes[i].element.length;
                                relation = xpathValues[2];
                                xpath = xpathValues[3];
                                //endOffset = range.endOffset + parseInt(startEndRange[1]);
                            } else {

                                startOffset = range.startOffset;
                                endOffset = nodes[i].element.length;
                                relation = "prevSib";
                                xpath = getXPath(nodes[i].element.previousSibling);
                                //endOffset = range.endOffset;
                            }
                        } else {

                            startOffset = range.startOffset;
                            endOffset = nodes[i].element.length;
                            relation = "parentNode";
                            xpath = getXPath(nodes[i].element.parentNode);
                            //endOffset = range.endOffset;
                        }
                        //###################################################################################
                        //wrap the slected text
                        //var wrapper = doc.createElement('span');
                        if (nodes[i].type === "svgText") {
                            var wrapper = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
                            wrapper.setAttribute('class', 'highlight ' + className);
                        } else {
                            var wrapper = doc.createElement(Hilite.hiliteTag);
                            wrapper.className = wrapper.className + "highlight " + className;
                        }



                        // set element as child of wrapper
                        wrapper.appendChild(doc.createTextNode(text2));

                        // Replace with existing text
                        parent.replaceChild(wrapper, nodes[i].element);

                        // Put text1 before wrapper
                        parent.insertBefore(doc.createTextNode(text1), wrapper);

                        //set class, id and data-offset
                        //wrapper.className = wrapper.className + "highlight " + className;

                        var dataOffset = startOffset + "|" + endOffset + "|" + relation + "|" + xpath; //EOE - End Of Element
                        wrapper.setAttribute('data-offset', base64.encode(dataOffset));
                        xpaths[nodes[i].position] = base64.encode(dataOffset);

                    } else if (i == (nodes.length - 1)) { //last node - stop at endOffset

                        var endOffset = range.endOffset,
                            relation, xpath;
                        var parent = nodes[i].element.parentNode;
                        // Split the text
                        var text1 = nodes[i].element.nodeValue.substr(0, endOffset);
                        var text2 = nodes[i].element.nodeValue.substr(endOffset);

                        //############################ Get offsets|relation|xpath for the last element #############################c
                        //############################Check for existing SPANS(.highlight) #############################

                        if (nodes[i].element.previousSibling) {

                            //check for "highlight" in previous node
                            var classHighlight = ((typeof nodes[i].element.previousSibling.getAttribute === 'function') && (nodes[i].element.previousSibling.getAttribute('class') !== null)) ?
                                nodes[i].element.previousSibling.getAttribute('class').split(" ") :
                                [null];


                            if (classHighlight[0] === 'highlight') {

                                xpathValues = nodes[i].element.previousSibling.getAttribute("data-offset");
                                xpathValues = base64.decode(xpathValues).split("|");
                                startOffset = parseInt(xpathValues[1]);
                                endOffset = range.endOffset + parseInt(xpathValues[1]);
                                relation = xpathValues[2];
                                xpath = xpathValues[3];
                                //endOffset = range.endOffset + parseInt(startEndRange[1]);
                            } else {

                                startOffset = 0;
                                endOffset = range.endOffset;
                                relation = "prevSib";
                                xpath = getXPath(nodes[i].element.previousSibling);
                                //endOffset = range.endOffset;
                            }
                        } else {
                            startOffset = 0;
                            endOffset = range.endOffset;
                            relation = "parentNode";
                            xpath = getXPath(nodes[i].element.parentNode);
                            //endOffset = range.endOffset;
                        }
                        //###################################################################################
                        //###################################################################################


                        //wrap the slected text
                        //var wrapper = doc.createElement('span');
                        if (nodes[i].type === "svgText") {
                            var wrapper = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
                            wrapper.setAttribute('class', 'highlight ' + className);
                        } else {
                            var wrapper = doc.createElement(Hilite.hiliteTag);
                            wrapper.className = wrapper.className + "highlight " + className;
                        }


                        // set text1 as child of wrapper
                        wrapper.appendChild(doc.createTextNode(text1));

                        // Replace with existing  text
                        parent.replaceChild(wrapper, nodes[i].element);

                        // append text2 to the wrapper
                        parent.insertBefore(doc.createTextNode(text2), wrapper.nextSibling);

                        //set class and data-offset attr
                        //wrapper.className = wrapper.className + "highlight " + className;

                        var dataOffset = startOffset + "|" + endOffset + "|" + relation + "|" + xpath;
                        wrapper.setAttribute('data-offset', base64.encode(dataOffset));
                        xpaths[nodes[i].position] = base64.encode(dataOffset);
                    } else { // else apply full highlight

                        var parent = nodes[i].element.parentNode,
                            relation, xpath;

                        //############################ Get relation|xpath for the inbetween elements #############################
                        //############################Check for existing SPANS(.highlight) #############################

                        if (nodes[i].element.previousSibling) {

                            //check for "highlight" in previous node
                            var classHighlight = ((typeof nodes[i].element.previousSibling.getAttribute === 'function') && (nodes[i].element.previousSibling.getAttribute('class') !== null)) ?
                                nodes[i].element.previousSibling.getAttribute('class').split(" ") :
                                [null];
                            if (classHighlight[0] === 'highlight') {

                                xpathValues = nodes[i].element.previousSibling.getAttribute("data-offset");
                                xpathValues = base64.decode(xpathValues).split("|");
                                startOffset = parseInt(xpathValues[1]);
                                endOffset = startOffset + nodes[i].element.length;
                                relation = xpathValues[2];
                                xpath = xpathValues[3];
                                //endOffset = range.endOffset + parseInt(startEndRange[1]);
                            } else {

                                startOffset = 0;
                                endOffset = nodes[i].element.length;
                                relation = "prevSib";
                                xpath = getXPath(nodes[i].element.previousSibling);
                                //endOffset = range.endOffset;
                            }
                        } else {

                            startOffset = 0;
                            endOffset = nodes[i].element.length;
                            relation = "parentNode";
                            xpath = getXPath(nodes[i].element.parentNode);
                            //endOffset = range.endOffset;
                        }
                        //###################################################################################

                        //wrap the slected text
                        //var wrapper = doc.createElement('span');
                        if (nodes[i].type === "svgText") {
                            var wrapper = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
                            wrapper.setAttribute('class', 'highlight ' + className);
                        } else {
                            var wrapper = doc.createElement(Hilite.hiliteTag);
                            wrapper.className = wrapper.className + "highlight " + className;
                        }


                        // set the wrapper as child (instead of the element)
                        parent.replaceChild(wrapper, nodes[i].element);

                        // set element as child of wrapper
                        wrapper.appendChild(nodes[i].element);

                        //set class and data-attr
                        //wrapper.className = wrapper.className + "highlight " + className;

                        var dataOffset = startOffset + "|" + endOffset + "|" + relation + "|" + xpath;
                        wrapper.setAttribute('data-offset', base64.encode(dataOffset)); //EOE - End Of Element
                        xpaths[nodes[i].position] = base64.encode(dataOffset);
                    }
                }
            }
        }

        /* ADD ID TO THE FIST TEXT NODE */
        //alert('asasasas: '+ className);
        doc.getElementsByClassName(className)[0].setAttribute('id', className.split(" ")[0]);
        //Hilite.log(nodes);
        //var serializedString = "";
        //for(var i=0; i < xpaths.length; i++)
        //serializedString += xpaths[i] + ",";
        //return serializedString;

        //return savedSelection;
    }

    function serializeHilite(hiliteData) {
        var sRelation, eRelation, sPath, ePath;

        if (hiliteData.startNode.previousSibling) {
            sRelation = "prevSib";
            sPath = getXPath(hiliteData.startNode.previousSibling)
        } else {
            sRelation = "parentNode";
            sPath = getXPath(hiliteData.startNode.parentNode)
        }
        if (hiliteData.endNode.previousSibling) {
            eRelation = "prevSib";
            ePath = getXPath(hiliteData.endNode.previousSibling)
        } else {
            eRelation = "parentNode";
            ePath = getXPath(hiliteData.endNode.parentNode)
        }

        return base64.encode(sPath + '::' + sRelation + ',' + ePath + '::' + eRelation + ',' + hiliteData.startOffset + ',' + hiliteData.endOffset);
    }

    return {
        applyHighlight: applyHighlight,
        serializeHilite: serializeHilite,
        getXPath: getXPath
    }

}


function ttsParserModule(window) {
    var window = window,
        document = window.document,

        //exceptions array previously had '.' in it, was removed to handle a CONTENT ISSUE where a '.' came alone and as a result two lines were 
        //getting higlighted together. Since the chances of a dot coming alone with spaces before and after it are slim, this change shouldnt create regression.
        exceptions = ['Mr.', 'Dr.', 'Mrs.', 'Ms.', 'Pres.', 'Gov.', 'U.S.', 'v.', 'al.'],
        inlineTags = ['SPAN', 'SMALL', 'EM', 'A', 'SUB', 'SUP', 'U', 'B', 'I', 'STRONG','FONT'],
        blockTags = ['P', 'DIV', 'ARTICLE', 'SECTION', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'ASIDE', 'NAV', 'HEADER', 'FOOTER', 'IMG', 'BR','LI'],

        currNode = null,
        endNode = null,
        startNode = null,
        serializedCurrNode = null,
        sentence = '',
        carryOver = false,
        /* carry over sentence from previous tect node */
        carryOverIndex = 0,
        textNodeLengthParsed = 0,
        words = [];

    /* public methods */
    function reset() {
        currNode = serializedCurrNode = null;
        carryOverIndex = textNodeLengthParsed = 0;
        sentence = '';
        carryOver = false;
        words = [];
    }

    function breakOnTag() {
        /*currNode.parantNode = inline tag OR currNode.nextSibling = inline tag
            Also, there is no text node available until the next Block node (e.g., <p><span></span></p>)*/
        if (inlineTags.indexOf(currNode.parentNode.tagName.toUpperCase()) > -1 || (currNode.nextSibling && inlineTags.indexOf(currNode.nextSibling.tagName.toUpperCase()) > -1)) {
            for (var node = currNode; node; node = getNextNode(node)) {
                if (node.nodeType === 3 && node.nodeValue.trim().length > 0 && node !== currNode) return false;
                else if (node.nodeType === 1 && blockTags.indexOf(node.tagName.toUpperCase()) > -1) break;
            }
        }
        return true;
    }

    function getSentence() {

        var ready = false,
            textNode,
            startOffset,
            tagBreak = false,
            terminate = false,
            trim = false;

        sentence = '';
        startNode = null;

        if (currNode !== null) {
            currNode = deserializeTextNode(serializedCurrNode);
        }

        // set  end node to null after speakText
        if (endNode !== null) {
            if (!carryOver) {
                currNode = getTextNode();
            }

            ready = true;
            sentence += currNode.textContent;
            startNode = currNode;
            startOffset = 0;
            textNodeLengthParsed = currNode.length + 1;
            if (currNode === endNode) terminate = true;
            if (carryOver) {
                carryOver = false;
            }
        }

        while (!ready) {

            sentence = sentence.trim(); /* remove any trailing spaces */

            if (!carryOver) {
                textNode = getTextNode();

                if (typeof textNode === "undefined") return undefined; /* end of audio */
                tagBreak = breakOnTag();

                if (startNode === null) {
                    startNode = textNode;
                    startOffset = 0
                }
                if (!/[\.!\?]/g.test(textNode.textContent)) {
                    sentence += textNode.textContent;
                    if (tagBreak) {
                        textNodeLengthParsed = currNode.length + 1;
                        break;
                    } else continue;

                } else {
                    words = textNode.nodeValue.trim().split(" ");
                    carryOverIndex = 0;
                    textNodeLengthParsed = 0;
                }

            } else {
                carryOver = false;
                startNode = currNode;
                startOffset = textNodeLengthParsed;
            }


            if (words && words instanceof Array) {
                for (var i = carryOverIndex, l = words.length, w = null; i < l; i++) {

                    textNodeLengthParsed += words[i].length + 1;
                    w = words[i].trim();

                    sentence += w + ' ';
                    if (/[\.!\?]/g.test(w.charAt(w.length - 1)) && exceptions.indexOf(w) === -1) {
                        ready = true;
                        if (l - i > 1) {
                            carryOver = true;
                            carryOverIndex = ++i
                        }
                        break;
                    }
                }

                if (tagBreak && !ready) { /* if sentences have exceptions words */
                    ready = true;
                    textNodeLengthParsed = currNode.length + 1;
                    break;
                }
            }
        }

        serializedCurrNode = serializeTextNode(currNode);
        return {
            sentence: sentence,
            hiliteData: ttsHilite.serializeHilite({
                startNode: startNode,
                endNode: currNode,
                startOffset: startOffset,
                endOffset: textNodeLengthParsed - 1
            }),
            terminate: terminate
        }
    }

    function getTextNode() {
        if (currNode === null) { // first time
            currNode = document.body;
        }
        while (1) {
            currNode = getNextNode(currNode);
            if (currNode === undefined) {
                currNode = document.body;
                return undefined;
            }
            if (currNode.nodeType === 3 && currNode.nodeValue.trim().length > 0) {
                return currNode;
            }
        }
    }

    function serializeTextNode(textNode) {
        var path, relation;

        if (textNode.previousSibling) {
            relation = "prevSib";
            path = ttsHilite.getXPath(textNode.previousSibling)
        } else {
            relation = "parentNode";
            path = ttsHilite.getXPath(textNode.parentNode)
        }

        //console.log(path + '::' + relation)
        return path + '::' + relation;
    }

    function deserializeTextNode(serializedString) {
        var path = serializedString.split('::')[0],
            relation = serializedString.split('::')[1],
            element;

        element = Hilite.getElementFromXPath(document, path);

        if (relation === "parentNode") {
            element = element.childNodes[0]
        } else {
            element = element.nextSibling
        }

        //console.log(element)
        return element;
    }

    function getNextNode(node) {
        if (node.firstChild)
            return node.firstChild;

        while (node) {
            if (node.nextSibling)
                return node.nextSibling;
            node = node.parentNode;
        }
    }

    function setCurrNode(node) {
        currNode = node;
        currNode = getTextNode();
        serializedCurrNode = serializeTextNode(currNode);
        carryOver = true;
        carryOverIndex = 0;
        textNodeLengthParsed = 0;
        words = currNode.nodeValue.trim().split(" ");
    }

    function setEndNode(node) {
        endNode = node;
    }

    function getEndNode(node) {
        return endNode;
    }

    function getCurrNode() {
        return currNode
    }

    function getStartNode() {
        return startNode
    }

    return {
        getNextNode: getNextNode,
        getSentence: getSentence,
        reset: reset,
        setCurrNode: setCurrNode,
        setEndNode: setEndNode,
        getEndNode: getEndNode,
        getCurrNode: getCurrNode,
        getStartNode: getStartNode
    }

}


function tts(window, readerWindow, serviceUrl, scrollDirection, serviceInfo, readText) {// Vivek : aaded new parameter serviceInfo to accomodate new api which required jwt token, and readText to read a particular text
    
    /* private variables */
    var window = window,
    document = window.document,
    ttsEndPt = serviceUrl.trim().length !== 0 ? serviceUrl : 'http://tts-api.com/tts.mp3?q={text}',
    currSent = null,
    nextSent = null,
    currHilite = null,
    nextHilte = null,
    currAudio = document.createElement('audio'),
    nextAudio = document.createElement('audio'),
    speakClass = 'tts-speak',
    currTerminate = false,
    nextTerminate = false,
    speak = false,
    click = false,
    active = false,
    uA = navigator.userAgent.toLowerCase(),
    mobile = (uA.indexOf("android") > -1 || uA.indexOf("ipad") > -1 || uA.indexOf("iphone") > -1) ? true : false,
    stopEvent = readerWindow.document.createEvent("HTMLEvents"),
    scrollDirection = scrollDirection === undefined ? 'vertical' : scrollDirection;
    playAfterLoad = false,
    ttsVoice = 'Emma';
    ttsSourceLanguage = "en";
    ttsPlaybackSpeed = 1;
    playOnlyAudioElementFlag = false;
    voiceListObject = {
        en: ["Emma", "Joanna", "Ivy", "Joey", "Kendra", "Justin", "Mathieu", "Kimberly", "Salli", "Brian", "Amy", "Russell", "Nicole", "Aditi", "Raveena", "Geraint"], es: ["Lupe", "Penelope", "Miguel", "Conchita", "Lucia", "Enrique", "Mia"], fr: ["Celine", "Léa", "Mathieu"], da: ["Naja", "Mads"], nl: ["Lotte", "Ruben"], de: ["Marlene", "Vicki", "Hans"], hi: ["Aditi"], it: ["Carla", "Bianca", "Giorgio"], ja: ["Mizuki", "Takumi"], ko: ["Seoyeon"], pl: ["Ewa", "Maja", "Jacek", "Jan"], pt: ["Camila", "Vitoria", "Ricardo", "Ines", "Cristiano"], sv: ["Astrid"], arb: ["Zeina"] };
    
    retryCount = 0;
    // $("#toggleTtsIcon .ttsModal").remove();
        
    stopEvent.initEvent("TtsStopped", true, true);

    /* private functions */
    function init() {
        ttsParser = ttsHilite = undefined;
        playAfterLoad = false;
        currAudio = document.createElement('audio');
        nextAudio = document.createElement('audio');

        /* Initialize ttsParser and ttsHilite modules */
        if (typeof ttsHilite === "undefined" || typeof ttsParser === "undefined") {
            ttsHilite = ttsHiliteModule(window);
            ttsParser = ttsParserModule(window);
        }

        if (!speak && !click) scrollHandlerHorizontal(); /* locating text not required for speak tts */

        //currAudio = document.createElement('audio');
        var currPassage = ttsParser.getSentence();
        if(!currPassage) {
            $("#ttsPause").css('display', 'none');
            $("#ttsPlay").css('display', 'inline-block');
            return;
        }
        currSent = currPassage.sentence;
        currHilite = currPassage.hiliteData;
        currTerminate = currPassage.terminate;

        if (!mobile) {
            sendRequest(currAudio, currSent, false);
            //nextAudio = document.createElement('audio');

            if (!currTerminate) {
                var nextPassage = ttsParser.getSentence();
                if (typeof nextPassage === "undefined") { /* end of audio */
                    currTerminate = true;
                } else {
                    nextSent = nextPassage.sentence;
                    nextHilite = nextPassage.hiliteData;
                    nextTerminate = nextPassage.terminate;
                    sendRequest(nextAudio, nextSent, false);
                }
            }
            nextAudio.addEventListener('canplay', canPlayHandler);
            // nextAudio.addEventListener('timeupdate', timeUpdateHandler);
            nextAudio.addEventListener('ended', endedHandler);
        }

        /* Event Listensers */
        // if (!mobile) document.addEventListener('mousedown', clickHandler);
        currAudio.addEventListener('canplay', canPlayHandler);
        // currAudio.addEventListener('timeupdate', timeUpdateHandler);
        currAudio.addEventListener('ended', endedHandler);
        currAudio.addEventListener('error', errorHandler);//Added to handel server errors -- Minati
        //document.addEventListener('touchend', clickHandler); parent.document.getElementById(dlayer).addEventListener('scroll', scrollHandler);
    }

    function updateState(prevFlag) { // prevFlag :: differentiation b/w next and prev
        if (prevFlag) {
            alert('prev coming soon...');
        }
        else {
            if (mobile) {
                var currPassage = ttsParser.getSentence();
                if (typeof currPassage === "undefined") { /* end of audio */
                    stop(true);
                    return;
                } else {
                    currSent = currPassage.sentence;
                    currHilite = currPassage.hiliteData;
                    currTerminate = currPassage.terminate;
                }
            } else {
                if (!playAfterLoad) {
                    var tempAudio = currAudio;
                    currAudio = nextAudio;
                    nextAudio = tempAudio;
                    playAfterLoad = true;

                    currSent = nextSent;
                    currHilite = nextHilite;
                    currTerminate = nextTerminate;
                    if (!currTerminate) {
                        var nextPassage = ttsParser.getSentence();
                        if (typeof nextPassage === "undefined") { /* end of audio */
                            currTerminate = true;
                        } else {
                            nextSent = nextPassage.sentence;
                            nextHilite = nextPassage.hiliteData;
                            nextTerminate = nextPassage.terminate;
                            sendRequest(nextAudio, nextSent, false);
                        }
                    }

                    if (active) play();
                } else {
                    retryCount--;
                    if (retryCount > 0)
                        setTimeout(function () { updateState() }, 50);
                    return;
                }
            }
        }
    }

    function applyHilite(hiliteData) {

        ttsHilite.applyHighlight(document, hiliteData, 'tts-highlight');
        var hilight = document.getElementsByClassName('tts-highlight');
        /*
            for(var i = 0; i < hilight.length; i++){
                hilight[i].style.backgroundColor = '#F5EBDB';
                hilight[i].style.color = 'rgb(82, 2, 2)';
                hilight[i].style.boxShadow = '#D6C19F 0px 1px 2px 1px';
                hilight[i].style.zIndex = '10';
            } */

        /* scroll the TTS document */
        if (scrollDirection === 'horizontal') {
            var leftOffset = document.querySelector('.tts-highlight').getBoundingClientRect().left;
            var columnWidth;
            if (document.getElementsByTagName('html')[0].style.webkitColumnWidth !== undefined) {
                columnWidth = parseInt(document.getElementsByTagName('html')[0].style.webkitColumnWidth);
            } else if (document.getElementsByTagName('html')[0].style.MozColumnWidth !== undefined) {
                columnWidth = parseInt(document.getElementsByTagName('html')[0].style.MozColumnWidth);
            }

            var columnGap;
            if (document.getElementsByTagName('html')[0].style.webkitColumnGap !== undefined) {
                columnGap = parseInt(document.getElementsByTagName('html')[0].style.webkitColumnGap);
            } else if (document.getElementsByTagName('html')[0].style.MozColumnGap !== undefined) {
                columnGap = parseInt(document.getElementsByTagName('html')[0].style.MozColumnGap);
            }

            var columnCount = parseInt(document.getElementsByTagName('html')[0].style.webkitColumnCount);
            columnCount = isNaN(columnCount) ? 1 : columnCount;
            if(columnCount === 2 && isNaN(columnWidth)) {
                columnWidth = parseInt(document.getElementsByTagName('html')[0].style.width);
                columnWidth = (columnWidth / 2) - (columnGap / 2);
            }
            var screenWidth = (columnWidth + columnGap) * columnCount;
            var shiftScreens = Math.floor(leftOffset / screenWidth);

            //window.scrollTo(window.scrollX + (shiftScreens * screenWidth), 0);
            for (var i = 0, count = Math.abs(shiftScreens); i < count; i++) {
                if (shiftScreens > 0) $(window.parent.document.querySelector('#next')).trigger('click');
                else $(window.parent.document.querySelector('#prev')).trigger('click');
            }

        } else if (scrollDirection === 'vertical') {
            var topOffset = document.querySelector('.tts-highlight').parentNode.getBoundingClientRect().top,
                scrollTop = $(window).scrollTop() + topOffset - 100;
            $("#scrolled-content-frame").animate({
              scrollTop: scrollTop
            });
            // window.scrollTo(0, scrollTop);
        }
    }

    function removeHilite() {

        var highlighNodes = document.getElementsByClassName('tts-highlight'),
            parent,
            childNodes,
            text;

        while (highlighNodes.length > 0) {

            parent = highlighNodes[0].parentNode,
                childNodes = parent.childNodes,
                text = highlighNodes[0].removeChild(highlighNodes[0].firstChild);
            parent.insertBefore(text, highlighNodes[0]);
            parent.removeChild(highlighNodes[0]);
            /* To find out the childNode index of the highlighted text after normalization - Eg: <parent> <tN> <p> </p> <tN> <hl> <tN> </parent>*/
            for (var i = 0, l = childNodes.length; i < l; i++) {
                if (childNodes[i] === text) break
            }
            while (i >= 0 && childNodes[i].previousSibling !== null && childNodes[i].previousSibling.nodeType === 3) {
                i--
            }
            /* end of logic */

            (navigator.userAgent.indexOf('rv:11')) === -1 ? parent.normalize() : Hilite.normalize(parent); /* parent.normalize(); */
        }
    }

    function reset() {
        removeHilite();
        ttsParser.reset();
    }

    function setVoice(voice) {
        ttsVoice = voice;
        currAudio.pause();
        if($("#ttsPlay").is(":visible")) {
            playOnlyAudioElementFlag = true;
        }
        sendRequest(currAudio, currSent, true);
        // currAudio.play();
        // updateState();
    }

    function setScrollDirection(direction) {
        scrollDirection = direction;
    }

    function setPlaybackSpeed(speed) {
        currAudio.playbackRate = speed;
        nextAudio.playbackRate = speed;
        ttsPlaybackSpeed = speed;
    }

    function getSourceLanguage(text ,cb) {
        if(!text) {
            cb({ SourceLanguageCode: "en" });
        }
        else {
            var inputText = text;
            var sourceLanguageCode = 'auto';
            var targetLanguageCode = "en";

            var params = {
                Text: inputText,
                SourceLanguageCode: sourceLanguageCode,
                TargetLanguageCode: targetLanguageCode
            };

            AWS.config.region = 'us-east-1'; // Region
            var that = this;
            //AWS.config.credentials = new AWS.Credentials("access key", "secret key");
            AWS.config.credentials = new AWS.Credentials("AKIAWTAXOEBHTZXLS6M6", "hB16iVgHZ/J+UY6zi1E8TClhb2yKbE2g2AUpIEJ2");

            this.translate = new AWS.Translate({ region: AWS.config.region });
            this.polly = new AWS.Polly();

            this.translate.translateText(params, function (err, data) {
                if (err) {
                    console.log(err, err.stack);
                    //alert("Error calling " + err.message);
                    //return;
                    cb(null);
                }
                if (data) {
                    cb(data);
                }
            });
        }
        
    }

    function sendRequest(audioElement, text, currAudioPlayFlag) {
        var readingText = readText ? readText : text;
        if (!readingText) {
            var xhttp = new XMLHttpRequest();
            xhttp.onreadystatechange = function () {
                if (this.readyState == 4 && this.status == 200) {
                    var audioUrl = JSON.parse(this.responseText).data.url
                    playAfterLoad = false;
                    audioElement.setAttribute('src', audioUrl);
                    audioElement.playbackRate = ttsPlaybackSpeed;
                    if (currAudioPlayFlag) {
                        if ($("#ttsPause").is(":visible")) {
                            audioElement.play();
                        } else {
                            audioElement.pause();
                        }
                        sendRequest(nextAudio, nextSent, false);
                        playAfterLoad = true;
                    }
                }
            };
            var audioUrl = ttsEndPt.replace('{text}', text).replace('{voice}', ttsVoice);
            xhttp.open("GET", audioUrl, true);
            xhttp.setRequestHeader("Content-type", "application/json");
            xhttp.setRequestHeader("X-Jwt-Token", serviceInfo.jwtToken);
            xhttp.send();
        }
        else {
            getSourceLanguage(readingText, function (res) {
                if (!res) {
                    res = {
                        SourceLanguageCode: ttsSourceLanguage
                    }
                }
                if (ttsSourceLanguage != res.SourceLanguageCode) {
                    ttsVoice = voiceListObject[res.SourceLanguageCode][0];
                    ttsSourceLanguage = res.SourceLanguageCode;
                    if (document.getElementById("voiceListContainer")) {
                        /* document.getElementById("voiceListContainer").innerHTML = voiceListObject[res.SourceLanguageCode].map((data) => {
                            return `<p class='${data === ttsVoice ? 'active' : ''}'>${data}</p>`
                        }).join(""); */

                        document.getElementById("voiceListContainer").innerHTML = voiceListObject[res.SourceLanguageCode].map(function (el) { 
                            return "<p class='${data === ttsVoice ? 'active' : ''}'>${data}</p>"
                        }).join("");
                        // var vector = labelsPrint.map(function (el) { return el.id; });

                        let childTags = document.getElementById("voiceListContainer").querySelectorAll("p");
                        for (let index = 0; index < childTags.length; index++) {
                            childTags[index].classList.remove("active");
                            if (childTags[index].innerHTML === ttsVoice) {
                                childTags[index].classList.add("active");
                            }
                        }
                    }
                    
                }
                
                text = readText ? encodeURIComponent(readText) : encodeURIComponent(text);  // Fixing  -> TTS skip the highlighted text after semicolon
                //console.log('Sending requesting'); console.log(ttsEndPt.replace('{text}', text));

                // Vivek: changes to accomodate new api

                var xhttp = new XMLHttpRequest();
                xhttp.onreadystatechange = function () {
                    if (this.readyState == 4 && this.status == 200) {
                        var audioUrl = JSON.parse(this.responseText).data.url
                        playAfterLoad = false;
                        audioElement.setAttribute('src', audioUrl);
                        audioElement.playbackRate = ttsPlaybackSpeed;
                        if (currAudioPlayFlag) {
                            if ($("#ttsPause").is(":visible")) {
                                audioElement.play();
                            } else {
                                audioElement.pause();
                            }
                            sendRequest(nextAudio, nextSent, false);
                            playAfterLoad = true;
                        }
                    }
                };
                var audioUrl = ttsEndPt.replace('{text}', text).replace('{voice}', ttsVoice);
                xhttp.open("GET", audioUrl, true);
                xhttp.setRequestHeader("Content-type", "application/json");
                xhttp.setRequestHeader("X-Jwt-Token", serviceInfo.jwtToken);
                xhttp.send();



                //audioElement.setAttribute('src', ttsEndPt.replace('{text}', text));
                if (mobile) {
                    showMsg('buffering');
                } else {
                    if (currAudio.readyState !== 4) showMsg('buffering');
                }
            });
        }
    }

    /* public functions */
    function play() {

        if(playOnlyAudioElementFlag) {
            currAudio.play();
            playOnlyAudioElementFlag = false;
            return;
        }

        if (!active) {
            init()
        } /* if first play initialize*/
        if (currSent.length === 0) {
            stop(true);
            return
        }
        if (currAudio.paused && currAudio.currentTime > 0 && currAudio.currentTime < currAudio.duration) {
            currAudio.play();
            return
        }

        applyHilite(currHilite);

        $iframe=$("#epubContentIframe").contents().find(".tts-highlight")[0];
        if($iframe) {
            let windowHeight = $("#epubContentIframe").contents().find("html").height();
        }

        if (mobile) sendRequest(currAudio, currSent, false);
        currAudio.play();
        active = true;
        //sendRequest(nextAudio, nextSent);
    }

    function pause() {
        currAudio.pause();
        hideMsg('buffering', false);
        //console.log('Paused');
    }

    function stop(endOfPage) {
        if(!active) {
            return;
        }
        if(currAudio.duration) {
            currAudio.pause();
        }

        if (ttsParser.getEndNode !== null) {
            removeHilite();
            clearSpeak();
        }
        reset();
        //init();
        active = false;
        // document.getElementById("ttsPause").style.display = 'none';
        // document.getElementById("ttsPlay").style.display = 'inline-block';
        $("#ttsPause").css('display', 'none');
        $("#ttsPlay").css('display', 'inline-block');
        if (endOfPage){
            setTimeout(function() {
                if(currAudio.paused) {
                    $("#toggleTtsIcon .ttsModal").hide();
                }
             }, 5000);  
            readerWindow.document.dispatchEvent(stopEvent);
        } 
            
    }

    /* Event handlers */
    function endedHandler() { 
        removeHilite();
        if (currTerminate) {
            stop(true);
            return
        } /* stop caching audio */
        if (currTerminate && speak) {
            clearSpeak();
            stop();
            return
        } /* stop speak audio*/
        retryCount = 1000;
        updateState();
        //play();
        // if (active) play(); /* active check added to avoid audio replay from the beginning */
    }

    function timeUpdateHandler(e) {
        if (currAudio.currentTime >= currAudio.duration) {
            endedHandler();
        }
    }

    function clickHandler(e) {
        if (speak) {
            e.preventDefault();
            return;
        }
        if (active) {
            if (getPlayState()) {
                e.preventDefault();
                pause();
                reset();
                ttsParser.setCurrNode(e.target);
                click = true;
                init();
                click = false;
                play();
            }
            else
                currAudio.pause();
        }
    }

    function canPlayHandler(e) {
        hideMsg('buffering', true);
    }

    function errorHandler(e) {
        showMsg('no_network');
    }

    function scrollHandler() {
        //console.log(active);
        //if(active) return;

        var findTextNode = null,
            prevLikelyElement = null,
            likelyElement = document.body;
        /*cuttOff = $(parent.document.getElementById(dlayer)).scrollTop();*/
        /* redefine dlayer */

        while (prevLikelyElement !== likelyElement) {
            prevLikelyElement = likelyElement;
            for (var i = 0, childNodes = likelyElement.childNodes, l = childNodes.length; i < l; i++) {
                if (childNodes[i].nodeType === 1) {
                    //if(childNodes[i].getBoundingClientRect().top  >= 0) break;
                    if (childNodes[i].getBoundingClientRect().top >= cuttOff) break;
                    likelyElement = childNodes[i];
                }
            }
        }

        for (var node = likelyElement; node; node = ttsParser.getNextNode(node)) {
            if (node.nodeType === 3 && node.nodeValue.trim().length > 0 && node.parentNode.getBoundingClientRect().top >= 0) {
                findTextNode = node;
                break;
            }
        }

        if (findTextNode === null) return;
        reset();
        ttsParser.setCurrNode(findTextNode.parentNode);
        //init();
    }

    function scrollHandlerHorizontal() {

        var findTextNode = null,
            prevLikelyElement = null,
            likelyElement = document.body;
        cuttOff = window.scrollX;

        for (var node = document.body; node; node = ttsParser.getNextNode(node)) {
            if (node.nodeType === 1 && node.getBoundingClientRect().left > 0 && node.getBoundingClientRect().left < parseInt(document.getElementsByTagName('html')[0].style.webkitColumnWidth) * 2 + 60) {
                likelyElement = node;
                break;
            }
        }

        for (var node = likelyElement; node; node = ttsParser.getNextNode(node)) {
            if (node.nodeType === 3 && node.nodeValue.trim().length > 0) {
                findTextNode = node;
                break;
            }
        }

        if (findTextNode === null) return;
        reset();
        ttsParser.setCurrNode(findTextNode.parentNode);
    }

    function showMsg(flag) {
        switch (flag) {
            case 'buffering':
                //console.log('buffering...');
                $("#ttsPause").css('display', 'none');
                $("#buffer-loader").css('display', 'inline-block');
                // $(readerWindow.document.body).append('<div data-phase="2" class="msg_buf offline-ui offline-ui-down offline-ui-connecting offline-ui-waiting" style="position:fixed;top:55px"><div class="offline-ui-content" data-retry-in="5 sec" data-retry-in-abbr="5 s">Buffering ...</div><a class="offline-ui-retry"></a></div>');
                break;
            case 'no_network':
                //console.log('error!');
                $(readerWindow.document.body).append('<div data-phase="1" class="msg_err offline-ui offline-ui-down offline-ui-down-5s" style="position:fixed;top:55px"><div class="offline-ui-content" data-retry-in data-retry-in-abbr>TTS server error. </div><a class="offline-ui-retry"></a></div>');
                errTimer = setTimeout(function () {
                    hideMsg('no_network', true);
                    hideMsg('buffering', true);
                }, 5000);
                break;
        }
    }

    function hideMsg(flag, loadingFlag) {
        switch (flag) {
            case 'buffering':
                //console.log('finished buffering...');
                if(loadingFlag && $("#buffer-loader").is(":visible")) {
                    $("#ttsPause").css('display', 'inline-block');
                    $("#buffer-loader").css('display', 'none');
                }
                $(readerWindow.document.body).find('.msg_buf').remove();
                break;
            case 'no_network':
                //console.log('removing error!');
                clearTimeout(errTimer);
                $(readerWindow.document.body).find('.msg_err').remove();
                break;
        }
    }

    function getPlayState() {
        if (currAudio === null) return false;
        return !currAudio.paused
    }

    function setVolume(vol) {
        currAudio.volume = nextAudio.volume = vol;
    }

    function clear() {
        ttsParser = ttsHilite = undefined;
        hideMsg('buffering', true);
    }

    function speakText() {

        /* In case speak is called before TTS */
        if (typeof ttsHilite === "undefined" || typeof ttsParser === "undefined") {
            ttsHilite = ttsHiliteModule(window);
            ttsParser = ttsParserModule(window);
        }
        speak = true;
        var speakNodes = document.getElementsByClassName(speakClass);
        angular.element(speakNodes).find('.nt-icon').remove();
        reset();
        ttsParser.setCurrNode(speakNodes[0]);
        ttsParser.setEndNode(speakNodes[speakNodes.length - 1].firstChild);
        //init();
        play();
    }

    var clearSpeak = function () {
        currTerminate = false;
        speak = false;
        ttsParser.setEndNode(null);
        Hilite.removeHilite(document.body, speakClass);
        //readerWindow.doGetBackHL();
    };

    document.addEventListener('DOMContentLoaded', function () {
        //init()
    });

    function initpublic() { }
    /* exposing public functions */

    function playNext(){
        currAudio.pause();
        endedHandler();
    }

    function playPrevious(){
        currAudio.pause();
        removeHilite();
        if (currTerminate) {
            stop(true);
            return
        } /* stop caching audio */
        if (currTerminate && speak) {
            clearSpeak();
            stop();
            return
        } /* stop speak audio*/
        retryCount = 1000;
        updateState(true);
    }

    function getCurrentAudio() {
        return currAudio;
    }
    
    return {
        init: initpublic,
        play: play,
        pause: pause,
        stop: stop,
        speakText: speakText,
        getPlayState: getPlayState,
        setVolume: setVolume,
        clear: clear,
        reset: reset,
        clickHandler: clickHandler,
        playNext: playNext,
        playPrevious: playPrevious,
        setVoice: setVoice,
        setPlaybackSpeed: setPlaybackSpeed,
        getCurrentAudio: getCurrentAudio,
        setScrollDirection: setScrollDirection
    }
}

function hideTTSPopup() {
    $("#toggleTtsIcon .ttsModal").hide();
}

/* Depedencies
	1. stop(): TtsStopped event triggered on <header> tag of the readerWindow
*/