/**
 * 
 * All content on this website (including text, images, source
 * code and any other original works), unless otherwise noted,
 * is licensed under a Creative Commons License.
 * 
 * http://creativecommons.org/licenses/by-nc-sa/2.5/
 * 
 * Copyright (C) Open-Xchange Inc., 2006-2011
 * Mail: info@open-xchange.com 
 * 
 * @author Viktor Pracht <viktor.pracht@open-xchange.com>
 * 
 */

  ////////////////////////
 //   Event handling   //
////////////////////////

/**
 * Table of currently registered global event listeners.
 * @private
 */
var events = {};

/**
 * Encapsulates a method of an object in a function.
 * @param {Object} obj The instance of the object to call.
 * @param {String} methodname The name of the method to call on obj.
 * @return A function that can be used as the second parameter to {@link #register}.
 * @type Function
 * @see #register
 */
function encapsulateMethod(obj, methodname) {
	return function() { obj[methodname].apply(obj, arguments); };
}

/**
 * Registers a function as a listener for a global event.
 * Registering the same function for the same event multiple times has no effect.
 * Any given function is called exactly once for every triggered event.
 * The order of calls to different functions registered for the same event is
 * not guaranteed.
 * @param {String} name The name of the event. Event names are global.
 * @param {Function} callback A function to be called when the named event is
 * triggered.
 * @see #unregister
 */
function register(name, callback) {
    if (name in triggerSingleton.singletons) {
        callback.apply(window, triggerSingleton.singletons[name]);
    }
    var list = (name in events) ? events[name] : events[name] = {};
    if ("oxEventId" in callback) {
        if (callback.oxEventId in list) return;
    } else {
        callback.oxEventId = Events.id++;
    }
    list[callback.oxEventId] = callback;
}

register.id = 1;

/**
 * Unregisters a previously registered listener from a global event.
 * Unregistering a listener which was not previously registered for the specified
 * event has no effect.
 * @param {String} name The name of the event. Event names are global.
 * @param {Function} callback The same function object as was used in a call to
 * {@link #register}. Return values from {@link #encapsulateMethod} must be stored
 * and reused instead of calling #encapsulateMethod again.
 */
function unregister(name, callback) {
    if (("oxEventId" in callback) && (name in events)) {
        delete events[name][callback.oxEventId];
    }
}

/**
 * Triggers a global event and calls all registered listeners.
 * @param {String} name The name of the event. Event names are global.
 * @param params Any further parameters are passed to the called listeners.
 * @see #register
 */
function triggerEvent() {
	var list = events[arguments[0]];
	var args = null;
	args = new Array(arguments.length - 1);
	for (var i = 1; i < arguments.length; i++)
		args[i - 1] = arguments[i];
	if (list) 
	{
		for (var cb in list)
		{ 
			list[cb].apply(null, args);
		}
	}
}

/**
 * Triggers a singleton event. A singleton event should be triggered
 * only once. Listeners, which are registered after the event was
 * triggered, are called immediately. The arguments to the event are saved
 * forever to be passed to eventual late listeners.
 * @param {String} name The name of the event. Event names are global.
 * @param params Any further parameters are passed to the called listeners.
 * @see #triggerEvent
 */
function triggerSingleton(name) {
    triggerEvent.apply(this, arguments);
    var args = triggerSingleton.singletons[name] = [];
    for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
}
triggerSingleton.singletons = {};

function Events() {
	this.events = {};
	this.posted = {};
}

Events.id = 1;

Events.prototype = {
	/**
	 * Registers a function as a listener for an event.
	 * Registering the same function for the same event multiple times has no
	 * effect. Any given function is called exactly once for every triggered
	 * event. The order of calls to different functions registered for the same
	 * event is not guaranteed.
	 * @param {String} name The name of the event. Event names are specific to
	 * an Events instance.
	 * @param {Function} callback A function to be called when the named event
	 * is triggered. Which parameters are passed to the callback is defined by
	 * the event.
	 * @see #unregister
	 */
	register: function(name, callback) {
		var list = (name in this.events) ? this.events[name]
		                                 : this.events[name] = new Array();
		for (var i in list) if (list[i] == callback) return;
		list.push(callback);
	},

	/**
	 * Unregisters a previously registered listener from an event.
	 * Unregistering a listener which was not previously registered for the
	 * specified event has no effect.
	 * @param {String} name The name of the event. Event names are specific to
	 * an Events instance.
	 * @param {Function} callback The same function object as was used in a call
	 * to {@link #register}. Return values from {@link #encapsulateMethod} must
	 * be stored and reused instead of calling #encapsulateMethod again.
	 */
	unregister: function(name, callback) {
		var list = this.events[name];
		if (!list) return;
		var cb = list.pop();
		if (cb == callback) return;
		for (var i = list.length - 1; i >= 0; i--) {
			if (list[i] == callback) {
				list[i] = cb;
				return;
			}
		}
		if(cb)
			list.push(cb);
	},

	/**
	 * Triggers an event and calls all registered listeners.
	 * @param {String} name The name of the event. Event names are specific to
	 * an Events instance.
	 * @param params Any further parameters are passed to the called listeners.
	 * @see #register
	 */
	trigger: function() {
		var list = this.events[arguments[0]];
		if (list) {
			var args = new Array(arguments.length - 1);
			for (var i = 1; i < arguments.length; i++)
				args[i - 1] = arguments[i];
			for (var cb in list) list[cb].apply(null, args);
		}
	},
	
	/**
	 * Postpones the triggering of an event until the currently executing
	 * JavaScript code exits and the browser enters its event loop.
	 * If an event is already posted, any further posts for the same event have
	 * no effect.
	 * @param {String} name The name of the event. Event names are specific to
	 * an Events instance.
	 * @param params Any further parameters are passed to the called listeners.
	 * If a parameter is a fuction, then it is evaluated once immediately before
	 * calling the first listener and the return value is passed to
	 * the listeners. In the case of multiple calls with the same event name
	 * without an opportunity to execute the listeners, the parameter values
	 * from the last call are used.
	 * @see #trigger
	 */
	post: function(name) {
		if (!this.posted[name]) {
			var Self = this;
			setTimeout(function() {
				var args = Self.posted[name];
				delete Self.posted[name];
				var list = Self.events[name];
				if (list) {
					var params = new Array(args.length - 1);
					for (var i = 1; i < args.length; i++) {
						if (args[i].constructor == Function)
							params[i - 1] = args[i]();
						else
							params[i - 1] = args[i];
					}
					for (var cb in list) list[cb].apply(null, params);
				}
			}, 0);
		}
		this.posted[name] = arguments;
	}
};