// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_TASK_PROMISE_ALL_CONTAINER_EXECUTOR_H_
#define BASE_TASK_PROMISE_ALL_CONTAINER_EXECUTOR_H_

#include <tuple>

#include "base/task/promise/abstract_promise.h"
#include "base/task/promise/helpers.h"
#include "base/task/promise/no_op_promise_executor.h"

namespace base {
namespace internal {

// For Promises::All(std::vector<Promise<T>& promises)
template <typename ResolveType, typename RejectType>
class AllContainerPromiseExecutor {
 public:
  bool IsCancelled() const { return false; }

  PromiseExecutor::PrerequisitePolicy GetPrerequisitePolicy() const {
    return PromiseExecutor::PrerequisitePolicy::kAll;
  }

  struct VoidResolveType {};
  struct NonVoidResolveType {};

  using ResolveTypeTag = std::conditional_t<std::is_void<ResolveType>::value,
                                            VoidResolveType,
                                            NonVoidResolveType>;

  void Execute(AbstractPromise* promise) {
    // All is rejected if any prerequisites are rejected.
    AbstractPromise* first_settled = promise->GetFirstSettledPrerequisite();
    if (first_settled && first_settled->IsRejected()) {
      AllPromiseRejectHelper<Rejected<RejectType>>::Reject(promise,
                                                           first_settled);
      promise->OnRejected();
      return;
    }

    ResolveInternal(promise, ResolveTypeTag());
    promise->OnResolved();
  }

#if DCHECK_IS_ON()
  PromiseExecutor::ArgumentPassingType ResolveArgumentPassingType() const {
    return UseMoveSemantics<ResolveType>::argument_passing_type;
  }

  PromiseExecutor::ArgumentPassingType RejectArgumentPassingType() const {
    return UseMoveSemantics<RejectType>::argument_passing_type;
  }

  bool CanResolve() const {
    return !std::is_same<ResolveType, NoResolve>::value;
  }

  bool CanReject() const { return !std::is_same<RejectType, NoReject>::value; }
#endif

 private:
  // For containers of Promise<void> there is no point resolving with
  // std::vector<Void>.
  void ResolveInternal(AbstractPromise* promise, VoidResolveType) {
    promise->emplace(Resolved<void>());
  }

  void ResolveInternal(AbstractPromise* promise, NonVoidResolveType) {
    using NonVoidResolveType = ToNonVoidT<ResolveType>;
    Resolved<std::vector<NonVoidResolveType>> result;

    const std::vector<DependentList::Node>* prerequisite_list =
        promise->prerequisite_list();
    DCHECK(prerequisite_list);
    result.value.reserve(prerequisite_list->size());

    for (const auto& node : *prerequisite_list) {
      DCHECK(node.prerequisite()->IsResolved());
      result.value.push_back(
          ArgMoveSemanticsHelper<
              NonVoidResolveType,
              Resolved<NonVoidResolveType>>::Get(node.prerequisite()));
    }

    promise->emplace(std::move(result));
  }
};

template <typename Container, typename ContainerT>
struct AllContainerHelper;

template <typename Container, typename ResolveType, typename RejectType>
struct AllContainerHelper<Container, Promise<ResolveType, RejectType>> {
  using PromiseResolve = std::vector<ToNonVoidT<ResolveType>>;

  // As an optimization we don't return std::vector<ResolveType> for void
  // ResolveType.
  using PromiseType = std::conditional_t<std::is_void<ResolveType>::value,
                                         Promise<void, RejectType>,
                                         Promise<PromiseResolve, RejectType>>;

  static PromiseType All(const Location& from_here, const Container& promises) {
    size_t i = 0;
    std::vector<DependentList::Node> prerequisite_list(promises.size());
    // TODO(alexclarke): Move construction of this list and AbstractPromise out
    // of line to reduce template bloat.
    for (auto& promise : promises) {
      prerequisite_list[i++].SetPrerequisite(promise.abstract_promise_.get());
    }

    internal::PromiseExecutor::Data executor_data(
        (in_place_type_t<
            AllContainerPromiseExecutor<ResolveType, RejectType>>()));

    return PromiseType(AbstractPromise::Create(
        nullptr, from_here,
        std::make_unique<AbstractPromise::AdjacencyList>(
            std::move(prerequisite_list)),
        RejectPolicy::kMustCatchRejection, DependentList::ConstructUnresolved(),
        std::move(executor_data)));
  }
};

// TODO(alexclarke): Maybe specalize to support containers of variants.
// E.g. Promises::All(std::vector<Variant<Promise<T>...>>& promises)

}  // namespace internal
}  // namespace base

#endif  // BASE_TASK_PROMISE_ALL_CONTAINER_EXECUTOR_H_
