#ifndef D_MESSAGE_H
#define D_MESSAGE_H

#include "common.h"
#include "d/d_base.h"
#include "d/d_tag_processor.h"
#include "libms/flowfile.h"
#include "libms/msgfile.h"
#include "sized_string.h"

class dFlowBase_c {
protected:
    enum Type_e {
        FLOW_NONE,
        FLOW_MESSAGE,
        FLOW_BRANCH,
        FLOW_EVENT,
        FLOW_ENTRY,
        FLOW_JUMP,
    };

public:
    virtual ~dFlowBase_c() {}

    /* vt 0x0C */ virtual void triggerEntryPoint(s32, s32) = 0;
    /* vt 0x10 */ virtual void triggerEntryPoint(const char *) = 0;
    /* vt 0x14 */ virtual void advanceFlow() = 0;
    /* vt 0x18 */ virtual bool vt_0x18() const = 0;
    /* vt 0x1C */ virtual bool handleEventInternal(const MsbFlowInfo *element) = 0;
    /* vt 0x20 */ virtual bool handleEvent() = 0;
    /* vt 0x24 */ virtual bool handleMessage() = 0;
    /* vt 0x28 */ virtual bool handleBranch() = 0;
    /* vt 0x2C */ virtual bool handleEntry() = 0;
    /* vt 0x30 */ virtual bool handleJump() = 0;
    /* vt 0x34 */ virtual void vt_0x34() {}
};

// Ghidra: ActorEventFlowManager
class dFlow_c : dFlowBase_c {
protected:
    enum BranchValue_e {
        BRANCH_SELECTED_OPTION_0,
        BRANCH_SELECTED_OPTION_1,
        BRANCH_SELECTED_OPTION_FI,
        BRANCH_STORYFLAG,
        BRANCH_NONE,
        BRANCH_ZONEFLAG,
        BRANCH_SCENEFLAG,
        BRANCH_EVENT_COUNTER_THRESHOLD_1,
        BRANCH_EVENT_COUNTER_THRESHOLD_2,
        BRANCH_TEMPFLAG,
        BRANCH_CURRENT_RUPEES,
        BRANCH_RAND_2,
        BRANCH_RAND_3,
        BRANCH_RAND_4,
        BRANCH_NPC_1, // handled by subclass
        BRANCH_NPC_2, // handled by subclass
        BRANCH_NPC_3, // handled by subclass
        BRANCH_FREE_SPACE_IN_POUCH,
        BRANCH_18,
        BRANCH_19,
        BRANCH_FREE_SPACE_IN_ITEM_CHECK,
        BRANCH_TARGET_ACTOR_HAS_KILL_COUNT,
        BRANCH_22,
    };

    enum Event_e {
        EVENT_SET_STORYFLAG = 0,
        EVENT_UNSET_STORYFLAG = 1,
        EVENT_SET_SCENEFLAG = 2,
        EVENT_UNSET_SCENEFLAG = 3,
        EVENT_SET_ZONEFLAG = 4,
        EVENT_UNSET_ZONEFLAG = 5,
        EVENT_DELAY = 6,
        EVENT_LOAD_FI_FLOW = 7,
        EVENT_RUPEES = 8,
        EVENT_SET_ITEM = 9,
        EVENT_EXIT = 10,

        EVENT_12 = 12,

        EVENT_COUNTER_THRESHOLD = 23,
        EVENT_PLAY_SOUND = 24,
        EVENT_ADD_ITEM = 25,

        EVENT_SET_TEMPFLAG = 28,
        EVENT_UNSET_TEMPFLAG = 29,

        EVENT_START_MAP_EVENT = 30,

        EVENT_END_MAP_EVENT = 34,

        EVENT_SET_STORYFLAG_217 = 37,
        EVENT_DEMO_METER_ITEM_SELECT = 38,
        EVENT_CAMERA_42 = 42,
        EVENT_LYT_MINI_GAME = 44,
        EVENT_LYT_MINI_GAME_END = 45,
        EVENT_46 = 46,

        EVENT_RESET_STORYFLAG = 52,
        EVENT_SET_ITEMFLAG = 53,
        EVENT_PALETTE = 54,
        EVENT_DEMO_DOWSING = 55,
        EVENT_DEMO_METER_DOWSING = 56,
        EVENT_DEMO_METER_MINUS_BTN = 57,
        EVENT_SELECT_STORY_DOWSING = 58,
        EVENT_DEMO_COLLECTION_SCREEN = 59,
    };

public:
    dFlow_c();

    virtual ~dFlow_c();

    typedef u16 (dFlow_c::*BranchHandler)(const MsbFlowInfo *element) const;

    /* vt 0x0C */ virtual void triggerEntryPoint(s32 labelPart1, s32 labelPart2) override;
    /* vt 0x10 */ virtual void triggerEntryPoint(const char *) override;
    /* vt 0x14 */ virtual void advanceFlow() override;
    /* vt 0x18 */ virtual bool vt_0x18() const override;
    /* vt 0x1C */ virtual bool handleEventInternal(const MsbFlowInfo *element) override;
    /* vt 0x20 */ virtual bool handleEvent() override;
    /* vt 0x24 */ virtual bool handleMessage() override;
    /* vt 0x28 */ virtual bool handleBranch() override;
    /* vt 0x2C */ virtual bool handleEntry() override;
    /* vt 0x30 */ virtual bool handleJump() override;
    /* vt 0x38 */ virtual bool vt_0x38() const {
        return false;
    }
    /* vt 0x3C */ virtual u16 getSwitchChoice(const MsbFlowInfo *element, u16 param) const;
    /* vt 0x40 */ virtual bool triggerEntryPointChecked(s32 labelPart1, s32 labelPart2);

    /**
     * "Speculatively" evaluate a flow until a given event gets hit, and write
     * its parameters into the provided pointer. Probably has no side effects.
     */
    bool advanceUntilEvent(s32 searchParam3, s32 *pOutParams1n2);
    bool advanceUntil(s32 searchType, s32 searchParam3, s32 *pOutParams1n2);

    static void playSound(u32);

    u16 getField_0x44() const;
    u16 getNextFiFlow() const;

protected:
    static s32 sExitId;

    static BranchHandler sBranchHandlers[];
    u16 branchHandler00(const MsbFlowInfo *element) const;
    u16 branchHandler01(const MsbFlowInfo *element) const;
    u16 branchHandler02(const MsbFlowInfo *element) const;
    u16 branchHandler03(const MsbFlowInfo *element) const;
    u16 branchHandler04(const MsbFlowInfo *element) const;
    u16 branchHandler05(const MsbFlowInfo *element) const;
    u16 branchHandler06(const MsbFlowInfo *element) const;
    u16 branchHandler07(const MsbFlowInfo *element) const;
    u16 branchHandler08(const MsbFlowInfo *element) const;
    u16 branchHandler09(const MsbFlowInfo *element) const;
    u16 branchHandler10(const MsbFlowInfo *element) const;
    u16 branchHandler11(const MsbFlowInfo *element) const;
    u16 branchHandler12(const MsbFlowInfo *element) const;
    u16 branchHandler13(const MsbFlowInfo *element) const;
    u16 branchHandler14(const MsbFlowInfo *element) const;
    u16 branchHandler15(const MsbFlowInfo *element) const;
    u16 branchHandler16(const MsbFlowInfo *element) const;
    u16 branchHandler17(const MsbFlowInfo *element) const;
    u16 branchHandler18(const MsbFlowInfo *element) const;
    u16 branchHandler19(const MsbFlowInfo *element) const;
    u16 branchHandler20(const MsbFlowInfo *element) const;
    u16 branchHandler21(const MsbFlowInfo *element) const;
    u16 branchHandler22(const MsbFlowInfo *element) const;

    u16 findEntryPoint(u16 labelPart1, u16 labelPart2);
    u16 findEntryPoint(const char *label);

    void start(u16 entry);
    void clear();
    void setNext(u16 next);
    void setField0x3C();
    bool checkField0x3C() const;
    bool shouldHideKillCountForActor(s32 id) const;
    void createLytMiniGame();
    void clearMinigame();

    /* 0x04 */ MsbfInfo *mpMsbf;
    /* 0x08 */ s32 mCurrentFlowIndex;
    /* 0x0C */ u16 field_0x0C;
    /* 0x0E */ u8 field_0x0E;
    /* 0x0F */ u8 field_0x0F;
    /* 0x10 */ u8 field_0x10;
    /* 0x14 */ s32 field_0x14;
    /* 0x18 */ s32 mResultFromCounterCheck;
    /* 0x1C */ SizedString<32> mCurrentTextLabelName;
    /* 0x3C */ u8 field_0x3C;
    /* 0x40 */ s32 field_0x40;
    /* 0x44 */ u16 field_0x44;
    /* 0x46 */ u16 mNextFiFlow;
    /* 0x48 */ s32 mDelayTimer;
    /* 0x4C */ MsbFlowInfo mFlowInfo;
    /* 0x5C */ s32 mFiSpeechArgument;
    /* 0x60 */ s32 mFiInfo0;
};

class dMessage_c : public dBase_c {
public:
    enum Minigame_e {
        MG_THRILL_DIGGER = 0,
        MG_BAMBOO_CUTTING,
        MG_INSECT_CAPTURE,
        MG_ROLLERCOASTER,
        MG_PUMPKIN_ARCHERY,
        MG_FUN_FUN_ISLAND,
        MG_TRIAL_TIME_ATTACK,
        MG_BOSS_RUSH,

        MG_NONE,
    };

public:
    dMessage_c();
    virtual ~dMessage_c() {
        sInstance = nullptr;
    }

    int create() override;
    int doDelete() override;
    int execute() override;
    int draw() override;

    static const char *getLanguageIdentifier();

    static dMessage_c *getInstance() {
        return sInstance;
    }

    static dTagProcessor_c *getGlobalTagProcessor() {
        return sTagProcessor;
    }

    void clearLightPillarRelatedArgs();
    void init();
    void reset();

    bool getField_0x329() const {
        return field_0x329;
    }

    s32 getField_0x2FC() const {
        return field_0x2FC;
    }

    void setField_0x2FC(s32 val) {
        field_0x2FC = val;
    }

    bool getInMapEvent() const {
        return mInMapEvent;
    }

    void setInMapEvent(bool val) {
        mInMapEvent = val;
    }

    s32 getMapEvent() const {
        return mMapEvent;
    }

    void setMapEvent(s32 val) {
        mMapEvent = val;
    }

    void setField_0x329(bool v) {
        field_0x329 = v;
    }

    void setField_0x32A(bool v) {
        field_0x32A = v;
    }

    bool getField_0x32A() const {
        return field_0x32A;
    }

    bool getField_0x330() const {
        return field_0x330;
    }

    void setField_0x330(bool v) {
        field_0x330 = v;
    }

    void setMiniGameVariant(s32 v) {
        mMinigameVariant = v;
    }

    s32 getMiniGameVariant() const {
        return mMinigameVariant;
    }

    void setField_0x344(s32 v) {
        field_0x344 = v;
    }

    bool hasSetMinigameResult() const {
        return mHasSetMinigameResult;
    }

    void setHasSetMinigameResult(bool v) {
        mHasSetMinigameResult = v;
    }

    static bool isValidTextLabel(const char *name);
    static void loadTextByLabel(const char *label, dTagProcessor_c *tagProcessor, bool, u32, u32);

    static s32 getMsbtIndexForLabel(const char *name);
    static const char *getMsbtFileName(s32 index);
    static MsbfInfo *getMsbfInfoForIndex(s32 index);
    static s32 getMsbtIndexForMsbfIndex(s32);
    static const char *getArcNameByIndex(s32 idx, bool);
    static MsbtInfo *getMsbtInfoForIndex(s32 index);

    static const wchar_t *getTextMessageByLabel(const char *label, bool global, wchar_t *dstBuf, u32 maxLen);
    static const wchar_t *
    getTextMessageByLabel(const char *label, dTagProcessor_c *pTagProcessor, bool global, wchar_t *dstBuf, u32 maxLen);

    static const wchar_t *formatText(const wchar_t *text);

    void storeLightPillarRelatedArg(u32);
    u32 getLightPillarRelatedArg(s32);

    s32 getCurrentTextFileNumber() const {
        return mCurrentTextFileNumber;
    }

    void setCurrentTextFileNumber(s32 num) {
        mCurrentTextFileNumber = num;
    }

private:
    static void *sZev0;
    static void *sZevStage;

    static void *getZev0Internal();
    static void *getZevStageInternal();

    void executeMinigame();

    const wchar_t *formatTextInternal(const wchar_t *text);
    const wchar_t *getTextMessageByLabelInternal(
        const char *label, dTagProcessor_c *pTagProcessor, bool global, wchar_t *dstBuf, u32 maxLen
    );

    static s32 getArcIndexForFile(const char *fileName);

    bool checkIsValidTextLabel(const char *name);

    static void setZevFromMsbArc();
    static void setStageZevFromMsbArc();
    static s32 getMsbtNumberByIndex(s32 index);
    static const char *getMsbfFileName(s32 index);
    static s32 getMsbfNumberByIndex(s32 index);
    static void *getDataFromMsbArc(s32 number, const char *fileName, bool);

    s32 getTextIndexForLabel(const char *label);
    s32 getMsbtIndexForLabelInternal(const char *label);
    MsbtInfo *getMsbtInfoForIndexInternal(s32 index);
    MsbfInfo *getMsbfInfoForIndexInternal(s32 index);

    static const char *getArcNameByIndexInternal(s32 idx, bool global);

    static dMessage_c *sInstance;
    static dTagProcessor_c *sTagProcessor;

    /* 0x068 */ MsbtInfo *mpMsgs[82];
    /* 0x1B0 */ MsbfInfo *mpFlows[80];
    /* 0x2F0 */ SizedString<8> mLanguage;
    /* 0x2F8 */ u32 mCurrentTextFileNumber;

    /* 0x2FC */ s32 field_0x2FC;

    /* 0x300 */ u32 field_0x300[10];

    /* 0x328 */ bool mInMapEvent;
    /* 0x329 */ bool field_0x329;
    /* 0x32A */ bool field_0x32A;

    /* 0x32B */ u8 field_0x32B;
    /* 0x32C */ s32 mMapEvent;
    /* 0x330 */ bool field_0x330;

    /* 0x334 */ u32 mMinigameResultPoints;
    /* 0x338 */ u32 mMinigameTime;
    /* 0x33C */ s32 mMinigameVariant;
    /* 0x340 */ bool mHasSetMinigameResult;
    /* 0x344 */ s32 field_0x344;
};

#endif
