#include "quiche/http2/adapter/nghttp2_util.h"

#include "quiche/http2/adapter/nghttp2_test_utils.h"
#include "quiche/http2/adapter/test_utils.h"
#include "quiche/common/platform/api/quiche_test.h"

namespace http2 {
namespace adapter {
namespace test {
namespace {

// This send callback assumes |source|'s pointer is a TestDataSource, and
// |user_data| is a std::string.
int FakeSendCallback(nghttp2_session*, nghttp2_frame* /*frame*/,
                     const uint8_t* framehd, size_t length,
                     nghttp2_data_source* source, void* user_data) {
  auto* dest = static_cast<std::string*>(user_data);
  // Appends the frame header to the string.
  absl::StrAppend(dest, ToStringView(framehd, 9));
  auto* test_source = static_cast<TestDataSource*>(source->ptr);
  absl::string_view payload = test_source->ReadNext(length);
  // Appends the frame payload to the string.
  absl::StrAppend(dest, payload);
  return 0;
}

TEST(MakeZeroCopyDataFrameSource, EmptyPayload) {
  std::string result;

  const absl::string_view kEmptyBody = "";
  TestDataSource body1{kEmptyBody};
  // The TestDataSource is wrapped in the nghttp2_data_provider data type.
  nghttp2_data_provider provider = body1.MakeDataProvider();

  // This call transforms it back into a DataFrameSource, which is compatible
  // with the Http2Adapter API.
  std::unique_ptr<DataFrameSource> frame_source =
      MakeZeroCopyDataFrameSource(provider, &result, FakeSendCallback);
  auto [length, eof] = frame_source->SelectPayloadLength(100);
  EXPECT_EQ(length, 0);
  EXPECT_TRUE(eof);
  frame_source->Send("ninebytes", 0);
  EXPECT_EQ(result, "ninebytes");
}

TEST(MakeZeroCopyDataFrameSource, ShortPayload) {
  std::string result;

  const absl::string_view kShortBody =
      "<html><head><title>Example Page!</title></head>"
      "<body><div><span><table><tr><th><blink>Wow!!"
      "</blink></th></tr></table></span></div></body>"
      "</html>";
  TestDataSource body1{kShortBody};
  // The TestDataSource is wrapped in the nghttp2_data_provider data type.
  nghttp2_data_provider provider = body1.MakeDataProvider();

  // This call transforms it back into a DataFrameSource, which is compatible
  // with the Http2Adapter API.
  std::unique_ptr<DataFrameSource> frame_source =
      MakeZeroCopyDataFrameSource(provider, &result, FakeSendCallback);
  auto [length, eof] = frame_source->SelectPayloadLength(200);
  EXPECT_EQ(length, kShortBody.size());
  EXPECT_TRUE(eof);
  frame_source->Send("ninebytes", length);
  EXPECT_EQ(result, absl::StrCat("ninebytes", kShortBody));
}

TEST(MakeZeroCopyDataFrameSource, MultiFramePayload) {
  std::string result;

  const absl::string_view kShortBody =
      "<html><head><title>Example Page!</title></head>"
      "<body><div><span><table><tr><th><blink>Wow!!"
      "</blink></th></tr></table></span></div></body>"
      "</html>";
  TestDataSource body1{kShortBody};
  // The TestDataSource is wrapped in the nghttp2_data_provider data type.
  nghttp2_data_provider provider = body1.MakeDataProvider();

  // This call transforms it back into a DataFrameSource, which is compatible
  // with the Http2Adapter API.
  std::unique_ptr<DataFrameSource> frame_source =
      MakeZeroCopyDataFrameSource(provider, &result, FakeSendCallback);
  auto ret = frame_source->SelectPayloadLength(50);
  EXPECT_EQ(ret.first, 50);
  EXPECT_FALSE(ret.second);
  frame_source->Send("ninebyte1", ret.first);

  ret = frame_source->SelectPayloadLength(50);
  EXPECT_EQ(ret.first, 50);
  EXPECT_FALSE(ret.second);
  frame_source->Send("ninebyte2", ret.first);

  ret = frame_source->SelectPayloadLength(50);
  EXPECT_EQ(ret.first, 44);
  EXPECT_TRUE(ret.second);
  frame_source->Send("ninebyte3", ret.first);

  EXPECT_EQ(result,
            "ninebyte1<html><head><title>Example Page!</title></head><bo"
            "ninebyte2dy><div><span><table><tr><th><blink>Wow!!</blink><"
            "ninebyte3/th></tr></table></span></div></body></html>");
}

}  // namespace
}  // namespace test
}  // namespace adapter
}  // namespace http2
