/**
	ForceField
	
	@author Zapper
	
	
	Objects using this library must call _inherited on:
		- Construction
		
	This library provides force fields of two types (attractor/repulsor) that can be placed arbitrarily.
	The force for a X|Y point can then be calculated, taking into account all forces and their specific falloff.
*/


static const Library_ForceField_Attractor = 0;
static const Library_ForceField_Repulsor = 1;

local ForceField;


public func Construction()
{
	ForceField =
	{
		emitters = [], // can be both attractors and repulsors
		default_max_distance = nil,
		inversed = 0,
		max_emitter_count = nil
	};
	return _inherited(...);
}

public func SetDefaultForceFieldMaxDistance(int max_distance)
{
	ForceField.default_max_distance = max_distance;
}

public func SetDefaultForceFieldTTD(int time_to_die)
{
	ForceField.default_time_to_die = time_to_die;
}

public func SetInverseForceFields(bool is_inversed)
{
	if (is_inversed)
		ForceField.inversed = 1;
	else ForceField.inversed = 0;
}

public func SetMaxEmitterNumber(int amount)
{
	ForceField.max_emitter_count = amount;
}

private func Helper_GetTargetX() { return this.target->GetX(); }
private func Helper_GetTargetY() { return this.target->GetY(); }
private func Helper_GetX() { return this.X; }
private func Helper_GetY() { return this.Y; }

public func AddRepulsor(x_or_obj, y, int strength, int max_distance, int time_to_die)
{
	max_distance = max_distance ?? ForceField.default_max_distance;
	time_to_die = time_to_die ?? ForceField.default_time_to_die;
	strength = strength ?? 1000;
	
	var new_emitter = 
	{
		target = 0, // must be different from "nil"
		type = Library_ForceField_Repulsor, 
		max_distance = max_distance,
		time_to_die = time_to_die,
		time_start = FrameCounter(),
		strength = strength
	};
		
	
	if (GetType(x_or_obj) == C4V_C4Object)
	{
		new_emitter.target = x_or_obj;
		new_emitter["GetX"] = Library_ForceField.Helper_GetTargetX;
		new_emitter["GetY"] = Library_ForceField.Helper_GetTargetY;
	}
	else
	{
		new_emitter["X"] = x_or_obj;
		new_emitter["Y"] = y;
		new_emitter["GetX"] = Library_ForceField.Helper_GetX;
		new_emitter["GetY"] = Library_ForceField.Helper_GetY;
	}
	
	// if we have too many emitters, clean up the oldest one
	var emitter_count = 0, len = GetLength(ForceField.emitters);
	var inserted = false, oldest = new_emitter.time_start, oldest_index = 0;
	for (var i = 0; i < len; ++i)
	{
		var insert = false;
		if (ForceField.emitters[i]) 
		{
			++emitter_count;
			if (ForceField.emitters[i].time_start < oldest)
			{
				oldest = ForceField.emitters[i].time_start;
				oldest_index = i;
			}
			
			// overwrite old emitters with the same target
			if (ForceField.emitters[i].target && ForceField.emitters[i].target == new_emitter.target)
				insert = true;
		}
		else
			insert = true;
		
		if (insert && !inserted)
		{
			ForceField.emitters[i] = new_emitter;
			inserted = true;
		}
	}
	if (!inserted)
		PushBack(ForceField.emitters, new_emitter);
	
	// is it necessary to delete one emitter?
	if (emitter_count + 1 > ForceField.max_emitter_count)
	{
		ForceField.emitters[oldest_index] = nil;
	}
	
	return new_emitter;
}

public func AddAttractor(...)
{
	var att = AddRepulsor(...);
	att.type = Library_ForceField_Attractor;
	return att;
}

public func CalculateForce(int x, int y)
{
	if (x == nil)
	{
		x = GetX();
		y = GetY();
	}
	
	var total_x_dir = 0, total_y_dir = 0;
	var total_force = 0;
	var frame_counter = FrameCounter();
	
	for (var i = 0; i < GetLength(ForceField.emitters); ++i)
	{	
		var emitter = ForceField.emitters[i];
		if (!emitter) continue;
		
		// kill old emitters
		var remaining_time = (emitter.time_start + emitter.time_to_die) - frame_counter;
		if (remaining_time <= 0 || emitter.target == nil)
		{
			ForceField.emitters[i] = nil;
			continue;
		}
		
		var type = emitter.type ^ ForceField.inversed; // this is an exclusive OR
		var x_dir = emitter->GetX() - x;
		var y_dir = emitter->GetY() - y;
		var distance = Sqrt(x_dir * x_dir + y_dir * y_dir);
		distance = Max(1, distance);
		// normalize further
		x_dir = 1000 * x_dir / distance;
		y_dir = 1000 * y_dir / distance;
		var normalized_distance = 1000 * distance / emitter.max_distance;
		
		// calculate actual force of the emitter
		var force = 0;
		if (type == Library_ForceField_Repulsor)
			force = -BoundBy(50000 / normalized_distance, 0, 1000);
		else // attractor
		{
			// -((x - 300)^2)/250 + x*2
			normalized_distance = BoundBy(normalized_distance, 0, 1000);
			force = - ((normalized_distance - 300)**2) / 250 + normalized_distance*2;
		}
		
		// now adjust the strength depending on several factors
		force = emitter.strength * force / 1000;
		force = remaining_time * force / emitter.time_to_die;
		emitter.last_force = force;
		
		total_x_dir += x_dir * force;
		total_y_dir += y_dir * force;
		total_force += Abs(force);
	}
	var divisor = total_force;
	if (!total_force) divisor = 1;
	
	return [total_x_dir / divisor, total_y_dir / divisor, total_force];
}

public func DebugShowForceField(int amount)
{
	if (amount == nil) amount = 200;
	
	for (var emitter in ForceField.emitters)
	{
		 if (!emitter) continue;
		 var force = Abs(emitter.last_force);
		 var speed = force / 60;
		 if (emitter.type == Library_ForceField_Repulsor)
		 {
		 	CreateParticle("SphereSpark", emitter->GetX() - GetX() + RandomX(-2, 2), emitter->GetY() - GetY() + RandomX(-2, 2), PV_Random(-speed, speed), PV_Random(-speed, speed), 60, {Prototype=Particles_Flash(), Size=PV_Linear(5, 0), R=255, G=0, B=0}, force / 20);
		 }
		 else
		 	CreateParticle("SphereSpark", emitter->GetX() - GetX() + RandomX(-2, 2), emitter->GetY() - GetY() + RandomX(-2, 2), PV_Random(-speed, speed), PV_Random(-speed, speed), 60, {Prototype=Particles_Flash(), Size=PV_Linear(5, 0), R=0, G=0, B=255}, force / 20);
	}
	
	if (amount > 0)
		ScheduleCall(this, "DebugShowForceField", 5, 1, amount - 1);
}