#include "Particle.h"

#include "Bitmap.h"
#include "ExMath.h"
#include "Level.h"
#include "Render.h"
#include "World.h"

/* Max distance that environmental particles should spawn around the player */
#define ENV_RADIUS 12.0f

static Particle particles[NUM_PARTICLES];
static int currentId = 0;

void initParticles(void)
{
	// Separate into new function to disperse particles
	for (int i = 0; i < NUM_PARTICLES; ++i)
	{
		Particle *particle = &particles[i];
		particle->x = 0;
		particle->y = 0;
		particle->z = 0;
		particle->velX = 0;
		particle->velY = 0;
		particle->velZ = -1.0f;
		particle->colour = 0xfffcfcfc; // Default colour
		particle->type = PTYPE_PHYSICS;
		particle->lifespan = 1.0f;
	}
}

static void SetupEnvironmentParticle(Particle *particle, float downVel, int col)
{
	particle->x = TOFLOAT(World_Players[0].sprite->x) + (Math_RandomFloat() * 2.0f - 1.0f) * ENV_RADIUS;
	particle->y = TOFLOAT(World_Players[0].sprite->y) + (Math_RandomFloat() * 2.0f - 1.0f) * ENV_RADIUS;
	particle->z = Math_RandomFloat() * Math_Abs((float)Level.wallHeight);
	particle->velX = 0.0f;
	particle->velY = 0.0f;
	particle->velZ = downVel + (Math_RandomFloat() * 0.1f);
	particle->colour = col;
	particle->type = PTYPE_ENVIRONMENT;
	particle->lifespan = 1.0f;
}

void P_Phys_Update(Particle *particle, float deltaTime)
{
	// Collide with and bounce off of walls
	int xt = (int)(particle->x + particle->velX * deltaTime);
	int yt = (int)(particle->y + particle->velY * deltaTime);
	if (Lvl_IsWall(xt, (int)particle->y))
	{
		particle->velY = particle->velY * -0.9f;
	}
	if (Lvl_IsWall((int)particle->x, yt))
	{
		particle->velX = particle->velX * -0.9f;
	}

	particle->x += particle->velX * deltaTime;
	particle->y += particle->velY * deltaTime;
	particle->z += particle->velZ * deltaTime;
	particle->velZ -= GRAVITY * deltaTime;

	// Stop moving on ground hit
	if (particle->z <= 0.0f)
	{
		if (Math_Abs(particle->velZ) > 0.5f)
		{
			particle->velX *= 0.5f;
			particle->velY *= 0.5f;
			particle->velZ *= -0.56f;
		}
		else
		{
			particle->velX = 0.0f;
			particle->velY = 0.0f;
			particle->velZ = 0.0f;
		}
	}

	// Bounce off of the ceiling
	if (Level.wallHeight > 0 && particle->z >= (float)Level.wallHeight)
	{
		particle->velZ = -0.2f;
	}

	particle->lifespan -= deltaTime;
}

void P_Env_Update(Particle *particle, float deltaTime)
{
	particle->x += particle->velX * deltaTime;
	particle->y += particle->velY * deltaTime;
	particle->z += particle->velZ * deltaTime;

	particle->velX += (Math_RandomFloat() * 2.0f - 1.0f) * deltaTime;
	particle->velY += (Math_RandomFloat() * 2.0f - 1.0f) * deltaTime;

	// Reset position on ground or ceiling hit
	if (particle->z <= 0.0f)
	{
		particle->x = TOFLOAT(World_Players[0].sprite->x) + (Math_RandomFloat() * 2.0f - 1.0f) * ENV_RADIUS;
		particle->y = TOFLOAT(World_Players[0].sprite->y) + (Math_RandomFloat() * 2.0f - 1.0f) * ENV_RADIUS;
		particle->z = Math_Abs((float)Level.wallHeight);
	}
	else if (particle->z >= Math_Abs((float)Level.wallHeight))
	{
		particle->x = TOFLOAT(World_Players[0].sprite->x) + (Math_RandomFloat() * 2.0f - 1.0f) * ENV_RADIUS;
		particle->y = TOFLOAT(World_Players[0].sprite->y) + (Math_RandomFloat() * 2.0f - 1.0f) * ENV_RADIUS;
		particle->z = 0.0f;
	}
}

void updateParticles(float deltaTime)
{
	for (int i = 0; i < NUM_PARTICLES; ++i) // update used particle slots
	{
		Particle *particle = &particles[i];
		if (particle->lifespan <= 0.0f)
		{
			if (Level.ambientParticleColour != 0)
				// Dead physics particles become ambient particles once their lifetime runs out.
				SetupEnvironmentParticle(particle, Level.ambientParticleSpeed, Level.ambientParticleColour);
			else
				continue;
		}

		if (particle->type == PTYPE_PHYSICS)
		{
			P_Phys_Update(particle, deltaTime);
		}
		else if (particle->type == PTYPE_ENVIRONMENT)
		{
			P_Env_Update(particle, deltaTime);
		}
	}
}

void renderParticles(void)
{
	for (int i = 0; i < NUM_PARTICLES; ++i)
	{
		if (particles[i].lifespan > 0)
		{
			Gfx_DrawParticle(&particles[i]);
		}
	}
}

void clearParticles(void)
{
	for (int i = 0; i < NUM_PARTICLES; ++i)
	{
		particles[i].lifespan = 0;
		particles[i].type = PTYPE_PHYSICS;
	}
}

void particleBurst(float x, float y, float z, float xd, float yd, float zd, int col, int count)
{
	for (int i = 0; i < count; i++)
	{
		Particle *particle = &particles[currentId];
		particle->x = x;
		particle->y = y;
		particle->z = z;
		particle->velX = Math_RandomFloat() * xd;
		particle->velY = Math_RandomFloat() * yd;
		particle->velZ = Math_RandomFloat() * zd;
		particle->type = PTYPE_PHYSICS;
		particle->colour = col;
		float minSpeed = 0.3f;
		float maxSpeed = 2.0f;

		float speed = Math_RandomFloat() * (maxSpeed - minSpeed) + minSpeed;

		// Random direction in spherical coordinates
		float theta = Math_RandomFloat() * 2 * MATH_PI; // Horizontal angle
		float phi = Math_RandomFloat() * MATH_PI / 2;   // Vertical angle (limit to upper hemisphere)

		particle->velX += speed * Math_Cos(theta) * Math_Sin(phi);
		particle->velY += speed * Math_Sin(theta) * Math_Sin(phi);
		particle->velZ += speed * Math_Cos(phi);
		particle->lifespan = Math_RandomFloat() * 6.0f + 4.0f;
		
		currentId = (currentId + 1) % NUM_PARTICLES;
	}
}

void EnvironmentParticles(void)
{
	int count = NUM_PARTICLES;
	float downVel = Level.ambientParticleSpeed;
	int col = Level.ambientParticleColour;
	if (Level.ambientParticleColour == 0)
		return;
	for (int i = 0; i < count; i++)
	{
		SetupEnvironmentParticle(&particles[currentId], downVel, col);
		currentId = (currentId + 1) % NUM_PARTICLES;
	}
}
