#ifndef TILE_BASE_FUTURE_BASIC_H
#define TILE_BASE_FUTURE_BASIC_H

#pragma once

#include "tile/base/future/types.h"

#include <tuple>
#include <type_traits>

namespace tile {
namespace future {
template<typename... Ts>
struct are_rvalue_refs : internal::conjunction<std::is_rvalue_reference<Ts>...> {};

static_assert(are_rvalue_refs<int &&, char &&>::value, "");
static_assert(!are_rvalue_refs<int, char &&>::value, "");

// Rebinds `Ts...` in `TT<....>` to `UT<...>`
// Example:
// rebind_t<Future<int, double>, Promise<>> -> Promise<int, double>
template<typename T, template<typename...> class To>
struct rebind;

template<template<typename...> class From, typename... Ts, template<typename...> class To>
struct rebind<From<Ts...>, To> {
    using type = To<Ts...>;
};
template<typename T, template<typename...> class To>
using rebind_t = typename rebind<T, To>::type;

static_assert(std::is_same<rebind_t<std::common_type<>, Types>, Types<>>::value, "");
static_assert(std::is_same<rebind_t<std::common_type<int, char>, Types>, Types<int, char>>::value, "");

// Basic templates.
template<typename... Ts>
class Future;
template<typename... Ts>
class Promise;
template<typename... Ts>
class Boxed;

template<typename T>
struct is_future : std::false_type {};

template<typename... Ts>
struct is_future<Future<Ts...>> : std::true_type {};

static_assert(!is_future<char *>::value, "");
static_assert(is_future<Future<>>::value, "");
static_assert(is_future<Future<char *>>::value, "");
static_assert(is_future<Future<char *, int>>::value, "");

template<typename... Ts>
struct is_futures : internal::conjunction<is_future<Ts>...> {};

template<typename... Ts>
struct futurize {
    using type = Future<Ts...>;
};

template<>
struct futurize<void> : futurize<> {};

template<typename... Ts>
struct futurize<Future<Ts...>> : futurize<Ts...> {};

template<typename... Ts>
using futurize_t = typename futurize<Ts...>::type;

static_assert(std::is_same<futurize_t<int>, Future<int>>::value, "");
static_assert(std::is_same<futurize_t<>, Future<>>::value, "");
static_assert(std::is_same<futurize_t<Future<int>>, Future<int>>::value, "");

// Concatenate `Ts...` in multiple `Future<Ts...>`
// Example:
// flatten<Future<T1, T2>, Future<T3>> -> Future<T1, T2, T3>
template<typename... Ts>
struct flatten {
    using type = rebind_t<types_cat_t<rebind_t<Ts, Types>...>, Future>;
};

template<typename... Ts>
using flatten_t = typename flatten<Ts...>::type;
static_assert(std::is_same<flatten_t<Future<void *>, Future<>, Future<char>>, Future<void *, char>>::value, "");

template<typename T>
using as_boxed_t = rebind_t<T, Boxed>;
template<typename T>
using as_promise_t = rebind_t<T, Promise>;

static_assert(std::is_same<as_boxed_t<Future<>>, Boxed<>>::value, "");
static_assert(std::is_same<as_boxed_t<Future<int, char>>, Boxed<int, char>>::value, "");
static_assert(std::is_same<as_promise_t<Future<>>, Promise<>>::value, "");
static_assert(std::is_same<as_promise_t<Future<int, char>>, Promise<int, char>>::value, "");

template<typename... Ts>
struct unboxed_type {
    using type =
        typename internal::conditional_t<sizeof...(Ts) == 0,
                                         std::common_type<void>,
                                         internal::conditional_t<sizeof...(Ts) == 1,
                                                                 std::common_type<Ts...>,
                                                                 typename std::common_type<std::tuple<Ts...>>>>::type;
};

template<typename... Ts>
using unboxed_type_t = typename unboxed_type<Ts...>::type;

static_assert(std::is_same<unboxed_type_t<>, void>::value, "");
static_assert(std::is_same<unboxed_type_t<int>, int>::value, "");
static_assert(std::is_same<unboxed_type_t<int, char>, std::tuple<int, char>>::value, "");

}// namespace future
}// namespace tile

#endif// TILE_BASE_FUTURE_BASIC_H
