define('jls/util/EventBroker', ['jls/lang/Class', 'jls/lang/Exception', 'jls/lang/Logger'], function (Class, Exception, Logger) {

var EventBroker;
EventBroker = Class.create( /** @lends jls.util.EventBroker.prototype */
{
    /**
     * Creates an event broker.
     * 
     * @constructs
     * @class This class represents an event broker.
     */
    initialize : function() {
        this._dispatchers = {};
    },
    /**
     * Subscribe to topic.
     * 
     * @param {String} name The event name.
     * @param {Function} callback The function to associate to this event name.
     * @param {Object} [thisArg] If defined, the this object to use when calling the callback function.
     * @returns {Object} the handle representing the subscription in order to be used with unsubscribe.
     */
    subscribe : function(name, callback, thisArg) {
        Logger.getInstance().trace('subscribe(' + name + ')');
        if ((typeof callback == 'object') && (name in callback)) {
            callback = callback[name];
        }
        if (typeof callback != 'function') {
            throw new Exception('Invalid callback');
        }
        if (typeof thisArg != 'undefined') {
            callback = callback.bind(thisArg);
        }
        var ed = null;
        if (name in this._dispatchers) {
        	ed = this._dispatchers[name];
        } else {
            this._dispatchers[name] = ed = new EventBroker.Dispatcher();
        }
        ed.addHandler(callback);
        return callback;
    },
    /**
     * Subscribe to multiple topics.
     * 
     * @param {Array} names The event names.
     * @param {Function} callback The function to associate to this event name.
     * @param {Object} [thisArg] If defined, the this object to use when calling the callback function.
     * @returns {Object} the handle representing the subscription in order to be used with unsubscribe.
     */
    subscribeAll : function(names, callback, thisArg) {
        Logger.getInstance().trace('subscribe(' + names.length + ')');
        for (var i = 0; i < names.length; i++) {
            this.subscribe(names[i], callback, thisArg);
        }
    },
    /**
     * Checks that an event exists.
     * 
     * @param {String} name The event name to look for.
     * @returns {Boolean} True if a subscription exists for this event name.
     */
    hasSubscription : function(name) {
        return name in this._dispatchers;
    },
    /**
     * Unsubscribe to a topic.
     * 
     * @param {String} name The event name to unsubscribe to.
     * @param {Object} [handle] The handle associated to the subscription.
     * @returns {jls.util.EventBroker} this event broker.
     */
    unsubscribe : function(name, handle) {
        Logger.getInstance().trace('unsubscribe(' + name + ')');
        if (! name in this._dispatchers) {
            throw new Exception('The event ' + name + ' does not exists');
        }
        var ed = this._dispatchers[name];
        if (handle) {
            ed.removeHandler(handle);
        } else {
            ed.removeAll();
        }
        if (ed.getCount() == 0) {
            delete this._dispatchers[name];
        }
        return this;
    },
    /**
     * Publish a custom event.
     * 
     * @param {String} name The name of the event to publish.
     * @param {Object} [message] An optional message object associated to the publication.
     * @returns {jls.util.EventBroker} this event broker.
     */
    publish : function(name, message) {
        Logger.getInstance().trace('publish(' + name + ')');
        if (name in this._dispatchers) {
        	this._dispatchers[name].dispatch(new EventBroker.Event(name, message))
        }
        return this;
    },
    /**
     * Creates a callback for a specified event name.
     * 
     * @param {String} name The name of the event.
     * @returns {Function} the callback.
     */
    callback : function(name) {
        if ((name == null) || (typeof name == 'undefined')) {
            return name; // Just act as a wrapper
        } else if (typeof name == 'function') {
    		return name; // Just act as a wrapper
    	} else if (typeof name != 'string') {
    		throw new Exception('Invalid event name');
    	}
    	var broker = this;
    	return function(message) {
    		broker.publish(name, message);
    	};
    },
    /**
     * Closes this event broker.
     */
    close : function() {
        Logger.getInstance().trace('close()');
        this._dispatchers = [];
    }
});

Object.extend(EventBroker,
{
    /**
     * The default event broker.
     * @type jls.util.EventBroker
     */
    DEFAULT: new EventBroker(),
    /**
     * Creates and returns a callback that calls the specified function when the specified count is reached.
     * @param {Number} count The count.
     * @param {Function} fn The function to call.
     * @returns {Function} the callback.
     */
    countDownCallback : function(count, fn) {
        if (typeof fn != 'function') {
            throw new Exception('Invalid function');
        }
        var n = 0;
        return function() {
            if (++n == count) {
                fn();
            }
        };
    }
});

EventBroker.Event = Class.create(
{
    initialize : function(name, message) {
        this.name = name;
        this.message = message;
    }
});

EventBroker.Dispatcher = Class.create(
{
    initialize : function() {
        this._callbacks = [];
    },
    addHandler : function(callback) {
        this._callbacks.push(callback);
        return this;
    },
    removeHandler : function(callback) {
        for (var i = 0; i < this._callbacks.length; i++) {
            if (callback === this._callbacks[i]) {
                this._callbacks.splice(i, 1);
                break;
            }
        }
        return this;
    },
    getCount : function() {
        return this._callbacks.length;
    },
    onException : function(e) {
        Logger.getInstance().warn('Uncaught exception during dispatching "' + e + '"');
        throw e;
    },
    dispatch : function(event) {
        Logger.getInstance().trace('dispatch() on ' + this._callbacks.length + ' callback(s)');
        for (var i = 0; i < this._callbacks.length; i++) {
            try {
                this._callbacks[i](event);
            } catch (e) {
            	this.onException(e);
            }
        }
        return this;
    },
    removeAll : function() {
        Logger.getInstance().trace('removeAll()');
        this._callbacks = [];
        return this;
    }
});

return EventBroker;
});
