/*
 * Copyright 2015 Facebook, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <folly/experimental/fibers/FiberManager.h>

namespace folly { namespace fibers {

namespace {

template <class F, class G>
typename std::enable_if<
  !std::is_same<typename std::result_of<F()>::type, void>::value, void>::type
inline callFuncs(F&& f, G&& g, size_t id) {
  g(id, f());
}

template <class F, class G>
typename std::enable_if<
  std::is_same<typename std::result_of<F()>::type, void>::value, void>::type
inline callFuncs(F&& f, G&& g, size_t id) {
  f();
  g(id);
}

}  // anonymous namespace

template <class InputIterator, class F>
inline void forEach(InputIterator first, InputIterator last, F&& f) {
  if (first == last) {
    return;
  }

  typedef typename std::iterator_traits<InputIterator>::value_type FuncType;

  size_t tasksTodo = 1;
  std::exception_ptr e;
  Baton baton;

#ifdef __clang__
#pragma clang diagnostic push // ignore generalized lambda capture warning
#pragma clang diagnostic ignored "-Wc++1y-extensions"
#endif
  auto taskFunc =
    [&tasksTodo, &e, &f, &baton] (size_t id, FuncType&& func) {
    return [id, &tasksTodo, &e, &f, &baton,
            func_ = std::forward<FuncType>(func)]() mutable {
        try {
          callFuncs(std::forward<FuncType>(func_), f, id);
        } catch (...) {
          e = std::current_exception();
        }
        if (--tasksTodo == 0) {
          baton.post();
        }
      };
    };
#ifdef __clang__
#pragma clang diagnostic pop
#endif

  auto firstTask = first;
  ++first;

  for (size_t i = 1; first != last; ++i, ++first, ++tasksTodo) {
    addTask(taskFunc(i, std::move(*first)));
  }

  taskFunc(0, std::move(*firstTask))();
  baton.wait();

  if (e != std::exception_ptr()) {
    std::rethrow_exception(e);
  }
}

}}  // folly::fibers
