/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 "paimon/common/memory/memory_segment_utils.h"

#include <cstdlib>
#include <string>

#include "gtest/gtest.h"
#include "paimon/testing/utils/testharness.h"

namespace paimon::test {
TEST(MemorySegmentUtilsTest, TestSetAndGetValue) {
    auto pool = GetDefaultPool();
    for (auto single_segment_size : {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 128}) {
        std::vector<MemorySegment> segments;
        segments.reserve(50);
        for (size_t i = 0; i < 50; i++) {
            segments.push_back(
                MemorySegment::Wrap(Bytes::AllocateBytes(single_segment_size, pool.get())));
        }
        int32_t offset = 0;
        MemorySegmentUtils::SetValue<int32_t>(&segments, offset, 233);
        ASSERT_EQ(233, MemorySegmentUtils::GetValue<int32_t>(segments, offset));

        offset += sizeof(int32_t);
        MemorySegmentUtils::SetValue<int64_t>(&segments, offset, 23333333333);
        ASSERT_EQ(23333333333, MemorySegmentUtils::GetValue<int64_t>(segments, offset));

        offset += sizeof(int64_t);
        MemorySegmentUtils::SetValue<float>(&segments, offset, 233.3);
        ASSERT_NEAR(233.3, MemorySegmentUtils::GetValue<float>(segments, offset), 0.001);

        offset += sizeof(float);
        MemorySegmentUtils::SetValue<double>(&segments, offset, 244.3);
        ASSERT_NEAR(244.3, MemorySegmentUtils::GetValue<double>(segments, offset), 0.001);

        offset += sizeof(double);
        MemorySegmentUtils::SetValue<int16_t>(&segments, offset, 5564);
        ASSERT_EQ(5564, MemorySegmentUtils::GetValue<int16_t>(segments, offset));

        offset += sizeof(int16_t);
        MemorySegmentUtils::SetValue<char>(&segments, offset, 123);
        ASSERT_EQ(123, MemorySegmentUtils::GetValue<char>(segments, offset));

        offset += sizeof(char);
        MemorySegmentUtils::SetValue<bool>(&segments, offset, true);
        ASSERT_EQ(true, MemorySegmentUtils::GetValue<bool>(segments, offset));

        ASSERT_EQ(233, MemorySegmentUtils::GetValue<int32_t>(segments, 0));
    }
}

TEST(MemorySegmentUtilsTest, TestCopyFromBytesAndGetBytes) {
    auto pool = GetDefaultPool();
    int32_t str_size = 1024;
    std::string test_string1, test_string2;
    test_string1.reserve(str_size);
    test_string2.reserve(str_size);
    for (int32_t j = 0; j < str_size; j++) {
        test_string1 += static_cast<char>(paimon::test::RandomNumber(0, 25) + 'a');
        test_string2 += static_cast<char>(paimon::test::RandomNumber(0, 25) + 'a');
    }
    test_string2 += test_string1;
    std::shared_ptr<Bytes> bytes1 = Bytes::AllocateBytes(test_string1, pool.get());
    std::shared_ptr<Bytes> bytes2 = Bytes::AllocateBytes(test_string2, pool.get());

    std::vector<MemorySegment> segs = {MemorySegment::AllocateHeapMemory(str_size, pool.get())};
    MemorySegmentUtils::CopyFromBytes(&segs, 0, *bytes1, 0, test_string1.size());
    PAIMON_UNIQUE_PTR<Bytes> bytes_serialize =
        MemorySegmentUtils::CopyToBytes(segs, 0, test_string1.size(), pool.get());
    auto bytes_get = MemorySegmentUtils::GetBytes(segs, /*base_offset=*/0,
                                                  /*size_in_bytes=*/str_size, pool.get());
    ASSERT_EQ(*bytes_get, *bytes1);
    std::string str_serialize = std::string(bytes_serialize->data(), bytes_serialize->size());
    ASSERT_EQ(test_string1, str_serialize);

    // test MultiSegments
    std::vector<MemorySegment> segs2 = {MemorySegment::AllocateHeapMemory(str_size, pool.get()),
                                        MemorySegment::AllocateHeapMemory(str_size, pool.get())};
    MemorySegmentUtils::CopyFromBytes(&segs2, 0, *bytes2, 0, test_string2.size());
    PAIMON_UNIQUE_PTR<Bytes> bytes_serialize2 =
        MemorySegmentUtils::CopyToBytes(segs2, 0, test_string2.size(), pool.get());
    std::string str_serialize2 = std::string(bytes_serialize2->data(), bytes_serialize2->size());
    ASSERT_EQ(test_string2, str_serialize2);

    std::shared_ptr<Bytes> bytes_serialize3 = MemorySegmentUtils::GetBytes(
        segs2, test_string2.size() - test_string1.size(), test_string1.size(), pool.get());
    std::string str_serialize3 = std::string(bytes_serialize3->data(), bytes_serialize3->size());
    ASSERT_EQ(test_string1, str_serialize3);
}

TEST(MemorySegmentUtilsTest, TestCopyToUnsafe) {
    auto pool = GetDefaultPool();
    int32_t str_size = 1024;
    std::string test_string1, test_string2;
    test_string1.reserve(str_size);
    test_string2.reserve(str_size);
    for (int32_t j = 0; j < str_size; j++) {
        test_string1 += static_cast<char>(paimon::test::RandomNumber(0, 25) + 'a');
        test_string2 += static_cast<char>(paimon::test::RandomNumber(0, 25) + 'a');
    }
    test_string2 += test_string1;
    std::shared_ptr<Bytes> bytes1 = Bytes::AllocateBytes(test_string1, pool.get());
    std::shared_ptr<Bytes> bytes2 = Bytes::AllocateBytes(test_string2, pool.get());

    std::vector<MemorySegment> segs = {MemorySegment::AllocateHeapMemory(str_size, pool.get())};
    MemorySegmentUtils::CopyFromBytes(&segs, 0, *bytes1, 0, test_string1.size());
    std::shared_ptr<Bytes> bytes_serialize = Bytes::AllocateBytes(test_string1.size(), pool.get());
    MemorySegmentUtils::CopyToUnsafe(segs, 0, bytes_serialize->data(), test_string1.size());
    ASSERT_EQ(test_string1, std::string(bytes_serialize->data(), bytes_serialize->size()));

    // test MultiSegments
    std::vector<MemorySegment> segs2 = {MemorySegment::AllocateHeapMemory(str_size, pool.get()),
                                        MemorySegment::AllocateHeapMemory(str_size, pool.get())};
    MemorySegmentUtils::CopyFromBytes(&segs2, 0, *bytes2, 0, test_string2.size());
    std::shared_ptr<Bytes> bytes_serialize2 = Bytes::AllocateBytes(test_string1.size(), pool.get());
    MemorySegmentUtils::CopyToUnsafe(segs2, test_string2.size() - test_string1.size(),
                                     bytes_serialize2->data(), test_string1.size());
    ASSERT_EQ(test_string1, std::string(bytes_serialize2->data(), bytes_serialize2->size()));
}

TEST(MemorySegmentUtilsTest, TestSetAndUnSet) {
    auto pool = GetDefaultPool();
    int32_t str_size = 1024;
    std::string test_string1, test_string2;
    test_string1.reserve(str_size);
    test_string2.reserve(str_size);
    for (int32_t j = 0; j < str_size; j++) {
        test_string1 += static_cast<char>(paimon::test::RandomNumber(0, 25) + 'a');
        test_string2 += static_cast<char>(paimon::test::RandomNumber(0, 25) + 'a');
    }
    test_string2 += test_string1;
    std::shared_ptr<Bytes> bytes2 = Bytes::AllocateBytes(test_string2, pool.get());

    std::vector<MemorySegment> segs = {MemorySegment::AllocateHeapMemory(str_size, pool.get()),
                                       MemorySegment::AllocateHeapMemory(str_size, pool.get())};
    MemorySegmentUtils::CopyFromBytes(&segs, 0, *bytes2, 0, test_string2.size());
    int32_t index = paimon::test::RandomNumber(0, str_size - 1);
    MemorySegmentUtils::BitUnSet(&segs, str_size, index);
    ASSERT_FALSE(MemorySegmentUtils::BitGet(segs, str_size, index));
    MemorySegmentUtils::BitSet(&segs, str_size, index);
    ASSERT_TRUE(MemorySegmentUtils::BitGet(segs, str_size, index));
    MemorySegmentUtils::BitSet(&segs[0], /*base_offset=*/0, index);
    ASSERT_TRUE(MemorySegmentUtils::BitGet(segs[0], /*base_offset=*/0, index));
    MemorySegmentUtils::BitUnSet(&segs[0], /*base_offset=*/0, index);
    ASSERT_FALSE(MemorySegmentUtils::BitGet(segs[0], /*base_offset=*/0, index));
}

TEST(MemorySegmentUtilsTest, TestBitSetAndUnSetSingleSegment) {
    auto pool = GetDefaultPool();
    int32_t segment_size = 128;
    std::vector<MemorySegment> segs = {MemorySegment::AllocateHeapMemory(segment_size, pool.get())};

    // initially all bits should be 0 after allocation
    for (int32_t i = 0; i < 16; i++) {
        ASSERT_FALSE(MemorySegmentUtils::BitGet(segs, /*base_offset=*/0, i));
    }

    // set bits at various indices and verify
    MemorySegmentUtils::BitSet(&segs, /*base_offset=*/0, /*index=*/0);
    ASSERT_TRUE(MemorySegmentUtils::BitGet(segs, /*base_offset=*/0, 0));

    MemorySegmentUtils::BitSet(&segs, /*base_offset=*/0, /*index=*/7);
    ASSERT_TRUE(MemorySegmentUtils::BitGet(segs, /*base_offset=*/0, 7));

    MemorySegmentUtils::BitSet(&segs, /*base_offset=*/0, /*index=*/15);
    ASSERT_TRUE(MemorySegmentUtils::BitGet(segs, /*base_offset=*/0, 15));

    // unset and verify
    MemorySegmentUtils::BitUnSet(&segs, /*base_offset=*/0, /*index=*/0);
    ASSERT_FALSE(MemorySegmentUtils::BitGet(segs, /*base_offset=*/0, 0));

    MemorySegmentUtils::BitUnSet(&segs, /*base_offset=*/0, /*index=*/7);
    ASSERT_FALSE(MemorySegmentUtils::BitGet(segs, /*base_offset=*/0, 7));

    // bit 15 should still be set
    ASSERT_TRUE(MemorySegmentUtils::BitGet(segs, /*base_offset=*/0, 15));

    MemorySegmentUtils::BitUnSet(&segs, /*base_offset=*/0, /*index=*/15);
    ASSERT_FALSE(MemorySegmentUtils::BitGet(segs, /*base_offset=*/0, 15));

    // test with non-zero base_offset
    int32_t base_offset = 10;
    MemorySegmentUtils::BitSet(&segs, base_offset, /*index=*/3);
    ASSERT_TRUE(MemorySegmentUtils::BitGet(segs, base_offset, 3));
    ASSERT_FALSE(MemorySegmentUtils::BitGet(segs, base_offset, 2));

    MemorySegmentUtils::BitUnSet(&segs, base_offset, /*index=*/3);
    ASSERT_FALSE(MemorySegmentUtils::BitGet(segs, base_offset, 3));
}

TEST(MemorySegmentUtilsTest, TestCopyMultiSegmentsFromBytes) {
    auto pool = GetDefaultPool();
    std::shared_ptr<Bytes> bytes = Bytes::AllocateBytes("abcdef", pool.get());
    int32_t segment_size = 10;
    std::vector<MemorySegment> segs = {MemorySegment::AllocateHeapMemory(segment_size, pool.get()),
                                       MemorySegment::AllocateHeapMemory(segment_size, pool.get())};
    {
        MemorySegmentUtils::CopyMultiSegmentsFromBytes(&segs, /*offset=*/3, *bytes,
                                                       /*bytes_offset=*/0,
                                                       /*num_bytes=*/bytes->size());
        auto result_bytes =
            MemorySegmentUtils::CopyToBytes(segs, /*offset=*/3,
                                            /*num_bytes=*/bytes->size(), pool.get());
        ASSERT_EQ(*bytes, *result_bytes);
    }
    {
        MemorySegmentUtils::CopyMultiSegmentsFromBytes(&segs, /*offset=*/12, *bytes,
                                                       /*bytes_offset=*/0,
                                                       /*num_bytes=*/bytes->size());
        auto result_bytes =
            MemorySegmentUtils::CopyToBytes(segs, /*offset=*/12,
                                            /*num_bytes=*/bytes->size(), pool.get());
        ASSERT_EQ(*bytes, *result_bytes);
    }
}

TEST(MemorySegmentUtilsTest, TestCopyToStream) {
    auto pool = GetDefaultPool();
    int32_t segment_size = 3;
    std::shared_ptr<Bytes> bytes1 = Bytes::AllocateBytes("abc", pool.get());
    std::shared_ptr<Bytes> bytes2 = Bytes::AllocateBytes("def", pool.get());
    std::vector<MemorySegment> segs = {MemorySegment::Wrap(bytes1), MemorySegment::Wrap(bytes2)};
    {
        MemorySegmentOutputStream out(/*segment_size=*/segment_size, pool);
        ASSERT_OK(MemorySegmentUtils::CopyToStream(segs, /*offset=*/0,
                                                   /*size_in_bytes=*/segment_size * 2, &out));
        std::shared_ptr<Bytes> expected_bytes = Bytes::AllocateBytes("abcdef", pool.get());
        auto bytes =
            MemorySegmentUtils::CopyToBytes(out.Segments(), 0, out.CurrentSize(), pool.get());
        ASSERT_EQ(*expected_bytes, *bytes);
    }
    {
        MemorySegmentOutputStream out(/*segment_size=*/segment_size, pool);
        ASSERT_OK(MemorySegmentUtils::CopyToStream(segs, /*offset=*/2, /*size_in_bytes=*/4, &out));
        std::shared_ptr<Bytes> expected_bytes = Bytes::AllocateBytes("cdef", pool.get());
        auto bytes =
            MemorySegmentUtils::CopyToBytes(out.Segments(), 0, out.CurrentSize(), pool.get());
        ASSERT_EQ(*expected_bytes, *bytes);
    }
    {
        MemorySegmentOutputStream out(/*segment_size=*/segment_size, pool);
        ASSERT_OK(MemorySegmentUtils::CopyToStream(segs, /*offset=*/4, /*size_in_bytes=*/2, &out));
        std::shared_ptr<Bytes> expected_bytes = Bytes::AllocateBytes("ef", pool.get());
        auto bytes =
            MemorySegmentUtils::CopyToBytes(out.Segments(), 0, out.CurrentSize(), pool.get());
        ASSERT_EQ(*expected_bytes, *bytes);
    }
}

TEST(MemorySegmentUtilsTest, TestFind) {
    auto pool = GetDefaultPool();
    std::shared_ptr<Bytes> bytes1 = Bytes::AllocateBytes("abc", pool.get());
    std::shared_ptr<Bytes> bytes2 = Bytes::AllocateBytes("def", pool.get());
    std::shared_ptr<Bytes> bytes3 = Bytes::AllocateBytes("adef", pool.get());
    std::shared_ptr<Bytes> bytes4 = Bytes::AllocateBytes("ghi", pool.get());
    std::vector<MemorySegment> segs1 = {MemorySegment::Wrap(bytes1),
                                        MemorySegment::Wrap(bytes2)};  // abcdef
    std::vector<MemorySegment> segs2 = {MemorySegment::Wrap(bytes3),
                                        MemorySegment::Wrap(bytes4)};  // adefghi
    // find ""
    ASSERT_EQ(1, MemorySegmentUtils::Find(segs1, /*offset1=*/1, /*num_bytes1=*/5, segs2,
                                          /*offset2=*/0, /*num_bytes2=*/0));
    // find "def"
    ASSERT_EQ(3, MemorySegmentUtils::Find(segs1, /*offset1=*/0, /*num_bytes1=*/6, segs2,
                                          /*offset2=*/1, /*num_bytes2=*/3));
    // find "defg"
    ASSERT_EQ(-1, MemorySegmentUtils::Find(segs1, /*offset1=*/0, /*num_bytes1=*/6, segs2,
                                           /*offset2=*/1, /*num_bytes2=*/4));
    // find "de" in "abc" of segs1
    ASSERT_EQ(-1, MemorySegmentUtils::Find(segs1, /*offset1=*/0, /*num_bytes1=*/3, segs2,
                                           /*offset2=*/1, /*num_bytes2=*/2));
    // find "a" in "abc" of segs1
    ASSERT_EQ(0, MemorySegmentUtils::Find(segs1, /*offset1=*/0, /*num_bytes1=*/3, segs2,
                                          /*offset2=*/0, /*num_bytes2=*/1));
}

}  // namespace paimon::test
