#include "upb/wire/eps_copy_input_stream.h"

#include <string.h>

#include <string>

#include "gtest/gtest.h"
#include "upb/upb.hpp"
// begin:google_only
// #include "testing/fuzzing/fuzztest.h"
// end:google_only

namespace {

TEST(EpsCopyInputStreamTest, ZeroSize) {
  upb_EpsCopyInputStream stream;
  const char* ptr = NULL;
  upb_EpsCopyInputStream_Init(&stream, &ptr, 0, false);
  EXPECT_TRUE(upb_EpsCopyInputStream_IsDoneWithCallback(&stream, &ptr, NULL));
}

// begin:google_only
//
// // We create a simple, trivial implementation of the stream that we can test
// // our real implementation against.
//
// class FakeStream {
//  public:
//   FakeStream(const std::string& data) : data_(data), offset_(0) {
//     limits_.push_back(data.size());
//   }
//
//   // If we reached one or more limits correctly, returns the number of limits
//   // ended.  If we tried to read beyond the current limit, returns -1.
//   // Otherwise, for simple success, returns 0.
//   int ReadData(int n, std::string* data) {
//     if (n > BytesUntilLimit()) return -1;
//
//     data->assign(data_.data() + offset_, n);
//     offset_ += n;
//
//     int end_limit_count = 0;
//
//     while (BytesUntilLimit() == 0) {
//       if (PopLimit()) {
//         end_limit_count++;
//       } else {
//         eof_ = true;
//         break;
//       }
//     }
//
//     return end_limit_count;
//   }
//
//   bool TryPushLimit(int limit) {
//     if (!CheckSize(limit)) return false;
//     limits_.push_back(offset_ + limit);
//     return true;
//   }
//
//   bool IsEof() const { return eof_; }
//
//  private:
//   int BytesUntilLimit() const { return limits_.back() - offset_; }
//   bool CheckSize(int size) const { return BytesUntilLimit() >= size; }
//
//   // Return false on EOF.
//   bool PopLimit() {
//     limits_.pop_back();
//     return !limits_.empty();
//   }
//
//   std::string data_;
//   // Limits, specified in absolute stream terms.
//   std::vector<int> limits_;
//   int offset_;
//   bool eof_ = false;
// };
//
// char tmp_buf[kUpb_EpsCopyInputStream_SlopBytes];
//
// class EpsStream {
//  public:
//   EpsStream(const std::string& data, bool enable_aliasing)
//       : data_(data), enable_aliasing_(enable_aliasing) {
//     ptr_ = data_.data();
//     upb_EpsCopyInputStream_Init(&eps_, &ptr_, data_.size(), enable_aliasing);
//   }
//
//   // Returns false at EOF or error.
//   int ReadData(int n, std::string* data) {
//     EXPECT_LE(n, kUpb_EpsCopyInputStream_SlopBytes);
//     if (enable_aliasing_) {
//       EXPECT_TRUE(upb_EpsCopyInputStream_AliasingAvailable(&eps_, ptr_, n));
//     }
//     // We want to verify that we can read kUpb_EpsCopyInputStream_SlopBytes
//     // safely, even if we haven't actually been requested to read that much.
//     // We copy to a global buffer so the copy can't be optimized away.
//     memcpy(&tmp_buf, ptr_, kUpb_EpsCopyInputStream_SlopBytes);
//     data->assign(tmp_buf, n);
//     ptr_ += n;
//     if (enable_aliasing_) {
//       EXPECT_TRUE(upb_EpsCopyInputStream_AliasingAvailable(&eps_, ptr_, 0));
//     }
//     return PopLimits();
//   }
//
//   int ReadString(int n, std::string* data) {
//     if (!upb_EpsCopyInputStream_CheckSize(&eps_, ptr_, n)) return -1;
//     const char* str_data = ptr_;
//     if (enable_aliasing_) {
//       EXPECT_TRUE(upb_EpsCopyInputStream_AliasingAvailable(&eps_, ptr_, n));
//     }
//     ptr_ = upb_EpsCopyInputStream_ReadString(&eps_, &str_data, n, arena_.ptr());
//     if (!ptr_) return -1;
//     if (enable_aliasing_ && n) {
//       EXPECT_GE(reinterpret_cast<uintptr_t>(str_data),
//                 reinterpret_cast<uintptr_t>(data_.data()));
//       EXPECT_LT(reinterpret_cast<uintptr_t>(str_data),
//                 reinterpret_cast<uintptr_t>(data_.data() + data_.size()));
//       EXPECT_TRUE(upb_EpsCopyInputStream_AliasingAvailable(&eps_, ptr_, 0));
//     }
//     data->assign(str_data, n);
//     return PopLimits();
//   }
//
//   bool TryPushLimit(int limit) {
//     if (!upb_EpsCopyInputStream_CheckSize(&eps_, ptr_, limit)) return false;
//     deltas_.push_back(upb_EpsCopyInputStream_PushLimit(&eps_, ptr_, limit));
//     return true;
//   }
//
//   bool IsEof() const { return eof_; }
//
//  private:
//   int PopLimits() {
//     int end_limit_count = 0;
//
//     while (IsAtLimit()) {
//       if (error_) return -1;
//       if (PopLimit()) {
//         end_limit_count++;
//       } else {
//         eof_ = true;  // EOF.
//         break;
//       }
//     }
//
//     return error_ ? -1 : end_limit_count;
//   }
//
//   bool IsAtLimit() {
//     return upb_EpsCopyInputStream_IsDoneWithCallback(
//         &eps_, &ptr_, &EpsStream::IsDoneFallback);
//   }
//
//   // Return false on EOF.
//   bool PopLimit() {
//     if (deltas_.empty()) return false;
//     upb_EpsCopyInputStream_PopLimit(&eps_, ptr_, deltas_.back());
//     deltas_.pop_back();
//     return true;
//   }
//
//   static const char* IsDoneFallback(upb_EpsCopyInputStream* e, const char* ptr,
//                                     int overrun) {
//     return _upb_EpsCopyInputStream_IsDoneFallbackInline(
//         e, ptr, overrun, &EpsStream::BufferFlipCallback);
//   }
//
//   static const char* BufferFlipCallback(upb_EpsCopyInputStream* e,
//                                         const char* old_end,
//                                         const char* new_start) {
//     EpsStream* stream = reinterpret_cast<EpsStream*>(e);
//     if (!old_end) stream->error_ = true;
//     return new_start;
//   }
//
//   upb_EpsCopyInputStream eps_;
//   std::string data_;
//   const char* ptr_;
//   std::vector<int> deltas_;
//   upb::Arena arena_;
//   bool error_ = false;
//   bool eof_ = false;
//   bool enable_aliasing_;
// };
//
// // Reads N bytes from the given position.
// struct ReadOp {
//   int bytes;  // Must be <= kUpb_EpsCopyInputStream_SlopBytes.
// };
//
// struct ReadStringOp {
//   int bytes;
// };
//
// // Pushes a new limit of N bytes from the current position.
// struct PushLimitOp {
//   int bytes;
// };
//
// typedef std::variant<ReadOp, ReadStringOp, PushLimitOp> Op;
//
// struct EpsCopyTestScript {
//   int data_size;
//   bool enable_aliasing;
//   std::vector<Op> ops;
// };
//
// auto ArbitraryEpsCopyTestScript() {
//   using ::fuzztest::Arbitrary;
//   using ::fuzztest::InRange;
//   using ::fuzztest::NonNegative;
//   using ::fuzztest::StructOf;
//   using ::fuzztest::VariantOf;
//   using ::fuzztest::VectorOf;
//
//   int max_data_size = 512;
//
//   return StructOf<EpsCopyTestScript>(
//       InRange(0, max_data_size),  // data_size
//       Arbitrary<bool>(),          // enable_aliasing
//       VectorOf(VariantOf(
//           // ReadOp
//           StructOf<ReadOp>(InRange(0, kUpb_EpsCopyInputStream_SlopBytes)),
//           // ReadStringOp
//           StructOf<ReadStringOp>(NonNegative<int>()),
//           // PushLimitOp
//           StructOf<PushLimitOp>(NonNegative<int>()))));
// }
//
// // Run a test that creates both real stream and a fake stream, and validates
// // that they have the same behavior.
// void TestAgainstFakeStream(const EpsCopyTestScript& script) {
//   std::string data(script.data_size, 'x');
//   for (int i = 0; i < script.data_size; ++i) {
//     data[i] = static_cast<char>(i & 0xff);
//   }
//
//   FakeStream fake_stream(data);
//   EpsStream eps_stream(data, script.enable_aliasing);
//
//   for (const auto& op : script.ops) {
//     if (const ReadOp* read_op = std::get_if<ReadOp>(&op)) {
//       std::string data_fake;
//       std::string data_eps;
//       int fake_result = fake_stream.ReadData(read_op->bytes, &data_fake);
//       int eps_result = eps_stream.ReadData(read_op->bytes, &data_eps);
//       EXPECT_EQ(fake_result, eps_result);
//       if (fake_result == -1) break;  // Error
//       EXPECT_EQ(data_fake, data_eps);
//       EXPECT_EQ(fake_stream.IsEof(), eps_stream.IsEof());
//       if (fake_stream.IsEof()) break;
//     } else if (const ReadStringOp* read_op = std::get_if<ReadStringOp>(&op)) {
//       std::string data_fake;
//       std::string data_eps;
//       int fake_result = fake_stream.ReadData(read_op->bytes, &data_fake);
//       int eps_result = eps_stream.ReadString(read_op->bytes, &data_eps);
//       EXPECT_EQ(fake_result, eps_result);
//       if (fake_result == -1) break;  // Error
//       EXPECT_EQ(data_fake, data_eps);
//       EXPECT_EQ(fake_stream.IsEof(), eps_stream.IsEof());
//       if (fake_stream.IsEof()) break;
//     } else if (const PushLimitOp* push = std::get_if<PushLimitOp>(&op)) {
//       EXPECT_EQ(fake_stream.TryPushLimit(push->bytes),
//                 eps_stream.TryPushLimit(push->bytes));
//     } else {
//       EXPECT_TRUE(false);  // Unknown op.
//     }
//   }
// }
//
// // Test with:
// //  $ blaze run --config=fuzztest third_party/upb:eps_copy_input_stream_test \
// //   -- --gunit_fuzz=
// FUZZ_TEST(EpsCopyFuzzTest, TestAgainstFakeStream)
//     .WithDomains(ArbitraryEpsCopyTestScript());
//
// TEST(EpsCopyFuzzTest, TestAgainstFakeStreamRegression) {
//   TestAgainstFakeStream({299,
//                          false,
//                          {
//                              PushLimitOp{2},
//                              ReadOp{14},
//                          }});
// }
//
// TEST(EpsCopyFuzzTest, AliasingEnabledZeroSizeReadString) {
//   TestAgainstFakeStream({510, true, {ReadStringOp{0}}});
// }
//
// TEST(EpsCopyFuzzTest, AliasingDisabledZeroSizeReadString) {
//   TestAgainstFakeStream({510, false, {ReadStringOp{0}}});
// }
//
// TEST(EpsCopyFuzzTest, ReadStringZero) {
//   TestAgainstFakeStream({0, true, {ReadStringOp{0}}});
// }
//
// TEST(EpsCopyFuzzTest, ReadZero) {
//   TestAgainstFakeStream({0, true, {ReadOp{0}}});
// }
//
// TEST(EpsCopyFuzzTest, ReadZeroTwice) {
//   TestAgainstFakeStream({0, true, {ReadOp{0}, ReadOp{0}}});
// }
//
// TEST(EpsCopyFuzzTest, ReadStringZeroThenRead) {
//   TestAgainstFakeStream({0, true, {ReadStringOp{0}, ReadOp{0}}});
// }
//
// TEST(EpsCopyFuzzTest, ReadStringOverflowsBufferButNotLimit) {
//   TestAgainstFakeStream({351,
//                          false,
//                          {
//                              ReadOp{7},
//                              PushLimitOp{2147483647},
//                              ReadStringOp{344},
//                          }});
// }
//
// TEST(EpsCopyFuzzTest, LastBufferAliasing) {
//   TestAgainstFakeStream({27, true, {ReadOp{12}, ReadStringOp{3}}});
// }
//
// TEST(EpsCopyFuzzTest, FirstBufferAliasing) {
//   TestAgainstFakeStream({7, true, {ReadStringOp{3}}});
// }
//
// end:google_only

}  // namespace
