#include "driver2.h"
#include "bomberman.h"
#include "convert.h"
#include "overmap.h"
#include "players.h"
#include "cars.h"
#include "glaunch.h"
#include "job_fx.h"
#include "dr2roads.h"
#include "pad.h"
#include "camera.h"
#include "draw.h"
#include "debris.h"
#include "mission.h"
#include "denting.h"
#include "cosmetic.h"
#include "bcollide.h"
#include "pedest.h"

#include "ASM/rndrasm.h"

MODEL* gBombModel;

static BOMB ThrownBombs[MAX_THROWN_BOMBS];

static int ThrownBombDelay = 0;
static int CurrentBomb = 0;
static int gWantFlash = 0;
CAR_DATA *gBombTargetVehicle = NULL;
static int flashval;

// [D] [T]
void InitThrownBombs(void)
{
	int i;

	for (i = 0; i < MAX_THROWN_BOMBS; i++)
		ThrownBombs[i].flags = 0;

	ThrownBombDelay = Random2(0) % 45 + 8;
	CurrentBomb = 0;
	gWantFlash = 0;
	gBombTargetVehicle = NULL;
}

// [D] [T]
void DrawThrownBombs(void)
{
	BOMB *bomb;
	int i;
	MATRIX object_matrix;
	VECTOR pos;

	if (gBombModel == NULL)
		return;

	bomb = ThrownBombs;
	i = 0;
	while (i < MAX_THROWN_BOMBS)
	{
		if ((bomb->flags & 1) != 0) 
		{
			InitMatrix(object_matrix);

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

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

			Apply_Inv_CameraMatrix(&pos);

			gte_SetRotMatrix(&object_matrix);
			gte_SetTransVector(&pos);

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

			SetFrustrumMatrix();

			if (FrustrumCheck(&pos, gBombModel->bounding_sphere) != -1) 
			{
				PlotMDL_less_than_128(gBombModel);
			}
		}

		i++;
		bomb++;
	}

	if (gWantFlash != 0) 
	{
		add_haze(flashval, flashval, 7);

		flashval -= 10;

		if (flashval < 1)
			gWantFlash = 0;
	}
}

// [D] [T]
void BombThePlayerToHellAndBack(int car)
{
	CAR_DATA* cp;
	BOMB *bomb;

	if (car == -1)
		return;

	cp = &car_data[car];
	
	cp->ap.damage[0] = 0xfff;
	cp->ap.damage[1] = 0xfff;
	cp->ap.damage[2] = 0xfff;
	cp->ap.damage[3] = 0xfff;
	cp->ap.damage[4] = 0xfff;
	cp->ap.damage[5] = 0xfff;

	cp->ap.needsDenting = 1;
	cp->totalDamage = MaxPlayerDamage[0];

	DentCar(&car_data[car]);

	if (maxCivCars == 0) 
	{
		trap(7);
	}

	gBombTargetVehicle = &car_data[(car + 1) % maxCivCars];

	bomb = &ThrownBombs[CurrentBomb++];
	CurrentBomb = CurrentBomb % MAX_THROWN_BOMBS;

	bomb->flags = 1;
	bomb->active = 1;

	bomb->position.vx = cp->hd.where.t[0];
	bomb->position.vy = cp->hd.where.t[1];
	bomb->position.vz = cp->hd.where.t[2];

	bomb->velocity.vx = 0;
	bomb->velocity.vy = 0;
	bomb->velocity.vz = 0;
		
	bomb = &ThrownBombs[CurrentBomb++];
	CurrentBomb = CurrentBomb % MAX_THROWN_BOMBS;

	bomb->flags = 1;
	bomb->active = 1;

	bomb->position.vx = cp->hd.where.t[0] + 170;
	bomb->position.vy = cp->hd.where.t[1];
	bomb->position.vz = cp->hd.where.t[2] + 73;

	bomb->velocity.vx = 0;
	bomb->velocity.vy = 0;
	bomb->velocity.vz = 0;
		
	bomb = &ThrownBombs[CurrentBomb++];
	CurrentBomb = CurrentBomb % MAX_THROWN_BOMBS;

	bomb->flags = 1;
	bomb->active = 1;
	bomb->position.vx = cp->hd.where.t[0] - 109;
	bomb->position.vy = cp->hd.where.t[1];
	bomb->position.vz = cp->hd.where.t[2] - 147;

	bomb->velocity.vx = 0;
	bomb->velocity.vy = 0;
	bomb->velocity.vz = 0;
}

// [D] [T]
int BombCollisionCheck(CAR_DATA *cp, VECTOR *pPos)
{
	CDATA2D cd[2] = {0};

	cd[1].theta = cp->hd.direction;
	cd[1].length[0] = car_cosmetics[cp->ap.model].colBox.vz;
	cd[1].length[1] = car_cosmetics[cp->ap.model].colBox.vx;
	cd[1].x.vx = cp->hd.where.t[0];
	cd[1].x.vz = cp->hd.where.t[2];

	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 = 0;
	cd[0].avel = 0;
	cd[0].vel.vx = 0;
	cd[0].vel.vy = 0;
	cd[0].vel.vz = 0;

	return bcollided2d(cd);
}

const int minBoxSize = 90;
const int littleBoxRange = 400;
const int halfStrike = 0x32000;
const int fullStrike = 0x32000;

// [D] [T]
void ExplosionCollisionCheck(CAR_DATA *cp, EXOBJECT *pE)
{
	static int setUsed = 0;

	int temp;
	int strikeVel;
	bool isCar;
	VECTOR *pos;
	int displacement;
	int denom;
	int debrisColour;
	CDATA2D cd[2];
	int carLength[2];
	CRET2D collisionResult;
	VECTOR velocity;
	LONGVECTOR4 pointVel;
	LONGVECTOR4 reaction;
	LONGVECTOR4 lever;

	isCar = (cp != &car_data[TANNER_COLLIDER_CARID]);

	if (player[0].playerType == 2 || isCar)
	{
		cd[1].x.vx = cp->hd.where.t[0];
		cd[1].length[0] = car_cosmetics[cp->ap.model].colBox.vz;
		cd[1].length[1] = car_cosmetics[cp->ap.model].colBox.vx;
		cd[1].theta = cp->hd.direction;
		cd[1].x.vz = cp->hd.where.t[2];

		if (pE->type <= LITTLE_BANG)
		{
			cd[0].length[1] = minBoxSize + ((pE->time / pE->speed) * littleBoxRange) / (4096 / pE->speed);
		}
		else if (pE->type == HEY_MOMMA) // biggest bang that might kill Tanner
		{
			pos = (VECTOR*)cp->hd.where.t;

			if (!isCar)
				pos = (VECTOR*)player->pos;

			if (cd[0].length[1] < 0)
				cd[0].length[1] = pos->vx - pE->pos.vx;
			else
				cd[0].length[1] = pE->pos.vx - pos->vx;

			temp = pE->pos.vz - pos->vz;

			if (temp < 0)
				temp = pos->vz - pE->pos.vz;

			if (cd[0].length[1] < temp)
				cd[0].length[1] = temp;

			if (cd[0].length[1] < 3000)
			{
				if (isCar)
				{
					if (CAR_INDEX(cp) == player[0].playerCarId)
					{
						cp->totalDamage = MaxPlayerDamage[0];
						player[0].dying = 1;
						lockAllTheDoors = 1;
					}
				}
				else
				{
					bKillTanner = 1;
					player[0].dying = 1;
				}
			}
			else
			{
				cd[0].length[1] = 10;
			}
		}
		else
		{
			cd[0].length[1] = 0;
		}

		cd[0].x.vx = (pE->pos).vx;
		cd[0].x.vz = (pE->pos).vz;
		cd[0].theta = 0;
		cd[0].avel = 0;
		cd[0].vel.vx = 0;
		cd[0].vel.vy = 0;
		cd[0].vel.vz = 0;
		cd[0].length[0] = cd[0].length[1];

		if (bcollided2d(cd))
		{
			bFindCollisionPoint(cd, &collisionResult);

			cp->hd.where.t[2] += FIXEDH(collisionResult.penetration * collisionResult.surfNormal.vz);
			cp->hd.where.t[0] += FIXEDH(collisionResult.penetration * collisionResult.surfNormal.vx);

			lever[0] = collisionResult.hit.vx - cp->hd.where.t[0];
			lever[1] = 60;
			lever[2] = collisionResult.hit.vz - cp->hd.where.t[2];

			collisionResult.surfNormal.vy = 0;

			if (pE->type == LITTLE_BANG)
				strikeVel = halfStrike - ((pE->time / pE->speed) * halfStrike) / (0x1000 / pE->speed);
			else if (pE->type == BIG_BANG)
				strikeVel = fullStrike;
			else if (pE->type == HEY_MOMMA)
				strikeVel = fullStrike << 1;
			else
				strikeVel = 0;

			setUsed = 1;

			if (strikeVel > 0)
			{
				collisionResult.hit.vy = -(cp->hd.where.t[1] + 60);

				velocity.vx = cp->st.n.linearVelocity[0] >> 0xc;
				velocity.vy = -17;
				velocity.vz = cp->st.n.linearVelocity[2] >> 0xc;

				if (strikeVel > 0x3600)
				{
					collisionResult.hit.vy += 30;
					velocity.vy = -37;
					Setup_Sparks(&collisionResult.hit, &velocity, 4, 0);
					collisionResult.hit.vy -= 30;
					velocity.vy += 20;

					if (strikeVel > 0x1b000)
					{
						debrisColour = GetDebrisColour(cp);
						Setup_Debris(&collisionResult.hit, &velocity, 6, debrisColour << 0x10);

						if (cp->controlType == CONTROL_TYPE_PLAYER)
							SetPadVibration(*cp->ai.padid, 1);
					}
				}

				if(isCar)
					DamageCar(cp, cd, &collisionResult, strikeVel);

				displacement = FIXEDH(lever[0] * collisionResult.surfNormal.vx + lever[1] * collisionResult.surfNormal.vy + lever[2] * collisionResult.surfNormal.vz);
				displacement = FIXEDH(((lever[0] * lever[0] + lever[2] * lever[2]) - displacement * displacement) * car_cosmetics[cp->ap.model].twistRateY) + 4096;

				if (strikeVel < 0x7f001)
					denom = (strikeVel * 4096) / displacement;
				else
					denom = (strikeVel / displacement) * 4096;

				denom >>= 6;

				pointVel[0] = denom * (collisionResult.surfNormal.vx >> 6);
				pointVel[1] = denom * (collisionResult.surfNormal.vy >> 6);
				pointVel[2] = denom * (collisionResult.surfNormal.vz >> 6);

				cp->st.n.linearVelocity[0] += pointVel[0];
				cp->st.n.linearVelocity[1] += pointVel[1];
				cp->st.n.linearVelocity[2] += pointVel[2];

				cp->hd.aacc[0] += FIXEDH(lever[1] * pointVel[2]) - FIXEDH(lever[2] * pointVel[1]);
				cp->hd.aacc[1] += FIXEDH(lever[2] * pointVel[0]) - FIXEDH(lever[0] * pointVel[2]);
				cp->hd.aacc[2] += FIXEDH(lever[0] * pointVel[1]) - FIXEDH(lever[1] * pointVel[0]);
			}
		}

		if (isCar)
			return;
	}

	if (setUsed != 0) 
	{
		pE->type = BANG_USED;
		setUsed = 0;
	}
}

// [D] [T]
void AddFlash(VECTOR *pos)
{
	int dist;

	dist = Long2DDistance(pos, NoPlayerControl ? &player[0].cameraPos : (VECTOR*)player[0].pos);

	if (dist < 2500)
	{
		gWantFlash = 1;
		flashval = 150 - dist / 20;
	}
}

// [D] [T]
void HandleThrownBombs(void)
{
	CAR_DATA* cp;
	BOMB* bomb;
	int i;
	int y;

	int dx, dz;
	VECTOR velocity;

	// throw bombs if we have vehicle
	if (gBombTargetVehicle)
	{
		ThrownBombDelay--;

		if (ThrownBombDelay == -1)
		{
			ThrownBombDelay = Random2(0) % 45 + 8;

			bomb = &ThrownBombs[CurrentBomb++];
			CurrentBomb = CurrentBomb % MAX_THROWN_BOMBS;

			bomb->flags = 1;
			bomb->active = 1;

			bomb->position.vx = gBombTargetVehicle->hd.where.t[0];
			bomb->position.vy = gBombTargetVehicle->hd.where.t[1] - 200;
			bomb->position.vz = gBombTargetVehicle->hd.where.t[2];

			velocity.vx = FIXEDH(gBombTargetVehicle->st.n.linearVelocity[0]);
			velocity.vz = FIXEDH(gBombTargetVehicle->st.n.linearVelocity[2]);

			bomb->velocity.vx = velocity.vx >> 10;
			bomb->velocity.vz = velocity.vz >> 10;
			bomb->velocity.vy = -(Long2DDistance(&bomb->position, (VECTOR*)player[0].pos) >> 7);

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

			if (bomb->velocity.vy < -100)
				bomb->velocity.vy = -100;
		}
	}

	bomb = ThrownBombs;

	for (i = 0; i < MAX_THROWN_BOMBS; i++)
	{
		if (bomb->flags & 1)
		{
			bomb->position.vx += bomb->velocity.vx;
			bomb->position.vy += bomb->velocity.vy;
			bomb->position.vz += bomb->velocity.vz;

			bomb->velocity.vy += 10;

			bomb->active++;

			y = -MapHeight(&bomb->position);

			if (y < bomb->position.vy)
			{
				if ((bomb->flags & 2) == 0)
				{
					bomb->flags |= 2;
					bomb->position.vy = y;
					bomb->velocity.vy = -bomb->velocity.vy / 2; // bounce
				}
				else
				{
					bomb->flags = 0;
					bomb->position.vy = y;

					AddExplosion(bomb->position, LITTLE_BANG);
					AddFlash(&bomb->position);

					dx = (bomb->position.vx - player[0].pos[0]);
					dz = (bomb->position.vz - player[0].pos[2]);

					if (FIXEDH(dx * dx + dz * dz) < 1024)
						SetPadVibration(player[0].padid, 3);		// [A] bug fix
				}
			}

			cp = car_data;

			while (cp < &car_data[MAX_CARS])
			{
				if (cp != gBombTargetVehicle && cp->controlType != CONTROL_TYPE_NONE && BombCollisionCheck(cp, &bomb->position) != 0)
				{
					bomb->flags = 0;

					AddExplosion(bomb->position, BIG_BANG);
					AddFlash(&bomb->position);
				}
				cp++;
			}
		}
		bomb++;
	}
}