#ifndef JKRARCHIVE_H_
#define JKRARCHIVE_H_

#include "JSystem/JKernel/JKRDvdFile.h"
#include "JSystem/JKernel/JKRFileLoader.h"
#include "JSystem/JKernel/JKRHeap.h"
#include "types.h"

#ifdef __cplusplus
// NOTE: Vtable offsets are off

#define JKRARCHIVE_ATTR_COMPRESSION 0x04
#define JKRARCHIVE_ATTR_YAY0 0x80

inline u32 read_big_endian_u32(void* ptr) {
    u8* uptr = (u8*)ptr;
    return ((u32)uptr[0] << 0x18) | ((u32)uptr[1] << 0x10) | ((u32)uptr[2] << 8) | (u32)uptr[3];
}

class JKRArchive : public JKRFileLoader {
  public:
    enum EMountMode {
        UNKNOWN_MOUNT_MODE = 0,
        MOUNT_MEM = 1,
        MOUNT_ARAM = 2,
        MOUNT_DVD = 3,
        MOUNT_COMP = 4,
    };

    enum EMountDirection {
        UNKNOWN_MOUNT_DIRECTION = 0,
        MOUNT_DIRECTION_HEAD = 1,
        MOUNT_DIRECTION_TAIL = 2,
    };

    class CArcName {
      public:
        CArcName(const char** p1, char p2) {
            p1[0] = store(p1[0], p2);
        }

        const char* getString() const {
            return mString;
        }
        u16 getHash() const {
            return mHash;
        }
        void store(const char*);
        const char* store(const char*, char);

        // Unused/inlined:
        CArcName() {
        }
        CArcName(const char* data) {
            store(data);
        }

        u16 mHash;         // _00
        u16 _02;           // _02
        char mString[256]; // _04
        u8 _104[4];        // _104, unknown, used to fix stack size
    };

    struct SDIFileEntry {
        u16 getNameHash() const {
            return mHash;
        }
        u32 getNameOffset() const {
            return mFlag & 0xFFFFFF;
        }
        u32 getFlags() const {
            return mFlag >> 24;
        }
        u32 getAttr() const {
            return getFlags();
        }
        u32 getSize() {
            return mSize;
        }
        u16 getFileID() const {
            return mFileID;
        }
        bool isDirectory() const {
            return (getFlags() & 0x02) != 0;
        }
        bool isCompressed() const {
            return (getFlags() & 0x04) != 0;
        }
        u8 getCompressFlag() const {
            return (getFlags() & 0x04);
        } // apparently both necessary?
        bool isYAZ0Compressed() const {
            return (getFlags() & 0x80) != 0;
        }

        bool getFlag01() const {
            return (getFlags() & 0x01) != 0;
        }
        bool getFlag04() {
            return mFlag >> 0x18 & 0x04;
        }
        bool getFlag10() {
            return mFlag >> 0x18 & 0x10;
        }
        bool getFlag80() {
            return mFlag >> 0x18 & 0x80;
        }

        u16 mFileID;     // _00
        u16 mHash;       // _02
        u32 mFlag;       // _04
        u32 mDataOffset; // _08
        u32 mSize;       // _0C
        void* mData;     // _10
    };

    struct SDirEntry {
        u8 mFlags;   // _00
        u8 _01;      // _01
        u16 mID;     // _02
        char* mName; // _04
    };

    struct SDIDirEntry {
        u32 mType;     // _00
        u32 mOffset;   // _04
        u16 _08;       // _08
        u16 mNum;      // _0A
        u32 mFirstIdx; // _0C
    };

    // NB: Fabricated name
    struct SArcDataInfo {
        u32 num_nodes;           // _00
        u32 node_offset;         // _04
        u32 num_file_entries;    // _08
        u32 file_entry_offset;   // _0C
        u32 string_table_length; // _10
        u32 string_table_offset; // _14
        u16 nextFreeFileID;      // _18
        bool isSyncIDs;          // _1A
        u8 _1B[5];               // _1B, unknown
    };

    // NB: Fabricated name - need to check size
    struct SArcHeader {
        u32 signature;        // _00
        u32 file_length;      // _04
        u32 header_length;    // _08
        u32 file_data_offset; // _0C
        u32 file_data_length; // _10
        u32 _14;              // _14
        u32 _18;              // _18
        u32 _1C;              // _1C
    };

  public:
    virtual bool becomeCurrent(const char*);               // _10
    virtual void* getResource(const char* path);           // _14
    virtual void* getResource(u32 type, const char* name); // _18
    virtual size_t readResource(void* resourceBuffer, u32 bufferSize, const char* path,
                                JKRExpandSwitch expandSwitch);                                     // _1C
    virtual size_t readResource(void* resourceBuffer, u32 bufferSize, u32 type, const char* name); // _20
    virtual void removeResourceAll();                                                              // _24
    virtual bool removeResource(void*);                                                            // _28
    virtual bool detachResource(void*);                                                            // _2C
    virtual s32 getResSize(const void*) const;                                                     // _30
    virtual u32 countFile(const char*) const;                                                      // _34
    virtual JKRFileFinder* getFirstFile(const char*) const;                                        // _38
    virtual void* fetchResource(SDIFileEntry* entry, u32* outSize) = 0;                            // _40
    virtual void* fetchResource(void* resourceBuffer, u32 bufferSize, SDIFileEntry* entry, u32* resSize,
                                JKRExpandSwitch expandSwitch) = 0; // _44

    JKRArchive(s32, EMountMode);
    JKRArchive();
    JKRArchive(const char* p1, EMountMode mountMode);
    ~JKRArchive();

    SDIDirEntry* findDirectory(const char*, u32) const;
    SDIFileEntry* findFsResource(const char*, u32) const;
    SDIFileEntry* findIdResource(u16) const;
    SDIFileEntry* findIdxResource(u32) const;
    SDIFileEntry* findNameResource(const char*) const;
    SDIFileEntry* findPtrResource(const void*) const;
    SDIFileEntry* findTypeResource(u32, const char*) const;
    bool isSameName(CArcName&, u32, u16) const;

    bool getDirEntry(SDirEntry*, u32) const;
    void* getIdxResource(u32 index);
    size_t readResource(void* resourceBuffer, u32 bufferSize, u16 id);

    static JKRArchive* mount(char const*, EMountMode, JKRHeap*, EMountDirection);
    static JKRArchive* mount(void*, JKRHeap*, EMountDirection);
    static JKRArchive* mount(s32, EMountMode, JKRHeap*, EMountDirection);
    static void* getGlbResource(u32 type, const char* name, JKRArchive* archive);
    static JKRArchive* check_mount_already(s32);
    static JKRArchive* check_mount_already(s32, JKRHeap*);

    SDIDirEntry* findResType(u32) const;
    SDIFileEntry* findTypeResource(u32, u32) const;

    static int convertAttrToCompressionType(int attr) {
        int compression;
        if (FLAG_ON(attr, JKRARCHIVE_ATTR_COMPRESSION))
            compression = JKRCOMPRESSION_NONE;
        else if (!FLAG_ON(attr, JKRARCHIVE_ATTR_YAY0))
            compression = JKRCOMPRESSION_YAZ0;
        else
            compression = JKRCOMPRESSION_YAY0;

        return compression;
    }

    u32 getMountMode() const {
        return mMountMode;
    }
    u32 countFile() const {
        return mArcInfoBlock->num_file_entries;
    }
    int countDirectory() const {
        return mArcInfoBlock->num_nodes;
    }
    static u32 getCurrentDirID() {
        return sCurrentDirID;
    }
    static void setCurrentDirID(u32 dirID) {
        sCurrentDirID = dirID;
    }

    static u32 sCurrentDirID;

  protected:
    // _00     = VTBL
    // _00-_38 = JKRFileLoader
    JKRHeap* mHeap;                  // _38
    u8 mMountMode;                   // _3C
    s32 mEntryNum;                   // _40
    SArcDataInfo* mArcInfoBlock;     // _44
    SDIDirEntry* mDirectories;       // _48
    SDIFileEntry* mFileEntries;      // _4C
    const char* mStrTable;           // _50
    int _54;                         // _54
    int mCompression;                // _58
    EMountDirection mMountDirection; // _5C
};

enum JKRMemBreakFlag { MBF_0 = 0, MBF_1 = 1 };

class JKRAramArchive : public JKRArchive {
  public:
    JKRAramArchive();
    JKRAramArchive(s32, EMountDirection);

    virtual ~JKRAramArchive();                                                                  // _08
    virtual void* fetchResource(SDIFileEntry*, u32*);                                           // _40
    virtual void* fetchResource(void*, u32, SDIFileEntry*, u32*, JKRExpandSwitch expandSwitch); // _44

    bool open(s32);
    u32 getAramAddress_Entry(SDIFileEntry* fileEntry);
    u32 getAramAddress(u32, const char* file);
    static u32 fetchResource_subroutine(u32, u32, u8*, u32, int);
    static u32 fetchResource_subroutine(u32, u32, JKRHeap*, int, u8**);

    void fixedInit(s32 entryNum, EMountDirection direction);
    bool mountFixed(s32 entryNum, EMountDirection direction);
    bool mountFixed(const char* path, EMountDirection direction);
    void unmountFixed();

    // _00     = VTBL
    // _00-_60 = JKRArchive
    JKRAramBlock* mBlock; // _60
    JKRFile* mDvdFile;    // _64
};

struct JKRCompArchive : public JKRArchive {
    JKRCompArchive(s32, EMountDirection);

    virtual ~JKRCompArchive();                                      // _08
    virtual void removeResourceAll();                               // _24
    virtual bool removeResource(void*);                             // _28
    virtual void* fetchResource(SDIFileEntry* entry, u32* outSize); // _40
    virtual void* fetchResource(void* resourceBuffer, u32 bufferSize, SDIFileEntry* entry, u32* resSize,
                                JKRExpandSwitch expandSwitch); // _44

    bool open(s32);

    // Unused/inlined:
    void fixedInit(s32);
    void mountFixed(s32);
    void mountFixed(const char*);
    void unmountFixed();

    // _00     = VTBL
    // _00-_5C = JKRArchive
    u32 _60;                 // _60
    JKRAramBlock* mAramPart; // _64
    u32 _68;                 // _68
    JKRFile* mDvdFile;       // _6C
    u32 mSizeOfMemPart;      // _70
    u32 mSizeOfAramPart;     // _74
    u32 _78;                 // _78
};

struct JKRDvdArchive : public JKRArchive {
    JKRDvdArchive();
    JKRDvdArchive(s32, JKRArchive::EMountDirection);

    virtual ~JKRDvdArchive();                                       // _00
    virtual void* fetchResource(SDIFileEntry* entry, u32* outSize); // _38
    virtual void* fetchResource(void* resourceBuffer, u32 bufferSize, SDIFileEntry* entry, u32* resSize,
                                JKRExpandSwitch expandSwitch); // _3C

    bool open(s32);
    static u32 fetchResource_subroutine(s32, u32, u32, u8*, u32, int, int);
    static u32 fetchResource_subroutine(s32, u32, u32, JKRHeap*, int, int, u8**);

    // Unused/inlined:
    unknown fixedInit(s32, EMountDirection);
    unknown mountFixed(s32, EMountDirection);
    unknown mountFixed(const char*, EMountDirection);
    unknown unmountFixed();

    // _00     = VTBL
    // _00-_5C = JKRArchive
    int _60;           // _60
    JKRFile* mDvdFile; // _64
};

struct JKRMemArchive : public JKRArchive {
    JKRMemArchive(); // unused/inlined
    JKRMemArchive(s32, EMountDirection);
    JKRMemArchive(void*, u32, JKRMemBreakFlag);
    JKRMemArchive(const char*, EMountDirection); // unused/inlined

    virtual ~JKRMemArchive();                                       // _08
    virtual void removeResourceAll();                               // _24
    virtual bool removeResource(void*);                             // _28
    virtual void* fetchResource(SDIFileEntry* entry, u32* outSize); // _40
    virtual void* fetchResource(void* resourceBuffer, u32 bufferSize, SDIFileEntry* entry, u32* resSize,
                                JKRExpandSwitch expandSwitch); // _44

    bool open(s32, EMountDirection);
    bool open(void*, u32, JKRMemBreakFlag);
    static u32 fetchResource_subroutine(u8*, u32, u8*, u32, int);

    // Unused/inlined:
    void fixedInit(s32);
    void mountFixed(s32, EMountDirection);
    void mountFixed(const char*, EMountDirection);
    void mountFixed(void*, JKRMemBreakFlag);
    void unmountFixed();
    void open(const char*, EMountDirection);

    // _00     = VTBL
    // _00-_60 = JKRArchive
    SArcHeader* mArcHeader; // _60
    u8* mArchiveData;       // _64
    bool mIsOpen;           // _68
};

inline int JKRConvertAttrToCompressionType(int attr) {
    return JKRArchive::convertAttrToCompressionType(attr);
}

inline JKRArchive* JKRMountArchive(const char* path, JKRArchive::EMountMode mountMode, JKRHeap* heap,
                                   JKRArchive::EMountDirection mountDirection) {
    return JKRArchive::mount(path, mountMode, heap, mountDirection);
}

inline JKRArchive* JKRMountArchive(void* inBuf, JKRHeap* heap, JKRArchive::EMountDirection mountDirection) {
    return JKRArchive::mount(inBuf, heap, mountDirection);
}

#endif
#endif
