/**
 * @file meta/serialized.c
 * @brief Serialize type into flat operations array to speed up deserialization.
 */

#include "meta.h"

#ifdef FLECS_META

static
int flecs_meta_serialize_type(
    ecs_world_t *world,
    ecs_entity_t type,
    ecs_size_t offset,
    ecs_vec_t *ops);

ecs_meta_type_op_kind_t flecs_meta_primitive_to_op_kind(ecs_primitive_kind_t kind) {
    return EcsOpPrimitive + kind;
}

static
ecs_size_t flecs_meta_type_size(ecs_world_t *world, ecs_entity_t type) {
    const EcsComponent *comp = ecs_get(world, type, EcsComponent);
    ecs_assert(comp != NULL, ECS_INTERNAL_ERROR, NULL);
    return comp->size;
}

static
ecs_meta_type_op_t* flecs_meta_ops_add(ecs_vec_t *ops, ecs_meta_type_op_kind_t kind) {
    ecs_meta_type_op_t *op = ecs_vec_append_t(NULL, ops, ecs_meta_type_op_t);
    op->kind = kind;
    op->offset = 0;
    op->count = 1;
    op->op_count = 1;
    op->size = 0;
    op->name = NULL;
    op->members = NULL;
    op->type = 0;
    op->member_index = 0;
    return op;
}

static
ecs_meta_type_op_t* flecs_meta_ops_get(ecs_vec_t *ops, int32_t index) {
    ecs_meta_type_op_t* op = ecs_vec_get_t(ops, ecs_meta_type_op_t, index);
    ecs_assert(op != NULL, ECS_INTERNAL_ERROR, NULL);
    return op;
}

static
int flecs_meta_serialize_primitive(
    ecs_world_t *world,
    ecs_entity_t type,
    ecs_size_t offset,
    ecs_vec_t *ops)
{
    const EcsPrimitive *ptr = ecs_get(world, type, EcsPrimitive);
    if (!ptr) {
        char *name = ecs_get_fullpath(world, type);
        ecs_err("entity '%s' is not a primitive type", name);
        ecs_os_free(name);
        return -1;
    }

    ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, flecs_meta_primitive_to_op_kind(ptr->kind));
    op->offset = offset;
    op->type = type;
    op->size = flecs_meta_type_size(world, type);
    return 0;
}

static
int flecs_meta_serialize_enum(
    ecs_world_t *world,
    ecs_entity_t type,
    ecs_size_t offset,
    ecs_vec_t *ops)
{
    (void)world;
    
    ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpEnum);
    op->offset = offset;
    op->type = type;
    op->size = ECS_SIZEOF(ecs_i32_t);
    return 0;
}

static
int flecs_meta_serialize_bitmask(
    ecs_world_t *world,
    ecs_entity_t type,
    ecs_size_t offset,
    ecs_vec_t *ops)
{
    (void)world;
    
    ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpBitmask);
    op->offset = offset;
    op->type = type;
    op->size = ECS_SIZEOF(ecs_u32_t);
    return 0;
}

static
int flecs_meta_serialize_array(
    ecs_world_t *world,
    ecs_entity_t type,
    ecs_size_t offset,
    ecs_vec_t *ops)
{
    (void)world;

    ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpArray);
    op->offset = offset;
    op->type = type;
    op->size = flecs_meta_type_size(world, type);
    return 0;
}

static
int flecs_meta_serialize_array_component(
    ecs_world_t *world,
    ecs_entity_t type,
    ecs_vec_t *ops)
{
    const EcsArray *ptr = ecs_get(world, type, EcsArray);
    if (!ptr) {
        return -1; /* Should never happen, will trigger internal error */
    }

    flecs_meta_serialize_type(world, ptr->type, 0, ops);

    ecs_meta_type_op_t *first = ecs_vec_first(ops);
    first->count = ptr->count;
    return 0;
}

static
int flecs_meta_serialize_vector(
    ecs_world_t *world,
    ecs_entity_t type,
    ecs_size_t offset,
    ecs_vec_t *ops)
{
    (void)world;
    ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpVector);
    op->offset = offset;
    op->type = type;
    op->size = flecs_meta_type_size(world, type);
    return 0;
}

static
int flecs_meta_serialize_custom_type(
    ecs_world_t *world,
    ecs_entity_t type,
    ecs_size_t offset,
    ecs_vec_t *ops)
{
    (void)world;
    ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpOpaque);
    op->offset = offset;
    op->type = type;
    op->size = flecs_meta_type_size(world, type);
    return 0;
}

static
int flecs_meta_serialize_struct(
    ecs_world_t *world,
    ecs_entity_t type,
    ecs_size_t offset,
    ecs_vec_t *ops)
{
    const EcsStruct *ptr = ecs_get(world, type, EcsStruct);
    ecs_assert(ptr != NULL, ECS_INTERNAL_ERROR, NULL);

    int32_t cur, first = ecs_vec_count(ops);
    ecs_meta_type_op_t *op = flecs_meta_ops_add(ops, EcsOpPush);
    op->offset = offset;
    op->type = type;
    op->size = flecs_meta_type_size(world, type);

    ecs_member_t *members = ecs_vec_first(&ptr->members);
    int32_t i, count = ecs_vec_count(&ptr->members);

    ecs_hashmap_t *member_index = NULL;
    if (count) {        
        op->members = member_index = flecs_name_index_new(
            world, &world->allocator);
    }

    for (i = 0; i < count; i ++) {
        ecs_member_t *member = &members[i];

        cur = ecs_vec_count(ops);
        flecs_meta_serialize_type(world, 
            member->type, offset + member->offset, ops);

        op = flecs_meta_ops_get(ops, cur);
        if (!op->type) {
            op->type = member->type;
        }

        if (op->count <= 1) {
            op->count = member->count;
        }

        const char *member_name = member->name;
        op->name = member_name;
        op->op_count = ecs_vec_count(ops) - cur;
        op->member_index = i;

        flecs_name_index_ensure(
            member_index, flecs_ito(uint64_t, cur - first - 1), 
                member_name, 0, 0);
    }

    flecs_meta_ops_add(ops, EcsOpPop);
    flecs_meta_ops_get(ops, first)->op_count = ecs_vec_count(ops) - first;
    return 0;
}

static
int flecs_meta_serialize_type(
    ecs_world_t *world,
    ecs_entity_t type,
    ecs_size_t offset,
    ecs_vec_t *ops)
{
    const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType);
    if (!ptr) {
        char *path = ecs_get_fullpath(world, type);
        ecs_err("missing EcsMetaType for type %s'", path);
        ecs_os_free(path);
        return -1;
    }

    switch(ptr->kind) {
    case EcsPrimitiveType: return flecs_meta_serialize_primitive(world, type, offset, ops);
    case EcsEnumType: return flecs_meta_serialize_enum(world, type, offset, ops);
    case EcsBitmaskType: return flecs_meta_serialize_bitmask(world, type, offset, ops);
    case EcsStructType: return flecs_meta_serialize_struct(world, type, offset, ops);
    case EcsArrayType: return flecs_meta_serialize_array(world, type, offset, ops);
    case EcsVectorType: return flecs_meta_serialize_vector(world, type, offset, ops);
    case EcsOpaqueType: return flecs_meta_serialize_custom_type(world, type, offset, ops);
    }

    return 0;
}

static
int flecs_meta_serialize_component(
    ecs_world_t *world,
    ecs_entity_t type,
    ecs_vec_t *ops)
{
    const EcsMetaType *ptr = ecs_get(world, type, EcsMetaType);
    if (!ptr) {
        char *path = ecs_get_fullpath(world, type);
        ecs_err("missing EcsMetaType for type %s'", path);
        ecs_os_free(path);
        return -1;
    }

    if (ptr->kind == EcsArrayType) {
        return flecs_meta_serialize_array_component(world, type, ops);
    } else {
        return flecs_meta_serialize_type(world, type, 0, ops);
    }
}

void ecs_meta_type_serialized_init(
    ecs_iter_t *it)
{
    ecs_world_t *world = it->world;

    int i, count = it->count;
    for (i = 0; i < count; i ++) {
        ecs_entity_t e = it->entities[i];
        ecs_vec_t ops;
        ecs_vec_init_t(NULL, &ops, ecs_meta_type_op_t, 0);
        flecs_meta_serialize_component(world, e, &ops);

        EcsMetaTypeSerialized *ptr = ecs_get_mut(
            world, e, EcsMetaTypeSerialized);
        if (ptr->ops.array) {
            ecs_meta_dtor_serialized(ptr);
        }

        ptr->ops = ops;
    }
}

#endif
