// JavaScript Document

// ------------------------------------------------------------
// Requirements.
// ------------------------------------------------------------

if(typeof dbg != "function"){
	var dbg = function(){};
}

// ------------------------------------------------------------
// Utilities.
// ------------------------------------------------------------

function hitch(obj, meth){
	return function(){
		return typeof meth == "function" ? meth.apply(obj, arguments) : obj[meth].apply(obj, arguments);
	}
}

// ------------------------------------------------------------
// Events
// ------------------------------------------------------------

Events_class = function(){
	this.init();
}

Events_class.prototype.init = function(){
	this.dbgColor = "#006666";
	this.debug = true;
	this.triggerElements = new Array();

	// memory cleanup

	this.add({
		element: window,
		eventType: 'unload',
		handler: this.clearTriggers,
		context: this
	});
}

Events_class.prototype.add = function (/* element, eventType, [object || function], [method] */){

	// Consolidate arguments
	var element;
	var eventType;
	var handler;
	var delay = 0;
	var dataPackage;
	var trace = false;
	var context = false;
	var label = "";

	if (arguments.length == 1){
		element = arguments[0].element;
		eventType = arguments[0].type;
		handler = arguments[0].handler;
		delay = parseInt(arguments[0].delay) || 0;

		if (typeof element == "undefined" || typeof eventType == "undefined" || typeof handler == "undefined"){
			return false;
		};

		context = typeof arguments[0].context != "undefined" ? arguments[0].context : false;
		dataPackage = typeof arguments[0].data != "undefined" ? arguments[0].data : {};
		trace = (arguments[0].trace == true);
		label = typeof arguments[0].label != "undefined" ? arguments[0].label : "";

		if (context){
			// will execute "handler" as a method of object instance: "context"
			handler = hitch(context, handler);
		};
	}
	else {
		element = arguments[0];
		eventType = arguments[1];
		// the third argument can be either a function pointer, or an object with a method reference

		if (typeof arguments[2] == "object" && typeof arguments[3] != "undefined"){
			// will execute "handler" as a method of object instance: "context"
			handler = hitch(context, handler);
			dataPackage = typeof arguments[4] == "undefined" ? {} : arguments[4];
		}
		else if (typeof arguments[2] == "function"){
			// executes handler as method of window (free floating function)
			handler = arguments[2];
			dataPackage = typeof arguments[3] == "undefined" ? {} : arguments[3];
		}
		else {
			return false;
		};
	};

	// CREATE AND STORE EVENT HANDLER
	// This puts a function reference on an array associated with the element an eventType. Handlers
	// so stored will be fired by the trigger function in sequence, the handler will always have
	// an event object passed to it because the trigger handles cross-browser event parameter issues

	// closure variables: element, eventType, handler, dataPackage, trace, label
	var evtHandler = function(e){
		if (trace){
			dbg("Event Triggered", "", "");
			dbg("Event Info", eventType, "");
			dbg("Object Info", element.tagName + (element.id ? ": " + element.id : ""));
			dbg("Handler Label", label);
		};
		handler(e, element, dataPackage);
	}

	this.storeHandler(element, eventType, evtHandler);

	// CREATE TRIGGER
	// The trigger will fire all handlers associated with a given element/eventType, the purpose of
	// the trigger is to encapsulate cross-browser parameter issues and to consolidate multiple
	// event references per element

	// re-reference "this" for the trigger closure
	var self = this;
	
	// create a closure to allow us to customize the event handler call
	var trigger = function (){
		// dbg("trigger firing on element: " + element);
		var e = arguments.length ? arguments[0] : window.event;
		self.runHandlers(e, element, eventType);
	};

	// create a delay if requested
	if (delay){
		trigger = this.createDelayedTrigger(element, eventType, trigger, delay);
	}

	return this.setTrigger(element, eventType, trigger);
};

Events_class.prototype.setTrigger = function(element, eventType, fnTrigger){
	if (!fnTrigger){
		dbg("Events.setTrigger: no trigger function provided");
		return false;
	};
	
	// default the trigger storage spot
	element._triggers = element._triggers || new Object();
	
	// wipe old trigger and old JS event (if any)
	this.removeTrigger(element, eventType);

	// store the new trigger function
	element._triggers[eventType] = fnTrigger;

	// element needs to be stored for memory cleanup at page unload
	this.registerTriggerElement(element);

	// Add trigger as the actual event that fires
	return this.addJSEvent(element, eventType, fnTrigger);
};

// wipe the old trigger, if one existed
Events_class.prototype.removeTrigger = function(element, eventType){
	if (element._triggers && element._triggers[eventType]){
		var fnTrigger = element._triggers[eventType] || null;
		this.removeJSEvent(element, eventType, fnTrigger);
		element._triggers[eventType] = null;
	};
}

// memory cleanup function
Events_class.prototype.clearTriggers = function(els){
	var rElements = new Array();
	if (els !== undefined){
		if (els instanceof Array){
			rElements = els;
		}
		else {
			rElements = new Array(els);
		};
	}
	else {
		rElements = this.triggerElements || new Array();
	};

	for(var i=0, thisEl, eventType; thisEl=rElements[i]; i++){
		if (thisEl._triggers !== undefined){
			for(eventType in thisEl._triggers){
				this.removeTrigger(thisEl, eventType);
			};

			delete(thisEl._triggers);
		};
	};
};

Events_class.prototype.registerTriggerElement = function(element){
	var found = false;
	
	for(var i=0, elCount = this.triggerElements.length, thisEl; thisEl = this.triggerElements[i]; i++){
		found = found || ( thisEl == element );
		if (found){
			return;
		};
	};
	if (!found){
		this.triggerElements.push(element);
	};
};

// low-level event add/remove wrappers
Events_class.prototype.removeJSEvent = function(element, eventType, fnHandler){
	if (fnHandler !== undefined){
		if (element.removeEventListener){
			// firefox
			element.removeEventListener(eventType, fnHandler, false);
			result = true;
		} 
		else if (element.detachEvent){
			// ie
			result = element.detachEvent("on" + eventType, fnHandler);
		}
		return result
	}
	return false;
};



Events_class.prototype.addJSEvent = function(element, eventType, fnHandler){
	if (element && eventType && fnHandler){
		if (element.addEventListener){
			// firefox
			element.addEventListener(eventType, fnHandler, false);
			return true;
		}
		else if (element.attachEvent){
			// ie
			return element.attachEvent("on" + eventType, fnHandler);
		};
	}
	return false;
};

// a handler is an individual operation assigned to an element/event

Events_class.prototype.storeHandler = function(el, eventType, fnHandler){
	// dbg("Events: store trigger", eventType, this.dbgColor);
	if (el && eventType && fnHandler){
		// default storage stuff, the triggers object is an associative array

		// of all the assigned event types which are assigned to this element
		el._handlers = el._handlers || new Object();
		el._handlers[eventType] = el._handlers[eventType] || new Array()

		var rHandlers = el._handlers[eventType];

		// push the function reference onto the handlers array
		rHandlers.push(fnHandler);
	};
};

Events_class.prototype.runHandlers = function(e, element, eventType){
	var rHandlers = element._handlers[eventType];
	if (rHandlers && rHandlers instanceof Array){
		for(var i=0, handlerCount = rHandlers.length; i < handlerCount; i++){
			rHandlers[i](e);
		};
	};
};

// REMOVE wipes all handlers tied to a given event/type
Events_class.prototype.removeEvent = function(element, eventType){
	this.removeTrigger(element, eventType);
	element._handlers = new Array();
};

Events_class.prototype.remove = Events_class.prototype.removeEvent;

// Delayed event firing, creates a trigger which is actually a timer reset that calls
// the originally defined trigger
Events_class.prototype._delayedTriggers = new Object();

Events_class.prototype.createDelayedTrigger = function(el, eventType, trigger, delay){
	// create an id that we can refer to with a string because that's the way setTimeout likes it
	var d = new Date();

	var id = "delayed_event_" + this.getUniqueID(el) + "_" + eventType + "_" + d.getTime();

	// store the trigger in a separate timer array
	this._delayedTriggers[id] = trigger;

	// create a timer object and the command it will fire
	var eventTimer = null;

	var cmd = "Events._delayedTriggers['" + id + "']()";
	
	return function(){
		// closure variables: eventTimer, cmd, delay
		if (eventTimer){
			clearTimeout(eventTimer);
		}
		eventTimer = setTimeout(cmd, delay);
	}
};

Events_class.prototype.idCounter = 0;

Events_class.prototype.getUniqueID = function(el){
	if (el.id) return el.id;
	return this.idCounter++;
};

// Events cancelling, halts execution of default event stuff
// using passed event object

Events_class.prototype.cancelEvent = function(event){

	if (event){

		if (event.preventDefault){
			event.preventDefault();
		};
		if (event.stopPropagation){
			event.stopPropagation()
		};
		event.cancelBubble = true;
		event.returnValue = false;
	};
	return false;
};

Events_class.prototype.cancel = Events_class.prototype.cancelEvent;

// Determines object which fired event from event object reference
// The second argument of every handler is now a direct reference to the assigned element, so I
// do not see the value of this function anymore, but it exists here for backwards compatibility

Events_class.prototype.getSrcElement = function(/* native js event object */){
	var el = arguments[0].srcElement || arguments[0].currentTarget || window;
	try {
		/* Safari triggers events on text nodes */
		if (el.nodeType == 3){
			el = el.parentNode;
		};
	}
	catch (e){
		el = window;
	};
	return el;
};

// Not sure these really belong in here, preserved for backwards compatibility

Events_class.prototype.disableTextSelect = function(){
	if (!document.onselectstart){
		document.onselectstart = function(){
			return false;
		};
	}
};

Events_class.prototype.removeTextSelect = Events_class.prototype.disableTextSelect; // backward compat

Events_class.prototype.enableTextSelect = function(){
	if (document.onselectstart) document.onselectstart = null;
};

var Events = new Events_class();