/*
 * Copyright 2020 Bloomberg Finance LP
 *
 * 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
 *
 *     http://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 <buildboxcommon_stringutils.h>
#include <gtest/gtest.h>

using namespace buildboxcommon;
using namespace testing;

int myCustomFilter(int ch) { return (ch == '@' || std::isspace(ch)); }

// TEST CASES
TEST(StringUtilsTest, LeftTrimTest)
{
    std::string inputString(" \t\ntesting one two three");
    std::string expectedString("testing one two three");
    StringUtils::ltrim(&inputString);
    EXPECT_EQ(inputString, expectedString);
}

TEST(StringUtilsTest, LeftTrimFailTest)
{
    std::string inputString(" \t\ntesting one two three");
    std::string expectedString(" testing one two three");
    StringUtils::ltrim(&inputString);
    EXPECT_NE(inputString, expectedString);
}

TEST(StringUtilsTest, RightTrimTest)
{
    std::string inputString("testing one two three \t\n");
    std::string expectedString("testing one two three");
    StringUtils::rtrim(&inputString);
    EXPECT_EQ(inputString, expectedString);
}

TEST(StringUtilsTest, TrimTest)
{
    std::string inputString(" \t\ntesting one two three \t\n");
    std::string expectedString("testing one two three");
    StringUtils::trim(&inputString);
    EXPECT_EQ(inputString, expectedString);
}

TEST(StringUtilsTest, LeftTrimTestCopy)
{
    std::string inputString(" \t\ntesting one two three");
    std::string expectedString("testing one two three");
    EXPECT_EQ(StringUtils::ltrim(inputString), expectedString);
}

TEST(StringUtilsTest, RightTrimTestCopy)
{
    std::string inputString("testing one two three \t\n");
    std::string expectedString("testing one two three");
    EXPECT_EQ(StringUtils::rtrim(inputString), expectedString);
}

TEST(StringUtilsTest, TrimTestCopy)
{
    std::string inputString(" \t\ntesting one two three \t\n");
    std::string expectedString("testing one two three");
    EXPECT_EQ(StringUtils::trim(inputString), expectedString);
}

TEST(StringUtilsTest, LeftTrimCustomSuccessTest)
{
    std::string inputString("@ \n\ttesting one two three");
    std::string expectedString("testing one two three");
    StringUtils::ltrim(&inputString, myCustomFilter);
    EXPECT_EQ(inputString, expectedString);
}

TEST(StringUtilsTest, LeftTrimCustomFailTest)
{
    std::string inputString("! \n\ttesting one two three");
    std::string expectedString("! \n\ttesting one two three");
    StringUtils::ltrim(&inputString, myCustomFilter);
    EXPECT_EQ(inputString, expectedString);
}

TEST(StringUtilsTest, OrdinalTest)
{
    const std::map<int, std::string> number_to_expected_ordinal = {
        {0, "0th"},      {1, "1st"},     {2, "2nd"},     {3, "3rd"},
        {4, "4th"},      {5, "5th"},     {6, "6th"},     {7, "7th"},
        {8, "8th"},      {9, "9th"},     {10, "10th"},   {11, "11th"},
        {12, "12th"},    {13, "13th"},   {14, "14th"},   {15, "15th"},
        {16, "16th"},    {17, "17th"},   {18, "18th"},   {19, "19th"},
        {20, "20th"},    {21, "21st"},   {22, "22nd"},   {23, "23rd"},
        {101, "101st"},  {111, "111th"}, {121, "121st"}, {143, "143rd"},
        {1601, "1601st"}};

    for (const auto &[number, ordinal] : number_to_expected_ordinal) {
        std::string result = StringUtils::ordinal(number);
        EXPECT_EQ(result, ordinal);
    }
}

TEST(StringUtilsTest, RandomHexStringTest)
{
    std::string random = StringUtils::getRandomHexString();
    EXPECT_EQ(random.size(), 8);

    const char *str = random.c_str();
    char *end = nullptr;
    auto _ = strtoull(str, &end, 16);
    (void)_;
    EXPECT_EQ(*end, '\0');
}

TEST(StringUtilsTest, UUIDStringTest)
{
    std::string uuid = StringUtils::getUUIDString();
    // Simple sanity checks that this looks like a UUID
    EXPECT_EQ(uuid.size(), 36);
    EXPECT_EQ(uuid.find('-'), 8);
    EXPECT_EQ(uuid.find('-', 9), 13);
    EXPECT_EQ(uuid.find('-', 14), 18);
    EXPECT_EQ(uuid.find('-', 19), 23);
    EXPECT_EQ(uuid.find('-', 24), uuid.npos);
    // Make sure it's not the NIL UUID
    EXPECT_NE(uuid, "00000000-0000-0000-0000-000000000000");
}

TEST(StringUtilsTest, ParseSizeTest)
{
    EXPECT_EQ(StringUtils::parseSize("0"), 0);
    EXPECT_EQ(StringUtils::parseSize("1"), 1);
    EXPECT_EQ(StringUtils::parseSize("16"), 16);
    EXPECT_EQ(StringUtils::parseSize("16K"), 16000);
    EXPECT_EQ(StringUtils::parseSize("16M"), 16000000);
    EXPECT_EQ(StringUtils::parseSize("16G"), 16000000000);
    EXPECT_EQ(StringUtils::parseSize("16T"), 16000000000000);
    EXPECT_THROW(StringUtils::parseSize("16X"), std::invalid_argument);
    EXPECT_THROW(StringUtils::parseSize("16XY"), std::invalid_argument);
    EXPECT_THROW(StringUtils::parseSize("-1"), std::invalid_argument);
    EXPECT_THROW(StringUtils::parseSize("-16"), std::invalid_argument);
    EXPECT_THROW(StringUtils::parseSize("-16K"), std::invalid_argument);
    EXPECT_THROW(StringUtils::parseSize("ab"), std::invalid_argument);
    EXPECT_THROW(StringUtils::parseSize("a0"), std::invalid_argument);
}

TEST(StringUtils, ParseDigest)
{
    Digest digest;
    EXPECT_TRUE(StringUtils::parseDigest("abc123def/1234", &digest));
    ASSERT_EQ("abc123def", digest.hash());
    ASSERT_EQ(1234, digest.size_bytes());

    EXPECT_FALSE(StringUtils::parseDigest("notvalid/123", &digest));
}

TEST(StringUtils, Split)
{
    EXPECT_THROW(StringUtils::split(std::string("abc"), ""),
                 std::invalid_argument);

    std::vector<std::string> expected;

    expected = {"a", "b", "c"};
    EXPECT_EQ(StringUtils::split(std::string("a,b,c"), ","), expected);
    expected = {"a", "b", "c", ""};
    EXPECT_EQ(StringUtils::split(std::string("a,b,c,"), ","), expected);
    expected = {"", "a", "b", "c"};
    EXPECT_EQ(StringUtils::split(std::string(",a,b,c"), ","), expected);
    expected = {"abc"};
    EXPECT_EQ(StringUtils::split(std::string("abc"), ","), expected);
    expected = {""};
    EXPECT_EQ(StringUtils::split(std::string(""), ","), expected);
}

TEST(StringUtils, Join)
{
    std::vector<std::string> input;

    input = {"a", "b", "c"};
    EXPECT_EQ(StringUtils::join(input, ","), "a,b,c");
    input = {"a", "b", "c", ""};
    EXPECT_EQ(StringUtils::join(input, ","), "a,b,c,");
    input = {"", "a", "b", "c"};
    EXPECT_EQ(StringUtils::join(input, ","), ",a,b,c");
    input = {"abc"};
    EXPECT_EQ(StringUtils::join(input, ","), "abc");
    input = {""};
    EXPECT_EQ(StringUtils::join(input, ","), "");
    input = {"a", "b", "c"};
    EXPECT_EQ(StringUtils::join(input), "a b c");
    input = {"", "a", "b", "c"};
    EXPECT_EQ(StringUtils::join(input), " a b c");
    input = {""};
    EXPECT_EQ(StringUtils::join(input), "");
}

TEST(StringUtils, ParseStatusCode)
{
    const auto valid = "RESOURCE_EXHAUSTED";
    EXPECT_EQ(StringUtils::parseStatusCode(valid),
              grpc::StatusCode::RESOURCE_EXHAUSTED);

    const auto invalid = "FOO";
    EXPECT_THROW(StringUtils::parseStatusCode(invalid), std::invalid_argument);
}
