Source: scene/SceneManager.js

/**
 * Author: thegoldenmule
 * Date: 8/11/13
 */

(function (global) {
    "use strict";

    var _root = null,
        _objectsById = {};

    /**
     * @class SceneManager
     * @desc Manages all objects in the scene and provides an interface for
     * querying.
     * @returns {SceneManager}
     * @author thegoldenmule
     * @constructor
     */
    global.SceneManager = {
        /**
         * @desc Initializes the SceneManager with the root DisplayObject.
         * @param {DisplayObject} displayObject The root DisplayObject.
         *
         * @private
         */
        __initialize: function(displayObject) {
            _root = displayObject;
        },

        /**
         * @desc Adds a DisplayObject to the SceneManager for management.
         * @param {DisplayObject} displayObject The DisplayObject instance to
         * add.
         *
         * @private
         */
        __addDisplayObject: function(displayObject) {
            _objectsById[displayObject.getUniqueId()] = displayObject;
        },

        /**
         * @desc Removes a DisplayObject from the SceneManager.
         * @param {DisplayObject} displayObject The DisplayObject instance to
         * remove.
         *
         * @private
         */
        __removeDisplayObject: function(displayObject) {
            delete _objectsById[displayObject.getUniqueId()];
        },

        /**
         * @desc Retrieves a DisplayObject from the scene by id.
         * @param {number} id The integral id of the DisplayObject to retrieve.
         * @return {DisplayObject}
         */
        getById: function(id) {
            return id in _objectsById ? _objectsById[id] : null;
        },

        /**
         * @desc Basic query method to find collections of objects.
         * @param {String} query A query is made up of DisplayObject names and
         * searches over either immediate children or descendants at arbitrary
         * depth. A search over immediate children is given by "." while a
         * recursive search is given by "..". An example follows.
         * @param {DisplayObject} context The DisplayObject to start the search from.
         * If no context is provided, the search starts at the top of the graph,
         * i.e. the scene root.
         *
         * @example
         * SceneManager.find("a.b..(@visible==true).(@name==z)");
         * SceneManager.find("a.b..(@visible==true)..(@name==z)");
         * SceneManager.find("..(@visible==true)..(@name==z)", q);
         * SceneManager.find("..(@test==true)");
         */
        find: function(query, context) {
            if (!_root) {
                throw new Error("Must initialize SceneManager before calling find.");
            }

            if (!query){
                return [];
            }
             // grab current context
            var current = !context ? [_root] : [context];

            // define vars here
            var i, j, k, iLength, jLength, kLength;
            var sceneQuery;
            var results;

            // split at recursive queries
            var recur = false;
            var recursiveQueries = query.split("..");
            for (i = 0, iLength = recursiveQueries.length; i < iLength; i++) {
                var recursiveQuery = recursiveQueries[i];

                // split into shallow queries
                var shallowQueries = recursiveQuery.split(".");

                // ? I don't understand why split works this way.
                if ("" === shallowQueries[0]) {
                    shallowQueries.shift();
                }

                // recursive query
                if (recur) {
                    var recursiveQueryString = shallowQueries.shift();

                    // create query
                    sceneQuery = new SceneQuery(recursiveQueryString);

                    // execute query on each of the current nodes
                    results = [];
                    for (k = 0, kLength = current.length; k < kLength; k++) {
                        executeQueryRecursive(current[k], sceneQuery, results);
                    }

                    if (0 !== results.length) {
                        current = results;
                    } else {
                        return [];
                    }
                }

                // perform shallow searches
                for (j = 0, jLength = shallowQueries.length; j < jLength; j++) {
                    var shallowQueryString = shallowQueries[j];

                    // create query
                    sceneQuery = new SceneQuery(shallowQueryString);

                    // execute query on each of the current nodes
                    results = [];
                    for (k = 0, kLength = current.length; k < kLength; k++) {
                        executeQuery(current[k], sceneQuery, results);
                    }

                    if (0 !== results.length) {
                        current = results;
                    } else {
                        return [];
                    }
                }

                recur = 0 === recursiveQuery.length || 0 === i % 2;
            }

            return current;
        }
    };

    function executeQueryRecursive(node, query, results) {
        var newChild;
        var children = node.getChildren();
        for (var i = 0, len = children.length; i < len; i++) {
            newChild = children[i];
            if (query.execute(newChild)) {
                results.push(newChild);
            }

            executeQueryRecursive(newChild, query, results);
        }
    }

    function executeQuery(node, query, results) {
        var children = node.getChildren();
        for (var i = 0, len = children.length; i < len; i++) {
            var newChild = children[i];
            if (query.execute(newChild)) {
                results.push(newChild);
            }
        }
    }

})(this);