/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * 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 <chrono>
#include <numeric>

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "ringbuffer.h"
using namespace testing;
using namespace std::chrono_literals;

template <typename Rep, typename Per>
nsecs_t toNsecs(std::chrono::duration<Rep, Per> time) {
  return std::chrono::duration_cast<std::chrono::nanoseconds>(time).count();
}

template <typename Rep, typename Per>
uint64_t toMs(std::chrono::duration<Rep, Per> time) {
  return std::chrono::duration_cast<std::chrono::milliseconds>(time).count();
}

struct TimeKeeperWrapper : histogram::TimeKeeper {
  TimeKeeperWrapper(std::shared_ptr<histogram::TimeKeeper> const &tk) : tk(tk) {}
  nsecs_t current_time() const final { return tk->current_time(); }
  std::shared_ptr<histogram::TimeKeeper> const tk;
};

struct TickingTimeKeeper : histogram::TimeKeeper {
  void tick() { fake_time = fake_time + toNsecs(1ms); }

  void increment_by(std::chrono::nanoseconds inc) { fake_time = fake_time + inc.count(); }

  nsecs_t current_time() const final { return fake_time; }

 private:
  nsecs_t mutable fake_time = 0;
};

void insertFrameIncrementTimeline(histogram::Ringbuffer &rb, TickingTimeKeeper &tk,
                                  drm_msm_hist &frame) {
  rb.insert(frame);
  tk.tick();
}

class RingbufferTestCases : public ::testing::Test {
  void SetUp() {
    for (auto i = 0u; i < HIST_V_SIZE; i++) {
      frame0.data[i] = fill_frame0;
      frame1.data[i] = fill_frame1;
      frame2.data[i] = fill_frame2;
      frame3.data[i] = fill_frame3;
      frame4.data[i] = fill_frame4;
      frame_saturate.data[i] = std::numeric_limits<uint32_t>::max();
    }
  }

 protected:
  std::unique_ptr<histogram::Ringbuffer> createFilledRingbuffer(
      std::shared_ptr<TickingTimeKeeper> const &tk) {
    auto rb = histogram::Ringbuffer::create(4, std::make_unique<TimeKeeperWrapper>(tk));
    insertFrameIncrementTimeline(*rb, *tk, frame0);
    insertFrameIncrementTimeline(*rb, *tk, frame1);
    insertFrameIncrementTimeline(*rb, *tk, frame2);
    insertFrameIncrementTimeline(*rb, *tk, frame3);
    return rb;
  }

  uint64_t fill_frame0 = 9;
  uint64_t fill_frame1 = 11;
  uint64_t fill_frame2 = 303;
  uint64_t fill_frame3 = 1030;
  uint64_t fill_frame4 = 112200;
  drm_msm_hist frame0;
  drm_msm_hist frame1;
  drm_msm_hist frame2;
  drm_msm_hist frame3;
  drm_msm_hist frame4;
  drm_msm_hist frame_saturate;

  int numFrames = 0;
  std::array<uint64_t, HIST_V_SIZE> bins;
};

TEST_F(RingbufferTestCases, ZeroSizedRingbufferReturnsNull) {
  EXPECT_THAT(histogram::Ringbuffer::create(0, std::make_unique<TickingTimeKeeper>()), Eq(nullptr));
}

TEST_F(RingbufferTestCases, NullTimekeeperReturnsNull) {
  EXPECT_THAT(histogram::Ringbuffer::create(10, nullptr), Eq(nullptr));
}

TEST_F(RingbufferTestCases, CollectionWithNoFrames) {
  auto rb = histogram::Ringbuffer::create(1, std::make_unique<TickingTimeKeeper>());

  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(0));
  EXPECT_THAT(bins, Each(0));
}

TEST_F(RingbufferTestCases, SimpleTest) {
  static constexpr int numInsertions = 3u;
  auto tk = std::make_shared<TickingTimeKeeper>();
  auto rb = histogram::Ringbuffer::create(numInsertions, std::make_unique<TimeKeeperWrapper>(tk));

  drm_msm_hist frame;
  for (auto i = 0u; i < HIST_V_SIZE; i++) {
    frame.data[i] = i;
  }

  insertFrameIncrementTimeline(*rb, *tk, frame);
  insertFrameIncrementTimeline(*rb, *tk, frame);
  insertFrameIncrementTimeline(*rb, *tk, frame);

  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();

  ASSERT_THAT(bins.size(), Eq(HIST_V_SIZE));
  for (auto i = 0u; i < bins.size(); i++) {
    EXPECT_THAT(bins[i], Eq(toMs(3ms) * i));
  }
}

TEST_F(RingbufferTestCases, TestEvictionSingle) {
  int fill_frame0 = 9;
  int fill_frame1 = 111;
  drm_msm_hist frame0;
  drm_msm_hist frame1;
  for (auto i = 0u; i < HIST_V_SIZE; i++) {
    frame0.data[i] = fill_frame0;
    frame1.data[i] = fill_frame1;
  }

  auto tk = std::make_shared<TickingTimeKeeper>();
  auto rb = histogram::Ringbuffer::create(1, std::make_unique<TimeKeeperWrapper>(tk));

  insertFrameIncrementTimeline(*rb, *tk, frame0);

  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(1));
  EXPECT_THAT(bins, Each(fill_frame0));

  insertFrameIncrementTimeline(*rb, *tk, frame1);
  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(1));
  EXPECT_THAT(bins, Each(fill_frame1));
}

TEST_F(RingbufferTestCases, TestEvictionMultiple) {
  auto tk = std::make_shared<TickingTimeKeeper>();
  auto rb = histogram::Ringbuffer::create(3, std::make_unique<TimeKeeperWrapper>(tk));

  insertFrameIncrementTimeline(*rb, *tk, frame0);
  insertFrameIncrementTimeline(*rb, *tk, frame1);
  insertFrameIncrementTimeline(*rb, *tk, frame2);

  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(3));
  EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + fill_frame2));

  insertFrameIncrementTimeline(*rb, *tk, frame3);
  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(3));
  EXPECT_THAT(bins, Each(fill_frame1 + fill_frame2 + fill_frame3));

  insertFrameIncrementTimeline(*rb, *tk, frame0);
  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(3));
  EXPECT_THAT(bins, Each(fill_frame2 + fill_frame3 + fill_frame0));
}

TEST_F(RingbufferTestCases, TestResizeToZero) {
  auto rb = histogram::Ringbuffer::create(4, std::make_unique<TickingTimeKeeper>());
  EXPECT_FALSE(rb->resize(0));
}

TEST_F(RingbufferTestCases, TestResizeDown) {
  auto tk = std::make_shared<TickingTimeKeeper>();
  auto rb = createFilledRingbuffer(tk);

  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(4));
  EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + fill_frame2 + fill_frame3));

  auto rc = rb->resize(2);
  EXPECT_THAT(rc, Eq(true));
  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(2));
  EXPECT_THAT(bins, Each(fill_frame2 + fill_frame3));

  insertFrameIncrementTimeline(*rb, *tk, frame0);
  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(2));
  EXPECT_THAT(bins, Each(fill_frame0 + fill_frame3));
}

TEST_F(RingbufferTestCases, TestResizeUp) {
  auto tk = std::make_shared<TickingTimeKeeper>();
  auto rb = histogram::Ringbuffer::create(2, std::make_unique<TimeKeeperWrapper>(tk));

  insertFrameIncrementTimeline(*rb, *tk, frame0);
  insertFrameIncrementTimeline(*rb, *tk, frame1);

  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(2));
  EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1));

  auto rc = rb->resize(3);
  EXPECT_THAT(rc, Eq(true));
  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(2));
  EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1));

  insertFrameIncrementTimeline(*rb, *tk, frame2);
  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(3));
  EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + fill_frame2));

  insertFrameIncrementTimeline(*rb, *tk, frame3);
  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(3));
  EXPECT_THAT(bins, Each(fill_frame1 + fill_frame2 + fill_frame3));
}

TEST_F(RingbufferTestCases, TestTimestampFiltering) {
  auto rb = createFilledRingbuffer(std::make_shared<TickingTimeKeeper>());

  std::tie(numFrames, bins) = rb->collect_after(toNsecs(1500us));
  EXPECT_THAT(numFrames, Eq(2));
  EXPECT_THAT(bins, Each(fill_frame2 + fill_frame3));

  std::tie(numFrames, bins) = rb->collect_after(toNsecs(45000us));
  EXPECT_THAT(numFrames, Eq(0));

  std::tie(numFrames, bins) = rb->collect_after(0);
  EXPECT_THAT(numFrames, Eq(4));
  EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + fill_frame2 + fill_frame3));
}

TEST_F(RingbufferTestCases, TestTimestampFilteringSameTimestamp) {
  auto tk = std::make_shared<TickingTimeKeeper>();
  auto rb = histogram::Ringbuffer::create(4, std::make_unique<TimeKeeperWrapper>(tk));
  insertFrameIncrementTimeline(*rb, *tk, frame0);
  insertFrameIncrementTimeline(*rb, *tk, frame1);
  insertFrameIncrementTimeline(*rb, *tk, frame2);
  rb->insert(frame3);
  rb->insert(frame4);
  tk->tick();

  std::tie(numFrames, bins) = rb->collect_after(toNsecs(3ms));
  EXPECT_THAT(numFrames, Eq(2));
  EXPECT_THAT(bins, Each(fill_frame4));
}

TEST_F(RingbufferTestCases, TestFrameFiltering) {
  auto rb = createFilledRingbuffer(std::make_shared<TickingTimeKeeper>());

  std::tie(numFrames, bins) = rb->collect_max(2);
  EXPECT_THAT(numFrames, Eq(2));
  EXPECT_THAT(bins, Each(fill_frame2 + fill_frame3));

  std::tie(numFrames, bins) = rb->collect_max(0);
  EXPECT_THAT(numFrames, Eq(0));
  EXPECT_THAT(bins, Each(0));

  std::tie(numFrames, bins) = rb->collect_max(3);
  EXPECT_THAT(numFrames, Eq(3));
  EXPECT_THAT(bins, Each(fill_frame1 + fill_frame2 + fill_frame3));

  std::tie(numFrames, bins) = rb->collect_max(8);
  EXPECT_THAT(numFrames, Eq(4));
  EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + fill_frame2 + fill_frame3));
}

TEST_F(RingbufferTestCases, TestTimestampAndFrameFiltering) {
  auto rb = createFilledRingbuffer(std::make_shared<TickingTimeKeeper>());

  std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(1500us), 1);
  EXPECT_THAT(numFrames, Eq(1));
  EXPECT_THAT(bins, Each(fill_frame3));

  std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(2500us), 0);
  EXPECT_THAT(numFrames, Eq(0));
  EXPECT_THAT(bins, Each(0));

  std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(10ms), 100);
  EXPECT_THAT(numFrames, Eq(0));
  EXPECT_THAT(bins, Each(0));

  std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(0ns), 10);
  EXPECT_THAT(numFrames, Eq(4));
  EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 + fill_frame2 + fill_frame3));
}

TEST_F(RingbufferTestCases, TestTimestampAndFrameFilteringAndResize) {
  auto rb = createFilledRingbuffer(std::make_shared<TickingTimeKeeper>());

  std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(500us), 1);
  EXPECT_THAT(numFrames, Eq(1));
  EXPECT_THAT(bins, Each(fill_frame3));

  std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(500us), 10);
  EXPECT_THAT(numFrames, Eq(3));
  EXPECT_THAT(bins, Each(fill_frame1 + fill_frame2 + fill_frame3));

  rb->resize(2);
  std::tie(numFrames, bins) = rb->collect_max_after(toNsecs(500us), 10);
  EXPECT_THAT(numFrames, Eq(2));
  EXPECT_THAT(bins, Each(fill_frame2 + fill_frame3));
}

TEST_F(RingbufferTestCases, TestCumulativeCounts) {
  auto tk = std::make_shared<TickingTimeKeeper>();
  auto rb = histogram::Ringbuffer::create(1, std::make_unique<TimeKeeperWrapper>(tk));
  insertFrameIncrementTimeline(*rb, *tk, frame0);

  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(1));
  EXPECT_THAT(bins, Each(fill_frame0));

  insertFrameIncrementTimeline(*rb, *tk, frame1);
  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();
  EXPECT_THAT(numFrames, Eq(1));
  EXPECT_THAT(bins, Each(fill_frame1));

  std::tie(numFrames, bins) = rb->collect_cumulative();
  EXPECT_THAT(numFrames, Eq(2));
  EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1));
  rb->insert(frame2);
  auto weight0 = std::chrono::duration_cast<std::chrono::nanoseconds>(1h);
  tk->increment_by(weight0);

  std::tie(numFrames, bins) = rb->collect_cumulative();
  EXPECT_THAT(numFrames, Eq(3));
  EXPECT_THAT(bins, Each(fill_frame0 + fill_frame1 +
                         (fill_frame2 *
                          std::chrono::duration_cast<std::chrono::milliseconds>(weight0).count())));

  auto weight1 = std::chrono::duration_cast<std::chrono::nanoseconds>(2min);
  tk->increment_by(weight1);
  std::tie(numFrames, bins) = rb->collect_cumulative();
  EXPECT_THAT(numFrames, Eq(3));
  EXPECT_THAT(
      bins,
      Each(fill_frame0 + fill_frame1 +
           (fill_frame2 *
            std::chrono::duration_cast<std::chrono::milliseconds>(weight0 + weight1).count())));
}

TEST_F(RingbufferTestCases, TestCumulativeCountsEmpty) {
  auto tk = std::make_shared<TickingTimeKeeper>();
  auto rb = histogram::Ringbuffer::create(1, std::make_unique<TimeKeeperWrapper>(tk));
  std::tie(numFrames, bins) = rb->collect_cumulative();
  EXPECT_THAT(numFrames, Eq(0));
}

TEST_F(RingbufferTestCases, TestCumulativeCountsSaturate) {
  auto tk = std::make_shared<TickingTimeKeeper>();
  auto rb = histogram::Ringbuffer::create(1, std::make_unique<TimeKeeperWrapper>(tk));
  insertFrameIncrementTimeline(*rb, *tk, frame_saturate);
  auto eon = std::chrono::nanoseconds(std::numeric_limits<uint64_t>::max());
  tk->increment_by(eon);
  std::tie(numFrames, bins) = rb->collect_cumulative();
  EXPECT_THAT(numFrames, Eq(1));
  EXPECT_THAT(bins, Each(std::numeric_limits<uint64_t>::max()));
}

TEST_F(RingbufferTestCases, TimeWeightingTest) {
  static constexpr int numInsertions = 4u;
  auto tk = std::make_shared<TickingTimeKeeper>();
  auto rb = histogram::Ringbuffer::create(numInsertions, std::make_unique<TimeKeeperWrapper>(tk));

  auto weight0 = std::chrono::duration_cast<std::chrono::nanoseconds>(1ms);
  auto weight1 = std::chrono::duration_cast<std::chrono::nanoseconds>(1h);
  auto weight2 = std::chrono::duration_cast<std::chrono::nanoseconds>(1s);
  using gigasecond = std::chrono::duration<uint64_t, std::giga>;
  auto weight3 = std::chrono::duration_cast<std::chrono::nanoseconds>(gigasecond(4));

  rb->insert(frame0);
  tk->increment_by(weight0);
  rb->insert(frame1);
  tk->increment_by(weight1);
  rb->insert(frame2);
  tk->increment_by(weight2);
  rb->insert(frame3);
  tk->increment_by(weight3);

  std::tie(numFrames, bins) = rb->collect_ringbuffer_all();

  ASSERT_THAT(bins.size(), Eq(HIST_V_SIZE));
  uint64_t expected_weight = fill_frame0 * toMs(weight0) + fill_frame1 * toMs(weight1) +
                             fill_frame2 * toMs(weight2) + fill_frame3 * toMs(weight3);
  for (auto i = 0u; i < bins.size(); i++) {
    EXPECT_THAT(bins[i], Eq(expected_weight));
  }
}

int main(int argc, char **argv) {
  ::testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}