/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifdef __OHOS__
#include <iomanip>
#else
#include "iomanip_wo_exceptions.h"
#endif

#include <sstream>

#include <base/containers/flat_map.h>
#include <base/util/base64_decode.h>
#include <base/util/base64_encode.h>
#include <core/io/intf_filesystem_api.h>

#include <meta/base/memfile.h>
#include <meta/ext/minimal_object.h>
#include <meta/interface/resource/intf_resource.h>

#include "file_resource_manager.h"
#include "resource_group_handle.h"

META_BEGIN_NAMESPACE()

FileResourceManager::FileResourceManager()
{
    if (auto factory = CORE_NS::GetInstance<CORE_NS::IFileSystemApi>(CORE_NS::UID_FILESYSTEM_API_FACTORY)) {
        fileManager_ = factory->CreateFilemanager();
        fileManager_->RegisterFilesystem("file", factory->CreateStdFileSystem());
    }
}

void FileResourceManager::AddListener(const IResourceListener::Ptr& p)
{
    std::unique_lock lock{listenerMutex_};
    for (auto&& v : listeners_) {
        if (v.lock() == p) {
            return;
        }
    }
    listeners_.push_back(p);
}
void FileResourceManager::RemoveListener(const IResourceListener::Ptr& p)
{
    std::unique_lock lock{listenerMutex_};
    auto it = listeners_.begin();
    for (; it != listeners_.end() && it->lock() != p; ++it) {
    }
    if (it != listeners_.end()) {
        listeners_.erase(it);
    }
}
void FileResourceManager::Notify(const BASE_NS::vector<ResourceIdContext>& ids, IResourceListener::EventType type)
{
    if (!ids.empty()) {
        BASE_NS::vector<IResourceListener::WeakPtr> list;
        {
            std::shared_lock lock{listenerMutex_};
            list = listeners_;
        }
        for (auto&& v : list) {
            if (auto l = v.lock()) {
                l->OnResourceEvent(type, ids);
            }
        }
    }
}
bool FileResourceManager::AddResourceType(CORE_NS::IResourceType::Ptr p)
{
    if (p) {
        std::unique_lock lock{mutex_};
        types_[ResourceTypeKey{p->GetResourceType(), p->GetVersion()}] = p;
    }
    return p != nullptr;
}
bool FileResourceManager::RemoveResourceType(const CORE_NS::ResourceType& type, BASE_NS::string_view version)
{
    std::unique_lock lock{mutex_};
    types_.erase(ResourceTypeKey{type, BASE_NS::string(version)});
    return true;
}
BASE_NS::vector<CORE_NS::IResourceType::Ptr> FileResourceManager::GetResourceTypes() const
{
    std::shared_lock lock{mutex_};
    BASE_NS::vector<CORE_NS::IResourceType::Ptr> res;
    for (auto&& v : types_) {
        res.push_back(v.second);
    }
    return res;
}
CORE_NS::ResourceIdContext FileResourceManager::ReadHeader(
    const std::string& line, const ResourceContextPtr& context, BASE_NS::vector<CORE_NS::IResource::Ptr>& destroy)
{
    std::istringstream in(line);
    std::string name;
    std::string group;
    std::string path;
    std::string type;
    in >> std::quoted(name) >> std::quoted(group) >> std::quoted(path) >> type;
    if (!in || type.size() != 36U) {
        return {};
    }

    auto data = BASE_NS::make_shared<ResourceData>();
    data->id =
        CORE_NS::ResourceId{BASE_NS::string(name.data(), name.size()), BASE_NS::string(group.data(), group.size())};
    data->path = BASE_NS::string(path.data(), path.size());
    data->type = BASE_NS::StringToUid(BASE_NS::string_view(type.data(), type.size()));

    data->options = GetObjectRegistry().Create<CORE_NS::IResourceOptions>(META_NS::ClassId::ObjectResourceOptions);
    if (!data->options) {
        CORE_LOG_E("Invalid state");
        return {};
    }
    std::string opt;
    if (in >> opt) {
        if (auto opts = interface_cast<IObjectResourceOptions>(data->options)) {
            opts->SetOptionData(BASE_NS::Base64Decode(opt.c_str()));
        }
    }
    auto& v = resources_[ResourceGroupContext{data->id.group, context}][data->id.name];
    if (v && v->object) {
        destroy.push_back(v->object);
    }
    v = data;
    return {data->id, context};
}
bool FileResourceManager::ReadHeaders(CORE_NS::IFile& file, const ResourceContextPtr& context,
    BASE_NS::vector<CORE_NS::ResourceIdContext>& result, BASE_NS::vector<CORE_NS::IResource::Ptr>& destroy)
{
    std::string vec;
    vec.resize(file.GetLength());
    file.Read(vec.data(), vec.size());
    std::istringstream ss(vec);
    std::string line;
    while (std::getline(ss, line)) {
        if (!line.empty() && line[0] != '#') {
            auto res = ReadHeader(line, context, destroy);
            if (!res.id.IsValid()) {
                return false;
            }
            result.push_back(std::move(res));
        }
    }
    return true;
}
FileResourceManager::Result FileResourceManager::Import(BASE_NS::string_view url, const ResourceContextPtr& context)
{
    BASE_NS::vector<CORE_NS::IResource::Ptr> destroy;
    BASE_NS::vector<CORE_NS::ResourceIdContext> result;
    {
        std::unique_lock lock{mutex_};
        auto f = fileManager_->OpenFile(url);
        if (!f) {
            CORE_LOG_W("Failed to open resource manager file: %s", BASE_NS::string(url).c_str());
            return Result::FILE_NOT_FOUND;
        }
        if (!ReadHeaders(*f, context, result, destroy)) {
            return Result::INVALID_FILE;
        }
    }
    Notify(result, IResourceListener::EventType::ADDED);
    return Result::OK;
}
BASE_NS::vector<CORE_NS::ResourceInfo> FileResourceManager::GetResourceInfos(
    const BASE_NS::array_view<const CORE_NS::MatchingResourceId>& selection, const ResourceContextPtr& context) const
{
    std::shared_lock lock{mutex_};
    BASE_NS::vector<CORE_NS::ResourceInfo> infos;
    for (auto&& g : resources_) {
        if (IsCorrectContext(g.first, context)) {
            for (auto&& v : g.second) {
                if (IsResourceMatch(selection, v.second->id)) {
                    infos.push_back(*v.second);
                }
            }
        }
    }
    return infos;
}
BASE_NS::vector<BASE_NS::string> FileResourceManager::GetResourceGroups(const ResourceContextPtr& context) const
{
    std::shared_lock lock{mutex_};
    BASE_NS::vector<BASE_NS::string> groups;
    for (auto&& g : resources_) {
        if (IsCorrectContext(g.first, context)) {
            groups.push_back(g.first.group);
        }
    }
    return groups;
}
CORE_NS::ResourceInfo FileResourceManager::GetResourceInfo(const ResourceIdContext& ric) const
{
    std::shared_lock lock{mutex_};
    auto git = resources_.find(ResourceGroupContext{ric});
    if (git != resources_.end()) {
        auto it = git->second.find(ric.id.name);
        return it != git->second.end() ? CORE_NS::ResourceInfo{*it->second} : CORE_NS::ResourceInfo{};
    }
    return {};
}
BASE_NS::shared_ptr<ResourceData> FileResourceManager::FindResource(const CORE_NS::ResourceIdContext& ric)
{
    auto git = resources_.find(ResourceGroupContext{ric});
    if (git == resources_.end()) {
        return nullptr;
    }
    auto it = git->second.find(ric.id.name);
    if (it == git->second.end()) {
        return nullptr;
    }
    return it->second;
}
CORE_NS::IResource::Ptr FileResourceManager::GetResource(const CORE_NS::ResourceIdContext& ric)
{
    {
        std::shared_lock lock{mutex_};
        if (auto res = FindResource(ric)) {
            if (res->object) {
                return res->object;
            }
        }
    }
    return ConstructResource(ric);
}
BASE_NS::vector<CORE_NS::IResource::Ptr> FileResourceManager::GetResources(
    const BASE_NS::array_view<const CORE_NS::MatchingResourceId>& selection, const ResourceContextPtr& context)
{
    BASE_NS::vector<CORE_NS::IResource::Ptr> res;
    BASE_NS::vector<ResourceIdContext> populate;

    auto pushRes = [&](const auto& r) {
        if (IsResourceMatch(selection, r.id)) {
            if (r.object) {
                res.push_back(r.object);
            } else {
                populate.push_back(ResourceIdContext{r.id, context});
            }
        }
    };

    {
        std::shared_lock lock{mutex_};
        for (auto&& g : resources_) {
            if (IsCorrectContext(g.first, context)) {
                for (auto&& v : g.second) {
                    pushRes(*v.second);
                }
            }
        }
    }
    for (auto&& v : populate) {
        if (auto p = ConstructResource(v)) {
            res.push_back(p);
        }
    }
    return res;
}
static void SaveResourceHeader(const ResourceData& r, CORE_NS::IFile& file)
{
    auto opts = interface_cast<IObjectResourceOptions>(r.options);
    if (!opts) {
        return;
    }

    std::ostringstream out;
    out << std::quoted(r.id.name.c_str()) << " " << std::quoted(r.id.group.c_str()) << " "
        << std::quoted(r.path.c_str()) << " " << BASE_NS::to_string(r.type).c_str() << " ";
    out << BASE_NS::Base64Encode(opts->GetOptionData()).c_str();
    out << "\n";
    auto buf = out.str();
    file.Write(buf.data(), buf.size());
}
FileResourceManager::Result FileResourceManager::SaveResourceData(const ResourceData& r)
{
    if (!r.object) {
        return Result::NO_RESOURCE_DATA;
    }
    auto it = types_.find(ResourceTypeKey{r.type, r.version});
    if (it == types_.end()) {
        CORE_LOG_W("Resource type not registered [type=%s]", BASE_NS::to_string(r.type).c_str());
        return Result::NO_RESOURCE_TYPE;
    }
    if (r.path.empty()) {
        return Result::OK;
    }
    MemFile data;
    if (!it->second->SaveResource(
            r.object, CORE_NS::IResourceType::StorageInfo{nullptr, &data, r.id, r.path, GetSelf<IResourceManager>()})) {
        CORE_LOG_W("Failed to save resource: %s", r.id.ToString().c_str());
        return Result::EXPORT_FAILURE;
    }
    if (data.GetLength()) {
        auto f = fileManager_->CreateFile(r.path);
        if (!f) {
            CORE_LOG_W("Failed to open resource file: %s", r.path.c_str());
            return Result::FILE_WRITE_ERROR;
        }
        f->Write(data.RawData(), data.GetLength());
    }
    return Result::OK;
}
bool FileResourceManager::UpdateOptions(ResourceData& r, CORE_NS::ResourceContextPtr context) const
{
    auto it = types_.find(ResourceTypeKey{r.type, r.version});
    if (it == types_.end()) {
        CORE_LOG_W("Resource type not registered [type=%s]", BASE_NS::to_string(r.type).c_str());
        return false;
    }
    if (!it->second->SaveResource(r.object,
            CORE_NS::IResourceType::StorageInfo{
                r.options, nullptr, r.id, r.path, GetSelf<IResourceManager>(), context})) {
        CORE_LOG_W("Failed to save resource options");
        return false;
    }
    return true;
}

FileResourceManager::Result FileResourceManager::Export(BASE_NS::string_view filePath,
    const BASE_NS::array_view<const CORE_NS::MatchingResourceId>& selection, const ResourceContextPtr& context)
{
    std::unique_lock lock{mutex_};
    auto f = fileManager_->CreateFile(filePath);
    if (!f) {
        CORE_LOG_W("Failed to create resource manager file: %s", BASE_NS::string(filePath).c_str());
        return Result::FILE_WRITE_ERROR;
    }
    BASE_NS::flat_map<CORE_NS::ResourceId, BASE_NS::shared_ptr<ResourceData>, ResourceIdLess> imap;
    auto pushRes = [&](auto res) {
        if (IsResourceMatch(selection, res->id)) {
            imap.insert({res->id, res});
        }
    };
    for (auto&& g : resources_) {
        if (IsCorrectContext(g.first, context)) {
            for (auto&& v : g.second) {
                pushRes(v.second);
            }
        }
    }
    BASE_NS::string vstr = "# Lume Resource Index Version 1\n";
    f->Write(vstr.data(), vstr.size());
    for (auto&& v : imap) {
        auto& res = *v.second;
        UpdateOptions(res, context);
        SaveResourceHeader(res, *f);
        SaveResourceData(res);
    }
    return Result::OK;
}

bool FileResourceManager::AddResource(const CORE_NS::IResource::Ptr& resource)
{
    return AddResource(resource, resource->GetResourceId().name);
}
bool FileResourceManager::AddResource(const CORE_NS::IResource::Ptr& resource, BASE_NS::string_view path)
{
    CORE_NS::IResource::Ptr res;
    auto id = resource->GetResourceId();
    {
        std::unique_lock lock{mutex_};
        auto it = types_.find(ResourceTypeKey{resource->GetResourceType(), ""});
        if (it == types_.end()) {
            CORE_LOG_W(
                "Resource type not registered [type=%s]", BASE_NS::to_string(resource->GetResourceType()).c_str());
            return false;
        }
        ResourceData d;
        d.object = resource;
        d.type = resource->GetResourceType();
        d.id = id;
        d.path = path;

        d.options = GetObjectRegistry().Create<CORE_NS::IResourceOptions>(META_NS::ClassId::ObjectResourceOptions);
        if (!d.options) {
            CORE_LOG_E("Invalid state");
            return false;
        }

        if (!it->second->SaveResource(resource,
                CORE_NS::IResourceType::StorageInfo{d.options, nullptr, d.id, d.path, GetSelf<IResourceManager>()})) {
            CORE_LOG_W("Failed to save resource options");
            return false;
        }
        auto& v = resources_[ResourceGroupContext{id.group, resource->GetContext()}][id.name];
        if (v) {
            res = v->object;
        }
        v = BASE_NS::make_shared<ResourceData>(BASE_NS::move(d));
    }
    Notify({ResourceIdContext{id, resource->GetContext()}}, IResourceListener::EventType::ADDED);
    return true;
}
bool FileResourceManager::AddResource(const ResourceIdContext& ric, const CORE_NS::ResourceType& type,
    BASE_NS::string_view path, const CORE_NS::IResourceOptions::Ptr& os)
{
    CORE_NS::IResourceOptions::Ptr options =
        os ? os
           : META_NS::GetObjectRegistry().Create<CORE_NS::IResourceOptions>(META_NS::ClassId::ObjectResourceOptions);
    if (!options) {
        return false;
    }
    CORE_NS::IResource::Ptr res;
    {
        std::unique_lock lock{mutex_};
        auto it = types_.find(ResourceTypeKey{type, ""});
        if (it == types_.end()) {
            CORE_LOG_W("Resource type not registered [type=%s]", BASE_NS::to_string(type).c_str());
            return false;
        }
        ResourceData d;
        d.type = type;
        d.id = ric.id;
        d.path = path;
        d.options = options;
        auto& v = resources_[ResourceGroupContext{ric}][ric.id.name];
        if (v) {
            res = v->object;
        }
        v = BASE_NS::make_shared<ResourceData>(BASE_NS::move(d));
    }
    Notify({ric}, IResourceListener::EventType::ADDED);
    return true;
}
bool FileResourceManager::ReloadResource(const CORE_NS::IResource::Ptr& resource)
{
    return ReapplyOptions(resource, resource->GetContext().lock());
}
bool FileResourceManager::RenameResource(const ResourceIdContext& ric, const ResourceIdContext& newId)
{
    {
        std::unique_lock lock{mutex_};
        auto git = resources_.find(ResourceGroupContext{ric});
        if (git == resources_.end()) {
            return false;
        }
        auto it = git->second.find(ric.id.name);
        if (it == git->second.end()) {
            return false;
        }
        auto& v = resources_[ResourceGroupContext{newId}][newId.id.name];
        if (v) {
            return false;
        }
        v = it->second;
        git->second.erase(it);
    }

    Notify({ric}, IResourceListener::EventType::REMOVED);
    Notify({newId}, IResourceListener::EventType::ADDED);
    return true;
}
bool FileResourceManager::PurgeResource(const ResourceIdContext& ric)
{
    CORE_NS::IResource::Ptr obj;
    std::unique_lock lock{mutex_};
    if (auto res = FindResource(ric)) {
        std::swap(obj, res->object);
        return true;
    }
    CORE_LOG_W("No such resource: %s", ric.id.ToString().c_str());
    return false;
}
bool FileResourceManager::RemoveResource(const ResourceIdContext& ric)
{
    CORE_NS::IResource::Ptr obj;
    bool res = false;
    {
        std::unique_lock lock{mutex_};
        if (auto git = resources_.find(ResourceGroupContext{ric}); git != resources_.end()) {
            if (auto it = git->second.find(ric.id.name); it != git->second.end()) {
                if (it->second) {
                    obj = it->second->object;
                }
                git->second.erase(it);
                res = true;
            }
        }
    }
    if (res) {
        Notify({ric}, IResourceListener::EventType::REMOVED);
    }
    return res;
}
size_t FileResourceManager::PurgeGroup(BASE_NS::string_view group, const ResourceContextPtr& context)
{
    BASE_NS::vector<CORE_NS::IResource::Ptr> objs;
    size_t count = 0;
    std::unique_lock lock{mutex_};
    auto git = resources_.find(ResourceGroupContext{BASE_NS::string(group), context});
    if (git != resources_.end()) {
        for (auto it = git->second.begin(); it != git->second.end(); ++it) {
            if (it->second->object) {
                objs.push_back(it->second->object);
                it->second->object = nullptr;
                ++count;
            }
        }
    }
    return count;
}
bool FileResourceManager::RemoveGroup(BASE_NS::string_view group, const ResourceContextPtr& context)
{
    return RemoveGroup(group, CORE_NS::ResourceContextWeakPtr(context));
}
bool FileResourceManager::RemoveGroup(BASE_NS::string_view group, const CORE_NS::ResourceContextWeakPtr& context)
{
    bool res = false;
    BASE_NS::vector<CORE_NS::IResource::Ptr> objs;
    BASE_NS::vector<ResourceIdContext> result;
    {
        ResourceGroupContext rgc{BASE_NS::string(group), context};
        std::unique_lock lock{mutex_};
        auto it = resources_.find(rgc);
        res = it != resources_.end();
        if (res) {
            for (auto&& g : it->second) {
                objs.push_back(g.second->object);
                result.push_back(ResourceIdContext{g.second->id, context});
            }
            resources_.erase(it);
        }
        ownedGroups_.erase(rgc);
    }
    if (res) {
        Notify(result, IResourceListener::EventType::REMOVED);
    }
    return res;
}
void FileResourceManager::RemoveAllResources()
{
    BASE_NS::vector<CORE_NS::IResource::Ptr> objs;
    {
        std::unique_lock lock{mutex_};
        for (auto&& g : resources_) {
            for (auto&& v : g.second) {
                objs.push_back(v.second->object);
            }
            ownedGroups_.erase(g.first);
        }
        resources_.clear();
    }
}
void FileResourceManager::RemoveAllResources(const ResourceContextPtr& context)
{
    BASE_NS::vector<CORE_NS::IResource::Ptr> objs;
    BASE_NS::vector<ResourceIdContext> result;
    {
        std::unique_lock lock{mutex_};
        BASE_NS::vector<ResourceGroupContext> groups;
        for (auto&& g : resources_) {
            if (IsCorrectContext(g.first, context)) {
                for (auto&& v : g.second) {
                    objs.push_back(v.second->object);
                    result.push_back({v.second->id, context});
                }
                ownedGroups_.erase(g.first);
                groups.push_back(g.first);
            }
        }
        for (auto&& v : groups) {
            resources_.erase(v);
        }
    }
    Notify(result, IResourceListener::EventType::REMOVED);
}

FileResourceManager::Result FileResourceManager::ExportResourcePayload(const CORE_NS::IResource::Ptr& resource)
{
    std::shared_lock lock{mutex_};
    auto res = FindResource(ResourceIdContext{resource->GetResourceId(), resource->GetContext()});
    if (!res) {
        CORE_LOG_W("No such resource: %s", resource->GetResourceId().ToString().c_str());
        return Result::NO_RESOURCE_DATA;
    }
    return SaveResourceData(*res);
}
void FileResourceManager::SetFileManager(CORE_NS::IFileManager::Ptr fileManager)
{
    std::unique_lock lock{mutex_};
    fileManager_ = BASE_NS::move(fileManager);
}
CORE_NS::IFileManager::Ptr FileResourceManager::GetFileManager() const
{
    std::shared_lock lock{mutex_};
    return fileManager_;
}

namespace {
struct ResourceLoadInfo {
    CORE_NS::IResourceType::ConstPtr type;
    BASE_NS::string path;
    CORE_NS::IResourceOptions::Ptr options;
    CORE_NS::IFileManager::Ptr fileManager;
};

CORE_NS::IResource::Ptr LoadResourceWithType(
    const CORE_NS::ResourceIdContext& ric, const ResourceLoadInfo& info, const CORE_NS::IResourceManager::Ptr& self)
{
    CORE_NS::IFile::Ptr f;
    if (!info.path.empty()) {
        f = info.fileManager->OpenFile(info.path);
        if (!f) {
            CORE_LOG_W("Failed to open resource file: %s", info.path.c_str());
            return nullptr;
        }
    }
    CORE_NS::IResource::Ptr resObj = info.type->LoadResource(
        CORE_NS::IResourceType::StorageInfo{info.options, f.get(), ric.id, info.path, self, ric.context.lock()});
    if (resObj) {
        if (auto i = interface_cast<CORE_NS::ISetResourceId>(resObj)) {
            auto rc = ric;
            rc.context = ResolveContext(ric.context.lock());
            i->SetResourceId(rc);
        }
    } else {
        CORE_LOG_W("Failed to load resource: %s", ric.id.ToString().c_str());
    }
    return resObj;
}
}  // namespace

CORE_NS::IResource::Ptr FileResourceManager::ConstructResource(const CORE_NS::ResourceIdContext& ric)
{
    BASE_NS::shared_ptr<ResourceData> res;
    ResourceLoadInfo info;
    {
        std::shared_lock lock{mutex_};
        res = FindResource(ric);
        if (res) {
            auto tit = types_.find(ResourceTypeKey{res->type, res->version});
            if (tit != types_.end()) {
                info.type = tit->second;
                info.path = res->path;
                info.options = res->options;
                info.fileManager = fileManager_;
            }
        }
    }

    CORE_NS::IResource::Ptr resObj;
    if (res && info.type) {
        resObj = LoadResourceWithType(ric, info, GetSelf<IResourceManager>());
    }

    if (res && resObj) {
        std::unique_lock lock{mutex_};
        if (res->object) {
            resObj = res->object;
        } else {
            res->object = resObj;
        }
    }
    return resObj;
}

IResourceGroupHandle::Ptr FileResourceManager::GetGroupHandle(
    BASE_NS::string_view group, const ResourceContextPtr& context)
{
    IResourceGroupHandle::Ptr res;
    std::unique_lock lock{mutex_};
    ResourceGroupContext gc{BASE_NS::string(group), context};
    auto it = ownedGroups_.find(gc);
    if (it != ownedGroups_.end()) {
        res = it->second.lock();
    }
    if (!res) {
        res = BASE_NS::make_shared<ResourceGroupHandle>(
            GetSelf<CORE_NS::IResourceManager>(), BASE_NS::string(group), context);
        ownedGroups_[gc] = res;
    }
    return res;
}

uint32_t FileResourceManager::GetAliveCount(
    const BASE_NS::array_view<const CORE_NS::MatchingResourceId>& selection, const ResourceContextPtr& context) const
{
    return FindAliveResources(selection, context).size();
}

BASE_NS::vector<CORE_NS::IResource::Ptr> FileResourceManager::FindAliveResources(
    const BASE_NS::array_view<const CORE_NS::MatchingResourceId>& selection, const ResourceContextPtr& context) const
{
    BASE_NS::vector<CORE_NS::IResource::Ptr> res;
    auto push = [&](const auto& r) {
        if (IsResourceMatch(selection, r.id)) {
            if (r.object) {
                res.push_back(r.object);
            }
        }
    };

    std::shared_lock lock{mutex_};
    for (auto&& g : resources_) {
        if (IsCorrectContext(g.first, context)) {
            for (auto&& v : g.second) {
                push(*v.second);
            }
        }
    }
    return res;
}

size_t FileResourceManager::GetResourceCount() const
{
    size_t res{};
    std::shared_lock lock{mutex_};
    for (auto&& g : resources_) {
        res += g.second.size();
    }
    return res;
}

META_REGISTER_CLASS(
    DependencyCollector, "b4d26357-6a82-4b0d-9f33-4b6caea3c05c", META_NS::ObjectCategoryBits::NO_CATEGORY)

class DependencyCollector : public IntroduceInterfaces<MinimalObject, ICollectResources, IResourceContext> {
    META_IMPLEMENT_OBJECT_TYPE_INTERFACE(ClassId::DependencyCollector)
public:
    DependencyCollector(ResourceContextPtr p) : context(BASE_NS::move(p))
    {}
    void AddResource(CORE_NS::ResourceIdContext mid) override
    {
        resources.push_back(BASE_NS::move(mid));
    }

    CORE_NS::ResourceContextPtr GetContext() const override
    {
        return context;
    }

    BASE_NS::vector<CORE_NS::ResourceIdContext> resources;
    ResourceContextPtr context;
};

void FileResourceManager::UpdateOptionsData(
    const BASE_NS::unordered_map<BASE_NS::string, BASE_NS::shared_ptr<ResourceData>>& data,
    const BASE_NS::array_view<const CORE_NS::MatchingResourceId>& selection, const IObject::Ptr& depsContext)
{
    for (auto&& v : data) {
        if (IsResourceMatch(selection, v.second->id)) {
            UpdateOptions(*v.second, depsContext);
        }
    }
}

BASE_NS::vector<CORE_NS::ResourceIdContext> FileResourceManager::UpdateOptionsData(
    const BASE_NS::array_view<const CORE_NS::MatchingResourceId>& selection, const ResourceContextPtr& context)
{
    DependencyCollector deps{context};
    IObject::Ptr depsContext(&deps, [](auto) {});
    std::unique_lock lock{mutex_};
    for (auto&& v : selection) {
        for (auto&& g : resources_) {
            if (IsCorrectContext(g.first, context)) {
                UpdateOptionsData(g.second, selection, depsContext);
            }
        }
    }
    return deps.resources;
}
BASE_NS::vector<CORE_NS::ResourceIdContext> FileResourceManager::UpdateOptionsData(
    const BASE_NS::array_view<const CORE_NS::ResourceIdContext>& res)
{
    BASE_NS::vector<CORE_NS::ResourceIdContext> list;
    std::unique_lock lock{mutex_};
    for (auto&& v : res) {
        if (auto r = FindResource(v)) {
            DependencyCollector deps{v.context.lock()};
            IObject::Ptr depsContext(&deps, [](auto) {});
            UpdateOptions(*r, depsContext);
            list.insert(list.end(), deps.resources.begin(), deps.resources.end());
        }
    }
    return list;
}
bool FileResourceManager::ReapplyOptions(
    const CORE_NS::IResource::Ptr& resource, const CORE_NS::ResourceContextPtr& context)
{
    CORE_NS::IFile::Ptr f;
    CORE_NS::IResourceOptions::Ptr options;
    BASE_NS::string path;
    CORE_NS::IResourceType::Ptr type;
    auto id = resource->GetResourceId();
    {
        std::unique_lock lock{mutex_};
        auto res = FindResource({id, resource->GetContext()});
        if (!res) {
            CORE_LOG_W("No such resource: %s", id.ToString().c_str());
            return false;
        }
        auto it = types_.find(ResourceTypeKey{res->type, res->version});
        if (it == types_.end()) {
            CORE_LOG_W(
                "Resource type not registered [type=%s]", BASE_NS::to_string(resource->GetResourceType()).c_str());
            return false;
        }
        type = it->second;
        path = res->path;
        if (!path.empty()) {
            f = fileManager_->OpenFile(path);
            if (!f) {
                CORE_LOG_W("Failed to open resource file: %s", path.c_str());
                return false;
            }
        }
        options = res->options;
    }
    CORE_NS::IResourceType::StorageInfo info{options, f.get(), id, path, GetSelf<IResourceManager>(), context};
    return type->ReloadResource(info, resource);
}

BASE_NS::vector<ResourceData> FileResourceManager::GetResources(
    const BASE_NS::array_view<const CORE_NS::MatchingResourceId>& selection, const ResourceContextPtr& context) const
{
    BASE_NS::vector<ResourceData> res;
    std::shared_lock lock{mutex_};

    auto func = [&](auto& r) {
        if (IsResourceMatch(selection, r.id)) {
            res.push_back(r);
        }
    };
    for (auto&& g : resources_) {
        if (IsCorrectContext(g.first, context)) {
            for (auto&& v : g.second) {
                func(*v.second);
            }
        }
    }
    return res;
}

ResourceData FileResourceManager::GetResource(const CORE_NS::ResourceIdContext& ric) const
{
    std::shared_lock lock{mutex_};
    auto d = const_cast<FileResourceManager*>(this)->FindResource(ric);
    return d ? *d : ResourceData{};
}

bool FileResourceManager::AddResource(ResourceData data, const CORE_NS::ResourceContextPtr& context)
{
    ResourceIdContext id{data.id, context};
    CORE_NS::IResource::Ptr obj;
    {
        std::unique_lock lock{mutex_};
        auto it = types_.find(ResourceTypeKey{data.type, data.version});
        if (it == types_.end()) {
            CORE_LOG_W("Resource type not registered [type=%s, version=%s]",
                BASE_NS::to_string(data.type).c_str(),
                data.version.c_str());
            return false;
        } else {
            auto& v = resources_[ResourceGroupContext{data.id.group, context}][data.id.name];
            if (v && v->object != data.object) {
                obj = v->object;
            }
            v = BASE_NS::make_shared<ResourceData>(BASE_NS::move(data));
        }
    }
    Notify({id}, IResourceListener::EventType::ADDED);
    return true;
}

void FileResourceManager::AddResources(
    const BASE_NS::array_view<const ResourceData> list, const ResourceContextPtr& context)
{
    BASE_NS::vector<ResourceIdContext> ids;
    BASE_NS::vector<CORE_NS::IResource::Ptr> objs;
    {
        std::unique_lock lock{mutex_};
        for (auto&& data : list) {
            auto it = types_.find(ResourceTypeKey{data.type, data.version});
            if (it == types_.end()) {
                CORE_LOG_W("Resource type not registered [type=%s]", BASE_NS::to_string(data.type).c_str());
            } else {
                auto& v = resources_[ResourceGroupContext{data.id.group, context}][data.id.name];
                if (v && v->object != data.object) {
                    objs.push_back(v->object);
                }
                ids.push_back(ResourceIdContext{data.id, context});
                v = BASE_NS::make_shared<ResourceData>(data);
            }
        }
    }
    Notify(ids, IResourceListener::EventType::ADDED);
}

bool FileResourceManager::SetResourceName(const ResourceIdContext& id, const BASE_NS::string& name)
{
    std::unique_lock lock{mutex_};
    auto res = FindResource(id);
    if (res) {
        res->name = name;
    }
    return res != nullptr;
}

CORE_NS::IResource::Ptr FileResourceManager::GetResourceFromGroup(
    const BASE_NS::unordered_map<BASE_NS::string, BASE_NS::shared_ptr<ResourceData>>& resources,
    BASE_NS::string_view name, const ResourceContextPtr& context)
{
    for (auto&& res : resources) {
        if (res.second->name == name) {
            if (res.second->object) {
                return res.second->object;
            }
            return ConstructResource({res.second->id, context});
        }
    }
    return nullptr;
}

CORE_NS::IResource::Ptr FileResourceManager::GetResourceByName(
    BASE_NS::string_view name, const ResourceContextPtr& context)
{
    std::unique_lock lock{mutex_};
    for (auto&& g : resources_) {
        if (IsCorrectContext(g.first, context)) {
            if (auto r = GetResourceFromGroup(g.second, name, context)) {
                return r;
            }
        }
    }
    return nullptr;
}

META_END_NAMESPACE()