// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google Inc.  All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd

#include "google/protobuf/generated_enum_util.h"

#include <algorithm>
#include <array>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <ostream>
#include <string>
#include <variant>
#include <vector>

#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/container/btree_set.h"
#include "absl/flags/marshalling.h"
#include "absl/strings/str_format.h"
#include "absl/types/span.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/unittest.pb.h"
#include "google/protobuf/unittest_lite.pb.h"


// Must be included last.
#include "google/protobuf/port_def.inc"

using testing::_;
using testing::ElementsAre;
using testing::Gt;
using testing::HasSubstr;
using testing::IsEmpty;
using testing::SizeIs;
using testing::VariantWith;

namespace google {
namespace protobuf {
namespace internal {
namespace {

TEST(GenerateEnumDataTest, DebugChecks) {
#if GTEST_HAS_DEATH_TEST
  // Not unique
  EXPECT_DEBUG_DEATH((void)GenerateEnumData({1, 1}), "sorted_and_unique");
  // Not sorted
  EXPECT_DEBUG_DEATH((void)GenerateEnumData({2, 1}), "sorted_and_unique");
#endif
}

uint32_t Make32(uint16_t a, uint16_t b) { return a | (b << 16); }
std::array<uint16_t, 2> Unmake32(uint32_t v) {
  return {static_cast<uint16_t>(v), static_cast<uint16_t>(v >> 16)};
}

struct Header {
  int16_t sequence_start;
  uint16_t sequence_length;
  uint16_t bitmap_length;
  uint16_t ordered_length;

  std::string ToString() const {
    return absl::StrFormat("(%d,%d,%d,%d)", sequence_start, sequence_length,
                           bitmap_length, ordered_length);
  }
  friend std::ostream& operator<<(std::ostream& os, Header header) {
    return os << header.ToString();
  }
};

MATCHER_P4(HeaderHas, sequence_start, sequence_length, bitmap_length,
           ordered_length, "") {
  return testing::ExplainMatchResult(sequence_start, arg.sequence_start,
                                     result_listener) &&
         testing::ExplainMatchResult(sequence_length, arg.sequence_length,
                                     result_listener) &&
         testing::ExplainMatchResult(bitmap_length, arg.bitmap_length,
                                     result_listener) &&
         testing::ExplainMatchResult(ordered_length, arg.ordered_length,
                                     result_listener);
}

Header ExtractHeader(absl::Span<const uint32_t> data) {
  return {
      static_cast<int16_t>(Unmake32(data[0])[0]),
      Unmake32(data[0])[1],
      Unmake32(data[1])[0],
      Unmake32(data[1])[1],
  };
}

TEST(GenerateEnumDataTest, BitmapSpaceOptimizationWorks) {
  std::vector<int32_t> values = {0};

  auto encoded = GenerateEnumData(values);
  EXPECT_THAT(ExtractHeader(encoded), HeaderHas(0, 1, 0, 0));
  EXPECT_THAT(encoded, SizeIs(2));

  // Adding one large value puts it on the fallback.
  values.push_back(100);
  encoded = GenerateEnumData(values);
  EXPECT_THAT(ExtractHeader(encoded), HeaderHas(0, 1, 0, 1));
  EXPECT_THAT(encoded, SizeIs(3));

  // Adding a second one still prefers the fallback.
  values.push_back(101);
  encoded = GenerateEnumData(values);
  EXPECT_THAT(ExtractHeader(encoded), HeaderHas(0, 1, 0, 2));
  EXPECT_THAT(encoded, SizeIs(4));

  // Adding two more now makes bitmap more efficient, so they are collapsed
  // to it. Because we can fit the bitmap in 128 bits, which is the same as the
  // ints.
  values.push_back(102);
  values.push_back(103);
  encoded = GenerateEnumData(values);
  EXPECT_THAT(ExtractHeader(encoded), HeaderHas(0, 1, 128, 0));
  EXPECT_THAT(encoded, SizeIs(6));

  // Add one value that falls into the existing bitmap, nothing changes.
  values.push_back(104);
  encoded = GenerateEnumData(values);
  EXPECT_THAT(ExtractHeader(encoded), HeaderHas(0, 1, 128, 0));
  EXPECT_THAT(encoded, SizeIs(6));

  // Add one value that is in the next 32 bits. It should grow the bitmap.
  values.push_back(130);
  encoded = GenerateEnumData(values);
  EXPECT_THAT(ExtractHeader(encoded), HeaderHas(0, 1, 160, 0));
  EXPECT_THAT(encoded, SizeIs(7));

  // Add one value far away, it should go into fallback.
  values.push_back(200);
  encoded = GenerateEnumData(values);
  EXPECT_THAT(ExtractHeader(encoded), HeaderHas(0, 1, 160, 1));
  EXPECT_THAT(encoded, SizeIs(8));

  // Another in the next 32-bit block will still make them fallback.
  values.push_back(230);
  encoded = GenerateEnumData(values);
  EXPECT_THAT(ExtractHeader(encoded), HeaderHas(0, 1, 160, 2));
  EXPECT_THAT(encoded, SizeIs(9));

  // One more in that same block should collapse them to bitmap.
  values.push_back(231);
  encoded = GenerateEnumData(values);
  EXPECT_THAT(ExtractHeader(encoded), HeaderHas(0, 1, 256, 0));
  EXPECT_THAT(encoded, SizeIs(10));
}

template <typename T>
std::variant<T, std::string> TryParseFlag(absl::string_view str) {
  T value;
  std::string error;
  if (!absl::ParseFlag(str, &value, &error)) {
    return error;
  }
  return value;
}

TEST(ProtoEnumTest, HasAbseilFlagSupport) {
  using T = proto2_unittest::TestAllTypes;
  using E = typename T::NestedEnum;

  // Exact match.
  EXPECT_THAT(TryParseFlag<E>("FOO"), VariantWith<E>(T::FOO));
  // Lowercase match.
  EXPECT_THAT(TryParseFlag<E>("baz"), VariantWith<E>(T::BAZ));
  // Numeric value
  EXPECT_THAT(TryParseFlag<E>("2"), VariantWith<E>(T::BAR));

  // Error
  EXPECT_THAT(TryParseFlag<E>("xxx"),
              VariantWith<std::string>(
                  HasSubstr("Invalid value 'xxx' for enum "
                            "'proto2_unittest.TestAllTypes.NestedEnum'. "
                            "Supported values are: FOO, BAR, BAZ, NEG.")));

  EXPECT_EQ("FOO", absl::UnparseFlag(T::FOO));
  EXPECT_EQ("100", absl::UnparseFlag(static_cast<E>(100)));
}

TEST(ProtoEnumTest, HasAbseilFlagSupportWithLiteEnums) {
  using T = proto2_unittest::TestAllTypesLite;
  using E = typename T::NestedEnum;

  // Exact match.
  EXPECT_THAT(TryParseFlag<E>("FOO"), VariantWith<E>(T::FOO));
  // Lowercase match.
  EXPECT_THAT(TryParseFlag<E>("baz"), VariantWith<E>(T::BAZ));
  // Numeric value
  EXPECT_THAT(TryParseFlag<E>("2"), VariantWith<E>(T::BAR));

  // We don't check errors because we don't generate them for LITE.
  EXPECT_THAT(TryParseFlag<E>("xxx"), VariantWith<std::string>(_));

  EXPECT_EQ("FOO", absl::UnparseFlag(T::FOO));
  EXPECT_EQ("100", absl::UnparseFlag(static_cast<E>(100)));
}

TEST(ProtoEnumTest, HasAbseilFlagSupportForVectorEnum) {
  using T = proto2_unittest::TestAllTypes;
  using E = typename T::NestedEnum;
  using V = std::vector<E>;

  // Empty
  EXPECT_THAT(TryParseFlag<V>(""), VariantWith<V>(V{}));
  EXPECT_EQ("", absl::UnparseFlag(V{}));
  // Single element
  EXPECT_THAT(TryParseFlag<V>("FOO"), VariantWith<V>(V{T::FOO}));
  EXPECT_EQ("FOO", absl::UnparseFlag(V{T::FOO}));
  // Many elements
  EXPECT_THAT(TryParseFlag<V>("BAZ,2,foo"),
              VariantWith<V>(V{T::BAZ, T::BAR, T::FOO}));
  EXPECT_EQ("BAZ,BAR,FOO", absl::UnparseFlag(V{T::BAZ, T::BAR, T::FOO}));

  // Error
  EXPECT_THAT(TryParseFlag<V>("FOO,xxx"),
              VariantWith<std::string>(
                  HasSubstr("Invalid value 'xxx' for enum "
                            "'proto2_unittest.TestAllTypes.NestedEnum'. "
                            "Supported values are: FOO, BAR, BAZ, NEG.")));
}

TEST(ProtoEnumTest, HasAbseilFlagSupportForVectorEnumWithLiteEnums) {
  using T = proto2_unittest::TestAllTypesLite;
  using E = typename T::NestedEnum;
  using V = std::vector<E>;

  // Empty
  EXPECT_THAT(TryParseFlag<V>(""), VariantWith<V>(V{}));
  EXPECT_EQ("", absl::UnparseFlag(V{}));
  // Single element
  EXPECT_THAT(TryParseFlag<V>("FOO"), VariantWith<V>(V{T::FOO}));
  EXPECT_EQ("FOO", absl::UnparseFlag(V{T::FOO}));
  // Many elements
  EXPECT_THAT(TryParseFlag<V>("BAZ,2,foo"),
              VariantWith<V>(V{T::BAZ, T::BAR, T::FOO}));
  EXPECT_EQ("BAZ,BAR,FOO", absl::UnparseFlag(V{T::BAZ, T::BAR, T::FOO}));

  // Error
  EXPECT_THAT(TryParseFlag<V>("FOO,xxx"), VariantWith<std::string>(_));
}

void GatherValidValues(absl::Span<const uint32_t> data, int32_t min,
                       int32_t max, absl::btree_set<int32_t>& out) {
  if (min >= max) return;
  for (int32_t i = min;; ++i) {
    if (ValidateEnum(i, data.begin())) out.insert(i);
    // We check the top limit before ++i to avoid overflows
    if (i == max) break;
  }
}

std::vector<int32_t> GetValidValues(absl::Span<const uint32_t> data,
                                    int32_t min, int32_t max) {
  // Btree to keep them sorted. Makes testing easier.
  absl::btree_set<int32_t> s;
  GatherValidValues(data, min, max, s);
  return std::vector<int32_t>(s.begin(), s.end());
}

TEST(ValidateEnumTest, SequentialRangeTest) {
  EXPECT_THAT(GetValidValues({0, 0}, -100, 100), ElementsAre());
  EXPECT_THAT(GetValidValues(
                  {// sequence start=3, length=3
                   Make32(5, 3),
                   // no bitmap, no fallback
                   Make32(0, 0)},
                  -100, 100),
              ElementsAre(5, 6, 7));
  EXPECT_THAT(GetValidValues(
                  {// sequence start=-2, length=10
                   Make32(-2, 10),
                   // no bitmap, no fallback
                   Make32(0, 0)},
                  -100, 100),
              ElementsAre(-2, -1, 0, 1, 2, 3, 4, 5, 6, 7));
}

TEST(ValidateEnumTest, BitmapRangeTest) {
  EXPECT_THAT(GetValidValues(
                  {// no sequence
                   Make32(0, 0),
                   // bitmap of 32 bits, no fallback
                   Make32(32, 0),
                   // bitmap
                   0b10011010101},
                  -100, 100),
              ElementsAre(0, 2, 4, 6, 7, 10));
  EXPECT_THAT(GetValidValues(
                  {// no sequence
                   Make32(0, 0),
                   // bitmap of 64 bits, no fallback
                   Make32(64, 0),
                   // bitmap
                   (1 << 4) | (1 << 21), 1 << 6},
                  -100, 100),
              ElementsAre(4, 21, 32 + 6));
}

TEST(ValidateEnumTest, GenerateEnumDataSequential) {
  EXPECT_THAT(GenerateEnumData({0, 1, 2, 3}), ElementsAre(
                                                  // sequence start=0, length=4
                                                  Make32(0, 4),
                                                  // no bitmap, no fallback.
                                                  Make32(0, 0)));
  EXPECT_THAT(GenerateEnumData({-2, -1, 0, 1, 2, 3}),
              ElementsAre(
                  // sequence start=-2, length=6
                  Make32(-2, 6),
                  // no bitmap, no fallback.
                  Make32(0, 0)));
}

void TestRoundTrip(absl::Span<const int32_t> values, int line) {
  auto encoded = GenerateEnumData(values);

  absl::btree_set<int32_t> s;

  // We test that all elements in `values` exist in the encoded data, and also
  // test a range of other values to verify that they do not exist in the
  // encoded data.

  // We keep track of the max seen to avoid testing the same values many times.
  int64_t max_seen = std::numeric_limits<int64_t>::min();
  const auto gather_valid_values_around = [&](int32_t v) {
    int32_t min = std::max({
        static_cast<int64_t>(v) - 100,
        static_cast<int64_t>(std::numeric_limits<int32_t>::min()),
        max_seen,
    });
    int32_t max =
        std::min(static_cast<int64_t>(v) + 100,
                 static_cast<int64_t>(std::numeric_limits<int32_t>::max()));
    max_seen = std::max(max_seen, int64_t{max});
    GatherValidValues(encoded, min, max, s);
  };

  // We look at a few values around the expected ones.
  // We could in theory test the whole int32_t domain, but that takes too long
  // to run.
  for (int32_t v : values) {
    gather_valid_values_around(v);
  }
  // Also gather some around 0, just to add more coverage, specially when
  // `values` is empty.
  gather_valid_values_around(0);

  // Skip the checks below if we are correct because they are expensive.
  if (std::equal(s.begin(), s.end(), values.begin(), values.end())) return;

  std::vector<int32_t> false_negatives;
  for (int32_t v : values) {
    if (!ValidateEnum(v, encoded.data())) false_negatives.push_back(v);
    s.erase(v);
  }
  const auto& false_positives = s;
  const auto print_data = [&] {
    auto header = ExtractHeader(encoded);
    return absl::StrFormat("line=%d header=%s", line, header.ToString());
  };
  EXPECT_THAT(false_negatives, IsEmpty())
      << "Missing values from the input. " << print_data()
      << "\nEncoded: " << testing::PrintToString(encoded);
  EXPECT_THAT(false_positives, IsEmpty())
      << "Found values not in input. " << print_data()
      << "\nEncoded: " << testing::PrintToString(encoded);
}

TEST(ValidateEnumTest, GenerateEnumDataSequentialWithOverflow) {
  std::vector<int32_t> values;
  for (int32_t i = -33000; i < 33000; ++i) {
    values.push_back(i);
  }
  const auto data = GenerateEnumData(values);
  EXPECT_THAT(ExtractHeader(data),
              HeaderHas(
                  // The sequence starts at the minimum possible value,
                  std::numeric_limits<int16_t>::min(),
                  // and it is as long as possible.
                  0xFFFF,
                  // we have some values in the bitmap
                  Gt(0),
                  // we have some in the fallback
                  Gt(0)));

  TestRoundTrip(values, __LINE__);
}

TEST(ValidateEnumTest, GenerateEnumDataBitmap) {
  EXPECT_THAT(GenerateEnumData({0, 1, 2, 4, 8, 16, 32}),
              ElementsAre(Make32(0, 3), Make32(32, 0),
                          0b100000000000000010000000100010));
  TestRoundTrip({}, __LINE__);
  TestRoundTrip({0, 1, 2, 4, 8, 16}, __LINE__);
  TestRoundTrip({0, 1, 2, 4, 8, 16, 32, 64, 128, 256}, __LINE__);
  TestRoundTrip({10000, 10001, 10002, 10004, 10006, 10008, 10010}, __LINE__);
  TestRoundTrip({std::numeric_limits<int32_t>::min(), -123123, -123, 213,
                 213213, std::numeric_limits<int32_t>::max()},
                __LINE__);
}

TEST(ValidateEnumTest, GenerateEnumDataBitmapWithOverflow) {
  std::vector<int32_t> values;
  // We step by 10 to guarantee each new value is more cost effective to add to
  // the bitmap, which would cause an overflow of the 16-bit bitmap size if we
  // didn't prevent it in the generator.
  for (int32_t i = -33000; i < 33000; i += 10) {
    values.push_back(i);
  }
  const auto data = GenerateEnumData(values);

  EXPECT_THAT(ExtractHeader(data),
              HeaderHas(_, _,
                        // we reached the maximum size for the bitmap.
                        0x10000 - 32,
                        // we have some in the fallback
                        Gt(0)));

  TestRoundTrip(values, __LINE__);
}

TEST(ValidateEnumTest, GenerateEnumDataWithOverflowOnBoth) {
  std::vector<int32_t> values;
  for (int32_t i = -33000; i < 100000; ++i) {
    values.push_back(i);
  }
  const auto data = GenerateEnumData(values);

  EXPECT_THAT(ExtractHeader(data),
              HeaderHas(
                  // The sequence starts at the minimum possible value,
                  std::numeric_limits<int16_t>::min(),
                  // and it is as long as possible.
                  0xFFFF,
                  // we reached the maximum size for the bitmap.
                  0x10000 - 32,
                  // we have some in the fallback
                  Gt(0)));

  TestRoundTrip(values, __LINE__);
}



}  // namespace
}  // namespace internal
}  // namespace protobuf
}  // namespace google

#include "google/protobuf/port_undef.inc"
