/**
 * @file staticstring.h
 * @brief A fixed-capacity string object handed out by the akgl string heap layer.
 *
 * The library allocates nothing at runtime, so a "string" here is a
 * PATH_MAX-sized buffer claimed from the pool with akgl_heap_next_string and
 * given back with akgl_heap_release_string. Capacity is fixed at compile time:
 * these functions truncate rather than grow, and truncation is silent.
 */

#ifndef _AKGL_STATICSTRING_H_
#define _AKGL_STATICSTRING_H_

#include <string.h>
#include <akerror.h>
#include <limits.h>

#define AKGL_MAX_STRING_LENGTH            PATH_MAX

/** @brief Provides a fixed-capacity, heap-managed string buffer. */
typedef struct
{
    int refcount;                       /**< Pool bookkeeping; 0 means the slot is free. Owned by the heap layer. */
    char data[AKGL_MAX_STRING_LENGTH];  /**< The characters. Not guaranteed NUL-terminated when filled to capacity. */
} akgl_String;

/**
 * @brief Set a pooled string's contents and mark the slot in use.
 *
 * Copies @p init into the buffer, or zeroes it when @p init is `NULL`, then
 * sets `refcount` to 1. Callers normally reach this through
 * akgl_heap_next_string rather than calling it directly.
 *
 * @param obj  The pooled string to (re)initialize. Required. Its previous
 *             contents are discarded without inspection.
 * @param init Initial contents, NUL-terminated. Optional -- `NULL` zero-fills
 *             the buffer instead. An @p init that does not fit is truncated,
 *             and the result is **always NUL-terminated**. Until 0.5.0 this was
 *             `strncpy` at exactly the buffer size, which left an over-long
 *             string unterminated -- and a pooled string is handed to `strcmp`,
 *             `realpath` and SDL property calls, none of which stop at 4096.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p obj is `NULL`.
 *
 * @note Until 0.5.0 the `NULL` @p init path zeroed `sizeof(akgl_String)` bytes
 *       starting at `data`, which is four bytes past the end of the buffer --
 *       `refcount` sits in front of it, so the overrun landed on the next pool
 *       slot's reference count.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_string_initialize(akgl_String *obj, char *init);
/**
 * @brief Copy the contents of one pooled string into another.
 *
 * A bounded copy between two already-claimed pool slots. It copies bytes only:
 * `refcount` is left alone, so @p dest keeps whatever pool state it had.
 *
 * @param src   Source string. Required. Read up to @p count bytes.
 * @param dest  Destination string. Required. Overwritten in place; the pool
 *              slot must already have been claimed.
 * @param count Maximum bytes to copy. 0 selects #AKGL_MAX_STRING_LENGTH, the
 *              whole buffer. A @p count shorter than the source truncates, and
 *              the result is **always NUL-terminated**. The remainder is not
 *              zero-padded, so this no longer writes the full buffer for a
 *              short copy. A negative @p count, or one above
 *              #AKGL_MAX_STRING_LENGTH, is refused -- both buffers are exactly
 *              that long, so a larger count walked off the end of two pool
 *              slots at once.
 * @return `NULL` on success, otherwise an error context owned by the caller.
 * @throws AKERR_NULLPOINTER If @p src or @p dest is `NULL`.
 * @throws AKERR_OUTOFBOUNDS If @p count is negative or above
 *         #AKGL_MAX_STRING_LENGTH.
 * @throws AKERR_VALUE, AKERR_OUTOFBOUNDS Whatever `aksl_strncpy` reports. The
 *         unreachable `errno` path this used to document went with the
 *         `strncpy` call it described.
 */
akerr_ErrorContext AKERR_NOIGNORE *akgl_string_copy(akgl_String *src, akgl_String *dest, int count);
#endif //_AKGL_STATICSTRING_H_
