#pragma once
// IWYU pragma: private, include "rlbox.hpp"
// IWYU pragma: friend "rlbox_.*\.hpp"

#include <type_traits>
#include <utility>

#include "rlbox_helpers.hpp"
#include "rlbox_struct_support.hpp"
#include "rlbox_types.hpp"

namespace rlbox {

namespace callback_detail {

  // Compute the expected type of the callback
  template<typename T_Sbx, typename T_Ret, typename... T_Args>
  using T_Cb =
    std::conditional_t<std::is_void_v<T_Ret>, void, tainted<T_Ret, T_Sbx>> (*)(
      rlbox_sandbox<T_Sbx>&,
      tainted<T_Args, T_Sbx>...);

  template<typename T_Sbx, typename T_Ret, typename... T_Args>
  T_Cb<T_Sbx, T_Ret, T_Args...> callback_type_helper(T_Ret (*)(T_Args...));

  // Compute the expected type of the interceptor
  template<typename T_Sbx, typename T_Ret, typename... T_Args>
  using T_I = detail::convert_to_sandbox_equivalent_t<T_Ret, T_Sbx> (*)(
    detail::convert_to_sandbox_equivalent_t<T_Args, T_Sbx>...);

  template<typename T_Sbx, typename T_Ret, typename... T_Args>
  T_I<T_Sbx, T_Ret, T_Args...> interceptor_type_helper(T_Ret (*)(T_Args...));
}

template<typename T, typename T_Sbx>
class sandbox_callback
{
  KEEP_CLASSES_FRIENDLY

private:
  rlbox_sandbox<T_Sbx>* sandbox;

  using T_Callback =
    decltype(callback_detail::callback_type_helper<T_Sbx>(std::declval<T>()));
  T_Callback callback;

  // The interceptor is the function that runs between the sandbox invoking the
  // callback and the actual callback running The interceptor is responsible for
  // wrapping and converting callback arguments, returns etc. to their
  // appropriate representations
  using T_Interceptor = decltype(
    callback_detail::interceptor_type_helper<T_Sbx>(std::declval<T>()));
  T_Interceptor callback_interceptor;

  // The trampoline is the internal sandbox representation of the callback
  // Depending on the sandbox type, this could be the callback pointer directly
  // or a trampoline function that gates exits from the sandbox.
  using T_Trampoline = detail::convert_to_sandbox_equivalent_t<T, T_Sbx>;
  T_Trampoline callback_trampoline;

  // The unique key representing the callback to pass to unregister_callback on
  // destruction
  void* key;

  inline void move_obj(sandbox_callback&& other)
  {
    sandbox = other.sandbox;
    callback = other.callback;
    callback_interceptor = other.callback_interceptor;
    callback_trampoline = other.callback_trampoline;
    key = other.key;
    other.sandbox = nullptr;
    other.callback = nullptr;
    other.callback_interceptor = nullptr;
    other.callback_trampoline = 0;
    other.key = nullptr;
  }

  template<typename T_Ret, typename... T_Args>
  inline void unregister_helper(T_Ret (*)(T_Args...))
  {
    if (callback != nullptr) {
      // Don't need to worry about race between unregister and move as
      // 1) this will not happen in a correctly written program
      // 2) if this does happen, the worst that can happen is an invocation of a
      // null function pointer, which causes a crash that cannot be exploited
      // for RCE
      sandbox->template unregister_callback<T_Ret, T_Args...>(key);
      sandbox = nullptr;
      callback = nullptr;
      callback_interceptor = nullptr;
      callback_trampoline = 0;
      key = nullptr;
    }
  }

  inline T_Callback get_raw_value() const noexcept { return callback; }
  inline T_Trampoline get_raw_sandbox_value() const noexcept
  {
    return callback_trampoline;
  }

  // Keep constructor private as only rlbox_sandbox should be able to create
  // this object
  sandbox_callback(rlbox_sandbox<T_Sbx>* p_sandbox,
                   T_Callback p_callback,
                   T_Interceptor p_callback_interceptor,
                   T_Trampoline p_callback_trampoline,
                   void* p_key)
    : sandbox(p_sandbox)
    , callback(p_callback)
    , callback_interceptor(p_callback_interceptor)
    , callback_trampoline(p_callback_trampoline)
    , key(p_key)
  {
    detail::dynamic_check(sandbox != nullptr,
                          "Unexpected null sandbox when creating a callback");
  }

public:
  sandbox_callback()
    : sandbox(nullptr)
    , callback(nullptr)
    , callback_interceptor(nullptr)
    , callback_trampoline(0)
    , key(nullptr)
  {}

  sandbox_callback(sandbox_callback&& other)
  {
    move_obj(std::forward<sandbox_callback>(other));
  }

  inline sandbox_callback& operator=(sandbox_callback&& other)
  {
    if (this != &other) {
      move_obj(std::forward<sandbox_callback>(other));
    }
    return *this;
  }

  void unregister()
  {
    T dummy = nullptr;
    unregister_helper(dummy);
  }

  ~sandbox_callback() { unregister(); }

  /**
   * @brief Check if callback is _not_ registered.
   */
  inline bool is_unregistered() const noexcept
  {
    return get_raw_value() == nullptr;
  }

  /**
   * @brief Unwrap a callback without verification. This is an unsafe operation
   * and should be used with care.
   */
  inline auto UNSAFE_unverified() const noexcept { return get_raw_value(); }
  /**
   * @brief Like UNSAFE_unverified, but get the underlying sandbox
   * representation.
   *
   * @param sandbox Reference to sandbox.
   */
  inline auto UNSAFE_sandboxed(rlbox_sandbox<T_Sbx>& sandbox) const noexcept
  {
    RLBOX_UNUSED(sandbox);
    return get_raw_sandbox_value();
  }
};

template<typename T, typename T_Sbx>
class app_pointer
{
  KEEP_CLASSES_FRIENDLY

private:
  app_pointer_map<typename T_Sbx::T_PointerType>* map;
  typename T_Sbx::T_PointerType idx;
  T idx_unsandboxed;

  inline void move_obj(app_pointer&& other)
  {
    map = other.map;
    idx = other.idx;
    idx_unsandboxed = other.idx_unsandboxed;
    other.map = nullptr;
    other.idx = 0;
    other.idx_unsandboxed = nullptr;
  }

  inline T get_raw_value() const noexcept
  {
    return to_tainted().get_raw_value();
  }
  inline typename T_Sbx::T_PointerType get_raw_sandbox_value() const noexcept
  {
    return idx;
  }

  app_pointer(app_pointer_map<typename T_Sbx::T_PointerType>* a_map,
              typename T_Sbx::T_PointerType a_idx,
              T a_idx_unsandboxed)
    : map(a_map)
    , idx(a_idx)
    , idx_unsandboxed(a_idx_unsandboxed)
  {}

public:
  app_pointer()
    : map(nullptr)
    , idx(0)
    , idx_unsandboxed(0)
  {}

  ~app_pointer() { unregister(); }

  app_pointer(app_pointer&& other)
  {
    move_obj(std::forward<app_pointer>(other));
  }

  inline app_pointer& operator=(app_pointer&& other)
  {
    if (this != &other) {
      move_obj(std::forward<app_pointer>(other));
    }
    return *this;
  }

  void unregister()
  {
    if (idx != 0) {
      map->remove_app_ptr(idx);
      map = nullptr;
      idx = 0;
      idx_unsandboxed = nullptr;
    }
  }

  tainted<T, T_Sbx> to_tainted()
  {
    return tainted<T, T_Sbx>::internal_factory(
      reinterpret_cast<T>(idx_unsandboxed));
  }

  /**
   * @brief Check if app pointer is _not_ registered.
   */
  inline bool is_unregistered() const noexcept { return idx == 0; }

  /**
   * @brief Unwrap app_pointer without verification. This is an unsafe operation
   * and should be used with care.
   */
  inline auto UNSAFE_unverified() const noexcept { return get_raw_value(); }
  /**
   * @brief Like UNSAFE_unverified, but get the underlying sandbox
   * representation.
   *
   * @param sandbox Reference to sandbox.
   */
  inline auto UNSAFE_sandboxed(rlbox_sandbox<T_Sbx>& sandbox) const noexcept
  {
    RLBOX_UNUSED(sandbox);
    return get_raw_sandbox_value();
  }
};

/**
 * @brief Tainted boolean value that serves as a "hint" and not a definite
 * answer.  Comparisons with a tainted_volatile return such hints.  They are
 * not `tainted<bool>` values because a compromised sandbox can modify
 * tainted_volatile data at any time.
 */
class tainted_boolean_hint
{
private:
  bool val;

public:
  tainted_boolean_hint(bool init)
    : val(init)
  {}
  tainted_boolean_hint(const tainted_boolean_hint&) = default;
  inline tainted_boolean_hint& operator=(bool rhs)
  {
    val = rhs;
    return *this;
  }
  inline tainted_boolean_hint operator!() const
  {
    return tainted_boolean_hint(!val);
  }
  template<size_t N>
  inline bool unverified_safe_because(const char (&reason)[N]) const
  {
    (void)reason; /* unused */
    return val;
  }
  inline bool UNSAFE_unverified() const { return val; }
  inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); }

  // Add a template parameter to make sure the assert only fires when called
  template<typename T = void>
  inline bool copy_and_verify(...) const
  {
    rlbox_detail_static_fail_because(
      detail::true_v<T>,
      "You can't call copy_and_verify on this value, as this is a result of a "
      "comparison with memory accessible by the sandbox. \n"
      "The sandbox could unexpectedly change the value leading to "
      "time-of-check-time-of-use attacks. \n"
      "You can avoid this by making a local copy of the data."
      "For example, if your original code, looked like \n"
      "if ((tainted_ptr->member == 5).copy_and_verify(...)) { ... } \n\n"
      "Change this to \n\n"
      "tainted<int> val = tainted_ptr->member\n"
      "if ((val == 5).copy_and_verify(...)) { ... } \n\n"
      "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox) {...} \n\n"
      "Alternately, if you are sure your code is safe you can use the "
      "unverified_safe_because API to remove tainting\n");

    // this is never executed, but we need it for the function to type-check
    return false;
  }
};

/**
 * @brief Tainted integer value that serves as a "hint" and not a definite
 * answer.  Comparisons with a tainted_volatile return such hints.  They are
 * not `tainted<int>` values because a compromised sandbox can modify
 * tainted_volatile data at any time.
 */
class tainted_int_hint
{
private:
  int val;

public:
  tainted_int_hint(int init)
    : val(init)
  {}
  tainted_int_hint(const tainted_int_hint&) = default;
  inline tainted_int_hint& operator=(int rhs)
  {
    val = rhs;
    return *this;
  }
  inline tainted_boolean_hint operator!() const
  {
    return tainted_boolean_hint(!val);
  }
  template<size_t N>
  inline int unverified_safe_because(const char (&reason)[N]) const
  {
    (void)reason; /* unused */
    return val;
  }
  inline int UNSAFE_unverified() const { return val; }
  inline auto INTERNAL_unverified_safe() const { return UNSAFE_unverified(); }

  // Add a template parameter to make sure the assert only fires when called
  template<typename T = void>
  inline int copy_and_verify(...) const
  {
    rlbox_detail_static_fail_because(
      detail::true_v<T>,
      "You can't call copy_and_verify on this value, as this is a result of a "
      "comparison with memory accessible by the sandbox. \n"
      "The sandbox could unexpectedly change the value leading to "
      "time-of-check-time-of-use attacks. \n"
      "You can avoid this by making a local copy of the data."
      "For example, if your original code, looked like \n"
      "if ((tainted_ptr->member == 5).copy_and_verify(...)) { ... } \n\n"
      "Change this to \n\n"
      "tainted<int> val = tainted_ptr->member\n"
      "if ((val == 5).copy_and_verify(...)) { ... } \n\n"
      "tainted<int, T_Sbx> foo(rlbox_sandbox<T_Sbx>& sandbox) {...} \n\n"
      "Alternately, if you are sure your code is safe you can use the "
      "unverified_safe_because API to remove tainting\n");

    // this is never executed, but we need it for the function to type-check
    return 0;
  }
};

}
