
#ifndef PLS_PARALLEL_FOR_IMPL_H
#define PLS_PARALLEL_FOR_IMPL_H

#include "pls/internal/scheduling/fork_join_task.h"
#include "pls/internal/scheduling/parallel_iterator_task.h"
#include "pls/internal/scheduling/scheduler.h"
#include "pls/internal/scheduling/scheduler.h"

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

namespace pls {
namespace algorithm {
namespace internal {
template<typename RandomIt, typename Function>
void parallel_for(RandomIt first, RandomIt last, const Function &function) {
  using namespace ::pls::internal::scheduling;
  using namespace ::pls::internal::helpers;
  using namespace ::pls::internal::base;
  constexpr long min_elements = 4;

  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 body = [=] { internal::parallel_for(first + middle_index, last, function); };
    fork_join_lambda_by_reference<decltype(body)> second_half_task(body);
    fork_join_sub_task::current()->spawn_child(second_half_task);

    parallel_for(first, first + middle_index, function);
    fork_join_sub_task::current()->wait_for_all();
  }
}
}

template<typename RandomIt, typename Function>
void parallel_for(RandomIt first, RandomIt last, const Function &function) {
  using namespace ::pls::internal::scheduling;
  using namespace ::pls::internal::helpers;
  using namespace ::pls::internal::base;
  static abstract_task::id id = unique_id::create<RandomIt, Function>();

  parallel_iterator_task<RandomIt, Function> iterator_task{first, last, function, id};
  scheduler::execute_task(iterator_task);
}

template<typename RandomIt, typename Function>
void parallel_for_fork_join(RandomIt first, RandomIt last, const Function &function) {
  using namespace ::pls::internal::scheduling;
  using namespace ::pls::internal::helpers;
  using namespace ::pls::internal::base;
  static abstract_task::id id = unique_id::create<RandomIt, Function>();

  auto body = [=] { internal::parallel_for(first, last, function); };
  fork_join_lambda_by_reference<decltype(body)> root_body(body);
  fork_join_task root_task{&root_body, id};
  scheduler::execute_task(root_task);
}

}
}

#endif //PLS_INVOKE_PARALLEL_IMPL_H
