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

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

#if defined(WINDOWS)
//#include <winsock2.h>
#else
// 2 Includes für socket()
#include <sys/types.h>
#include <sys/socket.h>
// 2 Includes für inet_addr()
#include <netinet/in.h>
#include <arpa/inet.h>
// 2 Includes für fcntl()
#include <unistd.h>
#include <fcntl.h>
// für memset()
#define INVALID_SOCKET -1
#define WSAGetLastError() errno
#endif

#include "player.h"
#include <time.H>

void Player::Run(void)
{
	FramePacket frame;
	KeysPacket keys;
	GameStatus game;
	char prevframe = 0;
	int t = 0;
	vector<KeysPacket> old_keys;
	unsigned int off_keys=0;

	for (;;)
	{
		if(off_keys<old_keys.size()&&off_keys>1)
			old_keys.erase(old_keys.begin(), old_keys.begin()+(off_keys-1));

		++t;         // Zeit
		++keys.ping; // jedes gesendete Päckchen erhält eine individuelle Nummer zur Latenzmessung
		
		old_keys.push_back(keys);
		SendPacket(keys);
/* nachträgliche, optionale Protokollerweiterung: Einmalig den Spielernamen schicken
   Bringt die ursprüngliche MAME-Version zum Abbruch; ist zum Spielen übers Internet gegen
   den Server asteroids.heise.de gedacht.
		if (t == 10)
			SendPlayerName();
*/
		ReceivePacket(frame);

		for(off_keys=0; off_keys<old_keys.size()&&old_keys[off_keys].ping!=frame.ping; off_keys++);
		if(off_keys>=old_keys.size()) {
			off_keys=0;
		}
		if(off_keys>=old_keys.size()) continue;

		static bool bLastR=false, bLastL=false;
		static int iShipPresent=-11;

		GL_mul=1.f;
		GL_latence=keys.ping - frame.ping;
		int goround=frame.frameno - prevframe;
		if(goround<0)
			goround=(goround+256)%256;
		if(GL_latence<0)
			GL_latence=(GL_latence+256)%256;


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

		bLastR=bLastL=false;

		InterpretScreen(frame, game);

		game.finalAstroid();

		if (game.ship.ship_present&&t-iShipPresent>2) {
			if(old_keys[off_keys].left()) {
				bLastL=true;
				game.ship.setDir(goround);
			}
			if(old_keys[off_keys].right()){
				bLastR=true;
				game.ship.setDir(-goround);
			}
			if(!old_keys[off_keys].right()&&!old_keys[off_keys].left())
				game.ship.setDir(0);
		}
		else if(!game.ship.ship_present) {
			iShipPresent=t;
		}

		keys.clear();   // alle Tasten loslassen

		int min_dist = 0x7fffffff;
		int min_dx = 0;
		int min_dy = 0;
		if (game.ship.ship_present)
		{

			int num=0;
			int mindis=0x7fffffff;
			Vector d;
			for(int i=MAX_ASTEROIDS; i<2*MAX_ASTEROIDS; i++)
				if(game.asteroids[i].status!=game.asteroids[i].NONE/*&&game.asteroids[i].ast.bActive*/) {
					game.objptrs[num++] = &game.asteroids[i].ast;
				}

			if(game.saucer_present) {
				game.objptrs[num++] = &game.saucer;
			}
			else
				game.saucer.size=-1;

			for(int i=0; i<num; i++)  {
				if(num<2)
					game.objptrs[i]->shot_time=game.objptrs[i]->shot_time*0.8-1;
			}
			game.circle.clear(game.objptrs,num);

			int dir;
			int dur;
			static int dur2=-1;
			{
				int old=game.ship.dir;
				while(game.ship.dir<0) game.ship.dir+=NUM_CIRCLE;
				dir=game.circle.calc2(game.objptrs,num,game.ship,!old_keys[off_keys].fire(),t,dur,dur2,dur2>0);
				//dur2=dur=1;
				dur2=min(dur2,15);
				dur =min(dur, 15);
				game.ship.dir=old;
			}
			for(int i=0; i<num; i++)  {
				if(game.objptrs[i]->shot_time<0&&game.objptrs[i]->getPos(1).dist2()-game.objptrs[i]->r*game.objptrs[i]->r-game.ship.r*game.ship.r<mindis) {
					d=game.objptrs[i]->getPos(1);
					mindis=game.objptrs[i]->getPos(1).dist2()-game.objptrs[i]->r*game.objptrs[i]->r-game.ship.r*game.ship.r;}
			}

			static int tdir=0;
			if(tdir==0) {
				if(dir%2==1)
					tdir=dur;
				else if(dir%2==-1)
					tdir=-dur;
			}

			if(tdir>0) {
				keys.right(true);
				tdir--;
			}
			else if(tdir<0) {
				keys.left(true);
				tdir++;
			}
			dur2--;
			if(mindis<32)
			{keys.hyperspace(true);}

			if(!(dir&2)) {
				tdir=0;
				dur2=-1;
			}

			keys.fire((!(dir&2)));

		}
	}

}

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

	game.clear();

	/* Vektor-RAM in 16-Bit-Worte konvertieren. War in der ersten Version mal ein sportlicher
	Typecast: unsigned short *vector_ram = (unsigned short*)packet.vectorram;
	Das klappt aber nur auf Little-Endian-Systemen, daher besser portabel: */
	for (int i=0; i<512; ++i)
		vector_ram[i] = (unsigned char)packet.vectorram[2*i] | (unsigned char)packet.vectorram[2*i+1] << 8;

	if (vector_ram[0] != 0xe001 && vector_ram[0] != 0xe201)
		return; // sollte nicht vorkommen; erster Befehl ist immer ein JMPL

				/*{FILE *fP=fopen("cab.txt","a");
				fprintf(fP,"---------------------\n");
				fclose(fP);}*/
	int pc = 1;
	while (pc < 512)
	{
		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.setAstroid(vx, vy, 1, vs);
				break;
			case 0x8ff:
				game.setAstroid(vx, vy, 2, vs);
				break;
			case 0x90d:
				game.setAstroid(vx, vy, 3, vs);
				break;
			case 0x91a:
				game.setAstroid(vx, vy, 4, vs);
				break;
			case 0x929:
				game.saucer_present = true;
				if(vs!=game.saucer.size)
					game.saucer = Saucer(Vector(vx,vy)-game.ship.pos,vs);
				else
					game.saucer.Set(Vector(vx,vy)-game.ship.pos);
				break;
			//case 2860:case 2861: case 2130: case 2862: case 2781: case 2669: case 2881: case 2888: case 2866: case 2895: case 2915: case 2874: case 2907: break; 
			case 2176: case 2198: case 2229: case 2256:				
				game.asteroids[game.naster++].status=GameStatus::TrackAst::CABOOM;
				/*cout<<"CAMBOOOM: "<<(vector_ram[pc] & 0xfff)<<endl;
				{FILE *fP=fopen("cab.txt","a");
				fprintf(fP,"%d\n",(vector_ram[pc] & 0xfff));
				fclose(fP);}*/
				break;
			default:
				if((vector_ram[pc] & 0xfff)>=2680&&(vector_ram[pc] & 0xfff)<=2915) break;
				//{FILE *fP=fopen("alt.txt","a");
				//fprintf(fP,"%d\n",(vector_ram[pc] & 0xfff));
				//fclose(fP);}
			//	cout<<"ALTERNATIVE: "<<(vector_ram[pc] & 0xfff)<<endl;
				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.shots[game.nshots++]=Shot(Vector(vx,vy));//.Set(Vector(vx, vy));
			if (op == 6 && vz == 12 && dx != 0 && dy != 0)
			{
				switch (shipdetect)
				{
				case 0:
					v1x = dx;
					v1y = dy;
					++shipdetect;
					break;
				case 1:
					game.ship.set(vx,vy,v1x-dx,v1y-dy,true);
					++shipdetect;
					break;
				}
			}
			else if (shipdetect == 1)
				shipdetect = 0;

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


void GameStatus::clearAstroid()
{
	//for(int i=0; i<MAX_ASTEROIDS; i++)
	//	asteroids[i+MAX_ASTEROIDS]=asteroids[i];
	naster=0;
}

void GameStatus::finalAstroid()
{
	
	for(int i=0; i<nshots; i++) {
		shots[i].pos=shots[i].pos-ship.pos;
		shots[i].normalize();
	}

	saucer.normalize();

	int s1[3]={};
	int s2[3]={};
	for(int i=0; i<naster; i++){
		if(asteroids[i].status==TrackAst::CABOOM) continue;
		s1[asteroids[i].ast.shape%2+(asteroids[i].ast.shape?1:0)]++;
		asteroids[i].ast.pos=asteroids[i].ast.pos-ship.pos;
		asteroids[i].ast.normalize();
	}
	for(int i=MAX_ASTEROIDS; i<2*MAX_ASTEROIDS; i++)
		if(asteroids[i].status!=asteroids[i].NONE&&asteroids[i].status!=asteroids[i].CABOOM)
			s2[asteroids[i].ast.shape%2+(asteroids[i].ast.shape?1:0)]++;

	if(s1[0]==s2[0]&&s1[1]==s2[1]&&s1[2]==s2[2]) {
		int start=MAX_ASTEROIDS-1;
		for(int i=0; i<naster; i++) {
			if(asteroids[i].status==TrackAst::CABOOM||asteroids[i].status==asteroids[i].NONE) continue;
			for(; start<2*MAX_ASTEROIDS;) {
				++start;
				if(asteroids[start].status!=TrackAst::CABOOM&&asteroids[start].status!=asteroids[i].NONE)
				{
					asteroids[start].ast.Set(asteroids[i].ast.pos);
					asteroids[start].status=asteroids[i+MAX_ASTEROIDS].NORMAL;

					asteroids[start].ast.shape = asteroids[i].ast.shape;
					asteroids[start].ast.type = asteroids[i].ast.type;
					asteroids[start].ast.r =asteroids[i].ast.r;
					break;
				}
			}
		}
	}
	else {
		for(int i=MAX_ASTEROIDS; i<2*MAX_ASTEROIDS; i++)
			if(asteroids[i].status!=asteroids[i].NONE)
				asteroids[i].status=asteroids[i].OLD;
		int j=0;

		for(int i=MAX_ASTEROIDS; j<naster&&i<2*MAX_ASTEROIDS; i++) {
			int sh=asteroids[j].ast.shape%2+(asteroids[j].ast.shape?1:0);

			if(asteroids[j].status==asteroids[j].CABOOM) {
				j++;
				continue;
			}
			if(asteroids[i].status==asteroids[i].NONE&& (s1[sh]>s2[sh])) {
				--s1[sh];
				asteroids[i].status=asteroids[i].NEW;
				asteroids[i].ast=asteroids[j++].ast;
			}
			else if(asteroids[i].status!=asteroids[i].NONE) {
				if(!asteroids[i].ast.Set(asteroids[j++].ast.pos)) {
					asteroids[i].ast.shape = asteroids[j-1].ast.shape;
					asteroids[i].ast.type = asteroids[j-1].ast.type;
					asteroids[i].ast.r =asteroids[j-1].ast.r;
					--j;
				}
				else
					asteroids[i].status=asteroids[i].NORMAL;
			}
		}
		for(int i=MAX_ASTEROIDS; i<2*MAX_ASTEROIDS; i++) {
			if(asteroids[i].status==asteroids[i].NEW) {
				unsigned int n=-1,d=-1;
				for(int j=MAX_ASTEROIDS; j<2*MAX_ASTEROIDS; j++) {
					if(asteroids[j].status==asteroids[j].OLD) {
						if((asteroids[j].ast.pos-asteroids[i].ast.pos).dist2()<d) {
							d=(asteroids[j].ast.pos-asteroids[i].ast.pos).dist2();
							n=j;
						}
					}
				}
				if(n!=-1&&(asteroids[n].ast.pos-asteroids[i].ast.pos).dist2()<15*15)
					asteroids[i].ast.Set(asteroids[n].ast.pos);
			}
		}
	}

	for(int i=0; i<MAX_ASTEROIDS; i++) {
		if(asteroids[i+MAX_ASTEROIDS].status==asteroids[i+MAX_ASTEROIDS].OLD)
			asteroids[i+MAX_ASTEROIDS].status=asteroids[i+MAX_ASTEROIDS].NONE;
	}
}

void GameStatus::setAstroid(int x, int y, int type, int sf)
{

	asteroids[naster].status=asteroids[naster].NEW;
	asteroids[naster++].ast=Asteroid(Vector(x,y),type,sf);

}
/*

void Shot::set(int x, int y)
{
	last_pos=pos;
	pos=Vector(x,y);
}*/

void GameStatus::clear(void)
{
	ship.ship_present = false;
	saucer_present = false;
	nastro_pos=0;
	nshots = 0;
	clearAstroid();
}

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::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))
	{
#if defined(WINDOWS)
		int err = WSAGetLastError();
		if (err != WSAEWOULDBLOCK)
		{
			fprintf(stderr, "Fehler %d bei sendto().\n", err);
			exit(1);
		}
#else
		if (errno != EAGAIN)
		{
			perror("Fehler bei sendto()");
			exit(1);
		}
#endif
	}
}


void Player::SendPlayerName()
{
	static char player_name[38] = "ctnameBeispiel von Harald BÃ¶geholz\0\0";

	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 (38 != sendto(sd, player_name, 38, 0, (sockaddr*)&server, sizeof server))
	{
#if defined(WINDOWS)
		int err = WSAGetLastError();
		if (err != WSAEWOULDBLOCK)
		{
			fprintf(stderr, "Fehler %d bei sendto().\n", err);
			exit(1);
		}
#else
		if (errno != EAGAIN)
		{
			perror("Fehler bei sendto()");
			exit(1);
		}
#endif
	}
}
