#ifndef TILE_BASE_FUTURE_FUTURE_H
#define TILE_BASE_FUTURE_FUTURE_H

#pragma once

#include "tile/base/future/basic.h"
#include "tile/base/future/boxed.h"
#include "tile/base/future/core.h"

namespace tile {
namespace future {
struct futurize_values_t {
    explicit futurize_values_t() = default;
};

struct futurize_tuple_t {
    explicit futurize_tuple_t() = default;
};

constexpr auto futurize_values = futurize_values_t();
constexpr auto futurize_tuple  = futurize_tuple_t();

template<typename... Ts>
class Future {
public:
    using action_type = typename Core<Ts...>::action_type;
    template<typename... Us, typename = enable_if_t<std::is_constructible<Boxed<Ts...>, box_values_t, Us &&...>::value>>
    explicit Future(futurize_values_t, Us &&...imms);

    template<typename... Us, typename = enable_if_t<std::is_constructible<Boxed<Ts...>, Boxed<Us...>>::value>>
    Future(futurize_tuple_t, std::tuple<Us...> values);

    template<
        typename U,
        typename = enable_if_t<std::is_constructible<typename internal::conditional_t<sizeof...(Ts) == 1,
                                                                                      std::common_type<Ts...>,
                                                                                      std::common_type<void>>::type,
                                                     U>::value
                               && !is_future<U>::value>>
    Future(U &&value);

    template<typename... Us, typename = enable_if_t<std::is_constructible<Boxed<Ts...>, Boxed<Us...>>::value>>
    Future(Future<Us...> &&future);

    Future()                          = default;

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

    Future(Future &&)                 = default;
    Future &operator=(Future &&)      = default;

    static_assert(!types_contains<Types<Ts...>, void>::value,
                  "There's no point in specifying `void` `Ts...`, "
                  "use `Future<>` if you meant to declare a future with no value.");

    template<typename F, typename R = call_by_boxed_t<F, Ts...>>
    auto Then(F &&fun) -> enable_if_t<std::is_void<R>::value, Future<>>;

    template<typename F, typename R = call_by_boxed_t<F, Ts...>>
    auto Then(F &&fun) -> enable_if_t<!std::is_void<R>::value && is_future<R>::value, futurize_t<R>>;

    template<typename F, typename R = call_by_boxed_t<F, Ts...>>
    auto Then(F &&fun) -> enable_if_t<!std::is_void<R>::value && !is_future<R>::value, futurize_t<R>>;

private:
    friend class Promise<Ts...>;
    template<typename... Us>
    friend class Future;

    explicit Future(std::shared_ptr<Core<Ts...>> core) : core_(std::move(core)) {}

    std::shared_ptr<Core<Ts...>> core_;
};

}// namespace future
}// namespace tile

#endif// TILE_BASE_FUTURE_FUTURE_H
