#include <algorithm>
#include <cstdint>
#include <memory>
#include <vector>

#include <benchmark/benchmark.h>
#include "absl/log/absl_check.h"
#include "google/protobuf/repeated_ptr_field.h"
#include "google/protobuf/rust/test/benchmarks/bench_data.pb.h"
#include "google/protobuf/rust/test/benchmarks/bench_data.upb.proto.h"
#include "protos/protos.h"

using benchmarks::BenchData;

#ifdef BENCHMARK_UPB
#define PROTO_BENCHMARK(NAME) EXTERN_BENCHMARK(NAME##_upb);
#else
#define PROTO_BENCHMARK(NAME) EXTERN_BENCHMARK(NAME##_cpp);
#endif

#define EXTERN_BENCHMARK(NAME)              \
  extern "C" {                              \
  void NAME##_bench();                      \
  }                                         \
  void BM_##NAME(benchmark::State& state) { \
    for (auto s : state) {                  \
      NAME##_bench();                       \
    }                                       \
  }                                         \
  BENCHMARK(BM_##NAME);

void BM_set_string_cpp(benchmark::State& state) {
  for (auto s : state) {
    auto data = std::make_unique<BenchData>();
    data->set_name(
        "a relatively long string that will avoid any short string "
        "optimizations.");
  }
}

BENCHMARK(BM_set_string_cpp);

PROTO_BENCHMARK(set_string_rs);

void BM_set_int_cpp(benchmark::State& state) {
  for (auto s : state) {
    auto data = std::make_unique<BenchData>();
    data->set_num2(123456789);
    ABSL_CHECK_EQ(data->num2(), 123456789);
  }
}

BENCHMARK(BM_set_int_cpp);

PROTO_BENCHMARK(set_int_rs);

extern "C" {
void benchmark_thunk_set_num2_rs(void* data, int32_t num2);
}

void BM_set_int_cpp_roundtrip(benchmark::State& state) {
  for (auto s : state) {
    auto data = std::make_unique<BenchData>();
    benchmark_thunk_set_num2_rs((void*)data.get(), 123456789);
    ABSL_CHECK_EQ(data->num2(), 123456789);
  }
}

BENCHMARK(BM_set_int_cpp_roundtrip);

void BM_add_10_repeated_msg_copy_cpp(benchmark::State& state) {
  for (auto s : state) {
    auto data = std::make_unique<BenchData>();
    for (int i = 0; i < 10; ++i) {
      auto sub_data = std::make_unique<BenchData>();
      sub_data->set_num2(i);
      *data->add_subs() = *sub_data;
    }
  }
}

BENCHMARK(BM_add_10_repeated_msg_copy_cpp);

PROTO_BENCHMARK(add_10_repeated_msg_rs);

void BM_add_10_repeated_msg_direct_cpp(benchmark::State& state) {
  for (auto s : state) {
    auto data = std::make_unique<BenchData>();
    for (int i = 0; i < 10; ++i) {
      auto sub_data = data->add_subs();
      sub_data->set_num2(i);
    }
  }
}

BENCHMARK(BM_add_10_repeated_msg_direct_cpp);

void BM_copy_from_10_repeated_msg_cpp(benchmark::State& state) {
  auto source = std::make_unique<BenchData>();
  for (int i = 0; i < 10; ++i) {
    auto sub_data = source->add_subs();
    sub_data->set_num2(i);
  }
  for (auto s : state) {
    benchmarks::BenchData data;
    *data.mutable_subs() = source->subs();
  }
}

BENCHMARK(BM_copy_from_10_repeated_msg_cpp);

void BM_back_inserter_from_10_repeated_msg_cpp(benchmark::State& state) {
  auto source = std::make_unique<BenchData>();
  for (int i = 0; i < 10; ++i) {
    auto sub_data = source->add_subs();
    sub_data->set_num2(i);
  }
  for (auto s : state) {
    benchmarks::BenchData data;
    std::copy(source->subs().begin(), source->subs().end(),
              google::protobuf::RepeatedFieldBackInserter(data.mutable_subs()));
  }
}

BENCHMARK(BM_back_inserter_from_10_repeated_msg_cpp);

PROTO_BENCHMARK(copy_from_10_repeated_msg_rs);

PROTO_BENCHMARK(extend_10_repeated_msg_rs);

void BM_add_100_ints_cpp(benchmark::State& state) {
  for (auto s : state) {
    auto data = std::make_unique<BenchData>();
    for (int i = 0; i < 100; ++i) {
      data->mutable_nums()->Add(i);
    }
  }
}

BENCHMARK(BM_add_100_ints_cpp);

void BM_add_100_ints_upb(benchmark::State& state) {
  for (auto s : state) {
    ::protos::Arena arena;
    auto data = ::protos::CreateMessage<benchmarks::protos::BenchData>(arena);
    for (int i = 0; i < 100; ++i) {
      data.add_nums(i);
    }
  }
}

BENCHMARK(BM_add_100_ints_upb);

PROTO_BENCHMARK(add_100_ints_rs);

extern "C" {
void benchmark_thunk_add_num_rs(void* data, int32_t num);
}

void BM_add_100_ints_rs_roundtrip(benchmark::State& state) {
  for (auto s : state) {
    auto data = std::make_unique<BenchData>();
    for (int i = 0; i < 100; ++i) {
      benchmark_thunk_add_num_rs((void*)data.get(), i);
    }
  }
}

BENCHMARK(BM_add_100_ints_rs_roundtrip);

void BM_copy_from_100_ints_cpp(benchmark::State& state) {
  auto source = std::make_unique<BenchData>();
  for (int i = 0; i < 100; ++i) {
    source->add_nums(i);
  }
  for (auto s : state) {
    auto data = std::make_unique<BenchData>();
    *data->mutable_nums() = source->nums();
    ABSL_CHECK_EQ(data->nums()[99], 99);
  }
}

BENCHMARK(BM_copy_from_100_ints_cpp);

void BM_copy_from_100_ints_upb(benchmark::State& state) {
  ::protos::Arena arena;
  auto source = ::protos::CreateMessage<benchmarks::protos::BenchData>(arena);
  for (int i = 0; i < 100; ++i) {
    source.add_nums(i);
  }
  for (auto s : state) {
    auto data = ::protos::CreateMessage<benchmarks::protos::BenchData>(arena);
    data.resize_nums(source.nums_size());
    std::copy(source.nums().begin(), source.nums().end(),
              data.mutable_nums()->begin());
    ABSL_CHECK_EQ(data.nums()[99], 99);
  }
}

BENCHMARK(BM_copy_from_100_ints_upb);

PROTO_BENCHMARK(copy_from_100_ints_rs);

PROTO_BENCHMARK(extend_100_ints_rs);

EXTERN_BENCHMARK(extend_100_ints_vec_rs);

PROTO_BENCHMARK(sum_1000_ints_rs);

EXTERN_BENCHMARK(sum_1000_ints_vec_rs);

void BM_sum_1000_ints_cpp(benchmark::State& state) {
  auto source = std::make_unique<BenchData>();
  for (int i = 0; i < 1000; ++i) {
    source->add_nums(i);
  }

  for (auto s : state) {
    int sum = 0;
    for (auto x : source->nums()) {
      sum += x;
    }
    ABSL_CHECK_EQ(sum, 499500);
  }
}

BENCHMARK(BM_sum_1000_ints_cpp);

void BM_sum_1000_ints_upb(benchmark::State& state) {
  ::protos::Arena arena;
  auto data = ::protos::CreateMessage<benchmarks::protos::BenchData>(arena);
  for (int i = 0; i < 1000; ++i) {
    data.add_nums(i);
  }
  for (auto s : state) {
    int sum = 0;
    for (auto x : data.nums()) {
      sum += x;
    }
    ABSL_CHECK_EQ(sum, 499500);
  }
}

BENCHMARK(BM_sum_1000_ints_upb);

void BM_sum_1000_ints_vector(benchmark::State& state) {
  std::vector<int32_t> nums;
  nums.reserve(1000);
  for (int i = 0; i < 1000; ++i) {
    nums.push_back(i);
  }
  for (auto s : state) {
    int sum = 0;
    for (auto x : nums) {
      sum += x;
    }
    ABSL_CHECK_EQ(sum, 499500);
  }
}

BENCHMARK(BM_sum_1000_ints_vector);
