/* Simple <strike>Ad Server</strike> Ad Network Optimizer written in javascript.
 * http://staff.wikia-inc.com/wiki/Athena
 *
 * Dependencies
 * jQuery for browser detection (optional)
 * YUI will optionally be used for console logging, if available and called with athena_debug
 */

var Athena = {
	baseUrl 	: 'http://athena-ads.wikia.com/athena/',
	chain 	  	: [],
	errors  	: [],
	geoUrl		: "/__varnish_geoip",
	loadDelay 	: 250,
	maxHops 	: 5,
	maxHopTime 	: 2500, // In milliseconds
	now		: new Date(),
	pageVars 	: [],
	statMax 	: 50,
	tagErrors 	: [],
	rejTags 	: []
};

/* Methods are in alphabetical order. See Athena.init for a starting point. */

Athena.$ = function(id){
	return document.getElementById(id);
};

/* Simple abstraction layer for event handling across browsers */
Athena.addEventListener = function(item, eventName, callback){
	// TODO: use jQuery if it's available
	if (window.addEventListener) { // W3C
		return item.addEventListener(eventName, callback, false);
	} else if (window.attachEvent){ // IE 
		return item.attachEvent("on" + eventName, callback);
	}
};


Athena.beaconCall = function (url){
	// Create an image and call the beacon
	var img = new Image(0, 0);
	// Append a cache buster
	img.src = url + '&cb=' + Math.random().toString().substring(2,8);
};


/*  Set up chain for slot
 *    a) See if there is a "sampled ad" to try
 *    b) Check against targeting criteria
 *    c) Check against frequency/rejection capping criteria
 *    d) Consider the maximum number of hops, have the last one be a garaunteed fill
 */
Athena.buildChain = function(slotname) {

	if (! window.AthenaConfigLoaded) {
		Athena.d("Ads not served for " + slotname + " because config not downlaoded");
		// Config isn't loaded. They have Ad Block on?
		return false;
	}

	Athena.config = window.AthenaConfigLoaded();
	Athena.slotTimer = Athena.slotTimer || [];

	var size = Athena.getSizeForSlotname(slotname);

	// Start the timer;
	var now = new Date();
	Athena.slotTimer[slotname] = now.getTime();

	var networks = [];
	Athena.chain[slotname] = [];

	// Do we have this slot?
	if (Athena.e(Athena.config.sizes[size])){
		throw "Unrecognized size in Athena: " + size;
	}

	// Sort the chain. Done client side for better caching and randomness
	Athena.config.sizes[size].sort(Athena.chainSort);


	// Build the chain
	for (var i = 0, l = Athena.config.sizes[size].length; i < l; i++){
		var t = Athena.clone(Athena.config.sizes[size][i]);
		if (!Athena.in_array(Athena.getConfigAlias(slotname), t.slotnames)){
			continue;
		}

		if (Athena.isValidCriteria(t)){
			Athena.config.sizes[size][i]['inChain'] = true;
			Athena.chain[slotname].push(t);	
			networks.push(t["network_name"] + ", #" + t["tag_id"]);

			if (t['guaranteed_fill'] === 'Yes'){
				Athena.d("Chain complete - last ad is garaunteed.", 2, networks);
				return true;
			} else if (Athena.chain[slotname].length == Athena.maxHops - 1){
				// Chain is full
				break;
			}
		}
	}
	
	// Guaranteed ad.
	var gAd = Athena.getGuaranteedAd(size);
	Athena.chain[slotname].push(gAd);
	networks.push("Guaranteed: " + gAd["network_name"] + ", #" + gAd["tag_id"]);

	// Sampled ad
	var sampledAd = Athena.getSampledAd(size);
	// Business rule: Don't do sampling if a tier 10 ad is present (exclusive)
	if (sampledAd !== false && Athena.isValidCriteria(sampledAd) && Athena.chain[slotname][0]['tier'] != "10"){
		// HACK: No easy way to put an element on to the beginning of an array in javascript, so reverse/push/reverse
		Athena.chain[slotname].reverse();
		Athena.chain[slotname].push(sampledAd);	
		Athena.chain[slotname].reverse();
		networks.push("Sampled: " + sampledAd["network_name"] + ", #" + sampledAd["tag_id"]);
	}


	Athena.d("Chain for " + slotname + " = ", 3, networks);
	return true;
};


/* Build up a query string from the supplied array (nvpairs). Optional separator, default ';' */
Athena.buildQueryString = function(nvpairs, sep){
	if (Athena.e(nvpairs)){
		return '';
	}
	if (Athena.e(sep)){
		sep = '&';
	}

	var out = '';
	for(var name in nvpairs){
		if (Athena.e(nvpairs[name])){
			continue;
		}
		out += sep + name + '=' + escape(nvpairs[name]);
	}

	return out.substring(sep.length);
};


/* Call an ad and execute it. */
Athena.callAd = function(slotname, iframe){

	var t = Athena.getNextTag(slotname);

	var loadDivId = slotname + '_' + t["tag_id"];

	// Clear other load divs for the current slot
	Athena.clearPreviousIframes(slotname);

	Athena.d("Ad #" + t["tag_id"] + " for " + t['network_name'] + " called in " + slotname);
	Athena.d("Config = ", 6, t);

	try { // try/catch block to isolate ad tag errors

		if (!Athena.e(iframe)){
			Athena.callIframeAd(slotname, t);
		} else {
			Athena.slotname = slotname; // Capture this for ads that need the slotname
			document.write('<div id="' + loadDivId + '">' + t["tag"] + "</div>");
		}
	} catch (e) {
		Athena.tagErrors = Athena.tagErrors || [];
		Athena.tagErrors.push([t['tag_id'], Athena.print_r(e)]);
		Athena.d("Error loading tag: ", 0, e);
	}

	return true;
};

Athena.callIframeAdDirect = function(slotname){
	var t = Athena.getNextTag(slotname);
	var adIframe = Athena.$(slotname + "_iframe");
	Athena.callIframeAd(slotname, t, adIframe);
};

Athena.callIframeAd = function(slotname, tag, adIframe){
			
	var iframeUrl = Athena.getIframeUrl(slotname, tag);
	if (iframeUrl == "about:blank"){
		Athena.d("Skipping No iframe ad called for No Ad for " + slotname, 3);
		return;
	}

	if (typeof adIframe == "object"){
		// Iframe passed in, use it
		adIframe.src = iframeUrl;
	} else {
		// Otherwise, create one and append it to load dive
		adIframe = document.createElement("iframe");
		var s = tag["size"].split("x");
		adIframe.src = iframeUrl;
		adIframe.width = s[0];
		adIframe.height = s[1];
		adIframe.scrolling = "no";
		adIframe.frameBorder = 0;
		adIframe.marginHeight = 0;
		adIframe.marginWidth = 0;
		adIframe.allowTransparency = true; // For IE
		adIframe.id = slotname + '_' + tag["tag_id"];
		Athena.getSlotLoadDiv(slotname).appendChild(adIframe);
	} 

};


/* Sort the chain based on the following criteria:
 * tier, weighted_random
 * The idea behind weighted_random is that we want to sort items
 * within the same tier randomly (to take advantage of cream skimming)
 * But we also want to favor the higher paying ads
 */
Athena.chainSort = function(a, b){
	var a_tier = parseInt(a['tier'], 10) || 0;
	var b_tier = parseInt(b['tier'], 10) || 0;
	if (a_tier > b_tier){
                return -1;
        } else if (a_tier < b_tier){
                return 1;
        } else {
                // Same tier, sort by weighted random
                var a_weight = Math.random() + (parseFloat(a['value']) || 0);
                var b_weight = Math.random() + (parseFloat(b['value']) || 0);
                return b_weight - a_weight;
        }
};


Athena.clearPreviousIframes = function(slotname){
	var loadDiv = Athena.getSlotLoadDiv(slotname);
	if (loadDiv === null){
		return false;
	}

	var iframes = loadDiv.getElementsByTagName("iframe");
	for (var i = 0, l = iframes.length; i < l; i++){
		iframes[i].style.display = "none";
	}

	return true;
};


/* By default, javascript passes by value, UNLESS you are passing a javascript or 
 * object, then it passes by reference.
 * Yes, I could have extended object prototype, but I hate it when people do that */
Athena.clone = function (obj){
	if (typeof obj == "object"){
		var t = new obj.constructor(); 
		for(var key in obj) {
			t[key] = Athena.clone(obj[key]);
		}

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


Athena.cookie = function(name, value, options) {
    if (arguments.length > 1) { // name and value given, set cookie
        options = options || {};
        if (Athena.e(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();
                d.setTime(d.getTime() + (options.expires));
            } else {
                d = options.expires;
            }
            expires = '; expires=' + d.toUTCString(); // use expires attribute, max-age is not supported by IE
        }
        // CAUTION: Needed to parenthesize options.path and options.domain
        // in the following expressions, otherwise they evaluate to undefined
        // in the packed version for some reason...
        var path = options.path ? '; path=' + (options.path) : '';
        var domain = options.domain ? '; domain=' + (options.domain) : '';
        var secure = options.secure ? '; secure' : '';
        return document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
    } else { // only name given, get cookie
        var cookieValue = null;
        if (!Athena.e(document.cookie)){
            var cookies = document.cookie.split(';');
            for (var i = 0, l = cookies.length; i < l; i++) {
                var cookie = cookies[i].replace( /^\s+|\s+$/g, "");
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) == (name + '=')) {
                    cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                    break;
                }
            }
        }
        return cookieValue;
    }
};


/* Send a message to the debug console if available, otherwise alert */
Athena.debug = function (msg, level){
	if (Athena.e(Athena.debugLevel)){
		return false;
	} else if (level > Athena.debugLevel){
		return false;
	}

	// Firebug enabled
	if (typeof console == "object" && console.firebug){
		console.log("Athena: " + msg);
		if (arguments.length > 2){
			console.dir(Athena.d.arguments[2]);
		}
	// Yahoo logging console
	} else if (typeof YAHOO == "object" && YAHOO.log){
		YAHOO.log(msg, "info", "Athena");
		if (arguments.length > 2){
			YAHOO.log(Athena.print_r(Athena.d.arguments[2]), "info", "Athena");
		}
	} else if (typeof console == "object" && console.log){
		console.log("Athena: " + msg);
		if (arguments.length > 2){
			console.log(Athena.print_r(Athena.d.arguments[2]));
		}
	} else {
		alert("Athena.d: " + msg);
	}

	return true;
};
Athena.d = Athena.debug; // Shortcut to reduce size of JS


/* Emulate php's empty(). Thanks to:
 * http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_empty/
 * Nick wrote: added the check for empty arrays
 * Nick wrote: added the check for number that is NaN
 */
Athena.empty = function ( v ) {
    
    if (v === "" || 
	v === 0 ||
	v === null ||
	v === false ||
	typeof v === "undefined" ||
	(typeof v === "number" && isNaN(v))){
	return true;
    } else if (typeof v === 'object') {
	for (var key in v) {
	    if (typeof v[key] !== 'function' ) {
	      return false;
	    }
	}
	return true;
    } else if (typeof v === 'array' && v.length === 0) {
	return true;
    }
    return false;
};
Athena.e = Athena.empty; // Shortcut to make the Javascript smaller


/* Normalize the browser string for common checks. Evil. Consider other methods first.
 * Originally written using YUI, then converted to jQuery */
Athena.getBrowser = function (){
	if (Athena.e(window.jQuery)){
		return null;
	}	
	if (jQuery.browser.mozilla) {
		return 'gecko';
	} else if (jQuery.browser.msie) {
		return 'ie';
	} else if (jQuery.browser.safari) {
		return 'webkit';
	} else if (jQuery.browser.opera) {
		return 'opera';
	} else {
		return 'rob'; // rest of browsers ;)
	}	
};


Athena.getConfigAlias = function(slotname){
	// Cheat. The chains for some slot names are copies of each other.
	// Trim down the config we download by aliasing here
	if (Athena.e(slotname)){
		return slotname;
	} else if (slotname.match(/^(HOME_)*LEFT_SKYSCRAPER_[2-9]$/)){
		return 'LEFT_LOWER_SKYSCRAPERS';
	/* We need to be able to control these independently
	} else if (slotname.match(/^INCONTENT_LEADERBOARD_[1-9]$/)){
		return 'INCONTENT_LEADERBOARDS';
	} else if (slotname.match(/^INCONTENT_BOXAD_[3-9]$/)){
		return 'INCONTENT_BOXADS';
        */
	} else {
		return slotname;
        }
};


/* If a *wikia.com domain, use wikia.com. Otherwise, use whatever it is */
Athena.getCookieDomain = function (){
	if (document.domain.indexOf('wikia.com') > 0){
		return 'wikia.com';
	} else {
		return document.domain;
	}
};


/* Get the users country */
Athena.getCountry = function(){
	if (!Athena.e(Athena.getCountryFound)){
		return Athena.getCountryFound;
	}

	var ac;
	if (!Athena.e(Athena.getRequestVal('athena_country'))){
		ac = Athena.getRequestVal('athena_country');
		Athena.d("Using athena_country for geo targeting (" + ac + ")", 8);
	} else if (typeof top.Geo == "undefined") {
		// sometimes Geo isn't available because geoiplookup hasn't returned
		Athena.d("Geo country not downloaded properly, defaulting to US for now");
		return "unknown"; // Bail here so Athena.getCountryFound doesn't get set
	} else if (typeof top.Geo.country == "undefined" ) {
		// It downloaded, but it's empty, because we were unable to determine the country
		Athena.d("Unable to find a country for this IP, defaulting to US");
		ac = "unknown";
	} else {
		// Everything worked
		ac = top.Geo.country.toLowerCase();
	}

	if (ac === "gb"){
		// Wankers.
		ac = "uk";
	}

	Athena.getCountryFound = ac;
	return ac;
};


/* Look through the list of ads in the potential chain, and return the best guaranteed fill */
Athena.getGuaranteedAd = function(size){

	for (var i = 0, l = Athena.config.sizes[size].length; i < l; i++){
		var t = Athena.config.sizes[size][i];

		if (t['guaranteed_fill'] === 'Yes' && Athena.isValidCriteria(t)){
			return Athena.clone(t);
		}
	}

	// Rut roh
	return false;
};

/* When an ad does a document.write and we are already passed that point on the page,
 * we need to call it in an lframe (document.write can only be executed inline)
 * We handle this by calling the iframe from Athena. This function returns the iframe url */
Athena.getIframeUrl = function(slotname, tag) {

	// Special case for DART.
	if (tag["network_name"] == "DART"){
		return AthenaDART.getUrl(slotname, tag.size, tag.network_options, true); 
	}

	// Check to see if the tag is already an iframe. 
	var m = tag["tag"].match(/<iframe[\s\S]+src="([^"]+)"/), iframeUrl;

	//if ( m !== null ){
	if ( false ){
		iframeUrl = m[1].replace(/&amp;/g, "&");
		Athena.d("Found iframe in tag, using " + iframeUrl, 3);
	// Handle noad.gif here so it doesn't get called by iframe 
	} else if (tag["network_name"] == "No Ad"){
		Athena.d("Using about:blank for 'No Ad' to avoid iframe", 3);
		iframeUrl = "about:blank";
	} else {
		var p = { "tag_id": tag["tag_id"], "size": tag["size"], "slotname": slotname};
		iframeUrl = 'http://' + document.domain + "/__varnish_athena/athena/tag/?" + Athena.buildQueryString(p);
		Athena.d("No iframe found in tag, using " + iframeUrl, 3);
	}
	return iframeUrl;
};





Athena.getMetaKeywords = function(){
	var metas = document.getElementsByTagName("meta");
	for (var i = 0, l = metas.length; i < l; i++){
		if (metas[i].name == "keywords"){
			return metas[i].content.replace(/,/, ' ');
		}
	}
	return false;
};

/* Returns the number of minutes that have elapsed since midnight, according to the users clock */
Athena.getMinutesSinceMidnight = function(){
	return (Athena.now.getHours() * 60) + Athena.now.getMinutes();
};


/* Return the number of minutes since the last reject for the supplied tag id.
 * null if there hasn't been a reject
 */
Athena.getMinutesSinceReject = function(tag_id){
	var m = Athena.getTagStat(tag_id, "m");
	if (m === null){
		return null;
	} else {
		return Athena.getMinutesSinceMidnight() - m;
	}
};


/* Target based on the minute of the hour */
Athena.getMinuteTargeting = function (){
	return Athena.now.getMinutes() % 15;
};


/* Iterate through the chain and deliver the next ad tag to be called */
Athena.getNextTag = function(slotname){
	// Do we need to build the chain?
	if (Athena.e(Athena.chain[slotname])){
		if ( Athena.buildChain(slotname) === false){
			return {tag:"Unrecognized slotname"};
		}
	}

	var now = new Date();

	for (var i = 0, l = Athena.chain[slotname].length; i < l; i++){
		if (!Athena.e(Athena.chain[slotname][i]['started'])){
			continue;
		// Do we have enough time left?
		} else if ((now.getTime() - Athena.slotTimer[slotname]) > Athena.maxHopTime){
			Athena.d("Hop Time of " + Athena.maxHopTime + " exceeded. Using the guaranteed fill", 2);
			Athena.slotTimer[slotname] = "exceeded";
			var lastOne = l-1;
			Athena.chain[slotname][i]['started'] = now.getTime();
			return Athena.chain[slotname][lastOne];
		} else {
			// Win nah!
			Athena.chain[slotname][i]['started'] = now.getTime();
			return Athena.chain[slotname][i];
		}
			
	}
	// Rut roh
	Athena.reportError("Athena config error wrong, last ad in chain is hopping?" + slotname);
	return {tag:"<!-- Athene config error: Last ad in chain is hopping?-->"};
	// Note: This code used to try to fall back by callign the last ad in the chain. 
	// Don't do that. it causes a never ending loop
};


/* Page vars is an arbitrary bucket of variables. Check to see if the supplied
 * variable exists, if not return defaultVal */
Athena.getPageVar = function (key, defaultVal){
	if (!Athena.e(Athena.pageVars[key.toLowerCase()])){
		return Athena.pageVars[key.toLowerCase()];
	} else if (arguments.length > 1 ){
		return defaultVal;	
	} else {
		return '';
	}
};


/* Look for the supplied varName value in the query string, and return it.
 * Optional second arg is a default. */
Athena.getRequestVal = function(varName, defaultval){
	if (Athena.e(Athena.nvpairs)){
		Athena.nvpairs = Athena.parseQueryString(document.location.search);
	}
	if (! Athena.e(Athena.nvpairs[varName])){
		return Athena.nvpairs[varName];
	} else if (! Athena.e(defaultval)) {
		return defaultval;
	} else {
		return '';
	}
};


/* Look through the list of ads in the potential chain, and find one that is sample-able */
Athena.getSampledAd = function(size){
	// Build up an array of the sample stats.
	var sArray = [], total = 0, myRandom = Athena.rand * 100;
	for (var i = 0, l = Athena.config.sizes[size].length; i < l; i++){
		var sample_rate = parseFloat(Athena.config.sizes[size][i]['sample_rate']);
		if (Athena.e(sample_rate)){
			continue;
		}
		total += sample_rate; 

		Athena.d("Sample Rate for " + Athena.config.sizes[size][i]['tag_id'] + " is " + sample_rate, 7);
		sArray.push( { "upper_bound": total, "index": i });

	}
	Athena.d("Sample Array = ", 7, sArray);

	// Now check to see if the random number is in sArray
	for (var j = 0, l2 = sArray.length; j < l2; j++){
		if (myRandom < sArray[j]["upper_bound"]){
			var f = sArray[j]["index"];
			return Athena.clone(Athena.config.sizes[size][f]);
		}
	}

	return false;
};


/* Figure out what size a slot is by looking at the config.
 * Should we pass a map of sizes->slotnames in the config? Maybe, but it would make it bigger...
 */
Athena.getSizeForSlotname = function (slotname){
        for (var slot in Athena.config.slotnames){
                if (typeof Athena.config.slotnames[slot] == "function"){
                        // Prototype js library overwrites the array handler and adds crap. EVIL.
                        continue;
                }
                if (slot == Athena.getConfigAlias(slotname)){
                        return Athena.config.slotnames[slot];
                }
        }

        return false;
};


/* Get the current slotname by looking at the DOM.
 * Supported scenarios
 * 1) NO starting element. If this is the case, then a startign element is created for you
 * 2) Starting Element passed as a string
 *
 * Tip: If you are inside an iframe, assign an "id" to the current 
 *	iframe with window.frameElement.id, then pass this id in as a string
 *
 * Nested iframes are not currently supported. Soon.
 */ 
Athena.getSlotnameFromDom = function (e){
	var tempElement, slotdiv = '';

	// Determine the starting point.
	if (typeof e == "string"){
		// Get the id from a string that is passed in
		Athena.d("Determined slotname for hop using string method", 7);
		tempElement = Athena.$(e);
	} else if (typeof e == "undefined" || typeof e.parentNode == "undefined"){
		// HACK: couldn't determine e's parent (or it wasn't passed in)
		// Write out a dummy div as a starting point. 
		var dummydivid = "dummydiv_" + Math.random();
		Athena.d("Determined slotname for hop by writing fake div", 7);
		document.write('<div style="display:none" id="' + dummydivid + '"></div>');
		// TODO remove the dummy div when done with it.
		tempElement = Athena.$(dummydivid);
	} else if (typeof e == "object"){
		Athena.d("Determined slotname for hop by using element", 7);
		tempElement = e;
	}

	var slots = Athena.getSlotNames();
	while (tempElement !== null) {
		for (var s = 0, l = slots.length; s < l; s++){
			var reg = new RegExp("^" + slots[s] + "_");
			if (!Athena.e(tempElement.id) && reg.test(tempElement.id)){
				Athena.d("Current slotname = " + slots[s], 5);
				return slots[s];
			}
		}
		tempElement = tempElement.parentNode;
	}

	return false;
};


/* Return the available slots called on this page */
Athena.getSlotNames = function (){
        var out = [];

	for (var chainslot in Athena.chain){
                if (typeof Athena.chain[chainslot] == "function"){
                        // Prototype js library overwrites the array handler and adds crap. EVIL.
                        continue;
                }
		out.push(chainslot);
	}

        for (var slot in Athena.config.slotnames){
                if (typeof Athena.config.slotnames[slot] == "function"){
                        // Prototype js library overwrites the array handler and adds crap. EVIL.
                        continue;
                }
		if (!Athena.in_array(slot, out)){
			out.push(slot);
		}
        }
        return out;
};


Athena.getSlotLoadDiv = function (slotname){
	return Athena.$(slotname + '_load') || Athena.$(slotname);
};

/* Format is $day_{$tag_id}l{$loads}r{$rejects}m{$lastrejecttime}
 * r and m are optional, only if there is a reject
 * $day -- 0 to 6, where 0 is Sunday
 * $tag_id -- you better know what this is 
 * $loads -- number of loads today
 * $rejects -- number of rejects today
 * $lastrejecttime -- minutes since midnight of the last reject
 */
Athena.getStatRegExp = function(tag_id){
	return new RegExp(Athena.now.getDay() + '_' + tag_id + 'l([0-9]+)r*([0-9]*)m*([0-9]*)' );
};


/* Get loads/rejects for a tag. Type = "l" for loads,  "r" for rejects,
 * and "m" for the minutes since midnight of the last rejection */
Athena.getTagStat = function (tag_id, type){
	var stat = null;

	var statMatch = Athena.tagStats.toString().match(Athena.getStatRegExp(tag_id));
	if (!Athena.e(statMatch)){
		var len = statMatch.length;
		if (type === "l" && len >= 2){
			stat = statMatch[1];
		} else if (type === "r" && len >= 3){
			stat = statMatch[2];
		} else if (type === "m" && len >= 4){
			stat = statMatch[3];
		} else if (type === "a"){
			var l = parseInt(statMatch[1], 0) || 0;
			var r = parseInt(statMatch[2], 0) || 0;
			stat = l + r; // attempts are loads + rejects
		}
	}

	if (Athena.e(stat)) {
		// For type = m, we return null if not found. Otherwise, return 0
		if ( type == "m" ){
			stat = null;
		} else {
			stat = 0;
		}
	} else {
		stat = parseInt(stat, 10); // convert to number for numerical comparison
	}

	Athena.d("Stats for " + tag_id + " type " + type + " = " + stat, 7);
	return stat;
};

/* This is the backup tag used to go to the next ad in the configuration */
Athena.hop = function (slotname){
	Athena.totalHops = Athena.totalHops || 0;
	Athena.totalHops++;

	if (Athena.totalHops > 50){
		// This can't be right.
		return false;
	}

	if (Athena.e(slotname)){
		slotname = Athena.getSlotnameFromDom();
	}
	Athena.d("Athena.hop() called for " + slotname);

	return Athena.callAd(slotname);
};


/* Hop called from inside an iframe */
Athena.iframeHop = function(iframeUrl){
	Athena.d("Athena.iframeHop() called from " + iframeUrl);
	var slotname = null;

	// Pull the slotname from the url if we can. 
	var match = iframeUrl.toString().match(/slotname=([^&;]+)/);
	if (match !== null){
		slotname = match[1];
	} else {
		// Go through all the irames to find the matching src
	
	        var iframes = document.getElementsByTagName("iframe");
       		for (var i = 0, len = iframes.length; i < len; i++){
               		if (iframes[i].src == iframeUrl){
                       		// Found match
				slotname = Athena.getSlotnameFromDom(iframes[i]);
				break;
			}
                }
        }


	if ( Athena.e(slotname)){
		throw ("Unable to find iframe for " + iframeUrl);
	}
	Athena.callAd(slotname, true);
};


Athena.iframeOnload = function(e) {

	var iframe = e.target || e;

	// Different browsers do/do not set the readyState. For the ones that don't set it here to normalize
	try { // Supress permission denied errors for cross domain iframes
		if (typeof iframe.readyState == "undefined" ) {
			iframe.readyState = "complete";
		}
	} catch (e) {}
};

/* Emulate PHP's in_array, which will return true/false if a key exists in an array */
Athena.in_array = function (needle, haystack){
    for (var key in haystack) {
	if (haystack[key] == needle) {
	    return true;
	}
    }
 
    return false;
};


/* Constructor/Setup 
 * 1) Pull configuration (list of ads to try)
 * 2) Pull Geo Targeting data
 */
Athena.init = function (){
	Athena.pullConfig();
	Athena.startTime = Athena.now.getTime();
	Athena.debugLevel = Athena.getRequestVal('athena_debug', 0);
	if (Athena.debugLevel > 0 && Athena.getBrowser() != "gecko" && window.YAHOO){
		// If no Firebug, dump debug at the bottom
		Athena.myLogReader = new YAHOO.widget.LogReader("", {newestOnTop:false, verboseOutput:false}); 
	}

	Athena.loadTagStats();

	// Call the beacon on page load. Exclude browsers that we don't care about that misbehave
	if (window.navigator.vendor != "Camino" && Athena.getBrowser() != "opera") {
		Athena.addEventListener(window, "load", Athena.onLoadHandler);
	}

	Athena.initRandom();

	// Ads In Content. 
	if ( Athena.e(window.wgIsMainpage) && !document.domain.match(/answers/)){
		// Give enough time for config to load
		window.setTimeout('AdsInContent.putAdsInContent("bodyContent");', 300);
	}
};


/* Set up the random variables. Seprate from init because the unit testing calls it repeatedly */
Athena.initRandom = function (){
	Athena.rand = Math.random();
	/* Generate random number to use as a cache buster during the call for ad (OpenX and DART) 
	 * AdsCB is referenced in a lot of places, but I'd like for us to use Athena.random() long term
 */
	// Intentionally global
	window.AdsCB = Math.floor(Math.random()*99999999);
};


/* Different browsers handle iframe load state differently. For once, IE actually does it best.
 * IE - document.readyState *and* iframes.readyState is "interactive" until all iframes loaded, then it is "complete"
 * Chrome/Safari - loading|loaded|complete, DOMFrameContentLoaded supported, but won't allow you to change iframe.readyState
 *      Unfortunately, nested iframes will be called "loaded"
 */
Athena.iframesLoaded = function(e){
        var iframes = document.getElementsByTagName("iframe");
        if (iframes.length === 0){ return true; }
	if (typeof e != "object" || e === null){
		return false;
	}

        var b = Athena.getBrowser();
        if (b == "gecko" && Athena.pageLoaded){
                // Firefox/Seamonkey/Camino - no document.readyState, but load event is *after* iframes
                return true;
        } else if (b == "ie" && document.readyState == "complete") {
                // We also need to check the document.readyState for each iframe
                for (var i = 0; i < iframes.length; i++){
                        if (iframes[i].document.readyState != "complete"){
                                return false;
                        }
                }
                return true;
        } else {
                // All other browsers will send the beacon when the time runs up.
                return false;
        }
};



/* Check to see if the user from the right geography */
Athena.isValidCountry = function (countryList){

	var ac = Athena.getCountry();

	Athena.d("Checking if " + ac + " is in:", 8, countryList);

	if (Athena.in_array("row", countryList) &&
		  !Athena.in_array(ac, ['us','uk','ca'])){
		Athena.d("ROW targetted, and country not in us, uk, ca", 6);
		return true;
	}
	if (Athena.in_array(ac, countryList)){
		return true;
	}

	return false;
};


/* Does the criteria match for this tag? */
Athena.isValidCriteria = function (t){
	if (Athena.in_array(t['tag_id'], Athena.rejTags)){
		Athena.d("Ad #" + t["tag_id"] + " rejected because of already rejected on this page", 3, Athena.rejTags);
		t['isValidCriteria'] = false;
		return t['isValidCriteria']; 
	}

        // For ads that have a frequency cap, don't load them more than once per page
        if (!Athena.e(t["inChain"]) && !Athena.e(t["freq_cap"])) {
		Athena.d("Ad #" + t["tag_id"] + " from " + t["network_name"] +
			" invalid: it has a freq cap and is already in another chain", 3);
		t['isValidCriteria'] = false;
		return t['isValidCriteria'];
        }

	if (!Athena.e(t['isValidCriteria'])){
		return t['isValidCriteria']; 
	}

	// Frequency
	if (!Athena.e(t["freq_cap"])){
		var a = Athena.getTagStat(t["tag_id"], "a");
		if (a >= parseInt(t["freq_cap"], 10)){
			Athena.d("Ad #" + t["tag_id"] + " from " + t["network_name"] +
				" invalid: " + a + " attempts is >= freq_cap of " +
				t["freq_cap"], 3);
			t['isValidCriteria'] = false;
			return t['isValidCriteria']; 
		}
		
	}

	// Rejection capping
	if (!Athena.e(t["rej_cap"])){
		var r = Athena.getTagStat(t["tag_id"], "r");
		if (r >= parseInt(t["rej_cap"], 10)){
			Athena.d("Ad #" + t["tag_id"] + " from " + t["network_name"] +
				" invalid: " + r + " rejects is greater then rej_cap of " +
				t["rej_cap"], 3);
			t['isValidCriteria'] = false;
			return t['isValidCriteria']; 
		}
		
	}

	// Rejection time
	if (!Athena.e(t["rej_time"])){
		var elapsedMinutes = Athena.getMinutesSinceReject(t["tag_id"]);

		if (elapsedMinutes !== null){
			Athena.d("Ad #" + t["tag_id"] + " from " + t["network_name"] + 
					" rej_time = " + t["rej_time"] + " elapsed = " + elapsedMinutes, 7);
			if (elapsedMinutes < parseInt(t["rej_time"], 10)){
				Athena.d("Ad #" + t["tag_id"] + " from " + t["network_name"] +
					" invalid:  tag was rejected sooner than rej_time of " +
					t["rej_time"], 3);
				t['isValidCriteria'] = false;
				return t['isValidCriteria']; 
			}
		}
		
	}

	if (Athena.e(t['criteria'])){ 
		t['isValidCriteria'] = true; 
		return t['isValidCriteria']; 
	} 
 
	try { 
 
	  for (var key in t.criteria){ 
		switch (key){ 
		  case 'Geography':  
			if ( ! Athena.isValidCountry(t.criteria.Geography)){ 
				Athena.d("Ad #" + t["tag_id"] + " rejected because of Invalid Geography", 8);
				t['isValidCriteria'] = false;
				return t['isValidCriteria']; 
			} 
			break;
		  default: 
			// If it is not predefined, assume it is a page var. 
			var list = eval("t.criteria." + key); 
			if (! Athena.in_array(Athena.getPageVar(key), list)){ 
				
				Athena.d("Ad #" + t["tag_id"] + " rejected because " + key + " not found in ", 8, list);
				t['isValidCriteria'] = false;
				return t['isValidCriteria']; 
			} 
			break; // Shouldn't be necessary, but silences a jslint error 
		} 
	  } 
 
	} catch(e){ 
		var emsg = "Javascript Error while checking criteria: " + Athena.print_r(e);
		Athena.d(emsg);
		return false; 
	} 


 
	// All criteria passed 
	Athena.d("Targeting criteria passed for tag #" + t["tag_id"], 8);
	t['isValidCriteria'] = true;
	return t['isValidCriteria']; 

};

/* Simple wrapper to handle json encoding */
Athena.json_encode = function(data){
	return JSON.stringify(data);
};

/* Load the supplied url inside a script tag  */
Athena.loadScript = function(url, noblock) {
	if (typeof noblock == "undefined"){
		// This method blocks
		document.write('\x3Cscript type="text/javascript" src="' + url + '">\x3C\/sc' + 'ript>');
	} else {
		// This method does not block
		var h = document.getElementsByTagName("head").item(0);
		var s = document.createElement("script");
		s.src = url;
		h.appendChild(s);
	}
};


/* Pull the stats from a cookie, clean up the old ones if there are any */
Athena.loadTagStats = function (){

	// Url override
	if (! Athena.e(Athena.getRequestVal('athena_tag_stats'))){
		Athena.d("Using tag stats from url = " + Athena.getRequestVal('athena_tag_stats'));
		Athena.tagStats = Athena.getRequestVal('athena_tag_stats');
	} else {

		// Pull from cookie
		var c = Athena.cookie("ATS");
		if (Athena.e(c)){
			Athena.tagStats = '';
			return Athena.tagStats;
		} else {
			Athena.tagStats = c;
		}
	}
	
	// Split based on comma and rebuild the array
	var pieces = Athena.tagStats.split(','), holder = [];
	for (var i = 0, l = pieces.length; i < l; i++){
		// Build up a temporary array with items from the same day (wipe out old)
		if (pieces[i].substring(0, 1) == Athena.now.getDay()){
			holder.push(pieces[i]);	
		}
		if (i > Athena.statMax){ break; }  // Sanity check, should be caught with "set"
	}
	Athena.tagStats = holder.join(',');

	Athena.d("Tag Stats Loaded = " + Athena.tagStats, 7);

	return Athena.tagStats;
};


/* Clean up the chain. Mark loads/rejects where we know what happened. */
Athena.markChain = function (slotname){
	var attemptFound = false, len = Athena.chain[slotname].length;
	// If an attempt was found, then everything else "started" was rejected
	Athena.d("Marking chain for " + slotname, 5);
	for (var i = len - 1; i >= 0; i--){
		if (attemptFound && !Athena.e(Athena.chain[slotname][i]['started'])){
			Athena.chain[slotname][i]['rejected'] = true;
			Athena.chain[slotname][i]['loaded'] = false;
			Athena.rejTags.push(Athena.chain[slotname][i]['tag_id']);
		} else if (!Athena.e(Athena.chain[slotname][i]['started'])){
			attemptFound = true;
		}
	}

	// If a garaunteed ad was filled, mark it as loaded
	for (var j = 0 ; j < len; j++){
		if (!Athena.e(Athena.chain[slotname][j]['started']) &&
		     Athena.chain[slotname][j]['guaranteed_fill'] == 'Yes' ){
			Athena.chain[slotname][j]['loaded'] = true;	
			return true;
		}
	}

	// If the slot/document is completely loaded, the last one called must be the one loaded
	if(Athena.iframesLoaded(Athena.getSlotLoadDiv(slotname))){
		for (var k = 0 ; k < len - 1; k++){
			if (Athena.e(Athena.chain[slotname][k+1]['started'])){
				Athena.chain[slotname][k]['loaded'] = true;	
				break;
			}
		}
	}
	return true;

};


/* As the name implies, this function does nothing. Used for tricking browsers to block by surrounding it with a document.write. */
Athena.noop = function (){
	return true;
};


Athena.onLoadHandler = function () {
        Athena.pageLoaded = true;
        Athena.loadDelay = Athena.loadDelay || 100;
        if ( Athena.iframesLoaded()) {
                Athena.sendBeacon();
        } else if (Athena.loadDelay < 2500){
                // Check again in a bit. Keep increasing the time
                Athena.loadDelay += Athena.loadDelay;
                window.setTimeout("Athena.onLoadHandler()", Athena.loadDelay);
        } else {
                Athena.d("Gave up waiting for ads to load, sending beacon now");
                Athena.sendBeacon();
        }
};



/* This code looks at the query string supplied in the url and parses it.
 * It returns an associative array of url decoded name value pairs
 */
Athena.parseQueryString = function (qs){
	var ret = [];

	if (Athena.e(qs)) { return ret; }
  
	if (qs.charAt(0) === '?') { qs = qs.substr(1); }
	
  
	qs=qs.replace(/\;/g, '&', qs);

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

	for (var i = 0, intIndex; i < nvpairs.length; i++){
		if (nvpairs[i].length === 0){
			continue;
		}
		
		var varName ='', varValue='';
		if ((intIndex = nvpairs[i].indexOf('=')) != -1) {
			varName = decodeURIComponent(nvpairs[i].substr(0, intIndex));
			varValue = decodeURIComponent(nvpairs[i].substr(intIndex + 1)); 
		} else {
			varName = decodeURIComponent(nvpairs[i].substr(0, intIndex));
			varValue = '';
		}

		if (varName === '' || varValue === ''){
			continue;
		}

		ret[varName] = varValue;
	}

	return ret;
};


/* Javascript equivalent of php's print_r. 
 * http://www.openjs.com/scripts/others/dump_function_php_print_r.php
 */
Athena.print_r = function (arr,level) {
	var text = ["\n"], padding = "";
	if(!level) { level = 0; }

	// saving a crash if you try to do something silly like print_r(top);
	if (level > 6) { return false; }
	
	//The padding given at the beginning of the line.
	for(var j = 0; j < level+1 ; j++) {
		padding += "	";
	}
	
	if(typeof arr  == 'object') { //Array/Hashes/Objects 
		for(var item in arr) {
			var value = arr[item];
			
			if(typeof value == 'object') { //If it is an array,
				text.join(padding + "'" + item + "' ...");
				text.join(Athena.print_r(value,level+1));
			} else {
				text.join(padding + "'" + item + "' => \"" + value + "\"\n");
			}
		}
	} else { //Stings/Chars/Numbers etc.
		text = ["===>"+arr+"<===("+typeof(arr)+")"];
	}
	return text.join("");
};


/* Pull the configuration data from our servers */
Athena.pullConfig = function (){
	if (window.AthenaConfigLoaded){
		return;
	}

	var p = {
		"skin": Athena.getPageVar('skin'),
		"wgDBname": Athena.getPageVar('wgDBname'),
		"v": 1.2 // versioning for config
	};

	// Allow for us to work in a dev environment
	if (! Athena.e(Athena.getRequestVal('athena_dev_hosts') ||
	      window.location.hostname == "athena.dev.wikia-inc.com") ||
	     !Athena.e(window.athena_dev_hosts)){
		// overwrite
		Athena.baseUrl = "http://athena.dev.wikia-inc.com/athena/";
	}

	var u = Athena.baseUrl  + 'config/?' + Athena.buildQueryString(p);
	Athena.d("Loading config from " + u, 2);
	Athena.loadScript(u);

		
	Athena.d("Loading geo data from " + Athena.geoUrl, 3);
	Athena.loadScript(Athena.geoUrl);
};

Athena.random = function (){
	return window.AdsCB;
};


/* Record the loads/rejects, and return a string of events to be sent by the beacon */
Athena.recordEvents = function(slotname){

	var e = '';
	for (var i = 0, l = Athena.chain[slotname].length; i < l; i++){
		var t = Athena.chain[slotname][i];
		if ( Athena.e(t['started'])){
			// There can't be a load or a reject if it wasn't started.
			continue;
		}

		var loads = Athena.getTagStat(t['tag_id'], "l");

		// Load
		if (!Athena.e(Athena.chain[slotname][i]['loaded'])){
			Athena.d("Recording Load for " + t["network_name"] + ", #" + t["tag_id"] + " in " + slotname, 4);
			Athena.setTagStat(t['tag_id'], "l");
			e += ',l' + t['tag_id'] + 'pl' + loads;

		// Reject
		} else if (! Athena.e(t['rejected'])){
			e += ',r' + t['tag_id'] + 'pl' + loads;
			Athena.d("Recording Reject for " + t["network_name"] + ", #" + t["tag_id"] + " in " + slotname, 5);
			Athena.setTagStat(t['tag_id'], "r");
			continue;

		}
	}

	return e.replace(/^,/, ''); // Strip off first comma
};


/* Catch errors and build them up in array, to be sent with the beacon */
Athena.reportError = function (msg, url, line){
	try {
		// Don't bother if they have AdBlock enabled
		if (!Athena.e(window.wgAdBlockEnabled)){
			return false;
		}

		// If the url isn't wikia, it ain't our problem.
		if (! url.match(/wiki/)){ // wiki anywhere in the url
			return false;
		}

		//  Ignore certain errors
		var ignore = [
			"Error loading script", // js didn't download. AdBlock.
			"Script error.", // User hit stop
			"Permission denied" // Ad Network attempting to access top level page from iframe
		];
		for (var i = 0, l = ignore.length; i < l; i++){
			if (msg.indexOf(ignore[i]) > -1){
				return false;
			}
		}

		Athena.errors.push([msg, url, line]);
		throw (msg);

	} catch (e){ /* Avoid recursion if this code produces an error */ }

	return false; // Continue to display the errors to the javascript console
};
window.onerror = Athena.reportError; // Register this funciton to catch all errors


/* Send a beacon back to our server so we know if it worked */
Athena.sendBeacon = function (){

	// This is called a second time from the *un*load handler, so make sure we don't call the beacon twice.
	if (!Athena.e(Athena.beaconCalled)){
		return true;
	}
	Athena.beaconCalled = true;

	var events = '', numSlots = 0;
	for(var slotname in Athena.chain){
		if (typeof Athena.chain[slotname] == "function"){
			// Prototype js library overwrites the array handler and adds crap. EVIL.
			continue;
		}
		numSlots++;
		// Clean up the chain
		Athena.markChain(slotname);
		// Set tag stats and get a string of events
		events += ',' + Athena.recordEvents(slotname);
	}

	events = events.replace(/^,/, ''); // Strip off first comma

	Athena.storeTagStats();

	var b = {};
	b.numSlots = numSlots;
	b.events = events;


	var now = new Date();

	// Ad Time
	var ms = (now.getTime() - Athena.startTime) / 1000;
	b.adTime = ms - parseFloat(Athena.loadDelay/1000); // subtract delayTime
	b.adTime = Math.floor(b.adTime * 10) / 10; // Round to 1 decimal

	// Page Time
	if (typeof window.wgNow == "object" ){
	      ms = (now.getTime() - window.wgNow.getTime()) / 1000;
	      b.pageTime = b.adTime - parseFloat(Athena.loadDelay/1000); // subtract delayTime
	      b.pageTime = Math.floor(b.pageTime * 10) / 10; // Round to 1 decimal
	      Athena.d ("Page loaded in " + b.pageTime + " seconds");
	}

	// Errors
	if (! Athena.e(Athena.errors)){
	      // Send javascript errors back with the beacon
	      Athena.d("sendBeacon() errors: ", 0, Athena.errors);
	      b.errors = Athena.errors;
	}

	// Tag Errors
	if (! Athena.e(Athena.tagErrors)){
	      // Send tag errors back with the beacon
	      Athena.d("sendBeacon() tagErrors: ", 0, Athena.tagErrors);
	      b.tagErrors = Athena.tagErrors;
	}

	// Pass along other goodies
	b.country = Athena.getCountry();
	b.cont_lang = Athena.getPageVar("cont_lang");
	b.user_lang = Athena.getPageVar("user_lang");
	if (!Athena.e(window.wgUserName)){
	      b.loggedIn = true;
	}
	
	// Timeouts
	var slotTimeouts = 0;
	for (var s in Athena.slotTimer){
	      if (typeof Athena.slotTimer[s] == "function"){
		      // Prototype js library overwrites the array handler and adds crap. EVIL.
		      continue;
	      }
	      if (Athena.slotTimer[s] == "exceeded"){
		      slotTimeouts++;
	      }
        }
	if (slotTimeouts > 0) {
		b.slotTimeouts = slotTimeouts;
	}

	Athena.d ("Beacon: ", 7, b);

	Athena.beaconCall(Athena.baseUrl + '/beacon/?beacon=' + encodeURIComponent(JSON.stringify(b)));

	Athena.d ("Athena done, beacon sent, ads loaded in " + b.adTime + " seconds");

	// Call the unit tests
	if (typeof top.AthenaTest == "object" && typeof top.AthenaTest.afterBeacon == "function"){
		top.AthenaTest.afterBeacon();
	}
	return true;
};


/* Set / get page variables */
Athena.setPageVar = function (key, value){
	Athena.pageVars[key.toLowerCase()] = value;
	Athena.d("Page var '" + key + "' set to '" + Athena.pageVars[key.toLowerCase()] + "'", 9);
	return true;
};


/* Set loads/rejects for a tag. type is "l" or "r" */
Athena.setTagStat = function (tag_id, type){
	Athena.d("Setting a " + type + " stat for " + tag_id, 6);

	var pieces = Athena.tagStats.split(','), holder = [], found=false;
	if (pieces.length > Athena.statMax){
		// If too may, take off the first one
		pieces.shift();
	}

	// Get the current stats and rebuild
	var loads = Athena.getTagStat(tag_id, "l");
	var rejects = Athena.getTagStat(tag_id, "r");
	var rejectMinutes = 0;

	if (type === "l"){
		loads++;
		rejectMinutes = Athena.getTagStat(tag_id, "m") || 0;
	} else if (type === "r"){
		rejects++;
		rejectMinutes = Athena.getMinutesSinceMidnight();
	}

	// Tack on the rejects/rejectMinutes
	var piece = Athena.now.getDay() + '_' + tag_id + "l" + loads;
	if (rejects > 0){
		piece = piece + "r" + rejects;
		piece = piece + "m" + rejectMinutes;
	}

	var ts = Athena.tagStats.replace(Athena.getStatRegExp(tag_id), piece);
	if (ts === Athena.tagStats){
		// tagid not found in stats, Append it to the end.
		Athena.tagStats = Athena.tagStats + ',' + piece;
	} else {
		Athena.tagStats = ts;
	}

	Athena.tagStats = Athena.tagStats.replace(/^,/, ''); // Strip off first comma

	Athena.d("Tag Stats After Set = " + Athena.tagStats, 6);
};


/* Store accepts/rejections in a cookie
 * Keep this as small as possible! 
 */
Athena.storeTagStats = function (){
	Athena.d("Stored Tag Stats = " + Athena.tagStats, 4);
	Athena.cookie("ATS", Athena.tagStats, {
		  domain: Athena.getCookieDomain(),
		  path: "/",
		  expires: 86400 * 1000 // one day in milliseconds
		 }
	);
};




/************************************************************************
 *									*
 * Initialize Athena, now that all dependencies have loaded.		*
 *									*
************************************************************************/ 


Athena.init();



/************************************************************************
 *									*
 * Begin AdsInContent, a class used for putting ads inline in the article
 *									*
************************************************************************/ 


var AdsInContent = {
	spaceBetweenAds : 550,
	numTries 	: 0,
	numAdsServed 	: 0,
	maxTries 	: 20,
	adsPerPage 	: 1
};

AdsInContent.putAdsInContent = function(htmlContainer) {
	if (Athena.$(htmlContainer) === null){
		// This isn't going to work out. Probably called on the wrong page
		return false;
	} 
	if (typeof window.AthenaConfigLoaded != "function" || typeof window.Geo == "undefined" ){
		// Give enough time for config to load
		if (AdsInContent.numTries > AdsInContent.maxTries){
			throw("AdsInContent: Giving up waiting for config."); 
		} else {
			AdsInContent.numTries++;
			Athena.d("Waiting another 500 milliseconds for config to load", 3);
			window.setTimeout('AdsInContent.putAdsInContent("' + htmlContainer + '");', 500);
			return false;
		}
	} 

	var html = Athena.$(htmlContainer).innerHTML;
        var numAdsServed = 0, lengthSince = 600, slotname;
	// Note that IE converts all tags to upper case when you call innerHTML, so regexp required
	var sections = html.split(/<\/H2>/i);

	// Start at section 1 instead of 0 to skip over the TOC
        for (var i = 1, l = sections.length; i < l; i++){

		// Note the selector is one less
		var selector = "#" + htmlContainer + " h2:eq(" + (i-1) + ")";
		var sectionHeight = AdsInContent.getPixelHeight(sections[i]);
		var sectionWidth = $(selector).width();

                if (lengthSince < AdsInContent.spaceBetweenAds ) { 
			Athena.d("AdsInContent: Section skipped, " + lengthSince + " pixels since last ad", 5);
                        lengthSince += sectionHeight;

		} else {
                        var slotConfig = AdsInContent.getSlotConfig(sections[i], numAdsServed+1, sectionWidth, sectionHeight);
			if (slotConfig.type == "leaderboard" || slotConfig === false){
				// Nothing to see here, move along.
                        	lengthSince += sectionHeight;
				continue;
			}
				
			// Display an ad
                        numAdsServed++;
			Athena.d("AdsInContent: Calling ad in section " + (i+1) + " with " + Athena.print_r(slotConfig), 3);
                        lengthSince = sectionHeight;

                        // Create div, apply styles, and insert into the DOM using jQuery

                        var loadDiv = $('<div id="' + slotConfig.name + '_load"></div>');
			for (var style in slotConfig.styles ){
				if (typeof slotConfig.styles[style] == "function"){
					// Prototype js library overwrites the array handler and adds crap. EVIL.
					continue;
				}
				if (style == "width" || style == "height" ) {
					// Don't reserve space for ads (#33171)
					continue;
				}
                        	loadDiv.css(style, slotConfig.styles[style]);
			}
                        loadDiv.insertAfter(selector);

                        Athena.callAd(slotConfig.name, true);

                	if (numAdsServed >= AdsInContent.adsPerPage){
                       		break;
                	}
                }
        }

	AdsInContent.numAdsServed = numAdsServed;
        return numAdsServed;
};


AdsInContent.getSlotConfig = function(sectionHtml, adNum, sectionWidth){
	var s = {};

	// Crude check for now. Check for collision or a short section. 
	if (sectionHtml.match(/(<table|wikia-gallery)/i) || sectionHtml.length < 1000 ){
		s.name = "INCONTENT_LEADERBOARD_" + adNum;
		s.type = "leaderboard";
	} else {
		s.name = "INCONTENT_BOXAD_" + adNum;
		s.type = "boxad";

		// If there is a bulleted list, put boxad on the right. 
		// Otherwise, alternate left/right
		if (sectionHtml.match(/<li/i)){
			s.pos = "right";
		} else {
			s.pos = "left";
		}

		//s.pos = "left";
		//Athena.d("Left-aligned ad forced via wgForceInContentAdsLeft", 5);
	}

	// Set styles
	if (s.type == "leaderboard"){ 
		s.styles = {"width":"728px",
			    "height":"90px",
			    "margin-left":"auto",
			    "margin-right":"auto",
			    "margin-bottom":"10px",
			    "clear": "both"
                           };
	} else if (s.type == "boxad" && s.pos == "right"){
		s.styles = {"width":"300px",
			    "height":"250px",
			    "float":"right",
			    "margin-top":"10px",
			    "margin-bottom":"10px",
			    "margin-left":"10px",
			    "clear": "right"
			   };
	} else {
		// Needs 20 for the bulleted lists. 
		s.styles = {"width":"300px",
			    "height":"250px",
	                    "float":"left",
			    "margin-top": "10px",
			    "margin-bottom":"10px",
			    "margin-right":"20px",
			    "clear": "left"
			   };
	}

	if (parseInt(s.styles.width.replace('px', ''), 10) + 100 > sectionWidth) {
		Athena.d("Section skipped, " + sectionWidth + " pixels not wide enough for ad", 5);
		return false;
	}
	
	return s;
};


AdsInContent.getPixelHeight = function(sectionHtml){
	// Crude stub for now
	return Math.floor(sectionHtml.length/7);
};


/************************************************************************
 *									*
 * Begin AQ, a class used for ContextWeb DAR				*
 *									*
************************************************************************/ 

if ( typeof AQ == "undefined"){
	var AQ = {
		eventUrls : [],
		chain : {},
		allTags : {},
		cwpid : 504082, // Our account number
		priceFloor : 0, // Tag must be worth this to serve it
		tries : 0
	};
}

AQ.next = function (slotgroup){
	top.Athena.d("AQ.next called with " + slotgroup, 3);
	top.Athena.d("AQ.chain for " + slotgroup + ": " + Athena.print_r(AQ.chain[slotgroup]), 5);

	// Mark the last tag as rejected
	var i, nexti = 0;
	for (i = AQ.chain[slotgroup].length-1; i >= 0; i--){
		if (!top.Athena.e(AQ.chain[slotgroup][i]['skipped'])){
			// Last one was skipped
			nexti = i+1;
			break;
		} else if (!top.Athena.e(AQ.chain[slotgroup][i]['attempt'])){
			// Send reject beacon. Mark as reject in the chain
			AQ.beacon(AQ.chain[slotgroup][i]['id'], "reject");
			AQ.chain[slotgroup][i]['reject'] = true;
			top.Athena.d("AQ tag #" + AQ.chain[slotgroup][i]['id'] + " rejected in " + slotgroup, 3);
			nexti = i+1;
			break;
		}
	}

	if (nexti > AQ.chain[slotgroup].length-1){
		// End of the chain
              	top.Athena.d("AQ: End of ContextWeb chain", 5);
		if (AQ.win == top){
			top.Athena.hop();
		} else {
			// We are in an iframe
			top.Athena.iframeHop(AQ.win.location);
		}
	} else {
		top.Athena.debug("AQ.next calling tag #" + AQ.chain[slotgroup][nexti]['id'] + " with a value of " + AQ.chain[slotgroup][nexti]['fValue'], 3);
		AQ.tag(slotgroup, AQ.chain[slotgroup][nexti]['id']);
	} 

};
window.AQNext = AQ.next; // Backward compatibility


/* Context Web Beacon */
AQ.beacon = function (cwtagid, action){
	if (top.Athena.rand < 0.5){
		return; 
	}
	var url = top.Athena.baseUrl + 'event/set?event=contextWebBeacon&cwtagid=' + cwtagid + '&action=' + action; 
	top.Athena.d("AQ event beacon url: " + url, 3);
	AQ.eventUrls.push(url);

	top.Athena.beaconCall(url);
};

/* 
 * Context Web populates a strCreative global variable. When the ad returns.
 * If this variable does not exist, check again in a few milliseconds
 *
 * Otherwise, figure out if it was a load or a reject by seeing if it is marked as "reject" in the chain, which is done by the AQ.next function
 *
 */

AQ.checkForLoad = function (slotgroup){

	top.Athena.d("AQ.checkForLoad called for " + slotgroup, 4);
	if (top.Athena.e(window.strCreative)){
		if (AQ.tries < 100){
			var recall = "AQ.checkForLoad('" + slotgroup + "');";
			top.Athena.d("AQ: Recalling " + recall + " in 100 ms", 6);
			window.setTimeout(recall, 100);
			AQ.tries++;
		} else {
			top.Athena.d("Giving up after 100 tries in AQ.checkForLoad");
		}
		return; 
	}

	// Walk backwards through the chain and find the first attempt
	for (var i = AQ.chain[slotgroup].length-1; i >= 0; i--){
		if (!top.Athena.e(AQ.chain[slotgroup][i]['attempt'])){
			// Found one that was started. See if AQ.next marked it as reject
			if (top.Athena.e(AQ.chain[slotgroup][i]['reject'])){
				// It loaded!
				AQ.beacon(AQ.chain[slotgroup][i]['id'], "load");
				AQ.chain[slotgroup][i]['load'] = true;
				top.Athena.d("AQ tag #" + AQ.chain[slotgroup][i]['id'] + " loaded in " + slotgroup, 3);
			}

			break; 
		}
	}
};

AQ.randomTag = function(slotgroup, win){
	AQ.randomTagCalled = true;

        // Find a the tags to choose from that aren't already in the chain
        var chooseFrom = [], found = false;
        for (var i = 0; i < AQ.allTags[slotgroup].length; i++){
                found = false;
                for (var j = 0; j < AQ.chain[slotgroup].length; j++){
                        if (AQ.allTags[slotgroup][i]['id'] == AQ.chain[slotgroup][j]['id']){
                                found = true;
                                break;
                        }
                }

                if (! found){
                        chooseFrom.push(Athena.clone(AQ.allTags[slotgroup][i]));
                }
        }

	if (top.Athena.e(chooseFrom)){
		// They are all there, no need to randomize.
		AQ.tag(slotgroup, AQ.chain[slotgroup][0]['id'], win);
		return;
	}

	// Build a new chain with the random one on top
	var r = Math.floor(Math.random() * chooseFrom.length);

	top.Athena.d("AQ.randomTag calling tag " + r + " in the chain for " + slotgroup, 2);

	var oldChain = AQ.chain[slotgroup];
	AQ.chain[slotgroup] = [];
	AQ.chain[slotgroup].push(Athena.clone(chooseFrom[r]));
	AQ.chain[slotgroup][0]['random'] = true;
	AQ.chain[slotgroup].push(Athena.clone(oldChain[0]));
	AQ.chain[slotgroup].push(Athena.clone(oldChain[1]));

	AQ.tag(slotgroup, AQ.chain[slotgroup][0]['id'], win);
};


AQ.tag = function(slotgroup, cwtagid, win){
	if (typeof win != "undefined"){
		// Keep track of which window to write to.
		AQ.win = win;
	}
	top.Athena.d("AQ.tag called for " + slotgroup + " with tag #" + cwtagid, 3);

	AQ.lastTag = cwtagid; 

	// Determine which tag in the chain we are dealing with
	var currenti; 
	for (var i = 0; i < AQ.chain[slotgroup].length; i++){
		if (AQ.chain[slotgroup][i]['id'] == cwtagid){
			currenti = i;
			break;
		}
	}

	/* Throttling for tags. The general idea is that if it is less than 50% fill rate,
	 * we are probably sending too much traffic.
	 * We want the fill rate to determine how much traffic. 
	 * Over 20% fillrate - 100% of traffic
	 * ...
	 * 10% fill rate - 20% of traffic
	var cFill = AQ.chain[slotgroup][currenti]['cFill'];
	if (top.Athena.e(AQ.randomTagCalled) && Math.random() > cFill * 5 ){
		AQ.chain[slotgroup][currenti]['skipped'] = true;
		top.Athena.d("Skipping current AQ tag due to throttling. Fill rate: " + cFill, 4);
		AQ.next(slotgroup);
		return true;
	}
	 */

	// Get size from slotgroup
	var size, w, h;
	switch (slotgroup){
	  case 'MR': size = "300x250"; w = 300; h = 250; break;
	  case 'LB': size = "728x90"; w = 728; h = 90; break;
	  case 'WS': size = "160x600"; w = 160; h = 600; break;
          default: document.write('<!-- Invalid slotgroup for AQ.tag -->'); return false;
	}
	
	// Try to figure out the page we are on using multiple methods
	try {
		var cwpage = "http://" + Athena.getPageVar("hostname") + Athena.getPageVar("request");
	} catch (e) {
		if (top != self && !Athena.e(document.referrer)) { // iframe
			cwpage = document.referrer;
		}
	}

	// Build the cw url
	var cwurl = 'http://tag.contextweb.com/TAGPUBLISH/getad.aspx';
	var cwp = { 
		"tagver": 1,
		"if": 0,
		"ca": "VIEWAD",
		"cp": AQ.cwpid,
		"ct": cwtagid,
		"cf": size,
		"cn": 1,
		"cr": 200,
		"cw": w,
		"ch": h,
		"cads": 0,
		"rq": 1,
		"cwu": cwpage, 
		"mrnd": Athena.rand
	};
	cwurl += '?' + Athena.buildQueryString(cwp, '&');

	// Send attempt beacon
	AQ.beacon(cwtagid, 'attempt');

	// Mark the chain.
	AQ.chain[slotgroup][currenti]['attempt'] = true;
	
	if ( currenti === 0 ) {
		// Check for load, only on the first tag
		window.setTimeout("AQ.checkForLoad('" + slotgroup + "');", 750 * AQ.chain[slotgroup].length);
	}

	// Write the tag
	AQ.win.document.write('<script type="text/javascript" src="' + cwurl + '"><\/scr' + 'ipt>');
	AQ.win.document.write('<script><\/script>');

	return true;
};


/************************************************************************
 *									*
 * Begin AdsInContent, a class used for putting ads inline in the article
 *									*
************************************************************************/ 

var Ad_Inspector = {};


Ad_Inspector.overlay = function( level ) {
	level = level || 0;
        var divs = document.getElementsByTagName("div");
        for (var i = 0, l = divs.length; i < l; i++){
                if (typeof divs[i].id != "undefined" && divs[i].id.match(/_load$/)){
                        var div = document.createElement("DIV");
        		var slotname = divs[i].id.replace(/_load$/, '');
			if (Athena.e(Athena.chain[slotname])){
				// This isn't going to do any good
				continue;
			}
                        div.id = divs[i].id + "_overlay";
                        div.style["width"] = divs[i].clientWidth + "px";
                        var z = divs[i].style["zIndex"] || 0;
                        div.style["zIndex"] = 500;
                        div.style["left"] = divs[i].style["left"];
                        div.style["backgroundColor"] = "yellow";
                        div.style["color"] = "black";
                        div.style["fontSize"] = "80%";
                        div.style["lineHeight"] = "90%";
                        div.style["position"] = "absolute";
                        div.innerHTML = Ad_Inspector.getHops(slotname, div);
			if (level > 0){
        			div.innerHTML += "<p>Script Urls<ol><li>" + Ad_Inspector.getScriptUrls(Athena.$(slotname + "_load")).join("<li>") + "</ol>";
			}
                        divs[i].appendChild(div);
                }
        }
};

Ad_Inspector.getHops = function(slotname, destDiv){
        var text = [], link;
	text.push("Athena info for " + slotname + ":");
        for (var i = 0, l = Athena.chain[slotname].length; i < l; i++){
                link = Athena.chain[slotname][i];
		var s = link.network_name + " #" +
			'<a href=http://athena-ads.wikia.com/athena/dashboard/add_tag?tag_id=' +
			link.tag_id + ' target=_blank>' + link.tag_id + '</a>';
                if (link.loaded === true){
                        text.push(s + " loaded");
			break;
                } else {
                        text.push(s + " rejected");
                }
        }
	return text.join(", ");
};


/* Find/ return all the urls from script tags within the given element. 
 * If no e is supplied, use document
 * Returns an array, empty one if none found, false on error
 */
Ad_Inspector.getScriptUrls = function(e) {
	if (typeof e != "object") {
		e = document;
	}
	var scripts = e.getElementsByTagName("script");
	var out = [];
	for (var i = 0, l = scripts.length; i < l; i++){
		if (scripts[i].src){
			out.push(scripts[i].src);
		}	
	}
	return out;
};



/************************************************************************
 *									*
 * Begin AthenaDART a class used for generating DART urls		*
 *									*
************************************************************************/ 

/* Logic for generating a dart tag. Documentation stripped from this version.
 * See AdProviderDART.php for more info
 */ 

var AthenaDART = {
	sites : {
	  'Auto' : 'wka.auto',
	  'Creative' : 'wka.crea',
	  'Education' : 'wka.edu',
	  'Entertainment' : 'wka.ent',
	  'Finance' : 'wka.fin',
	  'Gaming' : 'wka.gaming',
	  'Green' : 'wka.green',
	  'Humor' : 'wka.humor',
	  'Lifestyle' : 'wka.life',
	  'Music' : 'wka.music',
	  'Philosophy' : 'wka.phil',
	  'Politics' : 'wka.poli',
	  'Science' : 'wka.sci',
	  'Sports' : 'wka.sports',
	  'Technology' : 'wka.tech',
	  'Test Site' : 'wka.test',
	  'Toys' : 'wka.toys',
	  'Travel' : 'wka.travel'},
	slotconfig : {
	   'TOP_RIGHT_BOXAD': {'tile': 1, 'loc': "top"}, 
	   'TOP_LEADERBOARD': {'tile': 2, 'loc': "top", 'dcopt': "ist"},
	   'DOCKED_LEADERBOARD': {'tile': 8, 'loc': "bottom"},
	   'LEFT_SKYSCRAPER_1': {'tile': 3, 'loc': "top"},
	   'LEFT_SKYSCRAPER_2': {'tile': 3, 'loc': "middle"},
	   'LEFT_SKYSCRAPER_3': {'tile': 6, 'loc': "middle"},
	   'FOOTER_BOXAD': {'tile': 5, 'loc': "footer"},
	   'PREFOOTER_LEFT_BOXAD': {'tile': 5, 'loc': "footer"},
	   'PREFOOTER_RIGHT_BOXAD': {'tile': 5, 'loc': "footer"},
	   'PREFOOTER_BIG': {'tile': 5, 'loc': "footer"},
	   'HOME_TOP_RIGHT_BOXAD': {'tile': 1, 'loc': "top"},
	   'HOME_TOP_LEADERBOARD': {'tile': 2, 'loc': "top", 'dcopt': "ist"},
	   'HOME_LEFT_SKYSCRAPER_1': {'tile':3, 'loc': "top"},
	   'HOME_LEFT_SKYSCRAPER_2': {'tile':3, 'loc': "middle"},
	   'INCONTENT_BOXAD_1': {'tile':4, 'loc': "middle"},
	   'INCONTENT_BOXAD_2': {'tile':5, 'loc': "middle"},
	   'INCONTENT_BOXAD_3': {'tile':6, 'loc': "middle"},
	   'INCONTENT_BOXAD_4': {'tile':7, 'loc': "middle"},
	   'INCONTENT_BOXAD_5': {'tile':8, 'loc': "middle"},
	   'INCONTENT_LEADERBOARD_1': {'tile':4, 'loc': "middle"},
	   'INCONTENT_LEADERBOARD_2': {'tile':5, 'loc': "middle"},
	   'INCONTENT_LEADERBOARD_3': {'tile':6, 'loc': "middle"},
	   'INCONTENT_LEADERBOARD_4': {'tile':7, 'loc': "middle"},
	   'INCONTENT_LEADERBOARD_5': {'tile':8, 'loc': "middle"},
	   'EXIT_STITIAL_INVISIBLE': {'tile':1, 'loc': "exit", 'dcopt': "ist"},
	   'EXIT_STITIAL_BOXAD_1': {'tile':2, 'loc': "exit"},
	   'EXIT_STITIAL_BOXAD_2': {'tile':3, 'loc': "exit"},
	   'INVISIBLE_1': {'tile':10, 'loc': "invisible"},
	   'INVISIBLE_2': {'tile':11, 'loc': "invisible"},
	   'HOME_INVISIBLE_TOP': {'tile':12, 'loc': "invisible"},
	   'INVISIBLE_TOP': {'tile':13, 'loc': "invisible"}
	},
	sizeconfig : {
	   '300x250':'300x250,300x600',
	   '600x250':'600x250,300x250',
	   '728x90':'728x90,468x60',
	   '160x600':'160x600,120x600',
	   '0x0':'1x1'
	}
};

AthenaDART.callAd = function (slotname, size, network_options){
	if (Athena.e(AthenaDART.slotconfig[slotname])){
		Athena.d("Notice: AthenaDART not configured for " + slotname);
	}

	var url = AthenaDART.getUrl(slotname, size, network_options, false);
	return '<scr' + 'ipt type="text/javascript" src="' + url + '"><\/sc' + 'ript>';
};


AthenaDART.getUrl = function(slotname, size, network_options, iframe) {
	// Hack for dart sizes 
        if (AthenaDART.sizeconfig[size]){
                size = AthenaDART.sizeconfig[size];
        }
	var url = 'http://ad.doubleclick.net/' + 
		AthenaDART.getAdType(iframe) + '/' +
		AthenaDART.getDARTSite(Athena.getPageVar('hub')) + '/' +
		AthenaDART.getZone1(Athena.getPageVar('wgDBname')) + '/' +
		AthenaDART.getZone2() + ';' +
		AthenaDART.getAllDartKeyvalues(slotname) + 
		AthenaDART.getDcoptKV(slotname) +
		"sz=" + size + ';' +
		AthenaDART.getTileKV(slotname) +
                'mtfIFPath=/extensions/wikia/AdEngine/;' +  // http://www.google.com/support/richmedia/bin/answer.py?hl=en&answer=117857

		"ord=@@WIKIA_RANDOM@@?";

	url = url.replace(/@@WIKIA_RANDOM@@/, Athena.random());
	Athena.dartUrl = url;
	Athena.d("Dart URL = " + Athena.dartUrl, 4);
	return url;
};

AthenaDART.getAdType = function(iframe){
	if (iframe) {
		return 'adi';
	} else {
		return 'adj';
	}
};

AthenaDART.getDARTSite = function(hub){
	if(typeof AthenaDART.sites[hub] != "undefined"){
		return AthenaDART.sites[hub];
	} else {
		return 'wka.wikia';
	}
};

// Effectively the dbname, defaulting to wikia.
AthenaDART.getZone1 = function(dbname){
	// Zone1 is prefixed with "_" because zone's can't start with a number, and some dbnames do.
	if (Athena.e(dbname)){
		return '_wikia';
	} else {
		return '_' + dbname.replace('/[^0-9A-Z_a-z]/', '_');
	}
};

// Page type, ie, "home" or "article"
AthenaDART.getZone2 = function(){
	if(Athena.getPageVar('isMainPage') == 'true') {
		return 'home';
	} else {
		return 'article';
	}
};

AthenaDART.getTileKV = function (slotname){
	if (!Athena.e(AthenaDART.slotconfig[slotname]) && AthenaDART.slotconfig[slotname].tile){
		return 'tile=' + AthenaDART.slotconfig[slotname].tile + ';';
	} else {
		return '';
	}
};

AthenaDART.getDcoptKV = function(slotname){
	if (!Athena.e(AthenaDART.slotconfig[slotname]) && AthenaDART.slotconfig[slotname].dcopt){
		return 'dcopt=' + AthenaDART.slotconfig[slotname].dcopt + ';';
	} else {
		return '';
	}
};

AthenaDART.getLocKv = function (slotname){
	if (!Athena.e(AthenaDART.slotconfig[slotname]) && AthenaDART.slotconfig[slotname].loc){
		return 'loc=' + AthenaDART.slotconfig[slotname].loc + ';';
	} else {
		return '';
	}
};

// Title is one of the always-present key-values
AthenaDART.getArticleKV = function(){
	if (! Athena.e(Athena.getPageVar('article_id'))){
		return "artid=" + Athena.getPageVar('article_id') + ';';
	} else {
		return '';
	}
};


AthenaDART.getDomainKV = function (hostname){
	var lhost, pieces, sld='', np;
	lhost = hostname.toLowerCase();

	pieces = lhost.split(".");
	np = pieces.length;

	if (pieces[np-2] == 'co'){
		// .co.uk or .co.jp
		sld = pieces[np-3] + '.' + pieces[np-2] + '.' + pieces[np-1];
	} else {
		sld = pieces[np-2] + '.' + pieces[np-1];
	}

	if (sld !== ''){
		return 'dmn=' + sld.replace(/\./g, '_') + ';';
	} else {
		return '';
	}

};


AthenaDART.getAllDartKeyvalues = function (slotname){
	var out = 's0=' + AthenaDART.getDARTSite(Athena.getPageVar('hub')).replace(/wka\./, '') + ';' +
		's1=' + AthenaDART.getZone1(Athena.getPageVar('wgDBname')) + ';' +
		's2=' + AthenaDART.getZone2() + ';' +
		'@@WIKIA_PROVIDER_VALUES@@' +
		AthenaDART.getLocKv(slotname) +
		AthenaDART.getArticleKV() +
		'pos=' + slotname + ';'; 
	out = out.replace(/@@WIKIA_AQ@@/, Athena.getMinuteTargeting());
	out = out.replace(/@@WIKIA_PROVIDER_VALUES@@/, window.ProviderValues.string);
	return out;
};

/************************************************************************
 *									
 * Begin Meerkat library, adapted from 				
 * http://jarodtaylor.com/meerkat/js/jquery.meerkat.1.0.js 		
 * Made the following changes:
 *  Cleaned up javascript (jslint compatible)
 *  Cookie functionality ripped, we use frequency capping to prevent 	
 *  Removed IE < 6 support
 *  Added left': '0' to the meerkat-wrap class  for IE 7
 * second display							
 *
 * Depends on jquery
 * 
************************************************************************/ 

var meerkat = function (options) {
	
	this.settings = {
		close: 'none',
		dontShow: 'none',
		meerkatPosition: 'bottom',
		animationSpeed: 'slow',
		height: 'auto',
		background: 'none'
	};

	if(options){
		jQuery.extend(this.settings, options);
	}
	
	var settings = this.settings;
	
	$('html, body').css({'margin':'0', 'padding':'0', 'height':'100%'});
	$('#meerkat').show();
	
	$('#meerkat-wrap').css({'position':'fixed', 'width':'100%', 'height': settings.height, 'left': 0, 'z-index': 1000}).css(settings.meerkatPosition,"0");
	$('#meerkat-container').css({'background': settings.background, 'height': settings.height});
	//Give the close and dontShow elements a cursor (there's no need to use a href)
	$(settings.close+","+settings.dontShow).css({"cursor":"pointer"});
		
		
	$('#meerkat-wrap').hide().slideDown(settings.animationSpeed);
	$(settings.close).click(function(){
		$("#meerkat-wrap").slideUp();							
	});
			
	$(settings.dontShow).click(function () {
		$("#meerkat-wrap").slideUp();	
	});
};

/************************************************************************
 *									*
 * Begin JSON library from http://www.json.org/json2.json		*
 * Why on *earth* doesn't Javascript have a json_encode()???		*
 *									*
************************************************************************/ 


/* JSON from http://www.json.org/json2.js */
/*
    http://www.JSON.org/json2.js
    2008-11-19

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html

    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the object holding the key.

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.

    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.
*/


/*global JSON */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/

// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

if (!this.JSON) {
    var JSON = {};
}
(function () {

    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z';
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];

// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
	  default:
		break; // Shouldn't be necessary, but silences a jslint error 
        }

	return false;
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof JSON.stringify !== 'function') {
        JSON.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }

// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof JSON.parse !== 'function') {
        JSON.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('JSON.parse');
        };
    }
})();


/************************************************************************
 *									*
 * Begin XDM library for sending cross domain messages across iframes	*
 *									*
************************************************************************/ 


/* This Toolkit allows for you to send messages between windows, including cross domain.
 * Ideas borrowed from http://code.google.com/p/xssinterface/, but rewritten from scratch.
 *
 * XDM has two methods for sending messages cross domain. The first of which uses
 * postMessages(), an HTML 5 javascript method.  * As I write this, the following
 * browsers support postMessage
 * Firefox 3+
 * IE 8+
 * Safari 3 nightly builds. 
 *
 * For the rest of the browsers, we use a backward compatible hack that utilizes an
 * external html file that acts as a conduit for information - XDM.iframeUrl
 * This file is expected to be able to parse the query string and act upon
 * the parameters.
 */

var XDM = {
	allowedMethods : [],
	debugOn	   : false, // Print debug messages to console.log

	// These options only needed for the iframe based method,
	// for browsers that don't support postMessage
	// HTML file that calls "XDM.setCookieFromUrl"
	iframeUrl      : "/extensions/wikia/AdEngine/XDM_iframe.html", 
	postMessageEnabled : true // Set to false to force fallback method
};
/*
 * @param frame - the window object to execute the code in. Example: top, window.parent
 * @param method - the method to execute in the parent window. Note the other window has to be listening for it with XDMListen(), and the method must be in XDM.allowedMethods
 */ 
XDM.send = function (destWin, method, args){ 
	XDM.debug("XDM.send called from " + document.location.hostname);
	// Sanity checks
	if (typeof method != "string") {
		XDM.debug("Bad argument for XDM.send, 'method' is not a string, (" + typeof method + ")");
		return false;
	}
	if ( typeof args == "undefined" ){
		// Just set it to an empty array
		args = [];
	}

	if (XDM.canPostMessage()){
		return XDM._postMessage(destWin, method, args);
	} else {
		return XDM._postMessageWithIframe(destWin, method, args);
	}

};


XDM.getDestinationDomain = function(destWin){
	if (destWin == top){
		// Firefox and Safari don't allow children to read parent window,
		// pull domain from referrer. ;-)
		if (document.referrer.toString() !== ''){
			var m = document.referrer.toString().match(/https*:\/\/([^\/]+)/);
			XDM.debug("Hostname for destWin set to " + m[1] + " using referrer");
			return m[1];
		} else {
			return false;
		}
	} else {
		return destWin.location.hostname;
	}
};


XDM._postMessage = function(destWin, method, args) {
	XDM.debug("Sending message using postMessage()");
	var d = XDM.getDestinationDomain(destWin), targetOrigin;
	if (d === false){
		targetOrigin = '*';
	} else {
		targetOrigin = 'http://' + d;
	}
	

	var msg = XDM.serializeMessage(method, args);
	
	if(destWin.postMessage) { // HTML 5 Standard
		return destWin.postMessage(msg, targetOrigin);
	} else if(destWin.document.postMessage) { // Opera 9
		return destWin.document.postMessage(msg, targetOrigin);
	} else {
		throw ("No supported way of using postMessage");
	}
};


XDM._postMessageWithIframe = function(destWin, method, args) {
	XDM.debug("Sending message using iframe");
	var d = XDM.getDestinationDomain(destWin), targetOrigin;
	if (d === false){
		// No where to send 
		return false;
	} else {
		targetOrigin = 'http://' + d;
	}

	var iframeUrl = targetOrigin + XDM.iframeUrl + '?' + XDM.serializeMessage(method, args);
	XDM.debug("Calling iframe dispatch url: " + iframeUrl);
	
	if (typeof XDM.iframe == "undefined"){
		XDM.iframe = document.createElement("iframe");
		XDM.iframe.style.display = "none";
		XDM.iframe.width = 0;
		XDM.iframe.height = 0;
                if (document.body === null){
                        document.firstChild.appendChild(document.createElement("body"));
                }
		document.body.appendChild(XDM.iframe);
	}
	XDM.iframe.src = iframeUrl;
	
	return false;
};


XDM.serializeMessage = function(method, args){
	var out = 'method=' + escape(method.toString());
        for (var i = 0, l = args.length; i < l; i++){
                out += ';arg' + (i+1) + '=' + escape(args[i]);
        }
	XDM.debug("Serialized message: " + out);
	return out;
};


XDM.canPostMessage = function(){
	if (XDM.postMessageEnabled === false){
		return false;
	} else if( window.postMessage || window.document.postMessage) {
		return true;
	} else {
		return false;
	}
};


XDM.debug = function(msg){
	if (XDM.debugOn && typeof console == "object" && console.log){
		console.log("XDM debug: " +  msg);
	}
};


XDM.listenForMessages = function(handler){
	if (XDM.canPostMessage()){
		if (window.addEventListener) { // W3C
			return window.addEventListener("message", handler, false);
		} else if (window.attachEvent){ // IE 
			return window.attachEvent("onmessage", handler);
		}
	} else {
		// Remote iframe will execute the messages
		return true;
	}
};


XDM.isAllowedMethod = function(method){
	var found = false;
	for (var i = 0, l = XDM.allowedMethods.length; i < l; i++){
		if (method.toString() === XDM.allowedMethods[i]){
			found = true;
			break;
		}
        }
	return found;
};


XDM.executeMessage = function(serializedMessage){
	var nvpairs = XDM.parseQueryString(serializedMessage);
	if ( XDM.isAllowedMethod(nvpairs["method"])){
		// Execute the code. Note we prepend top to make sure it's executing in the right place.
		var code = "top." + nvpairs["method"]; 
		var functionArgs = [];
		// Build up the argument list
		for (var prop in nvpairs){
			if (prop.substring(0, 3) == "arg"){
				functionArgs.push(nvpairs[prop].replace(/"/g, '\\"'));
			}
		}

		if (functionArgs.length > 0){
			code += '("' + functionArgs.join('","') + '");';
		} else {
                	code += "();";
		}

		XDM.debug("Evaluating " + code + " from iframe");
		return eval(code);
	} else {
		XDM.debug("Invalid method: " + nvpairs["method"]);
		return false;
	}
};


/* Nick wrote: This code looks at the query string supplied in the url and parses it.
 * It returns an associative array of url decoded name value pairs
 */
XDM.parseQueryString = function (qs){
  var ret = [], intIndex;

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

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

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

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

    var varName ='', varValue='';
    if ((intIndex = nvpairs[i].indexOf('=')) != -1) {
      varName = decodeURIComponent(nvpairs[i].substr(0, intIndex));
      varValue = decodeURIComponent(nvpairs[i].substr(intIndex + 1));
    } else {
      varName = decodeURIComponent(nvpairs[i].substr(0, intIndex));
      varValue = '';
    }

    if (varName === '' || varValue === ''){
      continue;
    }

    ret[varName]=varValue;
  }

  return ret;
};


// Tell the parent window to listen to hop messages from hop.js
function crossDomainMessage(message){
        XDM.allowedMethods = ["Athena.iframeHop"];
        XDM.executeMessage(message.data);       
}
XDM.listenForMessages(crossDomainMessage);

/* Special callback function for hopping. Originally implemented for VideoEgg.
 * http://developer.videoegg.com/mediawiki/index.php/VideoEgg_Ad_Platform_Integration_Guide_-_Website#Step_6._.28Optional_But_Highly_Recommended.29_Specify_an_alternate_behavior_when_no_ad_is_available
 * Your function will be called when no ad is available with a single argument containing the DOM div object where the ad would normally appear. This allows you to collapse the div or fill it with alternate content. For example, you could collapse the ad unit div and dynamically fill another div at another location on your page: function myNoAdCallback(div) { div.style.display = "none"; insertMyAd(); } var ve_alternate = myNoAdCallback;
 */
Athena.collapseAndHop = function(div){
        div.style.display = "none";
	Athena.hop();
};
var ve_alternate = Athena.collapseAndHop;

