/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */

// SPDX-FileCopyrightText: 2021 - 2025 Kohei Yoshida
//
// SPDX-License-Identifier: MIT

/**
 * This test is to be run with valgrind, to ensure no memory leak occurs.
 */
void mtv_test_managed_block()
{
    MDDS_TEST_FUNC_SCOPE;
    {
        mtv_type db(1);
        db.set(0, new muser_cell(1.0));
        const muser_cell* p = db.get<muser_cell*>(0);
        TEST_ASSERT(p->value == 1.0);
        db.set(0, new muser_cell(2.0)); // overwrite.
        p = db.get<muser_cell*>(0);
        TEST_ASSERT(p->value == 2.0);
    }

    {
        // Overwrite with empty cells.
        mtv_type db(3);

        // Empty the upper part.
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        db.set(2, new muser_cell(3.0));
        db.set_empty(0, 0);

        // Empty the lower part.
        db.set(0, new muser_cell(4.0));
        db.set_empty(2, 2);

        // Empty the middle part.
        db.set(2, new muser_cell(5.0));
        db.set_empty(1, 1);
    }

    {
        // More overwrite with empty cells.
        mtv_type db(3);
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        db.set(2, 3.0);
        db.set_empty(1, 2);

        db.set(0, std::string("foo"));
        db.set(1, new muser_cell(4.0));
        db.set(2, new muser_cell(5.0));
        db.set_empty(0, 1);

        db.set(0, new muser_cell(6.0));
        db.set(1, static_cast<uint64_t>(12));
        db.set_empty(0, 2);
    }

    {
        // Another case for set_empty().
        mtv_type db(5);
        db.set(0, 1.2);
        db.set(1, new muser_cell(2.0));
        db.set(2, new muser_cell(3.0));
        db.set(3, new muser_cell(4.0));
        db.set(4, new muser_cell(5.0));
        db.set_empty(2, 4);

        db.set(2, new muser_cell(3.0));
        db.set(3, new muser_cell(4.0));
        db.set(4, new muser_cell(5.0));
        db.set_empty(1, 2);

        db.set(1, new muser_cell(2.0));
        db.set(2, new muser_cell(3.0));
        db.set_empty(2, 3);
    }

    {
        // Test for cloning.
        mtv_type db(3);
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        db.set(2, new muser_cell(3.0));

        // swap
        mtv_type db2;
        db2.swap(db);
        TEST_ASSERT(db.empty());
        TEST_ASSERT(db2.get<muser_cell*>(0)->value == 1.0);
        TEST_ASSERT(db2.get<muser_cell*>(1)->value == 2.0);
        TEST_ASSERT(db2.get<muser_cell*>(2)->value == 3.0);
        db.swap(db2);
        TEST_ASSERT(db2.empty());
        TEST_ASSERT(db.get<muser_cell*>(0)->value == 1.0);
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 2.0);
        TEST_ASSERT(db.get<muser_cell*>(2)->value == 3.0);

        // copy constructor
        mtv_type db_copied(db);
        TEST_ASSERT(db_copied.size() == 3);
        TEST_ASSERT(db_copied.get<muser_cell*>(0)->value == 1.0);
        TEST_ASSERT(db_copied.get<muser_cell*>(1)->value == 2.0);
        TEST_ASSERT(db_copied.get<muser_cell*>(2)->value == 3.0);

        // Assignment.
        mtv_type db_assigned = db;
        TEST_ASSERT(db_assigned.size() == 3);
        TEST_ASSERT(db_assigned.get<muser_cell*>(0)->value == 1.0);
        TEST_ASSERT(db_assigned.get<muser_cell*>(1)->value == 2.0);
        TEST_ASSERT(db_assigned.get<muser_cell*>(2)->value == 3.0);
    }

    {
        // Resize and clear
        mtv_type db(3);
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        db.set(2, new muser_cell(3.0));
        db.resize(1);
        TEST_ASSERT(db.get<muser_cell*>(0)->value == 1.0);

        db.clear();
    }

    {
        // Overwrite with a cell of different type.
        mtv_type db(3);
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        db.set(2, new muser_cell(3.0));
        db.set(1, 4.5);
    }

    {
        // Erase (single block)
        mtv_type db(3);

        // Erase the whole thing.
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        db.set(2, new muser_cell(3.0));
        db.erase(0, 2);
        TEST_ASSERT(db.empty());

        // Erase top.
        db.resize(3);
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        db.set(2, new muser_cell(3.0));
        db.erase(0, 1);
        TEST_ASSERT(db.size() == 1);

        // Erase bottom.
        db.resize(3);
        db.set(1, new muser_cell(4.0));
        db.set(2, new muser_cell(5.0));
        db.erase(1, 2);
        TEST_ASSERT(db.size() == 1);

        // Erase middle.
        db.resize(3);
        db.set(1, new muser_cell(4.0));
        db.set(2, new muser_cell(5.0));
        db.erase(1, 1);
        TEST_ASSERT(db.size() == 2);
    }

    {
        // Erase (single block with preceding block)
        mtv_type db(4);

        // Erase the whole thing.
        db.set(0, 1.1);
        db.set(1, new muser_cell(1.0));
        db.set(2, new muser_cell(2.0));
        db.set(3, new muser_cell(3.0));
        db.erase(1, 3);
        TEST_ASSERT(db.size() == 1);

        // Erase top.
        db.resize(4);
        db.set(1, new muser_cell(1.0));
        db.set(2, new muser_cell(2.0));
        db.set(3, new muser_cell(3.0));
        db.erase(1, 2);
        TEST_ASSERT(db.size() == 2);

        // Erase bottom.
        db.resize(4);
        db.set(2, new muser_cell(4.0));
        db.set(3, new muser_cell(5.0));
        db.erase(2, 3);
        TEST_ASSERT(db.size() == 2);

        // Erase middle.
        db.resize(4);
        db.set(2, new muser_cell(4.0));
        db.set(3, new muser_cell(5.0));
        db.erase(2, 2);
        TEST_ASSERT(db.size() == 3);
    }

    {
        // Erase (multi-block 1)
        mtv_type db(6);
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        db.set(2, new muser_cell(3.0));
        db.set(3, 4.1);
        db.set(4, 4.2);
        db.set(5, 4.3);
        db.erase(1, 4);
    }

    {
        // Erase (multi-block 2)
        mtv_type db(6);
        db.set(0, 4.1);
        db.set(1, 4.2);
        db.set(2, 4.3);
        db.set(3, new muser_cell(5.0));
        db.set(4, new muser_cell(6.0));
        db.set(5, new muser_cell(7.0));
        db.erase(1, 4);
    }

    {
        // Erase (multi-block 3)
        mtv_type db(6);
        db.set(0, 1.0);
        db.set(1, 2.0);
        db.set(2, new muser_cell(3.0));
        db.set(3, new muser_cell(4.0));
        db.set(4, 5.0);
        db.set(5, 6.0);
        db.erase(1, 4);
    }

    {
        // Insert into the middle of block.  This one shouldn't overwrite any
        // cells, but just to be safe...
        mtv_type db(2);
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        db.insert_empty(1, 2);
        TEST_ASSERT(db.size() == 4);
        TEST_ASSERT(db.get<muser_cell*>(0)->value == 1.0);
        TEST_ASSERT(db.get<muser_cell*>(3)->value == 2.0);
    }

    {
        // set_cells (simple overwrite)
        mtv_type db(2);
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));

        std::vector<muser_cell*> vals;
        vals.push_back(new muser_cell(3.0));
        vals.push_back(new muser_cell(4.0));
        db.set(0, vals.begin(), vals.end());
        TEST_ASSERT(db.get<muser_cell*>(0)->value == 3.0);
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 4.0);
    }

    {
        // set_cells (overwrite upper)
        mtv_type db(2);
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        double vals[] = {3.0};
        const double* p = &vals[0];
        db.set(0, p, p + 1);
        TEST_ASSERT(db.get<double>(0) == 3.0);
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 2.0);
    }

    {
        // set_cells (overwrite lower)
        mtv_type db(2);
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        double vals[] = {3.0};
        const double* p = &vals[0];
        db.set(1, p, p + 1);
        TEST_ASSERT(db.get<muser_cell*>(0)->value == 1.0);
        TEST_ASSERT(db.get<double>(1) == 3.0);
    }

    {
        // set_cells (overwrite middle)
        mtv_type db(4);
        db.set(0, 1.1);
        db.set(1, new muser_cell(1.0));
        db.set(2, new muser_cell(2.0));
        db.set(3, new muser_cell(3.0));
        double vals[] = {4.0};
        const double* p = &vals[0];
        db.set(2, p, p + 1);
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 1.0);
        TEST_ASSERT(db.get<double>(2) == 4.0);
        TEST_ASSERT(db.get<muser_cell*>(3)->value == 3.0);
    }
    {
        // insert_empty() to split the block into two.
        mtv_type db(3);
        db.set(0, 1.1);
        db.set(1, new muser_cell(1.0));
        db.set(2, new muser_cell(2.0));
        db.insert_empty(2, 2);
        TEST_ASSERT(db.size() == 5);
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 1.0);
        TEST_ASSERT(db.get<muser_cell*>(4)->value == 2.0);
    }

    {
        // erase() to merge two blocks.
        mtv_type db(4);
        db.set(0, 1.1);
        db.set(1, new muser_cell(1.0));
        db.set(2, static_cast<uint64_t>(2));
        db.set(3, new muser_cell(3.0));
        TEST_ASSERT(db.block_size() == 4);
        TEST_ASSERT(db.size() == 4);

        db.erase(2, 2);
        TEST_ASSERT(db.block_size() == 2);
        TEST_ASSERT(db.size() == 3);
        TEST_ASSERT(db.get<double>(0) == 1.1);
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 1.0);
        TEST_ASSERT(db.get<muser_cell*>(2)->value == 3.0);
    }

    {
        // set_cells() across multiple blocks.
        mtv_type db(5);
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        db.set(2, 1.2);
        db.set(3, new muser_cell(3.0));
        db.set(4, new muser_cell(4.0));
        uint64_t vals[] = {5, 6, 7};
        const uint64_t* p = &vals[0];
        db.set(1, p, p + 3);
    }

    {
        // set_cells() across multiple blocks, part 2.
        mtv_type db(6);
        db.set(0, static_cast<uint64_t>(12));
        db.set(1, new muser_cell(1.0));
        db.set(2, new muser_cell(2.0));
        db.set(3, 1.2);
        db.set(4, new muser_cell(3.0));
        db.set(5, new muser_cell(4.0));
        TEST_ASSERT(db.block_size() == 4);

        std::vector<muser_cell*> vals;
        vals.push_back(new muser_cell(5.0));
        vals.push_back(new muser_cell(6.0));
        vals.push_back(new muser_cell(7.0));
        db.set(2, vals.begin(), vals.end());
        TEST_ASSERT(db.block_size() == 2);
    }

    {
        // set_cell() to merge 3 blocks.
        mtv_type db(6);
        db.set(0, static_cast<uint64_t>(12));
        db.set(1, new muser_cell(1.0));
        db.set(2, new muser_cell(2.0));
        db.set(3, 1.2);
        db.set(4, new muser_cell(3.0));
        db.set(5, new muser_cell(4.0));
        TEST_ASSERT(db.block_size() == 4);
        TEST_ASSERT(db.get<uint64_t>(0) == 12);
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 1.0);
        TEST_ASSERT(db.get<muser_cell*>(2)->value == 2.0);
        TEST_ASSERT(db.get<double>(3) == 1.2);
        TEST_ASSERT(db.get<muser_cell*>(4)->value == 3.0);
        TEST_ASSERT(db.get<muser_cell*>(5)->value == 4.0);

        db.set(3, new muser_cell(5.0)); // merge blocks.
        TEST_ASSERT(db.block_size() == 2);
        TEST_ASSERT(db.get<uint64_t>(0) == 12);
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 1.0);
        TEST_ASSERT(db.get<muser_cell*>(2)->value == 2.0);
        TEST_ASSERT(db.get<muser_cell*>(3)->value == 5.0);
        TEST_ASSERT(db.get<muser_cell*>(4)->value == 3.0);
        TEST_ASSERT(db.get<muser_cell*>(5)->value == 4.0);
    }

    {
        // set_cell() to merge 2 blocks.
        mtv_type db(3);
        db.set(0, static_cast<uint64_t>(23));
        db.set(1, new muser_cell(2.1));
        db.set(2, new muser_cell(3.1));

        db.set(0, new muser_cell(4.2)); // merge
        TEST_ASSERT(db.block_size() == 1);
        TEST_ASSERT(db.get<muser_cell*>(0)->value == 4.2);
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 2.1);
        TEST_ASSERT(db.get<muser_cell*>(2)->value == 3.1);
    }

    {
        // insert_cells() to split block into two.
        mtv_type db(2);
        db.set(0, new muser_cell(2.1));
        db.set(1, new muser_cell(2.2));
        double vals[] = {3.1, 3.2};
        const double* p = &vals[0];
        db.insert(1, p, p + 2);
    }

    {
        // set_cells() - merge new data block with existing block below.
        mtv_type db(6);
        db.set(0, std::string("foo"));
        db.set(1, std::string("baa"));
        db.set(2, 1.1);
        db.set(3, 1.2);
        db.set(4, new muser_cell(2.2));
        db.set(5, new muser_cell(2.3));
        TEST_ASSERT(db.block_size() == 3);

        std::vector<muser_cell*> vals;
        vals.push_back(new muser_cell(2.4));
        vals.push_back(new muser_cell(2.5));
        vals.push_back(new muser_cell(2.6));
        db.set(1, vals.begin(), vals.end());
        TEST_ASSERT(db.block_size() == 2);

        TEST_ASSERT(db.get<std::string>(0) == "foo");
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 2.4);
        TEST_ASSERT(db.get<muser_cell*>(2)->value == 2.5);
        TEST_ASSERT(db.get<muser_cell*>(3)->value == 2.6);
        TEST_ASSERT(db.get<muser_cell*>(4)->value == 2.2);
        TEST_ASSERT(db.get<muser_cell*>(5)->value == 2.3);
    }

    {
        // set_cells() - merge new data block with existing block below, but
        // it overwrites the upper cell.
        mtv_type db(6);
        db.set(0, std::string("foo"));
        db.set(1, std::string("baa"));
        db.set(2, 1.1);
        db.set(3, new muser_cell(2.1));
        db.set(4, new muser_cell(2.2));
        db.set(5, new muser_cell(2.3));
        std::vector<muser_cell*> vals;
        vals.push_back(new muser_cell(2.4));
        vals.push_back(new muser_cell(2.5));
        vals.push_back(new muser_cell(2.6));
        db.set(1, vals.begin(), vals.end());
        TEST_ASSERT(db.block_size() == 2);

        TEST_ASSERT(db.get<std::string>(0) == "foo");
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 2.4);
        TEST_ASSERT(db.get<muser_cell*>(2)->value == 2.5);
        TEST_ASSERT(db.get<muser_cell*>(3)->value == 2.6);
        TEST_ASSERT(db.get<muser_cell*>(4)->value == 2.2);
        TEST_ASSERT(db.get<muser_cell*>(5)->value == 2.3);
    }

    {
        mtv_type db(3);
        db.set(0, new muser_cell(1.0));
        db.set(2, new muser_cell(1.0));
        db.set(1, new muser_cell(1.0));
        TEST_ASSERT(db.block_size() == 1);
    }

    {
        mtv_type db(10);
        for (size_t i = 0; i < 10; ++i)
            db.set(i, new muser_cell(1.1));

        std::vector<double> doubles(3, 2.2);
        db.set(3, doubles.begin(), doubles.end());
        TEST_ASSERT(db.block_size() == 3);

        std::vector<muser_cell*> cells;
        cells.push_back(new muser_cell(2.1));
        cells.push_back(new muser_cell(2.2));
        cells.push_back(new muser_cell(2.3));
        db.set(3, cells.begin(), cells.end());
        TEST_ASSERT(db.block_size() == 1);
        TEST_ASSERT(db.get<muser_cell*>(0)->value == 1.1);
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 1.1);
        TEST_ASSERT(db.get<muser_cell*>(2)->value == 1.1);
        TEST_ASSERT(db.get<muser_cell*>(3)->value == 2.1);
        TEST_ASSERT(db.get<muser_cell*>(4)->value == 2.2);
        TEST_ASSERT(db.get<muser_cell*>(5)->value == 2.3);
        TEST_ASSERT(db.get<muser_cell*>(6)->value == 1.1);
        TEST_ASSERT(db.get<muser_cell*>(7)->value == 1.1);
        TEST_ASSERT(db.get<muser_cell*>(8)->value == 1.1);
        TEST_ASSERT(db.get<muser_cell*>(9)->value == 1.1);
    }

    {
        mtv_type db(3);
        db.set(0, new muser_cell(1.0));
        db.set(1, new muser_cell(2.0));
        db.set(2, new muser_cell(3.0));
        db.set_empty(1, 1);
        TEST_ASSERT(db.block_size() == 3);
        TEST_ASSERT(db.get<muser_cell*>(0)->value == 1.0);
        TEST_ASSERT(db.is_empty(1));
        TEST_ASSERT(db.get<muser_cell*>(2)->value == 3.0);
    }

    {
        mtv_type db(3);
        db.set(1, new muser_cell(3.3));
        TEST_ASSERT(db.block_size() == 3);
        db.set_empty(1, 1);
        TEST_ASSERT(db.block_size() == 1);
    }

    {
        // Release an element.
        mtv_type db(1);
        muser_cell* p1 = new muser_cell(4.5);
        db.set(0, p1);
        muser_cell* p2 = db.release<muser_cell*>(0);
        TEST_ASSERT(p1 == p2);
        TEST_ASSERT(p2->value == 4.5);
        TEST_ASSERT(db.is_empty(0));
        delete p2;

        db = mtv_type(2);
        db.set(0, new muser_cell(23.3));
        TEST_ASSERT(db.block_size() == 2);
        p2 = db.release<muser_cell*>(0);
        TEST_ASSERT(db.is_empty(0));
        TEST_ASSERT(db.is_empty(1));
        TEST_ASSERT(db.block_size() == 1);
        delete p2;

        db = mtv_type(2);
        db.set(0, new muser_cell(1.2));
        db.set(1, new muser_cell(1.3));

        p2 = db.release<muser_cell*>(0);
        TEST_ASSERT(db.is_empty(0));
        TEST_ASSERT(!db.is_empty(1));
        TEST_ASSERT(p2->value == 1.2);
        delete p2;

        db.set(0, new muser_cell(1.4));
        p2 = db.release<muser_cell*>(1);
        TEST_ASSERT(!db.is_empty(0));
        TEST_ASSERT(db.is_empty(1));
        TEST_ASSERT(p2->value == 1.3);
        delete p2;

        db = mtv_type(3);
        db.set(0, new muser_cell(2.1));
        db.set(1, new muser_cell(2.2));
        db.set(2, new muser_cell(2.3));

        p2 = db.release<muser_cell*>(1);
        TEST_ASSERT(p2->value == 2.2);
        TEST_ASSERT(!db.is_empty(0));
        TEST_ASSERT(db.is_empty(1));
        TEST_ASSERT(!db.is_empty(2));

        delete p2;

        db = mtv_type(3);
        db.set(0, new muser_cell(2.1));
        db.set(1, new muser_cell(2.2));
        db.set(2, new muser_cell(2.3));
        db.set_empty(0, 2); // Make sure this doesn't release anything.

        // Release with position hint.
        db = mtv_type(4);
        db.set(0, new muser_cell(4.5));
        db.set(1, new muser_cell(4.6));
        db.set(3, new muser_cell(5.1));

        mtv_type::iterator pos = db.release(0, p1);
        TEST_ASSERT(pos == db.begin());
        pos = db.release(pos, 3, p2);
        ++pos;
        TEST_ASSERT(pos == db.end());
        TEST_ASSERT(p1->value == 4.5);
        TEST_ASSERT(p2->value == 5.1);
        TEST_ASSERT(db.block_size() == 3);
        TEST_ASSERT(db.is_empty(0));
        TEST_ASSERT(db.get<muser_cell*>(1)->value == 4.6);
        TEST_ASSERT(db.is_empty(2));
        TEST_ASSERT(db.is_empty(3));
        delete p1;
        delete p2;
    }

    {
        mtv_type db(5);

        db.set(1, new muser_cell(1.1));
        db.set(2, new muser_cell(1.2));
        db.set(3, new muser_cell(1.3));

        db.set(1, 2.1); // Don't leak the overwritten muser_cell instance.
        db.set(2, 2.2); // ditto
    }

    {
        mtv_type db(4);
        db.set(0, new muser_cell(1.1));
        db.set(1, new muser_cell(1.2));
        db.set(2, new muser_cell(1.3));

        db.set(2, 2.1); // Don't leak the overwritten muser_cell instance.
        db.set(1, 2.0); // ditto
    }

    {
        mtv_type db(8);
        db.set(3, new muser_cell(1.1));
        db.set(4, new muser_cell(1.2));
        db.set(5, 1.3);

        db.set(4, 2.2); // Overwrite muser_cell and don't leak.
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
