package net.flashpunk
{
    import flash.geom.Point;
    import flash.utils.Dictionary;
    import net.flashpunk.utils.Input;
    
    /**
     * Updated by Engine, main game container that holds all currently active Entities.
     * Useful for organization, eg. "Menu", "Level1", etc.
     */
    public class World extends Tweener
    {
        /**
         * If the render() loop is performed.
         */
        public var visible:Boolean = true;
        
        /**
         * Point used to determine drawing offset in the render loop.
         */
        public var camera:Point = new Point;
        
        /**
         * Constructor.
         */
        public function World() 
        {
            
        }
        
        /**
         * Override this; called when World is switch to, and set to the currently active world.
         */
        public function begin():void
        {
            
        }
        
        /**
         * Override this; called when World is changed, and the active world is no longer this.
         */
        public function end():void
        {
            
        }
        
        /**
         * Performed by the game loop, updates all contained Entities.
         * If you override this to give your World update code, remember
         * to call super.update() or your Entities will not be updated.
         */
        override public function update():void 
        {
            // update the entities
            var e:Entity = _updateFirst;
            while (e)
            {
                if (e.active)
                {
                    if (e._tween) e.updateTweens();
                    e.update();
                }
                if (e._graphic && e._graphic.active) e._graphic.update();
                e = e._updateNext;
            }
        }
        
        /**
         * Performed by the game loop, renders all contained Entities.
         * If you override this to give your World render code, remember
         * to call super.render() or your Entities will not be rendered.
         */
        public function render():void 
        {
            // render the entities in order of depth
            var e:Entity,
                i:int = _layerList.length;
            while (i --)
            {
                e = _renderLast[_layerList[i]];
                while (e)
                {
                    if (e.visible) e.render();
                    e = e._renderPrev;
                }
            }
        }
        
        /**
         * X position of the mouse in the World.
         */
        public function get mouseX():int
        {
            return FP.screen.mouseX + FP.camera.x;
        }
        
        /**
         * Y position of the mouse in the world.
         */
        public function get mouseY():int
        {
            return FP.screen.mouseY + FP.camera.y;
        }
        
        /**
         * Adds the Entity to the World at the end of the frame.
         * @param    e        Entity object you want to add.
         * @return    The added Entity object.
         */
        public function add(e:Entity):Entity
        {
            if (e._world) return e;
            _add[_add.length] = e;
            e._world = this;
            return e;
        }
        
        /**
         * Removes the Entity from the World at the end of the frame.
         * @param    e        Entity object you want to remove.
         * @return    The removed Entity object.
         */
        public function remove(e:Entity):Entity
        {
            if (e._world !== this) return e;
            _remove[_remove.length] = e;
            e._world = null;
            return e;
        }
        
        /**
         * Removes all Entities from the World at the end of the frame.
         */
        public function removeAll():void
        {
            var e:Entity = _updateFirst;
            while (e)
            {
                _remove[_remove.length] = e;
                e._world = null;
                e = e._updateNext;
            }
        }
        
        /**
         * Adds multiple Entities to the world.
         * @param    ...list        Several Entities (as arguments) or an Array/Vector of Entities.
         */
        public function addList(...list):void
        {
            var e:Entity;
            if (list[0] is Array || list[0] is Vector.<*>)
            {
                for each (e in list[0]) add(e);
                return;
            }
            for each (e in list) add(e);
        }
        
        /**
         * Removes multiple Entities from the world.
         * @param    ...list        Several Entities (as arguments) or an Array/Vector of Entities.
         */
        public function removeList(...list):void
        {
            var e:Entity;
            if (list[0] is Array || list[0] is Vector.<*>)
            {
                for each (e in list[0]) remove(e);
                return;
            }
            for each (e in list) remove(e);
        }
        
        /**
         * Adds an Entity to the World with the Graphic object.
         * @param    graphic        Graphic to assign the Entity.
         * @param    x            X position of the Entity.
         * @param    y            Y position of the Entity.
         * @param    layer        Layer of the Entity.
         * @return    The Entity that was added.
         */
        public function addGraphic(graphic:Graphic, layer:int = 0, x:int = 0, y:int = 0):Entity
        {
            var e:Entity = new Entity(x, y, graphic);
            if (layer != 0) e.layer = layer;
            e.active = false;
            return add(e);
        }
        
        /**
         * Adds an Entity to the World with the Mask object.
         * @param    mask    Mask to assign the Entity.
         * @param    type    Collision type of the Entity.
         * @param    x        X position of the Entity.
         * @param    y        Y position of the Entity.
         * @return    The Entity that was added.
         */
        public function addMask(mask:Mask, type:String, x:int = 0, y:int = 0):Entity
        {
            var e:Entity = new Entity(x, y, null, mask);
            if (type) e.type = type;
            e.active = e.visible = false;
            return add(e);
        }
        
        /**
         * Returns a new Entity, or a stored recycled Entity if one exists.
         * @param    classType        The Class of the Entity you want to add.
         * @param    addToWorld        Add it to the World immediately.
         * @return    The new Entity object.
         */
        public function create(classType:Class, addToWorld:Boolean = true):Entity
        {
            var e:Entity = _recycled[classType];
            if (e)
            {
                _recycled[classType] = e._recycleNext;
                e._recycleNext = null;
            }
            else e = new classType;
            if (addToWorld) return add(e);
            return e;
        }
        
        /**
         * Removes the Entity from the World at the end of the frame and recycles it.
         * The recycled Entity can then be fetched again by calling the create() function.
         * @param    e        The Entity to recycle.
         * @return    The recycled Entity.
         */
        public function recycle(e:Entity):Entity
        {
            if (e._world !== this) return e;
            e._recycleNext = _recycled[e._class];
            _recycled[e._class] = e;
            return remove(e);
        }
        
        /**
         * Clears stored reycled Entities of the Class type.
         * @param    classType        The Class type to clear.
         */
        public function clearRecycled(classType:Class):void
        {
            var e:Entity = _recycled[classType],
                n:Entity;
            while (e)
            {
                n = e._recycleNext;
                e._recycleNext = null;
                e = n;
            }
            delete _recycled[classType];
        }
        
        /**
         * Clears stored recycled Entities of all Class types.
         */
        public function clearRecycledAll():void
        {
            for (var classType:Object in _recycled) clearRecycled(classType as Class);
        }
        
        /**
         * Brings the Entity to the front of its contained layer.
         * @param    e        The Entity to shift.
         * @return    If the Entity changed position.
         */
        public function bringToFront(e:Entity):Boolean
        {
            if (e._world !== this || !e._renderPrev) return false;
            // pull from list
            e._renderPrev._renderNext = e._renderNext;
            if (e._renderNext) e._renderNext._renderPrev = e._renderPrev;
            else _renderLast[e._layer] = e._renderPrev;
            // place at the start
            e._renderNext = _renderFirst[e._layer];
            e._renderNext._renderPrev = e;
            _renderFirst[e._layer] = e;
            e._renderPrev = null;
            return true;
        }
        
        /**
         * Sends the Entity to the back of its contained layer.
         * @param    e        The Entity to shift.
         * @return    If the Entity changed position.
         */
        public function sendToBack(e:Entity):Boolean
        {
            if (e._world !== this || !e._renderNext) return false;
            // pull from list
            e._renderNext._renderPrev = e._renderPrev;
            if (e._renderPrev) e._renderPrev._renderNext = e._renderNext;
            else _renderFirst[e._layer] = e._renderNext;
            // place at the end
            e._renderPrev = _renderLast[e._layer];
            e._renderPrev._renderNext = e;
            _renderLast[e._layer] = e;
            e._renderNext = null;
            return true;
        }
        
        /**
         * Shifts the Entity one place towards the front of its contained layer.
         * @param    e        The Entity to shift.
         * @return    If the Entity changed position.
         */
        public function bringForward(e:Entity):Boolean
        {
            if (e._world !== this || !e._renderPrev) return false;
            // pull from list
            e._renderPrev._renderNext = e._renderNext;
            if (e._renderNext) e._renderNext._renderPrev = e._renderPrev;
            else _renderLast[e._layer] = e._renderPrev;
            // shift towards the front
            e._renderNext = e._renderPrev;
            e._renderPrev = e._renderPrev._renderPrev;
            e._renderNext._renderPrev = e;
            if (e._renderPrev) e._renderPrev._renderNext = e;
            else _renderFirst[e._layer] = e;
            return true;
        }
        
        /**
         * Shifts the Entity one place towards the back of its contained layer.
         * @param    e        The Entity to shift.
         * @return    If the Entity changed position.
         */
        public function sendBackward(e:Entity):Boolean
        {
            if (e._world !== this || !e._renderNext) return false;
            // pull from list
            e._renderNext._renderPrev = e._renderPrev;
            if (e._renderPrev) e._renderPrev._renderNext = e._renderNext;
            else _renderFirst[e._layer] = e._renderNext;
            // shift towards the back
            e._renderPrev = e._renderNext;
            e._renderNext = e._renderNext._renderNext;
            e._renderPrev._renderNext = e;
            if (e._renderNext) e._renderNext._renderPrev = e;
            else _renderLast[e._layer] = e;
            return true;
        }
        
        /**
         * If the Entity as at the front of its layer.
         * @param    e        The Entity to check.
         * @return    True or false.
         */
        public function isAtFront(e:Entity):Boolean
        {
            return e._renderPrev == null;
        }
        
        /**
         * If the Entity as at the back of its layer.
         * @param    e        The Entity to check.
         * @return    True or false.
         */
        public function isAtBack(e:Entity):Boolean
        {
            return e._renderNext == null;
        }
        
        /**
         * Returns the first Entity that collides with the rectangular area.
         * @param    type        The Entity type to check for.
         * @param    rX            X position of the rectangle.
         * @param    rY            Y position of the rectangle.
         * @param    rWidth        Width of the rectangle.
         * @param    rHeight        Height of the rectangle.
         * @return    The first Entity to collide, or null if none collide.
         */
        public function collideRect(type:String, rX:Number, rY:Number, rWidth:Number, rHeight:Number):Entity
        {
            var e:Entity = _typeFirst[type];
            while (e)
            {
                if (e.collideRect(e.x, e.y, rX, rY, rWidth, rHeight)) return e;
                e = e._typeNext;
            }
            return null;
        }

        /**
         * Returns the first Entity found that collides with the position.
         * @param    type        The Entity type to check for.
         * @param    pX            X position.
         * @param    pY            Y position.
         * @return    The collided Entity, or null if none collide.
         */
        public function collidePoint(type:String, pX:Number, pY:Number):Entity
        {
            var e:Entity = _typeFirst[type];
            while (e)
            {
                if (e.collidePoint(e.x, e.y, pX, pY)) return e;
                e = e._typeNext;
            }
            return null;
        }
        
        /**
         * Returns the first Entity found that collides with the line.
         * @param    type        The Entity type to check for.
         * @param    fromX        Start x of the line.
         * @param    fromY        Start y of the line.
         * @param    toX            End x of the line.
         * @param    toY            End y of the line.
         * @param    precision        
         * @param    p
         * @return
         */
        public function collideLine(type:String, fromX:int, fromY:int, toX:int, toY:int, precision:uint = 1, p:Point = null):Entity
        {
            // If the distance is less than precision, do the short sweep.
            if (precision < 1) precision = 1;
            if (FP.distance(fromX, fromY, toX, toY) < precision)
            {
                if (p)
                {
                    if (fromX == toX && fromY == toY)
                    {
                        p.x = toX; p.y = toY;
                        return collidePoint(type, toX, toY);
                    }
                    return collideLine(type, fromX, fromY, toX, toY, 1, p);
                }
                else return collidePoint(type, fromX, toY);
            }
            
            // Get information about the line we're about to raycast.
            var xDelta:int = Math.abs(toX - fromX),
                yDelta:int = Math.abs(toY - fromY),
                xSign:Number = toX > fromX ? precision : -precision,
                ySign:Number = toY > fromY ? precision : -precision,
                x:Number = fromX, y:Number = fromY, e:Entity;
            
            // Do a raycast from the start to the end point.
            if (xDelta > yDelta)
            {
                ySign *= yDelta / xDelta;
                if (xSign > 0)
                {
                    while (x < toX)
                    {
                        if ((e = collidePoint(type, x, y)))
                        {
                            if (!p) return e;
                            if (precision < 2)
                            {
                                p.x = x - xSign; p.y = y - ySign;
                                return e;
                            }
                            return collideLine(type, x - xSign, y - ySign, toX, toY, 1, p);
                        }
                        x += xSign; y += ySign;
                    }
                }
                else
                {
                    while (x > toX)
                    {
                        if ((e = collidePoint(type, x, y)))
                        {
                            if (!p) return e;
                            if (precision < 2)
                            {
                                p.x = x - xSign; p.y = y - ySign;
                                return e;
                            }
                            return collideLine(type, x - xSign, y - ySign, toX, toY, 1, p);
                        }
                        x += xSign; y += ySign;
                    }
                }
            }
            else
            {
                xSign *= xDelta / yDelta;
                if (ySign > 0)
                {
                    while (y < toY)
                    {
                        if ((e = collidePoint(type, x, y)))
                        {
                            if (!p) return e;
                            if (precision < 2)
                            {
                                p.x = x - xSign; p.y = y - ySign;
                                return e;
                            }
                            return collideLine(type, x - xSign, y - ySign, toX, toY, 1, p);
                        }
                        x += xSign; y += ySign;
                    }
                }
                else
                {
                    while (y > toY)
                    {
                        if ((e = collidePoint(type, x, y)))
                        {
                            if (!p) return e;
                            if (precision < 2)
                            {
                                p.x = x - xSign; p.y = y - ySign;
                                return e;
                            }
                            return collideLine(type, x - xSign, y - ySign, toX, toY, 1, p);
                        }
                        x += xSign; y += ySign;
                    }
                }
            }
            
            // Check the last position.
            if (precision > 1)
            {
                if (!p) return collidePoint(type, toX, toY);
                if (collidePoint(type, toX, toY)) return collideLine(type, x - xSign, y - ySign, toX, toY, 1, p);
            }
            
            // No collision, return the end point.
            if (p)
            {
                p.x = toX;
                p.y = toY;
            }
            return null;
        }
        
        /**
         * Populates an array with all Entities that collide with the rectangle. This
         * function does not empty the array, that responsibility is left to the user.
         * @param    type        The Entity type to check for.
         * @param    rX            X position of the rectangle.
         * @param    rY            Y position of the rectangle.
         * @param    rWidth        Width of the rectangle.
         * @param    rHeight        Height of the rectangle.
         * @param    into        The Array or Vector to populate with collided Entities.
         */
        public function collideRectInto(type:String, rX:Number, rY:Number, rWidth:Number, rHeight:Number, into:Object):void
        {
            if (into is Array || into is Vector.<*>)
            {
                var e:Entity = _typeFirst[type],
                    n:uint = into.length;
                while (e)
                {
                    if (e.collideRect(e.x, e.y, rX, rY, rWidth, rHeight)) into[n ++] = e;
                    e = e._typeNext;
                }
            }
        }
        
        /**
         * Populates an array with all Entities that collide with the position. This
         * function does not empty the array, that responsibility is left to the user.
         * @param    type        The Entity type to check for.
         * @param    pX            X position.
         * @param    pY            Y position.
         * @param    into        The Array or Vector to populate with collided Entities.
         * @return    The provided Array.
         */
        public function collidePointInto(type:String, pX:Number, pY:Number, into:Object):void
        {
            if (into is Array || into is Vector.<*>)
            {
                var e:Entity = _typeFirst[type],
                    n:uint = into.length;
                while (e)
                {
                    if (e.collidePoint(e.x, e.y, pX, pY)) into[n ++] = e;
                    e = e._typeNext;
                }
            }
        }
        
        /**
         * Finds the Entity nearest to the rectangle.
         * @param    type        The Entity type to check for.
         * @param    x            X position of the rectangle.
         * @param    y            Y position of the rectangle.
         * @param    width        Width of the rectangle.
         * @param    height        Height of the rectangle.
         * @return    The nearest Entity to the rectangle.
         */
        public function nearestToRect(type:String, x:Number, y:Number, width:Number, height:Number):Entity
        {
            var n:Entity = _typeFirst[type],
                nearDist:Number = Number.MAX_VALUE,
                near:Entity, dist:Number;
            while (n)
            {
                dist = squareRects(x, y, width, height, n.x - n.originX, n.y - n.originY, n.width, n.height);
                if (dist < nearDist)
                {
                    nearDist = dist;
                    near = n;
                }
                n = n._typeNext;
            }
            return near;
        }
        
        /**
         * Finds the Entity nearest to another.
         * @param    type        The Entity type to check for.
         * @param    e            The Entity to find the nearest to.
         * @param    useHitboxes    If the Entities' hitboxes should be used to determine the distance. If false, their x/y coordinates are used.
         * @return    The nearest Entity to e.
         */
        public function nearestToEntity(type:String, e:Entity, useHitboxes:Boolean = false):Entity
        {
            if (useHitboxes) return nearestToRect(type, e.x - e.originX, e.y - e.originY, e.width, e.height);
            var n:Entity = _typeFirst[type],
                nearDist:Number = Number.MAX_VALUE,
                near:Entity, dist:Number,
                x:Number = e.x - e.originX,
                y:Number = e.y - e.originY;
            while (n)
            {
                dist = (x - n.x) * (x - n.x) + (y - n.y) * (y - n.y);
                if (dist < nearDist)
                {
                    nearDist = dist;
                    near = n;
                }
                n = n._typeNext;
            }
            return near;
        }
        
        /**
         * Finds the Entity nearest to the position.
         * @param    type        The Entity type to check for.
         * @param    x            X position.
         * @param    y            Y position.
         * @param    useHitboxes    If the Entities' hitboxes should be used to determine the distance. If false, their x/y coordinates are used.
         * @return    The nearest Entity to the position.
         */
        public function nearestToPoint(type:String, x:Number, y:Number, useHitboxes:Boolean = false):Entity
        {
            var n:Entity = _typeFirst[type],
                nearDist:Number = Number.MAX_VALUE,
                near:Entity, dist:Number;
            if (useHitboxes)
            {
                while (n)
                {
                    dist = squarePointRect(x, y, n.x - n.originX, n.y - n.originY, n.width, n.height);
                    if (dist < nearDist)
                    {
                        nearDist = dist;
                        near = n;
                    }
                    n = n._typeNext;
                }
                return near;
            }
            while (n)
            {
                dist = (x - n.x) * (x - n.x) + (y - n.y) * (y - n.y);
                if (dist < nearDist)
                {
                    nearDist = dist;
                    near = n;
                }
                n = n._typeNext;
            }
            return near;
        }
        
        /**
         * How many Entities are in the World.
         */
        public function get count():uint { return _count; }
        
        /**
         * Returns the amount of Entities of the type are in the World.
         * @param    type        The type (or Class type) to count.
         * @return    How many Entities of type exist in the World.
         */
        public function typeCount(type:String):uint
        {
            return _typeCount[type] as uint;
        }
        
        /**
         * Returns the amount of Entities of the Class are in the World.
         * @param    c        The Class type to count.
         * @return    How many Entities of Class exist in the World.
         */
        public function classCount(c:Class):uint
        {
            return _classCount[c] as uint;
        }
        
        /**
         * Returns the amount of Entities are on the layer in the World.
         * @param    layer        The layer to count Entities on.
         * @return    How many Entities are on the layer.
         */
        public function layerCount(layer:int):uint
        {
            return _layerCount[layer] as uint;
        }
        
        /**
         * The first Entity in the World.
         */
        public function get first():Entity { return _updateFirst; }
        
        /**
         * How many Entity layers the World has.
         */
        public function get layers():uint { return _layerList.length; }
        
        /**
         * The first Entity of the type.
         * @param    type        The type to check.
         * @return    The Entity.
         */
        public function typeFirst(type:String):Entity
        {
            if (!_updateFirst) return null;
            return _typeFirst[type] as Entity;
        }
        
        /**
         * The first Entity of the Class.
         * @param    c        The Class type to check.
         * @return    The Entity.
         */
        public function classFirst(c:Class):Entity
        {
            if (!_updateFirst) return null;
            var e:Entity = _updateFirst;
            while (e)
            {
                if (e is c) return e;
                e = e._updateNext;
            }
            return null;
        }
        
        /**
         * The first Entity on the Layer.
         * @param    layer        The layer to check.
         * @return    The Entity.
         */
        public function layerFirst(layer:int):Entity
        {
            if (!_updateFirst) return null;
            return _renderFirst[layer] as Entity;
        }
        
        /**
         * The last Entity on the Layer.
         * @param    layer        The layer to check.
         * @return    The Entity.
         */
        public function layerLast(layer:int):Entity
        {
            if (!_updateFirst) return null;
            return _renderLast[layer] as Entity;
        }
        
        /**
         * The Entity that will be rendered first by the World.
         */
        public function get farthest():Entity
        {
            if (!_updateFirst) return null;
            return _renderLast[_layerList[_layerList.length - 1] as int] as Entity;
        }
        
        /**
         * The Entity that will be rendered last by the world.
         */
        public function get nearest():Entity
        {
            if (!_updateFirst) return null;
            return _renderFirst[_layerList[0] as int] as Entity;
        }
        
        /**
         * The layer that will be rendered first by the World.
         */
        public function get layerFarthest():int
        {
            if (!_updateFirst) return 0;
            return _layerList[_layerList.length - 1] as int;
        }
        
        /**
         * The layer that will be rendered last by the World.
         */
        public function get layerNearest():int
        {
            if (!_updateFirst) return 0;
            return _layerList[0] as int;
        }
        
        /**
         * How many different types have been added to the World.
         */
        public function get uniqueTypes():uint
        {
            var i:uint = 0;
            for (var type:String in _typeCount) i ++;
            return i;
        }
        
        /**
         * Pushes all Entities in the World of the type into the Array or Vector.
         * @param    type        The type to check.
         * @param    into        The Array or Vector to populate.
         * @return    The same array, populated.
         */
        public function getType(type:String, into:Object):void
        {
            if (into is Array || into is Vector.<*>)
            {
                var e:Entity = _typeFirst[type],
                    n:uint = into.length;
                while (e)
                {
                    into[n ++] = e;
                    e = e._typeNext;
                }
            }
        }
        
        /**
         * Pushes all Entities in the World of the Class into the Array or Vector.
         * @param    c            The Class type to check.
         * @param    into        The Array or Vector to populate.
         * @return    The same array, populated.
         */
        public function getClass(c:Class, into:Object):void
        {
            if (into is Array || into is Vector.<*>)
            {
                var e:Entity = _updateFirst,
                    n:uint = into.length;
                while (e)
                {
                    if (e is c) into[n ++] = e;
                    e = e._updateNext;
                }
            }
        }
        
        /**
         * Pushes all Entities in the World on the layer into the Array or Vector.
         * @param    layer        The layer to check.
         * @param    into        The Array or Vector to populate.
         * @return    The same array, populated.
         */
        public function getLayer(layer:int, into:Object):void
        {
            if (into is Array || into is Vector.<*>)
            {
                var e:Entity = _renderLast[layer],
                    n:uint = into.length;
                while (e)
                {
                    into[n ++] = e;
                    e = e._updatePrev;
                }
            }
        }
        
        /**
         * Pushes all Entities in the World into the array.
         * @param    into        The Array or Vector to populate.
         * @return    The same array, populated.
         */
        public function getAll(into:Object):void
        {
            if (into is Array || into is Vector.<*>)
            {
                var e:Entity = _updateFirst,
                    n:uint = into.length;
                while (e)
                {
                    into[n ++] = e;
                    e = e._updateNext;
                }
            }
        }
        
        /**
         * Updates the add/remove lists at the end of the frame.
         */
        public function updateLists():void
        {
            var e:Entity;
            
            // remove entities
            if (_remove.length)
            {
                for each (e in _remove)
                {
                    if (e._added != true && _add.indexOf(e) >= 0)
                    {
                        _add.splice(_add.indexOf(e), 1);
                        continue;
                    }
                    e._added = false;
                    e.removed();
                    removeUpdate(e);
                    removeRender(e);
                    if (e._type) removeType(e);
                    if (e.autoClear && e._tween) e.clearTweens();
                }
                _remove.length = 0;
            }
            
            // add entities
            if (_add.length)
            {
                for each (e in _add)
                {
                    e._added = true;
                    addUpdate(e);
                    addRender(e);
                    if (e._type) addType(e);
                    e.added();
                }
                _add.length = 0;
            }
            
            // sort the depth list
            if (_layerSort)
            {
                if (_layerList.length > 1) FP.sort(_layerList, true);
                _layerSort = false;
            }
        }
        
        /** @private Adds Entity to the update list. */
        private function addUpdate(e:Entity):void
        {
            // add to update list
            if (_updateFirst)
            {
                _updateFirst._updatePrev = e;
                e._updateNext = _updateFirst;
            }
            else e._updateNext = null;
            e._updatePrev = null;
            _updateFirst = e;
            _count ++;
            if (!_classCount[e._class]) _classCount[e._class] = 0;
            _classCount[e._class] ++;
        }
        
        /** @private Removes Entity from the update list. */
        private function removeUpdate(e:Entity):void
        {
            // remove from the update list
            if (_updateFirst == e) _updateFirst = e._updateNext;
            if (e._updateNext) e._updateNext._updatePrev = e._updatePrev;
            if (e._updatePrev) e._updatePrev._updateNext = e._updateNext;
            e._updateNext = e._updatePrev = null;
            
            _count --;
            _classCount[e._class] --;
        }
        
        /** @private Adds Entity to the render list. */
        internal function addRender(e:Entity):void
        {
            var f:Entity = _renderFirst[e._layer];
            if (f)
            {
                // Append entity to existing layer.
                e._renderNext = f;
                f._renderPrev = e;
                _layerCount[e._layer] ++;
            }
            else
            {
                // Create new layer with entity.
                _renderLast[e._layer] = e;
                _layerList[_layerList.length] = e._layer;
                _layerSort = true;
                e._renderNext = null;
                _layerCount[e._layer] = 1;
            }
            _renderFirst[e._layer] = e;
            e._renderPrev = null;
        }
        
        /** @private Removes Entity from the render list. */
        internal function removeRender(e:Entity):void
        {
            if (e._renderNext) e._renderNext._renderPrev = e._renderPrev;
            else _renderLast[e._layer] = e._renderPrev;
            if (e._renderPrev) e._renderPrev._renderNext = e._renderNext;
            else
            {
                // Remove this entity from the layer.
                _renderFirst[e._layer] = e._renderNext
                if (!e._renderNext)
                {
                    // Remove the layer from the layer list if this was the last entity.
                    if (_layerList.length > 1)
                    {
                        _layerList[_layerList.indexOf(e._layer)] = _layerList[_layerList.length - 1];
                        _layerSort = true;
                    }
                    _layerList.length --;
                }
            }
            _layerCount[e._layer] --;
            e._renderNext = e._renderPrev = null;
        }
        
        /** @private Adds Entity to the type list. */
        internal function addType(e:Entity):void
        {
            // add to type list
            if (_typeFirst[e._type])
            {
                _typeFirst[e._type]._typePrev = e;
                e._typeNext = _typeFirst[e._type];
                _typeCount[e._type] ++;
            }
            else
            {
                e._typeNext = null;
                _typeCount[e._type] = 1;
            }
            e._typePrev = null;
            _typeFirst[e._type] = e;
        }
        
        /** @private Removes Entity from the type list. */
        internal function removeType(e:Entity):void
        {
            // remove from the type list
            if (_typeFirst[e._type] == e) _typeFirst[e._type] = e._typeNext;
            if (e._typeNext) e._typeNext._typePrev = e._typePrev;
            if (e._typePrev) e._typePrev._typeNext = e._typeNext;
            e._typeNext = e._typePrev = null;
            _typeCount[e._type] --;
        }
        
        /** @private Calculates the squared distance between two rectangles. */
        private static function squareRects(x1:Number, y1:Number, w1:Number, h1:Number, x2:Number, y2:Number, w2:Number, h2:Number):Number
        {
            if (x1 < x2 + w2 && x2 < x1 + w1)
            {
                if (y1 < y2 + h2 && y2 < y1 + h1) return 0;
                if (y1 > y2) return (y1 - (y2 + h2)) * (y1 - (y2 + h2));
                return (y2 - (y1 + h1)) * (y2 - (y1 + h1));
            }
            if (y1 < y2 + h2 && y2 < y1 + h1)
            {
                if (x1 > x2) return (x1 - (x2 + w2)) * (x1 - (x2 + w2));
                return (x2 - (x1 + w1)) * (x2 - (x1 + w1));
            }
            if (x1 > x2)
            {
                if (y1 > y2) return squarePoints(x1, y1, (x2 + w2), (y2 + h2));
                return squarePoints(x1, y1 + h1, x2 + w2, y2);
            }
            if (y1 > y2) return squarePoints(x1 + w1, y1, x2, y2 + h2)
            return squarePoints(x1 + w1, y1 + h1, x2, y2);
        }
        
        /** @private Calculates the squared distance between two points. */
        private static function squarePoints(x1:Number, y1:Number, x2:Number, y2:Number):Number
        {
            return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
        }
        
        /** @private Calculates the squared distance between a rectangle and a point. */
        private static function squarePointRect(px:Number, py:Number, rx:Number, ry:Number, rw:Number, rh:Number):Number
        {
            if (px >= rx && px <= rx + rw)
            {
                if (py >= ry && py <= ry + rh) return 0;
                if (py > ry) return (py - (ry + rh)) * (py - (ry + rh));
                return (ry - py) * (ry - py);
            }
            if (py >= ry && py <= ry + rh)
            {
                if (px > rx) return (px - (rx + rw)) * (px - (rx + rw));
                return (rx - px) * (rx - px);
            }
            if (px > rx)
            {
                if (py > ry) return squarePoints(px, py, rx + rw, ry + rh);
                return squarePoints(px, py, rx + rw, ry);
            }
            if (py > ry) return squarePoints(px, py, rx, ry + rh)
            return squarePoints(px, py, rx, ry);
        }
        
        // Adding and removal.
        /** @private */    private var _add:Vector.<Entity> = new Vector.<Entity>;
        /** @private */    private var _remove:Vector.<Entity> = new Vector.<Entity>;
        
        // Update information.
        /** @private */    private var _updateFirst:Entity;
        /** @private */    private var _count:uint;
        
        // Render information.
        private var _renderFirst:Array = [];
        private var _renderLast:Array = [];
        private var _layerList:Array = [];
        private var _layerCount:Array = [];
        private var _layerSort:Boolean;
        private var _tempArray:Array = [];
        
        /** @private */    private var _classCount:Dictionary = new Dictionary;
        /** @private */    internal var _typeFirst:Object = { };
        /** @private */    private var _typeCount:Object = { };
        /** @private */    private var _recycled:Dictionary = new Dictionary;
    }
}