#ifndef TILE_BASE_FUTURE_WHEN_ANY_H
#define TILE_BASE_FUTURE_WHEN_ANY_H

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

#include <atomic>
#include <memory>

namespace tile {
namespace future {
template<template<typename...> class C, typename... Ts>
auto
WhenAnyPreservingErrors(C<Future<Ts...>> &&futures) -> Future<std::size_t, Boxed<Ts...>>
{
    TILE_CHECK(!futures.empty(), "Cannot wait for any future from an empty list.");

    struct Context {
        Promise<std::size_t, Boxed<Ts...>> promise;
        std::atomic<bool> ever_satisfied{false};
    };

    auto context = std::make_shared<Context>();

    for (std::size_t index = 0; index < futures.size(); ++index) {
        std::move(futures[index]).Then([context, index](Boxed<Ts...> boxed) {
            if (!context->ever_satisfied.exchange(true)) { context->promise.SetValue(index, std::move(boxed)); }
        });
    }

    return context->promise.GetFuture();
}

template<template<typename...> class C,
         typename... Ts,
         typename R = internal::conditional_t<std::is_void<unboxed_type_t<Ts...>>::value,
                                              Future<std::size_t>,
                                              Future<std::size_t, unboxed_type_t<Ts...>>>>
auto
WhenAny(C<Future<Ts...>> &&futures) -> enable_if_t<(sizeof...(Ts) == 0), R>
{
    return WhenAnyPreservingErrors(std::move(futures)).Then([](std::size_t index, Boxed<Ts...> values) {
        (void) values;
        return index;
    });
}

template<template<typename...> class C,
         typename... Ts,
         typename R = internal::conditional_t<std::is_void<unboxed_type_t<Ts...>>::value,
                                              Future<std::size_t>,
                                              Future<std::size_t, unboxed_type_t<Ts...>>>>
auto
WhenAny(C<Future<Ts...>> &&futures) -> enable_if_t<(sizeof...(Ts) != 0), R>
{
    return WhenAnyPreservingErrors(std::move(futures)).Then([](std::size_t index, Boxed<Ts...> values) {
        return R(futurize_values, index, values.Get());
    });
}

template<template<class...> class C, class... Ts>
auto
WhenAny(C<Future<Ts...>> *futures) -> decltype(WhenAny(std::move(*futures)))
{
    return WhenAny(std::move(*futures));
}

}// namespace future
}// namespace tile

#endif// TILE_BASE_FUTURE_WHEN_ANY_H
