// SPDX-FileCopyrightText: 2009-2020 ret2libc <sirmy15@gmail.com>
// SPDX-FileCopyrightText: 2009-2020 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: LGPL-3.0-only

#include <rz_types.h>
#include <rz_util.h>
#include <rz_io.h>

#include "buf_file.c"
#include "buf_sparse.c"
#include "buf_bytes.c"
#include "buf_mmap.c"
#include "buf_io_fd.c"
#include "buf_io.c"
#include "buf_ref.c"

#define GET_STRING_BUFFER_SIZE 32

static void buf_whole_buf_free(RzBuffer *b) {
	// free the whole_buf only if it was initially allocated by the buf types
	if (b->methods->get_whole_buf) {
		if (b->methods->free_whole_buf) {
			b->methods->free_whole_buf(b);
		}
	} else {
		RZ_FREE(b->whole_buf);
	}
}

static bool buf_init(RzBuffer *b, const void *user) {
	rz_return_val_if_fail(b, false);

	if (b->type == RZ_BUFFER_BYTES) {
		return buf_bytes_init(b, user);
	} else if (b->type == RZ_BUFFER_MMAP) {
		return buf_mmap_init(b, user);
	} else {
		rz_return_val_if_fail(b->methods, false);
		return b->methods->init ? b->methods->init(b, user) : true;
	}
}

static bool buf_fini(RzBuffer *b) {
	rz_return_val_if_fail(b, false);

	if (b->type == RZ_BUFFER_BYTES) {
		return buf_bytes_fini(b);
	} else if (b->type == RZ_BUFFER_MMAP) {
		return buf_mmap_fini(b);
	} else {
		rz_return_val_if_fail(b->methods, false);
		return b->methods->fini ? b->methods->fini(b) : true;
	}
}

static ut64 buf_get_size(RzBuffer *b) {
	rz_return_val_if_fail(b, UT64_MAX);

	if (b->type == RZ_BUFFER_BYTES || b->type == RZ_BUFFER_MMAP) {
		return buf_bytes_get_size(b);
	} else {
		rz_return_val_if_fail(b->methods, UT64_MAX);
		return b->methods->get_size ? b->methods->get_size(b) : 0;
	}
}

static st64 buf_read(RzBuffer *b, ut8 *buf, size_t len) {
	rz_return_val_if_fail(b, -1);

	if (b->type == RZ_BUFFER_BYTES || b->type == RZ_BUFFER_MMAP) {
		return buf_bytes_read(b, buf, len);
	} else {
		rz_return_val_if_fail(b->methods, -1);
		return b->methods->read ? b->methods->read(b, buf, len) : -1;
	}
}

static st64 buf_write(RzBuffer *b, const ut8 *buf, size_t len) {
	rz_return_val_if_fail(b && b->methods, -1);

	buf_whole_buf_free(b);

	return b->methods->write ? b->methods->write(b, buf, len) : -1;
}

static st64 buf_seek(RzBuffer *b, st64 addr, int whence) {
	rz_return_val_if_fail(b, -1);

	if (b->type == RZ_BUFFER_BYTES || b->type == RZ_BUFFER_MMAP) {
		return buf_bytes_seek(b, addr, whence);
	} else {
		rz_return_val_if_fail(b->methods, -1);
		return b->methods->seek ? b->methods->seek(b, addr, whence) : -1;
	}
}

static bool buf_resize(RzBuffer *b, ut64 newsize) {
	rz_return_val_if_fail(b, -1);

	if (b->type == RZ_BUFFER_BYTES) {
		return buf_bytes_resize(b, newsize);
	} else if (b->type == RZ_BUFFER_MMAP) {
		return buf_mmap_resize(b, newsize);
	} else {
		rz_return_val_if_fail(b->methods, -1);
		return b->methods->resize ? b->methods->resize(b, newsize) : false;
	}
}

typedef struct {
	ut16 repeat;
	ut8 type_size;
	ut8 big_endian;
} BufFormatToken;

static st64 buf_format(RzBuffer *dst, RzBuffer *src, const char *fmt, int n) {
	RzVector /*<BufFormatToken>*/ tokens;
	rz_vector_init(&tokens, sizeof(BufFormatToken), NULL, NULL);
	unsigned repeat = 1;
	for (size_t j = 0; fmt[j]; j++) {
		unsigned tsize = 0;
		bool bigendian = false;
		switch (fmt[j]) {
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
		case '8':
		case '9':
			if (repeat == 1) {
				char *end = (char *)&fmt[j + 1];
				repeat = strtoul(&fmt[j], &end, 10);
				j += (ptrdiff_t)(end - &fmt[j]) - 1;
			}
			continue;
		case 'S':
			bigendian = true;
			/* fall-thru */
		case 's':
			tsize = 2;
			break;
		case 'I':
			bigendian = true;
			/* fall-thru */
		case 'i':
			tsize = 4;
			break;
		case 'L':
			bigendian = true;
			/* fall-thru */
		case 'l':
			tsize = 8;
			break;
		case 'c':
			tsize = 1;
			break;
		default:
			goto err_exit;
		}
		if (!tsize || repeat > UT16_MAX) {
			goto err_exit;
		}
		BufFormatToken tok = {
			.repeat = repeat,
			.type_size = tsize,
			.big_endian = bigendian
		};
		if (!rz_vector_push(&tokens, &tok)) {
			goto err_exit;
		}
		repeat = 1;
	}

	if (rz_vector_empty(&tokens)) {
		goto err_exit;
	}

	st64 res = 0;
	for (int i = 0; i < n; i++) {
		const BufFormatToken *tok;
		rz_vector_foreach (&tokens, tok) {
			if (tok->type_size == 1) {
				ut8 tmp[8];
				size_t nbytes = tok->repeat;
				while (nbytes != 0) {
					size_t block_sz = RZ_MIN(sizeof(tmp), nbytes);
					if (rz_buf_read(src, tmp, block_sz) != block_sz) {
						goto err_exit;
					}
					if (rz_buf_write(dst, tmp, block_sz) != block_sz) {
						goto err_exit;
					}
					res += block_sz;
					nbytes -= block_sz;
				}
				continue;
			}
			for (int k = 0; k < tok->repeat; k++) {
				ut8 tmp[sizeof(ut64)];
				st64 r = rz_buf_read(src, tmp, tok->type_size);
				if (r < tok->type_size) {
					goto err_exit;
				}

				if ((RZ_HOST_IS_BIG_ENDIAN != (bool)tok->big_endian) && tok->type_size > 1) {
					// just swap endianness if the host endianness
					// is not the same and is not one byte
					switch (tok->type_size) {
					case 2: {
						ut16 value = rz_read_ble16(tmp, tok->big_endian);
						rz_write_ble16(tmp, value, RZ_HOST_IS_BIG_ENDIAN);
						break;
					}
					case 4: {
						ut32 value = rz_read_ble32(tmp, tok->big_endian);
						rz_write_ble32(tmp, value, RZ_HOST_IS_BIG_ENDIAN);
						break;
					}
					case 8: {
						ut64 value = rz_read_ble64(tmp, tok->big_endian);
						rz_write_ble64(tmp, value, RZ_HOST_IS_BIG_ENDIAN);
						break;
					}
					default:
						goto err_exit;
					}
				}
				r = rz_buf_write(dst, tmp, tok->type_size);

				if (r < tok->type_size) {
					goto err_exit;
				}
				res += r;
			}
		}
	}

	rz_vector_fini(&tokens);
	return res;

err_exit:
	rz_vector_fini(&tokens);
	return -1;
}

static bool buf_move_back(RZ_NONNULL RzBuffer *b, ut64 addr, ut64 length) {
	rz_return_val_if_fail(b, -1);

	ut64 size = rz_buf_size(b);
	if (size < addr) {
		return false;
	}

	ut8 *tmp = RZ_NEWS(ut8, size - addr);
	if (!tmp) {
		return false;
	}

	bool res = false;
	st64 tmp_length = rz_buf_read_at(b, addr, tmp, size - addr);
	if (tmp_length < 0) {
		goto err;
	}

	if (!rz_buf_resize(b, size + length)) {
		goto err;
	}

	if (rz_buf_write_at(b, addr + length, tmp, tmp_length) < 0) {
		goto err;
	}

	res = true;
err:
	free(tmp);
	return res;
}

static ut8 *get_whole_buf(RzBuffer *b, ut64 *size) {
	rz_return_val_if_fail(b && size, NULL);

	buf_whole_buf_free(b);

	if (b->type == RZ_BUFFER_BYTES) {
		return buf_bytes_get_whole_buf(b, size);
	} else if (b->type == RZ_BUFFER_MMAP) {
		return buf_mmap_get_whole_buf(b, size);
	} else {
		rz_return_val_if_fail(b && size && b->methods, NULL);
		if (b->methods->get_whole_buf) {
			return b->methods->get_whole_buf(b, size);
		}

		ut64 buf_size = rz_buf_size(b);
		if (buf_size == UT64_MAX) {
			return NULL;
		}

		b->whole_buf = RZ_NEWS(ut8, buf_size);
		if (!b->whole_buf) {
			return NULL;
		}

		if (rz_buf_read_at(b, 0, b->whole_buf, buf_size) < 0) {
			RZ_FREE(b->whole_buf);
			return NULL;
		}

		*size = buf_size;

		return b->whole_buf;
	}
}

static RzBuffer *new_buffer(RzBufferType type, void *user) {
	const RzBufferMethods *methods = NULL;

	switch (type) {
	case RZ_BUFFER_BYTES:
		methods = &buffer_bytes_methods;
		break;
	case RZ_BUFFER_MMAP:
		methods = &buffer_mmap_methods;
		break;
	case RZ_BUFFER_SPARSE:
		methods = &buffer_sparse_methods;
		break;
	case RZ_BUFFER_FILE:
		methods = &buffer_file_methods;
		break;
	case RZ_BUFFER_IO_FD:
		methods = &buffer_io_fd_methods;
		break;
	case RZ_BUFFER_IO:
		methods = &buffer_io_methods;
		break;
	case RZ_BUFFER_REF:
		methods = &buffer_ref_methods;
		break;
	default:
		rz_warn_if_reached();
		return NULL;
	}

	return rz_buf_new_with_methods(methods, user, type);
}

/**
 * \brief Creates a new empty buffer with a predefined size;
 * \param len The length in byte of the new buffer.
 * \return Return the new allocated buffer.
 *
 * The function creates a new buffer of the specified length in memory, filled
 * with \0.
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_empty(ut64 len) {
	ut8 *buf = RZ_NEWS0(ut8, len);
	if (!buf) {
		return NULL;
	}

	struct buf_bytes_user u = { 0 };
	u.data_steal = buf;
	u.length = len;
	u.steal = true;

	RzBuffer *res = new_buffer(RZ_BUFFER_BYTES, &u);
	if (!res) {
		free(buf);
	}

	return res;
}

/**
 * \brief Creates a new buffer from a file.
 * \param file The filename used to create the new buffer.
 * \param perm Same meaning than the symbolic constants defined in sys/stat.h.
 * \param mode Same meaning than the symbolic constants use with open (fcntl.h).
 * \return Return the new allocated buffer.
 *
 * \see rz_sys_open()
 *
 * The function creates a new buffer synchronized with the file content,
 * opening it with rz_sys_open and using regular read/write operations to
 * change its content.
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_file(const char *file, int perm, int mode) {
	struct buf_file_user u = { 0 };

	u.file = file;
	u.perm = perm;
	u.mode = mode;

	return new_buffer(RZ_BUFFER_FILE, &u);
}

/**
 * \brief Creates a new buffer from a file using rz_file_mmap.
 * \param file The filename used to create the new buffer.
 * \param perm Same meaning than the symbolic constants defined in sys/stat.h.
 * \param mode Same meaning than the symbolic constants use with open (fcntl.h).
 * \param io Optional RzIO for configuration variables.
 * \return Return the new allocated buffer.
 *
 * \see rz_file_mmap()
 *
 * The function creates a new buffer synchronized with the file content, using
 * mmap to access the file.
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_mmap(const char *filename, int perm, int mode, RZ_NULLABLE void /* RzIO */ *io) {
	rz_return_val_if_fail(filename, NULL);

	struct buf_mmap_user u = { 0 };
	u.filename = filename;
	u.perm = perm;
	u.mode = mode;
	u.io = io;

	return new_buffer(RZ_BUFFER_MMAP, &u);
}

/**
 * \brief Creates a new buffer from a slice of another buffer.
 * \param b The source buffer used to create the sub buffer.
 * \param offset The starting offset in the source buffer.
 * \param size The number of bytes that will be extract from the source buffer.
 * \return Return the new allocated buffer.
 *
 * \see rz_buf_ref()
 *
 * The function creates a new buffer which shows just a slice of another one,
 * passed as argument. The allocated buffer will reference the source buffer.
 * So both buffer are synchronized.
 *
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_slice(RzBuffer *b, ut64 offset, ut64 size) {
	struct buf_ref_user u = { 0 };

	u.parent = b;
	u.offset = offset;
	u.size = size;

	return new_buffer(RZ_BUFFER_REF, &u);
}

// TODO: rename to new_from_file ?
/**
 * \brief Creates a new buffer from a file.
 * \param file The filename used to create the new buffer.
 * \return Return the new allocated buffer.
 *
 * The function creates a new buffer in memory, initializing it with the whole
 * content of the specified file.
 *
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_slurp(const char *file) {
	size_t len;
	char *tmp = rz_file_slurp(file, &len);
	if (!tmp) {
		return NULL;
	}

	struct buf_bytes_user u = { 0 };

	u.data_steal = (ut8 *)tmp;
	u.length = (ut64)len;
	u.steal = true;

	return new_buffer(RZ_BUFFER_BYTES, &u);
}

/**
 * \brief Creates a sparse buffer.
 * \param Oxff The byte used to fill unpopulated bytes.
 * \return Return the new allocated buffer.
 *
 * The function creates a new allocated buffer using the RZ_BUFFER_SPARSE back end.
 * Creates a new sparse RzBuffer where unpopulated bytes are filled with Oxff parameter.
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_sparse(ut8 Oxff) {
	RzBuffer *b = new_buffer(RZ_BUFFER_SPARSE, NULL);
	if (b) {
		b->Oxff_priv = Oxff;
	}

	return b;
}

/**
 * \brief Creates a sparse buffer from a already populated buffer.
 * \param b The source buffer used to create the sub buffer.
 * \param write_mode Defined how byte are written to the buffer.
 * \return Return the new allocated buffer.
 *
 * The function creates a new allocated buffer using the RZ_BUFFER_SPARSE back end.
 * Creates a new sparse RzBuffer where unpopulated bytes are taken as-is from b
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_sparse_overlay(RzBuffer *b, RzBufferSparseWriteMode write_mode) {
	rz_return_val_if_fail(b, NULL);

	SparseInitConfig cfg = {
		.base = b,
		.write_mode = write_mode
	};

	return new_buffer(RZ_BUFFER_SPARSE, &cfg);
}

/**
 * \brief Creates a new buffer from a source buffer.
 * \param b The source buffer used to create the sub buffer.
 * \return Return the new allocated buffer.
 *
 * \see rz_buf_new_with_bytes()
 *
 * The function creates a new allocated buffer using the RZ_BUFFER_BYTES back end.
 * WARNING: this function doesn't use reference counting, this can lead to a lot
 * of memory overhead.
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_with_buf(RzBuffer *b) {
	ut64 size = 0;
	const ut8 *tmp = get_whole_buf(b, &size);

	return rz_buf_new_with_bytes(tmp, size);
}

/**
 * \brief Creates a new buffer with a bytes array.
 * \param bytes The bytes array used to initialized the buffer.
 * \param len The length of the bytes array.
 * \return Return the new allocated buffer.
 *
 * The function creates a new buffer in memory, initializing it with the bytes
 * passed as argument. The bytes parameter can be NULL, but the length should
 * be set to 0.
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_with_bytes(RZ_NULLABLE RZ_BORROW const ut8 *bytes, ut64 len) {
	rz_return_val_if_fail(bytes || !len, NULL); // if bytes == NULL, then len must be 0

	struct buf_bytes_user u = { 0 };
	u.data = bytes;
	u.length = len;

	return new_buffer(RZ_BUFFER_BYTES, &u);
}

/**
 * \brief Creates a new buffer with a bytes array.
 * The buffer takes ownership of the bytes array.
 *
 * \param bytes The bytes array used to initialized the buffer.
 * \param len The length of the bytes array.
 * \return Return the new allocated buffer.
 *
 * The function creates a new buffer in memory, initializing it with the bytes
 * passed as argument. The bytes parameter can be NULL, but the length should
 * be set to 0.
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_from_bytes(RZ_NULLABLE RZ_OWN ut8 *bytes, ut64 len) {
	rz_return_val_if_fail((bytes && len) || (!bytes && !len), NULL);

	struct buf_bytes_user u = { 0 };
	u.length = len;
	u.data_steal = bytes;
	u.steal = true;

	return new_buffer(RZ_BUFFER_BYTES, &u);
}

// TODO: Optimize to use memcpy when buffers are not in range..
// check buf boundaries and offsets and use memcpy or memmove

// copied from librz/io/cache.c:rz_io_cache_read
// ret # of bytes copied

/**
 * \brief Creates a new buffer wrapping a file descriptor accessed through RzIOBind.
 * \param iob Pointer to RzIOBind structure.
 * \param fd File descriptor to wrap in the buffer.
 * \return Return the new allocated buffer.
 *
 * The function creates a new buffer wrapping access to a file descriptor
 * through the RzIOBind methods specified in librz/io.
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_with_io_fd(RZ_NONNULL void *iob, int fd) {
	rz_return_val_if_fail(iob && fd >= 0, NULL);

	struct buf_io_fd_user u = { 0 };

	u.iob = (RzIOBind *)iob;
	u.fd = fd;

	return new_buffer(RZ_BUFFER_IO_FD, &u);
}

/**
 * \brief Creates a new buffer wrapping the memory map exposed by RzIOBind.
 * \param iob Pointer to RzIOBind structure.
 * \return Return the new allocated buffer.
 *
 * This buffer will use `rz_io_read_at_mapped()`/`rz_io_write_at()` as implemented by
 * the RzIOBind given.
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_with_io(RZ_NONNULL void *iob) {
	rz_return_val_if_fail(iob, NULL);
	return new_buffer(RZ_BUFFER_IO, iob);
}

/**
 * \brief Creates a new buffer with a specific back end.
 * \param methods A struct holding the list of methods to use.
 * \param init_user Some implementation specific information.
 * \return Return the new allocated buffer.
 *
 * \see RzBufferMethods
 *
 * The function creates a new allocated buffer using a custom back end. This function
 * should only be used when no other back end are appropriate.
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_with_methods(RZ_NONNULL const RzBufferMethods *methods, void *init_user, RzBufferType type) {
	RzBuffer *b = RZ_NEW0(RzBuffer);
	if (!b) {
		return NULL;
	}

	b->type = type;
	b->methods = methods;

	if (!buf_init(b, init_user)) {
		free(b);
		return NULL;
	}

	return b;
}

/**
 * \brief Creates a new buffer with a bytes array.
 * \param bytes The bytes array used to initialized the buffer.
 * \param len The length of the bytes array.
 * \param steal If the boolean is true the function take the ownership of the data.
 * \return Return the new allocated buffer.
 *
 * The function creates a new buffer in memory. The argument \p bytes is used
 * as the memory backend if \p steal is true, otherwise a new buffer is
 * allocated and its content is initialized with the one from \p bytes.
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_with_pointers(const ut8 *bytes, ut64 len, bool steal) {
	struct buf_bytes_user u = { 0 };

	u.data_steal = bytes;
	u.length = len;
	u.steal = steal;

	return new_buffer(RZ_BUFFER_BYTES, &u);
}

/**
 * \brief Creates a new buffer from a string.
 * \param msg The source string used to create the buffer.
 * \return Return the new allocated buffer.
 *
 * The function creates a new buffer in memory, initializing it with the string
 * passed as argument.
 */
RZ_API RZ_OWN RzBuffer *rz_buf_new_with_string(RZ_NONNULL const char *msg) {
	return rz_buf_new_with_bytes((const ut8 *)msg, (ut64)strlen(msg));
}

/**
 * \brief Get a string with a max length from the buffer
 * \param b RzBuffer pointer
 * \param addr The address of the string
 * \param size The max length authorized
 * \return A string with a length <= size or NULL
 *
 * Return an heap-allocated string read from the RzBuffer b at address addr. The
 * length depends on the first '\0' found and the arguments size in the buffer.
 * If there is no '\0' in the buffer, there is no string, thus NULL is returned.
 */
RZ_API RZ_OWN char *rz_buf_get_nstring(RZ_NONNULL RzBuffer *b, ut64 addr, size_t size) {
	rz_return_val_if_fail(b, NULL);

	RzStrBuf *buf = rz_strbuf_new(NULL);

	while (true) {
		char tmp[GET_STRING_BUFFER_SIZE + 1];
		st64 r = rz_buf_read_at(b, addr, (ut8 *)tmp, sizeof(tmp) - 1);
		if (r < 1) {
			rz_strbuf_free(buf);
			return NULL;
		}

		size_t count = rz_str_nlen(tmp, r);
		rz_strbuf_append_n(buf, tmp, count);

		if (count > size) {
			rz_strbuf_free(buf);
			return NULL;
		}

		if (count != r) {
			break;
		}

		addr += r;
		size -= count;
	}

	char *result = rz_strbuf_drain(buf);
	return result;
}

/**
 * \brief Get a string from the buffer
 * \param b RzBuffer pointer
 * \param addr The address of the string
 * \return A string with a length <= size or NULL
 *
 * Return an heap-allocated string read from the RzBuffer b at address addr. The
 * length depends on the first '\0' found in the buffer. If there is no '\0' in
 * the buffer, there is no string, thus NULL is returned.
 */
RZ_API RZ_OWN char *rz_buf_get_string(RZ_NONNULL RzBuffer *b, ut64 addr) {
	rz_return_val_if_fail(b, NULL);

	return rz_buf_get_nstring(b, addr, rz_buf_size(b));
}

/**
 * \brief Get a string from the buffer
 * \param b RzBuffer pointer
 * \param s The pointer to output the string
 * \return Length of the string
 *
 * Return an heap-allocated string read from \p b. The length depends on
 * the first '\0' found in the buffer. If there is no '\0' in
 * the buffer, there is no string, thus 0 is returned.
 */
RZ_API ut64 rz_buf_read_string(RZ_NONNULL RzBuffer *b, RZ_BORROW RZ_NULLABLE char **s) {
	rz_return_val_if_fail(b, 0);
	char *tmp = rz_buf_get_string(b, rz_buf_tell(b));
	if (!tmp) {
		return 0;
	}
	const ut64 string_len = strlen(tmp) + 1;
	rz_buf_seek(b, string_len, RZ_BUF_CUR);
	if (s) {
		*s = tmp;
	} else {
		free(tmp);
	}
	return string_len;
}

/**
 * \brief Stringify the buffer
 * \param b RzBuffer pointer
 * \return Return an allocated string.
 *
 * The string is guaranteed to be NULL terminated even if the buffer doesn't have
 * any NULL bytes.
 */
RZ_API RZ_OWN char *rz_buf_to_string(RZ_NONNULL RzBuffer *b) {
	rz_return_val_if_fail(b, NULL);

	ut64 size = rz_buf_size(b);
	char *result = RZ_NEWS(char, size + 1);
	if (!result) {
		return NULL;
	}

	if (rz_buf_read_at(b, 0, (ut8 *)result, size) < 0) {
		free(result);
		return NULL;
	}

	result[size] = '\0';

	return result;
}

/**
 * \brief Increment the reference count of the buffer
 * \param b RzBuffer to reference
 * \return Return the same value \p b
 *
 * The function increment the reference count of the buffer
 */
RZ_API RzBuffer *rz_buf_ref(RzBuffer *b) {
	if (b) {
		b->refctr++;
	}

	return b;
}

/**
 * \brief Append the content of the buffer a to the buffer b.
 * \param dst the destination buffer with write permission
 * \param src the source buffer
 * \return Return the status of the operation.
 *
 * The function append the content of the buffer a, the two buffer don't need to
 * have the same back end. WARNING: This function can allocate a lot of memory.
 */
RZ_API bool rz_buf_append_buf(RZ_NONNULL RzBuffer *b, RZ_NONNULL RzBuffer *a) {
	rz_return_val_if_fail(b && a && !b->readonly, false);

	ut64 size = 0;
	const ut8 *tmp = get_whole_buf(a, &size);

	return rz_buf_append_bytes(b, tmp, size);
}

/**
 * \brief Append a slice of the buffer a to the buffer b.
 * \param dst the destination buffer with write permission
 * \param src the source buffer
 * \param offset The starting offset in the buffer a.
 * \param size The number of bytes that will be extract from the buffer a.
 * \return Return the status of the operation.
 *
 * The function append a slice of the buffer a, the two buffer don't need to
 * have the same back end. WARNING: This function can allocate a lot of memory.
 */
RZ_API bool rz_buf_append_buf_slice(RZ_NONNULL RzBuffer *b, RZ_NONNULL RzBuffer *a, ut64 offset, ut64 size) {
	rz_return_val_if_fail(b && a && !b->readonly, false);

	ut8 *tmp = RZ_NEWS(ut8, size);
	if (!tmp) {
		return false;
	}

	st64 r = rz_buf_read_at(a, offset, tmp, size);
	if (r < 0) {
		free(tmp);
		return false;
	}

	bool result = rz_buf_append_bytes(b, tmp, r);

	free(tmp);
	return result;
}

/**
 * \brief Append an array of bytes to the buffer.
 * \param dst the destination buffer with write permission
 * \param src the source buffer
 * \param length ...
 * \return Return the status of the operation.
 */
RZ_API bool rz_buf_append_bytes(RZ_NONNULL RzBuffer *b, RZ_NONNULL const ut8 *buf, ut64 length) {
	rz_return_val_if_fail(b && buf && !b->readonly, false);

	if (rz_buf_seek(b, 0, RZ_BUF_END) < 0) {
		return false;
	}

	return rz_buf_write(b, buf, length) == length;
}

/**
 * \brief Extend the size of the buffer.
 * \param b A buffer with write permission.
 * \param length The number of bytes to add at the end of the buffer.
 * \return Return the status of the operation.
 */
RZ_API bool rz_buf_append_nbytes(RZ_NONNULL RzBuffer *b, ut64 length) {
	rz_return_val_if_fail(b && !b->readonly, false);

	return rz_buf_resize(b, rz_buf_size(b) + length);
}

/**
 * \brief Add a ut16 number at the end of the buffer.
 * \param b A buffer with write permission.
 * \param n
 * \return Return the status of the operation.
 *
 * ...
 */
RZ_API bool rz_buf_append_ut16(RZ_NONNULL RzBuffer *b, ut16 n) {
	rz_return_val_if_fail(b && !b->readonly, false);

	return rz_buf_append_bytes(b, (const ut8 *)&n, sizeof(n));
}

/**
 * \brief Add a ut32 number at the end of the buffer.
 * \param b A buffer with write permission.
 * \param n
 * \return Return the status of the operation.
 *
 * ...
 */
RZ_API bool rz_buf_append_ut32(RZ_NONNULL RzBuffer *b, ut32 n) {
	rz_return_val_if_fail(b && !b->readonly, false);

	return rz_buf_append_bytes(b, (const ut8 *)&n, sizeof(n));
}

/**
 * \brief Add a ut64 number at the end of the buffer.
 * \param b A buffer with write permission.
 * \param n
 * \return Return the status of the operation.
 *
 * ...
 */
RZ_API bool rz_buf_append_ut64(RZ_NONNULL RzBuffer *b, ut64 n) {
	rz_return_val_if_fail(b && !b->readonly, false);

	return rz_buf_append_bytes(b, (const ut8 *)&n, sizeof(n));
}

/**
 * \brief Dump the content of the buffer to a file.
 * \param b A buffer with write permission.
 * \param file The output file where the buffer content while be outputted.
 * \return Return the status of the operation.
 *
 * \see rz_file_dump()
 *
 * ...
 */
RZ_API bool rz_buf_dump(RZ_NONNULL RzBuffer *b, RZ_NONNULL const char *file) {
	rz_return_val_if_fail(b && file, false);

	ut64 size = 0;
	const ut8 *tmp = get_whole_buf(b, &size);

	return rz_file_dump(file, tmp, size, 0);
}

/**
 * \brief Free all internal data hold by the buffer.
 * \param b A buffer with write permission.
 * \return Return the status of the operation.
 *
 * ...
 */
RZ_API bool rz_buf_fini(RzBuffer *b) {
	if (!b) {
		return false;
	}

	if (b->refctr > 0) {
		b->refctr--;
		return false;
	}

	buf_whole_buf_free(b);

	return buf_fini(b);
}

/**
 * \brief Prepend an array of bytes to the buffer.
 * \param b A buffer with write permission.
 * \param buf ...
 * \param length ...
 * \return Return the status of the operation.
 *
 * ...
 */
RZ_API bool rz_buf_prepend_bytes(RZ_NONNULL RzBuffer *b, RZ_NONNULL const ut8 *buf, ut64 length) {
	rz_return_val_if_fail(b && buf && !b->readonly, false);

	return rz_buf_insert_bytes(b, 0, buf, length) >= 0;
}

/**
 * \brief Read a byte at the cursor in the buffer.
 * \param b ...
 * \param result ...
 * \return Return the status of the operation.
 *
 * ...
 */
RZ_API bool rz_buf_read8(RZ_NONNULL RzBuffer *b, RZ_NONNULL RZ_OUT ut8 *result) {
	rz_return_val_if_fail(b && result, false);

	return rz_buf_read(b, result, sizeof(ut8)) == sizeof(ut8);
}

/**
 * \brief Read a byte at the specified address in the buffer.
 * \param b ...
 * \param addr ...
 * \param result ...
 * \return Return the status of the operation.
 *
 * \see rz_buf_read8()
 * ...
 */
RZ_API bool rz_buf_read8_at(RzBuffer *b, ut64 addr, RZ_NONNULL RZ_OUT ut8 *result) {
	rz_return_val_if_fail(b && result, false);

	return rz_buf_read_at(b, addr, result, sizeof(ut8)) == sizeof(ut8);
}

/**
 * \brief Resize the buffer size.
 * \param b ...
 * \param newsize ...
 * \return Return the status of the operation.
 *
 * ...
 */
RZ_API bool rz_buf_resize(RZ_NONNULL RzBuffer *b, ut64 newsize) {
	rz_return_val_if_fail(b, false);

	return buf_resize(b, newsize);
}

/**
 * \brief Replace the content of the buffer with the bytes array.
 * \param b A buffer with write permission.
 * \param buf ...
 * \param length ...
 * \return Return the status of the operation.
 *
 * ...
 */
RZ_API bool rz_buf_set_bytes(RZ_NONNULL RzBuffer *b, RZ_NONNULL const ut8 *buf, ut64 length) {
	rz_return_val_if_fail(b && buf && !b->readonly, false);

	if (!rz_buf_resize(b, 0)) {
		return false;
	}

	if (rz_buf_seek(b, 0, RZ_BUF_SET) < 0) {
		return false;
	}

	if (!rz_buf_append_bytes(b, buf, length)) {
		return false;
	}

	return rz_buf_seek(b, 0, RZ_BUF_SET) >= 0;
}

/**
 * \brief Write a byte at the cursor in the buffer.
 * \param b ...
 * \param result ...
 * \return Return the status of the operation.
 *
 * ...
 */
RZ_API bool rz_buf_write8(RZ_NONNULL RzBuffer *b, ut8 value) {
	rz_return_val_if_fail(b, false);

	return rz_buf_write(b, &value, sizeof(ut8)) == sizeof(ut8);
}

/**
 * \brief Write a byte at the specified address in the buffer.
 * \param b ...
 * \param addr ...
 * \param value ...
 * \return Return the status of the operation.
 *
 * \see rz_buf_write8()
 *
 * ...
 */
RZ_API bool rz_buf_write8_at(RZ_NONNULL RzBuffer *b, ut64 addr, ut8 value) {
	rz_return_val_if_fail(b, false);

	return rz_buf_write_at(b, addr, &value, sizeof(ut8)) == sizeof(ut8);
}

/**
 * \brief Append a string to the buffer.
 * \param b A buffer with write permission.
 * \param str ...
 * \return Return the status of the operation.
 *
 * ...
 */
RZ_API st64 rz_buf_append_string(RZ_NONNULL RzBuffer *b, RZ_NONNULL const char *str) {
	rz_return_val_if_fail(b && str && !b->readonly, false);

	return rz_buf_append_bytes(b, (const ut8 *)str, strlen(str));
}

/**
 * \brief ...
 * \param b ...
 * \param buf ...
 * \param fmt ...
 * \param n ...
 * \return ...
 *
 * ...
 */
RZ_API st64 rz_buf_fread(RZ_NONNULL RzBuffer *b, RZ_NONNULL ut8 *buf, RZ_NONNULL const char *fmt, int n) {
	rz_return_val_if_fail(b && buf && fmt, -1);

	// XXX: we assume the caller knows what he's doing
	RzBuffer *dst = rz_buf_new_with_pointers(buf, UT64_MAX, false);
	st64 res = buf_format(dst, b, fmt, n);

	rz_buf_free(dst);

	return res;
}

/**
 * \brief ...
 * \param b ...
 * \param addr ...
 * \param buf ...
 * \param fmt ...
 * \param n ...
 * \return ...
 *
 * ...
 */
RZ_API st64 rz_buf_fread_at(RZ_NONNULL RzBuffer *b, ut64 addr, RZ_NONNULL ut8 *buf, RZ_NONNULL const char *fmt, int n) {
	rz_return_val_if_fail(b && buf && fmt, -1);

	st64 tmp = rz_buf_tell(b);
	if (tmp < 0) {
		return -1;
	}

	if (rz_buf_seek(b, addr, RZ_BUF_SET) < 0) {
		return -1;
	}

	st64 result = rz_buf_fread(b, buf, fmt, n);

	if (rz_buf_seek(b, tmp, RZ_BUF_SET) < 0) {
		return -1;
	}

	return result;
}

/**
 * \brief ...
 * \param b ...
 * \param addr ...
 * \param buf ...
 * \param fmt ...
 * \param n ...
 * \return ...
 *
 * ...
 */
RZ_API st64 rz_buf_fwrite(RZ_NONNULL RzBuffer *b, RZ_NONNULL const ut8 *buf, RZ_NONNULL const char *fmt, int n) {
	rz_return_val_if_fail(b && buf && fmt && !b->readonly, -1);

	// XXX: we assume the caller knows what he's doing
	RzBuffer *src = rz_buf_new_with_pointers(buf, UT64_MAX, false);
	st64 result = buf_format(b, src, fmt, n);

	rz_buf_free(src);

	return result;
}

/**
 * \brief ...
 * \param b ...
 * \param addr ...
 * \param buf ...
 * \param fmt ...
 * \param n ...
 * \return ...
 *
 * ...
 */
RZ_API st64 rz_buf_fwrite_at(RZ_NONNULL RzBuffer *b, ut64 addr, RZ_NONNULL const ut8 *buf, RZ_NONNULL const char *fmt, int n) {
	rz_return_val_if_fail(b && buf && fmt && !b->readonly, -1);

	st64 tmp = rz_buf_tell(b);
	if (tmp < 0) {
		return -1;
	}

	if (rz_buf_seek(b, addr, RZ_BUF_SET) < 0) {
		return -1;
	}

	st64 result = rz_buf_fwrite(b, buf, fmt, n);

	if (rz_buf_seek(b, tmp, RZ_BUF_SET) < 0) {
		return -1;
	}

	return result;
}

/**
 * \brief Insert an array of bytes in the buffer.
 * \param b A buffer with write permission.
 * \param addr ...
 * \param buf ...
 * \param length ...
 * \return Return the number of bytes written.
 *
 * ...
 */
RZ_API st64 rz_buf_insert_bytes(RZ_NONNULL RzBuffer *b, ut64 addr, RZ_NONNULL const ut8 *buf, ut64 length) {
	rz_return_val_if_fail(b && !b->readonly, -1);

	if (!buf_move_back(b, addr, length)) {
		return -1;
	}

	st64 result = rz_buf_write_at(b, addr, buf, length);
	if (result < 0) {
		return -1;
	}

	return result;
}

static RzIO *get_io_from_buffer(RZ_NONNULL RzBuffer *b) {
	rz_return_val_if_fail(b, NULL);
	switch (b->type) {
	case RZ_BUFFER_IO:
	case RZ_BUFFER_IO_FD: {
		RzIOBind *iob = b->type == RZ_BUFFER_IO ? ((BufIOPriv *)b->priv)->iob : ((struct buf_io_fd_priv *)b->priv)->iob;
		rz_return_val_if_fail(iob, NULL);
		return iob->io;
	}
	case RZ_BUFFER_MMAP:
		return ((struct buf_mmap_priv *)b->priv)->io;
	default:
		return NULL;
	}
}

/**
 * \brief Reads \p len bytes from buffer \p b into \p buf.
 * \p buf should have enough space to contain the bytes.
 * The seek of \p b is advanced by \p len bytes.
 * EXCEPT: RZ_BUF_IO_FD, RZ_BUF_FILE.
 * Because they were implemented without seek advancement.
 * And changing it breaks everything. Sorry :/
 *
 * \param b The buffer to read from.
 * \param buf The array to move te bytes into.
 * \param len The number of bytes to read from the buffer.
 *
 * \return The number of bytes read. -1 in case of error and 0 for EOF reached.
 */
RZ_API st64 rz_buf_read(RZ_NONNULL RzBuffer *b, RZ_NONNULL ut8 RZ_OUT *buf, ut64 len) {
	rz_return_val_if_fail(b && buf, -1);

	st64 result = buf_read(b, buf, len);
	if (result < 0) {
		return -1;
	}

	if (len > result) {
		ut8 Oxff = b->Oxff_priv;
		RzIO *io = get_io_from_buffer(b);
		if (io) {
			if (!io->ff) {
				RZ_LOG_ERROR("Incomplete buffer read: expected 0x%" PFMT64x " bytes, got %" PFMT64d " bytes.\n", len, result);
				return -1;
			}
			Oxff = io->Oxff;
		}
		memset(buf + result, Oxff, len - result);
	}

	return result;
}

/**
 * \brief Read len bytes of the buffer at the specified address.
 * \param b ...
 * \param addr ...
 * \param buf ...
 * \param len ...
 * \return Return the number of bytes read.
 *
 * ...
 */
RZ_API st64 rz_buf_read_at(RZ_NONNULL RzBuffer *b, ut64 addr, RZ_NONNULL RZ_OUT ut8 *buf, ut64 len) {
	rz_return_val_if_fail(b && buf, -1);

	st64 tmp = rz_buf_tell(b);
	if (tmp < 0) {
		return -1;
	}

	st64 result = -1;
	if (rz_buf_seek(b, addr, RZ_BUF_SET) >= 0) {
		result = rz_buf_read(b, buf, len);
	}

	if (rz_buf_seek(b, tmp, RZ_BUF_SET) < 0) {
		return -1;
	}

	return result;
}

/**
 * \brief Modify the current cursor position in the buffer.
 * \param b ...
 * \param addr ...
 * \param whence The relative position of the address.
 * \return Return the new cursor position
 *
 * ...
 */
RZ_API st64 rz_buf_seek(RZ_NONNULL RzBuffer *b, st64 addr, int whence) {
	rz_return_val_if_fail(b, -1);

	return buf_seek(b, addr, whence);
}

/**
 * \brief Write len bytes of the buffer at the cursor.
 * \param b A buffer with write permission.
 * \param buf ...
 * \param len ...
 * \return Return the number of bytes written.
 *
 * ...
 */
RZ_API st64 rz_buf_write(RZ_NONNULL RzBuffer *b, RZ_NONNULL const ut8 *buf, ut64 len) {
	rz_return_val_if_fail(b && buf && !b->readonly, -1);

	return buf_write(b, buf, len);
}

/**
 * \brief Write len bytes of the buffer at the specified address.
 * \param b A buffer with write permission.
 * \param addr ...
 * \param buf ...
 * \param len ...
 * \return Return the number of bytes written.
 *
 * ...
 */
RZ_API st64 rz_buf_write_at(RZ_NONNULL RzBuffer *b, ut64 addr, RZ_NONNULL const ut8 *buf, ut64 len) {
	rz_return_val_if_fail(b && buf && !b->readonly, -1);

	st64 tmp = rz_buf_tell(b);
	if (tmp < 0) {
		return -1;
	}

	if (rz_buf_seek(b, addr, RZ_BUF_SET) < 0) {
		return -1;
	}

	st64 result = rz_buf_write(b, buf, len);

	if (rz_buf_seek(b, tmp, RZ_BUF_SET) < 0) {
		return -1;
	}

	return result;
}

/**
 * \brief Return the size of the buffer
 * \param b ...
 * \return ...
 *
 * ...
 */
RZ_API ut64 rz_buf_size(RZ_NONNULL RzBuffer *b) {
	rz_return_val_if_fail(b, 0);

	return buf_get_size(b);
}

/**
 * \brief Return the current cursor position.
 * \param b ...
 * \return ...
 *
 * ...
 */
RZ_API ut64 rz_buf_tell(RZ_NONNULL RzBuffer *b) {
	rz_return_val_if_fail(b, 0);

	return rz_buf_seek(b, 0, RZ_BUF_CUR);
}

/**
 * \brief Free all internal data hold by the buffer and the buffer.
 * \param b ...
 * \return Return the status of the operation.
 *
 * \see rz_buf_fini()
 *
 * ...
 */
RZ_API void rz_buf_free(RzBuffer *b) {
	if (!b) {
		return;
	}
	if (rz_buf_fini(b)) {
		free(b);
	}
}

/**
 * \brief Change the overflow byte used in the RZ_BUFFER_SPARSE.
 * \param b ...
 * \param Oxff The new byte filling value.
 *
 * \see rz_buf_new_sparse()
 * \see rz_buf_new_sparse_overlay()
 *
 * Set the content that bytes read outside the buffer bounds should have.
 */
RZ_API void rz_buf_set_overflow_byte(RZ_NONNULL RzBuffer *b, ut8 Oxff) {
	rz_return_if_fail(b);

	b->Oxff_priv = Oxff;
}

/**
 * \brief Returns true if \b is a bytes buffer.
 *
 * \param b The buffer to check.
 *
 * \return True if the buffer is a raw bytes buffer. False otherwise.
 */
RZ_API bool rz_buf_is_bytes_buf(const RzBuffer *b) {
	return b->type == RZ_BUFFER_BYTES;
}

/**
 * \brief Return a borrowed array of bytes representing the buffer data.
 * \param b Buffer to get the data from.
 * \param size Size of the returned data.
 *
 * WARNING: this function should be used with care because it may allocate the
 * entire buffer in memory. Consider using the rz_buf_read* APIs or rz_buf_fwd_scan API instead and
 * read only the chunks you need.
 */
RZ_DEPRECATE RZ_API RZ_BORROW ut8 *rz_buf_data(RZ_NONNULL RzBuffer *b, RZ_NONNULL RZ_OUT ut64 *size) {
	rz_return_val_if_fail(b && size, NULL);

	return get_whole_buf(b, size);
}

/**
 * \brief Scans buffer linearly in chunks calling \p fwd_scan for each chunk.
 *
 * \param b RzBuffer to read
 * \param start Start address
 * \param amount Amount of bytes to read
 * \param fwd_scan Function to call for each chunk
 * \param user User data to pass to fwd_scan
 * \return Number of bytes read
 */
RZ_API ut64 rz_buf_fwd_scan(RZ_NONNULL RzBuffer *b, ut64 start, ut64 amount, RZ_NONNULL RzBufferFwdScan fwd_scan, RZ_NULLABLE void *user) {
	rz_return_val_if_fail(b && fwd_scan, 0);
	if (!amount) {
		return 0;
	}
	if (b->methods->get_whole_buf) {
		ut64 sz;
		ut8 *buf = NULL;
		if (b->type == RZ_BUFFER_BYTES) {
			buf = buf_bytes_get_whole_buf(b, &sz);
		} else if (b->type == RZ_BUFFER_MMAP) {
			buf = buf_mmap_get_whole_buf(b, &sz);
		} else {
			b->methods->get_whole_buf(b, &sz);
		}
		if (buf && (sz <= start)) {
			return 0;
		}
		if (buf) {
			return fwd_scan(buf + start, RZ_MIN(sz - start, amount), user);
		}
	}
	const ut64 size = rz_buf_size(b);
	if (size <= start) {
		return 0;
	}
	if (b->whole_buf) {
		return fwd_scan(b->whole_buf + start, RZ_MIN(size - start, amount), user);
	}
	ut64 addr = start;
	const ut64 user_end = UT64_ADD_OVFCHK(start, amount) ? UT64_MAX : start + amount;
	const ut64 end = RZ_MIN(user_end, size);
	const size_t buf_size = RZ_MIN(end - start, 0x1000);
	ut8 *buf = malloc(buf_size);
	if (!buf) {
		return 0;
	}
	while (addr < end) {
		const ut64 read_amount = RZ_MIN(buf_size, end - addr);
		rz_buf_read_at(b, addr, buf, read_amount);
		ut64 read = fwd_scan(buf, read_amount, user);
		if (!read) {
			break;
		}
		addr += read;
	}
	free(buf);
	return addr - start;
}

/**
 * \brief Get the whole buffer for scanning in hot paths.
 * Please use `rz_buf_fwd_scan()` or `rz_buf_read()`, if your use case is NOT performance critical.
 *
 * \param buffer RzBuffer to read.
 * \param sz Size of returned data in bytes.
 * \return Immutable pointer to the whole buffer or NULL in case of failure.
 */
RZ_API RZ_BORROW const ut8 *rz_buf_get_whole_hot_paths(RZ_NONNULL RzBuffer *b, RZ_NONNULL RZ_OUT ut64 *sz) {
	rz_return_val_if_fail(b && b->methods && b->methods->get_whole_buf, NULL);
	return b->methods->get_whole_buf(b, sz);
}

/**
 * \brief  Decodes ULEB128 from RzBuffer
 *
 * \param  buffer Buffer used to decode the ULEB128.
 * \param  value  Decoded value.
 * \return The number of bytes used.
 */
RZ_API st64 rz_buf_uleb128(RZ_NONNULL RzBuffer *buffer, RZ_NONNULL ut64 *value) {
	rz_return_val_if_fail(buffer && value, -1);

	ut64 sum = 0, used = 0, slice;
	ut32 shift = 0;
	ut8 byte = 0;
	do {
		if (rz_buf_read(buffer, &byte, sizeof(byte)) < 1) {
			// malformed uleb128 due end of buffer
			return -1;
		}
		used++;
		slice = byte & 0x7f;
		if (shift >= 64 || (shift == 63 && slice > 1ULL) || ((slice << shift) >> shift) != slice) {
			// uleb128 too big for ut64
			return -1;
		}
		sum += slice << shift;
		shift += 7;
	} while (byte >= 128);
	*value = sum;
	return used;
}

/**
 * \brief  Decodes SLEB128 from RzBuffer
 *
 * \param  buffer Buffer used to decode the SLEB128.
 * \param  value  Decoded value.
 * \return The number of bytes used.
 */
RZ_API st64 rz_buf_sleb128(RZ_NONNULL RzBuffer *buffer, RZ_NONNULL st64 *value) {
	rz_return_val_if_fail(buffer && value, -1);

	ut64 used = 0, slice;
	st64 sum = 0;
	ut32 shift = 0;
	ut8 byte = 0;
	do {
		if (rz_buf_read(buffer, &byte, sizeof(byte)) < 1) {
			// malformed sleb128 due end of buffer
			return -1;
		}
		used++;
		slice = byte & 0x7f;
		if ((shift >= 64 && slice != (sum < 0 ? 0x7f : 0x00)) ||
			(shift == 63 && slice != 0 && slice != 0x7f)) {
			// sleb128 too big for st64
			return -1;
		}
		sum |= slice << shift;
		shift += 7;
	} while (byte >= 128);

	if (shift < 64 && (byte & 0x40)) {
		// extends negative sign
		sum |= (-1ull) << shift;
	}
	*value = sum;
	return used;
}

RZ_API RzBufferType rz_buf_type(RZ_NONNULL const RzBuffer *b) {
	rz_return_val_if_fail(b, RZ_BUFFER_INVALID);
	return b->type;
}
