// 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_allocator/benchmarks/measurements.h"

#include "pw_metric/metric.h"
#include "pw_unit_test/framework.h"

namespace {

using pw::allocator::Measurement;
using pw::allocator::Measurements;
using pw::allocator::internal::BenchmarkSample;

constexpr pw::metric::Token kName = PW_TOKENIZE_STRING("test");

TEST(MeasurementTest, Construct_Default) {
  Measurement<size_t> measurement(kName, 0);

  EXPECT_EQ(measurement.nanoseconds(), 0.f);
  EXPECT_FLOAT_EQ(measurement.fragmentation(), 0.f);
  EXPECT_FLOAT_EQ(measurement.largest(), 0.f);
  EXPECT_EQ(measurement.failures(), 0u);
}

TEST(MeasurementTest, Update_Once) {
  BenchmarkSample data = {
      .nanoseconds = 1000,
      .fragmentation = 0.1f,
      .largest = 4096,
      .failed = false,
  };

  Measurement<size_t> measurement(kName, 0);
  measurement.Update(data);

  EXPECT_FLOAT_EQ(measurement.nanoseconds(), 1000.f);
  EXPECT_FLOAT_EQ(measurement.fragmentation(), 0.1f);
  EXPECT_FLOAT_EQ(measurement.largest(), 4096.f);
  EXPECT_EQ(measurement.failures(), 0u);
}

TEST(MeasurementTest, Update_TwiceSame) {
  BenchmarkSample data = {
      .nanoseconds = 1000,
      .fragmentation = 0.1f,
      .largest = 4096,
      .failed = true,
  };

  Measurement<size_t> measurement(kName, 0);
  measurement.Update(data);
  measurement.Update(data);

  EXPECT_FLOAT_EQ(measurement.nanoseconds(), 1000.f);
  EXPECT_FLOAT_EQ(measurement.fragmentation(), 0.1f);
  EXPECT_FLOAT_EQ(measurement.largest(), 4096.f);
  EXPECT_EQ(measurement.failures(), 2u);
}

TEST(MeasurementTest, Update_TwiceDifferent) {
  BenchmarkSample data = {
      .nanoseconds = 1000,
      .fragmentation = 0.1f,
      .largest = 4096,
      .failed = true,
  };

  Measurement<size_t> measurement(kName, 0);
  measurement.Update(data);

  data.nanoseconds = 2000;
  data.fragmentation = 0.04f;
  data.largest = 2048;
  data.failed = false;
  measurement.Update(data);

  EXPECT_FLOAT_EQ(measurement.nanoseconds(), 1500.f);
  EXPECT_FLOAT_EQ(measurement.fragmentation(), 0.07f);
  EXPECT_FLOAT_EQ(measurement.largest(), 3072.f);
  EXPECT_EQ(measurement.failures(), 1u);
}

TEST(MeasurementTest, Update_ManyVarious) {
  BenchmarkSample data;
  data.largest = 8192;
  Measurement<size_t> measurement(kName, 0);
  for (size_t i = 0; i < 10; ++i) {
    data.nanoseconds += 100.f;
    data.fragmentation += 0.02f;
    data.largest -= 512;
    data.failed = !data.failed;
    measurement.Update(data);
  }

  // sum([1..10]) is 55, for averages that are 5.5 times each increment.
  EXPECT_FLOAT_EQ(measurement.nanoseconds(), 5.5f * 100);
  EXPECT_FLOAT_EQ(measurement.fragmentation(), 5.5f * 0.02f);
  EXPECT_FLOAT_EQ(measurement.largest(), 8192 - (5.5f * 512));
  EXPECT_EQ(measurement.failures(), 5u);
}

class TestMeasurements : public Measurements {
 public:
  TestMeasurements() : Measurements(kName) {}
  ~TestMeasurements() { Measurements::Clear(); }
  using Measurements::AddByCount;
  using Measurements::AddByFragmentation;
  using Measurements::AddBySize;
  using Measurements::GetByCount;
  using Measurements::GetByFragmentation;
  using Measurements::GetBySize;
};

TEST(MeasurementsTest, ByCount) {
  Measurement<size_t> at_least_0(kName, 0);
  Measurement<size_t> at_least_10(kName, 10);
  Measurement<size_t> at_least_100(kName, 100);

  TestMeasurements by_count;
  by_count.AddByCount(at_least_0);
  by_count.AddByCount(at_least_10);
  by_count.AddByCount(at_least_100);

  EXPECT_EQ(&(by_count.GetByCount(0)), &at_least_0);
  EXPECT_EQ(&(by_count.GetByCount(9)), &at_least_0);
  EXPECT_EQ(&(by_count.GetByCount(10)), &at_least_10);
  EXPECT_EQ(&(by_count.GetByCount(99)), &at_least_10);
  EXPECT_EQ(&(by_count.GetByCount(100)), &at_least_100);
  EXPECT_EQ(&(by_count.GetByCount(size_t(-1))), &at_least_100);
}

TEST(MeasurementsTest, ByFragmentation) {
  Measurement<float> bottom_third(kName, 0.0f);
  Measurement<float> middle_third(kName, 0.33f);
  Measurement<float> top_third(kName, 0.66f);

  TestMeasurements by_fragmentation;
  by_fragmentation.AddByFragmentation(bottom_third);
  by_fragmentation.AddByFragmentation(middle_third);
  by_fragmentation.AddByFragmentation(top_third);

  EXPECT_EQ(&(by_fragmentation.GetByFragmentation(0)), &bottom_third);
  EXPECT_EQ(&(by_fragmentation.GetByFragmentation(0.3299)), &bottom_third);
  EXPECT_EQ(&(by_fragmentation.GetByFragmentation(0.33f)), &middle_third);
  EXPECT_EQ(&(by_fragmentation.GetByFragmentation(0.6599f)), &middle_third);
  EXPECT_EQ(&(by_fragmentation.GetByFragmentation(0.66f)), &top_third);
  EXPECT_EQ(&(by_fragmentation.GetByFragmentation(1.0f)), &top_third);
}

TEST(MeasurementsTest, BySize) {
  Measurement<size_t> at_least_0(kName, 0);
  Measurement<size_t> at_least_16(kName, 0x10);
  Measurement<size_t> at_least_256(kName, 0x100);

  TestMeasurements by_size;
  by_size.AddBySize(at_least_0);
  by_size.AddBySize(at_least_16);
  by_size.AddBySize(at_least_256);

  EXPECT_EQ(&(by_size.GetBySize(0)), &at_least_0);
  EXPECT_EQ(&(by_size.GetBySize(0xf)), &at_least_0);
  EXPECT_EQ(&(by_size.GetBySize(0x10)), &at_least_16);
  EXPECT_EQ(&(by_size.GetBySize(0xff)), &at_least_16);
  EXPECT_EQ(&(by_size.GetBySize(0x100)), &at_least_256);
  EXPECT_EQ(&(by_size.GetBySize(size_t(-1))), &at_least_256);
}

}  // namespace
