#include "m/m3d/m_shadow.h"

#include "c/c_math.h"
#include "m/m_mtx.h"
#include "m/m_quat.h"
#include "m/m_vec.h"
#include "nw4r/g3d/g3d_calcview.h"
#include "nw4r/g3d/g3d_draw.h"
#include "nw4r/g3d/g3d_draw1mat1shp.h"
#include "nw4r/g3d/g3d_scnmdl.h"
#include "nw4r/g3d/g3d_scnmdlsmpl.h"
#include "nw4r/g3d/g3d_state.h"
#include "nw4r/g3d/res/g3d_resmat.h"
#include "nw4r/g3d/res/g3d_resmdl.h"
#include "nw4r/g3d/res/g3d_resshp.h"

// All of this is completely made up, as we don't have symbols for this TU
// (contrary to the rest of m3d and most of nw4r::g3d)

#define FRM_HEAP_STATE 'tag0'

namespace m3d {

mShadow_c *mShadow_c::sInstance;

GXTexObj *mShadow_c::sTexObj;
nw4r::g3d::ResShp mShadow_c::sResShp;

mShadow_c::~mShadow_c() {
    remove();
}

void mShadow_c::drawOpa() {
    if (field_0x66) {
        beforeDraw();
        mShadowChild_c *child = static_cast<mShadowChild_c *>(nw4r::ut::List_GetFirst(&mList));
        while (child != nullptr) {
            child->draw();
            child = static_cast<mShadowChild_c *>(nw4r::ut::List_GetNext(&mList, child));
        }
        afterDraw();
    }
}

void mShadow_c::create(
    int count, u8 unk1, int unk2, u16 texSize, u32 drawOpaPriority, nw4r::g3d::ResMdl mdl, u32 heapSize
) {
    // Regswaps
    mAllocator.attach(mpHeap, 0x20);
    proc_c::create(&mAllocator, nullptr);
    u32 bufSize = GXGetTexBufferSize(texSize, texSize, GX_TF_RGB565, false, 0);
    mTexBufferSize = bufSize;
    u32 numTexBuffers = (unk2 + 2) / 3;
    mpTexBuf = mpHeap->alloc(bufSize * numTexBuffers, 0x20);

    // Maybe an Inline?
    EGG::FrmHeap **heap = mpFrmHeaps;
    for (int i = 0; i < 2; i++) {
        heap[0] = mHeap::createFrmHeap(heapSize, mpHeap, "ShadowTmp", 0x4, mHeap::OPT_NONE);
        heap[0]->recordState(FRM_HEAP_STATE);
        heap++;
    }
    swapHeaps();

    mShadowChild_c *childs = new (mpHeap, 0x04) mShadowChild_c[count];
    mpChilds = childs;
    for (int i = 0; i < count; i++) {
        childs->create(unk1, mpHeap);
        childs++;
    }

    setPriorityDraw(drawOpaPriority, 0);
    setOption(/* DISABLE_DRAW_XLU */ 0x07, 1);

    mChildCount = count;
    mFreeChildIdx = 0;
    mNumTexBuffers = numTexBuffers * 3;
    mFreeTexIdx = 0;
    mTexSize = texSize;

    nw4r::g3d::ResMat mat = mdl.GetResMat(0);
    sResShp = mdl.GetResShp(0);
    sTexObj = mat.GetResTexObj().GetTexObj(GX_TEXMAP0);
}

void mShadow_c::remove() {
    if (mpHeap != nullptr) {
        if (mpChilds != nullptr) {
            delete[] mpChilds;
            mpChilds = nullptr;
        }

        EGG::FrmHeap **heap = mpFrmHeaps;
        for (int i = 0; i < 2; i++) {
            if (heap[i] != nullptr) {
                mHeap::destroyFrmHeap(heap[i]);
                heap[i] = nullptr;
            }
        }

        if (mpTexBuf != nullptr) {
            mpHeap->free(mpTexBuf);
            mpTexBuf = nullptr;
        }

        scnLeaf_c::remove();
        mpHeap = nullptr;
    }
}

void mShadow_c::reset() {
    mShadowChild_c *iter = static_cast<mShadowChild_c *>(nw4r::ut::List_GetFirst(&mList));
    while (iter != nullptr) {
        iter->mNumLeaves = 0;
        mShadowCircle_c *circle = iter->mpCircle;
        iter->mpCircle = nullptr;
        circle->mpChild = nullptr;
        iter = static_cast<mShadowChild_c *>(nw4r::ut::List_GetNext(&mList, iter));
    }
    mFreeChildIdx = 0;
    mFreeTexIdx = 0;
    nw4r::ut::List_Init(&mList, 0);
    swapHeaps();
}

bool mShadow_c::addCircle(mShadowCircle_c *circle, u32 priority, u32 isMdl) {
    if (circle->mpChild != nullptr) {
        // Already linked, OK
        return true;
    }

    mShadowChild_c *childPtr;
    if (mFreeChildIdx < mChildCount && (!isMdl || mFreeTexIdx < mNumTexBuffers)) {
        // There are free entries in our buffer, so just add a new child
        childPtr = &mpChilds[mFreeChildIdx];
        if (mFreeChildIdx++ == 0) {
            entry();
        }

        if (isMdl) {
            mFreeTexIdx++;
        }
    } else {
        // There are no free entries in our buffer, so replace an existing entry?
        childPtr = static_cast<mShadowChild_c *>(nw4r::ut::List_GetLast(&mList));
        if (priority >= childPtr->mPriorityMaybe) {
            return false;
        }

        if (isMdl) {
            if (mFreeTexIdx >= mNumTexBuffers) {
                while (childPtr->mNumLeaves == 0) {
                    childPtr = static_cast<mShadowChild_c *>(nw4r::ut::List_GetPrev(&mList, childPtr));
                    if (priority >= childPtr->mPriorityMaybe) {
                        return false;
                    }
                }
            } else {
                mFreeTexIdx++;
            }
        }
        childPtr->mNumLeaves = 0;
        childPtr->mpCircle->mpChild = nullptr;
        nw4r::ut::List_Remove(&mList, childPtr);
    }

    circle->mpChild = childPtr;
    childPtr->mpCircle = circle;
    childPtr->mPriorityMaybe = priority;
    mShadowChild_c *iter = static_cast<mShadowChild_c *>(nw4r::ut::List_GetFirst(&mList));
    if (iter == nullptr) {
        nw4r::ut::List_Append(&mList, childPtr);
        return true;
    } else {
        do {
            if (childPtr->mPriorityMaybe < iter->mPriorityMaybe) {
                nw4r::ut::List_Insert(&mList, iter, childPtr);
                return true;
            }
            iter = static_cast<mShadowChild_c *>(nw4r::ut::List_GetNext(&mList, iter));
        } while (iter != nullptr);
        nw4r::ut::List_Append(&mList, childPtr);
    }

    return true;
}

bool mShadow_c::drawMdl(
    mShadowCircle_c *circle, u32 priority, scnLeaf_c &mdl, const mQuat_c &quat, mVec3_c &pos, mColor color, u32 param9,
    f32 dist
) {
    if (!addCircle(circle, priority, 1)) {
        return false;
    }

    mShadowChild_c *child = circle->mpChild;
    child->set(pos, dist, color);
    child->set0x154(param9);
    return child->addMdl(mdl, quat);
}

bool mShadow_c::addMdlToCircle(mShadowCircle_c *circle, scnLeaf_c &mdl, const mQuat_c &quat) {
    if (circle->mpChild == nullptr) {
        return false;
    }
    return circle->mpChild->addMdl(mdl, quat);
}

void mShadow_c::removeCircle(mShadowCircle_c *circle) {
    mShadowChild_c *child = circle->mpChild;
    if (child == nullptr) {
        return;
    }
    circle->mpChild = nullptr;
    child->mpCircle = nullptr;
    nw4r::ut::List_Remove(&mList, child);
}

static f32 sTexMtx[2][4] = {
    {1.0f, 0.0f, 0.0f, 0.0f},
    {0.0f, 1.0f, 0.0f, 0.0f}
};

static u32 STEP = 0x2492;

static GXColor sColors[] = {
    {0xFF, 0x00, 0x00, 0x00},
    {0x00, 0xFF, 0x00, 0x00},
    {0x00, 0x00, 0xFF, 0x00},
    {0x00, 0x00, 0x00, 0x00},
};

bool mShadow_c::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
) {
    if (!addCircle(circle, priority, 0)) {
        return false;
    }

    mShadowChild_c *child = circle->mpChild;
    child->set(pos, dist, color);
    child->set0x154(param9);
    return child->setGeom(texObj, mtx, quat);
}

static void drawSub2(void *data, u8 i) {
    s16 ang;
    u32 wid = i * 2;
    GXSetTexCopySrc(0, 0, wid, wid);
    GXSetTexCopyDst(i, i, GX_TF_RGB565, 1);
    GXCopyTex(data, false);
    GXPixModeSync();
    GXInvalidateTexAll();
    GXTexObj obj;
    GXInitTexObj(&obj, data, i, i, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, 0);
    GXInitTexObjLOD(&obj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1);
    GXLoadTexObj(&obj, GX_TEXMAP0);
    GXClearVtxDesc();
    GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
    GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);
    GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGB, GX_RGBX8, 0);
    GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_U8, 0);
    GXSetNumChans(0);
    GXSetNumTexGens(8);
    GXSetNumTevStages(8);
    GXSetTevColor(GX_TEVREG1, nw4r::ut::Color(0, 0, 0, 0x20));
    ang = 0;
    for (int id = GX_TEXCOORD0, idx = GX_TEXMTX0; id < GX_MAX_TEXCOORD;
         id += 1, idx += (GX_TEXMTX1 - GX_TEXMTX0), ang += STEP) {
        GXSetTexCoordGen2((GXTexCoordID)id, GX_TG_MTX2x4, GX_TG_TEX0, idx, FALSE, GX_DUALMTX_IDENT);
        sTexMtx[0][3] = 0.01f * nw4r::math::CosIdx(ang);
        sTexMtx[1][3] = 0.01f * nw4r::math::SinIdx(ang);
        GXLoadTexMtxImm(sTexMtx, idx, GX_MTX2x4);
    }
    GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
    GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_A0, GX_CC_ZERO);

    for (int id = GX_TEVSTAGE1; id < GX_TEVSTAGE8; id++) {
        GXSetTevSwapMode((GXTevStageID)id, GX_TEV_SWAP0, GX_TEV_SWAP0);
        GXSetTevOrder((GXTevStageID)id, (GXTexCoordID)id, GX_TEXMAP0, GX_COLOR_NULL);
        GXSetTevColorIn((GXTevStageID)id, GX_CC_ZERO, GX_CC_TEXC, GX_CC_A1, GX_CC_CPREV);
        GXSetTevColorOp((GXTevStageID)id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, 1, GX_TEVPREV);
        GXSetTevAlphaIn((GXTevStageID)id, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
        GXSetTevAlphaOp((GXTevStageID)id, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, 1, GX_TEVPREV);
    }
    GXSetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_SET);
    GXLoadPosMtxImm(mMtx_c::Identity, 0);
    GXSetCurrentMtx(0);

    GXBegin(GX_QUADS, GX_VTXFMT0, 4);

    GXPosition2u16(0, 0);
    GXPosition2u8(0, 0);

    GXPosition2u16(wid, 0);
    GXPosition2u8(1, 0);

    GXPosition2u16(wid, wid);
    GXPosition2u8(1, 1);

    GXPosition2u16(0, wid);
    GXPosition2u8(0, 1);
    // GXEnd();
}

static void drawSub1(void *data, u8 i) {
    Mtx44 mtx;
    u16 wid = i * 2;
    f32 wid2 = i * 2;
    C_MTXOrtho(mtx, 0.0f, wid2, 0.0f, wid2, 0.0f, 1.0f);
    GXSetProjection(mtx, GX_ORTHOGRAPHIC);
    drawSub2(data, i);
    GXSetNumChans(1);
    GXSetNumTexGens(0);
    GXSetNumTevStages(1);
    GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
    GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO);

    GXClearVtxDesc();
    GXSetVtxDesc(GX_VA_POS, GX_DIRECT);

    GXBegin(GX_LINESTRIP, GX_VTXFMT0, 5);
    GXPosition2u16(0, 0);
    GXPosition2u16(wid, 0);
    GXPosition2u16(wid, wid);
    GXPosition2u16(0, wid);
    GXPosition2u16(0, 0);
    // GXEnd();

    GXSetZMode(true, GX_ALWAYS, true);
    GXSetTexCopySrc(0, 0, wid, wid);
    GXSetTexCopyDst(i, i, GX_TF_RGB565, 1);
    GXCopyTex(data, true);
    GXPixModeSync();
    GXInvalidateTexAll();
}

void mShadow_c::drawAllShadows() {
    mShadowChild_c *iter = static_cast<mShadowChild_c *>(nw4r::ut::List_GetFirst(&mList));
    if (iter != nullptr) {
        f32 wid = mTexSize * 2;
        u16 wid2 = mTexSize * 2;
        GXSetViewport(0.0f, 0.0f, wid, wid, 0.0f, 1.0f);
        GXSetScissor(0, 0, wid2, wid2);
        GXSetScissorBoxOffset(0, 0);
        GXLoadTexObj(mShadow_c::sTexObj, GX_TEXMAP1);
        GXSetNumChans(1);
        GXSetChanCtrl(GX_COLOR0A0, false, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_NONE, GX_AF_NONE);
        GXSetNumTexGens(0);
        GXSetNumTevStages(1);
        GXSetNumIndStages(0);
        GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
        GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO);
        GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, 1, GX_TEVPREV);
        GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
        GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, 1, GX_TEVPREV);
        GXSetZCompLoc(1);
        GXSetBlendMode(GX_BM_NONE, GX_BL_ONE, GX_BL_ONE, GX_LO_SET);
        GXSetZMode(false, GX_ALWAYS, false);
        GXSetAlphaCompare(GX_ALWAYS, 0, GX_AOP_OR, GX_ALWAYS, 0);
        GXColor clr = {0};
        GXSetFog(GX_FOG_NONE, clr, 0.0f, 0.0f, 0.0f, 0.0f);
        GXSetFogRangeAdj(0, 0, 0);
        GXSetCullMode(GX_CULL_BACK);
        GXSetDither(0);
        GXSetClipMode(GX_CLIP_DISABLE);
        GXLoadPosMtxImm(mMtx_c::Identity, 0);
        GXSetCurrentMtx(0);
        Mtx44 mtx;
        C_MTXOrtho(mtx, 0.0f, wid, 0.0f, wid, 0.0f, 1.0f);
        GXSetProjection(mtx, GX_ORTHOGRAPHIC);
        GXSetLineWidth(0x18, 0);
        int i = 0;
        void *imgData = mpTexBuf;
        do {
            iter->updateMtx();
            if (iter->mNumLeaves != 0) {
                if (i == 0) {
                    GXClearVtxDesc();
                    GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
                    GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XY, GX_U16, 0);
                    GXBegin(GX_QUADS, GX_VTXFMT0, 4);
                    GXPosition2u16(0, 0);
                    GXPosition2u16(wid2, 0);
                    GXPosition2u16(wid2, wid2);
                    GXPosition2u16(0, wid2);
                    // GXEnd();
                    GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0);
                    GXSetBlendMode(GX_BM_LOGIC, GX_BL_ONE, GX_BL_ONE, GX_LO_OR);
                }
                iter->mColorChanIdx = i;
                GXInitTexObj(&iter->mTexObj, imgData, mTexSize, mTexSize, GX_TF_RGB565, GX_CLAMP, GX_CLAMP, false);
                GXInitTexObjLOD(&iter->mTexObj, GX_LINEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, 0, 0, GX_ANISO_1);
                iter->drawMdl();
                i = (i + 1) % 3;
                if (i == 0) {
                    drawSub1(imgData, mTexSize);
                    imgData = (void *)((u8 *)imgData + mTexBufferSize);
                }
            }
            iter = static_cast<mShadowChild_c *>(nw4r::ut::List_GetNext(&mList, iter));
        } while (iter != nullptr);

        if (i != 0) {
            drawSub1(imgData, mTexSize);
        }

        GXSetClipMode(GX_CLIP_ENABLE);
        GXSetDither(true);
        nw4r::g3d::G3DState::Invalidate(0x7FF);
    }
}

void mShadow_c::create(const mShadowCircleConfig *config, nw4r::g3d::ResMdl mdl, EGG::Heap *heap) {
    mShadow_c::sInstance = new (heap, 0x04) mShadow_c(heap);
    mShadow_c::GetInstance()->create(
        config->count, config->unk1, config->unk2, config->texBufferSize, config->drawOpaPriority, mdl, config->heapSize
    );
}

void mShadow_c::beforeDraw() {
    if (mpCallback != nullptr) {
        mpCallback->beforeDraw();
    }
}

void mShadow_c::draw(const mMtx_c &mtx, u32 unk) {
    if (mpCallback != nullptr) {
        mpCallback->draw(mtx, unk);
    }
}

void mShadow_c::afterDraw() {
    if (mpCallback != nullptr) {
        mpCallback->afterDraw();
    }
}

void mShadow_c::swapHeaps() {
    changeHeap(mCurrentHeapIdx + 1);
    mpCurrentHeap->freeByState(FRM_HEAP_STATE);
    mpCurrentHeap->recordState(FRM_HEAP_STATE);
}

void mShadow_c::destroy() {
    if (mShadow_c::sInstance != nullptr) {
        mShadow_c::GetInstance()->remove();
        delete mShadow_c::sInstance;
        mShadow_c::sInstance = nullptr;
    }
}

mShadowChild_c::~mShadowChild_c() {
    if (mpCircle != nullptr) {
        mpCircle->mpChild = nullptr;
        mpCircle = nullptr;
    }

    if (mpLeaves != nullptr) {
        mpHeap->free(mpLeaves);
        mpLeaves = nullptr;
    }
}

bool mShadowChild_c::create(u8 maxNum, EGG::Heap *heap) {
    if ((mpLeaves = static_cast<scnLeaf_c **>(heap->alloc(maxNum * sizeof(scnLeaf_c *), 0x04))) == nullptr) {
        return false;
    }
    mpHeap = heap;
    mMaxNumLeaves = maxNum;
    return true;
}

void mShadowChild_c::set(const mVec3_c &pos, f32 dist, mColor color) {
    mPositionMaybe = pos;
    mOffsetMaybe = dist;
    mShadowColor = color;
}

bool mShadowChild_c::addMdl(scnLeaf_c &mdl, const mQuat_c &quat) {
    if (!(mNumLeaves < mMaxNumLeaves)) {
        return false;
    }

    mMtx_c mtx;
    if (mdl.getType() == 0) {
        mdl.getLocalMtx(mtx);
    } else {
        mtx.copyFrom(static_cast<mCustomShadow_c &>(mdl).mMtx);
    }

    mQuat_c q = quat;
    mtx.applyQuat(q);

    if (mNumLeaves == 0) {
        mQuat = q;
    } else {
        mQuat.fn_802F2780(q);
    }
    mpLeaves[mNumLeaves++] = &mdl;
    return true;
}

bool mShadowChild_c::setGeom(const GXTexObj *texObj, const mMtx_c &mtx, const mQuat_c &quat) {
    mQuat = quat;
    MTXMultVec(mtx.m, mQuat.v, mQuat.v);
    if (texObj == nullptr) {
        mTexObj = *mShadow_c::sTexObj;
    } else {
        mTexObj = *texObj;
    }
    return true;
}

void mShadowChild_c::updateMtx() {
    // NONMATCHING
    field_0x13C = mQuat.w;

    mVec3_c a(mQuat.v.x, mQuat.v.y, mQuat.v.z);
    a += mPositionMaybe * GetOffset();

    mVec3_c b(mQuat.v.x, mQuat.v.y, mQuat.v.z);
    b -= mPositionMaybe * field_0x13C;

    const mVec3_c *up;
    if (cM::isZero((a - b).squareMagXZ())) {
        up = &mVec3_c::Ez;
    } else {
        up = &mVec3_c::Ey;
    }

    mMtx_c mtx;
    C_MTXLookAt(mtx.m, a, *up, b);

    f32 f = field_0x13C;
    mFrustum.set(f, -f, -f, f, f, f + GetOffset(), mtx, true);
}

void mShadowChild_c::drawMdl() {
    using namespace nw4r;
    Mtx44 mtx;
    GXSetTevColor(GX_TEVREG0, sColors[mColorChanIdx]);
    C_MTXOrtho(mtx, field_0x13C, -field_0x13C, -field_0x13C, field_0x13C, 0.0f, 100.0f + (-mFrustum.mFar));
    GXSetProjection(mtx, GX_ORTHOGRAPHIC);
    g3d::G3DState::Invalidate(0x7FF);

    mMtx_c &viewMtx = mFrustum.mView;

    for (scnLeaf_c **leaf = &mpLeaves[mNumLeaves - 1]; leaf >= &mpLeaves[0]; leaf--) {
        if ((*leaf)->getType() == 0 /* Model */) {
            g3d::ScnMdlSimple *mdl = g3d::G3dObj::DynamicCast<g3d::ScnMdlSimple>((*leaf)->getG3dObject());

            u32 bufSize = mdl->GetNumViewMtx() * sizeof(math::MTX34);
            math::MTX34 *viewPosArray =
                static_cast<math::MTX34 *>(mShadow_c::GetInstance()->mpCurrentHeap->alloc(bufSize, 0x20));

            g3d::ResMdl resMdl = mdl->GetResMdl();
            g3d::CalcView(
                viewPosArray, nullptr, mdl->GetWldMtxArray(), mdl->GetWldMtxAttribArray(), mdl->GetNumViewMtx(),
                viewMtx, resMdl, nullptr
            );
            DCStoreRange(viewPosArray, bufSize);

            g3d::ScnMdl *mdl2 = g3d::G3dObj::DynamicCast<g3d::ScnMdl>((*leaf)->getG3dObject());

            g3d::DrawResMdlReplacement *pRep = mdl2 ? &mdl2->GetDrawResMdlReplacement() : nullptr;

            g3d::DrawResMdlDirectly(
                resMdl, viewPosArray, nullptr, nullptr, mdl->GetByteCode(g3d::ScnMdlSimple::BYTE_CODE_DRAW_OPA),
                nullptr, pRep, g3d::RESMDL_DRAWMODE_FORCE_LIGHTOFF | g3d::RESMDL_DRAWMODE_IGNORE_MATERIAL
            );
            GXInvalidateVtxCache();
        } else {
            // this happens with bomb bag, and goes to 0x802EDC90 (mCustomShadow_c::draw)
            static_cast<mCustomShadow_c *>(*leaf)->draw(viewMtx);
        }
    }
}

static GXTevColorChan sChans[] = {GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA};

void mShadowChild_c::draw() {
    if (mNumLeaves != 0) {
        GXTevColorChan chan = sChans[mColorChanIdx];
        GXSetTevSwapModeTable(GX_TEV_SWAP0, chan, chan, chan, chan);
    } else {
        GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA);
    }
    GXLoadTexObj(&mTexObj, GX_TEXMAP0);
    GXSetTevColor(GX_TEVREG0, mShadowColor);
    Mtx mtx;
    C_MTXLightOrtho(mtx, field_0x13C, -field_0x13C, -field_0x13C, field_0x13C, 0.5f, -0.5f, 0.5f, 0.5f);
    MTXConcat(mtx, mFrustum.mView.m, mtx);
    GXLoadTexMtxImm(mtx, GX_TEXMTX0, GX_MTX3x4);
    mShadow_c::GetInstance()->draw(mFrustum.mView, field_0x154);
    GXSetTevSwapModeTable(GX_TEV_SWAP0, GX_CH_RED, GX_CH_GREEN, GX_CH_BLUE, GX_CH_ALPHA);
}

mShadowCircle_c::~mShadowCircle_c() {
    mShadow_c::GetInstance()->removeCircle(this);
}

mCustomShadow_c::~mCustomShadow_c() {}

int mCustomShadow_c::getType() const {
    return 0x2;
}

void mCustomShadow_c::draw(const mMtx_c &arg) {
    nw4r::g3d::G3DState::Invalidate(0x7FF);
    GXSetNumTexGens(1);
    GXSetTexCoordGen2(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, 0x3C, false, 0x7D);
    GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP1, GX_COLOR_NULL);
    GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_C0, GX_CC_TEXC, GX_CC_ZERO);

    mMtx_c result;
    calc(arg, result);
    static nw4r::g3d::ResMat nullMat(nullptr);

    nw4r::g3d::Draw1Mat1ShpDirectly(nullMat, mShadow_c::sResShp, result, result, 0x18, nullptr, nullptr);
    GXSetNumTexGens(0);
    GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD_NULL, GX_TEXMAP_NULL, GX_COLOR0A0);
    GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_C0);
}

void mCustomShadow_c::calc(mMtx_c mtx, mMtx_c &mtx2) const {
    mVec3_c trans;
    mtx2.copyFrom(mMtx);
    mVec3_c offset(0.0f, field_0x48, 0.0f);
    MTXMultVec(mtx2, offset, trans);
    MTXMultVec(mtx, trans, trans);

    MTXTrans(mtx2, trans.x, trans.y, trans.z);

    mMtx_c scaleMtx;
    MTXScale(scaleMtx, field_0x4C, field_0x4C, field_0x4C);
    MTXConcat(mtx2, scaleMtx, mtx2);
}

} // namespace m3d
