/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#pragma once

#include <folly/detail/Futex.h>
#include <folly/synchronization/ParkingLot.h>

namespace folly {
namespace detail {

/** Optimal when TargetClock is the same type as Clock.
 *
 *  Otherwise, both Clock::now() and TargetClock::now() must be invoked. */
template <typename TargetClock, typename Clock, typename Duration>
typename TargetClock::time_point time_point_conv(
    std::chrono::time_point<Clock, Duration> const& time) {
  using std::chrono::duration_cast;
  using TimePoint = std::chrono::time_point<Clock, Duration>;
  using TargetDuration = typename TargetClock::duration;
  using TargetTimePoint = typename TargetClock::time_point;
  if (time == TimePoint::max()) {
    return TargetTimePoint::max();
  } else if (std::is_same<Clock, TargetClock>::value) {
    // in place of time_point_cast, which cannot compile without if-constexpr
    auto const delta = time.time_since_epoch();
    return TargetTimePoint(duration_cast<TargetDuration>(delta));
  } else {
    // different clocks with different epochs, so non-optimal case
    auto const delta = time - Clock::now();
    return TargetClock::now() + duration_cast<TargetDuration>(delta);
  }
}

/**
 * Available overloads, with definitions elsewhere
 *
 * These functions are treated as ADL-extension points, the templates above
 * call these functions without them having being pre-declared.  This works
 * because ADL lookup finds the definitions of these functions when you pass
 * the relevant arguments
 */
int futexWakeImpl(
    const Futex<std::atomic>* futex, int count, uint32_t wakeMask);
FutexResult futexWaitImpl(
    const Futex<std::atomic>* futex,
    uint32_t expected,
    std::chrono::system_clock::time_point const* absSystemTime,
    std::chrono::steady_clock::time_point const* absSteadyTime,
    uint32_t waitMask);

int futexWakeImpl(
    const Futex<EmulatedFutexAtomic>* futex, int count, uint32_t wakeMask);
FutexResult futexWaitImpl(
    const Futex<EmulatedFutexAtomic>* futex,
    uint32_t expected,
    std::chrono::system_clock::time_point const* absSystemTime,
    std::chrono::steady_clock::time_point const* absSteadyTime,
    uint32_t waitMask);

template <typename Futex, typename Deadline>
typename std::enable_if<Deadline::clock::is_steady, FutexResult>::type
futexWaitImpl(
    Futex* futex,
    uint32_t expected,
    Deadline const& deadline,
    uint32_t waitMask) {
  return futexWaitImpl(futex, expected, nullptr, &deadline, waitMask);
}

template <typename Futex, typename Deadline>
typename std::enable_if<!Deadline::clock::is_steady, FutexResult>::type
futexWaitImpl(
    Futex* futex,
    uint32_t expected,
    Deadline const& deadline,
    uint32_t waitMask) {
  return futexWaitImpl(futex, expected, &deadline, nullptr, waitMask);
}

template <typename Futex>
FutexResult futexWait(
    const Futex* futex, uint32_t expected, uint32_t waitMask) {
  auto rv = futexWaitImpl(futex, expected, nullptr, nullptr, waitMask);
  assert(rv != FutexResult::TIMEDOUT);
  return rv;
}

template <typename Futex>
int futexWake(const Futex* futex, int count, uint32_t wakeMask) {
  return futexWakeImpl(futex, count, wakeMask);
}

template <typename Futex, class Clock, class Duration>
FutexResult futexWaitUntil(
    const Futex* futex,
    uint32_t expected,
    std::chrono::time_point<Clock, Duration> const& deadline,
    uint32_t waitMask) {
  using Target = typename std::conditional<
      Clock::is_steady,
      std::chrono::steady_clock,
      std::chrono::system_clock>::type;
  auto const converted = time_point_conv<Target>(deadline);
  return converted == Target::time_point::max()
      ? futexWaitImpl(futex, expected, nullptr, nullptr, waitMask)
      : futexWaitImpl(futex, expected, converted, waitMask);
}

} // namespace detail
} // namespace folly
