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

#ifndef _NJS_OBJECT_H_INCLUDED_
#define _NJS_OBJECT_H_INCLUDED_


typedef enum {
    NJS_OBJECT_PROP_DESCRIPTOR = 0,
    NJS_OBJECT_PROP_VALUE = 1,
    NJS_OBJECT_PROP_GETTER = 2,
    NJS_OBJECT_PROP_SETTER = 3,
#define njs_prop_type(flags)  (flags & 3)
    NJS_OBJECT_PROP_CREATE = 4,
    NJS_OBJECT_PROP_ENUMERABLE = 8,
    NJS_OBJECT_PROP_CONFIGURABLE = 16,
    NJS_OBJECT_PROP_WRITABLE = 32,
    NJS_OBJECT_PROP_UNSET = 64,
    NJS_OBJECT_PROP_IS_STRING = 128,
#define NJS_OBJECT_PROP_VALUE_ECW (NJS_OBJECT_PROP_VALUE                     \
                                   | NJS_OBJECT_PROP_ENUMERABLE              \
                                   | NJS_OBJECT_PROP_CONFIGURABLE            \
                                   | NJS_OBJECT_PROP_WRITABLE)
#define NJS_OBJECT_PROP_VALUE_EC  (NJS_OBJECT_PROP_VALUE                     \
                                   | NJS_OBJECT_PROP_ENUMERABLE              \
                                   | NJS_OBJECT_PROP_CONFIGURABLE)
#define NJS_OBJECT_PROP_VALUE_CW  (NJS_OBJECT_PROP_VALUE                     \
                                   | NJS_OBJECT_PROP_CONFIGURABLE            \
                                   | NJS_OBJECT_PROP_WRITABLE)
#define NJS_OBJECT_PROP_VALUE_E   (NJS_OBJECT_PROP_VALUE                     \
                                   | NJS_OBJECT_PROP_ENUMERABLE)
#define NJS_OBJECT_PROP_VALUE_C   (NJS_OBJECT_PROP_VALUE                     \
                                   | NJS_OBJECT_PROP_CONFIGURABLE)
#define NJS_OBJECT_PROP_VALUE_W   (NJS_OBJECT_PROP_VALUE                     \
                                   | NJS_OBJECT_PROP_WRITABLE)
} njs_object_prop_flags_t;


struct njs_object_init_s {
    const njs_object_prop_init_t  *properties;
    njs_uint_t                    items;
};


typedef struct njs_traverse_s  njs_traverse_t;

struct njs_traverse_s {
    struct njs_traverse_s      *parent;
    njs_object_prop_t          *prop;
    uint32_t                   atom_id;

    njs_value_t                value;
    njs_array_t                *keys;
    int64_t                    index;

#define NJS_TRAVERSE_MAX_DEPTH 32
};


typedef njs_int_t (*njs_object_traverse_cb_t)(njs_vm_t *vm,
    njs_traverse_t *traverse, void *ctx);


njs_object_t *njs_object_alloc(njs_vm_t *vm);
njs_object_t *njs_object_value_copy(njs_vm_t *vm, njs_value_t *value);
njs_object_value_t *njs_object_value_alloc(njs_vm_t *vm, njs_uint_t index,
    size_t extra,const njs_value_t *value);
njs_array_t *njs_object_enumerate(njs_vm_t *vm, const njs_object_t *object,
    uint32_t flags);
njs_array_t *njs_object_own_enumerate(njs_vm_t *vm, const njs_object_t *object,
    uint32_t flags);
njs_int_t njs_object_traverse(njs_vm_t *vm, njs_object_t *object, void *ctx,
    njs_object_traverse_cb_t cb);
njs_int_t njs_object_make_shared(njs_vm_t *vm, njs_object_t *object);
njs_int_t njs_object_hash_create(njs_vm_t *vm, njs_flathsh_t *hash,
    const njs_object_prop_init_t *prop, njs_uint_t n);
njs_int_t njs_primitive_prototype_get_proto(njs_vm_t *vm,
    njs_object_prop_t *prop, uint32_t unused, njs_value_t *value,
    njs_value_t *setval, njs_value_t *retval);
njs_int_t njs_object_prototype_create(njs_vm_t *vm, njs_object_prop_t *prop,
    uint32_t unused, njs_value_t *value, njs_value_t *setval,
    njs_value_t *retval);
njs_value_t *njs_property_prototype_create(njs_vm_t *vm, njs_flathsh_t *hash,
    njs_object_t *prototype);
njs_int_t njs_object_prototype_proto(njs_vm_t *vm, njs_object_prop_t *prop,
    uint32_t unused, njs_value_t *value, njs_value_t *setval,
    njs_value_t *retval);
njs_int_t njs_object_prototype_create_constructor(njs_vm_t *vm,
    njs_object_prop_t *prop, uint32_t unused, njs_value_t *value,
    njs_value_t *setval, njs_value_t *retval);
njs_value_t *njs_property_constructor_set(njs_vm_t *vm, njs_flathsh_t *hash,
    njs_value_t *constructor);
njs_int_t njs_object_to_string(njs_vm_t *vm, njs_value_t *value,
    njs_value_t *retval);
njs_int_t njs_object_prototype_to_string(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
njs_int_t njs_object_length(njs_vm_t *vm, njs_value_t *value, int64_t *dst);

njs_int_t njs_prop_private_copy(njs_vm_t *vm, njs_property_query_t *pq,
    njs_object_t *proto);
void njs_object_prop_init(njs_object_prop_t *prop, njs_object_prop_type_t type,
    uint8_t attributes);
njs_int_t njs_object_property(njs_vm_t *vm, njs_object_t *object,
    njs_flathsh_query_t *lhq, njs_value_t *retval);
njs_object_prop_t *njs_object_property_add(njs_vm_t *vm, njs_value_t *object,
    uint32_t atom_id, njs_bool_t replace);
njs_int_t njs_object_prop_define(njs_vm_t *vm, njs_value_t *object,
    uint32_t atom_id, njs_value_t *value, uint32_t flags);
njs_int_t njs_object_prop_descriptor(njs_vm_t *vm, njs_value_t *dest,
    njs_value_t *value, njs_value_t *setval);
njs_int_t njs_object_get_prototype_of(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval);
const char *njs_prop_type_string(njs_object_prop_type_t type);
njs_int_t njs_object_props_init(njs_vm_t *vm, const njs_object_init_t* init,
    njs_object_prop_t *base, uint32_t atom_id, njs_value_t *value,
    njs_value_t *retval);


njs_inline njs_bool_t
njs_is_data_descriptor(njs_object_prop_t *prop)
{
    return (prop->type == NJS_PROPERTY && njs_is_valid(njs_prop_value(prop)))
            || prop->type == NJS_PROPERTY_HANDLER
            || prop->type == NJS_PROPERTY_REF
            || prop->type == NJS_PROPERTY_PLACE_REF
            || prop->type == NJS_PROPERTY_TYPED_ARRAY_REF;
}


njs_inline njs_bool_t
njs_is_accessor_descriptor(njs_object_prop_t *prop)
{
    return prop->type == NJS_ACCESSOR;
}


njs_inline njs_int_t
njs_primitive_value_to_key(njs_vm_t *vm, njs_value_t *dst,
    const njs_value_t *src)
{
    switch (src->type) {
    case NJS_NULL:
        njs_atom_to_value(vm, dst, NJS_ATOM_STRING_null);
        return NJS_OK;

    case NJS_UNDEFINED:
        njs_atom_to_value(vm, dst, NJS_ATOM_STRING_undefined);
        return NJS_OK;

    case NJS_BOOLEAN:
        if (njs_is_true(src)) {
            njs_atom_to_value(vm, dst, NJS_ATOM_STRING_true);

        } else {
            njs_atom_to_value(vm, dst, NJS_ATOM_STRING_false);
        }

        return NJS_OK;

    case NJS_NUMBER:
    case NJS_SYMBOL:
    case NJS_STRING:
        *dst = *src;
        return NJS_OK;

    default:
        return NJS_ERROR;
    }
}


njs_inline njs_int_t
njs_value_to_key2(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value,
    njs_bool_t convert)
{
    njs_int_t    ret;
    njs_value_t  primitive;

    if (njs_slow_path(!njs_is_primitive(value))) {
        if (njs_slow_path(njs_is_object_symbol(value))) {
            /* should fail */
            value = njs_object_value(value);

        } else {
            if (convert) {
                ret = njs_value_to_primitive(vm, &primitive, value, 1);

            } else {
                ret = njs_object_to_string(vm, value, &primitive);
            }

            if (njs_slow_path(ret != NJS_OK)) {
                return ret;
            }

            value = &primitive;
        }
    }

    return njs_primitive_value_to_key(vm, dst, value);
}


njs_inline njs_int_t
njs_value_to_key(njs_vm_t *vm, njs_value_t *dst, njs_value_t *value)
{
    return njs_value_to_key2(vm, dst, value, 1);
}


njs_inline njs_int_t
njs_key_string_get(njs_vm_t *vm, njs_value_t *key, njs_str_t *str)
{
    njs_int_t  ret;

    if (njs_slow_path(njs_is_symbol(key))) {
        ret = njs_symbol_descriptive_string(vm, key, key);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }

    } else if (njs_slow_path(!njs_is_string(key))) {
        ret = njs_value_to_string(vm, key, key);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    njs_string_get(vm, key, str);

    return NJS_OK;
}


njs_inline void
njs_atom_string_get(njs_vm_t *vm, uint32_t atom_id, njs_str_t *str)
{
    njs_value_t  value;

    if (njs_atom_to_value(vm, &value, atom_id) != NJS_OK) {
        str->start = (u_char *) "unknown";
        str->length = njs_length("unknown");
    }

    njs_key_string_get(vm, &value, str);
}


njs_inline njs_int_t
njs_object_prop_define_val(njs_vm_t *vm, njs_value_t *object, njs_value_t *name,
    njs_value_t *value, unsigned flags)
{
    njs_int_t  ret;

    if (njs_value_atom(name) == NJS_ATOM_STRING_unknown) {
        ret = njs_atom_atomize_key(vm, name);
        if (ret != NJS_OK) {
            return ret;
        }
    }

    if (njs_is_string(name)) {
       flags |= NJS_OBJECT_PROP_IS_STRING;
    }

    return njs_object_prop_define(vm, object, name->atom_id, value, flags);
}


njs_inline njs_int_t
njs_value_create_data_prop(njs_vm_t *vm, njs_value_t *value,
    njs_value_t *name, njs_value_t *setval, uint32_t hash)
{
    return njs_object_prop_define_val(vm, value, name, setval,
                                      NJS_OBJECT_PROP_CREATE
                                      | NJS_OBJECT_PROP_VALUE_ECW);
}


njs_inline njs_int_t
njs_value_create_data_prop_i64(njs_vm_t *vm, njs_value_t *value, int64_t index,
    njs_value_t *setval, uint32_t hash)
{
    njs_value_t  key;

    njs_set_number(&key, index);

    return njs_value_create_data_prop(vm, value, &key, setval, hash);
}


njs_inline njs_int_t
njs_object_length_set(njs_vm_t *vm, njs_value_t *value, int64_t length)
{
    njs_value_t  index;

    njs_value_number_set(&index, length);

    return njs_value_property_set(vm, value, NJS_ATOM_STRING_length, &index);
}


njs_inline njs_int_t
njs_object_string_tag(njs_vm_t *vm, njs_value_t *value, njs_value_t *tag)
{
    njs_int_t  ret;

    ret = njs_value_property(vm, value, NJS_ATOM_SYMBOL_toStringTag, tag);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    if (!njs_is_string(tag)) {
        return NJS_DECLINED;
    }

    return NJS_OK;
}


njs_inline njs_object_t *
_njs_object_proto_lookup(njs_object_t *proto, njs_value_type_t type)
{
    do {
        if (njs_fast_path(proto->type == type)) {
            break;
        }

        proto = proto->__proto__;
    } while (proto != NULL);

    return proto;
}


#define njs_object_proto_lookup(proto, vtype, ctype)                         \
    (ctype *) _njs_object_proto_lookup(proto, vtype)


extern const njs_object_type_init_t  njs_obj_type_init;


#endif /* _NJS_OBJECT_H_INCLUDED_ */
