#include <api.h>

void Delete_setup(void) {
    ecs_log_set_level(-3);
}

void Delete_delete_1(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);

    ecs_delete(world, e);

    test_assert(ecs_exists(world, e));
    test_assert(!ecs_is_alive(world, e));
    
    ecs_fini(world);
}

void Delete_delete_1_again(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);

    ecs_delete(world, e);
    test_assert(ecs_exists(world, e));
    test_assert(!ecs_is_alive(world, e));

    ecs_delete(world, e);
    test_assert(ecs_exists(world, e));
    test_assert(!ecs_is_alive(world, e));
    
    ecs_fini(world);
}

void Delete_delete_recycled_tag_again(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new_id(world);
    test_assert(e != 0);

    ecs_delete(world, e);
    test_assert(ecs_exists(world, e));
    test_assert(!ecs_is_alive(world, e));

    ecs_entity_t t = ecs_new_id(world);
    test_assert(t != 0);
    test_assert((uint32_t)t == (uint32_t)e);
    test_assert(ecs_exists(world, e));
    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_exists(world, t));
    test_assert(ecs_is_alive(world, t));

    ecs_entity_t f = ecs_new_id(world);
    ecs_add_id(world, f, t);

    ecs_delete(world, e);
    test_assert(ecs_exists(world, e));
    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_exists(world, t));
    test_assert(ecs_is_alive(world, t));

    test_assert(ecs_has_id(world, f, t));

    ecs_fini(world);
}

void Delete_delete_empty(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, 0);
    test_assert(e != 0);

    ecs_delete(world, e);
    test_assert(ecs_exists(world, e));
    test_assert(!ecs_is_alive(world, e));
    
    ecs_fini(world);
}

void Delete_delete_nonexist(void) {
    ecs_world_t *world = ecs_mini();

    ecs_delete(world, 100);
    test_assert(!ecs_exists(world, 100));
    test_assert(!ecs_is_alive(world, 100));
    
    ecs_fini(world);
}

void Delete_delete_1st_of_3(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e1 = ecs_new(world, Position);
    ecs_entity_t e2 = ecs_new(world, Position);
    ecs_entity_t e3 = ecs_new(world, Position);
    test_assert(e1 != 0);
    test_assert(e2 != 0);
    test_assert(e3 != 0);

    ecs_delete(world, e1);
    test_assert(ecs_exists(world, e1));
    test_assert(!ecs_is_alive(world, e1));

    test_assert(ecs_exists(world, e2));
    test_assert(ecs_is_alive(world, e2));

    test_assert(ecs_exists(world, e3));
    test_assert(ecs_is_alive(world, e3));

    ecs_fini(world);
}

void Delete_delete_2nd_of_3(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e1 = ecs_new(world, Position);
    ecs_entity_t e2 = ecs_new(world, Position);
    ecs_entity_t e3 = ecs_new(world, Position);
    test_assert(e1 != 0);
    test_assert(e2 != 0);
    test_assert(e3 != 0);

    ecs_delete(world, e2);

    test_assert(ecs_exists(world, e1));
    test_assert(ecs_is_alive(world, e1));

    test_assert(ecs_exists(world, e2));
    test_assert(!ecs_is_alive(world, e2));

    test_assert(ecs_exists(world, e3));
    test_assert(ecs_is_alive(world, e3));
    
    ecs_fini(world);
}

void Delete_delete_3rd_of_3(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e1 = ecs_new(world, Position);
    ecs_entity_t e2 = ecs_new(world, Position);
    ecs_entity_t e3 = ecs_new(world, Position);
    test_assert(e1 != 0);
    test_assert(e2 != 0);
    test_assert(e3 != 0);

    ecs_delete(world, e3);

    test_assert(ecs_exists(world, e1));
    test_assert(ecs_is_alive(world, e1));

    test_assert(ecs_exists(world, e2));
    test_assert(ecs_is_alive(world, e2));

    test_assert(ecs_exists(world, e3));
    test_assert(!ecs_is_alive(world, e3));
    
    ecs_fini(world);
}

void Delete_delete_2_of_3(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e1 = ecs_new(world, Position);
    ecs_entity_t e2 = ecs_new(world, Position);
    ecs_entity_t e3 = ecs_new(world, Position);
    test_assert(e1 != 0);
    test_assert(e2 != 0);
    test_assert(e3 != 0);

    ecs_delete(world, e2);
    ecs_delete(world, e3);

    test_assert(ecs_exists(world, e1));
    test_assert(ecs_is_alive(world, e1));

    test_assert(ecs_exists(world, e2));
    test_assert(!ecs_is_alive(world, e2));

    test_assert(ecs_exists(world, e3));
    test_assert(!ecs_is_alive(world, e3));
    
    ecs_fini(world);
}

void Delete_delete_3_of_3(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e1 = ecs_new(world, Position);
    ecs_entity_t e2 = ecs_new(world, Position);
    ecs_entity_t e3 = ecs_new(world, Position);
    test_assert(e1 != 0);
    test_assert(e2 != 0);
    test_assert(e3 != 0);

    ecs_delete(world, e1);
    ecs_delete(world, e2);
    ecs_delete(world, e3);

    test_assert(ecs_exists(world, e1));
    test_assert(!ecs_is_alive(world, e1));

    test_assert(ecs_exists(world, e2));
    test_assert(!ecs_is_alive(world, e2));

    test_assert(ecs_exists(world, e3));
    test_assert(!ecs_is_alive(world, e3));

    ecs_fini(world);
}

static
void CreateEntity(ecs_iter_t *it) {
    ecs_id_t ecs_id(Position) = ecs_field_id(it, 1);
    const ecs_entity_t *ids = ecs_bulk_new(it->world, Position, 10);
    test_assert(ids != NULL);
}

static
void DeleteEntity(ecs_iter_t *it) {
    int i;
    for (i = 0; i < it->count; i ++) {
        ecs_delete(it->world, it->entities[i]);
    }
}

static int on_remove_system_invoked;

static
void OnRemoveSystem(ecs_iter_t *it) {
    int i;
    for (i = 0; i < it->count; i ++) {
        on_remove_system_invoked ++;
    }
}

void Delete_delete_w_on_remove(void) {
    ecs_world_t *world = ecs_init();

    ECS_COMPONENT(world, Position);

    ECS_SYSTEM(world, CreateEntity, EcsOnUpdate, [out] Position());
    ECS_SYSTEM(world, DeleteEntity, EcsOnStore, Position);
    ECS_OBSERVER(world, OnRemoveSystem, EcsOnRemove, Position);

    ecs_progress(world, 0);

    test_int( ecs_count(world, Position), 0);
    test_int(on_remove_system_invoked, 10);
    
    ecs_fini(world);
}

void Delete_clear_1_component(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);

    ecs_clear(world, e);
    test_assert(!ecs_get_type(world, e));

    ecs_entity_t e2 = ecs_new(world, 0);
    test_assert(e2 > e);
    
    ecs_fini(world);
}

void Delete_clear_2_components(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);
    ECS_COMPONENT(world, Velocity);

    ecs_entity_t e = ecs_new(world, Position);
    ecs_add(world, e, Velocity);
    test_assert(e != 0);

    ecs_clear(world, e);
    test_assert(!ecs_get_type(world, e));

    ecs_entity_t e2 = ecs_new(world, 0);
    test_assert(e2 > e);
    
    ecs_fini(world);
}

void Delete_alive_after_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);
    test_assert(ecs_is_alive(world, e));

    ecs_delete(world, e);
    
    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_exists(world, e));
    
    ecs_fini(world);
}

void Delete_alive_after_clear(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);
    test_assert(ecs_is_alive(world, e));

    ecs_clear(world, e);

    test_assert(!ecs_get_type(world, e));
    test_assert(ecs_is_alive(world, e));
    test_assert(ecs_exists(world, e));
    
    ecs_fini(world);
}

void Delete_alive_after_staged_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);
    test_assert(ecs_is_alive(world, e));

    ecs_defer_begin(world);
    ecs_delete(world, e);
    ecs_defer_end(world);
    
    test_assert(ecs_exists(world, e));
    test_assert(!ecs_is_alive(world, e));
    
    ecs_fini(world);
}

void Delete_alive_while_staged(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);
    test_assert(ecs_is_alive(world, e));

    ecs_defer_begin(world);
    test_assert(ecs_is_alive(world, e));
    ecs_defer_end(world);
    
    ecs_fini(world);
}

void Delete_alive_while_staged_w_delete(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);
    test_assert(ecs_is_alive(world, e));

    ecs_defer_begin(world);
    test_assert(ecs_is_alive(world, e));
    ecs_delete(world, e);
    test_assert(ecs_is_alive(world, e));
    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_exists(world, e));
    
    ecs_fini(world);
}

void Delete_alive_while_staged_w_delete_recycled_id(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);
    test_assert(ecs_is_alive(world, e));

    ecs_delete(world, e);
    e = ecs_new(world, 0);
    test_assert(ecs_is_alive(world, e));

    ecs_defer_begin(world);
    test_assert(ecs_is_alive(world, e));
    ecs_delete(world, e);
    test_assert(ecs_is_alive(world, e));
    ecs_defer_end(world);

    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_exists(world, e));
    
    ecs_fini(world);
}

void Delete_alive_after_recycle(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_entity_t e = ecs_new(world, Position);
    test_assert(e != 0);
    test_assert(ecs_is_alive(world, e));

    ecs_delete(world, e);
    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_exists(world, e));

    ecs_entity_t e2 = ecs_new(world, 0);
    test_assert(e2 != 0);
    test_assert(ecs_is_alive(world, e2));
    test_assert(e != e2);
    test_assert((ECS_ENTITY_MASK & e) == (ECS_ENTITY_MASK & e2));
    
    ecs_fini(world);
}

void Delete_delete_recycled(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new(world, 0);
    ecs_delete(world, e);
    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_exists(world, e));

    ecs_entity_t e2 = ecs_new(world, 0);
    test_assert(e != e2);
    test_assert((e2 & ECS_ENTITY_MASK) == (e & ECS_ENTITY_MASK));
    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_is_alive(world, e2));

    ecs_delete(world, e);
    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_is_alive(world, e2));

    ecs_fini(world);
}

void Delete_get_alive_for_alive(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new(world, 0);
    test_assert(e != 0);
    test_assert(ecs_is_alive(world, e));
    test_assert(ecs_exists(world, e));

    ecs_entity_t a = ecs_get_alive(world, e);
    test_assert(a != 0);
    test_assert(e == a);

    ecs_fini(world);
}

void Delete_get_alive_for_recycled(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new(world, 0);
    test_assert(e != 0);
    
    ecs_delete(world, e);
    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_exists(world, e));

    e = ecs_new(world, 0);
    test_assert(ecs_entity_t_lo(e) != e); // Ensure id is recycled

    ecs_entity_t a = ecs_get_alive(world, ecs_entity_t_lo(e));
    test_assert(a != 0);
    test_assert(e == a);

    ecs_fini(world);
}

void Delete_get_alive_for_not_alive(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new(world, 0);
    test_assert(e != 0);
    
    ecs_delete(world, e);
    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_exists(world, e));

    // Ensure id has no generation
    test_assert(ecs_entity_t_lo(e) == e);

    ecs_entity_t a = ecs_get_alive(world, e);
    test_assert(a == 0);

    ecs_fini(world);
}

void Delete_get_alive_w_generation_for_recycled_alive(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new(world, 0);
    test_assert(e != 0);
    
    ecs_delete(world, e);
    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_exists(world, e));

    e = ecs_new(world, 0);
    test_assert(ecs_entity_t_lo(e) != e);

    ecs_entity_t a = ecs_get_alive(world, e);
    test_assert(a != 0);
    test_assert(e == a);

    ecs_fini(world);
}

void Delete_get_alive_w_generation_for_recycled_not_alive(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_new(world, 0);
    test_assert(e != 0);
    
    ecs_delete(world, e);
    test_assert(!ecs_is_alive(world, e));
    test_assert(ecs_exists(world, e));

    e = ecs_new(world, 0);
    test_assert(ecs_entity_t_lo(e) != e);

    ecs_delete(world, e);

    test_assert(ecs_get_alive(world, e) == 0);

    ecs_fini(world);
}

void Delete_get_alive_for_0(void) {
    ecs_world_t *world = ecs_mini();

    test_assert(0 == ecs_get_alive(world, 0));

    ecs_fini(world);
}

void Delete_get_alive_for_nonexistent(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t e = ecs_get_alive(world, 1000);
    test_assert(e == 0);

    ecs_fini(world);
}

static int move_invoked = 0;
static int dtor_invoked = 0;

static int dtor_x;
static int dtor_y;

static int move_dst_x;
static int move_dst_y;

static int move_src_x;
static int move_src_y;

static ECS_DTOR(Position, ptr, {
    dtor_invoked ++;
    dtor_x = ptr->x;
    dtor_y = ptr->y;
})

static ECS_MOVE(Position, dst, src, {
    move_dst_x = dst->x;
    move_dst_y = dst->y; 
    move_src_x = src->x;
    move_src_y = src->y;         
    *dst = *src;
    move_invoked ++;
})

void Delete_move_w_dtor_move(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_set_hooks(world, Position, {
        .dtor = ecs_dtor(Position),
        .move = ecs_move(Position),
    });

    ecs_entity_t e_1 = ecs_new_id(world);
    test_assert(e_1 != 0);

    ecs_entity_t e_2 = ecs_new_id(world);
    test_assert(e_2 != 0);


    ecs_set(world, e_1, Position, {10, 20});
    ecs_set(world, e_2, Position, {30, 40}); // append after e_1

    test_int(move_invoked, 0);

    ecs_delete(world, e_1); // move e_2 to e_1

    test_assert(!ecs_is_alive(world, e_1));
    test_assert(ecs_is_alive(world, e_2));

    // counter intuitive, but this is because in this case the move is
    // responsible for cleaning up e_1, because e_2 is moved into e_1
    test_int(dtor_invoked, 1);
    test_int(dtor_x, 30);
    test_int(dtor_y, 40);

    test_int(move_invoked, 1);
    test_int(move_dst_x, 10);
    test_int(move_dst_y, 20);
    test_int(move_src_x, 30);
    test_int(move_src_y, 40);

    const Position *p = ecs_get(world, e_2, Position);
    test_assert(p != NULL);
    test_int(p->x, 30);
    test_int(p->y, 40);

    ecs_fini(world);
}

void Delete_move_w_dtor_no_move(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_set_hooks(world, Position, {
        .dtor = ecs_dtor(Position)
    });

    ecs_entity_t e_1 = ecs_new_id(world);
    test_assert(e_1 != 0);

    ecs_entity_t e_2 = ecs_new_id(world);
    test_assert(e_2 != 0);

    ecs_set(world, e_1, Position, {10, 20});
    ecs_set(world, e_2, Position, {30, 40}); // append after e_1

    ecs_delete(world, e_1); // move e_2 to e_1

    test_assert(!ecs_is_alive(world, e_1));
    test_assert(ecs_is_alive(world, e_2));

    test_int(dtor_invoked, 1);
    test_int(dtor_x, 10);
    test_int(dtor_y, 20);

    const Position *p = ecs_get(world, e_2, Position);
    test_assert(p != NULL);
    test_int(p->x, 30);
    test_int(p->y, 40);

    ecs_fini(world);
}

void Delete_move_w_no_dtor_move(void) {
    ecs_world_t *world = ecs_mini();

    ECS_COMPONENT(world, Position);

    ecs_set_hooks(world, Position, {
        .move = ecs_move(Position)
    });

    ecs_entity_t e_1 = ecs_new_id(world);
    test_assert(e_1 != 0);

    ecs_entity_t e_2 = ecs_new_id(world);
    test_assert(e_2 != 0);

    ecs_set(world, e_1, Position, {10, 20});
    ecs_set(world, e_2, Position, {30, 40}); // append after e_1

    ecs_delete(world, e_1); // move e_2 to e_1

    test_assert(!ecs_is_alive(world, e_1));
    test_assert(ecs_is_alive(world, e_2));

    test_int(move_invoked, 1);
    test_int(move_dst_x, 10);
    test_int(move_dst_y, 20);
    test_int(move_src_x, 30);
    test_int(move_src_y, 40);    

    const Position *p = ecs_get(world, e_2, Position);
    test_assert(p != NULL);
    test_int(p->x, 30);
    test_int(p->y, 40);

    ecs_fini(world);
}

void Delete_wrap_generation_count(void) {
    ecs_world_t *world = ecs_mini();

    ecs_entity_t start = ecs_new_id(world);
    ecs_entity_t e = start;

    for (int i = 0; i < 65535; i ++) {
        ecs_delete(world, e);
        e = ecs_new_id(world);
        test_assert(e != start);
        test_assert((uint32_t)e == start);
    }

    ecs_delete(world, e);
    e = ecs_new_id(world);
    test_assert(e == start);

    for (int i = 0; i < 65535; i ++) {
        ecs_delete(world, e);
        e = ecs_new_id(world);
        test_assert(e != start);
        test_assert((uint32_t)e == start);
    }

    ecs_delete(world, e);
    e = ecs_new_id(world);
    test_assert(e == start);

    ecs_fini(world);
}
