// player.cpp: Beispielspieler für tmpbobjs
// Harald Bögeholz / c't

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <math.h>

#include <winsock2.h>

#include "player.h"

int normalize(int v, int nv)
{
	while (v < -nv)  v += nv*2;
	while (v > nv-1) v -= nv*2;
	return v;
}

void Player::Run(void)
{
	FramePacket frame;
	KeysPacket keys;
	GameStatus game;
	char prevframe = 0;
	int t = 0, tx, ty;
	game.init();

	for (;;)
	{
		++t;         // Zeit
		++keys.ping; // jedes gesendete Päckchen erhält eine individuelle Nummer zur Latenzmessung
		SendPacket(keys);
		ReceivePacket(frame);

		if (frame.frameno != ++prevframe || frame.ping != keys.ping)
		{
			printf("Latenz %d. %d Frames verloren.\n", keys.ping - frame.ping, frame.frameno - prevframe);
			prevframe = frame.frameno;
		}

		InterpretScreen(frame, game);
		UpdateBObjects(game);

		keys.clear();   // alle Tasten loslassen
		int min_dist = INT_MAX; // 0x7fffffff;
		int colltime, min_colltime = INT_MAX, min_shotcolltime = INT_MAX;
		int min_dx = 0, min_dy = 0, min_dxc = 0, min_dyc = 0;
		int min_x = 0, min_y = 0, min_xc = 0, min_yc = 0;

		if (game.ship_present)
		{
			for (int i=0; i<MAX_BOBJECTS; i++)
			if (game.bobjects[i].valid()) 
			{   // gefährlichstes Objekt suchen
				if (game.bobjects[i].type==SHOT_X)
				  colltime=game.bobjects[i].collides(game.ship_x, game.ship_y, 30); // war 50
				else
				  colltime=game.bobjects[i].collides(game.ship_x, game.ship_y, 0); 
				
				int xp = game.bobjects[i].aimx;
				int dx = normalize(xp - game.ship_x, 512);

				int yp = game.bobjects[i].aimy;
				int dy = normalize(yp - game.ship_y, 384);

				int dist = dx*dx+dy*dy;  // Quadrat des Abstands zu diesem Objekt

				if (game.bobjects[i].type!=SHOT_X) {
					switch (game.bobjects[i].sf)
					{	// Abstand um den ungefähren Radius des Asteroiden korrigieren
						case 0:  // großer Asteroid
							dist -= 40*40;
							break;
						case 15: // mittlerer Asteroid
							dist -= 20*20;
							break;
						case 14: // kleiner Asteroid
							dist -= 10*10;
							break;
					}

					// Berechnen, wann ein jetzt abgefeuerter Schuss das Objekt erreichen würde
					if (game.bobjects[i].getimpactpos(game.ship_x, game.ship_y, game.angship, &tx, &ty)) {
						dx = normalize(tx - game.ship_x, 512);
						dy = normalize(ty - game.ship_y, 384);
						int dist = dx*dx+dy*dy;						
					}

				}
					
				// Objekte auf Kollisionskurs gesondert behandeln
				if (colltime != -1) {
					if (game.bobjects[i].type==SHOT_X) {
						if ((colltime < min_shotcolltime) && (colltime>=0))
							min_shotcolltime = colltime;
					} else {
						if ((colltime < min_colltime) && (colltime>=0)) {
							min_colltime = colltime;
							min_dxc = dx; min_xc = xp;
							min_dyc = dy; min_yc = yp;
						}
					}
				}

				// Das näheste Objekt suchen (Schüsse ignorieren, da diese nicht abgeschossen werden können)
				if ((dist < min_dist) && (game.bobjects[i].type!=SHOT_X)) {
					min_dist = dist;
					min_dx = dx; min_x = xp;
					min_dy = dy; min_y = yp;
				}
			}

			if (min_colltime != INT_MAX) {
				min_dx = min_dxc; min_x = min_xc;
				min_dy = min_dyc; min_y = min_yc;
			}

			/*if (min_shotcolltime != INT_MAX) 
				printf("Missile on collision course; TOA %d\n", min_shotcolltime);*/


			if ((game.saucer_present) && (min_colltime >= 20))
			{
				int dx = normalize(game.saucer_x - game.ship_x, 512);
				int dy = normalize(game.saucer_y - game.ship_y, 384);
				int dist = dx*dx+dy*dy;
				switch (game.saucer_size)
				{	// Abstand um den ungefähren Radius des UFOs korrigieren
				case 15: // großes UFO
					dist -= 20*12;
					break;
				case 14: // kleines UFO
					dist -= 10*6;
					break;
				}
				if (min_colltime > 100)//(dist < min_dist)
				{
					min_dist = dist;
					min_dx = dx;
					min_dy = dy;
				}
			}

			// Schiff in Richtung auf das nächstgelegene Objekt drehen
			// mathematisch wird hier das Kreuzprodukt aus den Vektoren 
			// ship_dx/y/0 und min_dx/y/0 berechnet
			if (game.ship_dx * min_dy - game.ship_dy * min_dx > 0)
				keys.left(true);
			else
				keys.right(true);

			if ((min_dist < 27*27) || (min_colltime<=4) || (0<min_shotcolltime) && (min_shotcolltime<=5)) // Flucht, wenn Kollision unausweichlich
				keys.hyperspace(true);

			if ((min_dist > 400*400) && (game.saucer_present)) // beschleunigen, wenn nichts in der Nähe
				keys.thrust(true);

			if ((t % 2 == 0) /*&& (abs(game.ship_dx * min_dy - game.ship_dy * min_dx) < TARGET_LOCKED)*/) // Feuerknopf drücken, so schnell es geht
				keys.fire(true);
		}
	}
}


void BallisticObject::set(int x, int y, int type, int sf)
{
	this->x0 = x; lastx=x;
	this->y0 = y; lasty=y;
	this->type = type;
	this->sf = sf;

	//valid = true;
	//vx = 0;
	//vy = 0;
	nxy1 = 0;
	step = 0;
}

bool BallisticObject::valid(void)
{
	return type!=TYPE_INV;
}


// - Vertraut darauf, dass jeder Frame bearbeitet wird
// - Verliert Asteroiden wenn diese "wrappen"
int BallisticObject::update(int fx, int fy, int ftype, int fsf)
{
	int i;

	if ((ftype != type) || (fsf != sf)) return 0;
	if ((abs(fx - lastx) > MAX_VXY) || (abs(fy - lasty) > MAX_VXY)) return 0;

	if (step == 0) {
		// Noch keine zweiten Koordinaten vorhanden -> als mögliche zweite Koordinaten eintragen
		x1[nxy1] = fx;
		y1[nxy1++] = fy;
		return 1;
	} 
	else if (step == 1) {
		// Noch keine Geschwindigkeit vorhanden
		for (i=0; i<nxy1; i++) {
			if ((x1[i]-x0 == fx-x1[i]) && (y1[i]-y0 == fy-y1[i])) {
				//vx = x1[i]-x0;
				//vy = y1[i]-y0;
				lastx = fx;
				lasty = fy;
				return 2;
			} 
			else return 0;
		}
	}	
	else if (step >= 2) {
		// Geschwindigkeit vorhanden -> überprüfen
		if ((abs(fx-lastx) <= abs(vx()+3)) && (abs(fy-lasty) <= abs(vy()+3))) {
			lastx = fx;
			lasty = fy;
			return 2;
		} 
		else {
			return 0;
		}
	}

	return 0;
}

float BallisticObject::vx(void)
{
	if (step!=0) return float(lastx - x0) / float(step); else return 0;
}

float BallisticObject::vy(void)
{
	if (step!=0) return float(lasty - y0) / float(step); else return 0;
}

float BallisticObject::v(void)
{
	if (step==0) return 0;
	return sqrt(vx()*vx() + vy()*vy());
}


int BallisticObject::interpolate_x(int t)
{
	return int(lastx+vx()*t);
}

int BallisticObject::interpolate_y(int t)
{
	return int(lasty+vy()*t);
}

#define PI 3,14159265359

bool BallisticObject::getimpactpos(int sx, int sy, float angship, int *x, int *y)
{
	int mindiff = INT_MAX;

	for(int t=1000; t>=0; t--) {
		int ax = interpolate_x(t);
		int ay = interpolate_y(t);
		int dist = int(sqrt(float((ax-sx)*(ax-sx) + (ay-sy)*(ay-sy))));
		float angastr=atan2(float(ay-sy), float(ax-sx));
		float angdiff=angship-angastr;
		if (angdiff>PI) angdiff=2*PI-angdiff;
		// #########################
		int st = dist / SHOT_SPEED + angdiff / 0.1496; // "Drehzeit" + "Schuss-Flugzeit"
		// #########################
		int diff = abs(t-st);
		if (diff<mindiff) {
			mindiff = diff;
			*x = ax;
			*y = ay;
		}
		/*if (diff>mindiff+10) {
			return (mindiff != INT_MAX);
		}*/
	}
	return (mindiff != INT_MAX);
}

void swap(int *int1, int *int2)
{
	int t;

	t=*int1;
	*int1=*int2;
	*int2=t;
}


int CollCheck(int x, int y, int astx, int asty, float vx, float vy, int tolerance)
{
	int txmax=0; 
	int tymax=0;
	int txmin=0;
	int tymin=0;

	int x1=x-tolerance;
	int x2=x+tolerance;
	if(vx!=0) {
		txmax=(x1-astx)/vx;
		txmin=(x2-astx)/vx;
	} else if ((x1<=astx) && (astx<=x2)) {
		txmax=INT_MAX;
	}

	int y1=y-tolerance;
	int y2=y+tolerance;
	if(vy!=0) {
		tymax=(y1-asty)/vy;
		tymin=(y2-asty)/vy;
	} else if ((y1<=asty) && (asty<=y2)) {
		tymax=INT_MAX;
	}

	if(txmax<txmin) 
		swap(&txmax, &txmin);
	if(tymax<tymin) 
		swap(&tymax, &tymin);

	if((tymax>=txmin) && (tymax<=txmax) || (txmax>=tymin) && (txmax<=tymax)) {
		int tmin=__max(txmin, tymin);
		if (tmin>=0) return tmin; else return -1;
	} 
	else return -1;
}

int min4(int a, int b, int c, int d)
{
	if (a<0) a=INT_MAX;
	if (b<0) b=INT_MAX;
	if (c<0) c=INT_MAX;
	if (d<0) d=INT_MAX;

	if ((a<b) && (a<c) && (a<d)) return a;
	if ((b<a) && (b<c) && (b<d)) return b;
	if ((c<a) && (c<b) && (c<d)) return c;
	if ((d<a) && (d<b) && (d<c)) return d;
}

#define MAX_TDIFF 10

int BallisticObject::collides(int x, int y, int tolerance)
{	
	int res=-1;
	int tbottom, ttop, tleft, tright, tmin;

	// Default aiming position - current position
	aimx=lastx;
	aimy=lasty;

	if (tolerance == 0) {
		if (sf==0) tolerance=40+15;
		else if (sf==15) tolerance=20+15;
		else if (sf==14) tolerance=10+15;
		else tolerance= 70;
	}

	if(step>=2) {
		// Collision check with real coordinates
		res=CollCheck(x, y, lastx, lasty, vx(), vy(), tolerance);
		if (res==-1)
		{
			// Check which screen border is reached first
			ttop    = (   0 - lasty)/vy();
			tbottom = ( 768 - lasty)/vy();
			tleft   = (   0 - lastx)/vx();
			tright  = (1024 - lastx)/vx();
			tmin    = min4(ttop, tbottom, tleft, tright);

			// "Mirror" the asteroid using that border
			if (tmin==ttop) {
				aimy=aimy+768;
				if (tleft-ttop  < MAX_TDIFF) aimx=aimx+1024;
				if (tright-ttop < MAX_TDIFF) aimx=aimx-1024;
			}
			else if (tmin==tbottom) {
				aimy=aimy-768;
				if (tleft-tbottom  < MAX_TDIFF) aimx=aimx+1024;
				if (tright-tbottom < MAX_TDIFF) aimx=aimx-1024;
			}
			else if (tmin==tleft) {
				aimx=aimx+1024;
				if (ttop-tleft  < MAX_TDIFF)   aimy=aimy+768;
				if (tbottom-tleft < MAX_TDIFF) aimy=aimy-768;
			}
			else if (tmin==tright) {
				aimx=aimx-1024;
				if (ttop-tright  < MAX_TDIFF)   aimy=aimy+768;
				if (tbottom-tright < MAX_TDIFF) aimy=aimy-768;
			}

			// Repeat collision check with new coordinates
			res=CollCheck(x, y, aimx, aimy, vx(), vy(), tolerance);
		}
	}
	return res;
}

int GameStatus::asteroidcount(void)
{
	int res=0;
	for(int i=0; i<MAX_BOBJECTS; i++)
		if(bobjects[i].valid()) 
			res++;
	return res;
}

void Player::UpdateBObjects(GameStatus& game)
{
	int i, j, res;

	//printf("%d bobjects on screen, %d in memory\n", game.ntmpbobjs, game.asteroidcount());

	for (i=0; i<game.ntmpbobjs; i++)	game.tmpbobjs[i].found=false;
	for (j=0; j<MAX_BOBJECTS; j++)		game.bobjects[j].found=false;

	for (i=0; i<game.ntmpbobjs; i++) {
		for (j=0; j<MAX_BOBJECTS; j++) {
			if(game.bobjects[j].valid()) {
				res=game.bobjects[j].update(game.tmpbobjs[i].x0, game.tmpbobjs[i].y0, game.tmpbobjs[i].type, game.tmpbobjs[i].sf);
				if (res!=0) game.bobjects[j].found=true; // vorhandener BallisticObject wird als gefunden betrachtet, auch wenn unsicher
				if (res==2) game.tmpbobjs[i].found=true; // neuen BallisticObject nur dann als gefunden betrachten wenn 100% sicher
			}
		}
	}

	// Nicht mehr gefundene Asteroiden aus Liste löschen
	for (j=0; j<MAX_BOBJECTS; j++) {
		if(game.bobjects[j].valid()) {
			if(!game.bobjects[j].found) { 
				if (game.bobjects[j].step > 50) {
					game.totspeeds[game.bobjects[j].type] = game.totspeeds[game.bobjects[j].type] + game.bobjects[j].v();
					game.spdcount[game.bobjects[j].type] ++;
					if (game.spdcount[SHOT_X]==100) {
						printf("speed: %f", game.totspeeds[SHOT_X] / float(game.spdcount[SHOT_X]));
					}
				}
				game.bobjects[j].type=TYPE_INV;
			}
			else
				game.bobjects[j].step++;
		}
	}

	// Nicht in Liste gefundene Asteroiden hinzufügen
	for (i=0; i<game.ntmpbobjs; i++) {
		if(!game.tmpbobjs[i].found) {
			for(j=0; j<MAX_BOBJECTS; j++) {
				if(!game.bobjects[j].valid()) { // TODO: initialize ???
					game.bobjects[j].set(game.tmpbobjs[i].x0, game.tmpbobjs[i].y0, game.tmpbobjs[i].type, game.tmpbobjs[i].sf);					
					break;
				}
			}
		}
	}

	/*for(j=0; j<10; j++) {
		if ((game.bobjects[j].valid()) && (game.bobjects[j].type == SHOT_X)) {
			printf("shot %2.2d: speed %3.3f\n", j, game.bobjects[j].v());
		}
	}
	printf("\n");*/
}

// Vor dem "Step", found auf false setzen
// Nach dem Step nicht mehr vorhandene Asteroiden löschen
// Nur Asteroiden hinzufügen wenn sich Anzahl ändert

void GameStatus::clear(void)
{
	ship_present = false;
	saucer_present = false;
	small_saucer = false;
	large_saucer = false;
	ntmpbobjs = 0;
}

void GameStatus::init(void)
{
	clear();

	for (int i=0; i<MAX_BOBJECTS; i++) bobjects[i].type=TYPE_INV;
	for (int i=1; i<=5; i++) { totspeeds[i]=0; spdcount[i]=0; }
}

void Player::ReceivePacket(FramePacket &packet)
{
	sockaddr_in sender;
	int sender_size = sizeof sender;
	fd_set readfds, writefds, exceptfds;

	do
	{
		FD_ZERO(&readfds);
		FD_ZERO(&writefds);
		FD_ZERO(&exceptfds);
		FD_SET(sd, &readfds);
		FD_SET(sd, &exceptfds);
		select(sd+1, &readfds, &writefds, &exceptfds, NULL);
		int bytes_received = recv(sd, (char *)&packet, sizeof packet, 0);
		if (bytes_received != sizeof packet)
		{
			int err = WSAGetLastError();
			fprintf(stderr, "Fehler %d bei recvfrom().\n", err);
			exit(1);
		}
		FD_ZERO(&readfds);
		FD_ZERO(&writefds);
		FD_ZERO(&exceptfds);
		FD_SET(sd, &readfds);
		timeval zero;
		zero.tv_sec = zero.tv_usec = 0;
		select(sd+1, &readfds, &writefds, &exceptfds, &zero);
	} while(FD_ISSET(sd, &readfds));
}

void Player::SendPacket(KeysPacket &packet)
{
	sockaddr_in server;
	memset(&server, 0, sizeof server);
	server.sin_family = AF_INET;
	server.sin_port = htons(1979);
	server.sin_addr.s_addr = server_ip;
	if (sizeof packet != sendto(sd, (char *)&packet, sizeof packet, 0, (sockaddr*)&server, sizeof server))
	{
		int err = WSAGetLastError();
		if (err != WSAEWOULDBLOCK)
		{
			fprintf(stderr, "Fehler %d bei sendto().\n", err);
			exit(1);
		}
	}
}

// ### KeysPacket
KeysPacket::KeysPacket(void)
{
	signature[0] = 'c';
	signature[1] = 't';
	signature[2] = 'm';
	signature[3] = 'a';
	signature[4] = 'm';
	signature[5] = 'e';
	keys = '@';
	ping = 0;
}

void KeysPacket::clear(void)
{
	keys = '@';
}

void KeysPacket::hyperspace(bool b)
{
	if (b)
		keys |= KEY_HYPERSPACE;
	else
		keys &= ~KEY_HYPERSPACE;
}

void KeysPacket::fire(bool b)
{
	if (b)
		keys |= KEY_FIRE;
	else
		keys &= ~KEY_FIRE;
}

void KeysPacket::thrust(bool b)
{
	if (b)
		keys |= KEY_THRUST;
	else
		keys &= ~KEY_THRUST;
}

void KeysPacket::left(bool b)
{
	if (b)
	{
		keys |= KEY_LEFT;
		right(false);
	}
	else
		keys &= ~KEY_LEFT;
}

void KeysPacket::right(bool b)
{
	if (b)
	{
		keys |= KEY_RIGHT;
		left(false);
	}
	else
		keys &= ~KEY_RIGHT;
}

void Player::InterpretScreen(FramePacket &packet, GameStatus& game)
{
	unsigned short *vector_ram = (unsigned short *)packet.vectorram;
	int dx, dy, sf, vx, vy, vz, vs;
	int v1x = 0;
	int v1y = 0;
	int shipdetect = 0;

	game.clear(); // löscht nur die temporären Daten
	if ((unsigned char)packet.vectorram[1] != 0xe0 && (unsigned char)packet.vectorram[1] != 0xe2)
		return; // sollte nicht vorkommen; erster Befehl ist immer ein JMPL

	int pc = 1;
	for (;;)
	{
		int op = vector_ram[pc] >> 12;
		switch (op)
		{
		case 0xa: // LABS
			vy = vector_ram[pc] & 0x3ff;
			vx = vector_ram[pc+1] & 0x3ff;
			vs = vector_ram[pc+1] >> 12;
			break;
		case 0xb: // HALT
			return;
		case 0xc: // JSRL
			switch (vector_ram[pc] & 0xfff)
			{
			case 0x8f3:
				game.tmpbobjs[game.ntmpbobjs++].set(vx, vy, ASTR_1, vs);
				break;
			case 0x8ff:
				game.tmpbobjs[game.ntmpbobjs++].set(vx, vy, ASTR_2, vs);
				break;
			case 0x90d:
				game.tmpbobjs[game.ntmpbobjs++].set(vx, vy, ASTR_3, vs);
				break;
			case 0x91a:
				game.tmpbobjs[game.ntmpbobjs++].set(vx, vy, ASTR_4, vs);
				break;
			case 0x929:
				game.saucer_present = true;
				game.saucer_x = vx;
				game.saucer_y = vy;
				game.saucer_size = vs;
				if (vs=15) game.large_saucer = true;
				if (vs=14) game.small_saucer = true;
				break;
			}  
			break;
		case 0xd: // RTSL
			return;
		case 0xe: // JMPL
			/*
			pc = vector_ram[pc] & 0xfff;
			break;
			*/
			return;
		case 0xf: // SVEC
			/*
			dy = vector_ram[pc] & 0x300;
			if ((vector_ram[pc] & 0x400) != 0)
				dy = -dy;
			dx = (vector_ram[pc] & 3) << 8;
			if ((vector_ram[pc] & 4) != 0)
				dx = -dx;
			sf = (((vector_ram[pc] & 8) >> 2) | ((vector_ram[pc] & 0x800) >> 11)) + 2;
			vz = (vector_ram[pc] & 0xf0) >> 4;
			*/
			break;
		default:
			dy = vector_ram[pc] & 0x3ff;
			if ((vector_ram[pc] & 0x400) != 0)
				dy = -dy;
			dx = vector_ram[pc+1] & 0x3ff;
			if ((vector_ram[pc+1] & 0x400) != 0)
				dx = -dx;
			sf = op;
			vz = vector_ram[pc+1] >> 12;
			if (dx == 0 && dy == 0 && vz == 15)
				game.tmpbobjs[game.ntmpbobjs++].set(vx, vy, SHOT_X, 0);
			if (op == 6 && vz == 12 && dx != 0 && dy != 0)
			{
				switch (shipdetect)
				{
				case 0:
					v1x = dx;
					v1y = dy;
					++shipdetect;
					break;
				case 1:
					game.ship_present = true;
					game.ship_x = vx;
					game.ship_y = vy;
					game.ship_dx = v1x - dx;
					game.ship_dy = v1y - dy;
					game.angship = atan2(float(game.ship_dy), float(game.ship_dx));
					++shipdetect;
					break;
				}
			}
			else if (shipdetect == 1)
				shipdetect = 0;

			break;
		}
		if (op <= 0xa)
			++pc;
		if (op != 0xe) // JMPL
			++pc;
	}   

}

