

#include "tensorflow/tsl/platform/mutex.h"

#include <time.h>

#include "nsync_cv.h"       // NOLINT
#include "nsync_mu.h"       // NOLINT
#include "nsync_mu_wait.h"  // NOLINT
#include "nsync_time.h"     // NOLINT

namespace tsl {

// Check that the MuData struct used to reserve space for the mutex
// in tsl::mutex is big enough.
static_assert(sizeof(nsync::nsync_mu) <= sizeof(internal::MuData),
              "tsl::internal::MuData needs to be bigger");

// Cast a pointer to internal::MuData to a pointer to the mutex
// representation.  This is done so that the header files for nsync_mu do not
// need to be included in every file that uses tsl's mutex.
static inline nsync::nsync_mu *mu_cast(internal::MuData *mu) {
  return reinterpret_cast<nsync::nsync_mu *>(mu);
}

mutex::mutex() { nsync::nsync_mu_init(mu_cast(&mu_)); }

void mutex::lock() { nsync::nsync_mu_lock(mu_cast(&mu_)); }

bool mutex::try_lock() { return nsync::nsync_mu_trylock(mu_cast(&mu_)) != 0; };

void mutex::unlock() { nsync::nsync_mu_unlock(mu_cast(&mu_)); }

void mutex::lock_shared() { nsync::nsync_mu_rlock(mu_cast(&mu_)); }

bool mutex::try_lock_shared() {
  return nsync::nsync_mu_rtrylock(mu_cast(&mu_)) != 0;
};

void mutex::unlock_shared() { nsync::nsync_mu_runlock(mu_cast(&mu_)); }

// A callback suitable for nsync_mu_wait() that calls Condition::Eval().
static int EvaluateCondition(const void *vcond) {
  return static_cast<int>(static_cast<const Condition *>(vcond)->Eval());
}

void mutex::Await(const Condition &cond) {
  nsync::nsync_mu_wait(mu_cast(&mu_), &EvaluateCondition, &cond, nullptr);
}

bool mutex::AwaitWithDeadline(const Condition &cond, uint64 abs_deadline_ns) {
  time_t seconds = abs_deadline_ns / (1000 * 1000 * 1000);
  nsync::nsync_time abs_time = nsync::nsync_time_s_ns(
      seconds, abs_deadline_ns - seconds * (1000 * 1000 * 1000));
  return nsync::nsync_mu_wait_with_deadline(mu_cast(&mu_), &EvaluateCondition,
                                            &cond, nullptr, abs_time,
                                            nullptr) == 0;
}

// Check that the CVData struct used to reserve space for the
// condition variable in tsl::condition_variable is big enough.
static_assert(sizeof(nsync::nsync_cv) <= sizeof(internal::CVData),
              "tsl::internal::CVData needs to be bigger");

// Cast a pointer to internal::CVData to a pointer to the condition
// variable representation.  This is done so that the header files for nsync_cv
// do not need to be included in every file that uses tsl's
// condition_variable.
static inline nsync::nsync_cv *cv_cast(internal::CVData *cv) {
  return reinterpret_cast<nsync::nsync_cv *>(cv);
}

condition_variable::condition_variable() {
  nsync::nsync_cv_init(cv_cast(&cv_));
}

void condition_variable::wait(mutex_lock &lock) {
  nsync::nsync_cv_wait(cv_cast(&cv_), mu_cast(&lock.mutex()->mu_));
}

void condition_variable::notify_one() { nsync::nsync_cv_signal(cv_cast(&cv_)); }

void condition_variable::notify_all() {
  nsync::nsync_cv_broadcast(cv_cast(&cv_));
}

namespace internal {
std::cv_status wait_until_system_clock(
    CVData *cv_data, MuData *mu_data,
    const std::chrono::system_clock::time_point timeout_time) {
  int r = nsync::nsync_cv_wait_with_deadline(cv_cast(cv_data), mu_cast(mu_data),
                                             timeout_time, nullptr);
  return r ? std::cv_status::timeout : std::cv_status::no_timeout;
}
}  // namespace internal

}  // namespace tsl
