#ifndef D_EMITTER_H
#define D_EMITTER_H

#include "JSystem/JParticle/JPADrawInfo.h"
#include "JSystem/JParticle/JPAEmitter.h"
#include "JSystem/JParticle/JPAParticle.h"
#include "common.h"
#include "d/a/obj/d_a_obj_base.h"
#include "d/col/c/c_cc_d.h"
#include "d/d_base.h"
#include "m/m2d.h"
#include "m/m_allocator.h"
#include "m/m_angle.h"
#include "m/m_color.h"
#include "m/m_mtx.h"
#include "m/m_vec.h"
#include "rvl/GX/GXTypes.h"
#include "toBeSorted/d_d3d.h"
#include "toBeSorted/d_unk_mdl_stuff_2.h"
#include "toBeSorted/tlist.h"

#include <cstddef>

class dEmitterBase_c;
class dEmitterCallback_c;
class dParticleCallback_c;

#include "toBeSorted/d_emitter_resource_map.inc"

class dEmitterBase_c {
    friend class dEmitterCallback_c;
    friend class dParticleCallback_c;

public:
    dEmitterBase_c() : mpEmitterHead(nullptr), mpEmitterCallback(nullptr), mpParticleCallback(nullptr) {}

    bool createEmitters(
        u16 effectResourceId, const mVec3_c &position, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1,
        const GXColor *c2, s32 idx1, s32 idx2
    );

    dEmitterCallback_c *getEmitterCallback() const {
        return mpEmitterCallback;
    }

    void setEmitterCallback(dEmitterCallback_c *cb);
    void setParticleCallback(dParticleCallback_c *cb);

protected:
    void deactivateEmitters();
    void stopCalcEmitters();
    void playCalcEmitters();
    static void loadColors(JPABaseEmitter *emitter, const GXColor *c1, const GXColor *c2, s32 idx1, s32 idx2);

    void setImmortal();

    static JPABaseEmitter *GetNextEmitter(JPABaseEmitter *head);

    /* 0x00 */ JPABaseEmitter *mpEmitterHead;
    /* 0x04 */ dEmitterCallback_c *mpEmitterCallback;
    /* 0x08 */ dParticleCallback_c *mpParticleCallback;
    /* 0x0C */ TListNode<dEmitterBase_c> mEmitterCallbackNode;
    /* 0x14 */ TListNode<dEmitterBase_c> mParticleCallbackNode;

public:
    // vtable at 0x1C
    virtual ~dEmitterBase_c();

    void stopDrawParticles();
    void playDrawParticles();
    void setPosRotScale(const mVec3_c &position, const mAng3_c *rot, const mVec3_c *scale);
    void setPosRotScaleWithScreenScale(const mVec3_c &position, const mAng3_c *rot, const mVec3_c *scale);
    void setTransform(const mMtx_c &mtx);
    void loadColors(const GXColor *c1, const GXColor *c2, s32 idx1, s32 idx2);
    void setGlobalAlpha(u8 alpha);
    void setRate(f32 rate);
    void setDynamicsScale(const mVec3_c &scale);
    void setParticleScale(const mVec3_c &scale);
    void setAwayFromCenterSpeed(f32 speed);
    void setVolumeSize(u16 size);
    void setLifeTime(s16 lifetime);
    void attachEmitterCallbackId(s32 id);     // corresponds to setup at 80028a80
    void bindShpEmitter(s32 id, bool unused); // corresponds to setup at 8002b6b0
};

class dEmitter_c : public dEmitterBase_c {
public:
    enum Flags_e {
        /** This emitter has been newly set up. */
        EMITTER_New = 0x1,
        /** This emitter actually does something. */
        EMITTER_Active = 0x2,
        /** This emitter will stop when it's done (not held). */
        EMITTER_OneShot = 0x4,
        /** This emitter has a changing alpha value */
        EMITTER_Fading = 0x8,
        EMITTER_0x10 = 0x10,
        EMITTER_0x20 = 0x20,
    };

    // vt at 0x1C
    dEmitter_c();
    dEmitter_c(dBase_c *);
    virtual ~dEmitter_c();

    inline void init(dBase_c *owner) {
        mpOwner = owner;
    }

    inline dBase_c *getOwner() const {
        return mpOwner;
    }

    void addToActiveEmittersList(u16 resourceId, bool bFlags);
    void removeFromActiveEmittersList();
    void execute();
    void setFading(u8 lifetime);

    void remove(bool bForceDeleteEmitters);

    bool holdEffect(
        u16 resourceId, const mVec3_c &pos, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1,
        const GXColor *c2
    );
    bool holdUIEffect(
        u16 resourceId, const mVec3_c &pos, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1,
        const GXColor *c2
    );
    bool holdEffect(u16 resourceId, const mMtx_c &transform, const GXColor *c1, const GXColor *c2);

    bool startEffect(
        u16 resourceId, const mVec3_c &pos, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1,
        const GXColor *c2
    );
    bool startUIEffect(
        u16 resourceId, const mVec3_c &pos, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1,
        const GXColor *c2
    );
    bool startEffect(u16 resourceId, const mMtx_c &transform, const GXColor *c1, const GXColor *c2);

    bool hasEmitters() const {
        return mpEmitterHead != 0;
    }

    u8 getGroupId() const {
        return mpEmitterHead->getGroupID();
    }

    bool checkFlag(u32 flag) const {
        return mFlags & flag;
    }

    void onFlag(u32 flag) {
        mFlags |= flag;
    }

    void offFlag(u32 flag) {
        mFlags &= ~flag;
    }

    void realizeAlpha();
    bool areAllEmittersDone();

protected:
    bool canReuse(u16 resourceId) const {
        return hasEmitters() && !checkFlag(EMITTER_Active) && mEffect == resourceId;
    }

    bool setupEffect(
        bool create, u16 resourceId, const mVec3_c &pos, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1,
        const GXColor *c2
    );
    bool setupEffect(bool create, u16 resourceId, const mMtx_c &transform, const GXColor *c1, const GXColor *c2);
    bool getOwnerPolyAttrs(s32 *pOut1, s32 *pOut2);

public:
    /* 0x24 */ TListNode<dEmitter_c> mNode;

protected:
    /* 0x28 */ dBase_c *mpOwner;
    /* 0x2C */ u8 mFadeTimer;
    /* 0x2D */ u8 mFadeDuration;
    /* 0x2E */ u8 _0x2D[0x30 - 0x2E];
    /* 0x30 */ u16 mFlags;
    /* 0x32 */ u16 mEffect;
};

class dWaterEffect_c {
private:
    /* 0x00 */ bool mIsInWater;
    /* 0x01 */ bool mIsSmall;
    /* 0x04 */ f32 mHeight;
    /* 0x08 */ f32 mDepth;
    /* 0x0C */ f32 mScale;
    /* 0x10 */ dEmitter_c mEff;

    dAcObjBase_c *getActor() const {
        return static_cast<dAcObjBase_c *>(mEff.getOwner());
    }

    f32 getActorGroundPos(dAcObjBase_c *obj) const {
        return obj->mPosition.y + mDepth;
    }

    f32 getActorCeilPos(dAcObjBase_c *obj) const {
        return obj->mPosition.y + mHeight;
    }

public:
    dWaterEffect_c() : mIsInWater(true), mIsSmall(false) {}
    virtual ~dWaterEffect_c() {}

    void init(dAcObjBase_c *, f32 height, f32 scale, f32 depth);
    void execute(f32 water, f32 ground);

    void setIsSmall(bool val) {
        mIsSmall = val;
    }
};

/** An emitter callback can be part of multiple emitters. */
class dEmitterCallback_c : public JPAEmitterCallBack {
    friend class dEmitterBase_c;

    typedef TList<dEmitterBase_c, offsetof(dEmitterBase_c, mEmitterCallbackNode)> EmitterCallbackList;

public:
    virtual ~dEmitterCallback_c();
    virtual void create(JPABaseEmitter *) {}
    virtual void vt_0x20(f32, f32) {}

    void remove(dEmitterBase_c *emitter);

    /* 0x04 */ EmitterCallbackList mEmitterList;
};

/** A particle callback can be part of multiple emitters. */
class dParticleCallback_c : public JPAParticleCallBack {
    friend class dEmitterBase_c;

    typedef TList<dEmitterBase_c, offsetof(dEmitterBase_c, mParticleCallbackNode)> ParticleCallbackList;

public:
    virtual ~dParticleCallback_c();
    virtual void create(JPABaseEmitter *) {}

    void remove(dEmitterBase_c *emitter);

    /* 0x04 */ ParticleCallbackList mEmitterList;
};

class dParticleFogProc_c : public d3d::UnkProc {
public:
    dParticleFogProc_c() {}
    virtual ~dParticleFogProc_c() {}
    virtual void drawOpa() override {
        doDraw();
    }
    virtual void drawXlu() override {
        doDraw();
    }

    bool create(u32 idx, s32 prioOpa, s32 prioXlu, mHeapAllocator_c *alloc);

private:
    void doDraw();
    /* 0x18 */ u32 mIdx;
    /* 0x1C */ bool field_0x1C;
};

class dEffect2D_c : public m2d::Base_c {
public:
    dEffect2D_c() : m2d::Base_c(0x80) {}
    /* vt 0x08 */ virtual ~dEffect2D_c() {}
    /* vt 0x0C */ virtual void draw() override;

    void create(u32 groupId, u8);

private:
    /* 0x10 */ u32 mGroupId;
};

class dShpEmitter_c;

class dShpEmitterProc : public dShpProc1_c {
public:
    dShpEmitterProc() : mpOwner(nullptr) {}
    virtual ~dShpEmitterProc() {
        mpOwner = nullptr;
    }
    virtual void drawOpa() override {
        doDraw();
    }
    virtual void drawXlu() override {
        doDraw();
    }

    void setOwner(dShpEmitter_c *cb) {
        mpOwner = cb;
    }

    void setAlpha(s32 idx, u8 val) {
        mAlpha[idx] = val;
    }

private:
    void doDraw();
    /* 0x34 */ dShpEmitter_c *mpOwner;
    // TODO: This is read from JParticle as alpha, but used as the nw4r::g3d lightSetId.
    // Is this a creative use of the particle color value, given that particles themselves
    // are never drawn?
    /* 0x38 */ u8 mAlpha[100];
};

class dShpEmitter_c : public JPAParticleCallBack {
public:
    virtual ~dShpEmitter_c() {}
    virtual void draw(JPABaseEmitter *, JPABaseParticle *) override;

    bool create(mHeapAllocator_c *alloc);
    void init(const char *mdlName, const char *arcName, bool priority);
    void clear();
    void remove();
    void draw() {
        mProc.entry();
    }

private:
    /* 0x04 */ dShpEmitterProc mProc;
    /* 0xA0 */ s32 field_0xA0;
};

class CommonEmitterCallback : public JPAEmitterCallBack {
public:
    CommonEmitterCallback() : field_0x04(0) {}
    virtual ~CommonEmitterCallback() {}
    virtual void draw(JPABaseEmitter *) override;
    void init(s32 v) {
        field_0x04 = v;
    }

private:
    /* 0x04 */ s32 field_0x04;
};

class dMassObjEmitterCallback_c : public dEmitterCallback_c {
public:
    virtual ~dMassObjEmitterCallback_c() {}
    virtual void executeAfter(JPABaseEmitter *) override;

    void execute();
    void clear();
    bool start(const mVec3_c &v1, dAcObjBase_c *owner);

private:
    /* 0x010 */ mVec3_c field_0x010[50];
    /* 0x268 */ mVec3_c field_0x268[50];
    /* 0x4C0 */ dAcObjBase_c *field_0x4C0[50];
    /* 0x588 */ dAcObjBase_c *field_0x588[50];
    /* 0x650 */ mColor field_0x650;
    /* 0x654 */ u32 field_0x654;
    /* 0x658 */ u32 field_0x658;
    // TODO unclear structure boundary
};

class dMassObjEmitter_c : public dEmitterBase_c {
public:
    dMassObjEmitter_c() {}
    virtual ~dMassObjEmitter_c() {
        remove();
    }

    void create(u16 resourceId);
    bool start(const mVec3_c &v1, dAcObjBase_c *owner) {
        return mCallback.start(v1, owner);
    }
    void remove();

    mColor getField_0x67C() const {
        return field_0x67C;
    }

    void setField_0x67C(GXColor clr) {
        field_0x67C.r = clr.r;
        field_0x67C.g = clr.g;
        field_0x67C.b = clr.b;
        field_0x67C.a = clr.a;
    }

    void setField_0x67C(mColor clr) {
        field_0x67C.r = clr.r;
        field_0x67C.g = clr.g;
        field_0x67C.b = clr.b;
        field_0x67C.a = clr.a;
    }

    void execute() {
        mCallback.execute();
    }

private:
    /* 0x020 */ dMassObjEmitterCallback_c mCallback;
    /* 0x67C */ mColor field_0x67C;
};

class dJEffManager_c {
public:
    static bool createEffManagers();
    static void removeEffManagers();
    static void draw(const JPADrawInfo *info, u32 groupId);
    static void draw();
    static void execute();
    static void doCustomSkywardSwordThing(f32 x, f32 y);
    static void setupEffects();
    static dEmitterBase_c *spawnEffect(
        u16 effectResourceId, const mVec3_c &position, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1,
        const GXColor *c2, s32 idx1, s32 idx2
    );
    static dEmitterBase_c *spawnUIEffect(
        u16 effectResourceId, const mVec3_c &position, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1,
        const GXColor *c2
    );
    static dEmitterBase_c *spawnEffect(
        u16 effectResourceId, const mMtx_c &transform, const GXColor *c1, const GXColor *c2, s32 idx1, s32 idx2
    );
    static dEmitterBase_c *spawnGroundEffect(
        const mVec3_c &pos, u8 polyAttr0, u8 polyAttr1, const mVec3_c &, s32 unk, f32 scale, f32 groundHeightMaybe
    );

    static void spawnHitMarkEffect(int type, cCcD_Obj &collider, const mVec3_c *pVec, bool);
    // "mass obj" = grass, fire
    static bool createMassObjEffect(u16 effectResourceId, const mVec3_c &v1, dAcObjBase_c *owner, const mColor *color);

    static s32 getGroupId(u16);
    static bool shouldBePaused(dBase_c *owner);

    enum GroundEffect_e {
        GROUND_0,
        GROUND_1,
        GROUND_2,
        GROUND_3,
        GROUND_4,
        GROUND_5,
        GROUND_6,
    };

    enum Fx_e {
        TsuboA,
        TsuboB,
        BRockA,
        FruitA,
        GrassCoil,
        BRockB,
        Beehive,
        VeraRock,
        Pumpkin,
        BRockC,
        F300BrokenRockWall_00,
        F300BrokenRockWall_01,
        F300BrokenRockWall_01T,
        LotusSeed,
        Beamoss,
        Cakram,
        Dodai,
        LegParts,
        AsuraFloorBrk,
        BreakPillar,
        Barrel,
        BarrelBomb,
        RockMiddle,
        BocoBone,
        BRockStopA,
        BWallD201,
        Gunho,
        TuboBig,
        Amos,
        HidokariBoneA,
        HidokariBoneB,
        Hydra,
        Captain,
        KibakoHang,
        IslTreIRock,
        BirdraceTarget,
        FlowerA00F,
        FlowerA00L,
        FlowerB00,
        FlowerB01,
        GirahimClaymore,
        BirdKobunAEgg,
        BirdKobunBEgg,
        BirdRivalEgg,
        BWallAF200,
        BWallBF200,
        BWallF210,
    };

    typedef TList<dEmitter_c, offsetof(dEmitter_c, mNode)> EffectsList;
    static EffectsList sPlayingEffectsList;
    static CommonEmitterCallback sCommonEmitterCallbacks[2];
    static dShpEmitter_c sShpEmitters[47];
    static dEmitterBase_c sEmitter;
    static dParticleFogProc_c sFogProcs[12];
    static dEffect2D_c s2DEffects[3];

private:
    static dEmitterBase_c *spawnEffectInternal(
        u16 effectResourceId, const mVec3_c &position, const mAng3_c *rot, const mVec3_c *scale, const GXColor *c1,
        const GXColor *c2, s32 idx1, s32 idx2
    );
    static dEmitterBase_c *spawnEffectInternal(
        u16 effectResourceId, const mMtx_c &transform, const GXColor *c1, const GXColor *c2, s32 idx1, s32 idx2
    );

    static GroundEffect_e polyAttrsToGroundEffectIdx(s32 polyAttr0, s32 polyAttr1);

    static mHeapAllocator_c *ms_allocator;
    static dMassObjEmitter_c *sMassObjEmitters;
    static u32 sInts[];
    static const u32 moreInts[];
    static u32 sInts2[];
};

#endif
