
/*
 * Copyright (C) Igor Sysoev
 * Copyright (C) NGINX, Inc.
 */


#include <njs_main.h>


static njs_int_t njs_external_prop_handler(njs_vm_t *vm,
    njs_object_prop_t *self, uint32_t atom_id, njs_value_t *value,
    njs_value_t *setval, njs_value_t *retval);


static njs_int_t
njs_external_add(njs_vm_t *vm, njs_arr_t *protos,
    const njs_external_t *external, njs_uint_t n)
{
    size_t                size;
    njs_int_t             ret;
    njs_value_t           prop_name;
    const u_char          *start;
    njs_flathsh_t         *hash;
    njs_function_t        *function;
    njs_object_prop_t     *prop;
    njs_exotic_slots_t    *slot, *next;
    njs_flathsh_query_t   fhq;
    const njs_external_t  *end;

    slot = njs_arr_add(protos);
    njs_memzero(slot, sizeof(njs_exotic_slots_t));

    hash = &slot->external_shared_hash;
    njs_flathsh_init(hash);

    if (n == 0) {
        return NJS_OK;
    }

    fhq.replace = 0;
    fhq.proto = &njs_object_hash_proto;
    fhq.pool = vm->mem_pool;

    end = external + n;

    while (external < end) {

        if ((external->flags & NJS_EXTERN_TYPE_MASK) == NJS_EXTERN_SELF) {
            slot->writable = external->u.object.writable;
            slot->configurable = external->u.object.configurable;
            slot->enumerable = external->u.object.enumerable;
            slot->prop_handler = external->u.object.prop_handler;
            slot->magic32 = external->u.object.magic32;
            slot->keys = external->u.object.keys;

            external++;
            continue;
        }

        if (external->flags & NJS_EXTERN_SYMBOL) {
            fhq.key_hash = external->name.symbol;

        } else {
            ret = njs_atom_string_create(vm, &prop_name,
                                         external->name.string.start,
                                         external->name.string.length);
            if (njs_slow_path(ret != NJS_OK)) {
                return NJS_ERROR;
            }

            fhq.key_hash = prop_name.atom_id;
        }

        ret = njs_flathsh_unique_insert(hash, &fhq);
        if (njs_slow_path(ret != NJS_OK)) {
            njs_internal_error(vm, "flathsh insert failed");
            return NJS_ERROR;
        }

        prop = fhq.value;

        prop->type = NJS_PROPERTY;
        prop->enumerable = external->enumerable;
        prop->configurable = external->configurable;
        prop->writable = external->writable;
        prop->u.value = njs_value_invalid;


        switch (external->flags & NJS_EXTERN_TYPE_MASK) {
        case NJS_EXTERN_METHOD:
            function = njs_mp_zalloc(vm->mem_pool, sizeof(njs_function_t));
            if (njs_slow_path(function == NULL)) {
                goto memory_error;
            }

            function->object.shared_hash = vm->shared->arrow_instance_hash;
            function->object.type = NJS_FUNCTION;
            function->object.shared = 1;
            function->object.extensible = 1;
            function->native = 1;
            function->u.native = external->u.method.native;
            function->magic8 = external->u.method.magic8;
            function->ctor = external->u.method.ctor;

            njs_set_function(njs_prop_value(prop), function);

            break;

        case NJS_EXTERN_PROPERTY:
            if (external->u.property.handler != NULL) {
                prop->type = NJS_PROPERTY_HANDLER;
                prop->u.value.type = NJS_INVALID;
                prop->u.value.data.truth = 1;
                njs_prop_magic16(prop) = external->u.property.magic16;
                njs_prop_magic32(prop) = external->u.property.magic32;
                njs_prop_handler(prop) = external->u.property.handler;

            } else {
                start = (u_char *) external->u.property.value;
                size = njs_strlen(start);

                ret = njs_string_create(vm, &prop->u.value, start, size);
                if (njs_slow_path(ret != NJS_OK)) {
                    return NJS_ERROR;
                }
            }

            break;

        case NJS_EXTERN_OBJECT:
        default:
            next = njs_arr_item(protos, protos->items);

            ret = njs_external_add(vm, protos, external->u.object.properties,
                                   external->u.object.nproperties);
            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

            prop->type = NJS_PROPERTY_HANDLER;
            prop->u.value.type = NJS_INVALID;
            prop->u.value.data.truth = 1;
            njs_prop_magic16(prop) = next - slot;
            njs_prop_magic32(prop) = fhq.key_hash;
            njs_prop_handler(prop) = njs_external_prop_handler;

            if (external->u.object.prop_handler) {
                if (next->prop_handler) {
                    njs_internal_error(vm, "overwritten self prop_handler");
                    return NJS_ERROR;
                }

                next->writable = external->u.object.writable;
                next->configurable = external->u.object.configurable;
                next->enumerable = external->u.object.enumerable;

                next->prop_handler = external->u.object.prop_handler;
                next->magic32 = external->u.object.magic32;
            }

            if (external->u.object.keys) {
                if (next->keys) {
                    njs_internal_error(vm, "overwritten self keys");
                    return NJS_ERROR;
                }

                next->keys = external->u.object.keys;
            }

            break;
        }

        external++;
    }

    return NJS_OK;

memory_error:

    njs_memory_error(vm);

    return NJS_ERROR;
}


static njs_int_t
njs_external_prop_handler(njs_vm_t *vm, njs_object_prop_t *self,
    uint32_t atom_id, njs_value_t *value, njs_value_t *setval,
    njs_value_t *retval)
{
    njs_int_t            ret;
    njs_object_prop_t    *prop;
    njs_external_ptr_t   external;
    njs_object_value_t   *ov;
    njs_exotic_slots_t   *slots;
    njs_flathsh_query_t  fhq;

    if (njs_slow_path(retval == NULL)) {
        return NJS_DECLINED;
    }

    slots = NULL;

    if (njs_slow_path(setval != NULL)) {
        *retval = *setval;

    } else {
        ov = njs_object_value_alloc(vm, NJS_OBJ_TYPE_OBJECT, 0, NULL);
        if (njs_slow_path(ov == NULL)) {
            return NJS_ERROR;
        }

        slots = njs_object(value)->slots + njs_prop_magic16(self);

        ov->object.shared_hash = slots->external_shared_hash;
        ov->object.slots = slots;

        external = njs_vm_external(vm, NJS_PROTO_ID_ANY, value);

        njs_set_data(&ov->value, external, njs_value_external_tag(value));
        njs_set_object_value(retval, ov);
    }

    fhq.key_hash = atom_id;
    fhq.replace = 1;
    fhq.pool = vm->mem_pool;
    fhq.proto = &njs_object_hash_proto;

    ret = njs_flathsh_unique_insert(njs_object_hash(value), &fhq);
    if (njs_slow_path(ret != NJS_OK)) {
        njs_internal_error(vm, "flathsh insert/replace failed");
        return NJS_ERROR;
    }

    prop = fhq.value;

    prop->type = NJS_PROPERTY;
    prop->enumerable = self->enumerable;
    prop->configurable = self->configurable;
    prop->writable = self->writable;

    prop->u.value = *retval;

    return NJS_OK;
}


static njs_uint_t
njs_external_protos(const njs_external_t *external, njs_uint_t size)
{
    njs_uint_t  n;

    n = 1;

    while (size != 0) {
        if ((external->flags & 3) == NJS_EXTERN_OBJECT) {
            n += njs_external_protos(external->u.object.properties,
                                     external->u.object.nproperties);
        }

        size--;
        external++;
    }

    return n;
}


njs_int_t
njs_vm_external_prototype(njs_vm_t *vm, const njs_external_t *definition,
    njs_uint_t n)
{
    njs_arr_t   *protos, **pr;
    njs_int_t   ret;
    njs_uint_t  size;

    size = njs_external_protos(definition, n) + 1;

    protos = njs_arr_create(vm->mem_pool, size, sizeof(njs_exotic_slots_t));
    if (njs_slow_path(protos == NULL)) {
        njs_memory_error(vm);
        return -1;
    }

    ret = njs_external_add(vm, protos, definition, n);
    if (njs_slow_path(ret != NJS_OK)) {
        njs_internal_error(vm, "njs_vm_external_add() failed");
        return -1;
    }

    if (vm->protos == NULL) {
        vm->protos = njs_arr_create(vm->mem_pool, 4, sizeof(njs_arr_t *));
        if (njs_slow_path(vm->protos == NULL)) {
            return -1;
        }
    }

    pr = njs_arr_add(vm->protos);
    if (njs_slow_path(pr == NULL)) {
        return -1;
    }

    *pr = protos;

    return vm->protos->items - 1;
}


static njs_int_t
njs_vm_external_constructor_handler(njs_vm_t *vm, njs_object_prop_t *prop,
    uint32_t atom_id, njs_value_t *value, njs_value_t *setval,
    njs_value_t *retval)
{
    njs_set_function(retval, &njs_vm_ctor(vm, njs_prop_magic32(prop)));

    return NJS_OK;
}


njs_int_t
njs_vm_external_constructor(njs_vm_t *vm, const njs_str_t *name,
    const njs_function_native_t native, const njs_external_t *ctor_props,
    njs_uint_t ctor_nprops, const njs_external_t *proto_props,
    njs_uint_t proto_nprops)
{
    njs_int_t               ret, index, proto_id;
    njs_arr_t               **pprotos;
    njs_function_t          *constructor;
    njs_exotic_slots_t      *slots;
    njs_object_prototype_t  *prototype;

    index = njs_vm_ctor_push(vm);
    if (njs_slow_path(index < 0)) {
        njs_internal_error(vm, "njs_vm_ctor_push() failed");
        return -1;
    }

    proto_id = njs_vm_external_prototype(vm, proto_props, proto_nprops);
    if (njs_slow_path(proto_id < 0)) {
        njs_internal_error(vm, "njs_vm_external_prototype(proto_props) failed");
        return -1;
    }

    prototype = njs_shared_prototype(vm->shared, index);
    njs_memzero(prototype, sizeof(njs_object_prototype_t));
    prototype->object.type = NJS_OBJECT;
    prototype->object.extensible = 1;

    pprotos = njs_arr_item(vm->protos, proto_id);
    slots = (*pprotos)->start;
    prototype->object.shared_hash = slots->external_shared_hash;

    proto_id = njs_vm_external_prototype(vm, ctor_props, ctor_nprops);
    if (njs_slow_path(proto_id < 0)) {
        njs_internal_error(vm, "njs_vm_external_prototype(ctor_props) failed");
        return -1;
    }

    constructor = njs_shared_ctor(vm->shared, index);
    njs_memzero(constructor, sizeof(njs_function_t));
    constructor->object.type = NJS_FUNCTION;
    constructor->u.native = native;
    constructor->magic8 = index;
    constructor->native = 1;
    constructor->ctor = 1;

    pprotos = njs_arr_item(vm->protos, proto_id);
    slots = (*pprotos)->start;
    constructor->object.shared_hash = slots->external_shared_hash;

    ret = njs_vm_bind_handler(vm, name, njs_vm_external_constructor_handler, 0,
                              index, 1);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    return index;
}


njs_int_t
njs_vm_external_create(njs_vm_t *vm, njs_value_t *value, njs_int_t proto_id,
    njs_external_ptr_t external, njs_bool_t shared)
{
    njs_arr_t           **pprotos;
    njs_object_value_t  *ov;
    njs_exotic_slots_t  *slots;

    if (vm->protos == NULL || (njs_int_t) vm->protos->items <= proto_id) {
        return NJS_ERROR;
    }

    ov = njs_object_value_alloc(vm, NJS_OBJ_TYPE_OBJECT, 0, NULL);
    if (njs_slow_path(ov == NULL)) {
        return NJS_ERROR;
    }

    pprotos = njs_arr_item(vm->protos, proto_id);
    slots = (*pprotos)->start;

    ov->object.shared_hash = slots->external_shared_hash;
    ov->object.shared = shared;
    ov->object.slots = slots;

    njs_set_object_value(value, ov);
    njs_set_data(&ov->value, external, njs_make_tag(proto_id));

    return NJS_OK;
}


njs_external_ptr_t
njs_vm_external(njs_vm_t *vm, njs_int_t proto_id, const njs_value_t *value)
{
    njs_external_ptr_t  external;

    if (njs_fast_path(njs_is_object_data(value, njs_make_tag(proto_id)))) {
        external = njs_object_data(value);
        if (external == NULL) {
            external = vm->external;
        }

        return external;
    }

    return NULL;
}


njs_int_t
njs_value_external_tag(const njs_value_t *value)
{
    if (njs_is_object_data(value, njs_make_tag(NJS_PROTO_ID_ANY))) {
        return njs_object_value(value)->data.magic32;
    }

    return -1;
}


njs_int_t
njs_external_property(njs_vm_t *vm, njs_object_prop_t *prop, uint32_t unused,
    njs_value_t *value, njs_value_t *setval, njs_value_t *retval)
{
    char        *p;
    njs_int_t   i;
    njs_uint_t  ui;

    p = njs_vm_external(vm, NJS_PROTO_ID_ANY, value);
    if (p == NULL) {
        njs_value_undefined_set(retval);
        return NJS_DECLINED;
    }

    switch (njs_vm_prop_magic16(prop)) {
    case NJS_EXTERN_TYPE_INT:
        i = *(njs_int_t *) (p + njs_vm_prop_magic32(prop));
        njs_value_number_set(retval, i);
        break;

    case NJS_EXTERN_TYPE_UINT:
        ui = *(njs_uint_t *) (p + njs_vm_prop_magic32(prop));
        njs_value_number_set(retval, ui);
        break;

    case NJS_EXTERN_TYPE_VALUE:
    default:
        njs_value_assign(retval,
                         (njs_value_t *) (p + njs_vm_prop_magic32(prop)));

    }

    return NJS_OK;
}
