
#ifndef PLS_PARALLEL_REDUCE_IMPL_H
#define PLS_PARALLEL_REDUCE_IMPL_H

#include "pls/internal/scheduling/scheduler.h"
#include "pls/algorithms/loop_partition_strategy.h"

namespace pls::algorithm {

namespace internal {

template<typename RandomIt, typename Function, typename Element>
static Element reduce(const RandomIt first,
                      const RandomIt last,
                      Element neutral,
                      const Function &reducer,
                      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
    Element acc = neutral;
    for (auto current = first; current != last; current++) {
      acc = reducer(acc, *current);
    }
    return acc;
  } else {
    // Cut in half recursively
    const long middle_index = num_elements / 2;

    Element left, right;
    scheduler::spawn([first, middle_index, last, neutral, &reducer, min_elements, &left] {
      left = internal::reduce<RandomIt, Function, Element>(first,
                                                           first + middle_index,
                                                           neutral,
                                                           reducer,
                                                           min_elements);
    });
    scheduler::spawn([first, middle_index, last, neutral, &reducer, min_elements, &right] {
      right = internal::reduce<RandomIt, Function, Element>(first + middle_index,
                                                            last,
                                                            neutral,
                                                            reducer,
                                                            min_elements);
    });
    scheduler::sync();
    return reducer(left, right);
  }
}

}

template<typename RandomIt, typename Function, typename ExecutionStrategy>
static auto reduce(RandomIt first,
                   RandomIt last,
                   decltype(*first) neutral,
                   const Function &reducer,
                   ExecutionStrategy execution_strategy) {
  long num_elements = std::distance(first, last);
  return internal::reduce<RandomIt, Function,
                          decltype(*first)>(first,
                                            last,
                                            neutral,
                                            reducer,
                                            execution_strategy.calculate_min_elements(num_elements));
}

template<typename RandomIt, typename Function>
static auto reduce(RandomIt first,
                   RandomIt last,
                   decltype(*first) neutral,
                   const Function &reducer) {
  return reduce(first, last, neutral, reducer, dynamic_strategy{4});
}

}

#endif //PLS_PARALLEL_REDUCE_IMPL_H
