content: package com.github.aesteve.vertx.nubes.utils.async;
  
  import com.github.aesteve.vertx.nubes.utils.functional.TriConsumer;
  import io.vertx.core.AsyncResult;
  import io.vertx.core.Future;
  import io.vertx.core.Handler;
  import io.vertx.core.logging.Logger;
  import io.vertx.core.logging.LoggerFactory;
  
  import java.util.Collection;
  import java.util.HashMap;
  import java.util.Map;
  import java.util.function.BiConsumer;
  import java.util.function.Function;
  
  import static com.github.aesteve.vertx.nubes.utils.async.AsyncUtils.completeOrFail;
  
  public class MultipleFutures<T> extends SimpleFuture<T> {
  
    private final Map<Handler<Future<T>>, Future<T>> consumers;
    private static final Logger LOG = LoggerFactory.getLogger(MultipleFutures.class);
  
    public MultipleFutures() {
      consumers = new HashMap<>();
    }
  
    public MultipleFutures(Future<T> after) {
      this();
      join(after);
    }
  
    public MultipleFutures(Handler<AsyncResult<T>> after) {
      this();
      join(after);
    }
  
    public <L> MultipleFutures(Collection<L> elements, Function<L, Handler<Future<T>>> transform) {
      this();
      addAll(elements, transform);
    }
  
    public <L> MultipleFutures(Collection<L> elements, BiConsumer<L, Future<T>> transform) {
      this();
      addAll(elements, transform);
    }
  
    public <K, V> MultipleFutures(Map<K, V> elements, TriConsumer<K, V, Future<T>> transform) {
      this();
      addAll(elements, transform);
    }
  
    //
  
    public MultipleFutures<T> add(Handler<Future<T>> handler) {
      if (handler == null) {
        return this;
      }
      Future<T> future = Future.future();
      future.setHandler(futureHandler -> checkCallHandler());
      consumers.put(handler, future);
      return this;
    }
  
    public MultipleFutures<T> start() {
      if (consumers.isEmpty()) {
        complete();
        return this;
      }
      consumers.forEach(Handler::handle);
      return this;
    }
  
    @Override
    public T result() {
      return null;
    }
  
    @Override
    public Throwable cause() {
      Exception e = new Exception("At least one future failed");
      consumers.forEach((consumer, future) -> {
        if (future.cause() != null) {
          LOG.error(future.cause());
          if (e.getCause() == null) {
            e.initCause(future.cause());
          } else {
            e.addSuppressed(future.cause());
          }
        }
      });
      return e;
    }
  
    @Override
    public boolean succeeded() {
      return consumers.values().stream().allMatch(Future::succeeded);
    }
  
    @Override
    public boolean failed() {
      return consumers.values().stream().anyMatch(Future::failed);
    }
  
    @Override
    public boolean isComplete() {
      if (super.isComplete()) { // has been marked explicitly
        return true;
      }
      return !consumers.isEmpty() &&
          consumers.values().stream().allMatch(Future::isComplete);
    }
  
    public MultipleFutures<T> join(Future<T> future) {
      setHandler(completeOrFail(future));
      return this;
    }
  
    public MultipleFutures<T> join(Handler<AsyncResult<T>> handler) {
      setHandler(handler);
      return this;
    }
  
    public <L> MultipleFutures<T> addAll(Collection<L> list, BiConsumer<L, Future<T>> transform) {
      list.forEach(element -> add(res -> transform.accept(element, res)));
      return this;
    }
  
    public <L> MultipleFutures<T> addAll(Collection<L> list, Function<L, Handler<Future<T>>> transform) {
      list.forEach(element -> add(transform.apply(element)));
      return this;
    }
  
    public <L> MultipleFutures<T> treatAllNow(Collection<L> list, Function<L, Handler<Future<T>>> transform) {
      addAll(list, transform);
      start();
      return this;
    }
  
    public <K, V> MultipleFutures<T> addAll(Map<K, V> map, TriConsumer<K, V, Future<T>> transform) {
      map.forEach((key, value) -> add(res -> transform.accept(key, value, res)));
      return this;
    }
  
  }
  