// Copyright 2021 The IREE Authors
//
// Licensed under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

#include <array>
#include <string>
#include <string_view>

#include "iree/base/api.h"
#include "iree/testing/gtest.h"
#include "iree/testing/status_matchers.h"

namespace {

using iree::StatusCode;
using iree::testing::status::IsOkAndHolds;
using iree::testing::status::StatusIs;
using testing::ElementsAre;
using testing::Eq;

static std::string ToString(iree_string_view_t value) {
  return std::string(value.data, value.size);
}

TEST(StringViewTest, Equal) {
  auto equal = [](const char* lhs, const char* rhs) -> bool {
    return iree_string_view_equal(iree_make_cstring_view(lhs),
                                  iree_make_cstring_view(rhs));
  };
  EXPECT_TRUE(equal("", ""));
  EXPECT_FALSE(equal("a", ""));
  EXPECT_FALSE(equal("", "a"));
  EXPECT_TRUE(equal("a", "a"));
  EXPECT_FALSE(equal("a", "ab"));
  EXPECT_FALSE(equal("b", "ab"));
  EXPECT_TRUE(equal("abc", "abc"));
  EXPECT_FALSE(equal("abc", "aBc"));
  EXPECT_TRUE(equal("a_c", "a_c"));
}

TEST(StringViewTest, EqualCase) {
  auto equal_case = [](const char* lhs, const char* rhs) -> bool {
    return iree_string_view_equal_case(iree_make_cstring_view(lhs),
                                       iree_make_cstring_view(rhs));
  };
  EXPECT_TRUE(equal_case("", ""));
  EXPECT_FALSE(equal_case("a", ""));
  EXPECT_FALSE(equal_case("", "a"));
  EXPECT_TRUE(equal_case("a", "a"));
  EXPECT_TRUE(equal_case("A", "a"));
  EXPECT_TRUE(equal_case("a", "A"));
  EXPECT_FALSE(equal_case("a", "ab"));
  EXPECT_FALSE(equal_case("b", "ab"));
  EXPECT_TRUE(equal_case("abc", "abc"));
  EXPECT_TRUE(equal_case("abc", "aBc"));
  EXPECT_TRUE(equal_case("a_c", "a_C"));
}

TEST(StringViewTest, FindChar) {
  auto find_char = [](const char* value, char c, iree_host_size_t pos) {
    return iree_string_view_find_char(iree_make_cstring_view(value), c, pos);
  };
  EXPECT_EQ(find_char("", 'x', 0), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_char("", 'x', 1), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_char("", 'x', IREE_STRING_VIEW_NPOS), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_char("x", 'x', 0), 0);
  EXPECT_EQ(find_char("x", 'x', 1), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_char("x", 'x', IREE_STRING_VIEW_NPOS), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_char("abc", 'x', 0), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_char("abc", 'x', 1), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_char("abc", 'x', IREE_STRING_VIEW_NPOS),
            IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_char("axbxc", 'x', 0), 1);
  EXPECT_EQ(find_char("axbxc", 'x', 1), 1);
  EXPECT_EQ(find_char("axbxc", 'x', 2), 3);
  EXPECT_EQ(find_char("axbxc", 'x', 3), 3);
  EXPECT_EQ(find_char("axbxc", 'x', 4), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_char("axbxc", 'x', IREE_STRING_VIEW_NPOS),
            IREE_STRING_VIEW_NPOS);
}

TEST(StringViewTest, FindFirstOf) {
  auto find_first_of = [](const char* value, const char* s,
                          iree_host_size_t pos) {
    return iree_string_view_find_first_of(iree_make_cstring_view(value),
                                          iree_make_cstring_view(s), pos);
  };
  EXPECT_EQ(find_first_of("", "", 0), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("", "", 1), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("", "", IREE_STRING_VIEW_NPOS),
            IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("", "x", 0), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("", "x", 1), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("", "x", IREE_STRING_VIEW_NPOS),
            IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("x", "x", 0), 0);
  EXPECT_EQ(find_first_of("x", "x", 1), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("x", "x", IREE_STRING_VIEW_NPOS),
            IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("x", "", 0), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("x", "", 1), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("x", "", IREE_STRING_VIEW_NPOS),
            IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("abc", "x", 0), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("abc", "x", 1), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("abc", "x", IREE_STRING_VIEW_NPOS),
            IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("axbxc", "xy", 0), 1);
  EXPECT_EQ(find_first_of("axbxc", "xy", 1), 1);
  EXPECT_EQ(find_first_of("axbxc", "xy", 2), 3);
  EXPECT_EQ(find_first_of("axbxc", "xy", 3), 3);
  EXPECT_EQ(find_first_of("axbxc", "xy", 4), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("axbxc", "xy", IREE_STRING_VIEW_NPOS),
            IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("aybxc", "xy", 0), 1);
  EXPECT_EQ(find_first_of("aybxc", "xy", 1), 1);
  EXPECT_EQ(find_first_of("aybxc", "xy", 2), 3);
  EXPECT_EQ(find_first_of("aybxc", "xy", 3), 3);
  EXPECT_EQ(find_first_of("aybxc", "xy", 4), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_first_of("aybxc", "xy", IREE_STRING_VIEW_NPOS),
            IREE_STRING_VIEW_NPOS);
}

TEST(StringViewTest, FindLastOf) {
  auto find_last_of = [](const char* value, const char* s,
                         iree_host_size_t pos) {
    return iree_string_view_find_last_of(iree_make_cstring_view(value),
                                         iree_make_cstring_view(s), pos);
  };
  EXPECT_EQ(find_last_of("", "", 0), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("", "", 1), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("", "", IREE_STRING_VIEW_NPOS), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("", "x", 0), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("", "x", 1), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("", "x", IREE_STRING_VIEW_NPOS),
            IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("x", "x", 0), 0);
  EXPECT_EQ(find_last_of("x", "x", 1), 0);
  EXPECT_EQ(find_last_of("x", "x", IREE_STRING_VIEW_NPOS), 0);
  EXPECT_EQ(find_last_of("x", "", 0), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("x", "", 1), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("x", "", IREE_STRING_VIEW_NPOS),
            IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("abc", "x", 0), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("abc", "x", 1), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("abc", "x", IREE_STRING_VIEW_NPOS),
            IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("axbxc", "xy", 0), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("axbxc", "xy", 1), 1);
  EXPECT_EQ(find_last_of("axbxc", "xy", 2), 1);
  EXPECT_EQ(find_last_of("axbxc", "xy", 3), 3);
  EXPECT_EQ(find_last_of("axbxc", "xy", 4), 3);
  EXPECT_EQ(find_last_of("axbxc", "xy", IREE_STRING_VIEW_NPOS), 3);
  EXPECT_EQ(find_last_of("aybxc", "xy", 0), IREE_STRING_VIEW_NPOS);
  EXPECT_EQ(find_last_of("aybxc", "xy", 1), 1);
  EXPECT_EQ(find_last_of("aybxc", "xy", 2), 1);
  EXPECT_EQ(find_last_of("aybxc", "xy", 3), 3);
  EXPECT_EQ(find_last_of("aybxc", "xy", 4), 3);
  EXPECT_EQ(find_last_of("aybxc", "xy", IREE_STRING_VIEW_NPOS), 3);
}

TEST(StringViewTest, StartsWith) {
  auto starts_with = [](const char* value, const char* prefix) -> bool {
    return iree_string_view_starts_with(iree_make_cstring_view(value),
                                        iree_make_cstring_view(prefix));
  };
  EXPECT_TRUE(starts_with("a", "a"));
  EXPECT_TRUE(starts_with("ab", "a"));
  EXPECT_TRUE(starts_with("ab", "ab"));
  EXPECT_TRUE(starts_with("abc", "ab"));
  EXPECT_TRUE(starts_with("abc", "abc"));
  EXPECT_FALSE(starts_with("abc", ""));
  EXPECT_FALSE(starts_with("", ""));
  EXPECT_FALSE(starts_with("", "a"));
  EXPECT_FALSE(starts_with("", "abc"));
  EXPECT_FALSE(starts_with("abc", "b"));
  EXPECT_FALSE(starts_with("abc", "bc"));
  EXPECT_FALSE(starts_with("a", "abc"));
}

TEST(StringViewTest, EndsWith) {
  auto ends_with = [](const char* value, const char* suffix) -> bool {
    return iree_string_view_ends_with(iree_make_cstring_view(value),
                                      iree_make_cstring_view(suffix));
  };
  EXPECT_TRUE(ends_with("a", "a"));
  EXPECT_TRUE(ends_with("ab", "b"));
  EXPECT_TRUE(ends_with("ab", "ab"));
  EXPECT_TRUE(ends_with("abc", "bc"));
  EXPECT_TRUE(ends_with("abc", "c"));
  EXPECT_FALSE(ends_with("abc", ""));
  EXPECT_FALSE(ends_with("", ""));
  EXPECT_FALSE(ends_with("", "a"));
  EXPECT_FALSE(ends_with("", "abc"));
  EXPECT_FALSE(ends_with("abc", "b"));
  EXPECT_FALSE(ends_with("abc", "ab"));
  EXPECT_FALSE(ends_with("a", "abc"));
}

TEST(StringViewTest, RemovePrefix) {
  auto remove_prefix = [](const char* value,
                          iree_host_size_t n) -> std::string {
    return ToString(
        iree_string_view_remove_prefix(iree_make_cstring_view(value), n));
  };
  EXPECT_EQ(remove_prefix("", 0), "");
  EXPECT_EQ(remove_prefix("", 1), "");
  EXPECT_EQ(remove_prefix("a", 10), "");
  EXPECT_EQ(remove_prefix("ab", 1), "b");
  EXPECT_EQ(remove_prefix("ab", 2), "");
  EXPECT_EQ(remove_prefix("abcdef", 2), "cdef");
}

TEST(StringViewTest, RemoveSuffix) {
  auto remove_suffix = [](const char* value,
                          iree_host_size_t n) -> std::string {
    return ToString(
        iree_string_view_remove_suffix(iree_make_cstring_view(value), n));
  };
  EXPECT_EQ(remove_suffix("", 0), "");
  EXPECT_EQ(remove_suffix("", 1), "");
  EXPECT_EQ(remove_suffix("a", 10), "");
  EXPECT_EQ(remove_suffix("ab", 1), "a");
  EXPECT_EQ(remove_suffix("ab", 2), "");
  EXPECT_EQ(remove_suffix("abcdef", 2), "abcd");
}

TEST(StringViewTest, StripPrefix) {
  auto strip_prefix = [](const char* value, const char* prefix) -> std::string {
    return ToString(iree_string_view_strip_prefix(
        iree_make_cstring_view(value), iree_make_cstring_view(prefix)));
  };
  EXPECT_EQ(strip_prefix("", ""), "");
  EXPECT_EQ(strip_prefix("", "a"), "");
  EXPECT_EQ(strip_prefix("a", ""), "a");
  EXPECT_EQ(strip_prefix("a", "a"), "");
  EXPECT_EQ(strip_prefix("ab", "a"), "b");
  EXPECT_EQ(strip_prefix("ab", "b"), "ab");
  EXPECT_EQ(strip_prefix("ab", "ab"), "");
  EXPECT_EQ(strip_prefix("ab", "abc"), "ab");
  EXPECT_EQ(strip_prefix("abcdef", "ab"), "cdef");
  EXPECT_EQ(strip_prefix("abcdef", "bc"), "abcdef");
}

TEST(StringViewTest, StripSuffix) {
  auto strip_suffix = [](const char* value, const char* suffix) -> std::string {
    return ToString(iree_string_view_strip_suffix(
        iree_make_cstring_view(value), iree_make_cstring_view(suffix)));
  };
  EXPECT_EQ(strip_suffix("", ""), "");
  EXPECT_EQ(strip_suffix("", "a"), "");
  EXPECT_EQ(strip_suffix("a", ""), "a");
  EXPECT_EQ(strip_suffix("a", "a"), "");
  EXPECT_EQ(strip_suffix("ab", "a"), "ab");
  EXPECT_EQ(strip_suffix("ab", "b"), "a");
  EXPECT_EQ(strip_suffix("ab", "ab"), "");
  EXPECT_EQ(strip_suffix("ab", "abc"), "ab");
  EXPECT_EQ(strip_suffix("abcdef", "ef"), "abcd");
  EXPECT_EQ(strip_suffix("abcdef", "de"), "abcdef");
}

TEST(StringViewTest, ConsumePrefix) {
  auto consume_prefix = [](const char* value,
                           const char* prefix) -> std::string {
    iree_string_view_t value_sv = iree_make_cstring_view(value);
    if (iree_string_view_consume_prefix(&value_sv,
                                        iree_make_cstring_view(prefix))) {
      return ToString(value_sv);
    } else {
      return "FAILED";
    }
  };
  EXPECT_EQ(consume_prefix("", ""), "FAILED");
  EXPECT_EQ(consume_prefix("", "a"), "FAILED");
  EXPECT_EQ(consume_prefix("a", ""), "FAILED");
  EXPECT_EQ(consume_prefix("a", "a"), "");
  EXPECT_EQ(consume_prefix("ab", "a"), "b");
  EXPECT_EQ(consume_prefix("ab", "b"), "FAILED");
  EXPECT_EQ(consume_prefix("ab", "ab"), "");
  EXPECT_EQ(consume_prefix("ab", "abc"), "FAILED");
  EXPECT_EQ(consume_prefix("abcdef", "ab"), "cdef");
  EXPECT_EQ(consume_prefix("abcdef", "bc"), "FAILED");
}

TEST(StringViewTest, ConsumePrefixChar) {
  auto consume_prefix_char = [](const char* value, char c) -> std::string {
    iree_string_view_t value_sv = iree_make_cstring_view(value);
    if (iree_string_view_consume_prefix_char(&value_sv, c)) {
      return ToString(value_sv);
    } else {
      return "FAILED";
    }
  };
  EXPECT_EQ(consume_prefix_char("", 'a'), "FAILED");
  EXPECT_EQ(consume_prefix_char("a", 'a'), "");
  EXPECT_EQ(consume_prefix_char("a", 'b'), "FAILED");
  EXPECT_EQ(consume_prefix_char("ab", 'a'), "b");
  EXPECT_EQ(consume_prefix_char("ab", 'b'), "FAILED");
  EXPECT_EQ(consume_prefix_char("{foo}", '{'), "foo}");
  EXPECT_EQ(consume_prefix_char("[1,2]", '['), "1,2]");
  EXPECT_EQ(consume_prefix_char(":", ':'), "");
  EXPECT_EQ(consume_prefix_char(",next", ','), "next");
}

TEST(StringViewTest, StartsWithChar) {
  auto starts_with_char = [](const char* value, char c) -> bool {
    return iree_string_view_starts_with_char(iree_make_cstring_view(value), c);
  };
  EXPECT_FALSE(starts_with_char("", 'a'));
  EXPECT_TRUE(starts_with_char("a", 'a'));
  EXPECT_FALSE(starts_with_char("a", 'b'));
  EXPECT_TRUE(starts_with_char("abc", 'a'));
  EXPECT_FALSE(starts_with_char("abc", 'b'));
  EXPECT_TRUE(starts_with_char("{", '{'));
  EXPECT_TRUE(starts_with_char("[1,2]", '['));
}

TEST(StringViewTest, ConsumeSuffix) {
  auto consume_suffix = [](const char* value,
                           const char* suffix) -> std::string {
    iree_string_view_t value_sv = iree_make_cstring_view(value);
    if (iree_string_view_consume_suffix(&value_sv,
                                        iree_make_cstring_view(suffix))) {
      return ToString(value_sv);
    } else {
      return "FAILED";
    }
  };
  EXPECT_EQ(consume_suffix("", ""), "FAILED");
  EXPECT_EQ(consume_suffix("", "a"), "FAILED");
  EXPECT_EQ(consume_suffix("a", ""), "FAILED");
  EXPECT_EQ(consume_suffix("a", "a"), "");
  EXPECT_EQ(consume_suffix("ab", "a"), "FAILED");
  EXPECT_EQ(consume_suffix("ab", "b"), "a");
  EXPECT_EQ(consume_suffix("ab", "ab"), "");
  EXPECT_EQ(consume_suffix("ab", "abc"), "FAILED");
  EXPECT_EQ(consume_suffix("abcdef", "ef"), "abcd");
  EXPECT_EQ(consume_suffix("abcdef", "de"), "FAILED");
}

TEST(StringViewTest, Trim) {
  auto trim = [](const char* value) -> std::string {
    return ToString(iree_string_view_trim(iree_make_cstring_view(value)));
  };
  EXPECT_EQ(trim(""), "");
  EXPECT_EQ(trim("a"), "a");
  EXPECT_EQ(trim(" a"), "a");
  EXPECT_EQ(trim("a "), "a");
  EXPECT_EQ(trim("a b"), "a b");
  EXPECT_EQ(trim(" a b "), "a b");
  EXPECT_EQ(trim("\t\t\na b\n \t "), "a b");
  EXPECT_EQ(trim("\n"), "");
  EXPECT_EQ(trim("\r\n"), "");
}

TEST(StringViewTest, Substr) {
  auto substr = [](const char* value, iree_host_size_t pos,
                   iree_host_size_t n) {
    return ToString(
        iree_string_view_substr(iree_make_cstring_view(value), pos, n));
  };
  EXPECT_EQ(substr("", 0, 0), "");
  EXPECT_EQ(substr("", 0, 1), "");
  EXPECT_EQ(substr("", 0, IREE_STRING_VIEW_NPOS), "");
  EXPECT_EQ(substr("", 1, 0), "");
  EXPECT_EQ(substr("", 1, 1), "");
  EXPECT_EQ(substr("", 1, IREE_STRING_VIEW_NPOS), "");

  EXPECT_EQ(substr("a", 0, 0), "");
  EXPECT_EQ(substr("a", 0, 1), "a");
  EXPECT_EQ(substr("a", 0, 2), "a");
  EXPECT_EQ(substr("a", 0, IREE_STRING_VIEW_NPOS), "a");
  EXPECT_EQ(substr("a", 1, 0), "");
  EXPECT_EQ(substr("a", 1, 1), "");
  EXPECT_EQ(substr("a", 1, IREE_STRING_VIEW_NPOS), "");

  EXPECT_EQ(substr("abc", 0, 1), "a");
  EXPECT_EQ(substr("abc", 1, 1), "b");
  EXPECT_EQ(substr("abc", 2, 1), "c");
  EXPECT_EQ(substr("abc", 0, 2), "ab");
  EXPECT_EQ(substr("abc", 1, 2), "bc");
  EXPECT_EQ(substr("abc", 1, IREE_STRING_VIEW_NPOS), "bc");
  EXPECT_EQ(substr("abc", 0, 3), "abc");
  EXPECT_EQ(substr("abc", 0, IREE_STRING_VIEW_NPOS), "abc");
}

TEST(StringViewTest, AtoiInt32Base) {
  auto atoi = [](const char* value, int base) -> int32_t {
    int32_t result = 0;
    EXPECT_TRUE(iree_string_view_atoi_int32_base(iree_make_cstring_view(value),
                                                 base, &result));
    return result;
  };
  // The base argument must be honored (it was previously ignored and the value
  // was always parsed with base 0 / C auto-detection).
  EXPECT_EQ(atoi("10", 10), 10);
  EXPECT_EQ(atoi("10", 16), 16);
  EXPECT_EQ(atoi("10", 2), 2);
  EXPECT_EQ(atoi("ff", 16), 255);
  EXPECT_EQ(atoi("-20", 8), -16);
  // base 0 keeps C auto-detection: "0x" -> hex, leading "0" -> octal.
  EXPECT_EQ(atoi("0x1a", 0), 26);
  EXPECT_EQ(atoi("010", 0), 8);
  EXPECT_EQ(atoi("10", 0), 10);
}

TEST(StringViewTest, Split) {
  auto split =
      [](const char* value,
         char split_char) -> std::tuple<intptr_t, std::string, std::string> {
    iree_string_view_t lhs = iree_string_view_empty();
    iree_string_view_t rhs = iree_string_view_empty();
    intptr_t index = iree_string_view_split(iree_make_cstring_view(value),
                                            split_char, &lhs, &rhs);
    return std::make_tuple(index, ToString(lhs), ToString(rhs));
  };
  EXPECT_EQ(split("", 'x'), std::make_tuple(-1, "", ""));
  EXPECT_EQ(split(" ", 'x'), std::make_tuple(-1, " ", ""));
  EXPECT_EQ(split("x", 'x'), std::make_tuple(0, "", ""));
  EXPECT_EQ(split(" x ", 'x'), std::make_tuple(1, " ", " "));
  EXPECT_EQ(split("axb", 'x'), std::make_tuple(1, "a", "b"));
  EXPECT_EQ(split("axxxb", 'x'), std::make_tuple(1, "a", "xxb"));
  EXPECT_EQ(split("ax", 'x'), std::make_tuple(1, "a", ""));
  EXPECT_EQ(split("xb", 'x'), std::make_tuple(0, "", "b"));
  EXPECT_EQ(split("axbxc", 'x'), std::make_tuple(1, "a", "bxc"));
}

// Tests that partial returns from iree_string_view_split (only LHS or RHS) work
// as expected even when no storage is provided.
TEST(StringViewTest, SplitLHSOnly) {
  auto split_lhs = [](const char* value,
                      char split_char) -> std::tuple<intptr_t, std::string> {
    iree_string_view_t lhs = iree_string_view_empty();
    intptr_t index = iree_string_view_split(iree_make_cstring_view(value),
                                            split_char, &lhs, nullptr);
    return std::make_tuple(index, ToString(lhs));
  };
  EXPECT_EQ(split_lhs("", 'x'), std::make_tuple(-1, ""));
  EXPECT_EQ(split_lhs(" ", 'x'), std::make_tuple(-1, " "));
  EXPECT_EQ(split_lhs("x", 'x'), std::make_tuple(0, ""));
  EXPECT_EQ(split_lhs(" x ", 'x'), std::make_tuple(1, " "));
  EXPECT_EQ(split_lhs("axb", 'x'), std::make_tuple(1, "a"));
  EXPECT_EQ(split_lhs("axxxb", 'x'), std::make_tuple(1, "a"));
  EXPECT_EQ(split_lhs("ax", 'x'), std::make_tuple(1, "a"));
  EXPECT_EQ(split_lhs("xb", 'x'), std::make_tuple(0, ""));
  EXPECT_EQ(split_lhs("axbxc", 'x'), std::make_tuple(1, "a"));
}
TEST(StringViewTest, SplitRHSOnly) {
  auto split_rhs = [](const char* value,
                      char split_char) -> std::tuple<intptr_t, std::string> {
    iree_string_view_t rhs = iree_string_view_empty();
    intptr_t index = iree_string_view_split(iree_make_cstring_view(value),
                                            split_char, nullptr, &rhs);
    return std::make_tuple(index, ToString(rhs));
  };
  EXPECT_EQ(split_rhs("", 'x'), std::make_tuple(-1, ""));
  EXPECT_EQ(split_rhs(" ", 'x'), std::make_tuple(-1, ""));
  EXPECT_EQ(split_rhs("x", 'x'), std::make_tuple(0, ""));
  EXPECT_EQ(split_rhs(" x ", 'x'), std::make_tuple(1, " "));
  EXPECT_EQ(split_rhs("axb", 'x'), std::make_tuple(1, "b"));
  EXPECT_EQ(split_rhs("axxxb", 'x'), std::make_tuple(1, "xxb"));
  EXPECT_EQ(split_rhs("ax", 'x'), std::make_tuple(1, ""));
  EXPECT_EQ(split_rhs("xb", 'x'), std::make_tuple(0, "b"));
  EXPECT_EQ(split_rhs("axbxc", 'x'), std::make_tuple(1, "bxc"));
}
TEST(StringViewTest, SplitReturnOnly) {
  // This is effectively a find but with extra steps.
  auto split_return = [](const char* value, char split_char) -> intptr_t {
    return iree_string_view_split(iree_make_cstring_view(value), split_char,
                                  nullptr, nullptr);
  };
  EXPECT_EQ(split_return("", 'x'), -1);
  EXPECT_EQ(split_return(" ", 'x'), -1);
  EXPECT_EQ(split_return("x", 'x'), 0);
  EXPECT_EQ(split_return(" x ", 'x'), 1);
  EXPECT_EQ(split_return("axb", 'x'), 1);
  EXPECT_EQ(split_return("axxxb", 'x'), 1);
  EXPECT_EQ(split_return("ax", 'x'), 1);
  EXPECT_EQ(split_return("xb", 'x'), 0);
  EXPECT_EQ(split_return("axbxc", 'x'), 1);
}

TEST(StringViewTest, ReplaceChar) {
  auto replace_char = [](const char* value, char old_char, char new_char) {
    std::string value_clone(value);
    iree_string_view_replace_char(
        iree_make_string_view(value_clone.data(), value_clone.size()), old_char,
        new_char);
    return value_clone;
  };
  EXPECT_EQ(replace_char("", 'x', 'y'), "");
  EXPECT_EQ(replace_char(" ", 'x', 'y'), " ");
  EXPECT_EQ(replace_char("a", 'x', 'y'), "a");
  EXPECT_EQ(replace_char("x", 'x', 'y'), "y");
  EXPECT_EQ(replace_char("xx", 'x', 'y'), "yy");
  EXPECT_EQ(replace_char("axbxc", 'x', 'y'), "aybyc");
}

TEST(StringViewTest, ToCStringEmpty) {
  char buffer[3] = {0x7F, 0x7F, 0x7F};
  iree_string_view_to_cstring(IREE_SV(""), buffer, sizeof(buffer));
  EXPECT_EQ(buffer[0], 0);     // NUL
  EXPECT_EQ(buffer[1], 0x7F);  // unchanged
}

TEST(StringViewTest, ToCStringNoBuffer) {
  // Nothing to test but ASAN ensuring we don't null deref.
  iree_string_view_to_cstring(IREE_SV("abc"), NULL, 0);
}

TEST(StringViewTest, ToCString) {
  char buffer[6] = {0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F};
  iree_string_view_to_cstring(IREE_SV("abc"), buffer, sizeof(buffer));
  EXPECT_THAT(buffer, ElementsAre('a', 'b', 'c', 0, 0x7F, 0x7F));
}

TEST(StringViewTest, ToCStringTruncate) {
  char buffer[3] = {0x7F, 0x7F, 0x7F};
  iree_string_view_to_cstring(IREE_SV("abcdef"), buffer, sizeof(buffer));
  EXPECT_THAT(buffer, ElementsAre('a', 'b', 0));
}

TEST(StringViewTest, AppendToBuffer) {
  char buffer[6] = {0, 1, 2, 3, 4, 5};
  iree_string_view_t source = iree_make_cstring_view("test");
  iree_string_view_t target = {};
  const iree_host_size_t size =
      iree_string_view_append_to_buffer(source, &target, buffer);
  EXPECT_EQ(size, source.size);
  EXPECT_EQ(target.size, source.size);
  EXPECT_EQ(target.data, buffer);
  // Make sure we did not write past the source size.
  EXPECT_THAT(buffer, ElementsAre('t', 'e', 's', 't', 4, 5));
}

TEST(StringViewTest, AppendToBufferEmptySource) {
  char buffer[4] = {0, 1, 2, 3};
  iree_string_view_t source = iree_make_string_view(nullptr, 0);
  iree_string_view_t target = {};
  const iree_host_size_t size =
      iree_string_view_append_to_buffer(source, &target, buffer);
  EXPECT_EQ(size, 0u);
  EXPECT_EQ(target.size, 0u);
  EXPECT_EQ(target.data, buffer);
  // Make sure we did not write to the buffer.
  EXPECT_THAT(buffer, ElementsAre(0, 1, 2, 3));
}

TEST(StringViewTest, AppendToBufferEmptySourceAndBuffer) {
  iree_string_view_t source = iree_make_string_view(nullptr, 0);
  iree_string_view_t target = {};
  const iree_host_size_t size =
      iree_string_view_append_to_buffer(source, &target, nullptr);
  EXPECT_EQ(size, 0u);
  EXPECT_EQ(target.size, 0u);
  EXPECT_EQ(target.data, nullptr);
}

template <size_t N>
static iree::StatusOr<std::array<uint8_t, N>> ParseHex(const char* value) {
  std::array<uint8_t, N> buffer;
  if (!iree_string_view_parse_hex_bytes(iree_make_cstring_view(value),
                                        buffer.size(), buffer.data())) {
    return iree_make_status(IREE_STATUS_INVALID_ARGUMENT);
  }
  return {buffer};
}

TEST(StringViewTest, ParseHex0) {
  EXPECT_THAT(ParseHex<0>(""), IsOkAndHolds(ElementsAre()));
  EXPECT_THAT(ParseHex<0>(" "), IsOkAndHolds(ElementsAre()));
  EXPECT_THAT(ParseHex<0>("  "), IsOkAndHolds(ElementsAre()));
}

TEST(StringViewTest, ParseHex1) {
  EXPECT_THAT(ParseHex<1>("EF"), IsOkAndHolds(ElementsAre(0xEF)));
  EXPECT_THAT(ParseHex<1>("Ef"), IsOkAndHolds(ElementsAre(0xEF)));
  EXPECT_THAT(ParseHex<1>("00"), IsOkAndHolds(ElementsAre(0x00)));
  EXPECT_THAT(ParseHex<1>("FF"), IsOkAndHolds(ElementsAre(0xFF)));
  EXPECT_THAT(ParseHex<1>(" EF "), IsOkAndHolds(ElementsAre(0xEF)));
}

TEST(StringViewTest, ParseHex2) {
  // No separators:
  EXPECT_THAT(ParseHex<2>("ABEF"), IsOkAndHolds(ElementsAre(0xAB, 0xEF)));
  // Mixed-case with separators:
  EXPECT_THAT(ParseHex<2>("Ab-eF"), IsOkAndHolds(ElementsAre(0xAB, 0xEF)));
  // Upper-case with separators:
  EXPECT_THAT(ParseHex<2>("AB-EF"), IsOkAndHolds(ElementsAre(0xAB, 0xEF)));
  // Lower-case with separators:
  EXPECT_THAT(ParseHex<2>("ab-ef"), IsOkAndHolds(ElementsAre(0xAB, 0xEF)));
  // Min:
  EXPECT_THAT(ParseHex<2>("00-00"), IsOkAndHolds(ElementsAre(0x00, 0x00)));
  // Max:
  EXPECT_THAT(ParseHex<2>("FF-FF"), IsOkAndHolds(ElementsAre(0xFF, 0xFF)));
}

TEST(StringViewTest, ParseHex16) {
  // No separators:
  EXPECT_THAT(ParseHex<16>("000102030405060708090A0B0C0D0E0F"),
              IsOkAndHolds(ElementsAre(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
                                       0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
                                       0x0E, 0x0F)));
  // Mixed-case with separators:
  EXPECT_THAT(ParseHex<16>("00010203-0405-0607-0809-0a0B0c0D0e0F"),
              IsOkAndHolds(ElementsAre(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
                                       0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
                                       0x0E, 0x0F)));
  // Upper-case with separators:
  EXPECT_THAT(ParseHex<16>("00010203-0405-0607-0809-0A0B0C0D0E0F"),
              IsOkAndHolds(ElementsAre(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
                                       0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
                                       0x0E, 0x0F)));
  // Lower-case with separators:
  EXPECT_THAT(ParseHex<16>("00010203-0405-0607-0809-0a0b0c0d0e0f"),
              IsOkAndHolds(ElementsAre(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
                                       0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
                                       0x0E, 0x0F)));
  // Min:
  EXPECT_THAT(ParseHex<16>("00000000-0000-0000-0000-000000000000"),
              IsOkAndHolds(ElementsAre(0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                       0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                       0x00, 0x00)));
  // Max:
  EXPECT_THAT(ParseHex<16>("FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF"),
              IsOkAndHolds(ElementsAre(0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                                       0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                                       0xFF, 0xFF)));
  // Morse code:
  EXPECT_THAT(ParseHex<16>("00-01-02-03-04-05-06-07-08-09-0a-0b-0c-0d-0e-0f"),
              IsOkAndHolds(ElementsAre(0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
                                       0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
                                       0x0E, 0x0F)));
}

TEST(StringViewTest, InvalidParseHex) {
  // Leading/trailing -'s:
  EXPECT_THAT(ParseHex<1>("-"), StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(ParseHex<1>("-ab"), StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(ParseHex<1>("ab-"), StatusIs(StatusCode::kInvalidArgument));

  // Too little data:
  EXPECT_THAT(ParseHex<1>(""), StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(ParseHex<1>("a"), StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(ParseHex<2>("abc"), StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(ParseHex<2>("ab-a"), StatusIs(StatusCode::kInvalidArgument));

  // Too much data:
  EXPECT_THAT(ParseHex<0>("ab"), StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(ParseHex<1>("abc"), StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(ParseHex<1>("abcd"), StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(ParseHex<1>("ab-"), StatusIs(StatusCode::kInvalidArgument));

  // Invalid characters:
  EXPECT_THAT(ParseHex<1>("ZF"), StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(ParseHex<2>("AB-C?"), StatusIs(StatusCode::kInvalidArgument));
}

iree::StatusOr<iree_device_size_t> ParseDeviceSize(std::string_view value) {
  iree_device_size_t size = 0;
  IREE_RETURN_IF_ERROR(iree::Status(iree_string_view_parse_device_size(
      iree_string_view_t{value.data(), (iree_host_size_t)value.size()},
      &size)));
  return size;
}

TEST(StringViewTest, ParseDeviceSize) {
  EXPECT_THAT(ParseDeviceSize("0"), IsOkAndHolds(Eq(0u)));
  EXPECT_THAT(ParseDeviceSize("1"), IsOkAndHolds(Eq(1u)));
  EXPECT_THAT(ParseDeviceSize("10000"), IsOkAndHolds(Eq(10000u)));
  EXPECT_THAT(ParseDeviceSize("0b"), IsOkAndHolds(Eq(0u)));
  EXPECT_THAT(ParseDeviceSize("0kb"), IsOkAndHolds(Eq(0u)));
  EXPECT_THAT(ParseDeviceSize("0gib"), IsOkAndHolds(Eq(0u)));
  EXPECT_THAT(ParseDeviceSize("1b"), IsOkAndHolds(Eq(1)));
  EXPECT_THAT(ParseDeviceSize("1kb"), IsOkAndHolds(Eq(1 * 1000u)));
  EXPECT_THAT(ParseDeviceSize("1kib"), IsOkAndHolds(Eq(1 * 1024u)));
  EXPECT_THAT(ParseDeviceSize("1000b"), IsOkAndHolds(Eq(1000 * 1u)));
  EXPECT_THAT(ParseDeviceSize("1000kb"), IsOkAndHolds(Eq(1000 * 1000u)));
  EXPECT_THAT(ParseDeviceSize("1000kib"), IsOkAndHolds(Eq(1000 * 1024u)));

  // NOTE: we don't verify very hard here and accept random suffixes just like
  // atoi does (because under the covers we're using atoi).
  EXPECT_THAT(ParseDeviceSize("123fb"), IsOkAndHolds(Eq(123u)));
}

TEST(StringViewTest, ParseDeviceSizeInvalid) {
  EXPECT_THAT(ParseDeviceSize(""), StatusIs(StatusCode::kInvalidArgument));
  EXPECT_THAT(ParseDeviceSize("abc"), StatusIs(StatusCode::kInvalidArgument));
}

TEST(StringViewTest, MatchPattern) {
  auto match = [](const char* value, const char* pattern) -> bool {
    return iree_string_view_match_pattern(iree_make_cstring_view(value),
                                          iree_make_cstring_view(pattern));
  };

  // Empty patterns and values.
  EXPECT_TRUE(match("", ""));
  EXPECT_FALSE(match("a", ""));
  EXPECT_FALSE(match("", "a"));

  // Exact matches.
  EXPECT_TRUE(match("abc", "abc"));
  EXPECT_FALSE(match("abc", "and"));
  EXPECT_FALSE(match("abc", "ab"));
  EXPECT_FALSE(match("ab", "abc"));

  // Single character wildcard (?).
  EXPECT_TRUE(match("a", "?"));
  EXPECT_TRUE(match("abc", "a?c"));
  EXPECT_TRUE(match("abc", "???"));
  EXPECT_FALSE(match("ab", "???"));
  EXPECT_FALSE(match("abcd", "???"));

  // Multi-character wildcard (*).
  EXPECT_TRUE(match("", "*"));
  EXPECT_TRUE(match("a", "*"));
  EXPECT_TRUE(match("abc", "*"));
  EXPECT_TRUE(match("abc", "a*"));
  EXPECT_TRUE(match("abc", "*c"));
  EXPECT_TRUE(match("abc", "a*c"));
  EXPECT_TRUE(match("abxyzc", "a*c"));
  EXPECT_FALSE(match("abc", "a*d"));

  // Combined wildcards.
  EXPECT_TRUE(match("abc", "?*"));
  EXPECT_TRUE(match("abc", "*?"));
  EXPECT_TRUE(match("abc", "?*?"));
  EXPECT_TRUE(match("abcdef", "a?c*f"));

  // Consecutive wildcards (tests coalescing to avoid exponential backtracking).
  EXPECT_TRUE(match("abc", "**"));
  EXPECT_TRUE(match("abc", "***"));
  EXPECT_TRUE(match("abc", "a**c"));
  EXPECT_TRUE(match("abc", "**c"));
  EXPECT_TRUE(match("abc", "a**"));

  // Pathological pattern that would cause exponential backtracking without
  // coalescing: many wildcards followed by a non-matching suffix.
  // This must complete in reasonable time (milliseconds, not seconds).
  EXPECT_FALSE(match("aaaaaaaaaaaaaaaaaaaab", "**************c"));
  EXPECT_TRUE(match("aaaaaaaaaaaaaaaaaaaab", "**************b"));

  // Alternating ?* patterns - also pathological without normalization.
  // ?* means "1 or more chars", ?*?* means "2 or more chars", etc.
  EXPECT_TRUE(match("ab", "?*"));
  EXPECT_TRUE(match("abc", "?*?"));
  EXPECT_TRUE(match("abc", "?*?*"));
  EXPECT_TRUE(match("abcd", "?*?*"));
  EXPECT_FALSE(match("a", "?*?*"));  // Need at least 2 chars.

  // Pathological alternating patterns - must complete quickly.
  EXPECT_FALSE(match("aaaaaaaaaaaaaaaaaaaab", "?*?*?*?*?*?*?*c"));
  EXPECT_TRUE(match("aaaaaaaaaaaaaaaaaaaab", "?*?*?*?*?*?*?*b"));
  EXPECT_FALSE(match("aaaaaaaaaaaaaaaaaaaab", "*?*?*?*?*?*?*?c"));
  EXPECT_TRUE(match("aaaaaaaaaaaaaaaaaaaab", "*?*?*?*?*?*?*?b"));

  // Patterns with too many wildcards are rejected (returns false).
  // Limit is 16 wildcards to prevent O(n^2) blowup.
  EXPECT_TRUE(match("abcdefghijklmnop", "????????????????"));  // 16 - ok
  EXPECT_FALSE(
      match("abcdefghijklmnopq", "?????????????????"));  // 17 - rejected
  EXPECT_FALSE(
      match("anything", "?*?*?*?*?*?*?*?*?*"));  // 18 wildcards - rejected
}

}  // namespace
