/*
 *  duk_hbuffer operations such as resizing and inserting/appending data to
 *  a dynamic buffer.
 *
 *  Append operations append to the end of the buffer and they are relatively
 *  efficient: the buffer is grown with a "spare" part relative to the buffer
 *  size to minimize reallocations.  Insert operations need to move existing
 *  data forward in the buffer with memmove() and are not very efficient.
 *  They are used e.g. by the regexp compiler to "backpatch" regexp bytecode.
 */

#include "duk_internal.h"

/*
 *  Resizing
 */

DUK_INTERNAL void duk_hbuffer_resize(duk_hthread *thr, duk_hbuffer_dynamic *buf, duk_size_t new_size) {
	void *res;
	duk_size_t prev_size;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
	DUK_ASSERT(!DUK_HBUFFER_HAS_EXTERNAL(buf));

	/*
	 *  Maximum size check
	 */

	if (new_size > DUK_HBUFFER_MAX_BYTELEN) {
		DUK_ERROR_RANGE(thr, "buffer too long");
	}

	/*
	 *  Note: use indirect realloc variant just in case mark-and-sweep
	 *  (finalizers) might resize this same buffer during garbage
	 *  collection.
	 */

	res = DUK_REALLOC_INDIRECT(thr->heap, duk_hbuffer_get_dynalloc_ptr, (void *) buf, new_size);
	if (res != NULL || new_size == 0) {
		/* 'res' may be NULL if new allocation size is 0. */

		DUK_DDD(DUK_DDDPRINT("resized dynamic buffer %p:%ld -> %p:%ld",
		                     (void *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(thr->heap, buf),
		                     (long) DUK_HBUFFER_DYNAMIC_GET_SIZE(buf),
		                     (void *) res,
		                     (long) new_size));

		/*
		 *  The entire allocated buffer area, regardless of actual used
		 *  size, is kept zeroed in resizes for simplicity.  If the buffer
		 *  is grown, zero the new part.
		 */

		prev_size = DUK_HBUFFER_DYNAMIC_GET_SIZE(buf);
		if (new_size > prev_size) {
			DUK_ASSERT(new_size - prev_size > 0);
#ifdef DUK_USE_ZERO_BUFFER_DATA
			DUK_MEMZERO((void *) ((char *) res + prev_size),
			            (duk_size_t) (new_size - prev_size));
#endif
		}

		DUK_HBUFFER_DYNAMIC_SET_SIZE(buf, new_size);
		DUK_HBUFFER_DYNAMIC_SET_DATA_PTR(thr->heap, buf, res);
	} else {
		DUK_ERROR_ALLOC_DEFMSG(thr);
	}

	DUK_ASSERT(res != NULL || new_size == 0);
}

DUK_INTERNAL void duk_hbuffer_reset(duk_hthread *thr, duk_hbuffer_dynamic *buf) {
	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(buf != NULL);
	DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC(buf));
	DUK_ASSERT(!DUK_HBUFFER_HAS_EXTERNAL(buf));

	duk_hbuffer_resize(thr, buf, 0);
}
