#pragma once

#include "string.h"

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

#include <util/string/reverse.h>

template <typename CharT, size_t N>
struct TCharBuffer {
    CharT Data[N];
    //! copies characters from string to the internal buffer without any conversion
    //! @param s    a string that must contain only characters less than 0x7F
    explicit TCharBuffer(const char* s) {
        // copy all symbols including null terminated symbol
        for (size_t i = 0; i < N; ++i) {
            Data[i] = s[i];
        }
    }
    const CharT* GetData() const {
        return Data;
    }
};

template <>
struct TCharBuffer<char, 0> {
    const char* Data;
    //! stores pointer to string
    explicit TCharBuffer(const char* s)
        : Data(s)
    {
    }
    const char* GetData() const {
        return Data;
    }
};

#define DECLARE_AND_RETURN_BUFFER(s)             \
    static TCharBuffer<CharT, sizeof(s)> buf(s); \
    return buf.GetData();

//! @attention this class can process characters less than 0x7F only (the low half of ASCII table)
template <typename CharT>
struct TTestData {
    // words
    const CharT* str1() {
        DECLARE_AND_RETURN_BUFFER("str1");
    }
    const CharT* str2() {
        DECLARE_AND_RETURN_BUFFER("str2");
    }
    const CharT* str__________________________________________________1() {
        DECLARE_AND_RETURN_BUFFER("str                                                  1");
    }
    const CharT* str__________________________________________________2() {
        DECLARE_AND_RETURN_BUFFER("str                                                  2");
    }
    const CharT* one() {
        DECLARE_AND_RETURN_BUFFER("one");
    }
    const CharT* two() {
        DECLARE_AND_RETURN_BUFFER("two");
    }
    const CharT* three() {
        DECLARE_AND_RETURN_BUFFER("three");
    }
    const CharT* thrii() {
        DECLARE_AND_RETURN_BUFFER("thrii");
    }
    const CharT* four() {
        DECLARE_AND_RETURN_BUFFER("four");
    }
    const CharT* enotw_() {
        DECLARE_AND_RETURN_BUFFER("enotw ");
    }
    const CharT* foo() {
        DECLARE_AND_RETURN_BUFFER("foo");
    }
    const CharT* abcdef() {
        DECLARE_AND_RETURN_BUFFER("abcdef");
    }
    const CharT* abcdefg() {
        DECLARE_AND_RETURN_BUFFER("abcdefg");
    }
    const CharT* aba() {
        DECLARE_AND_RETURN_BUFFER("aba");
    }
    const CharT* hr() {
        DECLARE_AND_RETURN_BUFFER("hr");
    }
    const CharT* hrt() {
        DECLARE_AND_RETURN_BUFFER("hrt");
    }
    const CharT* thr() {
        DECLARE_AND_RETURN_BUFFER("thr");
    }
    const CharT* tw() {
        DECLARE_AND_RETURN_BUFFER("tw");
    }
    const CharT* ow() {
        DECLARE_AND_RETURN_BUFFER("ow");
    }
    const CharT* opq() {
        DECLARE_AND_RETURN_BUFFER("opq");
    }
    const CharT* xyz() {
        DECLARE_AND_RETURN_BUFFER("xyz");
    }
    const CharT* abc() {
        DECLARE_AND_RETURN_BUFFER("abc");
    }
    const CharT* abcd() {
        DECLARE_AND_RETURN_BUFFER("abcd");
    }
    const CharT* abcde() {
        DECLARE_AND_RETURN_BUFFER("abcde");
    }
    const CharT* abcc() {
        DECLARE_AND_RETURN_BUFFER("abcc");
    }
    const CharT* abce() {
        DECLARE_AND_RETURN_BUFFER("abce");
    }
    const CharT* qwe() {
        DECLARE_AND_RETURN_BUFFER("qwe");
    }
    const CharT* cd() {
        DECLARE_AND_RETURN_BUFFER("cd");
    }
    const CharT* cde() {
        DECLARE_AND_RETURN_BUFFER("cde");
    }
    const CharT* cdef() {
        DECLARE_AND_RETURN_BUFFER("cdef");
    }
    const CharT* cdefgh() {
        DECLARE_AND_RETURN_BUFFER("cdefgh");
    }
    const CharT* ehortw_() {
        DECLARE_AND_RETURN_BUFFER("ehortw ");
    }
    const CharT* fg() {
        DECLARE_AND_RETURN_BUFFER("fg");
    }
    const CharT* abcdefgh() {
        DECLARE_AND_RETURN_BUFFER("abcdefgh");
    }

    // phrases
    const CharT* Hello_World() {
        DECLARE_AND_RETURN_BUFFER("Hello World");
    }
    const CharT* This_is_test_string_for_string_calls() {
        DECLARE_AND_RETURN_BUFFER("This is test string for string calls");
    }
    const CharT* This_is_teis_test_string_st_string_for_string_calls() {
        DECLARE_AND_RETURN_BUFFER("This is teis test string st string for string calls");
    }
    const CharT* This_is_test_stis_test_string_for_stringring_for_string_calls() {
        DECLARE_AND_RETURN_BUFFER("This is test stis test string for stringring for string calls");
    }
    const CharT* allsThis_is_test_string_for_string_calls() {
        DECLARE_AND_RETURN_BUFFER("allsThis is test string for string calls");
    }
    const CharT* ng_for_string_callsThis_is_test_string_for_string_calls() {
        DECLARE_AND_RETURN_BUFFER("ng for string callsThis is test string for string calls");
    }
    const CharT* one_two_three_one_two_three() {
        DECLARE_AND_RETURN_BUFFER("one two three one two three");
    }
    const CharT* test_string_for_assign() {
        DECLARE_AND_RETURN_BUFFER("test string for assign");
    }
    const CharT* other_test_string() {
        DECLARE_AND_RETURN_BUFFER("other test string");
    }
    const CharT* This_This_is_tefor_string_calls() {
        DECLARE_AND_RETURN_BUFFER("This This is tefor string calls");
    }
    const CharT* This_This_is_test_string_for_string_calls() {
        DECLARE_AND_RETURN_BUFFER("This This is test string for string calls");
    }

    const CharT* _0123456x() {
        DECLARE_AND_RETURN_BUFFER("0123456x");
    }
    const CharT* _0123456xy() {
        DECLARE_AND_RETURN_BUFFER("0123456xy");
    }
    const CharT* _0123456xyz() {
        DECLARE_AND_RETURN_BUFFER("0123456xyz");
    }
    const CharT* _0123456xyzZ() {
        DECLARE_AND_RETURN_BUFFER("0123456xyzZ");
    }
    const CharT* _0123456xyzZ0() {
        DECLARE_AND_RETURN_BUFFER("0123456xyzZ0");
    }
    const CharT* abc0123456xyz() {
        DECLARE_AND_RETURN_BUFFER("abc0123456xyz");
    }
    const CharT* BCabc0123456xyz() {
        DECLARE_AND_RETURN_BUFFER("BCabc0123456xyz");
    }
    const CharT* qweBCabc0123456xyz() {
        DECLARE_AND_RETURN_BUFFER("qweBCabc0123456xyz");
    }
    const CharT* _1qweBCabc0123456xyz() {
        DECLARE_AND_RETURN_BUFFER("1qweBCabc0123456xyz");
    }
    const CharT* _01abc23456() {
        DECLARE_AND_RETURN_BUFFER("01abc23456");
    }
    const CharT* _01ABCabc23456() {
        DECLARE_AND_RETURN_BUFFER("01ABCabc23456");
    }
    const CharT* ABC() {
        DECLARE_AND_RETURN_BUFFER("ABC");
    }
    const CharT* ABCD() {
        DECLARE_AND_RETURN_BUFFER("ABCD");
    }
    const CharT* QWE() {
        DECLARE_AND_RETURN_BUFFER("QWE");
    }
    const CharT* XYZ() {
        DECLARE_AND_RETURN_BUFFER("XYZ");
    }
    const CharT* W01ABCabc23456() {
        DECLARE_AND_RETURN_BUFFER("W01ABCabc23456");
    }
    const CharT* abcd456() {
        DECLARE_AND_RETURN_BUFFER("abcd456");
    }
    const CharT* abcdABCD() {
        DECLARE_AND_RETURN_BUFFER("abcdABCD");
    }
    const CharT* abcdABC123() {
        DECLARE_AND_RETURN_BUFFER("abcdABC123");
    }
    const CharT* z123z123() {
        DECLARE_AND_RETURN_BUFFER("z123z123");
    }
    const CharT* ASDF1234QWER() {
        DECLARE_AND_RETURN_BUFFER("ASDF1234QWER");
    }
    const CharT* asdf1234qwer() {
        DECLARE_AND_RETURN_BUFFER("asdf1234qwer");
    }
    const CharT* asDF1234qWEr() {
        DECLARE_AND_RETURN_BUFFER("asDF1234qWEr");
    }
    const CharT* AsDF1234qWEr() {
        DECLARE_AND_RETURN_BUFFER("AsDF1234qWEr");
    }
    const CharT* Asdf1234qwer() {
        DECLARE_AND_RETURN_BUFFER("Asdf1234qwer");
    }
    const CharT* Asdf1234qwerWWWW() {
        DECLARE_AND_RETURN_BUFFER("Asdf1234qwerWWWW");
    }
    const CharT* Asdf() {
        DECLARE_AND_RETURN_BUFFER("Asdf");
    }
    const CharT* orig() {
        DECLARE_AND_RETURN_BUFFER("orig");
    }
    const CharT* fdfdsfds() {
        DECLARE_AND_RETURN_BUFFER("fdfdsfds");
    }

    // numbers
    const CharT* _0() {
        DECLARE_AND_RETURN_BUFFER("0");
    }
    const CharT* _00() {
        DECLARE_AND_RETURN_BUFFER("00");
    }
    const CharT* _0000000000() {
        DECLARE_AND_RETURN_BUFFER("0000000000");
    }
    const CharT* _00000() {
        DECLARE_AND_RETURN_BUFFER("00000");
    }
    const CharT* _0123() {
        DECLARE_AND_RETURN_BUFFER("0123");
    }
    const CharT* _01230123() {
        DECLARE_AND_RETURN_BUFFER("01230123");
    }
    const CharT* _01234() {
        DECLARE_AND_RETURN_BUFFER("01234");
    }
    const CharT* _0123401234() {
        DECLARE_AND_RETURN_BUFFER("0123401234");
    }
    const CharT* _012345() {
        DECLARE_AND_RETURN_BUFFER("012345");
    }
    const CharT* _0123456() {
        DECLARE_AND_RETURN_BUFFER("0123456");
    }
    const CharT* _1() {
        DECLARE_AND_RETURN_BUFFER("1");
    }
    const CharT* _11() {
        DECLARE_AND_RETURN_BUFFER("11");
    }
    const CharT* _1100000() {
        DECLARE_AND_RETURN_BUFFER("1100000");
    }
    const CharT* _110000034() {
        DECLARE_AND_RETURN_BUFFER("110000034");
    }
    const CharT* _12() {
        DECLARE_AND_RETURN_BUFFER("12");
    }
    const CharT* _123() {
        DECLARE_AND_RETURN_BUFFER("123");
    }
    const CharT* _1233321() {
        DECLARE_AND_RETURN_BUFFER("1233321");
    }
    const CharT* _1221() {
        DECLARE_AND_RETURN_BUFFER("1221");
    }
    const CharT* _1234123456() {
        DECLARE_AND_RETURN_BUFFER("1234123456");
    }
    const CharT* _12334444321() {
        DECLARE_AND_RETURN_BUFFER("12334444321");
    }
    const CharT* _123344544321() {
        DECLARE_AND_RETURN_BUFFER("123344544321");
    }
    const CharT* _1234567890123456789012345678901234567890() {
        DECLARE_AND_RETURN_BUFFER("1234567890123456789012345678901234567890");
    }
    const CharT* _1234() {
        DECLARE_AND_RETURN_BUFFER("1234");
    }
    const CharT* _12345() {
        DECLARE_AND_RETURN_BUFFER("12345");
    }
    const CharT* _123456() {
        DECLARE_AND_RETURN_BUFFER("123456");
    }
    const CharT* _1234567() {
        DECLARE_AND_RETURN_BUFFER("1234567");
    }
    const CharT* _1234561234() {
        DECLARE_AND_RETURN_BUFFER("1234561234");
    }
    const CharT* _12356() {
        DECLARE_AND_RETURN_BUFFER("12356");
    }
    const CharT* _1345656() {
        DECLARE_AND_RETURN_BUFFER("1345656");
    }
    const CharT* _15656() {
        DECLARE_AND_RETURN_BUFFER("15656");
    }
    const CharT* _17856() {
        DECLARE_AND_RETURN_BUFFER("17856");
    }
    const CharT* _1783456() {
        DECLARE_AND_RETURN_BUFFER("1783456");
    }
    const CharT* _2() {
        DECLARE_AND_RETURN_BUFFER("2");
    }
    const CharT* _2123456() {
        DECLARE_AND_RETURN_BUFFER("2123456");
    }
    const CharT* _23() {
        DECLARE_AND_RETURN_BUFFER("23");
    }
    const CharT* _2345() {
        DECLARE_AND_RETURN_BUFFER("2345");
    }
    const CharT* _3() {
        DECLARE_AND_RETURN_BUFFER("3");
    }
    const CharT* _345() {
        DECLARE_AND_RETURN_BUFFER("345");
    }
    const CharT* _3456() {
        DECLARE_AND_RETURN_BUFFER("3456");
    }
    const CharT* _333333() {
        DECLARE_AND_RETURN_BUFFER("333333");
    }
    const CharT* _389() {
        DECLARE_AND_RETURN_BUFFER("389");
    }
    const CharT* _4294967295() {
        DECLARE_AND_RETURN_BUFFER("4294967295");
    }
    const CharT* _4444() {
        DECLARE_AND_RETURN_BUFFER("4444");
    }
    const CharT* _5() {
        DECLARE_AND_RETURN_BUFFER("5");
    }
    const CharT* _6() {
        DECLARE_AND_RETURN_BUFFER("6");
    }
    const CharT* _6543210() {
        DECLARE_AND_RETURN_BUFFER("6543210");
    }
    const CharT* _7() {
        DECLARE_AND_RETURN_BUFFER("7");
    }
    const CharT* _78() {
        DECLARE_AND_RETURN_BUFFER("78");
    }
    const CharT* _2004_01_01() {
        DECLARE_AND_RETURN_BUFFER("2004-01-01");
    }
    const CharT* _1234562004_01_01() {
        DECLARE_AND_RETURN_BUFFER("1234562004-01-01");
    }
    const CharT* _0123456_12345() {
        DECLARE_AND_RETURN_BUFFER("0123456_12345");
    }

    // letters
    const CharT* a() {
        DECLARE_AND_RETURN_BUFFER("a");
    }
    const CharT* b() {
        DECLARE_AND_RETURN_BUFFER("b");
    }
    const CharT* c() {
        DECLARE_AND_RETURN_BUFFER("c");
    }
    const CharT* d() {
        DECLARE_AND_RETURN_BUFFER("d");
    }
    const CharT* e() {
        DECLARE_AND_RETURN_BUFFER("e");
    }
    const CharT* f() {
        DECLARE_AND_RETURN_BUFFER("f");
    }
    const CharT* h() {
        DECLARE_AND_RETURN_BUFFER("h");
    }
    const CharT* o() {
        DECLARE_AND_RETURN_BUFFER("o");
    }
    const CharT* p() {
        DECLARE_AND_RETURN_BUFFER("p");
    }
    const CharT* q() {
        DECLARE_AND_RETURN_BUFFER("q");
    }
    const CharT* r() {
        DECLARE_AND_RETURN_BUFFER("r");
    }
    const CharT* s() {
        DECLARE_AND_RETURN_BUFFER("s");
    }
    const CharT* t() {
        DECLARE_AND_RETURN_BUFFER("t");
    }
    const CharT* w() {
        DECLARE_AND_RETURN_BUFFER("w");
    }
    const CharT* x() {
        DECLARE_AND_RETURN_BUFFER("x");
    }
    const CharT* y() {
        DECLARE_AND_RETURN_BUFFER("y");
    }
    const CharT* z() {
        DECLARE_AND_RETURN_BUFFER("z");
    }
    const CharT* H() {
        DECLARE_AND_RETURN_BUFFER("H");
    }
    const CharT* I() {
        DECLARE_AND_RETURN_BUFFER("I");
    }
    const CharT* W() {
        DECLARE_AND_RETURN_BUFFER("W");
    }

    const CharT* Space() {
        DECLARE_AND_RETURN_BUFFER(" ");
    }
    const CharT* Empty() {
        DECLARE_AND_RETURN_BUFFER("");
    }

    size_t HashOf_0123456() {
        return 0;
    }
};

template <>
size_t TTestData<char>::HashOf_0123456() {
    return 1229863857ul;
}

template <>
size_t TTestData<wchar16>::HashOf_0123456() {
    return 2775195331ul;
}

template <class TStringType, typename TTestData>
class TStringTestImpl {
protected:
    using char_type = typename TStringType::char_type;
    using traits_type = typename TStringType::traits_type;

    TTestData Data;

public:
    void TestMaxSize() {
        const size_t badMaxVal = TStringType{}.max_size() + 1;

        TStringType s;
        UNIT_CHECK_GENERATED_EXCEPTION(s.reserve(badMaxVal), std::length_error);
    }

    void TestConstructors() {
        TStringType s0(nullptr);
        UNIT_ASSERT(s0.size() == 0);
        UNIT_ASSERT_EQUAL(s0, TStringType());

        TStringType s;
        TStringType s1(*Data._0());
        TStringType s2(Data._0());
        UNIT_ASSERT(s1 == s2);

        TStringType fromZero(0);
        UNIT_ASSERT_VALUES_EQUAL(fromZero.size(), 0u);

        TStringType fromChar(char_type('a'));
        UNIT_ASSERT_VALUES_EQUAL(fromChar.size(), 1u);
        UNIT_ASSERT_VALUES_EQUAL(fromChar[0], char_type('a'));

#ifndef TSTRING_IS_STD_STRING
        TStringType s3 = TStringType::Uninitialized(10);
        UNIT_ASSERT(s3.size() == 10);
#endif

        TStringType s4(Data._0123456(), 1, 3);
        UNIT_ASSERT(s4 == Data._123());

        TStringType s5(5, *Data._0());
        UNIT_ASSERT(s5 == Data._00000());

        TStringType s6(Data._0123456());
        UNIT_ASSERT(s6 == Data._0123456());
        TStringType s7(s6);
        UNIT_ASSERT(s7 == s6);
#ifndef TSTRING_IS_STD_STRING
        UNIT_ASSERT(s7.c_str() == s6.c_str());
#endif

        TStringType s8(s7, 1, 3);
        UNIT_ASSERT(s8 == Data._123());

        TStringType s9(*Data._1());
        UNIT_ASSERT(s9 == Data._1());

        TStringType s10(Reserve(100));
        UNIT_ASSERT(s10.empty());
        UNIT_ASSERT(s10.capacity() >= 100);
    }

    void TestReplace() {
        TStringType s(Data._0123456());
        UNIT_ASSERT(s.copy() == Data._0123456());

        // append family
        s.append(Data.x());
        UNIT_ASSERT(s == Data._0123456x());

#ifdef TSTRING_IS_STD_STRING
        s.append(Data.xyz() + 1, 1);
#else
        s.append(Data.xyz(), 1, 1);
#endif
        UNIT_ASSERT(s == Data._0123456xy());

        s.append(TStringType(Data.z()));
        UNIT_ASSERT(s == Data._0123456xyz());

        s.append(TStringType(Data.XYZ()), 2, 1);
        UNIT_ASSERT(s == Data._0123456xyzZ());

        s.append(*Data._0());
        UNIT_ASSERT(s == Data._0123456xyzZ0());

        // prepend family
        s = Data._0123456xyz();
        s.prepend(TStringType(Data.abc()));
        UNIT_ASSERT(s == Data.abc0123456xyz());

        s.prepend(TStringType(Data.ABC()), 1, 2);
        UNIT_ASSERT(s == Data.BCabc0123456xyz());

        s.prepend(Data.qwe());
        UNIT_ASSERT(s == Data.qweBCabc0123456xyz());

        s.prepend(*Data._1());
        UNIT_ASSERT(s == Data._1qweBCabc0123456xyz());

        // substr
        s = Data.abc0123456xyz();
        s = s.substr(3, 7);
        UNIT_ASSERT(s == Data._0123456());

        // insert family
        s.insert(2, Data.abc());
        UNIT_ASSERT(s == Data._01abc23456());

        s.insert(2, TStringType(Data.ABC()));
        UNIT_ASSERT(s == Data._01ABCabc23456());

        s.insert(0, TStringType(Data.QWE()), 1, 1);
        UNIT_ASSERT(s == Data.W01ABCabc23456());

        // replace family
        s = Data._01abc23456();
        s.replace(0, 7, Data.abcd());
        UNIT_ASSERT(s == Data.abcd456());

        s.replace(4, 3, TStringType(Data.ABCD()));
        UNIT_ASSERT(s == Data.abcdABCD());

        s.replace(7, 10, TStringType(Data._01234()), 1, 3);
        UNIT_ASSERT(s == Data.abcdABC123());
        UNIT_ASSERT(Data.abcdABC123() == s);

        // remove, erase
        s.remove(4);
        UNIT_ASSERT(s == Data.abcd());
        s.erase(3);
        UNIT_ASSERT(s == Data.abc());

        // Read access
        s = Data._012345();
        UNIT_ASSERT(s.at(1) == *Data._1());
        UNIT_ASSERT(s[1] == *Data._1());
        UNIT_ASSERT(s.at(s.size()) == 0);
        UNIT_ASSERT(s[s.size()] == 0);
    }

#ifndef TSTRING_IS_STD_STRING
    void TestRefCount() {
        using TStr = TStringType;

        struct TestStroka: public TStr {
            using TStr::TStr;
            // un-protect
            using TStr::RefCount;
        };

        TestStroka s1(Data.orig());
        UNIT_ASSERT_EQUAL(s1.RefCount() == 1, true);
        TestStroka s2(s1);
        UNIT_ASSERT_EQUAL(s1.RefCount() == 2, true);
        UNIT_ASSERT_EQUAL(s2.RefCount() == 2, true);
        UNIT_ASSERT_EQUAL(s1.c_str() == s2.c_str(), true); // the same pointer
        char_type* beg = s2.begin();
        UNIT_ASSERT_EQUAL(s1 == beg, true);
        UNIT_ASSERT_EQUAL(s1.RefCount() == 1, true);
        UNIT_ASSERT_EQUAL(s2.RefCount() == 1, true);
        UNIT_ASSERT_EQUAL(s1.c_str() == s2.c_str(), false);
    }
#endif

    //  Find family

    void TestFind() {
        const TStringType s(Data._0123456_12345());
        const TStringType s2(Data._0123());

        UNIT_ASSERT(s.find(Data._345()) == 3);
        UNIT_ASSERT(s.find(Data._345(), 5) == 10);

        UNIT_ASSERT(s.find(Data._345(), 20) == TStringType::npos);
        UNIT_ASSERT(s.find(*Data._3()) == 3);
        UNIT_ASSERT(s.find(TStringType(Data._345())) == 3);
        UNIT_ASSERT(s.find(TStringType(Data._345()), 2) == 3);

        UNIT_ASSERT(s.find_first_of(TStringType(Data._389())) == 3);
        UNIT_ASSERT(s.find_first_of(Data._389()) == 3);
        UNIT_ASSERT(s.find_first_of(Data._389(), s.size()) == TStringType::npos);
        UNIT_ASSERT(s.find_first_not_of(Data._123()) == 0);
        UNIT_ASSERT(s.find_first_of('6') == 6);
        UNIT_ASSERT(s.find_first_of('1', 2) == 8);
        UNIT_ASSERT(s.find_first_not_of('0') == 1);
        UNIT_ASSERT(s.find_first_not_of('1', 1) == 2);

        const TStringType rs = Data._0123401234();
        UNIT_ASSERT(rs.rfind(*Data._3()) == 8);

        const TStringType empty;
        UNIT_ASSERT(empty.find(empty) == 0);
        UNIT_ASSERT(s.find(empty, 0) == 0);
        UNIT_ASSERT(s.find(empty, 1) == 1);
        UNIT_ASSERT(s.find(empty, s.length()) == s.length());
        UNIT_ASSERT(s.find(empty, s.length() + 1) == TStringType::npos);

        UNIT_ASSERT(s.rfind(empty) == s.length());
        UNIT_ASSERT(empty.rfind(empty) == 0);
        UNIT_ASSERT(empty.rfind(s) == TStringType::npos);

        UNIT_ASSERT(s2.rfind(s) == TStringType::npos);
        UNIT_ASSERT(s.rfind(s2) == 0);
        UNIT_ASSERT(s.rfind(TStringType(Data._345())) == 10);
        UNIT_ASSERT(s.rfind(TStringType(Data._345()), 13) == 10);
        UNIT_ASSERT(s.rfind(TStringType(Data._345()), 10) == 10);
        UNIT_ASSERT(s.rfind(TStringType(Data._345()), 9) == 3);
        UNIT_ASSERT(s.rfind(TStringType(Data._345()), 6) == 3);
        UNIT_ASSERT(s.rfind(TStringType(Data._345()), 3) == 3);
        UNIT_ASSERT(s.rfind(TStringType(Data._345()), 2) == TStringType::npos);
    }

    void TestContains() {
        const TStringType s(Data._0123456_12345());
        const TStringType s2(Data._0123());

        UNIT_ASSERT(s.Contains(Data._345()));
        UNIT_ASSERT(!s2.Contains(Data._345()));

        UNIT_ASSERT(s.Contains('1'));
        UNIT_ASSERT(!s.Contains('*'));

        TStringType empty;
        UNIT_ASSERT(s.Contains(empty));
        UNIT_ASSERT(!empty.Contains(s));
        UNIT_ASSERT(empty.Contains(empty));
        UNIT_ASSERT(s.Contains(empty, s.length()));
    }

    // Operators

    void TestOperators() {
        TStringType s(Data._0123456());

        // operator +=
        s += TStringType(Data.x());
        UNIT_ASSERT(s == Data._0123456x());

        s += Data.y();
        UNIT_ASSERT(s == Data._0123456xy());

        s += *Data.z();
        UNIT_ASSERT(s == Data._0123456xyz());

        // operator +
        s = Data._0123456();
        s = s + TStringType(Data.x());
        UNIT_ASSERT(s == Data._0123456x());

        s = s + Data.y();
        UNIT_ASSERT(s == Data._0123456xy());

        s = s + *Data.z();
        UNIT_ASSERT(s == Data._0123456xyz());

        // operator !=
        s = Data._012345();
        UNIT_ASSERT(s != TStringType(Data.xyz()));
        UNIT_ASSERT(s != Data.xyz());
        UNIT_ASSERT(Data.xyz() != s);

        // operator <
        UNIT_ASSERT_EQUAL(s < TStringType(Data.xyz()), true);
        UNIT_ASSERT_EQUAL(s < Data.xyz(), true);
        UNIT_ASSERT_EQUAL(Data.xyz() < s, false);

        // operator <=
        UNIT_ASSERT_EQUAL(s <= TStringType(Data.xyz()), true);
        UNIT_ASSERT_EQUAL(s <= Data.xyz(), true);
        UNIT_ASSERT_EQUAL(Data.xyz() <= s, false);

        // operator >
        UNIT_ASSERT_EQUAL(s > TStringType(Data.xyz()), false);
        UNIT_ASSERT_EQUAL(s > Data.xyz(), false);
        UNIT_ASSERT_EQUAL(Data.xyz() > s, true);

        // operator >=
        UNIT_ASSERT_EQUAL(s >= TStringType(Data.xyz()), false);
        UNIT_ASSERT_EQUAL(s >= Data.xyz(), false);
        UNIT_ASSERT_EQUAL(Data.xyz() >= s, true);
    }

    void TestOperatorsCI() {
        TStringType s(Data.ABCD());
        UNIT_ASSERT(s > Data.abc0123456xyz());
        UNIT_ASSERT(s == Data.abcd());

        using TCIStringBuf = TBasicStringBuf<char_type, traits_type>;

        UNIT_ASSERT(s > TCIStringBuf(Data.abc0123456xyz()));
        UNIT_ASSERT(TCIStringBuf(Data.abc0123456xyz()) < s);
        UNIT_ASSERT(s == TCIStringBuf(Data.abcd()));
    }

    void TestMulOperators() {
        {
            TStringType s(Data._0());
            s *= 10;
            UNIT_ASSERT_EQUAL(s, TStringType(Data._0000000000()));
        }
        {
            TStringType s = TStringType(Data._0()) * 2;
            UNIT_ASSERT_EQUAL(s, TStringType(Data._00()));
        }
    }

    // Test any other functions

    void TestFuncs() {
        TStringType s(Data._0123456());
        UNIT_ASSERT(s.c_str() == s.data());

        // length()
        UNIT_ASSERT(s.length() == s.size());
        UNIT_ASSERT(s.length() == traits_type::length(s.data()));

        // is_null()
        TStringType s1(Data.Empty());
        UNIT_ASSERT(s1.is_null() == true);
        UNIT_ASSERT(s1.is_null() == s1.empty());
        UNIT_ASSERT(s1.is_null() == !s1);

        TStringType s2(s);
        UNIT_ASSERT(s2 == s);

        // reverse()
        ReverseInPlace(s2);
        UNIT_ASSERT(s2 == Data._6543210());

        // to_upper()
        s2 = Data.asdf1234qwer();
        s2.to_upper();
        UNIT_ASSERT(s2 == Data.ASDF1234QWER());

        // to_lower()
        s2.to_lower();
        UNIT_ASSERT(s2 == Data.asdf1234qwer());

        // to_title()
        s2 = Data.asDF1234qWEr();
        s2.to_title();
        UNIT_ASSERT(s2 == Data.Asdf1234qwer());

        s2 = Data.AsDF1234qWEr();
        s2.to_title();
        UNIT_ASSERT(s2 == Data.Asdf1234qwer());

        // Friend functions
        s2 = Data.asdf1234qwer();
        TStringType s3 = to_upper(s2);
        UNIT_ASSERT(s3 == Data.ASDF1234QWER());
        s3 = to_lower(s2);
        UNIT_ASSERT(s3 == Data.asdf1234qwer());
        s3 = to_title(s2);
        UNIT_ASSERT(s3 == Data.Asdf1234qwer());
        s2 = s3;

        // resize family
        s2.resize(s2.size()); // without length change
        UNIT_ASSERT(s2 == Data.Asdf1234qwer());

        s2.resize(s2.size() + 4, *Data.W());
        UNIT_ASSERT(s2 == Data.Asdf1234qwerWWWW());

        s2.resize(4);
        UNIT_ASSERT(s2 == Data.Asdf());

        // assign family
        s2 = Data.asdf1234qwer();
        s2.assign(s, 1, 3);
        UNIT_ASSERT(s2 == Data._123());

        s2.assign(Data._0123456(), 4);
        UNIT_ASSERT(s2 == Data._0123());

        s2.assign('1');
        UNIT_ASSERT(s2 == Data._1());

        s2.assign(Data._0123456());
        UNIT_ASSERT(s2 == Data._0123456());

        // hash()
        TStringType sS = s2; // type 'TStringType' is used as is

        ComputeHash(sS); /*size_t hash_val = sS.hash();

        try {
            //UNIT_ASSERT(hash_val == Data.HashOf_0123456());
        } catch (...) {
            Cerr << hash_val << Endl;
            throw;
        }*/

        s2.assign(Data._0123456(), 2, 2);
        UNIT_ASSERT(s2 == Data._23());

        //s2.reserve();

        TStringType s5(Data.abcde());
        s5.clear();
        UNIT_ASSERT(s5 == Data.Empty());
    }

    void TestUtils() {
        TStringType s;
        s = Data._01230123();
        TStringType from = Data._0();
        TStringType to = Data.z();

        SubstGlobal(s, from, to);
        UNIT_ASSERT(s == Data.z123z123());
    }

    void TestEmpty() {
        TStringType s;
        s = Data._2();
        s = TStringType(Data.fdfdsfds(), (size_t)0, (size_t)0);
        UNIT_ASSERT(s.empty());
    }

    void TestJoin() {
        UNIT_ASSERT_EQUAL(TStringType::Join(Data._12(), Data._3456()), Data._123456());
        UNIT_ASSERT_EQUAL(TStringType::Join(Data._12(), TStringType(Data._3456())), Data._123456());
        UNIT_ASSERT_EQUAL(TStringType::Join(TStringType(Data._12()), Data._3456()), Data._123456());
        UNIT_ASSERT_EQUAL(TStringType::Join(Data._12(), Data._345(), Data._6()), Data._123456());
        UNIT_ASSERT_EQUAL(TStringType::Join(Data._12(), TStringType(Data._345()), Data._6()), Data._123456());
        UNIT_ASSERT_EQUAL(TStringType::Join(TStringType(Data._12()), TStringType(Data._345()), Data._6()), Data._123456());
        UNIT_ASSERT_EQUAL(TStringType::Join(TStringType(Data.a()), Data.b(), TStringType(Data.cd()), TStringType(Data.e()), Data.fg(), TStringType(Data.h())), Data.abcdefgh());
        UNIT_ASSERT_EQUAL(TStringType::Join(TStringType(Data.a()), static_cast<typename TStringType::char_type>('b'), TStringType(Data.cd()), TStringType(Data.e()), Data.fg(), TStringType(Data.h())), Data.abcdefgh());
    }

    void TestCopy() {
        TStringType s(Data.abcd());
        TStringType c = s.copy();

        UNIT_ASSERT_EQUAL(s, c);
        UNIT_ASSERT(s.end() != c.end());
    }

    void TestStrCpy() {
        {
            TStringType s(Data.abcd());
            char_type data[5];

            data[4] = 1;

            s.strcpy(data, 4);

            UNIT_ASSERT_EQUAL(data[0], *Data.a());
            UNIT_ASSERT_EQUAL(data[1], *Data.b());
            UNIT_ASSERT_EQUAL(data[2], *Data.c());
            UNIT_ASSERT_EQUAL(data[3], 0);
            UNIT_ASSERT_EQUAL(data[4], 1);
        }

        {
            TStringType s(Data.abcd());
            char_type data[5];

            s.strcpy(data, 5);

            UNIT_ASSERT_EQUAL(data[0], *Data.a());
            UNIT_ASSERT_EQUAL(data[1], *Data.b());
            UNIT_ASSERT_EQUAL(data[2], *Data.c());
            UNIT_ASSERT_EQUAL(data[3], *Data.d());
            UNIT_ASSERT_EQUAL(data[4], 0);
        }
    }

    void TestPrefixSuffix() {
        const TStringType emptyStr;
        UNIT_ASSERT_EQUAL(emptyStr.StartsWith('x'), false);
        UNIT_ASSERT_EQUAL(emptyStr.EndsWith('x'), false);
        UNIT_ASSERT_EQUAL(emptyStr.StartsWith(0), false);
        UNIT_ASSERT_EQUAL(emptyStr.EndsWith(0), false);
        UNIT_ASSERT_EQUAL(emptyStr.StartsWith(emptyStr), true);
        UNIT_ASSERT_EQUAL(emptyStr.EndsWith(emptyStr), true);

        const char_type chars[] = {'h', 'e', 'l', 'l', 'o', 0};
        const TStringType str(chars);
        UNIT_ASSERT_EQUAL(str.StartsWith('h'), true);
        UNIT_ASSERT_EQUAL(str.StartsWith('o'), false);
        UNIT_ASSERT_EQUAL(str.EndsWith('o'), true);
        UNIT_ASSERT_EQUAL(str.EndsWith('h'), false);
        UNIT_ASSERT_EQUAL(str.StartsWith(emptyStr), true);
        UNIT_ASSERT_EQUAL(str.EndsWith(emptyStr), true);
    }

#ifndef TSTRING_IS_STD_STRING
    void TestCharRef() {
        const char_type abc[] = {'a', 'b', 'c', 0};
        const char_type bbc[] = {'b', 'b', 'c', 0};
        const char_type cbc[] = {'c', 'b', 'c', 0};

        TStringType s0 = abc;
        TStringType s1 = s0;

        UNIT_ASSERT(!s0.IsDetached());
        UNIT_ASSERT(!s1.IsDetached());

        /* Read access shouldn't detach. */
        UNIT_ASSERT_VALUES_EQUAL(s0[0], (ui8)'a');
        UNIT_ASSERT(!s0.IsDetached());
        UNIT_ASSERT(!s1.IsDetached());

        /* Writing should detach. */
        s1[0] = (ui8)'b';
        TStringType s2 = s0;
        s0[0] = (ui8)'c';

        UNIT_ASSERT_VALUES_EQUAL(s0, cbc);
        UNIT_ASSERT_VALUES_EQUAL(s1, bbc);
        UNIT_ASSERT_VALUES_EQUAL(s2, abc);
        UNIT_ASSERT(s0.IsDetached());
        UNIT_ASSERT(s1.IsDetached());
        UNIT_ASSERT(s2.IsDetached());

        /* Accessing null terminator is OK. Note that writing into it is UB. */
        UNIT_ASSERT_VALUES_EQUAL(s0[3], (ui8)'\0');
        UNIT_ASSERT_VALUES_EQUAL(s1[3], (ui8)'\0');
        UNIT_ASSERT_VALUES_EQUAL(s2[3], (ui8)'\0');

        /* Assignment one char reference to another results in modification of underlying character */
        {
            const char_type dark_eyed[] = {'d', 'a', 'r', 'k', '-', 'e', 'y', 'e', 'd', 0};
            const char_type red_eared[] = {'r', 'e', 'd', '-', 'e', 'a', 'r', 'e', 'd', 0};
            TStringType s0 = dark_eyed;
            TStringType s1 = TStringType::Uninitialized(s0.size());
            for (size_t i = 0; i < s1.size(); ++i) {
                const size_t j = (3u * (i + 1u) ^ 1u) % s0.size();
                s1[i] = s0[j];
            }
            UNIT_ASSERT_VALUES_EQUAL(s1, red_eared);
        }
    }
#endif

    void TestBack() {
        const char_type chars[] = {'f', 'o', 'o', 0};

        TStringType str = chars;
        const TStringType constStr = str;

        UNIT_ASSERT_VALUES_EQUAL(constStr.back(), (ui8)'o');
        UNIT_ASSERT_VALUES_EQUAL(str.back(), (ui8)'o');

        str.back() = 'r';
        UNIT_ASSERT_VALUES_EQUAL(constStr.back(), (ui8)'o');
        UNIT_ASSERT_VALUES_EQUAL(str.back(), (ui8)'r');
    }

    void TestFront() {
        const char_type chars[] = {'f', 'o', 'o', 0};

        TStringType str = chars;
        const TStringType constStr = str;

        UNIT_ASSERT_VALUES_EQUAL(constStr.front(), (ui8)'f');
        UNIT_ASSERT_VALUES_EQUAL(str.front(), (ui8)'f');

        str.front() = 'r';
        UNIT_ASSERT_VALUES_EQUAL(constStr.front(), (ui8)'f');
        UNIT_ASSERT_VALUES_EQUAL(str.front(), (ui8)'r');
    }

    void TestIterators() {
        const char_type chars[] = {'f', 'o', 0};

        TStringType str = chars;
        const TStringType constStr = str;

        typename TStringType::const_iterator itBegin = str.begin();
        typename TStringType::const_iterator itEnd = str.end();
        typename TStringType::const_iterator citBegin = constStr.begin();
        typename TStringType::const_iterator citEnd = constStr.end();

        UNIT_ASSERT_VALUES_EQUAL(*itBegin, (ui8)'f');
        UNIT_ASSERT_VALUES_EQUAL(*citBegin, (ui8)'f');

        str.front() = 'r';
        UNIT_ASSERT_VALUES_EQUAL(*itBegin, (ui8)'r');
        UNIT_ASSERT_VALUES_EQUAL(*citBegin, (ui8)'f');

        UNIT_ASSERT_VALUES_EQUAL(2, itEnd - itBegin);
        UNIT_ASSERT_VALUES_EQUAL(2, citEnd - citBegin);

        UNIT_ASSERT_VALUES_EQUAL(*(++itBegin), (ui8)'o');
        UNIT_ASSERT_VALUES_EQUAL(*(++citBegin), (ui8)'o');

        UNIT_ASSERT_VALUES_EQUAL(*(--itBegin), (ui8)'r');
        UNIT_ASSERT_VALUES_EQUAL(*(--citBegin), (ui8)'f');

        UNIT_ASSERT_VALUES_EQUAL(*(itBegin++), (ui8)'r');
        UNIT_ASSERT_VALUES_EQUAL(*(citBegin++), (ui8)'f');
        UNIT_ASSERT_VALUES_EQUAL(*itBegin, (ui8)'o');
        UNIT_ASSERT_VALUES_EQUAL(*citBegin, (ui8)'o');

        UNIT_ASSERT_VALUES_EQUAL(*(itBegin--), (ui8)'o');
        UNIT_ASSERT_VALUES_EQUAL(*(citBegin--), (ui8)'o');
        UNIT_ASSERT_VALUES_EQUAL(*itBegin, (ui8)'r');
        UNIT_ASSERT_VALUES_EQUAL(*citBegin, (ui8)'f');
    }

    void TestReverseIterators() {
        const char_type chars[] = {'f', 'o', 0};

        TStringType str = chars;
        const TStringType constStr = str;

        typename TStringType::reverse_iterator ritBegin = str.rbegin();
        typename TStringType::reverse_iterator ritEnd = str.rend();
        typename TStringType::const_reverse_iterator critBegin = constStr.rbegin();
        typename TStringType::const_reverse_iterator critEnd = constStr.rend();

        UNIT_ASSERT_VALUES_EQUAL(*ritBegin, (ui8)'o');
        UNIT_ASSERT_VALUES_EQUAL(*critBegin, (ui8)'o');

        str.back() = (ui8)'r';
        UNIT_ASSERT_VALUES_EQUAL(*ritBegin, (ui8)'r');
        UNIT_ASSERT_VALUES_EQUAL(*critBegin, (ui8)'o');

        UNIT_ASSERT_VALUES_EQUAL(2, ritEnd - ritBegin);
        UNIT_ASSERT_VALUES_EQUAL(2, critEnd - critBegin);

        UNIT_ASSERT_VALUES_EQUAL(*(++ritBegin), (ui8)'f');
        UNIT_ASSERT_VALUES_EQUAL(*(++critBegin), (ui8)'f');

        UNIT_ASSERT_VALUES_EQUAL(*(--ritBegin), (ui8)'r');
        UNIT_ASSERT_VALUES_EQUAL(*(--critBegin), (ui8)'o');

        UNIT_ASSERT_VALUES_EQUAL(*(ritBegin++), (ui8)'r');
        UNIT_ASSERT_VALUES_EQUAL(*(critBegin++), (ui8)'o');
        UNIT_ASSERT_VALUES_EQUAL(*ritBegin, (ui8)'f');
        UNIT_ASSERT_VALUES_EQUAL(*critBegin, (ui8)'f');

        UNIT_ASSERT_VALUES_EQUAL(*(ritBegin--), (ui8)'f');
        UNIT_ASSERT_VALUES_EQUAL(*(critBegin--), (ui8)'f');
        UNIT_ASSERT_VALUES_EQUAL(*ritBegin, (ui8)'r');
        UNIT_ASSERT_VALUES_EQUAL(*critBegin, (ui8)'o');

        *ritBegin = (ui8)'e';
        UNIT_ASSERT_VALUES_EQUAL(*ritBegin, (ui8)'e');
    }
};