#include <cmath>
#include <ctime>
#include <cstdlib>

#include "api/ParticleEmitter.h"
#include "api/Vector2.h"

#include "ComponentManager.h"
#include "ParticleSystem.h"

using namespace crepe;

ParticleSystem::ParticleSystem() {}

void ParticleSystem::update() {

	// Get all emitters
	ComponentManager & mgr = ComponentManager::get_instance();
	std::vector<std::reference_wrapper<ParticleEmitter>> emitters
		= mgr.get_components_by_type<ParticleEmitter>();

	for (ParticleEmitter & emitter : emitters) {

		//	Get transform linked to emitter
		const Transform& transform = mgr.get_components_by_id<Transform>(emitter.game_object_id).front().get();

		
		// Emit particles based on emission_rate
		int updates = calculate_update(this->update_count,emitter.data.emission_rate);
		for (size_t i = 0; i < updates; i++)
		{
			emit_particle(emitter,transform);		
		}
		
		// Update all particles
		for (size_t j = 0; j < emitter.data.particles.size(); j++) {
			if (emitter.data.particles[j].active) {
				emitter.data.particles[j].update();
			}
		}

		//	Check if within boundary
		check_bounds(emitter,transform);
	}

	update_count++;
	if(update_count == MAX_UPDATE_COUNT) update_count = 0;
}

void ParticleSystem::emit_particle(ParticleEmitter & emitter,const Transform& transform) {
	constexpr double DEG_TO_RAD = M_PI / 180.0;

	Vector2 initial_position = emitter.data.position + transform.position;
	double min_angle = emitter.data.min_angle;
	double max_angle = emitter.data.max_angle;
	double random_angle;
	if(min_angle == max_angle){
		random_angle = min_angle;
	}
	else if (min_angle <= max_angle) {
			// Standard range (e.g., 10 to 20 degrees)
			double angle_offset = max_angle - min_angle;
			random_angle = min_angle + static_cast<double>(std::rand() % static_cast<uint32_t>(angle_offset));
	} else {
			// Wrap-around range (e.g., 350 to 10 degrees)
			double angle_offset = (360 - min_angle) + max_angle;
			random_angle = min_angle + static_cast<double>(std::rand() % static_cast<uint32_t>(angle_offset));
			
			// Wrap around to keep random_angle within 0-360 degrees
			if (random_angle >= 360) {
					random_angle -= 360;
			}
	}

	// Generate a random speed between min_speed and max_speed
	double speed_offset = emitter.data.max_speed - emitter.data.min_speed;
	double random_speed = 0.0;
	if(emitter.data.max_speed  == emitter.data.min_speed)
	{
		random_speed = emitter.data.min_speed;
	}
	else {
		random_speed = emitter.data.min_speed + static_cast<double>(std::rand() % static_cast<uint32_t>(speed_offset));
	}
	 
	// Convert random_angle to radians
	double angle_radians = random_angle * DEG_TO_RAD;

	Vector2 velocity = {
        random_speed * std::cos(angle_radians),
        random_speed * std::sin(angle_radians)
    };


	for (size_t i = 0; i < emitter.data.particles.size(); i++) {
		if (!emitter.data.particles[i].active) {
			emitter.data.particles[i].reset(emitter.data.end_lifespan, initial_position,velocity,random_angle);
			break;
		}
	}
}

int ParticleSystem::calculate_update(int count, double emission) {

	//get interger part of the emission
	double integer_part = std::floor(emission);

	// Get the fractional part of the emission
	double fractional_part = emission - integer_part;

	// Convert the fractional part to a denominator value
	int denominator = static_cast<int>(1.0 / fractional_part);

	// For emissions like 0.01, 0.1, 0.5, etc., calculate the update frequency
	if (fractional_part > 0) {
		// Calculate how often the update should be triggered based on the fractional part
		if (count % denominator == 0) {
			return 1;
		} else {
			return 0;
		}
	}
	
	// For integer emissions, return the emission directly
	return static_cast<int>(emission);
}

void ParticleSystem::check_bounds(ParticleEmitter & emitter,const Transform& transform)
{
	Vector2 offset = emitter.data.boundary.offset + transform.position + emitter.data.position;
	double half_width = emitter.data.boundary.width / 2.0;
	double half_height = emitter.data.boundary.height / 2.0;

	// Define boundary edges
	const double left = offset.x - half_width;
	const double right = offset.x + half_width;
	const double top = offset.y - half_height;
	const double bottom = offset.y + half_height;

	std::vector<Particle>& particles = emitter.data.particles;
	for (Particle& particle : particles)
	{
		const Vector2& position = particle.position;

		// Check if particle is within bounds
		bool within_bounds = (position.x >= left && position.x <= right && position.y >= top && position.y <= bottom);
		if (!within_bounds)
		{
			if (emitter.data.boundary.reset_on_exit)
			{
				particle.active = false;
			}
			else
			{
				particle.velocity = {0, 0};
				if (particle.position.x < left) particle.position.x = left;
        else if (particle.position.x > right) particle.position.x = right;
        if (particle.position.y < bottom) particle.position.y = bottom;
        else if (particle.position.y > top) particle.position.y = top;
			}
		}
	}
}