
var const_grabbing_interval_ms = 500;
var const_sending_interval_ms = 5000;
var const_mouse_delay_interval_ms = 100;
var const_scroll_delay_interval_ms = 100;
var const_stop_tracking_after_seconds = 2*3600;
var const_do_compress = 0;
var const_ignore_deactivate = false;
var const_deactivate_tag = "cmr_deactivate";
var const_use_xmlhttp = false;

// the serv url is completed later ...
//var const_serv_url = "cmr_proxy.php";
var const_serv_url = "cmr_tracker.php";

var const_inner_delimit = "$";
var const_outer_delimit = "|";
















function getWindowWidthHeight(win) 
{
	var width = 0, height = 0;
	
	if (win == undefined) win = window;   
	if (typeof(win.innerWidth) == 'number') 
	{  
		width = win.innerWidth;
		height = win.innerHeight;   
	}   
	else if (win.document.documentElement &&   
			win.document.documentElement.clientWidth) 
	{   
		width = win.document.documentElement.clientWidth;   
		height = win.document.documentElement.clientHeight;   
	}
	else
	{   
		width = win.document.body.offsetWidth;   
		height = win.document.body.offsetHeight;   
	}

	return [width, height];   
}   


function getScrollBarWidth () 
{  
    var inner = document.createElement('p');  
    inner.style.width = "100%";  
    inner.style.height = "200px";  
  
    var outer = document.createElement('div');  
    outer.style.position = "absolute";  
    outer.style.top = "0px";  
    outer.style.left = "0px";  
    outer.style.visibility = "hidden";  
    outer.style.width = "200px";  
    outer.style.height = "150px";  
    outer.style.overflow = "hidden";  
    outer.appendChild (inner);  
  
    document.body.appendChild (outer);  
    var w1 = inner.offsetWidth;  
    outer.style.overflow = 'scroll';  
    var w2 = inner.offsetWidth;  
    if (w1 == w2) w2 = outer.clientWidth;  
  
    document.body.removeChild (outer);  
  
    return (w1 - w2);  
};  


function getMouseXYFromEvent(e) // works on IE6,FF,Moz,Opera7
{ 
 	if (!e) e = window.event; // works on IE, but not NS (we rely on NS passing us the event)

 	var mousex = -1, mousey = -1;
 	
 	if (e)
 	{ 
    	if (e.pageX || e.pageY)
    	{ // this doesn't work on IE6!! (works on FF,Moz,Opera7)
      		mousex = e.pageX;
      		mousey = e.pageY;
      		//algor = '[e.pageX]';
      		//if (e.clientX || e.clientY) algor += ' [e.clientX] '
    	}
    	else if (e.clientX || e.clientY)
    	{ // works on IE6,FF,Moz,Opera7
    	    // Note: I am adding together both the "body" and "documentElement" scroll positions
    	    //       this lets me cover for the quirks that happen based on the "doctype" of the html page.
    	    //         (example: IE6 in compatibility mode or strict)
    	    //       Based on the different ways that IE,FF,Moz,Opera use these ScrollValues for body and documentElement
    	    //       it looks like they will fill EITHER ONE SCROLL VALUE OR THE OTHER, NOT BOTH 
    	    //         (from info at http://www.quirksmode.org/js/doctypes.html)
    	    mousex = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
			mousey = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
      		//algor = '[e.clientX]';
      		//if (e.pageX || e.pageY) algor += ' [e.pageX] '
    	}  
	}
	return [mousex, mousey];
}

function getScrollXY() {
	var scrOfX = 0, scrOfY = 0;

	if( typeof( window.pageYOffset ) == 'number' ) {
		//Netscape compliant
		scrOfY = window.pageYOffset;
		scrOfX = window.pageXOffset;
	} else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
		//DOM compliant
		scrOfY = document.body.scrollTop;
		scrOfX = document.body.scrollLeft;
	} else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
		//IE6 standards compliant mode
		scrOfY = document.documentElement.scrollTop;
		scrOfX = document.documentElement.scrollLeft;
	}
	return [ scrOfX, scrOfY ];
}

function getMaxScrollXY() {

	var pageWidth = 0, pageHeight = 0;
	if( window.innerHeight && window.scrollMaxY ) // Firefox 
	{
		pageWidth = window.innerWidth + window.scrollMaxX;
		pageHeight = window.innerHeight + window.scrollMaxY;
	}
	else if( document.body.scrollHeight > document.body.offsetHeight ) // all but Explorer Mac
	{
		pageWidth = document.body.scrollWidth;
		pageHeight = document.body.scrollHeight;
	}
	else // works in Explorer 6 Strict, Mozilla (not FF) and Safari
	{ 
		pageWidth = document.body.offsetWidth + document.body.offsetLeft; 
		pageHeight = document.body.offsetHeight + document.body.offsetTop; 
	}

	return [ pageWidth, pageHeight ]; 	
}






function onMouseMove(event)
{
	var mouse_xy = getMouseXYFromEvent(event);
	updateEventList(mouse_xy);	
}

function onMouseDown(event)
{
	var mouse_xy = getMouseXYFromEvent(event);
	updateEventList(mouse_xy, "d");		
}

function onMouseUp(event)
{
	var mouse_xy = getMouseXYFromEvent(event);
	updateEventList(mouse_xy, "u");			
}

function onWheelEvent()
{
	updateEventList();
}

function onTimerEvent()
{
	updateEventList();
}



var g_old_mouse_xy;
var g_old_button_state;
var g_old_mouse_time_delta;

var g_old_scroll_xy;
var g_old_scroll_time_delta;

var g_old_window_size;
var g_old_scroll_width;
var g_old_has_scrollbar;
var g_old_max_scroll_xy;

var g_send_list;
var g_sent;


var g_old_time_delta;
var g_start_timestamp;
var g_project_hash;
var g_ad_code;
var g_visitor_id;

var g_delayed_messages;

var g_activated;


function addDelayedEventMessage(index, message, message_time, delay)
{
	// get data
	var last_sent_msg_time = g_delayed_messages[index][0];
	var last_wait_msg_time = g_delayed_messages[index][1];
	var last_wait_msg = g_delayed_messages[index][2];

	//g_send_list += "drop:" + message;
	
	// do the logic ...
	if (last_sent_msg_time == -1)
	{	// no message sent ... so we send one now
		g_send_list += message;
		last_sent_msg_time = message_time;
	}
	else if (last_wait_msg_time == -1)
	{	// no wait message ... so use the current one
		last_wait_msg = message;
		last_wait_msg_time = message_time;
	}
	else
	{
		
		if ((message_time > last_wait_msg_time + delay) ||
			(message_time > last_sent_msg_time + delay) ||
			(last_wait_msg_time > last_sent_msg_time + delay))
		{	// last sent message is really far away or even the 
			// last wait message has been delayed enough 
			// (to the sent message before or the current message)
			// so we can send out the old
			g_send_list += last_wait_msg;
			last_sent_msg_time = last_wait_msg_time;
			last_wait_msg = message;
			last_wait_msg_time = message_time;
		}
		else
		{	
			// drop last wait message and replace it with 
			// current msg
			last_wait_msg = message;
			last_wait_msg_time = message_time;			
		}
	}
	
	// write back data
	g_delayed_messages[index][0] = last_sent_msg_time;
	g_delayed_messages[index][1] = last_wait_msg_time;
	g_delayed_messages[index][2] = last_wait_msg;
}

function flushDelayedMessages(max_index)
{
	var ret_msg = "";
	var i = 0;
	for (i = 0; i < max_index; i ++)
	{
		ret_msg += g_delayed_messages[i][2];
		
		g_delayed_messages[i][0] = -1;
		g_delayed_messages[i][1] = -1;
		g_delayed_messages[i][2] = "";
	}
	
	return ret_msg;
}



function createInitVisitorMessage(visitor_id, time,
								  document_url, ad_code)
{
	// format [visitor; start_time; url; ad_code]
	var message = "v" + const_inner_delimit;
	message += visitor_id + const_inner_delimit;
	message += time + const_inner_delimit;
	message += ad_code + const_inner_delimit;
	message += encodeURIComponent(document_url);
	message += const_outer_delimit;
	
	return message;
}

function calcPixelSpeed(last_xy, current_xy, time)
{
	var ret_speed = 0;

	if (last_xy[0] != -1 && last_xy[1] != -1 && 
		current_xy[0] != -1 && current_xy[1] != -1)
	{
		ret_speed = Math.sqrt((current_xy[0] - last_xy[0]) * (current_xy[0] - last_xy[0]) +
							  (current_xy[1] - last_xy[1]) * (current_xy[1] - last_xy[1]));
		ret_speed /= time;
	}
	ret_speed *= 1000;
	ret_speed = Math.round(ret_speed);
	if (ret_speed == Infinity)
	{
		// that's strange but may happen from time to time ...
		ret_speed = -1;
	}
	//ret_speed = ret_speed(2);

	return ret_speed;
}


function createScrollMessage(rel_time, last_rel_time, 
							 scroll_xy, last_scroll_xy)
{
	// in case of strange values -> don't exit & do nothing, we need this to find incompatible browsers
	//if (scroll_xy[0] == -1 || scroll_xy[0] == -1)	return "";

	var speed = calcPixelSpeed(last_scroll_xy, scroll_xy, 
							   rel_time - last_rel_time);
	
	// format [scrolling; delta_time_ms; x; y; speed]
	var message = "s" + const_inner_delimit;
	message += rel_time + const_inner_delimit;
	message += scroll_xy[0] + const_inner_delimit + scroll_xy[1] + const_inner_delimit;
	message += speed;
	message += const_outer_delimit;	
	
	return message;
}


function createMouseMessage(rel_time, last_rel_time,
							mouse_xy, last_mouse_xy, button_state)
{			
	// in case of strange values -> exit & do nothing
	if (mouse_xy[0] == -1 || mouse_xy[0] == -1)	return "";

	var speed = calcPixelSpeed(last_mouse_xy, mouse_xy, 
							   rel_time - last_rel_time);
	
	// format [mouse; delta_time_ms; x; y; button_state; speed]
	var message = "m" + const_inner_delimit;
	message += rel_time + const_inner_delimit;
	message += mouse_xy[0] + const_inner_delimit + mouse_xy[1] + const_inner_delimit;
	message += button_state + const_inner_delimit;
	message += speed;
	message += const_outer_delimit;
	
	return message;
}

function createWindowSize(time_delta, window_size, 
						  has_scrollbar, max_scroll_xy,
						  scroll_width)
{
	var message = "d" + const_inner_delimit;
	message += time_delta + const_inner_delimit;
	message += window_size[0] + const_inner_delimit + window_size[1] + const_inner_delimit;
	message += has_scrollbar[0] + const_inner_delimit + has_scrollbar[1] + const_inner_delimit;
	message += max_scroll_xy[0] + const_inner_delimit + max_scroll_xy[1] + const_inner_delimit;
	message += scroll_width;
	message += const_outer_delimit;

	return message;
}




function updateEventList(mouse_xy, button_state)
{
	var scroll_xy = getScrollXY();
	var max_scroll_xy = getMaxScrollXY();
	var window_size = getWindowWidthHeight();
	var scroll_width = getScrollBarWidth();

	// calculate scrollbar visibility 
	var has_scrollbar = [0, 0];
	if (window_size[0] < max_scroll_xy[0]) has_scrollbar[0] = 1;
	if (window_size[1] < max_scroll_xy[1]) has_scrollbar[1] = 1;
	
	var current_time = new Date().getTime();
	var time_delta = current_time - g_start_timestamp;
	
	/* now we create 3 types of packages from the data
	 	but only if we have new information
	 	we prepare strings containing one packet each ...
	 
	 	Format (php - explode-friendly):
	 	
	 	tag;data;data;...|
	 	tag;data;data ...|
	 	...
	 	
	 	Tags:
	 	
	 	scroll;delta_time_ms;x;y; speed
	 	display;delta_time_ms;width;height;has_x_scroll;has_y_scroll;max_scroll_x;max_scroll_y
	 	mouse;delta_time_ms;x;y;button_state [down|up]; speed
	 	
	 */
	
	
	if (scroll_xy[0] != g_old_scroll_xy[0] ||
		scroll_xy[1] != g_old_scroll_xy[1])
	{	// have new scroll data ...
		// TODO: perhaps use same system to send not too many messages
		g_send_list += createScrollMessage(time_delta, g_old_time_delta,
											scroll_xy, g_old_scroll_xy)
											
	}

	if (window_size[0] != g_old_window_size[0] ||
		window_size[1] != g_old_window_size[1] ||
		has_scrollbar[0] != g_old_has_scrollbar[0] ||
		has_scrollbar[1] != g_old_has_scrollbar[1] ||
		scroll_width != g_old_scroll_width)
	{	// have new window size data ...
		// format [display; delta_time_ms; width; height; has_x_scroll; has_y_scroll; max_x_scroll; max_y_scroll]
		
		g_send_list += createWindowSize(time_delta, window_size, 
										has_scrollbar, max_scroll_xy,
										scroll_width);				
	}

	if (mouse_xy)
	{
		var create_mouse_msg = false;
		var click_change = false;
		
		if (mouse_xy[0] != g_old_mouse_xy[0] ||
			mouse_xy[1] != g_old_mouse_xy[1])
		{ // have new mouse pos data ...
			create_mouse_msg = true;		
		}
		
		if (button_state)
		{
			if (button_state != g_old_button_state)
			{ // have new mouse pos data ...
				create_mouse_msg = true;
				click_change = true;
			}			
			// only set the old states if we have data
			g_old_button_state = button_state;
		}

		if (create_mouse_msg)
		{	// have new mouse button data
			var message = createMouseMessage(time_delta, g_old_time_delta,
											 mouse_xy, g_old_mouse_xy, 
											 g_old_button_state);
			
			// important:
			//	* we send all click changes
			//  * but we send only the mouse position every 100 ms
			if (click_change)
			{
				g_send_list += message;
			}
			else
			{
				// check if last message is old enough
				//g_send_list += message;
				addDelayedEventMessage(0, message, 
									   current_time, 
									   const_mouse_delay_interval_ms);
			}
			
		}

		// only set the old states if we have data
		g_old_mouse_xy = mouse_xy;
	}

	// set the old state/data
	g_old_scroll_xy = scroll_xy;
	g_old_max_scroll_xy = max_scroll_xy;
	g_old_window_size = window_size;
	g_old_scroll_width = scroll_width;
	g_old_has_scrollbar = has_scrollbar; 
	g_old_time_delta = time_delta;
}



function onSendTimer()
{
	if (!g_activated)	return; // we should never be called here once tracking is deactivated, but just to be sure ...
	
	/* now send out the scheduled data, we add a header containing 
	 * project data and visitor-identification data
 
 	Format (php - explode-friendly):
 	
 	compression_flag [0/1]|
 	<- all data below can be compressed
 	project_id|
 	visitor_id|
 	tag;data;data;...|
 	tag;data;data ...|
 	...
 	
 */
	
	// add delayed messages
	g_send_list += flushDelayedMessages(2);

	// if message queue is empty add at least one message
	if (g_send_list == "")
	{
		var message = createMouseMessage(g_old_time_delta, g_old_time_delta + 1, 
									 g_old_mouse_xy, g_old_mouse_xy, g_old_button_state);
		
		g_send_list = message;
	}
	
	g_send_list = g_project_hash + const_outer_delimit + 
				  g_visitor_id + const_outer_delimit + g_send_list;

	// compress if activated
/*	if (const_do_compress)
	{
		// TODO: compression
		g_send_list = lzw_encode(g_send_list);
//		var compressor = new LZ77();
//		g_send_list = compressor.compress(g_send_list);
	}*/

	g_send_list = const_do_compress + const_outer_delimit + g_send_list;
	
	// TODO add unique end sign to find crippled messages
	
	
	// emulated send
	g_sent = g_send_list;
	
	// real send
	sendTrackingData(g_send_list);
	
	
	
	// clean send list
	g_send_list = "";
}

function checkForDeactivateMessage(messageToCheck)
{
	if (messageToCheck.search(const_deactivate_tag) != -1)
	{	// we should deactivate ourselves ... do it if deactivation is allowed
		if (!const_ignore_deactivate)	
		{
			uninitTracker();
		}
	}
}

function sendTrackingData(data_to_send)
{
	var send_success = false; 
	if (const_use_xmlhttp)
	{
	    var xmlhttp = null;
	    // Mozilla
	    if (window.XMLHttpRequest) 
	    {
	        xmlhttp = new XMLHttpRequest();
	    }
	    // IE
	    else if (window.ActiveXObject) 
	    {
	        xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
	    }
	   
	    xmlhttp.open("POST", const_serv_url, true);
	//    xmlhttp.setRequestHeader('Content-Type', 'binary/octet-stream');
	    xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
	//    xmlhttp.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-16');
	    xmlhttp.setRequestHeader("Content-length", data_to_send.length);
	    xmlhttp.onreadystatechange = function() {
	        if(xmlhttp.readyState == 4)
	        {
	        	if (xmlhttp.status == 200) {
	        		// TODO: react on "cmr_deactivate" answers
	        		var http_answer = "success: " + xmlhttp.responseText + "\n\n";// + g_sent;
	        		
	        		checkForDeactivateMessage(xmlhttp.responseText);
	        		g_sent = http_answer;
	        	}
	        	else
	        	{
	        		g_sent = "error: " + xmlhttp.statusText;
	        	}
	        }
	    }
	    if (xmlhttp.send("data=" + data_to_send))
	    {
	    	send_success = true;
	    }
	}
	
	if (!send_success) // try it using other methods ..
	{
		var sendImg = new Image;
		sendImg.onerror = function()
			{	// fake a deactivate message
				checkForDeactivateMessage(const_deactivate_tag)
			}
		
		sendImg.src = const_serv_url + "?m=img&data=" + data_to_send;
    	send_success = true;
	}
}


function onTrackingTimeout()
{
	// send out last message ... (fake send timer event)
	onSendTimer();
	// and deactivate tracking ...
	uninitTracker();
}


var grabbing_timer;
var send_timer;
var timeout_timer;

function randomString(string_length) 
{
	var chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz";
	var randomstring = '';
	for (var i=0; i<string_length; i++) {
		var rnum = Math.floor(Math.random() * chars.length);
		randomstring += chars.substring(rnum,rnum+1);
	}
	return randomstring;
}

function initTracker()
{
	// init vars
	g_old_mouse_xy = [-1, -1];

	g_old_button_state = "up";
	g_old_scroll_xy = [-1, -1];
	
	g_old_window_size = [-1, -1];
	g_old_scroll_width = -1;
	g_old_has_scrollbar = [-1, -1];
	g_old_max_scroll_xy = [-1, -1];

	g_old_time_delta = -1;

	g_send_list = "";
	g_sent = "";
	g_delayed_messages = [[-1, -1, ""], [-1, -1, ""], [-1, -1, ""]];
	
	g_start_timestamp = new Date().getTime();

	// the proj hash is set through an external variable ... if not use secure default
	if (typeof(cmr_proj) == "undefined")
	{
		//g_project_hash = "79c662560b0a";
		g_project_hash = "empty";
	}
	else
	{
		g_project_hash = cmr_proj;
	}
	
	// set the ad code (through external var, too) ... may be nonexistent, then use default
	if (typeof(cmr_ad) == "undefined")	g_ad_code = "STD";
	else	g_ad_code = cmr_ad;
	
	// find where the tracking script lies
	//const_serv_url = "http://" + document.URL.split(/\/+/g)[1] + "/" + const_serv_url;
	var all_scripts = document.getElementsByTagName("script");
	for (var i=0, limit=all_scripts.length; i< limit; i++) {
		var script_name_pos = all_scripts[i].src.indexOf("cmr_tracker.js");
	    if (-1 != script_name_pos) {
	       // Have you found your script, extract your data
	    	const_serv_url = all_scripts[i].src.substr(0, script_name_pos) + const_serv_url;
	    }
	}

	// create random visitor hash ...
	g_visitor_id = randomString(12);

	g_send_list = createInitVisitorMessage(g_visitor_id, g_start_timestamp, 
											document.URL, g_ad_code);
	
	// create a timer for updates every 0.5 secs
	grabbing_timer = window.setInterval('onTimerEvent()', const_grabbing_interval_ms);
	send_timer = window.setInterval('onSendTimer()', const_sending_interval_ms);
	timeout_timer = window.setTimeout('onTrackingTimeout()', const_stop_tracking_after_seconds * 1000);
	
	
	// TODO: make sure the hooking doesn't interfere with other javascripts
	// -> save existing hooks / events in JS

	// hook the mousewheel event (if possible ...)
	/** Initialization code. 
	 * If you use your own event management code, change it as required.
	 */
	if (window.addEventListener)
	        /** DOMMouseScroll is for mozilla. */
	        window.addEventListener('DOMMouseScroll', onWheelEvent, false);
	/** IE/Opera. */
	window.onmousewheel = document.onmousewheel = onWheelEvent;

	// the other mouse messages
	document.onmousemove = onMouseMove; // update(event) implied on NS, update(null) implied on IE
	document.onmouseup = onMouseUp;
	document.onmousedown = onMouseDown;
	
	g_activated = true;
}



function uninitTracker()
{
	// deactivate tracker if possible & necessary
	if (g_activated)
	{
		document.onmousemove = 0; // update(event) implied on NS, update(null) implied on IE
		document.onmouseup = 0;
		document.onmousedown = 0;
		
		if (window.addEventListener)
	        /** DOMMouseScroll is for mozilla. */
	        window.removeEventListener('DOMMouseScroll', onWheelEvent, false);
		/** IE/Opera. */
		window.onmousewheel = document.onmousewheel = 0;

		g_send_list = createInitVisitorMessage(g_visitor_id, g_start_timestamp, 
				document.URL, g_ad_code);

		// create a timer for updates every 0.5 secs
		window.clearInterval(grabbing_timer);
		window.clearInterval(send_timer);
		
		g_send_list = "";
		
		g_activated = false;
	}
}

initTracker();


