////////////////////////////////////////////////////////////////////////////
//
// Copyright 2019 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_OS_UTIL_EVENT_LOOP_DISPATCHER_HPP
#define REALM_OS_UTIL_EVENT_LOOP_DISPATCHER_HPP

#include <realm/object-store/util/scheduler.hpp>

#include <tuple>

namespace realm {

namespace util {
template <class F>
class EventLoopDispatcher;

template <typename... Args>
class EventLoopDispatcher<void(Args...)> {
    using Tuple = std::tuple<typename std::remove_reference<Args>::type...>;

private:
    const std::shared_ptr<util::UniqueFunction<void(Args...)>> m_func;
    const std::shared_ptr<util::Scheduler> m_scheduler = util::Scheduler::make_default();

public:
    EventLoopDispatcher(util::UniqueFunction<void(Args...)> func)
        : m_func(std::make_shared<util::UniqueFunction<void(Args...)>>(std::move(func)))
    {
    }

    const util::UniqueFunction<void(Args...)>& func() const
    {
        return *m_func;
    }

    void operator()(Args... args)
    {
        if (m_scheduler->is_on_thread()) {
            (*m_func)(std::forward<Args>(args)...);
            return;
        }
        m_scheduler->invoke(
            [scheduler = m_scheduler, func = m_func, args = std::make_tuple(std::forward<Args>(args)...)]() mutable {
                std::apply(*func, std::move(args));
                // Each invocation block will retain the scheduler, so the scheduler
                // will not be released until all blocks are called
            });
    }
};

} // namespace util

namespace _impl::ForEventLoopDispatcher {
template <typename Sig>
struct ExtractSignatureImpl {};
template <typename T, typename... Args>
struct ExtractSignatureImpl<void (T::*)(Args...)> {
    using signature = void(Args...);
};
template <typename T, typename... Args>
struct ExtractSignatureImpl<void (T::*)(Args...) const> {
    using signature = void(Args...);
};
template <typename T, typename... Args>
struct ExtractSignatureImpl<void (T::*)(Args...)&> {
    using signature = void(Args...);
};
template <typename T, typename... Args>
struct ExtractSignatureImpl<void (T::*)(Args...) const&> {
    using signature = void(Args...);
};
template <typename T, typename... Args>
struct ExtractSignatureImpl<void (T::*)(Args...) noexcept> {
    using signature = void(Args...);
};
template <typename T, typename... Args>
struct ExtractSignatureImpl<void (T::*)(Args...) const noexcept> {
    using signature = void(Args...);
};
template <typename T, typename... Args>
struct ExtractSignatureImpl<void (T::*)(Args...)& noexcept> {
    using signature = void(Args...);
};
template <typename T, typename... Args>
struct ExtractSignatureImpl<void (T::*)(Args...) const& noexcept> {
    using signature = void(Args...);
};
// Note: no && specializations since util::UniqueFunction doesn't support them, so you can't construct an
// EventLoopDispatcher from something with that anyway.

template <typename T>
using ExtractSignature = typename ExtractSignatureImpl<T>::signature;
} // namespace _impl::ForEventLoopDispatcher

namespace util {

// Deduction guide for function pointers.
template <typename... Args>
EventLoopDispatcher(void (*)(Args...)) -> EventLoopDispatcher<void(Args...)>;

// Deduction guide for callable objects, such as lambdas. Only supports types with a non-overloaded, non-templated
// call operator, so no polymorphic (auto argument) lambdas.
template <typename T, typename Sig = _impl::ForEventLoopDispatcher::ExtractSignature<decltype(&T::operator())>>
EventLoopDispatcher(const T&) -> EventLoopDispatcher<Sig>;


} // namespace util
} // namespace realm

#endif
