// @(#)root/base:$Id$
// Author: Fons Rademakers   26/1/2002

/*************************************************************************
 * Copyright (C) 1995-2002, Rene Brun and Fons Rademakers.               *
 * All rights reserved.                                                  *
 *                                                                       *
 * For the licensing terms see $ROOTSYS/LICENSE.                         *
 * For the list of contributors see $ROOTSYS/README/CREDITS.             *
 *************************************************************************/

#ifndef ROOT_TPluginManager
#define ROOT_TPluginManager


//////////////////////////////////////////////////////////////////////////
//                                                                      //
// TPluginManager                                                       //
//                                                                      //
// This class implements a plugin library manager. It keeps track of    //
// a list of plugin handlers. A plugin handler knows which plugin       //
// library to load to get a specific class that is used to extend the   //
// functionality of a specific base class and how to create an object   //
// of this class. For example, to extend the base class TFile to be     //
// able to read SQLite files one needs to load the plugin library       //
// libRSQLite.so which defines the TSQLiteServer class. This loading    //
// should be triggered when a given URI contains a regular expression   //
// defined by the handler.                                              //
// Plugin handlers can be defined via macros in a list of plugin        //
// directories. With $ROOTSYS/etc/plugins the default top plugin        //
// directory specified in $ROOTSYS/etc/system.rootrc. Additional        //
// directories can be specified by adding them to the end of the list.  //
// Macros for identical plugin handlers in later directories will       //
// override previous ones (the inverse of normal search path behavior). //
// The macros must have names like <BaseClass>/PX0_<PluginClass>.C,     //
// e.g.:                                                                //
//    TSQLServer/P20_TMySQLServer.C, etc.                               //
// to allow easy sorting and grouping. If the BaseClass is in a         //
// namespace the directory must have the name NameSpace@@BaseClass as   //
// : is a reserved pathname character on some operating systems.        //
// Macros not beginning with 'P' and ending with ".C" are ignored.      //
// These macros typically look like:                                    //
//                                                                      //
//   void P10_TDCacheFile()                                             //
//   {                                                                  //
//       gPluginMgr->AddHandler("TFile", "^dcache", "TDCacheFile",      //
//          "DCache", "TDCacheFile(const char*,Option_t*)");            //
//   }                                                                  //
//                                                                      //
// Plugin handlers can also be defined via resources in the .rootrc     //
// file. Although now deprecated this method still works for backward   //
// compatibility, e.g.:                                                 //
//                                                                      //
//   Plugin.TSQLServer:  ^mysql:  TMySQLServer MySQL  "<constructor>"   //
//   +Plugin.TSQLServer: ^pgsql:  TPgSQLServer PgSQL  "<constructor>"   //
//   Plugin.TVirtualFitter: *     TFitter      Minuit "TFitter(Int_t)"  //
//                                                                      //
// Where the + in front of Plugin.TSQLServer says that it extends the   //
// existing definition of TSQLServer, useful when there is more than    //
// one plugin that can extend the same base class. The "<constructor>"  //
// should be the constructor or a static method that generates an       //
// instance of the specified class. Global methods should start with    //
// "::" in their name, like "::CreateFitter()".                         //
// Instead of being a shared library a plugin can also be a CINT        //
// script, so instead of libDialog.so one can have Dialog.C.            //
// The * is a placeholder in case there is no need for a URI to         //
// differentiate between different plugins for the same base class.     //
// For the default plugins see $ROOTSYS/etc/system.rootrc.              //
//                                                                      //
// Plugin handlers can also be registered at run time, e.g.:            //
//                                                                      //
//   gPluginMgr->AddHandler("TSQLServer", "^sqlite:",                   //
//                          "TSQLiteServer", "RSQLite",                 //
//             "TSQLiteServer(const char*,const char*,const char*)");   //
//                                                                      //
// A list of currently defined handlers can be printed using:           //
//                                                                      //
//   gPluginMgr->Print(); // use option="a" to see ctors                //
//                                                                      //
// The use of the plugin library manager removes all textual references //
// to hard-coded class and library names and the resulting dependencies //
// in the base classes. The plugin manager is used to extend a.o.       //
// TFile, TSQLServer, TGrid, etc. functionality.                        //
//                                                                      //
//////////////////////////////////////////////////////////////////////////

#include "TObject.h"
#include "TString.h"
#include "TMethodCall.h"
#include "TVirtualMutex.h"
#include "TInterpreter.h"
#include "TClass.h"

class TEnv;
class TList;
class THashTable;
class TFunction;
class TPluginManager;

#include <atomic>
#include <mutex>

class TPluginHandler : public TObject {

friend class TPluginManager;

private:
   using AtomicInt_t = std::atomic<Int_t>;

   TString      fBase;      // base class which will be extended by plugin
   TString      fRegexp;    // regular expression which must be matched in URI
   TString      fClass;     // class to be loaded from plugin library
   TString      fPlugin;    // plugin library which should contain fClass
   TString      fCtor;      // ctor used to instantiate object of fClass
   TString      fOrigin;    // origin of plugin handler definition
   TMethodCall *fCallEnv;   ///<!ctor method call environment
   TFunction   *fMethod;    ///<!ctor method or global function
   std::vector<std::string> fArgTupleTypeInfo; // Cached type_info name for fast comparison
   AtomicInt_t  fCanCall;   ///<!if 1 fCallEnv is ok, -1 fCallEnv is not ok, 0 fCallEnv not setup yet.
   Bool_t       fIsMacro;   // plugin is a macro and not a library
   Bool_t       fIsGlobal;  // plugin ctor is a global function
   std::once_flag fLoadStatusFlag; // plugin is loaded
   Int_t fLoadStatus;              // cached plugin load status

   TPluginHandler() :
      fBase(), fRegexp(), fClass(), fPlugin(), fCtor(), fOrigin(),
      fCallEnv(nullptr), fMethod(nullptr), fCanCall(0), fIsMacro(kTRUE), fIsGlobal(kTRUE) { }
   TPluginHandler(const char *base, const char *regexp,
                  const char *className, const char *pluginName,
                  const char *ctor, const char *origin);
   TPluginHandler(const TPluginHandler &) = delete;
   TPluginHandler& operator=(const TPluginHandler &) = delete;

   ~TPluginHandler();

   const char *GetBase() const { return fBase; }
   const char *GetRegexp() const { return fRegexp; }
   const char *GetPlugin() const { return fPlugin; }
   const char *GetCtor() const { return fCtor; }
   const char *GetOrigin() const { return fOrigin; }

   Bool_t CanHandle(const char *base, const char *uri);
   void   SetupCallEnv();

   Bool_t CheckForExecPlugin(Int_t nargs);
   void LoadPluginImpl();

   bool CheckNameMatch(int iarg, const std::type_info &ti);

   template <typename T0>
   bool CheckExactMatch(int iarg, const T0&)
   {
      return CheckNameMatch(iarg, typeid(T0));
   }

   template <typename T0, typename... T>
   bool CheckExactMatch(int iarg, const T0&, const T&... params)
   {
      if (CheckNameMatch(iarg, typeid(T0)))
         return CheckExactMatch(iarg + 1, params...);
      else
         return false;
   }

   template <typename... T>
   bool ExactArgMatch(const T&... params)
   {
      constexpr auto nargs = sizeof...(T);
      auto name = typeid(std::tuple<T...>).name();
      if (!fArgTupleTypeInfo[nargs - 1].empty())
         return name == fArgTupleTypeInfo[nargs - 1];

      R__LOCKGUARD(gInterpreterMutex);
      if (!CheckExactMatch<T...>(0, params...))
         return false;
      fArgTupleTypeInfo[nargs - 1] = name;
      return true;
   }

   template <typename... T> Longptr_t ExecPluginImpl(const T&... params)
   {
      constexpr auto nargs = sizeof...(params);
      if (!CheckForExecPlugin((Int_t)nargs)) return 0;

      Longptr_t ret;

      // check if types match such that function can be called directly
      if (ExactArgMatch<T...>(params...)) {
         const void *args[nargs] = {&params...};
         // locking is handled within this call, but will only be needed
         // on the first call for initialization
         fCallEnv->Execute(nullptr, args, nargs, &ret);

         return ret;
      }

      // Fallback to slow path with type conversion for arguments.

      // The fCallEnv object is shared, since the PluginHandler is a global
      // resource ... and both SetParams and Execute ends up taking the lock
      // individually anyway ...

      R__LOCKGUARD(gInterpreterMutex);
      fCallEnv->SetParams(params...);

      fCallEnv->Execute(ret);

      return ret;
   }

public:
   const char *GetClass() const { return fClass; }
   Int_t       CheckPlugin() const;
   Int_t       LoadPlugin();

   // zero arguments case
   Longptr_t ExecPluginImpl()
   {
      if (!CheckForExecPlugin(0))
         return 0;

      Longptr_t ret;
      // locking is handled within this call, but will only be needed
      // on the first call for initialization
      fCallEnv->Execute(nullptr, nullptr, 0, &ret);

      return ret;
   }

   // zero arguments case
   Longptr_t ExecPlugin(int nargs)
   {
      // For backward compatibility.
      if ((gDebug > 1) && (nargs != 0)) {
         Warning("ExecPlugin", "Announced number of args different from the real number of argument passed %d vs 0",
                 nargs);
      }

      return ExecPluginImpl();
   }

   template <typename... T> Longptr_t ExecPlugin(int nargs, const T&... params)
   {
      // For backward compatibility.
      if ((gDebug > 1) && (nargs != (int)sizeof...(params))) {
         Warning("ExecPlugin","Announced number of args different from the real number of argument passed %d vs %lu",
                 nargs, (unsigned long)sizeof...(params) );
      }
      return ExecPluginImpl<T...>(params...);
   }

   void        Print(Option_t *opt = "") const override;

   ClassDefOverride(TPluginHandler,3)  // Handler for plugin libraries
};


class TPluginManager : public TObject {

private:
   TList      *fHandlers;     // list of plugin handlers
   THashTable *fBasesLoaded;  ///<! table of base classes already checked or loaded
   Bool_t      fReadingDirs;  ///<! true if we are running LoadHandlersFromPluginDirs

   TPluginManager(const TPluginManager &) = delete;
   TPluginManager& operator=(const TPluginManager &) = delete;
   void   LoadHandlerMacros(const char *path);

public:
   TPluginManager();
   ~TPluginManager();

   void   LoadHandlersFromEnv(TEnv *env);
   void   LoadHandlersFromPluginDirs(const char *base = nullptr);
   void   AddHandler(const char *base, const char *regexp,
                     const char *className, const char *pluginName,
                     const char *ctor = nullptr, const char *origin = nullptr);
   void   RemoveHandler(const char *base, const char *regexp = nullptr);

   TPluginHandler *FindHandler(const char *base, const char *uri = nullptr);

   void   Print(Option_t *opt = "") const override;
   Int_t  WritePluginMacros(const char *dir, const char *plugin = nullptr) const;
   Int_t  WritePluginRecords(const char *envFile, const char *plugin = nullptr) const;

   ClassDefOverride(TPluginManager,1)  // Manager for plugin handlers
};

R__EXTERN TPluginManager *gPluginMgr;

#endif
