
Engine.EnterFrameManager = class {

    constructor()
    {
        this.enterFrameQueue = new Engine.CallbackQueue(this);
        this.postFrameQueue = new Engine.CallbackQueue(this);
        this.onException = null;
    }

    add(obj, func, useRealTime /* = false*/)
    {
		if (useRealTime === undefined) useRealTime = false;
        this.enterFrameQueue.add(obj, func, useRealTime);
    }

    remove(obj)
    {
        this.enterFrameQueue.remove(obj);
    }

    enterFrame(timeElapsed, realTimeElapsed)
    {
        this.enterFrameQueue.enterFrame(timeElapsed, realTimeElapsed);
    }

    addPostFrame(obj, func, useRealTime /* = false*/)
    {
		if (useRealTime === undefined) useRealTime = false;
        this.postFrameQueue.add(obj, func, useRealTime);
    }

    removePostFrame(obj)
    {
        this.postFrameQueue.remove(obj);
    }

    postFrame(timeElapsed, realTimeElapsed)
    {
        this.postFrameQueue.enterFrame(timeElapsed, realTimeElapsed);
    }
}


Engine.CallbackQueue = class
{

    constructor(owner)
    {
        this.owner = owner;

        this.LastItemsUpdated = 0;
    	this.LastTimeUpdated = 0;

		this.usesRealTime = new Map();			// object, bool
    	this.enterFrameCallbacks = new Map();
    	this.addQueue = new Map();
    	this.removeQueue = []
    }

    add(obj, func, useRealTime)
    {
		if (useRealTime && !this.usesRealTime.has(obj)) this.usesRealTime.set(obj, true);
        if (!useRealTime && this.usesRealTime.has(obj)) this.usesRealTime.delete(obj);
				
        // If the item is marked for removal, cancel the removal.
        if (this.removeQueue.indexOf(obj) != -1)
        {
            this.removeQueue.remove(obj);

            // don't add to the queue if this item is already active
            if (this.enterFrameCallbacks.has(obj)) return;
        }

        // Queue (if we aren't already adding this object...)
        if (!this.addQueue.has(obj))
        {
            this.addQueue.set(obj, func);
        }
    }


    remove(obj)
    {
		if (this.usesRealTime.has(obj)) this.usesRealTime.delete(obj);
		
        // If we haven't added this object yet, just remove it from the add queue
        if (this.addQueue.has(obj))
        {
            this.addQueue.delete(obj);
            return;
        }

        // Queue for removal
        if (!this.removeQueue.contains(obj))
        {
            this.removeQueue.push(obj);
        }
    }

    enterFrame(timeElapsed, realTimeElapsed)
    {
        for(var i = 0; i < this.removeQueue.length; i++)
        {
        	var obj = this.removeQueue[i];
            if (this.enterFrameCallbacks.has(obj))
            {
                this.enterFrameCallbacks.delete(obj);
            }
        }
        this.removeQueue = [];

        for (let obj of this.addQueue.keys())
        {
            if (!this.enterFrameCallbacks.has(obj))
            {
                this.enterFrameCallbacks.set(obj, this.addQueue.get(obj));
            }
        }
        this.addQueue.clear();

        this.LastItemsUpdated = 0;
        for (let obj of this.enterFrameCallbacks.keys())
        {
        	var func = this.enterFrameCallbacks.get(obj);
			
			var useTime = this.usesRealTime.has(obj) && this.usesRealTime.get(obj) ? realTimeElapsed : timeElapsed;
        	
            if (this.owner.OnException == null)
            {
                func.call(obj, useTime);
                this.LastItemsUpdated++;
            }
            else
            {
                try
                {
                    func.call(obj, useTime);
                    this.LastItemsUpdated++;
                }
                catch (e)
                {
                    this.owner.onException(e);
                }
            }

        }

        this.LastTimeUpdated = timeElapsed;
    }

}
