/*  Copyright (C) CZ.NIC, z.s.p.o. <knot-resolver@labs.nic.cz>
 *  SPDX-License-Identifier: GPL-3.0-or-later
 */

/**
 * @file pack.h
 * @brief A length-prefixed list of objects, also an array list.
 *
 * Each object is prefixed by item length, unlike array this structure
 * permits variable-length data. It is also equivalent to forward-only list
 * backed by an array.
 *
 * @note Maximum object size is 2^16 bytes, see  ::pack_objlen_t
 * @todo If some mistake happens somewhere, the access may end up in an infinite loop.
 *       (equality comparison on pointers)
 *
 * # Example usage:
 *
 * @code{.c}
 *      pack_t pack;
 *      pack_init(pack);
 *
 *      // Reserve 2 objects, 6 bytes total
 *      pack_reserve(pack, 2, 4 + 2);
 *
 *      // Push 2 objects
 *      pack_obj_push(pack, U8("jedi"), 4)
 *      pack_obj_push(pack, U8("\xbe\xef"), 2);
 *
 *      // Iterate length-value pairs
 *      uint8_t *it = pack_head(pack);
 *      while (it != pack_tail(pack)) {
 *          uint8_t *val = pack_obj_val(it);
 *          it = pack_obj_next(it);
 *      }
 *
 *      // Remove object
 *      pack_obj_del(pack, U8("jedi"), 4);
 *
 *      pack_clear(pack);
 * @endcode
 *
 * \addtogroup generics
 * @{
 */

#pragma once

#include <stdint.h>
#include <string.h>
#include "array.h"

#ifdef __cplusplus
extern "C" {
#endif

/** Packed object length type. */
typedef uint16_t pack_objlen_t;

/** Pack is defined as an array of bytes */
typedef array_t(uint8_t) pack_t;

/** Zero-initialize the pack. */
#define pack_init(pack) \
	array_init(pack)

/** Make the pack empty and free pointed-to memory (plain malloc/free). */
#define pack_clear(pack) \
	array_clear(pack)

/** Make the pack empty and free pointed-to memory.
 * Mempool usage: pass mm_free and a knot_mm_t* . */
#define pack_clear_mm(pack, free, baton) \
	array_clear_mm((pack), (free), (baton))

/** Reserve space for *additional* objects in the pack (plain malloc/free).
 * @return 0 if success, <0 on failure */
#define pack_reserve(pack, objs_count, objs_len) \
	pack_reserve_mm((pack), (objs_count), (objs_len), array_std_reserve, NULL)

/** Reserve space for *additional* objects in the pack.
 * Mempool usage: pass kr_memreserve and a knot_mm_t* .
 * @return 0 if success, <0 on failure */
#define pack_reserve_mm(pack, objs_count, objs_len, reserve, baton) \
	array_reserve_mm((pack), (pack).len + (sizeof(pack_objlen_t)*(objs_count) + (objs_len)), (reserve), (baton))

/** Return pointer to first packed object.
 *
 * Recommended way to iterate:
 *   for (uint8_t *it = pack_head(pack); it != pack_tail(pack); it = pack_obj_next(it))
 */
#define pack_head(pack) \
	((pack).len > 0 ? &((pack).at[0]) : NULL)

/** Return pack end pointer. */
#define pack_tail(pack) \
	((pack).len > 0 ? &((pack).at[(pack).len]) : NULL)

/** Return packed object length. */
static inline pack_objlen_t pack_obj_len(uint8_t *it)
{
	pack_objlen_t len = 0;
	if (it != NULL)
		memcpy(&len, it, sizeof(len));
	return len;
}

/** Return packed object value. */
static inline uint8_t *pack_obj_val(uint8_t *it)
{
	if (kr_fails_assert(it))
		return NULL;
	return it + sizeof(pack_objlen_t);
}

/** Return pointer to next packed object. */
static inline uint8_t *pack_obj_next(uint8_t *it)
{
	if (kr_fails_assert(it))
		return NULL;
	return pack_obj_val(it) + pack_obj_len(it);
}

/** Return pointer to the last packed object. */
static inline uint8_t *pack_last(pack_t pack)
{
	if (pack.len == 0)
		return NULL;
	uint8_t *it = pack_head(pack);
	uint8_t *tail = pack_tail(pack);
	while (true) {
		uint8_t *next = pack_obj_next(it);
		if (next == tail)
			return it;
		it = next;
	}
}

/** Push object to the end of the pack
  * @return 0 on success, negative number on failure
  */
static inline int pack_obj_push(pack_t *pack, const uint8_t *obj, pack_objlen_t len)
{
	if (kr_fails_assert(pack && obj))
		return kr_error(EINVAL);
	size_t packed_len = len + sizeof(len);
	if (pack->len + packed_len > pack->cap)
		return kr_error(ENOSPC);

	uint8_t *endp = pack->at + pack->len;
	memcpy(endp, (char *)&len, sizeof(len));
	memcpy(endp + sizeof(len), obj, len);
	pack->len += packed_len;
	return 0;
}

/** Returns a pointer to packed object.
  * @return pointer to packed object or NULL
  */
static inline uint8_t *pack_obj_find(pack_t *pack, const uint8_t *obj, pack_objlen_t len)
{
	if (!pack || kr_fails_assert(obj))
		return NULL;
	uint8_t *endp = pack_tail(*pack);
	uint8_t *it = pack_head(*pack);
	while (it != endp) {
		uint8_t *val = pack_obj_val(it);
		if (pack_obj_len(it) == len && memcmp(obj, val, len) == 0)
			return it;
		it = pack_obj_next(it);
	}
	return NULL;
}

/** Delete object from the pack
  * @return 0 on success, negative number on failure
  */
static inline int pack_obj_del(pack_t *pack, const uint8_t *obj, pack_objlen_t len)
{
	if (!pack || kr_fails_assert(obj))
		return kr_error(EINVAL);
	uint8_t *endp = pack_tail(*pack);
	uint8_t *it = pack_obj_find(pack, obj, len);
	if (it) {
		size_t packed_len = len + sizeof(len);
		memmove(it, it + packed_len, endp - it - packed_len);
		pack->len -= packed_len;
		return 0;
	}
	return -1;
}

/** Clone a pack, replacing destination pack; (*dst == NULL) is valid input.
 * @return kr_error(ENOMEM) on allocation failure. */
static inline int pack_clone(pack_t **dst, const pack_t *src, knot_mm_t *pool)
{
	if (kr_fails_assert(dst && src))
		return kr_error(EINVAL);
	/* Get a valid pack_t. */
	if (!*dst) {
		*dst = mm_alloc(pool, sizeof(pack_t));
		if (!*dst) return kr_error(ENOMEM);
		pack_init(**dst);
		/* Clone data only if needed */
		if (src->len == 0) return kr_ok();
	}
	/* Replace the contents of the pack_t. */
	int ret = array_reserve_mm(**dst, src->len, kr_memreserve, pool);
	if (ret < 0) {
		return kr_error(ENOMEM);
	}
	memcpy((*dst)->at, src->at, src->len);
	(*dst)->len = src->len;
	return kr_ok();
}

#ifdef __cplusplus
}
#endif

/** @} */
