/* Copyright 2017-present Renesas Electronics Corporation and other contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *		 http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "iotjs_def.h"
#include "modules/iotjs_module_buffer.h"
#include "iotjs_module_aligned_buffer.h"

#include <math.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>


IOTJS_DEFINE_NATIVE_HANDLE_INFO_THIS_MODULE(aligned_buffer_wrap);

static bool isPow2(size_t value)
{
	if( value == 0 ) {
		return false;
	}
	while( 1 < value ) {
		if( value % 2 ) {
			return false;
		}
		value /= 2;
	}
	return true;
}

static int aligned( int value, int align )
{
	return ( value + ( align - 1 ) ) / align * align;
}

iotjs_aligned_buffer_wrap_t* iotjs_aligned_buffer_wrap_create(const jerry_value_t jobject,
																size_t length, size_t alignment) {
	iotjs_aligned_buffer_wrap_t* bufferwrap;
	size_t metadata_size = sizeof(iotjs_aligned_buffer_wrap_t);
	uint32_t alloc_size = 0;
	uint32_t offset;

	if(!isPow2(alignment)) {
		/* アライメントが2の累乗でない */
		return NULL;
	}

	alloc_size += aligned( metadata_size, alignment );
	offset = alloc_size - metadata_size;
	alloc_size += aligned( length, alignment );

	bufferwrap = (iotjs_aligned_buffer_wrap_t*)memalign(alignment, alloc_size);
	if(bufferwrap == NULL) {
		return NULL;
	}
	memset(bufferwrap, 0, alloc_size);

	bufferwrap->jobject = jobject;
	jerry_set_object_native_pointer(jobject, bufferwrap,
																	&this_module_native_info);

	bufferwrap->length		= length;
	bufferwrap->alignment = alignment;
	bufferwrap->offset		= offset;

	IOTJS_ASSERT(
			bufferwrap ==
			(iotjs_aligned_buffer_wrap_t*)(iotjs_jval_get_object_native_handle(jobject)));

	return bufferwrap;
}


static void iotjs_aligned_buffer_wrap_destroy(iotjs_aligned_buffer_wrap_t* bufferwrap) {
	free(bufferwrap);
}


iotjs_aligned_buffer_wrap_t* iotjs_aligned_buffer_wrap_from_jbuffer(const jerry_value_t jbuffer) {
	IOTJS_ASSERT(jerry_value_is_object(jbuffer));
	iotjs_aligned_buffer_wrap_t* buffer =
			(iotjs_aligned_buffer_wrap_t*)iotjs_jval_get_object_native_handle(jbuffer);
	IOTJS_ASSERT(buffer != NULL);
	return buffer;
}


char* iotjs_aligned_buffer_wrap_native_buffer_ptr(iotjs_aligned_buffer_wrap_t* bufferwrap) {
	IOTJS_ASSERT(bufferwrap != NULL);
	return &bufferwrap->buffer[bufferwrap->offset];
}


jerry_value_t iotjs_aligned_buffer_wrap_create_buffer(size_t len, size_t alignment) {
	jerry_value_t jres_buffer = jerry_create_object();

	iotjs_aligned_buffer_wrap_t* bufferwrap =
			iotjs_aligned_buffer_wrap_create(jres_buffer, len, alignment);
	if(bufferwrap == NULL) {
		return JS_CREATE_ERROR(COMMON, "Memory allocation error");
	}

	iotjs_jval_set_property_number(jres_buffer, IOTJS_MAGIC_STRING_LENGTH, len);
	iotjs_jval_set_property_number(jres_buffer, "alignment", alignment);

	// Support for 'instanceof' operator
	jerry_value_t jglobal = jerry_get_global_object();
	jerry_value_t jbuffer =
			iotjs_jval_get_property(jglobal, "AlignedBuffer");
	jerry_release_value(jglobal);

	if (!jerry_value_is_error(jbuffer) && jerry_value_is_object(jbuffer)) {
		jerry_value_t jbuffer_proto =
				iotjs_jval_get_property(jbuffer, IOTJS_MAGIC_STRING_PROTOTYPE);

		if (!jerry_value_is_error(jbuffer_proto) &&
				jerry_value_is_object(jbuffer_proto)) {
			jerry_set_prototype(jres_buffer, jbuffer_proto);
		}

		jerry_release_value(jbuffer_proto);
	}
	jerry_release_value(jbuffer);

	return jres_buffer;
}


size_t iotjs_aligned_buffer_wrap_copy_internal(iotjs_aligned_buffer_wrap_t* bufferwrap,
																			const char* src, size_t src_from,
																			size_t src_to, size_t dst_from) {
	size_t copied = 0;
	size_t dst_length = bufferwrap->length;
	char *dst_ptr = iotjs_aligned_buffer_wrap_native_buffer_ptr(bufferwrap);
	for (size_t i = src_from, j = dst_from; i < src_to && j < dst_length;
			 ++i, ++j) {
		*(dst_ptr + j) = *(src + i);
		++copied;
	}
	return copied;
}


size_t iotjs_aligned_buffer_wrap_copy(iotjs_aligned_buffer_wrap_t* bufferwrap, const char* src,
														 size_t len) {
	return iotjs_aligned_buffer_wrap_copy_internal(bufferwrap, src, 0, len, 0);
}


JS_FUNCTION(AlignedBuffer) {
	DJS_CHECK_ARGS(3, object, number, number);

	const jerry_value_t jobject = JS_GET_ARG(0, object);
	size_t length = JS_GET_ARG(1, number);
	size_t alignment = JS_GET_ARG(2, number);

	iotjs_aligned_buffer_wrap_t* bufferwrap =
			iotjs_aligned_buffer_wrap_create(jobject, length, alignment);
	if(bufferwrap == NULL) {
		return JS_CREATE_ERROR(COMMON, "Memory allocation error");
	}
	return jerry_create_undefined();
}


JS_FUNCTION(CopyFromBuffer) {
	DJS_CHECK_ARGS(2, object, object);
	JS_DECLARE_OBJECT_PTR(0, aligned_buffer_wrap, dst_buffer_wrap);

	const jerry_value_t jsrc_buffer = JS_GET_ARG(1, object);
	iotjs_bufferwrap_t* src_buffer_wrap =
			iotjs_bufferwrap_from_jbuffer(jsrc_buffer);

	size_t src_length = src_buffer_wrap->length;
//	size_t dst_length = dst_buffer_wrap->length;
	size_t src_start = 0;
	size_t dst_start = 0;
	size_t src_end = src_length;

	const char* src_data = src_buffer_wrap->buffer;
	size_t copied = iotjs_aligned_buffer_wrap_copy_internal(dst_buffer_wrap, src_data,
																								 src_start, src_end, dst_start);

	return jerry_create_number(copied);
}


JS_FUNCTION(CopyFromAlignedBuffer) {
	DJS_CHECK_ARGS(2, object, object);
	JS_DECLARE_OBJECT_PTR(0, aligned_buffer_wrap, dst_buffer_wrap);

	const jerry_value_t jsrc_buffer = JS_GET_ARG(1, object);
	iotjs_aligned_buffer_wrap_t* src_buffer_wrap =
			iotjs_aligned_buffer_wrap_from_jbuffer(jsrc_buffer);

	size_t src_length = src_buffer_wrap->length;
//	size_t dst_length = dst_buffer_wrap->length;
	size_t src_start = 0;
	size_t dst_start = 0;
	size_t src_end = src_length;

	const char* src_data = iotjs_aligned_buffer_wrap_native_buffer_ptr(src_buffer_wrap);
	size_t copied = iotjs_aligned_buffer_wrap_copy_internal(dst_buffer_wrap, src_data,
																								 src_start, src_end, dst_start);

	return jerry_create_number(copied);
}


JS_FUNCTION(ToBuffer) {
	DJS_CHECK_ARGS(3, object, number, number);
	JS_DECLARE_OBJECT_PTR(0, aligned_buffer_wrap, aligned_buffer_wrap);

	size_t start = JS_GET_ARG(1, number);
	size_t end	 = JS_GET_ARG(2, number);

	if (end < start) {
		end = start;
	}

	if (start > aligned_buffer_wrap->length) {
		start = aligned_buffer_wrap->length;
	}

	if (end > aligned_buffer_wrap->length) {
		end = aligned_buffer_wrap->length;
	}

	size_t length = end - start;

	const char* data = iotjs_aligned_buffer_wrap_native_buffer_ptr(aligned_buffer_wrap) + start;

	jerry_value_t jbuffer = iotjs_bufferwrap_create_buffer(length);
	iotjs_bufferwrap_t* buffer_wrap = iotjs_bufferwrap_from_jbuffer(jbuffer);
	iotjs_bufferwrap_copy(buffer_wrap, data, length);

	return jbuffer;
}


jerry_value_t InitAlignedBuffer() {
	jerry_value_t buffer = jerry_create_external_function(AlignedBuffer);
	iotjs_jval_set_method(buffer, "copyFromBuffer", CopyFromBuffer);
	iotjs_jval_set_method(buffer, "copyFromAlignedBuffer", CopyFromAlignedBuffer);
	iotjs_jval_set_method(buffer, "toBuffer", ToBuffer);
	return buffer;
}
