#ifndef TILE_BASE_FUTURE_UTILS_H
#define TILE_BASE_FUTURE_UTILS_H

#pragma once

#include "tile/base/future.h"
#include "tile/base/future/basic.h"
#include "tile/base/future/impls.h"
#include "tile/base/optional.h"
#include "tile/base/thread/cond_var.h"
#include "tile/base/thread/mutex.h"
#include "tile/base/thread/scoped_lock.h"

// MakeReadyFuture
// Blocking Get
// Blocking Try Get
// WhenAll
// WhenAny

namespace tile {
namespace future {
namespace detail {
template<typename LockType, typename DurationOrTimePoint>
auto
WaitLockByCondVar(CondVar &cv, LockType &lk, const DurationOrTimePoint &timeout)
    -> enable_if_t<future::detail::is_duration<DurationOrTimePoint>::value, bool>
{
    return cv.WaitFor(lk, timeout);
}

template<typename LockType, typename DurationOrTimePoint>
auto
WaitLockByCondVar(CondVar &cv, LockType &lk, const DurationOrTimePoint &timeout)
    -> enable_if_t<future::detail::is_time_point<DurationOrTimePoint>::value, bool>
{
    return cv.WaitUntil(lk, timeout);
}

template<typename... Ts, typename DurationOrTimePoint>
auto BlockingTryGet(Future<Ts...> &&future, const DurationOrTimePoint &timeout)
    -> enable_if_t<std::is_void<unboxed_type_t<Ts...>>::value, bool>;

template<typename... Ts, typename DurationOrTimePoint>
auto BlockingTryGet(Future<Ts...> &&future, const DurationOrTimePoint &timeout)
    -> enable_if_t<!std::is_void<unboxed_type_t<Ts...>>::value, std::optional<unboxed_type_t<Ts...>>>;

template<typename... Ts>
auto BlockingGetPreservingErrors(Future<Ts...> &&future) -> Boxed<Ts...>;

template<typename... Ts, typename DurationOrTimePoint>
auto BlockingTryGetPreservingErrors(Future<Ts...> &&future,
                                    const DurationOrTimePoint &timeout) -> std::optional<Boxed<Ts...>>;

}// namespace detail

template<typename... Ts>
Future<decay_t<Ts>...> MakeReadyFuture(Ts &&...args);

template<typename... Ts>
auto BlockingGet(Future<Ts...> &&future) -> unboxed_type_t<Ts...>;

template<typename... Ts>
auto
BlockingGet(Future<Ts...> *future) -> unboxed_type_t<Ts...>
{
    return BlockingGet(std::move(*future));
}

template<typename... Ts, typename DurationOrTimePoint>
auto
BlockingTryGet(Future<Ts...> future,
               const DurationOrTimePoint &timeout) -> future::detail::optional_or_bool_t<unboxed_type_t<Ts...>>
{
    return future::detail::BlockingTryGet(std::move(future), timeout);
}

template<typename... Ts, typename DurationOrTimePoint>
auto
BlockingTryGet(Future<Ts...> *future,
               const DurationOrTimePoint &timeout) -> detail::optional_or_bool_t<unboxed_type_t<Ts...>>
{
    return BlockingTryGet(std::move(*future), timeout);
}

}// namespace future
}// namespace tile

namespace tile {
namespace future {
namespace detail {
template<typename... Ts>
auto
BlockingGetPreservingErrors(Future<Ts...> &&future) -> Boxed<Ts...>
{
    Mutex lock;
    CondVar cv;
    std::optional<Boxed<Ts...>> receiver;

    std::move(future).Then([&](Boxed<Ts...> &&boxed) {
        ScopedLock lk(lock);
        receiver.emplace(std::move(boxed));
        cv.NotifyOne();
    });
    ScopedLock lk(lock);
    cv.Wait(lk, [&] { return receiver.has_value(); });

    return std::move(*receiver);
}

template<typename... Ts, typename DurationOrTimePoint>
auto
BlockingTryGet(Future<Ts...> &&future,
               const DurationOrTimePoint &timeout) -> enable_if_t<std::is_void<unboxed_type_t<Ts...>>::value, bool>
{
    return !!BlockingTryGetPreservingErrors(std::move(future), timeout);
}

template<typename... Ts, typename DurationOrTimePoint>
auto
BlockingTryGet(Future<Ts...> &&future, const DurationOrTimePoint &timeout)
    -> enable_if_t<!std::is_void<unboxed_type_t<Ts...>>::value, std::optional<unboxed_type_t<Ts...>>>
{
    auto rc = BlockingTryGetPreservingErrors(std::move(future), timeout);
    if (rc) { return std::move(rc->Get()); }
    return std::nullopt;
}

template<typename... Ts, typename DurationOrTimePoint>
auto
BlockingTryGetPreservingErrors(Future<Ts...> &&future,
                               const DurationOrTimePoint &timeout) -> std::optional<Boxed<Ts...>>
{
    struct State {
        CondVar cv;
        Mutex lock;
        std::optional<Boxed<Ts...>> receiver;
    };

    auto state = std::make_shared<State>();

    std::move(future).Then([state](Boxed<Ts...> boxed) noexcept {
        ScopedLock lk(state->lock);
        state->receiver.emplace(std::move(boxed));
        state->cv.NotifyOne();
    });

    ScopedLock lk(state->lock);
    future::detail::WaitLockByCondVar(state->cv, lk, timeout);
    return std::move(state->receiver);
}

}// namespace detail

template<typename... Ts>
Future<decay_t<Ts>...>
MakeReadyFuture(Ts &&...args)
{
    return Future<decay_t<Ts>...>(futurize_values, std::forward<Ts>(args)...);
}

template<typename... Ts>
auto
BlockingGet(Future<Ts...> &&future) -> unboxed_type_t<Ts...>
{
    return future::detail::BlockingGetPreservingErrors(std::move(future)).Get();
}

}// namespace future
}// namespace tile
#endif// TILE_BASE_FUTURE_UTILS_H
