// Catch static asserts
#define flecs_static_assert(cond, str)\
    ecs_assert(cond, ECS_INVALID_OPERATION, str)

#include <cpp_api.h>

int Pod::ctor_invoked = 0;
int Pod::dtor_invoked = 0;
int Pod::copy_invoked = 0;
int Pod::move_invoked = 0;
int Pod::copy_ctor_invoked = 0;
int Pod::move_ctor_invoked = 0;

int CountNoDefaultCtor::ctor_invoked = 0;
int CountNoDefaultCtor::dtor_invoked = 0;
int CountNoDefaultCtor::copy_invoked = 0;
int CountNoDefaultCtor::move_invoked = 0;
int CountNoDefaultCtor::copy_ctor_invoked = 0;
int CountNoDefaultCtor::move_ctor_invoked = 0;

void ComponentLifecycle_ctor_on_add(void) {
    flecs::world world;

    world.component<Pod>();

    auto e = world.entity().add<Pod>();
    test_assert(e.id() != 0);
    test_assert(e.has<Pod>());

    const Pod *pod = e.get<Pod>();
    test_assert(pod != NULL);
    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 0);
    test_int(Pod::copy_invoked, 0);
    test_int(Pod::move_invoked, 0);
    
    test_int(pod->value, 10);
}

void ComponentLifecycle_dtor_on_remove(void) {
    flecs::world world;

    world.component<Pod>();

    auto e = world.entity().add<Pod>();
    test_assert(e.id() != 0);
    test_assert(e.has<Pod>());
    test_int(Pod::ctor_invoked, 1);

    e.remove<Pod>();
    test_assert(!e.has<Pod>());
    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 1);
    test_int(Pod::copy_invoked, 0);
    test_int(Pod::move_invoked, 0);
}

void ComponentLifecycle_move_on_add(void) {
    flecs::world world;

    world.component<Pod>();

    auto e = world.entity().add<Pod>();
    test_assert(e.id() != 0);
    test_assert(e.has<Pod>());

    const Pod *pod = e.get<Pod>();
    test_assert(pod != NULL);
    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 0);
    test_int(Pod::copy_invoked, 0);
    test_int(Pod::move_invoked, 0);
    test_int(Pod::copy_ctor_invoked, 0);
    test_int(Pod::move_ctor_invoked, 0);

    e.add<Position>();

    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 1);
    test_int(Pod::copy_invoked, 0);
    test_int(Pod::move_invoked, 0);
    test_int(Pod::copy_ctor_invoked, 0);
    test_int(Pod::move_ctor_invoked, 1);

    test_int(pod->value, 10);
}

void ComponentLifecycle_move_on_remove(void) {
    flecs::world world;

    world.component<Pod>();

    auto e = world.entity().add<Position>().add<Pod>();
    test_assert(e.id() != 0);
    test_assert(e.has<Pod>());

    const Pod *pod = e.get<Pod>();
    test_assert(pod != NULL);
    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 0);
    test_int(Pod::copy_invoked, 0);
    test_int(Pod::move_invoked, 0);
    test_int(Pod::copy_ctor_invoked, 0);
    test_int(Pod::move_ctor_invoked, 0);

    e.remove<Position>();

    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 1);
    test_int(Pod::copy_invoked, 0);
    test_int(Pod::move_invoked, 0);
    test_int(Pod::copy_ctor_invoked, 0);
    test_int(Pod::move_ctor_invoked, 1);

    test_int(pod->value, 10);
}

void ComponentLifecycle_copy_on_set(void) {
    flecs::world world;

    world.component<Pod>();

    auto e = world.entity();
    test_assert(e.id() != 0);

    e.set<Pod>({20});
    test_assert(e.has<Pod>());
    test_int(Pod::ctor_invoked, 2);
    test_int(Pod::dtor_invoked, 1);
    test_int(Pod::copy_invoked, 0);
    test_int(Pod::move_invoked, 1);
}

void ComponentLifecycle_copy_on_override(void) {
    flecs::world world;

    world.component<Pod>();

    auto base = world.entity();
    test_assert(base.id() != 0);

    base.set<Pod>({10});
    test_int(Pod::ctor_invoked, 2);
    test_int(Pod::dtor_invoked, 1);
    test_int(Pod::copy_invoked, 0);
    Pod::ctor_invoked = 0;
    Pod::dtor_invoked = 0;
    Pod::copy_invoked = 0;
    Pod::move_invoked = 0;

    auto e = world.entity();
    test_assert(e.id() != 0);
    
    e.add(flecs::IsA, base);
    test_int(Pod::ctor_invoked, 0);

    e.add<Pod>();
    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 0);
    test_int(Pod::copy_invoked, 1);
    test_int(Pod::move_invoked, 0);

    const Pod *pod = e.get<Pod>();
    test_assert(pod != NULL);
    test_int(pod->value, 10);
}

void ComponentLifecycle_struct_w_string_add(void) {
    flecs::world world;

    auto e = world.entity().add<Struct_w_string>();
    test_assert(e.id() != 0);
    test_assert(e.has<Struct_w_string>());

    const Struct_w_string *str = e.get<Struct_w_string>();
    test_assert(str != NULL);
    test_assert(str->value == "");
}

void ComponentLifecycle_struct_w_string_remove(void) {
    flecs::world world;

    auto e = world.entity().add<Struct_w_string>();
    test_assert(e.id() != 0);
    test_assert(e.has<Struct_w_string>());

    e.remove<Struct_w_string>();
    test_assert(!e.has<Struct_w_string>());
}

void ComponentLifecycle_struct_w_string_set(void) {
    flecs::world world;

    auto e = world.entity()
        .set<Struct_w_string>({"Hello World"});
    test_assert(e.id() != 0);
    test_assert(e.has<Struct_w_string>());

    const Struct_w_string *str = e.get<Struct_w_string>();
    test_assert(str != NULL);
    test_assert(str->value == "Hello World");
}

void ComponentLifecycle_struct_w_string_override(void) {
    flecs::world world;

    auto base = world.entity();
    test_assert(base.id() != 0);

    base.set<Struct_w_string>({"Hello World"});

    auto e = world.entity();
    test_assert(e.id() != 0);
    
    e.add(flecs::IsA, base);

    e.add<Struct_w_string>();

    const Struct_w_string *str = e.get<Struct_w_string>();
    test_assert(str != NULL);
    test_assert(str->value == "Hello World");
}

void ComponentLifecycle_struct_w_string_add_2_remove(void) {
    flecs::world world;

    auto e1 = world.entity().add<Struct_w_string>();
    auto e2 = world.entity().add<Struct_w_string>();

    const Struct_w_string *str1 = e1.get<Struct_w_string>();
    test_assert(str1 != NULL);
    test_assert(str1->value == "");
    const Struct_w_string *str2 = e2.get<Struct_w_string>();
    test_assert(str2 != NULL);
    test_assert(str2->value == "");

    e1.remove<Struct_w_string>();
    str1 = e1.get<Struct_w_string>();
    test_assert(str1 == NULL);

    str2 = e2.get<Struct_w_string>();
    test_assert(str2 != NULL);
    test_assert(str2->value == "");

    e2.remove<Struct_w_string>();
    str2 = e2.get<Struct_w_string>();
    test_assert(str2 == NULL);
}

void ComponentLifecycle_struct_w_string_set_2_remove(void) {
    flecs::world world;

    auto e1 = world.entity().set<Struct_w_string>({"hello"});
    auto e2 = world.entity().set<Struct_w_string>({"world"});

    const Struct_w_string *str1 = e1.get<Struct_w_string>();
    test_assert(str1 != NULL);
    test_assert(str1->value == "hello");
    const Struct_w_string *str2 = e2.get<Struct_w_string>();
    test_assert(str2 != NULL);
    test_assert(str2->value == "world");

    e1.remove<Struct_w_string>();
    str1 = e1.get<Struct_w_string>();
    test_assert(str1 == NULL);

    str2 = e2.get<Struct_w_string>();
    test_assert(str2 != NULL);
    test_assert(str2->value == "world");

    e2.remove<Struct_w_string>();
    str2 = e2.get<Struct_w_string>();
    test_assert(str2 == NULL);
}

void ComponentLifecycle_struct_w_string_add_2_remove_w_tag(void) {
    flecs::world world;

    auto e1 = world.entity().add<Tag>().add<Struct_w_string>();
    auto e2 = world.entity().add<Tag>().add<Struct_w_string>();

    const Struct_w_string *str1 = e1.get<Struct_w_string>();
    test_assert(str1 != NULL);
    test_assert(str1->value == "");
    const Struct_w_string *str2 = e2.get<Struct_w_string>();
    test_assert(str2 != NULL);
    test_assert(str2->value == "");

    e1.remove<Struct_w_string>();
    str1 = e1.get<Struct_w_string>();
    test_assert(str1 == NULL);

    str2 = e2.get<Struct_w_string>();
    test_assert(str2 != NULL);
    test_assert(str2->value == "");

    e2.remove<Struct_w_string>();
    str2 = e2.get<Struct_w_string>();
    test_assert(str2 == NULL);
}

void ComponentLifecycle_struct_w_string_set_2_remove_w_tag(void) {
    flecs::world world;

    auto e1 = world.entity().add<Tag>().set<Struct_w_string>({"hello"});
    auto e2 = world.entity().add<Tag>().set<Struct_w_string>({"world"});

    const Struct_w_string *str1 = e1.get<Struct_w_string>();
    test_assert(str1 != NULL);
    test_assert(str1->value == "hello");
    const Struct_w_string *str2 = e2.get<Struct_w_string>();
    test_assert(str2 != NULL);
    test_assert(str2->value == "world");

    e1.remove<Struct_w_string>();
    str1 = e1.get<Struct_w_string>();
    test_assert(str1 == NULL);

    str2 = e2.get<Struct_w_string>();
    test_assert(str2 != NULL);
    test_assert(str2->value == "world");

    e2.remove<Struct_w_string>();
    str2 = e2.get<Struct_w_string>();
    test_assert(str2 == NULL);
}

void ComponentLifecycle_struct_w_vector_add(void) {
    flecs::world world;

    auto e = world.entity().add<Struct_w_vector>();
    test_assert(e.has<Struct_w_vector>());

    const Struct_w_vector *ptr = e.get<Struct_w_vector>();
    test_assert(ptr != NULL);
    test_int(ptr->value.size(), 0);
}

void ComponentLifecycle_struct_w_vector_remove(void) {
    flecs::world world;

    auto e = world.entity().add<Struct_w_vector>();
    test_assert(e.has<Struct_w_vector>());

    e.remove<Struct_w_vector>();
    test_assert(!e.has<Struct_w_vector>());
}

void ComponentLifecycle_struct_w_vector_set(void) {
    flecs::world world;

    auto e = world.entity().set<Struct_w_vector>({std::vector<int>{1, 2}});
    test_assert(e.has<Struct_w_vector>());

    const Struct_w_vector *ptr = e.get<Struct_w_vector>();
    test_assert(ptr != NULL);
    test_int(ptr->value.size(), 2);
    test_int(ptr->value.at(0), 1);
    test_int(ptr->value.at(1), 2);
}

void ComponentLifecycle_struct_w_vector_override(void) {
    flecs::world world;

    auto base = world.entity().set<Struct_w_vector>({std::vector<int>{1, 2}});
    test_assert(base.has<Struct_w_vector>());

    auto e = world.entity().is_a(base).add<Struct_w_vector>();
    test_assert(e.has<Struct_w_vector>());

    const Struct_w_vector *ptr = base.get<Struct_w_vector>();
    test_assert(ptr != NULL);
    test_int(ptr->value.size(), 2);
    test_int(ptr->value.at(0), 1);
    test_int(ptr->value.at(1), 2);

    ptr = e.get<Struct_w_vector>();
    test_assert(ptr != NULL);
    test_int(ptr->value.size(), 2);
    test_int(ptr->value.at(0), 1);
    test_int(ptr->value.at(1), 2);
}

void ComponentLifecycle_struct_w_vector_add_2_remove(void) {
    flecs::world world;

    auto e1 = world.entity().add<Struct_w_vector>();
    auto e2 = world.entity().add<Struct_w_vector>();

    const Struct_w_vector *ptr1 = e1.get<Struct_w_vector>();
    test_assert(ptr1 != NULL);
    test_int(ptr1->value.size(), 0);
    const Struct_w_vector *ptr2 = e2.get<Struct_w_vector>();
    test_assert(ptr2 != NULL);
    test_int(ptr2->value.size(), 0);

    e1.remove<Struct_w_vector>();
    ptr1 = e1.get<Struct_w_vector>();
    test_assert(ptr1 == NULL);

    ptr2 = e2.get<Struct_w_vector>();
    test_assert(ptr2 != NULL);
    test_int(ptr2->value.size(), 0);

    e2.remove<Struct_w_vector>();
    ptr2 = e2.get<Struct_w_vector>();
    test_assert(ptr2 == NULL);
}

void ComponentLifecycle_struct_w_vector_set_2_remove(void) {
    flecs::world world;

    auto e1 = world.entity().set<Struct_w_vector>({std::vector<int>{1, 2}});
    auto e2 = world.entity().set<Struct_w_vector>({std::vector<int>{3, 4}});

    const Struct_w_vector *ptr1 = e1.get<Struct_w_vector>();
    test_assert(ptr1 != NULL);
    test_int(ptr1->value.size(), 2);
    test_int(ptr1->value.at(0), 1);
    test_int(ptr1->value.at(1), 2);
    const Struct_w_vector *ptr2 = e2.get<Struct_w_vector>();
    test_assert(ptr2 != NULL);
    test_int(ptr2->value.size(), 2);
    test_int(ptr2->value.at(0), 3);
    test_int(ptr2->value.at(1), 4);

    e1.remove<Struct_w_vector>();
    ptr1 = e1.get<Struct_w_vector>();
    test_assert(ptr1 == NULL);

    ptr2 = e2.get<Struct_w_vector>();
    test_assert(ptr2 != NULL);
    test_int(ptr2->value.size(), 2);
    test_int(ptr2->value.at(0), 3);
    test_int(ptr2->value.at(1), 4);

    e2.remove<Struct_w_vector>();
    ptr2 = e2.get<Struct_w_vector>();
    test_assert(ptr2 == NULL);
}

void ComponentLifecycle_struct_w_vector_add_2_remove_w_tag(void) {
    flecs::world world;

    auto e1 = world.entity().add<Tag>().add<Struct_w_vector>();
    auto e2 = world.entity().add<Tag>().add<Struct_w_vector>();

    const Struct_w_vector *ptr1 = e1.get<Struct_w_vector>();
    test_assert(ptr1 != NULL);
    test_int(ptr1->value.size(), 0);
    const Struct_w_vector *ptr2 = e2.get<Struct_w_vector>();
    test_assert(ptr2 != NULL);
    test_int(ptr2->value.size(), 0);

    e1.remove<Struct_w_vector>();
    ptr1 = e1.get<Struct_w_vector>();
    test_assert(ptr1 == NULL);

    ptr2 = e2.get<Struct_w_vector>();
    test_assert(ptr2 != NULL);
    test_int(ptr2->value.size(), 0);

    e2.remove<Struct_w_vector>();
    ptr2 = e2.get<Struct_w_vector>();
    test_assert(ptr2 == NULL);
}

void ComponentLifecycle_struct_w_vector_set_2_remove_w_tag(void) {
    flecs::world world;

    auto e1 = world.entity().add<Tag>().set<Struct_w_vector>({std::vector<int>{1, 2}});
    auto e2 = world.entity().add<Tag>().set<Struct_w_vector>({std::vector<int>{3, 4}});

    const Struct_w_vector *ptr1 = e1.get<Struct_w_vector>();
    test_assert(ptr1 != NULL);
    test_int(ptr1->value.size(), 2);
    test_int(ptr1->value.at(0), 1);
    test_int(ptr1->value.at(1), 2);
    const Struct_w_vector *ptr2 = e2.get<Struct_w_vector>();
    test_assert(ptr2 != NULL);
    test_int(ptr2->value.size(), 2);
    test_int(ptr2->value.at(0), 3);
    test_int(ptr2->value.at(1), 4);

    e1.remove<Struct_w_vector>();
    ptr1 = e1.get<Struct_w_vector>();
    test_assert(ptr1 == NULL);

    ptr2 = e2.get<Struct_w_vector>();
    test_assert(ptr2 != NULL);
    test_int(ptr2->value.size(), 2);
    test_int(ptr2->value.at(0), 3);
    test_int(ptr2->value.at(1), 4);

    e2.remove<Struct_w_vector>();
    ptr2 = e2.get<Struct_w_vector>();
    test_assert(ptr2 == NULL);
}

void ComponentLifecycle_get_mut_new(void) {
    flecs::world world;

    world.component<Pod>();

    auto e = world.entity();
    test_assert(e.id() != 0);

    Pod* value = e.get_mut<Pod>();
    test_assert(value != NULL);

    Pod::ctor_invoked = 1;
    Pod::dtor_invoked = 0;
    Pod::copy_invoked = 0;
    Pod::move_invoked = 0;

    e.modified<Pod>();

    Pod::ctor_invoked = 1;
    Pod::dtor_invoked = 0;
    Pod::copy_invoked = 0;
    Pod::move_invoked = 0;    
}

void ComponentLifecycle_get_mut_existing(void) {
    flecs::world world;

    world.component<Pod>();

    auto e = world.entity();
    test_assert(e.id() != 0);

    Pod* value = e.get_mut<Pod>();
    test_assert(value != NULL);

    Pod::ctor_invoked = 1;
    Pod::dtor_invoked = 0;
    Pod::copy_invoked = 0;
    Pod::move_invoked = 0;

    value = e.get_mut<Pod>();
    test_assert(value != NULL);

    /* Repeated calls to get_mut should not invoke constructor */
    Pod::ctor_invoked = 1;
    Pod::dtor_invoked = 0;
    Pod::copy_invoked = 0;
    Pod::move_invoked = 0;
}

void ComponentLifecycle_implicit_component(void) {
    flecs::world world;

    auto e = world.entity().add<Pod>();
    test_assert(e.id() != 0);
    test_assert(e.has<Pod>());
    test_int(Pod::ctor_invoked, 1);

    const Pod *pod = e.get<Pod>();
    test_assert(pod != NULL);

    test_int(pod->value, 10);

    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 0);
    test_int(Pod::copy_invoked, 0);
    test_int(Pod::move_invoked, 0);

    world.entity().add<Pod>();

    test_int(Pod::ctor_invoked, 2);
    test_int(Pod::move_ctor_invoked, 0);
    test_int(Pod::move_invoked, 0);

    world.entity().add<Pod>();

    test_int(Pod::ctor_invoked, 3);
    test_int(Pod::move_ctor_invoked, 2);
    test_int(Pod::move_invoked, 0);
}

void ComponentLifecycle_implicit_after_query(void) {
    flecs::world world;

    world.query<Pod>();

    auto e = world.entity().add<Pod>();
    test_assert(e.id() != 0);
    test_assert(e.has<Pod>());
    test_int(Pod::ctor_invoked, 1);

    const Pod *pod = e.get<Pod>();
    test_assert(pod != NULL);

    test_int(pod->value, 10);

    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 0);
    test_int(Pod::copy_invoked, 0);
    test_int(Pod::move_invoked, 0);

    world.entity().add<Pod>();

    test_int(Pod::ctor_invoked, 2);
    test_int(Pod::move_ctor_invoked, 0);
    test_int(Pod::move_invoked, 0); 

    world.entity().add<Pod>();

    test_int(Pod::ctor_invoked, 3);
    test_int(Pod::move_ctor_invoked, 2);
    test_int(Pod::move_invoked, 0); 
}

template <typename T>
static void try_add(flecs::world& ecs) {
    flecs::entity e = ecs.entity().add<T>();
    
    test_assert(e.has<T>());

    const T *ptr = e.get<T>();
    test_int(ptr->x_, 99);

    e.remove<T>();
    test_assert(!e.has<T>());
}

template <typename T>
static void try_add_relation(flecs::world& ecs) {
    flecs::entity obj = ecs.entity();

    flecs::entity e = ecs.entity().add<T>(obj);
    test_assert(e.has<T>());

    const T *ptr = e.get<T>();
    test_int(ptr->x_, 89);

    e.remove<T>();
    test_assert(!e.has<T>());
}

template <typename T>
static void try_add_second(flecs::world& ecs) {
    flecs::entity rel = ecs.entity();

    flecs::entity e = ecs.entity().add_second<T>(rel);
    test_assert(e.has<T>());

    const T *ptr = e.get<T>();
    test_int(ptr->x_, 89);

    e.remove<T>();
    test_assert(!e.has<T>());
}

template <typename T>
static void try_set(flecs::world& ecs) {
    flecs::entity e = ecs.entity().set<T>({10});

    const T *ptr = e.get<T>();
    test_int(ptr->x_, 10);
}

template <typename T>
static void try_emplace(flecs::world& ecs) {
    flecs::entity e = ecs.entity().emplace<T>(10);

    const T *ptr = e.get<T>();
    test_int(ptr->x_, 10);
}

template <typename T>
static void try_set_default(flecs::world& ecs) {
    flecs::entity e = ecs.entity().set(T());

    const T *ptr = e.get<T>();
    test_int(ptr->x_, 99);

    e.remove<T>();
}

void ComponentLifecycle_deleted_copy(void) {
    flecs::world ecs;

    ecs.component<NoCopy>();

    try_add<NoCopy>(ecs);

    try_set<NoCopy>(ecs);
}

void ComponentLifecycle_no_default_ctor_emplace(void) {
    flecs::world ecs;

    ecs.component<NoDefaultCtor>();

    try_emplace<NoDefaultCtor>(ecs);
}

void ComponentLifecycle_default_init(void) {
    flecs::world ecs;

    ecs.component<DefaultInit>();

    try_add<DefaultInit>(ecs);

    try_set<DefaultInit>(ecs);   
}

void ComponentLifecycle_no_default_ctor_add(void) {
    install_test_abort();

    flecs::world ecs;

    ecs.component<NoDefaultCtor>();

    test_expect_abort();

    try_add<NoDefaultCtor>(ecs);  
}

void ComponentLifecycle_no_default_ctor_add_relation(void) {
    install_test_abort();

    flecs::world ecs;

    ecs.component<NoDefaultCtor>();

    test_expect_abort();

    try_add_relation<NoDefaultCtor>(ecs); 
}

void ComponentLifecycle_no_default_ctor_add_second(void) {
    install_test_abort();

    flecs::world ecs;

    ecs.component<NoDefaultCtor>();

    test_expect_abort();

    try_add_second<NoDefaultCtor>(ecs);
}

void ComponentLifecycle_no_default_ctor_set(void) {
    install_test_abort();

    flecs::world ecs;

    ecs.component<NoDefaultCtor>();

    test_expect_abort();

    try_set<NoDefaultCtor>(ecs);  
}

void ComponentLifecycle_no_copy_ctor(void) {
    flecs::world ecs;

    ecs.component<NoCopyCtor>();

    try_add<NoCopyCtor>(ecs);

    try_set<NoCopyCtor>(ecs); 
}

void ComponentLifecycle_no_move_ctor(void) {
    install_test_abort();

    flecs::world ecs;

    test_expect_abort();

    ecs.component<NoMoveCtor>();
}

void ComponentLifecycle_no_copy_assign(void) {
    flecs::world ecs;

    ecs.component<NoCopyAssign>();

    try_add<NoCopyAssign>(ecs);

    try_set<NoCopyAssign>(ecs);
}

void ComponentLifecycle_no_move_assign(void) {
    install_test_abort();

    flecs::world ecs;

    test_expect_abort();

    ecs.component<NoMoveAssign>();
}

void ComponentLifecycle_no_copy(void) {
    flecs::world ecs;

    ecs.component<NoCopy>();

    try_add<NoCopy>(ecs);

    try_set<NoCopy>(ecs);
}

void ComponentLifecycle_no_move(void) {
    install_test_abort();

    flecs::world ecs;

    test_expect_abort();

    ecs.component<NoMove>();
}

void ComponentLifecycle_no_dtor(void) {
    install_test_abort();

    flecs::world ecs;

    test_expect_abort();

    ecs.component<NoDtor>();
}

void ComponentLifecycle_default_ctor_w_value_ctor(void) {
    flecs::world ecs;

    ecs.component<DefaultCtorValueCtor>();

    try_add<DefaultCtorValueCtor>(ecs);

    try_set<DefaultCtorValueCtor>(ecs);

    try_set_default<FlecsCtorDefaultCtor>(ecs);
}

void ComponentLifecycle_no_default_ctor_move_ctor_on_set(void) {
    flecs::world ecs;

    ecs.component<CountNoDefaultCtor>();

    // Emplace, construct
    auto e = ecs.entity().emplace<CountNoDefaultCtor>(10);
    test_assert(e.has<CountNoDefaultCtor>());

    const CountNoDefaultCtor* ptr = e.get<CountNoDefaultCtor>();
    test_assert(ptr != NULL);
    test_int(ptr->value, 10);

    test_int(CountNoDefaultCtor::ctor_invoked, 1);
    test_int(CountNoDefaultCtor::dtor_invoked, 0);
    test_int(CountNoDefaultCtor::copy_invoked, 0);
    test_int(CountNoDefaultCtor::move_invoked, 0);
    test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
    test_int(CountNoDefaultCtor::move_ctor_invoked, 0);

    // Set, move assign
    e.set<CountNoDefaultCtor>({10});

    test_int(CountNoDefaultCtor::ctor_invoked, 2);
    test_int(CountNoDefaultCtor::dtor_invoked, 1);
    test_int(CountNoDefaultCtor::copy_invoked, 0);
    test_int(CountNoDefaultCtor::move_invoked, 1);
    test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
    test_int(CountNoDefaultCtor::move_ctor_invoked, 0);    
}

void ComponentLifecycle_emplace_w_ctor(void) {
    flecs::world ecs;

    auto e = ecs.entity()
        .emplace<Pod>(10);

    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 0);

    const Pod *ptr = e.get<Pod>();
    test_assert(ptr != NULL);
    test_int(ptr->value, 10);

    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 0);    
}

void ComponentLifecycle_emplace_no_default_ctor(void) {
    flecs::world ecs;

    auto e = ecs.entity()
        .emplace<CountNoDefaultCtor>(10);

    test_int(CountNoDefaultCtor::ctor_invoked, 1);
    test_int(CountNoDefaultCtor::dtor_invoked, 0);

    const CountNoDefaultCtor *ptr = e.get<CountNoDefaultCtor>();
    test_assert(ptr != NULL);
    test_int(ptr->value, 10);

    test_int(CountNoDefaultCtor::ctor_invoked, 1);
    test_int(CountNoDefaultCtor::dtor_invoked, 0); 
}

void ComponentLifecycle_emplace_defer_use_move_ctor(void) {
    {
        flecs::world ecs;

        auto e = ecs.entity();

        ecs.defer_begin();
        e.emplace<CountNoDefaultCtor>(10);
        test_assert(!e.has<CountNoDefaultCtor>());
        test_int(CountNoDefaultCtor::ctor_invoked, 1);
        test_int(CountNoDefaultCtor::dtor_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 0);
        ecs.defer_end();

        test_assert(e.has<CountNoDefaultCtor>());
        test_int(CountNoDefaultCtor::ctor_invoked, 1);
        test_int(CountNoDefaultCtor::dtor_invoked, 1);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 1);

        const CountNoDefaultCtor *ptr = e.get<CountNoDefaultCtor>();
        test_assert(ptr != NULL);
        test_int(ptr->value, 10);

        test_int(CountNoDefaultCtor::ctor_invoked, 1);
        test_int(CountNoDefaultCtor::dtor_invoked, 1);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 1);
    }

    test_int(CountNoDefaultCtor::ctor_invoked, 1);
    test_int(CountNoDefaultCtor::dtor_invoked, 2);
    test_int(CountNoDefaultCtor::move_invoked, 0);
    test_int(CountNoDefaultCtor::move_ctor_invoked, 1);
}

void ComponentLifecycle_emplace_existing(void) {
    install_test_abort();

    flecs::world ecs;

    auto e = ecs.entity()
        .emplace<Pod>(10);

    const Pod *ptr = e.get<Pod>();
    test_assert(ptr != NULL);
    test_int(ptr->value, 10);    

    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 0);

    test_expect_abort();
    e.emplace<Pod>(20);
}

void ComponentLifecycle_emplace_singleton(void) {
    flecs::world ecs;

    ecs.emplace<Pod>(10);

    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 0);

    const Pod *ptr = ecs.get<Pod>();
    test_assert(ptr != NULL);
    test_int(ptr->value, 10);

    test_int(Pod::ctor_invoked, 1);
    test_int(Pod::dtor_invoked, 0); 
}

class CtorDtorNonTrivial {
public:
    CtorDtorNonTrivial(int x) : x_(x) {
        ctor_invoked ++;
    }

    ~CtorDtorNonTrivial() {
        dtor_invoked ++;
        dtor_value = x_;
    }

    int x_;
    std::string str_;

    static int ctor_invoked;
    static int dtor_invoked;
    static int dtor_value;
};

int CtorDtorNonTrivial::ctor_invoked;
int CtorDtorNonTrivial::dtor_invoked;
int CtorDtorNonTrivial::dtor_value;

void ComponentLifecycle_dtor_w_non_trivial_implicit_move(void) {
    flecs::world ecs;

    test_bool(std::is_trivially_move_assignable<CtorDtorNonTrivial>::value, false);
    test_bool(std::is_move_assignable<CtorDtorNonTrivial>::value, true);

    auto e_1 = ecs.entity().emplace<CtorDtorNonTrivial>(10);
    auto e_2 = ecs.entity().emplace<CtorDtorNonTrivial>(20);

    const CtorDtorNonTrivial *ptr = e_1.get<CtorDtorNonTrivial>();
    test_assert(ptr != nullptr);
    test_int(ptr->x_, 10);

    ptr = e_2.get<CtorDtorNonTrivial>();
    test_assert(ptr != nullptr);
    test_int(ptr->x_, 20);

    test_int(CtorDtorNonTrivial::ctor_invoked, 2);

    // Moves e_2 to e_1
    e_1.destruct();

    test_int(CtorDtorNonTrivial::ctor_invoked, 2);
    test_int(CtorDtorNonTrivial::dtor_invoked, 1);

    // Counter intuitive but correct. The class is not trivially movable, so
    // the move assignment should take care of cleaning up e_1 (10). That still
    // leaves the original e_2 which was moved from, but not destructed.
    //
    // In a real application the class should probably implement its own move
    // assignment to ensure correct destructor behavior.
    test_int(CtorDtorNonTrivial::dtor_value, 20);
}

class CtorDtor_w_MoveAssign {
public:
    CtorDtor_w_MoveAssign(int x) : x_(x) {
        ctor_invoked ++;
    }

    ~CtorDtor_w_MoveAssign() {
        dtor_invoked ++;
        dtor_value = x_;
    }

    CtorDtor_w_MoveAssign(const CtorDtor_w_MoveAssign& obj) = default;
    CtorDtor_w_MoveAssign(CtorDtor_w_MoveAssign&& obj) = default;
    CtorDtor_w_MoveAssign& operator=(const CtorDtor_w_MoveAssign& obj) = default;

    CtorDtor_w_MoveAssign& operator=(CtorDtor_w_MoveAssign&& obj) {
        move_value = this->x_;

        this->x_ = obj.x_;
        obj.x_ = 0;
        return *this;
    }    

    int x_;
    std::string str_;

    static int ctor_invoked;
    static int dtor_invoked;
    static int dtor_value;
    static int move_value;
};

int CtorDtor_w_MoveAssign::ctor_invoked;
int CtorDtor_w_MoveAssign::dtor_invoked;
int CtorDtor_w_MoveAssign::dtor_value;
int CtorDtor_w_MoveAssign::move_value;

void ComponentLifecycle_dtor_w_non_trivial_explicit_move(void) {
    flecs::world ecs;

    test_bool(std::is_trivially_move_assignable<CtorDtor_w_MoveAssign>::value, false);
    test_bool(std::is_move_assignable<CtorDtor_w_MoveAssign>::value, true);

    auto e_1 = ecs.entity().emplace<CtorDtor_w_MoveAssign>(10);
    auto e_2 = ecs.entity().emplace<CtorDtor_w_MoveAssign>(20);

    const CtorDtor_w_MoveAssign *ptr = e_1.get<CtorDtor_w_MoveAssign>();
    test_assert(ptr != nullptr);
    test_int(ptr->x_, 10);

    ptr = e_2.get<CtorDtor_w_MoveAssign>();
    test_assert(ptr != nullptr);
    test_int(ptr->x_, 20);

    test_int(CtorDtor_w_MoveAssign::ctor_invoked, 2);

    // Moves e_2 to e_1
    e_1.destruct();

    test_int(CtorDtor_w_MoveAssign::ctor_invoked, 2);
    test_int(CtorDtor_w_MoveAssign::dtor_invoked, 1);

    test_int(CtorDtor_w_MoveAssign::move_value, 10);
    test_int(CtorDtor_w_MoveAssign::dtor_value, 0);
}

void ComponentLifecycle_grow_no_default_ctor(void) {
	{
        flecs::world world;

        world.component<CountNoDefaultCtor>();

        auto e1 = world.entity().emplace<CountNoDefaultCtor>(1);
        auto e2 = world.entity().emplace<CountNoDefaultCtor>(2);

        test_int(CountNoDefaultCtor::ctor_invoked, 2);
        test_int(CountNoDefaultCtor::dtor_invoked, 0);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 0);

        auto e3 = world.entity().emplace<CountNoDefaultCtor>(3);
        
        test_int(CountNoDefaultCtor::ctor_invoked, 3);
        test_int(CountNoDefaultCtor::dtor_invoked, 2);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 2);

        test_assert(e1.has<CountNoDefaultCtor>());
        test_assert(e2.has<CountNoDefaultCtor>());
        test_assert(e3.has<CountNoDefaultCtor>());

        test_int(e1.get<CountNoDefaultCtor>()->value, 1);
        test_int(e2.get<CountNoDefaultCtor>()->value, 2);
        test_int(e3.get<CountNoDefaultCtor>()->value, 3);
    }

    test_int(CountNoDefaultCtor::ctor_invoked, 3);
    test_int(CountNoDefaultCtor::dtor_invoked, 5);
    test_int(CountNoDefaultCtor::copy_invoked, 0);
    test_int(CountNoDefaultCtor::move_invoked, 0);
    test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
    test_int(CountNoDefaultCtor::move_ctor_invoked, 2);
}

void ComponentLifecycle_grow_no_default_ctor_move(void) {
    {
        flecs::world world;

        world.component<CountNoDefaultCtor>();
        world.component<Tag>();

        auto e1 = world.entity().emplace<CountNoDefaultCtor>(1);
        auto e2 = world.entity().emplace<CountNoDefaultCtor>(2);

        test_int(CountNoDefaultCtor::ctor_invoked, 2);
        test_int(CountNoDefaultCtor::dtor_invoked, 0);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 0);

        CountNoDefaultCtor::reset();
        auto e3 = world.entity().emplace<CountNoDefaultCtor>(3);

        test_int(CountNoDefaultCtor::ctor_invoked, 1);
        test_int(CountNoDefaultCtor::dtor_invoked, 2);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 2);

        test_assert(e1.has<CountNoDefaultCtor>());
        test_assert(e2.has<CountNoDefaultCtor>());
        test_assert(e3.has<CountNoDefaultCtor>());

        test_int(e1.get<CountNoDefaultCtor>()->value, 1);
        test_int(e2.get<CountNoDefaultCtor>()->value, 2);
        test_int(e3.get<CountNoDefaultCtor>()->value, 3);

        CountNoDefaultCtor::reset();
        e1.add<Tag>();

        test_int(CountNoDefaultCtor::ctor_invoked, 0);
        test_int(CountNoDefaultCtor::dtor_invoked, 1);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 1);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 1);

        CountNoDefaultCtor::reset();
        e2.add<Tag>();

        test_int(CountNoDefaultCtor::ctor_invoked, 0);
        test_int(CountNoDefaultCtor::dtor_invoked, 1);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 1);

        CountNoDefaultCtor::reset();
        e3.add<Tag>();

        test_int(CountNoDefaultCtor::ctor_invoked, 0);
        test_int(CountNoDefaultCtor::dtor_invoked, 3);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 3);

        CountNoDefaultCtor::reset();
    }

    test_int(CountNoDefaultCtor::ctor_invoked, 0);
    test_int(CountNoDefaultCtor::dtor_invoked, 3);
    test_int(CountNoDefaultCtor::copy_invoked, 0);
    test_int(CountNoDefaultCtor::move_invoked, 0);
    test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
    test_int(CountNoDefaultCtor::move_ctor_invoked, 0);
}

void ComponentLifecycle_grow_no_default_ctor_move_w_component(void) {
    {
        flecs::world world;

        world.component<CountNoDefaultCtor>();
        world.component<Position>();

        auto e1 = world.entity().emplace<CountNoDefaultCtor>(1);
        auto e2 = world.entity().emplace<CountNoDefaultCtor>(2);

        test_int(CountNoDefaultCtor::ctor_invoked, 2);
        test_int(CountNoDefaultCtor::dtor_invoked, 0);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 0);

        CountNoDefaultCtor::reset();
        auto e3 = world.entity().emplace<CountNoDefaultCtor>(3);

        test_int(CountNoDefaultCtor::ctor_invoked, 1);
        test_int(CountNoDefaultCtor::dtor_invoked, 2);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 2);

        test_assert(e1.has<CountNoDefaultCtor>());
        test_assert(e2.has<CountNoDefaultCtor>());
        test_assert(e3.has<CountNoDefaultCtor>());

        test_int(e1.get<CountNoDefaultCtor>()->value, 1);
        test_int(e2.get<CountNoDefaultCtor>()->value, 2);
        test_int(e3.get<CountNoDefaultCtor>()->value, 3);

        CountNoDefaultCtor::reset();
        e1.add<Position>();

        test_int(CountNoDefaultCtor::ctor_invoked, 0);
        test_int(CountNoDefaultCtor::dtor_invoked, 1);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 1);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 1);

        CountNoDefaultCtor::reset();
        e2.add<Position>(); // e2 is last element, e3 got moved to e1's location

        test_int(CountNoDefaultCtor::ctor_invoked, 0);
        test_int(CountNoDefaultCtor::dtor_invoked, 1);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 1);

        CountNoDefaultCtor::reset();
        e3.add<Position>();

        test_int(CountNoDefaultCtor::ctor_invoked, 0);
        test_int(CountNoDefaultCtor::dtor_invoked, 3); /* 2 (e1,e2) for resize, 1 (e3) for moved away storage */
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 3); /* resize */

        CountNoDefaultCtor::reset();
    }

    test_int(CountNoDefaultCtor::ctor_invoked, 0);
    test_int(CountNoDefaultCtor::dtor_invoked, 3);
    test_int(CountNoDefaultCtor::copy_invoked, 0);
    test_int(CountNoDefaultCtor::move_invoked, 0);
    test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
    test_int(CountNoDefaultCtor::move_ctor_invoked, 0);
}

void ComponentLifecycle_delete_no_default_ctor(void) {
	{
        flecs::world world;

        world.component<CountNoDefaultCtor>();

        auto e1 = world.entity().emplace<CountNoDefaultCtor>(1);
        auto e2 = world.entity().emplace<CountNoDefaultCtor>(2);
        auto e3 = world.entity().emplace<CountNoDefaultCtor>(3);
        
        test_int(CountNoDefaultCtor::ctor_invoked, 3);
        test_int(CountNoDefaultCtor::dtor_invoked, 2);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 0);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 2);

        test_assert(e1.has<CountNoDefaultCtor>());
        test_assert(e2.has<CountNoDefaultCtor>());
        test_assert(e3.has<CountNoDefaultCtor>());

        test_int(e1.get<CountNoDefaultCtor>()->value, 1);
        test_int(e2.get<CountNoDefaultCtor>()->value, 2);
        test_int(e3.get<CountNoDefaultCtor>()->value, 3);

        e2.destruct();

        test_int(CountNoDefaultCtor::ctor_invoked, 3);
        test_int(CountNoDefaultCtor::dtor_invoked, 3);
        test_int(CountNoDefaultCtor::copy_invoked, 0);
        test_int(CountNoDefaultCtor::move_invoked, 1);
        test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
        test_int(CountNoDefaultCtor::move_ctor_invoked, 2);
    }

    test_int(CountNoDefaultCtor::ctor_invoked, 3);
    test_int(CountNoDefaultCtor::dtor_invoked, 5);
    test_int(CountNoDefaultCtor::copy_invoked, 0);
    test_int(CountNoDefaultCtor::move_invoked, 1);
    test_int(CountNoDefaultCtor::copy_ctor_invoked, 0);
    test_int(CountNoDefaultCtor::move_ctor_invoked, 2);
}

void ComponentLifecycle_on_add_hook(void) {
    int count = 0;

    {
        flecs::world ecs;

        ecs.component<Position>().on_add([&](Position& p) {
            count ++;
        });

        test_int(0, count);

        auto e = ecs.entity().add<Position>();
        test_int(1, count);

        e.add<Position>();
        test_int(1, count);
    }

    test_int(1, count);
}

void ComponentLifecycle_on_remove_hook(void) {
    int count = 0;
    
    {
        flecs::world ecs;

        ecs.component<Position>().on_remove([&](Position& p) {
            count ++;
        });

        test_int(0, count);

        auto e1 = ecs.entity().add<Position>();
        ecs.entity().add<Position>();
        test_int(0, count);

        e1.remove<Position>();
        test_int(1, count);
    }

    test_int(2, count);
}

void ComponentLifecycle_on_set_hook(void) {
    int count = 0;
    Position v = {0};

    {
        flecs::world ecs;

        ecs.component<Position>().on_set([&](Position& p) {
            count ++;
            v = p;
        });

        test_int(0, count);

        auto e1 = ecs.entity().add<Position>();
        test_int(0, count);

        e1.set<Position>({10, 20});
        test_int(1, count);
        test_int(10, v.x);
        test_int(20, v.y);

        ecs.entity().set<Position>({30, 40});
        test_int(2, count);
        test_int(30, v.x);
        test_int(40, v.y);
    }

    test_int(2, count);
}

void ComponentLifecycle_on_add_hook_w_entity(void) {
    int count = 0;
    flecs::entity e_arg;

    {
        flecs::world ecs;

        ecs.component<Position>().on_add([&](flecs::entity arg, Position& p) {
            e_arg = arg;
            count ++;
        });

        test_int(0, count);
        test_assert(e_arg == 0);

        auto e = ecs.entity().add<Position>();
        test_int(1, count);
        test_assert(e_arg == e);

        e.add<Position>();
        test_int(1, count);
    }

    test_int(1, count);
}

void ComponentLifecycle_on_remove_hook_w_entity(void) {
    int count = 0;
    flecs::entity e_arg;
    flecs::entity e2;
    
    {
        flecs::world ecs;

        ecs.component<Position>().on_remove([&](flecs::entity arg, Position& p){
            e_arg = arg;
            count ++;
        });

        test_int(0, count);
        test_assert(e_arg == 0);

        auto e1 = ecs.entity().add<Position>();
        e2 = ecs.entity().add<Position>();
        test_int(0, count);

        e1.remove<Position>();
        test_int(1, count);
        test_assert(e_arg == e1);
    }

    test_int(2, count);
    test_assert(e_arg == e2);
}

void ComponentLifecycle_on_set_hook_w_entity(void) {
    int count = 0;
    Position v = {0};
    flecs::entity e_arg;

    {
        flecs::world ecs;

        ecs.component<Position>().on_set([&](flecs::entity arg, Position& p) {
            count ++;
            v = p;
            e_arg = arg;
        });

        test_int(0, count);

        auto e1 = ecs.entity().add<Position>();
        test_int(0, count);

        e1.set<Position>({10, 20});
        test_int(1, count);
        test_assert(e_arg == e1);
        test_int(10, v.x);
        test_int(20, v.y);

        auto e2 = ecs.entity().set<Position>({30, 40});
        test_int(2, count);
        test_assert(e_arg == e2);
        test_int(30, v.x);
        test_int(40, v.y);
    }

    test_int(2, count);
}

void ComponentLifecycle_chained_hooks(void) {
    flecs::world ecs;

    int32_t add_count = 0;
    int32_t remove_count = 0;
    int32_t set_count = 0;

    ecs.component<Position>()
        .on_add([&](Position& p){
            add_count ++;
        })
        .on_set([&](Position& p){
            set_count ++;
        })
        .on_remove([&](Position& p){
            remove_count ++;
        });

    auto e = ecs.entity();
    test_int(0, add_count);
    test_int(0, set_count);
    test_int(0, remove_count);

    e.add<Position>();
    test_int(1, add_count);
    test_int(0, set_count);
    test_int(0, remove_count);

    e.set<Position>({10, 20});
    test_int(1, add_count);
    test_int(1, set_count);
    test_int(0, remove_count);

    e.remove<Position>();
    test_int(1, add_count);
    test_int(1, set_count);
    test_int(1, remove_count);
}

void ComponentLifecycle_ctor_w_2_worlds(void) {
    {
        flecs::world ecs;

        test_int(Pod::ctor_invoked, 0);

        ecs.entity().add<Pod>();
        test_int(Pod::ctor_invoked, 1);
    }

    Pod::ctor_invoked = 0;

    {
        flecs::world ecs;

        test_int(Pod::ctor_invoked, 0);

        ecs.entity().add<Pod>();
        test_int(Pod::ctor_invoked, 1);
    }
}

void ComponentLifecycle_ctor_w_2_worlds_explicit_registration(void) {
    {
        flecs::world ecs;

        ecs.component<Pod>();
        test_int(Pod::ctor_invoked, 0);

        ecs.entity().add<Pod>();
        test_int(Pod::ctor_invoked, 1);
    }

    Pod::ctor_invoked = 0;

    {
        flecs::world ecs;

        ecs.component<Pod>();
        test_int(Pod::ctor_invoked, 0);

        ecs.entity().add<Pod>();
        test_int(Pod::ctor_invoked, 1);
    }
}

struct DeferEmplaceTest {
    double x, y;

    DeferEmplaceTest(double x_, double y_) {
        x = x_;
        y = y_;
    }
};

void ComponentLifecycle_defer_emplace(void) {
    flecs::world ecs;

    flecs::entity e = ecs.entity();

    ecs.defer_begin();
    e.emplace<DeferEmplaceTest>(10.0, 20.0);
    test_assert(!e.has<DeferEmplaceTest>());
    ecs.defer_end();
    test_assert(e.has<DeferEmplaceTest>());

    const DeferEmplaceTest *p = e.get<DeferEmplaceTest>();
    test_assert(p != NULL);
    test_int(p->x, 10);
    test_int(p->y, 20);
}

void ComponentLifecycle_emplace_w_on_add(void) {
    flecs::world ecs;

    flecs::entity e1 = ecs.entity();

    int on_add = 0;
    ecs.component<Position>()
        .on_add([&](flecs::entity e, Position&) {
            on_add = true;
            test_assert(e == e1);
        });

    e1.emplace<Position>();
    test_int(on_add, 1);
}

void ComponentLifecycle_emplace_w_on_add_existing(void) {
    flecs::world ecs;

    flecs::entity e1 = ecs.entity().add<Velocity>();
    
    int on_add = 0;
    ecs.component<Position>()
        .on_add([&](flecs::entity e, Position&) {
            on_add = true;
            test_assert(e == e1);
        });

    e1.emplace<Position>();
    test_int(on_add, 1);
}

void ComponentLifecycle_set_pair_no_copy(void) {
    flecs::world ecs;

    struct Tag { };

    flecs::entity e = ecs.entity()
        .set<NoCopy, Tag>({ 10 });

    const NoCopy *ptr = e.get<NoCopy, Tag>();
    test_assert(ptr != NULL);
    test_int(ptr->x_, 10);
}

void ComponentLifecycle_set_pair_w_entity_no_copy(void) {
    flecs::world ecs;

    flecs::entity tag = ecs.entity();

    flecs::entity e = ecs.entity()
        .set<NoCopy>(tag, { 10 });

    const NoCopy *ptr = e.get<NoCopy>(tag);
    test_assert(ptr != NULL);
    test_int(ptr->x_, 10);
}

void ComponentLifecycle_set_pair_second_no_copy(void) {
    flecs::world ecs;

    flecs::entity tag = ecs.entity();

    flecs::entity e = ecs.entity()
        .set_second<NoCopy>(tag, { 10 });

    const NoCopy *ptr = e.get_second<NoCopy>(tag);
    test_assert(ptr != NULL);
    test_int(ptr->x_, 10);
}

void ComponentLifecycle_set_override_no_copy(void) {
    flecs::world ecs;

    flecs::entity e = ecs.entity()
        .set_override<NoCopy>({ 10 });

    const NoCopy *ptr = e.get<NoCopy>();
    test_assert(ptr != NULL);
    test_int(ptr->x_, 10);

    test_assert(e.has(flecs::Override | ecs.id<NoCopy>()));
}

void ComponentLifecycle_set_override_pair_no_copy(void) {
    flecs::world ecs;

    flecs::entity e = ecs.entity()
        .set_override<NoCopy, Tag>({ 10 });

    const NoCopy *ptr = e.get<NoCopy, Tag>();
    test_assert(ptr != NULL);
    test_int(ptr->x_, 10);

    test_assert(e.has(flecs::Override | ecs.pair<NoCopy, Tag>()));
}

void ComponentLifecycle_set_override_pair_w_entity_no_copy(void) {
    flecs::world ecs;

    flecs::entity tag = ecs.entity();

    flecs::entity e = ecs.entity()
        .set_override<NoCopy>(tag, { 10 });

    const NoCopy *ptr = e.get<NoCopy>(tag);
    test_assert(ptr != NULL);
    test_int(ptr->x_, 10);

    test_assert(e.has(flecs::Override | ecs.pair<NoCopy>(tag)));
}

void ComponentLifecycle_dtor_after_defer_set(void) {
    {
        flecs::world ecs;

        auto e = ecs.entity();

        ecs.defer_begin();
        e.set<Pod>({10});
        test_assert(!e.has<Pod>());
        test_int(Pod::ctor_invoked, 2);
        test_int(Pod::dtor_invoked, 1);
        test_int(Pod::move_invoked, 1);
        test_int(Pod::move_ctor_invoked, 0);
        ecs.defer_end();

        test_assert(e.has<Pod>());
        test_int(Pod::ctor_invoked, 3);
        test_int(Pod::dtor_invoked, 2);
        test_int(Pod::move_invoked, 2);
        test_int(Pod::move_ctor_invoked, 0);

        const Pod *ptr = e.get<Pod>();
        test_assert(ptr != NULL);
        test_int(ptr->value, 10);

        test_int(Pod::ctor_invoked, 3);
        test_int(Pod::dtor_invoked, 2);
        test_int(Pod::move_invoked, 2);
        test_int(Pod::move_ctor_invoked, 0);
    }

    test_int(Pod::ctor_invoked, 3);
    test_int(Pod::dtor_invoked, 3);
    test_int(Pod::move_invoked, 2);
    test_int(Pod::move_ctor_invoked, 0);
}

void ComponentLifecycle_dtor_with_relation(void) {
    {
        flecs::world ecs;

        auto e = ecs.entity();
        auto e2 = ecs.entity().set<Pod>({5});

        e.set<Pod>({10}).add<Tag>(e2);

        test_int(Pod::ctor_invoked, 4);
        test_int(Pod::dtor_invoked, 3);
        test_int(Pod::move_invoked, 2);
        test_int(Pod::move_ctor_invoked, 1);

        const Pod *ptr = e.get<Pod>();
        test_assert(ptr != NULL);
        test_int(ptr->value, 10);

        test_int(Pod::ctor_invoked, 4);
        test_int(Pod::dtor_invoked, 3);
        test_int(Pod::move_invoked, 2);
        test_int(Pod::move_ctor_invoked, 1);
    }

    test_int(Pod::ctor_invoked, 5);
    test_int(Pod::dtor_invoked, 6);
    test_int(Pod::move_invoked, 4);
    test_int(Pod::move_ctor_invoked, 1);
}

void ComponentLifecycle_register_parent_after_child_w_hooks(void) {
    {
        flecs::world ecs;

        ecs.component<Pod::Child>();
        ecs.component<Pod>();

        ecs.entity().set<Pod>({});
    }

    test_int(Pod::ctor_invoked, 2);
    test_int(Pod::dtor_invoked, 2);
    test_int(Pod::move_invoked, 1);
    test_int(Pod::move_ctor_invoked, 0);
    test_int(Pod::copy_invoked, 0);
    test_int(Pod::copy_ctor_invoked, 0);
}

void ComponentLifecycle_register_parent_after_child_w_hooks_implicit(void) {
    {
        flecs::world ecs;

        ecs.entity().add<Pod::Child>().set<Pod>({});
    }

    test_int(Pod::ctor_invoked, 2);
    test_int(Pod::dtor_invoked, 2);
    test_int(Pod::move_invoked, 1);
    test_int(Pod::move_ctor_invoked, 0);
    test_int(Pod::copy_invoked, 0);
    test_int(Pod::copy_ctor_invoked, 0);
}
