/*
 * Copyright (c) Meta Platforms, Inc. and 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.
 */

#include <folly/TimeoutQueue.h>

#include <folly/portability/GTest.h>

using namespace folly;

TEST(TimeoutQueue, Simple) {
  typedef std::vector<TimeoutQueue::Id> EventVec;
  EventVec events;

  TimeoutQueue q;
  TimeoutQueue::Callback cb = [&events](
                                  TimeoutQueue::Id id, int64_t /* now */) {
    events.push_back(id);
  };

  EXPECT_EQ(1, q.add(0, 10, cb));
  EXPECT_EQ(2, q.add(0, 11, cb));
  EXPECT_EQ(3, q.addRepeating(0, 9, cb));

  EXPECT_TRUE(events.empty());
  EXPECT_EQ(21, q.runOnce(12)); // now+9

  bool r = (EventVec{3, 1, 2} == events);
  EXPECT_TRUE(r);

  events.clear();
  EXPECT_EQ(49, q.runOnce(40));
  r = (EventVec{3} == events);
  EXPECT_TRUE(r);
}

TEST(TimeoutQueue, Erase) {
  typedef std::vector<TimeoutQueue::Id> EventVec;
  EventVec events;

  TimeoutQueue q;
  TimeoutQueue::Callback cb = [&events, &q](
                                  TimeoutQueue::Id id, int64_t /* now */) {
    events.push_back(id);
    if (id == 2) {
      q.erase(1);
    }
  };

  EXPECT_EQ(1, q.addRepeating(0, 10, cb));
  EXPECT_EQ(2, q.add(0, 35, cb));

  int64_t now = 0;
  while (now < std::numeric_limits<int64_t>::max()) {
    now = q.runOnce(now);
  }

  bool r = (EventVec{1, 1, 1, 2} == events);
  EXPECT_TRUE(r);
}

TEST(TimeoutQueue, RunOnceRepeating) {
  int count = 0;
  TimeoutQueue q;
  TimeoutQueue::Callback cb = [&count, &q](
                                  TimeoutQueue::Id id, int64_t /* now */) {
    if (++count == 100) {
      EXPECT_TRUE(q.erase(id));
    }
  };

  EXPECT_EQ(1, q.addRepeating(0, 0, cb));

  EXPECT_EQ(0, q.runOnce(0));
  EXPECT_EQ(1, count);
  EXPECT_EQ(0, q.runOnce(0));
  EXPECT_EQ(2, count);
  EXPECT_EQ(std::numeric_limits<int64_t>::max(), q.runLoop(0));
  EXPECT_EQ(100, count);
}

TEST(TimeoutQueue, RunOnceReschedule) {
  int count = 0;
  TimeoutQueue q;
  TimeoutQueue::Callback cb;
  cb = [&count, &q, &cb](TimeoutQueue::Id id, int64_t now) {
    if (++count < 100) {
      EXPECT_LT(id, q.add(now, 0, cb));
    }
  };

  EXPECT_EQ(1, q.add(0, 0, cb));

  EXPECT_EQ(0, q.runOnce(0));
  EXPECT_EQ(1, count);
  EXPECT_EQ(0, q.runOnce(0));
  EXPECT_EQ(2, count);
  EXPECT_EQ(std::numeric_limits<int64_t>::max(), q.runLoop(0));
  EXPECT_EQ(100, count);
}
