#include "d/a/obj/d_a_obj_stream_lava.h"

#include "common.h"
#include "d/col/bg/d_bg_s.h"
#include "d/d_room.h"
#include "d/d_stage.h"
#include "d/flag/sceneflag_manager.h"
#include "m/m3d/m_fanm.h"
#include "nw4r/g3d/res/g3d_resfile.h"

STATE_DEFINE(dAcOstreamLava_c, Wait);
STATE_DEFINE(dAcOstreamLava_c, Stream);

const f32 dAcOstreamLava_c::unkFloat0 = 0.0f;

static const char *const AcOstreamLava__OarcNames[] = {
    "FlowLavaD2", "FlowLavaD2", "FlowLavaLast_2", "FlowLavaLast_2", "FlowLavaLast_2",
};
static const char *const AcOstreamLava__ModelNames[] = {
    "FlowLavaD2_A", "FlowLavaD2_B", "FlowLavaLast_2A", "FlowLavaLast_2B", "FlowLavaLast_2C",
};
static const char *const AcOstreamLava__DbzNames[] = {
    "dzb/FlowLavaD2_A.dzb",    "dzb/FlowLavaD2_B.dzb",    "dzb/FlowLavaLast_2A.dzb",
    "dzb/FlowLavaLast_2B.dzb", "dzb/FlowLavaLast_2C.dzb",
};
static const char *const AcOstreamLava__PlcNames[] = {
    "dat/FlowLavaD2_A.plc",    "dat/FlowLavaD2_B.plc",    "dat/FlowLavaLast_2A.plc",
    "dat/FlowLavaLast_2B.plc", "dat/FlowLavaLast_2C.plc",
};
static const char *const AcOstreamLava__AnmClrNames[] = {
    "FlowLavaD2", "FlowLavaD2", "FlowLavaLast_2", "FlowLavaLast_2", "FlowLavaLast_2",
};

SPECIAL_ACTOR_PROFILE(OBJ_STREAM_LAVA, dAcOstreamLava_c, fProfile::OBJ_STREAM_LAVA, 0x1FA, 0, 6);

bool dAcOstreamLava_c::createHeap() {
    mSubtype = mParams & 0xF;
    mResFile = nw4r::g3d::ResFile(getOarcResFile(AcOstreamLava__OarcNames[mSubtype]));
    dStage_c::bindStageResToFile(&mResFile);

    nw4r::g3d::ResMdl mdl;
    nw4r::g3d::ResAnmTexSrt anmSrtWait;
    nw4r::g3d::ResAnmClr anmClr;

    mdl = mResFile.GetResMdl(AcOstreamLava__ModelNames[mSubtype]);
    TRY_CREATE(mModel.create(mdl, &mAllocator, 0x32C));

    anmSrtWait = mResFile.GetResAnmTexSrt("Wait");
    TRY_CREATE(mAnmTexSrtWait.create(mdl, anmSrtWait, &mAllocator, nullptr, 1));
    mModel.setAnm(mAnmTexSrtWait);

    anmClr = mResFile.GetResAnmClr(AcOstreamLava__AnmClrNames[mSubtype]);
    TRY_CREATE(mAnmMatClr.create(mdl, anmClr, &mAllocator, nullptr, 1));

    void *dzb = getOarcFile(AcOstreamLava__OarcNames[mSubtype], AcOstreamLava__DbzNames[mSubtype]);
    void *plc = getOarcFile(AcOstreamLava__OarcNames[mSubtype], AcOstreamLava__PlcNames[mSubtype]);
    updateMatrix();
    mModel.setLocalMtx(mWorldMtx);

    TRY_CREATE(!mCollision.Set((cBgD_t *)dzb, (PLC *)plc, cBgW::MOVE_BG_e, &mWorldMtx, &mScale));
    mCollision.Lock();

    TRY_CREATE(mCollision.InitMapStuff(&mAllocator));
    return true;
}

int dAcOstreamLava_c::create() {
    CREATE_ALLOCATOR(dAcOstreamLava_c);
    dBgS::GetInstance()->Regist(&mCollision, this);
    dBgS::GetInstance()->RegistBg(&mCollision, this);

    mShouldStreamSceneflag = getFromParams(4, 0xFF); // (params >> 4) & 0xFF;
    mEventId = getFromParams(0xC, 0xFF);             // params >> 0xC;
    mHideActor = !getFromParams(0x14, 1);            // (params >> 0x14 & 1) == 0;
    mModel.setAnm(mAnmMatClr);

    bool shouldStream = SceneflagManager::sInstance->checkFlag(mRoomID, mShouldStreamSceneflag);
    if (shouldStream) {
        mStateMgr.changeState(StateID_Stream);
    } else {
        mStateMgr.changeState(StateID_Wait);
    }

    mModel.setPriorityDraw(0x1C, 0x9);
    mVec3_c min, max;
    mModel.getBounds(&min, &max);
    mBoundingBox.Set(min, max);
    mCullingDistance = 50000.0f;

    int roomId_tmp = mRoomID;
    if (addActorToRoom(-1)) {
        mRoomID = roomId_tmp;
        changeLoadedEntitiesWithSet();
    }

    return SUCCEEDED;
}

int dAcOstreamLava_c::doDelete() {
    dBgS::GetInstance()->UnRegist(&mCollision);
    changeLoadedEntitiesNoSet();
    return SUCCEEDED;
}

int dAcOstreamLava_c::actorExecute() {
    mStateMgr.executeState();
    mAnmMatClr.play();

    dRoom_c *currentRoom = dStage_c::GetInstance()->getRoom(mRoomID);

    if (currentRoom->checkFlag(2)) {
        setObjectProperty(OBJ_PROP_0x200);
        return SUCCEEDED;
    } else {
        unsetObjectProperty(OBJ_PROP_0x200);
        return SUCCEEDED;
    }
}

int dAcOstreamLava_c::draw() {
    if (!mHideActor) {
        drawModelType1(&mModel);
    }
    return SUCCEEDED;
}

void dAcOstreamLava_c::initializeState_Wait() {
    mAnmTexSrtWait.setAnm(mModel, mResFile.GetResAnmTexSrt("Wait"), 0, m3d::PLAY_MODE_4);
}

void dAcOstreamLava_c::executeState_Wait() {
    mAnmTexSrtWait.play();

    bool shouldStream = SceneflagManager::sInstance->checkFlag(mRoomID, mShouldStreamSceneflag);
    if (shouldStream) {
        mStateMgr.changeState(StateID_Stream);
    }
}

// BADLY MATCHING
// Using the potential improvement causes issues with the andc instruction
// Should be: andc r6, r0, r9
// Currently: andc r6, r9, r0
// https://decomp.me/scratch/3PKdt
void dAcOstreamLava_c::finalizeState_Wait() {
    if (mEventId != 0xFF) {
        // Hacky solution:
        static volatile u32 FLAGS_1 = 0x00000001;
        static u32 FLAGS_2 = 0x00100001;
        u32 f1 = FLAGS_1;
        u32 f2 = FLAGS_2;
        u32 eventFlag = f2 & ~f1;

        // Potential improvement:
        // Swaps the numbers in .data and swaps the operands in andc
        // u32 eventFlag = Event::makeEventFlag(0x100001, 0x1);

        Event ev(mEventId, mRoomID, eventFlag, nullptr, nullptr);
        mEvent.scheduleEvent(ev, 0);
    }
}

void dAcOstreamLava_c::initializeState_Stream() {
    mAnmTexSrtWait.setAnm(mModel, mResFile.GetResAnmTexSrt("Flow"), 0, m3d::PLAY_MODE_4);
}

void dAcOstreamLava_c::executeState_Stream() {
    mAnmTexSrtWait.play();

    bool shouldStream = SceneflagManager::sInstance->checkFlag(mRoomID, mShouldStreamSceneflag);
    if (!shouldStream) {
        mStateMgr.changeState(StateID_Wait);
    }
}

void dAcOstreamLava_c::finalizeState_Stream() {}
