//extend jQuery with analytics functionality
(function($) {
	$.analytics = {
		//specify google account info
		//you MUST call $.analytics.configure() BEFORE making any other calls
		configure: function(args) {
			//sanity check
			var eventsHolder = args.events || [];
			var filesHolder = args.files || [];
			args.events = [];
			args.files = [];
			
			//store args in $.analytics
			$.extend($.analytics, args);
			
			//loop through events, and validate 
			for(var i in eventsHolder) {
				$.analytics.addEvent(eventsHolder[i]);
			}
			
			//loop through files, and validate
			for(var i in filesHolder) {
				$.analytics.addFile(filesHolder[i]);
			}
			
			//fluent response
			return $.analytics;
		},
		
		//automatically inserts script tags into document head
		//http://code.google.com/apis/analytics/docs/tracking/asyncTracking.html
		insertScript : function(ops) {
			if ($.analytics.account) {
				//set gaq variable
				window._gaq = window._gaq || [];
				
				//set account info
				_gaq.push(['_setAccount', $.analytics.account]);
				
				//set gaq to track page views
				if($.analytics.trackPageViews) {
					_gaq.push(['_trackPageview']);
				}	

				//write scripts to page
				var ga = document.createElement('script'); 
				ga.type = 'text/javascript'; 
				ga.async = $.analytics.async;
				ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
				
				var s = document.getElementsByTagName('script')[0]; 
				s.parentNode.insertBefore(ga, s);
			}
			
			//wait for _gaq to get registered
			$.analytics.waitForGaq(function() {	
				if(ops.success) {
					ops.success(_gaq);
				}
			});
		},
		
		//add a list of events to the $.analytics.events array
		//events with id's can be accessed via $.analytics.events[<event id>]
		addEvents : function() {
			$.analytics.events = $.analytics.events || [];

			for(var i = 0; i < arguments.length; i++) {
				$.analytics.addEvent(arguments[i]);
			}
			
			//fluent response
			return $.analytics;
		},
		
		//add a single event to the $.analytics.events array
		//events with id's can be accessed via $.analytics.events[<event id>]		
		addEvent : function(event) {
			//if the event object is valid, add it to the events array
			if ($.analytics.validateEvent(event)) {
				$.analytics.events[event.id] = event;
			}
			
			//fluent response
			return $.analytics;			
		},
		
		//for an event to be added to the $.analytics.events array,
		//it must contain all of the necessary properties.
		//if any properties are missing, an error will be logged to the console,
		//and the event will NOT be added to $.analytics.events
		validateEvent : function(event) {
			var isValid = true;
			
			//category is required
			if (!event.category) {
				log('  category required for event');
				isValid  = false;
			}
			if (!event.action) {
				log('  action required for event');
				isValid = false;
			}
			if (isValid) {
				//generate id
				id = event.id || $.analytics.guid();
				event.timesFired = event.timesFired || 0;
				event.isActive = typeof(event.isActive) == 'undefined' ? true : event.isActive;
			}
			
			return isValid;
		},
		
		//loop through list of events in the $.analytics.events array;
		//bind those events to user-generated page events.
		setEventListeners: function() {		
			for(var i in $.analytics.events) {
				var handler = $.analytics.events[i];
				
				if (handler.selector) {
					$(handler.selector).bind(handler.event || 'click',  { event : handler }, function(e) {
						$.analytics.fireEvent($(this), e.data.event);
					});
				}
			}
		},
		
		//add a list of files to the $.analytics.files array
		//files with id's can be accessed via $.analytics.files[<file id>]
		addFiles : function() {
			$.analytics.files = $.analytics.files || [];

			for(var i = 0; i < arguments.length; i++) {
				$.analytics.addFile(arguments[i]);
			}
			
			//fluent response
			return $.analytics;
		},
		
		//add a single file to the $.analytics.files array
		//files with id's can be accessed via $.analytics.files[<file id>]		
		addFile : function(file) {
			//if the file object is valid, add it to the files array
			if ($.analytics.validateFile(file)) {
				$.analytics.files[file.id] = file;
			}
			
			//fluent response
			return $.analytics;			
		},
		
		//for an file to be added to the $.analytics.files array,
		//it must contain all of the necessary properties.
		//if any properties are missing, an error will be logged to the console,
		//and the file will NOT be added to $.analytics.files
		validateFile : function(file) {
			var isValid = true;
			
			//category is required
			if (!file.extension || file.pattern) {
				log('  file extension or file pattern is required.');
				isValid  = false;
			}
			if (isValid) {
				//generate id
				file.isFile = true;
				file.id = file.id || $.analytics.guid();
				file.timesFired = file.timesFired || 0;
				file.isActive = typeof(file.isActive) == 'undefined' ? true : file.isActive;
				file.maxFires = file.maxFires || 1;
			}
			
			return isValid;
		},
		
		//loop through list of files in the $.analytics.events array;
		//bind those events to user download clicks.
		setFileListeners: function() {		
			for(var i in $.analytics.files) {
				var handler = $.analytics.files[i];		

				var selector = handler.selector || 'a[href$="' + handler.extension + '"]';
				
				$(selector).bind('mousedown', { file: handler }, function(e) {				
					var file = e.data.file;
					
					file.category = file.category || 'downloads';
					file.action = file.action || $(this).attr('href');
					
					$.analytics.fire(file);
				});
			}
		},
		
		//manually fire an event (or events)
		//should only be called AFTER the document is ready.
		//this function can be used to fire a single event: $.analytics.fire('<event id>');
		//it can be used to fire an anonymous event: $.analytics.fire({ action: 'actionValue', label:'labelValue', <etc.>});
		//it can be used to fire all of the events associated with a particular element: $.analytics.fire($('<selector>'));
		//it can be used to fire multiple events in one call: $.analytics.fire(<item 1>, <item 2>, <etc.>);
		fire : function(el) {
			if (arguments.length > 1) {
				//convert the arguments into an array, and recurse
				$.analytics.fire([].splice.call(arguments,0));
			}
			else if (el.constructor == Array) {
				//loop through items in array, recurse, call fire per item
				for(var i in el) {
					var item = el[i];
					$.analytics.fire(item);
				}
			}
			else {
				switch (typeof(el)) {
					case 'function' : 
						//fire the function, recurse, call fire on the returned value
						$.analytics.fire(el());
						break;
					case 'string' :
					case 'number' :
						//fire the event with the given id
						if ($.analytics.events[el]) {
							$.analytics.fireEventById(el);
						}
						break;
					case 'object' : 
						if (el instanceof jQuery) {
							//fire all of the events associated with this particular element
							$.analytics.fireTrackingForElement(el);
							break;
						}
						else if ($.analytics.validateEvent(el)) {
							//fire an anonymous event
							$.analytics.fireEvent(null, el);
							break;
						}
					default:
						//log error
						log('No suitable firing event found for passed argument.');
						log(el);
				}
			}
			
			//fluent response
			return $.analytics;
		},
		
		//fire an event, push event tracking data to google.
		//only tracks event if all event-based conditionals are met.
		fireEvent : function(el, event) {
			//this data will be passed back to functions in the events object
			//allows for easy configuration and hooking
			var data = {
				element: el,
				event: event,
				location: window.location,
				absolutePath: window.location.href, 
				relativePath: window.location.pathname,
				query: window.location.search
			};
			
			if ((event.isActive) && 
				(!event.maxFires || event.timesFired < event.maxFires || event.ignoreFire) &&
				(!event.bind || event.bind(data)) &&
				(!event.urlPattern || window.location.pathname.match(event.urlPattern))) {

				//add tick to counter
				event.timesFired++;
				
				//min number of fires not yet met
				if (event.minFires && event.timesFired < event.minFires) {
					return;
				}
			
				//convert functions into data
				var curEvent = $.analytics.handleEventFns(event, data);
				
				//fire hook function
				//allows user-specified overriding
				if (event.hook) {
					event.hook(data);
				}
				
				//TODO: remove log calls prior to production
				log((event.isFile? 'file' : 'event') + ' tracked: ');
				log(curEvent);
				log('  category: ' + curEvent.category);
				log('  action: ' + curEvent.action);
				log('  label: ' + curEvent.label);		
				log('  value: ' + curEvent.value);
				log('  times fired: ' + event.timesFired);
				log('--');
				
				//add metadata to jquery element
				if (el && el instanceof jQuery) {
					var metadata = el.data('eventTracking');
					if (!metadata) {
						metadata = [];
					}
					
					//add event to metadata
					metadata.push(event);

					//update metadata
					el.data('eventTracking', metadata);
				}
				
				//wait for gaq, push out the event
				$.analytics.waitForGaq(function() {
					if (curEvent.value) {
						//log('pushing with value');
						_gaq.push(['_trackEvent', curEvent.category, curEvent.action, curEvent.label, curEvent.value]); 
					}
					else {
						//log('pushing without value');
						_gaq.push(['_trackEvent', curEvent.category, curEvent.action, curEvent.label]); 					
					}
				});
			}		
		},
		
		//force a pageview to fire
		//loads a hidden iframe on the current page
		//iframe must contain necessary script tags
		firePageView : function(url) {
			$(function() {
				var iframe = $('<iframe />');
				iframe.attr('src', url);
				iframe.css('width', '1px');
				iframe.css('height', '1px');
				
				$('body').append(iframe);
			});
		},
		
		//fire ALL of the events associated with a single jQuery object
		fireTrackingForElement : function(element) {
			for(var i in $.analytics.events) {			
				var event = $.analytics.events[i];
				var jQueryObjs = $(event.selector);
					
				for(var j in element.toArray()) {
					var htmlObj = element[j];
					
					if ($.inArray(htmlObj, jQueryObjs.toArray()) >= 0) {
						$.analytics.fireEvent(element, event);
						continue;
					}
				}
			}
		},
		
		//fire the event with a given id
		fireEventById: function(id, countAsFire) {
			var event = $.extend($.analytics.events[id], {
				ignoreFire: countAsFire
			});
			
			//get jQuery element
			var el = event.selector? $(event.selector) : null;
			
			//fire event
			$.analytics.fireEvent(el, event);
		},
		
		//fire fn when _gaq is registered
		waitForGaq : function(fn, attemptsLeft) {
			//100 = default number of attempts
			//i.e. 10 seconds of async waiting, max
			var tick = attemptsLeft || 30; 
		
			if (typeof(_gaq) != 'object' || !_gaq._getAsyncTracker) {
				//_gaq isn't registered yet
				if (tick > 1) {
					//recurse
					setTimeout(function() {
						$.analytics.waitForGaq(fn, tick - 1);
					}, 100)
				}
				else {
					//no ticks left, log error
					log('jQuery Analytics failed to load');
				}
			}
			else {
				//gaq is loaded, fire fn
				fn();
			}
		},
		
		//converts fns in an event object into usable values
		handleEventFns : function(event, data) {
			var curEvent = new Object();
		
			for(var i in event) {
				var property = event[i];
			
				if (typeof(property) == 'function' && i != 'hook') {
					curEvent[i] = property(data);
				}
				else {
					curEvent[i] = property;
				}
			}
			
			return curEvent;
		},
		
		//deactivate an event
		deactivate : function(id) {
			($.analytics.events[id] || {}).isActive = false;
		},
		
		//activate an event
		activate : function(id) {
			($.analytics.events[id] || {}).isActive = true;
		},
		
		//create a unique identifier
		guid : function() {
			var S4 = function() {
				return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
			}

			return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4());
		},
		
		//library metadata
		metadata : {
			author: 'Evan Nagle',
			company: 'StarrTech Interactive',
			date: '9/20/2010',
			library: 'jQuery Analytics',
			dependencies: [ 'jQuery 1.4.2' ]
		}
	}
	
	$(function() {
		//insert script tag
		$.analytics.insertScript({
			success: function(gaq){
				$.analytics.setEventListeners();
				$.analytics.setFileListeners();
				
				log('jQuery Analytics is currently active.');
				log('==> ' + ($.analytics.account || 'N/A'));
			}
		});
	});
	
	$.extend($.fn, {
		track : function(ops) {
			if (!ops) {
				return $.analytics.fireTrackingForElement($(this));
			}
			
			return this.each(function() {
				ops.selector = ops.selector || $(this);
				$.analytics.addEvent(ops);
			});
		},
		eventsTracked : function() {
			return $(this).data('eventTracking');
		}
	});
	
	window.log = function(){
	  log.history = log.history || []; 
	  log.history.push(arguments);
	  if(this.console){
		console.log( Array.prototype.slice.call(arguments) );
	  }
	};		
})(jQuery);

