#ifndef _GAME_C
#define _GAME_C

#include <math.h>
#include <time.h>

#include "WoumInclude:lib/keyboard.h"

#include "game.h"
#include "car.h"

int num_laps;
int gametime;

void game_runcar(level *l,int c,int keys,int time)
{
	float friction;
	f1616 s,d; /* Forwards velocity & sideways drift */
	f1616 xd,yd; /* Direction car is facing */
	f1616 gh; /* Ground height */
	int b,b2;
	if (time == 0)
		return;
	xd = cars[c].x >> 16;
	yd = cars[c].y >> 16;
	if (xd < 0) /* Fix rounding mooseness */
		xd = -1;
	else
		xd = xd/l->sprw;
	if (yd < 0)
		yd = -1;
	else
		yd = yd/l->sprh;
	gh = ground_height(l,map_read(l,xd,yd),cars[c].x % (l->sprw << 16),cars[c].y % (l->sprh << 16));
	xd = f1616_cos(cars[c].ang);
	yd = f1616_sin(cars[c].ang);
	friction = car_friction;
	s = f1616_mul(cars[c].xd,xd)+f1616_mul(cars[c].yd,yd);
	/* Do keys */
	if (cars[c].lap >= num_laps)
		keys &= KEY_CAR_LEFT+KEY_CAR_RIGHT;
	if ((keys & KEY_CAR_LEFT) && (keys & KEY_CAR_RIGHT))
		keys ^= KEY_CAR_LEFT+KEY_CAR_RIGHT;
	if (cars[c].z > gh)
		keys = 0; /* No keys while in the air! */
	if (keys & KEY_CAR_ACCEL)
	{
		cars[c].xd += xd*car_accel*time/100;
		cars[c].yd += yd*car_accel*time/100;
		if (s < 0) /* Use breaking to get going forward quicker */
			friction = car_break;
	}
	if (keys & KEY_CAR_BREAK)
	{
		cars[c].xd -= xd*car_accel*time/400;
		cars[c].yd -= yd*car_accel*time/400;
		if (s > 0) /* Use breaking to get going backward quicker */
			friction = car_break;
	}
	if (s < 0) /* Driving backwards? */
		if ((keys & KEY_CAR_LEFT) || (keys & KEY_CAR_RIGHT))
			keys ^= KEY_CAR_LEFT+KEY_CAR_RIGHT;
	if (s) {
		if (keys & KEY_CAR_LEFT)
			cars[c].ang = ((360 << 16)+cars[c].ang-car_turn*time*655) % (360 << 16);
		if (keys & KEY_CAR_RIGHT)
			cars[c].ang = (cars[c].ang+car_turn*time*655) % (360 << 16);
	}
	/* Collision detection */
	for (b=c+1;b<MAX_CAR;b++) /* Check against cars which haven't moved yet */
		if (car_collide(b,c))
		{
			/* Work out how hard they're hitting each other */
			s = cars[b].xd-cars[c].xd;
			d = cars[b].yd-cars[c].yd;
			/* Amplify it to take into account the friction calculation that's going to take place later */
			s = f1616_div(s,f1616_FromFloat(pow(car_break,((float) time)/100)));
			d = f1616_div(d,f1616_FromFloat(pow(car_break,((float) time)/100)));
			/* Now swap each car's velocity */
			cars[b].xd -= s;
			cars[b].yd -= d;
			cars[c].xd += s;
			cars[c].yd += d;
		}
	/* Collisions with walls */
	b = (cars[c].x >> 16) % l->sprw;
	b2 = (cars[c].y >> 16) % l->sprh;
	if (b+car_rad >= l->sprw)
	{
		wall_collide(l,c,(cars[c].x >> 16)/l->sprw+1,(cars[c].y >> 16)/l->sprh,-1,0);
		if (b2+car_rad >= l->sprh)
			wall_collide(l,c,(cars[c].x >> 16)/l->sprw+1,(cars[c].y >> 16)/l->sprh+1,-1,-1);
		else if (b2-car_rad < 0)
			wall_collide(l,c,(cars[c].x >> 16)/l->sprw+1,(cars[c].y >> 16)/l->sprh-1,-1,1);
	}
	else if (b-car_rad < 0)
	{
		wall_collide(l,c,(cars[c].x >> 16)/l->sprw-1,(cars[c].y >> 16)/l->sprh,1,0);
		if (b2+car_rad >= l->sprh)
			wall_collide(l,c,(cars[c].x >> 16)/l->sprw-1,(cars[c].y >> 16)/l->sprh+1,1,-1);
		else if (b2-car_rad < 0)
			wall_collide(l,c,(cars[c].x >> 16)/l->sprw-1,(cars[c].y >> 16)/l->sprh-1,1,1);
	}
	if (b2+car_rad >= l->sprh)
		wall_collide(l,c,(cars[c].x >> 16)/l->sprw,(cars[c].y >> 16)/l->sprh+1,0,-1);
	else if (b2-car_rad < 0)
		wall_collide(l,c,(cars[c].x >> 16)/l->sprw,(cars[c].y >> 16)/l->sprh-1,0,1);
	/* Do physics */
	if (cars[c].z > gh)
	{
		cars[c].zd -= GRAVITY*time/100;
		cars[c].z += cars[c].zd*time/100;
		if ((gh < 0) && (cars[c].z < 0)) {
			cars[c].xd /= 2;
			cars[c].yd /= 2;
		}
		if (cars[c].z < -65536*32)
		{
			/* Relocate to last hint */
			cars[c].z = GRAVITY/2;
			cars[c].x = l->hints[cars[c].hint*2]*l->sprw << 16;
			cars[c].y = l->hints[cars[c].hint*2+1]*l->sprh << 16;
			cars[c].x += l->sprw << 15;
			cars[c].y += l->sprh << 15;
			cars[c].xd = cars[c].yd = cars[c].zd = 0;
		}
	}
	else
		cars[c].z = gh;
	if ((cars[c].xd) || (cars[c].yd))
	{
		if (cars[c].z <= gh)
		{
			s = f1616_mul(cars[c].xd,xd)+f1616_mul(cars[c].yd,yd);
			d = f1616_mul(cars[c].yd,xd)-f1616_mul(cars[c].xd,yd);
			s = f1616_mul(s,f1616_FromFloat(pow(friction,((float) time)/100)));
			d = f1616_mul(d,f1616_FromFloat(pow(car_break,((float) time)/100)));
			if (keys & KEY_CAR_LEFT)
				b = cars[c].ang+car_turn*time*327;
			else if (keys & KEY_CAR_RIGHT)
				b = cars[c].ang-car_turn*time*327;
			else
				b = cars[c].ang;
			xd = f1616_cos(b);
			yd = f1616_sin(b);
			cars[c].xd = f1616_mul(s,xd)+f1616_mul(d,-yd);
			cars[c].yd = f1616_mul(s,yd)+f1616_mul(d,xd);
		}
		b = ((cars[c].x >> 16)/l->sprw) + ((cars[c].y >> 16)/l->sprh)*l->w; /* Block number */
		cars[c].x += cars[c].xd*time/100;
		cars[c].y += cars[c].yd*time/100;
		/* Check ramp stuff */
		d = ground_height(l,map_read(l,(cars[c].x >> 16)/l->sprw,(cars[c].y >> 16)/l->sprh),cars[c].x % (l->sprw << 16),cars[c].y % (l->sprh << 16));
		if ((d > gh) && (cars[c].z <= gh)) { /* Going up? */
			cars[c].z = d;
			if (l->blocks[l->map[b]].flags & BLOCK_JUMP) /* Don't fly up in the air after climbing the wrong side of a ramp */
			{
				cars[c].zd = ((d-gh)*100)/time;
				if (cars[c].zd > 4*GRAVITY)
					cars[c].zd = 4*GRAVITY;
			}
		}
		/* Check rules */
		if ((cars[c].x >= 0) && (cars[c].y >= 0) && (cars[c].x < ((l->sprw*l->w) << 16)) && (cars[c].y < ((l->sprh*l->h) << 16)))
		{
			b2 = ((cars[c].x >> 16)/l->sprw) + ((cars[c].y >> 16)/l->sprh)*l->w;
			if ((b != b2) && (cars[c].lap < num_laps)) /* Moved to a new block? */
			{
				b = l->map[b2];
				if ((l->blocks[b].flags & BLOCK_GRID) && (cars[c].check == l->highest_check))
				{
					cars[c].lap++;
					cars[c].check = 0;
					cars[c].check_time = cars[c].lap_time = gametime;
					cars[c].hint = 0;
				}
				else if (b2 == l->hints[cars[c].hint*2+2]+l->hints[cars[c].hint*2+3]*l->w)
				{
					/* Hit next hint block */
					cars[c].hint++;
					if (l->blocks[b].flags & BLOCK_CHECK)
					{
						cars[c].check++;
						cars[c].check_time = gametime;
					}
				}
				else if ((l->blocks[b].flags & BLOCK_CHECK) && (cars[c].check == l->blocks[b].i-1))
				{
					cars[c].check++;
					cars[c].check_time = gametime;
					/* Set hint count */
					for (cars[c].hint=1;cars[c].hint < l->numhints-1;cars[c].hint++)
						if (l->hints[cars[c].hint*2]+l->hints[cars[c].hint*2+1]*l->w == b2)
							break;
				}
			}
		}
	}
}

int player_keys(level *l,int c)
{
	int keys = 0;
	if (keyboard_ispressed(KEY_UP))
		keys |= KEY_CAR_ACCEL;
	if (keyboard_ispressed(KEY_DOWN))
		keys |= KEY_CAR_BREAK;
	if (keyboard_ispressed(KEY_LEFT))
		keys |= KEY_CAR_LEFT;
	if (keyboard_ispressed(KEY_RIGHT))
		keys |= KEY_CAR_RIGHT;
	return keys;
}

#define ABS(x) ((x)<0?-(x):(x))

/* Return a between -180 and 180 */
static int norm_rel_ang(int a)
{
	while (a > 180)
		a -= 360;
	while (a <= -180)
		a += 360;
	return a;
}

int ai_keys(level *l,int c)
{
	int x,y;
	int tang,keys;
	/* Identify next target */
	if (cars[c].hint == l->numhints-1)
	{
		x = l->hints[0];
		y = l->hints[1];
	}
	else
	{
		x = l->hints[cars[c].hint*2+2];
		y = l->hints[cars[c].hint*2+3];
	}
	x = (x*l->sprw+l->sprw/2)-(cars[c].x >> 16);
	y = (y*l->sprh+l->sprh/2)-(cars[c].y >> 16);
	if ((x == 0) && (y == 0))
		return 0;
	tang = (int) (atan2((double) y,(double) x)*57.2957795);
	tang = norm_rel_ang(tang-(cars[c].ang >> 16));
	if ((ABS(tang) < 15) || ((cars[c].xd == 0) && (cars[c].yd == 0)))
		keys = KEY_CAR_ACCEL;
	else if (ABS(tang) > 90)
		keys = KEY_CAR_BREAK;
	else
		keys = 0;
	if (f1616_mul(cars[c].xd,f1616_cos(cars[c].ang))+f1616_mul(cars[c].yd,f1616_sin(cars[c].ang)) < 0)
		tang = -tang; /* Steer backwards if we're going in reverse */
	if (tang > 5)
		keys |= KEY_CAR_RIGHT;
	else if (tang < -5)
		keys |= KEY_CAR_LEFT;
	return keys;
}

int ranking(int c)
{
	/* Count how many cars are ahead of it */
	int rank = 1;
	int c2;
	for (c2=0;c2<MAX_CAR;c2++)
		if ((c2 != c) && (cars[c2].player))
		{
			if (cars[c2].lap > cars[c].lap)
				rank++; /* c2 is on a later lap */
			else if ((cars[c2].lap == cars[c].lap) && (cars[c2].hint > cars[c].hint))
				rank++; /* c2 is further through current lap */
			else if ((cars[c2].lap == num_laps) && (cars[c].lap == num_laps) && (cars[c2].lap_time < cars[c].lap_time))
				rank++; /* c2 finished race earlier */
		}
	return rank;
}

void wall_collide(level *l,int c,int x,int y,int fx,int fy)
{
	if ((l->blocks[map_read(l,x,y)].flags & BLOCK_WALL) == 0)
		return; /* Not wall! */
	/* Else work out if we really do collide with it */
	f1616 xd,yd;
	f1616 xa[4],ya[4];
	f1616 xb[4],yb[4];
	int li,i,coll;
	/* Check whether each corner of the car lies inside the wall, and vice-versa */
	xd = f1616_cos(cars[c].ang);
	yd = f1616_sin(cars[c].ang);
	/* 0,0 */
	xa[0] = cars[c].x-xd*(car_l/2)+yd*(car_w/2);
	ya[0] = cars[c].y-yd*(car_l/2)-xd*(car_w/2);
	/* w,0 */
	xa[1] = cars[c].x+xd*(car_l/2)+yd*(car_w/2);
	ya[1] = cars[c].y+yd*(car_l/2)-xd*(car_w/2);
	/* w,h */
	xa[2] = cars[c].x+xd*(car_l/2)-yd*(car_w/2);
	ya[2] = cars[c].y+yd*(car_l/2)+xd*(car_w/2);
	/* 0,h */
	xa[3] = cars[c].x-xd*(car_l/2)-yd*(car_w/2);
	ya[3] = cars[c].y-yd*(car_l/2)+xd*(car_w/2);
	/* Wall */
	/* 0,0 */
	xb[0] = l->sprw*x << 16;
	yb[0] = l->sprh*y << 16;
	/* w,0 */
	xb[1] = l->sprw*(x+1) << 16;
	yb[1] = yb[0];
	/* w,h */
	xb[2] = xb[1];
	yb[2] = l->sprh*(y+1) << 16;
	/* 0,h */
	xb[3] = xb[0];
	yb[3] = yb[2];
	/* Now check */
	coll = 0;
	for (i=0;i<4;i++)
	{
		for (li=0;li<4;li++)
			if (sideofline(xa[li],ya[li],xa[(li+1)&3],ya[(li+1)&3],xb[i],yb[i]) > 0)
				break;
		if (li == 4)
			coll = 1; /* One point lies inside all four lines */
	}
	if (coll == 0)
		for (i=0;i<4;i++)
		{
			for (li=0;li<4;li++)
				if (sideofline(xb[li],yb[li],xb[(li+1)&3],yb[(li+1)&3],xa[i],ya[i]) > 0)
					break;
			if (li == 4)
				coll = 1;
		}
	if (coll == 0)
		return;
	if ((fx != 0) && (cars[c].xd == 0))
		cars[c].xd = 0x10000;
	if ((fy != 0) && (cars[c].yd == 0))
		cars[c].yd = 0x10000;
	if (fx*cars[c].xd < 0)
		cars[c].xd = -cars[c].xd;
	if (fy*cars[c].yd < 0)
		cars[c].yd = -cars[c].yd;
}

#endif
