#pragma once

#include <enkiTS/TaskScheduler.h>

#include "crArray.h"
#include "crBatchedBillboards.h"
#include "crRandom.h"
#include "crStruct.h"

class crApp;
class crGraphics;

struct CParticleEmitter;
struct crAtlasSprite;

// enkiTS task adapter: integrates a disjoint range of particles — fused sweep (velocity, position,
// age, and the size/color life ramps in one touch); each particle reads and writes only its own
// slot, so ranges run without contention
class crParticleUpdateTask : public enki::ITaskSet
{
public:
    crBatchedBillboards::BillboardInstance* instances = nullptr;   // pos integrated, size/color/rotation advanced in place
    float3* vel         = nullptr;
    float*  age         = nullptr;
    float*  invLifetime = nullptr;
    float*  sizeStart   = nullptr;
    float*  sizeEnd     = nullptr;
    color4* colorStart  = nullptr;
    color4* colorEnd    = nullptr;
    float2* rotStep     = nullptr;   // per-step rotation delta as cos/sin — spin advances by complex
                                     // multiply, so the sweep needs no trig (zero spin = identity (1,0))
    float3* gravity     = nullptr;   // per-particle sim params, captured from the emitter at spawn
    float*  drag        = nullptr;

    float dt = 0.0f;

    void ExecuteRange( enki::TaskSetPartition range, uint32_t threadIndex ) override;
};

// particle pool + sim. the AoS instance array doubles as the sim's pos/size/color storage, so the
// FixedUpdate sweep touches each particle once and Render() uploads it with zero CPU work.
// step order: parallel integrate -> serial dead compaction (swap-remove) -> emit. RNG streams live
// per emitter slot (not per thread), so results are deterministic under any scheduling.
// visual-only — not part of the sim hash
class crParticleSystem
{
public:
    static constexpr int32_t CAPACITY = 65536;   // pool ceiling — spawns beyond it are dropped

private:
    static constexpr uint32_t UPDATE_GRAIN = 4096;   // particles per enki partition

    static constexpr uint64_t EMITTER_SEED_BASE = 0x5EEDBA5E;   // per-slot seed = base + allocation counter
    static constexpr uint64_t BURST_SEED        = 0x5EEDB0B5;

    // per-emitter state — CParticleEmitter.poolSlot indexes into this pool. the RNG stream follows
    // the emitter so spawn randomness is deterministic regardless of thread scheduling
    struct EmitterSlot
    {
        crRandom rng;
        float    accum;   // fractional spawns carried between steps
        bool     used;
        bool     seen;    // sweep mark: touched by a living emitter this step
    };

    crBatchedBillboards _batch;

    // instance AoS (pos/size/color, render-ready) + sim SoA — index-parallel, dense in [0, _count).
    // lifetime is stored inverted: no per-step division, and death is simply age * inv >= 1
    crArray<crBatchedBillboards::BillboardInstance> _instances;
    crArray<float3> _vel;
    crArray<float>  _age;
    crArray<float>  _invLifetime;
    crArray<float>  _sizeStart;    // life ramp endpoints — the sweep lerps them into the instance
    crArray<float>  _sizeEnd;
    crArray<color4> _colorStart;
    crArray<color4> _colorEnd;
    crArray<float2> _rotStep;      // per-step spin delta (cos/sin); the current angle lives in the instance
    crArray<float3> _gravity;      // per-particle sim params, captured from the emitter at spawn
    crArray<float>  _drag;
    crArray<uint8_t> _atlas;       // per-particle atlas id — draw routing only, never uploaded
    int32_t         _count  = 0;
    bool            _warned = false;

    crArray<crBatchedBillboards::BillboardInstance> _renderScratch;   // atlas-grouped copy — multi-atlas frames only
    int32_t _drawsPrev = 0;

    crArray<EmitterSlot> _emitters;
    uint64_t             _seedCounter   = 0;   // allocation-ordered -> deterministic per-slot seeds
    bool                 _emitterWarned = false;

    crRandom _burstRng;   // EmitBurst stream — reset in Clear, deterministic by call order

public:
    void Init();
    void Cleanup();
    void Clear();

    void FixedUpdate( const crApp* app, float fdt );   // sim step: integrate + compact + emit from ECS emitters
    void EmitBurst( const crApp* app, const CParticleEmitter& params, float3 pos, int32_t count );   // one-shot: count particles now at pos — rate/poolSlot are ignored
    void Render( GLuint program, const crGraphics* graphics );   // one instanced draw per atlas in use — additive blend lets the buckets reorder freely

    int32_t DrawsLastRender() const { return _drawsPrev; }

private:
    int32_t AllocEmitterSlot();   // reserve a slot (returns its index); grows with a one-shot warning
    void    Emit( const crApp* app, float fdt );   // sweep CParticleEmitter entities, spawn at their sim positions
    void    SpawnFromParams( const CParticleEmitter& e, float3 pos, const crAtlasSprite& sprite, int32_t atlas, crRandom& rng, float fdt );   // one randomized particle from emitter-style params
    void    Spawn( float3 pos, float3 vel, float lifetime, float sizeStart, float sizeEnd, color4 colorStart, color4 colorEnd, float2 uvMin, float2 uvMax, int32_t atlas, float2 rotCosSin, float2 rotStep, float3 gravity, float drag );
};
