var toco_prefs = {
    'topic':     1,
    'bookmaker': 6218,
    'format':    2,
    'highlight': 0,
    'mode':      1
};

var contentScanner = function() {

    var config = {
      'minNodeLength':5
    };

    var nodes = [];

    function seekNodesSlow(pNode) {
      if(pNode && pNode.nodeType==3) {
        if(pNode.length>=config.minNodeLength) {
            nodes.push(pNode);
        };
      } else if(pNode && pNode.hasChildNodes()){
        var childs=pNode.childNodes;
        for(var i=0;i<childs.length;i++) {
          seekNodesSlow(childs[i]);
        };
      };
    }

    var my = {
      'getTextNodes': function(pNode) {
        // we can cache textNode results here to avoid re scanning.
        if(document.evaluate) {
            var xPathResult = document.evaluate('.//text()[normalize-space(.) != ""]',pNode,null,XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,null);
            for (var i = 0, l = xPathResult.snapshotLength; i < l ; i++) {
                nodes[i] = xPathResult.snapshotItem(i);
            }
        } else {
            nodes = [];
            seekNodesSlow(pNode);
        }
        return nodes;
      }
    };

    return my;
}();

var toco = function() {
    /****************
    ** Preferences **
    *****************/

    function TOCO_getIntPref(prefName) {
        return toco_prefs[prefName];
    }
    function TOCO_getBoolPref(prefName) {
        return toco_prefs[prefName];
    }
    function TOCO_getCharPref(prefName) {
        return toco_prefs[prefName];
    }
    function TOCO_setIntPref(prefName, prefValue) {
        toco_prefs[prefName] = prefValue;
    }
    function TOCO_setBoolPref(prefName, prefValue) {
        toco_prefs[prefName] = prefValue;
    }
    function TOCO_setCharPref(prefName, prefValue) {
        toco_prefs[prefName] = prefValue;
    }

   /**
   * Safely, asyncronously, loads external json data using xmlhttprequest.
   * requires FF 3.5+ due to same origin policy
   *
   * @param   json_url    string. fully qualified URL to json data
   * @param   callback    function to call with results of parsed JSON.
   *
   */
  function loadJSON(json_url,callback) {
    json_url += '&jsonp=?';
    $.getJSON(json_url, callback);
    return null;
    var req = new XMLHttpRequest();
    req.open('GET', json_url, true);
    req.onreadystatechange = function (aEvt) {
        if (req.readyState == 4) {
            if(req.status == 200) {
                // neat huh? you can even pass in an anonymous function as the callback.
                callback(JSON.parse(req.responseText), json_url, callback);
            } else {
                //alert(req.status);
            }
        }
    };
    req.send();
  }

    /********************
    ** Magic Numbers:  **
    *********************/
    var TOPIC_UNKNOWN      = 0;
    var TOPIC_HORSE_RACING = 1;
    var TOPIC_FOOTBALL     = 2;

    /*************************
    ** Private Properties:  **
    *************************/
    var pageTopic = TOPIC_UNKNOWN; // default page topic

    var topics = {};
    topics[TOPIC_HORSE_RACING] = {
        'name': "Horse Racing",
        'terms':{}
    };

    var indexHashtable = {};
    var numGGLinksAdded = 0;
    var currently_hovering_over;
    var currXY;
    var runners;
    var insertedLinks = false;
    var prefs;
    var highlightElement = "span";
    var randomString = Math.round(Math.random()*8999)+1000;
    var timerId = 0; // used for storing setTimeout id.

    // define any images which need to be inserted directly into the page.
    // images references in the bubble can be referenced via chrome urls.
    var images = {};
    images.highlighter = "data:image/png;base64,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";

    // get chosen bookmaker:
    var bookie = "";

    // key is bookie id.
    // we could do a funky luminosity function to determine the best foreground color given a bg. But for the moment hardcoded white or black is fine.
    var highlightColors = {
        "0":    {"name":"Unknown","background":"#000000","foreground":"#FFFFFF"}
    };

    /**********************
    ** Private Methods:  **
    ***********************/

    /**
     * Adds content to found. Called from replace();
     *
     * @param   found   string. The matching content
     * @param   offset  int. The offset from the start of subject to found
     * @param   subject string. The content in which found was found
     *
     * @return  string, content to replace found with.
     *
     */
    function highlightContent(found,offset,subject) {
        // All keywords should begin with a capital letter. If it doesn't,
        // it is a false positive and should not be modified
        // We might be able to modify the xPath to do this for us.
        if (!found.match(/^[A-Z]/) || subject.toString().substring(offset + found.length, offset + found.length + 1).match(/[a-z]/i)) {
            return found;
        }

        // have we already added a link here?
        var linkPrefix = '<'+highlightElement;
        var linkSuffix = '</'+highlightElement+'>';

        // find the first open GG tag after the match:
        var nextOpen = subject.indexOf(linkPrefix, offset)
        // find the first close GG tag after the match:
        var nextClose = subject.indexOf(linkSuffix, offset)

        // if there's a close tag before the next open
        // or there's a close tag and no next open
        if(nextClose<nextOpen || (nextClose>=0 && nextOpen==-1)) {
            // then we're inside a tag already and shouldn't add anything.
            return found;
        } else {
            // add link
            numGGLinksAdded++;
            var returnVal = linkPrefix+' id="'+highlightElement+'Highlight'+numGGLinksAdded+'" class="class'+randomString+'" onMouseOver="';
            //var scrollLeft = (window.pageXOffset || document.scrollLeft  || document.body.scrollLeft || document.documentElement.scrollLeft);
            //var scrollTop  = (window.pageYOffset || document.scrollTop  || document.body.scrollTop || document.documentElement.scrollTop);
            returnVal += "toco.keepBubble();";
                returnVal += "if (event && event.clientX) {\
                  var x = event.clientX + (window.pageXOffset || document.scrollLeft  || document.body.scrollLeft || document.documentElement.scrollLeft), y = event.clientY + (window.pageYOffset || document.scrollTop || document.body.scrollTop || document.documentElement.scrollTop);\
                } else if (arguments[0] && arguments[0].clientX) {\
                  var x = arguments[0].clientX + (window.pageXOffset || document.scrollLeft  || document.body.scrollLeft || document.documentElement.scrollLeft), y = arguments[0].clientY + (window.pageYOffset || document.scrollTop || document.body.scrollTop || document.documentElement.scrollTop);\
                };\
                toco.hover('"+highlightElement+'Highlight'+numGGLinksAdded+"','"+found.toLowerCase()+"', x , y);";
                returnVal += 'return false;" ';
                returnVal += 'onMouseOut="toco.prepareToHideBubble();" ';
            var highlightIndex = 0;
            if(highlightColors[bookie]) {
                highlightIndex = bookie;
            }
            if(TOCO_getIntPref('highlight')==1) {
                returnVal +='style="background:'+highlightColors[highlightIndex].background+';color:'+highlightColors[highlightIndex].foreground+';font-weight:normal;cursor:help;-moz-border-radius:2px;padding:0px 3px;">'+found+linkSuffix;
            } else {
                returnVal +='style="background:url('+images.highlighter+');color:#000;'+highlightColors[highlightIndex].foreground+';font-weight:normal;cursor:help;-moz-border-radius:2px;padding:0px 3px;">'+found+linkSuffix;
            }
            return returnVal;
        }
    }

    /**
     * @return array of regexes to search for
     * @see my.setRunners, my.getRunners, my.initialDataLoaded
     */
    function getRegexes() {
        // generate the regexes
        // it's slow to do /horse1|...|horseN/, faster to do /horse1/ then /horse2/, even faster to do /horseM|...|horseM+K/ where k=some number between 2 and N. k between 2 and 30 seems to give most benefit
        // in other words, instead of doing lots of little regexes, or one big regex, we do a few medium sized ones.

        var re = [];
        var reStr = "";
        var numMultis = 30; // = k
        var numAdded = 1;
        var runner;

        // loop through keys of runners
        for(runner in runners.keywords) {

            // build hashtable so we don't have to loop back over keywords array when we have the name:
            if("cname" in runners.keywords[runner]) {
                // point non-canonical names to their canonical equivalents
                indexHashtable[runners.keywords[runner].word] = runners.keywords[runner].cname;
            } else {
                // point canonical names to themselves
                indexHashtable[runners.keywords[runner].word] = runner;
            }

            // add this runner to regular expression to test for
            if(runners.keywords[runner].word.length>=my.config.minRunnerNameLength) {
                //reStr += "( |>|^)"+runner;
                reStr += runners.keywords[runner].word;
                if(numAdded<numMultis) {
                    // haven't reached numMultis yet; add this runner to the regex
                    numAdded++;
                    reStr += "|";
                } else {
                    // have added numMultis runners to this regex, add it to the pile of regexes, clear vars and start creating a new regex
                    re.push(new RegExp(reStr,"gi"));
                    reStr = "";
                    numAdded = 1;
                }
            }
        }

        if(reStr!="") {
            // cut off the final |
            reStr = reStr.substr(0,reStr.length-1);
            // add the final re str, which will have fewer than numMultis
            re.push(new RegExp(reStr,"gi"));
        }
        return re;
    }

     /**
     * called when the initial external data is loaded (eg runners); once we've got everything we need we can start modifying the content
     */
    function initialDataLoaded() {
        if(!insertedLinks && runners) {
            // we haven't already inserted and we have runners
            // so let's do our funky magic.
            my.insertLinks();
        }
    }
    /******************
    ** Public Code:  **
    *******************/

    /**
     * The "public" part of the object. In fact under firefox this is running as (sandboxed) priviledged code, so none is public as far as the DOM is concerned.
     * But it's nice to keep separate what is conceptually "private" and "public". Also, using our callback system, methods declared here as public can be called from the DOM. Private cannot.
     * also, in IE it's truly public.
     */
    var my = {
        config: {
            minRunnerNameLength: 5, // minimum content length to replace. reduces false positives
            cacheRunners: true, // sent to the server to ask it to cache/not cache the data it returns
            BubbleID: randomString+"Bubble", // this can be changed if there's a name conflict. not that we'll know that in advance. Although we could say do{ BubbleID = someCreateIDFunction(); } while(document.getGetElementByID(BubbleID)!==null);
            BubbleTimeout: 500
        },
        /**
         * Called on (every) page load
         * starts the process for getting runners.
         */
        load: function() {
            // determine page topic
            //pageTopic = topicDetector.score(topics);
            // force to a particular topic
            pageTopic = {"name":topics[TOCO_getIntPref('topic')].name, "topic":TOCO_getIntPref('topic')};

            // get the list of runners
            my.getRunners();
        },
        /**
         * Loads runners from GG via JSON
         */
        getRunners: function() {
            var nocache = my.config.cacheRunners ? '' : '&nocache=1';
            switch(pageTopic.topic) {
            case TOPIC_HORSE_RACING:
                loadJSON("http://pricewise.gg.com/v3/map.php?s=horse-racing"+nocache, my.setRunners);
                break;
            case TOPIC_FOOTBALL:
                loadJSON("http://pricewise.gg.com/v3/map.php?s=football"+nocache, my.setRunners);
                break;
            default:
                // unknown topic - don't load toco.
                break;
            }
        },
        /**
         * When the runners JSON is loaded, this is the callback. Sets the runners
         *
         * @param   input   parsed json from pricewise
         *
         */
        setRunners: function(input) {
            runners = input;
            initialDataLoaded();
        },
        /**
         * Stops the Bubble being hidden
         */
        keepBubble: function() {
            window.clearTimeout(timerId);
        },
        prepareToHideBubble: function() {
            // if there's already a timer set, clear it
            window.clearTimeout(timerId);
            // then start a new timer
            // this triggers a warning in the add-on validator, but it's safe as we're not using it to eval code.
            timerId = window.setTimeout(my.hideBubble,my.config.BubbleTimeout);
        },
        /**
         * Called from DOM, clears Bubble
         */
        hideBubble: function(e) {
            var bub = document.getElementById(my.config.BubbleID);
            if(bub) {
                bub.style.display = "none";
            }
            currently_hovering_over = null;
        },
        placeBet: function(price, stake, outcome) {
            betfairAPI.placeBet(price, stake, outcome);
        },
        bubbleClick: function(args) {
            // get event element:
            var el = args.elementId;
            // get attributes:
            var currentWidth = args.currentwidth;
            var currentHeight = args.currentheight;
            var currentURL = args.currenturl;

            if(currentWidth<=0) {
                currentWidth = 200;
            }
            if(currentHeight<=0) {
                currentHeight = 200;
            }
            // open pricewise betslip:
            window.open(currentURL, '_blank', 'resizable=no,scrollbars=yes,status=no,menubar=no,toolbar=no,location=no,directories=no,width='+currentWidth+',height='+currentHeight+',scrollbars=no');
        },
        resizeBubbleWrapper: function(w, h) {
            h = parseInt(h);
            w = parseInt(w);
            var bub = document.getElementById(my.config.BubbleID);

            // position the iframe here too, to avoid bubble flash
            var bubTop  = currXY[1] - h;
            var bubLeft = currXY[0];

            var scrollTop  = (window.pageYOffset || document.scrollTop  || document.body.scrollTop || document.documentElement.scrollTop);

            if(bubTop-scrollTop<=0) {
                // adding Bubble above element would be off-screen, up
                // so move it down, plus some offset
                bubTop += h;
                bubLeft += 20; // and shift it right a bit so the cursor doesn't go over it.
            }
            // position bubble
            bub.style.left = bubLeft-45+"px";
            bub.style.top = bubTop+"px";
            // size bubble
            bub.width = w;
            bub.height = h;
        },
        /**
         * Draws Bubble onto page. Called when price is loaded
         *
         * @param   response    parsed JSON from pricewise
         * @param   runner  string, the runner
         * @param   course  string, the course name
         */
        addBubble: function(response, runner, venue, date) {
            // never hide if we're about to show
            my.keepBubble();
            if(!response.Error) {
                if(!response.Selections.Error) {
                    // Create the bubble container
                    var bub = document.getElementById(my.config.BubbleID);
                    if(!bub) {
                        // The bubble doesn't exist in the DOM yet; create and append it
                        bub = document.createElement('iframe');
                        // Set some attributes
                        bub.setAttribute('id', my.config.BubbleID);
                        bub.setAttribute('allowtransparency', 'true');
                        bub.setAttribute('marginwidth', 0);
                        bub.setAttribute('marginheight', 0);
                        bub.setAttribute('hspace', 0);
                        bub.setAttribute('vspace', 0);
                        bub.setAttribute('frameborder', 0);
                        bub.setAttribute('scrolling', 'no');
                        bub.style.border   = 'none';
                        bub.style.zIndex   = "1000100"; // nice big zIndex so it's always on top.
                        bub.style.position = "absolute";
                        bub.scrolling = 'no';
                        // Add the Bubble to the DOM
                        document.body.appendChild(bub);
                        // Add event listeners for show/hide
                        if(bub.addEventListener) {
                            bub.addEventListener("mouseover",my.keepBubble, false);
                            bub.addEventListener("mouseout",my.prepareToHideBubble, false);
                        } else if(bub.attachEvent) {
                            bub.attachEvent("onmouseover",my.keepBubble);
                            bub.attachEvent("onmouseout",my.prepareToHideBubble);
                        }
                    }
                    // the bubble is set to white-space:nowrap; so we can make it insy weenie and then resize up to accomodate the rendered width
                    bub.height = 0;
                    bub.width  = 0;
                    // show the bubble
                    bub.style.display = "inline";

                    // parameters to send to bubble for display
                    var params = {};
                    params.bookie = TOCO_getIntPref("bookmaker");
                    params.oddsFormat = TOCO_getIntPref("format");
                    params.runner = runner;
                    params.header = (pageTopic.topic==TOPIC_HORSE_RACING) ? venue : response.Selections[0].EventName;
                    params.topic = pageTopic.name;
                    params.date = date;
                    params.response = response;

                    // send mouse pos and scroll offset so we can set the correct arrow in the Micheal Buble.
                    params.pageYOffset = (window.pageYOffset || document.scrollTop  || document.body.scrollTop || document.documentElement.scrollTop);
                    params.mouseY = currXY[1];

                    // load bubble template
                    // when it's loaded, we send a message back and then position and resize via resizeBubbleWrapper
                    //bub.src = 'about:oa-bubble?params='+JSON.stringify(params);
                    bub.src = './bubble/bubble.html?params='+JSON.stringify(params);
                } else {
                    //alert(response.Selections.Error);
                }
            } else {
                // error from pricewise
                //response.Error.toString().indexOf("2100")
            }
        },
        /**
         * Called when mouse hovers over content, via callback mechanism
         *
         * @param   evt Object, the event. See callback functions
         */
        hover: function(el, found, x, y) {

            // get index of this runner:
            var idx = indexHashtable[found];
            var runner = runners.keywords[idx];
            var market = runners.markets[runner.market];
            currXY = [x,y];
            currXY[0] = parseInt(currXY[0]);
            currXY[1] = parseInt(currXY[1]);
            if(currently_hovering_over!=el) {
                currently_hovering_over = el;
                my.getPrices(market, runner.runner, runner.name);
            }
        },
        getPrices: function(market, runnerNum, runnerName) {

            // get the best prices for this location/day/time/runner
            var currDescriptor = "descriptor="+market.descriptor+"&runner=%runner%&bookmaker=%bookmaker%";
            // give me details for this runner:
            if(isNaN(parseInt(runnerNum))) {
                runnerNum = 0;
            }
            // give me details for this runner:
            currDescriptor = currDescriptor.replace("%runner%",runnerNum);
            // give me details for all runners:
            //currDescriptor = currDescriptor.replace("%runner%",0);
            // give me details for this bookie:
            currDescriptor = currDescriptor.replace("%bookmaker%",TOCO_getIntPref('bookmaker'));

            loadJSON("http://pricewise.gg.com/v3/?"+currDescriptor,
                function(input) {
                    if(!input.Error) {
                        my.addBubble(input, "", market.venue, market.date);
                    }
                }
            );
        },
        /**
         * Scans the content for things to modify
         * If it's running slowly, this is where to look.
         */
        insertLinks: function(parentElement) {

            if (parentElement === undefined) {
                parentElement = document.body;
            }

            var re = getRegexes();
            var num_regexes = re.length;
            var skip;
            var node;
            var p;
            var fishNode;
            var fish = false;
            var frag = false;
            var numLinksBefore;
            var totalLength = 1;
            var maxLength = 1;
            var l=0;
            var textNode;
            var nodes = contentScanner.getTextNodes(parentElement);
            for(node in nodes) {

                textNode = nodes[node];

                content = textNode.data;
                if(content.replace("/[^a-z]/","").length<5) {
                    continue;
                }

                skip = false;
                node = textNode;
                // walk back up all the parents, looking for forbidden elements
                // this probably takes some time to do as it's DOM traversal. jQuery might have a clever way of doing it that we can pinch.
                while(p = node.parentNode) {
                    if(p.tagName) {
                        parentTag = p.tagName.toUpperCase();
                        if((parentTag==highlightElement.toUpperCase() && p.className=='class'+randomString) || parentTag=="TEXTAREA" || parentTag=="SCRIPT" || parentTag=="STYLE" || parentTag=="IFRAME" || parentTag=="INPUT" || parentTag=="OPTION") {
                            skip = true;
                            break;
                        }
                    }
                    node = p;
                }

                if(!skip) {
                    totalLength += content.length;
                    if(content.length>maxLength) {
                        maxLength = content.length;
                    }
                    numLinksBefore = numGGLinksAdded;
                    for(var j=0 ; j<num_regexes ; j++) {
                        content = content.replace(re[j], highlightContent);
                    }
                    if(numLinksBefore != numGGLinksAdded) {
                        // only need to do this if there's something to replace.
                        if(!fish) {
                            fish = document.createElement('div');
                        }
                        fish.innerHTML = content;
                        if(!frag) {
                            frag = document.createDocumentFragment();
                        }
                        for(var j=0 ; j < fish.childNodes.length ; j++) {
                            fishNode = fish.childNodes[j].cloneNode(true);
                            frag.appendChild(fishNode);
                        }

                        textNode.data = "";
                        textNode.parentNode.insertBefore(frag, textNode.nextSibling);
                    }
                }
            }
            insertedLinks = true;
        }
    };
    // return the public part of the object, so it becomes public!
    return my;
}();

/* /toco: */
if (!$.browser.msie || ($.browser.msie && $.browser.version >= 8.0)) {
  $(toco.load);
};

