/*
 * Copyright 2016 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/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
