#include <addons.h>

static bool system_a_invoked;
static bool system_b_invoked;
static bool system_c_invoked;

static
void SystemA(ecs_iter_t *it) {
    test_int(it->delta_time, 1.0);
    test_int(it->delta_system_time, 3.0);
    system_a_invoked = true;
}

static 
void SystemB(ecs_iter_t *it) {
    test_int(it->delta_time, 1.0);
    test_int(it->delta_system_time, 3.0);
    system_b_invoked = true;
}

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

    ECS_COMPONENT(world, Position);

    ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position);

    ecs_new(world, Position);

    ecs_entity_t timer = ecs_set_timeout(world, SystemA, 3.0);
    test_assert(timer != 0);
    test_assert(timer == SystemA);

    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);

    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, true);

    system_a_invoked = false;

    /* Make sure this was a one-shot timer */
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);    
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);

    ecs_fini(world);
}

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

    ECS_COMPONENT(world, Position);

    ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position);

    ecs_new(world, Position);

    ecs_entity_t timer = ecs_set_interval(world, SystemA, 3.0);
    test_assert(timer != 0);
    test_assert(timer == SystemA);

    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, true);

    system_a_invoked = false;

    /* Make sure this was not a one-shot timer */
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);    
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, true);

    ecs_fini(world);
}

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

    ECS_COMPONENT(world, Position);

    ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position);
    ECS_SYSTEM(world, SystemB, EcsOnUpdate, Position);

    ecs_new(world, Position);

    ecs_entity_t timer = ecs_set_timeout(world, 0, 3.0);
    test_assert(timer != 0);

    ecs_set_tick_source(world, SystemA, timer);
    ecs_set_tick_source(world, SystemB, timer);

    test_bool(system_a_invoked, false);
    test_bool(system_b_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    test_bool(system_b_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    test_bool(system_b_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, true);
    test_bool(system_b_invoked, true);

    system_a_invoked = false;
    system_b_invoked = false;

    /* Make sure this was a one-shot timer */
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);  
    test_bool(system_b_invoked, false);  
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    test_bool(system_b_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    test_bool(system_b_invoked, false);

    ecs_fini(world);
}

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

    ECS_COMPONENT(world, Position);

    ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position);
    ECS_SYSTEM(world, SystemB, EcsOnUpdate, Position);

    ecs_new(world, Position);

    ecs_entity_t timer = ecs_set_interval(world, 0, 3.0);
    test_assert(timer != 0);

    ecs_set_tick_source(world, SystemA, timer);
    ecs_set_tick_source(world, SystemB, timer);

    test_bool(system_a_invoked, false);
    test_bool(system_b_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    test_bool(system_b_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    test_bool(system_b_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, true);
    test_bool(system_b_invoked, true);

    system_a_invoked = false;
    system_b_invoked = false;

    /* Make sure this was a one-shot timer */
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);  
    test_bool(system_b_invoked, false);  
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    test_bool(system_b_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, true);
    test_bool(system_b_invoked, true);

    ecs_fini(world);
}

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

    ECS_COMPONENT(world, Position);

    ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position);

    ecs_new(world, Position);

    ecs_entity_t timer = ecs_set_timeout(world, SystemA, 3.0);
    test_assert(timer != 0);
    test_assert(timer == SystemA);

    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);

    /* Stop timer */
    ecs_stop_timer(world, timer);

    /* Timer is stopped, should not trigger system */
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);

    /* Start timer, this should reset timer */
    ecs_start_timer(world, timer);

    /* Make sure this was a one-shot timer */
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);    
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);

    /* System should have been triggered */
    test_bool(system_a_invoked, true);

    ecs_fini(world);
}

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

    ECS_COMPONENT(world, Position);

    ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position);

    ecs_new(world, Position);

    ecs_entity_t timer = ecs_set_interval(world, SystemA, 3.0);
    test_assert(timer != 0);
    test_assert(timer == SystemA);

    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);

    /* Stop timer */
    ecs_stop_timer(world, timer);

    /* Timer is stopped, should not trigger system */
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);

    /* Start timer, this should reset timer */
    ecs_start_timer(world, timer);

    /* Make sure this was a one-shot timer */
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);    
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);

    /* System should have been triggered */
    test_bool(system_a_invoked, true);

    system_a_invoked = false;

    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);

    /* Ensure that timer still triggers repeatedly */
    test_bool(system_a_invoked, true);    

    ecs_fini(world);
}

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

    ECS_COMPONENT(world, Position);

    ECS_SYSTEM(world, SystemA, EcsOnUpdate, Position);

    ecs_new(world, Position);

    ecs_entity_t filter = ecs_set_rate(world, SystemA, 3, 0);
    test_assert(filter != 0);
    test_assert(filter == SystemA);

    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);

    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, true);

    system_a_invoked = false;

    /* Make sure this was a one-shot timer */
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);    
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, false);
    ecs_progress(world, 1.0);
    test_bool(system_a_invoked, true);

    ecs_fini(world);
}

static
void SystemC(ecs_iter_t *it) {
    test_int(it->delta_time, 1.0);
    test_int(it->delta_system_time, 6.0);
    system_c_invoked = true;
}

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

    ECS_COMPONENT(world, Position);

    ECS_SYSTEM(world, SystemC, EcsOnUpdate, Position);

    ecs_new(world, Position);

    ecs_entity_t filter_a = ecs_set_rate(world, 0, 2, 0);
    test_assert(filter_a != 0);

    ecs_entity_t filter_b = ecs_set_rate(world, SystemC, 3, filter_a);
    test_assert(filter_b != 0);
    test_assert(filter_b == SystemC);

    test_bool(system_c_invoked, false);

    int i;
    for (i = 0; i < 5; i ++) {
        ecs_progress(world, 1.0);
        test_bool(system_c_invoked, false);
    }

    ecs_progress(world, 1.0);
    test_bool(system_c_invoked, true);

    system_c_invoked = false;

    /* Make sure rate filter triggers repeatedly */
    for (i = 0; i < 5; i ++) {
        ecs_progress(world, 1.0);
        test_bool(system_c_invoked, false);
    }

    ecs_progress(world, 1.0);
    test_bool(system_c_invoked, true);

    ecs_fini(world);
}

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

    ECS_COMPONENT(world, Position);
    ECS_SYSTEM(world, SystemC, EcsOnUpdate, Position);
    ECS_ENTITY(world, E1, Position);

    ecs_entity_t timer = ecs_set_interval(world, 0, 2.0);
    test_assert(timer != 0);

    ecs_entity_t filter = ecs_set_rate(world, SystemC, 3, timer);
    test_assert(filter != 0);
    test_assert(filter == SystemC);

    test_bool(system_c_invoked, false);

    int i;
    for (i = 0; i < 5; i ++) {
        ecs_progress(world, 1.0);
        test_bool(system_c_invoked, false);
    }

    ecs_progress(world, 1.0);
    test_bool(system_c_invoked, true);

    system_c_invoked = false;

    /* Make sure rate filter triggers repeatedly */
    for (i = 0; i < 5; i ++) {
        ecs_progress(world, 1.0);
        test_bool(system_c_invoked, false);
    }

    ecs_progress(world, 1.0);
    test_bool(system_c_invoked, true);

    ecs_fini(world);
}

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

    ECS_COMPONENT(world, Position);

    ECS_SYSTEM(world, SystemC, EcsOnUpdate, Position);

    ecs_new(world, Position);

    // Not actually a tick source
    ecs_entity_t filter_a = ecs_new_id(world);
    test_assert(filter_a != 0);

    ecs_entity_t filter_b = ecs_set_rate(world, SystemC, 6, filter_a);
    test_assert(filter_b != 0);
    test_assert(filter_b == SystemC);

    test_bool(system_c_invoked, false);

    int i;
    for (i = 0; i < 5; i ++) {
        ecs_progress(world, 1.0);
        test_bool(system_c_invoked, false);
    }

    ecs_progress(world, 1.0);
    test_bool(system_c_invoked, true);

    system_c_invoked = false;

    /* Make sure rate filter triggers repeatedly */
    for (i = 0; i < 5; i ++) {
        ecs_progress(world, 1.0);
        test_bool(system_c_invoked, false);
    }

    ecs_progress(world, 1.0);
    test_bool(system_c_invoked, true);

    ecs_fini(world);
}

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

    ecs_entity_t timer = ecs_set_timeout(world, 0, 1.0);

    int i;
    for (i = 0; i < 3; i ++) {
        ecs_progress(world, 0.3);
        const EcsTickSource *src = ecs_get(world, timer, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    ecs_progress(world, 0.3);
    const EcsTickSource *src = ecs_get(world, timer, EcsTickSource);
    test_assert(src != NULL);
    test_bool(src->tick, true);   

    /* Ensure timer doesn't tick again */
    for (i = 0; i < 12; i ++) {
        ecs_progress(world, 0.3);
        test_bool(src->tick, false);
    }

    ecs_fini(world);
}

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

    ecs_entity_t timer = ecs_set_interval(world, 0, 1.0);

    int i;
    for (i = 0; i < 3; i ++) {
        ecs_progress(world, 0.3);
        const EcsTickSource *src = ecs_get(world, timer, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    ecs_progress(world, 0.3);
    const EcsTickSource *src = ecs_get(world, timer, EcsTickSource);
    test_assert(src != NULL);
    test_bool(src->tick, true);   

    for (i = 0; i < 2; i ++) {
        ecs_progress(world, 0.3);
        test_bool(src->tick, false);
    }

    /* Timer should tick again */
    ecs_progress(world, 0.3);
    test_bool(src->tick, true);

    ecs_fini(world);
}

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

    /* Specify 0 for source. This applies the rate to the frame ticks */
    ecs_entity_t rate = ecs_set_rate(world, 0, 4, 0);

    int i;
    for (i = 0; i < 3; i ++) {
        ecs_progress(world, 0);
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    ecs_progress(world, 0);
    const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
    test_assert(src != NULL);
    test_bool(src->tick, true);   

    for (i = 0; i < 3; i ++) {
        ecs_progress(world, 0);
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    /* Filter should tick again */
    ecs_progress(world, 0);
    test_bool(src->tick, true);

    ecs_fini(world);
}

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

    /* Nested rate filter */
    ecs_entity_t parent = ecs_set_rate(world, 0, 2, 0);
    ecs_entity_t rate = ecs_set_rate(world, 0, 2, parent);

    int i;
    for (i = 0; i < 3; i ++) {
        ecs_progress(world, 0);
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    ecs_progress(world, 0);
    const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
    test_assert(src != NULL);
    test_bool(src->tick, true);   

    for (i = 0; i < 3; i ++) {
        ecs_progress(world, 0);
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    /* Filter should tick again */
    ecs_progress(world, 0);
    test_bool(src->tick, true);

    ecs_fini(world);
}

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

    /* Rate filter with source that is not a tick source */
    ecs_entity_t parent = ecs_new(world, 0);
    ecs_entity_t rate = ecs_set_rate(world, 0, 4, parent);

    int i;
    for (i = 0; i < 3; i ++) {
        ecs_progress(world, 0);
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    ecs_progress(world, 0);
    const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
    test_assert(src != NULL);
    test_bool(src->tick, true);   

    for (i = 0; i < 3; i ++) {
        ecs_progress(world, 0);
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    /* Filter should tick again */
    ecs_progress(world, 0);
    test_bool(src->tick, true);

    ecs_fini(world);
}

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

    ecs_entity_t tick = ecs_set(world, 0, EcsTickSource, {0});

    int i;
    for (i = 0; i < 10; i ++) {
        ecs_progress(world, 0);
        const EcsTickSource *src = ecs_get(world, tick, EcsTickSource);
        test_assert(src != NULL);

        /* Tick should always be true, no filter is applied */
        test_bool(src->tick, true);
    }

    ecs_fini(world);
}

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

    ecs_entity_t timer = ecs_set_interval(world, 0, 1.0);
    ecs_entity_t rate = ecs_set_rate(world, 0, 3.0, timer);

    for (int i = 0; i < 5; i ++) {
        ecs_progress(world, 0.5);
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    ecs_progress(world, 0.5);

    {
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, true);
    }

    ecs_stop_timer(world, timer);

    for (int i = 0; i < 5; i ++) {
        ecs_progress(world, 0.5);
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    ecs_progress(world, 0.5);

    {
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }
    
    ecs_start_timer(world, timer);

    for (int i = 0; i < 5; i ++) {
        ecs_progress(world, 0.5);
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    ecs_progress(world, 0.5);

    {
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, true);
    }

    ecs_fini(world);
}

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

    ecs_entity_t timer = ecs_set_interval(world, 0, 1.0);
    ecs_entity_t rate = ecs_set_rate(world, timer, 3.0, timer);
    test_assert(rate == timer);

    for (int i = 0; i < 5; i ++) {
        ecs_progress(world, 0.5);
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    ecs_progress(world, 0.5);

    {
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, true);
    }

    ecs_stop_timer(world, timer);

    for (int i = 0; i < 5; i ++) {
        ecs_progress(world, 0.5);
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    ecs_progress(world, 0.5);

    {
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }
    
    ecs_start_timer(world, timer);

    for (int i = 0; i < 5; i ++) {
        ecs_progress(world, 0.5);
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, false);
    }

    ecs_progress(world, 0.5);

    {
        const EcsTickSource *src = ecs_get(world, rate, EcsTickSource);
        test_assert(src != NULL);
        test_bool(src->tick, true);
    }

    ecs_fini(world);
}

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

    ecs_entity_t timer_a = ecs_set_interval(world, 0, 1.0);
    {
        const EcsTimer *t = ecs_get(world, timer_a, EcsTimer);
        test_assert(t != NULL);
        test_assert(t->time == 0);
    }

    ecs_randomize_timers(world);

    {
        const EcsTimer *t = ecs_get(world, timer_a, EcsTimer);
        test_assert(t != NULL);
        test_assert(t->time != 0);
    }

    ecs_entity_t timer_b = ecs_set_interval(world, 0, 1.0);
    {
        const EcsTimer *t = ecs_get(world, timer_b, EcsTimer);
        test_assert(t != NULL);
        test_assert(t->time != 0);
    }

    ecs_fini(world);
}
