/*
An unusual nonrendered object for centralized control of flockable objects.
No formal spec for flockable (yet...?), but the gist is a flockable GO
should have a sprite with .x and .y, and its own .dx, .dy, .z and .dz.
Its Update motion should attempt a move by (dx,dy) and add dz to z.
*/
"use strict";

var IntVec3 = function(x,y,z){
	this.set(x,y,z);

	this._heap = {};
	this._heap.l = 0;
	this._heap.s = 0;
};
IntVec3.prototype.set = function(x,y,z){
	this.x=parseInt(x);
	this.y=parseInt(y);
	this.z=parseInt(z);
};
IntVec3.prototype.setVec = function(vec){
	this.x=vec.x;
	this.y=vec.y;
	this.z=vec.z;
};
IntVec3.prototype.addVec = function(vec){
	this.x+=vec.x;
	this.y+=vec.y;
	this.z+=vec.z;
};
IntVec3.prototype.add = function(x,y,z){
	this.x+=parseInt(x);
	this.y+=parseInt(y);
	this.z+=parseInt(z);
};
// not all vintage CPUs had dedicated multiply, 
// but iterative shift-and-add gets the job done in O(Log2(n))
IntVec3.prototype.mult = function(m){
	m = parseInt(m);
	this.x *= m;
	this.y *= m;
	this.z *= m;
}
// For the purposes of this project, and in the spirit of
// vintage integer CPUs, I am averse to division.
// However, this will be a real trick without it.
// On "real hardware," assume this would use a O(Log2(n))
// algorithm along the lines of upshifting d until its highbit
// was even with the component's highbit, then iteratively
// subtracting d if poosible and downshifting d regardless,
// with the result being the number comprised of bits indicating
// whether the upshifted d was successfully subtracted (>0 result)
// at each shift position.
IntVec3.prototype.div = function(d){
	d = parseInt(d);
	this.x = Math.round(this.x/d);
	this.y = Math.round(this.y/d);
	this.z = Math.round(this.z/d);
};
IntVec3.prototype.lsh = function(s){
	this.x = this.x<<s;
	this.y = this.y<<s;
	this.z = this.z<<s;
};
IntVec3.prototype.rsh = function(s){
	this.x = this.x>>s;
	this.y = this.y>>s;
	this.z = this.z>>s;
};

// length is really ugly, since it needs a sqrt.
// sqrt is hella expensive, as most direct methods 
// of obtaining it require iterating until convergence.
// The only practical quick way to do sqrt on a basic
// arithmetic chip is by LUT.
// Even in modern js, there's a chance LUT is faster,
// and this way I'll be confident behaviors will port more 
// consistently if I or someone ever does a port.
IntVec3.sqrtLUT = []; // LUT of 16*sqrt(x) for 0<=x<64
for (var i=0; i<64; ++i){
	IntVec3.sqrtLUT.push(Math.round(Math.sqrt(i)*16));
}
IntVec3.prototype.lenSq = function(){
 return this.x*this.x + this.y*this.y + this.z*this.z;
};
IntVec3.prototype.len = function(){
	this._heap.s=0;
	this._heap.l = this.lenSq();
	// sqrt(r/2) = sqrt(r)/sqrt(2)
	// so sqrt(r) = sqrt(r/2)*sqrt(2) 
	// so 16*sqrt(r) = 16*sqrt(r/2)*16*sqrt(2)/16
	while(this._heap.l>=64){
		++this._heap.s;
		this._heap.l = this._heap.l>>1;
	}
	this._heap.l = IntVec3.sqrtLUT[this._heap.l];
	while(this._heap.s>0){
		this._heap.l = (this._heap.l*IntVec3.sqrtLUT[2])>>4;
		--this._heap.s;
	}
	return this._heap.l;
};

IntVec3.prototype.normalize = function(len){
	len = parseInt(len);
	if (len===0){
		this.set(0,0,0);
		return;
	}
	this._heap.l = this.len();
	if (this._heap.l===0) return;
	this.mult(len);
	this.lsh(4); // bump our length 16x because our len() sqrts are 16x (16x/16x=1)
	this.div(this._heap.l);
};

var GO_Flock = function(affects,radius,speed,includeMeli){
	this.sprite = new Sprite(null,"Flock");
	this.sprite.collision_flags = 0;
	this.sprite.setBoundingBox(0,0,1,1);
	this.sprite.game_object = this; // no longer a generic GO

	this.affectedName = affects;
	this.radius = radius || 32;
	this.radSq = this.radius*this.radius;
	this.speed = speed || 4;
	this.includeMeli = includeMeli || false;

	this.meli = null;
	this.meliFind=false; // she probably won't be in the map when we init; hopefully when we update
	this.meliLastX = 0;
	this.meliLastY = 0;
	// Meli's z and dz are assumed 0

	this._heap.flock = [];
	this._heap.obj = null;
	this._heap.obj2 = null;
	this._heap.avgPos = new IntVec3(0,0,0);
	this._heap.avgVel = new IntVec3(0,0,0);
	this._heap.count = 0;

	this._heap.newVel = new IntVec3(0,0,0);

	this._heap.avoid = new IntVec3(0,0,0); // neighbor avoidance component
	this._heap.subAv = new IntVec3(0,0,0);
	this._heap.avCount = 0;
	this._heap.avRadius = 0;
	this._heap.seek = new IntVec3(0,0,0); // flock center-seeking component
	this._heap.drift = new IntVec3(0,0,0); // flock velocity trending component

};

GO_Flock.prototype = new GameObject(
	new Sprite( // because it's expected, not because it's needed
		null,
		"Flock"
	),
	"Flock"
	);

	GO_Flock.prototype.interactions = 
		0;

	GO_Flock.prototype.Update = function(){
		if (this.includeMeli && !this.meli && !this.meliFind){
			this.meli = this.manager.getNamedObject("Meli");
			this.meliFind = true;

			this.meliLastX = this.meli.sprite.x;
			this.meliLastY = this.meli.sprite.y;
		}

		this.manager.getAllNamedObjects(this.affectedName,this._heap.flock);

		this._heap.avgPos.set(0,0,0);
		this._heap.avgVel.set(0,0,0);
		this._heap.count = this._heap.flock.length+(this.includeMeli && this.meli ? 1:0);

		// back-of-the-napkin positions using just x and y, not box centers
		if (this.includeMeli && this.meli){
			this._heap.avgPos.set(this.meli.sprite.x,this.meli.sprite.y,0);
			this._heap.avgVel.set((this.meli.sprite.x - this.meliLastX),(this.meli.sprite.y - this.meliLastY),0);
			this.meliLastX = this.meli.sprite.x;
			this.meliLastY = this.meli.sprite.y;
		}

		for (this._heap.i=0; this._heap.i<this._heap.flock.length; ++this._heap.i){
			this._heap.obj = this._heap.flock[this._heap.i];

			this._heap.avgPos.add(this._heap.obj.sprite.x,this._heap.obj.sprite.y,this._heap.obj.z);
			this._heap.avgVel.add(this._heap.obj.dx,this._heap.obj.dy,this._heap.obj.dz);
		}
		if (this._heap.count!==0){
			this._heap.avgPos.div(this._heap.count);
			this._heap.avgVel.div(this._heap.count);
		}

		// for increased precision,
		// let's try upshifting the original deltas, 
		// adding any differences we want to apply at reduced upshift,
		// then downshifting back to original scale.
		this._heap.drift.setVec(this._heap.avgVel);
		this._heap.drift.lsh(1);

		for (this._heap.i=0; this._heap.i<this._heap.flock.length; ++this._heap.i){
			this._heap.obj = this._heap.flock[this._heap.i];

			this._heap.newVel.set(this._heap.obj.dx, this._heap.obj.dy, this._heap.obj.dz);
			this._heap.newVel.lsh(3);


			// avoidance component
			this._heap.avCount = 0;
			this._heap.avoid.set(0,0,0);
			for (this._heap.j=0; this._heap.j<this._heap.flock.length; ++this._heap.j){
				if (this._heap.j!==this._heap.i){
					// avoidance vec = ownpos - otherpos
					this._heap.obj2 = this._heap.flock[this._heap.j];
					this._heap.subAv.set(this._heap.obj2.sprite.x,this._heap.obj2.sprite.y,this._heap.obj2.z);
					this._heap.subAv.mult(-1);
					this._heap.subAv.add(this._heap.obj.sprite.x,this._heap.obj.sprite.y,this._heap.obj.z);

					//rough radius of influence based on combined sizes
					this._heap.avRadius = 
						Math.pow(
							(this._heap.obj.sprite.box_right - this._heap.obj.sprite.box_left) + 
							(this._heap.obj2.sprite.box_right - this._heap.obj2.sprite.box_left),
							2) +
						Math.pow(
							(this._heap.obj.sprite.box_bottom - this._heap.obj.sprite.box_top) + 
							(this._heap.obj2.sprite.box_bottom - this._heap.obj2.sprite.box_top),
							2);
					if (this._heap.subAv.lenSq() < this._heap.avRadius){
						// make the magnitude of the influence proportional to inverse of distance
						// i.e. new_length = const * (avRadius-old_length)
						this._heap.subAv.normalize(this._heap.avRadius-this._heap.subAv.lenSq());
						this._heap.avoid.addVec(this._heap.subAv);
						++this._heap.avCount;
					}
				}
			}
			if (this.includeMeli && this.meli){
				// avoidance vec = ownpos - otherpos
				this._heap.subAv.set(this.meli.sprite.x,this.meli.sprite.y,0);
				this._heap.subAv.mult(-1);
				this._heap.subAv.add(this._heap.obj.sprite.x,this._heap.obj.sprite.y,this._heap.obj.z);
				

				//rough radius of influence based on combined sizes
				this._heap.avRadius = 
					Math.pow(
						(this._heap.obj.sprite.box_right - this._heap.obj.sprite.box_left) + 
						(this.meli.sprite.box_right - this.meli.sprite.box_left),
						2) +
					Math.pow(
						(this._heap.obj.sprite.box_bottom - this._heap.obj.sprite.box_top) + 
						(this.meli.sprite.box_bottom - this.meli.sprite.box_top),
						2);
				if (this._heap.subAv.lenSq() < this._heap.avRadius){
					// make the magnitude of the influence proportional to inverse of distance
					// i.e. new_length = const * (avRadius-old_length)
					this._heap.subAv.normalize(this._heap.avRadius-this._heap.subAv.lenSq());
					this._heap.avoid.addVec(this._heap.subAv);
					++this._heap.avCount;
				}
			}
			if (this._heap.avCount!==0) this._heap.avoid.div(this._heap.avCount);
			// keep at 1x for now
			this._heap.newVel.addVec(this._heap.avoid);

			// average heading component
			this._heap.newVel.addVec(this._heap.drift);

			// average position component
			// seek vec = otherpos - ownpos
			this._heap.seek.set(this._heap.obj.sprite.x,this._heap.obj.sprite.y,this._heap.obj.z);
			this._heap.seek.mult(-1);
			this._heap.seek.addVec(this._heap.avgPos);
			// seek is in distance now
			// want seek.len==speed at distance=radius
			// or because we're in <<3 world, seek.len==speed<<3 at distance=radius
			this._heap.seek.normalize( Math.round( ((this.speed<<3) * this._heap.seek.lenSq()) / this.radSq) );

			this._heap.newVel.addVec(this._heap.seek);

			// normalize
			this._heap.newVel.normalize(this.speed<<3);

			// reduce back to scale
			//this._heap.newVel.rsh(3);
			this._heap.newVel.div(8);

			this._heap.obj.dx = this._heap.newVel.x;
			this._heap.obj.dy = this._heap.newVel.y;
			this._heap.obj.dz = this._heap.newVel.z;
			
		}
		this._heap.obj = null;
		this._heap.obj2 = null;

		
	}

