#include <cpp_api.h>

struct Other {
    int32_t value;
};

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

    flecs::query<Position, Velocity> q = 
        ecs.query_builder<Position, Velocity>().build();

    auto e1 = ecs.entity().add<Position>().add<Velocity>();
    ecs.entity().add<Position>();

    int32_t count = 0;
    q.each([&](flecs::entity e, Position& p, Velocity& v) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    flecs::query<> q = ecs.query_builder<Position, Velocity>().build();

    auto e1 = ecs.entity().add<Position>().add<Velocity>();
    ecs.entity().add<Position>();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    flecs::query<> q = ecs.query_builder<>()
        .term<Position>()
        .term<Velocity>()
        .build();

    auto e1 = ecs.entity().add<Position>().add<Velocity>();
    ecs.entity().add<Position>();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    flecs::query<Position, Velocity> q = 
        ecs.query_builder<Position, Velocity>().build();

    auto e1 = ecs.entity().add<Position>().add<Velocity>();
    ecs.entity().add<Position>();

    int32_t count = 0;
    q.each([&](flecs::entity e, Position& p, Velocity& v) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto q = ecs.query_builder<Position, Velocity>().build();

    auto e1 = ecs.entity().add<Position>().add<Velocity>();
    ecs.entity().add<Position>();

    int32_t count = 0;
    q.each([&](flecs::entity e, Position& p, Velocity& v) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto qb = ecs.query_builder<>();
    qb.term<Position>();
    qb.term<Velocity>();
    auto q = qb.build();

    auto e1 = ecs.entity().add<Position>().add<Velocity>();
    ecs.entity().add<Position>();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto q = ecs.query_builder<Position>().build();

    auto e1 = ecs.entity().add<Position>();
    ecs.entity().add<Velocity>();

    int32_t count = 0;
    q.each([&](flecs::entity e, Position& p) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto q = ecs.query_builder<>()
        .term<Position>()
        .build();

    auto e1 = ecs.entity().add<Position>();
    ecs.entity().add<Velocity>();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto q = ecs.query_builder<>()
        .term<Position>()
        .term<Velocity>()
        .build();

    auto e1 = ecs.entity().add<Position>().add<Velocity>();
    ecs.entity().add<Velocity>();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto q = ecs.query_builder<Position>()
        .term<Velocity>()
        .build();

    auto e1 = ecs.entity().add<Position>().add<Velocity>();
    ecs.entity().add<Velocity>();

    int32_t count = 0;
    q.each([&](flecs::entity e, Position& p) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto q = ecs.query_builder<Position>()
        .term<Velocity>()
        .term<Mass>()
        .build();

    auto e1 = ecs.entity().add<Position>().add<Velocity>().add<Mass>();
    ecs.entity().add<Velocity>();

    int32_t count = 0;
    q.each([&](flecs::entity e, Position& p) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto Likes = ecs.entity();
    auto Bob = ecs.entity();
    auto Alice = ecs.entity();

    auto q = ecs.query_builder<>()
        .term(Likes, Bob)
        .build();

    auto e1 = ecs.entity().add(Likes, Bob);
    ecs.entity().add(Likes, Alice);

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto q = ecs.query_builder<Position>()
        .term<Velocity>().oper(flecs::Not)
        .build();

    auto e1 = ecs.entity().add<Position>();
    ecs.entity().add<Position>().add<Velocity>();

    int32_t count = 0;
    q.each([&](flecs::entity e, Position& p) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto q = ecs.query_builder<>()
        .term<Position>().oper(flecs::Or)
        .term<Velocity>()
        .build();

    auto e1 = ecs.entity().add<Position>();
    auto e2 = ecs.entity().add<Velocity>();
    ecs.entity().add<Mass>();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1 || e == e2);
    });
    
    test_int(count, 2);
}

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

    auto q = ecs.query_builder<>()
        .term<Position>()
        .term<Velocity>().oper(flecs::Optional)
        .build();

    auto e1 = ecs.entity().add<Position>();
    auto e2 = ecs.entity().add<Position>().add<Velocity>();
    ecs.entity().add<Velocity>().add<Mass>();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1 || e == e2);
    });
    
    test_int(count, 2);
}

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

    auto q = ecs.query_builder<Position, Velocity*>().build();

    auto e1 = ecs.entity().add<Position>();
    auto e2 = ecs.entity().add<Position>().add<Velocity>();
    ecs.entity().add<Velocity>().add<Mass>();

    int32_t count = 0;
    q.each([&](flecs::entity e, Position& p, Velocity* v) {
        count ++;
        test_assert(e == e1 || e == e2);
    });
    
    test_int(count, 2);
}

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

    auto q = ecs.query_builder<const Position>().build();

    auto e1 = ecs.entity().add<Position>();
    ecs.entity().add<Velocity>();

    int32_t count = 0;
    q.each([&](flecs::entity e, const Position& p) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    ecs.component<Position>();

    auto q = ecs.query_builder<>()
        .expr("Position")
        .build();

    auto e1 = ecs.entity().add<Position>();
    ecs.entity().add<Velocity>();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    ecs.set<Other>({10});

    auto q = ecs.query_builder<Self>()
        .term<Other>().singleton()
        .build();

    auto 
    e = ecs.entity(); e.set<Self>({e});
    e = ecs.entity(); e.set<Self>({e});
    e = ecs.entity(); e.set<Self>({e});

    int32_t count = 0;

    q.iter([&](flecs::iter& it, Self *s) {
        auto o = it.field<const Other>(2);
        test_assert(!it.is_self(2));
        test_int(o->value, 10);
        
        const Other& o_ref = *o;
        test_int(o_ref.value, 10);

        for (auto i : it) {
            test_assert(it.entity(i) == s[i].value);
            count ++;
        }
    });
    
    test_int(count, 3);
}

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

    auto q = ecs.query_builder<Self>()
        .term<Other>().src().up()
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e});
    e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e});
    e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e});

    int32_t count = 0;

    q.iter([&](flecs::iter& it, Self *s) {
        auto o = it.field<const Other>(2);
        test_assert(!it.is_self(2));
        test_int(o->value, 10);

        for (auto i : it) {
            test_assert(it.entity(i) == s[i].value);
            count ++;
        }
    });
    
    test_int(count, 3);
}

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

    auto q = ecs.query_builder<Self>()
        .term<Other>().src().self().up()
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e});
    e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e});
    e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e});
    e = ecs.entity().set<Other>({20}); e.set<Self>({e});
    e = ecs.entity().set<Other>({20}); e.set<Self>({e});

    int32_t count = 0;
    int32_t owned_count = 0;

    q.iter([&](flecs::iter& it, Self *s) {
        auto o = it.field<const Other>(2);

        if (!it.is_self(2)) {
            test_int(o->value, 10);
        } else {
            for (auto i : it) {
                test_int(o[i].value, 20);
                owned_count ++;
            }
        }

        for (auto i : it) {
            test_assert(it.entity(i) == s[i].value);
            count ++;
        }
    });
    
    test_int(count, 5);
    test_int(owned_count, 2);
}

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

    auto q = ecs.query_builder<Self>()
        .term<Other>().src().up(flecs::ChildOf)
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().child_of(base); e.set<Self>({e});

    int32_t count = 0;

    q.iter([&](flecs::iter& it, Self *s) {
        auto o = it.field<const Other>(2);
        test_assert(!it.is_self(2));
        test_int(o->value, 10);

        for (auto i : it) {
            test_assert(it.entity(i) == s[i].value);
            count ++;
        }
    });
    
    test_int(count, 3);
}

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

    auto q = ecs.query_builder<Self>()
        .term<Other>().src().self().up(flecs::ChildOf)
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().set<Other>({20}); e.set<Self>({e});
    e = ecs.entity().set<Other>({20}); e.set<Self>({e});

    int32_t count = 0;
    int32_t owned_count = 0;

    q.iter([&](flecs::iter& it, Self *s) {
        auto o = it.field<const Other>(2);

        if (!it.is_self(2)) {
            test_int(o->value, 10);
        } else {
            for (auto i : it) {
                test_int(o[i].value, 20);
                owned_count ++;
            }
        }

        for (auto i : it) {
            test_assert(it.entity(i) == s[i].value);
            count ++;
        }
    });
    
    test_int(count, 5);
    test_int(owned_count, 2);
}

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

    auto q = ecs.query_builder<Self, Other>()
        .arg(2).src().up()
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e});
    e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e});
    e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e});

    int32_t count = 0;

    q.each([&](flecs::entity e, Self& s, Other& o) {
        test_assert(e == s.value);
        test_int(o.value, 10);
        count ++;
    });
    
    test_int(count, 3);
}

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

    auto q = ecs.query_builder<Self, Other>()
        .arg(2).src().self().up()
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e});
    e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e});
    e = ecs.entity().add(flecs::IsA, base); e.set<Self>({e});
    e = ecs.entity().set<Other>({10}); e.set<Self>({e});
    e = ecs.entity().set<Other>({10}); e.set<Self>({e});

    int32_t count = 0;

    q.each([&](flecs::entity e, Self& s, Other& o) {
        test_assert(e == s.value);
        test_int(o.value, 10);
        count ++;
    });
    
    test_int(count, 5);
}

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

    auto q = ecs.query_builder<Self, Other>()
        .arg(2).src().up(flecs::ChildOf)
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().child_of(base); e.set<Self>({e});

    int32_t count = 0;

    q.each([&](flecs::entity e, Self& s, Other& o) {
        test_assert(e == s.value);
        test_int(o.value, 10);
        count ++;
    });
    
    test_int(count, 3);
}

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

    auto q = ecs.query_builder<Self, Other>()
        .arg(2).src().self().up(flecs::ChildOf)
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().set<Other>({10}); e.set<Self>({e});
    e = ecs.entity().set<Other>({10}); e.set<Self>({e});

    int32_t count = 0;

    q.each([&](flecs::entity e, Self& s, Other& o) {
        test_assert(e == s.value);
        test_int(o.value, 10);
        count ++;
    });
    
    test_int(count, 5);
}

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

    auto q = ecs.query_builder<Self, Other>()
        .arg(2).up()
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().is_a(base); e.set<Self>({e});
    e = ecs.entity().is_a(base); e.set<Self>({e});
    e = ecs.entity().is_a(base); e.set<Self>({e});

    int32_t count = 0;

    q.each([&](flecs::entity e, Self& s, Other& o) {
        test_assert(e == s.value);
        test_int(o.value, 10);
        count ++;
    });
    
    test_int(count, 3);
}

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

    auto q = ecs.query_builder<Self, Other>()
        .arg(2).self().up(flecs::IsA)
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().is_a(base); e.set<Self>({e});
    e = ecs.entity().is_a(base); e.set<Self>({e});
    e = ecs.entity().is_a(base); e.set<Self>({e});
    e = ecs.entity().set<Other>({10}); e.set<Self>({e});
    e = ecs.entity().set<Other>({10}); e.set<Self>({e});

    int32_t count = 0;

    q.each([&](flecs::entity e, Self& s, Other& o) {
        test_assert(e == s.value);
        test_int(o.value, 10);
        count ++;
    });
    
    test_int(count, 5);
}

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

    auto q = ecs.query_builder<Self, Other>()
        .arg(2).up(flecs::ChildOf)
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().child_of(base); e.set<Self>({e});

    int32_t count = 0;

    q.each([&](flecs::entity e, Self& s, Other& o) {
        test_assert(e == s.value);
        test_int(o.value, 10);
        count ++;
    });
    
    test_int(count, 3);
}

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

    auto q = ecs.query_builder<Self, Other>()
        .arg(2).self().up(flecs::ChildOf)
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().child_of(base); e.set<Self>({e});
    e = ecs.entity().set<Other>({10}); e.set<Self>({e});
    e = ecs.entity().set<Other>({10}); e.set<Self>({e});

    int32_t count = 0;

    q.each([&](flecs::entity e, Self& s, Other& o) {
        test_assert(e == s.value);
        test_int(o.value, 10);
        count ++;
    });
    
    test_int(count, 5);
}

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

    auto Likes = ecs.entity();
    auto Bob = ecs.entity();
    auto Alice = ecs.entity();

    auto q = ecs.query_builder<Self>()
        .term(Likes, Bob)
        .build();

    auto 
    e = ecs.entity().add(Likes, Bob); e.set<Self>({e});
    e = ecs.entity().add(Likes, Bob); e.set<Self>({e});

    e = ecs.entity().add(Likes, Alice); e.set<Self>({0});
    e = ecs.entity().add(Likes, Alice); e.set<Self>({0});

    int32_t count = 0;

    q.each([&](flecs::entity e, Self& s) {
        test_assert(e == s.value);
        count ++;
    });
    
    test_int(count, 2);
}

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

    auto Likes = ecs.entity();
    auto Bob = ecs.entity();
    auto Alice = ecs.entity();

    auto q = ecs.query_builder<Self>()
        .term(Likes, flecs::Wildcard)
        .build();

    auto 
    e = ecs.entity().add(Likes, Bob); e.set<Self>({e});
    e = ecs.entity().add(Likes, Bob); e.set<Self>({e});

    e = ecs.entity().add(Likes, Alice); e.set<Self>({e});
    e = ecs.entity().add(Likes, Alice); e.set<Self>({e});

    e = ecs.entity(); e.set<Self>({0});
    e = ecs.entity(); e.set<Self>({0});

    int32_t count = 0;

    q.each([&](flecs::entity e, Self& s) {
        test_assert(e == s.value);
        count ++;
    });
    
    test_int(count, 4);
}

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

    auto Likes = ecs.entity();
    auto Dislikes = ecs.entity();
    auto Bob = ecs.entity();
    auto Alice = ecs.entity();

    auto q = ecs.query_builder<Self>()
        .term(flecs::Wildcard, Alice)
        .build();

    auto 
    e = ecs.entity().add(Likes, Alice); e.set<Self>({e});
    e = ecs.entity().add(Dislikes, Alice); e.set<Self>({e});

    e = ecs.entity().add(Likes, Bob); e.set<Self>({0});
    e = ecs.entity().add(Dislikes, Bob); e.set<Self>({0});

    int32_t count = 0;

    q.each([&](flecs::entity e, Self& s) {
        test_assert(e == s.value);
        count ++;
    });
    
    test_int(count, 2);
}

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

    struct Likes { };
    
    auto Dislikes = ecs.entity();
    auto Bob = ecs.entity();
    auto Alice = ecs.entity();

    auto q = ecs.query_builder<Self>()
        .term<Likes>(flecs::Wildcard)
        .build();

    auto 
    e = ecs.entity().add<Likes>(Alice); e.set<Self>({e});
    e = ecs.entity().add(Dislikes, Alice); e.set<Self>({0});

    e = ecs.entity().add<Likes>(Bob); e.set<Self>({e});
    e = ecs.entity().add(Dislikes, Bob); e.set<Self>({0});

    int32_t count = 0;

    q.each([&](flecs::entity e, Self& s) {
        test_assert(e == s.value);
        count ++;
    });
    
    test_int(count, 2);
}

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

    auto q = ecs.query_builder<Position>()
        .term<Template<int>>()
        .build();

    auto e1 = ecs.entity().add<Position>().add<Template<int>>();
    ecs.entity().add<Position>();

    int32_t count = 0;
    q.each([&](flecs::entity e, Position& p) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto q = ecs.query_builder<Position>()
        .term<Position>().id(flecs::This)
        .build();

    auto e1 = ecs.entity().add<Position>().add<Velocity>();
    ecs.entity().add<Velocity>();

    int32_t count = 0;
    q.each([&](flecs::entity e, Position& p) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    ecs.set<Position>({10, 20});

    auto q = ecs.query_builder<Position>()
        .term<Position>().src<Position>()
        .build();

    int32_t count = 0;
    q.each([&](flecs::entity e, Position& p) {
        test_int(p.x, 10);
        test_int(p.y, 20);
        count ++;
        test_assert(e == ecs.singleton<Position>());
    });
    
    test_int(count, 1);
}

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

    auto Likes = ecs.entity();
    auto Alice = ecs.entity();
    auto Bob = ecs.entity();

    auto q = ecs.query_builder<>()
        .term(Likes).second(Alice)
        .build();

    auto e1 = ecs.entity().add(Likes, Alice);
    ecs.entity().add(Likes, Bob);

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto Likes = ecs.entity();
    struct Alice { };
    auto Bob = ecs.entity();

    auto q = ecs.query_builder<>()
        .term(Likes).second<Alice>()
        .build();

    auto e1 = ecs.entity().add(Likes, ecs.id<Alice>());
    ecs.entity().add(Likes, Bob);

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto q = ecs.query_builder<>()
        .term(ecs.term(ecs.id<Position>()))
        .build();

    auto e1 = ecs.entity().add<Position>();
    ecs.entity().add<Velocity>();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto q = ecs.query_builder<>()
        .term(ecs.term<Position>())
        .build();

    auto e1 = ecs.entity().add<Position>();
    ecs.entity().add<Velocity>();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    struct Likes { };
    struct Alice { };
    struct Bob { };

    auto q = ecs.query_builder<>()
        .term(ecs.term<Likes, Alice>())
        .build();

    auto e1 = ecs.entity().add<Likes, Alice>();
    ecs.entity().add<Likes, Bob>();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto Apples = ecs.entity();
    auto Pears = ecs.entity();

    auto q = ecs.query_builder<>()
        .term(ecs.term(Apples))
        .build();

    auto e1 = ecs.entity().add(Apples);
    ecs.entity().add(Pears);

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto Likes = ecs.entity();
    auto Apples = ecs.entity();
    auto Pears = ecs.entity();

    auto q = ecs.query_builder<>()
        .term(ecs.term(Likes, Apples))
        .build();

    auto e1 = ecs.entity().add(Likes, Apples);
    ecs.entity().add(Likes, Pears);

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto Likes = ecs.entity();
    auto Apples = ecs.entity();

    auto qb = ecs.query_builder<>()
        .term<Position>();

    qb.term(Likes, Apples);

    auto q = qb.build();

    test_int(q.field_count(), 2);
    test_int(q.term(0).id(), ecs.id<Position>());
    test_int(q.term(1).id(), ecs.pair(Likes, Apples));
}

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

    struct Rel { int foo; };

    int32_t count = 0;

    auto s = ecs.system<Rel, const Velocity>()
        .arg(1).second(flecs::Wildcard)
        .arg(2).singleton()
        .iter([&](flecs::iter it){
            count += it.count();
        });
    
    ecs.entity().add<Rel, Tag>();
    ecs.set<Velocity>({});

    s.run();

    test_int(count, 1);
}

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

    struct TagA { };
    struct TagB { };

    auto q = ecs.query_builder()
        .term<TagA>()
        .term<TagB>().oper(flecs::Optional)
        .build();

    auto e_1 = ecs.entity().add<TagA>().add<TagB>();
    auto e_2 = ecs.entity().add<TagA>();

    int count = 0;

    q.iter([&](flecs::iter& it) {
        test_int(it.count(), 1);

        count += it.count();
        
        if (it.entity(0) == e_1) {
            test_bool(it.is_set(1), true);
            test_bool(it.is_set(2), true);
        } else {
            test_assert(it.entity(0) == e_2);
            test_bool(it.is_set(1), true);
            test_bool(it.is_set(2), false);
        }
    });

    test_int(count, 2);
}

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

    auto f = ecs.query_builder<>()
        .term<TagA>()
        .term<TagB>()
        .term<TagC>()
        .term<TagD>()
        .term<TagE>()
        .term<TagF>()
        .term<TagG>()
        .term<TagH>()
        .term<TagI>()
        .term<TagJ>()
        .build();

    test_int(f.field_count(), 10);

    auto e = ecs.entity()
        .add<TagA>()
        .add<TagB>()
        .add<TagC>()
        .add<TagD>()
        .add<TagE>()
        .add<TagF>()
        .add<TagG>()
        .add<TagH>()
        .add<TagI>()
        .add<TagJ>();

    int count = 0;
    f.iter([&](flecs::iter& it) {
        test_int(it.count(), 1);
        test_assert(it.entity(0) == e);
        test_int(it.field_count(), 10);
        count ++;
    });

    test_int(count, 1);
}

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

    auto f = ecs.query_builder<>()
        .term<TagA>()
        .term<TagB>()
        .term<TagC>()
        .term<TagD>()
        .term<TagE>()
        .term<TagF>()
        .term<TagG>()
        .term<TagH>()
        .term<TagI>()
        .term<TagJ>()
        .term<TagK>()
        .term<TagL>()
        .term<TagM>()
        .term<TagN>()
        .term<TagO>()
        .term<TagP>()
        .term<TagQ>()
        .term<TagR>()
        .term<TagS>()
        .term<TagT>()
        .build();

    test_int(f.field_count(), 20);

    auto e = ecs.entity()
        .add<TagA>()
        .add<TagB>()
        .add<TagC>()
        .add<TagD>()
        .add<TagE>()
        .add<TagF>()
        .add<TagG>()
        .add<TagH>()
        .add<TagI>()
        .add<TagJ>()
        .add<TagK>()
        .add<TagL>()
        .add<TagM>()
        .add<TagN>()
        .add<TagO>()
        .add<TagP>()
        .add<TagQ>()
        .add<TagR>()
        .add<TagS>()
        .add<TagT>();

    int count = 0;
    f.iter([&](flecs::iter& it) {
        test_int(it.count(), 1);
        test_assert(it.entity(0) == e);
        test_int(it.field_count(), 20);
        count ++;
    });

    test_int(count, 1);
}

uint64_t group_by_first_id(
    flecs::world_t *m_world,
    flecs::table_t *m_table,
    flecs::entity_t id,
    void *ctx)
{
    const flecs::type_t *type = ecs_table_get_type(m_table);
    return type->array[0];
}

uint64_t group_by_first_id_negated(
    flecs::world_t *m_world,
    flecs::table_t *m_table,
    flecs::entity_t id,
    void *ctx)
{
    return ~group_by_first_id(m_world, m_table, id, ctx);
}

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

    struct TagA { };
    struct TagB { };
    struct TagC { };
    struct TagX { };

    ecs.component<TagA>();
    ecs.component<TagB>();
    ecs.component<TagC>();
    ecs.component<TagX>();

    auto q = ecs.query_builder()
        .term<TagX>()
        .group_by(flecs::type_id<TagX>(), group_by_first_id)
        .build();

    auto q_reverse = ecs.query_builder()
        .term<TagX>()
        .group_by(flecs::type_id<TagX>(), group_by_first_id_negated)
        .build();

    auto e3 = ecs.entity().add<TagX>().add<TagC>();
    auto e2 = ecs.entity().add<TagX>().add<TagB>();
    auto e1 = ecs.entity().add<TagX>().add<TagA>();

    int count = 0;

    q.iter([&](flecs::iter& it){
        test_int(it.count(), 1);
        if(count == 0){
            test_bool(it.entity(0) == e1, true);
        }else if(count == 1){
            test_bool(it.entity(0) == e2, true);
        }else if(count == 2){
            test_bool(it.entity(0) == e3, true);
        }else{
            test_assert(false);
        }
        count++;
    });
    test_int(count, 3);
    
    count = 0;
    q_reverse.iter([&](flecs::iter& it){
        test_int(it.count(), 1);
        if(count == 0){
            test_bool(it.entity(0) == e3, true);
        }else if(count == 1){
            test_bool(it.entity(0) == e2, true);
        }else if(count == 2){
            test_bool(it.entity(0) == e1, true);
        }else{
            test_assert(false);
        }
        count++;
    });
    test_int(count, 3);
}

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

    struct TagA { };
    struct TagB { };
    struct TagC { };
    struct TagX { };

    ecs.component<TagA>();
    ecs.component<TagB>();
    ecs.component<TagC>();
    ecs.component<TagX>();

    auto q = ecs.query_builder()
        .term<TagX>()
        .group_by<TagX>(group_by_first_id)
        .build();

    auto q_reverse = ecs.query_builder()
        .term<TagX>()
        .group_by<TagX>( group_by_first_id_negated)
        .build();

    auto e3 = ecs.entity().add<TagX>().add<TagC>();
    auto e2 = ecs.entity().add<TagX>().add<TagB>();
    auto e1 = ecs.entity().add<TagX>().add<TagA>();

    int count = 0;

    q.iter([&](flecs::iter& it){
        test_int(it.count(), 1);
        if(count == 0){
            test_bool(it.entity(0) == e1, true);
        }else if(count == 1){
            test_bool(it.entity(0) == e2, true);
        }else if(count == 2){
            test_bool(it.entity(0) == e3, true);
        }else{
            test_assert(false);
        }
        count++;
    });
    test_int(count, 3);
    
    count = 0;
    q_reverse.iter([&](flecs::iter& it){
        test_int(it.count(), 1);
        if(count == 0){
            test_bool(it.entity(0) == e3, true);
        }else if(count == 1){
            test_bool(it.entity(0) == e2, true);
        }else if(count == 2){
            test_bool(it.entity(0) == e1, true);
        }else{
            test_assert(false);
        }
        count++;
    });
    test_int(count, 3);
}

static
uint64_t group_by_rel(flecs::world_t *world, flecs::table_t *table, flecs::entity_t id, void *ctx) {
    ecs_id_t match;
    if (ecs_search(world, table, ecs_pair(id, EcsWildcard), &match) != -1) {
        return ECS_PAIR_SECOND(match);
    }
    return 0;
}

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

    auto Rel = ecs.entity();
    auto TgtA = ecs.entity();
    auto TgtB = ecs.entity();
    auto TgtC = ecs.entity();
    auto Tag = ecs.entity();

    ecs.entity().add(Rel, TgtA);
    auto e2 = ecs.entity().add(Rel, TgtB);
    ecs.entity().add(Rel, TgtC);

    ecs.entity().add(Rel, TgtA).add(Tag);
    auto e5 = ecs.entity().add(Rel, TgtB).add(Tag);
    ecs.entity().add(Rel, TgtC).add(Tag);

    auto q = ecs.query_builder()
        .term(Rel, flecs::Wildcard)
        .group_by(Rel, group_by_rel)
        .build();

    bool e2_found = false;
    bool e5_found = false;
    int32_t count = 0;

    q.iter().set_group(TgtB).each([&](flecs::iter& it, size_t i) {
        flecs::entity e = it.entity(i);
        test_assert(it.group_id() == TgtB);

        if (e == e2) e2_found = true;
        if (e == e5) e5_found = true;
        count ++;
    });

    test_int(2, count);
    test_bool(true, e2_found);
    test_bool(true, e5_found);
}

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

    struct Rel { };
    struct TgtA { };
    struct TgtB { };
    struct TgtC { };
    struct Tag { };

    ecs.entity().add<Rel, TgtA>();
    auto e2 = ecs.entity().add<Rel, TgtB>();
    ecs.entity().add<Rel, TgtC>();

    ecs.entity().add<Rel, TgtA>().add<Tag>();
    auto e5 = ecs.entity().add<Rel, TgtB>().add<Tag>();
    ecs.entity().add<Rel, TgtC>().add<Tag>();

    auto q = ecs.query_builder()
        .term<Rel>(flecs::Wildcard)
        .group_by<Rel>(group_by_rel)
        .build();

    bool e2_found = false;
    bool e5_found = false;
    int32_t count = 0;

    q.iter().set_group<TgtB>().each([&](flecs::iter& it, size_t i) {
        flecs::entity e = it.entity(i);
        test_assert(it.group_id() == ecs.id<TgtB>());

        if (e == e2) e2_found = true;
        if (e == e5) e5_found = true;
        count ++;
    });

    test_int(2, count);
    test_bool(true, e2_found);
    test_bool(true, e5_found);
}

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

    auto Rel = ecs.entity();
    auto TgtA = ecs.entity();
    auto TgtB = ecs.entity();
    auto TgtC = ecs.entity();
    auto Tag = ecs.entity();

    auto e1 = ecs.entity().add(Rel, TgtA);
    auto e2 = ecs.entity().add(Rel, TgtB);
    auto e3 = ecs.entity().add(Rel, TgtC);

    auto e4 = ecs.entity().add(Rel, TgtA).add(Tag);
    auto e5 = ecs.entity().add(Rel, TgtB).add(Tag);
    auto e6 = ecs.entity().add(Rel, TgtC).add(Tag);

    auto q = ecs.query_builder()
        .term(Rel, flecs::Wildcard)
        .group_by(Rel, group_by_rel)
        .build();

    int e1_found = 0;
    int e2_found = 0;
    int e3_found = 0;
    int e4_found = 0;
    int e5_found = 0;
    int e6_found = 0;
    int32_t count = 0;
    uint64_t group_id = 0;

    const auto func = [&](flecs::iter& it, size_t i) {
        flecs::entity e = it.entity(i);
        test_assert(it.group_id() == group_id);
        if (e == e1) e1_found ++;
        if (e == e2) e2_found ++;
        if (e == e3) e3_found ++;
        if (e == e4) e4_found ++;
        if (e == e5) e5_found ++;
        if (e == e6) e6_found ++;
        count ++;
    };

    group_id = TgtB;
    q.iter().set_group(TgtB).each(func);
    test_int(2, count);
    test_int(1, e2_found);
    test_int(1, e5_found);

    group_id = TgtA;
    q.iter().set_group(TgtA).each(func);
    test_int(4, count);
    test_int(1, e1_found);
    test_int(1, e4_found);

    group_id = TgtC;
    q.iter().set_group(TgtC).each(func);
    test_int(6, count);
    test_int(1, e3_found);
    test_int(1, e6_found);

    test_int(1, e1_found);
    test_int(1, e2_found);
    test_int(1, e3_found);
    test_int(1, e4_found);
    test_int(1, e5_found);
    test_int(1, e6_found);
}

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

    auto Rel = ecs.entity();
    auto TgtA = ecs.entity();
    auto TgtB = ecs.entity();
    auto TgtC = ecs.entity();

    auto e1 = ecs.entity().add(Rel, TgtC);
    auto e2 = ecs.entity().add(Rel, TgtB);
    auto e3 = ecs.entity().add(Rel, TgtA);

    auto q = ecs.query_builder()
        .term(Rel, flecs::Wildcard)
        .group_by(Rel)
        .build();

    bool e1_found = false;
    bool e2_found = false;
    bool e3_found = false;
    int32_t count = 0;

    q.each([&](flecs::iter& it, size_t i) {
        flecs::entity e = it.entity(i);
        if (e == e1) {
            test_assert(it.group_id() == TgtC);
            test_assert(!e1_found);
            test_assert(e2_found);
            test_assert(e3_found);
            e1_found = true;
        }
        if (e == e2) {
            test_assert(it.group_id() == TgtB);
            test_assert(!e1_found);
            test_assert(!e2_found);
            test_assert(e3_found);
            e2_found = true;
        }
        if (e == e3) {
            test_assert(it.group_id() == TgtA);
            test_assert(!e1_found);
            test_assert(!e2_found);
            test_assert(!e3_found);
            e3_found = true;
        }
        count ++;
    });

    test_int(3, count);
    test_bool(true, e1_found);
    test_bool(true, e2_found);
    test_bool(true, e3_found);
}

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

    struct Rel { };
    auto TgtA = ecs.entity();
    auto TgtB = ecs.entity();
    auto TgtC = ecs.entity();

    auto e1 = ecs.entity().add<Rel>(TgtC);
    auto e2 = ecs.entity().add<Rel>(TgtB);
    auto e3 = ecs.entity().add<Rel>(TgtA);

    auto q = ecs.query_builder()
        .term<Rel>(flecs::Wildcard)
        .group_by<Rel>()
        .build();

    bool e1_found = false;
    bool e2_found = false;
    bool e3_found = false;
    int32_t count = 0;

    q.each([&](flecs::iter& it, size_t i) {
        flecs::entity e = it.entity(i);
        if (e == e1) {
            test_assert(it.group_id() == TgtC);
            test_assert(!e1_found);
            test_assert(e2_found);
            test_assert(e3_found);
            e1_found = true;
        }
        if (e == e2) {
            test_assert(it.group_id() == TgtB);
            test_assert(!e1_found);
            test_assert(!e2_found);
            test_assert(e3_found);
            e2_found = true;
        }
        if (e == e3) {
            test_assert(it.group_id() == TgtA);
            test_assert(!e1_found);
            test_assert(!e2_found);
            test_assert(!e3_found);
            e3_found = true;
        }
        count ++;
    });

    test_int(3, count);
    test_bool(true, e1_found);
    test_bool(true, e2_found);
    test_bool(true, e3_found);
}

static int group_by_ctx = 0;

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

    struct Rel { };
    auto TgtA = ecs.entity();
    auto TgtB = ecs.entity();
    auto TgtC = ecs.entity();

    auto e1 = ecs.entity().add<Rel>(TgtC);
    auto e2 = ecs.entity().add<Rel>(TgtB);
    auto e3 = ecs.entity().add<Rel>(TgtA);

    auto q = ecs.query_builder()
        .term<Rel>(flecs::Wildcard)
        .group_by<Rel>()
        .group_by_ctx(&group_by_ctx)
        .on_group_create(
            [](flecs::world_t *world, uint64_t id, void *group_by_arg) {
                test_assert(world != nullptr);
                test_assert(id != 0);
                test_assert(group_by_arg != nullptr);
                test_assert(group_by_arg == &group_by_ctx);
                uint64_t *ctx = ecs_os_malloc_t(uint64_t);
                *ctx = id;
                return (void*)ctx;
            })
        .on_group_delete(
            [](flecs::world_t *world, uint64_t id, void *ctx, void *group_by_arg) {
                test_assert(world != nullptr);
                test_assert(id != 0);
                test_assert(group_by_arg != nullptr);
                test_assert(group_by_arg == &group_by_ctx);
                test_assert(ctx != NULL);
                test_uint(*(uint64_t*)ctx, id);
                ecs_os_free(ctx);
            })
        .build();

    bool e1_found = false;
    bool e2_found = false;
    bool e3_found = false;
    int32_t count = 0;

    q.each([&](flecs::iter& it, size_t i) {
        flecs::entity e = it.entity(i);
        if (e == e1) {
            test_assert(it.group_id() == TgtC);
            test_assert(!e1_found);
            test_assert(e2_found);
            test_assert(e3_found);
            e1_found = true;
            uint64_t *ctx = (uint64_t*)q.group_ctx(it.group_id());
            test_uint(*ctx, it.group_id());
        }
        if (e == e2) {
            test_assert(it.group_id() == TgtB);
            test_assert(!e1_found);
            test_assert(!e2_found);
            test_assert(e3_found);
            e2_found = true;
            uint64_t *ctx = (uint64_t*)q.group_ctx(it.group_id());
            test_uint(*ctx, it.group_id());
        }
        if (e == e3) {
            test_assert(it.group_id() == TgtA);
            test_assert(!e1_found);
            test_assert(!e2_found);
            test_assert(!e3_found);
            e3_found = true;
            uint64_t *ctx = (uint64_t*)q.group_ctx(it.group_id());
            test_uint(*ctx, it.group_id());
        }
        count ++;
    });

    test_int(3, count);
    test_bool(true, e1_found);
    test_bool(true, e2_found);
    test_bool(true, e3_found);
}

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

    auto q = ecs.query_builder().term<Position>().build();

    auto e1 = ecs.entity().add<Position>();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto Likes = ecs.entity();
    auto Apple = ecs.entity();
    auto Mango = ecs.entity();

    auto e1 = ecs.entity()
        .add(Likes, Apple)
        .add(Likes, Mango);

    auto q = ecs.query_builder()
        .term(Likes, flecs::Any)
        .build();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;
        test_assert(e == e1);
    });
    
    test_int(count, 1);
}

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

    auto Tag = ecs.entity();
    auto Foo = ecs.entity();
    auto Bar = ecs.entity();

    auto e0 = ecs.entity().add(Tag);
    auto e1 = ecs.entity().is_a(e0);
    auto e2 = ecs.entity().is_a(e1);
    auto e3 = ecs.entity().is_a(e2);

    auto q = ecs.query_builder()
        .term(Tag).cascade()
        .build();

    e1.add(Bar);
    e2.add(Foo);

    bool e1_found = false;
    bool e2_found = false;
    bool e3_found = false;

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;

        if (e == e1) {
            test_bool(e1_found, false);
            test_bool(e2_found, false);
            test_bool(e3_found, false);
            e1_found = true;
        }
        if (e == e2) {
            test_bool(e1_found, true);
            test_bool(e2_found, false);
            test_bool(e3_found, false);
            e2_found = true;
        }
        if (e == e3) {
            test_bool(e1_found, true);
            test_bool(e2_found, true);
            test_bool(e3_found, false);
            e3_found = true;
        }
    });

    test_bool(e1_found, true);
    test_bool(e2_found, true);
    test_bool(e3_found, true);
    test_int(count, 3);
}

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

    auto Tag = ecs.entity();
    auto Foo = ecs.entity();
    auto Bar = ecs.entity();

    auto e0 = ecs.entity().add(Tag);
    auto e1 = ecs.entity().is_a(e0);
    auto e2 = ecs.entity().is_a(e1);
    auto e3 = ecs.entity().is_a(e2);

    auto q = ecs.query_builder()
        .term(Tag).cascade().desc()
        .build();

    e1.add(Bar);
    e2.add(Foo);

    bool e1_found = false;
    bool e2_found = false;
    bool e3_found = false;

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;

        if (e == e1) {
            test_bool(e1_found, false);
            test_bool(e2_found, true);
            test_bool(e3_found, true);
            e1_found = true;
        }
        if (e == e2) {
            test_bool(e1_found, false);
            test_bool(e2_found, false);
            test_bool(e3_found, true);
            e2_found = true;
        }
        if (e == e3) {
            test_bool(e1_found, false);
            test_bool(e2_found, false);
            test_bool(e3_found, false);
            e3_found = true;
        }
    });

    test_bool(e1_found, true);
    test_bool(e2_found, true);
    test_bool(e3_found, true);
    test_int(count, 3);
}

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

    auto Tag = ecs.entity();
    auto Foo = ecs.entity();
    auto Bar = ecs.entity();

    auto e0 = ecs.entity().add(Tag);
    auto e1 = ecs.entity().child_of(e0);
    auto e2 = ecs.entity().child_of(e1);
    auto e3 = ecs.entity().child_of(e2);

    auto q = ecs.query_builder()
        .term(Tag).cascade(flecs::ChildOf)
        .build();

    e1.add(Bar);
    e2.add(Foo);

    bool e1_found = false;
    bool e2_found = false;
    bool e3_found = false;

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;

        if (e == e1) {
            test_bool(e1_found, false);
            test_bool(e2_found, false);
            test_bool(e3_found, false);
            e1_found = true;
        }
        if (e == e2) {
            test_bool(e1_found, true);
            test_bool(e2_found, false);
            test_bool(e3_found, false);
            e2_found = true;
        }
        if (e == e3) {
            test_bool(e1_found, true);
            test_bool(e2_found, true);
            test_bool(e3_found, false);
            e3_found = true;
        }
    });

    test_bool(e1_found, true);
    test_bool(e2_found, true);
    test_bool(e3_found, true);
    test_int(count, 3);
}

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

    struct Rel { };

    ecs.component<Rel>().add(flecs::Traversable);

    auto q = ecs.query_builder<Self>()
        .term<Other>().src().up<Rel>()
        .build();

    auto base = ecs.entity().set<Other>({10});

    auto 
    e = ecs.entity().add<Rel>(base); e.set<Self>({e});
    e = ecs.entity().add<Rel>(base); e.set<Self>({e});
    e = ecs.entity().add<Rel>(base); e.set<Self>({e});

    int32_t count = 0;

    q.iter([&](flecs::iter& it, Self *s) {
        auto o = it.field<const Other>(2);
        test_assert(!it.is_self(2));
        test_int(o->value, 10);

        for (auto i : it) {
            test_assert(it.entity(i) == s[i].value);
            count ++;
        }
    });
    
    test_int(count, 3);
}

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

    struct Rel { };

    ecs.component<Rel>().add(flecs::Traversable);

    auto Tag = ecs.entity();
    auto Foo = ecs.entity();
    auto Bar = ecs.entity();

    auto e0 = ecs.entity().add(Tag);
    auto e1 = ecs.entity().add<Rel>(e0);
    auto e2 = ecs.entity().add<Rel>(e1);
    auto e3 = ecs.entity().add<Rel>(e2);

    auto q = ecs.query_builder()
        .term(Tag).cascade<Rel>()
        .build();

    e1.add(Bar);
    e2.add(Foo);

    bool e1_found = false;
    bool e2_found = false;
    bool e3_found = false;

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        count ++;

        if (e == e1) {
            test_bool(e1_found, false);
            test_bool(e2_found, false);
            test_bool(e3_found, false);
            e1_found = true;
        }
        if (e == e2) {
            test_bool(e1_found, true);
            test_bool(e2_found, false);
            test_bool(e3_found, false);
            e2_found = true;
        }
        if (e == e3) {
            test_bool(e1_found, true);
            test_bool(e2_found, true);
            test_bool(e3_found, false);
            e3_found = true;
        }
    });

    test_bool(e1_found, true);
    test_bool(e2_found, true);
    test_bool(e3_found, true);
    test_int(count, 3);
}

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

    auto e1 = ecs.entity().add<Position>();
    auto e2 = ecs.entity().add<Position>();

    auto q = ecs.query_builder("my_query")
        .term<Position>()
        .build();

    int32_t count = 0;
    q.each([&](flecs::entity e) {
        test_assert(e == e1 || e == e2);
        count ++;
    });
    test_int(count, 2);

    flecs::entity qe = q.entity();
    test_assert(qe != 0);
    test_str(qe.name(), "my_query");
}

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

    auto q = ecs.query_builder()
        .term<Position>()
        .term<Position>().write()
        .build();

    auto f = q.filter();
    test_assert(f.term(0).inout() == flecs::InOutDefault);
    test_assert(f.term(0).get_src() == flecs::This);
    test_assert(f.term(1).inout() == flecs::Out);
    test_assert(f.term(1).get_src() == 0);
}

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

    auto q = ecs.query_builder()
        .term<Position>()
        .term<Position>().read()
        .build();

    auto f = q.filter();
    test_assert(f.term(0).inout() == flecs::InOutDefault);
    test_assert(f.term(0).get_src() == flecs::This);
    test_assert(f.term(1).inout() == flecs::In);
    test_assert(f.term(1).get_src() == 0);
}

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

    ecs.set_stage_count(2);
    flecs::world stage = ecs.get_stage(1);

    auto e1 = ecs.entity().add<Position>();

    auto q = ecs.query<Position>();

    int32_t count = 0;
    q.each(stage, [&](flecs::iter& it, size_t i, Position&) {
        test_assert(it.world() == stage);
        test_assert(it.entity(i) == e1);
        count ++;
    });

    test_int(count, 1);
}
