"use strict";

var GameObjectManager = function(){
	this.objects = [];

	this.removedQueue = [];
	this.cleanupQueue = []; // parallel indices to removedQueue

	this._heap = {};
	this._heap._remove = {
		index: -1,
		deferred: null,
		defidx: 0
	};
	this._heap.destroyAll = {};
	this._heap.getNamedObject = {};
	this._heap.Update = {};
	this._heap.deferred = {}; // objects which have requested to be updated later
	this._heap.redeferred = {};
};
	GameObjectManager.prototype.add = function(gameobject){
		this.objects.push(gameobject);
		gameobject.manager=this;
	};

	GameObjectManager.prototype.remove = function(gameobject,nocleanup){
		// "kill" the objects action immediately, but don't pull it from the
		// objects list until the end of the frame
		this.removedQueue.push(gameobject);
		this.cleanupQueue.push(nocleanup);
		//gameobject.Update = noop;
	};

	GameObjectManager.prototype._remove = function(gameobject,nocleanup){
		this._heap._remove.index = this.objects.indexOf(gameobject);
		if (this._heap._remove.index!=-1){
			// since we track deferred updates by object index,
			// we need to keep that straight as well
			if (this._heap.deferred){
				// new null-bin, will be made when needed
				this._heap._remove.deferred = null;
				for (this._heap._remove.defidx in this._heap.deferred){
					if (this._heap._remove.defidx < this._heap._remove.index){
						// anything earlier isn't changing index
						this._heap._remove.deferred = this._heap._remove.deferred || {};
						this._heap._remove.deferred[this._heap._remove.defidx] = true;
					}
					else if (this._heap._remove.defidx > this._heap._remove.index){
						// anything later will be shifting up
						this._heap._remove.deferred = this._heap._remove.deferred || {};
						this._heap._remove.deferred[this._heap._remove.defidx-1] = true;
					}
					// anything equal to the pending-removal index
					// is about to have all its problems solved
					// and will not need to be revisited.
				}
				this._heap.deferred = this._heap._remove.deferred;
				this._heap._remove.deferred = null;
			}
			// most pathological case- things causing removal in the middle
			// of a deferred update will disrupt saved re-deferral indices
			if (this._heap.redeferred){
				// new null-bin, will be made when needed
				this._heap._remove.deferred = null;
				for (this._heap._remove.defidx in this._heap.redeferred){
					if (this._heap._remove.defidx < this._heap._remove.index){
						// anything earlier isn't changing index
						this._heap._remove.deferred = this._heap._remove.deferred || {};
						this._heap._remove.deferred[this._heap._remove.defidx] = true;
					}
					else if (this._heap._remove.defidx > this._heap._remove.index){
						// anything later will be shifting up
						this._heap._remove.deferred = this._heap._remove.deferred || {};
						this._heap._remove.deferred[this._heap._remove.defidx-1] = true;
					}
					// anything equal to the pending-removal index
					// is about to have all its problems solved
					// and will not need to be revisited.
				}
				this._heap.redeferred = this._heap._remove.deferred;
				this._heap._remove.deferred = null;
			}

			this.objects.splice(this._heap._remove.index,1);
			gameobject.manager=null;

			// clean out any intentional recursive references and things that will make the gc cry
			if (!nocleanup) gameobject.cleanup();
		}
	};

	GameObjectManager.prototype.destroyAll = function(){
		this._heap.destroyAll.obj;
		while (this.objects.length>0){
			this._heap.destroyAll.obj = this.objects.pop();
			this._heap.destroyAll.obj.manager=null;

			// clean out any intentional recursive references and things that will make the gc cry
			this._heap.destroyAll.obj.cleanup();
		}
	};

	// returns the FIRST object with the given name added to the manager
	GameObjectManager.prototype.getNamedObject = function(name){
		// while I could set up a parallel {} mapping names to objects,
		// we'd run into the same sorts of redundancy/ambiguity issues with name collision,
		// and the add/remove functions would need to manage both in parallel.
		// Since this should be an infrequent call, just do a linear search.
		for (this._heap.getNamedObject.i=0; this._heap.getNamedObject.i<this.objects.length; this._heap.getNamedObject.i++){
			if (this.objects[this._heap.getNamedObject.i].name==name && this.removedQueue.indexOf(this.objects[this._heap.getNamedObject.i])==-1) return this.objects[this._heap.getNamedObject.i];
		}
		return null;
	};

	// populates outArr with ALL objects with the given name in the manager
	// probably shouldn't use this more than needed
	GameObjectManager.prototype.getAllNamedObjects = function(name, outArr){
		outArr.length=0;
		for (this._heap.getNamedObject.i=0; this._heap.getNamedObject.i<this.objects.length; this._heap.getNamedObject.i++){
			if (this.objects[this._heap.getNamedObject.i].name==name && this.removedQueue.indexOf(this.objects[this._heap.getNamedObject.i])==-1){
				outArr.push(this.objects[this._heap.getNamedObject.i]);
			}
		}
	};

	GameObjectManager.prototype.Update = function(){
		// clean up anything removed e.g. by events so they don't jam the update loop before normal removal
		for (this._heap.Update.j=0; this._heap.Update.j<this.removedQueue.length; this._heap.Update.j++){
			this._remove(this.removedQueue[this._heap.Update.j], this.cleanupQueue[this._heap.Update.j]);
		}

		// main update "loop" including defer/retry logic
		this._heap.deferred = null;

		for (this._heap.Update.i=0; this._heap.Update.i<this.objects.length; this._heap.Update.i++){
			if (this.objects[this._heap.Update.i].shouldDeferUpdate()){
				this._heap.deferred = this._heap.deferred || {};
				this._heap.deferred[this._heap.Update.i] = true;
			}
			else{
				this._UpdateOne();
			}
		}
		this._heap.redeferred = null;
		while (this._heap.deferred){
			for (this._heap.Update.i in this._heap.deferred){
				if (this.objects[this._heap.Update.i].shouldDeferUpdate()){
					this._heap.redeferred = this._heap.redeferred || {};
					this._heap.redeferred[this._heap.Update.i] = true;
				}
				else{
					this._UpdateOne();
				}
			}
			this._heap.deferred = this._heap.redeferred;
			this._heap.redeferred = null;
		}

	};

	GameObjectManager.prototype._UpdateOne = function(){
		// note: still using "static" _heap index vars
		// so we don't re-explode allocation/deallocation

		if (this.objects[this._heap.Update.i].sfx){
			//this._heap.Update.oldlen = this.objects.length;
			this.objects[this._heap.Update.i].sfx.Update();
			if (!this.objects[this._heap.Update.i].sfx.is_playing) this.objects[this._heap.Update.i].sfx=null;
		}
		
		this.objects[this._heap.Update.i].Update();
		if (this.objects[this._heap.Update.i].interactions & DEFINE_GAMEOBJECT_INTERACTIONS.CONTROL){
			this.objects[this._heap.Update.i].controls.Update();
		}

		// expensive, but should clean up our removals in real time w/o index jigger
		this._heap.Update.oldi = this._heap.Update.i;
		for (this._heap.Update.j=0; this._heap.Update.j<this.removedQueue.length; this._heap.Update.j++){
			this._heap.Update.idx = this.objects.indexOf(this.removedQueue[this._heap.Update.j]);
			if (this._heap.Update.idx >= 0 && this._heap.Update.idx <= this._heap.Update.oldi){
				this._heap.Update.i--;
			}	
		}

		for (this._heap.Update.j=0; this._heap.Update.j<this.removedQueue.length; this._heap.Update.j++){
			this._remove(this.removedQueue[this._heap.Update.j], this.cleanupQueue[this._heap.Update.j]);
		}

		

		this.removedQueue.length=0;
		this.cleanupQueue.length=0;
	}
	