#include <dolphin/dvd.h>

#include "JSystem/JKernel/JKRAram.h"
#include "JSystem/JKernel/JKRArchive.h"
#include "JSystem/JKernel/JKRDisposer.h"
#include "JSystem/JKernel/JKRFileFinder.h"
#include "JSystem/JKernel/JKRHeap.h"
#include "JSystem/JUtility/JUTAssertion.h"
#include "types.h"

// TODO: this file should emit a vtable but it doesn't, luckily JKRArchivePri
// does that and it doesn't cause any issues

JKRArchive* JKRArchive::check_mount_already(s32 entryNum, JKRHeap* pHeap) {
    // UNUSED FUNCTION
    JKRHeap* heap = pHeap;
    if (heap == nullptr) {
        heap = JKRGetCurrentHeap();
    }

    JSUListIterator<JKRFileLoader> iterator;
    for (iterator = sVolumeList.getFirst(); iterator != sVolumeList.getEnd(); ++iterator) {
        if (iterator->getVolumeType() == 'RARC') {
            JKRArchive* archive = (JKRArchive*)iterator.getObject(); // in TP debug it calls operator-> ?
            if (archive->mEntryNum == entryNum && archive->mHeap == heap) {
                archive->mMountCount++;
                return archive;
            }
        }
    }
    return nullptr;
}

JKRArchive* JKRArchive::check_mount_already(s32 entryNum) {
    for (JSUListIterator<JKRFileLoader> iterator = sVolumeList.getFirst(); iterator != sVolumeList.getEnd();
         ++iterator) {
        if (iterator->getVolumeType() == 'RARC') {
            JKRArchive* archive = (JKRArchive*)iterator.getObject(); // in TP debug it calls operator-> ?
            if (archive->mEntryNum == entryNum) {
                archive->mMountCount++;
                return archive;
            }
        }
    }
    return nullptr;
}

JKRArchive* JKRArchive::mount(const char* path, EMountMode mode, JKRHeap* heap, EMountDirection direction) {
    int entryNum = DVDConvertPathToEntrynum((char*)path);
    if (entryNum < 0)
        return nullptr;

    JKRArchive* mountedArchive = check_mount_already(entryNum);
    if (mountedArchive != nullptr) {
        return mountedArchive;
    }

    int alignment = (direction == MOUNT_DIRECTION_HEAD) ? 4 : -4;
    JKRArchive* archive;
    switch (mode) {
        case MOUNT_MEM:
            if (entryNum == -1)
                archive = nullptr;
            else
                archive = new (heap, alignment) JKRMemArchive(entryNum, direction);
            break;
        case MOUNT_ARAM:
            archive = new (heap, alignment) JKRAramArchive(entryNum, direction);
            break;
        case MOUNT_DVD:
            archive = new (heap, alignment) JKRDvdArchive(entryNum, direction);
            break;
        case MOUNT_COMP:
            archive = new (heap, alignment) JKRCompArchive(entryNum, direction);
            break;
    }
    if (archive != nullptr && archive->getMountMode() == UNKNOWN_MOUNT_MODE) {
        delete archive;
        archive = nullptr;
    }
    return archive;
}

JKRArchive* JKRArchive::mount(void* p1, JKRHeap* heap, EMountDirection mountDirection) {
    JKRArchive* archive = check_mount_already((s32)p1, heap);
    if (archive != nullptr) {
        return archive;
    }
    return new (heap, (mountDirection == MOUNT_DIRECTION_HEAD) ? 4 : -4) JKRMemArchive(p1, 0xFFFF, MBF_0);
}

JKRArchive* JKRArchive::mount(s32 entryNum, EMountMode mountMode, JKRHeap* heap, EMountDirection mountDirection) {
    JKRArchive* archive = check_mount_already(entryNum, heap);
    if (archive) {
        return archive;
    } else {
        int i = (mountDirection == MOUNT_DIRECTION_HEAD) ? 4 : -4;
        JKRArchive* archive;
        switch (mountMode) {
            case MOUNT_MEM:
                archive = new (heap, i) JKRMemArchive(entryNum, mountDirection);
                break;
            case MOUNT_ARAM:
                archive = new (heap, i) JKRAramArchive(entryNum, mountDirection);
                break;
            case MOUNT_DVD:
                archive = new (heap, i) JKRDvdArchive(entryNum, mountDirection);
                break;
            case MOUNT_COMP:
                archive = new (heap, i) JKRCompArchive(entryNum, mountDirection);
                break;
        }
        if (archive != nullptr && archive->getMountMode() == UNKNOWN_MOUNT_MODE) {
            delete archive;
            archive = nullptr;
        }
        return archive;
    }
}

bool JKRArchive::becomeCurrent(const char* path) {
    SDIDirEntry* entry;
    if (*path == '/') {
        const char* directoryName = path + 1;
        if (*directoryName == '\0') {
            directoryName = nullptr;
        }
        entry = findDirectory(directoryName, 0);
    } else {
        entry = findDirectory(path, sCurrentDirID);
    }
    bool result = (entry != nullptr);
    if (result) {
        sCurrentVolume = this;
        sCurrentDirID = (entry - mDirectories);
    }
    return result;
}

bool JKRArchive::getDirEntry(JKRArchive::SDirEntry* dirEntry, u32 p2) const {
    SDIFileEntry* fileEntry = findIdxResource(p2);
    if (!fileEntry) {
        return false;
    }

    dirEntry->mFlags = fileEntry->mFlag >> 0x18;
    dirEntry->mID = fileEntry->mFileID;
    dirEntry->mName = (char*)mStrTable + (fileEntry->mFlag & 0xFFFFFF);
    return true;
}

void* JKRArchive::getGlbResource(u32 type, const char* name, JKRArchive* archive) {
    void* resource = nullptr;
    if (archive) {
        return archive->getResource(type, name);
    }
    for (JSULink<JKRFileLoader>* link = sVolumeList.getFirst(); link != nullptr; link = link->getNext()) {
        if (link->getObject()->getVolumeType() == 'RARC' && (resource = link->getObject()->getResource(type, name))) {
            break;
        }
    }
    return resource;
}

void* JKRArchive::getResource(const char* path) {
    JUT_ASSERT(isMounted());
    SDIFileEntry* fileEntry;
    if (*path == '/') {
        fileEntry = findFsResource(path + 1, 0);
    } else {
        fileEntry = findFsResource(path, sCurrentDirID);
    }
    return (fileEntry != nullptr) ? (void*)fetchResource(fileEntry, nullptr) : nullptr;
}

void* JKRArchive::getResource(u32 type, const char* name) {
    JUT_ASSERT(isMounted());
    SDIFileEntry* fileEntry;
    if (type == 0 || type == '????') {
        fileEntry = findNameResource(name);
    } else {
        fileEntry = findTypeResource(type, name);
    }
    return (fileEntry != nullptr) ? (void*)fetchResource(fileEntry, nullptr) : nullptr;
}

void* JKRArchive::getIdxResource(u32 index) {
    SDIFileEntry* fileEntry = findIdxResource(index);
    return (fileEntry != nullptr) ? (void*)fetchResource(fileEntry, nullptr) : nullptr;
}

size_t JKRArchive::readResource(void* resourceBuffer, u32 bufferSize, u32 type, const char* name) {
    JUT_ASSERT(isMounted());
    SDIFileEntry* fileEntry;
    if (type == 0 || type == '????') {
        fileEntry = findNameResource(name);
    } else {
        fileEntry = findTypeResource(type, name);
    }
    if (fileEntry) {
        u32 resourceSize;
        fetchResource(resourceBuffer, bufferSize, fileEntry, &resourceSize, EXPAND_SWITCH_DECOMPRESS);
        return resourceSize;
    }
    return 0;
}

// Returns the size of the resource at the given path, or 0 if not found.
size_t JKRArchive::readResource(void* resourceBuffer, u32 bufferSize, const char* path, JKRExpandSwitch expandSwitch) {
    JUT_ASSERT(isMounted());
    SDIFileEntry* fileEntry;
    if (*path == '/') {
        fileEntry = findFsResource(path + 1, 0);
    } else {
        fileEntry = findFsResource(path, sCurrentDirID);
    }
    if (fileEntry) {
        u32 resourceSize;
        fetchResource(resourceBuffer, bufferSize, fileEntry, &resourceSize, expandSwitch);
        return resourceSize;
    }
    return 0;
}

// Returns the size of the resource with the given ID, or 0 if not found.
size_t JKRArchive::readResource(void* resourceBuffer, u32 bufferSize, u16 id) {
    JUT_ASSERT(isMounted());
    SDIFileEntry* fileEntry = findIdResource(id);
    if (fileEntry) {
        u32 resSize;
        fetchResource(resourceBuffer, bufferSize, fileEntry, &resSize, EXPAND_SWITCH_DEFAULT);
        return resSize;
    }
    return 0;
}

// This function is currently full off bugs, it doesn't increase the pointer to
// the file entries, and sets the pointer of mData to null before it's sent to
// free
void JKRArchive::removeResourceAll() {
    if (mArcInfoBlock && mMountMode != MOUNT_MEM) {
        SDIFileEntry* entry = mFileEntries;
        for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) {
            if (entry->mData) {
                entry->mData = nullptr;
                JKRFreeToHeap(mHeap, entry->mData);
            }
        }
    }
}

bool JKRArchive::removeResource(void* resource) {
    JUT_ASSERT(resource != 0);
    SDIFileEntry* entry = findPtrResource(resource);
    if (entry == nullptr) {
        return false;
    }
    entry->mData = nullptr;
    JKRHeap::free(resource, mHeap);
    return true;
}

bool JKRArchive::detachResource(void* resource) {
    JUT_ASSERT(resource != 0);
    SDIFileEntry* entry = findPtrResource(resource);
    if (entry == nullptr) {
        return false;
    }
    entry->mData = nullptr;
    return true;
}

s32 JKRArchive::getResSize(const void* resource) const {
    JUT_ASSERT(resource != 0);
    SDIFileEntry* entry = findPtrResource(resource);
    return (entry == nullptr) ? -1 : entry->getSize();
}

u32 JKRArchive::countFile(const char* path) const {
    SDIDirEntry* dirEntry;
    if (*path == '/') {
        const char* pathPtr = path + 1;
        if (*pathPtr == '\0') {
            pathPtr = nullptr;
        }
        dirEntry = findDirectory(pathPtr, 0);
    } else {
        dirEntry = findDirectory(path, sCurrentDirID);
    }
    return (dirEntry) ? dirEntry->mNum : 0;
}

JKRFileFinder* JKRArchive::getFirstFile(const char* path) const {
    SDIDirEntry* dirEntry;
    if (*path == '/') {
        const char* pathPtr = path + 1;
        if (*pathPtr == '\0') {
            pathPtr = nullptr;
        }
        dirEntry = findDirectory(pathPtr, 0);
    } else {
        dirEntry = findDirectory(path, sCurrentDirID);
    }
    if (dirEntry) {
        return new (JKRGetSystemHeap(), 0)
            JKRArcFinder(const_cast<JKRArchive*>(this), dirEntry->mFirstIdx, dirEntry->mNum);
    }
    return nullptr;
}
