#ifndef CONTEXT_SWITCHER_CONTEXT_SWITCHER_H_
#define CONTEXT_SWITCHER_CONTEXT_SWITCHER_H_

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

#include "context.h"

#include "continuation.h"
#include "lambda_capture.h"

#ifdef THREAD_SANITIZER
#include "tsan_fiber_api.h"
#endif

namespace context_switcher {

continuation switch_context(continuation &&cont);

template<typename F>
continuation enter_context(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 - 64;
  stack_pointer_t stack_limit = stack_memory;

#ifdef CS_USE_CSCONTEXT
  cscontext::callback_t callback = lambda_capture_callback<lambda_capture<F>>;

  void *result = cscontext::__cs_enter_context(stack_base, captured_lambda, callback, stack_limit);
  return continuation{result};
#else // CS_USE_CSCONTEXT
  fcontext::callback_t callback = lambda_capture_callback<lambda_capture<F>>;
  fcontext::continuation_t new_context = fcontext::make_fcontext(stack_base, stack_base - stack_limit, callback);
#ifdef THREAD_SANITIZER
  void *new_fiber = __tsan_create_fiber(0);
  void *old_fiber = __tsan_get_current_fiber();
  captured_lambda->my_tsan_fiber_ = new_fiber;
  captured_lambda->previous_tsan_fiber_ = old_fiber;
  __tsan_switch_to_fiber(new_fiber, 0);
  fcontext::transfer_t transfer = fcontext::jump_fcontext(new_context, captured_lambda);
  if (transfer.data) {
    return continuation{transfer.continuation, transfer.data};
  } else {
    return continuation{nullptr, nullptr};
  }
#else
  fcontext::transfer_t transfer = fcontext::jump_fcontext(new_context, captured_lambda);
  if (transfer.data) {
    return continuation{transfer.continuation};
  } else {
    return continuation{nullptr};
  }
#endif // THREAD_SANITIZER
#endif // CS_USE_CSCONTEXT
}

}

#endif //CONTEXT_SWITCHER_CONTEXT_SWITCHER_H_
