// vi:set ft=cpp: -*- Mode: C++ -*-
#pragma once

#include "type_traits"

namespace cxx {

namespace Bits {
  template< typename Res, typename ...Args >
  struct _Callable
  {
    template< typename Functor >
    static Res invoke(void *functor, Args && ...args)
    { return (*reinterpret_cast<cxx::remove_reference_t<Functor> *>(functor))(cxx::forward<Args>(args)...); }

    template< typename Func >
    static Res invoke_func_ptr(void *func, Args && ...args)
    { return reinterpret_cast<Func>(func)(cxx::forward<Args>(args)...); }
  };
}

template< typename Signature > class functor;

/**
 * This implementation of functor stores only an address of the Functor object
 * passed to its constructor, i.e. it does not copy the object itself. This
 * means the caller must be careful not to pass a Functor object that is
 * out-lived by the functor instance.
 *
 * An exception are function pointers, they are copied, and thus are not
 * affected by the above restriction.
 */
template< typename Res, typename ...Args >
class functor<Res(Args...)>
{
protected:
  typedef Res (*Func)(void *, Args && ...);
  void *_d;
  Func _f;

public:
  template< typename Functor,
            typename = cxx::enable_if_t<
              !cxx::is_same_v<cxx::remove_cvref_t<Functor>, functor>> >
  functor(Functor &&f)
  {
    // Functor is just a function pointer?
    if constexpr (cxx::is_function_v<
                    cxx::remove_pointer_t<cxx::remove_reference_t<Functor>>>)
      {
        static_assert(!cxx::is_function_v<cxx::remove_reference_t<Functor>>,
                      "Expecting function pointer not reference (prefix with &).");
        static_assert(sizeof(void *) == sizeof(f));

        // Store as copy
        _d = reinterpret_cast<void *>(f);
        _f = Bits::_Callable<Res, Args...>::template invoke_func_ptr<Functor>;
      }
    else
      {
        // Store as reference
        _d = reinterpret_cast<void *>(&f);
        _f = Bits::_Callable<Res, Args...>::template invoke<Functor>;
      }
  }

  functor() : _d(nullptr), _f(nullptr) {}

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

  explicit operator bool () const { return _f; }

  Res operator () (Args ...a) const
  { return _f(_d, cxx::forward<Args>(a)...); }

  Res operator () (Args ...a)
  { return _f(_d, cxx::forward<Args>(a)...); }
};


}


