/**
 * @file observer.c
 * @brief Observer implementation.
 * 
 * The observer implementation contains functions for creating, deleting and
 * invoking observers. The code is split up into single-term observers and 
 * multi-term observers. Multi-term observers are created from multiple single-
 * term observers.
 */

#include "private_api.h"
#include <stddef.h>

static
ecs_entity_t flecs_get_observer_event(
    ecs_term_t *term,
    ecs_entity_t event)
{
    /* If operator is Not, reverse the event */
    if (term->oper == EcsNot) {
        if (event == EcsOnAdd) {
            event = EcsOnRemove;
        } else if (event == EcsOnRemove) {
            event = EcsOnAdd;
        }
    }

    return event;
}

static
ecs_flags32_t flecs_id_flag_for_event(
    ecs_entity_t e)
{
    if (e == EcsOnAdd) {
        return EcsIdHasOnAdd;
    }
    if (e == EcsOnRemove) {
        return EcsIdHasOnRemove;
    }
    if (e == EcsOnSet) {
        return EcsIdHasOnSet;
    }
    if (e == EcsUnSet) {
        return EcsIdHasUnSet;
    }
    if (e == EcsOnTableFill) {
        return EcsIdHasOnTableFill;
    }
    if (e == EcsOnTableEmpty) {
        return EcsIdHasOnTableEmpty;
    }
    if (e == EcsOnTableCreate) {
        return EcsIdHasOnTableCreate;
    }
    if (e == EcsOnTableDelete) {
        return EcsIdHasOnTableDelete;
    }
    return 0;
}

static
void flecs_inc_observer_count(
    ecs_world_t *world,
    ecs_entity_t event,
    ecs_event_record_t *evt,
    ecs_id_t id,
    int32_t value)
{
    ecs_event_id_record_t *idt = flecs_event_id_record_ensure(world, evt, id);
    ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL);
    
    int32_t result = idt->observer_count += value;
    if (result == 1) {
        /* Notify framework that there are observers for the event/id. This 
         * allows parts of the code to skip event evaluation early */
        flecs_notify_tables(world, id, &(ecs_table_event_t){
            .kind = EcsTableTriggersForId,
            .event = event
        });

        ecs_flags32_t flags = flecs_id_flag_for_event(event);
        if (flags) {
            ecs_id_record_t *idr = flecs_id_record_get(world, id);
            if (idr) {
                idr->flags |= flags;
            }
        }
    } else if (result == 0) {
        /* Ditto, but the reverse */
        flecs_notify_tables(world, id, &(ecs_table_event_t){
            .kind = EcsTableNoTriggersForId,
            .event = event
        });

        ecs_flags32_t flags = flecs_id_flag_for_event(event);
        if (flags) {
            ecs_id_record_t *idr = flecs_id_record_get(world, id);
            if (idr) {
                idr->flags &= ~flags;
            }
        }

        flecs_event_id_record_remove(evt, id);
        ecs_os_free(idt);
    }
}

static
void flecs_register_observer_for_id(
    ecs_world_t *world,
    ecs_observable_t *observable,
    ecs_observer_t *observer,
    size_t offset)
{
    ecs_id_t term_id = observer->register_id;
    ecs_term_t *term = &observer->filter.terms[0];
    ecs_entity_t trav = term->src.trav;

    int i;
    for (i = 0; i < observer->event_count; i ++) {
        ecs_entity_t event = flecs_get_observer_event(
            term, observer->events[i]);

        /* Get observers for event */
        ecs_event_record_t *er = flecs_event_record_ensure(observable, event);
        ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL);

        /* Get observers for (component) id for event */
        ecs_event_id_record_t *idt = flecs_event_id_record_ensure(
            world, er, term_id);
        ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL);

        ecs_map_t *observers = ECS_OFFSET(idt, offset);
        ecs_map_init_w_params_if(observers, &world->allocators.ptr);
        ecs_map_insert_ptr(observers, observer->filter.entity, observer);

        flecs_inc_observer_count(world, event, er, term_id, 1);
        if (trav) {
            flecs_inc_observer_count(world, event, er, 
                ecs_pair(trav, EcsWildcard), 1);
        }
    }
}

static
void flecs_uni_observer_register(
    ecs_world_t *world,
    ecs_observable_t *observable,
    ecs_observer_t *observer)
{
    ecs_term_t *term = &observer->filter.terms[0];
    ecs_flags32_t flags = term->src.flags;

    if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) {
        flecs_register_observer_for_id(world, observable, observer,
            offsetof(ecs_event_id_record_t, self_up));
    } else if (flags & EcsSelf) {
        flecs_register_observer_for_id(world, observable, observer,
            offsetof(ecs_event_id_record_t, self));
    } else if (flags & EcsUp) {
        ecs_assert(term->src.trav != 0, ECS_INTERNAL_ERROR, NULL);
        flecs_register_observer_for_id(world, observable, observer,
            offsetof(ecs_event_id_record_t, up));
    }
}

static
void flecs_unregister_observer_for_id(
    ecs_world_t *world,
    ecs_observable_t *observable,
    ecs_observer_t *observer,
    size_t offset)
{
    ecs_id_t term_id = observer->register_id;
    ecs_term_t *term = &observer->filter.terms[0];
    ecs_entity_t trav = term->src.trav;

    int i;
    for (i = 0; i < observer->event_count; i ++) {
        ecs_entity_t event = flecs_get_observer_event(
            term, observer->events[i]);

        /* Get observers for event */
        ecs_event_record_t *er = flecs_event_record_get(observable, event);
        ecs_assert(er != NULL, ECS_INTERNAL_ERROR, NULL);

        /* Get observers for (component) id */
        ecs_event_id_record_t *idt = flecs_event_id_record_get(er, term_id);
        ecs_assert(idt != NULL, ECS_INTERNAL_ERROR, NULL);

        ecs_map_t *id_observers = ECS_OFFSET(idt, offset);
        ecs_map_remove(id_observers, observer->filter.entity);
        if (!ecs_map_count(id_observers)) {
            ecs_map_fini(id_observers);
        }

        flecs_inc_observer_count(world, event, er, term_id, -1);
        if (trav) {
            flecs_inc_observer_count(world, event, er, 
                ecs_pair(trav, EcsWildcard), -1);
        }
    }
}

static
void flecs_unregister_observer(
    ecs_world_t *world,
    ecs_observable_t *observable,
    ecs_observer_t *observer)
{
    ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL);
    if (!observer->filter.terms) {
        ecs_assert(observer->filter.term_count == 0, ECS_INTERNAL_ERROR, NULL);
        return;
    }

    ecs_term_t *term = &observer->filter.terms[0];
    ecs_flags32_t flags = term->src.flags;

    if ((flags & (EcsSelf|EcsUp)) == (EcsSelf|EcsUp)) {
        flecs_unregister_observer_for_id(world, observable, observer,
            offsetof(ecs_event_id_record_t, self_up));
    } else if (flags & EcsSelf) {
        flecs_unregister_observer_for_id(world, observable, observer,
            offsetof(ecs_event_id_record_t, self));
    } else if (flags & EcsUp) {
        flecs_unregister_observer_for_id(world, observable, observer,
            offsetof(ecs_event_id_record_t, up));
    }
}

static
bool flecs_ignore_observer(
    ecs_observer_t *observer,
    ecs_table_t *table,
    int32_t evtx)
{
    ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL);
    ecs_assert(table != NULL, ECS_INTERNAL_ERROR, NULL);

    int32_t *last_event_id = observer->last_event_id;
    if (last_event_id && last_event_id[0] == evtx) {
        return true;
    }

    ecs_flags32_t table_flags = table->flags, filter_flags = observer->filter.flags;

    bool result = (table_flags & EcsTableIsPrefab) &&
        !(filter_flags & EcsFilterMatchPrefab);
    result = result || ((table_flags & EcsTableIsDisabled) &&
        !(filter_flags & EcsFilterMatchDisabled));

    return result;
}

static
bool flecs_is_simple_result(
    ecs_iter_t *it)
{
    return (it->count == 1) || (it->sizes[0] == 0) || (it->sources[0] == 0);
}

static
void flecs_observer_invoke(
    ecs_world_t *world,
    ecs_iter_t *it,
    ecs_observer_t *observer,
    ecs_iter_action_t callback,
    int32_t term_index,
    bool simple_result) 
{
    ecs_assert(it->callback != NULL, ECS_INVALID_PARAMETER, NULL);

    if (ecs_should_log_3()) {
        char *path = ecs_get_fullpath(world, it->system);
        ecs_dbg_3("observer: invoke %s", path);
        ecs_os_free(path);
    }

    ecs_log_push_3();

    world->info.observers_ran_frame ++;

    ecs_filter_t *filter = &observer->filter;
    ecs_assert(term_index < filter->term_count, ECS_INTERNAL_ERROR, NULL);
    ecs_term_t *term = &filter->terms[term_index];
    if (term->oper != EcsNot) {
        ecs_assert((it->offset + it->count) <= ecs_table_count(it->table), 
            ECS_INTERNAL_ERROR, NULL);
    }

    bool instanced = filter->flags & EcsFilterIsInstanced;
    bool match_this = filter->flags & EcsFilterMatchThis;
    bool table_only = it->flags & EcsIterTableOnly;
    if (match_this && (simple_result || instanced || table_only)) {
        callback(it);
    } else {
        ecs_entity_t observer_src = term->src.id;
        if (observer_src && !(term->src.flags & EcsIsEntity)) {
            observer_src = 0;
        }

        ecs_entity_t *entities = it->entities;
        int32_t i, count = it->count;
        ecs_entity_t src = it->sources[0];
        it->count = 1;
        for (i = 0; i < count; i ++) {
            ecs_entity_t e = entities[i];
            it->entities = &e;
            if (!observer_src) {
                callback(it);
            } else if (observer_src == e) {
                ecs_entity_t dummy = 0;
                it->entities = &dummy;
                if (!src) {
                    it->sources[0] = e;
                }
                callback(it);
                it->sources[0] = src;
                break;
            }
        }
        it->entities = entities;
        it->count = count;
    }

    ecs_log_pop_3();
}

static
void flecs_default_uni_observer_run_callback(ecs_iter_t *it) {
    ecs_observer_t *o = it->ctx;
    it->ctx = o->ctx;
    it->callback = o->callback;

    if (ecs_should_log_3()) {
        char *path = ecs_get_fullpath(it->world, it->system);
        ecs_dbg_3("observer %s", path);
        ecs_os_free(path);
    }

    ecs_log_push_3();
    flecs_observer_invoke(it->real_world, it, o, o->callback, 0,
        flecs_is_simple_result(it));
    ecs_log_pop_3();
}

static
void flecs_uni_observer_invoke(
    ecs_world_t *world,
    ecs_observer_t *observer,
    ecs_iter_t *it,
    ecs_table_t *table,
    ecs_entity_t trav,
    int32_t evtx,
    bool simple_result)
{
    ecs_filter_t *filter = &observer->filter;
    ecs_term_t *term = &filter->terms[0];
    if (flecs_ignore_observer(observer, table, evtx)) {
        return;
    }

    ecs_assert(trav == 0 || it->sources[0] != 0, ECS_INTERNAL_ERROR, NULL);
    if (trav && term->src.trav != trav) {
        return;
    }

    bool is_filter = term->inout == EcsInOutNone;
    ECS_BIT_COND(it->flags, EcsIterNoData, is_filter);
    it->system = observer->filter.entity;
    it->ctx = observer->ctx;
    it->binding_ctx = observer->binding_ctx;
    it->term_index = observer->term_index;
    it->terms = term;

    ecs_entity_t event = it->event;
    it->event = flecs_get_observer_event(term, event);

    if (observer->run) {
        it->next = flecs_default_observer_next_callback;
        it->callback = flecs_default_uni_observer_run_callback;
        it->ctx = observer;
        observer->run(it);
    } else {
        ecs_iter_action_t callback = observer->callback;
        it->callback = callback;
        flecs_observer_invoke(world, it, observer, callback, 0, simple_result);
    }

    it->event = event;
}

void flecs_observers_invoke(
    ecs_world_t *world,
    ecs_map_t *observers,
    ecs_iter_t *it,
    ecs_table_t *table,
    ecs_entity_t trav,
    int32_t evtx)
{
    if (ecs_map_is_init(observers)) {
        ecs_table_lock(it->world, table);

        bool simple_result = flecs_is_simple_result(it);
        ecs_map_iter_t oit = ecs_map_iter(observers);
        while (ecs_map_next(&oit)) {
            ecs_observer_t *o = ecs_map_ptr(&oit);
            ecs_assert(it->table == table, ECS_INTERNAL_ERROR, NULL);
            flecs_uni_observer_invoke(world, o, it, table, trav, evtx, simple_result);
        }

        ecs_table_unlock(it->world, table);
    }
}

static
bool flecs_multi_observer_invoke(ecs_iter_t *it) {
    ecs_observer_t *o = it->ctx;
    ecs_world_t *world = it->real_world;

    if (o->last_event_id[0] == world->event_id) {
        /* Already handled this event */
        return false;
    }

    o->last_event_id[0] = world->event_id;

    ecs_iter_t user_it = *it;
    user_it.field_count = o->filter.field_count;
    user_it.terms = o->filter.terms;
    user_it.flags = 0;
    ECS_BIT_COND(user_it.flags, EcsIterNoData,    
        ECS_BIT_IS_SET(o->filter.flags, EcsFilterNoData));
    user_it.ids = NULL;
    user_it.columns = NULL;
    user_it.sources = NULL;
    user_it.sizes = NULL;
    user_it.ptrs = NULL;

    flecs_iter_init(it->world, &user_it, flecs_iter_cache_all);
    user_it.flags |= (it->flags & EcsIterTableOnly);

    ecs_table_t *table = it->table;
    ecs_table_t *prev_table = it->other_table;
    int32_t pivot_term = it->term_index;
    ecs_term_t *term = &o->filter.terms[pivot_term];

    int32_t column = it->columns[0];
    if (term->oper == EcsNot) {
        table = it->other_table;
        prev_table = it->table;
    }

    if (!table) {
        table = &world->store.root;
    }
    if (!prev_table) {
        prev_table = &world->store.root;
    }

    if (column < 0) {
        column = -column;
    }

    user_it.columns[0] = 0;
    user_it.columns[pivot_term] = column;
    user_it.sources[pivot_term] = it->sources[0];
    user_it.sizes = o->filter.sizes;

    if (flecs_filter_match_table(world, &o->filter, table, user_it.ids, 
        user_it.columns, user_it.sources, NULL, NULL, false, pivot_term, 
        user_it.flags))
    {
        /* Monitor observers only invoke when the filter matches for the first
         * time with an entity */
        if (o->is_monitor) {
            if (flecs_filter_match_table(world, &o->filter, prev_table, 
                NULL, NULL, NULL, NULL, NULL, true, -1, user_it.flags)) 
            {
                goto done;
            }
        }

        /* While filter matching needs to be reversed for a Not term, the
         * component data must be fetched from the table we got notified for.
         * Repeat the matching process for the non-matching table so we get the
         * correct column ids and sources, which we need for populate_data */
        if (term->oper == EcsNot) {
            flecs_filter_match_table(world, &o->filter, prev_table, user_it.ids, 
                user_it.columns, user_it.sources, NULL, NULL, false, -1, 
                user_it.flags | EcsFilterPopulate);
        }

        flecs_iter_populate_data(world, &user_it, it->table, it->offset, 
            it->count, user_it.ptrs);

        user_it.ptrs[pivot_term] = it->ptrs[0];
        user_it.ids[pivot_term] = it->event_id;
        user_it.system = o->filter.entity;
        user_it.term_index = pivot_term;
        user_it.ctx = o->ctx;
        user_it.binding_ctx = o->binding_ctx;
        user_it.field_count = o->filter.field_count;
        user_it.callback = o->callback;
        
        flecs_iter_validate(&user_it);
        ecs_table_lock(it->world, table);
        flecs_observer_invoke(world, &user_it, o, o->callback, 
            pivot_term, flecs_is_simple_result(&user_it));
        ecs_table_unlock(it->world, table);
        ecs_iter_fini(&user_it);
        return true;
    }

done:
    ecs_iter_fini(&user_it);
    return false;
}

bool ecs_observer_default_run_action(ecs_iter_t *it) {
    ecs_observer_t *o = it->ctx;
    if (o->is_multi) {
        return flecs_multi_observer_invoke(it);
    } else {
        it->ctx = o->ctx;
        ecs_table_lock(it->world, it->table);
        flecs_observer_invoke(it->real_world, it, o, o->callback, 0,
            flecs_is_simple_result(it));
        ecs_table_unlock(it->world, it->table);
        return true;
    }
}

static
void flecs_default_multi_observer_run_callback(ecs_iter_t *it) {
    flecs_multi_observer_invoke(it);
}

/* For convenience, so applications can (in theory) use a single run callback 
 * that uses ecs_iter_next to iterate results */
bool flecs_default_observer_next_callback(ecs_iter_t *it) {
    if (it->interrupted_by) {
        return false;
    } else {
        /* Use interrupted_by to signal the next iteration must return false */
        it->interrupted_by = it->system;
        return true;
    }
}

/* Run action for children of multi observer */
static
void flecs_multi_observer_builtin_run(ecs_iter_t *it) {
    ecs_observer_t *observer = it->ctx;
    ecs_run_action_t run = observer->run;

    if (run) {
        it->next = flecs_default_observer_next_callback;
        it->callback = flecs_default_multi_observer_run_callback;
        it->interrupted_by = 0;
        run(it);
    } else {
        flecs_multi_observer_invoke(it);
    }
}

static
void flecs_uni_observer_yield_existing(
    ecs_world_t *world,
    ecs_observer_t *observer)
{
    ecs_iter_action_t callback = observer->callback;

    ecs_defer_begin(world);

    /* If yield existing is enabled, observer for each thing that matches
     * the event, if the event is iterable. */
    int i, count = observer->event_count;
    for (i = 0; i < count; i ++) {
        ecs_entity_t evt = observer->events[i];
        const EcsIterable *iterable = ecs_get(world, evt, EcsIterable);
        if (!iterable) {
            continue;
        }

        ecs_iter_t it;
        iterable->init(world, world, &it, &observer->filter.terms[0]);
        it.system = observer->filter.entity;
        it.ctx = observer->ctx;
        it.binding_ctx = observer->binding_ctx;
        it.event = evt;

        ecs_iter_next_action_t next = it.next;
        ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL);
        while (next(&it)) {
            it.event_id = it.ids[0];
            callback(&it);
        }
    }

    ecs_defer_end(world);
}

static
void flecs_multi_observer_yield_existing(
    ecs_world_t *world,
    ecs_observer_t *observer)
{
    ecs_run_action_t run = observer->run;
    if (!run) {
        run = flecs_default_multi_observer_run_callback;
    }

    ecs_run_aperiodic(world, EcsAperiodicEmptyTables);

    ecs_defer_begin(world);

    int32_t pivot_term = ecs_filter_pivot_term(world, &observer->filter);
    if (pivot_term < 0) {
        return;
    }

    /* If yield existing is enabled, invoke for each thing that matches
     * the event, if the event is iterable. */
    int i, count = observer->event_count;
    for (i = 0; i < count; i ++) {
        ecs_entity_t evt = observer->events[i];
        const EcsIterable *iterable = ecs_get(world, evt, EcsIterable);
        if (!iterable) {
            continue;
        }

        ecs_iter_t it;
        iterable->init(world, world, &it, &observer->filter.terms[pivot_term]);
        it.terms = observer->filter.terms;
        it.field_count = 1;
        it.term_index = pivot_term;
        it.system = observer->filter.entity;
        it.ctx = observer;
        it.binding_ctx = observer->binding_ctx;
        it.event = evt;

        ecs_iter_next_action_t next = it.next;
        ecs_assert(next != NULL, ECS_INTERNAL_ERROR, NULL);
        while (next(&it)) {
            it.event_id = it.ids[0];
            run(&it);
            world->event_id ++;
        }
    }

    ecs_defer_end(world);
}

static
int flecs_uni_observer_init(
    ecs_world_t *world,
    ecs_observer_t *observer,
    const ecs_observer_desc_t *desc)
{
    ecs_term_t *term = &observer->filter.terms[0];
    observer->last_event_id = desc->last_event_id;    
    if (!observer->last_event_id) {
        observer->last_event_id = &observer->last_event_id_storage;
    }
    observer->register_id = flecs_from_public_id(world, term->id);
    term->field_index = desc->term_index;

    if (ecs_id_is_tag(world, term->id)) {
        /* If id is a tag, downgrade OnSet/UnSet to OnAdd/OnRemove. */
        int32_t e, count = observer->event_count;
        for (e = 0; e < count; e ++) {
            if (observer->events[e] == EcsOnSet) {
                observer->events[e] = EcsOnAdd;
            } else
            if (observer->events[e] == EcsUnSet) {
                observer->events[e] = EcsOnRemove;
            }
        }
    }

    flecs_uni_observer_register(world, observer->observable, observer);

    if (desc->yield_existing) {
        flecs_uni_observer_yield_existing(world, observer);
    }

    return 0;
}

static
int flecs_multi_observer_init(
    ecs_world_t *world,
    ecs_observer_t *observer,
    const ecs_observer_desc_t *desc)
{
    /* Create last event id for filtering out the same event that arrives from
     * more than one term */
    observer->last_event_id = ecs_os_calloc_t(int32_t);
    
    /* Mark observer as multi observer */
    observer->is_multi = true;

    /* Create a child observer for each term in the filter */
    ecs_filter_t *filter = &observer->filter;
    ecs_observer_desc_t child_desc = *desc;
    child_desc.last_event_id = observer->last_event_id;
    child_desc.run = NULL;
    child_desc.callback = flecs_multi_observer_builtin_run;
    child_desc.ctx = observer;
    child_desc.ctx_free = NULL;
    child_desc.filter.expr = NULL;
    child_desc.filter.terms_buffer = NULL;
    child_desc.filter.terms_buffer_count = 0;
    child_desc.binding_ctx = NULL;
    child_desc.binding_ctx_free = NULL;
    child_desc.yield_existing = false;
    ecs_os_zeromem(&child_desc.entity);
    ecs_os_zeromem(&child_desc.filter.terms);
    ecs_os_memcpy_n(child_desc.events, observer->events, 
        ecs_entity_t, observer->event_count);

    int i, term_count = filter->term_count;
    bool optional_only = filter->flags & EcsFilterMatchThis;
    for (i = 0; i < term_count; i ++) {
        if (filter->terms[i].oper != EcsOptional) {
            if (ecs_term_match_this(&filter->terms[i])) {
                optional_only = false;
            }
        }
    }

    if (filter->flags & EcsFilterMatchPrefab) {
        child_desc.filter.flags |= EcsFilterMatchPrefab;
    }
    if (filter->flags & EcsFilterMatchDisabled) {
        child_desc.filter.flags |= EcsFilterMatchDisabled;
    }

    /* Create observers as children of observer */
    ecs_entity_t old_scope = ecs_set_scope(world, observer->filter.entity);

    for (i = 0; i < term_count; i ++) {
        if (filter->terms[i].src.flags & EcsFilter) {
            continue;
        }

        ecs_term_t *term = &child_desc.filter.terms[0];
        child_desc.term_index = filter->terms[i].field_index;
        *term = filter->terms[i];

        ecs_oper_kind_t oper = term->oper;
        ecs_id_t id = term->id;

        /* AndFrom & OrFrom terms insert multiple observers */
        if (oper == EcsAndFrom || oper == EcsOrFrom) {
            const ecs_type_t *type = ecs_get_type(world, id);
            int32_t ti, ti_count = type->count;
            ecs_id_t *ti_ids = type->array;

            /* Correct operator will be applied when an event occurs, and
             * the observer is evaluated on the observer source */
            term->oper = EcsAnd;
            for (ti = 0; ti < ti_count; ti ++) {
                ecs_id_t ti_id = ti_ids[ti];
                ecs_id_record_t *idr = flecs_id_record_get(world, ti_id);
                if (idr->flags & EcsIdDontInherit) {
                    continue;
                }

                term->first.name = NULL;
                term->first.id = ti_ids[ti];
                term->id = ti_ids[ti];

                if (ecs_observer_init(world, &child_desc) == 0) {
                    goto error;
                }
            }
            continue;
        }

        /* Single component observers never use OR */
        if (oper == EcsOr) {
            term->oper = EcsAnd;
        }

        /* If observer only contains optional terms, match everything */
        if (optional_only) {
            term->id = EcsAny;
            term->first.id = EcsAny;
            term->src.id = EcsThis;
            term->src.flags = EcsIsVariable;
            term->second.id = 0;
        } else if (term->oper == EcsOptional) {
            continue;
        }
        if (ecs_observer_init(world, &child_desc) == 0) {
            goto error;
        }

        if (optional_only) {
            break;
        }
    }

    ecs_set_scope(world, old_scope);

    if (desc->yield_existing) {
        flecs_multi_observer_yield_existing(world, observer);
    }

    return 0; 
error:
    return -1;
}

ecs_entity_t ecs_observer_init(
    ecs_world_t *world,
    const ecs_observer_desc_t *desc)
{
    ecs_check(world != NULL, ECS_INVALID_PARAMETER, NULL);
    ecs_check(desc != NULL, ECS_INVALID_PARAMETER, NULL);
    ecs_check(desc->_canary == 0, ECS_INVALID_PARAMETER, NULL);
    ecs_check(!(world->flags & EcsWorldFini), ECS_INVALID_OPERATION, NULL);

    ecs_entity_t entity = desc->entity;
    if (!entity) {
        entity = ecs_new(world, 0);
    }

    EcsPoly *poly = ecs_poly_bind(world, entity, ecs_observer_t);
    if (!poly->poly) {
        ecs_check(desc->callback != NULL || desc->run != NULL, 
            ECS_INVALID_OPERATION, NULL);

        ecs_observer_t *observer = ecs_poly_new(ecs_observer_t);
        ecs_assert(observer != NULL, ECS_INTERNAL_ERROR, NULL);
        observer->dtor = (ecs_poly_dtor_t)flecs_observer_fini;

        /* Make writeable copy of filter desc so that we can set name. This will
         * make debugging easier, as any error messages related to creating the
         * filter will have the name of the observer. */
        ecs_filter_desc_t filter_desc = desc->filter;
        filter_desc.entity = entity;
        ecs_filter_t *filter = filter_desc.storage = &observer->filter;
        *filter = ECS_FILTER_INIT;

        /* Parse filter */
        if (ecs_filter_init(world, &filter_desc) == NULL) {
            flecs_observer_fini(observer);
            return 0;
        }

        /* Observer must have at least one term */
        ecs_check(observer->filter.term_count > 0, ECS_INVALID_PARAMETER, NULL);

        poly->poly = observer;

        ecs_observable_t *observable = desc->observable;
        if (!observable) {
            observable = ecs_get_observable(world);
        }

        observer->run = desc->run;
        observer->callback = desc->callback;
        observer->ctx = desc->ctx;
        observer->binding_ctx = desc->binding_ctx;
        observer->ctx_free = desc->ctx_free;
        observer->binding_ctx_free = desc->binding_ctx_free;
        observer->term_index = desc->term_index;
        observer->observable = observable;

        /* Check if observer is monitor. Monitors are created as multi observers
         * since they require pre/post checking of the filter to test if the
         * entity is entering/leaving the monitor. */
        int i;
        for (i = 0; i < FLECS_EVENT_DESC_MAX; i ++) {
            ecs_entity_t event = desc->events[i];
            if (!event) {
                break;
            }

            if (event == EcsMonitor) {
                /* Monitor event must be first and last event */
                ecs_check(i == 0, ECS_INVALID_PARAMETER, NULL);

                observer->events[0] = EcsOnAdd;
                observer->events[1] = EcsOnRemove;
                observer->event_count ++;
                observer->is_monitor = true;
            } else {
                observer->events[i] = event;
            }

            observer->event_count ++;
        }

        /* Observer must have at least one event */
        ecs_check(observer->event_count != 0, ECS_INVALID_PARAMETER, NULL);

        bool multi = false;

        if (filter->term_count == 1 && !desc->last_event_id) {
            ecs_term_t *term = &filter->terms[0];
            /* If the filter has a single term but it is a *From operator, we
             * need to create a multi observer */
            multi |= (term->oper == EcsAndFrom) || (term->oper == EcsOrFrom);
            
            /* An observer with only optional terms is a special case that is
             * only handled by multi observers */
            multi |= term->oper == EcsOptional;
        }

        if (filter->term_count == 1 && !observer->is_monitor && !multi) {
            if (flecs_uni_observer_init(world, observer, desc)) {
                goto error;
            }
        } else {
            if (flecs_multi_observer_init(world, observer, desc)) {
                goto error;
            }
        }

        if (ecs_get_name(world, entity)) {
            ecs_trace("#[green]observer#[reset] %s created", 
                ecs_get_name(world, entity));
        }
    } else {
        ecs_poly_assert(poly->poly, ecs_observer_t);
        ecs_observer_t *observer = (ecs_observer_t*)poly->poly;

        if (desc->run) {
            observer->run = desc->run;
        }
        if (desc->callback) {
            observer->callback = desc->callback;
        }

        if (observer->ctx_free) {
            if (observer->ctx && observer->ctx != desc->ctx) {
                observer->ctx_free(observer->ctx);
            }
        }
        if (observer->binding_ctx_free) {
            if (observer->binding_ctx && observer->binding_ctx != desc->binding_ctx) {
                observer->binding_ctx_free(observer->binding_ctx);
            }
        }

        if (desc->ctx) {
            observer->ctx = desc->ctx;
        }
        if (desc->binding_ctx) {
            observer->binding_ctx = desc->binding_ctx;
        }
        if (desc->ctx_free) {
            observer->ctx_free = desc->ctx_free;
        }
        if (desc->binding_ctx_free) {
            observer->binding_ctx_free = desc->binding_ctx_free;
        }
    }

    ecs_poly_modified(world, entity, ecs_observer_t);

    return entity;
error:
    ecs_delete(world, entity);
    return 0;
}

void* ecs_observer_get_ctx(
    const ecs_world_t *world,
    ecs_entity_t observer)
{
    const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t);
    if (o) {
        ecs_poly_assert(o->poly, ecs_observer_t);
        return ((ecs_observer_t*)o->poly)->ctx;
    } else {
        return NULL;
    }     
}

void* ecs_observer_get_binding_ctx(
    const ecs_world_t *world,
    ecs_entity_t observer)
{
    const EcsPoly *o = ecs_poly_bind_get(world, observer, ecs_observer_t);
    if (o) {
        ecs_poly_assert(o->poly, ecs_observer_t);
        return ((ecs_observer_t*)o->poly)->binding_ctx;
    } else {
        return NULL;
    }      
}

void flecs_observer_fini(
    ecs_observer_t *observer)
{
    if (observer->is_multi) {
        ecs_os_free(observer->last_event_id);
    } else {
        if (observer->filter.term_count) {
            flecs_unregister_observer(
                observer->filter.world, observer->observable, observer);
        } else {
            /* Observer creation failed while creating filter */
        }
    }

    /* Cleanup filters */
    ecs_filter_fini(&observer->filter);

    /* Cleanup context */
    if (observer->ctx_free) {
        observer->ctx_free(observer->ctx);
    }

    if (observer->binding_ctx_free) {
        observer->binding_ctx_free(observer->binding_ctx);
    }

    ecs_poly_free(observer, ecs_observer_t);
}
