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


#include <njs_main.h>


njs_array_buffer_t *
njs_array_buffer_alloc(njs_vm_t *vm, uint64_t size, njs_bool_t zeroing)
{
    njs_object_t        *proto;
    njs_array_buffer_t  *array;

    if (njs_slow_path(size > UINT32_MAX)) {
        goto overflow;
    }

    array = njs_mp_alloc(vm->mem_pool, sizeof(njs_array_buffer_t));
    if (njs_slow_path(array == NULL)) {
        goto memory_error;
    }

    if (zeroing) {
        array->u.data = njs_mp_zalloc(vm->mem_pool, size);

    } else {
        array->u.data = njs_mp_alloc(vm->mem_pool, size);
    }

    if (njs_slow_path(array->u.data == NULL)) {
        goto memory_error;
    }

    proto = njs_vm_proto(vm, NJS_OBJ_TYPE_ARRAY_BUFFER);

    njs_flathsh_init(&array->object.hash);
    njs_flathsh_init(&array->object.shared_hash);
    array->object.__proto__ = proto;
    array->object.slots = NULL;
    array->object.type = NJS_ARRAY_BUFFER;
    array->object.shared = 0;
    array->object.extensible = 1;
    array->object.error_data = 0;
    array->object.fast_array = 0;
    array->size = size;

    return array;

memory_error:

    njs_memory_error(vm);

    return NULL;

overflow:

    njs_range_error(vm, "Invalid array length");

    return NULL;
}


static njs_int_t
njs_array_buffer_constructor(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused, njs_value_t *retval)
{
    uint64_t            size;
    njs_int_t           ret;
    njs_value_t         *value;
    njs_array_buffer_t  *array;

    if (!vm->top_frame->ctor) {
        njs_type_error(vm, "Constructor ArrayBuffer requires 'new'");
        return NJS_ERROR;
    }

    size = 0;
    value = njs_arg(args, nargs, 1);

    ret = njs_value_to_index(vm, value, &size);
    if (njs_slow_path(ret != NJS_OK)) {
        return NJS_ERROR;
    }

    array = njs_array_buffer_alloc(vm, size, 1);
    if (njs_slow_path(array == NULL)) {
        return NJS_ERROR;
    }

    njs_set_array_buffer(retval, array);

    return NJS_OK;
}


static njs_int_t
njs_array_buffer_get_this(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
{
    njs_value_assign(retval, njs_argument(args, 0));

    return NJS_OK;
}


static njs_int_t
njs_array_buffer_is_view(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused, njs_value_t *retval)
{
    njs_set_boolean(retval, njs_is_typed_array(njs_arg(args, nargs, 1)));

    return NJS_OK;
}


njs_int_t
njs_array_buffer_writable(njs_vm_t *vm, njs_array_buffer_t *buffer)
{
    void  *dst;

    if (!buffer->object.shared) {
        return NJS_OK;
    }

    dst = njs_mp_alloc(vm->mem_pool, buffer->size);
    if (njs_slow_path(dst == NULL)) {
        njs_memory_error(vm);
        return NJS_ERROR;
    }

    memcpy(dst, buffer->u.data, buffer->size);

    buffer->object.shared = 0;
    buffer->u.data = dst;

    return NJS_OK;
}


static const njs_object_prop_init_t  njs_array_buffer_constructor_properties[] =
{
    NJS_DECLARE_PROP_LENGTH(1),

    NJS_DECLARE_PROP_NAME("ArrayBuffer"),

    NJS_DECLARE_PROP_HANDLER(STRING_prototype, njs_object_prototype_create,
                             0, 0),

    NJS_DECLARE_PROP_GETTER(SYMBOL_species,
                            njs_array_buffer_get_this,
                            0),

    NJS_DECLARE_PROP_NATIVE(STRING_isView, njs_array_buffer_is_view, 1, 0),
};


const njs_object_init_t  njs_array_buffer_constructor_init = {
    njs_array_buffer_constructor_properties,
    njs_nitems(njs_array_buffer_constructor_properties),
};


static njs_int_t
njs_array_buffer_prototype_byte_length(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
{
    njs_value_t         *value;
    njs_array_buffer_t  *array;

    value = njs_argument(args, 0);

    if (!njs_is_array_buffer(value)) {
        njs_type_error(vm, "Method ArrayBuffer.prototype.byteLength called "
                       "on incompatible receiver");
        return NJS_ERROR;
    }

    array = njs_array_buffer(value);
    if (njs_slow_path(njs_is_detached_buffer(array))) {
        njs_set_number(retval, 0);
        return NJS_OK;
    }

    njs_set_number(retval, array->size);

    return NJS_OK;
}


static njs_int_t
njs_array_buffer_prototype_slice(njs_vm_t *vm, njs_value_t *args,
    njs_uint_t nargs, njs_index_t unused, njs_value_t *retval)
{
    int64_t             len, start, end;
    njs_int_t           ret;
    njs_value_t         *value;
    njs_array_buffer_t  *this, *buffer;

    value = njs_argument(args, 0);

    if (!njs_is_array_buffer(value)) {
        njs_type_error(vm, "Method ArrayBuffer.prototype.slice called "
                       "on incompatible receiver");
        return NJS_ERROR;
    }

    this = njs_array_buffer(value);
    if (njs_slow_path(njs_is_detached_buffer(this))) {
        njs_type_error(vm, "detached buffer");
        return NJS_ERROR;
    }

    len  = njs_array_buffer_size(this);
    end  = len;

    value = njs_arg(args, nargs, 1);

    ret = njs_value_to_integer(vm, value, &start);
    if (njs_slow_path(ret != NJS_OK)) {
        return ret;
    }

    value = njs_arg(args, nargs, 2);

    if (!njs_is_undefined(value)) {
        ret = njs_value_to_integer(vm, value, &end);
        if (njs_slow_path(ret != NJS_OK)) {
            return ret;
        }
    }

    buffer = njs_array_buffer_slice(vm, this, start, end);
    if (njs_slow_path(buffer == NULL)) {
        return NJS_ERROR;
    }

    njs_set_array_buffer(retval, buffer);

    return NJS_OK;
}


njs_int_t
njs_array_buffer_detach(njs_vm_t *vm, njs_value_t *args, njs_uint_t nargs,
    njs_index_t unused, njs_value_t *retval)
{
    njs_value_t         *value;
    njs_array_buffer_t  *buffer;

    value = njs_arg(args, nargs, 1);
    if (njs_slow_path(!njs_is_array_buffer(value))) {
        njs_type_error(vm, "\"this\" is not an ArrayBuffer");
        return NJS_ERROR;
    }

    buffer = njs_array_buffer(value);
    buffer->u.data = NULL;
    buffer->size = 0;

    njs_set_null(retval);

    return NJS_OK;
}




static const njs_object_prop_init_t  njs_array_buffer_prototype_properties[] =
{
    NJS_DECLARE_PROP_HANDLER(STRING_constructor,
                             njs_object_prototype_create_constructor, 0,
                             NJS_OBJECT_PROP_VALUE_CW),

    NJS_DECLARE_PROP_GETTER(STRING_byteLength,
                            njs_array_buffer_prototype_byte_length, 0),

    NJS_DECLARE_PROP_NATIVE(STRING_slice, njs_array_buffer_prototype_slice,
                            2, 0),

    NJS_DECLARE_PROP_VALUE(SYMBOL_toStringTag, njs_ascii_strval("ArrayBuffer"),
                           NJS_OBJECT_PROP_VALUE_C),
};


const njs_object_init_t  njs_array_buffer_prototype_init = {
    njs_array_buffer_prototype_properties,
    njs_nitems(njs_array_buffer_prototype_properties),
};


const njs_object_type_init_t  njs_array_buffer_type_init = {
    .constructor = njs_native_ctor(njs_array_buffer_constructor, 1, 0),
    .prototype_props = &njs_array_buffer_prototype_init,
    .constructor_props = &njs_array_buffer_constructor_init,
    .prototype_value = { .object = { .type = NJS_OBJECT } },
};
