#include "m/m3d/m3d.h"

#include "egg/gfx/eggDrawGX.h"
#include "egg/gfx/eggFogManager.h"
#include "egg/gfx/eggGfxEngine.h"
#include "egg/gfx/eggLightManager.h"
#include "egg/gfx/eggLightTexture.h"
#include "egg/gfx/eggLightTextureMgr.h"
#include "egg/gfx/eggScreen.h"
#include "egg/gfx/eggStateGX.h"
#include "m/m_heap.h"
#include "m/m_video.h"
#include "nw4r/g3d/g3d_camera.h"
#include "nw4r/g3d/g3d_init.h"
#include "nw4r/g3d/g3d_state.h"
#include "nw4r/g3d/res/g3d_resmat.h"
#include "nw4r/g3d/res/g3d_resmdl.h"

#include "rvl/GX.h" // IWYU pragma: export

namespace m3d {

namespace internal {

mAllocator_c *l_allocator_p;
nw4r::g3d::ScnRoot *l_scnRoot_p;
u32 l_numLightMgr;
EGG::LightManager **l_lightMgr_pp;
u32 l_numFogMgr;
EGG::FogManager **l_fogMgr_pp;
size_t l_alignment;

} // namespace internal

bool create(EGG::Heap *pHeap, u32 maxNumChildren, u32 maxNumScnObj, u32 numLightObj, u32 numLightSet) {
    internal::l_allocator_p = new (pHeap) mAllocator_c();

    if (!internal::l_allocator_p) {
        return false;
    }

    internal::l_allocator_p->attach(pHeap, 0x20);
    nw4r::g3d::G3dInit(true);
    nw4r::g3d::G3DState::SetRenderModeObj(*mVideo::m_video->pRenderMode);
    u32 allocedSize;
    internal::l_scnRoot_p = nw4r::g3d::ScnRoot::Construct(
        internal::l_allocator_p, &allocedSize, maxNumChildren, maxNumScnObj, numLightObj, numLightSet
    );
    if (internal::l_scnRoot_p == nullptr) {
        delete internal::l_allocator_p;
        internal::l_allocator_p = nullptr;
        return false;
    }
    return true;
}

bool create(
    EGG::Heap *pHeap, GXPixelFmt pxlFmt, GXColor clearColor, u32 maxNumChildren, u32 maxNumScnObj, u32 numLightObj,
    u32 numLightSet, u32 numLightMgr, u32 numFogMgr
) {
    if (internal::l_lightMgr_pp && internal::l_fogMgr_pp) {
        return false;
    }
    EGG::GfxEngine::Configuration config = EGG::GfxEngine::Configuration();

    u16 temp1[2] = {config.field_0x14, config.field_0x16};
    u16 temp2[2] = {config.efbHeight, config.efbHeight};

    EGG::StateGX::initialize(config.efbWidth, config.efbHeight, clearColor, pxlFmt);
    EGG::Screen::Initialize(temp1, temp2, nullptr);
    EGG::Screen::SetTVModeDefault();
    EGG::DrawGX::Initialize(pHeap);

    EGG::LightTexture::initialize(0x20, pHeap);

    internal::l_lightMgr_pp = new (pHeap) EGG::LightManager *[numLightMgr];
    if (internal::l_lightMgr_pp) {
        internal::l_numLightMgr = numLightMgr;
        internal::l_fogMgr_pp = new (pHeap) EGG::FogManager *[numFogMgr];
        if (internal::l_fogMgr_pp) {
            internal::l_numFogMgr = numFogMgr;
            memset(internal::l_lightMgr_pp, 0, numLightMgr * 4);
            memset(internal::l_fogMgr_pp, 0, numFogMgr * 4);

            if (create(pHeap, maxNumChildren, maxNumScnObj, numLightObj, numLightSet)) {
                return true;
            }
        }
    }

    // Creation failed :(
    delete[] internal::l_lightMgr_pp;
    internal::l_lightMgr_pp = nullptr;
    delete[] internal::l_fogMgr_pp;
    internal::l_fogMgr_pp = nullptr;
    return false;
}

bool createLightMgr(EGG::Heap *pHeap, u16 p2, u16 p3, u8 p4, bool bCreateFogMgr, int idx) {
    using namespace internal;

    if (!l_lightMgr_pp || (idx < 0 || idx >= l_numLightMgr || idx >= l_numFogMgr) ||
        (l_lightMgr_pp[idx] || l_fogMgr_pp[idx])) {
        return false;
    }

    EGG::Heap *prevHeap = mHeap::setCurrentHeap(pHeap);
    EGG::LightManager *lightMgr = new (pHeap) EGG::LightManager(p2, p3, p4);
    mHeap::setCurrentHeap(prevHeap);

    if (lightMgr) {
        if (!bCreateFogMgr || createFogMgr(pHeap, 0x20, idx)) {
            l_lightMgr_pp[idx] = lightMgr;
            return true;
        }
    }
    delete lightMgr;
    return false;

    u8 input;
    u8 *output;
    switch (input % 8) {
        do {
            case 0: *output++ = input;
            case 1: *output++ = input;
            case 2: *output++ = input;
        } while (true);
    }
}

bool createFogMgr(EGG::Heap *pHeap, int numFog, int idx) {
    using namespace internal;

    if (idx < 0 || idx >= l_numFogMgr || l_fogMgr_pp[idx]) {
        return false;
    }

    EGG::FogManager *fogMgr;
    {
        mHeap heap(pHeap);
        fogMgr = new (pHeap) EGG::FogManager(numFog);
    }

    if (!fogMgr) {
        return false;
    }
    l_fogMgr_pp[idx] = fogMgr;
    return true;
}

nw4r::g3d::ScnRoot *getScnRoot() {
    return internal::l_scnRoot_p;
}

nw4r::g3d::Camera getCamera(int id) {
    return internal::l_scnRoot_p->GetCamera(id);
}

nw4r::g3d::Camera getCurrentCamera() {
    return internal::l_scnRoot_p->GetCurrentCamera();
}

int getCurrentCameraID() {
    return internal::l_scnRoot_p->GetCurrentCameraID();
}

void setCurrentCamera(int id) {
    internal::l_scnRoot_p->SetCurrentCamera(id);
}

nw4r::g3d::LightSetting *getLightSettingP() {
    return &internal::l_scnRoot_p->GetLightSetting();
}

EGG::LightManager *getLightMgr(int idx) {
    return internal::l_lightMgr_pp[idx];
}

EGG::FogManager *getFogMgr(int idx) {
    return internal::l_fogMgr_pp[idx];
}

void drawDone(int idx) {
    if (internal::l_lightMgr_pp && internal::l_lightMgr_pp[idx]) {
        internal::l_lightMgr_pp[idx]->DoneDraw();
    }
    if (internal::l_fogMgr_pp && internal::l_fogMgr_pp[idx]) {
        internal::l_fogMgr_pp[idx]->DoneDraw();
    }
}

void drawLightMapTexture(int idx) {
    using namespace internal;

    if (!l_lightMgr_pp) {
        return;
    }
    if (!l_lightMgr_pp[idx]) {
        return;
    }

    l_lightMgr_pp[idx]->GetTextureMgr()->drawAndCaptureTexture(0.0f, 0.0f, 0.0f, 0.0f);
}

void calcWorld(int idx) {
    if (internal::l_lightMgr_pp && internal::l_lightMgr_pp[idx]) {
        internal::l_lightMgr_pp[idx]->Calc(internal::l_scnRoot_p);
    }
    if (internal::l_fogMgr_pp && internal::l_fogMgr_pp[idx]) {
        internal::l_fogMgr_pp[idx]->Calc();
    }
    internal::l_scnRoot_p->CalcWorld();
}

void calcMaterial() {
    internal::l_scnRoot_p->CalcMaterial();
}

void calcView(int idx) {
    if (internal::l_lightMgr_pp && internal::l_lightMgr_pp[idx]) {
        nw4r::math::MTX34 camMtx;
        getCurrentCamera().GetCameraMtx(&camMtx);
        internal::l_lightMgr_pp[idx]->CalcView(camMtx, getCurrentCameraID(), internal::l_scnRoot_p);
    }
    if (internal::l_fogMgr_pp && internal::l_fogMgr_pp[idx]) {
        internal::l_fogMgr_pp[idx]->CopyToG3D(internal::l_scnRoot_p);
    }

    internal::l_scnRoot_p->CalcView();
    internal::l_scnRoot_p->GatherDrawScnObj();
    internal::l_scnRoot_p->ZSort();
}

void drawOpa() {
    internal::l_scnRoot_p->DrawOpa();
}

void drawXlu() {
    internal::l_scnRoot_p->DrawXlu();
}

bool pushBack(nw4r::g3d::ScnObj *obj) {
    return internal::l_scnRoot_p->PushBack(obj);
}

void clear() {
    internal::l_scnRoot_p->Clear();
}

void reset() {
    nw4r::g3d::G3dReset();
    if (internal::l_lightMgr_pp || internal::l_fogMgr_pp) {
        EGG::StateGX::resetGXCache();
    }
}

int getMatID(nw4r::g3d::ResMdl mdl, const char *name) {
    if (mdl.IsValid()) {
        nw4r::g3d::ResMat mat = mdl.GetResMat(name);
        if (mat.IsValid()) {
            return mat.ref().id;
        }
    }
    return -1;
}

int getNodeID(nw4r::g3d::ResMdl mdl, const char *name) {
    if (mdl.IsValid()) {
        nw4r::g3d::ResNode node = mdl.GetResNode(name);
        if (node.IsValid()) {
            return node.GetID();
        }
    }
    return -1;
}

void resetMaterial() {
    GXSetNumIndStages(0);
    for (int i = GX_TEVSTAGE0; i < GX_MAX_TEVSTAGE; i++) {
        GXSetTevDirect((GXTevStageID)i);
    }
}

} // namespace m3d
