// 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/benchmark.h"

#include "pw_allocator/benchmarks/measurements.h"
#include "pw_allocator/fragmentation.h"
#include "pw_allocator/test_harness.h"
#include "pw_allocator/testing.h"
#include "pw_random/xor_shift.h"
#include "pw_unit_test/framework.h"

namespace {

constexpr size_t kCapacity = 65536;
constexpr size_t kMaxSize = 64;

using AllocatorForTest = ::pw::allocator::test::AllocatorForTest<kCapacity>;
using Benchmark =
    ::pw::allocator::DefaultBlockAllocatorBenchmark<AllocatorForTest>;
using ::pw::allocator::CalculateFragmentation;
using ::pw::allocator::Measurement;
using ::pw::allocator::Measurements;
using ::pw::allocator::test::AllocationRequest;
using ::pw::allocator::test::kToken;
using ::pw::allocator::test::Request;

template <typename GetByKey>
bool IsChanged(Benchmark& benchmark, GetByKey get_by_key) {
  return get_by_key(benchmark.measurements()).count() != 0;
}

bool ByCountChanged(Benchmark& benchmark, size_t count) {
  return IsChanged(benchmark, [count](Measurements& m) -> Measurement<size_t>& {
    return m.GetByCount(count);
  });
}

TEST(BenchmarkTest, ByCount) {
  AllocatorForTest allocator;
  Benchmark benchmark(kToken, allocator);
  benchmark.set_prng_seed(1);
  benchmark.set_available(kCapacity);

  EXPECT_FALSE(ByCountChanged(benchmark, 0));

  benchmark.GenerateRequest(kMaxSize);
  EXPECT_TRUE(ByCountChanged(benchmark, 0));

  while (benchmark.num_allocations() < 9) {
    benchmark.GenerateRequest(kMaxSize);
  }
  EXPECT_FALSE(ByCountChanged(benchmark, 10));

  while (benchmark.num_allocations() < 10) {
    benchmark.GenerateRequest(kMaxSize);
  }
  EXPECT_TRUE(ByCountChanged(benchmark, 10));

  while (benchmark.num_allocations() < 99) {
    benchmark.GenerateRequest(kMaxSize);
  }
  EXPECT_FALSE(ByCountChanged(benchmark, 100));

  while (benchmark.num_allocations() < 100) {
    benchmark.GenerateRequest(kMaxSize);
  }
  EXPECT_TRUE(ByCountChanged(benchmark, 100));

  while (benchmark.num_allocations() < 999) {
    benchmark.GenerateRequest(kMaxSize);
  }
  EXPECT_FALSE(ByCountChanged(benchmark, 1000));

  while (benchmark.num_allocations() < 1000) {
    benchmark.GenerateRequest(kMaxSize);
  }
  EXPECT_TRUE(ByCountChanged(benchmark, 1000));
}

size_t ByFragmentationChanged(Benchmark& benchmark, float fragmentation) {
  return IsChanged(benchmark,
                   [fragmentation](Measurements& m) -> Measurement<float>& {
                     return m.GetByFragmentation(fragmentation);
                   });
}

TEST(BenchmarkTest, ByFragmentation) {
  AllocatorForTest allocator;
  Benchmark benchmark(kToken, allocator);
  benchmark.set_prng_seed(1);
  benchmark.set_available(kCapacity);

  EXPECT_FALSE(ByFragmentationChanged(benchmark, 0.2f));

  while (CalculateFragmentation(allocator.MeasureFragmentation()) < 0.2f) {
    benchmark.GenerateRequest(kMaxSize);
  }
  EXPECT_TRUE(ByFragmentationChanged(benchmark, 0.2f));

  while (CalculateFragmentation(allocator.MeasureFragmentation()) < 0.4f) {
    benchmark.GenerateRequest(kMaxSize);
  }
  EXPECT_TRUE(ByFragmentationChanged(benchmark, 0.4f));

  while (CalculateFragmentation(allocator.MeasureFragmentation()) < 0.6f) {
    benchmark.GenerateRequest(kMaxSize);
  }
  EXPECT_TRUE(ByFragmentationChanged(benchmark, 0.6f));

  while (CalculateFragmentation(allocator.MeasureFragmentation()) < 0.8f) {
    benchmark.GenerateRequest(kMaxSize);
  }
  EXPECT_TRUE(ByFragmentationChanged(benchmark, 0.8f));
}

bool BySizeChanged(Benchmark& benchmark, size_t size) {
  return IsChanged(benchmark, [size](Measurements& m) -> Measurement<size_t>& {
    return m.GetBySize(size);
  });
}

TEST(BenchmarkTest, BySize) {
  AllocatorForTest allocator;
  Benchmark benchmark(kToken, allocator);
  benchmark.set_prng_seed(1);
  benchmark.set_available(kCapacity);
  AllocationRequest request;

  EXPECT_FALSE(BySizeChanged(benchmark, 4096));
  request.size = 8192;
  EXPECT_TRUE(benchmark.HandleRequest(request));
  EXPECT_TRUE(BySizeChanged(benchmark, 4096));
  EXPECT_FALSE(BySizeChanged(benchmark, 4095));

  EXPECT_FALSE(BySizeChanged(benchmark, 1024));
  request.size = 4095;
  EXPECT_TRUE(benchmark.HandleRequest(request));
  EXPECT_TRUE(BySizeChanged(benchmark, 1024));
  EXPECT_FALSE(BySizeChanged(benchmark, 1023));

  EXPECT_FALSE(BySizeChanged(benchmark, 256));
  request.size = 256;
  EXPECT_TRUE(benchmark.HandleRequest(request));
  EXPECT_TRUE(BySizeChanged(benchmark, 256));
  EXPECT_FALSE(BySizeChanged(benchmark, 255));

  EXPECT_FALSE(BySizeChanged(benchmark, 64));
  request.size = 96;
  EXPECT_TRUE(benchmark.HandleRequest(request));
  EXPECT_TRUE(BySizeChanged(benchmark, 64));
  EXPECT_FALSE(BySizeChanged(benchmark, 63));

  EXPECT_FALSE(BySizeChanged(benchmark, 16));
  request.size = 63;
  EXPECT_TRUE(benchmark.HandleRequest(request));
  EXPECT_TRUE(BySizeChanged(benchmark, 16));
  EXPECT_FALSE(BySizeChanged(benchmark, 15));
}

}  // namespace
