//
// 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/imaging/hd/api.h"
#include "pxr/imaging/hd/sceneIndex.h"
#include "pxr/imaging/hd/sceneIndexObserver.h"
#include "pxr/imaging/hf/pluginRegistry.h"

#include "pxr/base/tf/singleton.h"
#include "pxr/base/tf/staticTokens.h"
#include "pxr/base/tf/token.h"
#include "pxr/base/tf/weakPtr.h"

#include "pxr/pxr.h"

#include <string>
#include <memory>

PXR_NAMESPACE_OPEN_SCOPE

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

TF_DECLARE_PUBLIC_TOKENS(HdSceneIndexPluginRegistryTokens, HD_API,
    HDSCENEINDEXPLUGINREGISTRY_TOKENS);

class HdSceneIndexPlugin;

///
/// A registry for renderer- and application-specific scene indices.
///
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 renderer string for the loadWithRenderer JSON
    /// field) 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 loadWithApps 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 plugin to be instantiated for a specified
    /// renderer (or all renderers if \p rendererDisplayName is empty).
    ///
    /// \note
    /// The function name is misleading as this registers the plugin and not
    /// an instance of the scene index itself.
    ///
    /// \p sceneIndexPluginId identifies the associated scene index plugin.
    /// This should match the name used in the plugInfo.json entry and is
    /// typically the class name (i.e. CPP type name).
    /// \p inputArgs is a container data source of arguments that is provided as
    /// a parameter to the scene index plugin's _AppendSceneIndex method.
    /// \p insertionPhase is a broad ordering value with lower values indicating
    /// earlier instantiation (possibly given render plugin-specific meaning
    /// via enum values).
    /// \p insertionOrder indicates whether this entry should go at the start
    /// or end of the specified phase.
    ///
    /// \note
    /// The plugInfo entry for a scene index plugin may have a
    /// "loadWithRenderer" key that specifies a list of renderer display names
    /// for which the plugin library should be *loaded*. This is separate from
    /// the registration of the scene index plugin for a renderer here, which
    /// is still necessary to have it be instantiated and be a part of the
    /// scene index graph that is built for that renderer.
    ///
    /// \note
    /// This method is typically invoked via the TF_REGISTRY_FUNCTION macro at
    /// module load time, like:
    /// \code
    /// TF_REGISTRY_FUNCTION(HdSceneIndexPlugin)
    /// {
    ///     HdSceneIndexPluginRegistry::GetInstance()
    ///     .RegisterSceneIndexForRenderer(...);
    /// }
    /// \endcode
    ///
    /// \note
    /// This function may be invoked multiple times for the same
    /// \p sceneIndexPluginId to have multiple instances of the same
    /// set of scene indices inserted in _AppendSceneIndex at different points
    /// in the scene index graph.
    ///
    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 \p callback for a
    /// specified renderer (or all renderers if \p 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.
    ///
    /// \p inputArgs is a container data source of arguments that is provided as
    /// a parameter to the scene index plugin's _AppendSceneIndex method.
    /// \p insertionPhase is a broad ordering value with lower values indicating
    /// earlier instantiation (possibly given render plugin-specific meaning
    /// via enum values).
    /// \p insertionOrder 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.
    ///
    /// \note
    /// Unlike the pattern for registering scene index plugins, it is
    /// better to invoke this method explicitly in application code rather
    /// than using a TF_REGISTRY_FUNCTION block.
    ///
    HD_API
    void RegisterSceneIndexForRenderer(
        const std::string &rendererDisplayName,
        SceneIndexAppendCallback callback,
        const HdContainerDataSourceHandle &inputArgs,
        InsertionPhase insertionPhase,
        InsertionOrder insertionOrder);

    /// Loads plugins for \p rendererDisplayName and \p appName.
    /// Returns the sceneIndexPluginId's in the order they'll be run.
    /// This does not include scene indices registered via the callback
    /// registration method.
    ///
    /// This is primarily in service of tests.
    HD_API
    std::vector<TfToken> LoadAndGetSceneIndexPluginIds(
        const std::string& rendererDisplayName,
        const std::string& appName);

    struct PluginInsertionMetadata
    {
        std::string rendererDisplayName;
        TfToken sceneIndexPluginId;
        int insertionPhase = -1;
    };

    /// Gets metadata about the plugin that inserted the given scene index, if
    /// available.
    ///
    /// This is primarily intended for debugging and diagnostic use.
    bool
    GetPluginInsertionMetadataForSceneIndex(
        const HdSceneIndexBaseRefPtr& sceneIndex,
        PluginInsertionMetadata& metadata);

    /// Enum to specify the policy for determining the order of scene index 
    /// plugins. This is primarily in service of testing, and to provide a
    /// pathway to deprecating the registration API in favor of JSON
    /// metadata-based ordering.
    /// The default is based off the environment setting
    /// HD_SCENE_INDEX_PLUGIN_ORDERING_POLICY_DEFAULT.
    ///
    enum class PluginOrderingPolicy
    {
        /// The order of plugins is determined solely by the insertion 
        /// phase/order arguments of the C++ registration API.
        CppRegistrationOnly,

        /// The tags and ordering specified in the JSON metadata are used to
        /// drive the ordering of scene index plugins.
        JsonMetadataOnly,

        /// A hybrid ordering scheme where we attempt to honor *both* the
        /// insertion phase/order specified via the C++ registration API and the
        /// tags and ordering specified in the JSON metadata.
        Hybrid
    };

    /// Sets the policy for determining the order of scene index plugins.
    /// This is primarily in service of testing.
    HD_API
    void
    SetPluginOrderingPolicy(PluginOrderingPolicy policy);

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 _Impl;
    std::unique_ptr<_Impl> _impl;
};

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
