// See COPYING for copyright and license details
(function ()  
{
    var _sigCount = {};
    var _byName = {};
    var _byId = {};

    var _getByCallback = function(callback)
    {
        var id;
        for (id in _byId)
        {
            if (callback == _byId[id].callback)
            {
                return _byId[id];
            }
        }
        return null;
    };
    var _getBySelfOrCallback = function(selfOrCallback)
    {
        if (selfOrCallback instanceof Signal)
            return selfOrCallback;
        return _getByCallback(selfOrCallback);
    };
    Object.defineProperty(this, "Signal", {
            writable : true,
            value : (function() {
                var id = 0;
                return function(name, callback, predicate)
                {
                    if (!name)
                        throw new Error("new Signal() : missing signal name");
                    
                    id++;
                    return Object.create(Signal.prototype, 
                        {
                            "id" : { value : id },
                            "callback" : { value : callback, writable : true }, 
                            "predicate" : { value : predicate || null, writable : true }, 
                            "name" : { value : name },
                            "disconnect" : { 
                                value : function() { 
                                    if (!this.connected)
                                        return this;

                                    var name = this.name, id = this.id;
                                    if (_sigCount[name] > 0)
                                        _sigCount[name]--;

                                    _byName[name][id] = null;
                                    delete _byName[name][id];

                                    _byId[id] = null;
                                    delete _byId[id];

                                    if (_sigCount[name] == 0)
                                        signals[name] = null;

                                    return this;
                                }
                            },
                            "connect" : { 
                                value : function(callback, predicate) 
                                {
                                    if (callback)
                                        this.callback = callback;
                                    if (predicate)
                                        this.predicate = predicate;
                                    if (this.connected)
                                        return this;

                                    if (!this.callback)
                                        throw new Error("Signal.connect() : missing callback");

                                    var name = this.name, id = this.id;

                                    if (!_sigCount[name])
                                        _sigCount[name] = 0;

                                    if (!_byName[name])
                                        _byName[name] = {};

                                    if (_sigCount[name] == 0)
                                        signals[name] = function() { return Signal.emit(name, arguments); };

                                    _sigCount[name]++;
                                    _byName[name][id] = this;
                                    _byId[id] = this;

                                    return this;
                                }
                            }, 
                            "connected" : 
                            {
                                get : function() 
                                {
                                    return Boolean(_byId[this.id]);
                                }
                            },
                            "toggle" : 
                            {
                                value : function()
                                {
                                    var connected = this.connected;
                                    if (connected)
                                        this.disconnect();
                                    else 
                                        this.connect();
                                    return !connected;
                                }
                            }, 
                            "remove" : {
                                value : function() {
                                    this.disconnect();
                                }
                            }
                        }
                    );
                };
            })()
    });
    Object.defineProperties(Signal, {
            "connect" : 
            {
                value : function(name, callback, predicate)
                {
                    return new Signal(name, callback, predicate).connect();
                }
            },
            "once" : 
            {
                value : function(name, callback, predicate) 
                {
                    return new Signal(name, function() {
                        var ret = false;
                        if (!this.predicate || this.predicate.apply(this, arguments))
                        {
                            this.disconnect();
                            ret = callback.apply(this, arguments);
                        }
                        return ret;
                    }, predicate).connect();
                }
            },
            "disconnect" : 
            {
                value : function(selfOrCallback)
                {
                    var signal = _getBySelfOrCallback(selfOrCallback);
                    if (signal)
                        signal.disconnect();
                    return signal;
                }
            }, 
            "connectWebView" : 
            {
                value : function(name, callback)
                {
                    var wv;
                    tabs.forEach(function(w) {
                        w.connect(name, function() { callback.apply(w, arguments); });
                    });
                    Signal.connect("createTab", function(wv) {
                        wv.connect(name, function() { callback.apply(wv, arguments);});
                    });
                }
            }, 
            "emit" :
            {
                value : function(signal, args)
                {
                    var id, current;
                    var ret = false;
                    var connected = _byName[signal];
                    for (id in connected)
                    {
                        current = connected[id];
                        if (!current.predicate || current.predicate.apply(current, args)) {
                            ret = current.callback.apply(current, args) || ret;
                        }
                    }
                    return ret;
                }
            }, 
            "disconnectAll" : 
            {
                value : function(callback) 
                {
                    var signals = [];
                    var signal; 
                    while((signal = _getBySelfOrCallback(callback)))
                    {
                        if (signal.connected)
                        {
                            signals.push(signal);
                            signal.disconnect();
                        }
                    }
                    return signals;

                }
            }, 
            "connectAll" : 
            {
                value : function(signalOrArray, callback)
                {
                    var i, l;
                    if (signalOrArray instanceof Signal)
                        signalOrArray.connect(callback);
                    else 
                    {
                        for (i=signalOrArray.length-1; i>=0; i--)
                            signalOrArray[i].connect(callback);
                    }
                }
            }
    });
})();
