// usa.visa.com analytics code
// requires jquery library
//
//    implicit navigation events are tracked automatically: links, forms, image maps
//    to explicitly redirect to another site, use this code:
//        VISA.Analytics.redirect(url, description)
//         * description is an optional string/object
//         * the function will perform the redirect
//    to trigger a (non-navigating) beacon on an arbitrary event follow this example:
//        onMouseOver="VISA.Analytics.event(event)";
//        or, using jQuery:
//        $(element).mouseover(VISA.Analytics.event);
//
// HISTORY
//    Zng 2009.09.24: added google eventTracker on this.navigation function to track external links
//                    added referURL to page load visa tracking if the host name is different
//    LoL 2007.07.16: added generic .event code, dummy console, more robust link checking
//    LoL 2007.07.30: added redirect code
//    LoL 2007.08.10: fixed redirect 'visa_site' code, added new-window code to navigation
//    LoL 2007.09.18: fixed problem with .val() placement in navigation() method
//    LoL 2008.03.19: added support for arbitrary key/val pairs on beacon
//    LoL 2008.05.14: added support for key/val pairs on bind/submit
//    LoL 2008.05.23: fixed form submit tracking
//                    added document load tracking
//    LoL 2008.06.30: removed visa_site() in favor of equivalent VISA.has_USA_UI()
//    LoL 2008.07.15: added query string capture for page and destination
//                    using href dom property instead of href element attribute: always returns full URL
//                    now strip href from all parameters, not just some
//                    simplified "page" identifier code
//    MR 2009.07.16:  removed http condition for new window in this.navigation to deal with pdf issue
//
// TO DO:
//    document API better


// namespace for this code
VISA.Analytics = new (function() {
	var $ = jQuery; // just in case the $ had been overridden elsewhere
	
	var beacon = VISA.base_href + "/trk/bc/beek.gif";
	
	// get the page load time
	var start_time = new Date().getTime();
	
	// page identifier is either href, or referrer for iframes
	var page = (window.self !== window.top && document.referrer !== "") ? document.referrer : location.href;
	var query;
	if ((/\?/).test(page)) {
		var p = page.split(/\?/);
		page = p[0];
		query = p[1];
	}
	
	// default parameters included on all beacons
	// function values are not resolved until the beacon is sent
	var default_parameters = {
		page:         page,
		query:        query,
		time_on_page: function() { return ((new Date).getTime() - start_time) / 1000 },
		random:       function() { return Math.round(Math.random() * 100000000); } // 'cache busting' random string of digits
	};
	
	// custom parameters included on all beacons
	var custom_parameters = {};
	
	// click handler for the document, filters to all links/maps/submit/image buttons
	this.navigation = function(event) {
        try {
			var target = $(event.target).add($(event.target).parents()).filter("[href], :submit, :image").filter(":first");
			// only beacon the above items, or if we were given a specific destination
			if (!target.length) { return; }
			// for off-site, non-Visa urls, make sure they open in a new window (will not override existing values)
			var href = target.attr("href");
			if (href && !VISA.has_USA_UI(href) ) {
                if(!target.attr("target"))
                    target.attr("target", "_blank");

                // track google event on external link
                VISA.GoogleAnalytics.trackEvent(VISA.GoogleAnalytics.trackCategories["extLink"], target[0].href, location.href);
            }
			var params = {
				type: "navigation",
				// an image source, button value, or link text
				item: $.trim($(event.target).attr("src") || target.val() || target.text()),
				// link href, or form action: use DOM property instead of attribute to get full URL
				destination: target[0].href || target.parents("form")[0].action
			};
			// put destination query string into separate parameter
			if ((/\?/).test(params.destination)) {
				var dest = params.destination.split(/\?/);
				params.destination = dest[0];
				params.dest_query = dest[1];
			}
			
			$.extend(params, page_context(target));
			if (event.data) { $.extend(params, event.data); }
			send(params);
		} catch(e) {
			console.log("analytics failed on target: " + event.target + "\n" + e);
		} finally {
//			event.stopPropagation(); // for debug only
//			event.preventDefault();  // for debug only
		}
	}
	
	// redirects or opens new window as appropriate
	this.redirect = function(url, description) {
		try {
			// for script redirects, we are unable to determine the item or page context
			var params = { type: "redirect", destination: url };
			// description is optional, can be either a string or a key/value object
			if (description) {
				if (description instanceof String) { description = { description: description }; }
				$.extend(params, description);
			}
            if (!VISA.has_USA_UI(url))
            {
                VISA.GoogleAnalytics.trackEvent(VISA.GoogleAnalytics.trackCategories["extLink"], url, location.href);
            }
            // if params of type = "navigation" then we know it is a link
            send(params);
		} catch(e) {
			console.log("analytics failed on redirect: " + url + "\n" + e);
		} finally {
//			event.stopPropagation(); // for debug only
//			event.preventDefault();  // for debug only
		}
		// if we are on VISA, go to the new page; otherwise open in a new window
		if (VISA.has_USA_UI(url)) {
            location.href = url;
		} else {
            window.open(url, 'visa_redirect').focus();
		}
	}
	
	// always opens URL in a new window.  takes the same parameters as window.open
	// name and options are optional: if they are null it will still work
	this.open_window = function(url, window_name, window_options) {
		try {
	        // for script redirects, we are unable to determine the item or page context
	        var params = { type: "navigate", destination: url };
	        send(params);
	    } catch(e) {
	        console.log("analytics failed on new window: " + url + "\n" + e);
		} finally {
//			event.stopPropagation(); // for debug only
//			event.preventDefault();  // for debug only
	    }
	    window.open(url, window_name, window_options).focus();
	}
	
	// generic event beacon for any element, identifies the event type and object which sent it
	// you may wish to add an ID to the object to make sure the information is useful
	this.event = function(event, call_data) {
        try {
			var params = {
				type: event.type,
				item: $.trim(element_identifier(event.target || this))
			};
			$.extend(params, page_context(event.target || this));

            if (event.data) { $.extend(params, event.data); } // passed in function binding
            if (call_data) { $.extend(params, call_data); } // passed in event call
			send(params);
		} catch(e) {
			console.log("analytics failed on target: " + event.target + "\n" + e);
		} finally {
//			event.stopPropagation(); // for debug only
//			event.preventDefault();  // for debug only
		}
	}
	
	// means of adding key/value pairs to be added to every beacon call
	this.addParameters = function(obj) { $.extend(custom_parameters, obj); }
	
	// means of removing specific key/value pairs from beacon calls
	// this only affects custom parameters (though default parameters may be overridden)
	this.removeParameters = function(obj) {
		function remove(key) {
			if (custom_parameters[key]) { delete custom_parameters[key]; }
		}
		if (obj instanceof Object) { $.each(obj, remove); }
		else if (obj instanceof String) { remove(obj); }
	}
	
	// resolves and shows the parameters which would be appended to a beacon call
	this.showParameters = function() {
		var params = resolve_parameters(default_parameters);
		$.extend(params, resolve_parameters(custom_parameters));
		return params;
	}
	
	// 'resolves' parameters, converting function to static values
	// any value which is a function is replaced with the results of that function
	var resolve_parameters = function(params) {
		var resolved = {};
		$.each(params, function(key, val) {
			if (!val || key == "prototype") {
				// strip empty values and "prototype" (where is this being added?)
				return;
			}
			if (val instanceof Function) { val = val(); }
			val = val + ""; // force to string context
			val = $.trim(val); // remove whitespace
			val = val.replace(VISA.base_href, ""); // strip base href from any value, removing domain from relative links
			val = val.substr(0, 255); // trim to 255 chars
			resolved[key] = val;
		});
		return resolved;
	};
	
	// sends a beacon, returns when the request is sent (not returned) or after a short timeout
	var send = function(params) {
		$.extend(params, default_parameters, custom_parameters);
		params = resolve_parameters(params);
		var timeout = (new Date()).getTime() + 500;
		if (location.host === VISA.script_host) {
			// normally we use XHR to send a beacon
			var request = $.ajax({ data: params, url: beacon, async: false });
			while (true) {
				if (request.readyState >= 2) break;
				if ((new Date()).getTime() >= timeout) break;  
			}
		} else {
			// for offsite files we revert to using an image to avoid XSS errors
			var image = new Image();
			var pArray = [];
			$.each(params, function(i, n) { pArray.push(i + "=" + escape(n)); });
			image.src = beacon + "?" + pArray.join("&");
			while (true) {
				if ((new Date()).getTime() >= timeout) break;  
			}
		}
	}
	
	// returns an _object_ identifying the closest ancestor with class 'analytics'
	// returns null if none found
	var page_context = function(element) {
		var loc = $(element).parents(".analytics").filter(":first");
		if (loc.length) {
			return { page_context: loc.attr("id") || loc.attr("class") };
		} else {
			return {};
		}
	}
	
	// for an arbitrary element, returns a string identifying it (similar to that used in firebug)
	var element_identifier = function(element) {
		if (element == window) { return "window"; }
		if (element == document) { return "document"; }
		var identifier = (element.tagName + "").toLowerCase();
		if (element.id) { identifier += "#" + element.id; }
		if (element.className) { identifier += "." + element.className; }
		return identifier;
	}
	
	// and apply navigation tracking to the whole page: these are filtered on click
	$(document).click(this.navigation);
	
	$(document).ready(function(event) {
		// add class 'analytics' to well-known page areas, for the page-context feature
		$("#logo, #siteLinks, #primaryNav, #secondaryNav, #tertiaryNav, #content, #rightCol, .printIcon, #featurePrimary, #featureSecondary, .relatedInfo, #footer").addClass("analytics");
		
		// trigger a load beacon, but not on standard iFrame pages
		if (!location.pathname.match(/ext\/nav/)) {
			event.type = "load";
			event.target = document;
            
            // if referrer's host name is different than the current page, add the refURL to the tracking
            var addParam;
            var refURL = document.referrer;
            if(refURL && refURL !== ""){
                var refMatch = refURL.match(/^https?:\/\/([^\/]+)/);
                var urlMatch = location.href.match(/^https?:\/\/([^\/]+)/);
                if(refMatch[1].toLowerCase() !== urlMatch[1].toLowerCase()){
                    addParam = {"refURL": refURL};
                }
            }
            VISA.Analytics.event(event, addParam);
		}
	});
});
