//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================
#ifndef __smtk_common_Registry_h
#define __smtk_common_Registry_h

#include "smtk/common/CompilerInformation.h"

#include "smtk/TupleTraits.h"

#include <map>
#include <memory>
#include <type_traits>
#include <utility>
#include <vector>

namespace smtk
{
namespace common
{

/// Resources and operations both have optional managers that can be used as an
/// interface for their construction, to track their existence and to attach
/// observers to their actions. Additional modeling sessions and other plugins
/// can augment the functionality of these managers, but they must register
/// themselves with a manager to do so. Additionally, plugins that can be
/// unloaded must remove themselves from managers, or else managers may try to
/// call routines that are no longer in memory. To facilitate this registration
/// process, we introduce a Registry that accepts as template parameters a
/// Registrar, or class with pairs of "registerTo" and "unregisterFrom" methods
/// for each manager type to which the plugin  registers, and a variable number
/// of manager classes. The Registrar's registration methods are called for
/// each appropriate manager upon the Registry's construction, and the
/// Registrar's unregister methods are called for each manager (if they are
/// still in memory) upon the Registry's deletion.
///
/// The Registry class is implemented via templates to facilitate a) the
/// registration to only a subset of available managers, b) the abilty to
/// accommodate additional managers in the future without incurring a runtime
/// dispatch penalty, c) construction of an object that can call new code
/// during its own destruction (pure virtual methods should not be called in a
/// base class's destructor), and d) to keep homologous Manager clsses from
/// being entwined via a superfluous base class.

namespace detail
{
/// This template class takes as arguments a Registrar and a Manager. It
/// performs a compile-time check that the Registrar either registers to and
/// unregisters from the manager type, or does neither (this is a check to
/// ensure that Registrars clean up after themselves when their plugins go
/// away). The resulting compile-time value "type" reflects whether or not the
/// Registrar registers to the Manager.
template <typename Registrar, typename Manager>
class RegistersTo
{
  template <typename X>
  static std::true_type testRegisterTo(
    decltype(std::declval<Registrar>().registerTo(std::declval<std::shared_ptr<X> >()))*);
  template <typename X>
  static std::false_type testRegisterTo(...);

  template <typename X>
  static std::true_type testUnregisterFrom(
    decltype(std::declval<Registrar>().unregisterFrom(std::declval<std::shared_ptr<X> >()))*);
  template <typename X>
  static std::false_type testUnregisterFrom(...);

  static_assert(std::is_same<decltype(testRegisterTo<Manager>(nullptr)),
                  decltype(testUnregisterFrom<Manager>(nullptr))>::value == true,
    "Registrar must be able to both register and unregister from a manager.");

public:
  using type = decltype(testRegisterTo<Manager>(nullptr));
};

/// MaybeRegister accepts a Registrar, a Manager and a boolean type. If the
/// boolean type is false, then the Registrar is not associated with the
/// Manager and nothing happens. If the type is true, then a Schwartz counter
/// for each Manager is set up to call the Registrar exactly once when a
/// Registry enters the program's scope and again when the Registry leaves
/// scope.
template <typename Registrar, typename Manager, typename Choice>
class MaybeRegister;

template <typename Registrar, typename Manager>
class MaybeRegister<Registrar, Manager, std::false_type>
{
public:
  MaybeRegister(const std::shared_ptr<Manager>&) {}
  virtual ~MaybeRegister() {}

  template <typename M>
  bool has(const std::shared_ptr<M>&) const
  {
    return false;
  }
};

template <typename Registrar, typename Manager>
class MaybeRegister<Registrar, Manager, std::true_type>
{
public:
  MaybeRegister(const std::shared_ptr<Manager>& manager)
    : m_Manager(manager)
  {
    if (managers()[m_Manager]++ == 0)
    {
      Registrar().registerTo(manager);
    }
  }

  ~MaybeRegister()
  {
    std::shared_ptr<Manager> manager = m_Manager.lock();
    if (manager && --managers()[m_Manager] == 0)
    {
      Registrar().unregisterFrom(manager);
    }
  }

  template <typename M>
  bool has(const std::shared_ptr<M>&) const
  {
    return false;
  }

  bool has(const std::shared_ptr<Manager>& manager) const
  {
    auto m = m_Manager.lock();
    return m && m == manager;
  }

private:
  std::weak_ptr<Manager> m_Manager;
  static std::map<std::weak_ptr<Manager>, std::size_t, std::owner_less<std::weak_ptr<Manager> > >&
  managers();
};

template <typename Registrar, typename Manager>
std::map<std::weak_ptr<Manager>, std::size_t, std::owner_less<std::weak_ptr<Manager> > >&
MaybeRegister<Registrar, Manager, std::true_type>::managers()
{
  static std::map<std::weak_ptr<Manager>, std::size_t, std::owner_less<std::weak_ptr<Manager> > >
    managers;
  return managers;
}

/// Registrars may declare dependencies to other Registrars by defining a type
/// "Dependencies" as a std::tuple of additional Registrars. This declaration is
/// optional; if omitted, then a Registrar will only register the things it
/// explicitly lists. The detail::Dependencies class either returns a
/// Registrar's dependencies or an empty tuple if none are defined.
template <typename Registrar>
class Dependencies
{
  template <typename R>
  static typename R::Dependencies deps(typename R::Dependencies*);

  template <typename R>
  static std::tuple<> deps(...);

  template <typename>
  struct is_tuple : std::false_type
  {
  };

  template <typename... T>
  struct is_tuple<std::tuple<T...> > : std::true_type
  {
  };

public:
  using type = decltype(deps<Registrar>(nullptr));

  static_assert(
    is_tuple<type>::value == true, "Registrar dependencies must be given as a std::tuple.");
};

/// AllDependenciesWithDuplicates is an intermediate step in constructing a flat
/// list of all of the dependencies of a Registrar.
template <typename... Registrars>
struct AllDependenciesWithDuplicates;

template <typename Registrar, typename... Registrars>
struct AllDependenciesWithDuplicates<Registrar, Registrars...>
{
  // We are combining:
  //  a) the dependencies of Registrar,
  //  b) the dependencies of Registrar's dependencies, and
  //  c) the dependencies of the remaining Registrars in the parameter pack.
  using type = decltype(std::tuple_cat(
    std::declval<typename Dependencies<Registrar>::type>(),
    std::declval<
      typename AllDependenciesWithDuplicates<typename Dependencies<Registrar>::type>::type>(),
    std::declval<typename AllDependenciesWithDuplicates<Registrars...>::type>()));
};

// Specialization to strip out the contents of tuples.
template <typename T>
struct AllDependenciesWithDuplicates<std::tuple<T> >
{
  using type = typename AllDependenciesWithDuplicates<T>::type;
};

// Specialization to handle Registrars with no dependencies.
template <>
struct AllDependenciesWithDuplicates<std::tuple<> >
{
  using type = std::tuple<>;
};

// Specialization to end the list walk.
template <>
struct AllDependenciesWithDuplicates<>
{
  using type = std::tuple<>;
};

/// AllDependencies accepts a Registrar and returns a tuple of all of its
/// dependencies. It performs a recursive search across its dependent
/// Registrars.
template <typename Registrar>
struct AllDependencies
{
  using type = decltype(typename smtk::remove_from_tuple<Registrar,
    typename smtk::unique_tuple<typename smtk::flatten_tuple<
      typename AllDependenciesWithDuplicates<Registrar>::type>::type>::type>::type());
};
}

/// Since the Registry class is a template class, we introduce a non-templated
/// base class RegistryBase so that Registries can be manipulated in aggregate.
/// RegistryBase also holds the registries associated with the Registry's
/// dependencies.
class RegistryBase
{
public:
  virtual ~RegistryBase()
  {
    for (auto it = m_registries.begin(); it != m_registries.end(); ++it)
    {
      delete (*it);
    }
  }

protected:
  std::vector<RegistryBase*> m_registries;
};

/// Given a Registrar (a class that describes how to register and unregister a
/// plugin) and one or more Managers, the Registry is a class composed of
/// MaybeRegister-s for the Registrar and each Manager (and also a base class
/// RegistryBase for good measure). The lifetime of a plugin's registration to
/// the managers passed to the Registry is then guaranteed to be at least as
/// long as the lifetime of the Registry. Multiple Registries can exist with
/// overlapping registration conditions; multiple registration and early
/// unregistration are avoided via the use of Schwartz counters in
/// MaybeRegister.
///
/// Both gcc and clang are savvy to template class descriptions using parameter
/// packs. MSVC isn't quite there yet. The following code is bifurcated
/// according to compiler type because, even though the two code paths produce
/// functionally equivalent code, the first implementation does so with much
/// less compile-time (and library size) overhead. Eventually, only the first
/// implementation should be used.

#ifndef SMTK_MSVC

template <typename Registrar, typename Manager, typename... T>
class Registry
  : public detail::MaybeRegister<Registrar, Manager,
      typename detail::RegistersTo<Registrar, Manager>::type>,
    public detail::MaybeRegister<Registrar, T, typename detail::RegistersTo<Registrar, T>::type>...,
    public RegistryBase
{
public:
  Registry(const std::shared_ptr<Manager>& manager, const std::shared_ptr<T>&... managers)
    : detail::MaybeRegister<Registrar, Manager,
        typename detail::RegistersTo<Registrar, Manager>::type>(manager)
    , detail::MaybeRegister<Registrar, T, typename detail::RegistersTo<Registrar, T>::type>(
        managers)...
    , RegistryBase()
  {
    this->registerDependencies<0, typename detail::AllDependencies<Registrar>::type>(
      manager, std::forward<const std::shared_ptr<T>&>(managers)...);
  }

  template <typename M>
  bool has(const std::shared_ptr<M>& m)
  {
    auto tmp = dynamic_cast<
      detail::MaybeRegister<Registrar, M, typename detail::RegistersTo<Registrar, M>::type>*>(this);
    return tmp != nullptr && tmp->has(m);
  }

  virtual ~Registry() {}

  typedef int DoNotRegisterDependencies;

  Registry(DoNotRegisterDependencies, const std::shared_ptr<Manager>& manager,
    const std::shared_ptr<T>&... managers)
    : detail::MaybeRegister<Registrar, Manager,
        typename detail::RegistersTo<Registrar, Manager>::type>(manager)
    , detail::MaybeRegister<Registrar, T, typename detail::RegistersTo<Registrar, T>::type>(
        managers)...
    , RegistryBase()
  {
  }

private:
  template <std::size_t I, typename Tuple>
  inline typename std::enable_if<I != std::tuple_size<Tuple>::value>::type registerDependencies(
    const std::shared_ptr<Manager>& manager, const std::shared_ptr<T>&... managers)
  {
    m_registries.push_back(new Registry<typename std::tuple_element<I, Tuple>::type, Manager, T...>(
      0, manager, std::forward<const std::shared_ptr<T>&>(managers)...));
    return registerDependencies<I + 1, Tuple>(
      manager, std::forward<const std::shared_ptr<T>&>(managers)...);
  }

  template <std::size_t I, typename Tuple>
  inline typename std::enable_if<I == std::tuple_size<Tuple>::value>::type registerDependencies(
    const std::shared_ptr<Manager>&, const std::shared_ptr<T>&...)
  {
    return;
  }
};

#else

namespace detail
{
struct Sentinel
{
};
}

template <typename Registrar, typename Manager = detail::Sentinel, typename... T>
class Registry : public detail::MaybeRegister<Registrar, Manager,
                   typename detail::RegistersTo<Registrar, Manager>::type>,
                 public Registry<Registrar, T...>
{
public:
  Registry(const std::shared_ptr<Manager>& manager, const std::shared_ptr<T>&... managers)
    : detail::MaybeRegister<Registrar, Manager,
        typename detail::RegistersTo<Registrar, Manager>::type>(manager)
    , Registry<Registrar, T...>(managers...)
  {
    this->registerDependencies<0, typename detail::AllDependencies<Registrar>::type>(
      manager, std::forward<const std::shared_ptr<T>&>(managers)...);
  }

  virtual ~Registry() {}

  template <typename M>
  bool has(const std::shared_ptr<M>& m)
  {
    auto tmp = dynamic_cast<
      detail::MaybeRegister<Registrar, M, typename detail::RegistersTo<Registrar, M>::type>*>(this);
    return tmp != nullptr && tmp->has(m);
  }

  Registry(int, const std::shared_ptr<Manager>& manager, const std::shared_ptr<T>&... managers)
    : detail::MaybeRegister<Registrar, Manager,
        typename detail::RegistersTo<Registrar, Manager>::type>(manager)
    , Registry<Registrar, T...>(managers...)
  {
  }

private:
  template <std::size_t I, typename Tuple>
  inline typename std::enable_if<I != std::tuple_size<Tuple>::value>::type registerDependencies(
    const std::shared_ptr<Manager>& manager, const std::shared_ptr<T>&... managers)
  {
    m_registries.push_back(new Registry<typename std::tuple_element<I, Tuple>::type, Manager, T...>(
      0, manager, std::forward<const std::shared_ptr<T>&>(managers)...));
    return registerDependencies<I + 1, Tuple>(
      manager, std::forward<const std::shared_ptr<T>&>(managers)...);
  }

  template <std::size_t I, typename Tuple>
  inline typename std::enable_if<I == std::tuple_size<Tuple>::value>::type registerDependencies(
    const std::shared_ptr<Manager>&, const std::shared_ptr<T>&...)
  {
    return;
  }
};

template <typename Registrar>
class Registry<Registrar, detail::Sentinel> : public RegistryBase
{
public:
  Registry()
    : RegistryBase()
  {
  }

  virtual ~Registry() {}
};

#endif
}
}

#endif
