// Copyright 2021 The Pigweed Authors
//
// 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
//
//     https://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.

#include "pw_chrono/system_timer.h"

#include <mutex>
#include <thread>

#include "pw_chrono_stl/system_timer_native.h"

namespace pw::chrono::backend {
namespace internal {

bool NoDepsTimedThreadNotification::try_acquire() {
  std::unique_lock lock(lock_);
  bool was_set = is_set_;
  is_set_ = false;
  return was_set;
}

bool NoDepsTimedThreadNotification::try_acquire_until(
    SystemClock::time_point deadline) {
  std::unique_lock lock(lock_);
  // If deadline is specified as "infinite", use plain wait instead.
  // This prevents possible overflow issues in the STL implementation,
  // like libstdc++: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58931
  // TODO: https://pwbug.dev/427758785 - Ensure max works for all backends
  if (deadline == SystemClock::time_point::max()) {
    cv_.wait(lock, [&] { return is_set_; });
    is_set_ = false;
    return true;
  }
  if (cv_.wait_until(lock, deadline, [&] { return is_set_; })) {
    is_set_ = false;
    return true;
  }
  return false;
}

void NoDepsTimedThreadNotification::notify() {
  {
    std::unique_lock lock(lock_);
    is_set_ = true;
  }
  cv_.notify_one();
}

}  // namespace internal

NativeSystemTimer::NativeSystemTimer(internal::ExpiryFn&& callback)
    : timer_state_(
          std::make_shared<internal::TimerState>(std::move(callback))) {
  std::thread thread([state = timer_state_]() {
    while (true) {
      SystemClock::time_point sleep_until;
      {
        std::lock_guard lock(state->lock_);
        if (!state->running_) {
          return;
        }
        // Execute the callback while the expiry deadline is in the past.
        // This avoids an unnecessary unlock, sleep attempt, and relock in
        // the case that the intervals are short or the deadlines are in
        // the past.
        while (state->enabled_ &&
               state->expiry_deadline_ <= SystemClock::now()) {
          // Unset the thread notification.
          state->timer_thread_wakeup_.try_acquire();
          state->enabled_ = false;
          state->callback_(state->expiry_deadline_);
        }
        if (state->enabled_) {
          sleep_until = state->expiry_deadline_;
        } else {
          sleep_until = SystemClock::time_point::max();
        }
      }
      state->timer_thread_wakeup_.try_acquire_until(sleep_until);
    }
  });
  thread.detach();
}

void NativeSystemTimer::InvokeAt(SystemClock::time_point timestamp) {
  {
    std::lock_guard lock(timer_state_->lock_);
    timer_state_->enabled_ = true;
    timer_state_->expiry_deadline_ = timestamp;
  }
  timer_state_->timer_thread_wakeup_.notify();
}

void NativeSystemTimer::Cancel() {
  std::lock_guard lock(timer_state_->lock_);
  timer_state_->enabled_ = false;
}

void NativeSystemTimer::Kill() {
  {
    std::lock_guard lock(timer_state_->lock_);
    timer_state_->enabled_ = false;
    timer_state_->running_ = false;
  }
  timer_state_->timer_thread_wakeup_.notify();
}

}  // namespace pw::chrono::backend
