/*============================================================================

The Medical Imaging Interaction Toolkit (MITK)

Copyright (c) German Cancer Research Center (DKFZ)
All rights reserved.

Use of this source code is governed by a 3-clause BSD license that can be
found in the LICENSE file.

============================================================================*/


#include <usModule.h>

#include <usModuleContext.h>
#include <usModuleActivator.h>
#include "usModulePrivate.h"
#include <usModuleResource.h>
#include <usModuleSettings.h>
#include "usCoreModuleContext_p.h"

#include <usGlobalConfig.h>
#include <MitkCppMicroServicesExports.h>

namespace us {

const std::string& Module::PROP_ID()
{
  static const std::string s("module.id");
  return s;
}
const std::string& Module::PROP_NAME()
{
  static const std::string s("module.name");
  return s;
}
const std::string& Module::PROP_LOCATION()
{
  static const std::string s("module.location");
  return s;
}
const std::string& Module::PROP_VERSION()
{
  static const std::string s("module.version");
  return s;
}

const std::string&Module::PROP_VENDOR()
{
  static const std::string s("module.vendor");
  return s;
}

const std::string&Module::PROP_DESCRIPTION()
{
  static const std::string s("module.description");
  return s;
}

const std::string&Module::PROP_AUTOLOAD_DIR()
{
  static const std::string s("module.autoload_dir");
  return s;
}

const std::string&Module::PROP_AUTOLOADED_MODULES()
{
  static const std::string s("module.autoloaded_modules");
  return s;
}

Module::Module()
: d(nullptr)
{

}

Module::~Module()
{
  delete d;
}

void Module::Init(CoreModuleContext* coreCtx,
                  ModuleInfo* info)
{
  ModulePrivate* mp = new ModulePrivate(this, coreCtx, info);
  std::swap(mp, d);
  delete mp;
}

void Module::Uninit()
{
  if (d->moduleContext != nullptr)
  {
    //d->coreCtx->listeners.HooksModuleStopped(d->moduleContext);
    d->RemoveModuleResources();
    delete d->moduleContext;
    d->moduleContext = nullptr;
    d->coreCtx->listeners.ModuleChanged(ModuleEvent(ModuleEvent::UNLOADED, this));

    d->moduleActivator = nullptr;
  }
}

bool Module::IsLoaded() const
{
  return d->moduleContext != nullptr;
}

void Module::Start()
{

  if (d->moduleContext)
  {
    MITK_WARN << "Module " << d->info.name << " already started.";
    return;
  }

  d->moduleContext = new ModuleContext(this->d);

  typedef ModuleActivator*(*ModuleActivatorHook)(void);
  ModuleActivatorHook activatorHook = nullptr;

  std::string activator_func = "_us_module_activator_instance_" + d->info.name;
  void* activatorHookSym = ModuleUtils::GetSymbol(d->info, activator_func.c_str());
  std::memcpy(&activatorHook, &activatorHookSym, sizeof(void*));

  d->coreCtx->listeners.ModuleChanged(ModuleEvent(ModuleEvent::LOADING, this));
  // try to get a ModuleActivator instance

  if (activatorHook)
  {
    try
    {
      d->moduleActivator = activatorHook();
    }
    catch (...)
    {
      MITK_ERROR << "Creating the module activator of " << d->info.name << " failed";
      delete d->moduleContext;
      d->moduleContext = nullptr;
      throw;
    }

    try
    {
      d->moduleActivator->Load(d->moduleContext);
    }
    catch (...)
    {
      MITK_ERROR << "Calling the module activator Load() method of " << d->info.name << " failed!";
      d->moduleActivator = nullptr;
      delete d->moduleContext;
      d->moduleContext = nullptr;
      throw;
    }
  }

  if (ModuleSettings::IsAutoLoadingEnabled())
  {
    const std::vector<std::string> loadedPaths = AutoLoadModules(d->info);
    if (!loadedPaths.empty())
    {
      d->moduleManifest.SetValue(PROP_AUTOLOADED_MODULES(), Any(loadedPaths));
    }
  }

  d->coreCtx->listeners.ModuleChanged(ModuleEvent(ModuleEvent::LOADED, this));
}

void Module::Stop()
{
  if (d->moduleContext == nullptr)
  {
    MITK_WARN << "Module " << d->info.name << " already stopped.";
    return;
  }

  try
  {
    d->coreCtx->listeners.ModuleChanged(ModuleEvent(ModuleEvent::UNLOADING, this));

    if (d->moduleActivator)
    {
      d->moduleActivator->Unload(d->moduleContext);
    }
  }
  catch (...)
  {
    MITK_WARN << "Calling the module activator Unload() method of " << d->info.name << " failed!";

    try
    {
      this->Uninit();
    }
    catch (...) {}

    throw;
  }

  this->Uninit();
}

ModuleContext* Module::GetModuleContext() const
{
  return d->moduleContext;
}

long Module::GetModuleId() const
{
  return d->info.id;
}

std::string Module::GetLocation() const
{
  return d->info.location;
}

std::string Module::GetName() const
{
  return d->info.name;
}

ModuleVersion Module::GetVersion() const
{
  return d->version;
}

Any Module::GetProperty(const std::string& key) const
{
  return d->moduleManifest.GetValue(key);
}

std::vector<std::string> Module::GetPropertyKeys() const
{
  return d->moduleManifest.GetKeys();
}

std::vector<ServiceReferenceU> Module::GetRegisteredServices() const
{
  std::vector<ServiceRegistrationBase> sr;
  std::vector<ServiceReferenceU> res;
  d->coreCtx->services.GetRegisteredByModule(d, sr);
  for (std::vector<ServiceRegistrationBase>::const_iterator i = sr.begin();
       i != sr.end(); ++i)
  {
    res.push_back(i->GetReference());
  }
  return res;
}

std::vector<ServiceReferenceU> Module::GetServicesInUse() const
{
  std::vector<ServiceRegistrationBase> sr;
  std::vector<ServiceReferenceU> res;
  d->coreCtx->services.GetUsedByModule(const_cast<Module*>(this), sr);
  for (std::vector<ServiceRegistrationBase>::const_iterator i = sr.begin();
       i != sr.end(); ++i)
  {
    res.push_back(i->GetReference());
  }
  return res;
}

ModuleResource Module::GetResource(const std::string& path) const
{
  if (!d->resourceContainer.IsValid())
  {
    return ModuleResource();
  }
  ModuleResource result(path, d->resourceContainer);
  if (result) return result;
  return ModuleResource();
}

std::vector<ModuleResource> Module::FindResources(const std::string& path, const std::string& filePattern,
                                                  bool recurse) const
{
  std::vector<ModuleResource> result;
  if (!d->resourceContainer.IsValid())
  {
    return result;
  }

  std::string normalizedPath = path;
  // add a leading and trailing slash
  if (normalizedPath.empty()) normalizedPath.push_back('/');
  if (*normalizedPath.begin() != '/') normalizedPath = '/' + normalizedPath;
  if (*normalizedPath.rbegin() != '/') normalizedPath.push_back('/');
  d->resourceContainer.FindNodes(d->info.name + normalizedPath,
                                 filePattern.empty() ? "*" : filePattern,
                                 recurse, result);
  return result;
}

}

using namespace us;

std::ostream& operator<<(std::ostream& os, const Module& module)
{
  os << "Module[" << "id=" << module.GetModuleId() <<
        ", loc=" << module.GetLocation() <<
        ", name=" << module.GetName() << "]";
  return os;
}

std::ostream& operator<<(std::ostream& os, Module const * module)
{
  if (module == nullptr)
  {
    return os << "null";
  }
  return operator<<(os, *module);
}
