/*************************************************************************
 *
 * Copyright 2021 Realm Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **************************************************************************/

#ifndef REALM_UTIL_FUNCTIONAL
#define REALM_UTIL_FUNCTIONAL

#include <realm/util/assert.hpp>
#include <realm/util/type_traits.hpp>

#include <functional>
#include <memory>
#include <type_traits>

namespace realm::util {

template <typename Function>
class UniqueFunction;

/**
 * A `UniqueFunction` is a move-only, type-erased functor object similar to `std::function`.
 * It is useful in situations where a functor cannot be wrapped in `std::function` objects because
 * it is incapable of being copied.  Often this happens with C++14 or later lambdas which capture a
 * `std::unique_ptr` by move.  The interface of `UniqueFunction` is nearly identical to
 * `std::function`, except that it is not copyable.
 */
template <typename RetType, typename... Args>
class UniqueFunction<RetType(Args...)> {
private:
    template <typename Functor>
    using EnableIfCallable =
        std::enable_if_t<std::conjunction_v<std::is_invocable_r<RetType, Functor, Args...>,
                                            std::negation<std::is_same<std::decay_t<Functor>, UniqueFunction>>>,
                         int>;

    struct Impl {
        virtual ~Impl() noexcept = default;
        virtual RetType call(Args&&... args) = 0;
    };

public:
    using result_type = RetType;

    ~UniqueFunction() noexcept = default;
    UniqueFunction() = default;

    UniqueFunction(const UniqueFunction&) = delete;
    UniqueFunction& operator=(const UniqueFunction&) = delete;

    UniqueFunction(UniqueFunction&&) noexcept = default;
    UniqueFunction& operator=(UniqueFunction&&) noexcept = default;

    void swap(UniqueFunction& that) noexcept
    {
        using std::swap;
        swap(this->impl, that.impl);
    }

    friend void swap(UniqueFunction& a, UniqueFunction& b) noexcept
    {
        a.swap(b);
    }

    template <typename Functor, EnableIfCallable<Functor> = 0>
    /* implicit */
    UniqueFunction(Functor&& functor)
        // This does not use make_unique() because
        // std::unique_ptr<Base>(std::make_unique<Derived>()) results in
        // std::unique_ptr<Derived> being instantiated, which can have
        // surprisingly negative effects on debug build performance.
        : impl(new SpecificImpl<std::decay_t<Functor>>(std::forward<Functor>(functor)))
    {
    }

    UniqueFunction(std::nullptr_t) noexcept {}

    RetType operator()(Args... args) const
    {
        REALM_ASSERT(static_cast<bool>(*this));
        return impl->call(std::forward<Args>(args)...);
    }

    explicit operator bool() const noexcept
    {
        return static_cast<bool>(this->impl);
    }

    template <typename T>
    const T* target() const noexcept
    {
        if (impl && typeid(*impl) == typeid(SpecificImpl<T>)) {
            return &static_cast<SpecificImpl<T>*>(impl.get())->f;
        }
        return nullptr;
    }

    /// Release ownership of the owned implementation pointer, if any.
    ///
    /// If not null, the returned pointer _must_ be used at a later point to
    /// construct a new UniqueFunction. This can be used to move UniqueFunction
    /// instances over API boundaries which do not support C++ move semantics.
    Impl* release()
    {
        return impl.release();
    }

    /// Construct a UniqueFunction using a pointer returned by release().
    ///
    /// This takes ownership of the passed pointer.
    UniqueFunction(Impl* impl)
        : impl(impl)
    {
    }

    // Needed to make `std::is_convertible<util::UniqueFunction<...>, std::function<...>>` be
    // `std::false_type`.  `UniqueFunction` objects are not convertible to any kind of
    // `std::function` object, since the latter requires a copy constructor, which the former does
    // not provide.  If you see a compiler error which references this line, you have tried to
    // assign a `UniqueFunction` object to a `std::function` object which is impossible -- please
    // check your variables and function signatures.
    template <typename Signature>
    operator std::function<Signature>() = delete;
    template <typename Signature>
    operator std::function<Signature>() const = delete;

private:
    template <typename Functor>
    struct SpecificImpl : Impl {
        template <typename F>
        explicit SpecificImpl(F&& func)
            : f(std::forward<F>(func))
        {
        }

        RetType call(Args&&... args) override
        {
            if constexpr (std::is_void_v<RetType>) {
                // The result of this call is not cast to void, to help preserve detection of
                // `[[nodiscard]]` violations.
                f(std::forward<Args>(args)...);
            }
            else {
                return f(std::forward<Args>(args)...);
            }
        }

        Functor f;
    };

    std::unique_ptr<Impl> impl;
};

/**
 * Helper to pattern-match the signatures for all combinations of const and l-value-qualifed member
 * function pointers. We don't currently support r-value-qualified call operators.
 */
template <typename>
struct UFDeductionHelper {};
template <typename Class, typename Ret, typename... Args>
struct UFDeductionHelper<Ret (Class::*)(Args...)> : TypeIdentity<Ret(Args...)> {};
template <typename Class, typename Ret, typename... Args>
struct UFDeductionHelper<Ret (Class::*)(Args...)&> : TypeIdentity<Ret(Args...)> {};
template <typename Class, typename Ret, typename... Args>
struct UFDeductionHelper<Ret (Class::*)(Args...) const> : TypeIdentity<Ret(Args...)> {};
template <typename Class, typename Ret, typename... Args>
struct UFDeductionHelper<Ret (Class::*)(Args...) const&> : TypeIdentity<Ret(Args...)> {};

/**
 * Deduction guides for UniqueFunction<Sig> that pluck the signature off of function pointers and
 * non-overloaded, non-generic function objects such as lambdas that don't use `auto` arguments.
 */
template <typename Ret, typename... Args>
UniqueFunction(Ret (*)(Args...)) -> UniqueFunction<Ret(Args...)>;
template <typename T, typename Sig = typename UFDeductionHelper<decltype(&T::operator())>::type>
UniqueFunction(T) -> UniqueFunction<Sig>;

template <typename Signature>
bool operator==(const UniqueFunction<Signature>& lhs, std::nullptr_t) noexcept
{
    return !lhs;
}

template <typename Signature>
bool operator!=(const UniqueFunction<Signature>& lhs, std::nullptr_t) noexcept
{
    return static_cast<bool>(lhs);
}

template <typename Signature>
bool operator==(std::nullptr_t, const UniqueFunction<Signature>& rhs) noexcept
{
    return !rhs;
}

template <typename Signature>
bool operator!=(std::nullptr_t, const UniqueFunction<Signature>& rhs) noexcept
{
    return static_cast<bool>(rhs);
}

} // namespace realm::util

#endif // REALM_UTIL_FUNCTIONAL
