/*
 * 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 = &vm->prototypes[NJS_OBJ_TYPE_ARRAY_BUFFER].object;

    njs_lvlhsh_init(&array->object.hash);
    njs_lvlhsh_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)
{
    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(&vm->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)
{
    vm->retval = 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_set_boolean(&vm->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_t  njs_array_buffer_constructor_properties[] =
{
    {
        .type = NJS_PROPERTY,
        .name = njs_string("name"),
        .value = njs_string("ArrayBuffer"),
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("length"),
        .value = njs_value(NJS_NUMBER, 1, 1.0),
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY_HANDLER,
        .name = njs_string("prototype"),
        .value = njs_prop_handler(njs_object_prototype_create),
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_wellknown_symbol(NJS_SYMBOL_SPECIES),
        .value = njs_value(NJS_INVALID, 1, NAN),
        .getter = njs_native_function(njs_array_buffer_get_this, 0),
        .setter = njs_value(NJS_UNDEFINED, 0, NAN),
        .writable = NJS_ATTRIBUTE_UNSET,
        .configurable = 1,
        .enumerable = 0,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("isView"),
        .value = njs_native_function(njs_array_buffer_is_view, 1),
        .writable = 1,
        .configurable = 1,
    },
};


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         *value;
    njs_array_buffer_t  *array;

    value = njs_arg(args, nargs, 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_type_error(vm, "detached buffer");
        return NJS_ERROR;
    }

    njs_set_number(&vm->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)
{
    int64_t             len, start, end;
    njs_int_t           ret;
    njs_value_t         *value;
    njs_array_buffer_t  *this, *buffer;

    value = njs_arg(args, nargs, 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);
    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(&vm->retval, buffer);

    return NJS_OK;
}


static const njs_object_prop_t  njs_array_buffer_prototype_properties[] =
{
    {
        .type = NJS_PROPERTY_HANDLER,
        .name = njs_string("constructor"),
        .value = njs_prop_handler(njs_object_prototype_create_constructor),
        .writable = 1,
        .configurable = 1,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("byteLength"),
        .value = njs_value(NJS_INVALID, 1, NAN),
        .getter = njs_native_function(njs_array_buffer_prototype_byte_length,
                                      0),
        .setter = njs_value(NJS_UNDEFINED, 0, NAN),
        .writable = NJS_ATTRIBUTE_UNSET,
        .configurable = 1,
        .enumerable = 0,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_string("slice"),
        .value = njs_native_function(njs_array_buffer_prototype_slice, 2),
        .writable = 1,
        .configurable = 1,
        .enumerable = 0,
    },

    {
        .type = NJS_PROPERTY,
        .name = njs_wellknown_symbol(NJS_SYMBOL_TO_STRING_TAG),
        .value = njs_string("ArrayBuffer"),
        .configurable = 1,
    },
};


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 } },
};
