// Copyright 2021 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_protobuf/encoder.h"

#include "pw_bytes/array.h"
#include "pw_bytes/span.h"
#include "pw_protobuf/serialized_size.h"
#include "pw_span/span.h"
#include "pw_stream/memory_stream.h"
#include "pw_unit_test/framework.h"

namespace pw::protobuf {
namespace {

using stream::MemoryWriter;

// The tests in this file use the following proto message schemas.
//
//   message TestProto {
//     uint32 magic_number = 1;
//     sint32 ziggy = 2;
//     fixed64 cycles = 3;
//     float ratio = 4;
//     string error_message = 5;
//     NestedProto nested = 6;
//   }
//
//   message NestedProto {
//     string hello = 1;
//     uint32 id = 2;
//     repeated DoubleNestedProto pair = 3;
//   }
//
//   message DoubleNestedProto {
//     string key = 1;
//     string value = 2;
//   }
//

constexpr uint32_t kTestProtoMagicNumberField = 1;
constexpr uint32_t kTestProtoZiggyField = 2;
constexpr uint32_t kTestProtoCyclesField = 3;
constexpr uint32_t kTestProtoRatioField = 4;
constexpr uint32_t kTestProtoErrorMessageField = 5;
constexpr uint32_t kTestProtoNestedField = 6;
constexpr uint32_t kTestProtoPayloadFromStreamField = 7;

constexpr uint32_t kNestedProtoHelloField = 1;
constexpr uint32_t kNestedProtoIdField = 2;
constexpr uint32_t kNestedProtoPairField = 3;

constexpr uint32_t kDoubleNestedProtoKeyField = 1;
constexpr uint32_t kDoubleNestedProtoValueField = 2;

TEST(StreamEncoder, EncodePrimitives) {
  // TestProto tp;
  // tp.magic_number = 42;
  // tp.ziggy = -13;
  // tp.cycles = 0xdeadbeef8badf00d;
  // tp.ratio = 1.618034;
  // tp.error_message = "broken 💩";
  // tp.payload_from_stream = "byreader"

  // Hand-encoded version of the above.
  // clang-format off
  constexpr uint8_t encoded_proto[] = {
    // magic_number [varint k=1]
    0x08, 0x2a,
    // ziggy [varint k=2]
    0x10, 0x19,
    // cycles [fixed64 k=3]
    0x19, 0x0d, 0xf0, 0xad, 0x8b, 0xef, 0xbe, 0xad, 0xde,
    // ratio [fixed32 k=4]
    0x25, 0xbd, 0x1b, 0xcf, 0x3f,
    // error_message [delimited k=5],
    0x2a, 0x0b, 'b', 'r', 'o', 'k', 'e', 'n', ' ',
    // poop!
    0xf0, 0x9f, 0x92, 0xa9,
    // payload_from_stream [delimited k=7]
    0x3a, 0x08, 'b', 'y', 'r', 'e', 'a', 'd', 'e', 'r',
  };
  // clang-format on
  std::byte encode_buffer[64];
  std::byte dest_buffer[64];
  // This writer isn't necessary, it's just the most testable way to exercise
  // a stream interface. Use a MemoryEncoder when encoding a proto directly to
  // an in-memory buffer.
  MemoryWriter writer(dest_buffer);
  StreamEncoder encoder(writer, encode_buffer);

  EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());
  EXPECT_EQ(writer.bytes_written(), 2u);
  EXPECT_EQ(encoder.WriteSint32(kTestProtoZiggyField, -13), OkStatus());
  EXPECT_EQ(encoder.WriteFixed64(kTestProtoCyclesField, 0xdeadbeef8badf00d),
            OkStatus());
  EXPECT_EQ(encoder.WriteFloat(kTestProtoRatioField, 1.618034f), OkStatus());
  EXPECT_EQ(encoder.WriteString(kTestProtoErrorMessageField, "broken 💩"),
            OkStatus());

  const std::string_view kReaderMessage = "byreader";
  stream::MemoryReader msg_reader(as_bytes(span(kReaderMessage)));
  std::byte stream_pipe_buffer[1];
  EXPECT_EQ(encoder.WriteStringFromStream(kTestProtoPayloadFromStreamField,
                                          msg_reader,
                                          kReaderMessage.size(),
                                          stream_pipe_buffer),
            OkStatus());

  ASSERT_EQ(encoder.status(), OkStatus());
  ConstByteSpan result = writer.WrittenData();
  EXPECT_EQ(result.size(), sizeof(encoded_proto));
  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
            0);
}

TEST(StreamEncoder, EncodeInsufficientSpace) {
  std::byte encode_buffer[12];
  MemoryEncoder encoder(encode_buffer);

  // 2 bytes.
  EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());
  // 2 bytes.
  EXPECT_EQ(encoder.WriteSint32(kTestProtoZiggyField, -13), OkStatus());
  // 9 bytes; not enough space! The encoder will start writing the field but
  // should rollback when it realizes it doesn't have enough space.
  EXPECT_EQ(encoder.WriteFixed64(kTestProtoCyclesField, 0xdeadbeef8badf00d),
            Status::ResourceExhausted());
  // Any further write operations should fail.
  EXPECT_EQ(encoder.WriteFloat(kTestProtoRatioField, 1.618034f),
            Status::ResourceExhausted());

  ASSERT_EQ(encoder.status(), Status::ResourceExhausted());
}

TEST(StreamEncoder, EncodeInvalidArguments) {
  std::byte encode_buffer[12];
  MemoryEncoder encoder(encode_buffer);

  EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());
  // Invalid proto field numbers.
  EXPECT_EQ(encoder.WriteUint32(0, 1337), Status::InvalidArgument());

  // TODO(amontanez): Does it make sense to support this?
  // encoder.Clear();

  EXPECT_EQ(encoder.WriteString(1u << 31, "ha"), Status::InvalidArgument());

  // TODO(amontanez): Does it make sense to support this?
  // encoder.Clear();

  EXPECT_EQ(encoder.WriteBool(19091, false), Status::InvalidArgument());
  ASSERT_EQ(encoder.status(), Status::InvalidArgument());
}

TEST(StreamEncoder, WriteBytesWithCallback) {
  constexpr uint32_t kArbitraryFieldNumber = 3;
  std::array<std::byte, 16> encode_buffer;
  constexpr auto kData = bytes::Initialized<8>(0xaa);

  MemoryEncoder encoder(encode_buffer);
  EXPECT_EQ(encoder.WriteBytes(kArbitraryFieldNumber,
                               kData.size(),
                               [&kData](stream::Writer& writer) -> Status {
                                 ConstByteSpan data_span(kData);
                                 PW_TRY(writer.Write(data_span.first(4)));
                                 return writer.Write(data_span.subspan(4));
                               }),
            OkStatus());
  EXPECT_EQ(encoder.status(), OkStatus());

  EXPECT_EQ(encoder.size(),
            SizeOfDelimitedField(kArbitraryFieldNumber, kData.size()));
}

TEST(StreamEncoder, WriteBytesWithCallbackZeroLength) {
  constexpr uint32_t kArbitraryFieldNumber = 3;
  std::array<std::byte, 16> encode_buffer;
  MemoryEncoder encoder(encode_buffer);

  bool invoked = false;
  EXPECT_EQ(encoder.WriteBytes(kArbitraryFieldNumber,
                               0,
                               [&invoked](stream::Writer&) -> Status {
                                 invoked = true;
                                 return OkStatus();
                               }),
            OkStatus());
  EXPECT_EQ(encoder.status(), OkStatus());
  EXPECT_FALSE(invoked);
}

TEST(StreamEncoder, WriteBytesWithCallbackForwardsCallbackReturn) {
  constexpr uint32_t kArbitraryFieldNumber = 3;
  std::array<std::byte, 16> encode_buffer;
  MemoryEncoder encoder(encode_buffer);

  EXPECT_EQ(encoder.WriteBytes(kArbitraryFieldNumber,
                               8,
                               [](stream::Writer&) -> Status {
                                 return Status::Unimplemented();
                               }),
            Status::Unimplemented());
  EXPECT_EQ(encoder.status(), Status::Unimplemented());
}

TEST(StreamEncoder, WriteBytesWithCallbackFailsIfSizeMismatch) {
  constexpr uint32_t kArbitraryFieldNumber = 3;
  std::array<std::byte, 16> encode_buffer;
  constexpr auto kData = bytes::Initialized<8>(0xaa);

  MemoryEncoder encoder1(encode_buffer);

  // Write larger than specified size.
  EXPECT_EQ(encoder1.WriteBytes(kArbitraryFieldNumber,
                                kData.size() - 2,
                                [&kData](stream::Writer& writer) -> Status {
                                  return writer.Write(kData);
                                }),
            Status::OutOfRange());
  EXPECT_EQ(encoder1.status(), Status::OutOfRange());

  MemoryEncoder encoder2(encode_buffer);

  // Write smaller than specified size.
  EXPECT_EQ(encoder2.WriteBytes(kArbitraryFieldNumber,
                                kData.size() + 2,
                                [&kData](stream::Writer& writer) -> Status {
                                  return writer.Write(kData);
                                }),
            Status::DataLoss());
  EXPECT_EQ(encoder2.status(), Status::DataLoss());
}

// clang-format off
constexpr auto kExpectedDoubleNestedEncodedProto = bytes::Array<
  // magic_number
  0x08, 0x2a,
  // nested header (key, size)
  0x32, 0x30,
    // nested.hello
    0x0a, 0x05, 'w', 'o', 'r', 'l', 'd',
    // nested.pair[0] header (key, size) [MIX]
    0x1a, 0x10,
      // nested.pair[0].key
      0x0a, 0x07, 'v', 'e', 'r', 's', 'i', 'o', 'n',
      // nested.pair[0].value
      0x12, 0x05, '2', '.', '9', '.', '1',
    // nested.id
    0x10, 0xe7, 0x07,
    // nested.pair[1] header (key, size)
    0x1a, 0x12,
      // nested.pair[1].key
      0x0a, 0x06, 'd', 'e', 'v', 'i', 'c', 'e',
      // nested.pair[1].value
      0x12, 0x08, 'l', 'e', 'f', 't', '-', 's', 'o', 'c',
  // ziggy
  0x10, 0x19
>();
// clang-format on

// Encode a (double) nested proto using the traditional single-pass,
// scratch-based encoder.
TEST(StreamEncoder, Nested) {
  // This is the largest complete submessage in this test.
  constexpr size_t kLargestSubmessageSize = 0x30;
  constexpr size_t kScratchBufferSize =
      MaxScratchBufferSize(kLargestSubmessageSize, 2);
  std::byte encode_buffer[kScratchBufferSize];
  std::byte dest_buffer[128];
  MemoryWriter writer(dest_buffer);
  StreamEncoder encoder(writer, encode_buffer);

  // TestProto test_proto;
  // test_proto.magic_number = 42;
  EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());

  {
    // NestedProto& nested_proto = test_proto.nested;
    StreamEncoder nested_proto =
        encoder.GetNestedEncoder(kTestProtoNestedField);
    // nested_proto.hello = "world";
    EXPECT_EQ(nested_proto.WriteString(kNestedProtoHelloField, "world"),
              OkStatus());

    {
      // DoubleNestedProto& double_nested_proto = nested_proto.append_pair();
      StreamEncoder double_nested_proto =
          nested_proto.GetNestedEncoder(kNestedProtoPairField);
      // double_nested_proto.key = "version";
      EXPECT_EQ(double_nested_proto.WriteString(kDoubleNestedProtoKeyField,
                                                "version"),
                OkStatus());
      // double_nested_proto.value = "2.9.1";
      EXPECT_EQ(double_nested_proto.WriteString(kDoubleNestedProtoValueField,
                                                "2.9.1"),
                OkStatus());
    }  // end DoubleNestedProto

    // nested_proto.id = 999;
    EXPECT_EQ(nested_proto.WriteUint32(kNestedProtoIdField, 999), OkStatus());

    {
      // DoubleNestedProto& double_nested_proto = nested_proto.append_pair();
      StreamEncoder double_nested_proto =
          nested_proto.GetNestedEncoder(kNestedProtoPairField);
      // double_nested_proto.key = "device";
      EXPECT_EQ(
          double_nested_proto.WriteString(kDoubleNestedProtoKeyField, "device"),
          OkStatus());
      // double_nested_proto.value = "left-soc";
      EXPECT_EQ(double_nested_proto.WriteString(kDoubleNestedProtoValueField,
                                                "left-soc"),
                OkStatus());
      // Rely on destructor for finalization.
    }  // end DoubleNestedProto
  }  // end NestedProto

  // test_proto.ziggy = -13;
  EXPECT_EQ(encoder.WriteSint32(kTestProtoZiggyField, -13), OkStatus());

  // Verify success
  ASSERT_EQ(encoder.status(), OkStatus());
  EXPECT_EQ(writer.bytes_written(), kExpectedDoubleNestedEncodedProto.size());
  EXPECT_TRUE(std::equal(writer.begin(),
                         writer.end(),
                         kExpectedDoubleNestedEncodedProto.begin(),
                         kExpectedDoubleNestedEncodedProto.end()));
}

// Encode a (double) nested proto using the two-pass, scratch-less encoder.
TEST(StreamEncoder, NestedDoubleTwoPass) {
  std::byte dest_buffer[128];
  MemoryWriter writer(dest_buffer);
  StreamEncoder encoder(writer, {});  // No scratch buffer!

  // TestProto test_proto;
  // test_proto.magic_number = 42;
  EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());

  // Exercise flexible capture
  uint32_t id = 999;
  std::string_view key1 = "version";
  std::string_view value1 = "2.9.1";
  std::string_view key2 = "device";
  std::string_view value2 = "left-soc";

  // NestedProto& nested_proto = test_proto.nested;
  auto status = encoder.WriteNestedMessage(
      kTestProtoNestedField, [&](StreamEncoder& nested_proto) {
        // nested_proto.hello = "one";
        PW_TRY(nested_proto.WriteString(kNestedProtoHelloField, "world"));

        // DoubleNestedProto& double_nested_proto = nested_proto.append_pair();
        PW_TRY(nested_proto.WriteNestedMessage(
            kNestedProtoPairField, [&](StreamEncoder& double_nested_proto) {
              PW_TRY(double_nested_proto.WriteString(kDoubleNestedProtoKeyField,
                                                     key1));
              PW_TRY(double_nested_proto.WriteString(
                  kDoubleNestedProtoValueField, value1));
              return OkStatus();
            }));

        // nested_proto.id = 999;
        PW_TRY(nested_proto.WriteUint32(kNestedProtoIdField, id));

        // DoubleNestedProto& double_nested_proto = nested_proto.append_pair();
        PW_TRY(nested_proto.WriteNestedMessage(
            kNestedProtoPairField, [&](StreamEncoder& double_nested_proto) {
              PW_TRY(double_nested_proto.WriteString(kDoubleNestedProtoKeyField,
                                                     key2));
              PW_TRY(double_nested_proto.WriteString(
                  kDoubleNestedProtoValueField, value2));
              return OkStatus();
            }));

        return OkStatus();
      });
  PW_TEST_EXPECT_OK(status);

  // test_proto.ziggy = -13;
  EXPECT_EQ(encoder.WriteSint32(kTestProtoZiggyField, -13), OkStatus());

  // Verify success
  ASSERT_EQ(encoder.status(), OkStatus());
  EXPECT_EQ(writer.bytes_written(), kExpectedDoubleNestedEncodedProto.size());
  EXPECT_TRUE(std::equal(writer.begin(),
                         writer.end(),
                         kExpectedDoubleNestedEncodedProto.begin(),
                         kExpectedDoubleNestedEncodedProto.end()));
}

// Encode a (double) nested proto using a mix of the two-pass, scratch-less
// encoder and the traditional single-pass, scratch-based encoder. See [MIX].
TEST(StreamEncoder, NestedDoubleTwoPassMixed) {
  // This is the largest complete submessage in this test.
  // [MIX] This is only used for the submessage written using the single-pass,
  // scratch-based encoder.
  constexpr size_t kLargestSubmessageSize = 18;
  constexpr size_t kScratchBufferSize =
      MaxScratchBufferSize(kLargestSubmessageSize, 2);
  std::byte encode_buffer[kScratchBufferSize];
  std::byte dest_buffer[128];
  MemoryWriter writer(dest_buffer);
  StreamEncoder encoder(writer, encode_buffer);  // Minimal scratch buffer

  // TestProto test_proto;
  // test_proto.magic_number = 42;
  EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());

  // NestedProto& nested_proto = test_proto.nested;
  auto status = encoder.WriteNestedMessage(
      kTestProtoNestedField, [](StreamEncoder& nested_proto) {
        // nested_proto.hello = "one";
        PW_TRY(nested_proto.WriteString(kNestedProtoHelloField, "world"));

        // [MIX] Use the single-pass, scratch-based encoder for this first
        // double-nested message.
        {
          // DoubleNestedProto& double_nested_proto =
          // nested_proto.append_pair();
          StreamEncoder double_nested_proto =
              nested_proto.GetNestedEncoder(kNestedProtoPairField);
          // double_nested_proto.key = "version";
          EXPECT_EQ(double_nested_proto.WriteString(kDoubleNestedProtoKeyField,
                                                    "version"),
                    OkStatus());
          // double_nested_proto.value = "2.9.1";
          EXPECT_EQ(double_nested_proto.WriteString(
                        kDoubleNestedProtoValueField, "2.9.1"),
                    OkStatus());
        }  // end DoubleNestedProto

        // nested_proto.id = 999;
        PW_TRY(nested_proto.WriteUint32(kNestedProtoIdField, 999));

        // Use the two-pass encoder for the second double-nested message.
        // DoubleNestedProto& double_nested_proto = nested_proto.append_pair();
        PW_TRY(nested_proto.WriteNestedMessage(
            kNestedProtoPairField, [](StreamEncoder& double_nested_proto) {
              PW_TRY(double_nested_proto.WriteString(kDoubleNestedProtoKeyField,
                                                     "device"));
              PW_TRY(double_nested_proto.WriteString(
                  kDoubleNestedProtoValueField, "left-soc"));
              return OkStatus();
            }));

        return OkStatus();
      });
  PW_TEST_EXPECT_OK(status);

  // test_proto.ziggy = -13;
  EXPECT_EQ(encoder.WriteSint32(kTestProtoZiggyField, -13), OkStatus());

  // Verify success
  ASSERT_EQ(encoder.status(), OkStatus());
  EXPECT_EQ(writer.bytes_written(), kExpectedDoubleNestedEncodedProto.size());
  EXPECT_TRUE(std::equal(writer.begin(),
                         writer.end(),
                         kExpectedDoubleNestedEncodedProto.begin(),
                         kExpectedDoubleNestedEncodedProto.end()));
}

TEST(StreamEncoder, NestedTwoPassFailsIfInconsistentLonger) {
  std::byte dest_buffer[128];
  MemoryWriter writer(dest_buffer);
  StreamEncoder encoder(writer, {});

  unsigned int pass = 0;
  auto status = encoder.WriteNestedMessage(
      kTestProtoNestedField, [&pass](StreamEncoder& nested_proto) {
        // BAD BEHAVIOR: Writing different content on each pass.
        const char* value = (pass++ == 0) ? "first" : "much much longer!";
        PW_TRY(nested_proto.WriteString(kNestedProtoHelloField, value));
        return OkStatus();
      });

  // NOTE: This could reasonably return either RESOURCE_EXHAUSTED or
  // OUT_OF_RANGE. Stream::Write() can return either, depending on a boundary
  // condition, but StreamEncoder::UpdateStatusForWrite() checks
  // writer_.ConservativeWriteLimit() explicitly and returns
  // RESOURCE_EXHAUSTED.
  EXPECT_EQ(status, Status::ResourceExhausted());
  EXPECT_EQ(status, encoder.status());
}

TEST(StreamEncoder, NestedTwoPassFailsIfInconsistentShorter) {
  std::byte dest_buffer[128];
  MemoryWriter writer(dest_buffer);
  StreamEncoder encoder(writer, {});

  unsigned int pass = 0;
  auto status = encoder.WriteNestedMessage(
      kTestProtoNestedField, [&pass](StreamEncoder& nested_proto) {
        // BAD BEHAVIOR: Writing different content on each pass.
        const char* value = (pass++ == 0) ? "the first pass" : "short";
        PW_TRY(nested_proto.WriteString(kNestedProtoHelloField, value));
        return OkStatus();
      });

  // This comes from the check in WriteNestedMessage().
  EXPECT_EQ(status, Status::OutOfRange());
  EXPECT_EQ(status, encoder.status());
}

TEST(StreamEncoder, NestedTwoPassRespectsEmptyEncoderBehaviorWriteField) {
  std::byte dest_buffer[128];
  MemoryWriter writer(dest_buffer);
  StreamEncoder encoder(writer, {});

  // TestProto test_proto;
  // test_proto.magic_number = 42;
  EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());

  auto status = encoder.WriteNestedMessage(
      kTestProtoNestedField,
      [](StreamEncoder& /*nested_proto*/) {
        // Don't write any fields.
        return OkStatus();
      },
      StreamEncoder::EmptyEncoderBehavior::kWriteFieldNumber);

  // test_proto.ziggy = -13;
  EXPECT_EQ(encoder.WriteSint32(kTestProtoZiggyField, -13), OkStatus());

  // clang-format off
  constexpr auto kExpectedEncodedProto = bytes::Array<
    // magic_number
    0x08, 0x2a,
    // nested header (key, size)
    0x32, 0x00,
      // No fields in nested message
    // ziggy
    0x10, 0x19
  >();
  // clang-format on

  PW_TEST_EXPECT_OK(status);
  EXPECT_EQ(writer.bytes_written(), kExpectedEncodedProto.size());
}

TEST(StreamEncoder, NestedTwoPassRespectsEmptyEncoderBehaviorWriteNothing) {
  std::byte dest_buffer[128];
  MemoryWriter writer(dest_buffer);
  StreamEncoder encoder(writer, {});

  // TestProto test_proto;
  // test_proto.magic_number = 42;
  EXPECT_EQ(encoder.WriteUint32(kTestProtoMagicNumberField, 42), OkStatus());

  auto status = encoder.WriteNestedMessage(
      kTestProtoNestedField,
      [](StreamEncoder& /*nested_proto*/) {
        // Don't write any fields.
        return OkStatus();
      },
      StreamEncoder::EmptyEncoderBehavior::kWriteNothing);

  // test_proto.ziggy = -13;
  EXPECT_EQ(encoder.WriteSint32(kTestProtoZiggyField, -13), OkStatus());

  // clang-format off
  constexpr auto kExpectedEncodedProto = bytes::Array<
    // magic_number
    0x08, 0x2a,
    // No nested header
    // ziggy
    0x10, 0x19
  >();
  // clang-format on

  PW_TEST_EXPECT_OK(status);
  EXPECT_EQ(writer.bytes_written(), kExpectedEncodedProto.size());
}

TEST(StreamEncoder, RepeatedField) {
  std::byte encode_buffer[32];
  MemoryEncoder encoder(encode_buffer);

  // repeated uint32 values = 1;
  constexpr uint32_t values[] = {0, 50, 100, 150, 200};
  for (int i = 0; i < 5; ++i) {
    ASSERT_EQ(OkStatus(), encoder.WriteUint32(1, values[i]));
  }

  constexpr uint8_t encoded_proto[] = {
      0x08, 0x00, 0x08, 0x32, 0x08, 0x64, 0x08, 0x96, 0x01, 0x08, 0xc8, 0x01};

  ASSERT_EQ(encoder.status(), OkStatus());
  ConstByteSpan result(encoder);
  EXPECT_EQ(result.size(), sizeof(encoded_proto));
  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
            0);
}

TEST(StreamEncoder, PackedVarint) {
  std::byte encode_buffer[32];
  MemoryEncoder encoder(encode_buffer);

  // repeated uint32 values = 1;
  constexpr uint32_t values[] = {0, 50, 100, 150, 200};
  ASSERT_EQ(OkStatus(), encoder.WritePackedUint32(1, values));

  constexpr uint8_t encoded_proto[] = {
      0x0a, 0x07, 0x00, 0x32, 0x64, 0x96, 0x01, 0xc8, 0x01};
  //  key   size  v[0]  v[1]  v[2]  v[3]        v[4]

  ASSERT_EQ(encoder.status(), OkStatus());
  ConstByteSpan result(encoder);
  EXPECT_EQ(result.size(), sizeof(encoded_proto));
  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
            0);
}

TEST(StreamEncoder, PackedVarintInsufficientSpace) {
  std::byte encode_buffer[8];
  MemoryEncoder encoder(encode_buffer);

  constexpr uint32_t values[] = {0, 50, 100, 150, 200};
  ASSERT_EQ(Status::ResourceExhausted(), encoder.WritePackedUint32(1, values));

  EXPECT_EQ(encoder.status(), Status::ResourceExhausted());
}

TEST(StreamEncoder, PackedVarintVector) {
  std::byte encode_buffer[32];
  MemoryEncoder encoder(encode_buffer);

  // repeated uint32 values = 1;
  const pw::Vector<uint32_t, 5> values = {0, 50, 100, 150, 200};
  ASSERT_EQ(OkStatus(), encoder.WriteRepeatedUint32(1, values));

  constexpr uint8_t encoded_proto[] = {
      0x0a, 0x07, 0x00, 0x32, 0x64, 0x96, 0x01, 0xc8, 0x01};
  //  key   size  v[0]  v[1]  v[2]  v[3]        v[4]

  ASSERT_EQ(encoder.status(), OkStatus());
  ConstByteSpan result(encoder);
  EXPECT_EQ(result.size(), sizeof(encoded_proto));
  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
            0);
}

TEST(StreamEncoder, PackedVarintVectorInsufficientSpace) {
  std::byte encode_buffer[8];
  MemoryEncoder encoder(encode_buffer);

  const pw::Vector<uint32_t, 5> values = {0, 50, 100, 150, 200};
  ASSERT_EQ(Status::ResourceExhausted(),
            encoder.WriteRepeatedUint32(1, values));

  EXPECT_EQ(encoder.status(), Status::ResourceExhausted());
}

TEST(StreamEncoder, PackedBool) {
  std::byte encode_buffer[32];
  MemoryEncoder encoder(encode_buffer);

  // repeated bool values = 1;
  constexpr bool values[] = {true, false, true, true, false};
  ASSERT_EQ(OkStatus(), encoder.WritePackedBool(1, values));

  constexpr uint8_t encoded_proto[] = {
      0x0a, 0x05, 0x01, 0x00, 0x01, 0x01, 0x00};
  //  key   size  v[0]  v[1]  v[2]  v[3]  v[4]

  ASSERT_EQ(encoder.status(), OkStatus());
  ConstByteSpan result(encoder);
  EXPECT_EQ(result.size(), sizeof(encoded_proto));
  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
            0);
}

TEST(StreamEncoder, PackedFixed) {
  std::byte encode_buffer[32];
  MemoryEncoder encoder(encode_buffer);

  // repeated fixed32 values = 1;
  constexpr uint32_t values[] = {0, 50, 100, 150, 200};
  ASSERT_EQ(OkStatus(), encoder.WritePackedFixed32(1, values));

  // repeated fixed64 values64 = 2;
  constexpr uint64_t values64[] = {0x0102030405060708};
  ASSERT_EQ(OkStatus(), encoder.WritePackedFixed64(2, values64));

  constexpr uint8_t encoded_proto[] = {
      0x0a, 0x14, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x64,
      0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00,
      0x12, 0x08, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};

  ASSERT_EQ(encoder.status(), OkStatus());
  ConstByteSpan result(encoder);
  EXPECT_EQ(result.size(), sizeof(encoded_proto));
  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
            0);
}

TEST(StreamEncoder, PackedFixedVector) {
  std::byte encode_buffer[32];
  MemoryEncoder encoder(encode_buffer);

  // repeated fixed32 values = 1;
  const pw::Vector<uint32_t, 5> values = {0, 50, 100, 150, 200};
  ASSERT_EQ(OkStatus(), encoder.WriteRepeatedFixed32(1, values));

  // repeated fixed64 values64 = 2;
  const pw::Vector<uint64_t, 1> values64 = {0x0102030405060708};
  ASSERT_EQ(OkStatus(), encoder.WriteRepeatedFixed64(2, values64));

  constexpr uint8_t encoded_proto[] = {
      0x0a, 0x14, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x64,
      0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00,
      0x12, 0x08, 0x08, 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01};

  ASSERT_EQ(encoder.status(), OkStatus());
  ConstByteSpan result(encoder);
  EXPECT_EQ(result.size(), sizeof(encoded_proto));
  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
            0);
}

TEST(StreamEncoder, PackedSfixedVector) {
  std::byte encode_buffer[32];
  MemoryEncoder encoder(encode_buffer);

  // repeated fixed32 values = 1;
  const pw::Vector<int32_t, 5> values = {0, 50, 100, 150, 200};
  ASSERT_EQ(OkStatus(), encoder.WriteRepeatedSfixed32(1, values));

  // repeated fixed64 values64 = 2;
  const pw::Vector<int64_t, 1> values64 = {-2};
  ASSERT_EQ(OkStatus(), encoder.WriteRepeatedSfixed64(2, values64));

  constexpr uint8_t encoded_proto[] = {
      0x0a, 0x14, 0x00, 0x00, 0x00, 0x00, 0x32, 0x00, 0x00, 0x00, 0x64,
      0x00, 0x00, 0x00, 0x96, 0x00, 0x00, 0x00, 0xc8, 0x00, 0x00, 0x00,
      0x12, 0x08, 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

  ASSERT_EQ(encoder.status(), OkStatus());
  ConstByteSpan result(encoder);
  EXPECT_EQ(result.size(), sizeof(encoded_proto));
  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
            0);
}

TEST(StreamEncoder, PackedZigzag) {
  std::byte encode_buffer[32];
  MemoryEncoder encoder(encode_buffer);

  // repeated sint32 values = 1;
  constexpr int32_t values[] = {-100, -25, -1, 0, 1, 25, 100};
  ASSERT_EQ(OkStatus(), encoder.WritePackedSint32(1, values));

  constexpr uint8_t encoded_proto[] = {
      0x0a, 0x09, 0xc7, 0x01, 0x31, 0x01, 0x00, 0x02, 0x32, 0xc8, 0x01};

  ASSERT_EQ(encoder.status(), OkStatus());
  ConstByteSpan result(encoder);
  EXPECT_EQ(result.size(), sizeof(encoded_proto));
  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
            0);
}

TEST(StreamEncoder, PackedZigzagVector) {
  std::byte encode_buffer[32];
  MemoryEncoder encoder(encode_buffer);

  // repeated sint32 values = 1;
  const pw::Vector<int32_t, 7> values = {-100, -25, -1, 0, 1, 25, 100};
  ASSERT_EQ(OkStatus(), encoder.WriteRepeatedSint32(1, values));

  constexpr uint8_t encoded_proto[] = {
      0x0a, 0x09, 0xc7, 0x01, 0x31, 0x01, 0x00, 0x02, 0x32, 0xc8, 0x01};

  ASSERT_EQ(encoder.status(), OkStatus());
  ConstByteSpan result(encoder);
  EXPECT_EQ(result.size(), sizeof(encoded_proto));
  EXPECT_EQ(std::memcmp(result.data(), encoded_proto, sizeof(encoded_proto)),
            0);
}

TEST(StreamEncoder, ParentUnavailable) {
  std::byte encode_buffer[32];
  MemoryEncoder parent(encode_buffer);
  {
    StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField);
    ASSERT_EQ(child.status(), OkStatus());
  }
  ASSERT_EQ(parent.status(), OkStatus());
}

TEST(StreamEncoder, NestedEncoderRequiresBuffer) {
  MemoryEncoder parent((ByteSpan()));
  {
    StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField);

    ASSERT_EQ(child.status(), Status::ResourceExhausted());
  }
  ASSERT_EQ(parent.status(), Status::ResourceExhausted());
}

TEST(StreamEncoder, WriteTooBig) {
  constexpr size_t kTempBufferSize = 32;
  constexpr size_t kWriteSize = 2;
  std::byte encode_buffer[32];
  MemoryEncoder encoder(encode_buffer);
  // Each write is 2 bytes. Ensure we can write 16 times.
  for (size_t i = 0; i < kTempBufferSize; i += kWriteSize) {
    ASSERT_EQ(encoder.WriteUint32(1, 12), OkStatus());
  }
  ASSERT_EQ(encoder.size(), kTempBufferSize);
  ASSERT_EQ(encoder.WriteUint32(1, 12), Status::ResourceExhausted());
}

TEST(StreamEncoder, EmptyChildWrites) {
  std::byte encode_buffer[32];
  MemoryEncoder parent(encode_buffer);
  {
    StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField);
  }
  ASSERT_EQ(parent.status(), OkStatus());
  const size_t kExpectedSize =
      varint::EncodedSize(
          FieldKey(kTestProtoNestedField, WireType::kDelimited)) +
      varint::EncodedSize(0);
  ASSERT_EQ(parent.size(), kExpectedSize);
}

TEST(StreamEncoder, InvalidChildFieldNumber) {
  std::byte encode_buffer[32];
  MemoryEncoder parent(encode_buffer);
  {
    StreamEncoder child = parent.GetNestedEncoder(0);
    EXPECT_EQ(child.status(), Status::InvalidArgument());
  }
  EXPECT_EQ(parent.status(), Status::InvalidArgument());
}

TEST(StreamEncoder, NestedStatusPropagates) {
  std::byte encode_buffer[32];
  MemoryEncoder parent(encode_buffer);
  {
    StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField);
    ASSERT_EQ(child.WriteUint32(0, 0), Status::InvalidArgument());
  }
  ASSERT_EQ(parent.status(), Status::InvalidArgument());
}

TEST(StreamEncoder, ManualCloseEncoderWrites) {
  std::byte encode_buffer[32];
  MemoryEncoder parent(encode_buffer);
  StreamEncoder child = parent.GetNestedEncoder(kTestProtoNestedField);
  child.CloseEncoder();
  ASSERT_EQ(parent.status(), OkStatus());
  const size_t kExpectedSize =
      varint::EncodedSize(
          FieldKey(kTestProtoNestedField, WireType::kDelimited)) +
      varint::EncodedSize(0);
  ASSERT_EQ(parent.size(), kExpectedSize);
}

}  // namespace
}  // namespace pw::protobuf
