/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * ArmarX is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package    RobotAPI::ArmarXObjects::armem
 * @author     Simon Ottenhaus ( simon dot ottenhaus at kit dot edu )
 * @date       2020
 * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
 *             GNU General Public License
 */

#define BOOST_TEST_MODULE RobotAPI::ArmarXLibraries::armem

#define ARMARX_BOOST_TEST

#include <RobotAPI/Test.h>

#include <RobotAPI/libraries/armem/core/workingmemory/Memory.h>
#include <RobotAPI/libraries/armem/core/longtermmemory/Memory.h>
#include <RobotAPI/libraries/armem/core/diskmemory/Memory.h>
#include <RobotAPI/libraries/armem/core/error.h>

#include <iostream>
#include <SimoxUtility/meta/type_name.h>
#include <RobotAPI/libraries/aron/core/navigator/data/container/Dict.h>


namespace armem = armarx::armem;
namespace aron = armarx::aron;

BOOST_AUTO_TEST_CASE(test_time_to_string)
{
    // 111111: seconds, 345: milliseconds, 789: microseconds
    armem::Time time = armem::Time::microSeconds(111111345789);

    BOOST_CHECK_EQUAL(armem::toStringMilliSeconds(time), "111111345.789 ms");
    BOOST_CHECK_EQUAL(armem::toStringMilliSeconds(time, 0), "111111345 ms");
    BOOST_CHECK_EQUAL(armem::toStringMilliSeconds(time, 1), "111111345.7 ms");
    BOOST_CHECK_EQUAL(armem::toStringMilliSeconds(time, 2), "111111345.78 ms");
    BOOST_CHECK_EQUAL(armem::toStringMilliSeconds(time, 3), "111111345.789 ms");

    BOOST_CHECK_EQUAL(armem::toStringMicroSeconds(time), "111111345789 " "\u03BC" "s");

    BOOST_CHECK_EQUAL(armem::toDateTimeMilliSeconds(time), "1970-01-02 07:51:51.345789");
    BOOST_CHECK_EQUAL(armem::toDateTimeMilliSeconds(time, 0), "1970-01-02 07:51:51");
    BOOST_CHECK_EQUAL(armem::toDateTimeMilliSeconds(time, 3), "1970-01-02 07:51:51.345");
    BOOST_CHECK_EQUAL(armem::toDateTimeMilliSeconds(time, 6), "1970-01-02 07:51:51.345789");

    // 111111: seconds, 000: milliseconds, 789: microseconds
    time = armem::Time::microSeconds(111111000789);
    BOOST_CHECK_EQUAL(armem::toStringMilliSeconds(time), "111111000.789 ms");
    BOOST_CHECK_EQUAL(armem::toStringMicroSeconds(time), "111111000789 " "\u03BC" "s");
    BOOST_CHECK_EQUAL(armem::toDateTimeMilliSeconds(time), "1970-01-02 07:51:51.000789");
    BOOST_CHECK_EQUAL(armem::toDateTimeMilliSeconds(time, 0), "1970-01-02 07:51:51");
    BOOST_CHECK_EQUAL(armem::toDateTimeMilliSeconds(time, 3), "1970-01-02 07:51:51.000");
    BOOST_CHECK_EQUAL(armem::toDateTimeMilliSeconds(time, 6), "1970-01-02 07:51:51.000789");


    // 111111: seconds, 345: milliseconds, 000: microseconds
    time = armem::Time::microSeconds(111111345000);
    BOOST_CHECK_EQUAL(armem::toStringMilliSeconds(time), "111111345.000 ms");
    BOOST_CHECK_EQUAL(armem::toStringMicroSeconds(time), "111111345000 " "\u03BC" "s");
    BOOST_CHECK_EQUAL(armem::toDateTimeMilliSeconds(time), "1970-01-02 07:51:51.345000");
    BOOST_CHECK_EQUAL(armem::toDateTimeMilliSeconds(time, 0), "1970-01-02 07:51:51");
    BOOST_CHECK_EQUAL(armem::toDateTimeMilliSeconds(time, 3), "1970-01-02 07:51:51.345");
    BOOST_CHECK_EQUAL(armem::toDateTimeMilliSeconds(time, 6), "1970-01-02 07:51:51.345000");
}




namespace ArMemMemoryTest
{
    struct TestMemoryItem : public armem::base::detail::MemoryItem
    {
        using MemoryItem::MemoryItem;
        using MemoryItem::operator=;

        std::string getKeyString() const override
        {
            return "";
        }
        std::string getLevelName() const  override
        {
            return "";
        }
    };
    struct MemoryItemCtorOpTestFixture
    {
        const armem::MemoryID id {"A/B/C/123/1"};
        const armem::MemoryID moved {"////1"};  // int is not moved
        TestMemoryItem item { id };

        MemoryItemCtorOpTestFixture()
        {
            BOOST_CHECK_EQUAL(item.id(), id);
        }
    };
}


BOOST_FIXTURE_TEST_SUITE(MemoryItemTest, ArMemMemoryTest::MemoryItemCtorOpTestFixture)


BOOST_AUTO_TEST_CASE(test_copy_ctor)
{
    const ArMemMemoryTest::TestMemoryItem out(item);
    BOOST_CHECK_EQUAL(item.id(), id);
    BOOST_CHECK_EQUAL(out.id(), id);
}
BOOST_AUTO_TEST_CASE(test_copy_op)
{
    ArMemMemoryTest::TestMemoryItem out;
    out = item;
    BOOST_CHECK_EQUAL(item.id(), id);
    BOOST_CHECK_EQUAL(out.id(), id);
}
BOOST_AUTO_TEST_CASE(test_move_ctor)
{
    ArMemMemoryTest::TestMemoryItem in = item;
    const ArMemMemoryTest::TestMemoryItem out(std::move(in));
    BOOST_CHECK_EQUAL(in.id(), moved);
    BOOST_CHECK_EQUAL(out.id(), id);
}
BOOST_AUTO_TEST_CASE(test_move_op)
{
    ArMemMemoryTest::TestMemoryItem in = item;
    ArMemMemoryTest::TestMemoryItem out;
    out = std::move(in);
    BOOST_CHECK_EQUAL(in.id(), moved);
    BOOST_CHECK_EQUAL(out.id(), id);
}


BOOST_AUTO_TEST_SUITE_END()



namespace ArMemMemoryTest
{
    struct TestMemoryContainer : public armem::base::detail::MemoryContainerBase<std::vector<int>, TestMemoryContainer>
    {
        using MemoryContainerBase::MemoryContainerBase;
        using MemoryContainerBase::operator=;

        std::string getKeyString() const override
        {
            return "";
        }
        std::string getLevelName() const  override
        {
            return "";
        }
    };
    struct MemoryContainerCtorOpTestFixture
    {
        const armem::MemoryID id {"A/B/C/123/1"};
        const armem::MemoryID moved {"////1"};  // int is not moved
        TestMemoryContainer cont {id};

        MemoryContainerCtorOpTestFixture()
        {
            cont.container() = std::vector<int>{ -1, 2, -3 };
            BOOST_CHECK_EQUAL(cont.id(), id);
            BOOST_CHECK_EQUAL(cont.size(), 3);
        }
    };

}

BOOST_FIXTURE_TEST_SUITE(MemoryContainerTest, ArMemMemoryTest::MemoryContainerCtorOpTestFixture)

BOOST_AUTO_TEST_CASE(test_copy_ctor)
{
    const ArMemMemoryTest::TestMemoryContainer out(cont);
    BOOST_CHECK_EQUAL(cont.id(), id);
    BOOST_CHECK_EQUAL(cont.size(), 3);
    BOOST_CHECK_EQUAL(out.id(), id);
    BOOST_CHECK_EQUAL(out.size(), 3);
}
BOOST_AUTO_TEST_CASE(test_copy_op)
{
    ArMemMemoryTest::TestMemoryContainer out;
    out = cont;
    BOOST_CHECK_EQUAL(cont.id(), id);
    BOOST_CHECK_EQUAL(cont.size(), 3);
    BOOST_CHECK_EQUAL(out.id(), id);
    BOOST_CHECK_EQUAL(out.size(), 3);
}
BOOST_AUTO_TEST_CASE(test_move_ctor)
{
    ArMemMemoryTest::TestMemoryContainer in = cont;
    const ArMemMemoryTest::TestMemoryContainer out(std::move(in));
    BOOST_CHECK_EQUAL(in.id(), moved);
    BOOST_CHECK_EQUAL(in.size(), 0);
    BOOST_CHECK_EQUAL(out.id(), id);
    BOOST_CHECK_EQUAL(out.size(), 3);
}
BOOST_AUTO_TEST_CASE(test_move_op)
{
    ArMemMemoryTest::TestMemoryContainer in = cont;
    ArMemMemoryTest::TestMemoryContainer out;
    out = std::move(in);
    BOOST_CHECK_EQUAL(in.id(), moved);
    BOOST_CHECK_EQUAL(in.size(), 0);
    BOOST_CHECK_EQUAL(out.id(), id);
    BOOST_CHECK_EQUAL(out.size(), 3);
}

BOOST_AUTO_TEST_SUITE_END()



BOOST_AUTO_TEST_CASE(test_key_ctors)
{
    armem::wm::EntityInstance instance(10);
    BOOST_CHECK_EQUAL(instance.index(), 10);

    armem::wm::EntitySnapshot snapshot(armem::Time::milliSeconds(100));
    BOOST_CHECK_EQUAL(snapshot.time(), armem::Time::milliSeconds(100));

    armem::wm::Entity entity("entity");
    BOOST_CHECK_EQUAL(entity.name(), "entity");

    armem::wm::ProviderSegment provSeg("provSeg");
    BOOST_CHECK_EQUAL(provSeg.name(), "provSeg");

    armem::wm::CoreSegment coreSeg("coreSeg");
    BOOST_CHECK_EQUAL(coreSeg.name(), "coreSeg");

    armem::wm::Memory memory("memory");
    BOOST_CHECK_EQUAL(memory.name(), "memory");
}


template <class ...Args>
auto add_element(std::vector<Args...>& vector)
{
    return vector.emplace_back();
}
template <class ...Args>
auto add_element(std::map<Args...>& map)
{
    return map.emplace();
}


template <class T>
struct CustomChecks
{
    static void checkEqual(const T& lhs, const T& rhs)
    {
        (void) lhs, (void) rhs;
    }
    static void checkMoved(const T& moved)
    {
        (void) moved;
    }
};


template <class T>
void checkEqual_d_ltm(const T& lhs, const T& rhs)
{
    BOOST_CHECK_EQUAL(lhs.path, rhs.path);
}
template <class T>
void checkMoved_d_ltm(const T& moved)
{
    BOOST_CHECK_EQUAL(moved.path, nullptr);
}

template <>
struct CustomChecks<armem::wm::EntityInstance>
{
    static void checkEqual(const armem::wm::EntityInstance& lhs, const armem::wm::EntityInstance& rhs)
    {
        BOOST_CHECK_EQUAL(lhs.metadata(), rhs.metadata());
        BOOST_CHECK_EQUAL(lhs.data(), rhs.data());
    }
    static void checkMoved(const armem::wm::EntityInstance& moved)
    {
        BOOST_CHECK_EQUAL(moved.data(), nullptr);
    }
};
template <>
struct CustomChecks<armem::d_ltm::EntityInstance>
{
    static void checkEqual(const armem::d_ltm::EntityInstance& lhs, const armem::d_ltm::EntityInstance& rhs)
    {
        checkEqual_d_ltm(lhs, rhs);
    }
    static void checkMoved(const armem::d_ltm::EntityInstance& moved)
    {
        checkMoved_d_ltm(moved);
    }
};
template <>
struct CustomChecks<armem::d_ltm::EntitySnapshot>
{
    static void checkEqual(const armem::d_ltm::EntitySnapshot& lhs, const armem::d_ltm::EntitySnapshot& rhs)
    {
        checkEqual_d_ltm(lhs, rhs);
    }
    static void checkMoved(const armem::d_ltm::EntitySnapshot& moved)
    {
        checkMoved_d_ltm(moved);
    }
};
template <>
struct CustomChecks<armem::d_ltm::Entity>
{
    static void checkEqual(const armem::d_ltm::Entity& lhs, const armem::d_ltm::Entity& rhs)
    {
        checkEqual_d_ltm(lhs, rhs);
    }
    static void checkMoved(const armem::d_ltm::Entity& moved)
    {
        checkMoved_d_ltm(moved);
    }
};
template <>
struct CustomChecks<armem::d_ltm::ProviderSegment>
{
    static void checkEqual(const armem::d_ltm::ProviderSegment& lhs, const armem::d_ltm::ProviderSegment& rhs)
    {
        checkEqual_d_ltm(lhs, rhs);
    }
    static void checkMoved(const armem::d_ltm::ProviderSegment& moved)
    {
        checkMoved_d_ltm(moved);
    }
};
template <>
struct CustomChecks<armem::d_ltm::CoreSegment>
{
    static void checkEqual(const armem::d_ltm::CoreSegment& lhs, const armem::d_ltm::CoreSegment& rhs)
    {
        checkEqual_d_ltm(lhs, rhs);
    }
    static void checkMoved(const armem::d_ltm::CoreSegment& moved)
    {
        checkMoved_d_ltm(moved);
    }
};
template <>
struct CustomChecks<armem::d_ltm::Memory>
{
    static void checkEqual(const armem::d_ltm::Memory& lhs, const armem::d_ltm::Memory& rhs)
    {
        checkEqual_d_ltm(lhs, rhs);
    }
    static void checkMoved(const armem::d_ltm::Memory& moved)
    {
        checkMoved_d_ltm(moved);
    }
};


struct CopyMoveCtorsOpsTestBase
{
    const armem::MemoryID id {"A/B/C/123000"};  // int index would not be moved
    const armem::MemoryID idMoved;

    std::string typeName;

    CopyMoveCtorsOpsTestBase(const std::string& typeName) :
        typeName(typeName)
    {
    }
    virtual ~CopyMoveCtorsOpsTestBase()
    {
    }


    void test()
    {
        BOOST_TEST_CONTEXT("Type " << typeName)
        {
            reset();
            BOOST_TEST_CONTEXT("copy ctor")
            {
                testCopyCtor();
            }
            reset();
            BOOST_TEST_CONTEXT("copy op")
            {
                testCopyOp();
            }
            reset();
            BOOST_TEST_CONTEXT("move ctor")
            {
                testMoveCtor();
            }
            reset();
            BOOST_TEST_CONTEXT("move op")
            {
                testMoveOp();
            }
        }
    }

    virtual void reset()
    {
    }

    virtual void testCopyCtor() = 0;
    virtual void testCopyOp() = 0;
    virtual void testMoveCtor() = 0;
    virtual void testMoveOp() = 0;
};



template <class T>
struct InstanceCopyMoveCtorsOpsTest : public CopyMoveCtorsOpsTestBase
{
    T in;

    InstanceCopyMoveCtorsOpsTest() :
        CopyMoveCtorsOpsTestBase(simox::meta::get_type_name<T>())
    {
    }
    virtual ~InstanceCopyMoveCtorsOpsTest() override = default;

    void reset() override
    {
        in = T {id};
        BOOST_CHECK_EQUAL(in.id(), id);
    }

    void testCopyCtor() override
    {
        T out { in };

        BOOST_CHECK_EQUAL(out.id(), id);

        CustomChecks<T>::checkEqual(out, in);
    }
    void testCopyOp() override
    {
        T out;
        out = in;

        BOOST_CHECK_EQUAL(out.id(), id);

        CustomChecks<T>::checkEqual(out, in);
    }
    void testMoveCtor() override
    {
        T out { std::move(in) };

        BOOST_CHECK_EQUAL(in.id(), idMoved);
        BOOST_CHECK_EQUAL(out.id(), id);

        CustomChecks<T>::checkMoved(in);
    }
    void testMoveOp() override
    {
        T out;
        out = std::move(in);

        BOOST_CHECK_EQUAL(in.id(), idMoved);
        BOOST_CHECK_EQUAL(out.id(), id);

        CustomChecks<T>::checkMoved(in);
    }
};


template <class T>
struct CopyMoveCtorsOpsTest : public CopyMoveCtorsOpsTestBase
{
    T in;

    CopyMoveCtorsOpsTest() : CopyMoveCtorsOpsTestBase(simox::meta::get_type_name<T>())
    {
    }
    virtual ~CopyMoveCtorsOpsTest() override = default;

    void reset() override
    {
        in = T {id};
        add_element(in.container());

        BOOST_CHECK_EQUAL(in.id(), id);
        BOOST_CHECK_EQUAL(in.size(), 1);
    }

    void testCopyCtor() override
    {
        T out { in };
        BOOST_CHECK_EQUAL(in.id(), id);
        BOOST_CHECK_EQUAL(in.size(), 1);
        BOOST_CHECK_EQUAL(out.id(), id);
        BOOST_CHECK_EQUAL(out.size(), 1);

        CustomChecks<T>::checkEqual(out, in);
    }
    void testCopyOp() override
    {
        T out;
        out = in;
        BOOST_CHECK_EQUAL(in.id(), id);
        BOOST_CHECK_EQUAL(in.size(), 1);
        BOOST_CHECK_EQUAL(out.id(), id);
        BOOST_CHECK_EQUAL(out.size(), 1);

        CustomChecks<T>::checkEqual(out, in);
    }
    void testMoveCtor() override
    {
        T out { std::move(in) };

        BOOST_CHECK_EQUAL(in.id(), idMoved);
        BOOST_CHECK_EQUAL(in.size(), 0);
        BOOST_CHECK_EQUAL(out.id(), id);
        BOOST_CHECK_EQUAL(out.size(), 1);

        CustomChecks<T>::checkMoved(in);
    }
    void testMoveOp() override
    {
        T out;
        out = std::move(in);

        BOOST_CHECK_EQUAL(in.id(), idMoved);
        BOOST_CHECK_EQUAL(in.size(), 0);
        BOOST_CHECK_EQUAL(out.id(), id);
        BOOST_CHECK_EQUAL(out.size(), 1);

        CustomChecks<T>::checkMoved(in);
    }
};



BOOST_AUTO_TEST_CASE(test_copy_move_ctors_ops)
{
    {
        InstanceCopyMoveCtorsOpsTest<armem::wm::EntityInstance>().test();
        CopyMoveCtorsOpsTest<armem::wm::EntitySnapshot>().test();
        CopyMoveCtorsOpsTest<armem::wm::Entity>().test();
        CopyMoveCtorsOpsTest<armem::wm::ProviderSegment>().test();
        CopyMoveCtorsOpsTest<armem::wm::CoreSegment>().test();
        CopyMoveCtorsOpsTest<armem::wm::Memory>().test();
    }
    {
        InstanceCopyMoveCtorsOpsTest<armem::ltm::EntityInstance>().test();
        CopyMoveCtorsOpsTest<armem::ltm::EntitySnapshot>().test();
        CopyMoveCtorsOpsTest<armem::ltm::Entity>().test();
        CopyMoveCtorsOpsTest<armem::ltm::ProviderSegment>().test();
        CopyMoveCtorsOpsTest<armem::ltm::CoreSegment>().test();
        CopyMoveCtorsOpsTest<armem::ltm::Memory>().test();
    }
    {
        InstanceCopyMoveCtorsOpsTest<armem::d_ltm::EntityInstance>().test();
        CopyMoveCtorsOpsTest<armem::d_ltm::EntitySnapshot>().test();
        CopyMoveCtorsOpsTest<armem::d_ltm::Entity>().test();
        CopyMoveCtorsOpsTest<armem::d_ltm::ProviderSegment>().test();
        CopyMoveCtorsOpsTest<armem::d_ltm::CoreSegment>().test();
        CopyMoveCtorsOpsTest<armem::d_ltm::Memory>().test();
    }
}



BOOST_AUTO_TEST_CASE(test_segment_setup)
{
    armem::EntityUpdate update;

    armem::wm::Memory memory("Memory");
    BOOST_CHECK_EQUAL(memory.name(), "Memory");
    {
        update.entityID = armem::MemoryID::fromString("OtherMemory/SomeSegment");
        BOOST_CHECK_THROW(memory.update(update), armem::error::ContainerNameMismatch);
        update.entityID = armem::MemoryID::fromString("Memory/MissingSegment");
        BOOST_CHECK_THROW(memory.update(update), armem::error::MissingEntry);
    }

    armem::wm::CoreSegment& coreSegment = memory.addCoreSegment("ImageRGB");
    BOOST_CHECK_EQUAL(coreSegment.name(), "ImageRGB");
    BOOST_CHECK(memory.hasCoreSegment(coreSegment.name()));
    {
        update.entityID = armem::MemoryID::fromString("Memory/OtherCoreSegment");
        BOOST_CHECK_THROW(coreSegment.update(update), armem::error::ContainerNameMismatch);
        update.entityID = armem::MemoryID::fromString("Memory/ImageRGB/MissingProvider");
        BOOST_CHECK_THROW(coreSegment.update(update), armem::error::MissingEntry);
    }

    armem::wm::ProviderSegment& providerSegment = coreSegment.addProviderSegment("SomeRGBImageProvider");
    BOOST_CHECK_EQUAL(providerSegment.name(), "SomeRGBImageProvider");
    BOOST_CHECK(coreSegment.hasProviderSegment(providerSegment.name()));
    {
        update.entityID = armem::MemoryID::fromString("Memory/ImageRGB/OtherRGBImageProvider");
        BOOST_CHECK_THROW(providerSegment.update(update), armem::error::ContainerNameMismatch);
    }


    // A successful update.

    update.entityID = armem::MemoryID::fromString("Memory/ImageRGB/SomeRGBImageProvider/image");
    update.instancesData =
    {
        std::make_shared<aron::datanavigator::DictNavigator>(),
        std::make_shared<aron::datanavigator::DictNavigator>()
    };
    update.timeCreated = armem::Time::milliSeconds(1000);
    BOOST_CHECK_NO_THROW(providerSegment.update(update));

    BOOST_CHECK_EQUAL(providerSegment.entities().size(), 1);
    BOOST_CHECK(providerSegment.hasEntity("image"));
    BOOST_CHECK(!providerSegment.hasEntity("other_image"));

    armem::wm::Entity& entity = providerSegment.getEntity("image");
    BOOST_CHECK_EQUAL(entity.name(), "image");
    BOOST_CHECK_EQUAL(entity.history().size(), 1);
    BOOST_CHECK_EQUAL(entity.history().count(update.timeCreated), 1);

    armem::wm::EntitySnapshot& entitySnapshot = entity.history().at(update.timeCreated);
    BOOST_CHECK_EQUAL(entitySnapshot.instances().size(), update.instancesData.size());


    // Another update (on memory).

    update.instancesData = { std::make_shared<aron::datanavigator::DictNavigator>() };
    update.timeCreated = armem::Time::milliSeconds(2000);
    memory.update(update);
    BOOST_CHECK_EQUAL(entity.history().size(), 2);
    BOOST_CHECK_EQUAL(entity.history().count(update.timeCreated), 1);
    BOOST_CHECK_EQUAL(entity.history().at(update.timeCreated).instances().size(), update.instancesData.size());


    // A third update (on entity).
    update.instancesData = { std::make_shared<aron::datanavigator::DictNavigator>() };
    update.timeCreated = armem::Time::milliSeconds(3000);
    entity.update(update);
    BOOST_CHECK_EQUAL(entity.history().size(), 3);

}



BOOST_AUTO_TEST_CASE(test_history_size_in_entity)
{
    armem::wm::Entity entity("entity");

    armem::EntityUpdate update;
    update.entityID.entityName = entity.name();

    // With unlimited history.
    update.timeCreated = armem::Time::milliSeconds(1000);
    entity.update(update);
    update.timeCreated = armem::Time::milliSeconds(2000);
    entity.update(update);
    update.timeCreated = armem::Time::milliSeconds(3000);
    entity.update(update);
    BOOST_CHECK_EQUAL(entity.history().size(), 3);

    // Now with maximum history size.
    entity.setMaxHistorySize(2);
    BOOST_CHECK_EQUAL(entity.history().size(), 2);
    BOOST_CHECK_EQUAL(entity.history().count(armem::Time::milliSeconds(1000)), 0);
    BOOST_CHECK_EQUAL(entity.history().count(armem::Time::milliSeconds(2000)), 1);
    BOOST_CHECK_EQUAL(entity.history().count(armem::Time::milliSeconds(3000)), 1);


    update.timeCreated = armem::Time::milliSeconds(4000);
    entity.update(update);
    BOOST_CHECK_EQUAL(entity.history().size(), 2);
    BOOST_CHECK_EQUAL(entity.history().count(armem::Time::milliSeconds(2000)), 0);
    BOOST_CHECK_EQUAL(entity.history().count(armem::Time::milliSeconds(3000)), 1);
    BOOST_CHECK_EQUAL(entity.history().count(armem::Time::milliSeconds(4000)), 1);

    // Disable maximum history size.
    entity.setMaxHistorySize(-1);

    update.timeCreated = armem::Time::milliSeconds(5000);
    entity.update(update);
    BOOST_CHECK_EQUAL(entity.history().size(), 3);
    BOOST_CHECK_EQUAL(entity.history().count(armem::Time::milliSeconds(3000)), 1);
    BOOST_CHECK_EQUAL(entity.history().count(armem::Time::milliSeconds(4000)), 1);
    BOOST_CHECK_EQUAL(entity.history().count(armem::Time::milliSeconds(5000)), 1);
}


BOOST_AUTO_TEST_CASE(test_history_size_in_provider_segment)
{
    armem::wm::ProviderSegment providerSegment("SomeRGBImageProvider");

    armem::EntityUpdate update;
    update.entityID.providerSegmentName = providerSegment.name();

    std::vector<std::string> entityNames = { "A", "B" };

    // Fill entities and histories with unlimited size.
    for (const auto& name : entityNames)
    {
        update.entityID.entityName = name;

        update.timeCreated = armem::Time::milliSeconds(1000);
        providerSegment.update(update);
        update.timeCreated = armem::Time::milliSeconds(2000);
        providerSegment.update(update);
        update.timeCreated = armem::Time::milliSeconds(3000);
        providerSegment.update(update);
    }
    update.entityID.entityName = entityNames.back();
    update.timeCreated = armem::Time::milliSeconds(4000);
    providerSegment.update(update);

    BOOST_CHECK_EQUAL(providerSegment.getEntity("A").history().size(), 3);
    BOOST_CHECK_EQUAL(providerSegment.getEntity("B").history().size(), 4);


    // Employ maximum history size.
    providerSegment.setMaxHistorySize(3);
    BOOST_CHECK_EQUAL(providerSegment.getEntity("A").history().size(), 3);
    BOOST_CHECK_EQUAL(providerSegment.getEntity("B").history().size(), 3);

    providerSegment.setMaxHistorySize(2);
    BOOST_CHECK_EQUAL(providerSegment.getEntity("A").history().size(), 2);
    BOOST_CHECK_EQUAL(providerSegment.getEntity("B").history().size(), 2);

    providerSegment.setMaxHistorySize(3);
    BOOST_CHECK_EQUAL(providerSegment.getEntity("A").history().size(), 2);
    BOOST_CHECK_EQUAL(providerSegment.getEntity("B").history().size(), 2);

    // Add new entity.
    providerSegment.setMaxHistorySize(2);

    update.entityID.entityName = "C";
    update.timeCreated = armem::Time::milliSeconds(1000);
    providerSegment.update(update);
    update.timeCreated = armem::Time::milliSeconds(2000);
    providerSegment.update(update);
    update.timeCreated = armem::Time::milliSeconds(3000);
    providerSegment.update(update);

    // Check correctly inherited history size.
    BOOST_CHECK_EQUAL(providerSegment.getEntity("C").getMaxHistorySize(), 2);
    // Check actual history size.
    BOOST_CHECK_EQUAL(providerSegment.getEntity("C").history().size(), 2);

    // Remove maximum.
    providerSegment.setMaxHistorySize(-1);

    entityNames.push_back("C");
    for (const auto& name : entityNames)
    {
        update.entityID.entityName = name;
        update.timeCreated = armem::Time::milliSeconds(5000);
        providerSegment.update(update);
        BOOST_CHECK_EQUAL(providerSegment.getEntity(name).history().size(), 3);
    }
}



template <class T>
struct CopyTest
{
    CopyTest()
    {
    }

    void test()
    {
        // At least check whether they can be called.
        T t;
        T t2 = t.copy();

        if constexpr (std::is_base_of_v<armem::base::EntityInstanceBase<T>, T>)
        {
            t2 = t.copyEmpty();
        }
    }
};


BOOST_AUTO_TEST_CASE(test_copy)
{
    {
        CopyTest<armem::wm::EntityInstance>().test();
        CopyTest<armem::wm::EntitySnapshot>().test();
        CopyTest<armem::wm::Entity>().test();
        CopyTest<armem::wm::ProviderSegment>().test();
        CopyTest<armem::wm::CoreSegment>().test();
        CopyTest<armem::wm::Memory>().test();
    }
    {
        CopyTest<armem::ltm::EntityInstance>().test();
        CopyTest<armem::ltm::EntitySnapshot>().test();
        CopyTest<armem::ltm::Entity>().test();
        CopyTest<armem::ltm::ProviderSegment>().test();
        CopyTest<armem::ltm::CoreSegment>().test();
        CopyTest<armem::ltm::Memory>().test();
    }
    {
        CopyTest<armem::d_ltm::EntityInstance>().test();
        CopyTest<armem::d_ltm::EntitySnapshot>().test();
        CopyTest<armem::d_ltm::Entity>().test();
        CopyTest<armem::d_ltm::ProviderSegment>().test();
        CopyTest<armem::d_ltm::CoreSegment>().test();
        CopyTest<armem::d_ltm::Memory>().test();
    }
}