
#ifndef PREDICTABLE_PARALLEL_PATTERNS_APP_CONTEXT_SWITCH_FIBER_CALL_H_
#define PREDICTABLE_PARALLEL_PATTERNS_APP_CONTEXT_SWITCH_FIBER_CALL_H_

#include <utility>
#include <cstdio>
#include <cstdint>
#include <new>

namespace pls {
namespace internal {
namespace base {

using continuation_t = void *;
using stack_pointer_t = char *;
using callback_t = continuation_t (*)(continuation_t, void *);

template<typename F>
struct lambda_capture {
  F lambda_;

  explicit lambda_capture(F &&lambda) : lambda_{std::forward<F>(lambda)} {}

  continuation_t operator()(continuation_t continuation) {
    return lambda_(continuation);
  }
};

template<typename T>
continuation_t execute_callable(continuation_t continuation, void *param) {
  T *callable = reinterpret_cast<T *>(param);
  continuation_t result = (*callable)(continuation);

  callable->~T();
  return result;
}

extern "C" {
continuation_t __fiber_call(stack_pointer_t stack_base,
                            void *callback_arg,
                            callback_t callback,
                            stack_pointer_t stack_limit);
continuation_t __fiber_continue(continuation_t continuation);
}

template<typename F>
static lambda_capture<F> *place_lambda_capture(F &&lambda, char *memory) {
  return new(memory) lambda_capture<F>(std::forward<F>(lambda));
}

template<typename F>
continuation_t fiber_call(stack_pointer_t stack_memory, size_t stack_size, F &&lambda) {
  stack_pointer_t lambda_memory = stack_memory + stack_size - sizeof(lambda_capture<F>);
  auto *captured_lambda = place_lambda_capture(std::forward<F>(lambda), lambda_memory);

  stack_pointer_t stack_base = lambda_memory;
  stack_pointer_t stack_limit = stack_memory;

  callback_t callback = execute_callable<lambda_capture<F>>;

  return __fiber_call(stack_base, captured_lambda, callback, stack_limit);
}

}
}
}

#endif //PREDICTABLE_PARALLEL_PATTERNS_APP_CONTEXT_SWITCH_FIBER_CALL_H_
