// Flag that this javascript module was found.
window.lookup = true;

//////////////////////////////////////////////////////////////////////////////
// Scan document text contents and tag for scripture reference lookup.
// contact@SpiritAndTruth.org
//////////////////////////////////////////////////////////////////////////////
// List of remaining features to complete (‘+' means completed):
//
// - Build grease monkey script to inject lookup into existing web page
//   (and compile to produce firefox extension 
//   http://arantius.com/misc/greasemonkey/script-compiler)
// 
// - Fall back approach: 1) attempt an HttpRequest. 2) If it fails, then
//   fall back to iFrame for popup.
//   On mouse-over:
//       - create, position, size it
//       - load it with the full chapter URL (to the exact verse).
//   Won't be as compact or pretty, but should do the basic job and has
//   two advantages: cross-domain compatibility, works without server
//   (no HttpRequest).
//
// - Look at injecting a script into each bible chapter page which provides 
//   additional control (select translation, paragraph vs. versified format,
//   size, etc.). Can this be injected from the parent in the external domain
//   (probably not)? Otherwise modify an existing <script>...</script> entry
//   (x.js or xalone.js) in the chapter files to add these capabilities.
//
// - Use an iFrame when lookup.js (and therefore Bibles) are local. This
//   should allow popup to operate without internet access.
//   (See pp. 476-481 of "The Javascript Anthology")
//   - Used nested iframes? 
//   http://pipwerks.com/2008/11/30/iframes-and-cross-domain-security-part-2/
//
//   1. other.com/file.html (which includes lookup.js)
//       2. hidden iFrame with S&T bible chapter in it (querystring=address?)
//           3. hidden iFrame loads other.com/file.html?<encoded bible chapter>
//              (Could even be parsed subset so smaller).
//              1. Sees query string and loads popup from it.
//
// - Selecting translation when served from external domain doesn't stick.
//   Cookie is only valid at spiritandtruth.org.) Need to allow translation
//   selection from within the context of the external domain so cookie is
//   relative to that domain.
//     - Place code that creates and responds to translation selection into 
//       a separate javascript and include that at the end of the document?
//
//  - After identification of book, allow 'ch.' 'chapter(s)' 'chapters'
//    followed by digits.
//  - After confident identification of chapter (e.g., M:N), allow 'verse(s)'
//    'vv.' and digits.
//
// - Support for using tool from other domains 
//    - Work around cross-domain HttpRequest.
//    - Example: http://www.mabanachapel.org/lookup_example.htm
//
// - Support for commentary links (+ for Daniel and Revelation)?
// - Spanish bible needs character set specification to view in pop-up.
// - Roman numeral support
//

var TIP_ID = "_tip_id_";
var LOOKUP = "_lookup_";
var SPIRIT_WEB = "http://www.spiritandtruth.org"
var LOOKUP_EXP = /(.*\/)lookup.js/
var LOOKUP_PATH;
var LOOKUP_PATH_LOCAL;
var LOOKUP_PATH_REMOTE;
var BOOK_AND_VERSES_PATTERN = 
//  Force bookname to begin with uppercase.
    /((1|2|3|I|II|III)\s*)?([A-Z][\w\.]+\s+)([1-9]\d*)(([a-d]|ff.?)?([:\.,;\-\–]?\s*([1-9]\d*([a-d]|ff.?)?)(?!\s+[A-Z]))*)(?=(\W|$))/gm;
//  Allow verse pattern to have nothing following (be at end of line).
//  /((1|2|3|I|II|III)\s*)?([\w\.]+\s+)([1-9]\d*)(([a-d]|ff.?)?([:\.,;-]?\s*([1-9]\d*([a-d]|ff.?)?)(?!\s+[A-Z]))*)(?=(\W|$))/gm;
//  Allow bookname and opening chapter to be tested on their own.
//  /((1|2|3|I|II|III)\s*)?([\w\.]+\s+)([1-9]\d*)(([a-d]|ff.?)?([:\.,;-]?\s*([1-9]\d*([a-d]|ff.?)?)(?!\s+[A-Z]))*)(?=\W)/gm;
//   12                    3           4         5
//
//  Allow both ":" and "." as chapter.verse separator.
//  /((1|2|3|I|II|III)\s*)?(([\w\.]+\s+)([1-9]\d*([a-d]|ff.?)?([:\.,;-]?\s*([1-9]\d*([a-d]|ff.?)?)(?!\s+[A-Z]))*)(?=\W))/gm;
//
//  Allow "<bookname><space><chapter><space> followed by <booknumber><space><bookname><space><chapter>".
//  Make sure booknumber is parsed as part of second book.
//  /((1|2|3|I|II|III)\s*)?(([\w\.]+\s+)([1-9]\d*([a-d]|ff.?)?([:,;-]?\s*([1-9]\d*([a-d]|ff.?)?)(?!\s+[A-Z]))*)(?=\W))/gm;
//
//  Prevent inclusion of book number preceding next address as a verse or chapter in this book's addresses.
//  /((1|2|3|I|II|III)\s*)?(([\w\.]+\s+)([1-9]\d*([a-d]|ff.?)?([:,;-]?(\s+)?([1-9]\d*([a-d]|ff.?)?)(?!\s))+)(?=\W))/gim;
//
//   Allow ff. suffix after digits.
//  /((1|2|3|I|II|III)\s*)?(([\w\.]+\s+)([1-9]\d*([a-d]|ff.?)?([:,;-]?(\s+)?[1-9]\d*([a-d]|ff.?)?)+)(?=\W))/gim;
//
//  Allow a-d suffix on digits.
//  /((1|2|3|I|II|III)\s*)?(([\w\.]+\s+)([1-9]\d*[a-d]?([:,;-]?(\s+)?[1-9]\d*[a-d]?)+)(?=\W))/gim;
//
//  /((1|2|3|I|II|III)\s*)?(([\w\.]+\s+)([1-9]\d*([:,;-]?(\s+)?[1-9]\d*)+)(?=\W))/gim;
//
//  /((1|2|3|I|II|III)\s*)?((\S+\s+)([0-9]\d*([:,;-]?(\s+)?[0-9]\d*)+)(?=\W))/gim;
// 
//  /((1|2|3|I|II|III)\s*)?((\S+\s+)(\d+([:,;-]?(\s+)?\d+)+)(?=\W))/gim;

var PATTERN_VERSES = 
//   
//  Allow . as chapter.verse separator.
//   <---------------- X:Y-Z --------------------------------------------> <----------- X:Y-------- --------------> <----------------------- X-Y ------------------------> <--------- X --------->
    /(([1-9]\d*)[:\.]([1-9]\d*)([a-d]|ff.?)?[\-\–]([1-9]\d*)([a-d]|ff.?)?)|(([1-9]\d*)[:\.]([1-9]\d*)([a-d]|ff.?)?)|(([1-9]\d*)([a-d]|ff.?)?[\-\–]([1-9]\d*)([a-d]|ff.?)?)|([1-9]\d*)([a-d]|ff.?)?/gim
//   12              3         4             5         6              78          9         A              BC         D             E         F              G         H
//
//  Allow ff. suffix after digits.
//   <---------------- X:Y-Z ---------------------------------> <----------- X:Y -----------------> <----------------------- X-Y -----------------> <--------- X ------->
//  /(([1-9]\d*):([1-9]\d*)([a-d]|ff.?)?-([1-9]\d*)([a-d]|ff.?)?)|(([1-9]\d*):([1-9]\d*)([a-d]|ff.?)?)|(([1-9]\d*)([a-d]|ff.?)?-([1-9]\d*)([a-d]|ff.?)?)|([1-9]\d*)([a-d]|ff.?)?/gim
//   12          3         4             5         6              78          9         A              BC         D             E         F              G         H
//
//  Allow a-d suffix on digits.
//   <---- X:Y-Z ---------------------------------> <----- X:Y -----------------> <----------- X-Y -----------------> <---- X ------->
//  /(([1-9]\d*):([1-9]\d*)[a-d]?-([1-9]\d*)[a-d]?)|(([1-9]\d*):([1-9]\d*)[a-d]?)|(([1-9]\d*)[a-d]?-([1-9]\d*)[a-d]?)|([1-9]\d*)[a-d]?/gim
//
//  /(([1-9]\d*):([1-9]\d*)-([1-9]\d*))|(([1-9]\d*):([1-9]\d*))|(([1-9]\d*)-([1-9]\d*))|([1-9]\d*)/gim
//

function defined(element) {
    return (typeof element != "undefined");
}

// Convert path to name.
function pathToName(path) {
    return path.replace(/.*?([^\/]+).js$/g, '$1');
}

// Load javascript file on-demand.
function loadJs(domain, path) {
    var name = pathToName(path);
    //alert( 'name ' + name );
    if (eval("self." + name)) {
        return;
    }
    var head = document.getElementsByTagName("head")[0];
    js = document.createElement('script');
    js.id = name;
    js.type = 'text/javascript';
    js.src = domain + path;
    //alert( 'loading ' + js.src );
    head.appendChild(js);
}

// Unload javascript file.
function unloadJs(path) {
    var name = pathToName(path);
    var js = document.getElementById(name);
    if (defined(js)) {
        js.parentNode.removeChild(js);
        delete js;
    }
}

// Register onload and onunload actions.
addLoadListener(loadLookup);
addUnloadListener(unloadLookup);

function unloadLookup() {
    // Turn off any remaining tooltip.
    hideTip();
}

function loadLookup() {

    // Find paths to lookup location (on web and/or local file system).
    getLookupPaths();

    // Find out if we have access for lookup on the local file system?
    if (defined(LOOKUP_PATH_LOCAL)) {
        window.onweb = false;
        loadJs(LOOKUP_PATH_LOCAL, 'onweb.js');
    }

    // Wait to allow onweb.js time to load, if accessible.
    setTimeout("checkWeb()", 100);
}

function checkWeb() {

    // Erase local path if we don't have access to it.
    if (defined(LOOKUP_PATH_LOCAL) && !window.onweb) {
        LOOKUP_PATH_LOCAL = undefined;
    }
    
    // Check to see if we have access to the remote lookup.
    window.onweb = false;
    loadJs(SPIRIT_WEB, '/lookup/onweb.js');

    // Wait to allow onweb.js time to load, if accessible.
    setTimeout("loadMore()", 1000);
}

// After we determine whether we have internet access, 
// load other javascript files.
function loadMore() {
    
    // Assume remote lookup path except of local path is specified
    // and accessible.
    //alert( 'local ' + LOOKUP_PATH_LOCAL );
    //alert( 'remote ' + LOOKUP_PATH_REMOTE );
    LOOKUP_PATH = LOOKUP_PATH_REMOTE;
    if (defined(LOOKUP_PATH_LOCAL)) {
        LOOKUP_PATH = LOOKUP_PATH_LOCAL;
    }
    //alert( 'lookup ' + LOOKUP_PATH );


    // Load related javascript files from website or local file system.
    loadJs(LOOKUP_PATH, 'cookie.js');
    loadJs(LOOKUP_PATH, 'address.js');
    setTimeout("lookupLoaded()", 100);
}

// Scan and mark verses.
function lookupLoaded() {

    // Wait until associated javascript has been loaded.
    if (!defined(window.Address)) {
        setTimeout("lookupLoaded()", 250);
        return;
    }
    //alert( 'address loaded' );

    // Lookup default translation.
    window.translation = 'NASB';
    if (defined(window.readCookie)) {
        var translation = readCookie( 'translation' );
        //alert( 'translation ' + translation );
        if (translation) {
            translation = translation.toLowerCase();
            if (translation == "asv bible") {
                window.translation = 'ASV';
            } else if (translation == "kj2000 bible") {
                window.translation = 'KJ2K';
            } else if (translation == "kjv bible") {
                window.translation = 'KJV';
            } else if (translation == "kjv-strongs bible") {
                window.translation = 'KJVS';
            } else if (translation == "litv bible") {
                window.translation = 'LITV';
            } else if (translation == "mkjv bible") {
                window.translation = 'MKJV';
            } else if (translation == "nasb-strongs bible") {
                window.translation = 'NASBS';
            } else if (translation == "net bible") {
                window.translation = 'NET';
            } else if (translation == "nkjv bible") {
                window.translation = 'NKJV';
            } else if (translation == "spanish bible") {
                window.translation = 'SPARV';
            } else if (translation == "web bible") {
                window.translation = 'WEB';
            } else if (translation == "ylt bible") {
                window.translation = 'YLT';
            } else {
                window.translation = 'NASB';
            }
        }
    }

    // Replace explicit verse spans with corresponding verse links first.
    // (The newly created links are ignored by our subsequent scanning.)
    var verses = getExplicitVerses();
    for (var i = 0 ; i < verses.length ; i++){
        var verse = verses[i];
        var verseAddress = verse.getAttribute("verse");
        if (!defined(verseAddress) || !verseAddress) {
            verseAddress = verse.getAttribute("title");
        }
        var show = verse.innerHTML;
        var linkNode = verseLink(verseAddress, show);
        if (defined(linkNode)) {
            verse.parentNode.replaceChild(linkNode, verse);
        }
    }

    // Unless disabled, automatically scan text for verses and mark them.
    if (!defined(window.LOOKUP_EXPLICIT_ONLY) || !window.LOOKUP_EXPLICIT_ONLY) {
        scanForVerses(document.body);
    }

}

// Find path to lookup location (on web and local file).
function getLookupPaths() {
    var netPath;
    LOOKUP_PATH_REMOTE = SPIRIT_WEB + "/lookup/";
    var scripts = document.getElementsByTagName("script");
    for (var i = 0; i < scripts.length; i++) {
        var src = scripts[i].getAttribute("src");
        if (src) {
            src = src.toLowerCase();

            // If found script pointing to "lookup.js"
            if (-1 != src.search(LOOKUP_EXP)) {
                var path = src.replace(LOOKUP_EXP, "$1");

                // If path is over the internet.
                if (-1 != path.search(/spiritandtruth.org/)) {
                    
                    // Save remote path. Use same domain form we were invoked
                    // with (may lack 'www' prefix).
                    LOOKUP_PATH_REMOTE = path;
                    var href = document.location.href.toLowerCase();
                    if (-1 != href.search(/\/spiritandtruth.org\//)) {
                        LOOKUP_PATH_REMOTE = LOOKUP_PATH_REMOTE.replace(
                            /\/www.spiritandtruth.org\//, 
                            "/spiritandtruth.org/");
                    }

                // Else path is on local file system.
                } else {
                    LOOKUP_PATH_LOCAL = path;
                }
            }
        }
    }
}

// Get all the explicitly-specified verses.
function getExplicitVerses() {
    var verses = new Array();
    // Find <span verse="address">.
    var spans = document.getElementsByTagName("span");
    for (var i = 0; i < spans.length; i++) {
        var verse = spans[i].getAttribute("verse");
        if (defined(verse)) {
            verses[verses.length] = spans[i];
        }
    }
    // Find <cite class="bibleref">.
    // TEMPORARILY DISABLED UNTIL LBS LINKS ARE EXPUNGED FROM WEBSITE
    if (0) {
        var citations = document.getElementsByTagName("cite");
        for (var i = 0; i < citations.length; i++) {
            var citation = citations[i];
            var classString = getClass(citation);
            if (classString && ("bibleref" == classString.toLowerCase())) {
                var titleString = citation.getAttribute("title");
                if (defined(titleString)) {
                    verses[verses.length] = citation;
                }
            }
        }
    }
    return verses;
}

// Open a URL and return as a request.
var request;
function openUrl(url) {

    // Delete any anchor or arguments.
    url = url.replace(/[#\?].*/,  '');
    //alert( 'openUrl ' + url);

    // Make a synchronous request.
    try {
        request = new XMLHttpRequest();
    }
    catch (error) {
        try {
            request = new ActiveXObject("Microsoft.XMLHTTP");
        }
        catch (error) {
            request = null;
        }
    }
    request.open( "GET", url, false );
    request.send(null);

    // Not sure why firefox request.status displays as 200 in debugger but
    // tests as "undefined". statusText seems to work OK.
    var success = request.status == 200 || request.statusText == "OK";
    if (!success) {
        request = null;
    }
    return request;
}

// Find the containing verse of an event. 
function getContainingVerse(event) {
    if (!defined(event)) {
        event = window.event;
    }
    var target = getEventTarget(event);
    while( getClass(target) != LOOKUP ) {
        target = target.parentNode;
    }
    return target;
}

// Show a tooltip when hovering over a verse.
function showTip(event) {

    // Immediately hide any previous tip.
    hideTip();

    // Get the full address (including range) which we stored in the 'id'.
    var target = getContainingVerse(event);
    var url = target.getAttribute("href");
    // HttpRequest can only use external website for access for now.
    var remoteUrl = url.replace(LOOKUP_PATH, LOOKUP_PATH_REMOTE);
    var address = target.getAttribute( "id" );

    // Request the corresponding bible chapter file.
    var request;
    var content;
    try {
        // May result in access denied.
        request = openUrl(remoteUrl);
        content = request.responseText;
    }
    catch (error) {
        try {
            if (defined(netscape) && defined(netscape.security) &&
                defined(netscape.security.PrivilegeManager)) {
                netscape.security.PrivilegeManager.enablePrivilege( 
                    "UniversalBrowserRead");
                request = openUrl(remoteUrl);
                content = request.responseText;
            }
        }
        catch (error) {
            // Nothing to do here.
        }
    }

    // Identify the verse range.
    var v1;
    var v2;
    var verseRange = address.match( /([0-9]\d*)-([0-9]\d*)/ );
    if (verseRange != null) {
        v1 = Number(verseRange[1]);
        v2 = Number(verseRange[2]);
    } else {
        verseRange = address.match( /:([0-9]\d*)/ );
        if (verseRange != null) {
            v1 = v2 = Number(verseRange[1]);
        } else {
            verseRange = address.match( /[0-9]\d*/ );
            v1 = v2 = Number(verseRange[0]);
        }
    }

    // Create a new 'div' element to hold the tooltip.
    var tip = document.createElement("div");
    tip.setAttribute("id", TIP_ID);

    // If our request for the bible page was successful (when it fails, it
    // is usually because of security restrictions when this file is served
    // up from the local hard drive and tries to access the
    // SpiritAndTruth.org domain via the web).
    var page = content;
    if (page) {
        
        // Strip extraneous content from bible page HTML, constrain within
        // verse range, highlight verse of interest within context.
        page = page.replace(/[\n\r]/gim, " " );
        page = page.replace( 
            /<A HREF="JAVASCRIPT:X[^>]+">([^<]+)<\/A>/gim, "$1");
        page = page.replace(/<A HREF=.+?<\/A>/gim, '');
        page = page.replace(/<A NAME="__BOT__">.*/gim, '');
        page = page.replace( 
            /<A CLASS=A .+?>([0-9]\d*)<\/A><P CLASS=V>(.+?)<\/P>/gim,
            "<sup><b>$1</b></sup>$2 ");
        var showFrom = v1;
        var showTo = v2;
        showTo += 1;
        if (1 < showFrom) {
            showFrom -= 1;
        }
        
        // Find content ahead of show from.
        var find = "<sup><b>" + showFrom.toString() + "<\/b><\/sup>";
        var start = page.search(find);
        if (start == -1) {
            start = 0;
        }
        // Find content after show to.
        find = "<sup><b>" + (showTo+1).toString() + "<\/b><\/sup>";
        var end = page.search(find);
        if (end == -1) {
            end = page.length;
        }
        page = page.substring(start, end);

        // Embolden key verses?
        find = "(<sup><b>" + v1.toString() + "<\/b><\/sup>.*)(<sup>)";
        // If this is the last verse.
        if (-1 == page.search(find)) {
            // Embolden all the way to the end.
            find = "(<sup><b>" + v1.toString() + "<\/b><\/sup>.*)";
            page = page.replace(RegExp(find), "<b>$1<\/b>");
        // Else more verses follow.
        } else {
            // Just embolden the verse.
            page = page.replace(RegExp(find), "<b>$1<\/b>$2");
        }
        page += ' <A HREF="' + url + '">More...</A>';

        var translationLink = '(<A HREF="' + LOOKUP_PATH + 'lookup.htm">' 
            + window.translation + '</A>)';
        page = '<b><center>' + address + ' ' + translationLink + 
            '</b></center><hr>' + page;
        page = '<font size="-1">' + page + '</font>'

    // Else our request was denied or failed.
    } else {

        // Tell user he must manually click link to open Bible as a
        // separate action.
        page = "Click to open ";
        page +=  '<A HREF="' + LOOKUP_PATH + 'lookup.htm">' + 
            window.translation + '</A>';
        page += " Bible."
    }

    // Set up tooltip with appropriate content.
    tip.innerHTML = page;
    
    target.tooltip = tip;
    tip.className = "tooltip";
    var scrollingPosition = getScrollingPosition();
    var cursorPosition = [0, 0];
    if (defined(event.pageX) && defined(event.x)) {
        cursorPosition[0] = event.pageX;
        cursorPosition[1] = event.pageY;
    } else {
        cursorPosition[0] = event.clientX + scrollingPosition[0];
        cursorPosition[1] = event.clientY + scrollingPosition[1];
    }
    tip.style.position = "absolute";
    tip.style.width = "350px";
    //tip.style.height = "164px";
    tip.style.zIndex="9999999";
    tip.style.left = cursorPosition[0] + 10 + "px";
    tip.style.top = cursorPosition[1] + 10 + "px";
    tip.style.visibility = "hidden";
    tip.style.color = "black";
    tip.style.background = "lightyellow";
    tip.style.padding = "10px";
    tip.style.border = "2px solid black";
    //tip.style.overflow = "hidden";
    document.getElementsByTagName("body")[0].appendChild(tip);

    // Make sure tip will fit.
    var viewportSize = getViewportSize();
    if (cursorPosition[0] - scrollingPosition[0] + 10 + 
        tip.offsetWidth > viewportSize[0] - 25) {
        tip.style.left = scrollingPosition[0] + viewportSize[0] - 
            25 - tip.offsetWidth + "px";
    } else {
        tip.style.left = cursorPosition[0] + 10 + "px";
    }
    if (cursorPosition[1] - scrollingPosition[1] + 10 + 
        tip.offsetHeight > viewportSize[1] - 25) {
        if (event.clientX > (viewportSize[0] - 25 - tip.offsetWidth)) {
            tip.style.top = cursorPosition[1] - tip.offsetHeight - 10 + "px";
        } else {
            tip.style.top = scrollingPosition[1] + viewportSize[1] - 25 - 
                tip.offsetHeight + "px";
        }
    } else {
        tip.style.top = cursorPosition[1] + 10 + "px";
    }
    if ( "-" == tip.style.top[0] ) {
        //alert( "top was " + tip.style.top );
        tip.style.top = "0px";
    }

    if ( "-" == tip.style.left[0] ) {
        //alert( "left was " + tip.style.left );
        tip.style.left = "0px";
    }

    // Now that we've positioned the tip within a visible region we can
    // make it appear.
    tip.style.visibility = "visible";

    return true;
}

// Hide tooltip.
var hideTipPending = false;
function hideTip() {
    if (hideTipPending) {
        var tip;
        while((tip = document.getElementById(TIP_ID)) != null) {
            tip.parentNode.removeChild(tip);
        }
    }
    hideTipPending = false;
    if (defined(window.onunloadChain)) {
        window.onunloadChain();
    }
    return false;
}

// Leave tip up after mouse moves away.
var tipTimeout = 1500;
function hideTipTimeout(event) {
    hideTipPending = true;
    setTimeout("hideTip()", tipTimeout);
}

// Get the class of a node.
function getClass(node) {
    var className = node.getAttribute( "class" );
    if ( className == null ) {
        className = node.getAttribute( "className" );
    }
    return className;
}

// Scan text nodes to find bible verses and mark them.
function scanForVerses(node) {
    // Don't scan within existing links.
    var tagName = node.tagName;
    if (tagName) {
        if (tagName.toLowerCase() == "a") return;
    }
    for (var i = 0; i < node.childNodes.length ; i++) {
        var child = node.childNodes[i];
        //alert( 'child ' + child.tagName + ' ' + child.nodeType + ' ' + child.childNodes.length );
        if (child.nodeType == 3) {
            markVerses(child);
        }
        scanForVerses(child);
    }
}

// Mark bible verses within a text node.
function markVerses(textNode) {

    // Can't do any verse marking if javascript code for address object
    // failed to load.
    if (!defined(self.Address)) {
        return;
    }

    //alert( 'mark ' + textNode.nodeValue );

    var text = textNode.nodeValue;

    // Get rid of newlines in the text which can mess up matching.
    text = text.replace( "[\n\r]+", " " );

    // For each potential scripture verse match.
    var start = 0;
    var result;
    var address;
    var match;
    var aMatch = false;
    var newChild;
    while((result = BOOK_AND_VERSES_PATTERN.exec(text)) != null) {
        var offset = 0;

        // If the potential match doesn't form a valid address.
        //alert('match1 ' + match);
        //var address = new Address(match);
        var numberBookChapter = result[1] + result[3] + result[4];
        //alert( 'numberBookChapter ' + numberBookChapter );
        var address = new Address(numberBookChapter)
        if (!defined(address) || !address.isValid()) {
            
            // Omit book number and try again.
            var bookChapter = result[3] + result[4];
            if (defined(result[1])) {
                offset = result[1].length;
            }
            //alert('match2 ' + bookChapter);
            address = new Address(bookChapter);
        }
        if (defined(address) && address.isValid()) {
            //alert( 'Address ' + address.human());

            // Omit book number and book name from matched field.
            aMatch = true;
            var match = result[4] + result[5];
            offset = result[3].length;
            if (defined(result[1])) {
                offset += result[1].length;
            }

            // Create new child node on first match.
            if (!defined(newChild)) {
                newChild = document.createElement("span");
            }

            // Convert preceding text into new text node.
            var end = result.index + offset;
            if (start < end) {
                newChild.appendChild(document.createTextNode(
                    text.substring(start, end)));
            }

            // Walk down verse list to find chapters and verses.
            var ch1 = null;
            var startV = 0;
            var endV = 0;
            while((resultV = PATTERN_VERSES.exec(match)) != null) {
                var ch2 = null;
                var v1 = 1;
                var v2 = null;

                // Convert preceding text into new text node.
                var endV = resultV.index;
                if (startV < endV) {
                    newChild.appendChild(document.createTextNode(
                        match.substring(startV, endV)));
                }

                // Assign chapter and verse based on match pattern.
                var matchV = resultV[0];
                var chapterTentative = false;
                // x:y-z
                if (resultV[1]) {
                    ch1 = resultV[2];
                    v1 = resultV[3];
                    v2 = resultV[5];
                // x:y
                } else if (resultV[7]) {
                    ch1 = resultV[8];
                    v1 = resultV[9];
                // x-y
                } else if (resultV[11]) {
                    if (ch1 == null) {
                        chapterTentative = true;
                        if (address.chapters() == 1) {
                            ch1 = 1;
                            v1 = resultV[12];
                            v2 = resultV[14]
                        } else {
                            ch1 = resultV[12];
                            ch2 = resultV[14]
                        }
                    } else {
                        v1 = resultV[12];
                        v2 = resultV[14]
                    }
                // x
                } else {
                    if (ch1 == null) {
                        chapterTentative = true;
                        if (address.chapters() == 1) {
                            ch1 = 1;
                            v1 = resultV[16];
                        } else {
                            ch1 = resultV[16];
                            v1 = 1;
                        }
                    } else {
                        v1 = resultV[16];
                    }
                }
                var range = address.bookNameLong() + ' ' + ch1 + ':' + v1;
                if ( v2 ) {
                    range += '-' + v2;
                }
                //alert( range );

                // Append new verse link.
                var link = verseLink(range, matchV);
                if (defined(link)) {
                    newChild.appendChild(link);
                }

                // Erase chapter if it was assumed rather than specified.
                // (We only hang onto the chapter for upcoming verses if we
                // have a high degree of confidence in it.)
                if (chapterTentative) {
                    ch1 = null;
                }

                // Move to next verse number.
                startV = resultV.index + matchV.length;
            }
            
            // Convert remaining text after verses into text node.
            if (startV < match.length) {
                newChild.appendChild(document.createTextNode(
                    text.substr(startV, match.length)));
            }

            // Continue searching from next location.
            start = result.index + offset + match.length;
        }
    }
    // If we found at least one match (so we have inserted a link).
    if (aMatch) {

        // Convert remaining text into text node.
        if (start < text.length) {
            newChild.appendChild(document.createTextNode(
                text.substr(start, text.length)));
        }

        // Append original child's children.
        while (0 < textNode.childNodes.length) {
            newChild.appendChild(textNode.firstChild);
        }

        // Replace original child in parent.
        textNode.parentNode.replaceChild(newChild, textNode);

    }
}

// Append a verse link element.
// Returns 'undefined' if verse address is not valid.
function verseLink(verseAddress, show) {
    // Create corresponding address and create URL for Bible.
    // Bible URL always uses the remote path because we are
    // limited to serving up tool tips using HttpRequest for now.
    var anchor;
    var anchorVerse = new Address( verseAddress );
    if (anchorVerse.isValid()) {
        anchor = document.createElement("a");
        var anchorText = document.createTextNode(show);
        var url = LOOKUP_PATH + "../bibles/" + 
            window.translation.toLowerCase() + "/" + 
            anchorVerse.bookFile() + '#' + anchorVerse.anchor();
        anchor.setAttribute("href", url);
        anchor.setAttribute("id", verseAddress);
        //alert('url ' + url);
        anchor.setAttribute("class", LOOKUP);     // For Firefox.
        anchor.setAttribute("className", LOOKUP); // For IE.
        anchor.appendChild(anchorText);

        // Watch mouse activity over the verse.
        attachEventListener( anchor, "mouseover", showTip, false );
        attachEventListener( anchor, "mouseout", hideTipTimeout, false );
    }
    return anchor;
}

///////////////////////////////////////////////////////////////////////////////
// Code below this line is adapted from the book "The Javascript Anthology"
// by James Edwards & Cameron Adams.
///////////////////////////////////////////////////////////////////////////////

function attachEventListener(target, eventType, functionRef, capture)
{
  if (defined(target.addEventListener))
  {
    target.addEventListener(eventType, functionRef, capture);
  }
  else if (defined(target.attachEvent))
  {
    target.attachEvent("on" + eventType, functionRef);
  }
  else
  {
    eventType = "on" + eventType;

    if (typeof target[eventType] == "function")
    {
      var oldListener = target[eventType];

      target[eventType] = function()
      {
        oldListener();

        return  functionRef();
      }
    }
    else
    {
      target[eventType] = functionRef;
    }
  }

  return true; 
}

function getElementsByAttribute(attribute, attributeValue)
{
  var elementArray = new Array();
  var matchedArray = new Array();

  if (document.all)
  {
    elementArray = document.all;
  }
  else
  {
    elementArray = document.getElementsByTagName("*");
  }

  for (var i = 0; i < elementArray.length; i++)
  {
    if (attribute == "class")
    {
      var pattern = new RegExp("(^| )" + attributeValue + "( |$)");

      if (pattern.test(elementArray[i].className))
      {
        matchedArray[matchedArray.length] = elementArray[i];
      }
    }
    else if (attribute == "for")
    {
      if (elementArray[i].getAttribute("htmlFor") || elementArray[i].getAttribute("for"))
      {
        if (elementArray[i].htmlFor == attributeValue)
        {
          matchedArray[matchedArray.length] = elementArray[i];
        }
      }
    }
    else if (elementArray[i].getAttribute(attribute) == attributeValue)
    {
      matchedArray[matchedArray.length] = elementArray[i];
    }
  }

  return matchedArray;
}

function makePopup(url, width, height, overflow)
{
  if (width > 640) { width = 640; }
  if (height > 480) { height = 480; }

  if (overflow == '' || !/^(scroll|resize|both)$/.test(overflow))
  {
    overflow = "both";
  }

  var win = window.open(url, '',
      "width=" + width + ",height=" + height
      + ",scrollbars=" + (/^(scroll|both)$/.test(overflow) ? "yes" : "no")
      + ",resizable=" + (/^(resize|both)$/.test(overflow) ? "yes" : "no")
      + ",status=yes,toolbar=no,menubar=no,location=no"
  );

  return win;
}

function getEventTarget(event)
{
    var targetElement = null;
    if (defined(event.target)) {
        targetElement = event.target;
    } else {
        targetElement = event.srcElement;
    }
    while (targetElement.nodeType == 3 && targetElement.parentNode != null ) {
        targetElement = targetElement.parentNode;
    }
    return targetElement;
}


function getScrollingPosition()
{
  var position = [0, 0];

  if (defined(window.pageYOffset))
  {
    position = [
        window.pageXOffset,
        window.pageYOffset
    ];
  }

  else if (defined(document.documentElement.scrollTop)
      && (document.documentElement.scrollTop > 0 ||
      document.documentElement.scrollLeft > 0))
  {
    position = [
        document.documentElement.scrollLeft,
        document.documentElement.scrollTop
    ];
  }

  else if (defined(document.body.scrollTop))
  {
    position = [
        document.body.scrollLeft,
        document.body.scrollTop
    ];
  }

  return position;
}


function getViewportSize()
{
  var size = [0, 0];

  if (defined(window.innerWidth))
  {
    size = [
        window.innerWidth,
        window.innerHeight
    ];
  }
  else if (defined(document.documentElement)
      && defined(document.documentElement.clientWidth)
      && document.documentElement.clientWidth != 0)
  {
    size = [
        document.documentElement.clientWidth,
        document.documentElement.clientHeight
    ];
  }
  else
  {
    size = [
        document.getElementsByTagName("body")[0].clientWidth,
        document.getElementsByTagName("body")[0].clientHeight
    ];
  }

  return size;
}

//addLoadListener(initRPC);

function initRPC()
{
  createIframeRPC();

  var newA = document.createElement("a");
  newA.setAttribute("href", "#");
  newA.appendChild(document.createTextNode("Get remote data"));
  newA.onclick = function()
  {
    executeIframeRPC("retrieve_data_iframe_query.html");

    return false;
  };

  document.getElementsByTagName("body")[0].appendChild(newA);

  return true;
}

function createIframeRPC()
{
  var body = document.getElementsByTagName("body")[0];
  var iframe = document.createElement("iframe");

  iframe.setAttribute("id", "iframeRPC");

  body.appendChild(iframe);

  if (typeof iframe.document != "undefined" && typeof iframe.contentDocument == "undefined" && typeof iframe.contentWindow == "undefined")
  {
    body.removeChild(iframe);

    var iframeHTML = '<iframe id="iframeRPC"></iframe>';

    body.innerHTML += iframeHTML;

    iframe = document.getElementById("iframeRPC");
    iframe.contentWindow = new Object();
    iframe.contentWindow.document = new Object();
    iframe.contentWindow.document.location = new Object();
    iframe.contentWindow.document.location.iframeRef = iframe;
    iframe.contentWindow.document.location.replace = locationReplaceIE5;
  }

  iframe.style.position = "absolute";
  //iframe.style.left = "-1500em";
  iframe.style.left = "0";
  iframe.style.top = "0";
  iframe.style.width = "40";
  iframe.style.height = "100";
  iframe.setAttribute("tabIndex", "-1");

  return true;
}

function locationReplaceIE5(URL)
{
  this.iframeRef.setAttribute("src", URL);

  return true;
}

function executeIframeRPC(URL)
{
  var iframe = document.getElementById("iframeRPC");

  if (typeof iframe.contentDocument != "undefined")
  {
    iframeDocument = iframe.contentDocument;
  }
  else if (typeof iframe.contentWindow != "undefined")
  {
    iframeDocument = iframe.contentWindow.document;
  }
  else
  {
    return false;
  }

  iframeDocument.location.replace(URL);

  return true;
}

function addLoadListener(fn)
{
  if (typeof window.addEventListener != 'undefined')
  {
    window.addEventListener('load', fn, false);
  }
  else if (typeof document.addEventListener != 'undefined')
  {
    document.addEventListener('load', fn, false);
  }
  else if (typeof window.attachEvent != 'undefined')
  {
    window.attachEvent('onload', fn);
  }
  else
  {
    var oldfn = window.onload;
    if (typeof window.onload != 'function')
    {
      window.onload = fn;
    }
    else
    {
      window.onload = function()
      {
        oldfn();
        fn();
      };
    }
  }
}

function addUnloadListener(fn)
{
  if (typeof window.addEventListener != 'undefined')
  {
    window.addEventListener('unload', fn, false);
  }
  else if (typeof document.addEventListener != 'undefined')
  {
    document.addEventListener('unload', fn, false);
  }
  else if (typeof window.attachEvent != 'undefined')
  {
    window.attachEvent('onunload', fn);
  }
  else
  {
    var oldfn = window.onunload;
    if (typeof window.onunload != 'function')
    {
      window.onunload = fn;
    }
    else
    {
      window.onunload = function()
      {
        fn();
        oldfn();
      };
    }
  }
}
