#include "driver2.h"
#include "denting.h"
#include "system.h"
#include "mission.h"
#include "cars.h"
#include "convert.h"
#include "pause.h"
#include "camera.h"
#include "draw.h"
#include "dr2roads.h"
#include "debris.h"
#include "players.h"
#include "main.h"

char* DentingFiles[] =
{
	"LEVELS\\CHICAGO.DEN",
	"LEVELS\\HAVANA.DEN",
	"LEVELS\\VEGAS.DEN",
	"LEVELS\\RIO.DEN",
};

// DO NOT CHANGE THIS!
#define NUM_DAMAGE_ZONES			6
#define MAX_FILE_DAMAGE_ZONE_VERTS	50
#define MAX_FILE_DAMAGE_ZONE_POLYS	70
#define MAX_FILE_DAMAGE_LEVELS		256

u_char gCarDamageZoneVerts[MAX_CAR_MODELS][NUM_DAMAGE_ZONES][MAX_DAMAGE_ZONE_VERTS];
u_char gHDCarDamageZonePolys[MAX_CAR_MODELS][NUM_DAMAGE_ZONES][MAX_DAMAGE_ZONE_POLYS];
u_char gHDCarDamageLevels[MAX_CAR_MODELS][MAX_DAMAGE_LEVELS];

// [D] [T]
void InitialiseDenting(void)
{
	LoadDenting(GameLevel);
	InitHubcap();
}

// [D] [T]
void DentCar(CAR_DATA *cp)
{
	int Zone;
	int Damage;
	int MaxDamage;
	DENTUVS *dentptr;
	SVECTOR *CleanVertPtr;
	int VertNo;
	unsigned char *DamPtr;
	SVECTOR *DamVertPtr;
	MODEL *pCleanModel;
	int model;
	int Poly;

	short tempDamage[256];
	memset((u_char*)tempDamage, 0, sizeof(tempDamage));

	MaxDamage = 0;
	model = cp->ap.model;
	cp->lowDetail = -1;
	cp->ap.qy = 0;
	cp->ap.qw = 0;

	pCleanModel = gCarCleanModelPtr[model];

	// collect vertices from zones
	if (pCleanModel != NULL) 
	{
		VertNo = 0;
		while (VertNo < pCleanModel->num_vertices)
			tempDamage[VertNo++] = 0;

		Zone = 0;
		do {
			Damage = cp->ap.damage[Zone];

			if (Damage > MaxDamage)
				MaxDamage = Damage;

			DamPtr = gCarDamageZoneVerts[cp->ap.model][Zone];

			VertNo = 0;
			while (VertNo < MAX_DAMAGE_ZONE_VERTS && *DamPtr != 0xFF)
			{
				if (tempDamage[*DamPtr] == 0)
					tempDamage[*DamPtr] += Damage;
				else 
					tempDamage[*DamPtr] += Damage / 2;

				DamPtr++;

				VertNo++;
			}

			Zone++;
		} while (Zone < NUM_DAMAGE_ZONES);
	}

	// update vertices positon
	if (gCarCleanModelPtr[model] != NULL && gCarDamModelPtr[model] != NULL) 
	{
		DamVertPtr = (SVECTOR *)gCarDamModelPtr[model]->vertices;
		CleanVertPtr = (SVECTOR *)gCarCleanModelPtr[model]->vertices;

		VertNo = 0;
		while (VertNo < pCleanModel->num_vertices)
		{
			gTempCarVertDump[cp->id][VertNo].vx = CleanVertPtr->vx + FIXEDH((DamVertPtr->vx - CleanVertPtr->vx) * tempDamage[VertNo] / 2);
			gTempCarVertDump[cp->id][VertNo].vy = CleanVertPtr->vy + FIXEDH((DamVertPtr->vy - CleanVertPtr->vy) * tempDamage[VertNo] / 2);
			gTempCarVertDump[cp->id][VertNo].vz = CleanVertPtr->vz + FIXEDH((DamVertPtr->vz - CleanVertPtr->vz) * tempDamage[VertNo] / 2);

			DamVertPtr++;
			CleanVertPtr++;

			VertNo++;
		}
	}

	// update polygon UVs
	if (pCleanModel != NULL) 
	{
		dentptr = gTempHDCarUVDump[cp->id];

		// reset UV coordinates
		Poly = 0;
		while (Poly < pCleanModel->num_polys)
		{
			dentptr->u3 = 0;
			Poly++;
			dentptr++;
		}

		Zone = 0;
		do {
			Damage = cp->ap.damage[Zone];

			Poly = 0;
			while (Poly < MAX_DAMAGE_ZONE_POLYS && gHDCarDamageZonePolys[cp->ap.model][Zone][Poly] != 0xFF)
			{
				dentptr = gTempHDCarUVDump[cp->id] + gHDCarDamageZonePolys[cp->ap.model][Zone][Poly];

				// add a damage level
				dentptr->u3 += (Damage >> 0xA);

				// clamp level
				if (dentptr->u3 > 2)
					dentptr->u3 = 2;

				Poly++;
			}

			Zone++;
		} while (Zone < NUM_DAMAGE_ZONES);

		Poly = 0;

		DamPtr = gHDCarDamageLevels[model];
		dentptr = gTempHDCarUVDump[cp->id];

		while (Poly < pCleanModel->num_polys)
		{
			// calculate the UV offset with strange XORs
			if(dentptr->u3 > 0)
				dentptr->u3 = (*DamPtr ^ 1 ^ (*DamPtr ^ 1 | dentptr->u3)) * 64;

			dentptr++;
			
			DamPtr++;
			Poly++;
		}
	}
}

// [D] [T]
void CreateDentableCar(CAR_DATA *cp)
{
	MODEL *srcModel;
	SVECTOR *dst;
	int count;
	SVECTOR *src;
	int vcount;
	int model;

	model = cp->ap.model;

	srcModel = gCarCleanModelPtr[model];
	if (srcModel != NULL)
	{
		src = (SVECTOR *)srcModel->vertices;
		dst = gTempCarVertDump[cp->id];

		vcount = srcModel->num_vertices;

		while (vcount-- != -1) 
			*dst++ = *src++;

		count = 0;
		while (count < srcModel->num_polys)
		{
			gTempHDCarUVDump[cp->id][count].u3 = 0;
			count++;
		}
	}
	else
	{
		printError("gCarCleanModelPtr is NULL in CreateDentableCar\n");
	}

	srcModel = gCarLowModelPtr[model];
	if (srcModel != NULL)
	{
		count = 0;
		while (count < srcModel->num_polys)
		{
			gTempLDCarUVDump[cp->id][count].u3 = 0;
			count++;
		}
	}
	else
	{
		printError("gCarLowModelPtr is NULL in CreateDentableCar\n");
	}

	if (gDontResetCarDamage == 0) 
	{
		count = 0;
		while (count < NUM_DAMAGE_ZONES)
		{
			cp->ap.damage[count] = 0;
			count++;
		}

		cp->totalDamage = 0;
	}
}

extern HUBCAP gHubcap;
int gHubcapTime = 0;

// [D] [T]
void InitHubcap(void)
{
	gHubcapTime = Random2(1) & 0x7ff;
}


// [D] [T]
void LoseHubcap(int car, int Hubcap, int Velocity)
{
	SVECTOR InitialLocalAngle = { 0, 0, 10 };
	CAR_DATA* cp;

	SVECTOR* wheelDisp;
	
	cp = &car_data[car];

	// check speed and if hubcap lost
	if (cp->hd.wheel_speed < 0 || (cp->ap.flags & 1 << Hubcap))
		return;

	wheelDisp = &cp->ap.carCos->wheelDisp[Hubcap];

	cp->ap.flags |= (1 << Hubcap);		// [A] cars now hold hubcaps

	gHubcap.Position.vx = wheelDisp->vx;
	gHubcap.Position.vy = wheelDisp->vy;
	gHubcap.Position.vz = wheelDisp->vz;

	SetRotMatrix(&cp->hd.where);
	_MatrixRotate(&gHubcap.Position);

	gHubcap.Position.vx += cp->hd.where.t[0];
	gHubcap.Position.vy -= cp->hd.where.t[1];
	gHubcap.Position.vz += cp->hd.where.t[2];

	gHubcap.Orientation = cp->hd.where;
	gHubcap.Rotation = 0;
	
	if (Hubcap > 1)
	{
		gHubcap.Orientation.m[0][0] = -gHubcap.Orientation.m[0][0];
		gHubcap.Orientation.m[1][0] = -gHubcap.Orientation.m[1][0];

		gHubcap.Orientation.m[2][0] = -gHubcap.Orientation.m[2][0];
		gHubcap.Orientation.m[0][2] = -gHubcap.Orientation.m[0][2];

		gHubcap.Orientation.m[1][2] = -gHubcap.Orientation.m[1][2];
		gHubcap.Orientation.m[2][2] = -gHubcap.Orientation.m[2][2];
	}

	gHubcap.Duration = 100;
	
	gHubcap.Direction.vx = FIXEDH(FIXEDH(cp->st.n.angularVelocity[1]) * wheelDisp->vz) + FIXEDH(cp->st.n.linearVelocity[0]);
	gHubcap.Direction.vy = FIXEDH(cp->st.n.linearVelocity[1]);
	gHubcap.Direction.vz = FIXEDH(-FIXEDH(cp->st.n.angularVelocity[1]) * wheelDisp->vx) + FIXEDH(cp->st.n.linearVelocity[2]);
}


// [A]
void HandlePlayerHubcaps(int playerId)
{
	int VelocityMagnitude;
	CAR_DATA* cp;
	int playerCarId;

	playerCarId = player[playerId].playerCarId;

	if (playerCarId < 0)
		return;

	cp = &car_data[playerCarId];

	if (gHubcap.Duration < 1 && gHubcapTime == 0)
	{
		VelocityMagnitude = cp->st.n.angularVelocity[0] + cp->st.n.angularVelocity[1] + cp->st.n.angularVelocity[2];

		if (VelocityMagnitude < -1000000)
		{
			LoseHubcap(playerCarId, Random2(2) & 1, VelocityMagnitude);
			VelocityMagnitude = 3;
		}
		else if (VelocityMagnitude > 1000001)
		{
			LoseHubcap(playerCarId, Random2(4) & 1 | 2, VelocityMagnitude);
			VelocityMagnitude = 5;
		}
		else
			return;

		gHubcapTime = Random2(VelocityMagnitude) & 0x417;
	}
}

// [D] [T]
void MoveHubcap()
{
	int CurrentMapHeight;
	int savecombo;
	int cmb;
	VECTOR ShadowPos;
	VECTOR Position;
	MATRIX Orientation;
	CVECTOR col = {72,72,72};

	InitMatrix(Orientation);

	if (pauseflag == 0)
	{
		if(gHubcapTime > 0)
			gHubcapTime--;
	}

	// draw current hubcap
	if (gHubcap.Duration > 0) 
	{
		if (pauseflag == 0 && CurrentPlayerView == 0)
		{
			gHubcap.Duration--;

			// move hubcap
			gHubcap.Position.vx += gHubcap.Direction.vx;
			gHubcap.Position.vy += gHubcap.Direction.vy;
			gHubcap.Position.vz += gHubcap.Direction.vz;

			gHubcap.Rotation -= 220;

			_RotMatrixX(&Orientation, gHubcap.Rotation);

			gHubcap.Direction.vy += 5;

			CurrentMapHeight = MapHeight(&gHubcap.Position);

			if (gHubcap.Position.vy >= -40 - CurrentMapHeight)
			{
				gHubcap.Direction.vy = -(gHubcap.Direction.vy / 2);
				gHubcap.Position.vy = -40 - CurrentMapHeight;
			}
		}

		MulMatrix0(&gHubcap.Orientation, &Orientation, &Orientation);

		ShadowPos.vx = gHubcap.Position.vx - camera_position.vx;
		ShadowPos.vy = -MapHeight(&gHubcap.Position);
		ShadowPos.vz = gHubcap.Position.vz - camera_position.vz;

		Position.vx = ShadowPos.vx;
		Position.vy = gHubcap.Position.vy - camera_position.vy;
		Position.vz = ShadowPos.vz;

		SetRotMatrix(&inv_camera_matrix);

		_MatrixRotate(&Position);
		savecombo = combointensity;

		if (gTimeOfDay == 3)
		{
			cmb = (combointensity & 0xffU) / 3;
			combointensity = cmb << 0x10 | cmb << 8 | cmb;
		}

		RenderModel(gHubcapModelPtr, &Orientation, &Position, 0, 0, 0, 0);
		ShadowPos.vy -= camera_position.vy;

		combointensity = savecombo;

		RoundShadow(&ShadowPos, &col, 65);
	}
}

#ifndef PSX
// [A] loads car model from file
char* LoadCarDentingFromFile(char* dest, int modelNumber)
{
	char* mem;
	char filename[64];

	sprintf(filename, "LEVELS\\%s\\CARMODEL_%d.DEN", LevelNames[GameLevel], modelNumber);
	if(FileExists(filename))
	{
		mem = dest ? dest : ((char*)_other_buffer + modelNumber * 4096);

		// get model from file
		Loadfile(filename, mem);
		return mem;
	}

	return NULL;
}
#endif

// [D] [T]
void ProcessDentLump(char *lump_ptr, int lump_size)
{
	int model;
	int i;
	int offset;
	u_char* mem;

	i = 0;

	while (i < MAX_CAR_MODELS)
	{
		model = MissionHeader->residentModels[i];

		if (model == 13)
		{
			model = 10 - (MissionHeader->residentModels[0] + MissionHeader->residentModels[1] + MissionHeader->residentModels[2]);

			if (model < 1) 
				model = 1;
			else if(model > 4)
				model = 4;
		}

		if (model != -1) 
		{
			offset = *(int *)(lump_ptr + model * 4);
			mem = (u_char*)lump_ptr;
#ifndef PSX
			char* newDenting = LoadCarDentingFromFile(NULL, model);
			if(newDenting)
			{
				mem = (u_char*)newDenting;
				offset = 0;
			}
#endif

			memcpy((u_char*)gCarDamageZoneVerts[i], mem + offset, NUM_DAMAGE_ZONES * MAX_FILE_DAMAGE_ZONE_VERTS);
			offset += NUM_DAMAGE_ZONES * MAX_FILE_DAMAGE_ZONE_VERTS;
			
			memcpy((u_char*)gHDCarDamageZonePolys[i], mem + offset, NUM_DAMAGE_ZONES * MAX_FILE_DAMAGE_ZONE_POLYS);
			offset += NUM_DAMAGE_ZONES * MAX_FILE_DAMAGE_ZONE_POLYS;
			
			memcpy((u_char*)gHDCarDamageLevels[i], mem + offset, MAX_FILE_DAMAGE_LEVELS);
		}

		i++;
	}
}


// [D] [T]
void SetupSpecDenting(char *loadbuffer)
{
	int offset;

	// [A] this is better
	memcpy((u_char*)gCarDamageZoneVerts[4], (u_char*)loadbuffer, NUM_DAMAGE_ZONES * MAX_FILE_DAMAGE_ZONE_VERTS);
	offset = NUM_DAMAGE_ZONES * MAX_FILE_DAMAGE_ZONE_VERTS;

	memcpy((u_char*)gHDCarDamageZonePolys[4], (u_char*)loadbuffer + offset, NUM_DAMAGE_ZONES * MAX_FILE_DAMAGE_ZONE_POLYS);
	offset += NUM_DAMAGE_ZONES * MAX_FILE_DAMAGE_ZONE_POLYS;

	memcpy((u_char*)gHDCarDamageLevels[4], (u_char*)loadbuffer + offset, MAX_FILE_DAMAGE_LEVELS);
}

// [D] [T]
void LoadDenting(int level)
{
	LoadfileSeg(DentingFiles[level], (char*)_other_buffer, 0, 12727);
	ProcessDentLump((char*)_other_buffer, 0);
}



