#include <cpp_api.h>

flecs::opaque<std::string> std_string_support(flecs::world&) {
    flecs::opaque<std::string> ts;

    // Let reflection framework know what kind of type this is
    ts.as_type(flecs::String);

    // Forward std::string value to (JSON/...) serializer
    ts.serialize([](const flecs::serializer *s, const std::string *data) {
        const char *value = data->c_str();
        return s->value(flecs::String, &value);
    });

    // Serialize string into std::string
    ts.assign_string([](std::string *data, const char *value) {
        *data = value;
    });

    return ts;
}

template <typename T> 
flecs::opaque<std::vector<T>, T> std_vector_support(flecs::world& world) {
    flecs::opaque<std::vector<T>, T> ts;

    // Let reflection framework know what kind of type this is
    ts.as_type(world.vector<T>());

    // Forward elements of std::vector value to (JSON/...) serializer
    ts.serialize([](const flecs::serializer *s, const std::vector<T> *data) {
        for (const auto& el : *data) {
            s->value(el);
        }
        return 0;
    });

    // Return vector count
    ts.count([](const std::vector<T> *data) {
        return data->size();
    });

    // Ensure element exists, return
    ts.ensure_element([](std::vector<T> *data, size_t elem) {
        if (data->size() <= elem) {
            data->resize(elem + 1);
        }

        return &data->data()[elem];
    });

    // Resize contents of vector
    ts.resize([](std::vector<T> *data, size_t size) {
        data->resize(size);
    });

    return ts;
}


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

    struct Test {
        int32_t a;
        float b;
    };

    auto c = ecs.component<Test>()
        .member<int32_t>("a")
        .member<float>("b");    
    test_assert(c != 0);
    
    auto a = c.lookup("a");
    test_assert(a != 0);
    test_assert( a.has<flecs::Member>() );
    const flecs::Member *m = a.get<flecs::Member>();
    test_uint(m->type, flecs::I32);

    auto b = c.lookup("b");
    test_assert(b != 0);
    test_assert( b.has<flecs::Member>() );
    m = b.get<flecs::Member>();
    test_uint(m->type, flecs::F32);
}

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

    struct Test {
        int32_t x;
    };

    struct Nested {
        Test a;
    };

    auto t = ecs.component<Test>()
        .member<int32_t>("x");

    auto n = ecs.component<Nested>()
        .member(t, "a");
    test_assert(n != 0);
    
    auto a = n.lookup("a");
    test_assert(a != 0);
    test_assert( a.has<flecs::Member>() );
    const flecs::Member *m = a.get<flecs::Member>();
    test_uint(m->type, t);
}

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

    struct Test {
        uintptr_t a;
        uintptr_t b;
        flecs::entity_t c;
        flecs::entity_t d;
    };

    auto t = ecs.component<Test>()
        .member<uintptr_t>("a")
        .member(flecs::Uptr, "b")
        .member<flecs::entity_t>("c")
        .member(flecs::Entity, "d");
    test_assert(t != 0);

    auto a = t.lookup("a");
    test_assert(a != 0);
    test_assert( a.has<flecs::Member>() );
    const flecs::Member *m = a.get<flecs::Member>();
    test_uint(m->type, ecs.component<uintptr_t>());

    auto b = t.lookup("b");
    test_assert(b != 0);
    test_assert( b.has<flecs::Member>() );
    m = b.get<flecs::Member>();
    test_uint(m->type, flecs::Uptr);

    auto c = t.lookup("c");
    test_assert(c != 0);
    test_assert( c.has<flecs::Member>() );
    m = c.get<flecs::Member>();
    test_uint(m->type, flecs::U64);

    auto d = t.lookup("d");
    test_assert(d != 0);
    test_assert( d.has<flecs::Member>() );
    m = d.get<flecs::Member>();
    test_uint(m->type, flecs::Entity);
}

void Meta_units(void) {
    struct Test {
        int32_t meters;
        int32_t custom_unit;
    };

    flecs::world ecs;

    ecs.import<flecs::units>();

    auto custom_unit = ecs.entity("some_unit").unit("u");
    test_assert(custom_unit != 0);
    test_str(custom_unit.name(), "some_unit");

    const flecs::Unit *unit = custom_unit.get<flecs::Unit>();
    test_assert(unit != nullptr);
    test_str(unit->symbol, "u");

    auto t = ecs.component<Test>()
        .member<int32_t, flecs::units::length::Meters>("meters")
        .member<int32_t>(custom_unit, "custom_unit");
    test_assert(t != 0);

    flecs::entity m_meters = t.lookup("meters");
    test_assert(m_meters != 0);
    const flecs::Member *m = m_meters.get<flecs::Member>();
    test_assert(m != nullptr);
    test_uint(m->type, flecs::I32);
    test_uint(m->unit, ecs.id<flecs::units::length::Meters>());

    flecs::entity m_custom_unit = t.lookup("custom_unit");
    test_assert(m_custom_unit != 0);
    m = m_custom_unit.get<flecs::Member>();
    test_assert(m != nullptr);
    test_uint(m->type, flecs::I32);
    test_uint(m->unit, custom_unit);
}

void Meta_unit_w_quantity(void) {
    struct Test {
        int32_t m_1;
        int32_t m_2;
    };

    flecs::world ecs;

    ecs.import<flecs::units>();

    auto custom_quantity = ecs.entity().quantity();
    auto unit_1 = ecs.entity().unit("u1").quantity(custom_quantity);
    auto unit_2 = ecs.entity().unit("u2").quantity<flecs::units::Length>();

    test_assert(unit_1.has(flecs::Quantity, custom_quantity));
    test_assert(unit_2.has(flecs::Quantity, ecs.id<flecs::units::Length>()));

    auto t = ecs.component<Test>()
        .member<int32_t>(unit_1, "m_1")
        .member<int32_t>(unit_2, "m_2");
    test_assert(t != 0);

    flecs::entity m_1 = t.lookup("m_1");
    test_assert(m_1 != 0);
    const flecs::Member *m = m_1.get<flecs::Member>();
    test_assert(m != nullptr);
    test_uint(m->type, flecs::I32);
    test_uint(m->unit, unit_1);

    flecs::entity m_2 = t.lookup("m_2");
    test_assert(m_2 != 0);
    m = m_2.get<flecs::Member>();
    test_assert(m != nullptr);
    test_uint(m->type, flecs::I32);
    test_uint(m->unit, unit_2);
}

void Meta_unit_w_prefix(void) {
    struct Test {
        int32_t m_1;
        int32_t m_2;
    };

    flecs::world ecs;

    ecs.import<flecs::units>();

    auto prefix = ecs.entity().unit_prefix("p", 100, 1);
    
    auto unit_1 = ecs.entity().unit("U1");
    const flecs::Unit *unit = unit_1.get<flecs::Unit>();
    test_assert(unit != nullptr);
    test_str(unit->symbol, "U1");

    auto unit_2 = ecs.entity().unit(prefix, unit_1);
    unit = unit_2.get<flecs::Unit>();
    test_assert(unit != nullptr);
    test_str(unit->symbol, "pU1");
}

void Meta_unit_w_over(void) {
    struct Test {
        int32_t m_1;
        int32_t m_2;
    };

    flecs::world ecs;

    ecs.import<flecs::units>();

    auto prefix = ecs.entity().unit_prefix("p", 100, 1);
    
    auto unit_0 = ecs.entity().unit("U0");
    const flecs::Unit *unit = unit_0.get<flecs::Unit>();
    test_assert(unit != nullptr);
    test_str(unit->symbol, "U0");

    auto unit_1 = ecs.entity().unit("U1");
    unit = unit_1.get<flecs::Unit>();
    test_assert(unit != nullptr);
    test_str(unit->symbol, "U1");

    auto unit_2 = ecs.entity().unit(prefix, unit_1, unit_0);
    unit = unit_2.get<flecs::Unit>();
    test_assert(unit != nullptr);
    test_str(unit->symbol, "pU1/U0");
}

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

    auto c = ecs.component<Position>()
        .member<float>("x");
    test_assert(c != 0);

    const flecs::Component *ptr = c.get<flecs::Component>();
    test_int(ptr->size, 8);
    test_int(ptr->alignment, 4);
    
    auto xe = c.lookup("x");
    test_assert(xe != 0);
    test_assert( xe.has<flecs::Member>() );
    const flecs::Member *x = xe.get<flecs::Member>();
    test_uint(x->type, flecs::F32);
    test_uint(x->offset, 0);
}

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

    auto c = ecs.component<Position>()
        .member<float>("y", 1, offsetof(Position, y));
    test_assert(c != 0);

    const flecs::Component *ptr = c.get<flecs::Component>();
    test_int(ptr->size, 8);
    test_int(ptr->alignment, 4);
    
    auto xe = c.lookup("y");
    test_assert(xe != 0);
    test_assert( xe.has<flecs::Member>() );
    const flecs::Member *x = xe.get<flecs::Member>();
    test_uint(x->type, flecs::F32);
    test_uint(x->offset, 4);
}

struct Toppings : flecs::bitmask {
    static constexpr uint32_t Bacon = 0x1;
    static constexpr uint32_t Lettuce = 0x2;
    static constexpr uint32_t Tomato = 0x4;
};

struct Sandwich {
    uint32_t toppings;
};

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

    // Register components with reflection data
    ecs.component<Toppings>()
        .bit("Bacon", Toppings::Bacon)
        .bit("Lettuce", Toppings::Lettuce)
        .bit("Tomato", Toppings::Tomato);

    ecs.component<Sandwich>()
        .member<Toppings>("toppings");

    // Create entity with Position as usual
    auto e = ecs.entity()
        .set<Sandwich>({Toppings::Bacon | Toppings::Lettuce});

    // Convert position component to flecs expression string
    const Sandwich *ptr = e.get<Sandwich>();
    test_str(ecs.to_expr(ptr).c_str(), "{toppings: Lettuce|Bacon}");
}

struct Int {
    int32_t v;
};

struct TVector {
    std::vector<int> v;
};

struct TString {
    std::string v;
};

struct TVectorString {
    std::vector<int> v;
    std::string s;
};

struct TStringVector {
    std::string s;
    std::vector<int> v;
};

struct TStringString {
    std::string s1;
    std::string s2;
};

struct TVectorVector {
    std::vector<int> v1;
    std::vector<int> v2;
};

struct TVectorVectorString {
    std::vector<int> v1;
    std::vector<int> v2;
    std::string s;
};

struct TVectorStringVector {
    std::vector<int> v1;
    std::string s;
    std::vector<int> v2;
};

static
int Int_serialize(const flecs::serializer *s, const Int *data) {
    return s->value(data->v);
}

static
int String_serialize(const flecs::serializer *s, const std::string *data) {
    const char *str = data->c_str();
    return s->value(flecs::String, &str);
}

template <typename Elem>
int Vector_serialize(const flecs::serializer *ser, const std::vector<Elem> *data) {
    for (const auto& el : *data) {
        if (ser->value(el)) {
            return -1;
        }
    }

    return 0;
}

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

    ecs.component<Int>().opaque(flecs::I32)
        .serialize(Int_serialize);

    Int v = {10};
    flecs::string json = ecs.to_json(&v);
    test_str(json, "10");
}

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

    ecs.component<std::string>().opaque(flecs::String)
        .serialize(String_serialize);

    std::string v = {"Hello World"};
    flecs::string json = ecs.to_json(&v);
    test_str(json, "\"Hello World\"");
}

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

    ecs.component<std::vector<int>>().opaque(ecs.vector<int>())
        .serialize(Vector_serialize<int>);

    std::vector<int> v = {1, 2, 3};
    flecs::string json = ecs.to_json(&v);
    test_str(json, "[1, 2, 3]");
}

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

    ecs.component<std::string>().opaque(flecs::String)
        .serialize(String_serialize);

    ecs.component<std::vector<std::string>>().opaque(ecs.vector(flecs::String))
        .serialize(Vector_serialize<std::string>);

    std::vector<std::string> v = {"hello", "world", "foo"};
    flecs::string json = ecs.to_json(&v);
    test_str(json, "[\"hello\", \"world\", \"foo\"]");
}

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

    ecs.component<std::vector<int>>().opaque(ecs.vector<int>())
        .serialize(Vector_serialize<int>);

    ecs.component<TVector>()
        .member<std::vector<int>>("v");

    TVector v = {{1, 2, 3}};
    flecs::string json = ecs.to_json(&v);
    test_str(json, "{\"v\":[1, 2, 3]}");
}

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

    ecs.component<std::string>().opaque(flecs::String)
        .serialize(String_serialize);

    ecs.component<TString>()
        .member<std::string>("v");

    TString v = {"hello world"};
    flecs::string json = ecs.to_json(&v);
    test_str(json, "{\"v\":\"hello world\"}");
}

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

    ecs.component<std::vector<int>>().opaque(ecs.vector<int>())
        .serialize(Vector_serialize<int>);

    ecs.component<std::string>().opaque(flecs::String)
        .serialize(String_serialize);

    ecs.component<TVectorString>()
        .member<std::vector<int>>("v")
        .member<std::string>("s");

    TVectorString v = {{1, 2, 3}, "hello world"};
    flecs::string json = ecs.to_json(&v);
    test_str(json, "{\"v\":[1, 2, 3], \"s\":\"hello world\"}");
}

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

    ecs.component<std::vector<int>>().opaque(ecs.vector<int>())
        .serialize(Vector_serialize<int>);

    ecs.component<std::string>().opaque(flecs::String)
        .serialize(String_serialize);

    ecs.component<TStringVector>()
        .member<std::string>("s")
        .member<std::vector<int>>("v");

    TStringVector v = {"hello world", {1, 2, 3}};
    flecs::string json = ecs.to_json(&v);
    test_str(json, "{\"s\":\"hello world\", \"v\":[1, 2, 3]}");
}

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

    ecs.component<std::vector<int>>().opaque(ecs.vector<int>())
        .serialize(Vector_serialize<int>);

    ecs.component<std::string>().opaque(flecs::String)
        .serialize(String_serialize);

    ecs.component<TStringString>()
        .member<std::string>("s1")
        .member<std::string>("s2");

    TStringString v = {"hello world", "foo bar"};
    flecs::string json = ecs.to_json(&v);
    test_str(json, "{\"s1\":\"hello world\", \"s2\":\"foo bar\"}");
}

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

    ecs.component<std::vector<int>>().opaque(ecs.vector<int>())
        .serialize(Vector_serialize<int>);

    ecs.component<std::string>().opaque(flecs::String)
        .serialize(String_serialize);

    ecs.component<TVectorVector>()
        .member<std::vector<int>>("v1")
        .member<std::vector<int>>("v2");

    TVectorVector v = {{1, 2, 3}, {4, 5, 6}};
    flecs::string json = ecs.to_json(&v);
    test_str(json, "{\"v1\":[1, 2, 3], \"v2\":[4, 5, 6]}");
}

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

    ecs.component<std::vector<int>>().opaque(ecs.vector<int>())
        .serialize(Vector_serialize<int>);

    ecs.component<std::string>().opaque(flecs::String)
        .serialize(String_serialize);

    ecs.component<TVectorStringVector>()
        .member<std::vector<int>>("v1")
        .member<std::string>("s")
        .member<std::vector<int>>("v2");

    TVectorStringVector v = {{1, 2, 3}, "hello world", {4, 5, 6}};
    flecs::string json = ecs.to_json(&v);
    test_str(json, "{\"v1\":[1, 2, 3], \"s\":\"hello world\", \"v2\":[4, 5, 6]}");
}

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

    ecs.component<std::vector<int>>().opaque(ecs.vector<int>())
        .serialize(Vector_serialize<int>);

    ecs.component<std::string>().opaque(flecs::String)
        .serialize(String_serialize);

    ecs.component<TVectorVectorString>()
        .member<std::vector<int>>("v1")
        .member<std::vector<int>>("v2")
        .member<std::string>("s");

    TVectorVectorString v = {{1, 2, 3}, {4, 5, 6}, "hello world"};
    flecs::string json = ecs.to_json(&v);
    test_str(json, "{\"v1\":[1, 2, 3], \"v2\":[4, 5, 6], \"s\":\"hello world\"}");
}

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

    flecs::entity t = ecs.primitive(flecs::meta::I32);
    test_assert(t != 0);

    test_assert(t.has<flecs::Component>());
    test_assert(t.has<flecs::MetaType>());
    test_assert(t.has<flecs::Primitive>());

    const flecs::Component *c = t.get<flecs::Component>();
    test_assert(c != nullptr);
    test_int(c->size, 4);
    test_int(c->alignment, 4);

    const flecs::MetaType *mt = t.get<flecs::MetaType>();
    test_assert(mt != nullptr);
    test_assert(mt->kind == flecs::meta::PrimitiveType);

    const flecs::Primitive *pt = t.get<flecs::Primitive>();
    test_assert(pt != nullptr);
    test_assert(pt->kind == flecs::meta::I32);
}

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

    flecs::entity t = ecs.array(flecs::I32, 3);
    test_assert(t != 0);

    test_assert(t.has<flecs::Component>());
    test_assert(t.has<flecs::MetaType>());
    test_assert(t.has<flecs::Array>());

    const flecs::Component *c = t.get<flecs::Component>();
    test_assert(c != nullptr);
    test_int(c->size, 3 * 4);
    test_int(c->alignment, 4);

    const flecs::MetaType *mt = t.get<flecs::MetaType>();
    test_assert(mt != nullptr);
    test_assert(mt->kind == flecs::meta::ArrayType);

    const flecs::Array *at = t.get<flecs::Array>();
    test_assert(at != nullptr);
    test_assert(at->type == ecs.id<int32_t>());
    test_int(at->count, 3);
}

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

    flecs::entity t = ecs.vector(flecs::I32);
    test_assert(t != 0);

    test_assert(t.has<flecs::Component>());
    test_assert(t.has<flecs::MetaType>());
    test_assert(t.has<flecs::Vector>());

    const flecs::Component *c = t.get<flecs::Component>();
    test_assert(c != nullptr);
    test_int(c->size, ECS_SIZEOF(ecs_vec_t));
    test_int(c->alignment, ECS_SIZEOF(void*));

    const flecs::MetaType *mt = t.get<flecs::MetaType>();
    test_assert(mt != nullptr);
    test_assert(mt->kind == flecs::meta::VectorType);

    const flecs::Vector *vt = t.get<flecs::Vector>();
    test_assert(vt != nullptr);
    test_assert(vt->type == ecs.id<int32_t>());
}

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

    int32_t v = 0;
    const char *r = ecs.from_json(&v, "10");
    test_str(r, "");
    test_int(v, 10);
}

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

    ecs.component<Position>()
        .member<float>("x")
        .member<float>("y");

    Position v = {};
    const char *r = ecs.from_json(&v, "{\"x\":10, \"y\":20}");
    test_str(r, "");
    test_int(v.x, 10);
    test_int(v.y, 20);
}

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

    flecs::entity pos = ecs.component<Position>()
        .member<float>("x")
        .member<float>("y");

    Position v = {};
    void *ptr = &v;
    const char *r = ecs.from_json(pos, ptr, "{\"x\":10, \"y\":20}");
    test_str(r, "");
    test_int(v.x, 10);
    test_int(v.y, 20);
}

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

    ecs.component<Position>()
        .member<float>("x")
        .member<float>("y");

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

    const char *r = e.from_json("{}");
    test_str(r, "");
}

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

    ecs.component<Position>()
        .member<float>("x")
        .member<float>("y");

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

    const char *r = e.from_json("{\"path\":\"ent\"}");
    test_str(r, "");

    test_assert(e != 0);
    test_str(e.name(), "ent");
}

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

    ecs.component<Position>()
        .member<float>("x")
        .member<float>("y");

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

    const char *r = e.from_json("{\"path\":\"ent\", \"ids\":[[\"Position\"]]}");
    test_str(r, "");

    test_assert(e != 0);
    test_str(e.name(), "ent");
    test_assert(e.has<Position>());
}

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

    ecs.component<Position>()
        .member<float>("x")
        .member<float>("y");

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

    const char *r = e.from_json("{\"path\":\"ent\", \"ids\":[[\"Position\"]], \"values\":[{\"x\":10, \"y\":20}]}");
    test_str(r, "");

    test_assert(e != 0);
    test_str(e.name(), "ent");
    test_assert(e.has<Position>());

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

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

    ecs.component<Position>()
        .member<float>("x")
        .member<float>("y");

    flecs::entity e = ecs.entity()
        .set_json<Position>("{\"x\":10, \"y\":20}");

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

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

    ecs.component<Position>()
        .member<float>("x")
        .member<float>("y");

    flecs::entity e = ecs.entity()
        .set_json<Position, Tag>("{\"x\":10, \"y\":20}");

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

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

    ecs.component<Position>()
        .member<float>("x")
        .member<float>("y");

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

    flecs::entity e = ecs.entity()
        .set_json<Position>(tgt, "{\"x\":10, \"y\":20}");

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

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

    flecs::entity pos = ecs.component<Position>()
        .member<float>("x")
        .member<float>("y");

    flecs::entity e = ecs.entity()
        .set_json_second<Tag>(pos, "{\"x\":10, \"y\":20}");

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

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

    flecs::entity pos = ecs.component<Position>()
        .member<float>("x")
        .member<float>("y");

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

    flecs::entity e = ecs.entity()
        .set_json(pos, tgt, "{\"x\":10, \"y\":20}");

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

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

    flecs::entity pos = ecs.component<Position>()
        .member<float>("x")
        .member<float>("y");

    flecs::entity e = ecs.entity()
        .set_json(pos, "{\"x\":10, \"y\":20}");

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

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

    world.component<std::string>()
        .opaque(std_string_support);

    std::string v = "hello world";
    test_str(world.to_json(&v).c_str(), "\"hello world\"");

    world.from_json(&v, "\"foo bar\"");
    test_str(world.to_json(&v).c_str(), "\"foo bar\"");
}

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

    world.component<std::vector<int>>()
        .opaque(std_vector_support<int>);

    std::vector<int> v = {1, 2, 3};
    test_str(world.to_json(&v).c_str(), "[1, 2, 3]");

    world.from_json(&v, "[4, 5, 6]");
    test_str(world.to_json(&v).c_str(), "[4, 5, 6]");
}

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

    world.component<std::string>()
        .opaque(std_string_support);


    world.component<std::vector<std::string>>()
        .opaque(std_vector_support<std::string>);

    std::vector<std::string> v = {"foo", "bar"};
    test_str(world.to_json(&v).c_str(), "[\"foo\", \"bar\"]");

    world.from_json(&v, "[\"hello\", \"world\"]");
    test_str(world.to_json(&v).c_str(), "[\"hello\", \"world\"]");
}

struct CppTypes {
    std::string s;
    std::vector<std::string> v;
};

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

    world.component<std::string>()
        .opaque(std_string_support);

    world.component<std::vector<std::string>>()
        .opaque(std_vector_support<std::string>);

    world.component<CppTypes>()
        .member<std::string>("s")
        .member<std::vector<std::string>>("v");

    CppTypes v = {"hello", {"world"}};

    test_str(world.to_json(&v).c_str(), "{\"s\":\"hello\", \"v\":[\"world\"]}");

    world.from_json(&v, "{\"s\":\"foo\", \"v\":[\"bar\"]}");

    test_str(world.to_json(&v).c_str(), "{\"s\":\"foo\", \"v\":[\"bar\"]}");
}

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

    flecs::entity e1 = world.entity("ent1");
    flecs::entity e2 = world.entity("ent2");

    flecs::entity v = e1;
    test_str(world.to_json(&v).c_str(), "\"ent1\"");

    world.from_json(&v, "\"ent2\"");
    test_str(world.to_json(&v).c_str(), "\"ent2\"");
    test_assert(v == e2);
}

struct CppEntity {
    flecs::entity entity;
};

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

    world.component<CppEntity>()
        .member<flecs::entity>("entity");

    flecs::entity e1 = world.entity("ent1");
    flecs::entity e2 = world.entity("ent2").set<CppEntity>({e1});

    {
        const CppEntity *ptr = e2.get<CppEntity>();
        test_assert(ptr != nullptr);
        test_str(world.to_json(ptr).c_str(), "{\"entity\":\"ent1\"}");
    }

    auto json = world.to_json();
    world.from_json(json);

    test_assert(e1.is_alive());
    test_assert(e2.is_alive());

    {
        const CppEntity *ptr = e2.get<CppEntity>();
        test_assert(ptr != nullptr);
        test_str(world.to_json(ptr).c_str(), "{\"entity\":\"ent1\"}");
    }
}

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

    world.component<CppEntity>()
        .member<flecs::entity>("entity");

    flecs::entity e1 = world.entity("ent1");
    flecs::entity e2 = world.entity("ent2").set<CppEntity>({e1});

    {
        const CppEntity *ptr = e2.get<CppEntity>();
        test_assert(ptr != nullptr);
        test_str(world.to_json(ptr).c_str(), "{\"entity\":\"ent1\"}");
    }

    auto json = world.to_json();

    flecs::world world2;

    world2.component<CppEntity>()
        .member<flecs::entity>("entity");

    world2.from_json(json);
    
    e1 = world2.lookup("ent1");
    test_assert(e1 != 0);
    e2 = world2.lookup("ent2");
    test_assert(e2 != 0);

    test_assert(e1.is_alive());
    test_assert(e2.is_alive());

    {
        const CppEntity *ptr = e2.get<CppEntity>();
        test_assert(ptr != nullptr);
        test_str(world2.to_json(ptr).c_str(), "{\"entity\":\"ent1\"}");
    }
}

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

    world.component<CppEntity>()
        .member<flecs::entity>("entity");

    flecs::entity e1 = world.entity("ent1").set<CppEntity>({});

    {
        const CppEntity *ptr = e1.get<CppEntity>();
        test_assert(ptr != nullptr);
        test_str(world.to_json(ptr).c_str(), "{\"entity\":0}");
    }

    auto json = world.to_json();

    flecs::world world2;

    world2.component<CppEntity>()
        .member<flecs::entity>("entity");

    world2.from_json(json);
    
    e1 = world2.lookup("ent1");
    test_assert(e1 != 0);

    test_assert(e1.is_alive());

    {
        const CppEntity *ptr = e1.get<CppEntity>();
        test_assert(ptr != nullptr);
        test_str(world2.to_json(ptr).c_str(), "{\"entity\":0}");
    }
}

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

    world.component<std::vector<int>>().opaque<int>(world.vector<int>())
        .serialize([](const flecs::serializer *s, const std::vector<int> *data) {
            for (const auto& el : *data) {
                s->value(el);
            }
            return 0;
        })
        .count([](const std::vector<int> *data) {
            return data->size();
        })
        .ensure_element([](std::vector<int> *data, size_t elem) {
            if (data->size() <= elem) {
                data->resize(elem + 1);
            }

            return &data->data()[elem];
        })
        .resize([](std::vector<int> *data, size_t size) {
            data->resize(size);
        });

    std::vector<int> v = {};

    world.from_json(&v, "[10, 20, 30]");
    test_uint(v.size(), 3);
    test_int(v[0], 10);
    test_int(v[1], 20);
    test_int(v[2], 30);

    test_str(world.to_json(&v).c_str(), "[10, 20, 30]");
}

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

    flecs::entity ent = world.entity("ent");

    flecs::entity e;
    world.from_json(&e, "\"ent\"");

    test_assert(e == ent);
    test_str(e.path().c_str(), "::ent");
}

enum class EnumWithBits : uint32_t {
	BitA = 0,
	BitB = 1 << 0,
	BitAll = 0xffffffff
};

struct EnumWithBitsStruct {
	EnumWithBits bits = EnumWithBits::BitAll;
};

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

    flecs::log::set_level(-4);

    /* It is illegal to register an enumeration as bitset, this test makes sure
     * the code doesn't crash. */
	ecs.component<EnumWithBits>()
		.bit("BitA", (uint32_t)EnumWithBits::BitA)
		.bit("BitB", (uint32_t)EnumWithBits::BitB)
        .bit("BitAll", (uint32_t)EnumWithBits::BitAll);

	ecs.component<EnumWithBitsStruct>()
		.member<EnumWithBits>("bits");

	for (int i = 0; i < 30; i++)
	{
		ecs.entity()
			.child_of(ecs.entity())
			.add<EnumWithBitsStruct>();
	}

    auto q = ecs.query<EnumWithBitsStruct>();
    auto s = q.iter().to_json();
    test_str(s.c_str(), "");
}

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

    flecs::entity s = ecs.component<Position>()
        .member<float>("x").range(-1, 1)
        .member<float>("y").range(-2, 2);

    flecs::entity x = s.lookup("x");
    test_assert(x != 0);
    test_assert(x.has<flecs::MemberRanges>());
    {
        const flecs::MemberRanges *ranges = x.get<flecs::MemberRanges>();
        test_assert(ranges != nullptr);
        test_assert(ranges->value.min == -1);
        test_assert(ranges->value.max == 1);
    }

    flecs::entity y = s.lookup("y");
    test_assert(y != 0);
    test_assert(y.has<flecs::MemberRanges>());
    {
        const flecs::MemberRanges *ranges = y.get<flecs::MemberRanges>();
        test_assert(ranges != nullptr);
        test_assert(ranges->value.min == -2);
        test_assert(ranges->value.max == 2);
    }
}

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

    flecs::entity s = ecs.component<Position>()
        .member<float>("x").warning_range(-1, 1)
        .member<float>("y").warning_range(-2, 2);

    flecs::entity x = s.lookup("x");
    test_assert(x != 0);
    test_assert(x.has<flecs::MemberRanges>());
    {
        const flecs::MemberRanges *ranges = x.get<flecs::MemberRanges>();
        test_assert(ranges != nullptr);
        test_assert(ranges->warning.min == -1);
        test_assert(ranges->warning.max == 1);
    }

    flecs::entity y = s.lookup("y");
    test_assert(y != 0);
    test_assert(y.has<flecs::MemberRanges>());
    {
        const flecs::MemberRanges *ranges = y.get<flecs::MemberRanges>();
        test_assert(ranges != nullptr);
        test_assert(ranges->warning.min == -2);
        test_assert(ranges->warning.max == 2);
    }
}

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

    flecs::entity s = ecs.component<Position>()
        .member<float>("x").error_range(-1, 1)
        .member<float>("y").error_range(-2, 2);

    flecs::entity x = s.lookup("x");
    test_assert(x != 0);
    test_assert(x.has<flecs::MemberRanges>());
    {
        const flecs::MemberRanges *ranges = x.get<flecs::MemberRanges>();
        test_assert(ranges != nullptr);
        test_assert(ranges->error.min == -1);
        test_assert(ranges->error.max == 1);
    }

    flecs::entity y = s.lookup("y");
    test_assert(y != 0);
    test_assert(y.has<flecs::MemberRanges>());
    {
        const flecs::MemberRanges *ranges = y.get<flecs::MemberRanges>();
        test_assert(ranges != nullptr);
        test_assert(ranges->error.min == -2);
        test_assert(ranges->error.max == 2);
    }
}

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

    struct Test {
        int32_t x;
    };
    struct Test2 {
        double y;
    };
    struct Nested {
        Test a;
        int pad; // This will be ignored
        Test2 b[2];
    };

    auto t = ecs.component<Test>()
        .member("x", &Test::x);

    auto t2 = ecs.component<Test2>()
        .member("y", &Test2::y);

    auto n = ecs.component<Nested>()
        .member("a", &Nested::a)
        .member("b", &Nested::b);

    { // Validate Test
        test_assert(t != 0);

        auto x = t.lookup("x");
        test_assert(x != 0);
        test_assert(x.has<flecs::Member>());
        const flecs::Member *xm = x.get<flecs::Member>();
        test_uint(xm->type, flecs::I32);
        test_uint(xm->offset, offsetof(Test, x));
    }
    { // Validate Test2
        test_assert(t2 != 0);

        auto y = t2.lookup("y");
        test_assert(y != 0);
        test_assert(y.has<flecs::Member>());
        const flecs::Member *ym = y.get<flecs::Member>();
        test_uint(ym->type, flecs::F64);
        test_uint(ym->offset, offsetof(Test2, y));
    }

    // Validate Nested
    test_assert(n != 0);
    {
        auto a = n.lookup("a");
        test_assert(a != 0);
        test_assert(a.has<flecs::Member>());
        const flecs::Member *am = a.get<flecs::Member>();
        test_uint(am->type, t);
        test_uint(am->offset, offsetof(Nested, a));
    }
    {
        auto b = n.lookup("b");
        test_assert(b != 0);
        test_assert(b.has<flecs::Member>());
        const flecs::Member *bm = b.get<flecs::Member>();
        test_uint(bm->type, t2);
        test_uint(bm->offset, offsetof(Nested, b));
        test_uint(bm->count, 2);
    }
}

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

#if defined(__GNUC__) || defined(__clang__)
    struct PackedStruct {
        char a;
        int b;
        char pad[2];
        double c;
    } __attribute__((__packed__));
#endif

#if defined(_MSC_VER)
#pragma pack(push, 1)
    struct PackedStruct {
        char a;
        int b;
        char pad[2];
        double c;
    };
#pragma pack(pop)
#endif

    auto s = ecs.component<PackedStruct>()
        .member("a", &PackedStruct::a)
        .member("b", &PackedStruct::b)
        .member("c", &PackedStruct::c);

    test_assert(s != 0);

    {
        auto a = s.lookup("a");
        test_assert(a != 0);
        test_assert(a.has<flecs::Member>());
        const flecs::Member *am = a.get<flecs::Member>();
        test_uint(am->type, flecs::Char);
        test_uint(am->offset, offsetof(PackedStruct, a));
    }
    {
        auto b = s.lookup("b");
        test_assert(b != 0);
        test_assert(b.has<flecs::Member>());
        const flecs::Member *bm = b.get<flecs::Member>();
        test_uint(bm->type, flecs::I32);
        test_uint(bm->offset, offsetof(PackedStruct, b));
    }
    {
        auto c = s.lookup("c");
        test_assert(c != 0);
        test_assert(c.has<flecs::Member>());
        const flecs::Member *cm = c.get<flecs::Member>();
        test_uint(cm->type, flecs::F64);
        test_uint(cm->offset, offsetof(PackedStruct, c));
    }
}

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

    flecs::entity c = ecs.component<Position>()
        .array<float>(2);

    test_assert(c.has<flecs::Array>());

    const flecs::Array *ptr = c.get<flecs::Array>();
    test_assert(ptr != nullptr);
    test_assert(ptr->type == ecs.id<float>());
    test_int(ptr->count, 2);
}
