#ifndef D_SND_STATE_MGR_H
#define D_SND_STATE_MGR_H

#include "common.h"
#include "d/snd/d_snd_event.h"
#include "d/snd/d_snd_util.h"
#include "nw4r/snd/snd_FxReverbStdDpl2.h"
#include "nw4r/snd/snd_SoundHandle.h"
#include "sized_string.h"

SND_DISPOSER_FORWARD_DECL(dSndStateMgr_c);

// Previous Ghidra name: EnemySoundMgr
class dSndStateMgr_c {
public:
    SND_DISPOSER_MEMBERS(dSndStateMgr_c);

public:
    typedef void (*OnEventStartCallback)(s32 soundEventId, u32 flags);
    typedef void (*OnFlagsChangeCallback)(u32 newFlags, u32 oldFlags);

    enum StageFlags_e {
        STAGE_FIELD = 0x1,
        STAGE_DUNGEON = 0x2,
        STAGE_BOSS = 0x4,
        STAGE_SILENT_REALM = 0x8,

        STAGE_ROOM = 0x10, // only for sky

        STAGE_SKY = 0x20,
        STAGE_FOREST = 0x40,
        STAGE_MOUNTAIN = 0x80,
        STAGE_DESERT = 0x100,
        STAGE_SILENT_GROUNDS = 0x200,
    };

    enum Flag0x10_e {
        FLAG0x10_0x01 = 0x01,
        FLAG0x10_0x02 = 0x02,
        FLAG0x10_0x04 = 0x04,
        FLAG0x10_0x08 = 0x08,
        FLAG0x10_0x10 = 0x10,
    };

    // 0x94
    enum EventFlags_e {
        EVENT_IN_EVENT = 0x1,
        EVENT_DEMO = 0x2,
        EVENT_0x04 = 0x4,
        EVENT_MUTE_BGM_PARTIAL = 0x8,
        EVENT_MUTE_BGM_FULL = 0x10,
        EVENT_MUTE_STAGE_EFFECTS_PARTIAL = 0x20,
        EVENT_MUTE_STAGE_EFFECTS_FULL = 0x40,
        EVENT_MUTE_ENEMY_PARTIAL = 0x80,
        EVENT_MUTE_ENEMY_FULL = 0x100,
        EVENT_MUTE_OBJ_PARTIAL = 0x200,
        EVENT_MUTE_OBJ_FULL = 0x400,
        EVENT_0x800 = 0x800,
        EVENT_0x400000 = 0x400000,
    };

    dSndStateMgr_c();

    void setup(EGG::Heap *pHeap);
    void calc();
    void restoreEffects();

    static bool isInStage(const char *stageName);

    bool getField_0x064() const {
        return field_0x064;
    }

    s32 getField_0x14() const {
        return field_0x014;
    }

    s32 getStageId_0x040() const {
        return mStageId;
    }

    s32 getStageId_0x044() const {
        return field_0x044;
    }

    s32 getLayer() const {
        return mLayer;
    }

    u32 getFrameCounter() const {
        return mFrameCounter;
    }

    f32 getFxSend3D() const {
        return mFxSend3D;
    }

    void resetEventName() {
        mEventName = "EVENT_NONE";
    }

    const char *getCameraFmt() {
        return "%s_C%d_%d";
    }

    const char *getMsgWaitFmt() {
        return "%s_M%d_W%d_%d";
    }

    const char *getFrameFmt() {
        return "%s_%d";
    }

    bool checkFlag0x18(u32 mask);

    bool checkFlag0x258(u32 mask) {
        return field_0x258 & mask;
    }

    void resetFlag0x258() {
        field_0x258 = 0;
    }

    void onFlag0x258(u32 mask) {
        field_0x258 |= mask;
    }

    void offFlag0x258(u32 mask) {
        field_0x258 &= ~mask;
    }

    bool checkStageTypeFlag(u32 mask) {
        return mStageTypeFlags & mask;
    }

    bool checkFlag0x10(u32 mask) {
        return field_0x010 & mask;
    }

    void onFlag0x10(u32 mask) {
        field_0x010 |= mask;
    }

    void offFlag0x10(u32 mask) {
        field_0x010 &= ~mask;
    }

    bool checkEventFlag(u32 mask) {
        return mEventFlags & mask;
    }

    void onEventFlag(u32 mask) {
        mEventFlags |= mask;
    }

    void offEventFlag(u32 mask) {
        mEventFlags &= ~mask;
    }

    void setFlowEvent(u32 eventId);

    const char *getCurrentStageMusicDemoName() const;
    bool isInDemo() const {
        // implicit conversion required for regalloc in loadStageSound
        return getCurrentStageMusicDemoName();
    }

    void setStbEventName(const char *eventName);
    void setEvent(const char *eventName);
    // not sure, subtype is unused
    bool isActiveDemoMaybe(s32 subtype) const;
    bool isInEvent(const char *eventName);
    bool isInEvent();
    bool onSkipEvent() const;

    void onMsgStart(s32 idx);
    void onMsgEnd();

    void onMsgWaitStart();
    void onMsgWaitEnd();

    void onLinkDie();

    f32 getUserParamVolume(u32 userParam);

    void onStageOrLayerUpdate();
    void onStageLoad();
    void onRestartScene(s32 fadeFrames);
    void onGotoStage(s32 fadeFrames);
    void loadStageSound();
    void loadObjectSound();

    // TODO better names
    static const char *getStageName(s32 id);
    const char *getStageName4(s32 id);
    const char *getCurrentStageName4();

    static s32 getSndStageId(const char *stageName, s32 layer);
    static s32 getSndStageId3(const char *stageName, s32 layer);
    static s32 getSndStageId4(const char *stageName, s32 layer);

    static s32 getSndStageId2(s32 id);
    static s32 getSndStageId4(s32 id);

    static s32 getNextSndStageId(s32 id);
    static bool specialLayerVersionExists(const char *stageName, s32 layer);

    enum SoundIdLookup_e {
        LOOKUP_BGM,
        LOOKUP_BGM_MAIN,
        LOOKUP_SE_A,
    };

    u32 getSoundIdForStageAndLayer(SoundIdLookup_e lookup, const char *stageName, s32 layer, s32 stageId);

    void setFiltersIfUnderwater();
    void setBgmLpfAndFxSendIfUnderwater();
    void resetLpfAndFxSend();
    void resetBgmLpfAndFxSend();
    void setBgmAndStageEffectLpf();
    void setBgmLpf();
    void setBgmLpf(s32 fadeFrames);
    void resetBgmAndStageEffectLpf();
    void resetBgmLpf();

    void onCameraCut(s32 cutIdx);

    static void clearEventExecuteCallback();

private:
    void loadStageSound(bool force);

    void resetOverrides();
    void initializeEventCallbacks(const char *name);
    bool handleGlobalEvent(const char *name);
    bool handleStageEvent(const char *name);
    void handleDemoEvent(const char *name);

    void calcEvent();
    void calcRoomId();
    void setRoomId(s32 roomId);
    void calcTgSnd();
    void calcFilters();
    void calcFxSend3D();
    void setFxSend3DTarget();

    u32 getBgmLabelSoundId();
    void doLabelSuffix(const char *suffix);

    u32 convertSeLabelToSoundId(const char *label);
    u32 convertBgmLabelToSoundId(const char *label);
    u32 convertCmdLabelToSoundId(const char *label);

    void doBgm(const char *label);
    void doSe(const char *label);
    void doCmd(const char *label);

    u32 getSeCameraId();
    u32 getSeMsgWaitId();
    u32 getSeFrameCountId();

    u32 getBgmCameraId();
    u32 getBgmMsgWaitId();
    u32 getBgmFrameCountId();

    u32 getCmdCameraId();
    u32 getCmdMsgWaitId();
    u32 getCmdFrameCountId();

    void calcSe();
    bool calcBgm();
    void calcCmd();

    bool playSe(u32 soundId);
    bool playFanOrBgm(u32 soundId);
    bool playCmd(u32 soundId);

    void handleFan();
    void handleSe();
    void handleCmd();
    void handleSeLv();

    /** Called when no event is running anymore */
    void endEvent(bool skipped);
    /** Called when an event stops */
    bool finalizeEvent(bool skipped);

    void resetEventVars();

    // Callbacks start
    static void cbUnkNoop();

    // Callbacks end

    u32 getStageTypeFlags(const char *stageName) const;
    // ET, FS, or corresponding Sky Keep rooms
    static bool isVolcanicDungeon(u32 stageId);
    // Checks if the given stage + layer is the stage you're transported
    // to when viewing hint movies.
    static bool isSeekerStoneStage(const char *stageName, s32 layer);
    bool isSomeSkyloftRoom() const;

    void setCallbacksForStage();

    u32 getStageId(const char *name, s32 layer);
    static u32 getStageUnk2(u32 stageId);

    static SndEventCallback sEventExecuteCallback;

    /* 0x010 */ u32 field_0x010;
    /* 0x014 */ UNKWORD field_0x014;
    /* 0x018 */ UNKWORD field_0x018;
    /* 0x01C */ SizedString<32> mStageName;
    /* 0x03C */ s32 field_0x03C;
    /* 0x040 */ s32 mStageId;
    /* 0x044 */ UNKWORD field_0x044;
    /* 0x048 */ u8 _0x048[0x050 - 0x048];
    /* 0x050 */ s32 mPreviousStageId;
    /* 0x054 */ u32 mStageTypeFlags;
    /* 0x058 */ s32 mLayer;
    /* 0x05C */ s32 mRoomId;
    /* 0x060 */ s32 mCheckRoomIdCooldown;
    /* 0x064 */ bool field_0x064;
    /* 0x065 */ bool field_0x065;
    /* 0x066 */ bool mHasChangedTgSndAreaFlags;
    /* 0x067 */ bool mHasChangedTgSndAreaMgFlags;
    /* 0x068 */ u32 mSavedTgSndAreaFlags;
    /* 0x06C */ u32 mSavedTgSndAreaMgFlags;
    /* 0x070 */ void (*mpUnkCallback)();
    /* 0x074 */ UNKWORD field_0x074;
    /* 0x078 */ OnEventStartCallback mpOnEventStartCallback;
    /* 0x07C */ UNKWORD field_0x07C;
    /* 0x080 */ OnFlagsChangeCallback mpTgSndAreaFlagsChangeCallback;
    /* 0x084 */ OnFlagsChangeCallback mpTgSndAreaMgFlagsChangeCallback;
    /* 0x088 */ UNKWORD field_0x088;
    /* 0x08C */ s32 mSoundEventId;
    /* 0x090 */ s32 mCameraCutCounter;
    /* 0x094 */ u32 mEventFlags;
    /* 0x098 */ SizedString<64> mEventName;
    /* 0x0D8 */ SizedString<64> mPrevEventName;
    /* 0x118 */ const char *mpStbEventName;
    /* 0x11C */ u32 mFrameCounter;
    /* 0x120 */ u32 mCameraCutFrameCounter;
    /* 0x124 */ u32 mMsgFrameCounter;
    /* 0x128 */ u32 mSeLvSoundId;
    /* 0x12C */ SizedString<64> mSeName;
    /* 0x16C */ SizedString<64> mBgmName;
    /* 0x1AC */ SizedString<64> mFanName;
    /* 0x1EC */ SizedString<64> mCmdName;
    /* 0x22C */ UNKWORD field_0x22C;
    /* 0x230 */ nw4r::snd::SoundHandle mSeLvSoundHandle;
    /* 0x234 */ const SndEventDef *mpSoundEventDef;
    /* 0x238 */ u32 field_0x238;
    /* 0x23C */ u8 field_0x23C;
    /* 0x23D */ u8 field_0x23D;
    /* 0x240 */ UNKWORD field_0x240;
    /* 0x244 */ UNKWORD field_0x244;
    /* 0x248 */ UNKWORD field_0x248;
    /* 0x24C */ s32 mMsgCounter;
    /* 0x250 */ s32 mMsgWaitSelectCounter;
    /* 0x254 */ u8 field_0x254;
    /* 0x258 */ u32 field_0x258;
    /* 0x25C */ nw4r::snd::FxReverbStdDpl2 mFx;
    /* 0x48C */ UNKWORD field_0x48C;
    /* 0x490 */ f32 mFxSend3DDefault;
    /* 0x494 */ f32 mFxSend3DOverride;
    /* 0x498 */ f32 mFxSend3DNext;
    /* 0x49C */ f32 mFxSend3D;
    /* 0x4A0 */ f32 mFxSend3DTarget;
    /* 0x4A4 */ u32 mPrevStageGroup;
    /* 0x4A8 */ u8 field_0x4A8;
    /* 0x4A9 */ bool mNeedsGroupsReload;
};

#endif
