#ifndef M3D_M_SHADOW_H
#define M3D_M_SHADOW_H

#include "egg/core/eggFrmHeap.h"
#include "egg/core/eggHeap.h"
#include "m/m3d/m_proc.h"
#include "m/m_color.h"
#include "m/m_frustum.h"
#include "m/m_heap.h"
#include "m/m_mtx.h"
#include "m/m_quat.h"
#include "m/m_vec.h"

namespace m3d {

/**
 * A configuration struct for our global shadow manager.
 * The game uses one for most rooms and one for the Koloktos boss fight.
 */
struct mShadowCircleConfig {
    u32 heapSize;
    u8 count;
    u8 unk1;
    u8 unk2;
    u8 texBufferSize;
    u8 drawOpaPriority;
};

/**
 * A callback that allows the stage manager to customize parts of
 * the shadow pipeline.
 */
class mShadowCallback_c {
public:
    virtual ~mShadowCallback_c() {}
    virtual void beforeDraw() = 0;
    virtual void draw(const mMtx_c &, u32) = 0;
    virtual void afterDraw() = 0;
};

class mShadowCircle_c;

/**
 * Represents an internal shadow circle.
 */
class mShadowChild_c {
    friend class mShadow_c;

public:
    nw4r::ut::Node mNode;

    mShadowChild_c() : mpCircle(nullptr), mpLeaves(nullptr), field_0x154(0), mNumLeaves(0) {}
    /* vt at 0x08 */
    virtual ~mShadowChild_c();

    bool create(u8, EGG::Heap *);
    void set(const mVec3_c &pos, f32 dist, mColor color);
    bool setGeom(const GXTexObj *texObj, const mMtx_c &mtx, const mQuat_c &quat);
    void draw();
    void updateMtx();
    void drawMdl();

    bool addMdl(scnLeaf_c &mdl, const mQuat_c &quat);

    void set0x154(UNKWORD arg) {
        field_0x154 = arg;
    }

    const mQuat_c &GetQuat() const {
        return mQuat;
    }
    const mVec3_c &GetPosition() const {
        return mPositionMaybe;
    }
    const f32 GetOffset() const {
        return mOffsetMaybe;
    }
    const f32 Get0x13C() const {
        return field_0x13C;
    }
    void Set0x13C(const f32 f) {
        field_0x13C = f;
    }

    const mFrustum_c &GetFrustum() const {
        return mFrustum;
    }

private:
    /* 0x00C */ EGG::Heap *mpHeap;
    /* 0x010 */ mShadowCircle_c *mpCircle;
    /* 0x014 */ u32 mPriorityMaybe;
    /* 0x018 */ GXTexObj mTexObj;
    /* 0x038 */ mColor mShadowColor;
    /* 0x03C */ mQuat_c mQuat;
    /* 0x04C */ mFrustum_c mFrustum;
    /* 0x13C */ f32 field_0x13C;
    /* 0x140 */ scnLeaf_c **mpLeaves;
    /* 0x144 */ mVec3_c mPositionMaybe;
    /* 0x150 */ f32 mOffsetMaybe;
    /* 0x154 */ UNKWORD field_0x154;
    /* 0x158 */ u8 mMaxNumLeaves;
    /* 0x159 */ u8 mNumLeaves;
    /* 0x15A */ u8 mColorChanIdx;
};

/**
 * An RAII handle to a shadow circle.
 */
class mShadowCircle_c {
    friend class mShadow_c;
    friend class mShadowChild_c;

public:
    mShadowCircle_c() : mpChild(nullptr) {}
    virtual ~mShadowCircle_c();

private:
    /* 0x04 */ mShadowChild_c *mpChild;
};

/**
 * The global shadow circle manager.
 */
class mShadow_c : public proc_c {
    friend class mShadowCircle_c;
    friend class mShadowChild_c;

public:
    mShadow_c(EGG::Heap *heap)
        : mpHeap(heap),
          mpCurrentHeap(nullptr),
          mpChilds(nullptr),
          mpTexBuf(nullptr),
          mpCallback(nullptr),
          mCurrentHeapIdx(0),
          mFreeChildIdx(0),
          field_0x66(true) {
        nw4r::ut::List_Init(&mList, 0);
    }
    virtual ~mShadow_c();
    virtual void remove() override;
    virtual void drawOpa() override;

    bool drawMdl(
        mShadowCircle_c *circle, u32 priority, scnLeaf_c &mdl, const mQuat_c &quat, mVec3_c &pos, mColor color,
        u32 param9, f32 dist
    );
    bool drawTexObj(
        mShadowCircle_c *circle, u32 priority, const GXTexObj *texObj, const mMtx_c &mtx, const mQuat_c &quat,
        mVec3_c &pos, mColor color, u32 param9, f32 dist
    );

    bool addMdlToCircle(mShadowCircle_c *circle, scnLeaf_c &mdl, const mQuat_c &quat);

    void beforeDraw();
    void draw(const mMtx_c &, u32);
    void afterDraw();

    void changeHeap(int index) {
        mCurrentHeapIdx = index % 2;
        mpCurrentHeap = mpFrmHeaps[mCurrentHeapIdx];
    }
    void swapHeaps();
    void
    create(int count, u8 unk1, int unk2, u16 texBufferSize, u32 drawOpaPriority, nw4r::g3d::ResMdl mdl, u32 heapSize);
    void reset();

    bool addCircle(mShadowCircle_c *circle, u32 priority, u32 unk);
    void removeCircle(mShadowCircle_c *circle);

    void drawAllShadows();

    static void create(const mShadowCircleConfig *, nw4r::g3d::ResMdl mdl, EGG::Heap *heap);
    static void destroy();

    static mShadow_c *GetInstance() {
        return sInstance;
    }

private:
    static mShadow_c *sInstance;

    /* 0x18 */ EGG::Heap *mpHeap;
    /* 0x1C */ mAllocator_c mAllocator;
    /* 0x38 */ EGG::FrmHeap *mpFrmHeaps[2];
    /* 0x40 */ EGG::FrmHeap *mpCurrentHeap;
    /* 0x44 */ nw4r::ut::List mList;
    /* 0x50 */ mShadowChild_c *mpChilds;
    /* 0x54 */ void *mpTexBuf;
    /* 0x58 */ u32 mTexBufferSize;
    /* 0x5C */ mShadowCallback_c *mpCallback;
    /* 0x60 */ u8 mCurrentHeapIdx;
    /* 0x61 */ u8 mChildCount;
    /* 0x62 */ u8 mFreeChildIdx;
    /* 0x63 */ u8 mNumTexBuffers;
    /* 0x64 */ u8 mFreeTexIdx;
    /* 0x65 */ u8 mTexSize;
    /* 0x66 */ bool field_0x66;

public:
    static GXTexObj *sTexObj;
    static nw4r::g3d::ResShp sResShp;
};

class mCustomShadow_c : public scnLeaf_c {
public:
    virtual ~mCustomShadow_c();
    virtual int getType() const override;
    /* 0x24 */ virtual void draw(const mMtx_c &);

    void calc(mMtx_c, mMtx_c &) const;

    /* 0x18 */ mMtx_c mMtx;
    /* 0x48 */ f32 field_0x48;
    /* 0x4C */ f32 field_0x4C;
};

} // namespace m3d

#endif
