#include "Project/Resource/ResourceSystem.h"

#include <container/seadStrTreeMap.h>
#include <heap/seadHeapMgr.h>

#include "Library/Base/Macros.h"
#include "Library/Base/StringUtil.h"
#include "Library/File/FileUtil.h"
#include "Library/LiveActor/ActorInitResourceData.h"
#include "Library/LiveActor/ActorResourceFunction.h"
#include "Library/Resource/Resource.h"
#include "Library/Yaml/ByamlIter.h"

namespace al {

ResourceSystem::ResourceSystem(const char* name) {
    addCategory("リソースシステム", 1, sead::HeapMgr::instance()->getCurrentHeap());

    const char* resourceName = "SystemData/ResourceSystem";
    if (name)
        resourceName = name;

    if (isExistArchive(resourceName)) {
        Resource* resource =
            findOrCreateResourceCategory(resourceName, "リソースシステム", nullptr);
        if (resource)
            mResourceCategoryTable = new ByamlIter(resource->getByml("ResourceCategoryTable"));
    }
}

ALWAYS_INLINE void ResourceSystem::createResourceCore(Resource* resource) {
    StringTmp<256> fileName = StringTmp<256>("%s.bfres", resource->getArchiveName());

    if (!resource->isExistFile(fileName))
        return;

    ByamlIter iter;
    nn::g3d::ResFile* resFile = nullptr;
    if (tryGetActorInitFileIter(&iter, resource, "InitModel", nullptr)) {
        const char* textureArc = nullptr;
        iter.tryGetStringByKey(&textureArc, "TextureArc");

        if (textureArc) {
            StringTmp<256> filePath = StringTmp<256>("ObjectData/%s", textureArc);
            resFile = findOrCreateResource(filePath, nullptr)->getResFile();
        }
    }
    resource->tryCreateResGraphicsFile(fileName, resFile);
    resource->setActorInitResourceData(new ActorInitResourceData(resource));
}

Resource* ResourceSystem::createResource(const sead::SafeString& name, ResourceCategory* category,
                                         const char* ext) {
    sead::ScopedCurrentHeapSetter setter(category->heap);

    Resource* resource = nullptr;
    resource = ext ? new Resource(name, loadArchiveWithExt(name, ext)) : new Resource(name);
    category->treeMap.insert(name, resource);

    createResourceCore(resource);

    return resource->getFileArchive() ? resource : nullptr;
}

void cleanupResGraphicsFile(const sead::SafeString& key, Resource* resource) {
    resource->cleanupResGraphicsFile();
}

Resource* ResourceSystem::findResource(const sead::SafeString& categoryName) {
    return findResourceCore(categoryName, nullptr);
}

Resource* ResourceSystem::findResourceCore(const sead::SafeString& name,
                                           sead::RingBuffer<ResourceCategory*>::iterator* outIter) {
    for (auto iter = mCategories.begin(); iter != mCategories.end(); ++iter) {
        auto* node = (*iter)->treeMap.find(name);
        if (!node)
            continue;
        if (outIter)
            *outIter = iter;
        return node->value();
    }

    return nullptr;
}

Resource* ResourceSystem::findOrCreateResource(const sead::SafeString& categoryName,
                                               const char* name) {
    Resource* resource = findResource(categoryName);
    if (resource)
        return resource;

    return createResource(categoryName, findResourceCategory(categoryName), name);
}

void ResourceSystem::setCurrentCategory(const char* name) {
    mCurrentCategoryName = name;
}

const char* ResourceSystem::findCategoryNameFromTable(const sead::SafeString& name) const {
    if (!mResourceCategoryTable)
        return nullptr;

    for (s32 i = 0; i < mResourceCategoryTable->getSize(); i++) {
        ByamlIter categoryIter;
        if (!mResourceCategoryTable->tryGetIterByIndex(&categoryIter, i))
            continue;

        const char* categoryName = nullptr;
        if (!categoryIter.tryGetStringByKey(&categoryName, "Category"))
            continue;

        ByamlIter arcsIter;
        categoryIter.tryGetIterByKey(&arcsIter, "Arcs");

        bool isLocalized = false;
        categoryIter.tryGetBoolByKey(&isLocalized, "Localized");

        for (s32 j = 0; j < arcsIter.getSize(); j++) {
            ByamlIter subArcIter;
            arcsIter.tryGetIterByIndex(&subArcIter, j);

            const char* arcName = nullptr;
            if (!subArcIter.tryGetStringByKey(&arcName, "Name"))
                continue;

            sead::FixedSafeString<0x80> localizedName;
            if (isLocalized) {
                if (isEqualString(name, "TrialRating"))
                    makeLocalizedArchivePathByCountryCode(&localizedName, name);
                else
                    makeLocalizedArchivePath(&localizedName, arcName);
                arcName = localizedName.cstr();
            }

            if (isEqualString(arcName, name.cstr()))
                return categoryName;
        }
    }

    return nullptr;
}

bool ResourceSystem::tryGetTableCategoryIter(ByamlIter* iter, const sead::SafeString& name) const {
    if (!mResourceCategoryTable)
        return false;

    for (s32 i = 0; i < mResourceCategoryTable->getSize(); i++) {
        ByamlIter categoryIter;
        if (!mResourceCategoryTable->tryGetIterByIndex(&categoryIter, i))
            continue;

        const char* categoryName = nullptr;
        if (!categoryIter.tryGetStringByKey(&categoryName, "Category"))
            continue;

        if (isEqualString(categoryName, name.cstr()) &&
            mResourceCategoryTable->tryGetIterByIndex(iter, i)) {
            return true;
        }
    }

    return false;
}

bool ResourceSystem::tryGetGraphicsInfoIter(ByamlIter* iter, const sead::SafeString& name) const {
    if (!mResourceCategoryTable)
        return false;

    for (s32 i = 0; i < mResourceCategoryTable->getSize(); i++) {
        ByamlIter categoryIter;
        if (!mResourceCategoryTable->tryGetIterByIndex(&categoryIter, i))
            continue;

        ByamlIter graphicsInfoIter;
        if (!categoryIter.tryGetIterByKey(&graphicsInfoIter, "GraphicsInfo"))
            continue;

        for (s32 j = 0; j < graphicsInfoIter.getSize(); j++) {
            graphicsInfoIter.tryGetIterByIndex(iter, j);

            const char* arcName = nullptr;
            iter->tryGetStringByKey(&arcName, "Arc");
            if (isEqualString(name, arcName))
                return true;
        }
    }

    return false;
}

}  // namespace al
