/**********************************************************************

  Audacity: A Digital Audio Editor

  PluginManager.h

  Leland Lucius

**********************************************************************/

#ifndef __AUDACITY_PLUGINMANAGER_H__
#define __AUDACITY_PLUGINMANAGER_H__

#include <wx/defs.h>

#include <map>
#include <memory>

// Tenacity libraries
#include <lib-components/EffectInterface.h>
#include <lib-components/PluginInterface.h>
#include <lib-strings/wxArrayStringEx.h>

class wxArrayString;
class FileConfig;

///////////////////////////////////////////////////////////////////////////////
//
// PluginDescriptor
//
///////////////////////////////////////////////////////////////////////////////

typedef enum : unsigned {
   PluginTypeNone = 0,          // 2.1.0 placeholder entries...not used by 2.1.1 or greater
   PluginTypeStub =1,               // Used for plugins that have not yet been registered
   PluginTypeEffect =1<<1,
   PluginTypeAudacityCommand=1<<2,
   PluginTypeExporter=1<<3,
   PluginTypeImporter=1<<4,
   PluginTypeModule=1<<5,
} PluginType;

// TODO:  Convert this to multiple derived classes
class TENACITY_DLL_API PluginDescriptor
{
public:
   PluginDescriptor();
   PluginDescriptor &operator =(PluginDescriptor &&);
   virtual ~PluginDescriptor();

   bool IsInstantiated() const;

   PluginType GetPluginType() const;

   // All plugins

   // These return untranslated strings
   const wxString & GetID() const;
   const wxString & GetProviderID() const;
   const PluginPath & GetPath() const;
   const ComponentInterfaceSymbol & GetSymbol() const;

   wxString GetUntranslatedVersion() const;
   // There is no translated version

   wxString GetVendor() const;

   bool IsEnabled() const;
   bool IsValid() const;

   void SetEnabled(bool enable);
   void SetValid(bool valid);

   // Effect plugins only

   // Internal string only, no translated counterpart!
   // (Use Effect::GetFamilyName instead)
   // This string persists in configuration files
   // So config compatibility will break if it is changed across Audacity versions
   wxString GetEffectFamily() const;

   EffectType GetEffectType() const;
   bool IsEffectDefault() const;
   bool IsEffectInteractive() const;
   bool IsEffectLegacy() const;
   bool IsEffectRealtime() const;
   bool IsEffectAutomatable() const;

   // Importer plugins only

   const wxString & GetImporterIdentifier() const;
   const TranslatableString & GetImporterFilterDescription() const;
   const FileExtensions & GetImporterExtensions() const;

private:
   friend class PluginManager;

   ComponentInterface *GetInstance();
   void SetInstance(std::unique_ptr<ComponentInterface> instance);

   void SetPluginType(PluginType type);

   // These should be passed an untranslated value
   void SetID(const PluginID & ID);
   void SetProviderID(const PluginID & providerID);
   void SetPath(const PluginPath & path);
   void SetSymbol(const ComponentInterfaceSymbol & symbol);

   // These should be passed an untranslated value wrapped in XO() so
   // the value will still be extracted for translation
   void SetVersion(const wxString & version);
   void SetVendor(const wxString & vendor);

   // "family" should be an untranslated string wrapped in wxT()
   void SetEffectFamily(const wxString & family);
   void SetEffectType(EffectType type);
   void SetEffectDefault(bool dflt);
   void SetEffectInteractive(bool interactive);
   void SetEffectLegacy(bool legacy);
   void SetEffectRealtime(bool realtime);
   void SetEffectAutomatable(bool automatable);

   void SetImporterIdentifier(const wxString & identifier);
   void SetImporterFilterDescription(const TranslatableString & filterDesc);
   void SetImporterExtensions(FileExtensions extensions);

   // Common

   // Among other purposes, PluginDescriptor acts as the resource handle,
   // or smart pointer, to a resource created in a plugin library, and is responsible
   // for a cleanup of this pointer.
   std::unique_ptr<ComponentInterface> muInstance; // may be null for a module
   ComponentInterface *mInstance;

   PluginType mPluginType;

   wxString mID;
   PluginPath mPath;
   ComponentInterfaceSymbol mSymbol;
   wxString mVersion;
   wxString mVendor;
   wxString mProviderID;
   bool mEnabled;
   bool mValid;

   // Effects

   wxString mEffectFamily;
   EffectType mEffectType;
   bool mEffectInteractive;
   bool mEffectDefault;
   bool mEffectLegacy;
   bool mEffectRealtime;
   bool mEffectAutomatable;

   // Importers

   wxString mImporterIdentifier;
   FileExtensions mImporterExtensions;
};

///////////////////////////////////////////////////////////////////////////////
//
// PluginManager
//
///////////////////////////////////////////////////////////////////////////////

typedef std::map<PluginID, PluginDescriptor> PluginMap;

typedef wxArrayString PluginIDs;

class PluginRegistrationDialog;

class TENACITY_DLL_API PluginManager final : public PluginManagerInterface
{
public:

   RegistryPath GetPluginEnabledSetting( const PluginID &ID ) const;
   RegistryPath GetPluginEnabledSetting( const PluginDescriptor &desc ) const;

   // PluginManagerInterface implementation

   bool IsPluginRegistered(
      const PluginPath &path, const TranslatableString *pSymbol) override;

   const PluginID & RegisterPlugin(ModuleInterface *module) override;
   const PluginID & RegisterPlugin(ModuleInterface *provider, ComponentInterface *command);
   const PluginID & RegisterPlugin(ModuleInterface *provider, EffectDefinitionInterface *effect, int type) override;

   void FindFilesInPathList(const wxString & pattern,
                                    const FilePaths & pathList,
                                    FilePaths & files,
                                    bool directories = false) override;

   bool HasSharedConfigGroup(const PluginID & ID, const RegistryPath & group) /* not override */;
   bool GetSharedConfigSubgroups(const PluginID & ID, const RegistryPath & group, RegistryPaths &subgroups) override;

   bool GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, wxString & value, const wxString & defval = _T("")) override;
   bool GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, int & value, int defval = 0) override;
   bool GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, bool & value, bool defval = false) override;
   bool GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, float & value, float defval = 0.0) override;
   bool GetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, double & value, double defval = 0.0) override;

   bool SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const wxString & value) override;
   bool SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const int & value) override;
   bool SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const bool & value) override;
   bool SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const float & value) override;
   bool SetSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const double & value) override;

   bool RemoveSharedConfigSubgroup(const PluginID & ID, const RegistryPath & group) override;
   bool RemoveSharedConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) override;

   bool HasPrivateConfigGroup(const PluginID & ID, const RegistryPath & group) /* not override */;
   bool GetPrivateConfigSubgroups(const PluginID & ID, const RegistryPath & group, RegistryPaths &subgroups) override;

   bool GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, wxString & value, const wxString & defval = _T("")) override;
   bool GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, int & value, int defval = 0) override;
   bool GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, bool & value, bool defval = false) override;
   bool GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, float & value, float defval = 0.0) override;
   bool GetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, double & value, double defval = 0.0) override;

   bool SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const wxString & value) override;
   bool SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const int & value) override;
   bool SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const bool & value) override;
   bool SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const float & value) override;
   bool SetPrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key, const double & value) override;

   bool RemovePrivateConfigSubgroup(const PluginID & ID, const RegistryPath & group) override;
   bool RemovePrivateConfig(const PluginID & ID, const RegistryPath & group, const RegistryPath & key) override;

   // PluginManager implementation

   void Initialize();
   void Terminate();

   bool DropFile(const wxString &fileName);

   static PluginManager & Get();

   static PluginID GetID(ModuleInterface *module);
   static PluginID GetID(ComponentInterface *command);
   static PluginID GetID(EffectDefinitionInterface *effect);

   // This string persists in configuration files
   // So config compatibility will break if it is changed across Audacity versions
   static wxString GetPluginTypeString(PluginType type);

   int GetPluginCount(PluginType type);
   const PluginDescriptor *GetPlugin(const PluginID & ID) const;

   //! @name iteration over plugins of certain types, supporting range-for syntax
   //! @{
   class Iterator {
   public:
      //! Iterates all, even disabled
      explicit Iterator(PluginManager &manager);
      //! Iterates only enabled and matching plugins, with family enabled too if an effect
      Iterator(PluginManager &manager,
         int pluginType //!< bitwise or of values in PluginType
      );
      //! Iterates only enabled and matching effects, with family enabled too
      Iterator(PluginManager &manager, EffectType type);
      bool operator != (int) const {
         return mIterator != mPm.mPlugins.end();
      }
      Iterator &operator ++ ();
      auto &operator *() const { return mIterator->second; }
   private:
      void Advance(bool incrementing);
      const PluginManager &mPm;
      PluginMap::iterator mIterator;
      EffectType mEffectType{ EffectTypeNone };
      int mPluginType{ PluginTypeNone };
   };
   struct Range {
      Iterator first;
      Iterator begin() const { return first; }
      int end() const { return 0; }
   };

   Range AllPlugins() { return { Iterator{ *this } }; }
   Range PluginsOfType(int type) { return { Iterator{ *this, type } }; }
   Range EffectsOfType(EffectType type) { return { Iterator{ *this, type } }; }
   //! @}

   bool IsPluginEnabled(const PluginID & ID);
   void EnablePlugin(const PluginID & ID, bool enable);

   const ComponentInterfaceSymbol & GetSymbol(const PluginID & ID);
   ComponentInterface *GetInstance(const PluginID & ID);

   void CheckForUpdates(bool bFast = false);

   //! Used only by Nyquist Workbench module
   const PluginID & RegisterPlugin(
      std::unique_ptr<EffectDefinitionInterface> effect, PluginType type );
   void UnregisterPlugin(const PluginID & ID);

   //! Load from preferences
   void Load();
   //! Save to preferences
   void Save();

private:
   // private! Use Get()
   PluginManager();
   ~PluginManager();

   void LoadGroup(FileConfig *pRegistry, PluginType type);
   void SaveGroup(FileConfig *pRegistry, PluginType type);

   PluginDescriptor & CreatePlugin(const PluginID & id, ComponentInterface *ident, PluginType type);

   FileConfig *GetSettings();

   bool HasGroup(const RegistryPath & group);
   bool GetSubgroups(const RegistryPath & group, RegistryPaths & subgroups);

   bool GetConfig(const RegistryPath & key, wxString & value, const wxString & defval = L"");
   bool GetConfig(const RegistryPath & key, int & value, int defval = 0);
   bool GetConfig(const RegistryPath & key, bool & value, bool defval = false);
   bool GetConfig(const RegistryPath & key, float & value, float defval = 0.0);
   bool GetConfig(const RegistryPath & key, double & value, double defval = 0.0);

   bool SetConfig(const RegistryPath & key, const wxString & value);
   bool SetConfig(const RegistryPath & key, const int & value);
   bool SetConfig(const RegistryPath & key, const bool & value);
   bool SetConfig(const RegistryPath & key, const float & value);
   bool SetConfig(const RegistryPath & key, const double & value);

   /* Return values are keys for lookup in a config file */
   RegistryPath SettingsPath(const PluginID & ID, bool shared);
   RegistryPath SharedGroup(const PluginID & ID, const RegistryPath & group);
   RegistryPath SharedKey(const PluginID & ID, const RegistryPath & group, const RegistryPath & key);
   RegistryPath PrivateGroup(const PluginID & ID, const RegistryPath & group);
   RegistryPath PrivateKey(const PluginID & ID, const RegistryPath & group, const RegistryPath & key);

   // The PluginID must be kept unique.  Since the wxFileConfig class does not preserve
   // case, we use base64 encoding.
   wxString ConvertID(const PluginID & ID);
   wxString b64encode(const void *in, int len);
   int b64decode(const wxString &in, void *out);

private:
   friend std::default_delete<PluginManager>;
   static std::unique_ptr<PluginManager> mInstance;

   bool IsDirty();
   void SetDirty(bool dirty = true);
   std::unique_ptr<FileConfig> mSettings;

   bool mDirty;
   int mCurrentIndex;

   PluginMap mPlugins;
};

// Defining these special names in the low-level PluginManager.h
// is unfortunate
// Internal name should be stable across versions
#define NYQUIST_PROMPT_ID wxT("Nyquist Prompt")
// User-visible name might change in later versions
#define NYQUIST_PROMPT_NAME XO("Nyquist Prompt")

#endif /* __AUDACITY_PLUGINMANAGER_H__ */
