#include "tile/base/string.h"

#include "gtest/gtest.h"

#include "tile/base/internal/logging.h"
#include "tile/base/random.h"

#include <cmath>

namespace tile {
TEST(String, TryParseIntegral)
{
    ASSERT_FALSE(TryParse<int>(std::string(123456, 'a')));
    ASSERT_FALSE(TryParse<int>(""));
    ASSERT_FALSE(TryParse<int>("a"));
    ASSERT_FALSE(TryParse<int>("1-2"));

    ASSERT_FALSE(TryParse<std::int8_t>(std::to_string(INT8_MAX + 1LL)));
    ASSERT_FALSE(TryParse<std::int8_t>(std::to_string(INT8_MIN - 1LL)));
    ASSERT_FALSE(TryParse<std::uint8_t>(std::to_string(UINT8_MAX + 1LL)));
    ASSERT_FALSE(TryParse<std::uint8_t>("-1"));

    ASSERT_FALSE(TryParse<std::int16_t>(std::to_string(INT16_MAX + 1LL)));
    ASSERT_FALSE(TryParse<std::int16_t>(std::to_string(INT16_MIN - 1LL)));
    ASSERT_FALSE(TryParse<std::uint16_t>(std::to_string(UINT16_MAX + 1LL)));
    ASSERT_FALSE(TryParse<std::uint16_t>("-1"));

    ASSERT_FALSE(TryParse<std::int32_t>(std::to_string(INT32_MAX + 1LL)));
    ASSERT_FALSE(TryParse<std::int32_t>(std::to_string(INT32_MIN - 1LL)));
    ASSERT_FALSE(TryParse<std::uint32_t>(std::to_string(UINT32_MAX + 1LL)));
    ASSERT_FALSE(TryParse<std::uint32_t>("-1"));

    ASSERT_FALSE(TryParse<std::int64_t>(std::to_string(INT64_MAX + 1ULL)));
    ASSERT_FALSE(TryParse<std::int64_t>(std::to_string(INT64_MIN) + "0"));
    ASSERT_FALSE(TryParse<std::uint64_t>(std::to_string(UINT64_MAX) + "0"));
    // FIXME: TryParse<std::uint64_t>("-1") should return false.
    // ASSERT_FALSE(TryParse<std::uint64_t>("-1"));

    ASSERT_TRUE(TryParse<int>("0"));
    ASSERT_TRUE(TryParse<std::int64_t>(std::to_string(INT64_MAX)));
    ASSERT_TRUE(TryParse<std::int8_t>("0"));
    ASSERT_TRUE(TryParse<std::uint8_t>("0"));
    ASSERT_TRUE(TryParse<std::int16_t>("0"));
    ASSERT_TRUE(TryParse<std::uint16_t>("0"));
    ASSERT_TRUE(TryParse<std::int32_t>("0"));
    ASSERT_TRUE(TryParse<std::uint32_t>("0"));
    ASSERT_TRUE(TryParse<std::int64_t>("0"));
    ASSERT_TRUE(TryParse<std::uint64_t>("0"));

    for (int i = -100; i != 100; ++i) {
        auto rc = TryParse<int>(std::to_string(i));
        ASSERT_TRUE(rc);
        ASSERT_EQ(*rc, i);
    }
    for (int i = 0; i != 100000; ++i) {
        auto x  = Random<std::int64_t>();
        auto rc = TryParse<std::int64_t>(std::to_string(x));
        ASSERT_TRUE(rc);
        ASSERT_EQ(x, *rc);
    }
    for (int i = 0; i != 100000; ++i) {
        auto x  = Random<std::uint64_t>();
        auto rc = TryParse<std::uint64_t>(std::to_string(x));
        ASSERT_TRUE(rc);
        ASSERT_EQ(x, *rc);
    }
}

TEST(String, TryParseFloatingPoint)
{
    ASSERT_FALSE(TryParse<float>(""));
    ASSERT_FALSE(TryParse<double>(""));
    ASSERT_FALSE(TryParse<long double>(""));
    ASSERT_FALSE(TryParse<double>("a"));
    ASSERT_FALSE(TryParse<long double>("a"));

    ASSERT_TRUE(TryParse<float>(std::to_string(HUGE_VALF)));
    ASSERT_TRUE(TryParse<float>(std::to_string(-HUGE_VALF)));
    ASSERT_TRUE(TryParse<double>(std::to_string(HUGE_VAL)));
    ASSERT_TRUE(TryParse<double>(std::to_string(-HUGE_VAL)));
    ASSERT_TRUE(TryParse<long double>(std::to_string(HUGE_VALL)));
    ASSERT_TRUE(TryParse<long double>(std::to_string(-HUGE_VALL)));

    for (int i = 0; i != 100000; ++i) {
        auto x = 1.0 * Random() * Random();
        {
            auto rc = TryParse<float>(std::to_string(x));
            ASSERT_TRUE(rc);
            ASSERT_NEAR(x, *rc, fabs(x + *rc) / 2 * 1e-5);
        }
        {
            auto rc = TryParse<double>(std::to_string(x));
            ASSERT_TRUE(rc);
            ASSERT_NEAR(x, *rc, fabs(x + *rc) / 2 * 1e-5);
        }
        {
            auto rc = TryParse<long double>(std::to_string(x));
            ASSERT_TRUE(rc);
            ASSERT_NEAR(x, *rc, fabsl(x + *rc) / 2 * 1e-5);
        }
    }
}

TEST(String, TryParseBool)
{
    ASSERT_FALSE(TryParse<bool>(""));
    ASSERT_FALSE(TryParse<bool>(".."));
    ASSERT_TRUE(TryParse<bool>("2"));

    ASSERT_TRUE(TryParse<bool>("1"));
    ASSERT_TRUE(TryParse<bool>("0"));
    ASSERT_TRUE(TryParse<bool>("y"));
    ASSERT_TRUE(TryParse<bool>("n"));
    ASSERT_TRUE(TryParse<bool>("Y"));
    ASSERT_TRUE(TryParse<bool>("N"));
    ASSERT_TRUE(TryParse<bool>("Yes"));
    ASSERT_TRUE(TryParse<bool>("nO"));
    ASSERT_TRUE(TryParse<bool>("TRue"));
    ASSERT_TRUE(TryParse<bool>("faLse"));

    ASSERT_TRUE(*TryParse<bool>("1"));
    ASSERT_FALSE(*TryParse<bool>("0"));
    ASSERT_TRUE(*TryParse<bool>("y"));
    ASSERT_FALSE(*TryParse<bool>("n"));
    ASSERT_TRUE(*TryParse<bool>("Y"));
    ASSERT_FALSE(*TryParse<bool>("N"));
    ASSERT_TRUE(*TryParse<bool>("yeS"));
    ASSERT_FALSE(*TryParse<bool>("No"));
    ASSERT_TRUE(*TryParse<bool>("tRUe"));
    ASSERT_FALSE(*TryParse<bool>("falsE"));
}

TEST(String, StartsWith)
{
    ASSERT_TRUE(StartsWith("asdf", "asdf"));
    ASSERT_TRUE(StartsWith("asdf", "asd"));
    ASSERT_TRUE(StartsWith("asdf", "as"));
    ASSERT_TRUE(StartsWith("asdf", "a"));
    ASSERT_TRUE(StartsWith("asdf", ""));
    ASSERT_TRUE(StartsWith("", ""));
    ASSERT_TRUE(!StartsWith("asdf", "b"));
    ASSERT_TRUE(!StartsWith("", "b"));
}

TEST(String, EndsWith)
{
    ASSERT_TRUE(EndsWith("asdf", "asdf"));
    ASSERT_TRUE(EndsWith("asdf", "sdf"));
    ASSERT_TRUE(EndsWith("asdf", "df"));
    ASSERT_TRUE(EndsWith("asdf", "f"));
    ASSERT_TRUE(EndsWith("asdf", ""));
    ASSERT_TRUE(EndsWith("", ""));
    ASSERT_TRUE(!EndsWith("asdf", "b"));
    ASSERT_TRUE(!EndsWith("", "b"));
}

TEST(String, Replace)
{
    ASSERT_EQ("//////", Replace("////////////", "//", "/"));
    ASSERT_EQ("aabb", Replace("bbbb", "b", "a", 2));
    ASSERT_EQ("/././././", Replace("/.//.//.//./", "//", "/"));
    ASSERT_EQ("/.//./././", Replace("/.///.//.//./", "//", "/"));
    ASSERT_EQ("abbb", Replace("bbbb", "b", "a", 1));
    ASSERT_EQ("//", Replace("//", "/", "/"));
    ASSERT_EQ("", Replace("//", "/", ""));
    ASSERT_EQ("//", Replace("///", "//", "/"));
}

TEST(String, Trim)
{
    ASSERT_EQ("", Trim(""));
    ASSERT_EQ("", Trim(" "));
    ASSERT_EQ("", Trim("  "));
    ASSERT_EQ("", Trim("   "));
    ASSERT_EQ("aa", Trim("aa"));
    ASSERT_EQ("aa", Trim(" aa"));
    ASSERT_EQ("aa", Trim("  aa"));
    ASSERT_EQ("aa", Trim("aa "));
    ASSERT_EQ("aa", Trim("aa  "));
    ASSERT_EQ("aa", Trim(" aa "));
    ASSERT_EQ("aa", Trim("  aa "));
    ASSERT_EQ("aa", Trim(" aa  "));
    ASSERT_EQ("aa", Trim("  aa  "));
    ASSERT_EQ("a a", Trim("  a a  "));
}

TEST(String, TrimByCustset)
{
    static_assert(std::is_constructible<Slice, decltype("d")>::value, "");
    static_assert(!std::is_constructible<Slice, int(int)>::value, "");
    ASSERT_EQ(" ", Trim("abcd abcd", "abcd"));
    ASSERT_EQ("abcd abc", Trim("abcd abcd", "d"));
    ASSERT_EQ("abcd abcd ", Trim("abcd abcd ", "d"));
    ASSERT_EQ(" abcd abcd ", Trim(" abcd abcd ", "ad"));
    ASSERT_EQ("bcd abc", Trim(" abcd abcd ", "ad "));
}

TEST(String, Split1)
{
    auto splited = Split("/a/b/c/d/e/f///g", '/');
    ASSERT_EQ(7, splited.size());
    ASSERT_EQ("a", splited[0]);
    ASSERT_EQ("b", splited[1]);
    ASSERT_EQ("c", splited[2]);
    ASSERT_EQ("d", splited[3]);
    ASSERT_EQ("e", splited[4]);
    ASSERT_EQ("f", splited[5]);
    ASSERT_EQ("g", splited[6]);
}

TEST(String, Split2)
{
    auto splited = Split("a///g/", '/');
    ASSERT_EQ(2, splited.size());
    ASSERT_EQ(splited[0], "a");
    ASSERT_EQ(splited[1], "g");
}

TEST(String, Split3)
{
    auto splited = Split("/////a/g", '/');
    ASSERT_EQ(2, splited.size());
    ASSERT_EQ("a", splited[0]);
    ASSERT_EQ("g", splited[1]);
}

TEST(String, Split4)
{
    auto splited = Split("/////a/g///", '/');
    ASSERT_EQ(2, splited.size());
    ASSERT_EQ("a", splited[0]);
    ASSERT_EQ("g", splited[1]);
}

TEST(String, Split5)
{
    auto splited = Split("////a//g//", "//");
    ASSERT_EQ(2, splited.size());
    ASSERT_EQ("a", splited[0]);
    ASSERT_EQ("g", splited[1]);
}

TEST(String, Split6)
{
    auto splited = Split("//a//g", "//");
    ASSERT_EQ(2, splited.size());
    ASSERT_EQ("a", splited[0]);
    ASSERT_EQ("g", splited[1]);
}

TEST(String, Split7)
{
    auto splited = Split("a//g", "//");
    ASSERT_EQ(2, splited.size());
    ASSERT_EQ("a", splited[0]);
    ASSERT_EQ("g", splited[1]);
}

TEST(String, Split8)
{
    auto splited = Split("//", "//");
    ASSERT_EQ(0, splited.size());
}

TEST(String, Split9)
{
    auto splited = Split("", '/');
    ASSERT_EQ(0, splited.size());
}

TEST(String, SplitWithLimit)
{
    auto splited = Split("a/b/c/d/e/f/g", '/', false, 3);
    ASSERT_EQ(3, splited.size());
    ASSERT_EQ(splited[0], "a");
    ASSERT_EQ(splited[1], "b");
    ASSERT_EQ(splited[2], "c/d/e/f/g");
}

TEST(String, SplitKeepEmpty1)
{
    auto splited = Split("///a//g///", '/', true);
    ASSERT_EQ(9, splited.size());
    ASSERT_EQ("", splited[0]);
    ASSERT_EQ("", splited[1]);
    ASSERT_EQ("", splited[2]);
    ASSERT_EQ("a", splited[3]);
    ASSERT_EQ("", splited[4]);
    ASSERT_EQ("g", splited[5]);
    ASSERT_EQ("", splited[6]);
    ASSERT_EQ("", splited[7]);
    ASSERT_EQ("", splited[8]);
}

TEST(String, SplitKeepEmpty2)
{
    auto splited = Split("//", "//", true);
    ASSERT_EQ(2, splited.size());
    ASSERT_EQ("", splited[0]);
    ASSERT_EQ("", splited[1]);
}

TEST(String, Join)
{
    ASSERT_EQ("a\nbb\nccc", Join({"a", "bb", "ccc"}, "\n"));
    ASSERT_EQ("a\n\nbb\nccc", Join({"a", "", "bb", "ccc"}, "\n"));
    ASSERT_EQ("a\n\nbb\nccc", Join({"a", "", "bb", "ccc"}, "\n"));
    std::vector<Slice> s = {"a", "", "bb", "ccc"};
    ASSERT_EQ("a\n\nbb\nccc", Join(s, "\n"));
}

TEST(String, EqualsIgnoreCase)
{
    ASSERT_TRUE(EqualsIgnoreCase("abc", "abc"));
    ASSERT_TRUE(EqualsIgnoreCase("abc", "aBc"));
    ASSERT_TRUE(EqualsIgnoreCase("abc", "ABC"));
    ASSERT_FALSE(EqualsIgnoreCase("abc", "ab"));
    ASSERT_FALSE(EqualsIgnoreCase("abc", "abcd"));
    ASSERT_FALSE(EqualsIgnoreCase("abc", "d"));
}

TEST(String, ToUpper)
{
    std::string s = "abCD";
    ToUpper(&s);
    ASSERT_EQ("ABCD", s);
    ASSERT_EQ("ABCD", ToUpper("aBCd"));
}

TEST(String, ToLower)
{
    std::string s = "abCD";
    ToLower(&s);
    ASSERT_EQ("abcd", s);
    ASSERT_EQ("abcd", ToLower("aBCd"));
}
}// namespace tile
