#ifndef GNR_FORWARDER_HPP
# define GNR_FORWARDER_HPP
# pragma once

// std::size_t
#include <cstddef>

#include <functional>

#include <type_traits>

#include <utility>

namespace gnr
{

namespace detail::forwarder
{

enum : std::size_t { default_size = 4 * sizeof(void*) };

template <typename, std::size_t, bool>
class forwarder_impl2;

template <typename R, typename ...A, std::size_t N, bool E>
class forwarder_impl2<R (A...), N, E>
{
protected:
  R (*stub_)(void const*, A...) noexcept(E) {};

  std::aligned_storage_t<N> store_;

  template <typename F>
  static constexpr auto is_invocable() noexcept
  {
    return std::is_invocable_r_v<R, F, A...>;
  }

public:
  using result_type = R;

public:
  template <typename ...B>
  R operator()(B&& ...b) const noexcept(E)
  {
    //assert(stub_);
    return stub_(std::addressof(store_), std::forward<B>(b)...);
  }

  template <typename F,
    typename = std::enable_if_t<std::is_invocable_r_v<R, F, A...>>
  >
  void assign(F&& f) noexcept(noexcept(std::decay_t<F>(std::forward<F>(f))))
  {
    using functor_type = std::decay_t<F>;
    static_assert(sizeof(functor_type) <= sizeof(store_),
      "functor too large");
    static_assert(std::is_trivially_copyable<functor_type>{},
      "functor not trivially copyable");

    ::new (std::addressof(store_)) functor_type(std::forward<F>(f));

    stub_ = [](void const* const ptr, A... a) noexcept(E) -> R
      {
        return std::invoke(
          *static_cast<functor_type*>(const_cast<void*>(ptr)),
          std::forward<A>(a)...);
      };
  }
};

template <typename, std::size_t>
class forwarder_impl;

template <typename R, typename ...A, std::size_t N>
class forwarder_impl<R (A...), N> :
  public forwarder_impl2<R (A...), N, false>
{
};

template <typename R, typename ...A, std::size_t N>
class forwarder_impl<R (A...) noexcept, N> :
  public forwarder_impl2<R (A...), N, true>
{
};

}

template <typename A, std::size_t N = detail::forwarder::default_size>
class forwarder : public detail::forwarder::forwarder_impl<A, N>
{
  using inherited_t = detail::forwarder::forwarder_impl<A, N>;

public:
  enum : std::size_t { size = N };

  forwarder() = default;

  forwarder(forwarder const&) = default;

  forwarder(forwarder&&) = default;

  template <typename F,
    typename = std::enable_if_t<
      !std::is_same_v<std::decay_t<F>, forwarder> &&
      inherited_t::template is_invocable<F>()
    >
  >
  forwarder(F&& f) noexcept(noexcept(inherited_t::assign(std::forward<F>(f))))
  {
    inherited_t::assign(std::forward<F>(f));
  }

  forwarder& operator=(forwarder const&) = default;

  forwarder& operator=(forwarder&&) = default;

  template <typename F,
    typename = std::enable_if_t<
      !std::is_same_v<std::decay_t<F>, forwarder> &&
      inherited_t::template is_invocable<F>()
    >
  >
  forwarder& operator=(F&& f) noexcept(
    noexcept(inherited_t::assign(std::forward<F>(f))))
  {
    static_assert(inherited_t::template is_invocable<F>());
    return inherited_t::assign(std::forward<F>(f)), *this;
  }

  auto& operator=(std::nullptr_t) noexcept
  {
    return reset(), *this;
  }

  explicit operator bool() const noexcept
  {
    return inherited_t::stub_;
  }

  bool operator==(std::nullptr_t) noexcept
  {
    return *this;
  }

  bool operator!=(std::nullptr_t) noexcept
  {
    return !operator==(nullptr);
  }

  void assign(std::nullptr_t) noexcept
  {
    reset();
  }

  void reset() noexcept
  {
    inherited_t::stub_ = {};
  }

  void swap(forwarder& other) noexcept
  {
    std::swap(*this, other);
  }

  void swap(forwarder&& other) noexcept
  {
    std::swap(*this, std::move(other));
  }

  template <typename T>
  auto target() noexcept
  {
    return reinterpret_cast<T*>(std::addressof(inherited_t::store_));
  }

  template <typename T> 
  auto target() const noexcept
  {
    return reinterpret_cast<T const*>(std::addressof(inherited_t::store_));
  }
};

}

#endif // GNR_FORWARDER_HPP