// commonplayer.cpp: Beispielspieler für Asteroids (Gemeinsame Funktionen)
// Matthias Fuchs
// Original: Harald Bögeholz / c't

#include "commonplayer.h"
#include "game.h"

namespace CommonPlayer
{

void InitializeDistanceVectors(GameObject** closestObj, GameObject** attackObj, GameObject* currObj, Ship* ship, ObjectManager* objm, int* currPrio, int* currDist)
{
	// Ganz wichtig, dass obj->dist am Anfang ermittelt wird!
	currObj->dist = currObj->pos - ship->pos;
	ModuloScreen(&currObj->dist);
	CalculateLeadingShot(currObj, ship);
	CalculateTargetPriority(currObj, ship, objm);
					
	if (currObj->prio > *currPrio)
	{
		*currPrio = currObj->prio;
		*attackObj = currObj;
	}
	if (currObj->dist.length() < *currDist)
	{ 
		*currDist = (int) currObj->dist.length();
		*closestObj = currObj;
	}  	
}

float CalculateLeadingShot(GameObject* obj, Ship* ship)
{	
	Vector2 v_rel, v_rel_norm, dist_norm;
	float h, beta, alpha;
	
	v_rel = obj->vel - ship->vel; // Relativgeschwindigkeit des Objektes bzgl. Schiff
	dist_norm = obj->dist + obj->vel; // experimentell noch geschw. drauf 
	v_rel_norm = v_rel; 
	
	dist_norm.normalize();
	v_rel_norm.normalize();
	
	// Skalarprodukt mal Vorzeichen vom Kreuzprodukt
	beta = acos(dist_norm * v_rel_norm) * sgn(v_rel_norm.cross(dist_norm));
	
	h = v_rel.length() * sin(beta);
	alpha = asin(h / SHOT_VELOCITY);
	
	obj->att_flighttime = obj->dist.length() / (v_rel.length() * cos(beta) + SHOT_VELOCITY * cos(alpha));
	
	// Das hier nochmal angucken
	obj->att_dist = obj->dist + obj->vel * obj->att_flighttime;
	ModuloScreen(&obj->att_dist);
	
	if (fabs(h) <= SHOT_VELOCITY)
		obj->att_alpha = alpha;
	else
		obj->att_alpha = 0;
		
	return obj->att_alpha;
			
} 

float FiringAccuracy(GameObject* obj)
{
	if (obj->att_dist.length() > 0)
		return asin(obj->radius / obj->att_dist.length());
	return 0.07;
}


void CalculateTargetPriority(GameObject* obj, Ship* ship, ObjectManager* objm)
{
	int Priority = 0;
	bool found;
	
	// Relative Geschwindigkeit Ship <--> Objekt mittels Skalarprodukt
	// projeziert auf den genormten Richtungsvektor
	Vector2 v_rel = obj->vel - ship->vel;
	Vector2 v_rel_proj = v_rel.projectOn(obj->dist);
	float emergency_factor = v_rel_proj.length() / obj->dist.length();
	
	if (obj->objecttype == TYPE_ASTEROID && 
		emergency_factor > 0.016 && // Distanz-Test: ... || obj->dist.length() < 150
		obj->dist * v_rel < 0 && // Skalarprodukt < 0, bewegt sich drauf zu 
		flybyDist(obj, ship) < 20 + obj->radius + ship->radius)
	{
		Priority += (int) floor(20000 * emergency_factor);
		/*
		printf("Emergency! ");
		if (v_rel_proj.length() / obj->dist.length() > 0.016) printf("SPEED! ");
		if ((obj->dist.length() < 150)) printf ("RANGE! ");
		printf("d: %.0f, vr: %.2f, rt: %.3f, fb: %.0f tag: %d p: %d\n", obj->dist.length(), v_rel_proj.length(), v_rel_proj.length() / obj->dist.length(), flybyDist(obj, ship), obj->tag, obj->prio);
		*/
	}
	
	// Distanz des Objekts *wenn Schuss einschlägt* (nicht tatsächliche)
	//Das Zeug funktioniert noch nicht richtig. Nur feuern wenns von der Reichweite auch passt
	Priority += (int) floor(exp((200 - obj->att_dist.length()) * 0.002) * 400); 
	if (obj->att_dist.length() > 500) Priority -= 300; 
	//if (obj->att_flighttime > FRAMERATE * 1.2) Priority -= 800;
	
	// Drehwinkel des Schiffes bezüglich des Objekts 
	Priority -= (int) fabs(obj->dist.vectorAngle(&ship->view)) * 80;
	if (ship->vel.length() > 2)
		Priority -= (int) fabs(obj->dist.vectorAngle(&ship->vel)) * 40;
	
	// Objekt-Typ-spezifisches Offset: Punktezahl für Abschuss
	if (obj->objecttype == TYPE_SAUCER) Priority += 400;
	if (obj->objecttype == TYPE_ASTEROID) Priority += obj->points * 2;
	
	// Schon mal drauf geschossen
	if (obj->objecttype == TYPE_ASTEROID && obj->radius == RADIUS_SMALL)
	{
		found = false;
		for (int i = 0; i < objm->count(); i++)
		{
			if (obj->tag == (*objm)[i]->tag)
			{
				found = true;
				break;
			}
		}
		if (found) Priority -= 400;
	}
	
	// Alte Priorität mit reinnehmen bringt irgendwie nix
	//Priority += (int) floor(obj->prio * 0.9);
		
	obj->prio = Priority;
}

float flybyDist(GameObject* obj, Ship* ship)
{
	// Emergency: Kollisionskurs berechnen (Skalarmultiplikation und 
	// Hesse'sche Normalform: r * u = d)
	Vector2 v_rel = obj->vel - ship->vel; 
	Vector2 v_rel_norm(v_rel.y, -v_rel.x);
	v_rel_norm.normalize();
	// Nimm mal die Modulo-position statt absolute!
	return fabs((ship->pos + obj->dist) * v_rel_norm - ship->pos * v_rel_norm);
}

bool ObjectCentered(Vector2& pos, float ratio)
{
	// Prüft, ob Objekt weit genug in Bildschirmmitte ist
	return (pos.x > SCREEN_MIN_X + SCREEN_SIZE_X * ratio && 
			pos.x < SCREEN_MAX_X - SCREEN_SIZE_X * ratio &&
			pos.y > SCREEN_MIN_Y + SCREEN_SIZE_Y * ratio &&
			pos.y < SCREEN_MAX_Y - SCREEN_SIZE_Y * ratio);
}


void SteerShipTo(const Vector2 target, Ship* ship, TaskList* sched)
{
	float gamma;
	Vector2 direc = target - ship->pos;
	ModuloScreen(&direc);
	gamma = normalizeAngle(direc.phase() - ship->omega());
	
	// Schiff in Zielrichtung drehen
	if (gamma >  ROTATION_ANGLE / (float) 2)
	{
		sched->plan(TASK_TURNLEFT);
		ship->ChangeWinkelbyte(1);
	}
	if (gamma < -ROTATION_ANGLE / (float) 2)
	{
   		sched->plan(TASK_TURNRIGHT);
   		ship->ChangeWinkelbyte(-1);
	}
	
   	if (fabs(gamma) < ROTATION_ANGLE)
   		sched->plan(TASK_THRUST);
}


} // end namespace
