// Copyright 2024 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_async2/system_time_provider.h"

#include <chrono>

#include "pw_unit_test/framework.h"

namespace {

using ::pw::async2::Context;
using ::pw::async2::Dispatcher;
using ::pw::async2::GetSystemTimeProvider;
using ::pw::async2::Pending;
using ::pw::async2::Poll;
using ::pw::async2::Ready;
using ::pw::async2::Task;
using ::pw::async2::TimeFuture;
using ::pw::chrono::SystemClock;
using ::std::chrono_literals::operator""ms;
using ::std::chrono_literals::operator""s;

struct WaitTask : public Task {
  WaitTask(TimeFuture<SystemClock>&& future)
      : time_completed_(Pending()), future_(std::move(future)) {}

  Poll<> DoPend(Context& cx) final {
    if (future_.Pend(cx).IsPending()) {
      return Pending();
    }
    time_completed_ = SystemClock::now();
    return Ready();
  }

  Poll<SystemClock::time_point> time_completed_ = Pending();
  TimeFuture<SystemClock> future_;
};

TEST(SystemTimeProvider, InvokesTimerAfterDelay) {
  SystemClock::time_point start_time = SystemClock().now();
  SystemClock::time_point expected_completion = start_time + 50ms;
  WaitTask task(GetSystemTimeProvider().WaitUntil(expected_completion));
  Dispatcher dispatcher;
  dispatcher.Post(task);
  dispatcher.RunToCompletion();
  ASSERT_TRUE(task.time_completed_.IsReady());
  EXPECT_GE(*task.time_completed_, expected_completion);
}

TEST(SystemTimeProvider, InvokesTwoTimersInOrder) {
  SystemClock::time_point start_time = SystemClock().now();
  SystemClock::time_point expected_c1 = start_time + 200ms;
  SystemClock::time_point expected_c2 = start_time + 10ms;
  WaitTask t1(GetSystemTimeProvider().WaitUntil(expected_c1));
  WaitTask t2(GetSystemTimeProvider().WaitUntil(expected_c2));
  Dispatcher dispatcher;
  dispatcher.Post(t1);
  dispatcher.Post(t2);
  dispatcher.RunToCompletion();
  ASSERT_TRUE(t1.time_completed_.IsReady());
  EXPECT_GE(t1.time_completed_->time_since_epoch().count(),
            expected_c1.time_since_epoch().count());
  ASSERT_TRUE(t2.time_completed_.IsReady());
  EXPECT_GE(*t2.time_completed_, expected_c2);
  EXPECT_GT(*t1.time_completed_, *t2.time_completed_);
}

TEST(SystemTimeProvider, CancelThroughDestruction) {
  // We can't control the SystemClock's period configuration, so just in case
  // duration cannot be accurately expressed in integer ticks, round the
  // duration up.
  constexpr SystemClock::duration kRoundedArbitraryLongDuration =
      SystemClock::for_at_least(1s);

  std::ignore = GetSystemTimeProvider().WaitFor(kRoundedArbitraryLongDuration);
  // TimeFuture destroyed here and not used.
}

}  // namespace
