
#ifndef PLS_PARALLEL_FOR_IMPL_H
#define PLS_PARALLEL_FOR_IMPL_H

#include "pls/internal/scheduling/scheduler.h"
#include "pls/internal/helpers/range.h"

namespace pls::algorithm {

namespace internal {

template<typename RandomIt, typename Function>
static void for_each(const RandomIt first,
                     const RandomIt last,
                     const Function &function,
                     const long min_elements) {
  using namespace ::pls::internal::scheduling;

  const long num_elements = std::distance(first, last);
  if (num_elements <= min_elements) {
    // calculate last elements in loop to avoid overhead
    for (auto current = first; current != last; current++) {
      function(*current);
    }
  } else {
    // Cut in half recursively
    const long middle_index = num_elements / 2;

    scheduler::spawn([first, middle_index, last, &function, min_elements] {
      internal::for_each(first,
                         first + middle_index,
                         function,
                         min_elements);
    });
    scheduler::spawn([first, middle_index, last, &function, min_elements] {
      internal::for_each(first + middle_index,
                         last,
                         function,
                         min_elements);
    });
    scheduler::sync();
  }
}

}

template<typename RandomIt, typename Function, typename ExecutionStrategy>
static void for_each(RandomIt
                     first,
                     RandomIt last,
                     const Function &function,
                     ExecutionStrategy
                     execution_strategy) {
  long num_elements = std::distance(first, last);
  return
      internal::for_each(first, last, function, execution_strategy.calculate_min_elements(num_elements));
}

template<typename RandomIt, typename Function>
static void for_each(RandomIt first, RandomIt last, const Function &function) {
  return for_each(first, last, function, dynamic_strategy{4});
}

template<typename Function, typename ExecutionStrategy>
static void for_each_range(unsigned long first,
                           unsigned long last,
                           const Function &function,
                           ExecutionStrategy execution_strategy) {
  auto range = pls::internal::helpers::range(first, last);
  return for_each(range.begin(), range.end(), function, execution_strategy);
}

template<typename Function>
static void for_each_range(unsigned long first,
                           unsigned long last,
                           const Function &function) {
  auto range = pls::internal::helpers::range(first, last);
  return for_each(range.begin(), range.end(), function);
}

}

#endif //PLS_INVOKE_PARALLEL_IMPL_H
