/**
 * @fileoverview	The functionality found in this package is used for sending on click events
 *					to the iClick server.
 *					The event is sent to the server as HTTP GET by dynamically adding a new script element
 *					to the body's DOM structure. The element is removed afterwards to preserver memory.
 *					A click event consist of the following data:
 *					- URL for the "on click" event
 *					- X-Coordinate the click occurred at
 *					- Y-Coordinate the click occurred at
 *					- element the user clicked on
 *					- value of the element the user clicked on
 *
 * @author Jonatan Evald Buus
 */


/**
 * Extends the properties of the built-in Javascript String object to be able to perform a standard
 * trim operation on the string.
 * This will remove any whitespace characters at both ends of the string
 *
 * @return 		Trimmed string with white space characters removed from both ends
 * @type string
 */
String.prototype.trim = function()
{
	return this.replace(/^\s+|\s+$/g,"");
}
/**
 * Extends the properties of the built-in Javascript String object to be able to perform a standard 
 * left trim operation on the string.
 * This will remove any whitespace characters at the beginning of the string.
 *
 * @return 		Trimmed string with white space characters removed at the beginning of the string
 * @type string
 */
String.prototype.ltrim = function()
{
	return this.replace(/^\s+/,"");
}
/**
 * Extends the properties of the built-in Javascript String object to be able to perform a standard 
 * right trim operation on the string.
 * This will remove any whitespace characters at the end of the string.
 *
 * @return 		Trimmed string with white space characters removed at the end of the string
 * @type string
 */
String.prototype.rtrim = function()
{
	return this.replace(/\s+$/,"");
}

/**
 * Defines the debug mode of the package, this makes the Client ouput messages to the tag with id="debug"
 *
 * 1 - Output Errors
 * 2 - Output Warnings
 * 3 - Output Errors and Warnings
 * 4 - Output Notices
 * 5 - Output Errors and Notices
 * 6 - Output Warnings and Notices
 * 7 - Output Errors, Warnings and Notices
 * 8 - Output Debug Info
 * 9 - Output Errors and Debug Info
 * 10 - Output Warnings and Debug Info
 * 11 - Output Errors, Warnings and Debug Info
 * 12 - Output Notices and Debug Info
 * 13 - Output Errors, Notices and Debug Info
 * 14 - Output Warnings, Notices and Debug Info
 * 15 - Output Errors, Warnings, Notices and Debug Info
 *
 * @type integer
 */
var iDEBUG_MODE = 0;

/**
 * Global function for writing output for error or debug purposes.
 * The method can be called with a code identifying the type of report:
 * 1 - Error
 * 2 - Warning
 * 4 - Notice
 * 8 - Debug
 * The method uses the global variable: iDEBUG_MODE to determine whether a report should be displayed or not
 * Please note, for the function to work, the page MUST contain an element with id="debug"
 */
function report(code, msg)
{
	if ( (iDEBUG_MODE&code) == code)
	{
		if (document.getElementById("debug").innerHTML != "") { document.getElementById("debug").innerHTML += "<hr />"; }
		switch (code)
		{
		case (1):
			document.getElementById("debug").innerHTML += "ERROR - ";
			break;
		case (2):
			document.getElementById("debug").innerHTML += "WARNING - ";
			break;
		case (4):
			document.getElementById("debug").innerHTML += "NOTICE - ";
			break;
		case (8):
			document.getElementById("debug").innerHTML += "DEBUG - ";
			break;
		default:
			document.getElementById("debug").innerHTML += "UNKNOWN - ";
			break;
		}
		msg = msg.replace(/</g, "&lt;");
		msg = msg.replace(/>/g, "&gt;");
		msg = msg.replace(/\n/g, "<br />");
		
		document.getElementById("debug").innerHTML += msg;
	}
}

function iClick (url)
{
	/**
	 * URL to the iClick server where click data should be sent to
	 *
	 * @type string
	 */
	this.sURL = url;
	
	/**
	 * Mouse Cursor's current X coordinate
	 *
	 * @type integer
	 */
	this.iMouseX = 0;
	/**
	 * Mouse Cursor's current Y coordinate
	 *
	 * @type integer
	 */
	this.iMouseY = 0;
	
	/**
	 * Original "On Mouse Move" event handler method
	 *
	 * @type object
	 */
	this.obj_OrgOnMouseMove = null;
	/**
	 * Original "On Click" event handler method
	 *
	 * @type object
	 */
	this.obj_OrgOnClick = null;
	
	/**
	 * Pointer to the object itself as callback methods cannot use the "this" pointer
	 *
	 * @type iClick
	 */
	var obj_This = this;
	
	/**
	 * Get's the mouse cursor's current X/Y coordinate
	 * If an "On Mouse Over" event handler was originally defined, the method will pass
	 * the event to this handler
	 *
	 * @member 	iClick
	 *
	 * @param	{event} e 	"on mouse move" event passed by the Gecko engine, will be null for IE
	 */
	iClick.prototype.getMouseCoord = function (e)
	{
		// Works on IE, but not NS (we rely on NS passing us the event)	
		if (!e) { e = window.event; }
	
		if (e)
		{
			// This doesn't work on IE5.5 & IE6, only works on Firefox, Mozilla, Opera7
			if (e.pageX || e.pageY)
			{
				obj_This.iMouseX = e.pageX;
				obj_This.iMouseY = e.pageY;
			}
			// Works on IE5.5, IE6,Firefox, Mozilla, Opera7
			else if (e.clientX || e.clientY)
			{
				obj_This.iMouseX = e.clientX + document.body.scrollLeft;
				obj_This.iMouseY = e.clientY + document.body.scrollTop;
			}
		}
		// Run original On Mouse Move Event handler
		if (obj_This.obj_OrgOnMouseMove != null) { obj_This.obj_OrgOnMouseMove(e); }
	}
	
	/**
	 * Returns the source element from which an event originates.
	 * For onclick events the returned element will be where the user clicked.
	 *
	 * @member 	iClick
	 *
	 * @param	{event} e 	Event to find source element for
	 * @return	{object} 	Source element where the event originated from
	 * @type	object
	 */
	iClick.prototype.getEventSource = function (e)
	{
		var obj_Src;
	
		// Sets the event object if it hasn't already been defined
		if (!e) { e = window.event; }
	
		// Fetches reference to the source element where the even occured
		if (e.target) { obj_Src = e.target; }				// W3C event-model
		else if (e.srcElement) { obj_Src = e.srcElement; }	// Microsoft event-model
	
		/* 
		 * Safari bug:
		 * When the event originates from a link, Safari doesn't return the actual link but rather the link text
		 * If source element for the event is a DOM text node, it must be the parent node which is the actual
		 * originator for the event
		 */
		if (obj_Src.nodeType == 3) { obj_Src = obj_Src.parentNode; }
	
		return obj_Src;
	}
	
	/**
	 * Sends the X / Y coordinate and source element data to the remote server
	 * If an "On Click" event handler was originally defined, the method will pass
	 * the event to this handler
	 *
	 * @member 	iClick
	 *
	 * @param	{event} e 	Event click event occured on
	 */
	iClick.prototype.onClickHandler = function (e)
	{
		var obj_Src = obj_This.getEventSource(e);
		var elem = obj_Src.tagName;
		// Element has multiple relevant sub-types
		if (elem.toLowerCase() == "input")
		{
			// Type attribute defined for element
			if (obj_Src.getAttribute("type") != null && obj_Src.getAttribute("type") != "")
			{
				elem += " "+ obj_Src.getAttribute("type");
			}
		}
		// Send data to server
		obj_This.send(obj_This.iMouseX, obj_This.iMouseY, elem.toLowerCase(), obj_This.getValue(obj_Src), obj_This.isLink(obj_Src) );
		
		// Run original On Click Event handler
		if (obj_This.obj_OrgOnClick != null) { obj_This.obj_OrgOnClick(e); }
	}
	
	/**
	 * Sends data as HTTP GET to the server for logging
	 * Data is URL encoded prior to being sent to the server to prevent data corruption.
	 * The data is sent by adding a script element to the DOM for the body the tag is removed afterwards
	 * to prevent memory overflow.
	 *
	 * @member 	iClick
	 *
	 * @param	{integer} x 	X Coordinate where the user has clicked
	 * @param	{integer} y 	Y Coordinate where the user has clicked
	 * @param	{string} e 		Tag name of the element where the user has clicked
	 * @param	{string} v 		Value of the element where the user has clicked	 
	 * @param	{integer} l 	Flag defining whether the element is a link: 0 - No, 1 - Yes
	 */
	iClick.prototype.send = function (x, y, e, v, l)
	{
		// Create new script object for sending data as HTTP GET
		var obj_Send = document.createElement('script');
		
		var url = window.location.href;
		// Remove Protocol from URL
		url = url.replace(/http:\/\//i, "");
		url = url.replace(/https:\/\//i, "");
		// Remove www. prefix from URL
		url = url.replace(/www\./i, "");
		// Construct complete URL
		url = obj_This.sURL +"?w="+ screen.width +"&h="+ screen.height +"&x="+ x +"&y="+ y +"&link="+ l +"&element="+ this.urlEncode(e) +"&value="+ this.urlEncode(v) +"&url="+ this.urlEncode(url);
		
		report(8, url);
		// Set URL to send to as the src attribute
		obj_Send.setAttribute("src", url);
		/**
		 * Internal method for ensuring that the click data is sent to the iClick server
		 * This method is invoked by a timer to ensure it's executed
		 *
		 * @member iClick
		 *
		 * @private
		 */
		var _timerMethod = function ()
		{
			// Send data
			document.getElementsByTagName("body")[0].appendChild(obj_Send);
			// Remove object from DOM structure to preserve memory
			document.getElementsByTagName("body")[0].removeChild(obj_Send)
		}
		setTimeout(_timerMethod, 1);
	}
	
	/**
	 * Encodes the string so it can be transmitted as a URL in accordance with the
	 * application/x-www-urlencoded mimetype.
	 *
	 * @member 	iClick
	 *
	 * @param	{string} s 		String which should be URL encoded
	 * @return 	{string} 		URL Encoded string
	 * @type	{string}
	 */
	iClick.prototype.urlEncode = function (s)
	{
		// Define safe characters which can be transmitted without being encoddd
		var SAFECHARS = "0123456789" +					// Numeric
						"ABCDEFGHIJKLMNOPQRSTUVWXYZ" +	// Alphabetic
						"abcdefghijklmnopqrstuvwxyz" +
						"-_.!~*'()";					// RFC2396 Mark characters
		// Define Hex characters
		var HEX = "0123456789ABCDEF";
		// Initialise encoded string
		var encoded = "";
		
		if (s != null)
		{
			/*
			 * Encode each character, spaces are application/x-www-urlencoded as + ,
			 * rather than ASCII encoded as %20
			 */
			for (var i=0; i<s.length; i++ )
			{
				var ch = s.charAt(i);
				// x-www-urlencoded the space as a +
				if (ch == " ")
				{
					encoded += "+";
				}
				// Safe character, append
				else if (SAFECHARS.indexOf(ch) != -1)
				{
					encoded += ch;
				}
				// Unsafe character, encode
				else
				{
					var charCode = ch.charCodeAt(0);
					if (charCode <= 255)
					{
						encoded += "%";
						encoded += HEX.charAt((charCode >> 4) & 0xF);
						encoded += HEX.charAt(charCode & 0xF);
					}
				}
			}
		}
	
		return encoded;
	}
	
	/**
	 * Determines whether the element the user has clicked on is a link
	 * A link is defined as either an element with a defined onclick event or
	 * either an anchor (a) tag or a submit button.
	 * The method will go through parent elemtns recursively until the HTML Body
	 * has been reached.
	 *
	 * @member 	iClick
	 *
	 * @param	{object} elem 	Element to determine link capabilities for
	 * @return 	{integer} 		Result of search: 0 - No, Element is not a link, 1 - Yes, Element is a link
	 * @type	{integer}
	 */
	iClick.prototype.isLink = function (elem)
	{
		var iLink = 0;
		var name = elem.tagName;
		name = name.toLowerCase();
		
		// Anchor tag (a)
		if (name == "a")
		{
			iLink = 1;
		}
		// Submit button
		else if (name == "input" && elem.getAttribute("type") == "submit")
		{
			iLink = 1;
		}
		// Reached top of document and no on click function was originally defined
		else if (name== "body" && this.obj_OrgOnClick == null)
		{
			iLink = 0;
		}
		// Element has an onclick event defined
		else if (elem.getAttribute("onclick") != null && elem.getAttribute("onclick") != "")
		{
			iLink = 1;
		}
		// Look at parent node
		else
		{
			iLink = this.isLink(elem.parentNode);
		}
		report(8, name);
		
		return iLink;
	}
	
	/**
	 * Determines the value of the clicked element, i.e. what did the user click on.
	 * The value is determined as follows:
	 * 	- For anchor, button, td, div and span tags, the inner HTML is used if it doesn't contain HTML code
	 * 	- For input or submit buttons, the value is used
	 * 	- For images, the value of the src attribute is used
	 *	- For all other tags the value is set to an empty string
	 *
	 * @member 	iClick
	 *
	 * @param	{object} elem 	Element to find value for
	 * @return 	{string} 		Value of the Element
	 * @type	{string}
	 */
	iClick.prototype.getValue = function (elem)
	{
		var name = elem.tagName;
		name = name.toLowerCase();

		var value = "";
		// anchor, button, td, div or span tag
		if (name == "a" || name == "button" || name == "td" || name == "div" || name == "span")
		{
			value = elem.innerHTML;
			value = value.trim();
			// Image
			if (value.substr(0, 4) == "<img")
			{
				value = elem.childNodes[0].src; 
				// Remove Protocol from Image Source URL
				value = value.replace(/http:\/\//i, "");
				value = value.replace(/https:\/\//i, "");
				// Remove www. prefix from Image Source URL
				value = value.replace(/www\./i, "");
			}
			// Other HTML
			else if (value.substr(0, 1) == "<")
			{
				value = "";
			}
		}
		// Input or Submit button
		else if (name == "input" && (elem.getAttribute("type") == "submit" || elem.getAttribute("type") == "button") )
		{
			value = elem.value;
		}
		// Image
		else if (name == "img")
		{
			value = elem.src;
			// Remove Protocol from Image Source URL
			value = value.replace(/http:\/\//i, "");
			value = value.replace(/https:\/\//i, "");
			// Remove www. prefix from Image Source URL
			value = value.replace(/www\./i, "");
		}
		report(8, value);
		
		return value;
	}
	
	/*
	 * Registers method for receiving "on mouse move" events 
	 * This allows the class to always know the X/Y coordinate of the mouse cursor
	 */
	if (document.onmousemove) { this.obj_OrgOnMouseMove = document.onmousemove; }
	document.onmousemove = this.getMouseCoord;
	
	/*
	 * Registers method for receiving "on click" events 
	 */
	if (document.onclick) { this.obj_OrgOnClick = document.onclick; }
	document.onclick = this.onClickHandler;
}