/*
A log swinging on vines.
One of the more involved pieces of engineering in the game.
Getting interaction fizziks would be bad enough WITHOUT keeping to my vintage-style
computational techniques heuristic

Notions:
dy for given dx comes from trig table
use integer division to get whole-unit offset at each vine link
use modulo to get remaining offset
probably/initially table per chain length for how to distribute remaining offset

1px outside-top contact/ride box
rule 1: whenever player in ride box moves on x, move log half the distance in the opposite direction
rule 2: whenever [log is away from center and has not moved due to player] OR [momentum counter is already towards center], move log towards center and increment momentum counter towards center
rule 3: whenever momentum counter is away from center, move log away from center and decrement momentum counter towards center
rule 4: when log passes center, additionally decrement momentum counter towards center
rule 5: (possibly) if dx causes y to decrease (log rises), decrement momentum counter towards center by ~5; increase by 5 when y increases(log falls)
rule 6: any dx/dy applied to log is then applied to player and all solids in ride box
*/
"use strict";

var GO_LogSwing = function(numlinks){
	numlinks = (numlinks && numlinks>1) ? numlinks : 5;

	this.sprite = new Sprite(GO_LogSwing.spriteSheet,"LogSwing");
	this.sprite.collision_flags = 
		DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_PASSIVE |
		DEFINE_SPRITE_COLLISION_FLAGS.SOLID;
	this.sprite.setBoundingBox(0,1,31,10);
	//sprite.constraints=constraints;
	this.sprite.max_step = 0;
	this.sprite.game_object = this; // no longer a generic GO

	this.ridebox = new Sprite(null,"LogSwing_ridebox");
	this.ridebox.setBoundingBox(-1,0,32,11); // negative bounding box... never tried this before.  Engine should handle it...
	this.ridebox.collision_flags = 
		DEFINE_SPRITE_COLLISION_FLAGS.SPRITES_ACTIVE;
	this.ridebox.game_object = this;

	this.num_links = numlinks;
	this.startx = -1;
	this.starty = -1;
	// coordinates of the top link in each vine, keeling in mind these are 8x8 links embelished into 16x16 tiles
	this.hookleftx = -1;
	this.hookrightx = -1;
	this.hooky = -1;

	this.vines = {left: [], right: []};
	for (this._heap.linkIdx = 0; this._heap.linkIdx<this.num_links; ++this._heap.linkIdx){
		this.vines.left.push(new Sprite(GO_LogSwing.linkSheet,"LogSwing_ridebox"));
		this.vines.left[this._heap.linkIdx].setBoundingBox(4,4,11,11);
		this.vines.left[this._heap.linkIdx].collision_flags = 0;
		this.vines.left[this._heap.linkIdx].game_object = this;

		this.vines.right.push(new Sprite(GO_LogSwing.linkSheet,"LogSwing_ridebox"));
		this.vines.right[this._heap.linkIdx].setBoundingBox(4,4,11,11);
		this.vines.right[this._heap.linkIdx].collision_flags = 0;
		this.vines.right[this._heap.linkIdx].game_object = this;
	}

	this.radius = this.num_links<<3;
	this.arc = [];
	for (this._heap.i=0; this._heap.i<64; ++this._heap.i){
		this.arc.push((this.radius * SIN[this._heap.i])>>7);
	}

	this.meli = null;
	this.meliFind=false; // she probably won't be in the map when we init; hopefully when we update
	this.meli_rel_x = 0; // relative to sprite
	this.meli_abs_x = 0; // overall
	this.meli_on = false;

	this.offx = 0;
	this.momentum = 0;

	this._heap.riders = [];
	this._heap.clobbers = [];
};

GO_LogSwing.spriteImg = SDS_Load_Image("art/sheets/sprite/logswing.png");
GO_LogSwing.spriteSheet = new TileSheet(5, 4, 0, 0, GO_LogSwing.spriteImg);

GO_LogSwing.linkImg = SDS_Load_Image("art/sheets/sprite/logswing_link.png");
GO_LogSwing.linkSheet = new TileSheet(4, 4, 0, 0, GO_LogSwing.linkImg);

GO_LogSwing.prototype = new GameObject(
	new Sprite(
		GO_LogSwing.spriteSheet,
		"LogSwing"
	),
	"LogSwing"
	);

	GO_LogSwing.prototype.interactions = 0;

	GO_LogSwing.prototype.addToRoom = function(mapRoom){
		mapRoom.addSprite(this.sprite);
		mapRoom.addSprite(this.ridebox);
		for (this._heap.linkIdx = 0; this._heap.linkIdx<this.num_links; ++this._heap.linkIdx){
			mapRoom.addSprite(this.vines.left[this._heap.linkIdx]);
			mapRoom.addSprite(this.vines.right[this._heap.linkIdx]);
		}
	}

	GO_LogSwing.prototype.Update = function(){
		if (!this.meli && !this.meliFind){
			this.meli = this.manager.getNamedObject("Meli");
			this.meliFind = true;
		}
		if (this.startx<0 || this.starty<0){ // runners: if you can find a way to swing these off the left or top of the screen, weird things will happen
			this.startx = this.sprite.x;
			this.starty = this.sprite.y;
			this.hookleftx = this.startx - 4;
			this.hookrightx = this.startx + 20;
			this.hooky = this.sprite.y - this.radius - 4;
		}

		// motion code (Riele Kwality Fizziks(TM))
		// rule 1: whenever player in ride box moves on x, move log half the distance in the opposite direction
		this._heap.dx = 0; // change in offx
		if (this.ridebox.isTouchingSprite(this.meli.sprite)){
			this._heap.dx = ((this.meli.sprite.x - this.sprite.x) - this.meli_rel_x);
			if (this.meli_on){
				// Newton's third law of Moshun(TM)
				this.offx -= this._heap.dx>>1;
			}
			else{
				// first contact; absorb her ~forward~ momentum, amplified for effect
				//this.momentum += this._heap.dx<<2;
				this.momentum += (this.meli.sprite.x - this.meli_abs_x)<<2;
			}

			this.meli_on = true;
		}
		else{
			this.meli_on = false;
		}
		// rule 2: whenever [log is away from center and has not moved due to player] OR [momentum counter is already towards center], move log towards center and increment momentum counter towards center
		this._heap.oldoffx = this.offx;
		//this._heap.speed = Math.max((Math.abs(this.momentum)>>3),1);
		//this._heap.speed = ((127 - SIN[this.momentum+64])>>4) + 1;
		this._heap.speed = Math.max(SQRT[Math.abs(this.momentum)],1);
		if ((this.offx !== 0 && this._heap.dx===0 && this.momentum===0) || (this.offx<0 && this.momentum>0) || (this.offx>0 && this.momentum<0)){
			if (this.offx<0){
				//++this.offx;
				//++this.momentum;
				//this.offx += this._heap.speed;
				//this.momentum += this._heap.speed;
				
				this.offx += this._heap.speed;
				this.momentum += Math.min(-this._heap.oldoffx,this._heap.speed); // only increase towards center
				this.momentum -= Math.max(this.offx, 0); // decrease again if overshot
			}
			else{
				//--this.offx;
				//--this.momentum;
				//this.offx -= this._heap.speed;
				//this.momentum -= this._heap.speed;
				
				this.offx -= this._heap.speed;
				this.momentum -= Math.min(this._heap.oldoffx,this._heap.speed); // only increase towards center
				this.momentum += Math.max(-this.offx, 0); // decrease again if overshot
			}
		}
		// rule 3: whenever momentum counter is away from center, move log away from center and decrement momentum counter towards center
		else if (this.momentum !== 0){
			if (this.momentum<0){
				//--this.offx;
				//++this.momentum;
				this.offx -= this._heap.speed;
				this.momentum += this._heap.speed;
			}
			else{
				//++this.offx;
				//--this.momentum;
				this.offx += this._heap.speed;
				this.momentum -= this._heap.speed;
			}
		}
		// rule 4: when log passes center, additionally decrement momentum counter towards center
		//if (this.offx===0 && this.momentum!==0){
		if (((this._heap.oldoffx<0 && this.offx>=0) || (this._heap.oldoffx>0 && this.offx<=0)) && this.momentum!==0){
			if (this.momentum<0){
				//++this.momentum;
				this.momentum += this._heap.speed;
			}
			else{
				//--this.momentum;
				this.momentum -= this._heap.speed;
			}
		}
		// rule 5: (possibly) if dx causes y to decrease (log rises), decrement momentum counter towards center by ~5; increase by 5 when y increases(log falls)
		// rule 6: any dx/dy applied to log is then applied to player and all solids in ride box
		//this.offx = Math.min(63,Math.max(this.offx,-63));
		//this._heap.dx = (this.arc[Math.abs(this.offx)] * (this.offx<0 ? -1 : 1)) - (this.sprite.x - this.startx);
		//this._heap.dy = (-this.radius + this.arc[63-Math.abs(this.offx)] + 1) - (this.sprite.y - this.starty);

		// tweak for diverse chain lengths- normalize to an extent by length
		this._heap.arcidx = Math.abs(this.offx);
		this._heap.arcidx = Math.round(this._heap.arcidx*5/this.num_links);
		this._heap.arcidx = Math.min(63,this._heap.arcidx);
		this._heap.dx = (this.arc[this._heap.arcidx] * (this.offx<0 ? -1 : 1)) - (this.sprite.x - this.startx);
		this._heap.dy = (-this.radius + this.arc[63-this._heap.arcidx] + 1) - (this.sprite.y - this.starty);

		if (this._heap.dx!=0 || this._heap.dy!=0){
			//console.log("Log Moved "+this._heap.dx+" "+this._heap.dy);
			this.ridebox.getCollisions(this._heap.riders);

			// to reconcile the ejection mechanics on move() for solid objects
			// we must be sure not to move the log into any solid riders or
			// any riders into the solid log.
			// We could either change the order of application based on movement direction
			// (riders first on -dy, log first on +dy) OR just make the log non-solid until done.
			// The former will introduce potential weirdness on objects caught below the log,
			// the latter may fail to trigger crush behavior
			if (this._heap.dy>=0) this.sprite.move(this._heap.dx,this._heap.dy);

			for (this._heap.i=0; this._heap.i<this._heap.riders.length; ++this._heap.i){
				// don't move ourselves, other swings, doors, or triggers.
				// could probably use a more explicit flag for mobs, but the need is not critical just yet
				if (!(this._heap.riders[this._heap.i].game_object instanceof GO_LogSwing) && 
					!(this._heap.riders[this._heap.i].game_object.interactions & 
						(
							DEFINE_GAMEOBJECT_INTERACTIONS.TRIGGER |
							DEFINE_GAMEOBJECT_INTERACTIONS.PORT |
							DEFINE_GAMEOBJECT_INTERACTIONS.AUTOPORT
						)
					)){
					this._heap.riders[this._heap.i].move(this._heap.dx, this._heap.dy);
					//console.log("-> moved "+this._heap.riders[this._heap.i].game_object.name+" "+this._heap.dx+" "+this._heap.dy);
				}
			}

			if (this._heap.dy<0) this.sprite.move(this._heap.dx,this._heap.dy);

			// fast-moving horizontal logs can still jump
			// into player hitbox, triggering crush.
			// Make one last attempt to push things out of the way.
			this.sprite.getCollisions(this._heap.clobbers, false, true);
			for (this._heap.i=0; this._heap.i<this._heap.clobbers.length; ++this._heap.i){
				if (this._heap.riders.indexOf(this._heap.clobbers[this._heap.i])!=-1){
					//console.log("-> not avoiding clobber of "+this._heap.clobbers[this._heap.i].game_object.name+" since it was already moved as a rider");
					continue; // you had your chance.
				}
				if ((this._heap.clobbers[this._heap.i].game_object instanceof GO_LogSwing) || 
					(this._heap.clobbers[this._heap.i].game_object.interactions & 
						(
							DEFINE_GAMEOBJECT_INTERACTIONS.TRIGGER |
							DEFINE_GAMEOBJECT_INTERACTIONS.PORT |
							DEFINE_GAMEOBJECT_INTERACTIONS.AUTOPORT
						)
					)){
					//console.log("-> not avoiding clobber of "+this._heap.clobbers[this._heap.i].game_object.name+" since it is an excluded category");
					continue; // still not moving these types of things
				}

				//console.log("-> tried to avoid clobber of "+this._heap.clobbers[this._heap.i].game_object.name);
				if (this._heap.dx<0 && this._heap.clobbers[this._heap.i].x+this._heap.clobbers[this._heap.i].center.x < this.sprite.x+this.sprite.center.x){
					//console.log("--> required dx "+(this.sprite.x+this.sprite.box_left) - (this._heap.clobbers[this._heap.i].x+this._heap.clobbers[this._heap.i].box_right));
					this._heap.clobbers[this._heap.i].move((this.sprite.x+this.sprite.box_left) - (this._heap.clobbers[this._heap.i].x+this._heap.clobbers[this._heap.i].box_right) - 1,0);
				}
				else if (this._heap.dx>0 && this._heap.clobbers[this._heap.i].x+this._heap.clobbers[this._heap.i].center.x > this.sprite.x+this.sprite.center.x){
					//console.log("--> required dx "+(this.sprite.x+this.sprite.box_right) - (this._heap.clobbers[this._heap.i].x+this._heap.clobbers[this._heap.i].box_left));
					this._heap.clobbers[this._heap.i].move((this.sprite.x+this.sprite.box_right) - (this._heap.clobbers[this._heap.i].x+this._heap.clobbers[this._heap.i].box_left) + 1,0);
				}
			}
			//if (this._heap.clobbers.length<1) console.log("LogSwing solemnly swears it has no clobber collisions.");

			this._heap.riders.length=0; // don't retain references to riders
			this._heap.clobbers.length=0;
		}

		// latch for next cycle
		this.meli_rel_x = this.meli.sprite.x - this.sprite.x;
		this.meli_abs_x = this.meli.sprite.x;

		// redistribute vine links
		this._heap.dx = this.sprite.x - this.startx; // recycle to mean total distance off center
		this._heap.dy = this.sprite.y - this.starty;
		this._heap.stepx = Math.trunc(this._heap.dx/(this.num_links+1)); // cheat.  retro way to do this would be a subtract loop
		this._heap.stepy = Math.trunc(this._heap.dy/(this.num_links+1));
		this._heap.remx = this._heap.dx - (this.num_links+1)*this._heap.stepx;
		this._heap.remy = this._heap.dy - (this.num_links+1)*this._heap.stepy;
		// if I have e.g. 3 units to distribute between 5 links,
		// I want them on links e.g. 1*(5/3), 2*(5/3) and 3*(5/3)  (or 0*, 1* and 2*)
		// so if I make a 5-counter, add 3 to it each link-iteration, and increment the offset whenever it overflows,
		// the effect is achieved sans division
		this._heap.dx = 0; // recycle to accumulate distance off center
		this._heap.dy = 0;
		this._heap.fracx = 0;
		this._heap.fracy = 0;
		for (this._heap.linkIdx = 0; this._heap.linkIdx<this.num_links; ++this._heap.linkIdx){
			this._heap.dx += this._heap.stepx;
			this._heap.dy += this._heap.stepy;

			this._heap.fracx += Math.abs(this._heap.remx);
			while (this._heap.fracx >= this.num_links){
				this._heap.dx += (this._heap.remx>0) ? 1 : -1;
				this._heap.fracx -= this.num_links;
			}
			this._heap.fracy += Math.abs(this._heap.remy);
			while (this._heap.fracy >= this.num_links){
				this._heap.dy += (this._heap.remy>0) ? 1 : -1;
				this._heap.fracy -= this.num_links;
			}

			this.vines.left[this._heap.linkIdx].x = this.hookleftx + this._heap.dx;
			this.vines.right[this._heap.linkIdx].x = this.hookrightx + this._heap.dx;

			this.vines.left[this._heap.linkIdx].y = this.hooky + this._heap.dy;
			this.vines.right[this._heap.linkIdx].y = this.hooky + this._heap.dy;

			this._heap.dy += 8; // get to next link in a natural hang
		}
		this.ridebox.x = this.sprite.x;
		this.ridebox.y = this.sprite.y;
	}
