#include "strbuf.h"

#include <library/cpp/unittest/registar.h>

#include <string_view>

Y_UNIT_TEST_SUITE(TStrBufTest) {
    Y_UNIT_TEST(TestConstructorsAndOperators) {
        TStringBuf str("qwerty");

        UNIT_ASSERT_EQUAL(*str.data(), 'q');
        UNIT_ASSERT_EQUAL(str.size(), 6);

        TStringBuf str1(AsStringBuf("qwe\0rty"));
        TStringBuf str2(str1.data());
        UNIT_ASSERT_VALUES_UNEQUAL(str1, str2);
        UNIT_ASSERT_VALUES_EQUAL(str1.size(), 7);
        UNIT_ASSERT_VALUES_EQUAL(str2.size(), 3);

        std::string_view helloWorld("Hello, World!");
        TStringBuf fromStringView(helloWorld);
        UNIT_ASSERT_EQUAL(fromStringView.data(), helloWorld.data());
        UNIT_ASSERT_EQUAL(fromStringView.size(), helloWorld.size());

        std::string_view fromStringBuf = fromStringView;
        UNIT_ASSERT_EQUAL(helloWorld.data(), fromStringBuf.data());
        UNIT_ASSERT_EQUAL(helloWorld.size(), fromStringBuf.size());
    }

    Y_UNIT_TEST(TestConstExpr) {
        static constexpr TStringBuf str1("qwe\0rty", 7);
        static constexpr TStringBuf str2(str1.data(), str1.size());
        static constexpr TStringBuf str3 = AsStringBuf("qwe\0rty");

        UNIT_ASSERT_VALUES_EQUAL(str1.size(), 7);

        UNIT_ASSERT_VALUES_EQUAL(str1, str2);
        UNIT_ASSERT_VALUES_EQUAL(str2, str3);
        UNIT_ASSERT_VALUES_EQUAL(str1, str3);

        static constexpr std::string_view view1(str1);
        UNIT_ASSERT_VALUES_EQUAL(str1, view1);
        static_assert(str1.data() == view1.data());
        static_assert(str1.size() == view1.size());

        static constexpr TStringBuf str4(view1);
        UNIT_ASSERT_VALUES_EQUAL(str1, str4);
        static_assert(str1.data() == str4.data());
        static_assert(str1.size() == str4.size());
    }

    Y_UNIT_TEST(TestAfter) {
        TStringBuf str("qwerty");

        UNIT_ASSERT_VALUES_EQUAL(str.After('w'), AsStringBuf("erty"));
        UNIT_ASSERT_VALUES_EQUAL(str.After('x'), AsStringBuf("qwerty"));
        UNIT_ASSERT_VALUES_EQUAL(str.After('y'), TStringBuf());
        UNIT_ASSERT_STRINGS_EQUAL(str.After('='), str);

        // Also works properly on empty strings
        TStringBuf empty;
        UNIT_ASSERT_STRINGS_EQUAL(empty.After('x'), empty);
    }

    Y_UNIT_TEST(TestBefore) {
        TStringBuf str("qwerty");

        UNIT_ASSERT_VALUES_EQUAL(str.Before('w'), AsStringBuf("q"));
        UNIT_ASSERT_VALUES_EQUAL(str.Before('x'), AsStringBuf("qwerty"));
        UNIT_ASSERT_VALUES_EQUAL(str.Before('y'), AsStringBuf("qwert"));
        UNIT_ASSERT_VALUES_EQUAL(str.Before('q'), TStringBuf());
    }

    Y_UNIT_TEST(TestRAfterBefore) {
        TStringBuf str("a/b/c");
        UNIT_ASSERT_STRINGS_EQUAL(str.RAfter('/'), "c");
        UNIT_ASSERT_STRINGS_EQUAL(str.RAfter('_'), str);
        UNIT_ASSERT_STRINGS_EQUAL(str.RAfter('a'), "/b/c");
        UNIT_ASSERT_STRINGS_EQUAL(str.RBefore('/'), "a/b");
        UNIT_ASSERT_STRINGS_EQUAL(str.RBefore('_'), str);
        UNIT_ASSERT_STRINGS_EQUAL(str.RBefore('a'), "");
    }

    Y_UNIT_TEST(TestAfterPrefix) {
        TStringBuf str("cat_dog");

        TStringBuf r = "the_same";
        UNIT_ASSERT(!str.AfterPrefix("dog", r));
        UNIT_ASSERT_EQUAL(r, "the_same");
        UNIT_ASSERT(str.AfterPrefix("cat_", r));
        UNIT_ASSERT_EQUAL(r, "dog");

        //example:
        str = "http://ya.ru";
        if (str.AfterPrefix("http://", r)) {
            UNIT_ASSERT_EQUAL(r, "ya.ru");
        }

        // SkipPrefix()
        TStringBuf a = "abcdef";
        UNIT_ASSERT(a.SkipPrefix("a") && a == "bcdef");
        UNIT_ASSERT(a.SkipPrefix("bc") && a == "def");
        UNIT_ASSERT(a.SkipPrefix("") && a == "def");
        UNIT_ASSERT(!a.SkipPrefix("xyz") && a == "def");
        UNIT_ASSERT(!a.SkipPrefix("defg") && a == "def");
        UNIT_ASSERT(a.SkipPrefix("def") && a == "");
        UNIT_ASSERT(a.SkipPrefix("") && a == "");
        UNIT_ASSERT(!a.SkipPrefix("def") && a == "");
    }

    Y_UNIT_TEST(TestBeforeSuffix) {
        TStringBuf str("cat_dog");

        TStringBuf r = "the_same";
        UNIT_ASSERT(!str.BeforeSuffix("cat", r));
        UNIT_ASSERT_EQUAL(r, "the_same");
        UNIT_ASSERT(str.BeforeSuffix("_dog", r));
        UNIT_ASSERT_EQUAL(r, "cat");

        //example:
        str = "maps.yandex.com.ua";
        if (str.BeforeSuffix(".ru", r)) {
            UNIT_ASSERT_EQUAL(r, "maps.yandex");
        }

        // ChopSuffix()
        TStringBuf a = "abcdef";
        UNIT_ASSERT(a.ChopSuffix("f") && a == "abcde");
        UNIT_ASSERT(a.ChopSuffix("de") && a == "abc");
        UNIT_ASSERT(a.ChopSuffix("") && a == "abc");
        UNIT_ASSERT(!a.ChopSuffix("xyz") && a == "abc");
        UNIT_ASSERT(!a.ChopSuffix("abcd") && a == "abc");
        UNIT_ASSERT(a.ChopSuffix("abc") && a == "");
        UNIT_ASSERT(a.ChopSuffix("") && a == "");
        UNIT_ASSERT(!a.ChopSuffix("abc") && a == "");
    }

    Y_UNIT_TEST(TestEmpty) {
        UNIT_ASSERT(TStringBuf().empty());
        UNIT_ASSERT(!AsStringBuf("q").empty());
    }

    Y_UNIT_TEST(TestShift) {
        TStringBuf qw("qwerty");
        TStringBuf str;

        str = qw;
        str.Chop(10);
        UNIT_ASSERT(str.empty());

        str = qw;
        UNIT_ASSERT_EQUAL(str.SubStr(2), AsStringBuf("erty"));
        UNIT_ASSERT_EQUAL(str.Skip(3), qw.SubStr(3));
        str.Chop(1);
        UNIT_ASSERT_EQUAL(str, AsStringBuf("rt"));
    }

    Y_UNIT_TEST(TestSplit) {
        TStringBuf qw("qwerty");
        TStringBuf lt, rt;

        rt = qw;
        lt = rt.NextTok('r');
        UNIT_ASSERT_EQUAL(lt, AsStringBuf("qwe"));
        UNIT_ASSERT_EQUAL(rt, AsStringBuf("ty"));

        lt = qw;
        rt = lt.SplitOff('r');
        UNIT_ASSERT_EQUAL(lt, AsStringBuf("qwe"));
        UNIT_ASSERT_EQUAL(rt, AsStringBuf("ty"));

        rt = qw;
        lt = rt.NextTok('r');
        TStringBuf ty = rt.NextTok('r'); // no 'r' in "ty"
        UNIT_ASSERT_EQUAL(rt.size(), 0);
        UNIT_ASSERT_EQUAL(ty, AsStringBuf("ty"));
    }

    Y_UNIT_TEST(TestNextTok) {
        TStringBuf buf("12q45q");
        TStringBuf tok;

        UNIT_ASSERT(buf.NextTok('q', tok) && tok == "12");
        UNIT_ASSERT(buf.NextTok('q', tok) && tok == "45");
        UNIT_ASSERT(!buf.NextTok('q', tok));
    }

    Y_UNIT_TEST(TestNextStringTok) {
        TStringBuf buf1("a@@b@@c");
        UNIT_ASSERT_EQUAL(buf1.NextTok("@@"), AsStringBuf("a"));
        UNIT_ASSERT_EQUAL(buf1.NextTok("@@"), AsStringBuf("b"));
        UNIT_ASSERT_EQUAL(buf1.NextTok("@@"), AsStringBuf("c"));
        UNIT_ASSERT_EQUAL(buf1, TStringBuf());

        TStringBuf buf2("a@@b@@c");
        UNIT_ASSERT_EQUAL(buf2.RNextTok("@@"), AsStringBuf("c"));
        UNIT_ASSERT_EQUAL(buf2.RNextTok("@@"), AsStringBuf("b"));
        UNIT_ASSERT_EQUAL(buf2.RNextTok("@@"), AsStringBuf("a"));
        UNIT_ASSERT_EQUAL(buf2, TStringBuf());

        TStringBuf buf3("a@@b@@c");
        UNIT_ASSERT_EQUAL(buf3.RNextTok("@@@"), AsStringBuf("a@@b@@c"));
        UNIT_ASSERT_EQUAL(buf3, TStringBuf());
    }

    Y_UNIT_TEST(TestReadLine) {
        TStringBuf buf("12\n45\r\n\r\n23");
        TStringBuf tok;

        buf.ReadLine(tok);
        UNIT_ASSERT_VALUES_EQUAL(tok, "12");

        buf.ReadLine(tok);
        UNIT_ASSERT_VALUES_EQUAL(tok, "45");

        buf.ReadLine(tok);
        UNIT_ASSERT_VALUES_EQUAL(tok, "");

        buf.ReadLine(tok);
        UNIT_ASSERT_VALUES_EQUAL(tok, "23");

        UNIT_ASSERT(!buf.ReadLine(tok));
    }

    Y_UNIT_TEST(TestRFind) {
        TStringBuf buf1 = AsStringBuf("123123456");
        UNIT_ASSERT_EQUAL(buf1.rfind('3'), 5);
        UNIT_ASSERT_EQUAL(buf1.rfind('4'), 6);
        UNIT_ASSERT_EQUAL(buf1.rfind('7'), TStringBuf::npos);

        TStringBuf buf2;
        UNIT_ASSERT_EQUAL(buf2.rfind('3'), TStringBuf::npos);

        TStringBuf buf3 = TStringBuf("123123456", 6);
        UNIT_ASSERT_EQUAL(buf3.rfind('3'), 5);
        UNIT_ASSERT_EQUAL(buf3.rfind('4'), TStringBuf::npos);
        UNIT_ASSERT_EQUAL(buf3.rfind('7'), TStringBuf::npos);

        TStringBuf buf4 = TStringBuf("123123456", 5);
        UNIT_ASSERT_EQUAL(buf4.rfind('3'), 2);
    }

    Y_UNIT_TEST(TestRNextTok) {
        TStringBuf buf1("a.b.c");
        UNIT_ASSERT_EQUAL(buf1.RNextTok('.'), AsStringBuf("c"));
        UNIT_ASSERT_EQUAL(buf1, AsStringBuf("a.b"));

        TStringBuf buf2("a");
        UNIT_ASSERT_EQUAL(buf2.RNextTok('.'), AsStringBuf("a"));
        UNIT_ASSERT_EQUAL(buf2, TStringBuf());

        TStringBuf buf3("ab cd ef"), tok;
        UNIT_ASSERT(buf3.RNextTok(' ', tok) && tok == "ef" && buf3 == "ab cd");
        UNIT_ASSERT(buf3.RNextTok(' ', tok) && tok == "cd" && buf3 == "ab");
        UNIT_ASSERT(buf3.RNextTok(' ', tok) && tok == "ab" && buf3 == "");
        UNIT_ASSERT(!buf3.RNextTok(' ', tok) && tok == "ab" && buf3 == ""); // not modified
    }

    Y_UNIT_TEST(TestRSplitOff) {
        TStringBuf buf1("a.b.c");
        UNIT_ASSERT_EQUAL(buf1.RSplitOff('.'), AsStringBuf("a.b"));
        UNIT_ASSERT_EQUAL(buf1, AsStringBuf("c"));

        TStringBuf buf2("a");
        UNIT_ASSERT_EQUAL(buf2.RSplitOff('.'), TStringBuf());
        UNIT_ASSERT_EQUAL(buf2, AsStringBuf("a"));
    }

    Y_UNIT_TEST(TestCBeginCEnd) {
        const char helloThere[] = "Hello there";
        TStringBuf s{helloThere};

        size_t index = 0;
        for (auto it = s.cbegin(); s.cend() != it; ++it, ++index) {
            UNIT_ASSERT_VALUES_EQUAL(helloThere[index], *it);
        }
    }

    Y_UNIT_TEST(TestSplitOnAt) {
        TStringBuf s = "abcabc";
        TStringBuf l, r;

        size_t pos = s.find('a');
        UNIT_ASSERT(s.TrySplitOn(pos, l, r));
        UNIT_ASSERT(l == "" && r == "bcabc");
        UNIT_ASSERT(s.TrySplitAt(pos, l, r));
        UNIT_ASSERT(l == "" && r == "abcabc");

        pos = s.find("ca");
        UNIT_ASSERT(s.TrySplitOn(pos, l, r));
        UNIT_ASSERT(l == "ab" && r == "abc");
        UNIT_ASSERT(s.TrySplitOn(pos, l, r, 2));
        UNIT_ASSERT(l == "ab" && r == "bc");
        UNIT_ASSERT(s.TrySplitAt(pos, l, r));
        UNIT_ASSERT(l == "ab" && r == "cabc");

        // out of range
        pos = 100500;
        UNIT_ASSERT(s.TrySplitOn(pos, l, r)); // still true
        UNIT_ASSERT(l == "abcabc" && r == "");
        l = "111";
        r = "222";
        UNIT_ASSERT(s.TrySplitAt(pos, l, r)); // still true
        UNIT_ASSERT(l == "abcabc" && r == "");

        // npos
        pos = s.find("missing");
        l = "111";
        r = "222";
        UNIT_ASSERT(!s.TrySplitOn(pos, l, r));
        UNIT_ASSERT(l == "111" && r == "222"); // not modified
        s.SplitOn(pos, l, r);
        UNIT_ASSERT(l == "abcabc" && r == ""); // modified

        l = "111";
        r = "222";
        UNIT_ASSERT(!s.TrySplitAt(pos, l, r));
        UNIT_ASSERT(l == "111" && r == "222"); // not modified
        s.SplitAt(pos, l, r);
        UNIT_ASSERT(l == "abcabc" && r == ""); // modified
    }

    template <class T>
    void PassByConstReference(const T& val) {
        // In https://st.yandex-team.ru/IGNIETFERRO-294 was assumed that `const char[]` types are compile time strings
        // and that CharTraits::Length may not be called for them. Unfortunately that is not true, `char[]` types
        // are easily converted to `const char[]` if they are passed to a function accepting `const T&`.
        UNIT_ASSERT(TStringBuf(val).size() == 5);
    }

    Y_UNIT_TEST(TestPassingArraysByConstReference) {
        char data[] = "Hello\0word";
        PassByConstReference(data);
    }
}

Y_UNIT_TEST_SUITE(TWtrBufTest) {
    Y_UNIT_TEST(TestConstExpr) {
        static constexpr TWtringBuf str1(u"qwe\0rty", 7);
        static constexpr TWtringBuf str2(str1.data(), str1.size());
        static constexpr TWtringBuf str3 = AsStringBuf(u"qwe\0rty");

        UNIT_ASSERT_VALUES_EQUAL(str1.size(), 7);

        UNIT_ASSERT_VALUES_EQUAL(str1, str2);
        UNIT_ASSERT_VALUES_EQUAL(str2, str3);
        UNIT_ASSERT_VALUES_EQUAL(str1, str3);

        static constexpr std::u16string_view view1(str1);
        UNIT_ASSERT_VALUES_EQUAL(str1, view1);
        static_assert(str1.data() == view1.data());
        static_assert(str1.size() == view1.size());

        static constexpr TWtringBuf str4(view1);
        UNIT_ASSERT_VALUES_EQUAL(str1, str4);
        static_assert(str1.data() == str4.data());
        static_assert(str1.size() == str4.size());
    }
}
