/* vim: set filetype=javascript tabstop=4 expandtab:
 *
 * Copyright 2010-2012 Krux Digital, Inc.  All Rights Reserved.
 * No permission is granted to use, copy or modify this code.
 * 
Typical usage of the krux.js script:
<script type="text/javascript">
KRUXSetup = { pubid: "de1f97dc-d6a6-47ec-be0f-924b2c6e4569" };
</script>
<script type="text/javascript" src="//cdn.krxd.net/krux.js"></script>

Optional setup parameters are passed in the KRUXSetup block, and documented in KRUXSetupDefaults

Methods are in alphabetimical order. Please keep it that way.
*/

// Check to see if we should call the stage/dev or specific release versions instead if ?krux_release=yyy is in the url
if (! window.krux_js && location.search.indexOf('krux_release') > -1){
    (function(){ // closure so variables are safe
            var kruxm = location.search.match(/krux_release=(dev|stage)/);
            // Note that krux_js variable is global so the next script that is called knows it was.
            if ( kruxm ) {
                window.krux_js = 'http://kruxcontent-' + kruxm[1] + '.s3.amazonaws.com' + '/krux.js?' + Math.random();
            } else {
                var kruxm_r = location.search.match(/krux_release=([\w.]+)/);
                if (kruxm_r) {
                    window.krux_js = '//cdn.krxd.net/kruxcontent/releases/krux-' + kruxm_r[1] + '.js';
                }
            }
            if (window.krux_js) {
                window.console && console.log && console.log("Using " + window.krux_js);
                var tag = document.createElement("script");
                tag.src = window.krux_js;
                var script1 = document.getElementsByTagName('script')[0];
                script1.parentNode.insertBefore(tag, script1);
            }
    })();
} else if (! window.KRUX ){

// Wrap everything in a function. This provides a protected space for not polluting
// globals, as well as allowing fo the optimizer to shrink all references
// to krux and kruxSetup (made global at the end of this function)
// This results in a 13% smaller packed file once Google Closure Compiler packs it
(function( KRUXSetup, win, doc) {

var KRUXSetupDefaults = {
    pubid:          '', // required, assigned by KRUX
    site:           '', // If more than one site.
    section:        '', // Example: "Entertainment"
    subsection:     '', // Example "action-movies",
    channels:       '', // For multiple, pass as a comma separated list. "Example: movies,18-24"
    async:          false, // Needs to be false if *visible* content is called
    autoCollect:    true, // Automatically call KRUX.collect
    autoInit:       true, // Automatically call KRUX.init (rarely disabled)
    pixelTime:      1000, // default timeout in milliseconds to wait for pixels (also config'd)
    reportErrors:   true,
    allTagsOffsite: false, // internal debug - force tags for unit tests
    userAttributes: {},
    pageAttributes: {},
    trackSocial : false
};

if (!KRUXSetup) {
    KRUXSetup = KRUXSetupDefaults;
    win.KRUXSetup = KRUXSetup;
}

// we need jquery.extend...
for (var name in KRUXSetupDefaults) {
    // Set up the default values
    if (!KRUXSetupDefaults.hasOwnProperty(name)) { continue; }
    if (typeof KRUXSetup[name] == 'undefined') {
        KRUXSetup[name] = KRUXSetupDefaults[name];
    }
}

// Determine the protocol dynamically from the url, but only if it's http or https.
// This catches the case where they are using file:// for local testing
var protocol = location.protocol.indexOf('http') === 0 ? location.protocol : 'http:';
var servicesUrl = protocol + '//services.krxd.net/';

var KRUX = {
    _kvs:           {},
    _parameters:    {},
    backgroundGets: [],
    blockUrl:       servicesUrl + 'block.gif',
    collectors:     [], // Internal bucket for all the collectors found on the page (objects)
    collectUrl:     servicesUrl + 'pixel.gif',
    socialUrl:      servicesUrl + 'social.gif',
    eventUrl:       servicesUrl + 'event.gif',
    geo:            null,
    geoUrl:         servicesUrl + "geoip?root_name=KRUX.geo", //"http://ssl.gstatic.com/gb/js/pwm_0e44f32cbac2196df4c324ca3ab2ecf7.js",   

    configCache:    "na",
    debugLevel:     0,
    errorUrl:       '/jserror',
    kruxIframeUrl:  '//cdn.krxd.net/kruxcontent/krux_iframe.html?',
    loadedScripts:  [], // scripts thave have been loaded with KRUX.loadScript (unit testing)
    onloadCalled:   false,
    predbg:         '',
    release:        '4.9.1',
    scriptTimeout:  20000, // how long to wait for a remote script to load in ms
    sTag:           /^<script((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/
};

/* The _parameters variable is used to track all the parameters that
 * have been set.  These parameters are sent as-is to the Krux servers.
 */
var _parameters = {};
KRUX._parameters = _parameters;

/* api for browser version and type. Inspired by jQuery and jsmin
 * ######## Note about using ##########
 * I resisted writing this function because it can lead to bad code.
 * You should resist using it for the same reason. Try to use feature sniffing instead.
 *
 * This is placed at the top of the file because other functions use it when they are setting up
 */
var is = {};
is.ua = navigator.userAgent.toLowerCase();
var m = is.ua.match(/(webkit)[ \/]([\w.]+)/) ||
        is.ua.match(/(opera)(?:.*version)?[ \/]([\w.]+)/) ||
        is.ua.match(/(msie) ([\w.]+)/) ||
        is.ua.indexOf("compatible") < 0 && is.ua.match(/(mozilla)(?:.*? rv:([\w.]+))?/) ||
        ["", "", 0];
// shortcut variable for the optimizer
is.browser = m[1];
// Massage the version number to change 1.9.212 into a valid float (1.9) for comparisons
is.version = parseFloat((m[2] || "0").match(/^[0-9]+[.0-9]*/)[0], 10);
is.msie = m[1] == "msie";
is.mozilla = m[1] == "mozilla";
is.webkit = m[1] == "webkit";
is.opera = m[1] == "opera";
is.gecko = is.ua.indexOf('gecko/') != -1;
is.chrome = !!win.chrome;

var depth = win.screen.colorDepth;
var screen = win.screen;
// Some browsers have long strings for their depth like 143424320 
if (depth == 8 || depth == 16 || depth == 24 || depth == 32) {
    is.res = [screen.width, screen.height, depth].join("x"); 
} else {
    is.res = [screen.width, screen.height].join("x");
}
KRUX.is = KRUX._is = is; // _ is so it's at the top
KRUX.is.awesome = true; // ;-)


/* Simple convenience function for getElementById */
KRUX._ = function(id) {
    return doc.getElementById(id);
};


/* API for setting/getting attributes */
var attribute = function(prefix, name, value) {
    var cleanName = (prefix || "") + name.replace(/[^0-9A-Za-z_]/g, '_');
    if (value) {
        value = KRUX.toS(value).substring(0, 542); // let's not go crazy
        KRUX.d('Passing ' + name + ' as ' + cleanName + ' with a value of ' + value, 6);
        _parameters[cleanName] = value;
        return true;
    } else {
        return _parameters[cleanName];
    }
};
// Bulk function for doing multiple
var attributes = function(attrMap, prefix){
    var kvs = KRUX.keyValues(attrMap);
    for (var i = 0; i < kvs.keys.length; i++) {
        attribute(prefix, kvs.keys[i], kvs.values[i]);
    }
};
// Convenience apis for custom control tags
KRUX.context = function(name, value) { return attribute('_kcp_', name, value); };
KRUX.pageAttribute = function(name, value) { return attribute('_kpa_', name, value); };
KRUX.userAttribute = function(name, value) { return attribute('_kua_', name, value); };


/* Allow users to register a call back for when the tags are loaded (if delayed loading is used) */
KRUX.afterLoadCallback = function(f){
    if (! KRUX.afterLoadFunctions ){
        KRUX.afterLoadFunctions = [];
    }
    KRUX.afterLoadFunctions.push(f);
};


KRUX.trackSocial = function() {
    KRUX.trackFacebook();
    KRUX.trackTwitter();
};


/**
 *Track Facebook
 */
KRUX.trackFacebook = function() {
    if (win.FB && win.FB.Event && win.FB.Event.subscribe) {
        win.FB.Event.subscribe('edge.create', function(targetUrl) {
            var params = KRUX.clone(_parameters);
            params._ksoc_t = "fb";
            params._ksoc_e = "like";
            params._ksoc_url = targetUrl;
            backgroundGet(KRUXSetup.socialUrl || KRUX.socialUrl, params);
        });
        win.FB.Event.subscribe('edge.remove', function(targetUrl) {
            var params = KRUX.clone(_parameters);
            params._ksoc_t = "fb";
            params._ksoc_e = "unlike";
            params._ksoc_url = targetUrl;
            backgroundGet(KRUXSetup.socialUrl || KRUX.socialUrl, params);
        });
        win.FB.Event.subscribe('message.send', function(targetUrl) {
            var params = KRUX.clone(_parameters);
            params._ksoc_t = "fb";
            params._ksoc_e = "send";
            params._ksoc_url = targetUrl;
            backgroundGet(KRUXSetup.socialUrl || KRUX.socialUrl, params);
        });
    }
};


/*
 * Track twitter
 */
KRUX.trackTwitter = function() {
    if (!win.twttr){
        return;   
    }
    if (win.twttr.events && win.twttr.events.bind) {
        win.twttr.events.bind('tweet', function(event) {
        if (event) {
          var targetUrl; // Default value is undefined.
          
          if (event.target && event.target.nodeName == 'IFRAME') {
            targetUrl = KRUX.getRequestVal('url', '', event.target.src);
          }
          var params = KRUX.clone(_parameters);
          params._ksoc_t = "twttr";
          params._ksoc_e = "tweet";
          params._ksoc_url = targetUrl;
          backgroundGet(KRUXSetup.socialUrl || KRUX.socialUrl, params);
        }
      });
    }
};


/* Create a tag with the specified type and append it to the page
 * @tested_with load_script (indirectly)
 */
KRUX.appendTag = function(tagType, attributes) {
    // Create the tag
    var t = doc.createElement(tagType);
    for (var a in attributes) {
        if (typeof a == "string") { // exclude array overrides
            t[a] = attributes[a];
        }
    }

    // Try the head first. It's the most reliable. 
    var h = doc.getElementsByTagName("head");
    if (h && h[0]) {
        h[0].appendChild(t);
    } else {
        // http://paulirish.com/2011/surefire-dom-element-insertion/
        // Changed to use for loop because some browsers would rearrange
        // the subscript (Firefox 4, IE 7)
        var scripts = doc.getElementsByTagName('script');
        for (var i = 0, len = scripts.length; i < len; i++){
            if (scripts[i] && scripts[i].parentNode){
                scripts[i].parentNode.insertBefore(t, scripts[i]);
                break;
            }
        }
    }

    return t;
};


/**
 * Downcast any variable type to a boolean representation. Handy for testing user strings, such as those supplied in a url.
 * !! does most of the work for us.
 * Unit tested with "bool"
 */
KRUX.bool = function(i) {
    if ((typeof i == "string" && i.toLowerCase() === "false") || i === "0") {
        return false;
    } else {
        return !!i;
    }
};


/* Takes the supplied tag and hash of key value pairs and creates an element.
 * TODO: buildElement unit test
 * */
KRUX.buildElement = function(tag, attr) {
    var el = doc.createElement(tag);
    var a = [], kvs = KRUX.keyValues(attr);
    for (var i = 0; i < kvs.keys.length; i++) {
        var n, v;
        try {
          // attribute names can only contain certain characters. lower case just cuz
          n = kvs.keys[i].replace(/[^A-Za-z0-9_]/g, '_').toLowerCase();
          v = KRUX.toS(kvs.values[i]).replace(/"/g, '&quot;');
          el.setAttribute(n, v);
        } catch (e) { } // Something bad, skip this one
    }
    return el;
};


/* Takes the supplied hash of key value pairs and turns it into a string of html attributes.
 * @tested_with build_attributes */
KRUX.buildAttributes = function(attr) {
    var a = [], kvs = KRUX.keyValues(attr);
    for (var i = 0; i < kvs.keys.length; i++) {
        var n, v;
        try {
          // attribute names can only contain certain characters. lower case just cuz
          n = kvs.keys[i].replace(/[^A-Za-z0-9_]/g, '_').toLowerCase();
          v = KRUX.toS(kvs.values[i]).replace(/"/g, '&quot;');
          a.push(n + '="' + v + '"');
        } catch (e) { } // Something bad, skip this one
    }
    return a.join(' ');
};


/**
 * The main data and pixel collection function.  This is the function
 * that must be called to collect pixel request and user data.
 * @tested_with publisher_leakage and pixel.gif
 */
KRUX.collectBeacon = function(j) {

    // Pass the geo data back to us
    var geoConfig = (supertagConfig && supertagConfig.geo) || null;
    if (geoConfig) {
        _parameters.geo_country = geoConfig.COUNTRY;
        _parameters.geo_region = geoConfig.REGION;
        _parameters.geo_city = geoConfig.CITY;
        _parameters.geo_dma = geoConfig.DMA;
    }

    // record load for any tags that we loaded
    if (supertagConfig && supertagConfig.tags) {
        for (var i = 0, l = supertagConfig.tags.length; i < l; i++) {
            // started will be populated for any tag that has been
            if (supertagConfig.tags[i].started) {
                supertag.loadSequence = supertag.loadSequence ? supertag.loadSequence+1: 1;
                _parameters['kplt' + supertag.loadSequence ] = supertagConfig.tags[i].id;
            }
        }
    }

    // Additional data
    _parameters._knifr = win.frames.length;
    _parameters._kpid = KRUXSetup.pubid;
    KRUX.context('s', KRUXSetup.site);
    KRUX.context('sc', KRUXSetup.section);
    KRUX.context('ssc', KRUXSetup.subSection);
    attributes(KRUXSetup.userAttributes, '_kua_');
    attributes(KRUXSetup.pageAttributes, '_kpa_');

    backgroundGet(KRUXSetup.collectUrl || KRUX.collectUrl, _parameters);
};


/* By default, javascript passes by value, UNLESS you are passing a javascript
 * object, then it passes by reference. This function clones a new object.
 * Yes, I could have extended object prototype, but I hate it when people do that.
 * Yes, I tried to use this on Gullo and Nick. It didn't work.
 * @tested_with clone
 */
KRUX.clone = function(obj) {
    if (typeof obj == 'object' && obj !== null) {
        var t = new obj.constructor();
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                t[key] = KRUX.clone(obj[key]);
            }
        }

        return t;
    } else {
        // Some other type (undefined, string, number)
        return obj;
    }
};



// Note, we're using head.js for loading remote
// scripts. It's modified to support executing JS within
// our special spans.
// 
// MODIFIED version - samy kamkar
/**
    Head JS     The only script in your <HEAD>
    Copyright   Tero Piirainen (tipiirai)
    License     MIT / http://bit.ly/mit-license
    Version     0.9
    
    http://headjs.com
*/
(function() { 

var KRUXhead = doc.documentElement,      
     smallwait,
     isDomReady, 
     domWaiters = [],
     queue = [],        // if not -> defer execution
     handlers = {}, // user functions waiting for events
     khscripts = {},      // loadable scripts in different states
     
     isAsync = doc.createElement("script").async === true ||
                "MozAppearance" in doc.documentElement.style ||
                win.opera;        

// public API 
var head_var = win.head_conf && win.head_conf.KRUXhead || "KRUXhead",
     api = win[head_var] = (win[head_var] || function() { api.ready.apply(null, arguments); }); 

// states
var PRELOADED = 0,
     PRELOADING = 1,         
     LOADING    = 2,
     LOADED = 3;
    

// Method 1: simply load and let browser take care of ordering  
if (isAsync) {          
    
    api.js = function() {

        var args = arguments,
             fn = args[args.length -1],
             els = [];

        var node = args[0];
        args = [].slice.call(args, 1);

        if (!isFunc(fn)) { fn = null; }  
        
        each(args, function(el, i) {
                
            if (el != fn) {                 
                el = getScript(el);
                els.push(el);
                                    
                load(node, el, fn && i == args.length -2 ? function() {
                    if (allLoaded(els)) { one(fn, node); }
                        
                } : null);
            }                           
        });
        
        return api;      
    };
    
    
// Method 2: preload    with text/cache hack
} else {
    
    api.js = function() {
            
        var args = arguments;
        var node = args[0];
        var rest = [].slice.call(args, 2),
             next = rest[0];

            
        // wait for a while. immediate execution causes some browsers to ignore caching  
        if (!smallwait) {
            queue.push(function()  {
                api.js.apply(null, args);               
            });
            return api;
        }
        
        // multiple arguments    
        if (next) {             
            
            // load
            each(rest, function(el) {
                if (!isFunc(el)) {
                    preload(node, getScript(el));
                } 
            });         
            
            // execute
            load(node, getScript(args[1]), isFunc(next) ? next : function() {
                api.js.apply(null, rest);
            });
            
            
        // single script    
        } else {                
            load(node, getScript(args[1]));
        }
        
        return api;      
    };      
} 

api.ready = function(key, fn) {
    
    if (key == 'dom') {
        if (isDomReady) { one(fn);  } 
        else { domWaiters.push(fn); }
        return api;
    }
    
    // shift arguments  
    if (isFunc(key)) {
        fn = key; 
        key = "ALL";
    }               
    
    var script = khscripts[key];      
    
    if (script && script.state == LOADED || key == 'ALL' && allLoaded() && isDomReady) {
        one(fn);            
        return api;
    }  
                    
    var arr = handlers[key];
    if (!arr) { arr = handlers[key] = [fn]; }
    else { arr.push(fn); }
    return api;     
};


// perform this when DOM is ready
api.ready("dom", function() {       
    
    if (smallwait && allLoaded()) {
        each(handlers.ALL, function(fn) {
            one(fn);
        });
    }
    
    if (api.feature) {
        api.feature("domloaded", true); 
    }
}); 
    

/// private functions
// call function once
function one(fn, pass) {
    if (fn._done) { return; }       
    fn(pass);
    fn._done = 1;   
}


function toLabel(url) {     
    var els = url.split("/"),
         name = els[els.length -1],
         i = name.indexOf("?");
         
    return i != -1 ? name.substring(0, i) : name;                
}


function getScript(url) {
    
    var script;
    
    if (typeof url == 'object') {
        for (var key in url) {
            if (url[key]) {
                script = { name: key, url: url[key] };
            }
        }
    } else { 
        script = { name: toLabel(url),  url: url }; 
    }

    var existing = khscripts[script.name];
    if (existing && existing.url === script.url) { return existing; }
    
    khscripts[script.name] = script;
    return script;
}


function each(arr, fn) {
    if (!arr) { return; }
    
    // arguments special type
    if (typeof arr == 'object') { arr = [].slice.call(arr); }
    
    // do the job
    for (var i = 0; i < arr.length; i++) {
        fn.call(arr, arr[i], i);
    }
}

function isFunc(el) {
    return Object.prototype.toString.call(el) == '[object Function]';
} 

function allLoaded(els) {        
    
    els = els || khscripts;       
    var loaded = false,
         count = 0;
    
    for (var name in els) {
        if (!els.hasOwnProperty(name)) { continue; }
        if (els[name].state != LOADED) { return false; }
        loaded = true;
        count++;
    }
    return loaded || count === 0;           
}


function onPreload(script) {
    script.state = PRELOADED;

    each(script.onpreload, function(el) {
        el.call();
    });                 
}

function preload(node, script, callback) {
    
    if (!script.state) {
                    
        script.state = PRELOADING;
        script.node = node;
        script.onpreload = [];

        loadScript(script.url, false, function() { onPreload(script); },
            callback, node, 'cache');
    }
} 

function load(node, script, callback) {
    
    if (script.state == LOADED && callback) { 
        return callback(node); 
    }
    
    if (script.state == LOADING) {
        return api.ready(script.name, callback);    
    }
        
    if (script.state == PRELOADING) {           
        return script.onpreload.push(function() {
            load(script, callback); 
        });
    }  
    
    script.state = LOADING; 
    script.node = node;

    loadScript(script.url, false, function() {
        
        script.state = LOADED;
        
        if (callback) { callback(node); }
        
        // handlers for this script
        each(handlers[script.name], function(fn) {
            one(fn, node);
        });
    
        
        if (isDomReady && allLoaded()) {
            each(handlers.ALL, function(fn) {
                one(fn, node);
            });
        }
    });

    return true;
}

setTimeout(function() {
    smallwait = true;
    each(queue, function(fn) { fn(); });        
}, 0);    


KRUX.fireReady = function () {      
    if (!isDomReady) {
        isDomReady = true;
        each(domWaiters, function(fn) {
            one(fn);
        });
    }
};

})(document); // wrapper for head.js


//
//
//
//
// END SK Samy Kamkar END 
// 
//
//
//


/*
 * HTML Parser By John Resig (ejohn.org)
 * Original code by Erik Arvidsson, Mozilla Public License
 * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
 *
 * // Use like so:
 * HTMLParser(passed_argument, htmlString, {
 *     start: function(tag, attrs, unary) {},
 *     end: function(tag) {},
 *     chars: function(text) {},
 *     comment: function(text) {}
 * });
 *
 */

// Please note that the HTML Parser is modified. It still
// parses and behaves the same by default, but allows callbacks 
// to pre-emptively stop parsing (by returning -1), passes the
// current HTML back to all callbacks, and allows passing an
// optional parameter (of anything) to all of the callbacks.

(function(){

// Regular Expressions for parsing tags and attributes (startTag/endTag now support fb:FBML tags)
var startTag = /^<([\w:]+)((?:\s+[\w\-:]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/,
    endTag = /^<\/([\w:]+)[^>]*>/,
    attr = /([\w:]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g;

var makeMap = function(str) {
    var obj = {}, items = str.split(",");
    for ( var i = 0; i < items.length; i++ ) {
        obj[ items[i] ] = true;
    }
    return obj;
};

// Empty Elements - HTML 4.01
var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed");

// Block Elements - HTML 4.01
var block = makeMap("address,applet,blockquote,button,center,dd,del,dir,div,dl,dt,fieldset,form,frameset,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,p,pre,table,tbody,td,tfoot,th,thead,tr,ul");

// Inline Elements - HTML 4.01
var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var");

// Elements that you can, intentionally, leave open
// (and which close themselves)
var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr");

// Attributes that have their values filled in disabled="disabled"
var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected");

// Special Elements (can contain anything)
var special = makeMap("script,style");

KRUX.HTMLParser = function( tmpnode, html, handler ) {
    var inode = tmpnode, unparsed = '', index, chars, match, stack = [], last = html, nested = 0;
    var parser = this;
    stack.last = function(){
        return this[ this.length - 1 ];
    };

    this.parse = function(tmpnode, moreHTML, handler, ignoreStack) {
        inode = tmpnode || inode;
        KRUX.d("stack =" + stack.join(" -> "), 10);
        if (typeof(unparsed) == 'string') {
            moreHTML = unparsed + moreHTML;
        }
        unparsed = ''; 

        last = html = moreHTML;
        KRUX.d("n=" + nested+" stack.last="+stack.last()+" html=" + html, 10);
        while ( html ) {
            chars = true;

            // Make sure we're not in a script or style element
            if ( !stack.last() || !special[ stack.last() ] ) {

                // Comment
                if ( html.indexOf("<!--") === 0 ) {
                    index = html.indexOf("-->");
    
                    if ( index >= 0 ) {
                        if ( handler.comment ) {
                            if (handler.comment( html.substring( 4, index ), inode, html ) == -1) {
                                html = "";
                            }
                        }
                        html = html.substring( index + 3 );
                        chars = false;
                    }
    
                // end tag
                } else if ( html.indexOf("</") === 0 ) {
                    match = html.match( endTag );
    
                    if ( match ) {
                        html = html.substring( match[0].length );
                         KRUX.d("also calling: end tag " + stack.last(), 10);
                        match[0].replace( endTag, parseEndTag );
                        chars = false;
                    }
    
                // start tag
                } else if ( html.indexOf("<") === 0 ) {
                    match = html.match( startTag );
    
                    if ( match ) {
                        html = html.substring( match[0].length );
                        match[0].replace( startTag, parseStartTag );
                        chars = false;
                    }
                }

                if ( chars ) {
                    index = html.indexOf("<");
                    
                    var text = index < 0 ? html : html.substring( 0, index );
                    html = index < 0 ? "" : html.substring( index );
                    
                    if ( handler.chars ) {
                        if (handler.chars( text, inode, html ) == -1) {
                            html = "";
                        }
                    }
                }

            // in a special element
            } else if (html.indexOf('</' + stack.last() + '>') != -1) {
                var tag = stack.last();
                html = html.replace(new RegExp("([^\0]*?)<\/" + stack.last() + "[^>]*>", 'm'), function(all, text){
                    text = text.replace(/<!--(.*?)-->/g, "$1")
                        .replace(/<!\[CDATA\[(.*?)\]\]>/g, "$1");

                    KRUX.d("1stack=" + stack.join(" -> "), 10);
                    if (stack.last() == 'script') { nested++; }
                    if (tag == 'script') { stack.pop(); }
                    KRUX.d("nesting last="+stack.last() + "n="+nested, 10);
                    if ( handler.chars ) {
                        if (handler.chars( text, inode, html, nested, 1) == -1) {
                            html = "";
                        }
                    }
                    if (tag == 'script') { stack.push(tag); }
                    if (stack.last() == 'script') { nested--; }
                    KRUX.d("unnesting last="+stack.last() + "n="+nested, 10);

                    return "";
                });
                KRUX.d("calling: end tag " + stack.last(), 10);
                parseEndTag( "", stack.last() );
            }

            if ( html == last ) {
                unparsed = html;
                KRUX.d("unparsed=" + unparsed, 10);
                return unparsed;
            }
            last = html;
        }
        
        return "";
        // Clean up any remaining tags
        //parseEndTag(); // don't think this should happen with a true doc.write
    };
    this.setUnparsed = function(d) {
        if (typeof(d) == 'string' || typeof(d) == 'number'){
            unparsed += d + "";
        }
        return unparsed;
    };
    if (html.length) {
        this.parse(inode, html, handler);
    }

    function parseStartTag( tag, tagName, rest, unary ) {
        // support for fb:FBML tags
        if ( block[ tagName ] || tagName.toLowerCase().indexOf('fb:') === 0) {
            while ( stack.last() && inline[ stack.last() ] ) {
                KRUX.d("wtf calling: end tag="+tag+" tN="+tagName+" l=" + stack.last(), 10);
                parseEndTag( "", stack.last() );
            }
        }

        if ( closeSelf[ tagName ] && stack.last() == tagName ) {
                KRUX.d("wtf2 calling: end tag " + stack.last(), 10);
            parseEndTag( "", tagName );
        }

        unary = empty[ tagName ] || !!unary;

        if ( !unary ) {
            stack.push( tagName );
        }
        
        if ( handler.start ) {
            var attrs = [];

            rest.replace(attr, function(match, name) {
                var value = arguments[2] ? arguments[2] :
                    arguments[3] ? arguments[3] :
                    arguments[4] ? arguments[4] :
                    fillAttrs[name] ? name : "";
                
                attrs.push({
                    name: name,
                    value: value,
                    escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //"
                });
            });

            KRUX.predbg += ' '; 
            if ( handler.start ) {
                if (handler.start( tagName, attrs, unary, inode, html, parser ) == -1) {
                    html = "";
                }
            }
            if (unary) {
                KRUX.predbg = KRUX.predbg.substr(0, KRUX.predbg.length-1);
            }
        }
    }

    function parseEndTag( tag, tagName ) {
        // If no tag name is provided, clean shop
        var pos = 0;
        // Find the closest opened tag of the same type
        if ( tagName ) {
            for ( pos = stack.length - 1; pos >= 0; pos-- ) {
                if ( stack[ pos ] == tagName ) {
                    KRUX.d("FOUND TAG: "+tagName, 10);
                    break;
                }
            }
        }
        KRUX.d("pet: pos="+pos+" stack: " + stack.join(" -> "), 10);
        
        if ( pos >= 0 ) {
            // Close all the open elements, up the stack
            for ( var i = stack.length - 1; i >= pos; i-- ) {
                if ( handler.end ) {
                    if (handler.end( stack[ i ], inode, html ) == -1) {
                        html = "";
                    }
                    KRUX.predbg = KRUX.predbg.substr(0, KRUX.predbg.length-1);
                }
            }
            
            // Remove the open elements from the stack
            KRUX.d("STACK1: t="+tag+" T="+tagName+" p="+pos+ " l=" + stack.length +": " + stack.join('->'), 10);
            stack.length = pos;
            KRUX.d("STACK2: t="+tag+" T="+tagName+" p="+pos+ " l=" + stack.length +": " + stack.join('->'), 10);
        }
    }
};

})(); // wrapper for html parser

/* Begin Counter Measures. Peew Peew.
 * --------------------------------------------------------------------------------------*/

var counterMeasures = {
    blocklist   : [],
    urlDisabled: false
};
KRUX.CM = counterMeasures;


// Preserve existing functions
doc.__krux_write_real = doc.write;
doc.__krux_writeln = doc.writeln;
doc.__krux_write = function(html)
{
    // only write if we can
    if (KRUX.literate()) {
        return doc.__krux_write_real(html);
    } else {
        if (win.KRUXTest && win.KRUXTest.throwDebugErrors) {
            // Be noisy about this on tests
            throw("FAILED to write to page, it's too late. rs="+doc.readyState+": "+html);
        } else {
            KRUX.d("FAILED to write to page, it's too late. rs="+doc.readyState+": "+html, -1);
            return false;
        }
    }
};

var tagWriter = {
    scriptStack: [],
    insideStack: [],
    scriptParse: 0,
    _src:        undefined
};
KRUX.tagWriter = tagWriter; // possibly unnecessary global promotion

// Override document.write to handle blocked pixels and nested document.writes 
// .docWrite("<b>html code</b>", [ spanNodeCreatedFromScriptCall, [don't pixelBlock] ]) --
// This function is either called from document.write()
tagWriter.setup = function () {
    if ( tagWriter.setup.called ) {
        return;
    }

    tagWriter.setup.called =true;
        
    doc.write = doc.writeln = tagWriter.docWrite = function(html, curnode, attempts) {
    var dontPB = counterMeasures.blocklist.length < 1 || counterMeasures.urlDisabled;

    // don't use tag writer unless we need to
    // confirmed the new tagwriter works on these sites without it.
    if (dontPB && KRUX.literate()) { 
        doc.__krux_write(html);
        return;
    }

    // in case 'html' isn't a string, stringify it (e.g. document.write(3))
    html = html + "";

    // don't write nothin!
    if (html.length === 0) {
        return;
    }

    if (curnode) {
        var oldNode = tagWriter.node;
        var oldInside = inside;
        tagWriter.node = curnode;
        inside.length = 0;
        inside.push(1);
    } else {
        var scripts = doc.getElementsByTagName('script');
        if (scripts.length != tagWriter.scripts) //&& tagWriter.node.nodeName == 'SCRIPT')
        {
            tagWriter.scripts = scripts.length;
            KRUX.d("n9="+tagWriter.node.nodeName, 10);
            tagWriter.node = scripts[scripts.length-1];
            inside.push(0);
            KRUX.d("n10="+tagWriter.node.nodeName, 10);
        }
    }

    
    if (tagWriter.parser) {
        tagWriter.parser.parse(curnode, html, {
           start: PBstart,
           end: PBend,
           chars: PBchars
        });
    } else {
        tagWriter.parser = new KRUX.HTMLParser(curnode, html, {
           start: PBstart,
           end: PBend,
           chars: PBchars
        });
    }

    if (curnode) {
        tagWriter.node = oldNode;
        inside = oldInside;
    }

    return;

    // BEGIN HTML PARSER FUNCTIONS
    function PBstart(tag, attrs, unary, wnode, htmlleft, parser) {
        var a = {}, lt = tag.toLowerCase();
        var html = '<' + lt;

        for (var i = 0, len = attrs.length; i < len; i++)
        {
            if (!dontPB && (attrs[i].name.toLowerCase() == "src" || attrs[i].name.toLowerCase() == "krux_deferred_src") && counterMeasures.checkBL(attrs[i].value))
            {
                a.blocked_src = attrs[i].value;
                a.krux_blocked = "1";
                counterMeasures.recordBlock(attrs[i].value);
            } else {
                if ((attrs[i].name.toLowerCase() == 'krux_deferred_src' || attrs[i].name.toLowerCase() == 'src') && lt == 'script') {
                    tagWriter._src = attrs[i].value;
                }
                a[attrs[i].name] = attrs[i].value;
            }

            // Hide blocked images and iframes
            if (a.krux_blocked && (lt == "img" || lt == "iframe")) {
                a.style = "display: none";          
            }
        }

        KRUX.d("app in="+inside.last(), 10);

        if (tag == 'script')// && a['src'])
        {

            if (a['src'] && KRUX.literate()) {
                KRUX.d('real doc.write: <script src="' + a.src + '"><\/script>', 7);
                doc.__krux_write('<script src="' + a.src + '"><\/script>');
                return;
            } else if (a.src) { // too late to write to page
                // stop parsing until our js is loaded, then resume (save what we haven't parsed)
                KRUX.d('stopping parser, leaving in queue: ' + htmlleft, 8);
                parser.setUnparsed(htmlleft);

                // load script, then resume
                win.KRUXhead.js(undefined, a['src'], function(n) {
                    KRUX.d('resumed ' + parser.setUnparsed());
                    // resume page writing
                    parser.parse(wnode || tagWriter.node, "", {
                       start: PBstart,
                       end: PBend,
                       chars: PBchars
                    });
                });

                // end parsing now
                return;
            } else { 
                tagWriter.scripts++;
            }
        }

        var el = KRUX.buildElement(tag, a);
        KRUX.d("About to append tag=" +tag+ " " + el + " + to " + tagWriter.node, 10);

        var node = tagWriter.node;
        KRUX.d("n11="+tagWriter.node.nodeName+ " in="+inside.last(), 10);
        try {
            if (inside.last()) {
                node.appendChild(el);
            } else {
                node.parentNode.insertBefore(el, node.nextSibling);
            }
        } catch(e) { }
        inside.push(unary ? 0 : 1);
        tagWriter.node = el; 
        KRUX.d("n12="+tagWriter.node.nodeName+ " in="+inside.last(), 10);
    }

    function PBend(tag, wnode, htmlleft) {
        KRUX.d("end1: sp="+tagWriter.scriptParse+" tag="+tag, 7);
        KRUX.d("n1="+tagWriter.node.nodeName + " in="+inside.last(), 10);
        inside.pop();
        if (inside.last()) {
            tagWriter.node = tagWriter.node.parentNode;
        }
        KRUX.d("n2="+tagWriter.node.nodeName, 10);
        KRUX.d("in2="+inside.last(), 10);

        KRUX.d("end: tag="+tag, 7);
    }

    function PBchars(text, wnode, htmlleft, nested, script) {
        KRUX.d("n="+nested+" LEFT2="+htmlleft, 10);
        var node = tagWriter.node;
        KRUX.d("inside=" + inside.last(), 10);
        KRUX.d("crap2 text="+ text + " node="+tagWriter.node+" - "+node.nodeName, 10);

        if (!inside.last()) {
            try {
                tagWriter.node = node.parentNode.insertBefore(doc.createTextNode(text), node.nextSibling);
            } catch(e) { }
        } else {
            try { // non-IE
                inside.push(0);
                node.appendChild(doc.createTextNode(text));
                inside.pop();
                //inside.push(0);
            } // IE
            catch(e) {
                node.text += text; // XXX -- += or = ?
            }
        }
        KRUX.d("type:" + tagWriter.node+"--"+node, 10);
        KRUX.d("n5="+tagWriter.node.nodeName, 10);
        //        tagWriter.node = node;
        KRUX.d("n6="+tagWriter.node.nodeName, 10);
        KRUX.d("chars: text="+text, 9);
    }

  }; // function definition for doc write
};


counterMeasures.addToBL = function(url) {
    counterMeasures.blocklist.push(url.toLowerCase());
};
win.addToBlocklist = counterMeasures.addToBL;
 
 
// public function to determine if a specific pixel is blocklisted or not
// supports a wide range of possible URIs to prevent evasion
// returns 1 or blocklisted, 0 for not
counterMeasures.checkBL = function(url) {

    // faster than case-insensitive regexp
    if (typeof(url) != 'string') {
        return 0;
    }

    // strip off the query string before checking it
    url = url.toLowerCase().replace(/\?.+$/, '');

    for (var i = 0; i < counterMeasures.blocklist.length; i++) {
         // Chop the url down to domain/firstdir before doing the comparison
         var re = new RegExp(counterMeasures.blocklist[i]);
         if (url.match(re)) {
             return 1;
         }
     }
     return 0;
};


counterMeasures.recordBlock = function(url) {
    backgroundGet(KRUX.blockUrl + "?" + buildQueryString({
        "_kpid" : KRUXSetup.pubid,
        "_kcp_s" : KRUXSetup.site,
        "_kcp_sc" : KRUXSetup.section,
        "_kcp_ssc" : KRUXSetup.subSection,
        "_kurl_0" : url
    }));
};


/* End Counter Measures
 * --------------------------------------------------------------------------------------*/

/* This function called as an onload handler for the config call
 * Called with jsonp from the config service */
KRUX.configOnload = function(config) {
    supertagConfig = config;
    KRUX.ST.config = config; //global version

    // Load the blocklist for counter measures
    if (config.blocklist) {
        for (var i = 0; i < config.blocklist.length; i++){
            KRUX.d(config.blocklist[i].company + " added to the blocklist", 3);
            win.addToBlocklist(config.blocklist[i].pattern);
        }
    }


    // Set up publisher parameters
    if (config.params) {
        if ( config.params.max_slot_time !== undefined ) {
            KRUXSetup.pixelTime = config.params.max_slot_time;
        }
        KRUXSetup.autoCollect = KRUX.bool(config.params.capture_leakage);
        KRUXSetup.captureErrors = KRUX.bool(config.params.capture_js_errors);
        KRUXSetup.siteLevel = KRUX.bool(config.params.site_level_supertag_config);
    }

	// Load geo info
    KRUX.ST.loadGeo(config);

    // Turn on the tag writer if they are using pixel blocking or super tag
    if ((config.blocklist && config.blocklist.length > 0)) {
        tagWriter.setup();
    }

    // Write out the pixels
    if (config.tags) {
        KRUX.ST.deliverPixels();
    }

};




/* Send a message to the debug console if available.
 * level is an integer that represents the importance/verbosity.
 *  -2 is for errors
 *  -1 is for warnings
 *  1-9 is for verbosity, 1 being important, 9 being obstreperous
 * obj is optional extra data that will be printed with console.dir if available
 * Note that this will only work for browsers that have a javascript debugging console
 * IE 7+
 * Safari 4+
 * Chrome 5+
 * Firefox 3+ (with Firebug)
 * @tested_with debug
 */
KRUX.d = function(msg, level, obj) {
    // Shortcut variables for the optimizer
    var cons = win.console, test = win.KRUXTest;
    if ( typeof cons != 'object' || // This browser is lame
        level > KRUX.debugLevel){
        return;
    }
    if (test && test.throwDebugErrors && level <= -1) {
        // Fail on a unit test
        throw("KRUX.d reported an error: " + msg);
    }

    if (level <= -2) {
        cons.error ? cons.error('KRUX: ' + msg) : cons.log('KRUX ERROR: ' + msg);
    } else if (level == -1) {
        cons.warn ? cons.warn('KRUX: ' + msg) : cons.log('KRUX WARNING: ' + msg);
    } else if (level <= 1) {
        cons.info ? cons.info('KRUX: ' + msg) : cons.log('KRUX INFO: ' + msg);
    } else {
        cons.log('KRUX DEBUG: ' + KRUX.predbg + msg);
    }

    // Print the data
    obj && cons.dir && cons.dir(obj);
};


/* Begin DART functions 
 * --------------------------------------------------------------------------------------*/

KRUX.DART = {};

/* Take the dart zone and pull out the site/section/subsection. 
 * @tested_with dart
 */
KRUX.DART.parseZone = function(dartZone){
    var out = {};
    var split = dartZone.split("/");
    for (var i = 0; i < split.length; i++){
        switch(i){
          case 0: out.site = split[i]; break;
          case 1: out.section = split[i]; break;
          case 2: out.subSection = split[i]; break;
          default: out["subSection"+(i-1)] = split[i]; break;
        }
    }
    return out;
};

KRUX.DART.keyValues = function(url){
    var out = {}, m;
    var split = url.split(";");
    for (var i = 0; i < split.length; i++){
        // List possible theories for what ascii art this regexp represents:
        // boobs with nipple piercings (Kate)
        // Mathemetician with glasses (Nick)
        m = split[i].match(/(.+)=(.+)/);  
        if (!m) {
            continue;
        }
        if (m[1].length > 25) {
            // We are seeing some weird data.
            continue;
        }
        out[m[1]] = m[2];
    }
    // Exclude noise
    out = KRUX.removeKeys(out, ["sz", "dcopt", /^ord/, "tile", "pos", /uri/, /click/, "ksgmnt", /null/, /undefined/]);

    return out;
};


KRUX.DART.passDartKvsAsPageAttributes = function(additionalExcludes){
    // Look on the page for a dart script or iframe
    var url = KRUX.tagSrcWithMatchingDomain("ad.doubleclick.net", "script") || KRUX.tagSrcWithMatchingDomain("ad.doubleclick.net", "iframe");
    
    if (! url ) {
        // No dart tag on this page
        return {};
    }
    
    var kvs = KRUX.DART.keyValues(url);

    kvs = KRUX.removeKeys(kvs, additionalExcludes);

    for ( var name in kvs ) {
        if (typeof name == "string") {
            KRUX.pageAttribute(name, kvs[name]);
        }
    }
    return kvs;
};
KRUX.passDartToKrux = KRUX.DART.passDartKvsAsPageAttributes;


/* End DART functions 
 * --------------------------------------------------------------------------------------*/

/* Javascript port of krux kuid. Note this must match the perl/python versions
 * @tested_with kuid
 * --------------------------------------------------------------------------------------*/
KRUX.genUid = function() {

    var longMap = 'abcdefghijklmnopqrstuvwxyz' + '0123456789';
    var shortMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' + longMap + '-_';
    // Note that some browsers support string[index], but not all do (at least IE 7), so we use an array
    longMap = longMap.split("");
    shortMap = shortMap.split("");

    var _encode = function(uid, charmap ) {
        /*
        encoding function
        takes the number to convert and the datamap to do the
        conversion with. the base is the length of the charmap
        */
        var l = charmap.length, remainder;
        var out = [];

        KRUX.d("Encoding " + uid + " with " + charmap.toString(), 6);

        while (uid > 0) {
            remainder = uid%l;
            uid = Math.floor(uid/l);
            out.push(charmap[remainder]);
        }

        /*
        reverse the list; this makes the first characters
        match the left side of the time stamp, ie makes them
        sortable by age without decoding
        */
        out.reverse();
        return out.join("");
    };

    var genUidFromInt = function( i ){
        /*
        Generates two UID strings based on the input string, which can
        only exist of 'integers', e.g. the input '297277574676944330'
        will generate the output ('QgJGEOTxXK', 'cjled2q9eycc') in base 
        64 and base36 respectively. In general, the resulting base 64 
        string will be about half the length of the input string.    
        */

        return [_encode(i,shortMap), _encode(i, longMap)];
    };

    /* Generates an 8 character unique ID based on the current
    time to the 10 microsecond level, e.g, 'GvuCz6Dy'.    
    */
    // convert a high precision float time to a string
    // NOTE that javascript precision is only to the millisecond level.
    // Use random for the last 3
    var t = ((new Date()).getTime()/1000) + "" + Math.random().toString().substring(3,6) + "";

    /* remove first and last character, this gives a shorter result
    first won't cycle over for another 30 years
    last is very high precision and unlikely to collide
    */
    var s = t.substring(1, t.length-1);

    // remove the dot
    s = s.replace('.', "");

    return genUidFromInt(parseInt(s, 10));
};

/* For the supplied url or domain, return the 2nd level domain. For example,
 * http://www.google.com returns google.com
 * http://www.google.co.uk returns google.co.uk
 * www.google.com returns google.com
 * @tested_with get_2nd_level_domain
 */
KRUX.get2ndLevelDomain = function(urlOrDomain) {
    var domainMatch = urlOrDomain.match(/https*:\/\/([^:\/]+)/), domain;
    // Url or domain?
    if (domainMatch) {
        domain = domainMatch[1].toLowerCase();
    } else {
        domain = urlOrDomain.toLowerCase();
    }

    // IP address. No, this isn't technically a 2nd level domain, but it's what we meant.
    if (domain.match(/^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$/)){
        return domain;
    }

    // private dns names like localhost (no periods)
    if (domain.indexOf(".") === -1){
        return domain;
    }

    // .co.uk and .com.mx 
    var sldMatchWankersAndBeaners = domain.match(/([\w\-]{1,249}\.com*\.[a-z]{2})$/);
    if (sldMatchWankersAndBeaners) {
        return sldMatchWankersAndBeaners[1];
    }

    // standard domains
    var sldMatch = domain.match(/([\w\-]{1,250}\.[a-z]{2,3})$/);
    if (sldMatch) {
        return sldMatch[1];
    }

    // Huh?
    return false;
};


/* Normalize the language of the browser.
 * FF, Safari, Chrome, Camino use 'language'
 * Opera uses browserLanguage and userLanguage
 * IE uses 'systemLanguage', and 'userLanguage'
 * May vary depending on platform based on what the OS exposes
 * @tested_with broswer_language
 * note that KRUX.forcedLanguage can be set - handy for unit tests.
 */
KRUX.getBrowserLang = function() {
    var n = win.navigator;
    var l = KRUX.forcedLanguage || n.language || n.systemLanguage || n.browserLanguage || n.userLanguage || '';
    return l.substring(0, 2);
};


/* Walk through the DOM and get images that look like pixels, setting them in the parameters */
KRUX.getCollectors = function(scope) {

    var setCollector = function(collector) {
        KRUX.d('Collector found! ' + collector.nodeName + ' ' + collector.src, 2);
        _parameters._knpix = _parameters._knpix ? _parameters._knpix + 1 : 1;
        // Strip off the query string to make the pixel request shorter.
        _parameters['_kpix_' + KRUX.collectors.length] = collector.src.replace(/\?.+/, '');
        KRUX.collectors.push(collector);
    };

    /* It's surprisingly difficult to get the height and width of an image when
     * you consider all the edge cases and variations of
     * A) if the image is still loading
     * and
     * B) f it has the height/width defined in the HTML.
     * Sort that out here (the best we can)
     */
    var getImageDimensions = function(image) {
        // Try the HTML supplied height and width first
        var o = {w: null, h: null};
        var attributes = image.attributes;
        for (var i = 0, len = attributes.length; i < len; i++) {
            if (attributes[i].name.toLowerCase() == 'height') {
                o.h = parseInt(attributes[i].value, 10);
            } else if (attributes[i].name.toLowerCase() == 'width') {
                o.w = parseInt(attributes[i].value, 10);
            }
        }
        if (o.w && o.h) {
            return o;
        }

        // No html supplied height/width
        if (image.complete) {
            // Image is loaded, trust the DOM
            o.w = image.width; o.h = image.height;
            return o;
        } else {
            // Image isn't downloaded, and the height/width wasn't supplied. :(
            return o;
        }
    };

    if (! scope.getElementsByTagName ) {
        // Text node?
        return;
    }

    // IMAGES
    var images = scope.getElementsByTagName("img");

    for (var i = 0, len = images.length; i < len; i++) {
        // Is it offsite?
        if (! KRUX.isOffsiteTag(images[i])) {
            continue;
        }

        // Is it hidden?
        var dim = getImageDimensions(images[i]);
        if (! ((dim.w === 1 && dim.h === 1) || (dim.w === 0 && dim.h === 0))) {
            continue;
        }

        // Looks like a collector, smells like a collector...
        setCollector(images[i]);
    }

    // TODO: Check for Hidden iframes
};



/* Pull parameters from url */
KRUX.getRequestVal = function(varName, defaultVal, qstring) {
    var nvs = KRUX.parseQueryString(qstring || location.search);
    if (varName in nvs) {
        return nvs[varName];
    } else {
        return defaultVal;
    }
};




/* @tested_with background_get */
var backgroundGet = function(url, data) {
    if (data) {
        KRUX.d('GET calling ' + url + ' with: ', 3, data);
        url += '?' + buildQueryString(data);
    } else {
        KRUX.d('GET calling ' + url, 3);
    }
    /* IE <= 6 doesn't support over 4k url length */
    var maxUrlLength = KRUX._is.ie && KRUX._is.version <= 6 ? 4000 : 8000;
    if (url.length > maxUrlLength) {
        KRUX.d('Truncating background get to ' + maxUrlLength + ' characters', 1);
        url = url.substring(0, maxUrlLength);
    }
    (new Image()).src = url;
    KRUX.backgroundGets.push(url);
    return url;
};
KRUX.backgroundGet = backgroundGet;


/* For the supplied key value pairs in data, return a string with names and values url encoded,
 * as a string suitable for 
 * @sep is the separator, '&' by default
 * @tested_with query_string
 */
var buildQueryString = function(data, sep) {
    var pairs = [], v, kvs = KRUX.keyValues(data);
    sep = sep || '&';

    for (var i = 0; i < kvs.keys.length; i++) {
        v = KRUX.toS(kvs.values[i]);
        pairs.push(encodeURIComponent(kvs.keys[i]) + '=' + encodeURIComponent(v));
    }
    return pairs.join(sep);
};
KRUX.buildQueryString = buildQueryString;


/* Set/get cookies. Borrowed from a jquery plugin. Note that options.expires is either a date object,
 * or a number of seconds until the cookie expires
 * @tested_with storage (indirectly)
 */
KRUX.cookie = function(name, value, options) {
    if (arguments.length > 1) { // name and value given, set cookie
        options = options || {};
        if (! value) {
            value = '';
            options.expires = -1;
        }
        var expires = '';
        if (options.expires && (typeof options.expires === 'number' || options.expires.toUTCString)) {
            var d;
            if (typeof options.expires == 'number') {
                d = new Date();
                KRUX.d('Setting cookie expire ' + options.expires + ' seconds from ' + d.toUTCString(), 7);
                d.setTime(d.getTime() + (options.expires * 1000)); // milliseconds
            } else {
                d = options.expires;
            }
            expires = '; expires=' + d.toUTCString(); // use expires attribute, max-age is not supported by IE
        }
        var path = options.path ? '; path=' + (options.path) : '';
        var domain = options.domain ? '; domain=' + (options.domain) : '';
        var secure = options.secure ? '; secure' : '';
        KRUX.d('Set-Cookie: ' + [name, '=', encodeURIComponent(value), expires, path, domain, secure].join(''), 6);
        doc.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
        return true;
    } else { // only name given, get cookie
        if (doc.cookie) {
            var m = doc.cookie.match(new RegExp(name +'=([^;]+)'));
            return m ? unescape(m[1]) : null;
        } else {
            return null;
        }
    }
};


/* For the supplied url encoded query string, return a hash of key/value pairs
 * @tested_with query_string */
KRUX.parseQueryString = function(qs) {
    var o = []; // [] instead of {} so that it has a .length
    if (! qs) { return o; }

    if (qs.charAt(0) === '?') { qs = qs.substr(1); }

    qs = qs.replace(/\;/g, '&').replace(/\+/g, '%20');

    var nvs = qs.split('&');

    for (var i = 0, intIndex; i < nvs.length; i++) {
        if (nvs[i].length === 0) {
            continue;
        }

        var n = '', v = '';
        if ((intIndex = nvs[i].indexOf('=')) != -1) {
            n = decodeURIComponent(nvs[i].substr(0, intIndex));
            v = decodeURIComponent(nvs[i].substr(intIndex + 1));
        } else {
            // No value, but it's there
            n = decodeURIComponent(nvs[i]);
            v = true;
        }

        o[n] = v;
    }

    return o;
};


/* Set up */
KRUX.init = function() {
    // I couldn't figure out how this should be named, so there are two other variances. Shame on me.
    KRUXSetup.subSection = KRUXSetup.subSection || KRUXSetup.subsection || KRUXSetup.sub_section;
    // userData used to be called userAttributes
    KRUXSetup.userAttributes = KRUXSetup.userData || KRUXSetup.userAttributes;

    var d2 = KRUX.get2ndLevelDomain(doc.domain);
    KRUX.context('d', d2);
    KRUXSetup.site = KRUXSetup.site || d2; // default site to 2nd level domain

    if (KRUX.isInAdIframe()) {
        // inside an iframe, use the referrer as the url
        // also, don't pass the referer, because it will be the parent page
        _parameters._inifr = 'true';
    } else {
        // otherwise use the current url as the url
        // Pass the referrer, if we have one.
        if ( doc.referrer ) {
            _parameters._kpref_ = doc.referrer;
        }
    }

    if (KRUXSetup.trackSocial) {
        KRUX.load(KRUX.trackSocial);
    }

    // HACK FIXME TODO to temporarily fix wsj for Firefox <= 3.5
    // which is delivered through the akamai tag
    if (KRUXSetup.pubid == "1c6ac852-8938-481a-84b8-4806affc8c13") {
        KRUXSetup.async = true;
    }

    // Store a first party cookie with kuid
    var kuid = KRUX.S.get("kuid");
    if (! kuid ) {
        kuid = KRUX.genUid()[0];
        KRUX.S.set("kuid", kuid);
    }
    _parameters._kuid = kuid;

    var scripts = doc.getElementsByTagName('script');
    inside.push(0);

    // We track how many scripts we have on a page because we can 
    // determine whether we're doc.write()ing from a new script
    // on the page, or a script we created ourselves.
    // This should allow for asynchronous behaviour...
    tagWriter.scripts = scripts.length;
    tagWriter.node = scripts[scripts.length-1];

};

// Simple stack function used by tagWriter (Samy)
var inside = [];
inside.last = function() { 
            return this[ this.length - 1 ];
};
inside.p = inside.pop;
inside.pop = function() {
    return this.p();
};
inside.s = inside.push;
inside.push = function(x) {
    return this.s(x);
};
KRUX.inside = inside;


/* Set / Get an iframes contents, depending on the number of arguments */
KRUX.iframeContents = function(iframe, html) {

    /* Cross browser method for closing the iframe after it's been created.  */
    var iframeContentsClose = function(iframeid) {
        var idoc = KRUX.iframeDoc(KRUX._(iframeid));
        if (win.addEventListener) {
            // Non IE
            idoc.close();
        } else {
            // Hack for IE. Wait until the readyState changes from "loading" to "interactive"
            if (idoc.readyState == 'interactive') {
                idoc.close();
            } else {
                setTimeout(function() { iframeContentsClose(iframeid); }, 250);
            }
        }
    };

    if (typeof iframe === 'string') {
        iframe = KRUX._(iframe);
    }
    if (! iframe) {
        // This isn't going to work out
        KRUX.d('Unable to use the iframe passed to iframeContents', -1);
        return html === undefined ? false : "";
    }

    var idoc = KRUX.iframeDoc(iframe);

    // Got the handle, now set or get
    if (html === undefined) {
        return idoc.body ? idoc.body.innerHTML : '';
    } else {
        KRUX.d('Filling in iframe with: ' + html, 7);
        // Set the title to be the same as the parent page for tags that need it
        // We also make the KRUX object available in case the tags reference KRUX
        var str = '<html><head><title>' + doc.title + '</title></head>';
        str += '<body><script>window.KRUX = window.parent.KRUX; window.KRUXSetup = window.parent.KRUXSetup;window.KRUXHTTP = window.parent.KRUXHTTP;<\/script>' + html + '</body></html>';
        idoc.open();
        idoc.write(str);
        iframeContentsClose(iframe.id);
        return true;
    }
};



/* Get the dom object for the supplied iframe
 * IE does one way, W3C is another. Sooprise!
 * Thanks to: http://bindzus.wordpress.com/2007/12/24/adding-dynamic-contents-to-iframes/
 */
KRUX.iframeDoc = function(iframe) {
    try {
        if (iframe.contentDocument) {
            // Firefox, Opera
            return iframe.contentDocument;
        } else if (iframe.contentWindow) {
            // IE
            return iframe.contentWindow.document;
        }
    } catch (e) {
        // rut roh
        KRUX.d('Error getting iframe document for this iframe (probably different domain)' + e.msg, 'onerror', -1);
        return false;
    }
};


var tagSource = function(tag) {
    // In some situations IE will fail if you reference tag.src if the url is malformed
    try { 
        return tag.src;
    } catch (e) {
        return null;
    } 
};


/* Emulate PHP's in_array, which will return true/false if a key exists in an array */
KRUX.in_array = function(needle, haystack, ignoreCase) {
    var kvs = KRUX.keyValues(haystack);
    for (var i = 0; i < kvs.keys.length; i++) {
        if (ignoreCase) {
            if (KRUX.toS(kvs.values[i]).toLowerCase() == KRUX.toS(needle).toLowerCase()) {
                return true;
            }
        } else {
            if (kvs.values[i] == needle) {
                return true;
            }
        }
    }

    return false;
};



/* We don't need to see all the errors that come through
 * @tested_with ignored_errors
 */
KRUX.isIgnoredError = function(error) {
    if (KRUX.getBrowserLang() != 'en') {
        // Nothing personal, we just can't read it to debug.
        return true;
    }
    var ignores = [
        (/^Permission denied/), // Cross domain iframe access (frequent)
        (/Error loading script/), // Script doesn't download. Adblock? Down? User?
        (/Script error\./),
        (/Access is denied/)
    ];

    for (var i = 0; i < ignores.length; i++) {
        if (ignores[i].test(error)) {
            return true;
        }
    }

    return false;
};


/* Determine if we are loaded inside an ad unit. Slightly more complex than "is in iframe",
 * because the page may be loaded inside a navigation iframe like http://bitchplease.com 
 * @tested_with in_iframe
 */
KRUX.isInAdIframe = function() {
    if (win.self == win.top ){
        return false;
    } else {
        // Check the pixel squre footage. Biggest Ad Unit I could find was 300x600. If it's bigger
        // then that, assume it's a navigational iframe
        // Of course, every browser is different. And IE is even different depending on
        // quirks mode
        // http://www.howtocreate.co.uk/tutorials/javascript/browserwindow
        var w = 0, h = 0;
        // shortcuts for the optimizer
        var docElement = doc.documentElement;
        var body = doc.body;
        if( typeof( win.innerWidth ) == 'number' ) {
            // Non-IE
            w = win.innerWidth;
            h = win.innerHeight;
        } else if( docElement && ( docElement.clientWidth || docElement.clientHeight ) ) {
            // IE 6+ in 'standards compliant mode'
            w = docElement.clientWidth;
            h = docElement.clientHeight;
        } else if( body && ( body.clientWidth || body.clientHeight ) ) {
            // IE 4 compatible
            w = body.clientWidth;
            h = body.clientHeight;
        }

        return (w * h) <= 180000;
    } 
};


/* Is this particular tag (object) an offsite tag ? */
KRUX.isOffsiteTag = function(tag, domain) {
    var src = tagSource(tag);
    if (! src ) {
        return false;
    }

    if (KRUXSetup.allTagsOffsite) {
        // Handy for unit tests
        return true;
    }

    // Does it start with http?
    if (! src.match(/https*:\/\//)) {
        // Local script. IE does this. Other browsers give full url always
        return false;
    }

    var sld = KRUX.get2ndLevelDomain(domain || doc.domain);
    return KRUX.get2ndLevelDomain(src) != sld;
};


/* User defined key values. If second argument is passed, then set. If not, get.
 * Also allows for access to KRUXSetup[key] and KRUXSetup.userData[key]
 */
KRUX.kv = function(key, value) {
    if (value !== undefined) {
        KRUX._kvs[key] = value;
        return value;
    } else {
        return KRUX._kvs[key] || (KRUXSetup.userData && KRUXSetup.userData[key]) || KRUXSetup[key] || null;
    }
};


/* For the supplied object, return an object with an array of keys, and an associated list of values
 * Why not just use for(var name in obj) ... you ask? Because the various javascript frameworks override
 * the underlying prototypes of these and add functions that interfere with normal looping
 * unit tested with key_values
 */
KRUX.keyValues = function(obj) {
    var keys = [], values = [];
    for (var name in obj) {
        if (obj.hasOwnProperty(name)) {
            keys.push(name);
            values.push(obj[name]);
        }
    }
    return {'keys': keys, 'values': values};
};


/* Function to tell if we can write to the window or not (is it too late for document.write) */
// It's important to note that just because KRUX.readyHasRun == false does NOT mean you can still do a document.write(). This is because FF in 'interactive' mode, the document cannot be 'written' to, NOR is it considered ready. 
KRUX.literate = function(){
    // never write when 'complete' (or 'interactive' on any version of FF, also causes blanking)
    // https://bugzilla.mozilla.org/show_bug.cgi?id=300992
    var readyState = doc.readyState;
    if (readyState == "complete" || readyState == "loaded" || (readyState == 'interactive' && (KRUX._is.gecko || KRUX._is.webkit))) {
        KRUX.d("lit=false, rs="+readyState+", ready="+KRUX.readyHasRun, 10);
        return false;
    }  else {
        KRUX.d("lit=true, rs="+readyState+", ready="+KRUX.readyHasRun, 10);
        return true;
    } 
};


/* KRUX Segment loader
 * @tested_with segments_header and segments_footer
 * --------------------------------------------------------------------------------------*/

var kruxSegments = {
    _kuid: null,
    _segments: null, // Where the segments will be stored
    _responseData: null, // Raw response data
    serviceUrl: (win.KRUXSetup && KRUXSetup.segmentsServiceUrl) || servicesUrl + '/user_data/segments',
    usingCache: false,
    cookieLifetime: 60*60*24*30, // expiry for the cookie, if used
    cacheLifetime: 60*60*4, // check every 4 hours for new user data
    realtimeSegmentLifetime: 60*60*24, // how long to hold on to realtime segments
    loadOptions: {
        html5Storage: true,
        varName: "ksgmnts" 
    }
};
KRUX.Segments = kruxSegments;

kruxSegments.getEngine = function() {
    var engine = kruxStorage.HTML5;
    if (! kruxSegments.loadOptions.html5Storage || !engine.isSupported()){
        engine = kruxStorage.Cookies;
    }
    return engine;
};


/* Public function that the publisher calls on the page. 
 * options contains various settings/flags to alter the behavior
 *      varName
 *      useHtml5Storage
 */
kruxSegments.load = function(options) {

    // Set up options
    options = options || {};
    // TODO: Build the equivalent of jquery's extend
    for (var name in options) {
        if (name in kruxSegments.loadOptions){
            kruxSegments.loadOptions[name] = options[name];
        }
    }

    // Do we have it in cache?
    var age;
    var engine = kruxSegments.getEngine();
    if (engine.get(kruxSegments.loadOptions.varName) !== null){
        KRUX.d("Cached copy of krux segments found in " + engine.name, 6);
        kruxSegments.usingCache = true;
        kruxSegments._segments = engine.get(kruxSegments.loadOptions.varName);
        age = engine.get(kruxSegments.loadOptions.varName + '_lu');
    } 

    kruxSegments._kuid = engine.get("kuid");

    // Fill in realtime segments.
    kruxSegments._segments = kruxSegments.realtimeSegments();

    if (kruxSegments.load.called) {
        // No need to reissue the http request for the second tag that is segment targeted
        return;
    }
    kruxSegments.load.called = true;

    // if no cached copy found, or it's too old, ask the service
    // Note that last updated cookie is a base 36 number of seconds (not ms) since epoch
    if (!age || (new Date()).getTime()/1000 - parseInt(age,36) > kruxSegments.cacheLifetime){
          var u = kruxSegments.serviceUrl + "?" + buildQueryString({
              callback: "KRUX.Segments.callback",
              pubid: KRUXSetup.pubid
          });
          // pass false to load it asynchronously
          loadScript(u, false);
    }
};
KRUX.loadSegments = kruxSegments.load;


kruxSegments.callback = function(response) {
    kruxSegments._kuid = response.kuid;
    if (response.segments) {
        kruxSegments._segments = response.segments.join(",");
    } else {
        return;
    }

    // Cache it for next time, also set a last updated value so we know how long it's been cached.
    // shorten the timestamp by 
    var timeStamp = KRUX.timeStamp();
    var engine = kruxSegments.getEngine();
    engine.set(kruxSegments.loadOptions.varName, kruxSegments._segments, kruxSegments.cookieLifetime);
    engine.set(kruxSegments.loadOptions.varName + '_lu', timeStamp, kruxSegments.cookieLifetime);
    engine.set("kuid", kruxSegments._kuid);
};

/* Handle realtime segments, which evaluate segments from the config service and populate / store them with js */
kruxSegments.realtimeSegments = function() {
    KRUX.d("Filling in realtime segments", 7);
    if ( !supertagConfig || !supertagConfig.segments || !supertagConfig.segments.length) {
        return;
    }
    var out = [];

    for (var i = 0, l = supertagConfig.segments.length; i < l ; i++) {
        KRUX.d("Checking if segment is valid", 8, supertagConfig.segments[i]);
        if (kruxSegments.isValidSegment(supertagConfig.segments[i])) {
            KRUX.d("Segment is valid, storing for this user", 5, supertagConfig.segments[i]);
            out.push(supertagConfig.segments[i].segment_id);
        }
    }
    kruxSegments._rsegments = out.join(',');
   
    // Store it 
    var engine = kruxSegments.getEngine();
    engine.set(kruxSegments.loadOptions.varName + '_r', kruxSegments._rsegments, kruxSegments.realtimeSegmentLifetime);
};


/* Return a combination of the segments from user data service and the realtime segments */
kruxSegments.get = function(){
    var storage = kruxStorage;
    var regularSegments = kruxSegments._segments ||
        (storage.HTML5.isSupported() && storage.HTML5.get(kruxSegments.loadOptions.varName)) ||
        storage.Cookies.get(kruxSegments.loadOptions.varName) || 
        '';
    
    var realtimeSegments = (storage.HTML5.isSupported() && storage.HTML5.get(kruxSegments.loadOptions.varName + "_r")) ||
        storage.Cookies.get(kruxSegments.loadOptions.varName + '_r') ||
        '';

    var out = regularSegments.split(',').concat(realtimeSegments.split(',')).join(',');
    return out.replace(/^,+/, '').replace(/,+$/, '');
};
KRUX.getSegments = kruxSegments.get;

/* Return the krux user id, from wherever we can find it */
kruxSegments.getKuid = function(){
    return kruxSegments._kuid || (kruxStorage.HTML5.isSupported() && kruxStorage.HTML5.get("kuid")) || kruxStorage.Cookies.get("kuid") || "";
};
KRUX.getKuid = kruxSegments.getKuid;


kruxSegments.isValidSegment = function(segment) {

    function getValue(type, name) {
        // Find the selected value. currently only user and page attributes, but someday 3rd party data?
        switch (type) {
          case 'user': return KRUX.userAttribute(name) || '';
          case 'page': return KRUX.pageAttribute(name) || '';
          default: 
            KRUX.d("Unknown segment type - " + type, -2);
            return '';
        }
    }

    // Check the ors
    if (segment.or && segment.or.length) {
        var orMatch = false;
        for (var i = 0, l = segment.or.length; i < l; i++) {
            var orv = getValue(segment.or[i].type, segment.or[i].name);
            KRUX.d("Checking segment - is " + orv + ' in ' + segment.or[i].values, 8);
            if (KRUX.in_array(orv, segment.or[i].values.split(','))){
                orMatch = true;
                break;
            }
        }

        if ( ! orMatch ) {
            KRUX.d('Segment not valid because no OR match found', 6);
            return false;
        }
    }
    
    // Check the ands
    if (segment.and && segment.and.length) {
        for (var i2 = 0, l2 = segment.and.length; i2 < l2; i2++) {
            var andv = getValue(segment.and[i2].type, segment.and[i2].name);
            KRUX.d("Checking segment - is " + andv + ' in ' + segment.and[i2].values, 8);
            if (! KRUX.in_array(andv, segment.and[i2].values.split(','))){
                KRUX.d("Segment criteria not met." + andv + ' is not in ' + segment.and[i2].values, 8);
                return false;
            }
        }
    }
    
    return true;
};
/* --------------------------------------------------------------------------------------*/


/* Simple abstraction layer for cross-browser event handling */
KRUX.listen = function(item, eventName, callback) {
    if (win.addEventListener) { // W3C
        return item.addEventListener(eventName, callback, false);
    } else if (win.attachEvent) { // IE
        return item.attachEvent('on' + eventName, callback);
    } else {
        // What the deuce?!
        return false;
    }
};


/* Functionality equivalent to http://api.jquery.com/ready/, except for load.
 * If the page is already loaded, then run it right now
 * Otherwise, schedule it to run when the page loads
 * This is needed because we may be called after page loading, with asynch
 * loaders like gatling or the (future) async version of our tag
 */
KRUX.load = function(func) {
    if (!KRUX.literate()) {
        func.call();
    } else {
        KRUX.listen(win, "load", func);
    }
};


/* Load javascript from a url
 * inline is a parameter that indicates if the script will be written inline (blocking), or called in the head (non blocking)
 * onloadCallback is an optional function that will be called when the script loads.
 * @tested_with load_script
 */
var loadScript = function(url, inline, onloadCallback, node, type) {
    KRUX.d('loadScript: ' + url + ', inline=' + inline, 10);
    KRUX.loadedScripts.push(url);
    KRUX.loadScript.sequence = KRUX.loadScript.sequence ? KRUX.loadScript.sequence+1: 1;
    var id = "kscript_" + KRUX.loadScript.sequence;
    var attr = {id: id, node: node, type: 'text/' + (type || 'javascript'), src: url.src || url};

    // We need to make sure it's safe to load asynchronously
    if (KRUXSetup.async === false && inline && KRUX.literate()) {
        // This method blocks 
        doc.__krux_write('<script ' + KRUX.buildAttributes(attr) + '><\/script>');
    } else {
        attr.async = "true";
        KRUX.appendTag('script', attr);
    }

    if (typeof onloadCallback == 'function'){
        KRUX.loadScriptCallback(id, onloadCallback, node);
    }
};
KRUX.loadScript = loadScript;

KRUX.loadScriptCallback = function(id, onloadCallback, node){
    var s = KRUX._(id);
    if (! s ) {
        // Try again soon.
        // Some browsers do not immediately allow for document.written elements to be accessed
        // since document.write() may be buffered by the browser
        setTimeout(function(){ KRUX.loadScriptCallback(id, onloadCallback, node); }, 50);
        return;
    }

    // Thanks Steve. http://stevesouders.com/efws/script-onload.php
    s.onreadystatechange = s.onload = function() {

        var state = s.readyState;

        if (!onloadCallback.done && (!state || (/loaded|complete/).test(state))) {
            onloadCallback(node);
            onloadCallback.done = true;
        }
    };
};


/* Load a tag that comes from a krux template. The template definition is 
 * stored in the companies table, and the parameters are a hash input by the user
 * @tested_with template_tag
 */
KRUX.loadTemplateTag = function(templateDefinition, params) {
    // Convert from string to an object
    // Note that we don't use a local function variable here because Closure Compiler eats it,
    // because it doesn't know what's going on in the eval
    KRUX.loadTemplateTag.td = {};
   
    try {
        eval('KRUX.loadTemplateTag.td=' + templateDefinition);
    } catch(e) {
        KRUX.reportError("Error with tag template. " + e.msg + "\n" + templateDefinition, "email-template-tag");
        return;
    }

    // short cut variable for the rest of the function
    var template = KRUX.loadTemplateTag.td;
        
    // If there is a "before" function, run it.
    if (template.before) {
        template.before(params);
    }

    // Replace funtion that changes @@varName@@ into params.varName
    var replacePattern = /@@([^@]+)@@/g;
    var replacer = function(match, varName){
        if (params[varName]) {
            return params[varName];
        } else {
            // not found as a parameter, just leave it alone
            return match;
        }
    };

    // Construct the url
    var protocol = location.protocol, url = '';
    if (protocol == "https:" && template.sslHost ) {
        url += '//' + template.sslHost;
    } else {
        url += 'http://' + template.host;
    }
    var path = template.path.replace(replacePattern, replacer);
    
    url += path;

    // Append the query string
    if (template.query) {
        // Replace any values
        var kvs = KRUX.keyValues(template.query);
        for (var i = 0, l = kvs.keys.length; i < l; i++){
            template.query[kvs.keys[i]] = KRUX.toS(kvs.values[i]).replace(replacePattern, replacer);
        }
        // Does the url already have a query string?
        url += url.indexOf('?') > -1 ? '&' : '?';
        url += buildQueryString(template.query);
    }

    // Load, and call the 'after' when done
    loadScript(url, false, template.after);
};


/* Take the input and return a javascript Date object (if it's not already).
 * Normalizes some cross-browser oddities, notably that Firefox doesn't parse
 * YYYY-MM-DD correctly (screws up the timezone)
 */
KRUX.makeDate = function(input) {
    if (typeof input === 'object' && input !== null && input.constructor == Date) {
        // Already a date;
        return input;
    } else if (KRUX.toS(input).match(/^[0-9]+$/)) {
        // This input is all numbers. Even though Date.parse()
        // can handle the number of milliseconds since epoch and that's valid,
        // supporting numbers introduces a host of other more confusing behavior,
        // like "1234" coming through as the "1234-01-01". Design decision to support
        // date objects and date strings only
        return false;
    // Explicitly test for YYYY-MM-DD, because it behaves differently cross browser, so we take control
    } else if (KRUX.toS(input).match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})/)) {
        var m = KRUX.toS(input).match(/^([0-9]{4})-([0-9]{2})-([0-9]{2})/);
        return new Date(m[1], m[2] - 1, m[3]); // GOTCHA: note that month is 0 based. RETARDED!
    } else if (Date.parse(input)) {
        var d = new Date();
        d.setTime(Date.parse(input) + d.getTimezoneOffset() * 60 * 1000);
        return d;
    } else {
        return false;
    }
};


/* Return the value of the <meta> tag matching name, otherwise null */
KRUX.metaTag = function(name) {
    var metaTags = doc.getElementsByTagName('meta');
    for (var i = 0, len = metaTags.length; i < len; i++){
        if (metaTags[i].name == name) {
            return metaTags[i].content; 
        }
    }
    return null;
};


// Put all onload functions here
KRUX.onload = function() {
    // make sure we don't get called twice
    if (KRUX.onloadCalled) {
        return;
    }
    KRUX.onloadCalled = true;

    _parameters.fired = "load";

    // Background iframe to bump the cookie exp
    if (Math.random() < 0.05) {
        // Call it 1 out of 20 times. What happens if we don't see a user 20 times in 6 months? 
        // They are probably not usable anyway.
        KRUX.appendTag("iframe", {width: 0, height: 0, src: KRUX.kruxIframeUrl + "bumpCookie"}).style.display = "none";
    }

    // If the user registered after load function(s), call them
    if ( KRUX.afterLoadFunctions ){
        for (var i = 0; i < KRUX.afterLoadFunctions.length; i++){
             KRUX.afterLoadFunctions[i]();
        }
    }
    if (KRUXSetup.autoCollect === true) {
        KRUX.getCollectors(doc);
        KRUX.collectBeacon();
    }

    // GC out old storage
    KRUX.Storage.expunge(KRUX.S);
    KRUX.Storage.cleanup(KRUX.S);
};


/* The user may navigate away from the page before the onload event fires. 
 * Pass what we can. Not supported in Opera. */
KRUX.onunload = function() {
    if ( _parameters.fired ) {
        // Pixel already fired by another event
        return;
    }
    _parameters.fired = "unload";
    KRUX.collectBeacon();
};
KRUX.onbeforeunload = function() {
    if ( _parameters.fired ) {
        // Pixel already fired by another event
        return;
    }
    _parameters.fired = "beforeunload";
    KRUX.collectBeacon();
};


/* Use JSON if we can. It's faster and more secure. Borrowed from jQuery */
KRUX.parseJSON = function(data){
    return win.JSON && win.JSON.parse ? win.JSON.parse( data ) : (new Function("return " + data))();
};


/* The KRUX PM class (short for postMessage) implements HTML 5 post message if the browser supports it.
 * Fails silently if not.
 * --------------------------------------------------------------------------------------*/

KRUX.PM = {};

KRUX.PM.supported = function(){
    return ( win.postMessage || doc.postMessage) ? true : false;
};

KRUX.PM.listen = function(handler){
    if (! KRUX.PM.supported()){
        return false;
    }

    return KRUX.listen(win, "message", handler);
};

KRUX.PM.send = function(destWin, data) {
    if (! KRUX.PM.supported()){
        return false;
    }

    var m = KRUX.toS(data);

    if(destWin.postMessage) { // HTML 5 Standard
        return destWin.postMessage(m, '*');
    } else if(destWin.document.postMessage) { // Opera 9
        return destWin.document.postMessage(m, '*');
    }
};
        

/* The KRUX.Provider class handles the code for the various data provider interactions
 * --------------------------------------------------------------------------------------*/

KRUX.Provider = {};

/* The default class for providers. Copy/Extend/Overwrite with KRUX.clone() */
KRUX.Provider.base = {
    id: 'base', // a unique id for this provider
    getUrl: function() { throw 'getUrl not implemented for provider: ' + this.id;},
    checkForNew: 86400,
    checkForUpdate: 86400 * 3,
    blocking: false,
    httpMethod: 'script', // script, iframe, or image
    noData: 'no data' // constant
};


KRUX.Provider.base.cacheKey = function() { return this.id + '_d'; };

KRUX.Provider.base.run = function() {
    if (! KRUX.S.get(this.cacheKey())) {
        KRUX.d('Calling ' + this.id + ' data provider with ' + this.getUrl(), 3);
        var u = this.getUrl();
        if (! u) {
            KRUX.d('Error getting the url for ' + this.id, 1);
            return false;
        }
        if (this.httpMethod == 'script') {
            loadScript(this.getUrl(), this.blocking);
        } else if (this.httpMethod == 'image') {
            backgroundGet(this.getUrl());
        } else if (this.httpMethod == 'iframe') {
            KRUX.appendTag("iframe", {width: 0, height: 0, src: this.getUrl()});
        }
        return true;
    } else {
        KRUX.d('Skipping ' + this.id + ' data provider, already have data.', 5);
        return false;
    }
};

KRUX.Provider.base.callback = function(data) {
    var expires;
    // Different providers have different rules about when the data is refreshed.
    // Sometimes the expiry is different depending on if there is no response vs a response
    if (! data) {
        data = this.noData;
        expires = this.checkForNew;
    } else {
        data = buildQueryString(data);
        expires = this.checkForUpdate;
    }
    KRUX.S.set(this.cacheKey(), data, expires);
};

KRUX.Provider.base.getData = function() {
    var cache = KRUX.S.get(this.cacheKey());
    if (cache == this.noData) {
        return null;
    } else {
        return KRUX.parseQueryString(cache);
    }
};


KRUX.Provider.serviceUrl = function(uuid, type) {
    var prefix = location.protocol ? "https:" : "http:";
    // Don't use // here, because the referrer is in the url for providers
    // and sometimes they need it (bizo, for example)
    return servicesUrl + 'data.' + (type || "gif") + '?_kdpid=' + uuid;
};


/* Functions to handle running when the dom is ready for it, modeled after jQuery's .ready() function 
 * @tested_with domready
 * --------------------------------------------------------------------------------------*/

KRUX.readyFunctions = [];
KRUX.ready = function(fn){
    if (KRUX.readyHasRun){
        // run it now!
        fn();
    } else {
        // Queue it for later
        KRUX.readyFunctions.push(fn);
    }
};


KRUX.readyRun = function(){
    if (KRUX.readyHasRun) {
        return;
    }
    KRUX.readyHasRun = true;
    for (var i = 0; i < KRUX.readyFunctions.length; i++){
        KRUX.readyFunctions[i](win, []);
    }
};


/* Polling check for IE to see if it's ready 
 * http://javascript.nwbox.com/IEContentLoaded/
 */
KRUX.doScrollHack = function(){
    if (KRUX.readyHasRun) {
        return;
    }

    try {
        // If IE is used, use the trick by Diego Perini
        // http://javascript.nwbox.com/IEContentLoaded/
        doc.documentElement.doScroll("left");
    } catch(e) {
        setTimeout(KRUX.doScrollHack, 1 );
        return;
    }

    // and execute any waiting functions
    KRUX.readyRun();
};


// See the bindReady function in jquery for inspiration
// Note that KRUX.readyRun will only be run once, so the idea here is that we should try
// multiple different methods, first one wins.
KRUX.readyBind = function(){
    // No lieutenant, your page is *already* loaded.
    if (doc.readyState == 'complete') {
        // Handle it asynchronously to allow scripts the opportunity to delay ready
        setTimeout(KRUX.readyRun, 1);
        return;
    }

    // Mozilla, Opera, Webkit and IE 8+ support this
    if (doc.addEventListener) { // Safari + this onload handler + Gigya = conflict
        // Cleanup function
        KRUX.DOMContentLoaded = function() {
            doc.removeEventListener("DOMContentLoaded", KRUX.DOMContentLoaded, false);
            KRUX.readyRun();
        };

        KRUX.listen(doc, "DOMContentLoaded", KRUX.DOMContentLoaded);

        // Fallback. Note that readyRun will only be run once.
        KRUX.listen(win, "load", KRUX.readyRun);
    }

    // IE event model
    else if ( doc.attachEvent ) {
        // cleanup function
        KRUX.DOMContentLoaded = function() {
            if (doc.readyState == "complete") {
                doc.detachEvent("onreadystatechange", KRUX.DOMContentLoaded);
                KRUX.readyRun();
            }
        };

        // ensure firing before onload,
        // maybe late but safe also for iframes
        KRUX.listen(doc, 'readystatechange', KRUX.DOMContentLoaded);

        // A fallback to window.onload, that will always work
        win.attachEvent("onload", KRUX.readyRun);

        // If IE and not a frame
        // continually check to see if the document is ready
        var toplevel = false;

        try {
            toplevel = !(!!win.frameElement);
        } catch(e) {}

        if (doc.documentElement.doScroll && toplevel) {
            KRUX.doScrollHack();
        }
    }
};
KRUX.readyBind();

// necessary for KRUXhead
KRUX.ready(KRUX.fireReady);

/* End ready functions
 * --------------------------------------------------------------------------------------*/




/* For the provided array, exclude keys that match. Exclude can be either a string or a regexp */
KRUX.removeKeys = function(map, excludes){
    var out = KRUX.clone(map);
    var kvs = KRUX.keyValues(out);
    for (var i = 0, l = excludes.length; i < l; i++){
        if (typeof excludes[i] == "string") {
            delete(out[excludes[i]]);
        } else {
            // regexp
            for (var i2 = 0, l2 = kvs.keys.length; i2 < l2; i2++){
                if ("test" in excludes[i] && excludes[i].test(kvs.keys[i2])){
                    delete(out[kvs.keys[i2]]);
                }
            }
        }
    }
    return out;
};

/* Report errors back to us */
KRUX.reportError = function(msg, type) {
    if (window.KRUXTest) {
        throw("reportError:" + msg);
    }
    // FIXME: Temporarily disable these until we have a better way of fielding them
    return;
    // wrapped in a try catch block because if this function is reporting an error,
    // all hell breaks loose
    try {

        if ( ! KRUXSetup.captureErrors ) {
            return;
        }

        // Note that the unit tests also track the number of errors
        KRUX.errorCount = KRUX.errorCount || 0;
        KRUX.errorCount++;

        if (KRUX.errorCount > 5) {
            // Don't overwhelm our servers if the browser is stuck in a loop.
            return;
        }

        if (KRUX.isIgnoredError(msg)) {
            KRUX.d('Error message should be ignored, skipping report', 5);
            return;
        }

        if (location.pathname.match(/unit_tests/)){
            return;
        }

        var p = {
            'msg' : msg,
            'type': type || 'general',
            'pubid' : KRUXSetup.pubid,
            'url' : location.href,
            'browser' : KRUX._is.browser,
            'browser_version' : KRUX._is.version,
            'ua' : navigator.userAgent,
            'krux_js_release' : KRUX.release
        };

        if (Math.random() > 0) { // Use this to throttle the errors if we need to
            backgroundGet(KRUX.errorUrl + '?' + buildQueryString(p));
        }

    } catch (e) { }
};
// Only catch/report errors for Firefox. Why?
// It doesn't work for Chrome/Safari, the onerror only catches network script errors.
// IE sucks and doesn't provide good information anyway.
// This causes an error with Gigya's Social Integration. Never saw it in production, originally
// reported by Reuters
if (KRUX.is.mozilla && typeof window.onerror != 'function') { // Don't overwrite the publishers error handling function if they have one
  // Note that I tried to use KRUX.listen and attach an event listener, but the "event"
  // object that is passed in doesn't contain the error message.
  window.onerror = function(msg, url, line) {
    KRUX.reportError('Error on line #' + line + ' of ' + url + ': ' + msg, 'onerror');
    return false; // so that default error handling continues
  };
}


/* Produce a short timestamp when space matters (such as when storing in a cookie) */
KRUX.timeStamp = function(time){
    // First divide by 1000, we don't need subsecond resolution
    time = time || (new Date()).getTime();
    var n = Math.round(time/1000);
    // Base 36
    return n.toString(36);
};

/* Super Tag. Da.ta.dah. Datadadah!
 * --------------------------------------------------------------------------------------*/

/* Module for displaying publisher configured tags, yo.
 @ sold by Wayne
 @ written by Nick
 (in that order)

Old school ascii sequence diagram:
| -      page loading           |
    | - This js downloaded  |
        | - Init called
        | - Config request to get the publishers current tags/blocklist  |
            | - KRUX.configOnload fired (called from config)
                | - Build config
                | - Deliver "asap" pixels
                    ### rest of publishers content loads ###
                    | - Page load fired
                    | - Load "onload" tags
                    | - Fire beacon back to us with stats and activity
More info: https://www.assembla.com/wiki/show/krux_interchange/Super_Tag_Delivery
*/
var supertagConfig = null; 

var supertag = {
    // Note this has to use http instead of '//' until Akamai gets their shit together with https.
    configUrl:      '//cdn.krxd.net/config/',
    chains:         {},
    executedTags:   []
};
// Alias for shorter names internally. External references should use KRUX.SuperTag.
KRUX.ST = supertag;
KRUX.SuperTag = KRUX.ST;


/* GEO */
/* Take a look at the config and our caches to see if we need to make a geo request */
KRUX.ST.loadGeo = function (config) {
    // shortcut variables for the optimizer
    var superTag = KRUX.ST;
    // Do we have one cached with HTML 5 storage?
    if (KRUX.S.name == "HTML5") {
        var geo = KRUX.S.get("kgeo");
        if (geo) {
            KRUX.ST.geoFromCache = true;
            try {
                KRUX.geo = KRUX.parseJSON(geo);
            } catch(e) {
                KRUX.d("Error parsing cached geo data", -2);
                KRUX.S.del("kgeo");
            } 
            return;
        }
    }

    // Bummer. Do we need it to be blocking?
    var geoTagFound = false;
    for (var i = 0; i < config.tags.length; i++) {
        if (config.tags[i].rules) {
            for (var i2 = 0; i2 < config.tags[i].rules.length; i2++) {
                if (config.tags[i].rules[i2].name == "country") {
                    geoTagFound = true; // TODO: break out of outer loop as well.
                    break;
                }
            }
        }
    }

    var gu = KRUXSetup.geoUrl || KRUX.geoUrl;
    if (geoTagFound) {
        // Must be a blocking request so that it is ready for the tag on this page.
        KRUX.loadScript(gu, true, superTag.loadGeoSave);
    } else {
        superTag.geoDeferred = true;
        // Load it in the background with a call back to store it
        if (KRUX.S.name == "HTML5" && win.JSON ) {
        	KRUX.loadScript(gu, false, superTag.loadGeoSave);
        }
    }
};
KRUX.ST.loadGeoSave = function() {
    // Only do this if the browser supports html 5 storage and native json. 
    // too big for a normal cookie and too much of a hassle to stringify JSON
    if (KRUX.S.name == "HTML5" && win.JSON ) {
        // Store it in html 5 storage for a day.
        kruxStorage.HTML5.set("kgeo", win.JSON.stringify(KRUX.geo), 86400);
    }
    KRUX.ST.reportGeoDiscrepancy();
};


KRUX.ST.reportGeoDiscrepancy = function() {
    // Do this 5% of the time 
    if (Math.random() > 0.05) { 
        return;
    }
    
    var data = {name: "geo_akamai_vs_krux"}, key, akam = KRUX.ST.config.geo;
    for (key in akam) {
        data['akamai_' + key] = akam[key];
    }
    for (key in KRUX.geo) {
        data['krux_' + key.toUpperCase()] = KRUX.geo[key];
    }
    
    KRUX.backgroundGet(KRUX.eventUrl, data);
    
};

/* Public function that publishers call on their page.
 */
supertag.callTag = function(format, node) {
    // drop a node to populate later
    if (!node) {
        supertag.sequence = supertag.sequence ? supertag.sequence+1: 1;
        node = 'krux_tag_' + format + '_' + supertag.sequence;
    }

    if (!supertagConfig) {
        KRUX.d('Krux Supertag configuration not available. SuperTag not enabled?' , -1);
        return false;
    }

    KRUX.d('Ad called on page for ' + format, 1);
    var tag = supertag.gimmeTag(format);
    tag.html_id = node;
    if (tag === false) {
        KRUX.reportError('No available tags for this format: ' + format + '. If the format is valid, check to make sure you have a non-frequency capped tag at the bottom of the chain.', 'email-config');
        return false;
    }
    return supertag.writeTag(tag);
};
supertag.callAd = supertag.callTag; // alias


/* Deliver the invisible content */
supertag.deliverPixels = function() {
    KRUX.pixelsDelivered = 0;

    if (!supertagConfig) {
        KRUX.d('Krux Supertag configuration not available. SuperTag not enabled?' , -1);
        return false;
    }
    var pixelsStarted = (new Date()).getTime(), timeSpent = 0;
    // loop through all the available ads for this format
    for (var i = 0; i < supertagConfig.tags.length; i++) {
        timeSpent += ((new Date()).getTime() - pixelsStarted);
        if (timeSpent > KRUXSetup.pixelTime) {
            // TODO report a pixelTimeout
            KRUX.d('Ran out of time for pixels - ' + KRUX.pixelsDelivered + ' delivered', -1);
            return KRUX.pixelsDelivered;
        }

        var tag = supertagConfig.tags[i];
        if (tag.format == '0x0' && supertag.isValidRules(tag) && !tag.started) {
            supertag.writeTag(tag);
            KRUX.pixelsDelivered++;
        }
    }

    return KRUX.pixelsDelivered;
};
// Aliases for backward compatibility
supertag.invisibleTags = function() { }; // No op. Handled with configonload / tag writer now.


/* Return the country from the geo data in the config, using "Unknown" if there is a problem */
supertag.getCountry = function() {
    if (KRUX.getRequestVal('krux_country')) {
        return KRUX.getRequestVal('krux_country');
    } else if (KRUXSetup.forcedCountry){
        return KRUXSetup.forcedCountry;
    }

    return (KRUX.geo && KRUX.geo.country) || "Unknown";
};
// Public reference
KRUX.getCountry = supertag.getCountry;


/* return the next available tag for the supplied format. */
supertag.gimmeTag = function(format) {
    var forcedTag = KRUX.getRequestVal('krux_tagid');

    // loop through all the available ads for this format
    for (var i = 0; i < supertagConfig.tags.length; i++) {
        var tag = supertagConfig.tags[i]; // TODO clone so we aren't passing by reference?

        // If it's a forced tag, don't worry about targeting criteria and make sure it's first
        if (forcedTag && tag.format == format && tag.id == forcedTag) {
            KRUX.d('Using tag #' + forcedTag + ' from krux_tagid');
            return tag;
        } else if (tag.format == format && supertag.isValidRules(tag)) {
            // Correct format, and the rules match
            return tag;
        }
    }
    return false;
};


/* Super tag set up */
supertag.init = function() {

    // shortcut variables for the optimizer
    var supertagSetup = KRUXSetup.superTag, blocking = true;

    if (supertagSetup.enabled !== undefined && !KRUX.bool(supertagSetup.enabled)) {
        return false;
    }

    if (supertagSetup.config) {
        // Allow for the config to be passed in on the page. Handy for unit tests
        KRUX.d('Using config from KRUXSetup', 1);
        KRUX.configOnload(supertagSetup.config);
    } else {
        var cp = {
            pubid: KRUXSetup.pubid,
            site: KRUXSetup.site,
            callback: "KRUX.configOnload"
        };
        if (KRUX.getRequestVal("krux_config") == "unpublished"){
            cp.state = "unpublished";
            cp.cache = "false";
            cp.cb = Math.random();
        }
        var cu = supertag.configUrl + '?' + buildQueryString(cp);
        KRUX.d('Downloading config from ' + cu, 1);

        loadScript(cu, blocking);
    }

    return true;
};


/* For the supplied comparison, look in 'against' to see if we have a match based on the operator
 * against can either be an array or a string with comma separated values
 *
 * In the case of <,>,<=,>= there will be a simple attempt at type conversion. If both values
 * can be processed as numbers, number comparison will be used. If either type is not a string
 * or a number, null will be returned (note that null evaluates to false.
 *
 * For before/after, either a date object or a string in any format that Date.parse can handle.
 * YYYY-MM-DD is recommended. If an invalid date is passed for either before/or after, null will
 * be returned
 */
supertag.isValidCriteria = function(compare, operator, against) {
    KRUX.d('Checking if ' + compare + operator + against, 6);
    // FIXME to use  is instanceof Array instead
    try { // try/catch because against may be "undefined" or "null" and not have a constructor
        if (against.constructor == Array) {
            against = against.toString(); // Converts it to comma separated list - 1,2,3
        }
    } catch (e) { }
    var againstArray = KRUX.toS(against).split(/, */);

    // Data type juggling for lt/gt
    if (KRUX.toS(operator).match(/[<>]/)) {
        if (!isNaN(parseInt(compare, 10)) && !isNaN(parseInt(against, 10))) {
            // Both are numbers, or strings that can be parsed as numbers
            compare = parseInt(compare, 10);
            against = parseInt(against, 10);
        } else if (typeof compare != 'string' || typeof against != 'string') {
            KRUX.d("Invalid comparison. 'compare' is a " + typeof compare + " and 'against' is a " + typeof against, 2);
            return null;
        }
    // Date parsing for before/after
    } else if (KRUX.in_array(operator, ['before', 'after'])) {
        compare = KRUX.makeDate(compare);
        against = KRUX.makeDate(against);
        if (!compare || !against) {
            KRUX.d("Invalid date comparison. Bad date format? 'compare' is " + compare + " and 'against' is a " + against, 2);
            return null;
        }
    }

    switch (operator) {
      case '=': return KRUX.in_array(compare, againstArray, true);
      case '!=': return !KRUX.in_array(compare, againstArray, true);
      case '>': return compare > against;
      case '<': return compare < against;
      case '>=': return compare >= against;
      case '<=': return compare <= against;
      case 'before': return compare.getTime() < against.getTime();
      case 'after': return compare.getTime() > against.getTime();
      case 'matches': 
            var re; 
            try {
                if (KRUX.toS(against).indexOf('/') === 0) {
                    // It's already a regexp, eval it.
                    re = eval(against);
                } else {
                    re = new RegExp(against);
                }
                return re.test(compare);
            } catch (e) {
                KRUX.d("Error with regular expression in matches operator " + against + ": " + e.msg, -2);
                return false;
            }
      case 'contains':
        var compare_s = KRUX.toS(compare);
        for (var i = 0; i < againstArray.length; i++) {
            if (compare_s.indexOf(againstArray[i]) > -1){
                return true;
            }
        }
        return false;
      default: KRUX.d('Unrecognized operator: ' + operator, -1); return null;
    }
};


/* Walk through all the rules and see if they pass */
supertag.isValidRules = function(tag) {
    // shortcut variables for the optimizer
    var isValidCriteria = supertag.isValidCriteria;

    var ruleLength = tag.rules ? tag.rules.length : 0;
    for (var i = 0; i < ruleLength; i++) {
        var r = tag.rules[i];
        KRUX.d('Checking rule for tag#' + tag.id, 7, r);
        switch (r.name) {
          case 'country':
            if (! isValidCriteria(supertag.getCountry(), r.operator, r.value)) {
                tag.rejected = "country";
                return false;
            } else {
                break;
            }
          case 'site':
            if (! isValidCriteria(KRUXSetup.site, r.operator, r.value)) {
                tag.rejected = "site";
                return false;
            } else {
                break;
            }
          case 'section':
            if (! isValidCriteria(KRUXSetup.section, r.operator, r.value)) {
                tag.rejected = "section";
                return false;
            } else {
                break;
            }
          case 'sub_section': /* a little annoying that this is sub_section, but python ... */
            if (! isValidCriteria(KRUXSetup.subSection, r.operator, r.value)) {
                tag.rejected = "sub_section";
                return false;
            } else {
                break;
            }
          case 'segment': 
            KRUX.loadSegments();
            // Segments are slightly different because the user is in multiple, 
            // and the operators don't make sense. 
            var userSegments = KRUX.getSegments().split(","), segmentFound = false;
            // Fill in segments from the url (for troubleshooting)
            var forcedSegment = KRUX.getRequestVal("krux_segment");
            if (forcedSegment) {
                userSegments.push(forcedSegment);
            }
            var tagSegments = KRUX.toS(r.value).split(",");
            for (var i2 = 0; i2 < tagSegments.length; i2++ ){
                for (var i3 = 0; i3 < userSegments.length; i3++ ){
                    if (tagSegments[i2] == userSegments[i3]) {
                        segmentFound = true;
                        break;
                    }
                }
            }
            if (! segmentFound) {
                tag.rejected = "segment"; 
                return false;
            } else {
                break;
            }
          case 'url':
            if (! isValidCriteria(location.href, r.operator, r.value)) {
                tag.rejected = "url";
                return false;
            } else {
                break;
            }
          default:
            if (r.name.match(/^userAttributes/)) {
                var udName = r.name.replace(/^userAttributes[0-9]+\./, '');
                if (! isValidCriteria(KRUXSetup.userAttributes[udName], r.operator, r.value)) {
                    tag.rejected = "userAttributes";
                    return false;
                } else {
                    break;
                }
            } else if (r.name.match(/^pageAttributes/)) {
                var name = r.name.replace(/^pageAttributes[0-9]+\./, '');
                if (! isValidCriteria(KRUXSetup.pageAttributes[name], r.operator, r.value)) {
                    tag.rejected = "pageAttributes";
                    return false;
                } else {
                    break;
                }
            } else {
                KRUX.reportError('Unrecognized rule - ' + r.name, 'email-javascript'); return false;
            }
            
        } // switch rule.name

    } // each rule

    // Check user percent
    if (tag.user_percent) {
        if (Math.random() * 100 > tag.user_percent) {
            tag.rejected = "user %";
            return false;
        }
    }

    // Check start/end dates date
    // A bit hokey, but we're doing a string comparison on the date.
    // Since we control the date format on both sides, (YYYY-MM-DD)
    // Seems to work without the messiness of date parsing
    var today = KRUX.YYYYMMDD();
    if (tag.start_date) {
        if (tag.start_date >= today) {
            tag.rejected = "start date";
            return false;
        }
    }

    // Check end date
    if (tag.end_date) {
        if (tag.end_date < today) {
            tag.rejected = "end date";
            return false;
        }
    }

    // Check frequency cap
    if (tag.freq_cap) {
        var imp = KRUX.S.get('l' + tag.id);
        KRUX.d('Tag #' + tag.id + ' has ' + imp + ' previous impressions', 5);
        if (parseInt(imp, 10) >= parseInt(tag.freq_cap, 10)) {
            tag.rejected = "freq cap";
            return false;
        }
    }

    // Everything is good
    return true;
};


/* For the supplied html, add deferred attributes to the script tags for delayed writers 
 * @depends on tag writer
 * @tested_with deferred_html
 * // TODO what about uppercase "type" and "src" attributes?
 */
supertag.makeHtmlDeferred = function(html) {
    var out = [];
    var onStartTag = function(tagName, attrs, unary) {
        var a = {}, lt = tagName.toLowerCase();
        out.push('<' + tagName);

        // Massage the attributes
        for (var i = 0, len = attrs.length; i < len; i++){
            if ( lt == "script" && attrs[i].name == "src" ){
                a.krux_deferred_src = attrs[i].value;
            } else {
                a[attrs[i].name] = attrs[i].value;
            }
        }
        if (lt == "script" ){
            a.type = "text/krux_delayed_script";
            len = 1;
        }

        // Rebuild the tag
        if (len > 0){
            out.push(' ' + KRUX.buildAttributes(a) + (unary ? '/>' : '>'));
        } else {
            out.push(unary ? '/>' : '>');
        }
    };
    var onEndTag = function(tagName) {
        out.push('</' + tagName + '>');
    };
    var onChars = function(text) {
        out.push(text);
    };

    var parser = new KRUX.HTMLParser(undefined, html, {
           start: onStartTag,
           end: onEndTag,
           chars: onChars
        });

    return out.join('');
};

supertag.recordAttempt = function(tag) {
    KRUX.d('Attempting tag #' + tag.id, 2);
    tag.started = (new Date()).getTime();
    supertag.executedTags.push(tag);
    if (tag.freq_cap) {
        KRUX.S.inc('l' + tag.id, 86400); // frequency caps are 24 hour period
    }
};


/* The behavior of this function depends on the chosen method for tag delivery.
 * TODO: Document
 */
supertag.writeTag = function(tag) {
    KRUX.d("writeTag(" + tag + ") tag.html_id=" + tag.html_id, 4);

    if (!tag.html_id) {
        supertag.sequence = supertag.sequence ? supertag.sequence+1: 1;
        tag.html_id = 'krux_tag_' + tag.format + '_' + supertag.sequence;
        KRUX.d("writeTag(" + tag + ") now tag.html_id=" + tag.html_id, 5);
    }

    if (! KRUX.in_array(tag.timing, ["asap", "ready", "onload"])) {
        // Unrecognized timing, default to asap
        tag.timing = "asap";
    }

    
    // One of our templates?
    if (tag.template_definition) {
        var loadMe = function() {
            supertag.recordAttempt(tag);
            KRUX.loadTemplateTag(tag.template_definition, tag.params || {});
        };
        if ( tag.timing == 'onload' ) {
            KRUX.load(loadMe);
        } else if ( tag.timing == 'ready' ) {
            KRUX.ready(loadMe);
        } else {
            loadMe();
        }
    } else {

        if (tag.method == 'iframe') {
            supertag.writeTagIframe(tag);
        } else {
            supertag.writeTagScript(tag, tag.timing == 'onload');
        }
    }

    return tag.html_id;
};


supertag.writeTagIframe = function(tag) {
    var wh = tag.format.split('x');
    var loadTag = function(){
        supertag.recordAttempt(tag);
        KRUX.iframeContents(tag.html_id, tag.html);
    };
    if (KRUX.literate()) { // The page is still loading
        doc.__krux_write('<iframe ' + KRUX.buildAttributes({
            width: wh[0],
            height: wh[1],
            scrolling: 'no',
            frameborder: 0,
            marginheight: 0,
            marginwidth: 0,
            id: tag.html_id
        }) + '><\/iframe>');

        if (tag.timing == 'onload') {
            // load this tag after page loads
            KRUX.load(loadTag);
        } else if (tag.timing == 'ready') {
            // load this tag after the document is ready
            KRUX.ready(loadTag);
        } else {
            // this should append the iframe to the page immediately, no need to wait
            loadTag();
        }
    } else if (!KRUX.literate() && tag.format == "0x0") {
        // Tag called after page load, but since it's an invisible tag, we can load it
        KRUX.appendTag("iframe", {id: tag.html_id, width: 0, height: 0}).style.display = "none";
        loadTag();
    } else {
        KRUX.d("writeTagIframe called after page load for a visible tag. WTF?", -1, tag);
    }
};


/* Execution flow for script tags:
    page still loading? (safe to document.write)
        is counter measures on, or special case where we want to fix bad tags?
            docwrite a div
            set up tag writer
            write with tag writer
        else
            write with built in doc write
    else
        if 0x0
            create a div and inject it
        else
            No can do. How are we going to know where to put a visible ad after page load?
*/
supertag.writeTagScript = function(tag, onload) {
    supertag.recordAttempt(tag);
    if (KRUX.literate()) {
        // Are we using tag writer or built in doc write?
        if (onload ||
            // Krux counter measures is turned on
            counterMeasures.blocklist.length > 0 ||
            // IE has problems with tags that weren't meant to be delivered through an ad server,
            // such as Blue Kai and Google Analytics
            // Force these tags to use our tag writer to manage proper execution order
            ((KRUX.is.msie || KRUX.is.opera) && tag.html.match(/(__bkframe|google-analytics.com|widgets\.twimg)/))
          ){
            doc.__krux_write('<div id="' + tag.html_id + '">' + supertag.makeHtmlDeferred(tag.html) + '</div>');
            // XXX script onload is not supported. Converted to asap. Only iframe onload is currently supported
            // Do it now!
            tagWriter.setup();
            supertag.loadDeferred();
        } else {
            // No special conditions exist, we can use built in doc write
            doc.__krux_write_real('<div id="' + tag.html_id + '">' + tag.html + '</div>');
        }
    } else {
        // The page has finished loading already, document.write isn't going to work.
        // Instead, create a place holder, and inject it using tagWriter. But only if it's a pixel
        if (tag.format == "0x0") {
            tag.timing = "onload";
            tag.forced_delayed_load = true;
            var div = KRUX.appendTag("div", {id: tag.html_id}).style.display = "none";
            div.innerHTML = supertag.makeHtmlDeferred(tag.html);
            supertag.loadDeferred();
        } else {
            KRUX.d("writeTagScript called after page load for a visible tag. WTF?", -1, tag);
        }
    }
};


supertag.loadDeferred = function() {
    KRUX.d("loading deferred scripts", 5);
    var s = doc.getElementsByTagName('script');
    for (var i = 0, len = s.length; i < len; i++)
    {
        var oldScript = s[i];
        if (oldScript.getAttribute && oldScript.getAttribute('type') == 'text/krux_delayed_script')
        {
            var script = oldScript.cloneNode(true);
            script.removeAttribute('krux_deferred_src');
            script.removeAttribute('language');
            script.setAttribute('type', 'text/javascript');
            KRUX.d('got krux_delayed_script (s[' + i + '])', 8);

            tagWriter.node = script;
            inside.length = 0;
            inside.push(0);
            if (oldScript.text && !oldScript.getAttribute('krux_deferred_src')) {
//h4w -- not working properly? what if doc.write is called?
                oldScript.setAttribute('type', 'text/javascript');
                eval(oldScript.text);
            }
            // remote script
            else
            {
                // if the script doesn't load in 20 secs (configurable), move on
                script.timer = setTimeout((function(s)
                {
                    return function() {
                        KRUX.d("timed out getting " + s.src);
                        if (!s.done)
                        {
                            s.done = true;
                            supertag.loadDeferred();
                        }
                    };
                })(script), KRUX.scriptTimeout || 20000);

                script.onreadystatechange = script.onload = function() {
                    var state = script.readyState;
                    if (!script.done && (!state || (/loaded|complete/).test(state))) {
                        clearTimeout(script.timer);
                        script.done = true;
                        supertag.loadDeferred();
                    }
                };

                if (oldScript.getAttribute('krux_deferred_src')) {
                    script.setAttribute('src', oldScript.attributes.krux_deferred_src.value);
                }
                oldScript.parentNode.replaceChild(script, oldScript);
                return; // the timer or script callback will move onto the next scripts
            }
        }
    }
};



/* END Super tag
 * --------------------------------------------------------------------------------------*/

/* Client side storage library. Uses HTML 5's storage engine when available, cookies as a fallback,
 * and finally an in memory page only version if cookies are not enabled.
 * See https://www.assembla.com/wiki/show/krux_interchange/Client_Side_Storage
 *
 * Others engines could be added including flash, Internet Explorer's userData, and webkit's sqllite
 *
 * Usage:
 * var storage = KRUX.Storage.getEngine();
 *
 * set it
 * storage.set(key, value, expires);
 *
 * later...
 * storage.get(key);
 *
 * Shortcuts:
 * KRUX.S.get(key);
 *
 * --------------------------------------------------------------------------------------*/


KRUX.Storage = {
    availableEngines: [],

    /* Factory. Figure out which storage engine to use and return it */
    getEngine : function() {
        for (var i = 0; i < KRUX.Storage.availableEngines.length; i++) {
            if (KRUX.Storage.availableEngines[i].isSupported()) {
                return KRUX.Storage.availableEngines[i];
            }
        }
        KRUX.d('No Supported storage engines', -2);
        return false;
    },


    // Clear any keys that don't return a value (they are expired)
    expunge : function(engine) {
        var keys = engine.keyList();
        for (var i = 0; i < keys.length; i++) {
            if (engine.get(keys[i]) === null) {
                engine.del(keys[i]);
            }
        }
    },

    /* Clean up any data that was stored in the old format.
     * Instituted on 07/18/2011, safe to remove after one month
     */
    cleanup : function(engine) {
        var keys = engine.keyList();
        for (var i = 0; i < keys.length; i++) {
            var match = engine.get(keys[i]).match(/\|~([0-9]+)$/);
            if (match) {
                // Delete the existing key, and re add it with an expiration
                var value = engine.get(keys[i]).replace(/\|~([0-9]+)$/, '');
                engine.del(keys[i]);
                // existing time stamps are the number of seconds since midnight
                var expires = (match[1] - (new Date()).getTime()) / 1000;
                engine.set(keys[i], value, expires);
            }
         }
    }

}; // KRUX.Storage
// Shortcut for the optimizer
var kruxStorage = KRUX.Storage;

/* ----------------- Storage Engines ---------------------- */
/* New engines must include the following methods:
 * get(key)
 * set(key, value, expires = never)
 * del(key)
 * inc(key, expires = never)
 * count()
 * clear()
 * keyList()
 *
 * And the following properties
 * name (name of the storage engine). Must be unique
 */

/* --- HTML 5 --- */
kruxStorage.HTML5 = { 
    name: 'HTML5',

    isSupported : function(key) {
        if (win.localStorage) {
            // HTML 5 supported
            KRUX.d('HTML 5 storage engine supported', 6);
            return true;
        } else {
            KRUX.d('HTML 5 storage engine not supported', 5);
            return false;
        }
    },

    get : function(key) {
        // is there another value with __exp that contains the expiration date? Note that exp is stored as a base 36 number from KRUX.timeStamp
        var exp = win.localStorage.getItem(key + '__exp');
        if (exp && exp < KRUX.timeStamp()) {
            return null;
        } else {
            return win.localStorage.getItem(key);
        }
    },

    set : function(key, value, expires) {
        // Safari in private browsing mode 5.0.5 might fatal error with "QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota." without try/catch
        try {
            win.localStorage.setItem(key, KRUX.toS(value));
            if (expires) {
                win.localStorage.setItem(key + '__exp', KRUX.timeStamp((new Date()).getTime() + expires * 1000));
            }
        } catch(e) { }
    },

    del : function(key) {
        win.localStorage.removeItem(key + '__exp');
        return win.localStorage.removeItem(key);
    },

    inc : function(key, expires) {
        var c = kruxStorage.HTML5.get(key) || 0;
        c++;
        kruxStorage.HTML5.set(key, c, expires);
        return c;
    },

    count : function() {
        return kruxStorage.HTML5.keyList().length;
    },

    clear : function() {
        return win.localStorage.clear();
    },

    keyList : function() {
        var o = [], storage = win.localStorage;
        for (var i = 0; i < storage.length; i++) {
            if (! storage.key(i).match(/__exp$/)) {
                o.push(storage.key(i));
            }
        }
        return o;
    }
}; // KRUX.Storage.HTML5 {

// Register the engine
kruxStorage.availableEngines.push(kruxStorage.HTML5);

/* --- Cookies--- */

kruxStorage.Cookies = {
    name: 'Cookies',
    cookieName: 'KRUXS',
    cookieExpires: 60 * 60 * 24 * 30,

    isSupported : function(key) {
        if (navigator.cookieEnabled) {
            KRUX.d('Storage: cookies enabled', 6);
            return true;
        } else {
            KRUX.d('Storage: cookies not enabled', 4);
            return false;
        }
    },

    get : function(key) {
        var data = kruxStorage.Cookies._getData();
        if (key in data) {
            var exp = data[key + '__exp'];
            if (exp && exp < KRUX.timeStamp()) {
                return null;
            }
            return data[key];
        } else {
            return null;
        }
    },

    set : function(key, value, expires) {
        var data = kruxStorage.Cookies._getData();
        data[key] = KRUX.toS(value);
        if (expires) {
            data[key + '__exp'] = KRUX.timeStamp((new Date()).getTime() + expires * 1000);
        }
        KRUX.cookie(kruxStorage.Cookies.cookieName, buildQueryString(data));
    },

    del : function(key) {
        var data = kruxStorage.Cookies._getData();
        var out = KRUX.removeKeys(data, [key, key + '__exp']);
        KRUX.cookie(kruxStorage.Cookies.cookieName, buildQueryString(out));
    },

    inc : function(key, expires) {
        var c = kruxStorage.Cookies.get(key) || 0;
        c++;
        return kruxStorage.Cookies.set(key, c, expires);
    },

    count : function() {
        return KRUX.keyValues(kruxStorage.Cookies.keyList()).keys.length;
    },

    clear : function() {
        KRUX.cookie(kruxStorage.Cookies.cookieName, null);
    },

    keyList : function() {
        var kvs = KRUX.keyValues(KRUX.removeKeys(kruxStorage.Cookies._getData(), [(/__exp$/)]));
        return kvs.keys;
    },

    _getData : function() {
        var cookie = KRUX.cookie(kruxStorage.Cookies.cookieName);
        if (cookie === null) {
            return {};
        } else {
            return KRUX.parseQueryString(cookie);
        }
    }
}; // KRUX.Storage.Cookies {

// Register the engine
kruxStorage.availableEngines.push(kruxStorage.Cookies);

/* --- Dummy (if they have an html 4 or lower browser, and cookies turned off--- */
kruxStorage.Dummy = {
    name: 'Dummy',
    store: {},
    isSupported : function() {return true;},

    set : function(key, value, expires) { 
        kruxStorage.Dummy.store[key] = KRUX.toS(value);
        if (expires) {
            kruxStorage.Dummy.store[key + '__exp'] = KRUX.timeStamp((new Date()).getTime() + expires * 1000);
        }
    },

    get : function(key) {
        if (kruxStorage.Dummy.store[key] !== undefined) {
            var exp = kruxStorage.Dummy.store[key + '__exp'];
            if (exp && exp < KRUX.timeStamp()) {
                return null;
            }
            return kruxStorage.Dummy.store[key];
        } else {
            return null;
        }
    },

    del : function(key) { kruxStorage.Dummy.store = KRUX.removeKeys(kruxStorage.Dummy.store, [key, key + '__exp']); },
    inc : function(key, expires) {
        var val = kruxStorage.Dummy.get(key) || 0;
        val++;
        kruxStorage.Dummy.set(key, val, expires);
    },

    count : function() { return KRUX.keyValues(kruxStorage.Dummy.keyList()).keys.length;},
    clear : function() { kruxStorage.Dummy.store = {}; },

    keyList : function() {
        var o = [];
        for (var key in KRUX.removeKeys(kruxStorage.Dummy.store, [(/__exp$/)])) {
            o.push(key);
        }
        return o;
    }

}; // KRUX.Storage.Dummy {

// Register the engine
kruxStorage.availableEngines.push(kruxStorage.Dummy);

// shortcut variable 
KRUX.S = kruxStorage.getEngine();
/* END Storage
 * --------------------------------------------------------------------------------------*/


/* For the supplied domain, look through all the tags of tag_type and
*  see if the src attribute matches. If so, return the url. No matches returns null.
*  domain can either be a string or regexp
*  TODO: unit test
*/
KRUX.tagSrcWithMatchingDomain = function (domain, tag_type) {
    var tags = doc.getElementsByTagName(tag_type);
    for (var i = 0, l = tags.length; i < l; i++){
        if (! tags[i].src) {
            continue;
        }
        // Only use the first part of the url
        var urlDomainMatch = tags[i].src.match(/^http:\/\/([^\/]+)/);
        if (! urlDomainMatch ) {
            // Relative url
            continue;
        }

        // Note that somebrowsers have regexp as object, others as function
        if (typeof domain != "string" && domain.test(urlDomainMatch[1])) {
            return tags[i].src;
        } else if (typeof domain == "string" && urlDomainMatch[1].indexOf(domain) > -1){
            return tags[i].src;
        }
    }
    return null;
};


/* For the supplied value, force convert it to string. Functionality that goes beyond toString():
 * .toString() doesn't work on null and undefined
 */
KRUX.toS = function(value) {
    if (typeof value === 'string') {
        return value;
    } else if (value === null) {
        return 'null';
    } else if (value === undefined) {
        return 'undefined';
    } else if (typeof value == 'object') {
        return '[object Object]';
    } else {
        return '' + value;
    }
};


/* For the supplied document, return the url to use for determining the domain
 * @tested_with url_for_domain
 */
KRUX.urlForDomain = function(doc) {
    if (KRUX.isInAdIframe()) {
        // inside an iframe, use the referrer as the url
        // also, don't pass the referer, because it will be the parent page
        return doc.referrer;
    } else {
        // If the page has a <base href> tag, then use that. This is for search
        // engine caches like webcache.googleusercontent.com
        var baseTags = doc.getElementsByTagName("base");
        for (var i = 0, len = baseTags.length; i < len; i++ ) {
            if (baseTags[i].href) {
                return baseTags[i].href;
            }
        }

        // No base href tag, just use the url
        return doc.URL;
    }
};


// Return a date in YYYY-MM-DD format. If no date object is supplied, assume now
KRUX.YYYYMMDD = function(date) {

    var padString = function(str) {
        return str.toString().length == 1 ? '0' + str : str;
    };

    if (! date) {
        date = new Date();
    }
    return date.getFullYear() + '-' + padString((date.getMonth() + 1)) + '-' + padString(date.getDate());
};

// make necessary variables global
win.KRUX = KRUX;
win.KRUXHTTP = KRUX; // these two objects are now one, but we have public references to KRUXHTTP

// the end of the function definition for (function( KRUXSetup, win, doc) { *the one at the top*
})(window.KRUXSetup, window, document);

} // if window.KRUX is already set up, and it's not being pulled in with ?krux_release


// ******** Set up (ok to be called twice) ***********
if (window.KRUX) {

  (function(KRUX, KRUXSetup, win, doc) {
    KRUX.debugLevel = KRUX.getRequestVal('krux_debug', 0);
    if (win.krux_js) {
        KRUX.d('Using javascript from ' + win.krux_js);
    }

    var getRequestVal = KRUX.getRequestVal;
    // Pull the values from the url.
    // Could have been dynamic in the loop above, but I wanted more control
    KRUXSetup.pubid = getRequestVal('krux_pubid', KRUXSetup.pubid);
    KRUXSetup.site = getRequestVal('krux_site', KRUXSetup.site);
    KRUXSetup.section = getRequestVal('krux_section', KRUXSetup.section);
    KRUXSetup.subSection = getRequestVal('krux_subSection', KRUXSetup.subSection);
    KRUXSetup.channels = getRequestVal('krux_channels', KRUXSetup.channels);
    KRUXSetup.autoCollect = KRUX.bool(getRequestVal('krux_autoCollect', KRUXSetup.autoCollect));
    KRUXSetup.autoInit = KRUX.bool(getRequestVal('krux_autoInit', KRUXSetup.autoInit));
    KRUXSetup.superTag = KRUXSetup.superTag || {};
    // KRUX.literate should be enough, but if gatling exists, that also causes problems
    if (win.jQuery && jQuery.gatling) {
        KRUXSetup.async = true;
    }

    if (getRequestVal('krux_block')) {
        win.addToBlocklist(getRequestVal('krux_block'));
    }

    // clear cookies, etc
    getRequestVal('krux_twinkie') && KRUX.S.clear();

    if ( ! KRUXSetup.pubid) {
        KRUX.d('KRUX setup error: missing pubid in KRUXSetup', -2);
    } else {
        // We're good. Set up
        if (KRUX.bool(KRUXSetup.autoInit) === true) {
            KRUX.init();
            KRUX.ST.init();
        }
        if (KRUX.bool(KRUXSetup.loadSegments)) {
            KRUX.loadSegments();
        }
    }

    KRUX.listen(win, 'beforeunload', KRUX.onbeforeunload);
    KRUX.listen(win, 'unload', KRUX.onunload);
    KRUX.load(KRUX.onload);

  // the end of the function definition for (function(KRUX, KRUXSetup, win) {
  })(window.KRUX, window.KRUXSetup, window);

} // \ if window.KRUX

