#include "d/d_sc_game.h"

#include "common.h"
#include "d/d_base.h"
#include "d/d_fader.h"
#include "d/d_gfx.h"
#include "d/d_stage_mgr.h"
#include "d/flag/storyflag_manager.h"
#include "d/snd/d_snd_state_mgr.h"
#include "f/f_base.h"
#include "f/f_profile_name.h"
#include "m/m_fader_base.h"
#include "m/m_vec.h"
#include "s/s_FPhase.h"
#include "s/s_State.hpp"
#include "toBeSorted/arc_managers/layout_arc_manager.h"
#include "toBeSorted/arc_managers/oarc_manager.h"
#include "toBeSorted/file_manager.h"
#include "toBeSorted/minigame_mgr.h"
#include "toBeSorted/unk_save_time.h"

#include <cstring>

SPECIAL_BASE_PROFILE(GAME, dScGame_c, fProfile::GAME, 0x4, 0x6F9);

STATE_VIRTUAL_DEFINE(dScGame_c, Stanby);
STATE_VIRTUAL_DEFINE(dScGame_c, Action);

SpawnInfo dScGame_c::currentSpawnInfo;
SpawnInfo dScGame_c::nextSpawnInfo(dScGame_c::currentSpawnInfo);

dScGame_c *dScGame_c::sInstance;
u32 dScGame_c::sUpdateFrameCount;
u8 dScGame_c::sCurrentLayer;
u8 dScGame_c::sReloaderType;
bool dScGame_c::sCopyFileBToCurrentAfterRespawn;
bool dScGame_c::sDoSomethingWithFileAOnTransition;
bool dScGame_c::sPreventClearingSomeFlag;

sFPhase<dScGame_c>::phaseCallback dScGame_c::sCallbacks[] = {
    &dScGame_c::cb1, &dScGame_c::cb2, &dScGame_c::cb3, nullptr
};

void dScGame_c::someGfxThingCallback() {
    dScGame_c *scGame = dScGame_c::GetInstance();
    scGame->somethingWithScreen();
    scGame->mFader.draw();
}

dScGame_c::dScGame_c()
    : mPhases(this, sCallbacks),
      mStateMgr(*this),
      mStageMgrRef(nullptr),
      mScreen1(0x85),
      mScreen2(0x89),
      field_0x228(0),
      mReloadTrigger(fProfile::PROFILE_MAX) {
    resetLinkState();
    mBeedleShopState = 0;
    mLastAreaType = 6;
    mType0PosFlag = 0;
    mSavePromptFlag = false;
    mPreventSaveRespawnInfo = true;
    sInstance = this;
    currentSpawnInfo.reset();
}

int dScGame_c::create() {
    sFPhaseBase::sFPhaseState phaseResult = mPhases.step();
    if (phaseResult != sFPhaseBase::PHASE_ALL_DONE) {
        return NOT_READY;
    }

    dGfx_c::GetInstance()->setDrawCallback(someGfxThingCallback);
    return SUCCEEDED;
}

int dScGame_c::doDelete() {
    MinigameManager::destroy();
    mRelCtrl.do_unlink();
    mLayoutCtrl.release();

    return SUCCEEDED;
}

int dScGame_c::execute() {
    mFader.calc();
    mStateMgr.executeState();
    MinigameManager::execute();

    return SUCCEEDED;
}

int dScGame_c::draw() {
    mScreen1.addToDrawList();
    mScreen2.addToDrawList();

    return SUCCEEDED;
}

void dScGame_c::deleteReady() {
    dGfx_c::GetInstance()->setDrawCallback(nullptr);
}

sFPhaseBase::sFPhaseState dScGame_c::cb1() {
    mFader.create();
    mFader.setFrames(15, 15);
    mFader.setTypes(dFader_c::FADER_GREY, dFader_c::FADER_GREY);
    mFader.initForLyt();
    mRelCtrl.do_link();
    mLayoutCtrl.load(nullptr);

    return sFPhaseBase::PHASE_NEXT;
}

sFPhaseBase::sFPhaseState dScGame_c::cb2() {
    if (!mRelCtrl.do_link()) {
        return sFPhaseBase::PHASE_RETRY;
    }

    if (OarcManager::GetInstance()->ensureAllEntriesLoaded() != D_ARC_RESULT_OK) {
        return sFPhaseBase::PHASE_RETRY;
    }

    if (LayoutArcManager::GetInstance()->ensureAllEntriesLoaded() != D_ARC_RESULT_OK) {
        return sFPhaseBase::PHASE_RETRY;
    }

    MinigameManager::create();
    setReloadTrigger(fProfile::STAGE_MANAGER);
    mStateMgr.changeState(StateID_Stanby);
    return sFPhaseBase::PHASE_NEXT;
}

sFPhaseBase::sFPhaseState dScGame_c::cb3() {
    if (checkChildProcessCreateState()) {
        return sFPhaseBase::PHASE_RETRY;
    }
    return sFPhaseBase::PHASE_NEXT;
}

void dScGame_c::initializeState_Stanby() {
    setProcControl(ROOT_DISABLE_EXECUTE | ROOT_DISABLE_DRAW);
    if (mReloadTrigger == fProfile::STAGE_MANAGER) {
        copySpawnNextToCurrent();
    }
    dBase_c *base = dBase_c::createBase(mReloadTrigger, this, 0, OTHER);
    mStageMgrRef.link(base);
    mReloadTrigger = fProfile::PROFILE_MAX;
}

void dScGame_c::executeState_Stanby() {
    mStateMgr.changeState(StateID_Action);
}

void dScGame_c::finalizeState_Stanby() {}

void dScGame_c::initializeState_Action() {
    mFader.fadeIn();
}

void dScGame_c::executeState_Action() {
    if (mFader.isStatus(mFaderBase_c::FADED_OUT)) {
        if (attemptDelete()) {
            mStateMgr.changeState(StateID_Stanby);
        }
    } else if (mReloadTrigger != fProfile::PROFILE_MAX) {
        mFader.fadeOut();
        dSndStateMgr_c::GetInstance()->onGotoStage(mFader.getFadeOutFrame());
    }
}

void dScGame_c::finalizeState_Action() {}

void dScGame_c::somethingWithScreen() {}

void dScGame_c::setRespawnInfo(const mVec3_c &linkPos, const mAng3_c &linkRot, bool unk) {
    bool isInThunderDragonTrial = MinigameManager::isInMinigameState(MinigameManager::TRIAL_TIME_ATTACK) ||
                                          MinigameManager::isInMinigameState(MinigameManager::BOSS_RUSH) ?
                                      true :
                                      false;
    bool isInHordeFight = isCurrentStage("F403") && !StoryflagManager::sInstance->getFlag(STORYFLAG_HORDE_DEFEATED);
    s32 areaType = dStageMgr_c::GetInstance()->getAreaType();
    if (mPreventSaveRespawnInfo != true && !isInThunderDragonTrial && !isInHordeFight &&
        (areaType != 2 && !isCurrentStage("B100_1") && !sPreventClearingSomeFlag &&
         (areaType != 1 || (mLastAreaType != 1 && mLastAreaType != 2)))) {
        FileManager::GetInstance()->setT3Info(linkPos, linkRot);
        if (mLastAreaType != 5 && unk) {
            StoryflagManager::sInstance->unsetFlag(STORYFLAG_752);
        }
    }

    if (!isInThunderDragonTrial && isCurrentStage("B400") && mLastAreaType != 2) {
        FileManager::GetInstance()->copyCurrentToFileB();
    }
    sPreventClearingSomeFlag = 0;
    mPreventSaveRespawnInfo = false;
    mLastAreaType = areaType;
}

const LinkReloadInfo &dScGame_c::getLinkReloadInfo() const {
    return mReloadInfo;
}

bool dScGame_c::attemptDelete() {
    if (mStageMgrRef.p_owner == nullptr) {
        return true;
    } else {
        if (!mStageMgrRef.p_owner->mDeleteRequest) {
            mStageMgrRef.p_owner->deleteRequest();
        }
        return false;
    }
}

bool dScGame_c::setReloadTrigger(fProfile::PROFILE_NAME_e trigger) {
    if (mReloadTrigger != fProfile::PROFILE_MAX) {
        return false;
    }
    mReloadTrigger = trigger;
    return true;
}

void dScGame_c::setSpawnInfo(u8 roomid, const mVec3_c &pos, mAng rot) {
    mSpawnInfo.setSpawnData(currentSpawnInfo.stageName, roomid, sCurrentLayer, 0, pos, rot);
}

const SpawnInfoExt &dScGame_c::getSpawnInfo() const {
    return mSpawnInfo;
}

void dScGame_c::clearSpawnInfo() {
    mSpawnInfo.setSpawnData("", 0, 0, 0, mVec3_c::Zero, 0);
}

extern "C" void processEventFlags();
void dScGame_c::triggerExit(s32 room, u16 exitIndex, s32 forcedNight, s32 forcedTrial) {
    processEventFlags();
    // TODO
}

void SpawnInfoExt::setSpawnData(
    const char *i_stageName, u8 i_roomid, u8 i_layer, u8 i_entrance, const mVec3_c &pos, mAng rot
) {
    setData(
        i_stageName, i_roomid, i_layer, i_entrance, dScGame_c::currentSpawnInfo.night,
        dScGame_c::currentSpawnInfo.trial, 0, 15, -1
    );
    mType0Pos = pos;
    mType0RotY = rot;
}

void LinkReloadInfo::set(u8 roomid, const mVec3_c &pos, mAng rot, u32 linkParams) {
    mRoomId = roomid;
    mPosition = pos;
    mRotY = rot;
    mLinkParams = linkParams;
    mType0CsFlag = 1;
}

bool dScGame_c::isCurrentStage(const char *stage) {
    return dScGame_c::currentSpawnInfo.stageName == stage;
}

extern "C" void fn_801BB9C0();
extern "C" void maybeResetSkykeepPuzzle(bool);

void dScGame_c::copySpawnNextToCurrent() {
    if (sDoSomethingWithFileAOnTransition == true) {
        fn_801BB9C0();
        sDoSomethingWithFileAOnTransition = false;
    }
    if (std::strncmp(currentSpawnInfo.stageName, nextSpawnInfo.stageName, 2)) {
        SaveTimeRelated::GetInstance()->fn_801909A0();
    }
    currentSpawnInfo = nextSpawnInfo;
    sCurrentLayer = currentSpawnInfo.layer;
    maybeResetSkykeepPuzzle(false);
}

void dScGame_c::actuallyTriggerEntrance(
    const char *stageName, u8 roomid, u8 layer, u8 entrance, int forcedNight, int forcedTrial, u8 transitionType,
    u16 transitionFadeFrames, s8 field0x28
) {
    s32 trial, night;
    if (forcedTrial == SpawnInfo::RETAIN_TRIAL) {
        trial = currentSpawnInfo.trial;
    } else {
        trial = forcedTrial;
    }

    if (forcedNight == SpawnInfo::RETAIN_TOD) {
        night = currentSpawnInfo.night;
    } else {
        night = forcedNight;
    }

    nextSpawnInfo.setData(
        stageName, roomid, layer, entrance, night, trial, transitionType, transitionFadeFrames, field0x28
    );

    if (dScGame_c::GetInstance() != nullptr) {
        dScGame_c::GetInstance()->setFadeInType(transitionType);
        dScGame_c::GetInstance()->setFadeOutType(transitionType);
        dScGame_c::GetInstance()->setFaderFrames(transitionFadeFrames);
        dScGame_c::GetInstance()->resetLinkState();
        dScGame_c::GetInstance()->setBeedleShopState(0);
    }
}

void SpawnInfo::setData(
    const char *i_stageName, u8 i_roomid, u8 i_layer, u8 i_entrance, s32 i_night, s32 i_trial, u8 i_transitionType,
    u16 i_fadeFrames, s8 i_field0x28
) {
    stageName = i_stageName;
    room = i_roomid;
    layer = i_layer;
    entrance = i_entrance;
    night = i_night;
    trial = i_trial;
    transitionType = i_transitionType;
    transitionFadeFrames = i_fadeFrames;
    unk = i_field0x28;
}
