#include "driver2.h"
#include "drivinggames.h"
#include "scores.h"
#include "mission.h"
#include "cutscene.h"
#include "main.h"
#include "system.h"
#include "draw.h"
#include "cars.h"
#include "players.h"
#include "cosmetic.h"
#include "bcollide.h"
#include "camera.h"
#include "convert.h"
#include "gamesnd.h"
#include "map.h"
#include "sound.h"
#include "glaunch.h"

#include "ASM/rndrasm.h"

struct TRAILBLAZER_DATA
{
	int x, z;
	short y, rot;
};

struct SMASHED_CONE
{
	char cone;
	u_char active : 7;
	u_char side : 1;
	short rot_speed;
	VECTOR velocity;
};

MODEL* gTrailblazerConeModel; 
SMASHED_CONE smashed_cones[MAX_SMASHED_CONES];

TRAILBLAZER_DATA *gTrailblazerData;
int gTrailblazerConeCount;
int gTrailblazerConeIndex;
static int gTrailblazerPrevConeDelay = 0;

static int wrongside[2][MAX_SMASHED_CONES];
static int current_smashed_cone = 0;

// [D] [T]
void InitDrivingGames(void)
{
	char filename[64];
	int i;
	int j;

	if (NewLevel)
		gTrailblazerData = NULL;
	
	gPlayerScore.time = 0;
	gPlayerScore.items = 0;
	gPlayerScore.P2time = 0;
	gPlayerScore.P2items = 0;

	if (GameType == GAME_GATERACE || GameType == GAME_TRAILBLAZER) 
	{
		gTrailblazerConeCount = 0;
		gTrailblazerConeIndex = 0;

		if(NewLevel)
		{
			gTrailblazerData = (TRAILBLAZER_DATA*)D_MALLOC(1200); // [A] use malloc

			sprintf(filename, "TRAILS\\TRAIL.%d", gCurrentMissionNumber);

			if (FileExists(filename) != 0)
				Loadfile(filename, (char*)gTrailblazerData);
		}
	}

	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < MAX_SMASHED_CONES; j++)
            wrongside[i][j] = 0;
    }

	for (i = 0; i < MAX_SMASHED_CONES; i++)
	{
		smashed_cones[i].cone = -1;
		smashed_cones[i].active = 0;
		smashed_cones[i].side = 0;
		smashed_cones[i].rot_speed = 0;
    }

    current_smashed_cone = 0;
    gTrailblazerPrevConeDelay = 0;
}


// [D] [T]
int CarConeCollision(VECTOR *pPos, int car)
{
	int model;
	CDATA2D cd[2];
	CAR_DATA* cp1;
	
	cd[0].x.vx = pPos->vx;
	cd[0].x.vz = pPos->vz;
	cd[0].length[0] = 40;
	cd[0].length[1] = 40;
	cd[0].theta = pPos->pad;

	cp1 = &car_data[car];

	if (cp1->controlType == CONTROL_TYPE_NONE) 
		return 0;

	model = cp1->ap.model;

	cd[1].x.vx = cp1->hd.where.t[0];
	cd[1].length[0] = car_cosmetics[model].colBox.vz;
	cd[1].length[1] = car_cosmetics[model].colBox.vx;

	cd[1].theta = cp1->hd.direction;
	cd[1].x.vz = cp1->hd.where.t[2];

	return bcollided2d(cd);
}



// [D] [T]
void SetSmashedCone(int cone, VECTOR *velocity, int player, int side)
{
	int chan;
	TRAILBLAZER_DATA *tbd;
	SMASHED_CONE *sc;

	sc = smashed_cones + current_smashed_cone;
	current_smashed_cone++;

	if (current_smashed_cone > MAX_SMASHED_CONES-1)
		current_smashed_cone = 0;
	
	sc->rot_speed = 256;
	sc->side = side;
	sc->cone = cone;

	sc->velocity.vx = velocity->vx >> 10;
	sc->velocity.vz = velocity->vz >> 10;

	if (sc->velocity.vx < 0)
		sc->velocity.vy = velocity->vx;
	else 
		sc->velocity.vy = -velocity->vx;

	if (sc->velocity.vz < 0)
		sc->velocity.vy += velocity->vz;
	else
		sc->velocity.vy -= velocity->vz;
	
	sc->velocity.vy /= 2;

	if ((rand() & 1) == 0)
		sc->rot_speed = -sc->velocity.vy;
	else
		sc->rot_speed = sc->velocity.vy;

	if (sc->velocity.vy < -67)
		sc->velocity.vy = -67;

	chan = GetFreeChannel();

	if (chan != -1) 
	{
		if (NumPlayers > 1 && NoPlayerControl == 0) 
			SetPlayerOwnsChannel(chan, player);

		tbd = &gTrailblazerData[cone];
		Start3DSoundVolPitch(chan, SOUND_BANK_SFX, 1, tbd->x, tbd->y, tbd->z, -2000, 800);
	}
}


// [D] [T]
void MoveSmashedCones(void)
{
	TRAILBLAZER_DATA *tbd;
	SMASHED_CONE *sc;
	int i;

	if (gTrailblazerData == NULL)
		return;

	sc = smashed_cones;

	for (i = 0;  i < MAX_SMASHED_CONES; i++)
	{
		if (sc->cone != -1) 
		{
			tbd = &gTrailblazerData[sc->cone];

			if (tbd->y < 50 - player[0].pos[1])
			{
				tbd->x += sc->velocity.vx;
				tbd->y += sc->velocity.vy;
				tbd->z += sc->velocity.vz;

				sc->velocity.vy += 10;
				sc->active++;

			}
			else
			{
				sc->cone = -1;
				sc->active = 0;
			}
		}
		
		sc++;
	}
}

// [D] [T]
void DrawCone(VECTOR *position, int cone)
{
	MATRIX matrix;
	VECTOR pos;

	if (gTrailblazerData == NULL)
		return;

	if (PositionVisible(position) == 0 || FrustrumCheck(position, gTrailblazerConeModel->bounding_sphere) == -1)
		return;
	
	InitMatrix(matrix);
	_RotMatrixY(&matrix, gTrailblazerData[cone].rot);

	pos.vx = position->vx - camera_position.vx;
	pos.vy = position->vy - camera_position.vy;
	pos.vz = position->vz - camera_position.vz;

	gte_SetRotMatrix(&inv_camera_matrix);

	_MatrixRotate(&pos);
	RenderModel(gTrailblazerConeModel, &matrix, &pos, 0, 0, 0, 0);
}

// [D] [T]
void DrawSmashedCone(SMASHED_CONE *sc, VECTOR *wpos)
{
	MATRIX object_matrix;
	VECTOR pos;

	InitMatrix(object_matrix);

	RotMatrixY(sc->rot_speed * sc->active * 3 & 0xfff, &object_matrix);
	RotMatrixZ(sc->rot_speed * sc->active & 0xfff, &object_matrix);

	pos.vx = wpos->vx - camera_position.vx;
	pos.vy = wpos->vy - camera_position.vy;
	pos.vz = wpos->vz - camera_position.vz;

	Apply_Inv_CameraMatrix(&pos);
	SetRotMatrix(&object_matrix);

	gte_SetTransVector(&pos);

	SetFrustrumMatrix();

	if (FrustrumCheck(wpos, gTrailblazerConeModel->bounding_sphere) != -1)
	{
		PlotMDL_less_than_128(gTrailblazerConeModel);
	}
}

// [D] [T]
void GetConePos(int cone, VECTOR *pos, int side)
{
	TRAILBLAZER_DATA *tbd;
	int x, z;
	int radius;

	tbd = &gTrailblazerData[cone];

	if (side == -1)
	{
		pos->vx = tbd->x;
		pos->vy = tbd->y;
		pos->vz = tbd->z;
		pos->pad = tbd->rot;	// [A] store cone rotation
	}
	else 
	{
		radius = cone * -4 + 600;

		x = radius * RCOS(tbd->rot);
		z = -radius * RSIN(tbd->rot);

		if (side == 0) 
		{
			pos->vx = tbd->x - FIXED(x);
			pos->vy = tbd->y;
			pos->vz = tbd->z - FIXED(z);
		}
		else 
		{
			pos->vx = tbd->x + FIXED(x);
			pos->vy = tbd->y;
			pos->vz = tbd->z + FIXED(z);
		}

		pos->pad = tbd->rot;	// [A] store cone rotation
	}
}

// [D] [T]
void DrawSmashedCones(void)
{
	SMASHED_CONE* sc;
	int i;

	VECTOR wpos;

	if (gTrailblazerData == NULL)
		return;

	for (i = 0; i < MAX_SMASHED_CONES; i++)
	{
		sc = &smashed_cones[i];

		if (sc->active)
		{
			if (GameType == GAME_GATERACE)
			{
				GetConePos(sc->cone, &wpos, sc->side);
				DrawSmashedCone(sc, &wpos);
			}
			else
			{
				GetConePos(sc->cone, &wpos, -1);
				DrawSmashedCone(sc, &wpos);
			}
		}
	}
}

// [D] [T]
void HandleDrivingGames(void)
{
	char playerCarId;
	TRAILBLAZER_DATA* tbd;
	int id;
	VECTOR vel;
	VECTOR pos1;
	VECTOR pos2;
	int i, j, k;
	int side;
	int cone;

	if (gTrailblazerPrevConeDelay > 0)
		gTrailblazerPrevConeDelay--;

	if (gTrailblazerData)
	{
		if (GameType == GAME_GATERACE)
		{
			MoveSmashedCones();

			for (id = 0; id < NumPlayers; id++)
			{
				playerCarId = player[id].playerCarId;

				for (i = 0; i < MAX_SMASHED_CONES; i++)
				{
					cone = gTrailblazerConeIndex + i;

					if (cone < 100)
					{
						GetConePos(cone, &pos1, 0);
						GetConePos(cone, &pos2, 1);

						side = 0;

						if (CarConeCollision(&pos1, playerCarId))
							side = 1;
						else if (CarConeCollision(&pos2, playerCarId))
							side = 2;

						if (side == 0)
						{
							int sn, cs;
							int dx, dz;
							int radius;

							tbd = &gTrailblazerData[cone];
							
							radius = (600 - (gTrailblazerConeIndex * 400 + i) / 100) * 4096;

							sn = RSIN(tbd->rot);
							cs = RCOS(tbd->rot);

							dx = player[id].pos[0] - tbd->x;
							dz = player[id].pos[2] - tbd->z;

							if (dx * sn + dz * cs < 1)
							{
								wrongside[id][i] = 1;
								continue;
							}

							if (wrongside[id][i] == 0)
								continue;

							wrongside[id][i] = 0;

							if (ABS(dx * cs - dz * sn) < radius)
							{
								gTrailblazerConeCount++;

								Mission.timer[id].count += 3000;

								if (id == 0)
									gPlayerScore.items++;
								else
									gPlayerScore.P2items++;

								gTrailblazerConeIndex += i + 1;
								gTrailblazerPrevConeDelay = 10;
							}

							// reset side
							for (j = 0; j < 2; j++)
							{
								for (k = 0; k < MAX_SMASHED_CONES; k++)
									wrongside[j][k] = 0;
							}
						}
						else
						{
							vel.vx = FIXED(car_data[playerCarId].st.n.linearVelocity[0]);
							vel.vy = -17;
							vel.vz = FIXED(car_data[playerCarId].st.n.linearVelocity[2]);

							SetSmashedCone(cone, &vel, id, side - 1);
							gTrailblazerConeIndex += i + 1;

							Mission.timer[id].count -= 3000;
							SetPlayerMessage(id, G_LTXT(GTXT_Minus1second), 2, 1);
						}
					}
				}
			}

			if (gTrailblazerConeIndex == 100)
				MissionTargets[NumPlayers - 1].s.target_flags |= TARGET_FLAG_COMPLETED_ALLP;
		}
		else if (GameType == GAME_TRAILBLAZER)
		{
			MoveSmashedCones();

			for (id = 0; id < NumPlayers; id++)
			{
				playerCarId = player[id].playerCarId;

				for (i = 0; i < MAX_SMASHED_CONES; i++)
				{
					cone = gTrailblazerConeIndex + i;

					if (cone < 100)
					{
						GetConePos(cone, &pos1, -1);

						if (CarConeCollision(&pos1, playerCarId))
						{
							Mission.timer[id].count += 3000;

							vel.vx = FIXED(car_data[playerCarId].st.n.linearVelocity[0]);
							vel.vy = -17;
							vel.vz = FIXED(car_data[playerCarId].st.n.linearVelocity[2]);

							SetSmashedCone(cone, &vel, id, 0);

							gTrailblazerConeCount++;
							gTrailblazerConeIndex += i + 1;

							if (id == 0)
								gPlayerScore.items++;
							else
								gPlayerScore.P2items++;
						}
					}
				}
			}

			if (gTrailblazerConeIndex == 100)
				MissionTargets[NumPlayers - 1].s.target_flags |= TARGET_FLAG_COMPLETED_ALLP;
		}
	}

	if (Mission.timer[0].count < 0)
		Mission.timer[0].count = 0;

	if (Mission.timer[1].count < 0)
		Mission.timer[1].count = 0;

	gPlayerScore.time = Mission.timer[0].count;
	gPlayerScore.P2time = Mission.timer[1].count;
}

// [D] [T]
void DrawDrivingGames(void)
{
	int cone;
	int i;
	VECTOR wpos;

	if (GameType != GAME_GATERACE && GameType != GAME_TRAILBLAZER ||
		gTrailblazerConeModel == NULL || gTrailblazerData == NULL)
		return;

	SetFrustrumMatrix();

	if (gTrailblazerConeIndex < 100)
	{
		for (i = 0; i < MAX_SMASHED_CONES; i++)
		{
			cone = gTrailblazerConeIndex + i;

			if (gTrailblazerPrevConeDelay)
				cone -= 1;

			if (GameType == GAME_GATERACE)
			{
				GetConePos(cone, &wpos, 0);
				DrawCone(&wpos, cone);

				GetConePos(cone, &wpos, 1);
				DrawCone(&wpos, cone);
			}
			else
			{
				GetConePos(cone, &wpos, -1);
				DrawCone(&wpos, cone);
			}
		}
	}

	DrawSmashedCones();
}



