#include <balancer/kernel/memory/chunks.h>

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

#include <string>
#include <numeric>

using namespace NSrvKernel;

Y_UNIT_TEST_SUITE(TBalancerChunkListTest) {
    constexpr TStringBuf SAMPLE_VALUE = "abc";
    constexpr TStringBuf SAMPLE_VALUE2 = "1234567890";

    TChunkList BuildChunkList(const TVector<TStringBuf>& items) {
        TChunkList res;

        for (const auto item : items) {
            res.PushNonOwning(item);
        }

        return res;
    }

    TChunkList MakeString(const TStringBuf sb, const size_t chunkLen) {
        TChunkList lst;

        for (size_t i = 0; i < sb.size(); i += chunkLen) {
            lst.Push(TString(sb.substr(i, std::min(chunkLen, sb.size() - i))));
        }
        return lst;
    }

    Y_UNIT_TEST(TestNewChunkFromStringLiteral) {
        auto chunk = NSrvKernel::NewChunk("test");
        UNIT_ASSERT_EQUAL(chunk->AsStringBuf(), "test");
    }

    Y_UNIT_TEST(TestNewChunkFromBuffer) {
        TBuffer buf("xxx", 3);
        UNIT_ASSERT_EQUAL(NSrvKernel::TChunkList(std::move(buf)), "xxx");
        UNIT_ASSERT(buf.Empty());
    }

    Y_UNIT_TEST(TestChunkListFromEmptyChunks) {
        TChunkList lst{EmptyChunk()};
        UNIT_ASSERT(lst.Empty());
        lst.PushFront(EmptyChunk());
        UNIT_ASSERT(lst.Empty());
        lst.PushNonOwning("");
        UNIT_ASSERT(lst.Empty());
    }

    Y_UNIT_TEST(TestCompare) {
        for (size_t i = 1; i <= 8; ++i) {
            UNIT_ASSERT_EQUAL(MakeString("abcdef", i), "abcdef");
            UNIT_ASSERT_UNEQUAL(MakeString("abcdef", i), "hihklm");
        }

        UNIT_ASSERT_UNEQUAL(MakeString("abcdef", 1), "axcdef");
        UNIT_ASSERT_UNEQUAL(MakeString("abcdef", 2), "abxdef");
        UNIT_ASSERT_UNEQUAL(MakeString("abcdef", 4), "abcxef");
        UNIT_ASSERT_UNEQUAL(MakeString("abcdef", 4), "abcdxf");
    }

    Y_UNIT_TEST(TestCopyMove) {
        TChunkList lst;
        UNIT_ASSERT_EQUAL(lst, TChunkList{});
        lst.PushNonOwning(SAMPLE_VALUE);
        UNIT_ASSERT_EQUAL(lst, SAMPLE_VALUE);

        TChunkList lstCopy = lst.Copy();
        UNIT_ASSERT_EQUAL(lstCopy, lst);
        UNIT_ASSERT_EQUAL(lstCopy, SAMPLE_VALUE);

        TChunkList lstCopyAssign;
        UNIT_ASSERT_EQUAL(lstCopyAssign, TChunkList{});
        UNIT_ASSERT_UNEQUAL(lstCopyAssign, lst);
        lstCopyAssign = lstCopy.Copy();
        UNIT_ASSERT_EQUAL(lstCopyAssign, lst);
        UNIT_ASSERT_EQUAL(lstCopyAssign, lstCopy);
        UNIT_ASSERT_EQUAL(lstCopy, SAMPLE_VALUE);

        TChunkList lstMove = std::move(lstCopyAssign);
        UNIT_ASSERT_EQUAL(lstMove, lst);
        UNIT_ASSERT_EQUAL(lstMove, SAMPLE_VALUE);
        UNIT_ASSERT(lstCopyAssign.Empty());

        TChunkList lstMoveAssign;
        UNIT_ASSERT_EQUAL(lstMoveAssign, TChunkList{});
        UNIT_ASSERT_UNEQUAL(lstMoveAssign, lst);
        lstMoveAssign = std::move(lstMove);
        UNIT_ASSERT_EQUAL(lstMoveAssign, lst);
        UNIT_ASSERT_UNEQUAL(lstMoveAssign, lstMove);
        UNIT_ASSERT(lstMove.Empty());
        UNIT_ASSERT_EQUAL(lstMoveAssign, SAMPLE_VALUE);
    }

    Y_UNIT_TEST(TestIteratorsBasics) {
        TChunkList lst;
        UNIT_ASSERT_EQUAL(lst.begin(), lst.cbegin());
        UNIT_ASSERT_EQUAL(lst.begin(), lst.end());
        UNIT_ASSERT_EQUAL(lst.begin(), lst.cend());

        UNIT_ASSERT_EQUAL(lst.rbegin(), lst.crbegin());
        UNIT_ASSERT_EQUAL(lst.rbegin(), lst.rend());
        UNIT_ASSERT_EQUAL(lst.rbegin(), lst.crend());

        lst.PushNonOwning(SAMPLE_VALUE);
        UNIT_ASSERT_EQUAL(lst.begin(), lst.cbegin());
        UNIT_ASSERT_EQUAL(lst.end(), lst.cend());
        UNIT_ASSERT_UNEQUAL(lst.begin(), lst.end());
        UNIT_ASSERT_EQUAL((size_t)std::distance(lst.begin(), lst.end()), SAMPLE_VALUE.size());

        UNIT_ASSERT_EQUAL(lst.rbegin(), lst.crbegin());
        UNIT_ASSERT_EQUAL(lst.rend(), lst.crend());
        UNIT_ASSERT_UNEQUAL(lst.rbegin(), lst.rend());
        UNIT_ASSERT_EQUAL((size_t)std::distance(lst.rbegin(), lst.rend()), SAMPLE_VALUE.size());

        UNIT_ASSERT_EQUAL(lst.begin(), lst.rend().base());
        UNIT_ASSERT_EQUAL(lst.end(), lst.rbegin().base());
        UNIT_ASSERT_UNEQUAL(lst.begin(), lst.rbegin().base());
        UNIT_ASSERT_UNEQUAL(lst.end(), lst.rend().base());
    }

    Y_UNIT_TEST(TestIterators) {
        TVector<TStringBuf> toPush = {SAMPLE_VALUE, SAMPLE_VALUE2};
        const auto sizeSum = std::accumulate(toPush.cbegin(), toPush.cend(), (size_t)0,
                                             [](auto sum, auto sb) { return sum + sb.size(); });
        TChunkList lst;
        for (auto sb : toPush) {
            lst.PushNonOwning(sb);
        }
        UNIT_ASSERT_EQUAL(lst.ChunksCount(), toPush.size());
        UNIT_ASSERT_EQUAL(lst.size(), sizeSum);
        UNIT_ASSERT_EQUAL((size_t)std::distance(lst.begin(), lst.end()), sizeSum);
    }

    Y_UNIT_TEST(TestRValuePush) {
        auto lst = TChunkList{}.Push("abc").PushNonOwning("def");
        UNIT_ASSERT_EQUAL(lst, "abcdef");
        lst = TChunkList{}.PushNonOwning("123").Push("456");
        UNIT_ASSERT_EQUAL(lst, "123456");
    }

    Y_UNIT_TEST(TestEquals) {
        UNIT_ASSERT_EQUAL(BuildChunkList({}), "");
        UNIT_ASSERT_UNEQUAL(BuildChunkList({}), "a");

        UNIT_ASSERT_EQUAL(BuildChunkList({"", ""}), "");
        UNIT_ASSERT_UNEQUAL(BuildChunkList({"", ""}), "a");

        UNIT_ASSERT_EQUAL(BuildChunkList({"a"}), "a");
        UNIT_ASSERT_UNEQUAL(BuildChunkList({"a"}), "");
        UNIT_ASSERT_UNEQUAL(BuildChunkList({"a"}), "b");

        UNIT_ASSERT_EQUAL(BuildChunkList({"", "a", ""}), "a");
        UNIT_ASSERT_UNEQUAL(BuildChunkList({"", "a", ""}), "");
        UNIT_ASSERT_UNEQUAL(BuildChunkList({"", "a", ""}), "b");

        UNIT_ASSERT_EQUAL(BuildChunkList({"ab"}), "ab");
        UNIT_ASSERT_UNEQUAL(BuildChunkList({"ab"}), "aa");
        UNIT_ASSERT_UNEQUAL(BuildChunkList({"ab"}), "bb");

        UNIT_ASSERT_EQUAL(BuildChunkList({"", "ab", ""}), "ab");
        UNIT_ASSERT_UNEQUAL(BuildChunkList({"", "ab", ""}), "aa");
        UNIT_ASSERT_UNEQUAL(BuildChunkList({"", "ab", ""}), "bb");

        UNIT_ASSERT_EQUAL(BuildChunkList({"", "a", "", "b", ""}), "ab");
        UNIT_ASSERT_UNEQUAL(BuildChunkList({"", "a", "", "b", ""}), "aa");
        UNIT_ASSERT_UNEQUAL(BuildChunkList({"", "a", "", "b", ""}), "bb");
    }

    Y_UNIT_TEST(TestUnion) {
        auto chunk = Union(BuildChunkList({}));
        UNIT_ASSERT_EQUAL(chunk->AsStringBuf(), "");
        chunk = Union(BuildChunkList({""}));
        UNIT_ASSERT_EQUAL(chunk->AsStringBuf(), "");
        chunk = Union(BuildChunkList({"a"}));
        UNIT_ASSERT_EQUAL(chunk->AsStringBuf(), "a");
        chunk = Union(BuildChunkList({"a", "b"}));
        UNIT_ASSERT_EQUAL(chunk->AsStringBuf(), "ab");

        auto lst = BuildChunkList({"abc", "def", "ghi"});
        auto* first = lst.Front();
        chunk = Union(lst);
        UNIT_ASSERT_UNEQUAL(chunk.Get(), first);
        UNIT_ASSERT_EQUAL(chunk->AsStringBuf(), "abcdefghi");
    }

    Y_UNIT_TEST(TestUnionInplace) {
        auto lst = BuildChunkList({});
        auto* chunk = UnionInplace(lst);
        UNIT_ASSERT_EQUAL(chunk, nullptr);

        lst = BuildChunkList({"abc"});
        UNIT_ASSERT_EQUAL(lst, "abc");

        auto* first = lst.Front();
        chunk = UnionInplace(lst);
        UNIT_ASSERT_EQUAL(first, chunk);
        UNIT_ASSERT_EQUAL(chunk->AsStringBuf(), "abc");

        lst = BuildChunkList({"a", "b", "c"});
        UNIT_ASSERT_EQUAL(lst.ChunksCount(), 3);

        first = lst.Front();
        chunk = UnionInplace(lst);
        UNIT_ASSERT_UNEQUAL(first, chunk);
        UNIT_ASSERT_EQUAL(chunk->AsStringBuf(), "abc");
        UNIT_ASSERT_EQUAL(lst, "abc");
        UNIT_ASSERT_EQUAL(lst.ChunksCount(), 1);
    }

    Y_UNIT_TEST(ChunkLength) {
        TChunk::ResetDefaultLengthBase();
        UNIT_ASSERT(NewChunkReserve()->Length() > 1024);
        TChunk::SetDefaultLengthBase(1);
        UNIT_ASSERT(NewChunkReserve()->Length() == 1);
        TChunk::SetDefaultLengthBase(6 * 1024);
        UNIT_ASSERT(NewChunkReserve()->Length() > 5 * 1024 && NewChunkReserve()->Length() < 6 * 1024);
        TChunk::ResetDefaultLengthBase();
    }

    Y_UNIT_TEST(Cut) {
        {
            TChunkList lst;

            UNIT_ASSERT_EQUAL(ToString(lst.Cut(0)), "");
        }

        {
            TChunkList lst;

            UNIT_ASSERT_EQUAL(ToString(lst.Cut(100)), "");
        }

        {
            TChunkList lst{"AAABBB"};

            UNIT_ASSERT_EQUAL(ToString(lst.Cut(0)), "");
            UNIT_ASSERT_EQUAL(ToString(lst), "AAABBB");
        }

        {
            TChunkList lst{"AAABBB"};

            UNIT_ASSERT_EQUAL(ToString(lst.Cut(3)), "AAA");
            UNIT_ASSERT_EQUAL(ToString(lst), "BBB");
        }

        {
            TChunkList lst{"AAABBB"};

            UNIT_ASSERT_EQUAL(ToString(lst.Cut(10)), "AAABBB");
            UNIT_ASSERT_EQUAL(ToString(lst), "");
        }
    }

    Y_UNIT_TEST(SubList) {
        {
            TChunkList lst;

            UNIT_ASSERT_EQUAL(ToString(lst.SubList(0)), "");
        }

        {
            TChunkList lst;

            UNIT_ASSERT_EQUAL(ToString(lst.SubList(100)), "");
        }

        {
            TChunkList lst{"AAABBB"};

            UNIT_ASSERT_EQUAL(ToString(lst.SubList(0)), "");
            UNIT_ASSERT_EQUAL(ToString(lst), "AAABBB");
        }

        {
            TChunkList lst{"AAABBB"};

            UNIT_ASSERT_EQUAL(ToString(lst.SubList(3)), "AAA");
            UNIT_ASSERT_EQUAL(ToString(lst), "AAABBB");
        }

        {
            TChunkList lst{"AAABBB"};

            UNIT_ASSERT_EQUAL(ToString(lst.SubList(10)), "AAABBB");
            UNIT_ASSERT_EQUAL(ToString(lst), "AAABBB");
        }
    }
}
