/* comments:
@author: Remy Damour
@date: Sept. 9th, 2008
@copyright: copyleft

_pk_empty function: useless => replaced with empty function where called
_pk_pause function: very bad, prefer using setTimeout instead but don't know what's its purpose => left untouched
=> added function: .load() with onDomLoad event

script_size: 1ko more once yui-compressed due to dom-ready event

ie_plugin_detector function not working properly: generating error msg => desactivated

global variables list (used for easy configuration): (not set to Piwik.user_options or Piwik.options because this require piwik.js to be loaded first whereas here it can be loaded at any time)
- piwik_action_name
- piwik_idsite
- piwik_url
- piwik_install_tracker
- piwik_tracker_pause
- piwik_download_extensions

did not understand why this._logged was set to true only when action name is empty => always set it to true after first log

usage:
<script type="text/javascript" src="http://piwik.qc4web.com/piwik.js"></script><noscript><p>Free web analytics <img src="http://piwik.qc4web.com/piwik.php" style="border:0" alt="piwik"/></p></noscript>
<script type="text/javascript">var piwik_script_url = "http://piwik.qc4web.com/piwik.php", piwik_id_site=1;</script>

advantages:
- flexibility
- executed on domready => does not slow down script executions
- place it in any order (variables can be defined before or after piwik.js script node)
- minimized use of global variables
- yui-compressible (optimized with minimized this.prop calls)
- easier to maintain (documented code)
- <script> can be generated and loaded once dom load event (so that do not delay other scripts due to hostname resolution) (write such example)

suggestion:
within <noscript> uri, add idsite=n too

tested on IE7, FFox, Opera, looks ok: cf. generated uri through firebug: "http://piwik.qc4web.com/piwik.php?url=http%3A%2F%2Fwww.qc4web.com%2F&action_name=QC4Web%20%3A%3A%20Contr%C3%B4le%20Qualit%C3%A9%20Pour%20Applications%20Internet&idsite=1&res=1280x800&col=24&h=20&m=52&s=17&fla=1&dir=0&qt=1&realp=0&pdf=1&wma=1&java=0&cookie=1&title=QC4Web%20%3A%3A%20Contr%C3%B4le%20Qualit%C3%A9%20Pour%20Applications%20Internet&urlref="
*/

try {
	if (Piwik) ;
} catch(e) {
	throw 'Piwik already defined, piwik analysis cancelled.';
}

var Piwik = {
	options: { // each option can be overwritten with corresponding global var: piwik_<option_name>
		install_tracker: 1,
		tracker_pause: 500,
		download_extensions: '7z|aac|avi|csv|doc|exe|flv|gif|gz|jpe?g|js|mp(3|4|e?g)|mov|pdf|phps|png|ppt|rar|sit|tar|torrent|txt|wma|wmv|xls|xml|zip',
		hosts_alias: []
	},
	_plugin_map: {
		dir: ['application/x-director', 'SWCtl.SWCtl.1'],
		fla: ['application/x-shockwave-flash', 'ShockwaveFlash.ShockwaveFlash.1'],
		qt: ['video/quicktime', 'Quicktime.Quicktime'], // Old : "QuickTimeCheckObject.QuickTimeCheck.1"
		rea: ['audio/x-pn-realaudio-plugin', 'rmocx.RealPlayer G2 Control.1'],
		wma: ['application/x-mplayer2', 'wmplayer.ocx'], // Old : "MediaPlayer.MediaPlayer.1"
		pdf: ['application/pdf', ['PDF.PdfCtrl.1', 'PDF.PdfCtrl.5', 'PDF.PdfCtrl.6']]
	},
	_tracker_options: null,
	_ie_plugin_found: null,
	_dom_loaded: null,
	_logged: false,
	
	/**
	 * Start piwik process on DOM ready event
	 *
	 * This function automatically recalls itself until dom is ready. 
	 * It takes an optional argument, that when set to true, consider DOM to be loaded. This argument allow to add <script> node linked to piwik.js from external js file, once domready has been triggered (to allow download delays due to piwik.js stored in external domain). In this case, domready event would already have been triggered and therefore logging function could never get executed. Here, we simply need to call again initialize() with 'true' argument so that content gets logged (and if already done, no pbm since we have a variable checking this to avoid logging doublons: this._logged)
	 * @param bool dom_is_ready Optional // can be removed, user simply needs to turn Piwik._dom_loaded = true;
	 */
	initialize: function(dom_is_ready)
	{
		this._dom_loaded = dom_is_ready || this._dom_loaded;
		if (!this._isDomReady()) {
			var bind = this, args = arguments, fn = arguments.callee, recall = function() { return fn.apply(bind, args); };
			return setTimeout(recall, 50);
		}
		
		var action_name = this._getOption('action_name', document.title),
			id_site = this._getOption('idsite', 1),
			php_script_url = this._getOption('url', window.location.hostname+'/piwik/piwik.php');
		this.log(action_name, id_site, php_script_url);
	},
	
	/**
	 * Proceed to piwik information logging
	 *
	 * Call php function throw img uri. Wrap img node within <p> node to be XHTML DTD compliant + hide p node (does not use display:none; since this could get the image not to be generated, ie. data not to be logged)
	 * @param string action_name
	 * @param int id_site
	 * @param string piwik_url
	 * @param array custom_vars
	 */
	log: function(action_name, id_site, piwik_url, custom_vars)
	{
		//if (this._logged && (!action_name || action_name == '')) return;
		//if (this._logged) return;
		var div = document.createElement('div'), log_uri = this._getUrlLog(action_name, id_site, piwik_url, custom_vars);
		div.innerHTML = '<p style="visibility:hidden;width:0;height:0;display:inline;"><img src="'+log_uri+'" alt="Piwik" /></p>';
		document.body.appendChild(div.firstChild);
		//alert(action_name);
		//alert(log_uri);
		//if (!action_name || action_name == '') this._logged = true;
		this._logged = true;
		this._initTracker(id_site, piwik_url);
	},
	
	/**
	 * Called to track followed links
	 * 
	 * @param string link_url
	 * @param string link_type ('download'|'link') only 'download' and 'link' types supported
	 */
	track: function(link_url, link_type)
	{
		var image = new Image(), piwik_url = this._tracker_options.url, id_site = this._tracker_options.id_site;
		image.onLoad = function() {};
		image.src = piwik_url + '?idsite=' + id_site + '&' + link_type + '=' + escape(link_url) + '&rand=' + Math.random() + '&redirect=0';
		this._pause( this._tracker_options.tracker_pause );
	},
	
	/**
	 * Take a string as input and returned escaped version
	 *
	 * @param string txt
	 * @return string
	 */
	escape: function(txt)
	{
		if(typeof(encodeURIComponent) == 'function') {
			return encodeURIComponent(txt);
		} else {
			return escape(txt);
		}
	},
	
	/**
	 * Browser agent detection, return true if windows+IE environment, false otherwise
	 *
	 * @return bool
	 */
	_isWindowsTest: function()
	{
		var agent = navigator.userAgent.toLowerCase(), ie = agent.indexOf("msie") != -1, win = (agent.indexOf("win") != -1) || (agent.indexOf("32bit") != -1);
		return ie && win;
	},
	
	/**
	 * Cookie status detection
	 *
	 * @return bool
	 */
	_isCookieEnabled: function()
	{
		var cookie_enabled = navigator.cookieEnabled;
		if(typeof (cookie_enabled) == "undefined") {
			document.cookie="_pk_testcookie"
			cookie_enabled = (document.cookie.indexOf("_pk_testcookie") != -1);
		}
		return !!cookie_enabled;
	},
	
	/**
	 * Detect installed plugins
	 *
	 * @param bool windows_test
	 * @return {} Object with keys of this._plugin_map and values among ('0'|'1')
	 */
	_getPlugins: function(windows_test)
	{
		var plugin_list = {}, mime_types = this._getMimeTypes(windows_test);
		for (plugin_type in this._plugin_map) {
			plugin_list[plugin_type] = this._testPlugin(plugin_type, mime_types, windows_test);
		}
		return plugin_list;
	},
	
	/**
	 * Concatenate all existing mime types on non IE + WINDOWS config
	 *
	 * @param bool windows_test
	 * @return string
	 */
	_getMimeTypes: function(windows_test)
	{
		var mime_types = '';
		if (!windows_test) {
			for (var i=0; i < navigator.mimeTypes.length; i++) {
				mime_types += navigator.mimeTypes[i].type.toLowerCase();
			}
		}
		return mime_types;
	},
	
	/**
	 * Test presence of passed plugin id
	 *
	 * @param string plugin_type
	 * @param string mime_types
	 * @param bool windows_tester
	 * @return string ('0'|'1') '0' == plugin not found, '1' == plugin found
	 */
	_testPlugin: function(plugin_type, mime_types, windows_tester)
	{
		var tester = this._getPluginTester(windows_tester), plugin_names = this._plugin_map[plugin_type][windows_tester ? 1 : 0];
		if (typeof(plugin_names) == 'string') plugin_names = [plugin_names];
		for (var i=0; i < plugin_names.length; i++) {
			if (tester(plugin_names[i], mime_types)) return '1';
		}
		return '0';
	},
	
	/**
	 * Return appropriate plugin tester based on client's platform
	 *
	 * IE testing is not implemented yet (we generate a vbscript node => must wait for content to be generated => should use a Piwik.IEPluginTested function call?)
	 * @return function
	 */
	_getPluginTester: function(windows_tester)
	{
		if (windows_tester) {
			return function() {return '0';} /* below function generates error on IE, even though we use "on error resume next". I am no vbscript guru and don't want to be one + error msg is ActiveX, ie. dealing with security zones => don't want to waste time on it => return always false */
			return function(plugin_name) {
				Piwik._ie_plugin_found = false;
				var script = document.createElement('script');
				script.type = 'text/vbscript';
				script.text = '\n on error resume next \n Piwik._ie_plugin_found = IsObject(CreateObject("' + plugin_name + '")) ';
				document.body.appendChild(script);
				if (script.parentNode) document.body.removeChild(script);
				return Piwik._ie_plugin_found ? '1' : '0';
			};
		} else {
			return function(plugin_name, mime_types) {
				return (mime_types.indexOf(plugin_name) != -1 && (navigator.mimeTypes[plugin_name].enabledPlugin != null));
			};
		}
	},
	
	/**
	 * Return referrer, if any or empty string
	 *
	 * @return string
	 */
	_getReferrer: function()
	{
		var referrer = null;
		try {
			referrer = top.document.referrer;
		} catch(e1) {
			if(parent){ 
				try{ referrer = parent.document.referrer; } catch(e2) { referrer = null; }
			}
		}
		if(!referrer) {
			referrer = document.referrer;
		}
		return referrer;
	},
	
	/**
	 * Return piwik url to be called
	 *
	 * @param string action_name
	 * @param string id_site
	 * @param string piwik_url
	 * @param array|null custom_vars
	 * @return string
	 */
	_getUrlLog: function(action_name, id_site, piwik_url, custom_vars)
	{
		var custom_vars_str = '';
		if (custom_vars) {
			for (var i in custom_vars){
				if (!Array.prototype[i]){
					custom_vars_str = custom_vars_str + '&vars['+ escape(i) + ']' + "=" + escape(custom_vars[i]);
				}
			}
		}

		var cookie = this._isCookieEnabled() ? '1' : 0, 
			plugin_list = this._getPlugins( this._isWindowsTest() ), 
			referrer = this._getReferrer(),
			java = navigator.javaEnabled() ? '1' : '0',
			title = this.escape(document.title),
			current_date = new Date();
			
		return piwik_url+'?url='+this.escape(document.location.href)+'&action_name='+this.escape(action_name)+'&idsite='+id_site+'&res='+screen.width+'x'+screen.height+'&col='+screen.colorDepth+'&h='+current_date.getHours()+'&m='+current_date.getMinutes()+'&s='+current_date.getSeconds()+'&fla='+plugin_list['fla']+'&dir='+plugin_list['dir']+'&qt='+plugin_list['qt']+'&realp='+plugin_list['rea']+'&pdf='+plugin_list['pdf']+'&wma='+plugin_list['wma']+'&java='+java+'&cookie='+cookie+'&title='+title+'&urlref='+this.escape(referrer)+custom_vars_str;
	},
	
	/**
	 * Init tracker
	 *
	 * @param int id_site
	 * @param string piwik_url
	 */
	_initTracker: function(id_site, piwik_url)
	{
		var install_tracker = this._getOption('install_tracker'), 
			tracker_pause = this._getOption('tracker_pause'), 
			download_extensions = this._getOption('download_extensions'),
			hosts_alias = this._getOption('hosts_alias');
		
		if(!install_tracker) return;
		
		hosts_alias.push(window.location.hostname);
		this._tracker_options = {
			tracker_pause: tracker_pause,
			hosts_alias: hosts_alias,
			id_site: id_site,
			url: piwik_url
		};
		
		if (document.getElementsByTagName) {
			links_elements = document.getElementsByTagName('a');
			for (var i = 0; i < links_elements.length; i++) {
			if( links_elements[i].className.indexOf('piwik_ignore') == -1 )
				this._addEvent(links_elements[i], 'mousedown', this._onClickEvent, false);
			}
		}
	},
	
	/**
	 * Add event listener, browser independent
	 *
	 * @param Element el
	 * @param string event_type
	 * @param function fn
	 * @param bool use_capture
	 */
	_addEvent: function(el, event_type, fn, use_capture)
	{
		var bind = this, binded_fn = function() {return fn.apply(bind, arguments);};
		if (el.addEventListener) { 
			el.addEventListener(event_type, binded_fn, use_capture); 
		} else if (el.attachEvent) { 
			el.attachEvent('on' + event_type, binded_fn);
		} else {
			el['on' + event_type] = binded_fn;
		}
	},
	
	/**
	 * Each option can be overwritten with corresponding global var prefixed with "piwik_"
	 *
	 * For instance, option "install_tacker" can be overwritten with global var "piwik_install_tracker"
	 * @param string option_name
	 * @param mixed default_value Optional
	 * @return mixed
	 */
	_getOption: function(option_name, default_value)
	{
		try {
			return eval('piwik_'+option_name);
		} catch (e) {
			return default_value ? default_value : this.options[option_name];
		}
	},
	
	/**
	 * Called on click event, to track clicked urls
	 *
	 * Always return true not to stop event propagation and default behaviour of click event
	 * @param Event e
	 * @return true
	 */
	_onClickEvent: function(e)
	{
		var source;
		if (typeof e == 'undefined') var e = window.event;
		if (typeof e.target != 'undefined') source = e.target;
		else if (typeof e.srcElement != 'undefined') source = e.srcElement;
		else return true;

		while( source.tagName.toUpperCase() != 'A' )
			source = source.parentNode;
		if( typeof source.href == 'undefined' ) return true;

		var preg_download = new RegExp('\\.(' + this._getOption('download_extensions') + ')$', 'i'), 
			not_site_hostname = !this._isSiteHostname(source.hostname),
			link_type;
		if( source.className.indexOf('piwik_download') != -1 ) {
			link_type = 'download';
		} else if( source.className.indexOf('piwik_link') != -1 ) {
			link_type = 'link';
			not_site_hostname = 1;
		} else link_type = preg_download.test(source.href) ? 'download' : 'link';

		if( not_site_hostname || link_type == 'download' ) 
			this.track(source.href, link_type);

		return true;
	},
	
	/**
	 * Check if passed name is among defined hostname alias
	 *
	 * @param string hostname
	 * @return bool
	 */
	_isSiteHostname: function(hostname)
	{
		for(var i = 0; i < this._tracker_options.hosts_alias.length; i++)
			if( hostname == this._tracker_options.hosts_alias[i] ) 
				return true;
		return false;
	},
	
	/**
	 * Function used to generate a pause
	 *
	 * TODO: change it and use setInterval() or setTimeout() instead (less computing intensive)
	 * @param int time_msec
	 */
	_pause: function(time_msec)
	{
		var now = new Date();
		var expire = now.getTime() + time_msec;
		while(now.getTime() < expire)
			now = new Date();
	},
	
	/**
	 * Function returning true once dom is ready, false otherwise
	 *
	 * DOM ready event detection is taken from mootools framework (http://mootools.net)
	 * @return bool
	 */
	_isDomReady: function()
	{
		var Browser = {Engine: {name:'unknown', version:''}};
		if (window.opera) Browser.Engine = {name: 'presto', version: (document.getElementsByClassName) ? 950 : 925};
		else if (window.ActiveXObject) Browser.Engine = {name: 'trident', version: (window.XMLHttpRequest) ? 5 : 4};
		else if (!navigator.taintEnabled) Browser.Engine = {name: 'webkit', version: (Browser.Features.xpath) ? 420 : 419};
		else if (document.getBoxObjectFor != null) Browser.Engine = {name: 'gecko', version: (document.getElementsByClassName) ? 19 : 18};
		Browser.Engine[Browser.Engine.name] = Browser.Engine[Browser.Engine.name + Browser.Engine.version] = true;

		switch (Browser.Engine.name){
			case 'webkit':
				return document.readyState == 'loaded' || document.readyState == 'complete';
			case 'trident':
				var temp = document.createElement('div');
				try {
					temp.doScroll('left');
					temp.innerHTML = 'temp';
					document.body.appendChild(temp);
					if (temp.parentNode) {
						temp.parentNode.removeChild(temp);
						return true;
					}
					return false;
				} catch(e) { return; }
			default:
				if (null === this._dom_loaded) {
					this._dom_loaded = false;
					var bind = this, fn = function() {bind._dom_loaded = true;}
					this._addEvent(window, 'load', fn);
					this._addEvent(window.document, 'DOMContentLoaded', fn);
					return false;
				} else {
					return !!this._dom_loaded;
				}
		}
	}

};

Piwik.initialize();
