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

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 "usModuleHooks_p.h"

#include <usModuleEventHook.h>
#include <usModuleFindHook.h>
#include "usCoreModuleContext_p.h"
#include <usGetModuleContext.h>
#include <usModuleContext.h>
#include "usServiceReferenceBasePrivate.h"

namespace us {

ModuleHooks::ModuleHooks(CoreModuleContext* ctx)
  : coreCtx(ctx)
{
}

Module* ModuleHooks::FilterModule(const ModuleContext* mc, Module* module) const
{
  if(module == nullptr)
  {
    return nullptr;
  }

  std::vector<ServiceRegistrationBase> srl;
  coreCtx->services.Get(us_service_interface_iid<ModuleFindHook>(), srl);
  if (srl.empty())
  {
    return module;
  }
  else
  {
    std::vector<Module*> ml;
    ml.push_back(module);
    this->FilterModules(mc, ml);
    return ml.empty() ? nullptr : module;
  }
}

void ModuleHooks::FilterModules(const ModuleContext* mc, std::vector<Module*>& modules) const
{
  std::vector<ServiceRegistrationBase> srl;
  coreCtx->services.Get(us_service_interface_iid<ModuleFindHook>(), srl);
  ShrinkableVector<Module*> filtered(modules);

  std::sort(srl.begin(), srl.end());
  for (std::vector<ServiceRegistrationBase>::reverse_iterator srBaseIter = srl.rbegin(), srBaseEnd = srl.rend();
       srBaseIter != srBaseEnd; ++srBaseIter)
  {
    ServiceReference<ModuleFindHook> sr = srBaseIter->GetReference();
    ModuleFindHook* const fh = reinterpret_cast<ModuleFindHook*>(sr.d->GetService(GetModuleContext()->GetModule()));
    if (fh != nullptr)
    {
      try
      {
        fh->Find(mc, filtered);
      }
      catch (const std::exception& e)
      {
        MITK_WARN << "Failed to call Module FindHook  #" << sr.GetProperty(ServiceConstants::SERVICE_ID()).ToString()
                << ": " << e.what();
      }
      catch (...)
      {
        MITK_WARN << "Failed to call Module FindHook  #" << sr.GetProperty(ServiceConstants::SERVICE_ID()).ToString()
                << ": unknown exception type";
      }
      sr.d->UngetService(GetModuleContext()->GetModule(), false);
    }
  }
}

void ModuleHooks::FilterModuleEventReceivers(const ModuleEvent& evt,
                                             ServiceListeners::ModuleListenerMap& moduleListeners)
{
  std::vector<ServiceRegistrationBase> eventHooks;
  coreCtx->services.Get(us_service_interface_iid<ModuleEventHook>(), eventHooks);

  {
    MutexLock lock(coreCtx->listeners.moduleListenerMapMutex);
    moduleListeners = coreCtx->listeners.moduleListenerMap;
  }

  if(!eventHooks.empty())
  {
    std::vector<ModuleContext*> moduleContexts;
    for (ServiceListeners::ModuleListenerMap::iterator le = moduleListeners.begin(),
         leEnd = moduleListeners.end(); le != leEnd; ++le)
    {
      moduleContexts.push_back(le->first);
    }
    std::sort(moduleContexts.begin(), moduleContexts.end());
    moduleContexts.erase(std::unique(moduleContexts.begin(), moduleContexts.end()), moduleContexts.end());

    const std::size_t unfilteredSize = moduleContexts.size();
    ShrinkableVector<ModuleContext*> filtered(moduleContexts);

    std::sort(eventHooks.begin(), eventHooks.end());
    for (std::vector<ServiceRegistrationBase>::reverse_iterator iter = eventHooks.rbegin(),
         iterEnd = eventHooks.rend(); iter != iterEnd; ++iter)
    {
      ServiceReference<ModuleEventHook> sr;
      try
      {
        sr = iter->GetReference();
      }
      catch (const std::logic_error& e)
      {
        MITK_WARN << "Failed to get event hook service reference: " << e.what();
        continue;
      }

      ModuleEventHook* eh = reinterpret_cast<ModuleEventHook*>(sr.d->GetService(GetModuleContext()->GetModule()));
      if (eh != nullptr)
      {
        try
        {
          eh->Event(evt, filtered);
        }
        catch (const std::exception& e)
        {
          MITK_WARN << "Failed to call Module EventHook #" << sr.GetProperty(ServiceConstants::SERVICE_ID()).ToString()
                  << ": " << e.what();
        }
        catch (...)
        {
          MITK_WARN << "Failed to call Module EventHook #" << sr.GetProperty(ServiceConstants::SERVICE_ID()).ToString()
                  << ": unknown exception type";
        }
        sr.d->UngetService(GetModuleContext()->GetModule(), false);
      }
    }

    if (unfilteredSize != moduleContexts.size())
    {
      for (ServiceListeners::ModuleListenerMap::iterator le = moduleListeners.begin();
           le != moduleListeners.end();)
      {
        if(std::find(moduleContexts.begin(), moduleContexts.end(), le->first) == moduleContexts.end())
        {
          moduleListeners.erase(le++);
        }
        else
        {
          ++le;
        }
      }
    }
  }
}

}
