#ifndef TOBESORTED_ATTENTION_H
#define TOBESORTED_ATTENTION_H

#include "common.h"
#include "d/a/obj/d_a_obj_base.h"
#include "m/m3d/m_anmchr.h"
#include "m/m3d/m_anmmatclr.h"
#include "m/m3d/m_anmtexpat.h"
#include "m/m3d/m_smdl.h"
#include "m/m_allocator.h"
#include "toBeSorted/d_emitter.h"
#include "toBeSorted/raii_ptr.h"

static const u32 OFF = 'off ';
static const u32 NONE = 'none';
static const u32 KEEP = 'keep';
static const u32 NEXT = 'next';
static const u32 ON = 'on  ';
static const u32 AWAY = 'away';

class InteractionMdl {
public:
    m3d::smdl_c mMdl;
    m3d::anmMatClr_c mAnmClr;
    m3d::anmTexPat_c mAnmTex;
    RaiiPtr<m3d::anmChr_c> mAnmChr;
    u8 field_0x78;
    u8 field_0x79;

    InteractionMdl() {}
    ~InteractionMdl() {}
};

enum InteractionType {
    /*  0 */ UNK_0,
    /*  1 */ PICK_UP,
    /*  2 */ OPEN,
    /*  3 */ UNK_3,
    /*  4 */ TALK,
    /*  5 */ EXAMINE_TALK,
    /*  6 */ EXAMINE_6,
    /*  7 */ UNK_7,
    /*  8 */ GET_IN,
    /*  9 */ READY_SWORD,
    /* 10 */ UNK_10,
    /* 11 */ UNK_11,
    /* 12 */ READ,
    /* 13 */ GRAB,
    /* 14 */ CATCH,
    /* 15 */ UNK_15,
    /* 16 */ UNK_16,
    /* 17 */ DIG,
    /* 18 */ UNK_18,
    /* 19 */ SIT,
    /* 20 */ UNK_20,

    UNK_0x58 = 0x58,
};

class AttentionInfo {
public:
    AttentionInfo() {}
    ~AttentionInfo() {}

    /* 0x00 */ u8 unk;
    /* 0x01 */ u8 mActorIdx;
    /* 0x02 */ u8 mInteractionType;
    /* 0x03 */ u8 field_0x03;
    /* 0x04 */ f32 field_0x04;
    /* 0x08 */ mVec3_c field_0x08;
};

class AttentionPool {
public:
    AttentionPool() {}
    ~AttentionPool() {}
    dAcRef_c<dAcObjBase_c> mRefs[8];
    AttentionInfo mInfos[8];
    s32 mNumUsedRefs;

    int fn_80096190(dAcObjBase_c *actor, u8 unk, InteractionType interactionType);
    bool insertTarget(
        dAcObjBase_c *actor, u32 unk1, mVec3_c *pos, InteractionType interactionType, u8 field_0x03, f32 field_0x04
    );

    dAcObjBase_c *getActor(s32 i) {
        // This only matches if the comparision is always true
        // Logic is hard sometimes I guess xD
        return i < mNumUsedRefs && mInfos[i].mActorIdx != -1 ? mRefs[mInfos[i].mActorIdx].get() : nullptr;
    }

    void clear() {
        for (int i = 0; i < 8; i++) {
            mInfos[i].unk = 0;
            mInfos[i].mActorIdx = -1;
            mInfos[i].field_0x04 = 0.0f;
            mInfos[i].field_0x03 = 0;
            mInfos[i].mInteractionType = 0;
            mRefs[i].unlink();
        }
        mNumUsedRefs = 0;
    }
};

class AttentionGroup {
public:
    AttentionGroup() {}
    ~AttentionGroup() {}

    AttentionPool mPools[2];
    u32 mWhichPool;

    AttentionPool *getOtherPool() {
        return &mPools[mWhichPool ^ 1];
    }

    AttentionPool *getPool() {
        return &mPools[mWhichPool];
    }

    void fn_800964B0();
};

struct InteractionTargetDef {
    s32 field_0x00;
    u32 field_0x04;
    u32 field_0x08;
    InteractionType interactType;
    u32 interactFlags;
    f32 field_0x14;
    f32 field_0x18;
    f32 field_0x1C;
    f32 field_0x20;
    f32 field_0x24;
    f32 field_0x28;
    f32 field_0x2C;
};

// TODO this may include more things from AttentionManager, see AttentionManager's dtor
class InteractionModels {
public:
    /* 0x00 */ UNKWORD field_0xA5C;
    /* 0x04 */ s32 mCurrentTargetInfoIdx;
    /* 0x08 */ u32 mState;
    /* 0x0C */ nw4r::g3d::ResFile mResFile;
    /* 0x10 */ InteractionMdl mMdls[2];
};

class AttentionManager {
    mHeapAllocator_c mAllocator;
    /* 0x01C */ AttentionGroup mGroups[5];
    /* 0xA58 */ bool mTargeted;
    /* 0xA59 */ bool mHoldingZ;
    /* 0xA5A */ u8 field_0xA58[0xA5C - 0xA5A];
    /* 0xA5C */ InteractionModels mModels;
    /* 0xB64 */ u8 mHasTarget;
    /* 0xB65 */ u8 field_0xB65[0xB68 - 0xB65];
    /* 0xB68 */ UNKWORD field_0xB68;
    /* 0xB6C */ dEmitter_c mEffect1;
    /* 0xBA0 */ dEmitter_c mEffect2;
    /* 0xBD4 */ u8 field_0xBD4;
    /* 0xBD5 */ u8 field_0xBD5;

public:
    AttentionManager();
    /* vt at 0xBDC */ virtual ~AttentionManager();

    static AttentionManager *GetInstance() {
        return sInstance;
    }

private:
    /* 0xBDC */ u8 field_0xBDC;

public:
    static AttentionManager *sInstance;
    enum Flags_e {
        FLAGS_0,
        FLAGS_1,
        FLAGS_2,
        FLAGS_3,
    };
    bool create();
    bool createHeap();
    bool execute();
    bool draw();

    void addTarget(dAcObjBase_c &actor, const InteractionTargetDef &def, u32, mVec3_c *);

    void addPickUpTarget(dAcObjBase_c &actor, f32 field_0x14);
    void addSitTarget(dAcObjBase_c &actor, f32 field_0x14, Flags_e flags);

    void addTalkTarget_unused(dAcObjBase_c &actor);

    void
    addExamineTalkTarget(dAcObjBase_c &actor, u32 flags, f32 field_0x14, f32 ignored, f32 field_0x20, f32 field_0x24);
    void addExamineTalkTarget(dAcObjBase_c &actor, u32 flags, f32 field_0x14, f32 field_0x20, f32 field_0x24);

    void addCatchTarget(dAcObjBase_c &actor, u32 flags, f32 field_0x14, f32 field_0x24_neg, f32 field_0x20_neg);

    void addCatchLikeTarget(
        dAcObjBase_c &actor, InteractionType interactionType, u32 flags, f32 field_0x14, f32 field_0x24_neg,
        f32 field_0x20_neg
    );

    void addUnk3Target(dAcObjBase_c &actor, u32 flags, f32 field_0x14, f32 field_0x24_neg, f32 field_0x20_neg);

    void
    addUnk3Target(dAcObjBase_c &actor, u32 flags, f32 arg5, f32 field_0x14, f32 field_0x24_neg, f32 field_0x20_neg);
    void addUnk3Target(
        dAcObjBase_c &actor, u32 arg2, mVec3_c *arg3, u32 flags, f32 arg5, f32 field_0x14, f32 field_0x24_neg,
        f32 field_0x20_neg
    );

    void addNpcTalkTarget(dAcObjBase_c &actor, u32 flags, f32 field_0x14, f32 ignored, f32 field_0x20, f32 field_0x24);

    void
    addUnk7Target(dAcObjBase_c &actor, u32 flags, f32 arg5, f32 field_0x14, f32 field_0x24_neg, f32 field_0x20_neg);

    void addReadTarget(dAcObjBase_c &actor, u32 flags, f32 field_0x14, f32 ignored, f32 field_0x20, f32 field_0x24);
    void addReadTarget2(dAcObjBase_c &actor, u32 flags, f32 field_0x14, f32 field_0x20, f32 field_0x24);

    bool checkLink2() const;

    dAcObjBase_c *getTargetedActor() {
        if (!isInNormalGameState() || !mTargeted) {
            return nullptr;
        }
        return getTargetedActorUnchecked();
    }

    dAcObjBase_c *getTargetedActorUnchecked() {
        return mGroups[1].getOtherPool()->getActor(mModels.mCurrentTargetInfoIdx);
    }

private:
    bool isInNormalGameState() const;
    bool checkZButtonPressed() const;
    bool checkZButtonHeld() const;
    bool checkLink1() const;
    bool switchTarget(s32 target);
    bool playTargetAnim(s32 target);

    bool isZButtonPressed() const;
    bool isZButtonHeld() const;
    bool checkUnknown() const;

    bool collisionCheck0(const mVec3_c &a, const mVec3_c &b);
    bool collisionCheck1(const mVec3_c &a, const mVec3_c &b);
    bool collisionCheck2(const mVec3_c &a, const mVec3_c &b);

    bool fn_80096B40(dAcObjBase_c *actor);
    f32 targetScore(dAcObjBase_c *target, dAcObjBase_c *origin);
};

#endif
