

#include "tpluginmanager.h"
#include "tsystem.h"
#include "tconvert.h"
#include "tlogger.h"

#ifdef _WIN32
#include <windows.h>
#else

// QUALE DI QUESTI SERVE VERAMENTE??
#include <grp.h>
#include <utime.h>
#include <sys/param.h>
#include <unistd.h>
#include <sys/types.h>
#ifndef FREEBSD
#include <dirent.h>
#endif
#include <stdio.h>
#include <unistd.h>
#ifndef HAIKU
#include <sys/dir.h>
#endif
#include <sys/param.h>  // for getfsstat
#ifdef MACOSX
#include <sys/ucred.h>
#endif
#ifndef HAIKU
#include <sys/mount.h>
#endif
#include <pwd.h>
#include <dlfcn.h>
#endif

//-----------------------------------------------------------------------------

class TPluginManager::Plugin {
public:
#ifdef _WIN32
  typedef HINSTANCE Handle;
#else
  typedef void *Handle;
#endif

private:
  Handle m_handle;
  TPluginInfo m_info;

public:
  Plugin(Handle handle) : m_handle(handle) {}

  Handle getHandle() const { return m_handle; }
  const TPluginInfo &getInfo() const { return m_info; }
  void setInfo(const TPluginInfo &info) { m_info = info; }
  std::string getName() const { return m_info.getName(); }
};

//-----------------------------------------------------------------------------

typedef const TPluginInfo *TnzLibMainProcType();

//--------------------------------------------------------------

namespace {
const char *TnzLibMainProcName = "TLibMain";
#if !defined(_WIN32)
const char *TnzLibMainProcName2 = "_TLibMain";
#endif
}

//=============================================================================

TPluginManager::TPluginManager() { m_ignoreList.insert("tnzimagevector"); }

//-----------------------------------------------------------------------------

TPluginManager::~TPluginManager() {
  //   try { unloadPlugins(); } catch(...) {}
}

//-----------------------------------------------------------------------------

TPluginManager *TPluginManager::instance() {
  static TPluginManager _instance;
  return &_instance;
}

//-----------------------------------------------------------------------------

bool TPluginManager::isIgnored(std::string name) const {
  return m_ignoreList.count(toLower(name)) > 0;
}

//-----------------------------------------------------------------------------

void TPluginManager::unloadPlugins() {
  for (PluginTable::iterator it = m_pluginTable.begin();
       it != m_pluginTable.end(); ++it) {
    Plugin::Handle handle = (*it)->getHandle();
#if !(defined(LINUX) || defined(FREEBSD))
#ifdef _WIN32
    FreeLibrary(handle);
#else
    dlclose(handle);
#endif
#endif
    delete (*it);
  }
  m_pluginTable.clear();
}

//--------------------------------------------------------------

void TPluginManager::loadPlugin(const TFilePath &fp) {
  if ((int)m_loadedPlugins.count(fp) > 0) {
    TLogger::debug() << "Already loaded " << fp;
    return;
  }
  std::string name = fp.getName();
  if (isIgnored(name)) {
    TLogger::debug() << "Ignored " << fp;
    return;
  }

  TLogger::debug() << "Loading " << fp;
#ifdef _WIN32
  Plugin::Handle handle = LoadLibraryW(fp.getWideString().c_str());
#else
  Plugin::Handle handle =
      dlopen(::to_string(fp).c_str(), RTLD_NOW);  // RTLD_LAZY
#endif
  if (!handle) {
    // non riesce a caricare la libreria;
    TLogger::warning() << "Unable to load " << fp;
#ifdef _WIN32
    std::wstring getFormattedMessage(DWORD lastError);
    TLogger::warning() << ::to_string(getFormattedMessage(GetLastError()));
#else
    TLogger::warning() << dlerror();
#endif
  } else {
    m_loadedPlugins.insert(fp);
    Plugin *plugin = new Plugin(handle);
    m_pluginTable.push_back(plugin);
    // cout << "loaded" << endl;
    TnzLibMainProcType *tnzLibMain = 0;
#ifdef _WIN32
    tnzLibMain =
        (TnzLibMainProcType *)GetProcAddress(handle, TnzLibMainProcName);
#else
    tnzLibMain = (TnzLibMainProcType *)dlsym(handle, TnzLibMainProcName);
    if (!tnzLibMain)  // provo _ come prefisso
      tnzLibMain = (TnzLibMainProcType *)dlsym(handle, TnzLibMainProcName2);
#endif

    if (!tnzLibMain) {
      // La libreria non esporta TLibMain;
      TLogger::warning() << "Corrupted " << fp;

#ifdef _WIN32
      FreeLibrary(handle);
#else
      dlclose(handle);
#endif
    } else {
      const TPluginInfo *info = tnzLibMain();
      if (info) plugin->setInfo(*info);
    }
  }
}

//--------------------------------------------------------------

void TPluginManager::loadPlugins(const TFilePath &dir) {
#if defined(_WIN32)
  const std::string extension = "dll";
#elif defined(LINUX) || defined(FREEBSD) || defined(__sgi) || defined(HAIKU)
  const std::string extension = "so";
#elif defined(MACOSX)
  const std::string extension = "dylib";
#endif

  TFilePathSet dirContent = TSystem::readDirectory(dir, false);
  if (dirContent.empty()) return;

  for (TFilePathSet::iterator it = dirContent.begin(); it != dirContent.end();
       it++) {
    TFilePath fp = *it;
    if (fp.getType() != extension) continue;
    std::wstring fullpath = fp.getWideString();

#ifdef _WIN32

    bool isDebugLibrary =
        (fullpath.find(L".d.") == fullpath.size() - (extension.size() + 3));

#ifdef _DEBUG
    if (!isDebugLibrary)
#else
    if (isDebugLibrary)
#endif
      continue;

#endif

    try {
      loadPlugin(fp);
    } catch (...) {
      TLogger::warning() << "unexpected error loading " << fp;
    }
  }
}

//--------------------------------------------------------------

void TPluginManager::loadStandardPlugins() {
  TFilePath pluginsDir = TSystem::getDllDir() + "plugins";

  // loadPlugins(pluginsDir + "io");
  loadPlugins(pluginsDir + "fx");
}

//--------------------------------------------------------------

void TPluginManager::setIgnoredList(const std::set<std::string> &names) {
  m_ignoreList.clear();
  for (std::set<std::string>::const_iterator it = names.begin();
       it != names.end(); ++it)
    m_ignoreList.insert(toLower(*it));
}
