#ifndef TILE_BASE_FUTURE_FUTURE_IMPL_H
#define TILE_BASE_FUTURE_FUTURE_IMPL_H

#pragma once
#include "tile/base/future/future.h"

#include "tile/base/internal/logging.h"

namespace tile {
namespace future {
template<typename... Ts>
template<typename... Us, typename>
Future<Ts...>::Future(futurize_values_t, Us &&...imms) : core_(std::make_shared<Core<Ts...>>(GetDefaultExecutor()))
{
    core_->SetBoxed(Boxed<Ts...>(box_values, std::forward<Us>(imms)...));
}

template<typename... Ts>
template<typename... Us, typename>
Future<Ts...>::Future(futurize_tuple_t, std::tuple<Us...> values)
    : core_(std::make_shared<Core<Ts...>>(GetDefaultExecutor()))
{
    core_->SetBoxed(Boxed<Ts...>(box_values, std::move(values)));
}

template<class... Ts>
template<class U, class>
Future<Ts...>::Future(U &&value) : core_(std::make_shared<Core<Ts...>>(GetDefaultExecutor()))
{
    static_assert(sizeof...(Ts) == 1, "");
    core_->SetBoxed(Boxed<Ts...>(box_values, std::forward<U>(value)));
}

template<class... Ts>
template<class... Us, class>
Future<Ts...>::Future(Future<Us...> &&future)
{
    Promise<Ts...> p;

    // Here we "steal" `p.GetFuture()`'s core and "install" it into ourself,
    // thus once `p` is satisfied, we're satisfied as well.
    core_              = p.GetFuture().core_;
    auto moved_promise = MakeMoveOnCopy(std::move(p));
    std::move(future).Then([moved_promise](Boxed<Us...> boxed) mutable noexcept {
        moved_promise->SetBoxed(std::move(boxed));
    });
}

template<typename... Ts>
template<typename F, typename R>
auto
Future<Ts...>::Then(F &&fun) -> enable_if_t<std::is_void<R>::value, Future<>>
{
    using NewFuture = futurize_t<R>;
    as_promise_t<NewFuture> promise(core_->GetExecutor());

    auto rc            = promise.GetFuture();

    auto moved_promise = MakeMoveOnCopy(promise);
    auto moved_fun     = MakeMoveOnCopy(std::forward<F>(fun));

    std::function<void(Boxed<Ts...> &&) noexcept> cont =
        [moved_promise, moved_fun](Boxed<Ts...> &&value) mutable noexcept {
            CallByBoxed(moved_fun.Move(), std::move(value));

            moved_promise->SetBoxed(Boxed<>(box_values));
        };

    core_->ChainAction(std::move(cont));

    return rc;
}

// if R is `Future<...>`
template<typename... Ts>
template<typename F, typename R>
auto
Future<Ts...>::Then(F &&fun) -> enable_if_t<!std::is_void<R>::value && is_future<R>::value, futurize_t<R>>
{
    using NewFuture = futurize_t<R>;
    as_promise_t<NewFuture> promise(core_->GetExecutor());

    auto rc            = promise.GetFuture();

    auto moved_promise = MakeMoveOnCopy(promise);
    auto moved_fun     = MakeMoveOnCopy(std::forward<F>(fun));

    auto cont          = [moved_promise, moved_fun](Boxed<Ts...> &&value) mutable noexcept {
        auto future = CallByBoxed(moved_fun.Move(), std::move(value));
        // R is Future<Us...>
        // build a lambda to set the value of the promise

        future.core_->ChainAction([moved_promise](as_boxed_t<NewFuture> &&nested_v) mutable noexcept {
            static_assert(std::is_same<as_boxed_t<NewFuture>, remove_reference_t<decltype(nested_v)>>::value, "");
            moved_promise->SetBoxed(std::move(nested_v));
        });
    };

    core_->ChainAction(std::move(cont));

    return rc;
}

template<typename... Ts>
template<typename F, typename R>
auto
Future<Ts...>::Then(F &&fun) -> enable_if_t<!std::is_void<R>::value && !is_future<R>::value, futurize_t<R>>
{
    using NewFuture = futurize_t<R>;
    as_promise_t<NewFuture> promise(core_->GetExecutor());

    auto rc            = promise.GetFuture();

    auto moved_promise = MakeMoveOnCopy(promise);
    auto moved_fun     = MakeMoveOnCopy(std::forward<F>(fun));

    auto cont          = [moved_promise, moved_fun](Boxed<Ts...> &&value) mutable {
        as_boxed_t<NewFuture> boxed_value(box_values, CallByBoxed(moved_fun.Move(), std::move(value)));
        moved_promise->SetBoxed(std::move(boxed_value));
    };

    core_->ChainAction(std::move(cont));
    return rc;
}

}// namespace future
}// namespace tile

#endif// TILE_BASE_FUTURE_FUTURE_IMPL_H
