const MszWatcher = function() {
    let watchers = [];

    return {
        watch: function(watcher, thisArg, args) {
            if(typeof watcher !== 'function')
                throw 'watcher must be a function';
            if(watchers.indexOf(watcher) >= 0)
                return;

            watchers.push(watcher);

            if(thisArg !== undefined) {
                if(!Array.isArray(args)) {
                    if(args !== undefined)
                        args = [args];
                    else args = [];
                }

                // initial call
                args.push(true);

                watcher.apply(thisArg, args);
            }
        },
        unwatch: function(watcher) {
            $ari(watchers, watcher);
        },
        call: function(thisArg, args) {
            if(!Array.isArray(args)) {
                if(args !== undefined)
                    args = [args];
                else args = [];
            }

            args.push(false);

            for(const watcher of watchers)
                watcher.apply(thisArg, args);
        },
    };
};

const MszWatcherCollection = function() {
    const collection = new Map;

    const watch = function(name, watcher, thisArg, args) {
        const watchers = collection.get(name);
        if(watchers === undefined)
            throw 'undefined watcher name';
        watchers.watch(watcher, thisArg, args);
    };

    const unwatch = function(name, watcher) {
        const watchers = collection.get(name);
        if(watchers === undefined)
            throw 'undefined watcher name';
        watchers.unwatch(watcher);
    };

    return {
        define: function(names) {
            if(!Array.isArray(names))
                names = [names];
            for(const name of names)
                collection.set(name, new MszWatcher);
        },
        call: function(name, thisArg, args) {
            const watchers = collection.get(name);
            if(watchers === undefined)
                throw 'undefined watcher name';
            watchers.call(thisArg, args);
        },
        watch: watch,
        unwatch: unwatch,
        proxy: function(obj) {
            obj.watch = function(name, watcher) {
                watch(name, watcher);
            };
            obj.unwatch = unwatch;
        },
    };
};
