#ifndef D_SOUND_SOURCE_H
#define D_SOUND_SOURCE_H

#include "common.h"
#include "d/snd/d_snd_3d_actor.h"
#include "d/snd/d_snd_source_if.h"
#include "d/snd/d_snd_types.h"
#include "d/snd/d_snd_util.h"
#include "nw4r/math/math_types.h"
#include "nw4r/snd/snd_SoundHandle.h"
#include "nw4r/snd/snd_SoundStartable.h"
#include "nw4r/ut/ut_list.h"

/** Manages sound relating to a particular actor. */
/** Size: probably 0x15C */
class dSoundSource_c : public dSoundSourceIf_c, public dSnd3DActor_c {
public:
    dSoundSource_c(s32 sourceType, dAcBase_c *, const char *name, dSndSourceGroup_c *pOwnerGroup);
    virtual ~dSoundSource_c();

    u32 modifySoundId(u32 baseSoundId);
    static u32 modifySoundId(u32 baseSoundId, dSoundSource_c *source);
    u32 getRemoConSoundVariant(u32 soundId) const;
    s32 getRoomId() const;
    bool isInaudibleInternal();
    void stopAllPlaySounds(s32 fadeFrames);
    f32 timeAreaCheck();
    void handleRemoCon(nw4r::snd::SoundHandle *pHandle, u32 baseId);
    bool startRemoConSound(u32 soundId);
    nw4r::snd::SoundHandle *startSound(u32 soundId, nw4r::snd::SoundHandle *handle);
    dSndDistantSoundActor_c *
    startSoundAtPosition(u32 soundId, const nw4r::math::VEC3 *position, nw4r::snd::SoundHandle *handle);
    nw4r::snd::SoundHandle *holdSound(u32 soundId, nw4r::snd::SoundHandle *handle);
    u32 specializeBgHitSoundId(u32 baseSoundId, u32 polyAttr0, u32 polyAttr1);

    // This is where it gets a bit wild and this class starts mixing in overrides between
    // new virtual functions, which causes the vtable to list these functions in exactly this
    // order.
    /* 0x17C */ virtual const char *getName() const {
        return mpName;
    }
    /* 0x180 */ virtual void setOrigName(const char *arg) {
        mpOrigName = arg;
    }
    /* 0x184 */ virtual void preCalc();
    /* 0x188 */ virtual void postSetup() {}
    /* 0x18C */ virtual void postCalc();
    /* 0x190 */ virtual bool d_s_vt_0x190() const;
    /* 0x194 */ virtual u32 overrideStartSoundId(u32 soundId);

    /* 0x198 */ virtual void postStartSound(nw4r::snd::SoundHandle &handle, dSndSeSound_c *pSound, u32 id) override;

    /* 0x19C */ virtual u32 overrideHitObjSoundId(u32 soundId, dSoundSource_c *source);
    /* 0x1A0 */ virtual u32 overrideHoldSoundId(u32 soundId, bool initial);

    /* 0x1A4 */ virtual void
    postHoldSound(nw4r::snd::SoundHandle &handle, dSndSeSound_c *pSound, u32 id, UNKWORD) override;
    /* 0x1A8 */ virtual StartResult
    SetupSound(nw4r::snd::SoundHandle *pHandle, u32 soundId, const StartInfo *pStartInfo, void *) override;

    /* 0x1AC */ virtual bool d_s_vt_0x1AC(u32 soundId);
    /* 0x1B0 */ virtual StartResult
    setupSound(nw4r::snd::SoundHandle *pHandle, u32 soundId, const StartInfo *pStartInfo, void *arg, bool holdFlag);
    /* 0x1B4 */ virtual void attachDistantSound(dSndDistantSoundActor_c *);
    /* 0x1B8 */ virtual void detachDistantSound(dSndDistantSoundActor_c *);
    /* 0x1BC */ virtual void detachAllDistantSounds();
    /* 0x1C0 */ virtual bool hasDistantSounds() const {
        return nw4r::ut::List_GetFirstConst(&mDistantSoundList) != nullptr;
    }
    /* 0x1C4 */ virtual void pauseAllDistantSounds(bool flag, int fadeFrames);
    /* 0x1C8 */ virtual void resetTempParams();
    /* 0x1CC */ virtual void postSetupSound(u32 playingId, u32 requestedId, dSndSeSound_c *seSound) {}

    /* 0x1D0 */ virtual void setPause(bool flag, int fadeFrames) override;

    /* 0x1D4 */ virtual dSndAnimSound_c *getAnimSound() {
        return nullptr;
    }
    /* 0x1D8 */ virtual void registerAdditionalSource(dSoundSource_c *other) {
        return;
    }
    /* 0x1DC */ virtual bool isMultiSource() const {
        return 0;
    }
    /* 0x1E0 */ virtual void onAnimSoundEvent(s32 arg) {
        mAnimEventValue = arg;
    }
    /* 0x1E4 */ virtual void d_s_vt_0x1E4_noop();
    /* 0x1E8 */ virtual u32 d_s_vt_0x1E8(u32 soundId);

    // Overrides of dSoundSourceIf_c - always in the first section of
    // the vtable, so the order is not certain. May have to reorder for weak
    // function order.
    /* 0x00C */ virtual void setup() override;
    /* 0x010 */ virtual s32 getCategory() const override {
        return mSourceCategory;
    }
    /* 0x014 */ virtual bool isCategory(s32 category) const override {
        return category == mSourceCategory;
    }
    /* 0x018 */ virtual s32 getSourceType() const override {
        return mSourceType;
    }
    /* 0x01C */ virtual bool isSourceType(s32 type) const override {
        return type == mSourceType;
    }
    /* 0x020 */ virtual void vt_0x020() override {}
    /* 0x024 */ virtual void setSubtype(u8 subtype) override {
        mSubtype = subtype;
    }
    /* 0x028 */ virtual const nw4r::math::VEC3 &getListenerPosition() const override;
    /* 0x02C */ void calc(const nw4r::math::VEC3 &) override;
    /* 0x030 */ virtual void onFlag(u32 mask) override {
        mFlagsPersistent |= mask;
    }
    /* 0x034 */ virtual void offFlag(u32 mask) override {
        mFlagsPersistent &= ~mask;
    }
    /* 0x038 */ virtual bool checkFlag(u32 mask) const override {
        return (mFlagsPersistent & mask) != 0 || (mFlagsHold & mask) != 0;
    }
    /* 0x03C */ virtual void holdFlag(u32 mask) override {
        mFlagsHold |= mask;
    }
    /* 0x040 */ virtual void stopAllSound(s32 fadeFrames) override {
        SoundActor::StopAllSound(fadeFrames);
    }
    /* 0x044 */ virtual void shutdown() override;
    /* 0x048 */ virtual bool hasPlayingSounds() const override;
    /* 0x04C */ virtual bool isPlayingSound(u32 soundId) override;
    /* 0x050 */ virtual bool isPlayingSound(const char *soundId) override;
    /* 0x054 */ virtual void setCtxParam(const dSndSoundCtxParam *arg) override {
        mpCtxParam = arg;
    }
    /* 0x058 */ virtual void setCtxFloat(f32 value) override;
    /* 0x05C */ virtual void setCtxInt(s32 value) override;
    /* 0x060 */ virtual bool startSound(u32 soundId) override;
    /* 0x064 */ virtual bool startSoundWithIntParam(u32 soundId, s32 value) override;
    /* 0x068 */ virtual bool startSoundWithFloatParam(u32 soundId, f32 value) override;
    /* 0x06C */ virtual bool startSoundWithParams(u32 soundId, f32 fValue, s32 value) override;
    /* 0x070 */ virtual bool startSound(const char *label) override;
    /* 0x074 */ virtual bool startSoundWithIntParam(const char *label, s32 value) override;
    /* 0x078 */ virtual bool startSoundWithFloatParam(const char *label, f32 value) override;
    /* 0x07C */ virtual bool startSoundWithParams(const char *label, f32 fValue, s32 value) override;
    /* 0x080 */ virtual bool startSoundAtPosition(u32 soundId, const nw4r::math::VEC3 *position) override;
    /* 0x084 */ virtual bool
    startObjHitSound(u32 baseSoundId, dSoundSourceIf_c *source, const nw4r::math::VEC3 *position) override;
    /* 0x088 */ virtual bool
    startBgHitSound(u32 baseSoundId, u32 polyAttr0, u32 polyAttr1, const nw4r::math::VEC3 *position) override;
    /* 0x08C */ virtual bool startSoundAtPosition(const char *label, const nw4r::math::VEC3 *position) override;
    /* 0x090 */ virtual bool
    startObjHitSound(const char *label, dSoundSourceIf_c *source, const nw4r::math::VEC3 *position) override;
    /* 0x094 */ virtual bool
    startBgHitSound(const char *label, u32 polyAttr0, u32 polyAttr1, const nw4r::math::VEC3 *position) override;
    /* 0x098 */ virtual bool startSoundAtPosition2(u32 soundId, const nw4r::math::VEC3 *position) override;
    /* 0x09C */ virtual bool startSoundAtPosition2(const char *label, const nw4r::math::VEC3 *position) override;
    /* 0x0A0 */ virtual bool maybeUnusedEnemyDeath() override;
    /* 0x0A4 */ virtual void stopSounds(u32 soundId, s32 fadeFrames) override;
    /* 0x0A8 */ virtual void stopSounds(const char *label, s32 fadeFrames) override;
    /* 0x0AC */ virtual bool holdSound(u32 soundId) override;
    /* 0x0B0 */ virtual bool holdSoundWithIntParam(u32 soundId, s32 value) override;
    /* 0x0B4 */ virtual bool holdSoundWithFloatParam(u32 soundId, f32 value) override;
    /* 0x0B8 */ virtual bool holdSoundWithParams(u32 soundId, f32 fValue, s32 value) override;
    /* 0x0BC */ virtual bool holdSound(const char *label) override;
    /* 0x0C0 */ virtual bool holdSoundWithIntParam(const char *label, s32 value) override;
    /* 0x0C4 */ virtual bool holdSoundWithFloatParam(const char *label, f32 value) override;
    /* 0x0C8 */ virtual bool holdSoundWithParams(const char *label, f32 fValue, s32 value) override;
    /* 0x0CC */ virtual bool holdSoundAtPosition(u32 soundId, const nw4r::math::VEC3 *position) override;
    /* 0x0D0 */ virtual bool holdSoundAtPosition(const char *label, const nw4r::math::VEC3 *position) override;
    /* 0x0D4 */ virtual bool startVoiceLine(u32 id) override {
        return false;
    }
    /* 0x0D8 */ virtual bool startVoiceLine(const char *label) override;
    /* 0x0DC */ virtual bool vt_0xDC(u32 id) override {
        return false;
    }
    /* 0x0E0 */ virtual void stopCurrentActionSound() override {}
    /* 0x0E4 */ virtual void stopActionSound(u32 id) override {}
    /* 0x0E8 */ virtual bool isPlayingActionSound() const override {
        return false;
    }
    /* 0x0EC */ virtual bool isCurrentActionSound(u32 id) const override {
        return false;
    }
    /* 0x0F0 */ virtual void vt_0x0F0_noop() override {}
    /* 0x0F4 */ virtual void setField0x101(bool value) override {
        field_0x101 = value;
    }
    /* 0x0F8 */ virtual void setField0x102(u8 value) override {
        field_0x102 = value;
    }
    /* 0x0FC */ virtual bool isInaudible() override;
    /* 0x100 */ virtual bool hasAnimSound() override {
        return false;
    }
    /* 0x104 */ virtual void load(void *data, const char *name) override {}
    /* 0x108 */ virtual void setFrame(f32 frame) override {}
    /* 0x10C */ virtual void setRate(f32 frame) override {}
    /* 0x110 */ virtual void setPolyAttrs(u8 polyAttr0, u8 polyAttr1) override;
    /* 0x114 */ virtual void setBattleBgmRelated(UNKWORD) override {}
    /* 0x118 */ virtual UNKWORD checkBattleBgmRelated() override {
        return 0;
    }

    dSndSeSound2_c *getHandleType2ForSoundId(u32 soundId);

    // Would have loved to roll this in with a PrepareSound function
    // but that doesn't work
    void markPrepared() {
        field_0x11C |= 0x80000000;
    }

    nw4r::snd::SoundStartable::StartResult
    startSound(u32 soundId, nw4r::snd::SoundHandle *handle, nw4r::snd::SoundHandle **pOutHandle);

protected:
    StartResult setupSoundCommon(nw4r::snd::SoundHandle *pHandle, u32 soundId, const StartInfo *pStartInfo, void *arg);
    void setVolumeFade(f32 volume, u32 fadeFrames);
    nw4r::snd::SoundStartable::StartResult onSetupError();

private:
    /**
     * Sound sources can cause other sounds to be started. E.g when
     * walking, Link will produce a walk sound, but an additional sound
     * will be played, a different one per surface material.
     *
     * If this flag isn't set, the code will find the correct variant for
     * a base sound, play the variant, and then play the base sound with this
     * flag set so that we don't attempt to find and play the variant again.
     */
    static bool sIsStartingBaseSound;
    f32 getBaseSoundVolume(u32 variantSoundId, u32 baseSoundId);
    dSndDistantSoundActor_c *startBaseSoundAtPosition(u32 baseSoundId, const nw4r::math::VEC3 *position, f32 volume);
    nw4r::snd::SoundHandle *startBaseSound(u32 baseSoundId, f32 volume);

    nw4r::snd::SoundHandle *continueHoldingSound(
        dSndSeSound2_c *seHandle, u32 soundId, dSndDistantSoundActor_c *distant, const nw4r::math::VEC3 *position
    );

    void addSeHandleType1(dSndSeSound1_c *handle);
    void addSeHandleType2(dSndSeSound2_c *handle);
    void removeSeHandleType1(dSndSeSound1_c *handle, bool stop);
    void removeSeHandleType2(dSndSeSound2_c *handle);
    void removeAllSeHandles();
    void removeAllSeHandlesType1();
    void removeAllSeHandlesType2();

    void stopSoundHandles(u32 soundId, s32 fadeFrames);

    void calcHandles();
    void calcHandlesType1();
    void calcHandlesType2();

    dSndSeSound1_c *getHandleType1ForSoundId(u32 soundId);
    dSndSeSound1_c *getHandleType1ForSoundHandle(nw4r::snd::SoundHandle *handle);

    dSndSeSound1_c *getHandleType1ForSoundHandle_Dupe(nw4r::snd::SoundHandle *pHandle);
    dSndSeSound2_c *getHandleType2ForSoundHandle_Dupe(nw4r::snd::SoundHandle *pHandle);

    void initVolumeFade();
    void calcVolumeFade();

    // at 0x00: dSoundSourceIf_c vtable
    // at 0x04: dSnd3DActor_c sub-object
    // at 0x58: thunk-vtable
protected:
    /* 0x0E8 */ nw4r::ut::Node mMgrLink;
    /* 0x0F0 */ const char *mpName;
    /* 0x0F4 */ const char *mpOrigName;
    /* 0x0F8 */ dAcBase_c *mpActor;
    /* 0x0FC */ u8 mSourceCategory;
    /* 0x0FD */ u8 mSourceType;
    /* 0x0FE */ u8 mSubtype;
    /* 0x0FF */ bool mIsSetup;
    /* 0x100 */ u8 field_0x100;
    /* 0x101 */ bool field_0x101;
    /* 0x102 */ u8 field_0x102;
    /* 0x104 */ u32 mFlagsPersistent;
    /* 0x108 */ u32 mFlagsHold;
    /* 0x10C */ const dSndSoundCtxParam *mpCtxParam;
    /* 0x110 */ LIST_MEMBER(dSndDistantSoundActor_c, DistantSound); // node offset 0xEC
    /* 0x11C */ UNKWORD field_0x11C;
    /* 0x120 */ LIST_MEMBER(dSndSeSound1_c, HandleType1); // node offset 0x4
    /* 0x12C */ LIST_MEMBER(dSndSeSound2_c, HandleType2); // node offset 0x4
    /* 0x138 */ nw4r::ut::Node mGroupLink;                // node for list in dSndSourceGroup_c
    /* 0x140 */ dSndSourceGroup_c *mpOwnerGroup;
    /* 0x144 */ f32 mVolumeFadeTarget;
    /* 0x148 */ f32 mVolumeFadeStepSize;
    /* 0x14C */ nw4r::ut::Node mSubSourceLink; // node for list in dSndSourceEnemyMultiBase_c
    /* 0x154 */ s32 mAnimEventValue;
    /* 0x158 */ s16 mPolyAttr0;
    /* 0x15A */ s16 mPolyAttr1;
};

#endif
