/* global using, MO5, setTimeout, console, window, module */

(function MO5EventBusBootstrap () {

    if (typeof using === "function") {
        using().define("MO5.EventBus", MO5EventBusModule);
    }
    else if (typeof window !== "undefined") {
        window.MO5 = MO5 || {};
        window.MO5.EventBus = MO5EventBusModule();
    }
    else {
        module.exports = MO5EventBusModule();
    }

    function MO5EventBusModule () {

        "use strict";

        function EventBus (args) {

            var self = this;

            args = args || {};

            this.debug = args.debug || false;
            this.interceptErrors = args.interceptErrors || false;
            this.log = args.log || false;
            this.logData = args.logData || false;
            this.defaults = args.defaults || {};
            this.defaults.flowType = this.defaults.flowType || EventBus.FLOW_TYPE_ASYNCHRONOUS;

            this.callbacks = {
                "*": []
            };

            this.subscribe(errorListener, "EventBus.error");

            function errorListener (data) {

                var name;

                if (self.debug !== true) {
                    return;
                }

                name = data.error.name || "Error";
                console.log(name + " in listener; Event: " + data.info.event + "; Message: " +
                    data.error.message);
            }
        }

        EventBus.FLOW_TYPE_ASYNCHRONOUS = 0;
        EventBus.FLOW_TYPE_SYNCHRONOUS = 1;

        EventBus.create = function(args) {

            args = args || {};

            return new EventBus(args);
        };

        EventBus.prototype.subscribe = function(parameter1, parameter2) {

            var listener, event, self = this;

            if (parameter2 === undefined) {
                event = "*";
                listener = parameter1;
            }
            else if (typeof parameter1 === "string" || typeof parameter1 === "number") {
                event = parameter1;
                listener = parameter2;
            }
            else if (typeof parameter2 === "string" || typeof parameter2 === "number") {
                event = parameter2;
                listener = parameter1;
            }

            if (typeof event !== "string" && typeof event !== "number") {
                throw new Error("Event names can only be strings or numbers! event: ", event);
            }

            if (typeof listener !== "function") {
                throw new Error("Only functions may be used as listeners!");
            }

            event = event || '*';

            this.callbacks[event] = this.callbacks[event] || [];
            this.callbacks[event].push(listener);

            this.trigger(
                "EventBus.subscribe", 
                {
                    listener: listener,
                    event: event,
                    bus: this
                }
            );

            return function unsubscriber () {
                self.unsubscribe(listener, event);
            };
        };

        EventBus.prototype.unsubscribe = function(parameter1, parameter2) {

            var cbs, len, i, listener, event;

            if (parameter2 === undefined) {
                event = "*";
                listener = parameter1;
            }
            else if (typeof parameter1 === "string" || typeof parameter1 === "number") {
                event = parameter1;
                listener = parameter2;
            }
            else if (typeof parameter2 === "string" || typeof parameter2 === "number") {
                event = parameter2;
                listener = parameter1;
            }

            if (typeof event !== "string" && typeof event !== "number") {
                throw new Error("Event names can only be strings or numbers! event: ", event);
            }

            if (typeof listener !== "function") {
                throw new Error("Only functions may be used as listeners!");
            }

            event = event || '*';
            cbs = this.callbacks[event] || [];
            len = cbs.length;

            for (i = 0; i < len; ++i) {
                if (cbs[i] === listener) {
                    this.callbacks[event].splice(i, 1);
                }
            }

            this.trigger(
                "EventBus.unsubscribe", 
                {
                    listener: listener,
                    event: event,
                    bus: this
                }
            );
        };

        EventBus.prototype.once = function (listenerOrEvent1, listenerOrEvent2) {

            var fn, self = this, event, listener;
            var firstParamIsFunction, secondParamIsFunction, called = false;

            firstParamIsFunction = typeof listenerOrEvent1 === "function";
            secondParamIsFunction = typeof listenerOrEvent2 === "function";

            if ((firstParamIsFunction && secondParamIsFunction) || 
                    (!firstParamIsFunction && !secondParamIsFunction)) {
                throw new Error("Parameter mismatch; one parameter needs to be a function, " +
                    "the other one must be a string.");
            }

            if (firstParamIsFunction) {
                listener = listenerOrEvent1;
                event = listenerOrEvent2;
            }
            else {
                listener = listenerOrEvent2;
                event = listenerOrEvent1;
            }

            event = event || "*";

            fn = function (data, info) {

                if (called) {
                    return;
                }

                called = true;
                self.unsubscribe(fn, event);
                listener(data, info);
            };

            this.subscribe(fn, event);
        };

        EventBus.prototype.trigger = function(event, data, async) {

            var cbs, len, info, j, f, cur, self, flowType;

            if (
                typeof event !== "undefined" &&
                typeof event !== "string" &&
                typeof event !== "number"
            ) {
                throw new Error("Event names can only be strings or numbers! event: ", event);
            }

            self = this;
            event = arguments.length ? event : "*";

            flowType = (typeof async !== "undefined" && async === false) ?
                EventBus.FLOW_TYPE_SYNCHRONOUS :
                this.defaults.flowType;

            // get subscribers in all relevant namespaces
            cbs = (function() {

                var n, words, wc, matches, k, kc, old = "", out = [];

                // split event name into namespaces and get all subscribers
                words = event.split(".");

                for (n = 0, wc = words.length ; n < wc ; ++n) {

                    old = old + (n > 0 ? "." : "") + words[n];
                    matches = self.callbacks[old] || [];

                    for (k = 0, kc = matches.length; k < kc; ++k) {
                        out.push(matches[k]);
                    }
                }

                if (event === "*") {
                    return out;
                }

                // get subscribers for "*" and add them, too
                matches = self.callbacks["*"] || [];

                for (k = 0, kc = matches.length ; k < kc ; ++k) {
                    out.push( matches[ k ] );
                }

                return out;
            }());

            len = cbs.length;

            info = {
                event: event,
                subscribers: len,
                async: flowType === EventBus.FLOW_TYPE_ASYNCHRONOUS ? true : false,
                getQueueLength: function() {

                    if (len === 0) {
                        return 0;
                    }

                    return len - (j + 1);
                }
            };

            function asyncThrow (e) {
                setTimeout(
                    function () {
                        throw e;
                    },
                    0
                );
            }

            // function for iterating through the list of relevant listeners
            f = function() {

                if (self.log === true) {
                    console.log( 
                        "EventBus event triggered: " + event + "; Subscribers: " + len, 
                        self.logData === true ? "; Data: " + data : "" 
                    );
                }

                for (j = 0; j < len; ++j) {

                    cur = cbs[j];

                    try {
                        cur(data, info);
                    }
                    catch (e) {

                        console.log(e);

                        self.trigger(
                            "EventBus.error", 
                            {
                                error: e,
                                info: info
                            }
                        );

                        if (self.interceptErrors !== true) {
                            asyncThrow(e);
                        }
                    }
                }
            };

            if (flowType === EventBus.FLOW_TYPE_ASYNCHRONOUS) {
                setTimeout(f, 0);
            }
            else {
                f();
            }
        };

        EventBus.prototype.triggerSync = function (event, data) {
            return this.trigger(event, data, false);
        };

        EventBus.prototype.triggerAsync = function (event, data) {
            return this.trigger(event, data, true);
        };

        EventBus.inject = function (obj, args) {

            args = args || {};

            var squid = new EventBus(args);

            obj.subscribe = function (listener, event) {
                squid.subscribe(listener, event);
            };

            obj.unsubscribe = function (listener, event) {
                squid.unsubscribe(listener, event);
            };

            obj.once = function (listener, event) {
                squid.once(listener, event);
            };

            obj.trigger = function (event, data, async) {
                async = (typeof async !== "undefined" && async === false) ? false : true;
                squid.trigger(event, data, async);
            };

            obj.triggerSync = squid.triggerSync.bind(squid);
            obj.triggerAsync = squid.triggerAsync.bind(squid);

            obj.subscribe("destroyed", function () {
                squid.callbacks = [];
            });
        };

        return EventBus;

    }
}());

Documents

docs/reference/elements/nametemplate.md
docs/reference/elements/stop.md
docs/development.md
docs/documentation.md
docs/downloads.md
docs/examples.md
docs/games.md
docs/index.md
docs/reference/elements/alert.md
docs/reference/elements/animation.md
docs/reference/elements/assets.md
docs/reference/elements/audio.md
docs/reference/elements/background.md
docs/reference/elements/break.md
docs/reference/elements/character.md
docs/reference/elements/choice.md
docs/reference/elements/clear.md
docs/reference/elements/composite.md
docs/reference/elements/conditionals.md
docs/reference/elements/confirm.md
docs/reference/elements/curtain.md
docs/reference/elements/displayname.md
docs/reference/elements/do.md
docs/reference/elements/easing_attribute.md
docs/reference/elements/else.md
docs/reference/elements/flash.md
docs/reference/elements/flicker.md
docs/reference/elements/fn.md
docs/reference/elements/global.md
docs/reference/elements/globalize.md
docs/reference/elements/goto.md
docs/reference/elements/group.md
docs/reference/elements/hide.md
docs/reference/elements/image.md
docs/reference/elements/imagepack.md
docs/reference/elements/line.md
docs/reference/elements/localize.md
docs/reference/elements/move.md
docs/community.md
docs/reference/elements/option.md
docs/reference/elements/pause.md
docs/reference/elements/play.md
docs/reference/elements/prompt.md
docs/reference/elements/restart.md
docs/reference/elements/scene.md
docs/reference/elements/scenes.md
docs/reference/elements/set.md
docs/reference/elements/set_vars.md
docs/reference/elements/settings.md
docs/reference/elements/shake.md
docs/reference/elements/show.md
docs/reference/elements/source.md
docs/reference/elements/stage.md
docs/reference/elements/start.md
docs/beginners-guide.md
docs/reference/elements/sub.md
docs/reference/elements/tag.md
docs/reference/elements/textbox.md
docs/reference/elements/track.md
docs/reference/elements/transform.md
docs/reference/elements/trigger.md
docs/reference/elements/trigger_command.md
docs/reference/elements/triggers.md
docs/reference/elements/var.md
docs/reference/elements/wait.md
docs/reference/elements/when.md
docs/reference/elements/while.md
docs/reference/elements/with.md
docs/reference/elements/ws.md
docs/reference/elements.md
docs/reference/language.md
docs/reference/structure.md
docs/reference/syntax.md
docs/web-servers.md
libs/MO5/README.md
libs/MO5/libs/using.js/README.md
libs/MO5/js/EventBus.js
libs/MO5/js/Animation.js
libs/MO5/js/CoreObject.js
libs/MO5/js/Exception.js
libs/MO5/js/List.js
libs/MO5/js/MO5.js
libs/MO5/js/Map.js
libs/MO5/js/Point.js
libs/MO5/js/Promise.js
libs/MO5/js/Queue.js
libs/MO5/js/Result.js
libs/MO5/js/Set.js
libs/MO5/js/Size.js
libs/MO5/js/Timer.js
libs/MO5/js/TimerWatcher.js
libs/MO5/js/ajax.js
libs/MO5/js/assert.js
libs/MO5/js/dom.Element.js
libs/MO5/js/dom.effects.typewriter.js
libs/MO5/js/dom.escape.js
libs/MO5/js/easing.js
libs/MO5/js/fail.js
libs/MO5/js/globals.document.js
libs/MO5/js/globals.window.js
libs/MO5/js/range.js
libs/MO5/js/tools.js
libs/MO5/js/transform.js
libs/MO5/js/types.js
libs/MO5/libs/using.js/tests/index.js
libs/MO5/libs/using.js/tests/module1.js
libs/MO5/libs/using.js/tests/module2.js
libs/MO5/libs/using.js/tests/module3.js
libs/MO5/libs/using.js/using.js
libs/MO5/tests/node/EventBus.test.js
libs/MO5/tests/node/Set.test.js
src/savegames.js
src/tools/reveal.js
src/tools/ui.js
src/loader.js
src/tools/tools.js
src/tools/compile.js
src/functions.js
src/DisplayObject.js
src/Game.js
src/Interpreter.js
src/Keys.js
src/LoadingScreen.js
src/Trigger.js
src/assets/Audio.js
src/assets/Background.js
src/assets/Character.js
src/assets/Composite.js
src/assets/Curtain.js
src/assets/Imagepack.js
src/assets/Textbox.js
src/assets.js
src/bus.js
src/commands/alert.js
src/commands/break.js
src/commands/choice.js
src/commands/confirm.js
src/commands/do.js
src/commands/fn.js
src/commands/global.js
src/commands/globalize.js
src/commands/goto.js
src/commands/line.js
src/commands/localize.js
src/commands/prompt.js
src/commands/restart.js
src/commands/set_vars.js
src/commands/sub.js
src/commands/trigger.js
src/commands/var.js
src/commands/wait.js
src/commands/while.js
src/commands/with.js
src/commands.js
src/dataSources/LocalStorage.js
src/dataSources.js
src/engine.js
src/extensions/button.js
src/extensions/colored-rectangle.js
src/extensions/get-backtrace.js
src/extensions/hello.js
src/extensions/side-images.js
CHANGELOG.md
LICENSE.md
README.md
build.js
index.js
index.md
package.js