/**
 * @file addons/cpp/mixins/query/impl.hpp
 * @brief Query implementation.
 */

#pragma once

#include "builder.hpp"

namespace flecs 
{

/** Base class for queries.
 *
 * @ingroup cpp_core_queries
 */
struct query_base {
    /** Default constructor. */
    query_base() { }

    /** Construct from a mutable query pointer. */
    query_base(query_t *q)
        : query_(q) {
            flecs_poly_claim(q);
        }

    /** Construct from a const query pointer. */
    query_base(const query_t *q)
        : query_(ECS_CONST_CAST(query_t*, q)) {
            flecs_poly_claim(q);
        }

    /** Construct from a world and a query descriptor. */
    query_base(world_t *world, ecs_query_desc_t *desc) {
        if (desc->entity && desc->terms[0].id == 0) {
            const flecs::Poly *query_poly = ecs_get_pair(
                world, desc->entity, EcsPoly, EcsQuery);
            if (query_poly) {
                query_ = static_cast<flecs::query_t*>(query_poly->poly);
                flecs_poly_claim(query_);
                return;
            }
        }

        query_ = ecs_query_init(world, desc);
    }

    /** Copy constructor. */
    query_base(const query_base& obj) {
        this->query_ = obj.query_;
        if (this->query_)
        {
            flecs_poly_claim(this->query_);
        }
    }

    /** Copy assignment operator. */
    query_base& operator=(const query_base& obj) {
        this->~query_base();
        this->query_ = obj.query_;
        if (this->query_)
        {
            flecs_poly_claim(this->query_);
        }
        return *this; 
    }

    /** Move constructor. */
    query_base(query_base&& obj) noexcept {
        this->query_ = obj.query_;
        obj.query_ = nullptr;
    }

    /** Move assignment operator. */
    query_base& operator=(query_base&& obj) noexcept {
        this->query_ = obj.query_;
        obj.query_ = nullptr;
        return *this; 
    }

    /** Get the entity associated with the query. */
    flecs::entity entity() const {
        return flecs::entity(query_->world, query_->entity);
    }

    /** Get a pointer to the underlying C query. */
    const flecs::query_t* c_ptr() const {
        return query_;
    }

    /** Convert to a const query pointer. */
    operator const flecs::query_t*() const {
        return query_;
    }

    /** Check if the query is valid. */
    operator bool() const {
        return query_ != nullptr;
    }

    /** Free a persistent query.
     * A persistent query is a query that is associated with an entity, such as
     * system queries and named queries. Persistent queries must be deleted with
     * destruct(), or will be deleted automatically at world cleanup.
     */
    void destruct() {
        ecs_assert(query_->entity != 0, ECS_INVALID_OPERATION, "destruct() "
            "should only be called on queries associated with entities");
        ecs_query_fini(query_);
        query_ = nullptr;
    }

    /** Destructor. Only frees the query if it is not associated with an entity. */
    ~query_base() {
        if (query_ && !query_->entity) {
            if (!flecs_poly_release(query_)) {
                ecs_query_fini(query_);
                query_ = nullptr;
            }
        }
    }

    /** Return whether the query data changed since the last iteration.
     * This operation must be invoked before obtaining the iterator, as this will
     * reset the changed state. The operation will return true after:
     * - new entities have been matched with
     * - matched entities were deleted
     * - matched components were changed
     * 
     * @return true if entities changed, otherwise false.
     */
    bool changed() const {
        return ecs_query_changed(query_);
    }

    /** Get info for a group.
     *
     * @param group_id The group ID for which to retrieve the info.
     * @return The group info.
     */
    const flecs::query_group_info_t* group_info(uint64_t group_id) const {
        return ecs_query_get_group_info(query_, group_id);
    }

    /** Get context for a group.
     *
     * @param group_id The group ID for which to retrieve the context.
     * @return The group context.
     */
    void* group_ctx(uint64_t group_id) const {
        const flecs::query_group_info_t *gi = group_info(group_id);
        if (gi) {
            return gi->ctx;
        } else {
            return NULL;
        }
    }

    /** Iterate each term in the query, invoking a callback for each. */
    template <typename Func>
    void each_term(const Func& func) {
        for (int i = 0; i < query_->term_count; i ++) {
            flecs::term t(query_->world, query_->terms[i]);
            func(t);
            t.reset(); // prevent freeing resources
        }
    }

    /** Get term at the specified index. */
    flecs::term term(int32_t index) const {
        return flecs::term(query_->world, query_->terms[index]);
    }

    /** Get the number of terms in the query. */
    int32_t term_count() const {
        return query_->term_count;
    }

    /** Get the number of fields in the query. */
    int32_t field_count() const {
        return query_->field_count;
    }

    /** Find a variable by name. */
    int32_t find_var(const char *name) const {
        return ecs_query_find_var(query_, name);
    }

    /** Convert the query to a string expression. */
    flecs::string str() const {
        char *result = ecs_query_str(query_);
        return flecs::string(result);
    }

    /** Return a string representing the query plan.
     * This can be used to analyze the behavior and performance of the query.
     * @see ecs_query_plan()
     */
    flecs::string plan() const {
        char *result = ecs_query_plan(query_);
        return flecs::string(result);
    }

    /** Convert to a typed query. */
    operator query<>() const;

#   ifdef FLECS_JSON
#   include "../json/query.inl"
#   endif

protected:
    query_t *query_ = nullptr;
};

/** Typed query.
 *
 * @ingroup cpp_core_queries
 */
template<typename ... Components>
struct query : query_base, iterable<Components...> {
private:
    using Fields = typename _::field_ptrs<Components...>::array;

public:
    using query_base::query_base;

    /** Default constructor. */
    query() : query_base() { } // necessary not to confuse MSVC

    /** Copy constructor. */
    query(const query& obj) : query_base(obj) { }

    /** Copy assignment operator. */
    query& operator=(const query& obj) {
        query_base::operator=(obj);
        return *this;
    }

    /** Move constructor. */
    query(query&& obj) noexcept : query_base(FLECS_MOV(obj)) { }

    /** Move assignment operator. */
    query& operator=(query&& obj) noexcept {
        query_base::operator=(FLECS_FWD(obj));
        return *this;
    }

    /** Get the cache query, if any. */
    flecs::query<> cache_query() const {
        const flecs::query_t *q = ecs_query_get_cache_query(query_);
        return flecs::query<>(q);
    }

private:
    ecs_iter_t get_iter(flecs::world_t *world) const override {
        ecs_assert(query_ != nullptr, ECS_INVALID_PARAMETER, 
            "cannot iterate invalid query");
        if (!world) {
            world = query_->world;
        }
        return ecs_query_iter(world, query_);
    }

    ecs_iter_next_action_t next_action() const override {
        return ecs_query_next;
    }
};

// World mixin implementation
template <typename... Comps, typename... Args>
inline flecs::query<Comps...> world::query(Args &&... args) const {
    return flecs::query_builder<Comps...>(world_, FLECS_FWD(args)...)
        .build();
}

inline flecs::query<> world::query(flecs::entity query_entity) const {
    ecs_query_desc_t desc = {};
    desc.entity = query_entity;
    return flecs::query<>(world_, &desc);
}

template <typename... Comps, typename... Args>
inline flecs::query_builder<Comps...> world::query_builder(Args &&... args) const {
    return flecs::query_builder<Comps...>(world_, FLECS_FWD(args)...);
}

// world::each
namespace _ {

// Each with entity parameter
template<typename Func, typename ... Args>
struct query_delegate_w_ent;

template<typename Func, typename E>
struct query_delegate_w_ent<Func, arg_list<E> >
{
    query_delegate_w_ent(const flecs::world& world, Func&& func) {
        ecs_entities_t entities = ecs_get_entities(ecs_get_world(world));

        for (int32_t i = 0; i < entities.alive_count; i ++) {
            func(flecs::entity(world, entities.ids[i]));
        }
    }
};

template<typename Func, typename E, typename ... Args>
struct query_delegate_w_ent<Func, arg_list<E, Args ...> >
{
    query_delegate_w_ent(const flecs::world& world, Func&& func) {
        auto f = world.query<Args ...>();
        f.each(FLECS_MOV(func));
    }
};

// Each without entity parameter
template<typename Func, typename ... Args>
struct query_delegate_no_ent;

template<typename Func, typename ... Args>
struct query_delegate_no_ent<Func, arg_list<Args ...> >
{
    query_delegate_no_ent(const flecs::world& world, Func&& func) {
        auto f = world.query<Args ...>();
        f.each(FLECS_MOV(func));
    }
};

// Switch between function with and without entity parameter
template<typename Func, typename T = int>
struct query_delegate;

template <typename Func>
struct query_delegate<Func, if_t<is_same<first_arg_t<Func>, flecs::entity>::value> > {
    query_delegate(const flecs::world& world, Func&& func) {
        query_delegate_w_ent<Func, arg_list_t<Func>>(world, FLECS_MOV(func));
    }
};

template <typename Func>
struct query_delegate<Func, if_not_t<is_same<first_arg_t<Func>, flecs::entity>::value> > {
    query_delegate(const flecs::world& world, Func&& func) {
        query_delegate_no_ent<Func, arg_list_t<Func>>(world, FLECS_MOV(func));
    }
};

}

template <typename Func>
inline void world::each(Func&& func) const {
    _::query_delegate<Func> f_delegate(*this, FLECS_MOV(func));
}

template <typename T, typename Func>
inline void world::each(Func&& func) const {
    ecs_iter_t it = ecs_each_id(world_, _::type<T>::id(world_));

    while (ecs_each_next(&it)) {
        _::each_delegate<Func, T>(func).invoke(&it);
    }
}

template <typename Func>
inline void world::each(flecs::id_t each_id, Func&& func) const {
    ecs_iter_t it = ecs_each_id(world_, each_id);

    while (ecs_each_next(&it)) {
        _::each_delegate<Func>(func).invoke(&it);
    }
}

// query_base implementation
inline query_base::operator flecs::query<> () const {
    return flecs::query<>(query_);
}

}
