
#ifndef PLS_INVOKE_PARALLEL_IMPL_H
#define PLS_INVOKE_PARALLEL_IMPL_H

#include <pls/internal/scheduling/fork_join_task.h>
#include "pls/internal/scheduling/fork_join_task.h"
#include "pls/internal/scheduling/scheduler.h"
#include "pls/internal/helpers/unique_id.h"
#include "pls/internal/base/alignment.h"

namespace pls {
namespace algorithm {
namespace internal {

using namespace ::pls::internal::scheduling;

template<typename Body>
inline void run_body(const Body &internal_body, const abstract_task::id &id) {
  // Make sure we are in the context of this invoke_parallel instance,
  // if not we will spawn it as a new 'fork-join-style' task.
  auto current_task = scheduler::current_task();
  if (current_task->unique_id() == id) {
    internal_body();
  } else {
    fork_join_lambda_by_reference<Body> root_body(internal_body);
    fork_join_task root_task{&root_body, id};
    scheduler::execute_task(root_task);
  }
}
}

template<typename Function1, typename Function2>
void invoke_parallel(const Function1 &function1, const Function2 &function2) {
  using namespace ::pls::internal::scheduling;
  using namespace ::pls::internal::helpers;
  using namespace ::pls::internal::base;
  static abstract_task::id id = unique_id::create<Function1, Function2>();

  auto internal_body = [&]() {
    auto current_task = fork_join_sub_task::current();
    auto sub_task_2 = fork_join_lambda_by_reference<Function2>(function2);

    current_task->spawn_child(sub_task_2);
    function1(); // Execute first function 'inline' without spawning a sub_task object
    current_task->wait_for_all();
  };

  internal::run_body(internal_body, id);
}

template<typename Function1, typename Function2, typename Function3>
void invoke_parallel(const Function1 &function1, const Function2 &function2, const Function3 &function3) {
  using namespace ::pls::internal::scheduling;
  using namespace ::pls::internal::helpers;
  static abstract_task::id id = unique_id::create<Function1, Function2, Function3>();

  auto internal_body = [&]() {
    auto current_task = fork_join_sub_task::current();
    auto sub_task_2 = fork_join_lambda_by_reference<Function2>(function2);
    auto sub_task_3 = fork_join_lambda_by_reference<Function3>(function3);

    current_task->spawn_child(sub_task_2);
    current_task->spawn_child(sub_task_3);
    function1(); // Execute first function 'inline' without spawning a sub_task object
    current_task->wait_for_all();
  };

  internal::run_body(internal_body, id);
}

}
}

#endif //PLS_INVOKE_PARALLEL_IMPL_H
