#include "ParticleSystem.hpp"
#include <algorithm>
#include <cmath>
#include <ctime>
#include "utils/Time.hpp"
#include "elements/Transformable.hpp"

void TSE::ParticleSystem::OnUpdate()
{
    if(firstFrame)
    {
        firstFrame = false;
        return;
    }
    particles.erase(
        std::remove_if(particles.begin(), particles.end(),
        [](auto* particle)
        {
            particle->lifetime -= Time::deltaTime();
            if (particle->lifetime <= 0.0f) {
                delete particle;
                return true; // aus vector löschen
            }
            return false; // behalten
        }),
        particles.end()
    );
    for(auto particle : particles)
    {
        MoveParticle(particle);
        RotateParticle(particle);
        ColorParticle(particle);
    }
    time += Time::deltaTime();
    if(particles.size() < particleCount)
    {
        const float spawnInterwal = maxLifetime / particleCount;
        if(spawnInterwal < Time::deltaTime())
        {
            time = 0;
            int count = Time::deltaTime() / spawnInterwal;
            for (size_t i = 0; i < count; i++)
            {
                CreateParticle();
            }
        }
        else
        {
            if(time >= spawnInterwal)
            {
                time = 0;
                CreateParticle();
            }
        }

    }
}

void TSE::ParticleSystem::Start()
{
    rng = std::mt19937(std::time(nullptr));
}

const std::vector<TSE::Particle *> &TSE::ParticleSystem::GetParticles()
{
    return particles;
}

float naive_lerp(float a, float b, float t)
{
    return a + t * (b - a);
}

void TSE::ParticleSystem::MoveParticle(Particle *&particle)
{
    float age = particle->lifetime / maxLifetime;
    float speed = startSpeed;
    if(changeSpeedOverLifetime)
    {
        speed = naive_lerp(startSpeed, endSpeed, age);
    }
    if(speed == 0) return;
    particle->position = Vector3::Lerp(particle->position, particle->position + particle->direction, speed * Time::deltaTime());
}

void TSE::ParticleSystem::RotateParticle(Particle *&particle)
{
    float age = particle->lifetime / maxLifetime;
    float speed = startRotationSpeed;
    if(changeRotationspeedOverLifetime)
    {
        speed = naive_lerp(startRotationSpeed, endRotationSpeed, age);
    }
    if(speed == 0) return;
    particle->rotationRad = naive_lerp(particle->rotationRad, particle->rotationRad - particle->rotDirection, speed * Time::deltaTime());
}

void TSE::ParticleSystem::ColorParticle(Particle *&particle)
{
    float age = particle->lifetime / maxLifetime;
    Color c = startColor;
    if(changeColorOverLifetime)
    {
        c = Color::Lerp(endColor, startColor, age);
    }
    particle->color = c;
}

void TSE::ParticleSystem::CreateParticle()
{
    std::uniform_real_distribution<float> dist(0.0f, 1.0f);
    std::uniform_int_distribution<int> distI(0,1);

    Particle* part = new Particle();
    part->color = startColor;
    part->lifetime = maxLifetime;
    if(startWithRandomLifetime)
    {
        part->lifetime = naive_lerp(minLifetime, maxLifetime, dist(rng));
    }
    part->size = maxSize;
    if(startWithRandomSize)
    {
        part->size = naive_lerp(minSize, maxSize, dist(rng));
    }
    part->rotationRad = maxrotationRad;
    if(startWithRandomRotation)
    {
        part->rotationRad = naive_lerp(minRotationRad, maxrotationRad, dist(rng));
    }
    part->rotDirection = rotationDirection;
    if(startWithRandomRotationDirection)
    {
        int rnd = distI(rng);
        if(rnd == 0) rnd--;
        part->rotDirection = rnd;
    }
    Vector3 localPos = SamplePointInVolume(rng);
    part->position = baseObject->LocalToGlobalPosition(localPos);
    Vector3 direction = localPos - offset;
    direction.Normalize();
    Vector3 globalOBJPos = baseObject->GetGlobalPosition();
    Vector3 globalOffsetPos = baseObject->LocalToGlobalPosition(direction);

    Vector3 globalDirection = globalOffsetPos - globalOBJPos;
    globalDirection.Normalize();
    part->direction = globalDirection;

    particles.push_back(part);
}

float TSE::ParticleSystem::PlaneAsCosAlpha()
{
    return std::clamp(conePlane * 2.0f - 1.0f, -1.0f, 1.0f);
}

bool TSE::ParticleSystem::InVolume(const Vector3 &x)
{
    // no cone set
    if(conePlane == 0) return false;
    
    // out of radius
    const Vector3 v = x - offset;
    const float r2 = v.LengthSqr();
    if(r2 > radius * radius ) return false;

    //full circle
    if(conePlane == 1 || r2 <= TSE_EPSILON) return true;

    const float invr = 1.0f / std::sqrt(r2);
    const float cosTheta = Vector3::Dot(Vector3(v.x * invr, v.y * invr, v.z * invr), forward);
    const float cosA = PlaneAsCosAlpha();

    if (conePlane <= 0.5f) return cosTheta >= cosA;
    else                   return cosTheta <= cosA; // Inverse
}

void TSE::ParticleSystem::BuildTangent(Vector3 &t, Vector3 &b)
{
    if (std::abs(forward.z) < 0.999f)
    {
        t = Vector3::Cross(forward, Vector3::forward);
    }
    else
    {
        t = Vector3::Cross(forward, Vector3::up);
    }
    t.NormalizeSafe(Vector3::right);
    
    b = Vector3::Cross(forward, t);
}

TSE::Vector3 TSE::ParticleSystem::SampleDirection(float u1, float u2)
{
    const float cosA = PlaneAsCosAlpha();

    float z;
    if(conePlane == 1)
    {
        z = -1.0f + 2.0f * u1;
    }
    // else if(conePlane <= 0.5f)
    // {
    //     z = 1.0f - u1 * (1.0f - cosA);
    // }
    else
    {
        z = -1.0f + u1 * (1.0f + cosA);
    }

    const float phi = 2.0f * TSE_PI * u2;
    const float s = std::sqrt(((0.0f) > (1.0f - z * z)) ? (0.0f) : (1.0f - z * z));

    Vector3 t, b;
    BuildTangent(t,b);
    return Vector3(t.x * (s * std::cos(phi)) + b.x * (s * std::sin(phi)) + forward.x * z,
                   t.y * (s * std::cos(phi)) + b.y * (s * std::sin(phi)) + forward.y * z,
                   t.z * (s * std::cos(phi)) + b.z * (s * std::sin(phi)) + forward.z * z);
}

TSE::Vector3 TSE::ParticleSystem::samplePointInVolume(float u1, float u2, float u3)
{
    const Vector3 dir = SampleDirection(u1,u2);
    const float r = radius * std::cbrt(u3);
    return offset + dir * r;
}

TSE::Vector3 TSE::ParticleSystem::SamplePointInVolume(std::mt19937 &rng)
{
    if(conePlane == 0) return Vector3::zero;
    std::uniform_real_distribution<float> U(0.0f, 1.0f);
    return samplePointInVolume(U(rng), U(rng), U(rng));
}
