/*
 * 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.
 */

#include "render_context.h"

#include <base/containers/fixed_string.h>
#include <base/containers/vector.h>
#include <core/intf_engine.h>
#include <core/io/intf_file_manager.h>
#include <core/plugin/intf_class_register.h>

#if (RENDER_PERF_ENABLED == 1)
#include <core/perf/intf_performance_data_manager.h>
#endif

#include "datastore/render_data_store_default_acceleration_structure_staging.h"
#include "datastore/render_data_store_default_gpu_resource_data_copy.h"
#include "datastore/render_data_store_default_staging.h"
#include "datastore/render_data_store_manager.h"
#include "datastore/render_data_store_pod.h"
#include "datastore/render_data_store_post_process.h"
#include "datastore/render_data_store_shader_passes.h"
#include "default_engine_constants.h"
#include "device/device.h"
#include "device/shader_manager.h"
#include "loader/render_data_loader.h"
#include "node/core_render_node_factory.h"
#include "nodecontext/render_node_graph_manager.h"
#include "nodecontext/render_node_graph_node_store.h"
#include "nodecontext/render_node_manager.h"
#include "nodecontext/render_node_post_process_util.h"
#include "renderer.h"
#include "util/render_util.h"

#if RENDER_HAS_VULKAN_BACKEND
#include "vulkan/device_vk.h"
#endif

#if (RENDER_HAS_GL_BACKEND) || (RENDER_HAS_GLES_BACKEND)
#include "gles/device_gles.h"
#include "gles/swapchain_gles.h"
#endif

#include <algorithm>

using namespace BASE_NS;
using namespace CORE_NS;

RENDER_BEGIN_NAMESPACE()
namespace {
struct RegisterPathStrings {
    string_view protocol;
    string_view uri;
};
static constexpr RegisterPathStrings RENDER_DATA_PATHS[] = {
    { "rendershaders", "rofsRndr://shaders/" },
    { "rendershaderstates", "rofsRndr://shaderstates/" },
    { "rendervertexinputdeclarations", "rofsRndr://vertexinputdeclarations/" },
    { "renderpipelinelayouts", "rofsRndr://pipelinelayouts/" },
    { "renderrenderdataconfigurations", "rofsRndr://renderdataconfigurations/" },
    { "renderrendernodegraphs", "rofsRndr://rendernodegraphs/" },
};

#if (RENDER_EMBEDDED_ASSETS_ENABLED == 1)
// Core Rofs Data.
extern "C" const uint64_t SIZEOFDATAFORRENDER;
extern "C" const void* const BINARYDATAFORRENDER[];
#endif

// This is defined in the CMake generated version.cpp
void LogRenderBuildInfo()
{
#define RENDER_TO_STRING_INTERNAL(x) #x
#define RENDER_TO_STRING(x) RENDER_TO_STRING_INTERNAL(x)

    PLUGIN_LOG_I("RENDER_VALIDATION_ENABLED=" RENDER_TO_STRING(RENDER_VALIDATION_ENABLED));
    PLUGIN_LOG_I("RENDER_DEV_ENABLED=" RENDER_TO_STRING(RENDER_DEV_ENABLED));
}

template<class RenderDataStoreType>
RenderDataStoreTypeInfo FillRenderDataStoreTypeInfo()
{
    return {
        { RenderDataStoreTypeInfo::UID },
        RenderDataStoreType::UID,
        RenderDataStoreType::TYPE_NAME,
        RenderDataStoreType::Create,
        RenderDataStoreType::Destroy,
    };
}

void RegisterCoreRenderDataStores(RenderDataStoreManager& renderDataStoreManager)
{
    renderDataStoreManager.AddRenderDataStoreFactory(FillRenderDataStoreTypeInfo<RenderDataStorePod>());
    renderDataStoreManager.AddRenderDataStoreFactory(
        FillRenderDataStoreTypeInfo<RenderDataStoreDefaultAccelerationStructureStaging>());
    renderDataStoreManager.AddRenderDataStoreFactory(FillRenderDataStoreTypeInfo<RenderDataStoreDefaultStaging>());
    renderDataStoreManager.AddRenderDataStoreFactory(
        FillRenderDataStoreTypeInfo<RenderDataStoreDefaultGpuResourceDataCopy>());
    renderDataStoreManager.AddRenderDataStoreFactory(FillRenderDataStoreTypeInfo<RenderDataStoreShaderPasses>());
    renderDataStoreManager.AddRenderDataStoreFactory(FillRenderDataStoreTypeInfo<RenderDataStorePostProcess>());
}

template<typename DataStoreType>
IRenderDataStore* CreateDataStore(IRenderDataStoreManager& renderDataStoreManager, const string_view name)
{
    IRenderDataStore* renderDataStore = renderDataStoreManager.Create(DataStoreType::UID, name.data());
    PLUGIN_ASSERT(renderDataStore);
    return renderDataStore;
}

void CreateDefaultRenderDataStores(IRenderDataStoreManager& renderDataStoreManager, RenderDataLoader& renderDataLoader)
{
    // add pod store
    {
        auto renderDataStorePod =
            CreateDataStore<RenderDataStorePod>(renderDataStoreManager, RenderDataStorePod::TYPE_NAME);
        if (renderDataStorePod) {
            IRenderDataStorePod* renderDataStorePodTyped = static_cast<IRenderDataStorePod*>(renderDataStorePod);

            NodeGraphBackBufferConfiguration backBufferConfig {};
            const auto len =
                DefaultEngineGpuResourceConstants::CORE_DEFAULT_BACKBUFFER.copy(backBufferConfig.backBufferName,
                    NodeGraphBackBufferConfiguration::CORE_MAX_BACK_BUFFER_NAME_LENGTH - 1);
            backBufferConfig.backBufferName[len] = '\0';
            renderDataStorePodTyped->CreatePod(
                "NodeGraphConfiguration", "NodeGraphBackBufferConfiguration", arrayviewU8(backBufferConfig));

            // load and store configurations
            renderDataLoader.Load("render", *renderDataStorePodTyped);
        }
    }

    CreateDataStore<RenderDataStoreDefaultAccelerationStructureStaging>(
        renderDataStoreManager, RenderDataStoreDefaultAccelerationStructureStaging::TYPE_NAME);
    CreateDataStore<RenderDataStoreDefaultStaging>(renderDataStoreManager, RenderDataStoreDefaultStaging::TYPE_NAME);
    CreateDataStore<RenderDataStoreDefaultGpuResourceDataCopy>(
        renderDataStoreManager, RenderDataStoreDefaultGpuResourceDataCopy::TYPE_NAME);
    CreateDataStore<RenderDataStoreShaderPasses>(renderDataStoreManager, RenderDataStoreShaderPasses::TYPE_NAME);
    CreateDataStore<RenderDataStorePostProcess>(renderDataStoreManager, RenderDataStorePostProcess::TYPE_NAME);
}

void CreateDefaultBuffers(IGpuResourceManager& gpuResourceMgr, vector<RenderHandleReference>& defaultGpuResources)
{
    defaultGpuResources.push_back(gpuResourceMgr.Create(DefaultEngineGpuResourceConstants::CORE_DEFAULT_GPU_BUFFER,
        GpuBufferDesc { CORE_BUFFER_USAGE_TRANSFER_SRC_BIT | CORE_BUFFER_USAGE_TRANSFER_DST_BIT |
                            CORE_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | CORE_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT |
                            CORE_BUFFER_USAGE_UNIFORM_BUFFER_BIT | CORE_BUFFER_USAGE_STORAGE_BUFFER_BIT |
                            CORE_BUFFER_USAGE_INDEX_BUFFER_BIT | CORE_BUFFER_USAGE_VERTEX_BUFFER_BIT |
                            CORE_BUFFER_USAGE_INDIRECT_BUFFER_BIT | CORE_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT |
                            CORE_BUFFER_USAGE_ACCELERATION_STRUCTURE_STORAGE_BIT,
            (CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT), 0u, 1024u }));
}

void CreateDefaultTextures(IGpuResourceManager& gpuResourceMgr, vector<RenderHandleReference>& defaultGpuResources)
{
    GpuImageDesc desc { ImageType::CORE_IMAGE_TYPE_2D, ImageViewType::CORE_IMAGE_VIEW_TYPE_2D,
        Format::BASE_FORMAT_R8G8B8A8_UNORM, ImageTiling::CORE_IMAGE_TILING_OPTIMAL,
        ImageUsageFlagBits::CORE_IMAGE_USAGE_SAMPLED_BIT | ImageUsageFlagBits::CORE_IMAGE_USAGE_TRANSFER_DST_BIT,
        MemoryPropertyFlagBits::CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
        0, // ImageCreateFlags
        0, // EngineImageCreationFlags
        2, 2, 1, 1, 1, SampleCountFlagBits::CORE_SAMPLE_COUNT_1_BIT, {} };

    constexpr uint32_t sizeOfUint32 = sizeof(uint32_t);
    constexpr const uint32_t rgbData[4u] = { 0x0, 0x0, 0x0, 0x0 };
    const auto rgbDataView = array_view(reinterpret_cast<const uint8_t*>(rgbData), sizeOfUint32 * countof(rgbData));
    defaultGpuResources.push_back(
        gpuResourceMgr.Create(DefaultEngineGpuResourceConstants::CORE_DEFAULT_GPU_IMAGE, desc, rgbDataView));
    constexpr const uint32_t rgbDataWhite[4u] = { 0xFFFFffff, 0xFFFFffff, 0xFFFFffff, 0xFFFFffff };
    const auto rgbDataViewWhite =
        array_view(reinterpret_cast<const uint8_t*>(rgbDataWhite), sizeOfUint32 * countof(rgbDataWhite));
    defaultGpuResources.push_back(
        gpuResourceMgr.Create(DefaultEngineGpuResourceConstants::CORE_DEFAULT_GPU_IMAGE_WHITE, desc, rgbDataViewWhite));
}

void CreateDefaultTargets(IGpuResourceManager& gpuResourceMgr, vector<RenderHandleReference>& defaultGpuResources)
{
    {
        // hard-coded default backbuffer
        // all presentations and swapchain semaphore syncs are done automatically for this client handle
        GpuImageDesc desc {
            ImageType::CORE_IMAGE_TYPE_2D,
            ImageViewType::CORE_IMAGE_VIEW_TYPE_2D,
            Format::BASE_FORMAT_R8G8B8A8_UNORM,
            ImageTiling::CORE_IMAGE_TILING_OPTIMAL,
            ImageUsageFlagBits::CORE_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
            MemoryPropertyFlagBits::CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
            0, // ImageCreateFlags
            EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_RESET_STATE_ON_FRAME_BORDERS |
                EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_DYNAMIC_BARRIERS, // EngineImageCreationFlags
            2,
            2,
            1,
            1,
            1,
            SampleCountFlagBits::CORE_SAMPLE_COUNT_1_BIT,
            {},
        };
        GpuResourceManager& gpuResourceMgrImpl = (GpuResourceManager&)gpuResourceMgr;
        // create as a swapchain image to get correct handle flags for fast check-up for additional processing
        defaultGpuResources.push_back(gpuResourceMgrImpl.CreateSwapchainImage(
            {}, DefaultEngineGpuResourceConstants::CORE_DEFAULT_BACKBUFFER, desc));
    }
    {
        GpuImageDesc desc {
            ImageType::CORE_IMAGE_TYPE_2D,
            ImageViewType::CORE_IMAGE_VIEW_TYPE_2D,
            Format::BASE_FORMAT_D16_UNORM,
            ImageTiling::CORE_IMAGE_TILING_OPTIMAL,
            ImageUsageFlagBits::CORE_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT |
                ImageUsageFlagBits::CORE_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT,
            MemoryPropertyFlagBits::CORE_MEMORY_PROPERTY_DEVICE_LOCAL_BIT |
                MemoryPropertyFlagBits::CORE_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT,
            0,                                                                        // ImageCreateFlags
            EngineImageCreationFlagBits::CORE_ENGINE_IMAGE_CREATION_DYNAMIC_BARRIERS, // EngineImageCreationFlags
            2,
            2,
            1,
            1,
            1,
            SampleCountFlagBits::CORE_SAMPLE_COUNT_1_BIT,
            {},
        };
        defaultGpuResources.push_back(
            gpuResourceMgr.Create(DefaultEngineGpuResourceConstants::CORE_DEFAULT_BACKBUFFER_DEPTH, desc));
    }
}

void CreateDefaultSamplers(IGpuResourceManager& gpuResourceMgr, vector<RenderHandleReference>& defaultGpuResources)
{
    defaultGpuResources.push_back(
        gpuResourceMgr.Create(DefaultEngineGpuResourceConstants::CORE_DEFAULT_SAMPLER_NEAREST_REPEAT,
            GpuSamplerDesc {
                Filter::CORE_FILTER_NEAREST,                          // magFilter
                Filter::CORE_FILTER_NEAREST,                          // minFilter
                Filter::CORE_FILTER_NEAREST,                          // mipMapMode
                SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_REPEAT, // addressModeU
                SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_REPEAT, // addressModeV
                SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_REPEAT, // addressModeW
            }));
    defaultGpuResources.push_back(
        gpuResourceMgr.Create(DefaultEngineGpuResourceConstants::CORE_DEFAULT_SAMPLER_NEAREST_CLAMP,
            GpuSamplerDesc {
                Filter::CORE_FILTER_NEAREST,                                 // magFilter
                Filter::CORE_FILTER_NEAREST,                                 // minFilter
                Filter::CORE_FILTER_NEAREST,                                 // mipMapMode
                SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // addressModeU
                SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // addressModeV
                SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // addressModeW
            }));

    defaultGpuResources.push_back(
        gpuResourceMgr.Create(DefaultEngineGpuResourceConstants::CORE_DEFAULT_SAMPLER_LINEAR_REPEAT,
            GpuSamplerDesc {
                Filter::CORE_FILTER_LINEAR,                           // magFilter
                Filter::CORE_FILTER_LINEAR,                           // minFilter
                Filter::CORE_FILTER_LINEAR,                           // mipMapMode
                SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_REPEAT, // addressModeU
                SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_REPEAT, // addressModeV
                SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_REPEAT, // addressModeW
            }));
    defaultGpuResources.push_back(
        gpuResourceMgr.Create(DefaultEngineGpuResourceConstants::CORE_DEFAULT_SAMPLER_LINEAR_CLAMP,
            GpuSamplerDesc {
                Filter::CORE_FILTER_LINEAR,                                  // magFilter
                Filter::CORE_FILTER_LINEAR,                                  // minFilter
                Filter::CORE_FILTER_LINEAR,                                  // mipMapMode
                SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // addressModeU
                SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // addressModeV
                SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // addressModeW
            }));

    GpuSamplerDesc linearMipmapRepeat {
        Filter::CORE_FILTER_LINEAR,                           // magFilter
        Filter::CORE_FILTER_LINEAR,                           // minFilter
        Filter::CORE_FILTER_LINEAR,                           // mipMapMode
        SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_REPEAT, // addressModeU
        SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_REPEAT, // addressModeV
        SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_REPEAT, // addressModeW
    };
    linearMipmapRepeat.minLod = 0.0f;
    linearMipmapRepeat.maxLod = 32.0f;
    defaultGpuResources.push_back(gpuResourceMgr.Create(
        DefaultEngineGpuResourceConstants::CORE_DEFAULT_SAMPLER_LINEAR_MIPMAP_REPEAT, linearMipmapRepeat));
    GpuSamplerDesc linearMipmapClamp {
        Filter::CORE_FILTER_LINEAR,                                  // magFilter
        Filter::CORE_FILTER_LINEAR,                                  // minFilter
        Filter::CORE_FILTER_LINEAR,                                  // mipMapMode
        SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // addressModeU
        SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // addressModeV
        SamplerAddressMode::CORE_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, // addressModeW
    };
    linearMipmapClamp.minLod = 0.0f;
    linearMipmapClamp.maxLod = 32.0f;
    defaultGpuResources.push_back(gpuResourceMgr.Create(
        DefaultEngineGpuResourceConstants::CORE_DEFAULT_SAMPLER_LINEAR_MIPMAP_CLAMP, linearMipmapClamp));
}

string_view GetPipelineCacheUri(DeviceBackendType backendType)
{
    switch (backendType) {
        case DeviceBackendType::VULKAN:
            return "cache://deviceVkCache.bin";
        case DeviceBackendType::OPENGLES:
            return "cache://deviceGLESCache.bin";
        case DeviceBackendType::OPENGL:
            return "cache://deviceGLCache.bin";
        default:
            break;
    }
    return "";
}
} // namespace

IRenderContext* RenderPluginState::CreateInstance(IEngine& engine)
{
    if (!context_) {
        context_ = IRenderContext::Ptr { new RenderContext(*this, engine) };
    }
    return context_.get();
}

IRenderContext* RenderPluginState::GetInstance()
{
    return context_.get();
}

void RenderPluginState::Destroy()
{
    context_.reset();
}

RenderContext::RenderContext(RenderPluginState& pluginState, IEngine& engine)
    : pluginState_(pluginState), engine_(engine), fileManager_(&engine.GetFileManager())
{
    for (const auto& ref : interfaceInfos_) {
        RegisterInterfaceType(ref);
    }
    LogRenderBuildInfo();
    RegisterDefaultPaths();
}

RenderContext::~RenderContext()
{
    GetPluginRegister().RemoveListener(*this);

    if (device_) {
        device_->WaitForIdle();
    }

    for (auto& info : plugins_) {
        if (info.second && info.second->destroyPlugin) {
            info.second->destroyPlugin(info.first);
        }
    }

    // NOTE: device needs to be active for render resources (e.g. with GLES)
    if (device_) {
        device_->Activate();

        if (device_->GetDeviceConfiguration().configurationFlags & CORE_DEVICE_CONFIGURATION_PIPELINE_CACHE_BIT) {
            vector<uint8_t> pipelineCache = device_->GetPipelineCache();
            if (auto file = fileManager_->CreateFile(GetPipelineCacheUri(device_->GetBackendType())); file) {
                file->Write(pipelineCache.data(), pipelineCache.size());
            }
        }
    }
    defaultGpuResources_.clear();
    renderer_.reset();
    renderNodeGraphMgr_.reset();
    renderDataStoreMgr_.reset();
    renderUtil_.reset(); // GLES semaphore/fence destruction device needs to be active
    if (device_) {
        device_->Deactivate();
    }
}

RenderResultCode RenderContext::Init(const RenderCreateInfo& createInfo)
{
    PLUGIN_LOG_D("Render init.");

    createInfo_ = createInfo;
    device_ = CreateDevice(createInfo_.deviceCreateInfo);
    if (!device_) {
        PLUGIN_LOG_E("device not created successfully, invalid render interface");
        return RenderResultCode::RENDER_ERROR;
    } else {
        device_->Activate();

        // Initialize the pipeline/ program cache.
        if (device_->GetDeviceConfiguration().configurationFlags & CORE_DEVICE_CONFIGURATION_PIPELINE_CACHE_BIT) {
            vector<uint8_t> pipelineCache;
            if (auto file = fileManager_->OpenFile(GetPipelineCacheUri(device_->GetBackendType())); file) {
                pipelineCache.resize(static_cast<size_t>(file->GetLength()));
                file->Read(pipelineCache.data(), pipelineCache.size());
            }
            device_->InitializePipelineCache(pipelineCache);
        }

        // set engine file manager with registered paths
        ShaderManager& shaderMgr = (ShaderManager&)device_->GetShaderManager();
        shaderMgr.SetFileManager(engine_.GetFileManager());

        {
            IShaderManager::ShaderFilePathDesc desc;
            desc.shaderPath = "rendershaders://";
            desc.pipelineLayoutPath = "renderpipelinelayouts://";
            // NOTE: does not have states and vids
            shaderMgr.LoadShaderFiles(desc);
        }
        // make sure shaders found above have been created
        shaderMgr.HandlePendingAllocations();

        renderDataStoreMgr_ = make_unique<RenderDataStoreManager>(*this);
        RegisterCoreRenderDataStores(*renderDataStoreMgr_);

        // Add render data stores from plugins
        for (auto info : CORE_NS::GetPluginRegister().GetTypeInfos(RenderDataStoreTypeInfo::UID)) {
            renderDataStoreMgr_->AddRenderDataStoreFactory(*static_cast<const RenderDataStoreTypeInfo*>(info));
        }

        auto loader = RenderDataLoader(*fileManager_);
        CreateDefaultRenderDataStores(*renderDataStoreMgr_, loader);

        renderNodeGraphMgr_ = make_unique<RenderNodeGraphManager>(*device_, *fileManager_);

        auto& renderNodeMgr = renderNodeGraphMgr_->GetRenderNodeManager();
        RegisterCoreRenderNodes(renderNodeMgr);
        // Add render nodes from plugins
        for (auto info : CORE_NS::GetPluginRegister().GetTypeInfos(RenderNodeTypeInfo::UID)) {
            renderNodeMgr.AddRenderNodeFactory(*static_cast<const RenderNodeTypeInfo*>(info));
        }

        renderUtil_ = make_unique<RenderUtil>(*this);

        renderer_ = make_unique<Renderer>(*this);

        IGpuResourceManager& gpuResourceMgr = device_->GetGpuResourceManager();
        CreateDefaultBuffers(gpuResourceMgr, defaultGpuResources_);
        CreateDefaultTextures(gpuResourceMgr, defaultGpuResources_);
        CreateDefaultTargets(gpuResourceMgr, defaultGpuResources_);
        CreateDefaultSamplers(gpuResourceMgr, defaultGpuResources_);

        device_->Deactivate();

        GetPluginRegister().AddListener(*this);

        for (auto info : CORE_NS::GetPluginRegister().GetTypeInfos(IRenderPlugin::UID)) {
            if (auto renderPlugin = static_cast<const IRenderPlugin*>(info);
                renderPlugin && renderPlugin->createPlugin) {
                auto token = renderPlugin->createPlugin(*this);
                plugins_.push_back({ token, renderPlugin });
            }
        }

        return RenderResultCode::RENDER_SUCCESS;
    }
}

IDevice& RenderContext::GetDevice() const
{
    if (!device_) {
        PLUGIN_LOG_E("Render Init not called or result was not success");
    }
    return *device_;
}

IRenderer& RenderContext::GetRenderer() const
{
    if (!renderer_) {
        PLUGIN_LOG_E("Render Init not called or result was not success");
    }
    return *renderer_;
}

IRenderDataStoreManager& RenderContext::GetRenderDataStoreManager() const
{
    if (!renderDataStoreMgr_) {
        PLUGIN_LOG_E("Render Init not called or result was not success");
    }
    return *renderDataStoreMgr_;
}

IRenderNodeGraphManager& RenderContext::GetRenderNodeGraphManager() const
{
    if (!renderNodeGraphMgr_) {
        PLUGIN_LOG_E("Render Init not called or result was not success");
    }
    return *renderNodeGraphMgr_;
}

IRenderUtil& RenderContext::GetRenderUtil() const
{
    if (!renderUtil_) {
        PLUGIN_LOG_E("Render Init not called or result was not success");
    }
    return *renderUtil_;
}

void RenderContext::RegisterDefaultPaths()
{
    // Already handeled during plugin registration. If own filemanager instance is used then these are needed.
#if (RENDER_EMBEDDED_ASSETS_ENABLED == 1)
    // Create engine:// protocol that points to embedded engine asset files.
    PLUGIN_LOG_D("Registered core asset path: 'rofsRndr://render/'");
    fileManager_->RegisterPath("render", "rofsRndr://render/", false);
#endif
    for (uint32_t idx = 0; idx < countof(RENDER_DATA_PATHS); ++idx) {
        fileManager_->RegisterPath(RENDER_DATA_PATHS[idx].protocol, RENDER_DATA_PATHS[idx].uri, false);
    }
}

unique_ptr<Device> RenderContext::CreateDevice(const DeviceCreateInfo& createInfo)
{
    switch (createInfo.backendType) {
        case DeviceBackendType::OPENGL:
#if (RENDER_HAS_GL_BACKEND)
            return CreateDeviceGL(*this, createInfo);
#else
            return nullptr;
#endif
        case DeviceBackendType::OPENGLES:
#if (RENDER_HAS_GLES_BACKEND)
            return CreateDeviceGLES(*this, createInfo);
#else
            return nullptr;
#endif
        case DeviceBackendType::VULKAN:
#if (RENDER_HAS_VULKAN_BACKEND)
            return CreateDeviceVk(*this, createInfo);
#else
            return nullptr;
#endif
        default:
            break;
    }
    return nullptr;
}

IEngine& RenderContext::GetEngine() const
{
    return engine_;
}

string_view RenderContext::GetVersion()
{
    return {};
}

RenderCreateInfo RenderContext::GetCreateInfo() const
{
    return createInfo_;
}

const IInterface* RenderContext::GetInterface(const Uid& uid) const
{
    if ((uid == IRenderContext::UID) || (uid == IClassFactory::UID) || (uid == IInterface::UID)) {
        return static_cast<const IRenderContext*>(this);
    }
    if (uid == IClassRegister::UID) {
        return static_cast<const IClassRegister*>(this);
    }
    return nullptr;
}

IInterface* RenderContext::GetInterface(const Uid& uid)
{
    if ((uid == IRenderContext::UID) || (uid == IClassFactory::UID) || (uid == IInterface::UID)) {
        return static_cast<IRenderContext*>(this);
    }
    if (uid == IClassRegister::UID) {
        return static_cast<IClassRegister*>(this);
    }
    return nullptr;
}

void RenderContext::Ref()
{
    refCount_++;
}

void RenderContext::Unref()
{
    if (--refCount_ == 1) {
        pluginState_.Destroy();
        delete this;
    }
}

IInterface::Ptr RenderContext::CreateInstance(const Uid& uid)
{
    const auto& data = GetInterfaceMetadata(uid);
    if (data.createInterface) {
        return IInterface::Ptr { data.createInterface(*this, data.token) };
    }
    return {};
}

void RenderContext::RegisterInterfaceType(const InterfaceTypeInfo& interfaceInfo)
{
    // keep interfaceTypeInfos_ sorted according to UIDs
    const auto pos = std::upper_bound(interfaceTypeInfos_.cbegin(), interfaceTypeInfos_.cend(), interfaceInfo.uid,
        [](Uid value, const InterfaceTypeInfo* element) { return value < element->uid; });
    interfaceTypeInfos_.insert(pos, &interfaceInfo);
}

void RenderContext::UnregisterInterfaceType(const InterfaceTypeInfo& interfaceInfo)
{
    if (!interfaceTypeInfos_.empty()) {
        const auto pos = std::lower_bound(interfaceTypeInfos_.cbegin(), interfaceTypeInfos_.cend(), interfaceInfo.uid,
            [](const InterfaceTypeInfo* element, Uid value) { return element->uid < value; });
        if ((pos != interfaceTypeInfos_.cend()) && (*pos)->uid == interfaceInfo.uid) {
            interfaceTypeInfos_.erase(pos);
        }
    }
}

array_view<const InterfaceTypeInfo* const> RenderContext::GetInterfaceMetadata() const
{
    return interfaceTypeInfos_;
}

const InterfaceTypeInfo& RenderContext::GetInterfaceMetadata(const Uid& uid) const
{
    static InterfaceTypeInfo invalidType {};

    if (!interfaceTypeInfos_.empty()) {
        const auto pos = std::lower_bound(interfaceTypeInfos_.cbegin(), interfaceTypeInfos_.cend(), uid,
            [](const InterfaceTypeInfo* element, Uid value) { return element->uid < value; });
        if ((pos != interfaceTypeInfos_.cend()) && (*pos)->uid == uid) {
            return *(*pos);
        }
    }
    return invalidType;
}

IInterface* RenderContext::GetInstance(const Uid& uid) const
{
    const auto& data = GetInterfaceMetadata(uid);
    if (data.getInterface) {
        return data.getInterface(const_cast<RenderContext&>(*this), data.token);
    }
    return nullptr;
}

void RenderContext::OnTypeInfoEvent(EventType type, array_view<const ITypeInfo* const> typeInfos)
{
    auto& renderNodeMgr = renderNodeGraphMgr_->GetRenderNodeManager();
    if (type == EventType::ADDED) {
        for (const auto* info : typeInfos) {
            if (info && info->typeUid == IRenderPlugin::UID && static_cast<const IRenderPlugin*>(info)->createPlugin) {
                auto renderPlugin = static_cast<const IRenderPlugin*>(info);
                if (std::none_of(plugins_.begin(), plugins_.end(),
                        [renderPlugin](const pair<PluginToken, const IRenderPlugin*>& pluginData) {
                            return pluginData.second == renderPlugin;
                        })) {
                    auto token = renderPlugin->createPlugin(*this);
                    plugins_.push_back({ token, renderPlugin });
                }
            } else if (info && info->typeUid == RenderDataStoreTypeInfo::UID) {
                renderDataStoreMgr_->AddRenderDataStoreFactory(*static_cast<const RenderDataStoreTypeInfo*>(info));
            } else if (info && info->typeUid == RenderNodeTypeInfo::UID) {
                renderNodeMgr.AddRenderNodeFactory(*static_cast<const RenderNodeTypeInfo*>(info));
            }
        }
    } else if (type == EventType::REMOVED) {
        for (const auto* info : typeInfos) {
            if (info && info->typeUid == IRenderPlugin::UID) {
                auto renderPlugin = static_cast<const IRenderPlugin*>(info);
                if (auto pos = std::find_if(plugins_.begin(), plugins_.end(),
                        [renderPlugin](const pair<PluginToken, const IRenderPlugin*>& pluginData) {
                            return pluginData.second == renderPlugin;
                        });
                    pos != plugins_.end()) {
                    if (renderPlugin->destroyPlugin) {
                        renderPlugin->destroyPlugin(pos->first);
                    }
                    plugins_.erase(pos);
                }
            } else if (info && info->typeUid == RenderDataStoreTypeInfo::UID) {
                renderDataStoreMgr_->RemoveRenderDataStoreFactory(*static_cast<const RenderDataStoreTypeInfo*>(info));
            } else if (info && info->typeUid == RenderNodeTypeInfo::UID) {
                renderNodeGraphMgr_->Destroy(static_cast<const RenderNodeTypeInfo*>(info)->typeName);
                renderNodeMgr.RemoveRenderNodeFactory(*static_cast<const RenderNodeTypeInfo*>(info));
            }
        }
    }
}

RENDER_END_NAMESPACE()