// SPDX-FileCopyrightText: Copyright (c) Kitware Inc.
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkPVPluginTracker.h"

#include "vtkClientServerInterpreterInitializer.h"
#include "vtkCommand.h"
#include "vtkObjectFactory.h"
#include "vtkPResourceFileLocator.h"
#include "vtkPSystemTools.h"
#include "vtkPVDynamicInitializerPluginInterface.h"
#include "vtkPVLogger.h"
#include "vtkPVPlugin.h"
#include "vtkPVPluginLoader.h"
#include "vtkPVPythonModule.h"
#include "vtkPVPythonPluginInterface.h"
#include "vtkPVServerManagerPluginInterface.h"
#include "vtkPVXMLElement.h"
#include "vtkPVXMLParser.h"
#include "vtkProcessModule.h"
#include "vtkRemotingCoreConfiguration.h"

#include "vtksys/FStream.hxx"
#include "vtksys/String.hxx"
#include "vtksys/SystemTools.hxx"

#include <cassert>
#include <sstream>
#include <string>
#include <vector>

#if defined(_WIN32) && !defined(__CYGWIN__)
/* String comparison routine. */
#define VTKSTRNCASECMP _strnicmp
#else
#include "strings.h"
#define VTKSTRNCASECMP strncasecmp
#endif

namespace
{

class vtkItem
{
public:
  std::string FileName;
  std::string PluginName;
  vtkPVPlugin* Plugin = nullptr;
  bool AutoLoad = false;
  bool DelayedLoad = false;
  std::vector<std::string> XMLs;
  std::string Version;
  std::string Description;
};

/**
 * Convert a plugin name to its library name i.e. add platform specific
 * library prefix and suffix.
 */
std::string vtkGetPluginLibraryFileNameFromName(const std::string& pluginname)
{
#if defined(_WIN32) && !defined(__CYGWIN__)
  return pluginname + ".dll";
#else
  // starting with ParaView 5.7, we are building .so's even on macOS
  // since they are built as "add_library(.. MODULE)" which by default generates
  // `.so`s which seems to be the convention.
  return pluginname + ".so";
#endif
}

using VectorOfSearchFunctions = std::vector<vtkPluginSearchFunction>;
VectorOfSearchFunctions RegisteredPluginSearchFunctions;

using VectorOfListFunctions = std::vector<vtkPluginListFunction>;
VectorOfListFunctions RegisteredPluginListFunctions;

/**
 * Locate a plugin library or a config file anchored at standard locations
 * for locating plugins.
 */
std::string vtkLocatePluginOrConfigFile(const char* plugin, const char* hint, bool isPlugin)
{
  vtkVLogScopeF(PARAVIEW_LOG_PLUGIN_VERBOSITY(), "looking for plugin '%s'", plugin);

  auto pm = vtkProcessModule::GetProcessModule();
  // Make sure we can get the options before going further
  if (pm == nullptr)
  {
    vtkLogF(ERROR, "vtkProcessModule does not exist!");
    return std::string();
  }

  // First search in the static lookup tables.
  if (isPlugin)
  {
    for (auto searchFunction : RegisteredPluginSearchFunctions)
    {
      if (searchFunction && searchFunction(plugin))
      {
        vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(), "found plugin linked in statically!");
        return plugin;
      }
    }

#if !BUILD_SHARED_LIBS
    // in static builds, we don't attempt to locate plugin so's/dll's since we
    // can't load them anyways. This has a design flaw that we are also skipping *.py
    // or *.xml plugins. Since this has been the case historically, we'll leave
    // that as is for now. When we refactor the plugin infrastructure we should
    // fix that.
    return std::string();
#endif
  }

  const std::string exe_dir = pm->GetSelfDir();

  std::vector<std::string> prefixes = {
    std::string(PARAVIEW_RELATIVE_LIBPATH "/" PARAVIEW_SUBDIR "/") + plugin,
    std::string(PARAVIEW_RELATIVE_LIBPATH "/" PARAVIEW_SUBDIR "/"),

// .app bundles
#if defined(__APPLE__)
    std::string("../Plugins/") + plugin, //
    std::string("../Plugins/"),
#endif
    std::string()
  };

  const std::string pluginName{ plugin };
  std::vector<std::string> possibleFiles;
  if (isPlugin)
  {
    possibleFiles.push_back(vtkGetPluginLibraryFileNameFromName(pluginName));
    possibleFiles.push_back(pluginName + ".py");
    possibleFiles.push_back(pluginName + ".xml");
  }
  else
  {
    possibleFiles.push_back(pluginName);
  }

  vtkNew<vtkPResourceFileLocator> locator;
  locator->SetLogVerbosity(PARAVIEW_LOG_PLUGIN_VERBOSITY());

  if (hint && *hint)
  {
    const std::string hintdir = vtksys::SystemTools::GetFilenamePath(hint);
    for (const auto& file : possibleFiles)
    {
      auto path = locator->Locate(std::string(hintdir).append("/").append(plugin), file);
      if (!path.empty())
      {
        return std::string(path).append("/").append(file);
      }
    }
  }

  // First try the plugin search paths, if any.
  for (const auto& spath : vtkRemotingCoreConfiguration::GetInstance()->GetPluginSearchPaths())
  {
    vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(), "check `plugin-search-path` first.");
    for (const auto& file : possibleFiles)
    {
      auto path = locator->Locate(spath, file);
      if (!path.empty())
      {
        return std::string(path).append("/").append(file);
      }
    }
  }

  if (!exe_dir.empty())
  {
    vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(),
      "check various prefixes relative to executable location: `%s`", exe_dir.c_str());
    for (const auto& file : possibleFiles)
    {
      auto pluginpath = locator->Locate(exe_dir, prefixes, file);
      if (!pluginpath.empty())
      {
        return std::string(pluginpath).append("/").append(file);
      }
    }
  }

  vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(), "failed!!!");
  return std::string();
}
}

class vtkPVPluginTracker::vtkPluginsList : public std::vector<vtkItem>
{
public:
  iterator LocateUsingPluginName(const char* pluginname)
  {
    if (!pluginname)
    {
      return this->end();
    }

    for (iterator iter = this->begin(); iter != this->end(); ++iter)
    {
      if (iter->PluginName == pluginname)
      {
        return iter;
      }
    }
    return this->end();
  }

  iterator LocateUsingFileName(const char* filename)
  {
    if (!filename)
    {
      return this->end();
    }

    for (iterator iter = this->begin(); iter != this->end(); ++iter)
    {
      if (iter->FileName == filename)
      {
        return iter;
      }
    }
    return this->end();
  }
};

vtkStandardNewMacro(vtkPVPluginTracker);
//----------------------------------------------------------------------------
vtkPVPluginTracker::vtkPVPluginTracker()
{
  this->PluginsList = new vtkPluginsList();
  if (vtksys::SystemTools::GetEnv("PV_PLUGIN_DEBUG") != nullptr)
  {
    vtkWarningMacro("`PV_PLUGIN_DEBUG` environment variable has been deprecated. "
                    "Please use `PARAVIEW_LOG_PLUGIN_VERBOSITY=INFO` instead.");
    vtkPVLogger::SetPluginVerbosity(vtkLogger::VERBOSITY_INFO);
  }
}

//----------------------------------------------------------------------------
vtkPVPluginTracker::~vtkPVPluginTracker()
{
  delete this->PluginsList;
  this->PluginsList = nullptr;
}

//----------------------------------------------------------------------------
vtkPVPluginTracker* vtkPVPluginTracker::GetInstance()
{
  static vtkSmartPointer<vtkPVPluginTracker> Instance;
  if (Instance.GetPointer() == nullptr)
  {
    vtkPVPluginTracker* mgr = vtkPVPluginTracker::New();
    Instance = mgr;
    mgr->FastDelete();
  }
  return Instance;
}

//----------------------------------------------------------------------------
void vtkPVPluginTracker::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}

//----------------------------------------------------------------------------
void vtkPVPluginTracker::LoadPluginConfigurationXMLs(const char* appname)
{
  if (!appname || !*appname)
  {
    return;
  }

  auto pm = vtkProcessModule::GetProcessModule();
  if (!pm)
  {
    return;
  }

#if BUILD_SHARED_LIBS
  // it makes sense to look for plugin configuration xmls and such only for
  // shared-builds. for static builds, it's not only not necessary, but also not
  // correct as we're missing all statically linked plugins that are available.
  const std::string exe_dir = pm->GetSelfDir();
  if (!exe_dir.empty())
  {
#if defined(__APPLE__)
    // Try it as a bundle.
    {
      auto conf = exe_dir + "/../Resources/" + appname + ".conf";
      if (vtksys::SystemTools::FileExists(conf))
      {
        this->LoadPluginConfigurationXMLConf(exe_dir, conf);
        return;
      }
    }
#endif // defined(__APPLE__)
    // Load it from beside the executable.
    {
      auto conf = exe_dir + "/" + appname + ".conf";
      if (vtksys::SystemTools::FileExists(conf))
      {
        this->LoadPluginConfigurationXMLConf(exe_dir, conf);
        return;
      }
    }
  }
#endif // BUILD_SHARED_LIBS

  std::vector<std::string> names;
  for (auto& listFunction : RegisteredPluginListFunctions)
  {
    if (listFunction)
    {
      listFunction(appname, names);
    }
  }

  vtkNew<vtkPVXMLElement> root;
  root->SetName("Plugins");
  for (const auto& name : names)
  {
    vtkNew<vtkPVXMLElement> child;
    child->SetName("Plugin");
    child->AddAttribute("name", name.c_str());
    root->AddNestedElement(child);
  }
  this->LoadPluginConfigurationXML(root, /*forceLoad=*/false);
}

//----------------------------------------------------------------------------
void vtkPVPluginTracker::LoadPluginConfigurationXMLConf(
  std::string const& exe_dir, std::string const& conf)
{
  vtksys::ifstream fin(conf.c_str());
  std::string line;
  // TODO: Replace with a JSON parser.
  while (std::getline(fin, line))
  {
    if (!vtksys::SystemTools::FileIsFullPath(line))
    {
      line = std::string(exe_dir).append("/").append(line);
    }

    this->LoadPluginConfigurationXML(line.c_str(), false);
  }
}

//----------------------------------------------------------------------------
void vtkPVPluginTracker::LoadPluginConfigurationXML(const char* filename, bool forceLoad)
{
  if (!vtkPSystemTools::FileExists(filename, true))
  {
    vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(),
      "Loading plugin configuration xml `%s` -- failed, not found!", filename);
    return;
  }

  vtkSmartPointer<vtkPVXMLParser> parser = vtkSmartPointer<vtkPVXMLParser>::New();
  parser->SetFileName(filename);
  parser->SuppressErrorMessagesOn();
  if (!parser->Parse())
  {
    vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(),
      "Loading plugin configuration xml `%s` -- failed, invalid XML!", filename);
    return;
  }

  vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(), "Loading plugin configuration xml `%s`.", filename);
  this->LoadPluginConfigurationXMLHinted(parser->GetRootElement(), filename, forceLoad);
}

//----------------------------------------------------------------------------
void vtkPVPluginTracker::LoadPluginConfigurationXMLFromString(
  const char* xmlcontents, bool forceLoad)
{
  vtkSmartPointer<vtkPVXMLParser> parser = vtkSmartPointer<vtkPVXMLParser>::New();
  parser->SuppressErrorMessagesOn();
  if (!parser->Parse(xmlcontents))
  {
    vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(), "string not a valid xml -- failed!");
    return;
  }

  this->LoadPluginConfigurationXML(parser->GetRootElement(), forceLoad);
}

//----------------------------------------------------------------------------
void vtkPVPluginTracker::LoadPluginConfigurationXML(vtkPVXMLElement* root, bool forceLoad)
{
  this->LoadPluginConfigurationXMLHinted(root, nullptr, forceLoad);
}

//----------------------------------------------------------------------------
void vtkPVPluginTracker::LoadPluginConfigurationXMLHinted(
  vtkPVXMLElement* root, char const* hint, bool forceLoad)
{
  if (root == nullptr)
  {
    return;
  }

  if (strcmp(root->GetName(), "Plugins") != 0)
  {
    vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(),
      "Root element in the xml must be `<Plugins/>`, got `%s` -- failed!", root->GetName());
    return;
  }

  for (unsigned int cc = 0; cc < root->GetNumberOfNestedElements(); cc++)
  {
    vtkPVXMLElement* child = root->GetNestedElement(cc);
    if (child && child->GetName() && strcmp(child->GetName(), "Plugin") == 0)
    {
      std::string name = child->GetAttributeOrEmpty("name");
      if (name.empty())
      {
        vtkVLogF(
          PARAVIEW_LOG_PLUGIN_VERBOSITY(), "Missing required attribute name. Skipping element.");
        continue;
      }

      int auto_load = 0;
      child->GetScalarAttribute("auto_load", &auto_load);
      bool autoLoad = auto_load == 1;
      int delayed_load = 0;
      child->GetScalarAttribute("delayed_load", &delayed_load);
      bool delayedLoad = delayed_load == 1;

      vtkVLogF(
        PARAVIEW_LOG_PLUGIN_VERBOSITY(), "Trying to locate plugin with name `%s`", name.c_str());
      std::string plugin_filename;
      if (child->GetAttribute("filename"))
      {
        std::string filename = child->GetAttribute("filename");
        if (hint && !vtksys::SystemTools::FileIsFullPath(filename))
        {
          std::string basedir =
            vtksys::SystemTools::CollapseFullPath(vtksys::SystemTools::GetFilenamePath(hint));

          plugin_filename = vtksys::SystemTools::CollapseFullPath(filename, basedir);

          // Ensure the path is under the base directory given.
          if (!vtksys::SystemTools::IsSubDirectory(plugin_filename, basedir))
          {
            vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(),
              "Invalid `filename=` setting for %s; must be underneath the XML directory.",
              name.c_str());
            continue;
          }
        }
        else
        {
          plugin_filename = filename;
        }

        if (!vtkPSystemTools::FileExists(plugin_filename, true))
        {
          vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(),
            "Invalid `filename=` setting for %s; the given path (%s) must exist.", name.c_str(),
            plugin_filename.c_str());
          // TODO: Warn that the plugin was not found.
          plugin_filename.clear();
        }
      }
      else
      {
        plugin_filename = vtkLocatePluginOrConfigFile(name.c_str(), hint, true);
      }
      if (plugin_filename.empty())
      {
        int required = 0;
        child->GetScalarAttribute("required", &required);
        if (required)
        {
          vtkErrorMacro("Failed to locate required plugin: "
            << name << "\nApplication may not work exactly as expected.");
        }
        vtkVLogF(
          PARAVIEW_LOG_PLUGIN_VERBOSITY(), "Failed to locate file plugin `%s`", name.c_str());
        continue;
      }
      vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(), "found `%s`", plugin_filename.c_str());

      std::string version;
      std::string description;
      std::vector<std::string> xmls;

      // Check if plugin is already known
      vtkPluginsList::iterator iter =
        this->PluginsList->LocateUsingFileName(plugin_filename.c_str());
      bool isPluginLoaded = false;
      if (iter == this->PluginsList->end())
      {
        iter = this->PluginsList->LocateUsingPluginName(name.c_str());
      }
      if (iter == this->PluginsList->end())
      {
        // New plugin, add an entry
        vtkItem item;
        item.FileName = plugin_filename;
        item.PluginName = name;

        // Recover version and description if available
        if (child->GetAttribute("version"))
        {
          version = child->GetAttribute("version");
        }
        if (child->GetAttribute("description"))
        {
          description = child->GetAttribute("description");
        }
        item.Version = version;
        item.Description = description;
        item.AutoLoad = autoLoad;
        item.DelayedLoad = delayedLoad;

        // It is a delayed load plugin, recover the XMLs if provided
        // We could consider always recovering them when available
        if (delayedLoad)
        {
          for (unsigned int cd = 0; cd < child->GetNumberOfNestedElements(); cd++)
          {
            vtkPVXMLElement* xmlChild = child->GetNestedElement(cd);
            if (strcmp(xmlChild->GetName(), "XML") != 0)
            {
              vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(),
                "XML child elements are expected to be named XML, but one is named instead: `%s`, "
                "skipping element",
                xmlChild->GetName());
              continue;
            }
            if (!xmlChild->GetAttribute("filename"))
            {
              vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(),
                "XML child elements are expected to contain a filename attribute, skipping "
                "element");
              continue;
            }
            std::string xmlFilename = xmlChild->GetAttribute("filename");
            if (hint && !vtksys::SystemTools::FileIsFullPath(xmlFilename))
            {
              std::string basedir =
                vtksys::SystemTools::CollapseFullPath(vtksys::SystemTools::GetFilenamePath(hint));
              xmlFilename = vtksys::SystemTools::CollapseFullPath(xmlFilename, basedir);

              // Ensure the path is under the base directory given.
              if (!vtksys::SystemTools::IsSubDirectory(xmlFilename, basedir))
              {
                vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(),
                  "Invalid `filename=` attribute for %s; must be underneath the XML directory.",
                  name.c_str());
                continue;
              }
            }
            xmls.push_back(xmlFilename);
          }
        }
        item.XMLs = xmls;
        this->PluginsList->push_back(item);
        this->InvokeEvent(vtkPVPluginTracker::RegisterAvailablePluginEvent);
      }
      else
      {
        // Merge modifiable booleans
        iter->AutoLoad = iter->AutoLoad | autoLoad;

        // Recover needed info for loading
        version = iter->Version;
        description = iter->Description;
        xmls = iter->XMLs;

        // Check if the plugin already loaded
        isPluginLoaded = iter->Plugin != nullptr;
      }

      if ((autoLoad || forceLoad) && !isPluginLoaded)
      {
        // load the plugin.
        vtkNew<vtkPVPluginLoader> loader;
        if (delayedLoad)
        {
          if (xmls.empty())
          {
            vtkVLogF(PARAVIEW_LOG_PLUGIN_VERBOSITY(),
              "No XML child element defined with a delayed_load plugin, aborting load");
          }
          else
          {
            loader->LoadDelayedLoadPlugin(name, xmls, plugin_filename, version, description);
          }
        }
        else
        {
          loader->LoadPlugin(plugin_filename.c_str());
        }
      }
    }
  }
}

//----------------------------------------------------------------------------
unsigned int vtkPVPluginTracker::GetNumberOfPlugins()
{
  return static_cast<unsigned int>(this->PluginsList->size());
}

//----------------------------------------------------------------------------
void vtkPVPluginTracker::RegisterPlugin(vtkPVPlugin* plugin)
{
  assert(plugin != nullptr);

  vtkPluginsList::iterator iter = this->PluginsList->LocateUsingFileName(plugin->GetFileName());
  if (iter == this->PluginsList->end())
  {
    iter = this->PluginsList->LocateUsingPluginName(plugin->GetPluginName());
  }
  if (iter == this->PluginsList->end())
  {
    vtkItem item;
    item.FileName = plugin->GetFileName() ? plugin->GetFileName() : "linked-in";
    item.PluginName = plugin->GetPluginName();
    item.Plugin = plugin;
    this->PluginsList->push_back(item);
  }
  else
  {
    iter->Plugin = plugin;
    if (plugin->GetFileName())
    {
      iter->FileName = plugin->GetFileName();
    }
  }

  // Do some basic processing of the plugin here itself.

  // If this plugin has arbitrary code that needs to run upon load, run it now.
  auto* diplugin = dynamic_cast<vtkPVDynamicInitializerPluginInterface*>(plugin);
  if (diplugin)
  {
    diplugin->Initialize();
  }

  // If this plugin has functions for initializing the interpreter, we set them
  // up right now.
  vtkPVServerManagerPluginInterface* smplugin =
    dynamic_cast<vtkPVServerManagerPluginInterface*>(plugin);
  if (smplugin)
  {
    if (smplugin->GetInitializeInterpreterCallback())
    {
      // This also initializes any existing instances of
      // vtkClientServerInterpreter. Refer to
      // vtkClientServerInterpreterInitializer::RegisterCallback implementation
      // for details.
      vtkClientServerInterpreterInitializer::GetInitializer()->RegisterCallback(
        smplugin->GetInitializeInterpreterCallback());
    }
  }

  // If this plugin has Python modules, process those.
  vtkPVPythonPluginInterface* pythonplugin = dynamic_cast<vtkPVPythonPluginInterface*>(plugin);
  if (pythonplugin)
  {
    std::vector<std::string> modules, sources;
    std::vector<int> package_flags;
    pythonplugin->GetPythonSourceList(modules, sources, package_flags);
    assert(modules.size() == sources.size() && sources.size() == package_flags.size());
    for (size_t cc = 0; cc < modules.size(); cc++)
    {
      vtkPVPythonModule* module = vtkPVPythonModule::New();
      module->SetFullName(modules[cc].c_str());
      module->SetSource(sources[cc].c_str());
      module->SetIsPackage(package_flags[cc]);
      vtkPVPythonModule::RegisterModule(module);
      module->Delete();
    }
  }

  this->InvokeEvent(vtkCommand::RegisterEvent, plugin);
}

//----------------------------------------------------------------------------
vtkPVPlugin* vtkPVPluginTracker::GetPlugin(unsigned int index)
{
  if (index >= this->GetNumberOfPlugins())
  {
    vtkWarningMacro("Invalid index: " << index);
    return nullptr;
  }
  return (*this->PluginsList)[index].Plugin;
}

//----------------------------------------------------------------------------
const char* vtkPVPluginTracker::GetPluginName(unsigned int index)
{
  if (index >= this->GetNumberOfPlugins())
  {
    vtkWarningMacro("Invalid index: " << index);
    return nullptr;
  }
  return (*this->PluginsList)[index].PluginName.c_str();
}

//----------------------------------------------------------------------------
const char* vtkPVPluginTracker::GetPluginFileName(unsigned int index)
{
  if (index >= this->GetNumberOfPlugins())
  {
    vtkWarningMacro("Invalid index: " << index);
    return nullptr;
  }
  return (*this->PluginsList)[index].FileName.c_str();
}

//----------------------------------------------------------------------------
bool vtkPVPluginTracker::GetPluginLoaded(unsigned int index)
{
  if (index >= this->GetNumberOfPlugins())
  {
    vtkWarningMacro("Invalid index: " << index);
    return false;
  }
  return (*this->PluginsList)[index].Plugin != nullptr;
}

//----------------------------------------------------------------------------
bool vtkPVPluginTracker::GetPluginAutoLoad(unsigned int index)
{
  if (index >= this->GetNumberOfPlugins())
  {
    vtkWarningMacro("Invalid index: " << index);
    return false;
  }
  return (*this->PluginsList)[index].AutoLoad;
}

//----------------------------------------------------------------------------
bool vtkPVPluginTracker::GetPluginDelayedLoad(unsigned int index)
{
  if (index >= this->GetNumberOfPlugins())
  {
    vtkWarningMacro("Invalid index: " << index);
    return false;
  }
  return (*this->PluginsList)[index].DelayedLoad;
}

//----------------------------------------------------------------------------
std::vector<std::string> vtkPVPluginTracker::GetPluginXMLs(unsigned int index)
{
  if (index >= this->GetNumberOfPlugins())
  {
    vtkWarningMacro("Invalid index: " << index);
    return {};
  }
  return (*this->PluginsList)[index].XMLs;
}

//----------------------------------------------------------------------------
std::string vtkPVPluginTracker::GetPluginVersion(unsigned int index)
{
  if (index >= this->GetNumberOfPlugins())
  {
    vtkWarningMacro("Invalid index: " << index);
    return "";
  }
  return (*this->PluginsList)[index].Version;
}

//----------------------------------------------------------------------------
std::string vtkPVPluginTracker::GetPluginDescription(unsigned int index)
{
  if (index >= this->GetNumberOfPlugins())
  {
    vtkWarningMacro("Invalid index: " << index);
    return "";
  }
  return (*this->PluginsList)[index].Description;
}

//-----------------------------------------------------------------------------
void vtkPVPluginTracker::RegisterStaticPluginSearchFunction(vtkPluginSearchFunction function)
{
  RegisteredPluginSearchFunctions.push_back(function);
}

//-----------------------------------------------------------------------------
void vtkPVPluginTracker::RegisterStaticPluginListFunction(vtkPluginListFunction function)
{
  RegisteredPluginListFunctions.push_back(function);
}
