/********************************************************************
	Minecraft: Pocket Edition - Decompilation Project
	Copyright (C) 2023 iProgramInCpp

	The following code is licensed under the BSD 1 clause license.
	SPDX-License-Identifier: BSD-1-Clause
 ********************************************************************/

#include "Explosion.hpp"

Explosion::Explosion(Level* level, Entity* entity, float x, float y, float z, float power)
{
	field_20 = 0;

	m_bIsFiery = false;

	m_pos = Vec3(x, y, z);
	m_power = power;
	m_pEntity = entity;
	m_pLevel = level;

	assert(m_power != 0.0f);
}

// This doesn't do a complete explosion, instead, it only adds tiles that should be blown up.
// addParticles() completes the explosion.
void Explosion::explode()
{
	for (int i = 0; i < 16; i++)
	{
		for (int j = 0; j < 16; j++)
		{
			for (int k = 0; k < 16; k++)
			{
				// what
				if (i != 0 && j != 0 && k != 0 && i != 15 && j != 15 && k != 15)
					continue;

				float rayX = float(i) / 15.0f * 2.0f - 1.0f;
				float rayY = float(j) / 15.0f * 2.0f - 1.0f;
				float rayZ = float(k) / 15.0f * 2.0f - 1.0f;

				// normalize the ray vector
				float length = sqrtf(rayX * rayX + rayY * rayY + rayZ * rayZ);
				rayX /= length;
				rayY /= length;
				rayZ /= length;

				float mult = m_power * (0.7f + 0.6f * m_pLevel->m_random.nextFloat());

				float posX = m_pos.x;
				float posY = m_pos.y;
				float posZ = m_pos.z;

				while (true)
				{
					if (mult < 0)
						break;

					int pxf = Mth::floor(posX);
					int pyf = Mth::floor(posY);
					int pzf = Mth::floor(posZ);

					TileID tile = m_pLevel->getTile(pxf, pyf, pzf);
					if (tile > 0)
						mult -= 0.3f * (0.3f + Tile::tiles[tile]->getExplosionResistance(m_pEntity));

					if (mult > 0)
						m_tiles.insert(TilePos(pxf, pyf, pzf));

					mult -= 0.225f;

					if (mult < 0)
						break;

					posX += rayX * 0.3f;
					posY += rayY * 0.3f;
					posZ += rayZ * 0.3f;
				}
			}
		}
	}

	m_power *= 2;

	AABB aabb(
		(float)Mth::floor(m_pos.x - m_power - 1.0f),
		(float)Mth::floor(m_pos.y - m_power - 1.0f),
		(float)Mth::floor(m_pos.z - m_power - 1.0f),
		(float)Mth::floor(m_pos.x + m_power + 1.0f),
		(float)Mth::floor(m_pos.y + m_power + 1.0f),
		(float)Mth::floor(m_pos.z + m_power + 1.0f)
	);

	EntityVector* pEnts = m_pLevel->getEntities(m_pEntity, aabb);
	for (int i = 0; i < pEnts->size(); i++)
	{
		Entity* entity = pEnts->at(i);
		float distPowerRatio = entity->distanceTo(m_pos.x, m_pos.y, m_pos.z) / m_power;
		if (distPowerRatio > 1.0f)
			continue;

		float deltaX = entity->m_pos.x - m_pos.x;
		float deltaY = entity->m_pos.y - m_pos.y;
		float deltaZ = entity->m_pos.z - m_pos.z;

		// @NOTE: They used it here, but not when normalizing the 16*16*16=4096 rays shot before...
		float normInv = Mth::invSqrt(deltaX * deltaX + deltaY * deltaY + deltaZ * deltaZ);
		float hurtPercent = m_pLevel->getSeenPercent(m_pos, entity->m_hitbox) * (1.0f - distPowerRatio);

		entity->hurt(m_pEntity, int((hurtPercent * hurtPercent + hurtPercent) / 2.0f * 8.0f * this->m_power + 1.0f));

		entity->m_vel.x += deltaX * normInv * hurtPercent;
		entity->m_vel.y += deltaY * normInv * hurtPercent;
		entity->m_vel.z += deltaZ * normInv * hurtPercent;
	}

	std::vector<TilePos> vec;
	// @NOTE: Could avoid this copy if m_bFiery is false
	vec.insert(vec.begin(), m_tiles.begin(), m_tiles.end());

	if (!m_bIsFiery)
		return;

	for (int i = int(vec.size() - 1); i >= 0; i--)
	{
		TilePos tp = vec[i];

		TileID tile = m_pLevel->getTile(tp.x, tp.y, tp.z), tileBelow = m_pLevel->getTile(tp.x, tp.y - 1, tp.z);
		if (tile == TILE_AIR && Tile::solid[tileBelow] && m_random.nextInt(3) == 0)
			m_pLevel->setTile(tp.x, tp.y, tp.z, Tile::fire->m_ID);
	}
}

void Explosion::addParticles()
{
	m_pLevel->playSound(m_pos.x, m_pos.y, m_pos.z, "random.explode", 4.0f, 0.7f * (1.0f + 0.2f * (m_pLevel->m_random.nextFloat() - m_pLevel->m_random.nextFloat())));

	std::vector<TilePos> vec;
	vec.insert(vec.begin(), m_tiles.begin(), m_tiles.end());

	for (int i = int(vec.size() - 1); i >= 0; i--)
	{
		TilePos tp = vec[i];
		TileID tile = m_pLevel->getTile(tp.x, tp.y, tp.z);

		// Spawn a particle only for every 8th tile. Weird
		if ((i & 0x7) == 0)
		{
			float x2, y2, z2, vx, vy, vz, mult;
			x2 = float(tp.x) + m_pLevel->m_random.nextFloat();
			y2 = float(tp.y) + m_pLevel->m_random.nextFloat();
			z2 = float(tp.z) + m_pLevel->m_random.nextFloat();

			float dX, dY, dZ;
			dX = x2 - m_pos.x;
			dY = y2 - m_pos.y;
			dZ = z2 - m_pos.z;

			// @NOTE: Can use Mth::invSqrt
			float dist = Mth::sqrt(dX * dX + dY * dY + dZ * dZ);
			vx = dX / dist;
			vy = dY / dist;
			vz = dZ / dist;

			// @HUH: Dividing by the inverse is the same as multiplying. Thanks, IDA! :)
			float power1 = m_power / (1.0f / dist) + 0.1f;

			mult = ((m_pLevel->m_random.nextFloat() * m_pLevel->m_random.nextFloat()) + 0.3f) * (0.5f / power1);

			m_pLevel->addParticle("explode", (x2 + m_pos.x) / 2, (y2 + m_pos.y) / 2, (z2 + m_pos.z) / 2, vx * mult, vy * mult, vz * mult);
			m_pLevel->addParticle("smoke", x2, y2, z2, vx * mult, vy * mult, vz * mult);
		}

		if (tile > 0)
		{
			Tile::tiles[tile]->spawnResources(m_pLevel, tp.x, tp.y, tp.z, m_pLevel->getData(tp.x, tp.y, tp.z), 0.3f);
			m_pLevel->setTile(tp.x, tp.y, tp.z, TILE_AIR);
			Tile::tiles[tile]->wasExploded(m_pLevel, tp.x, tp.y, tp.z);
		}
	}

}
