/* add-event.js -- cross browser utilities to add
 * and remove event handlers or listeners.
 *
 * Functions:
 *	addEvent
 *	removeEvent
 *	removeBinding
 */
function addEvent(obj, event_name, fnc, assigment_only) {
	var rtrn = true;
	if (typeof obj == "undefined") {return false;}


	if (!assigment_only && obj.attachEvent) {
		obj.attachEvent("on"+event_name, fnc);
	}
	else if (!assigment_only && obj.addEventListener) {
		obj.addEventListener(event_name, fnc, false);
	}
	else {
		rtrn = _bindEvent(obj, event_name, fnc);
	}
	return rtrn;
}//eof addEvent

function removeEvent(obj, eventName, fnc, assigment_only) {
	var rtrn = true;

	if (typeof obj == "undefined") {return false;}

	if (!assigment_only && obj.detachEvent) {
		obj.detachEvent("on"+eventName, fnc);
	}
	else if (!assigment_only && obj.removeEventListener) {
		obj.removeEventListener(eventName, fnc, false);
	}
	else {
		rtrn = _unbindEvent(obj, eventName, fnc);
	}
	return rtrn;
}//eof removeEvent

function removeBinding(obj, evt, fnc) {
	if (!removeEvent(obj, evt, fnc, true) )
		return removeEvent(obj, evt, fnc, false) ;
}//eof removeBinding

function _bindEvent(obj, event_name, fnc) {
	var str = '', handlers = '';

	if (obj["on" + event_name] != null)
		str = obj["on" + event_name].toString();	 //current event handler wrapper fx

	if (str != '')
		handlers = _removeWrapper(str, event_name);   //current handlers from without wrapper

	var newListener = fnc.toString();				//new function to string
	var newArray = new Array();
	var rtrnArray = new Array();
	var addReturn = null;
   /* Create function call string to be placed in handler wrapper
	* 1. Create string of passed (new) function declaration line
	*	converting to function call:
	*	 e.g. function x() {etc.} becomes x();
	* 2. append to string of existing event handlers putting
	*	any with return value at end of list.
	*	(new ones should not have a return value)
	*/
	if (/return \w+;/.test(newListener) ) addReturn = true;

	newListener = (addReturn ? 'return ' : '') + newListener.match(/function ([\w$]*\s*\([\w]*\))\s*\{?/)[1];
	handlers += '\n' + newListener + ';';

	/* test if returns default action flag */
	var handlerArray = handlers.match(/[^;]+;\s*/g);
	for (var i = 0; i < handlerArray.length; i++) {
		if (!/return /.test(handlerArray[i]) ) {
			newArray[newArray.length] = handlerArray[i].replace(/^\s*|\s*$/g, '');
		}
		else {
			rtrnArray[rtrnArray.length] = handlerArray[i].replace(/^\s*|\s*$/g, '');
		}
	}

	handlers = newArray.join('\n');
	if (rtrnArray.length > 0) handlers  += '\n' + rtrnArray.join('\n');

	/* Bind handlers back to object's event*/
	obj["on" + event_name] = new Function("evt", handlers);
	return true;
}//eof _bindEvent

function _unbindEvent(obj, eventName, fnc) {
	if (obj["on" + eventName] != null)
		var eventHandler = obj["on" + eventName].toString();
	else
		return false;

	var eventListeners = _removeWrapper(eventHandler, eventName);

	/* Get call line for fx being removed */
	fnc = fnc.toString();

	var fx_name = fnc.match(/function ([\w$])*\s*\([\w]*\)\s*\{?/)[1];
	var re = new RegExp(fx_name);

	/* split event listeners into array & create array for kept listeners */
	var fxArray = eventListeners.match(/[^;]+;\s*/g);
	var newArray = new Array();

	/* Copy listeners being retained to newArray skipping one being deleted*/
	var looking = true;   //so only one entry is removed
	fxArray.reverse();	//take last entry first

	for (var i = 0; i < fxArray.length; i++) {
		if (looking && re.test(fxArray[i]) )
			looking = false;
		else
			newArray[newArray.length] = fxArray[i].replace(/^\s*|\s*$/g, '');
	}

	/* Bind kept event listeners to event */
	if (!looking) {
		if (newArray.length > 0) {
			newArray.reverse(); //Back to original order
			eventListeners = newArray.join('\n');
			obj["on" + eventName] = new Function("evt", eventListeners);
		}
		else {
			obj["on" + eventName] = null;
		}
	}
	return !looking;
}//eof _unbindEvent

function _removeWrapper(eventHdlrStr, eName) {
	/* Remove handler wrapper for function calls using RegExp*/
	var re1 = /^\s*function anonymous\([\w]*\)\s*\{\s*/;
	var re2 = new RegExp("function on" + eName + "\\(\\w*\\)\\s*\\{");
	var re3 = /^\s*function\s+\(\w*\)\s*\{\s*/;

	/* Remove handlerr wrapper */
	if (re1.test(eventHdlrStr) )
		eventHdlrStr = eventHdlrStr.replace(re1, "");		//remove anonymous fx declaration IE and this
	else if (re2.test(eventHdlrStr))
		eventHdlrStr = eventHdlrStr.replace(re2, "");		//remove onevent fx declaration Mozilla
	else if (re3.test(eventHdlrStr) )
		eventHdlrStr = eventHdlrStr.replace(re3, "");		//remove KHTML anonymous (no name);
	else {
		var addReturn = /return \w+/.test(eventHdlrStr);
		eventHdlrStr.match(/function ([\w$]*\s*\([\w]*\))\s*\{?/);
		eventHdlrStr = ((addReturn) ? "return " : "") + RegExp.$1 + ';';
	}

	eventHdlrStr = eventHdlrStr.replace(/\s*\}\s*$/, '');	//remove closing brace

	/* Cleanup remaining function calls */
	eventHdlrStr = eventHdlrStr.replace(/^\s*|\s*$/g, '');	 //all trim
	eventHdlrStr = eventHdlrStr.replace(/\s+/g, ' ');		  //Replace multiple white spaces with one space
	eventHdlrStr = eventHdlrStr.replace(/\bevent\b/g, 'evt');  //replace key word event with local evt
	if (! /;$/.test(eventHdlrStr)) {eventHdlrStr += ";";}	  //add closing semicolon

	/* create array of function calls based on semicolon */
	var handlerArray = eventHdlrStr.match(/[^;]+;\s*/g);

	/* trim each function call */
	if (handlerArray) {
		for (var i = 0; handlerArray && i < handlerArray.length; i++) {
			handlerArray[i] = handlerArray[i].replace(/^\s*|\s*$/g, '');
		}
		return handlerArray.join('\n');
	}
	else {
		return '';
	}
}//eof _removeWrapper
