_WGLOBAL = window;

if(window.parent != window && document.getElementById("js-bcq-fif")) {
	_WGLOBAL = window.parent;
}
_WGLOBAL.bc = _WGLOBAL.bc || [];
bc = _WGLOBAL.bc;

_WGLOBAL._bcc = _WGLOBAL._bcc || {};
_bcc = _WGLOBAL._bcc;

_WGLOBAL._bcq = _WGLOBAL._bcq || [];
_bcq = _WGLOBAL._bcq;

_WGLOBAL.BOOMR = _WGLOBAL.BOOMR || {};
BOOMR = _WGLOBAL.BOOMR;

bc.utils.log('Loading the Beacon Configuration...');

/**
 * Configuration variable for the Beacon Library. "bcc" stands for BeaCon Configuration
 * @author Carlos Soto <csoto@walmartlabs.com>
 */
var bcc = {
	configuration: {
        // Configuration for the Walmart Instrumentation Code goes here.
		page_type_map: 		{}
        // ...
    },
    partners : {
        boomerang: {
            configuration: {
                beacon_url: 	bc.domain, // you can also hardcode the domain, but that variable will always have the beacon domain in it.
				site_domain: 	_WGLOBAL.document.domain, // you can also hardcode the domain, but that variable will always have it.
				log: 			bc.utils.log, //you can send "false" to disable logging
				RT : 			{cookie:null}
            },
            actions: {}
        },
        
        omniture: {
        	configuration : {
        		log:		bc.utils.log,
        		autorun: 	false
        	},
        	actions: {
        		ITEM:{
	        		visitorMigrationKey			: {value : "4EB1C758"	},
	        		visitorMigrationServer		: {value : "walmart.112.2o7.net"	},
	        		trackingServer 				: {value : 'omniture.walmart.com'	},
	        		trackingServerSecure		: {value : 'omniture-ssl.walmart.com'	},
	        		reportSuite 				: {template		: 'walmartcom,walmart{{category}}',
	        									   format  		: 'LOWER_CASE'	},
	        									   
	        		pageName:{
	        					template  : '[{{category}}] Product Details'	
	        				 },
	        		prop1  : {
	        					attribute : 'pageType'
	        			 	 },
	        		prop2  : {
	        			 		attribute : {
	        			 		 iterator : 'item',
	        			 		 properties : ['title']
	        			 	 	}
	        			 	 },
	        		prop3  : {
	        			 		attribute : 'category'
	        			 	 },
	        		prop4  : {
	        					attribute : 'category'
	        				 },
	        		prop5  : {
	        					attribute : 'category'
	        			 	 },
	        		prop8  : {
	        			 		value	  : 'Product Details'
	        			 	 },
	        		prop10 : {
	        			 		attribute : {
	        			 		 iterator : 'vendors',
	        			 		 separator : '|'
	        			 	 	}
	        			 	 },
	        		prop11 : {
	        			 		attribute : 'prevPageName'
	        			 	 },
	        		prop12 : {
	        			 		template  : 'D={{trafficType}}:{{pageName}}' 
	        			 	 },
	        		prop17 : {
	        			 		attribute : 'wmvisitorId'
	        			 	 },
	        		prop19 : {
	        			 		attribute : 's_code'
	        			 	 },
	        		prop21 : {
	        			 		attribute : {
	        			 		 iterator : 'deliveryMethod',
	        			 		 separator : ','
	        			 	 	}
	        			 	 },
	        		prop26 : {
	        			 		attribute : 'wmsessionId'
	        			 	 },
	        		prop34 : {
	        			 		template : '{{prevStoreId}}>{{currStoreId}}'
	        			 	 },
	        		prop38 : {
	        			 		attribute : 'pageType'
	        			 	 },
	        		prop42 : {
	        			 		attribute : 'pageType'
	        			 	 },
	        		prop57 : {
	        			 		attribute : 'prevPageName'
	        			 	 },
	
	        		eVar28 : {
	        			 		attribute : 'localTime'
	        			 	 },
	        		eVar54 : {
	        			 		attribute : 'fsvPageName'
	        			 	 },
	        		eVar55 : {
	        			 		value : 'D=pageName'
	        			 	 },
	        		eVar59 : {
	        			 		attribute : 'fsvCategory'
	        			 	 },
	        		eVar62 : {
	        			 		attribute : 'isThShpAvail'
	        			 	 },
	        		eVar63 : {
	        			 		value : 'D=pageName'
	        			 	 },
	        		eVar64 : {
	        			 		value : 'D=c2'
	        			 	 },
	        		events : {
	        			 		value : 'prodView,event1,event33'
	        			 	 }
        		}
    		}
    	}
        
    }
};

/*
 * Copyright (c) 2011, Yahoo! Inc.  All rights reserved.
 * Copyrights licensed under the BSD License. See the accompanying LICENSE.txt file for terms.
 */

/**
\file boomerang.js
boomerang measures various performance characteristics of your user's browsing
experience and beacons it back to your server.

\details
To use this you'll need a web site, lots of users and the ability to do
something with the data you collect.  How you collect the data is up to
you, but we have a few ideas.
*/

// Measure the time the script started
// This has to be global so that we don't wait for the entire
// BOOMR function to download and execute before measuring the
// time.  We also declare it without `var` so that we can later
// `delete` it.  This is the only way that works on Internet Explorer
BOOMR_start = new Date().getTime();

// beaconing section
// the parameter is the window
(function(w) {

var impl, boomr, k, d=w.document;

// Short namespace because I don't want to keep typing BOOMERANG
if(typeof BOOMR === "undefined") {
	BOOMR = {};
}
// don't allow this code to be included twice
if(BOOMR.version) {
	return;
}

BOOMR.version = "0.9.1370628800";


// impl is a private object not reachable from outside the BOOMR object
// users can set properties by passing in to the init() method
impl = {
	// properties
	beacon_url: "",
	// strip out everything except last two parts of hostname.
	// This doesn't work well for domains that end with a country tld,
	// but we allow the developer to override site_domain for that.
	site_domain: w.location.hostname.
				replace(/.*?([^.]+\.[^.]+)\.?$/, '$1').
				toLowerCase(),
	//! User's ip address determined on the server.  Used for the BA cookie
	user_ip: '',

	events: {
		"page_ready": [],
		"page_unload": [],
		"visibility_changed": [],
		"before_beacon": []
	},

	vars: {},

	disabled_plugins: {},

	fireEvent: function(e_name, data) {
		var i, h, e;
		if(!this.events.hasOwnProperty(e_name)) {
			return false;
		}

		e = this.events[e_name];

		for(i=0; i<e.length; i++) {
			h = e[i];
			h[0].call(h[2], data, h[1]);
		}

		return true;
	},

	addListener: function(el, sType, fn) {
		if(el.addEventListener) {
			el.addEventListener(sType, fn, false);
		}
		else if(el.attachEvent) {
			el.attachEvent("on" + sType, fn);
		}
	}
};


// We create a boomr object and then copy all its properties to BOOMR so that
// we don't overwrite anything additional that was added to BOOMR before this
// was called... for example, a plugin.
boomr = {
	t_start: BOOMR_start,
	t_end: null,

	// Utility functions
	utils: {
		getCookie: function(name) {
			if(!name) {
				return null;
			}

			name = ' ' + name + '=';

			var i, cookies;
			cookies = ' ' + d.cookie + ';';
			if ( (i=cookies.indexOf(name)) >= 0 ) {
				i += name.length;
				cookies = cookies.substring(i, cookies.indexOf(';', i));
				return cookies;
			}

			return null;
		},

		setCookie: function(name, subcookies, max_age, path, domain, sec) {
			var value = "",
			    k, nameval, c,
			    exp = "";

			if(!name) {
				return false;
			}

			for(k in subcookies) {
				if(subcookies.hasOwnProperty(k)) {
					value += '&' + encodeURIComponent(k)
							+ '=' + encodeURIComponent(subcookies[k]);
				}
			}
			value = value.replace(/^&/, '');

			if(max_age) {
				exp = new Date();
				exp.setTime(exp.getTime() + max_age*1000);
				exp = exp.toGMTString();
			}

			nameval = name + '=' + value;
			c = nameval +
				((max_age) ? "; expires=" + exp : "" ) +
				((path) ? "; path=" + path : "") +
				((typeof domain !== "undefined") ? "; domain="
						+ (domain !== null ? domain : impl.site_domain ) : "") +
				((sec) ? "; secure" : "");

			if ( nameval.length < 4000 ) {
				d.cookie = c;
				// confirm cookie was set (could be blocked by user's settings, etc.)
				return ( value === this.getCookie(name) );
			}

			return false;
		},

		getSubCookies: function(cookie) {
			var cookies_a,
			    i, l, kv,
			    cookies={};

			if(!cookie) {
				return null;
			}

			cookies_a = cookie.split('&');

			if(cookies_a.length === 0) {
				return null;
			}

			for(i=0, l=cookies_a.length; i<l; i++) {
				kv = cookies_a[i].split('=');
				kv.push("");	// just in case there's no value
				cookies[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1]);
			}

			return cookies;
		},

		removeCookie: function(name) {
			return this.setCookie(name, {}, 0, "/", null);
		},

		pluginConfig: function(o, config, plugin_name, properties) {
			var i, props=0;

			if(!config || !config[plugin_name]) {
				return false;
			}

			for(i=0; i<properties.length; i++) {
				if(typeof config[plugin_name][properties[i]] !== "undefined") {
					o[properties[i]] = config[plugin_name][properties[i]];
					props++;
				}
			}

			return (props>0);
		}
	},

	init: function(config) {
		var i, k,
		    properties = ["beacon_url", "site_domain", "user_ip"];

		if(!config) {
			config = {};
		}

		for(i=0; i<properties.length; i++) {
			if(typeof config[properties[i]] !== "undefined") {
				impl[properties[i]] = config[properties[i]];
			}
		}

		if(typeof config.log  !== "undefined") {
			this.log = config.log;
		}
		if(!this.log) {
			this.log = function(m,l,s) { };
		}

		for(k in this.plugins) {
			// config[pugin].enabled has been set to false
			if( config[k]
				&& ("enabled" in config[k])
				&& config[k].enabled === false
			) {
				impl.disabled_plugins[k] = 1;
				continue;
			}
			else if(impl.disabled_plugins[k]) {
				delete impl.disabled_plugins[k];
			}

			// plugin exists and has an init method
			if(this.plugins.hasOwnProperty(k)
				&& typeof this.plugins[k].init === "function"
			) {
				this.plugins[k].init(config);
			}
		}

		// The developer can override onload by setting autorun to false
		if(!("autorun" in config) || config.autorun !== false) {
			impl.addListener(w, "load",
						function() {
							impl.fireEvent("page_ready");
						}
					);
		}

		// visibilitychange is useful to detect if the page loaded through prerender
		// or if the page never became visible
		// http://www.w3.org/TR/2011/WD-page-visibility-20110602/
		// http://www.nczonline.net/blog/2011/08/09/introduction-to-the-page-visibility-api/
		var fire_visible = function() { impl.fireEvent("visibility_changed"); }
		if(d.webkitVisibilityState)
			impl.addListener(d, "webkitvisibilitychange", fire_visible);
		else if(d.msVisibilityState)
			impl.addListener(d, "msvisibilitychange", fire_visible);
		else if(d.visibilityState)
			impl.addListener(d, "visibilitychange", fire_visible);

		// This must be the last one to fire
		impl.addListener(w, "unload", function() { w=null; });

		return this;
	},

	// The page dev calls this method when they determine the page is usable.
	// Only call this if autorun is explicitly set to false
	page_ready: function() {
		impl.fireEvent("page_ready");
		return this;
	},

	subscribe: function(e_name, fn, cb_data, cb_scope) {
		var i, h, e;

		if(!impl.events.hasOwnProperty(e_name)) {
			return this;
		}

		e = impl.events[e_name];

		// don't allow a handler to be attached more than once to the same event
		for(i=0; i<e.length; i++) {
			h = e[i];
			if(h[0] === fn && h[1] === cb_data && h[2] === cb_scope) {
				return this;
			}
		}
		e.push([ fn, cb_data || {}, cb_scope || null ]);

		// Attach unload handlers directly to the window.onunload and
		// window.onbeforeunload events. The first of the two to fire will clear
		// fn so that the second doesn't fire. We do this because technically
		// onbeforeunload is the right event to fire, but all browsers don't
		// support it.  This allows us to fall back to onunload when onbeforeunload
		// isn't implemented
		if(e_name === 'page_unload') {
			impl.addListener(w, "unload",
						function() {
							if(fn) {
								fn.call(cb_scope, null, cb_data);
							}
							fn=cb_scope=cb_data=null;
						}
					);
			impl.addListener(w, "beforeunload",
						function() {
							if(fn) {
								fn.call(cb_scope, null, cb_data);
							}
							fn=cb_scope=cb_data=null;
						}
					);
		}

		return this;
	},

	addVar: function(name, value) {
		if(typeof name === "string") {
			impl.vars[name] = value;
		}
		else if(typeof name === "object") {
			var o = name, k;
			for(k in o) {
				if(o.hasOwnProperty(k)) {
					impl.vars[k] = o[k];
				}
			}
		}
		return this;
	},

	removeVar: function() {
		var i, params;
		if(!arguments.length) {
			return this;
		}

		if(arguments.length === 1
				&& Object.prototype.toString.apply(arguments[0]) === "[object Array]") {
			params = arguments[0];
		}
		else {
			params = arguments;
		}

		for(i=0; i<params.length; i++) {
			if(impl.vars.hasOwnProperty(params[i])) {
				delete impl.vars[params[i]];
			}
		}

		return this;
	},

	sendBeacon: function() {
		var k, url, img, nparams=0;

		// At this point someone is ready to send the beacon.  We send
		// the beacon only if all plugins have finished doing what they
		// wanted to do
		for(k in this.plugins) {
			if(this.plugins.hasOwnProperty(k)) {
				if(impl.disabled_plugins[k]) {
					continue;
				}
				if(!this.plugins[k].is_complete()) {
					return this;
				}
			}
		}

		// If we reach here, all plugins have completed
		impl.fireEvent("before_beacon", impl.vars);

		// Don't send a beacon if no beacon_url has been set
		if(!impl.beacon_url) {
			return this;
		}

		// if there are already url parameters in the beacon url,
		// change the first parameter prefix for the boomerang url parameters to &

		url = impl.beacon_url + ((impl.beacon_url.indexOf('?') > -1)?'&':'?') +
			'v=' + encodeURIComponent(BOOMR.version) +
			'&u=' + encodeURIComponent(d.URL.replace(/#.*/, ''));
			// use d.URL instead of location.href because of a safari bug

		for(k in impl.vars) {
			if(impl.vars.hasOwnProperty(k)) {
				nparams++;
				url += "&" + encodeURIComponent(k)
					+ "="
					+ (
						impl.vars[k]===undefined || impl.vars[k]===null
						? ''
						: encodeURIComponent(impl.vars[k])
					);
			}
		}

		// only send beacon if we actually have something to beacon back
		if(nparams) {
			img = new Image();
			img.src=url;
		}

		return this;
	}

};

delete BOOMR_start;

var make_logger = function(l) {
	return function(m, s) {
		this.log(m, l, "boomerang" + (s?"."+s:"")); return this;
	};
};

boomr.debug = make_logger("debug");
boomr.info = make_logger("info");
boomr.warn = make_logger("warn");
boomr.error = make_logger("error");

if(w.YAHOO && w.YAHOO.widget && w.YAHOO.widget.Logger) {
	boomr.log = w.YAHOO.log;
}
else if(typeof w.Y !== "undefined" && typeof w.Y.log !== "undefined") {
	boomr.log = w.Y.log;
}
else if(typeof console !== "undefined" && typeof console.log !== "undefined") {
	boomr.log = function(m,l,s) { console.log(s + ": [" + l + "] ", m); };
}


for(k in boomr) {
	if(boomr.hasOwnProperty(k)) {
		BOOMR[k] = boomr[k];
	}
}

BOOMR.plugins = BOOMR.plugins || {};

}(_WGLOBAL));


// end of boomerang beaconing section
// Now we start built in plugins.


// This is the Round Trip Time plugin.  Abbreviated to RT
// the parameter is the window
(function(w) {

var d=w.document;

BOOMR = BOOMR || {};
BOOMR.plugins = BOOMR.plugins || {};

// private object
var impl = {
	complete: false,	//! Set when this plugin has completed

	timers: {},		//! Custom timers that the developer can use
				// Format for each timer is { start: XXX, end: YYY, delta: YYY-XXX }
	cookie: 'RT',		//! Name of the cookie that stores the start time and referrer
	cookie_exp:600,		//! Cookie expiry in seconds
	strict_referrer: true,	//! By default, don't beacon if referrers don't match.
				// If set to false, beacon both referrer values and let
				// the back end decide

	navigationType: 0,
	navigationStart: undefined,
	responseStart: undefined,

	// The start method is fired on page unload.  It is called with the scope
	// of the BOOMR.plugins.RT object
	start: function() {
		var t_end, t_start = new Date().getTime();

		// Disable use of RT cookie by setting its name to a falsy value
		if(!this.cookie) {
			return this;
		}

		// We use document.URL instead of location.href because of a bug in safari 4
		// where location.href is URL decoded
		if(!BOOMR.utils.setCookie(this.cookie,
						{ s: t_start, r: d.URL.replace(/#.*/, '') },
						this.cookie_exp,
						"/", null)
		) {
			BOOMR.error("cannot set start cookie", "rt");
			return this;
		}

		t_end = new Date().getTime();
		if(t_end - t_start > 50) {
			// It took > 50ms to set the cookie
			// The user Most likely has cookie prompting turned on so
			// t_start won't be the actual unload time
			// We bail at this point since we can't reliably tell t_done
			BOOMR.utils.removeCookie(this.cookie);

			// at some point we may want to log this info on the server side
			BOOMR.error("took more than 50ms to set cookie... aborting: "
					+ t_start + " -> " + t_end, "rt");
		}

		return this;
	},

	initNavTiming: function() {
		var ti, p, source;

		if(this.navigationStart) {
			return;
		}

		// Get start time from WebTiming API see:
		// https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html
		// http://blogs.msdn.com/b/ie/archive/2010/06/28/measuring-web-page-performance.aspx
		// http://blog.chromium.org/2010/07/do-you-know-how-slow-your-web-page-is.html
		p = w.performance || w.msPerformance || w.webkitPerformance || w.mozPerformance;

		if(p && p.navigation) {
			this.navigationType = p.navigation.type;
		}

		if(p && p.timing) {
			ti = p.timing;
		}
		else if(w.chrome && w.chrome.csi && w.chrome.csi().startE) {
			// Older versions of chrome also have a timing API that's sort of documented here:
			// http://ecmanaut.blogspot.com/2010/06/google-bom-feature-ms-since-pageload.html
			// source here:
			// http://src.chromium.org/viewvc/chrome/trunk/src/chrome/renderer/loadtimes_extension_bindings.cc?view=markup
			ti = {
				navigationStart: w.chrome.csi().startE
			};
			source = "csi";
		}
		else if(w.gtbExternal && w.gtbExternal.startE()) {
			// The Google Toolbar exposes navigation start time similar to old versions of chrome
			// This would work for any browser that has the google toolbar installed
			ti = {
				navigationStart: w.gtbExternal.startE()
			};
			source = 'gtb';
		}

		if(ti) {
			// Always use navigationStart since it falls back to fetchStart
			// If not set, we leave t_start alone so that timers that depend
			// on it don't get sent back.  Never use requestStart since if
			// the first request fails and the browser retries, it will contain
			// the value for the new request.
			BOOMR.addVar("rt.start", source || "navigation");
			this.navigationStart = ti.navigationStart || undefined;
			this.responseStart = ti.responseStart || undefined;

			// bug in Firefox 7 & 8 https://bugzilla.mozilla.org/show_bug.cgi?id=691547
			if(navigator.userAgent.match(/Firefox\/[78]\./)) {
				this.navigationStart = ti.unloadEventStart || ti.fetchStart || undefined;
			}
		}
		else {
			BOOMR.warn("This browser doesn't support the WebTiming API", "rt");
		}

		return;
	}
};

BOOMR.plugins.RT = {
	// Methods

	init: function(config) {
		impl.complete = false;
		impl.timers = {};

		BOOMR.utils.pluginConfig(impl, config, "RT",
					["cookie", "cookie_exp", "strict_referrer"]);

		BOOMR.subscribe("page_ready", this.done, null, this);
		BOOMR.subscribe("page_unload", impl.start, null, impl);

		if(BOOMR.t_start) {
			// How long does it take Boomerang to load up and execute
			this.startTimer('boomerang', BOOMR.t_start);
			this.endTimer('boomerang', BOOMR.t_end);	// t_end === null defaults to current time

			// How long did it take till Boomerang started
			this.endTimer('boomr_fb', BOOMR.t_start);
		}

		return this;
	},

	startTimer: function(timer_name, time_value) {
		if(timer_name) {
			if (timer_name === 't_page') {
				this.endTimer('t_resp', time_value);
			}
			impl.timers[timer_name] = {start: (typeof time_value === "number" ? time_value : new Date().getTime())};
			impl.complete = false;
		}

		return this;
	},

	endTimer: function(timer_name, time_value) {
		if(timer_name) {
			impl.timers[timer_name] = impl.timers[timer_name] || {};
			if(!("end" in impl.timers[timer_name])) {
				impl.timers[timer_name].end =
						(typeof time_value === "number" ? time_value : new Date().getTime());
			}
		}

		return this;
	},

	setTimer: function(timer_name, time_delta) {
		if(timer_name) {
			impl.timers[timer_name] = { delta: time_delta };
		}

		return this;
	},

	// Called when the page has reached a "usable" state.  This may be when the
	// onload event fires, or it could be at some other moment during/after page
	// load when the page is usable by the user
	done: function() {
		var t_start, r, r2,
		    subcookies, basic_timers = { t_done: 1, t_resp: 1, t_page: 1},
		    ntimers = 0, t_name, timer, t_other=[];

		if(impl.complete) {
			return this;
		}

		impl.initNavTiming();

		if(
			(d.webkitVisibilityState && d.webkitVisibilityState === "prerender")
			||
			(d.msVisibilityState && d.msVisibilityState === 3)
		) {
			// This means that onload fired through a pre-render.  We'll capture this
			// time, but wait for t_done until after the page has become either visible
			// or hidden (ie, it moved out of the pre-render state)
			// http://code.google.com/chrome/whitepapers/pagevisibility.html
			// http://www.w3.org/TR/2011/WD-page-visibility-20110602/
			// http://code.google.com/chrome/whitepapers/prerender.html

			this.startTimer("t_load", impl.navigationStart);
			this.endTimer("t_load");		// this will measure actual onload time for a prerendered page
			this.startTimer("t_prerender", impl.navigationStart);
			this.startTimer("t_postrender");	// time from prerender to visible or hidden

			BOOMR.subscribe("visibility_changed", this.done, null, this);

			return this;
		}

		// If the dev has already called endTimer, then this call will do nothing
		// else, it will stop the page load timer
		this.endTimer("t_done");

		if(impl.responseStart) {
			// Use NavTiming API to figure out resp latency and page time
			// t_resp will use the cookie if available or fallback to NavTiming
			this.endTimer("t_resp", impl.responseStart);
			if(impl.timers.t_load) {
				this.setTimer("t_page", impl.timers.t_load.end - impl.responseStart);
			}
			else {
				this.setTimer("t_page", new Date().getTime() - impl.responseStart);
			}
		}
		else if(impl.timers.hasOwnProperty('t_page')) {
			// If the dev has already started t_page timer, we can end it now as well
			this.endTimer("t_page");
		}

		// If a prerender timer was started, we can end it now as well
		if(impl.timers.hasOwnProperty('t_postrender')) {
			this.endTimer("t_postrender");
			this.endTimer("t_prerender");
		}

		// A beacon may be fired automatically on page load or if the page dev fires
		// it manually with their own timers.  It may not always contain a referrer
		// (eg: XHR calls).  We set default values for these cases

		r = r2 = d.referrer.replace(/#.*/, '');

		// If impl.cookie is not set, the dev does not want to use cookie time
		if(impl.cookie) {
			subcookies = BOOMR.utils.getSubCookies(BOOMR.utils.getCookie(impl.cookie));
			BOOMR.utils.removeCookie(impl.cookie);

			if(subcookies && subcookies.s && subcookies.r) {
				r = subcookies.r;
				if(!impl.strict_referrer || r === r2) {
					t_start = parseInt(subcookies.s, 10);
				}
			}
		}

		if(t_start && impl.navigationType != 2) {	// 2 is TYPE_BACK_FORWARD but the constant may not be defined across browsers
			BOOMR.addVar("rt.start", "cookie");	// if the user hit the back button, referrer will match, and cookie will match
		}						// but will have time of previous page start, so t_done will be wrong
		else {
			t_start = impl.navigationStart;
		}

		// make sure old variables don't stick around
		BOOMR.removeVar('t_done', 't_page', 't_resp', 'r', 'r2', 'rt.bstart', 'rt.end');

		BOOMR.addVar('rt.bstart', BOOMR.t_start);
		BOOMR.addVar('rt.end', impl.timers.t_done.end);

		for(t_name in impl.timers) {
			if(!impl.timers.hasOwnProperty(t_name)) {
				continue;
			}

			timer = impl.timers[t_name];

			// if delta is a number, then it was set using setTimer
			// if not, then we have to calculate it using start & end
			if(typeof timer.delta !== "number") {
				if(typeof timer.start !== "number") {
					timer.start = t_start;
				}
				timer.delta = timer.end - timer.start;
			}

			// If the caller did not set a start time, and if there was no start cookie
			// then timer.delta will be NaN, in which case we discard it.
			if(isNaN(timer.delta)) {
				continue;
			}

			if(basic_timers.hasOwnProperty(t_name)) {
				BOOMR.addVar(t_name, timer.delta);
			}
			else {
				t_other.push(t_name + '|' + timer.delta);
			}
			ntimers++;
		}

		if(ntimers) {
			BOOMR.addVar("r", r);

			if(r2 !== r) {
				BOOMR.addVar("r2", r2);
			}

			if(t_other.length) {
				BOOMR.addVar("t_other", t_other.join(','));
			}
		}

		impl.timers = {};
		impl.complete = true;

		BOOMR.sendBeacon();	// we call sendBeacon() anyway because some other plugin
					// may have blocked waiting for RT to complete
		return this;
	},

	is_complete: function() { return impl.complete; }

};

}(_WGLOBAL));

// End of RT plugin

// This is the Bandwidth & Latency plugin abbreviated to BW
// the parameter is the window
(function(w) {

var d=w.document;

BOOMR = BOOMR || {};
BOOMR.plugins = BOOMR.plugins || {};

// We choose image sizes so that we can narrow down on a bandwidth range as
// soon as possible the sizes chosen correspond to bandwidth values of
// 14-64kbps, 64-256kbps, 256-1024kbps, 1-2Mbps, 2-8Mbps, 8-30Mbps & 30Mbps+
// Anything below 14kbps will probably timeout before the test completes
// Anything over 60Mbps will probably be unreliable since latency will make up
// the largest part of download time. If you want to extend this further to
// cover 100Mbps & 1Gbps networks, use image sizes of 19,200,000 & 153,600,000
// bytes respectively
// See https://spreadsheets.google.com/ccc?key=0AplxPyCzmQi6dDRBN2JEd190N1hhV1N5cHQtUVdBMUE&hl=en_GB
// for a spreadsheet with the details
var images=[
	{ name: "image-0.png", size: 11483, timeout: 1400 },
	{ name: "image-1.png", size: 40658, timeout: 1200 },
	{ name: "image-2.png", size: 164897, timeout: 1300 },
	{ name: "image-3.png", size: 381756, timeout: 1500 },
	{ name: "image-4.png", size: 1234664, timeout: 1200 },
	{ name: "image-5.png", size: 4509613, timeout: 1200 },
	{ name: "image-6.png", size: 9084559, timeout: 1200 }
];

images.end = images.length;
images.start = 0;

// abuse arrays to do the latency test simply because it avoids a bunch of
// branches in the rest of the code.
// I'm sorry Douglas
images.l = { name: "image-l.gif", size: 35, timeout: 1000 };

// private object
var impl = {
	// properties
	base_url: 'images/',
	timeout: 15000,
	nruns: 5,
	latency_runs: 10,
	user_ip: '',
	cookie_exp: 7*86400,
	cookie: 'BA',

	// state
	results: [],
	latencies: [],
	latency: null,
	runs_left: 0,
	aborted: false,
	complete: false,
	running: false,

	// methods

	// numeric comparator.  Returns negative number if a < b, positive if a > b and 0 if they're equal
	// used to sort an array numerically
	ncmp: function(a, b) { return (a-b); },

	// Calculate the interquartile range of an array of data points
	iqr: function(a)
	{
		var l = a.length-1, q1, q3, fw, b = [], i;

		q1 = (a[Math.floor(l*0.25)] + a[Math.ceil(l*0.25)])/2;
		q3 = (a[Math.floor(l*0.75)] + a[Math.ceil(l*0.75)])/2;

		fw = (q3-q1)*1.5;

		l++;

		for(i=0; i<l && a[i] < q3+fw; i++) {
			if(a[i] > q1-fw) {
				b.push(a[i]);
			}
		}

		return b;
	},

	calc_latency: function()
	{
		var	i, n,
			sum=0, sumsq=0,
			amean, median,
			std_dev, std_err,
			lat_filtered;

		// We first do IQR filtering and use the resulting data set
		// for all calculations
		lat_filtered = this.iqr(this.latencies.sort(this.ncmp));
		n = lat_filtered.length;

		BOOMR.debug(lat_filtered, "bw");

		// First we get the arithmetic mean, standard deviation and standard error
		// We ignore the first since it paid the price of DNS lookup, TCP connect
		// and slow start
		for(i=1; i<n; i++) {
			sum += lat_filtered[i];
			sumsq += lat_filtered[i] * lat_filtered[i];
		}

		n--;	// Since we started the loop with 1 and not 0

		amean = Math.round(sum / n);

		std_dev = Math.sqrt( sumsq/n - sum*sum/(n*n));

		// See http://en.wikipedia.org/wiki/1.96 and http://en.wikipedia.org/wiki/Standard_error_%28statistics%29
		std_err = (1.96 * std_dev/Math.sqrt(n)).toFixed(2);

		std_dev = std_dev.toFixed(2);


		n = lat_filtered.length-1;

		median = Math.round(
				(lat_filtered[Math.floor(n/2)] + lat_filtered[Math.ceil(n/2)]) / 2
			);

		return { mean: amean, median: median, stddev: std_dev, stderr: std_err };
	},

	calc_bw: function()
	{
		var	i, j, n=0,
			r, bandwidths=[], bandwidths_corrected=[],
			sum=0, sumsq=0, sum_corrected=0, sumsq_corrected=0,
			amean, std_dev, std_err, median,
			amean_corrected, std_dev_corrected, std_err_corrected, median_corrected,
			nimgs, bw, bw_c;

		for(i=0; i<this.nruns; i++) {
			if(!this.results[i] || !this.results[i].r) {
				continue;
			}

			r=this.results[i].r;

			// the next loop we iterate through backwards and only consider the largest
			// 3 images that succeeded that way we don't consider small images that
			// downloaded fast without really saturating the network
			nimgs=0;
			for(j=r.length-1; j>=0 && nimgs<3; j--) {
				// if we hit an undefined image time, we skipped everything before this
				if(!r[j]) {
					break;
				}
				if(r[j].t === null) {
					continue;
				}

				n++;
				nimgs++;

				// multiply by 1000 since t is in milliseconds and not seconds
				bw = images[j].size*1000/r[j].t;
				bandwidths.push(bw);

				bw_c = images[j].size*1000/(r[j].t - this.latency.mean);
				bandwidths_corrected.push(bw_c);
			}
		}

		BOOMR.debug('got ' + n + ' readings', "bw");

		BOOMR.debug('bandwidths: ' + bandwidths, "bw");
		BOOMR.debug('corrected: ' + bandwidths_corrected, "bw");

		// First do IQR filtering since we use the median here
		// and should use the stddev after filtering.
		if(bandwidths.length > 3) {
			bandwidths = this.iqr(bandwidths.sort(this.ncmp));
			bandwidths_corrected = this.iqr(bandwidths_corrected.sort(this.ncmp));
		} else {
			bandwidths = bandwidths.sort(this.ncmp);
			bandwidths_corrected = bandwidths_corrected.sort(this.ncmp);
		}

		BOOMR.debug('after iqr: ' + bandwidths, "bw");
		BOOMR.debug('corrected: ' + bandwidths_corrected, "bw");

		// Now get the mean & median.
		// Also get corrected values that eliminate latency
		n = Math.max(bandwidths.length, bandwidths_corrected.length);
		for(i=0; i<n; i++) {
			if(i<bandwidths.length) {
				sum += bandwidths[i];
				sumsq += Math.pow(bandwidths[i], 2);
			}
			if(i<bandwidths_corrected.length) {
				sum_corrected += bandwidths_corrected[i];
				sumsq_corrected += Math.pow(bandwidths_corrected[i], 2);
			}
		}

		n = bandwidths.length;
		amean = Math.round(sum/n);
		std_dev = Math.sqrt(sumsq/n - Math.pow(sum/n, 2));
		std_err = Math.round(1.96 * std_dev/Math.sqrt(n));
		std_dev = Math.round(std_dev);

		n = bandwidths.length-1;
		median = Math.round(
				(bandwidths[Math.floor(n/2)] + bandwidths[Math.ceil(n/2)]) / 2
			);

		n = bandwidths_corrected.length;
		amean_corrected = Math.round(sum_corrected/n);
		std_dev_corrected = Math.sqrt(sumsq_corrected/n - Math.pow(sum_corrected/n, 2));
		std_err_corrected = (1.96 * std_dev_corrected/Math.sqrt(n)).toFixed(2);
		std_dev_corrected = std_dev_corrected.toFixed(2);

		n = bandwidths_corrected.length-1;
		median_corrected = Math.round(
					(
						bandwidths_corrected[Math.floor(n/2)]
						+ bandwidths_corrected[Math.ceil(n/2)]
					) / 2
				);

		BOOMR.debug('amean: ' + amean + ', median: ' + median, "bw");
		BOOMR.debug('corrected amean: ' + amean_corrected + ', '
				+ 'median: ' + median_corrected, "bw");

		return {
			mean: amean,
			stddev: std_dev,
			stderr: std_err,
			median: median,
			mean_corrected: amean_corrected,
			stddev_corrected: std_dev_corrected,
			stderr_corrected: std_err_corrected,
			median_corrected: median_corrected
		};
	},

	defer: function(method)
	{
		var that=this;
		return setTimeout(function() { method.call(that); that=null;}, 10);
	},

	load_img: function(i, run, callback)
	{
		var url = this.base_url + images[i].name
			+ '?t=' + (new Date().getTime()) + Math.random(),	// Math.random() is slow, but we get it before we start the timer
		    timer=0, tstart=0,
		    img = new Image(),
		    that=this;

		img.onload=function() {
			img.onload=img.onerror=null;
			img=null;
			clearTimeout(timer);
			if(callback) {
				callback.call(that, i, tstart, run, true);
			}
			that=callback=null;
		};
		img.onerror=function() {
			img.onload=img.onerror=null;
			img=null;
			clearTimeout(timer);
			if(callback) {
				callback.call(that, i, tstart, run, false);
			}
			that=callback=null;
		};

		// the timeout does not abort download of the current image, it just sets an
		// end of loop flag so we don't attempt download of the next image we still
		// need to wait until onload or onerror fire to be sure that the image
		// download isn't using up bandwidth.  This also saves us if the timeout
		// happens on the first image.  If it didn't, we'd have nothing to measure.
		timer=setTimeout(function() {
					if(callback) {
						callback.call(that, i, tstart, run, null);
					}
				},
				images[i].timeout
					+ Math.min(400, this.latency ? this.latency.mean : 400)
			);

		tstart = new Date().getTime();
		img.src=url;
	},

	lat_loaded: function(i, tstart, run, success)
	{
		if(run !== this.latency_runs+1) {
			return;
		}

		if(success !== null) {
			var lat = new Date().getTime() - tstart;
			this.latencies.push(lat);
		}
		// if we've got all the latency images at this point,
		// so we can calculate latency
		if(this.latency_runs === 0) {
			this.latency = this.calc_latency();
		}

		this.defer(this.iterate);
	},

	img_loaded: function(i, tstart, run, success)
	{
		if(run !== this.runs_left+1) {
			return;
		}

		if(this.results[this.nruns-run].r[i])	{	// already called on this image
			return;
		}

		// if timeout, then we set the next image to the end of loop marker
		if(success === null) {
			this.results[this.nruns-run].r[i+1] = {t:null, state: null, run: run};
			return;
		}

		var result = {
				start: tstart,
				end: new Date().getTime(),
				t: null,
				state: success,
				run: run
			};
		if(success) {
			result.t = result.end-result.start;
		}
		this.results[this.nruns-run].r[i] = result;

		// we terminate if an image timed out because that means the connection is
		// too slow to go to the next image
		if(i >= images.end-1
			|| typeof this.results[this.nruns-run].r[i+1] !== "undefined"
		) {
			BOOMR.debug(this.results[this.nruns-run], "bw");
			// First run is a pilot test to decide what the largest image
			// that we can download is. All following runs only try to
			// download this image
			if(run === this.nruns) {
				images.start = i;
			}
			this.defer(this.iterate);
		} else {
			this.load_img(i+1, run, this.img_loaded);
		}
	},

	finish: function()
	{
		if(!this.latency) {
			this.latency = this.calc_latency();
		}
		var	bw = this.calc_bw(),
			o = {
				bw:		bw.median_corrected,
				bw_err:		parseFloat(bw.stderr_corrected, 10),
				lat:		this.latency.mean,
				lat_err:	parseFloat(this.latency.stderr, 10),
				bw_time:	Math.round(new Date().getTime()/1000)
			};

		BOOMR.addVar(o);

		// If we have an IP address we can make the BA cookie persistent for a while
		// because we'll recalculate it if necessary (when the user's IP changes).
		if(!isNaN(o.bw)) {
			BOOMR.utils.setCookie(this.cookie,
						{
							ba: Math.round(o.bw),
							be: o.bw_err,
							l:  o.lat,
							le: o.lat_err,
							ip: this.user_ip,
							t:  o.bw_time
						},
						(this.user_ip ? this.cookie_exp : 0),
						"/",
						null
				);
		}

		this.complete = true;
		BOOMR.sendBeacon();
		this.running = false;
	},

	iterate: function()
	{
		if(this.aborted) {
			return false;
		}

		if(!this.runs_left) {
			this.finish();
		}
		else if(this.latency_runs) {
			this.load_img('l', this.latency_runs--, this.lat_loaded);
		}
		else {
			this.results.push({r:[]});
			this.load_img(images.start, this.runs_left--, this.img_loaded);
		}
	},

	setVarsFromCookie: function(cookies) {
		var ba = parseInt(cookies.ba, 10),
		    bw_e = parseFloat(cookies.be, 10),
		    lat = parseInt(cookies.l, 10) || 0,
		    lat_e = parseFloat(cookies.le, 10) || 0,
		    c_sn = cookies.ip.replace(/\.\d+$/, '0'),	// Note this is IPv4 only
		    t = parseInt(cookies.t, 10),
		    p_sn = this.user_ip.replace(/\.\d+$/, '0'),

		// We use the subnet instead of the IP address because some people
		// on DHCP with the same ISP may get different IPs on the same subnet
		// every time they log in

		    t_now = Math.round((new Date().getTime())/1000);	// seconds

		// If the subnet changes or the cookie is more than 7 days old,
		// then we recheck the bandwidth, else we just use what's in the cookie
		if(c_sn === p_sn && t >= t_now - this.cookie_exp) {
			this.complete = true;
			BOOMR.addVar({
				'bw': ba,
				'lat': lat,
				'bw_err': bw_e,
				'lat_err': lat_e
			});

			return true;
		}

		return false;
	}

};
}(_WGLOBAL));
// End of BW plugin



/*jslint white: false, devel: true, onevar: true, browser: true, undef: true, nomen: true, regexp: false, continue: true, plusplus: false, bitwise: false, newcap: true, maxerr: 50, indent: 4 */
/*
 * Copyright (c), Buddy Brewer.
 */

/**
\file navtiming.js
Plugin to collect metrics from the W3C Navigation Timing API. For more information about Navigation Timing,
see: http://www.w3.org/TR/navigation-timing/
*/

// w is the window object
(function(w) {

// First make sure BOOMR is actually defined.  It's possible that your plugin is loaded before boomerang, in which case
// you'll need this.
BOOMR = BOOMR || {};
BOOMR.plugins = BOOMR.plugins || {};

// A private object to encapsulate all your implementation details
var impl = {
	complete: false,
	done: function() {
		var p, pn, pt, data;
		p = w.performance || w.msPerformance || w.webkitPerformance || w.mozPerformance;
		if(p && p.timing && p.navigation) {
			BOOMR.info("This user agent supports NavigationTiming.", "nt");
			pn = w.performance.navigation;
			pt = w.performance.timing;
			data = {
				nt_red_cnt: pn.redirectCount,
				nt_nav_type: pn.type,
				nt_nav_st: pt.navigationStart,
				nt_red_st: pt.redirectStart,
				nt_red_end: pt.redirectEnd,
				nt_fet_st: pt.fetchStart,
				nt_dns_st: pt.domainLookupStart,
				nt_dns_end: pt.domainLookupEnd,
				nt_con_st: pt.connectStart,
				nt_con_end: pt.connectEnd,
				nt_req_st: pt.requestStart,
				nt_res_st: pt.responseStart,
				nt_res_end: pt.responseEnd,
				nt_domloading: pt.domLoading,
				nt_domint: pt.domInteractive,
				nt_domcontloaded_st: pt.domContentLoadedEventStart,
				nt_domcontloaded_end: pt.domContentLoadedEventEnd,
				nt_domcomp: pt.domComplete,
				nt_load_st: pt.loadEventStart,
				nt_load_end: pt.loadEventEnd,
				nt_unload_st: pt.unloadEventStart,
				nt_unload_end: pt.unloadEventEnd
			};
			if (pt.secureConnectionStart) {
				// secureConnectionStart is OPTIONAL in the spec
				data.nt_ssl_st = pt.secureConnectionStart;
			}
			BOOMR.addVar(data);
		}
		this.complete = true;
		BOOMR.sendBeacon();
	}
};

BOOMR.plugins.NavigationTiming = {
	init: function() {
		BOOMR.subscribe("page_ready", impl.done, null, impl);
		return this;
	},

	is_complete: function() {
		return impl.complete;
	}
};

}(_WGLOBAL));


BOOMR.t_end = new Date().getTime();

bc.utils.log('Loading the Beacon Instrumentation Library...');

// -------------------------------------------------- UTILITY FUNCTIONS --------------------------------------------------
/**
 * @desc Binds an event to a DOM Object
 * @desc Inspired from: https://github.com/yahoo/boomerang/blob/master/boomerang.js
 * @author Carlos Soto <csoto@walmartlabs.com>
 * @param {Object} el DOM Object
 * @param {String} event Type of event to which we need to bind the function (click, load, etc.)
 * @param {Function} fn The function to bind 
 * @return void
 */
bc.utils.bind = function (el, event, fn) {
	'use strict';
	if (el.addEventListener) {
		el.addEventListener(event, fn, false);
	} else if(el.attachEvent) {
		el.attachEvent("on" + event, fn);
	}
};

/**
 * @desc Creates a Class Constructor
 * @desc Inspired from: http://www.htmlgoodies.com/html5/tutorials/create-an-object-oriented-javascript-class-constructor.html#fbid=QlAWBNynR5l
 * @author Carlos Soto <csoto@walmartlabs.com>
 * @param {Object} attributes Set of attributes/methods the new Class will have
 * @return {Object} new Class Constructor
 */
bc.utils.createClass = function (attributes) {
	'use strict';
	var prop, // iterator
		klass = function () { // New class to be returned    
        this.initialize.apply(this, arguments);          
    };
  
	// ------------------------------
	// Add the methods/attributes sent as parameters to the new class
    for (prop in attributes) { 
       klass.prototype[prop] = attributes[prop];
    }
	
	// ------------------------------
	// Set a default initialize function 
	// in case the class doesn't have one
    if (!klass.prototype.initialize) {
		klass.prototype.initialize = function () {};      
    }
    return klass;    
};

// -------------------------------------------------- UTILITY FUNCTIONS --------------------------------------------------
/**
 * @desc Extends and creates a class from a list of objects
 * @desc Inspired from: https://bugzilla.mozilla.org/show_bug.cgi?id=433351
 * @author Carlos Soto <csoto@walmartlabs.com>
 * @param {Objects} [object1, object2, etc.] List of following objects to merge 
 * @return {Object} merged Class
 */
bc.utils.extend = function () {
	'use strict';
	return bc.utils.createClass(bc.utils.merge.apply(this, arguments));
};

/**
 * @desc Transforms an underscore/lower case String to a CamelCase String
 * @desc Inspired from: http://stackoverflow.com/questions/6660977/convert-hyphens-to-camel-case-camelcase
 * @author Carlos Soto <csoto@walmartlabs.com>
 * @param {String} text String to transform
 * @return {String} camelCased String
 */
bc.utils.toCamelCase = function (text, capitalize) {
	'use strict';
	var result = text.replace(/(\-[a-z])/g, function($1){return $1.toUpperCase().replace('-','');});
	if (capitalize) {
		result = text.charAt(0).toUpperCase() + text.slice(1);
	}
    return result;
};


/**
 * @desc Templating engine for Beacon. The wildcards are inserted on the templates as {{wilcard_name}} 
 * @desc this will be replaced by it's String or Integer value. Constant values are also accepted. 
 * @desc The rest of the code can be javascript (which must return a value). The key of the wildcard 
 * @desc must exist on the data object, otherwise, the template will be returned as a result.
 * @author Carlos Soto <csoto@walmartlabs.com>
 * @param {String} tpl The template to be used for creating the result, ex: 
 * - "typeof {{page_type}} === 'string' && page_type_map[{{page_type}}] ? page_type_map[{{page_type}}] : {{page_type}}"
 * - "{{page_type}}"
 * - "HOME"
 * - "5"
 * @param {String} action The action the user is trying to tag using the template function
 * @param {Object} data The associate array with the wildcards as the keys and the values to be used instead. It can be null for constant templates. Ex:
 * - {page_type: 42}
 * @return {Mixed} result of the templating.
 */
bc.utils.template = function(tpl, action, data) {
	'use strict';
	var result = '', rg, i, value, my_data = data || {};
	
	// ------------------------------
	// Add the global data as part of the data for the templating engine
	bc.utils.merge(my_data, bc.data, {action: action});
	
	if (tpl.value) {													//Rule 1
		result = tpl.value;		
	} else if (tpl.attribute && typeof tpl.attribute === "string") {	//Rule 2
		result = my_data[tpl.attribute];
		if (tpl.cases) {												//Rule 7
			result = bc.utils.conditionalTpl(tpl, result);
		}
	} else if (tpl.template) {											//Rule 3 & 4
		result = bc.utils.simpleTpl(tpl.template, my_data);
	} else if (tpl.attribute && typeof tpl.attribute === "object") {	//Rule 5
		result = bc.utils.attributeTpl(my_data, tpl.attribute);
	} else if (tpl.attributes) {										//Rule 6
		result = bc.utils.attributesTpl(my_data, tpl.attributes, tpl.separator);
	} else if (tpl.func && typeof tpl.func === "function") {			//Function mapping
		result = tpl.func.apply(this, my_data, {});
	}
	
	result = bc.utils.format(tpl.format, result);
		
	/**Do we need eval?
	 * try {
	 * result = eval(tpl);
	 * } catch(e) {
	 * result = tpl;
	 * }
	 */
	return result;
};

/**
 * @desc Format a String to a lowercase, UPPERCASE or CamelCase String
 * @author Laxmikant Kurhe <lkurhe@walmartlabs.com>
 * @param {String} format to whcih string will get converted
 * @return {String} value to be formatted
 */
bc.utils.format = function(type, value){
	'use strict';
	switch(type){
		case "LOWER_CASE" : value = typeof result === "string" ? value.toLowerCase() : value; break;
		case "UPPER_CASE" : value = typeof result === "string" ? value.toUpperCase() : value; break;
		case "CAMEL_CASE" : value = typeof result === "string" ? bc.utils.toCamelCase(value) : value; break;
	}
	return value;
};

/**
 * @desc replace attribute names with actual value of corresponding attribute
 * @desc '{{attributeName}} is template' converted to 'attributeValue is template' 
 * @desc '{{category}}|{{category_id}}' converted to 'categoryName|000'
 * @author Laxmikant Kurhe <lkurhe@walmartlabs.com>
 * @param {Object} template Object
 * @return {Object} attribute value object
 */
bc.utils.simpleTpl = function(tpl, my_data){
	'use strict';
	var i, val, rg, tpl = tpl || '';
	for (i in my_data) {
		// of the data for that entry is a string, then add quotes (") to the ends of it before the eval
		//value = typeof my_data[i] === 'string' ? "'" + my_data[i] + "'" : my_data[i];
		val = my_data[i];
		rg = new RegExp('{{' + i + '}}', 'g');
		if(val){
			tpl = tpl.replace(rg, val);
		}
	}
	return tpl;
};

/**
 * @desc matches the attribute value based on different conditional cases
 * @desc say if {{category_id}} is : 000 then value will be "First category", 111 then value will be "second category"
 * @desc if nothing matches then default value is "default category"   
 * @author Laxmikant Kurhe <lkurhe@walmartlabs.com>
 * @param {Object} template Object
 * @param {String} attribute current value
 */
bc.utils.conditionalTpl = function(tpl, result){
	'use strict';
	var i, len, cases, caseMatch = false, result = result || '';
	cases = tpl.cases;
	len = cases.length;
	if(cases && len){
		for(i = 0; i < len; i++){
			if(cases[i] && cases[i].attrValue === result){
				result = cases[i].value;
				caseMatch = true;
				break;
			}
		}
		if(!caseMatch){
			result = tpl.defaultValue;
		}
	}
	return result;
};

/**
 * @desc fetch values of the form id|price|qty from an object
 * @author Laxmikant Kurhe <lkurhe@walmartlabs.com>
 * @param {Object} attribute values object
 * @param {Object} template
 */
bc.utils.attributeTpl = function(my_data, tplAttr){
	'use strict';
	var result = '', i, len, 
		attrList, attrName,
		valueObj,
		valSeparator = (tplAttr && tplAttr.separator) || ',';
	
	//Get Array out of actual object and key will be the param name
	if(Array.isArray(tplAttr['properties'])){
		attrList = tplAttr['properties'];
		valueObj = my_data[tplAttr['iterator']] || {};
		len = attrList.length;
		for (i = 0; i < len; i++){
			attrName = attrList[i];
			result += (valueObj[attrName] ?  valueObj[attrName] : '') + valSeparator;
		}			
	}else if(Array.isArray(my_data[tplAttr['iterator']])){
		//This is array of string
		return my_data[tplAttr['iterator']].join(valSeparator);
	}
	return result.substr(0, result.length - 1);
};

/**
 * @desc fetch values of the form id1|price1|qty1;id2|price2|qty3
 * @desc objSeparator is ";" 
 * @author Laxmikant Kurhe <lkurhe@walmartlabs.com>
 * @param {Object} attribute values object
 * @param {Object} template
 * @param {String} object separator
 */
bc.utils.attributesTpl = function(my_data, tplAttr, objSeparator){
	'use strict';
	var result = '', key, i, k, len, attrLen, 
		attrList, attrName,
		valueList, valueObj,
		valSeparator = (tplAttr && tplAttr.separator) || ',',
		objSeparator = objSeparator || ',';
	
	//Get Array out of actual object and key will be the param name
	if(Array.isArray(tplAttr['properties'])){
		attrList = tplAttr['properties'];
	
		valueList = my_data[tplAttr['iterator']];
		if(Array.isArray(valueList)){
			len = valueList.length;
			for (k = 0; k < len; k++){
				attrLen = attrList.length;
				valueObj = valueList[k] || {};
				for(i = 0; i < attrLen; i++){
					attrName = attrList[i];
					result += (valueObj[attrName] ? valueObj[attrName] : '') + valSeparator;
				}
				result = result.substr(0, result.length - 1);
				if(!objSeparator){
					return result;	
				}
				result += objSeparator;
			}
		}
	}
	return result.substr(0, result.length - 1);
};

// -------------------------------------------------- Partner Handlers --------------------------------------------------
/**
 * @desc Namespace for the Partner Handler Objects, we'll have one for each of our partners, and they will be
 * @desc used depending on whether they are available in the configuration object (bcc). We're also Creating a
 * @desc Partner Handler class that will be used as an abstract class to extend all of the other Partner Handlers
 * @author Carlos Soto <csoto@walmartlabs.com> 
 */
bc.handlers = bc.handlers || {}; 
bc.classes.Handler = {
	/**
	 * Initialize function for the Partner Handlers
	 * @author Carlos Soto <csoto@walmartlabs.com>
	 * @param {Object} info Data with the configuration and the different actions enabled for the Partner
	 * @return {void}
	 */
	initialize : function (info) {
		'use strict';
		this.actions = info.actions;
		this.configuration = info.configuration;	
	},
	
	/**
	 * Merges the attributes sent by the consumer and the templates defined for the action into the parameters for the tag action
	 * @author Carlos Soto <csoto@walmartlabs.com>
	 * @param {String} action Name of the action we need to tag
	 * @param {Object} data Data set sent by the consumer
	 * @return {Object} set of params to send for the action
	 */
	getParams : function (action, attributes) {
		'use strict';
		var params = {},
			templates,
			i;
			
		// ------------------------------
		// Transform the data into the right parameters for the tag call
		if(this.actions[action] !== 'undefined') {
			templates = this.actions[action];
			for (i in templates) {
				params[i] = bc.utils.template(templates[i], action, attributes);
			}
		}
		return params;
	},
	
	/**
	 * @desc Placeholder for the "load" function of the Partner Handlers
	 * @author Carlos Soto <csoto@walmartlabs.com>
	 * @param {Function} callback Function to be called after the library has been loaded
	 * @return {void}
	 */	
	load : function (callback) {
		'use strict';
		callback();
	},
	
	/**
	 * @desc Placeholder for the "tagAction" function of the Partner Handlers
	 * @author Carlos Soto <csoto@walmartlabs.com>
	 * @param {String} action Name of the Action to be tagged.
     * @param {Object} attributes Beacon Attributes to be sent to Beacon and transformed into Partner Variables
     * @param {String} context the identifier of the context for the action (where the action took place)
	 * @return {Int} whether the action was tagged or not (0||1)
	 */
	tagAction : function (action, attributes, context) {
		'use strict';
		return 0;
	}
}
/**
 * @desc Boomerang Handler
 * @author Carlos Soto <csoto@walmartlabs.com> 
 */
bc.classes.Boomerang = bc.utils.extend({}, bc.classes.Handler, {
	/**
	 * @desc Initialize function for the Partner Handlers
	 * @author Carlos Soto <csoto@walmartlabs.com>
	 * @param {Object} info Data with the configuration and the different actions enabled for the Partner
	 * @return {void}
	 */
	initialize : function (info) {
		'use strict';
		this.actions = info.actions;
		this.configuration = info.configuration;
		// ------------------------------
		// Set the autorun value for Boomerang explicitly to false
		this.configuration.autorun = false;

		// ------------------------------		
		// Take the beacon_url and remove the protocol whatever that was it.
		var beacon_url = this.configuration.beacon_url.replace(/.*:\/\//g, "");

		// ------------------------------		
		// Add a slash (/) to the end if the beacon_url doesn't have it
		if (beacon_url.charAt(beacon_url.length - 1) !== '/') {
			beacon_url += '/';
		}	
		this.configuration.beacon_url = 'https://' + beacon_url + 'beacon.gif'
		
		
		// ------------------------------
		// Initialize Boomerang Here
		BOOMR.init(this.configuration);
		BOOMR.plugins.RT.startTimer('t_page', bc.options.start_time);
		BOOMR.plugins.RT.startTimer('t_window', bc.options.start_time);
	},
	
	/**
	 * Redefines the implementation of getParams. For Boomerang we don't need rules mapping
	 * @author Carlos Soto <csoto@walmartlabs.com>
	 * @param {String} action Name of the action we need to tag
     * @param {Object} attributes Beacon Attributes to be sent to Beacon and transformed into Partner Variables
     * @return {Object} set of params to send for the action
	 */
	getParams : function (action, attributes) {
		'use strict';
		return attributes;
	},
	
	/**
	 * @desc Tag function for Boomerang
	 * @author Carlos Soto <csoto@walmartlabs.com>
	 * @param {String} action Name of the Action to be tagged.
     * @param {Object} attributes Beacon Attributes to be sent to Beacon and transformed into Partner Variables
     * @param {String} context the identifier of the context for the action (where the action took place) 
	 * @return {Int} whether the action was tagged or not (0||1)
	 */	
	tagAction : function (action, attributes, context) {
		'use strict';
		var tag = 1, 
			params = this.getParams(action, attributes), 
			i, k, // iterators
			url = this.configuration.beacon_url,
			img;
			
            // ------------------------------
            // Add default values for the timestamp and the url if not present on the dataset
            params.ts = params.ts || new Date().getTime(); //Timestamp that's needed to add as a "random" parameter to avoid url caching
            params.pv_id = bc.page_view_id; // Add the Page View ID to the call
            params.a = action;
            params.u = params.u || _WGLOBAL.document.URL.replace(/#.*/, ''); // use URL instead of location.href because of a Safari bug
            params.ctx = context;
            
			// ------------------------------
			// This is a very special case for the entire library.
			// Since the BE is expecting the performance data from 
			// Boomerang, we're sending it for the action called "PAGE_READY"			
			if (action === 'PAGE_READY') {
                delete params.u; // remove the u param as it's going to be added by boomerang anyways
				if(bc.options.above_the_fold_end > 0){		// To capture above the fold timing if specified under setOptions
					BOOMR.plugins.RT.startTimer('t_atf', bc.options.start_time);
					BOOMR.plugins.RT.endTimer('t_atf', bc.options.above_the_fold_end);
				}
				BOOMR.plugins.RT.endTimer("t_page");
				// code-tempSolution specific to samsclub, which is required to identify perf data and samsclub dont want to make code changes :(
				// TODO: to be removed after discussion with samsclub
				if(!params.ctx && (bc.options.bh === 'beacon.samsclub.com' || bc.options.bh === 'beacon.qa.samsclub.com')){
					if(_WGLOBAL.pageData && typeof _WGLOBAL.pageData.page_type === 'string'){
						params.ctx = _WGLOBAL.pageData.page_type;
					}else if(typeof _WGLOBAL.currentPageType === 'string'){
						params.ctx = _WGLOBAL.currentPageType;
					}
				}else if(!params.ctx && (bc.options.bh === 'beacon.walmart.ca' || bc.options.bh === 'beacon.qa.walmart.ca')){
					if(_WGLOBAL.walmart && _WGLOBAL.walmart.context && typeof _WGLOBAL.walmart.context.pageType === 'string'){
						params.ctx = _WGLOBAL.walmart.context.pageType;
					}
				}else if(bc.options.bh === 'beacon.mweb.samsclub.com' || bc.options.bh === 'beacon.qa.mweb.samsclub.com'){
					// Including for request https://jira.walmart.com/browse/PULSE-1984 where dashboard is required for samsclub mweb
					// page event beacons are not yet triggered but dashboard is high priority, so pick from page perf events
					params.si = 'smmw';
				}
				BOOMR.addVar(params);
				BOOMR.page_ready();
			} else {
				// ------------------------------
                // Create the base url for the Beacon Call
				params.r = params.r || _WGLOBAL.document.referrer;
				url = url + ((url.indexOf('?') > -1)?'&':'?') + bc.utils.urlSerialize(params);
				img = new Image();
				img.src = url;
			}
		return tag;
	}
});
		
/**
 * @desc Omniture Handler
 * @author Laxmikant Kurhe <lkurhe@walmartlabs.com> 
 */
bc.classes.Omniture = bc.utils.extend({}, bc.classes.Handler, {
	/**
	 * @desc Initialize function for the Partner Handlers
	 * @author Laxmikant Kurhe <lkurhe@walmartlabs.com>
	 * @param {Object} info Data with the configuration and the different actions enabled for the Partner
	 * @return {void}
	 */
	initialize : function (info) {
		'use strict';
		this.actions = info.actions;
		this.configuration = info.configuration;
	},
	
	/**
	 * @desc Tags an error to Omniture
	 * @author Laxmikant Kurhe <lkurhe@walmartlabs.com>
	 * @param {String} action Name of the Action to be tagged.
	 * @param {Object} data Data to be sent to our partners
	 * @return {Int} whether the action was tagged or not (0||1)
	 */	
	tagError : function (action, data) {
		'use strict'
	},
	
	/**
	 * @desc Tag function for Omniture
	 * @author Laxmikant Kurhe <lkurhe@walmartlabs.com>
	 * @param {String} action Name of the Action to be tagged.
	 * @param {Object} data Data to be sent to our partners
	 * @return {Int} whether the action was tagged or not (0||1)
	 */	
	tagAction : function (action, data) {
		'use strict';
		var s_omni, 
			tag = 0,
			params = this.getParams(action, data) || {}, //{'s_account': 'walmartcom'},
			reportSuite = params.reportSuite;
		
		if(!reportSuite){
			bc.utils.log("No report suite found for action [" + action + "]");
			return tag;
		}
		s_omni = s_gi(reportSuite) || {};
		
		s_omni = bc.utils.merge(s_omni, params);
		//TODO : set all variables as prop evar and events
		if(s_omni.t){
			var s_code = s_omni.t();
			tag = 1;
		}
		return tag;
	}
});

// -------------------------------------------------- Commands Definition --------------------------------------------------

/**
 * @desc Merges the current bc.options with some new ones
 * @author Carlos Soto <csoto@walmartlabs.com>
 * @param {Object} options new options to be set
 * @return {void}
 */	
bc.commands._setOptions = function (options) {
	'use strict';
	bc.utils.merge(bc.options, options);
};

/**
 * @desc Default functionality for the hook to be called before sending tags
 * @author Carlos Soto <csoto@walmartlabs.com>
 * @param {String} action Name of the Action to be tagged.
 * @param {Object} attributes Beacon Attributes to be sent to Beacon and transformed into Partner Variables
 * @return {Object} new Beacon Attributes
 */	
bc.commands._beforeTag = function (action, attributes) {
	'use strict';
	return attributes;
};  

/**
 * @desc Default functionality for the hook to be called after a tag was sent
 * @author Carlos Soto <csoto@walmartlabs.com>
 * @param {String} action Name of the Action that was tagged.
 * @param {Object} attributes Beacon Attributes to be sent to Beacon and transformed into Partner Variables
 * @return {Object} new Beacon Attributes
 */	
bc.commands._afterTag = function (action, attributes) {
	'use strict';
};  


/**
 * @desc definition of the tagAction Command 
 * @desc sends the beacon attributes to each of the partners for it's subsequent tagging.
 * @author Carlos Soto <csoto@walmartlabs.com>
 * @param {String} action Name of the Action to be tagged.
 * @param {Object} Beacon Attributes to be sent to Beacon and transformed into Partner Variables
 * @return void
 */
bc.commands._tagAction = function (action, attributes, context) {
	'use strict';
	var i,
		tag_count = 0,
		ctx = context || bc.options.global_context;
		
	attributes = bc.commands._beforeTag(action, attributes);
	for (i in bc.handlers) {
		tag_count += bc.handlers[i].tagAction(action, attributes, ctx);
	}
	bc.commands._afterTag(action, attributes);
	
	// ------------------------------
	// Log how many Partners tagged the action
	if (!tag_count) {
		bc.utils.log('The action [' + action + '] was tagged by ' + tag_count + ' Partners');
	}
};

/**
 * @description Tags the "PAGE_READY" action, sending Page Performance Metrics (Boomerang data) to Beacon
 * @author Carlos Soto <csoto@walmartlabs.com>
 * @return void
 */
bc.commands._tagPageReady = function (attributes) {
	'use strict';
    attributes = attributes || {};
	bc.commands._tagAction('PAGE_READY', attributes);
};

/**
 * @description Infamous "push". Based on the push function from Google Analytics. Receives arrays of commands as parameters
 * @description and executes a specifc function from the bc.commands namespace if possible
 * @description inspired from: https://developers.google.com/analytics/devguides/collection/gajs/
 * @author Carlos Soto <csoto@walmartlabs.com>
 * @param {Array} an array or list of arrays containing at least the command to be executed
 * @return void
 */
bc.push = function () {
    'use strict';
    var i,
        cmd,
        name,
        len;

    // ------------------------------
    // Iterate through the arguments as people can add one or more 
    // commands Make sure that all of the commands are Arrays and 
    // that have at least one entry (the name of the command)
    len = arguments.length;
    for (i = 0; i<len ; i++) {
        cmd = arguments[i];
        if (Array.isArray(cmd) && cmd.length) {
            // ------------------------------
            // Get the name of the command and see if it's a "set" command (like _setAfterTag, or _setBeforeTag).
            // If it is, then, remove the "set" string from the name of the command, and then, transform the second character
            // to lowercase, so we transform a command from _setAfterTag to _afterTag. Then, replace the default hook that's
            // on bc.commands with the new implementation, which should be the second parameter of the command array.
            name = cmd[0];
            if (name !== '_setOptions' && name.search("_set") && cmd.lenth > 1 && typeof cmd[1] === 'function') {
                name = name.replace('set', '');
                bc.commands[name] = cmd[1];
            
            // ------------------------------
            // if it's not a "set" command, then look for the command un bc.commands, and if it exists,
            // then call that function using the rest of the entries of the command array (without the
            // command name, hence the shift() call) as the arguments for that command
            } else if (bc.commands.hasOwnProperty(name)) {
                cmd.shift(); // Remove the name of the command from the command array (so it only has arguments)
                bc.commands[name].apply(bc, cmd);
            }
        }
    }
};


// -------------------------------------------------- Main Controller --------------------------------------------------
/**
 * @desc Loads and initialize the Partner's libraries we need to deploy
 * @author Carlos Soto <csoto@walmartlabs.com>
 * @param {Object} Configuration data about the Partners
 * @return void
 */
(function (partners) {
	var i, partner, // iterator(s)
		class_name = '',
        queue; 
		
	for (i in partners) {
		// ------------------------------
		// Transform the id of the partner into a the class name of the Handler (ie:
		// from "boomerang" to "Boomerang", or "RICH_RELEVANCE" to "RichRelevance", etc.)
		class_name = bc.utils.toCamelCase(i, true);
		
		// ------------------------------
		// With the class name, create the Handler Object and load the libraries for that handler
		bc.handlers[i] = new bc.classes[class_name](bcc.partners[i]);
	}
	
    // ------------------------------
	// Create the _bcq into an object (Make the _bcq the same namespace as bc)
	queue = _bcq;
    _bcq = bc;
    _bcq.push.apply(bc, queue);
    if(window.parent && window.parent._bcq){
    	window.parent._bcq = _bcq;
    }
	
	// Bind the "_tagPageReay" command when the window loads if autorun = true
	/*if (bc.options.autorun) {
		bc.utils.bind(_WGLOBAL, 'load', function () {
            'use strict';
            // ------------------------------
            // Ask for the autorun once more, just in case they turned it off after the window.load event.
            if (bc.options.autorun) {
                bc.commands._tagPageReady();
            }
        });
	}else{
		BOOMR.plugins.RT.endTimer('t_window', (new Date()).getTime());
	}*/
})(bcc.partners);

bc.utils.log('Loading the Data Sniffer...');
(function() {
    'use strict';
    var sniff = function () {
        'use strict';
        bc.utils.log('Starting Sniffing...');
        _bcq.push(['_tagPageReady']);
    }
    
    if (bc.options.autorun){
        bc.utils.bind(_WGLOBAL, 'load', sniff);                
    }else{
		BOOMR.plugins.RT.endTimer('t_window', (new Date()).getTime());
	}
})();
