/*!
 * dwAnalytics - Web Analytics Tracking
 * Based partially on Piwik
 *
 * @link http://piwik.org
 * @license http://www.gnu.org/licenses/gpl-3.0.html Gpl v3 or later
 */

// Create dw namespace if necessary
if (typeof dw == 'undefined') {
	var dw = {};
}

if (typeof dw.__dwAnalyticsLoaded ==  'undefined') 
{
	dw.__dwAnalyticsLoaded = true;
	
	// DWAnalytics singleton and namespace
	dw.__dwAnalytics = (function () 
	{
		/************************************************************
		 * Private data
		 ************************************************************/

		var expireDateTime,

		/* plugins */
		plugins = {},

		/* alias frequently used globals for added minification */
		documentAlias = document,
		navigatorAlias = navigator,
		screenAlias = screen,
		windowAlias = window,
		hostnameAlias = windowAlias.location.hostname;

		/************************************************************
		 * Private methods
		 ************************************************************/

		/*
		 * Is property (or variable) defined?
		 */
		function isDefined(property) 
		{
			return typeof property !== 'undefined';
		}

		/*
		 * DWAnalytics Tracker class
		 *
		 * trackerUrl and trackerSiteId are optional arguments to the constructor
		 *
		 * See: Tracker.setTrackerUrl() and Tracker.setSiteId()
		 */
		function Tracker(trackerUrl, siteId) 
		{
			/************************************************************
			 * Private members
			 ************************************************************/

			var	// Tracker URL
			configTrackerUrl = trackerUrl || '',

			// Document URL
			configCustomUrl,

			// Document title
			configTitle = documentAlias.title,

			// Client-side data collection
			browserHasCookies = '0',
			pageReferrer,

			// Plugin, Parameter name, MIME type, detected
			pluginMap = {
				// document types
				pdf:         ['pdf',   'application/pdf',               '0'],
				// media players
				quicktime:   ['qt',    'video/quicktime',               '0'],
				realplayer:  ['realp', 'audio/x-pn-realaudio-plugin',   '0'],
				wma:         ['wma',   'application/x-mplayer2',        '0'],
				// interactive multimedia 
				director:    ['dir',   'application/x-director',        '0'],
				flash:       ['fla',   'application/x-shockwave-flash', '0'],
				// RIA
				java:        ['java',  'application/x-java-vm',         '0'],
				gears:       ['gears', 'application/x-googlegears',     '0'],
				silverlight: ['ag',    'application/x-silverlight',     '0']
			},

			// Guard against installing the link tracker more than once per Tracker instance
			linkTrackingInstalled = false,

			/*
			 * encode or escape
			 * - encodeURIComponent added in IE5.5
			 */
			escapeWrapper = windowAlias.encodeURIComponent || escape,

			/*
			 * decode or unescape
			 * - decodeURIComponent added in IE5.5
			 */
			unescapeWrapper = windowAlias.decodeURIComponent || unescape,

			/*
			 * stringify
			 * - based on public domain JSON implementation at http://www.json.org/json2.js (2009-04-16)
			 */
			stringify = function (value) {

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

				// 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.
				function quote(string) {
					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 f(n) {
					return n < 10 ? '0' + n : n;
				}

				// Produce a string from holder[key].
				function str(key, holder) {
					var i,          // The loop counter.
						k,          // The member key.
						v,          // The member value.
						partial,
						value = holder[key];

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

					// 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);
					}

					// 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);

					case 'object':
						// Make an array to hold the partial results of stringifying this object value.
						partial = [];

						// Is the value an array?
						// if (Object.prototype.toString.call(value)=="[object Array]") {	// call added in IE5.5
						if (value instanceof Array) {
							// The value is an array. Stringify every element. Use null as a placeholder
							// for non-JSON values.
							for (i = 0; i < value.length; i++) {
								partial[i] = str(i, value) || 'null';
							}

							// Join all of the elements together, separated with commas, and wrap them in
							// brackets.
							v = partial.length === 0 ? '[]' : '[' + partial.join(',') + ']';
							return v;
						}

						// if (Object.prototype.toString.call(value)=="[object Date]") {	// call added in IE5.5
						if (value instanceof Date) {
							return quote(value.getUTCFullYear()   + '-' +
							           f(value.getUTCMonth() + 1) + '-' +
							           f(value.getUTCDate())      + 'T' +
							           f(value.getUTCHours())     + ':' +
							           f(value.getUTCMinutes())   + ':' +
							           f(value.getUTCSeconds())   + 'Z');
						}

						// Otherwise, iterate through all of the keys in the object.
						for (k in value) {
							v = str(k, value);
							if (v) {
								// partial.push(quote(k) + ':' + v); // array.push added in IE5.5
								partial[partial.length] = quote(k) + ':' + v;
							}
						}

						// Join all of the member texts together, separated with commas,
						// and wrap them in braces.
						v = partial.length === 0 ? '{}' : '{' + partial.join(',') + '}';
						return v;
					}
				}

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

			/*
			 * registered (user-defined) hooks
			 */
			registeredHooks = {};

			/*
			 * Set cookie value
			 */
			function setCookie(cookieName, value, daysToExpire, path, domain, secure) 
			{
				var expiryDate;

				if (daysToExpire) 
				{
					// time is in milliseconds
					expiryDate = new Date();
					// there are 1000 * 60 * 60 * 24 milliseconds in a day (i.e., 86400000 or 8.64e7)
					expiryDate.setTime(expiryDate.getTime() + daysToExpire * 8.64e7);
				}

				documentAlias.cookie = cookieName + '=' + escapeWrapper(value) +
					                  (daysToExpire ? ';expires=' + expiryDate.toGMTString() : '') +
					                  ';path=' + (path ? path : '/') +
					                  (domain ? ';domain=' + domain : '') +
					                  (secure ? ';secure' : '');
			}

			/*
			 * Get cookie value
			 */
			function getCookie(cookieName) 
			{
				var cookiePattern = new RegExp('(^|;)[ ]*' + cookieName + '=([^;]*)'),

					cookieMatch = cookiePattern.exec(documentAlias.cookie);

				return cookieMatch ? unescapeWrapper(cookieMatch[2]) : 0;
			}

			/*
			 * Send image request to DWAnalytics server using GET.
			 * The infamous web bug is a transparent, single pixel (1x1) image
			 * Async with a delay of 100ms.
			 */
			function getImage(url) 
			{
				dw.__timeoutCallback = function() {
					var image = new Image(1, 1); image.onLoad = function () {}; image.src = url;
				}
				setTimeout("dw.__timeoutCallback()", 100);
			}

			/*
			 * Browser plugin tests
			 */
			function detectBrowserPlugins() 
			{
				var i, mimeType;

				// Safari and Opera
				// IE6: typeof navigator.javaEnabled == 'unknown'
				if (typeof navigatorAlias.javaEnabled !== 'undefined' && navigatorAlias.javaEnabled()) {
					pluginMap.java[2] = '1';
				}

				// Firefox
				if (typeof windowAlias.GearsFactory === 'function') {
					pluginMap.gears[2] = '1';
				}

				if (navigatorAlias.mimeTypes && navigatorAlias.mimeTypes.length) {
					for (i in pluginMap) {
						mimeType = navigatorAlias.mimeTypes[pluginMap[i][1]];
						if (mimeType && mimeType.enabledPlugin) {
							pluginMap[i][2] = '1';
						}
					}
				}
			}

			/*
			 * Get page referrer
			 */
			function getReferrer() 
			{
				var referrer = '';
				try {
					referrer = top.document.referrer;
				} catch (e) {
					if (parent) {
						try {
							referrer = parent.document.referrer;
						} catch (e2) {
							referrer = '';
						}
					}
				}
				if (referrer === '') {
					referrer = documentAlias.referrer;
				}

				return referrer;
			}

			/*
			 * Does browser have cookies enabled (for this site)?
			 */
			function hasCookies() 
			{
				var testCookieName = '_pk_testcookie';
				if (!isDefined(navigatorAlias.cookieEnabled)) 
				{
					setCookie(testCookieName, '1');
					return getCookie(testCookieName) == '1' ? '1' : '0';
				}

				return navigatorAlias.cookieEnabled ? '1' : '0';
			}

			/*
			 * Log the page view / visit
			 */
			function logPageView(customTitle) 
			{
				var i, now, request;

				request = 'url=' + escapeWrapper(isDefined(configCustomUrl) ? configCustomUrl : documentAlias.location.href) +
				        '&res=' + screenAlias.width + 'x' + screenAlias.height +
				        '&cookie=' + browserHasCookies +
				        '&ref=' + escapeWrapper(pageReferrer);
			
				request += '&title=' + escapeWrapper(isDefined(customTitle) ? customTitle : configTitle); // refs #530;

				// plugin data
	            for (i in pluginMap) {
					request += '&' + pluginMap[i][0] + '=' + pluginMap[i][2];
				}
				
				request =  configTrackerUrl + '?' + request;

				getImage(request);
			}

			/************************************************************
			 * Constructor
			 ************************************************************/

			/*
			 * initialize tracker
			 */
			pageReferrer = getReferrer();
			browserHasCookies = hasCookies();
			detectBrowserPlugins();

			/************************************************************
			 * Public data and methods
			 ************************************************************/

			return {
				/*
				 * Log visit to this page
				 */
				trackPageView: function (customTitle) 
				{
					logPageView(customTitle);
				}
			};
		}

		/************************************************************
		 * Public data and methods
		 ************************************************************/

		return {
			/*
			 * Get Tracker
			 */
			getTracker: function (analyticsUrl) 
			{
				return new Tracker(analyticsUrl);
			}
		};
	}());
}
