/**
  A require.s2 module implementing a basic publish-subscriber
  manager.

  Example usage:

  http://fossil.wanderinghorse.net/repos/cwal/index.cgi/finfo?name=s2/toys/r-pubsub.s2

*/
const ps /* need a temporary symbol for the new() method to keep as our prototype */ = {
    __typename: 'PubSub',

    /**
       Subscribes a callback to events published for a given key.
       key may be of any value type. func must be-a Function.
       
       Returns a unique-per-subscription value (of an unspecified
       type) which can be passed to unsub() to opt out of a
       subscription or ignored (for a permanent subscription,
       automatically renewed periodically at no extra cost).
    */
    sub:proc callee(key, func){
        affirm func inherits callee.prototype /* is-a Function */;
        (this.$map[key] ||| (this.$map[key]={}))[++this.$id] = func;
        return [key, this.$id];
    },

    /**
       Expects id to be a value returned by this.sub() and
       unsubscribes a subscriber registered with that id.

       Returns true if it finds/removes a subscription, else
       false.
    */
    unsub: proc(id){
        return id && this.$map.hasOwnProperty(id.0)
            ? this.$map[id.0].unset(id.1)
            : false;
    },

    /**
       Publishes an event to all subscribers of the given key (if
       any), calling them:

       a) in an UNSPECIFIED and (very) POSSIBLY CHANGING order.

       b) passing on any arguments given after the event key to each
       subscriber callback function.

       Returns this object (for lack of a better option).

       It propagates any exceptions thrown by a subscriber.

       Pedantic side-node: each subscriber gets its own copy of the
       arguments array, so it's safe to change it or keep a reference
       to it in the subscribers without affecting others.
    */
    pub:proc(key){
        (this.$map[key]|||return this).
            eachProperty(((const args=argv), args.shift(), shoot));
        return this;
    }.importSymbols({shoot: proc(/*key,val*/){argv.1.apply(argv.1,args.slice())}}
                    /*need a copy to avoid subscriber side-effects^^^^^^^^^^^^*/),

    /**
       Removes all subscriptions for the given key or
       (if no arguments are passed) all subscriptions
       for all keys.
       
       Returns this object (for lack of a better option).
    */
    unsubAll:proc(key){
        argv.length()
            ? this.$map.unset(key)
            : this.$map.clearProperties();
        return this;
    }
};
return (
/**
   Factory function for new instances of this class:
       
   var pubber = thisObj.new();
   assert pubber inherits thisObj; // this will hold

   Each instance maintains its own, independent list of
   subscriptions.

   This function always uses the "original" prototype as the
   return value's prototype, not the current 'this' of
   the call to new(), though it is callable via arbitrary
   instances.
*/
ps.new = proc(){
    return { prototype: that, $map:{}, $id:0 };
}.importSymbols({that: ps})
)();
