// 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_string/util.h"

#include <cstring>

#include "pw_unit_test/framework.h"

namespace pw::string {
namespace {

using namespace std::literals::string_view_literals;

TEST(ClampedLength, Nullptr_Returns0) {
  EXPECT_EQ(0u, internal::ClampedLength(nullptr, 100));
}

TEST(ClampedLength, EmptyString_Returns0) {
  EXPECT_EQ(0u, internal::ClampedLength("", 0));
  EXPECT_EQ(0u, internal::ClampedLength("", 100));
}

TEST(ClampedLength, MaxLongerThanString_ReturnsStrlen) {
  EXPECT_EQ(5u, internal::ClampedLength("12345", 100));
}

TEST(ClampedLength, StringMaxLongerThanMax_ReturnsMax) {
  EXPECT_EQ(0u, internal::ClampedLength("12345", 0));
  EXPECT_EQ(4u, internal::ClampedLength("12345", 4));
}

TEST(ClampedLength, LengthEqualsMax) {
  EXPECT_EQ(5u, internal::ClampedLength("12345", 5));
}

TEST(ClampedCString, NullPtr_ReturnsEmpty) {
  EXPECT_TRUE(ClampedCString(nullptr, 100).empty());
}

TEST(ClampedCString, EmptyString_Returns0) {
  EXPECT_TRUE(ClampedCString("", 0).empty());
  EXPECT_TRUE(ClampedCString("", 100).empty());
}

TEST(ClampedCString, MaxLongerThanString_ReturnsStr) {
  static constexpr char kInput[] = "12345";
  const std::string_view result = ClampedCString(kInput, 100);
  EXPECT_EQ(result.size(), strlen(kInput));
  EXPECT_EQ(result.data(), &kInput[0]);
}

TEST(ClampedCString, StringMaxLongerThanMax_ClampsView) {
  static constexpr char kInput[] = "12345";

  EXPECT_TRUE(ClampedCString(kInput, 0).empty());

  const std::string_view result = ClampedCString(kInput, 4);
  EXPECT_EQ(result.size(), 4u);
  EXPECT_EQ(result.data(), &kInput[0]);
}

TEST(ClampedCString, FullStringView) {
  static constexpr char kInput[] = "12345";
  const std::string_view result = ClampedCString(kInput);
  EXPECT_EQ(result.size(), strlen(kInput));
  EXPECT_EQ(result.data(), &kInput[0]);
}

TEST(NullTerminatedLength, EmptyString_RequiresNullTerminator) {
  EXPECT_TRUE(NullTerminatedLength("", 0).status().IsOutOfRange());

  ASSERT_TRUE(NullTerminatedLength("", 100).status().ok());
  EXPECT_EQ(0u, NullTerminatedLength("", 100).value());
}

TEST(NullTerminatedLength, MaxLongerThanString_ReturnsStrlen) {
  ASSERT_TRUE(NullTerminatedLength("12345", 100).status().ok());
  EXPECT_EQ(5u, NullTerminatedLength("12345", 100).value());
}

TEST(NullTerminatedLength, StringMaxLongerThanMax_Fails) {
  EXPECT_TRUE(NullTerminatedLength("12345", 0).status().IsOutOfRange());
  EXPECT_TRUE(NullTerminatedLength("12345", 4).status().IsOutOfRange());
}

TEST(NullTerminatedLength, LengthEqualsMax) {
  static constexpr char kInput[] = "12345";
  ASSERT_TRUE(NullTerminatedLength(kInput).ok());
  EXPECT_EQ(5u, NullTerminatedLength(kInput).value());
}

class TestWithBuffer : public ::testing::Test {
 protected:
  static constexpr char kStartingString[] = "!@#$%^&*()!@#$%^&*()";

  TestWithBuffer() { std::memcpy(buffer_, kStartingString, sizeof(buffer_)); }

  char buffer_[sizeof(kStartingString)];
};

class CopyTest : public TestWithBuffer {};

TEST_F(CopyTest, EmptyStringView_WritesNullTerminator) {
  EXPECT_EQ(0u, Copy("", buffer_).size());
  EXPECT_EQ('\0', buffer_[0]);
}

TEST_F(CopyTest, EmptyBuffer_WritesNothing) {
  auto result = Copy("Hello", span(buffer_, 0));
  EXPECT_EQ(0u, result.size());
  EXPECT_FALSE(result.ok());
  EXPECT_STREQ(kStartingString, buffer_);
}

TEST_F(CopyTest, TooSmall_Truncates) {
  auto result = Copy("Hi!", span(buffer_, 3));
  EXPECT_EQ(2u, result.size());
  EXPECT_FALSE(result.ok());
  EXPECT_STREQ("Hi", buffer_);
}

TEST_F(CopyTest, ExactFit) {
  auto result = Copy("Hi!", span(buffer_, 4));
  EXPECT_EQ(3u, result.size());
  EXPECT_TRUE(result.ok());
  EXPECT_STREQ("Hi!", buffer_);
}

TEST_F(CopyTest, NullTerminatorsInString) {
  ASSERT_EQ(4u, Copy("\0!\0\0"sv, span(buffer_, 5)).size());
  EXPECT_EQ("\0!\0\0"sv, std::string_view(buffer_, 4));
}

class InlineStringUtilTest : public ::testing::Test {
 protected:
  InlineString<5> string_;
};

TEST_F(InlineStringUtilTest, Assign_EmptyStringView_WritesNullTerminator) {
  EXPECT_EQ(OkStatus(), Assign(string_, ""));
  EXPECT_EQ(string_, "");
}

TEST_F(InlineStringUtilTest, Assign_EmptyBuffer_WritesNothing) {
  InlineString<0> zero_capacity;
  EXPECT_EQ(Status::ResourceExhausted(), Assign(zero_capacity, "Hello"));
  EXPECT_TRUE(zero_capacity.empty());
  EXPECT_EQ(zero_capacity.c_str()[0], '\0');
}

TEST_F(InlineStringUtilTest, Assign_TooSmall_Truncates) {
  EXPECT_EQ(Status::ResourceExhausted(), Assign(string_, "12345HELLO?"));
  EXPECT_EQ("12345", string_);
}

TEST_F(InlineStringUtilTest, Assign_ExactFit) {
  EXPECT_EQ(OkStatus(), Assign(string_, "12345"));
  EXPECT_EQ("12345", string_);
}

TEST_F(InlineStringUtilTest, Assign_NullTerminatorsInString) {
  EXPECT_EQ(OkStatus(), Assign(string_, "\0!\0\0\0"sv));
  EXPECT_EQ("\0!\0\0\0"sv, string_);
}

TEST_F(InlineStringUtilTest, Assign_ExistingContent_Replaces) {
  string_ = "12345";
  EXPECT_EQ(OkStatus(), Assign(string_, ""));
  EXPECT_EQ("", string_);
}

TEST_F(InlineStringUtilTest, Assign_ExistingContent_ExactFit) {
  string_.append("yo");
  EXPECT_EQ(OkStatus(), Assign(string_, "12345"));
  EXPECT_EQ("12345", string_);
}

TEST_F(InlineStringUtilTest, Assign_ExistingContent_Truncates) {
  string_.append("yo");
  EXPECT_EQ(Status::ResourceExhausted(), Assign(string_, "1234567"));
  EXPECT_EQ("12345", string_);
}

TEST_F(InlineStringUtilTest, Append_EmptyStringView_WritesNullTerminator) {
  EXPECT_EQ(OkStatus(), Append(string_, ""));
  EXPECT_EQ(string_, "");
}

TEST_F(InlineStringUtilTest, Append_EmptyBuffer_WritesNothing) {
  InlineString<0> zero_capacity;
  EXPECT_EQ(Status::ResourceExhausted(), Append(zero_capacity, "Hello"));
  EXPECT_TRUE(zero_capacity.empty());
  EXPECT_EQ(zero_capacity.c_str()[0], '\0');
}

TEST_F(InlineStringUtilTest, Append_TooSmall_Truncates) {
  EXPECT_EQ(Status::ResourceExhausted(), Append(string_, "12345HELLO?"));
  EXPECT_EQ("12345", string_);
}

TEST_F(InlineStringUtilTest, Append_ExactFit) {
  EXPECT_EQ(OkStatus(), Append(string_, "12345"));
  EXPECT_EQ("12345", string_);
}

TEST_F(InlineStringUtilTest, Append_NullTerminatorsInString) {
  EXPECT_EQ(OkStatus(), Append(string_, "\0!\0\0\0"sv));
  EXPECT_EQ("\0!\0\0\0"sv, string_);
}

TEST_F(InlineStringUtilTest, Append_ExistingContent_AppendNothing) {
  string_ = "12345";
  EXPECT_EQ(OkStatus(), Append(string_, ""));
  EXPECT_EQ("12345", string_);
}

TEST_F(InlineStringUtilTest, Append_ExistingContent_ExactFit) {
  string_.append("yo");
  EXPECT_EQ(OkStatus(), Append(string_, "123"));
  EXPECT_EQ("yo123", string_);
}

TEST_F(InlineStringUtilTest, Append_ExistingContent_Truncates) {
  string_.append("yo");
  EXPECT_EQ(Status::ResourceExhausted(), Append(string_, "12345"));
  EXPECT_EQ("yo123", string_);
}

class PrintableCopyTest : public TestWithBuffer {};

TEST_F(PrintableCopyTest, EmptyBuffer_WritesNothing) {
  auto result = PrintableCopy("Hello", span(buffer_, 0));
  EXPECT_EQ(0u, result.size());
  EXPECT_FALSE(result.ok());
  EXPECT_STREQ(kStartingString, buffer_);
}

TEST_F(PrintableCopyTest, TooSmall_Truncates) {
  auto result = PrintableCopy("Hi!", span(buffer_, 3));
  EXPECT_EQ(2u, result.size());
  EXPECT_FALSE(result.ok());
  EXPECT_STREQ("Hi", buffer_);
}

TEST_F(PrintableCopyTest, ExactFit) {
  auto result = PrintableCopy("Hi!", span(buffer_, 4));
  EXPECT_EQ(3u, result.size());
  EXPECT_TRUE(result.ok());
  EXPECT_STREQ("Hi!", buffer_);
}

TEST_F(PrintableCopyTest, StartingString) {
  memset(buffer_, '\0', sizeof(buffer_));
  auto result = PrintableCopy(kStartingString, span(buffer_));
  EXPECT_EQ(sizeof(kStartingString) - 1, result.size());
  EXPECT_TRUE(result.ok());
  EXPECT_STREQ(kStartingString, buffer_);
}

TEST_F(PrintableCopyTest, NullTerminatorsInString) {
  ASSERT_EQ(4u, PrintableCopy("\0!\0\0"sv, span(buffer_, 5)).size());
  EXPECT_STREQ(".!..", buffer_);
}

TEST_F(PrintableCopyTest, ControlCharsInString) {
  ASSERT_EQ(
      14u,
      PrintableCopy("\n!\t\n\x10\x7F\xFF\vabcd\b\r"sv, span(buffer_)).size());
  EXPECT_STREQ(".!......abcd..", buffer_);
}

}  // namespace
}  // namespace pw::string
