//
// Copyright 2021 Pixar
//
// Licensed under the terms set forth in the LICENSE.txt file available at
// https://openusd.org/license.
//
#ifndef PXR_IMAGING_HD_SCENE_INDEX_PLUGIN_REGISTRY_H
#define PXR_IMAGING_HD_SCENE_INDEX_PLUGIN_REGISTRY_H

#include "pxr/pxr.h"
#include "pxr/base/tf/singleton.h"
#include "pxr/base/tf/staticTokens.h"
#include "pxr/imaging/hf/pluginRegistry.h"
#include "pxr/imaging/hd/api.h"
#include "pxr/imaging/hd/sceneIndex.h"

PXR_NAMESPACE_OPEN_SCOPE

#define HDSCENEINDEXPLUGINREGISTRY_TOKENS \
    ((rendererDisplayName, "__rendererDisplayName")) \


TF_DECLARE_PUBLIC_TOKENS(HdSceneIndexPluginRegistryTokens, HD_API,
    HDSCENEINDEXPLUGINREGISTRY_TOKENS);



class HdSceneIndexPlugin;

class HdSceneIndexPluginRegistry final  : public HfPluginRegistry
{
public:
    ///
    /// Returns the singleton registry for \c HdSceneIndexPlugin
    ///
    HD_API
    static HdSceneIndexPluginRegistry &GetInstance();

    ///
    /// Entry point for defining an HdSceneIndexPlugin plugin.
    ///
    template<typename T, typename... Bases>
    static void Define();

    ///
    /// Given a specific scene index plug-in id, give the plug-in a chance
    /// to add scene indices to the end of the chain. Return value is the
    /// last scene index -- or inputScene if the plug-in chooses not to act.
    /// Input arguments may be nullptr if not relevant to this plug-in.
    HD_API
    HdSceneIndexBaseRefPtr AppendSceneIndex(
        const TfToken &sceneIndexPluginId,
        const HdSceneIndexBaseRefPtr &inputScene,
        const HdContainerDataSourceHandle &inputArgs,
        const std::string &renderInstanceId=std::string());

    ///
    /// Append scene indices generated by plug-ins registered (via 
    /// RegisterSceneIndexForRenderer) for this renderer. Return value is the
    /// last scene index -- or inputScene if no plugins are registered or
    /// taking action. This also includes plug-ins registered for all
    /// renderers (via an empty rendererDisplayName) to be added in advance
    /// of any registered for the specified renderer.
    ///
    /// Plugin libraries will only be loaded if they declare they are enabled
    /// for the provided app name. By default, plugins are auto-loaded for all
    /// apps, but plugin authors can put an preloadInApps array in their
    /// plugInfo to narrow down the set of applications they are auto-loaded
    /// for. Providing an empty app name here (the default) means this will not
    /// auto-load any application-specific plugin libraries.
    ///
    HD_API
    HdSceneIndexBaseRefPtr AppendSceneIndicesForRenderer(
        const std::string &rendererDisplayName,
        const HdSceneIndexBaseRefPtr &inputScene,
        const std::string &renderInstanceId=std::string(),
        const std::string &appName=std::string());

    enum InsertionOrder
    {
        InsertionOrderAtStart,
        InsertionOrderAtEnd,
    };

    using InsertionPhase = int;

    ///
    /// Register a scene index to be instantiated for a specified 
    /// renderer (or all renderers if rendererDisplayName is empty).
    /// 
    /// Insertion phase is a broad ordering value with lower values indicating
    /// earlier instantiation (possibly given render plugin-specific meaning
    /// via enum values). Insertion order indicates whether this entry
    /// should go at the start or end of the specified phase.
    HD_API
    void RegisterSceneIndexForRenderer(
        const std::string &rendererDisplayName,
        const TfToken &sceneIndexPluginId,
        const HdContainerDataSourceHandle &inputArgs,
        InsertionPhase insertionPhase,
        InsertionOrder insertionOrder);


    using SceneIndexAppendCallback = 
        std::function<
            HdSceneIndexBaseRefPtr(
                const std::string &renderInstanceId,
                const HdSceneIndexBaseRefPtr &inputScene,
                const HdContainerDataSourceHandle &inputArgs)>;

    ///
    /// Register a scene index to be instantiated via a callback for a
    /// specified renderer (or all renderers if rendererDisplayName is empty).
    ///
    /// This is most useful for application-specific behavior which wants to
    /// append and manage scene index instances associated with a specific
    /// render. To aid in that association, the callback is provided a
    /// renderInstanceId value typically defined by the application itself.
    ///
    /// Insertion phase is a broad ordering value with lower values indicating
    /// earlier instantiation (possibly given render plugin-specific meaning
    /// via enum values). Insertion order indicates whether this entry
    /// should go at the start or end of the specified phase.
    ///
    /// \note This method should be invoked *before* render index construction
    ///       when Hydra scene index emulation is enabled.
    ///
    HD_API
    void RegisterSceneIndexForRenderer(
        const std::string &rendererDisplayName,
        SceneIndexAppendCallback callback,
        const HdContainerDataSourceHandle &inputArgs,
        InsertionPhase insertionPhase,
        InsertionOrder insertionOrder);



protected:

     void _CollectAdditionalMetadata(
        const PlugRegistry &plugRegistry, const TfType &pluginType) override;

private:
    friend class TfSingleton<HdSceneIndexPluginRegistry>;
    
    // Singleton gets private constructed
    HdSceneIndexPluginRegistry();
    ~HdSceneIndexPluginRegistry() override;

    void _LoadPluginsForRenderer(
        const std::string &rendererDisplayName,
        const std::string &appName);

    HdSceneIndexPlugin *_GetSceneIndexPlugin(const TfToken &pluginId);

    struct _Entry
    {
        _Entry(const TfToken &sceneIndexPluginId,
                const HdContainerDataSourceHandle &args)
        : sceneIndexPluginId(sceneIndexPluginId)
        , args(args)
        {}

        _Entry(SceneIndexAppendCallback callback,
                const HdContainerDataSourceHandle &args)
        : args(args)
        , callback(callback)
        {}

        TfToken sceneIndexPluginId;
        HdContainerDataSourceHandle args;
        SceneIndexAppendCallback callback;
    };

    using _EntryList = std::vector<_Entry>;
    using _PhasesMap = std::map<InsertionPhase, _EntryList>;
    using _RenderersMap = std::map<std::string, _PhasesMap>;

    HdSceneIndexBaseRefPtr _AppendForPhases(
        const HdSceneIndexBaseRefPtr &inputScene,
        const _PhasesMap &phasesMap,
        const HdContainerDataSourceHandle &argsUnderlay,
        const std::string &renderInstanceId);

    _RenderersMap _sceneIndicesForRenderers;

    // Used to track plugins whose plugInfo entries contain "loadWithRenderer"
    // values to load when the specified renderer or renderers are used.
    // Loading the plug-in allows for further registration code to run when
    // a plug-in wouldn't be loaded elsewhere.
    using _PreloadMap = std::map<std::string, TfTokenVector>;
    _PreloadMap _preloadsForRenderer;

    // Used to track app-name-based filtering for plugin loading. If a plugin
    // declares "preloadInApps" in its plugInfo, the plugin will appear in this
    // map. When a plugin is in this map, its library will only be loaded if
    // the appName provided to AppendSceneIndexes is in the list of
    // preloadInApps for the plugin.
    using _EnabledAppsMap = std::map<TfToken, std::set<std::string>>;
    _EnabledAppsMap _preloadAppsForPlugins;
};

template<typename T, typename... Bases>
void HdSceneIndexPluginRegistry::Define()
{
    HfPluginRegistry::Define<T, HdSceneIndexPlugin, Bases...>();
}

PXR_NAMESPACE_CLOSE_SCOPE

#endif // PXR_IMAGING_HD_SCENE_INDEX_PLUGIN_REGISTRY_H
