#include "common.h"

#include "main.h"
#include "FileMgr.h"
#include "Physical.h"
#include "HandlingMgr.h"

cHandlingDataMgr mod_HandlingManager;

const char *HandlingFilename = "HANDLING.CFG";

const char VehicleNames[NUMHANDLINGS][14] = {
	"LANDSTAL",
	"IDAHO",
	"STINGER",
	"LINERUN",
	"PEREN",
	"SENTINEL",
	"PATRIOT",
	"FIRETRUK",
	"TRASH",
	"STRETCH",
	"MANANA",
	"INFERNUS",
	"PONY",
	"MULE",
	"CHEETAH",
	"AMBULAN",
	"FBICAR",
	"MOONBEAM",
	"ESPERANT",
	"TAXI",
	"KURUMA",
	"BOBCAT",
	"MRWHOOP",
	"BFINJECT",
	"POLICE",
	"ENFORCER",
	"SECURICA",
	"BANSHEE",
	"BUS",
	"RHINO",
	"BARRACKS",
	"TRAIN",
	"HELI",
	"DODO",
	"COACH",
	"CABBIE",
	"STALLION",
	"RUMPO",
	"RCBANDIT",
	"MAFIA",
	"AIRTRAIN",
	"DEADDODO",
	"FLATBED",
	"YANKEE",
	"GOLFCART",
	"VOODOO",
	"WASHING",
	"CUBAN",
	"ROMERO",
	"PACKER",
	"ADMIRAL",
	"GANGBUR",
	"ZEBRA",
	"TOPFUN",
	"GLENDALE",
	"OCEANIC",
	"HERMES",
	"SABRE1",
	"SABRETUR",
	"PHEONIX",
	"WALTON",
	"REGINA",
	"COMET",
	"DELUXO",
	"BURRITO",
	"SPAND",
	"BAGGAGE",
	"KAUFMAN",
	"RANCHER",
	"FBIRANCH",
	"VIRGO",
	"GREENWOO",
	"HOTRING",
	"SANDKING",
	"BLISTAC",
	"BOXVILLE",
	"BENSON",
	"DESPERAD",
	"LOVEFIST",
	"BLOODRA",
	"BLOODRB",
	"BIKE",
	"MOPED",
	"DIRTBIKE",
	"ANGEL",
	"FREEWAY",
	"PREDATOR",
	"SPEEDER",
	"REEFER",
	"RIO",
	"SQUALO",
	"TROPIC",
	"COASTGRD",
	"DINGHY",
	"MARQUIS",
	"CUPBOAT",
	"SEAPLANE",
	"SPARROW",
	"SEASPAR",
	"MAVERICK",
	"COASTMAV",
	"POLMAV",
	"HUNTER",
	"RCBARON",
	"RCGOBLIN",
	"RCCOPTER"
};

cHandlingDataMgr::cHandlingDataMgr(void)
{
	memset(this, 0, sizeof(*this));
}

void
cHandlingDataMgr::Initialise(void)
{
	LoadHandlingData();
	field_0 = 0.1f;
	fWheelFriction = 0.9f;
	field_8 = 1.0f;
	field_C = 0.8f;
	field_10 = 0.98f;
}

void
cHandlingDataMgr::LoadHandlingData(void)
{
	char *start, *end;
	char line[201];	// weird value
	char delim[4];	// not sure
	char *word;
	int field, handlingId;
	int keepGoing;
	tHandlingData *handling;
	tFlyingHandlingData *flyingHandling;
	tBoatHandlingData *boatHandling;
	tBikeHandlingData *bikeHandling;

	CFileMgr::SetDir("DATA");
	CFileMgr::LoadFile(HandlingFilename, work_buff, sizeof(work_buff), "r");
	CFileMgr::SetDir("");

	start = (char*)work_buff;
	end = start+1;
	handling = nil;
	flyingHandling = nil;
	boatHandling = nil;
	bikeHandling = nil;
	keepGoing = 1;

	while(keepGoing){
		// find end of line
		while(*end != '\n') end++;

		// get line
		strncpy(line, start, end - start);
		line[end - start] = '\0';
		start = end+1;
		end = start+1;

		// yeah, this is kinda crappy
		if(strcmp(line, ";the end") == 0)
			keepGoing = 0;
		else if(line[0] != ';'){
			if(line[0] == '!'){
				// Bike data
				field = 0;
				strcpy(delim, " \t");
				// FIX: game seems to use a do-while loop here
				for(word = strtok(line, delim); word; word = strtok(nil, delim)){
					switch(field){
					case  0: break;
					case  1:
						handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS);
						assert(handlingId >= 0 && handlingId < NUMHANDLINGS);
						bikeHandling = GetBikePointer(handlingId);
						bikeHandling->nIdentifier = (tVehicleType)handlingId;
						break;
					case  2: bikeHandling->fLeanFwdCOM = atof(word); break;
					case  3: bikeHandling->fLeanFwdForce = atof(word); break;
					case  4: bikeHandling->fLeanBakCOM = atof(word); break;
					case  5: bikeHandling->fLeanBackForce = atof(word); break;
					case  6: bikeHandling->fMaxLean = atof(word); break;
					case  7: bikeHandling->fFullAnimLean = atof(word); break;
					case  8: bikeHandling->fDesLean = atof(word); break;
					case  9: bikeHandling->fSpeedSteer = atof(word); break;
					case 10: bikeHandling->fSlipSteer = atof(word); break;
					case 11: bikeHandling->fNoPlayerCOMz = atof(word); break;
					case 12: bikeHandling->fWheelieAng = atof(word); break;
					case 13: bikeHandling->fStoppieAng = atof(word); break;
					case 14: bikeHandling->fWheelieSteer = atof(word); break;
					case 15: bikeHandling->fWheelieStabMult = atof(word); break;
					case 16: bikeHandling->fStoppieStabMult = atof(word); break;
					}
					field++;
				}
				ConvertBikeDataToGameUnits(bikeHandling);
			}else if(line[0] == '$'){
				// Flying data
				field = 0;
				strcpy(delim, " \t");
				// FIX: game seems to use a do-while loop here
				for(word = strtok(line, delim); word; word = strtok(nil, delim)){
					switch(field){
					case  0: break;
					case  1:
						handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS);
						assert(handlingId >= 0 && handlingId < NUMHANDLINGS);
						flyingHandling = GetFlyingPointer(handlingId);
						flyingHandling->nIdentifier = (tVehicleType)handlingId;
						break;
					case  2: flyingHandling->fThrust = atof(word); break;
					case  3: flyingHandling->fThrustFallOff = atof(word); break;
					case  4: flyingHandling->fYaw = atof(word); break;
					case  5: flyingHandling->fYawStab = atof(word); break;
					case  6: flyingHandling->fSideSlip = atof(word); break;
					case  7: flyingHandling->fRoll = atof(word); break;
					case  8: flyingHandling->fRollStab = atof(word); break;
					case  9: flyingHandling->fPitch = atof(word); break;
					case 10: flyingHandling->fPitchStab = atof(word); break;
					case 11: flyingHandling->fFormLift = atof(word); break;
					case 12: flyingHandling->fAttackLift = atof(word); break;
					case 13: flyingHandling->fMoveRes = atof(word); break;
					case 14: flyingHandling->vecTurnRes.x = atof(word); break;
					case 15: flyingHandling->vecTurnRes.y = atof(word); break;
					case 16: flyingHandling->vecTurnRes.z = atof(word); break;
					case 17: flyingHandling->vecSpeedRes.x = atof(word); break;
					case 18: flyingHandling->vecSpeedRes.y = atof(word); break;
					case 19: flyingHandling->vecSpeedRes.z = atof(word); break;
					}
					field++;
				}
			}else if(line[0] == '%'){
				// Boat data
				field = 0;
				strcpy(delim, " \t");
				// FIX: game seems to use a do-while loop here
				for(word = strtok(line, delim); word; word = strtok(nil, delim)){
					switch(field){
					case  0: break;
					case  1:
						handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS);
						assert(handlingId >= 0 && handlingId < NUMHANDLINGS);
						boatHandling = GetBoatPointer(handlingId);
						boatHandling->nIdentifier = (tVehicleType)handlingId;
						break;
					case  2: boatHandling->fThrustY = atof(word); break;
					case  3: boatHandling->fThrustZ = atof(word); break;
					case  4: boatHandling->fThrustAppZ = atof(word); break;
					case  5: boatHandling->fAqPlaneForce = atof(word); break;
					case  6: boatHandling->fAqPlaneLimit = atof(word); break;
					case  7: boatHandling->fAqPlaneOffset = atof(word); break;
					case  8: boatHandling->fWaveAudioMult = atof(word); break;
					case  9: boatHandling->vecMoveRes.x = atof(word); break;
					case 10: boatHandling->vecMoveRes.y = atof(word); break;
					case 11: boatHandling->vecMoveRes.z = atof(word); break;
					case 12: boatHandling->vecTurnRes.x = atof(word); break;
					case 13: boatHandling->vecTurnRes.y = atof(word); break;
					case 14: boatHandling->vecTurnRes.z = atof(word); break;
					case 15: boatHandling->fLook_L_R_BehindCamHeight = atof(word); break;
					}
					field++;
				}
			}else{
				field = 0;
				strcpy(delim, " \t");
				// FIX: game seems to use a do-while loop here
				for(word = strtok(line, delim); word; word = strtok(nil, delim)){
					switch(field){
					case  0:
						handlingId = FindExactWord(word, (const char*)VehicleNames, 14, NUMHANDLINGS);
						assert(handlingId >= 0 && handlingId < NUMHANDLINGS);
						handling = &HandlingData[handlingId];
						handling->nIdentifier = (tVehicleType)handlingId;
						break;
					case  1: handling->fMass = atof(word); break;
					case  2: handling->Dimension.x = atof(word); break;
					case  3: handling->Dimension.y = atof(word); break;
					case  4: handling->Dimension.z = atof(word); break;
					case  5: handling->CentreOfMass.x = atof(word); break;
					case  6: handling->CentreOfMass.y = atof(word); break;
					case  7: handling->CentreOfMass.z = atof(word); break;
					case  8: handling->nPercentSubmerged = atoi(word); break;
					case  9: handling->fTractionMultiplier = atof(word); break;
					case 10: handling->fTractionLoss = atof(word); break;
					case 11: handling->fTractionBias = atof(word); break;
					case 12: handling->Transmission.nNumberOfGears = atoi(word); break;
					case 13: handling->Transmission.fMaxVelocity = atof(word); break;
					case 14: handling->Transmission.fEngineAcceleration = atof(word) * 0.4; break;
					case 15: handling->Transmission.nDriveType = word[0]; break;
					case 16: handling->Transmission.nEngineType = word[0]; break;
					case 17: handling->fBrakeDeceleration = atof(word); break;
					case 18: handling->fBrakeBias = atof(word); break;
					case 19: handling->bABS = !!atoi(word); break;
					case 20: handling->fSteeringLock = atof(word); break;
					case 21: handling->fSuspensionForceLevel = atof(word); break;
					case 22: handling->fSuspensionDampingLevel = atof(word); break;
					case 23: handling->fSeatOffsetDistance = atof(word); break;
					case 24: handling->fCollisionDamageMultiplier = atof(word); break;
					case 25: handling->nMonetaryValue = atoi(word); break;
					case 26: handling->fSuspensionUpperLimit = atof(word); break;
					case 27: handling->fSuspensionLowerLimit = atof(word); break;
					case 28: handling->fSuspensionBias = atof(word); break;
					case 29: handling->fSuspensionAntidiveMultiplier = atof(word); break;
					case 30:
						sscanf(word, "%x", &handling->Flags);
						handling->Transmission.Flags = handling->Flags;
						break;
					case 31: handling->FrontLights = atoi(word); break;
					case 32: handling->RearLights = atoi(word); break;
					}
					field++;
				}
				ConvertDataToGameUnits(handling);
			}
		}
	}
}

int
cHandlingDataMgr::FindExactWord(const char *word, const char *words, int wordLen, int numWords)
{
	int i;

	for(i = 0; i < numWords; i++){
		// BUG: the game does something really stupid here, it's fixed here
		if(strncmp(word, words, wordLen) == 0)
			return i;
		words += wordLen;
	}
	return numWords;
}


void
cHandlingDataMgr::ConvertDataToGameUnits(tHandlingData *handling)
{
	// convert distance to m, time to 1/50s
	float velocity, a, b;

	handling->Transmission.fEngineAcceleration *= 1.0f/(50.0f*50.0f);
	handling->Transmission.fMaxVelocity *= 1000.0f/(60.0f*60.0f * 50.0f);
	handling->fBrakeDeceleration *= 1.0f/(50.0f*50.0f);
	handling->fTurnMass = (sq(handling->Dimension.x) + sq(handling->Dimension.y)) * handling->fMass / 12.0f;
	if(handling->fTurnMass < 10.0f)
		handling->fTurnMass *= 5.0f;
	handling->fInvMass = 1.0f/handling->fMass;
	handling->fCollisionDamageMultiplier *= 2000.0f/handling->fMass;
	handling->fBuoyancy = 100.0f/handling->nPercentSubmerged * GRAVITY*handling->fMass;

	// Don't quite understand this. What seems to be going on is that
	// we calculate a drag (air resistance) deceleration for a given velocity and
	// find the intersection between that and the max engine acceleration.
	// at that point the car cannot accelerate any further and we've found the max velocity.
	a = 0.0f;
	b = 100.0f;
	velocity = handling->Transmission.fMaxVelocity;
	while(a < b && velocity > 0.0f){
		velocity -= 0.01f;
		// what's the 1/6?
		a = handling->Transmission.fEngineAcceleration/6.0f;
		// no density or drag coefficient here...
		float a_drag = 0.5f*SQR(velocity) * handling->Dimension.x*handling->Dimension.z / handling->fMass;
		// can't make sense of this... maybe  v - v/(drag + 1)  ? but that doesn't make so much sense either
		b = -velocity * (1.0f/(a_drag + 1.0f) - 1.0f);
	}

	if(handling->nIdentifier == HANDLING_RCBANDIT){
		handling->Transmission.fMaxCruiseVelocity = handling->Transmission.fMaxVelocity;
		handling->Transmission.fMaxReverseVelocity = -handling->Transmission.fMaxVelocity;
	}else if(handling->nIdentifier >= HANDLING_BIKE && handling->nIdentifier <= HANDLING_FREEWAY){
		handling->Transmission.fMaxCruiseVelocity = velocity;
		handling->Transmission.fMaxVelocity = velocity * 1.2f;
		handling->Transmission.fMaxReverseVelocity = -0.05f;
	}else{
		handling->Transmission.fMaxCruiseVelocity = velocity;
		handling->Transmission.fMaxVelocity = velocity * 1.2f;
		handling->Transmission.fMaxReverseVelocity = -0.2f;
	}

	if(handling->Transmission.nDriveType == '4')
		handling->Transmission.fEngineAcceleration /= 4.0f;
	else
		handling->Transmission.fEngineAcceleration /= 2.0f;

	handling->Transmission.InitGearRatios();
}

void
cHandlingDataMgr::ConvertBikeDataToGameUnits(tBikeHandlingData *handling)
{
	handling->fMaxLean = Sin(DEGTORAD(handling->fMaxLean));
	handling->fFullAnimLean = DEGTORAD(handling->fFullAnimLean);
	handling->fWheelieAng = Sin(DEGTORAD(handling->fWheelieAng));
	handling->fStoppieAng = Sin(DEGTORAD(handling->fStoppieAng));
}

int32
cHandlingDataMgr::GetHandlingId(const char *name)
{
	int i;
	for(i = 0; i < NUMHANDLINGS; i++)
		if(strncmp(VehicleNames[i], name, 14) == 0)
			break;
	return i;
}

tFlyingHandlingData*
cHandlingDataMgr::GetFlyingPointer(uint8 id)
{
	if(id >= HANDLING_SEAPLANE && id <= HANDLING_RCCOPTER)
		return &FlyingHandlingData[id-HANDLING_SEAPLANE];
	return &FlyingHandlingData[0];
}

tBoatHandlingData*
cHandlingDataMgr::GetBoatPointer(uint8 id)
{
	if(id >= HANDLING_PREDATOR && id <= HANDLING_SEAPLANE)
		return &BoatHandlingData[id-HANDLING_PREDATOR];
	return &BoatHandlingData[0];
}
