/* global using, module, require, window */

(function MO5SetBootstrap () {

    if (typeof using === "function") {
        using("MO5.CoreObject", "MO5.types").
        define("MO5.Set", MO5SetModule);
    }
    else if (typeof window !== "undefined") {
        window.MO5.Set = MO5SetModule(MO5.CoreObject, MO5.types);
    }
    else {
        module.exports = MO5SetModule(require("./CoreObject.js"), require("./types.js"));
    }

    function MO5SetModule (CoreObject, types) {

        var KEY_PREFIX = "MO5Set_";

        /**
         * A set implementation that is similar to the ES6 Set, but knows about CoreObjects.
         *
         * @type void|[any] -> Set
         * @param items Optional array or other .forEach()-implementing object with items to add.
         */
        function Set (items) {

            CoreObject.call(this);

            // We need to hold onto CoreObject listeners to unsubscribe them
            // when the item gets deleted from the set:
            this._unsubscribers = {};

            // An object where the keys are "hashes" for simple values or CoreObject IDs:
            this._stringItems = {};

            // An array that holds non-"hashable" items, that is arrays and non-CoreObject objects:
            this._items = [];

            if (items && types.hasForEach(items)) {
                this.addMany(items);
            }
        }

        Set.fromRange = function (first, last) {

            var set = new Set(), i;

            for (i = first; i <= last; i += 1) {
                set.add(i);
            }

            return set;
        };

        Set.prototype = new CoreObject();

        /**
         * Checks whether an item is contained in the set.
         *
         * @type any -> boolean
         * @param item The item to check.
         * @return Is the item contained in the set?
         */
        Set.prototype.has = function (item) {

            var i, length;

            if (canBeConvertedToKey(item)) {
                return (toKey(item) in  this._stringItems);
            }
            else {

                for (i = 0, length = this._items.length; i < length; i += 1) {
                    if (this._items[i] === item) {
                        return true;
                    }
                }

                return false;
            }
        };

        /**
         * Adds an item to the set.
         *
         * @type any -> Set
         * @param item The item to add.
         * @return The Set object.
         */
        Set.prototype.add = function (item) {

            var key;

            if (this.has(item)) {
                return this;
            }

            if (canBeConvertedToKey(item)) {

                key = toKey(item);
                this._stringItems[key] = item;

                if (CoreObject.isCoreObject(item)) {
                    this._unsubscribers[key] = this.delete.bind(this, item);
                    item.subscribe("destroyed", this._unsubscribers[key]);
                }
            }
            else {
                this._items.push(item);
            }

            return this;
        };

        /**
         * Removes an item from the set.
         *
         * @type any -> boolean
         * @param item The item to remove.
         * @return Has the item been deleted?
         */
        Set.prototype.delete = function (item) {

            var key;

            if (!this.has(item)) {
                return false;
            }

            if (canBeConvertedToKey(item)) {

                key = toKey(item);

                delete this._stringItems[key];

                if (CoreObject.isCoreObject(item)) {
                    item.unsubscribe("destroyed", this._unsubscribers[key]);
                    delete this._unsubscribers[key];
                }
            }
            else {
                this._items.splice(this._items.indexOf(item), 1);
            }

            return true;
        };

        /**
         * Removes all items from the set.
         *
         * @type void -> undefined
         */
        Set.prototype.clear = function () {
            this._items.forEach(this.delete.bind(this));
            this._items = [];
            this._stringItems = {};
            this._unsubscribers = {};
        };

        /**
         * Calls a callback for each of the items in the set with the following arguments:
         * 1) the item
         * 2) the index - this should be in the order in which the items have been added
         * 3) the set itself
         *
         * @type function -> undefined
         * @param fn A callback function.
         */
        Set.prototype.forEach = function (fn) {

            var key, i = 0;

            for (key in this._stringItems) {
                fn(this._stringItems[key], i, this);
                i += 1;
            }

            this._items.forEach(function (item) {
                fn(item, i, this);
                i += 1;
            }.bind(this));
        };

        /**
         * Returns all items in the set as an array.
         *
         * @type void -> [any]
         */
        Set.prototype.values = function () {

            var values = [];

            this.forEach(function (item) {
                values.push(item);
            });

            return values;
        };

        Set.prototype.keys = Set.prototype.values;

        /**
         * Adds many items to the set at once.
         *
         * @type [any] -> Set
         * @param items An array or other iterable object with a .forEach() method.
         * @return The Set object.
         */
        Set.prototype.addMany = function (items) {
            items.forEach(this.add.bind(this));
            return this;
        };

        Set.prototype.intersection = function (otherSet) {

            var result = new Set();

            otherSet.forEach(function (item) {
                if (this.has(item)) {
                    result.add(item);
                }
            }.bind(this));

            return result;
        };

        Set.prototype.difference = function (otherSet) {

            var result = new Set(this.values());

            otherSet.forEach(function (item) {
                if (result.has(item)) {
                    result.delete(item);
                }
                else {
                    result.add(item);
                }
            });

            return result;
        };

        Set.prototype.size = function () {

            var length = this._items.length, key;

            for (key in this._stringItems) {
                length += 1;
            }

            return length;
        };

        return Set;

        /**
         * Checks whether an item can be converted to string in some form to be used as a key.
         *
         * @type any -> boolean
         * @param item The item to check.
         * @return Can the item be converted to key?
         */
        function canBeConvertedToKey (item) {

            if (types.isObject(item)) {

                if (types.isNumber(item.id)) {
                    return true;
                }

                return false;
            }

            return true;
        }

        /**
         * Converts an item to a key that can be used as a primitive "hash" to identifiy
         * the object inside the set.
         *
         * @type any -> string
         * @param item The item to convert to key.
         * @return The key as a string.
         */
        function toKey (item) {

            if (types.isObject(item)) {
                return KEY_PREFIX + "MO5CoreObject_" + item.id;
            }

            return KEY_PREFIX + "SimpleValue_" + JSON.stringify(item);
        }

    }

}());

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