// Copyright 2024 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_json/builder.h"

#include <array>
#include <cstring>
#include <string_view>

#include "pw_assert/check.h"
#include "pw_compilation_testing/negative_compilation.h"
#include "pw_unit_test/framework.h"

namespace {

using namespace std::string_view_literals;

// First example for the docs.
static_assert([] {
  bool is_simple = true;
  int safety_percentage = 100;

  std::string_view features[] = {"values", "arrays", "objects", "nesting!"};

  // DOCTSAG: [pw-json-builder-example-1]
  pw::JsonBuffer<256> json_buffer;
  pw::JsonObject& object = json_buffer.StartObject();
  object.Add("tagline", "Easy, efficient JSON serialization!")
      .Add("simple", is_simple)
      .Add("safe", safety_percentage)
      .Add("dynamic allocation", false);

  pw::NestedJsonArray nested_array = object.AddNestedArray("features");
  for (const std::string_view feature : features) {
    nested_array.Append(feature);
  }
  // DOCTSAG: [pw-json-builder-example-1]
  return json_buffer;
}() == R"({"tagline": "Easy, efficient JSON serialization!", "simple": true,)"
       R"( "safe": 100, "dynamic allocation": false, "features":)"
       R"( ["values", "arrays", "objects", "nesting!"]})"sv);

// Second example for the docs.
static_assert([] {
  constexpr char empty[128] = {};
  std::string_view huge_string_that_wont_fit(empty, sizeof(empty));

  // DOCTSAG: [pw-json-builder-example-2]
  // Declare a JsonBuffer (JsonBuilder with included buffer) and start a JSON
  // object in it.
  pw::JsonBuffer<128> json_buffer;
  pw::JsonObject& json = json_buffer.StartObject();

  const char* name = "Crag";
  constexpr const char* kOccupationKey = "job";

  // Add key-value pairs to a JSON object.
  json.Add("name", name).Add(kOccupationKey, "hacker");

  // Add an array as the value in a key-value pair.
  pw::NestedJsonArray nested_array = json.AddNestedArray("skills");

  // Append items to an array.
  nested_array.Append(20).Append(1).Append(1).Append(1);

  // Check that everything fit in the JSON buffer.
  PW_ASSERT(json.ok());

  // Compare the contents of the JSON to a std::string_view.
  PW_ASSERT(std::string_view(json) ==
            R"({"name": "Crag", "job": "hacker", "skills": [20, 1, 1, 1]})");

  // Add an object as the value in a key-value pair.
  pw::NestedJsonObject nested_object = json.AddNestedObject("items");

  // Declare another JsonArray, and add it as nested value.
  pw::JsonBuffer<10> inner_buffer;
  inner_buffer.StartArray().Append(nullptr);
  nested_object.Add("misc", inner_buffer);

  // Add a value that is too large for the JsonBuffer.
  json.Add("way too big!", huge_string_that_wont_fit);

  // Adding the last entry failed, but the JSON is still valid.
  PW_ASSERT(json.status().IsResourceExhausted());

  PW_ASSERT(std::string_view(json) ==
            R"({"name": "Crag", "job": "hacker", "skills": [20, 1, 1, 1],)"
            R"( "items": {"misc": [null]}})");
  // DOCTSAG: [pw-json-builder-example-2]
  return json_buffer;
}() == R"({"name": "Crag", "job": "hacker", "skills": [20, 1, 1, 1],)"
       R"( "items": {"misc": [null]}})"sv);

}  // namespace

namespace pw {
namespace {

class JsonOverflowTest : public ::testing::Test {
 public:
  ~JsonOverflowTest() override {
    EXPECT_STREQ(&buffer_[end_], kTag) << "Overflow occurred!";
  }

 protected:
  void MarkBufferEnd(const JsonBuilder& json) {
    end_ = json.max_size() + 1;
    ASSERT_LT(end_, sizeof(buffer_) - sizeof(kTag));
    std::memcpy(&buffer_[end_], kTag, sizeof(kTag));
  }

  char buffer_[512];

 private:
  static constexpr const char kTag[] = "Hi! Your buffer is safe.";

  size_t end_ = 0;
};

TEST(JsonObject, BasicJson) {
  char buffer[128];
  std::memset(buffer, '?', sizeof(buffer));
  JsonBuilder json_buffer(buffer);
  JsonObject& json = json_buffer.StartObject();

  EXPECT_EQ(buffer, json.data());
  EXPECT_STREQ("{}", json.data());
  EXPECT_EQ(2u, json.size());

  EXPECT_EQ(OkStatus(), json.Add("foo", "bar").status());
  EXPECT_STREQ(R"({"foo": "bar"})", json.data());
  EXPECT_EQ(std::strlen(json.data()), json.size());

  EXPECT_EQ(OkStatus(), json.Add("bar", 0).status());
  EXPECT_STREQ(R"({"foo": "bar", "bar": 0})", json.data());
  EXPECT_EQ(std::strlen(json.data()), json.size());

  EXPECT_EQ(OkStatus(), json.Add("baz", nullptr).status());
  EXPECT_STREQ(R"({"foo": "bar", "bar": 0, "baz": null})", json.data());
  EXPECT_EQ(std::strlen(json.data()), json.size());

  EXPECT_EQ(OkStatus(), json.Add("EMPTY STR!", "").status());
  EXPECT_STREQ(R"({"foo": "bar", "bar": 0, "baz": null, "EMPTY STR!": ""})",
               json.data());
  EXPECT_EQ(std::strlen(json.data()), json.size());
}

TEST(JsonObject, OverflowAtKey) {
  JsonBuffer<19> json_buffer;
  JsonObject& json = json_buffer.StartObject();

  EXPECT_EQ(OkStatus(), json.Add("a", 5l).Add("b", "!").status());
  EXPECT_STREQ(R"({"a": 5, "b": "!"})", json.data());  // 18 chars + \0

  EXPECT_EQ(Status::ResourceExhausted(), json.Add("b", "!").status());
  EXPECT_STREQ(R"({"a": 5, "b": "!"})", json.data());
  EXPECT_EQ(Status::ResourceExhausted(), json.Add("b", "").status());
  EXPECT_STREQ(R"({"a": 5, "b": "!"})", json.data());
  EXPECT_EQ(Status::ResourceExhausted(), json.status());
  EXPECT_EQ(Status::ResourceExhausted(), json.last_status());
  json.clear_status();
  EXPECT_STREQ(R"({"a": 5, "b": "!"})", json.data());
  EXPECT_EQ(OkStatus(), json.status());
  EXPECT_EQ(OkStatus(), json.last_status());
}

TEST_F(JsonOverflowTest, ObjectOverflowAtFirstEntry) {
  JsonBuilder json_builder(buffer_, 5);
  MarkBufferEnd(json_builder);

  JsonObject& json = json_builder.StartObject();
  ASSERT_STREQ("{}", json.data());
  EXPECT_EQ(Status::ResourceExhausted(), json.Add("some_key", "").status());
  EXPECT_STREQ("{}", json.data());
}

TEST_F(JsonOverflowTest, ObjectOverflowAtStringValue) {
  JsonBuilder json_builder(buffer_, 32);
  MarkBufferEnd(json_builder);

  JsonObject& json = json_builder.StartObject();
  EXPECT_EQ(OkStatus(), json.Add("a", 5l).status());
  EXPECT_STREQ(R"({"a": 5})", json.data());

  EXPECT_EQ(
      Status::ResourceExhausted(),
      json.Add("b", "This string is so long that it won't fit!!!!").status());
  EXPECT_STREQ(R"({"a": 5})", json.data());
  EXPECT_EQ(Status::ResourceExhausted(), json.status());

  EXPECT_EQ(OkStatus(), json.Add("b", "This will!").last_status());
  EXPECT_STREQ(R"({"a": 5, "b": "This will!"})", json.data());
  EXPECT_EQ(Status::ResourceExhausted(), json.status());
  EXPECT_EQ(OkStatus(), json.last_status());
}

TEST_F(JsonOverflowTest, OverflowAtUnicodeCharacter) {
  JsonBuilder json_builder(buffer_, 10);
  MarkBufferEnd(json_builder);

  JsonValue& overflow_at_unicode = json_builder.StartValue();
  EXPECT_EQ(Status::ResourceExhausted(), overflow_at_unicode.Set("234\x01"));
  EXPECT_STREQ("null", overflow_at_unicode.data());
  EXPECT_EQ(Status::ResourceExhausted(), overflow_at_unicode.Set("2345\x01"));
  EXPECT_STREQ("null", overflow_at_unicode.data());
  EXPECT_EQ(Status::ResourceExhausted(),
            overflow_at_unicode.Set("23456789\x01"));
  EXPECT_STREQ("null", overflow_at_unicode.data());
}

TEST_F(JsonOverflowTest, ObjectOverflowAtNumber) {
  JsonBuilder json_builder(buffer_, 14);
  MarkBufferEnd(json_builder);

  JsonObject& json = json_builder.StartObject();
  EXPECT_EQ(OkStatus(), json.Add("a", 123456).status());
  EXPECT_STREQ(R"({"a": 123456})", json.data());
  EXPECT_EQ(json.max_size(), json.size());
  json.clear();

  EXPECT_EQ(Status::ResourceExhausted(), json.Add("a", 1234567).status());
  EXPECT_STREQ(R"({})", json.data());
  EXPECT_EQ(2u, json.size());
  json.clear();

  EXPECT_EQ(Status::ResourceExhausted(), json.Add("a", 12345678).status());
  EXPECT_STREQ(R"({})", json.data());
  EXPECT_EQ(2u, json.size());
}

TEST(JsonObject, StringValueFillsAllSpace) {
  JsonBuffer<15> json_buffer;

  JsonObject& json = json_buffer.StartObject();
  EXPECT_EQ(OkStatus(), json.Add("key", "12\\").status());
  EXPECT_STREQ(R"({"key": "12\\"})", json.data());
  EXPECT_EQ(15u, json.size());

  json.clear();
  EXPECT_EQ(Status::ResourceExhausted(), json.Add("key", "123\\").status());
  EXPECT_STREQ(R"({})", json.data());
}

TEST(JsonObject, NestedJson) {
  JsonBuffer<64> outside_builder;
  JsonBuffer<32> inside_builder;

  JsonObject& outside = outside_builder.StartObject();
  JsonObject& inside = inside_builder.StartObject();

  ASSERT_EQ(OkStatus(), inside.Add("inside", 123).status());
  ASSERT_STREQ(R"({"inside": 123})", inside.data());

  EXPECT_EQ(OkStatus(), outside.Add("some_value", inside).status());
  EXPECT_STREQ(R"({"some_value": {"inside": 123}})", outside.data());

  inside.clear();
  EXPECT_EQ(OkStatus(), outside.Add("MT", inside).status());
  EXPECT_STREQ(R"({"some_value": {"inside": 123}, "MT": {}})", outside.data());

  outside.AddNestedArray("key").Append(99).Append(1);
  EXPECT_EQ(outside,
            R"({"some_value": {"inside": 123}, "MT": {}, "key": [99, 1]})"sv);
}

TEST(JsonObject, NestedArrayOverflowWhenNesting) {
  JsonBuffer<5> buffer;
  JsonArray& array = buffer.StartArray();
  array.Append(123);
  ASSERT_EQ(array, "[123]"sv);

  NestedJsonArray nested_array = array.AppendNestedArray();
  EXPECT_EQ(Status::ResourceExhausted(), array.status());
  nested_array.Append(1);
  EXPECT_EQ(array, "[123]"sv);
}

TEST(JsonObject, NestedArrayOverflowAppend) {
  JsonBuffer<5> buffer;
  JsonArray& array = buffer.StartArray();
  NestedJsonArray nested_array = array.AppendNestedArray();

  EXPECT_EQ(OkStatus(), array.status());
  nested_array.Append(10);
  EXPECT_EQ(Status::ResourceExhausted(), array.status());
  EXPECT_EQ(array, "[[]]"sv);
}

TEST(JsonObject, NestedArrayOverflowSecondAppend) {
  JsonBuffer<7> buffer;
  JsonArray& array = buffer.StartArray();
  NestedJsonArray nested_array = array.AppendNestedArray();

  EXPECT_EQ(OkStatus(), array.status());
  nested_array.Append(1);
  EXPECT_EQ(array, "[[1]]"sv);
  EXPECT_EQ(OkStatus(), array.status());

  nested_array.Append(2);
  EXPECT_EQ(array, "[[1]]"sv);
  EXPECT_EQ(Status::ResourceExhausted(), array.status());
}

TEST(JsonObject, NestedObjectOverflowWhenNesting) {
  JsonBuffer<5> buffer;
  JsonArray& array = buffer.StartArray();
  array.Append(123);
  ASSERT_EQ(array, "[123]"sv);

  std::ignore = array.AppendNestedObject();
  EXPECT_EQ(Status::ResourceExhausted(), array.status());
  EXPECT_EQ(array, "[123]"sv);
}

TEST(JsonObject, NestedObjectOverflowAppend) {
  JsonBuffer<5> buffer;
  JsonArray& array = buffer.StartArray();
  NestedJsonObject nested_object = array.AppendNestedObject();

  EXPECT_EQ(OkStatus(), array.status());
  nested_object.Add("k", 10);
  EXPECT_EQ(Status::ResourceExhausted(), array.status());
  EXPECT_EQ(array, "[{}]"sv);
}

TEST(JsonObject, NestedObjectOverflowSecondAppend) {
  JsonBuffer<14> buffer;
  JsonArray& array = buffer.StartArray();
  NestedJsonObject nested_object = array.AppendNestedObject();

  EXPECT_EQ(OkStatus(), array.status());
  nested_object.Add("k", 1);
  EXPECT_EQ(array, R"([{"k": 1}])"sv);
  EXPECT_EQ(OkStatus(), array.status());

  nested_object.Add("K", 2);
  EXPECT_EQ(array, R"([{"k": 1}])"sv);
  EXPECT_EQ(Status::ResourceExhausted(), array.status());
}

TEST_F(JsonOverflowTest, ObjectNestedJsonOverflow) {
  JsonBuffer<32> inside_buffer;
  JsonObject& inside = inside_buffer.StartObject();

  JsonBuilder outside_builder(buffer_, 20);
  MarkBufferEnd(outside_builder);
  JsonObject& outside = outside_builder.StartObject();

  ASSERT_EQ(OkStatus(), inside.Add("k", 78).status());
  ASSERT_EQ(9u, inside.size());  // 9 bytes, will fit

  EXPECT_EQ(OkStatus(), outside.Add("data", inside).status());
  EXPECT_STREQ(R"({"data": {"k": 78}})", outside.data());  // 20 bytes total

  inside.clear();
  ASSERT_EQ(OkStatus(), inside.Add("k", 789).status());
  ASSERT_EQ(10u, inside.size());  // 10 bytes, won't fit

  outside.clear();
  EXPECT_EQ(Status::ResourceExhausted(), outside.Add("data", inside).status());
  EXPECT_EQ(Status::ResourceExhausted(), outside.last_status());
  EXPECT_EQ(Status::ResourceExhausted(), outside.status());
  EXPECT_STREQ(R"({})", outside.data());

  inside.clear();
  EXPECT_EQ(OkStatus(), outside.Add("data", inside).last_status());
  EXPECT_EQ(OkStatus(), outside.last_status());
  EXPECT_EQ(Status::ResourceExhausted(), outside.status());
}

TEST(JsonValue, BasicValues) {
  JsonBuffer<13> json;
  EXPECT_EQ(OkStatus(), json.SetValue(-15));
  EXPECT_STREQ("-15", json.data());
  EXPECT_EQ(3u, json.size());

  EXPECT_EQ(OkStatus(), json.SetValue(0));
  EXPECT_STREQ("0", json.data());
  EXPECT_EQ(1u, json.size());

  EXPECT_EQ(OkStatus(), json.SetValue(static_cast<char>(35)));
  EXPECT_STREQ("35", json.data());
  EXPECT_EQ(2u, json.size());

  EXPECT_EQ(OkStatus(), json.SetValue(nullptr));
  EXPECT_STREQ("null", json.data());
  EXPECT_EQ(4u, json.size());

  EXPECT_EQ(OkStatus(), json.SetValue(static_cast<const char*>(nullptr)));
  EXPECT_STREQ("null", json.data());
  EXPECT_EQ(4u, json.size());

  EXPECT_EQ(OkStatus(), json.SetValue(""));
  EXPECT_STREQ(R"("")", json.data());
  EXPECT_EQ(2u, json.size());

  EXPECT_EQ(OkStatus(), json.SetValue("Hey\n!"));
  EXPECT_STREQ(R"("Hey\n!")", json.data());
  EXPECT_EQ(8u, json.size());

  JsonValue& json_value = json.StartValue();
  EXPECT_STREQ("null", json_value.data());

  char str[] = R"(Qu"o"tes)";
  EXPECT_EQ(OkStatus(), json_value.Set(str));
  EXPECT_STREQ(R"("Qu\"o\"tes")", json_value.data());
  EXPECT_EQ(12u, json_value.size());

  EXPECT_EQ(OkStatus(), json_value.Set(true));
  EXPECT_STREQ("true", json_value.data());
  EXPECT_EQ(4u, json_value.size());

  bool false_value = false;
  EXPECT_EQ(OkStatus(), json.SetValue(false_value));
  EXPECT_STREQ("false", json.data());
  EXPECT_EQ(5u, json.size());

  EXPECT_EQ(OkStatus(), json_value.Set(static_cast<double>(1)));
  EXPECT_EQ(json_value, "1"sv);
  EXPECT_EQ(OkStatus(), json_value.Set(-1.0f));
  EXPECT_EQ(json_value, "-1"sv);
}

TEST_F(JsonOverflowTest, ValueOverflowUnquoted) {
  JsonBuilder json(buffer_, 5);
  MarkBufferEnd(json);
  ASSERT_EQ(4u, json.max_size());

  EXPECT_EQ(Status::ResourceExhausted(), json.SetValue(12345));
  EXPECT_STREQ("null", json.data());
  EXPECT_EQ(4u, json.size());

  EXPECT_EQ(OkStatus(), json.SetValue(1234));
  EXPECT_STREQ("1234", json.data());
  EXPECT_EQ(4u, json.size());

  EXPECT_EQ(Status::ResourceExhausted(), json.SetValue(false));
  EXPECT_STREQ("null", json.data());
  EXPECT_EQ(4u, json.size());

  EXPECT_EQ(OkStatus(), json.SetValue(true));
  EXPECT_STREQ("true", json.data());
  EXPECT_EQ(4u, json.size());
}

TEST_F(JsonOverflowTest, ValueOverflowQuoted) {
  JsonBuilder json(buffer_, 8);
  MarkBufferEnd(json);
  ASSERT_EQ(7u, json.max_size());

  EXPECT_EQ(OkStatus(), json.SetValue("34567"));
  EXPECT_STREQ(R"("34567")", json.data());
  EXPECT_EQ(7u, json.size());

  EXPECT_EQ(Status::ResourceExhausted(), json.SetValue("345678"));
  EXPECT_STREQ("null", json.data());
  EXPECT_EQ(4u, json.size());

  EXPECT_EQ(OkStatus(), json.SetValue("567\n"));
  EXPECT_STREQ(R"("567\n")", json.data());
  EXPECT_EQ(7u, json.size());

  EXPECT_EQ(Status::ResourceExhausted(), json.SetValue("5678\n"));
  EXPECT_STREQ("null", json.data());
  EXPECT_EQ(4u, json.size());

  EXPECT_EQ(Status::ResourceExhausted(), json.SetValue("\x05"));
  EXPECT_STREQ("null", json.data());
  EXPECT_EQ(4u, json.size());

  JsonBuffer<9> bigger_json;
  EXPECT_EQ(OkStatus(), bigger_json.SetValue("\x05"));
  EXPECT_STREQ(R"("\u0005")", bigger_json.data());
  EXPECT_EQ(8u, bigger_json.size());
}

TEST(JsonValue, NestedJson) {
  JsonBuffer<11> json;
  JsonBuffer<12> object_buffer;
  JsonObject& object = object_buffer.StartObject();

  ASSERT_EQ(OkStatus(), object.Add("3", 7890).status());
  ASSERT_STREQ(R"({"3": 7890})", object.data());
  ASSERT_EQ(json.max_size(), object.size());

  EXPECT_EQ(OkStatus(), json.SetValue(object));
  EXPECT_STREQ(R"({"3": 7890})", json.data());
  EXPECT_EQ(11u, json.size());

  object.clear();
  ASSERT_EQ(OkStatus(), object.Add("3", 78901).status());
  ASSERT_STREQ(R"({"3": 78901})", object.data());
  ASSERT_EQ(object.max_size(), object.size());
  ASSERT_GT(object.size(), json.size());

  EXPECT_EQ(Status::ResourceExhausted(), json.SetValue(object));
  EXPECT_STREQ("null", json.data());
  EXPECT_EQ(4u, json.size());

  JsonBuffer<12> value;
  const char* something = nullptr;
  EXPECT_EQ(OkStatus(), value.SetValue(something));

  EXPECT_EQ(OkStatus(), json.SetValue(value));
  EXPECT_STREQ("null", json.data());
  EXPECT_EQ(4u, json.size());
}

TEST(JsonValue, SetFromOtherJsonValue) {
  JsonBuffer<32> first = JsonBuffer<32>::Value("$$02$ok$$C");
  constexpr const char kExpected[] = R"("$$02$ok$$C")";
  ASSERT_STREQ(kExpected, first.data());
  ASSERT_EQ(sizeof(kExpected) - 1, first.size());

  JsonBuffer<24> second;
  EXPECT_EQ(OkStatus(), second.SetValue(first));
  EXPECT_STREQ(kExpected, second.data());
  EXPECT_EQ(sizeof(kExpected) - 1, second.size());
}

TEST(JsonValue, ToJsonValue) {
  static constexpr auto value = JsonBuffer<4>::Value(1234);

  EXPECT_STREQ("1234", value.data());
  EXPECT_STREQ("\"1234\"", JsonBuffer<6>::Value("1234").data());
  EXPECT_STREQ("null", JsonBuffer<4>::Value(nullptr).data());
  EXPECT_STREQ("false", JsonBuffer<5>::Value(false).data());

#if PW_NC_TEST(ValueDoesNotFit)
  PW_NC_EXPECT("PW_ASSERT\(json.SetValue\(initial_value\).ok\(\)\)");
  [[maybe_unused]] static constexpr auto fail = JsonBuffer<4>::Value(12345);
#endif  // PW_NC_TEST
}

static_assert([] {
  JsonBuffer<32> buffer;
  buffer.StartObject().Add("hello", "world").Add("ptr", nullptr);
  return buffer;
}() == std::string_view(R"({"hello": "world", "ptr": null})"));

TEST(JsonArray, BasicUse) {
  JsonBuffer<48> list_buffer;
  JsonArray& list = list_buffer.StartArray();
  ASSERT_EQ(OkStatus(), list.Append(nullptr).last_status());
  ASSERT_EQ(OkStatus(), list.Append("what").status());

  JsonBuffer<96> big_list_buffer;
  JsonArray& big_list = big_list_buffer.StartArray();
  EXPECT_EQ(OkStatus(), big_list.Append(list).status());
  EXPECT_EQ(OkStatus(), big_list.Append(123).status());

  JsonBuffer<48> object_buffer;
  JsonObject& object = object_buffer.StartObject();
  ASSERT_EQ(OkStatus(), object.Add("foo", "bar").status());
  ASSERT_EQ(OkStatus(), object.Add("bar", list).status());
  EXPECT_EQ(OkStatus(), big_list.Append(object).status());

  EXPECT_EQ(OkStatus(), big_list.Append('\0').status());

  std::array<bool, 2> bools{true, false};
  EXPECT_EQ(OkStatus(), big_list.Extend(bools).status());

  const char kExpected[] =
      R"([[null, "what"], 123, {"foo": "bar", "bar": [null, "what"]}, )"
      R"(0, true, false])";
  EXPECT_STREQ(kExpected, big_list.data());
  EXPECT_EQ(sizeof(kExpected) - 1, big_list.size());
}

TEST(JsonArray, FromArray) {
  JsonBuffer<31> array_buffer;
  JsonArray& array = array_buffer.StartArray();
  EXPECT_EQ(OkStatus(), array.Extend({1, 2, 3, 4, 5}).status());
  EXPECT_STREQ("[1, 2, 3, 4, 5]", array.data());
  EXPECT_EQ(15u, array.size());

  EXPECT_EQ(OkStatus(), array.Extend({6, 7, 8, 9, 0}).status());
  EXPECT_STREQ("[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]", array.data());
  EXPECT_EQ(30u, array.size());
}

TEST_F(JsonOverflowTest, FromArrayOverflow) {
  JsonBuilder array_buffer(buffer_, 31);
  MarkBufferEnd(array_buffer);
  JsonArray& array = array_buffer.StartArray();

  EXPECT_EQ(OkStatus(), array.Extend({1, 2, 3, 4, 5}).status());
  EXPECT_STREQ("[1, 2, 3, 4, 5]", array.data());
  EXPECT_EQ(15u, array.size());

  EXPECT_EQ(Status::ResourceExhausted(),
            array.Extend({6, 7, 8, 9, 0, 1, 2, 3}).status());
  EXPECT_STREQ("[1, 2, 3, 4, 5]", array.data());
  EXPECT_EQ(15u, array.size());

  EXPECT_EQ(Status::ResourceExhausted(),
            array.Extend({6, 7, 8}).Extend({9, 0}).status());
  EXPECT_STREQ("[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]", array.data());
  EXPECT_EQ(30u, array.size());

  EXPECT_EQ(Status::ResourceExhausted(), array.Extend({5}).status());
  EXPECT_STREQ("[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]", array.data());
  EXPECT_EQ(30u, array.size());
}

TEST(JsonArray, AppendIndividualExtendContainer) {
  JsonBuffer<64> array_buffer;
  JsonArray& array = array_buffer.StartArray();
  constexpr int kInts[] = {1, 2, 3};
#if PW_NC_TEST(CannotAppendArrays)
  PW_NC_EXPECT("JSON values may only be numbers, strings, JSON");
  array.Append(kInts);
#endif  // PW_NC_TEST

  ASSERT_EQ(OkStatus(), array.Extend(kInts).status());
  EXPECT_STREQ("[1, 2, 3]", array.data());
}

TEST(JsonArray, NestingArray) {
  JsonBuffer<64> array_buffer;
  JsonArray& array = array_buffer.StartArray();
  std::ignore = array.AppendNestedArray();

  EXPECT_STREQ(array.data(), "[[]]");

  NestedJsonArray nested = array.AppendNestedArray();
  EXPECT_EQ(OkStatus(), array.last_status());
  EXPECT_STREQ(array.data(), "[[], []]");

  nested.Append(123);
  EXPECT_EQ(array.size(), sizeof("[[], [123]]") - 1);
  EXPECT_STREQ(array.data(), "[[], [123]]");

  nested.Append("");
  EXPECT_STREQ(array.data(), "[[], [123, \"\"]]");
}

TEST(JsonArray, NestingObject) {
  JsonBuffer<64> array_buffer;
  JsonArray& array = array_buffer.StartArray();
  NestedJsonObject object = array.AppendNestedObject();

  EXPECT_STREQ(array.data(), "[{}]");

  object.Add("key", 123);
  EXPECT_EQ(array, R"([{"key": 123}])"sv);

  object.Add("k", true);
  EXPECT_EQ(array, R"([{"key": 123, "k": true}])"sv);

  array.AppendNestedArray().Append("done").Append("!");
  EXPECT_EQ(array, R"([{"key": 123, "k": true}, ["done", "!"]])"sv);
}

TEST(JsonBuilder, DeepNesting) {
  JsonBuffer<64> buffer;
  JsonArray& arr1 = buffer.StartArray();
  std::ignore = arr1.AppendNestedObject();

  EXPECT_EQ(buffer, "[{}]"sv);

  auto arr2 = arr1.AppendNestedObject().Add("a", 1).AddNestedArray("b");
  arr2.Append(0).Append(1).AppendNestedObject().Add("yes", "no");
  arr2.Append(2);

  EXPECT_EQ(buffer, R"([{}, {"a": 1, "b": [0, 1, {"yes": "no"}, 2]}])"sv);

  arr1.Append(true);
  EXPECT_EQ(buffer, R"([{}, {"a": 1, "b": [0, 1, {"yes": "no"}, 2]}, true])"sv);
}

TEST(JsonBuilder, ConvertBetween) {
  JsonBuffer<64> buffer;
  EXPECT_STREQ("null", buffer.data());
  EXPECT_TRUE(buffer.IsValue());
  EXPECT_FALSE(buffer.IsObject());
  EXPECT_FALSE(buffer.IsArray());

  JsonObject& object = buffer.StartObject();
  EXPECT_STREQ("{}", buffer.data());
  EXPECT_FALSE(object.IsValue());
  EXPECT_FALSE(object.IsArray());
  EXPECT_TRUE(object.IsObject());
  object.Add("123", true);
  EXPECT_STREQ(R"({"123": true})", buffer.data());

  JsonArray& array = buffer.StartArray();

  EXPECT_FALSE(object.IsObject()) << "No longer an object";
  EXPECT_TRUE(object.ok()) << "Still OK, just not an object";
  EXPECT_FALSE(array.IsValue());
  EXPECT_TRUE(array.IsArray());
  EXPECT_FALSE(array.IsObject());

  EXPECT_STREQ("[]", buffer.data());
  EXPECT_EQ(OkStatus(), array.Extend({1, 2, 3}).status());
  EXPECT_STREQ("[1, 2, 3]", buffer.data());

  EXPECT_EQ(OkStatus(), array.Append(false).Append(-1).status());
  EXPECT_STREQ("[1, 2, 3, false, -1]", buffer.data());

  object.clear();
  EXPECT_EQ(OkStatus(), object.Add("yes", nullptr).status());
  EXPECT_STREQ(R"({"yes": null})", buffer.data());
  EXPECT_EQ(OkStatus(), buffer.status());
}

static_assert([] {
  JsonBuffer<64> buffer;
  auto& object = buffer.StartObject();
  auto nested_array = object.AddNestedArray("array");
  nested_array.Append(1);
  object.Add("key", "value");
  if (buffer != R"({"array": [1], "key": "value"})"sv) {
    return false;
  }

#if PW_NC_TEST(NestedJsonAttemptsToDetectWrongType)
  PW_NC_EXPECT("PW_ASSERT\(.*// Nested structure must match the expected type");
  nested_array.Append(2);
#elif PW_NC_TEST(NestedJsonDetectsClearedJson)
  PW_NC_EXPECT("PW_ASSERT\(.*// JSON must not have been cleared since nesting");
  object.clear();
  nested_array.Append(2);
#endif  // PW_NC_TEST
  return true;
}());

static_assert([] {
  JsonBuffer<64> buffer;
  auto& array = buffer.StartArray();

  NestedJsonArray nested = array.AppendNestedArray();
  for (int i = 1; i < 16; ++i) {
    nested = nested.AppendNestedArray();
  }
  // 17 arrays total (1 outer array, 16 levels of nesting inside it).
  //              1234567890123456712345678901234567
  if (array != R"([[[[[[[[[[[[[[[[[]]]]]]]]]]]]]]]]])"sv) {
    return JsonBuffer<64>{};
  }
  nested.Append("-_-");

#if PW_NC_TEST(NestingLimit)
  PW_NC_EXPECT(
      "PW_ASSERT\(.*// Arrays or objects may be nested at most 17 times");
  std::ignore = nested.AppendNestedArray();
#endif  // PW_NC_TEST
  return buffer;
}() == R"([[[[[[[[[[[[[[[[["-_-"]]]]]]]]]]]]]]]]])"sv);

TEST(JsonBuffer, SetClear) {
  JsonBuffer<4> buffer;
  EXPECT_EQ(buffer, "null"sv);
  ASSERT_EQ(OkStatus(), buffer.SetValue(""));
  EXPECT_TRUE(buffer.ok());
  EXPECT_EQ(buffer, "\"\""sv);

  ASSERT_EQ(Status::ResourceExhausted(), buffer.SetValue("234"));
  EXPECT_FALSE(buffer.ok());
  EXPECT_EQ(buffer, "null"sv);

  buffer.clear();
  EXPECT_TRUE(buffer.ok());
  EXPECT_EQ(buffer, "null"sv);
}

TEST(JsonBuffer, Copy) {
  JsonBuffer<64> foo;
  ASSERT_EQ(OkStatus(), foo.SetValue("yes"));

  JsonBuffer<48> bar;
  auto& object = bar.StartObject().Add("no", true);
  EXPECT_EQ(object, R"({"no": true})"sv);
  EXPECT_EQ(OkStatus(), bar.StartArray().Append(1).Append(2).status());

  foo = bar;
  EXPECT_STREQ("[1, 2]", foo.data());
  EXPECT_EQ(6u, foo.size());

  JsonBuffer<128> baz(foo);
  EXPECT_STREQ(foo.data(), baz.data());
}

// Tests character escaping using a table generated with the following Python:
//
// import json
// print(', '.join('R"_({})_"'.format(json.dumps(chr(i))) for i in range(128)))
TEST(JsonBuilder, TestEscape) {
  static constexpr std::array<const char*, 128> kEscapedCharacters = {
      R"_("\u0000")_", R"_("\u0001")_", R"_("\u0002")_", R"_("\u0003")_",
      R"_("\u0004")_", R"_("\u0005")_", R"_("\u0006")_", R"_("\u0007")_",
      R"_("\b")_",     R"_("\t")_",     R"_("\n")_",     R"_("\u000b")_",
      R"_("\f")_",     R"_("\r")_",     R"_("\u000e")_", R"_("\u000f")_",
      R"_("\u0010")_", R"_("\u0011")_", R"_("\u0012")_", R"_("\u0013")_",
      R"_("\u0014")_", R"_("\u0015")_", R"_("\u0016")_", R"_("\u0017")_",
      R"_("\u0018")_", R"_("\u0019")_", R"_("\u001a")_", R"_("\u001b")_",
      R"_("\u001c")_", R"_("\u001d")_", R"_("\u001e")_", R"_("\u001f")_",
      R"_(" ")_",      R"_("!")_",      R"_("\"")_",     R"_("#")_",
      R"_("$")_",      R"_("%")_",      R"_("&")_",      R"_("'")_",
      R"_("(")_",      R"_(")")_",      R"_("*")_",      R"_("+")_",
      R"_(",")_",      R"_("-")_",      R"_(".")_",      R"_("/")_",
      R"_("0")_",      R"_("1")_",      R"_("2")_",      R"_("3")_",
      R"_("4")_",      R"_("5")_",      R"_("6")_",      R"_("7")_",
      R"_("8")_",      R"_("9")_",      R"_(":")_",      R"_(";")_",
      R"_("<")_",      R"_("=")_",      R"_(">")_",      R"_("?")_",
      R"_("@")_",      R"_("A")_",      R"_("B")_",      R"_("C")_",
      R"_("D")_",      R"_("E")_",      R"_("F")_",      R"_("G")_",
      R"_("H")_",      R"_("I")_",      R"_("J")_",      R"_("K")_",
      R"_("L")_",      R"_("M")_",      R"_("N")_",      R"_("O")_",
      R"_("P")_",      R"_("Q")_",      R"_("R")_",      R"_("S")_",
      R"_("T")_",      R"_("U")_",      R"_("V")_",      R"_("W")_",
      R"_("X")_",      R"_("Y")_",      R"_("Z")_",      R"_("[")_",
      R"_("\\")_",     R"_("]")_",      R"_("^")_",      R"_("_")_",
      R"_("`")_",      R"_("a")_",      R"_("b")_",      R"_("c")_",
      R"_("d")_",      R"_("e")_",      R"_("f")_",      R"_("g")_",
      R"_("h")_",      R"_("i")_",      R"_("j")_",      R"_("k")_",
      R"_("l")_",      R"_("m")_",      R"_("n")_",      R"_("o")_",
      R"_("p")_",      R"_("q")_",      R"_("r")_",      R"_("s")_",
      R"_("t")_",      R"_("u")_",      R"_("v")_",      R"_("w")_",
      R"_("x")_",      R"_("y")_",      R"_("z")_",      R"_("{")_",
      R"_("|")_",      R"_("}")_",      R"_("~")_",      R"_("\u007f")_"};

  JsonBuffer<9> buffer;

  for (size_t i = 0; i < kEscapedCharacters.size(); ++i) {
    const char character = static_cast<char>(i);
    ASSERT_EQ(OkStatus(), buffer.SetValue(std::string_view(&character, 1)));
    ASSERT_STREQ(kEscapedCharacters[i], buffer.data());
  }
}

class JsonObjectTest : public ::testing::Test {
 protected:
  static constexpr size_t kMaxSize = 127;
  static constexpr size_t kBufferSize = kMaxSize + 1;

  JsonObjectTest() : object_(json_buffer_.StartObject()) {}

  JsonBuffer<kMaxSize> json_buffer_;
  JsonObject& object_;
};

TEST_F(JsonObjectTest, TestSingleStringValue) {
  EXPECT_EQ(OkStatus(), object_.Add("key", "value").status());
  EXPECT_STREQ("{\"key\": \"value\"}", object_.data());
}

TEST_F(JsonObjectTest, TestEscapedQuoteString) {
  const char* buf = "{\"key\": \"\\\"value\\\"\"}";
  EXPECT_STREQ(buf, object_.Add("key", "\"value\"").data());
}

TEST_F(JsonObjectTest, TestEscapedSlashString) {
  const char* buf = "{\"key\": \"\\\\\"}";
  EXPECT_STREQ(buf, object_.Add("key", "\\").data());
}

TEST_F(JsonObjectTest, TestEscapedCharactersString) {
  const char* buf = "{\"key\": \"\\r\\n\\t\"}";
  EXPECT_STREQ(buf, object_.Add("key", "\r\n\t").data());
}

TEST_F(JsonObjectTest, TestEscapedControlCharacterString) {
  EXPECT_STREQ("{\"key\": \"\\u001f\"}", object_.Add("key", "\x1F").data());
  object_.clear();
  EXPECT_STREQ("{\"key\": \"\\u0080\"}", object_.Add("key", "\x80").data());
}

TEST_F(JsonObjectTest, TestNullptrString) {
  EXPECT_STREQ("{\"key\": null}",
               object_.Add("key", static_cast<const char*>(nullptr)).data());
}

TEST_F(JsonObjectTest, TestCharValue) {
  EXPECT_STREQ("{\"key\": 88}",
               object_.Add("key", static_cast<unsigned char>('X')).data());
  object_.clear();
  EXPECT_STREQ("{\"key\": 88}", object_.Add("key", 'X').data());
}

TEST_F(JsonObjectTest, TestShortValue) {
  EXPECT_STREQ("{\"key\": 88}",
               object_.Add("key", static_cast<unsigned short>(88)).data());
  object_.clear();
  EXPECT_STREQ("{\"key\": -88}",
               object_.Add("key", static_cast<short>(-88)).data());
}

TEST_F(JsonObjectTest, TestIntValue) {
  EXPECT_STREQ("{\"key\": 88}",
               object_.Add("key", static_cast<unsigned int>(88)).data());
  object_.clear();
  EXPECT_STREQ("{\"key\": -88}", object_.Add("key", -88).data());
}

TEST_F(JsonObjectTest, TestLongValue) {
  EXPECT_STREQ("{\"key\": 88}", object_.Add("key", 88UL).data());
  object_.clear();
  EXPECT_STREQ("{\"key\": -88}", object_.Add("key", -88L).data());
}

TEST_F(JsonObjectTest, TestMultipleValues) {
  char buf[16] = "nonconst";
  EXPECT_STREQ("{\"one\": \"nonconst\", \"two\": null, \"three\": -3}",
               object_.Add("one", buf)
                   .Add("two", static_cast<char*>(nullptr))
                   .Add("three", -3)
                   .data());
}

TEST_F(JsonObjectTest, TestOverflow) {
  // Create a buffer that is just large enough to overflow with "key".
  std::array<char, kBufferSize + 1 /* NUL */ - (sizeof("{\"key\": \"\"}") - 1)>
      buf;
  std::memset(buf.data(), 'z', sizeof(buf));
  buf[sizeof(buf) - 1] = '\0';

  // Make sure the overflow happens at exactly the right character.
  EXPECT_EQ(Status::ResourceExhausted(),
            object_.Add("key", buf.data()).status());
  EXPECT_EQ(Status::ResourceExhausted(), object_.status());

  object_.clear();
  EXPECT_EQ(OkStatus(), object_.Add("ke", buf.data()).status());
  EXPECT_EQ(OkStatus(), object_.status());

  // Ensure the internal buffer is NUL-terminated still, even on overflow.
  EXPECT_EQ(object_.data()[object_.size()], '\0');
}

}  // namespace
}  // namespace pw
