
#ifndef PLS_PARALLEL_FOR_IMPL_H
#define PLS_PARALLEL_FOR_IMPL_H

#include "pls/internal/scheduling/task.h"
#include "pls/internal/scheduling/scheduler.h"

#include "pls/internal/helpers/unique_id.h"
#include "pls/internal/helpers/range.h"

namespace pls {
namespace algorithm {
namespace internal {

template<typename RandomIt, typename Function>
void for_each(RandomIt first, RandomIt last, const Function &function) {
  using namespace ::pls::internal::scheduling;
  constexpr long min_elements = 1; // TODO: tune this value/allow for execution strategies

  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
    long middle_index = num_elements / 2;

    auto second_half_body =
        [first, middle_index, last, &function] { internal::for_each(first + middle_index, last, function); };
    using second_half_t = lambda_task_by_reference<decltype(second_half_body)>;
    scheduler::spawn_child<second_half_t>(std::move(second_half_body));

    auto first_half_body =
        [first, middle_index, last, &function] { internal::for_each(first, first + middle_index, function); };
    using first_half_t = lambda_task_by_reference<decltype(first_half_body)>;
    scheduler::spawn_child_and_wait<first_half_t>(std::move(first_half_body));
  }
}

}

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

template<typename RandomIt, typename Function>
void for_each(RandomIt first, RandomIt last, const Function &function) {
  internal::for_each(first, last, function);
}

}
}

#endif //PLS_INVOKE_PARALLEL_IMPL_H
