#ifndef _XSTRING_H
#define _XSTRING_H 1

#include <assert.h>
#include <sys/types.h>

/**
 * struct xstring - string metadata
 * @str: pointer to buf
 * @len: current length of buf contents
 * @cap: maximum capacity of buf
 * @truncated: -1 indicates truncation
 */
typedef struct xstring {
	char *str;
	size_t len;
	size_t cap;
	int truncated;
} xstring;

/**
 * xstrNew - mallocs and inits
 * @size: size of buffer to allocate
 *
 * mallocs both buffer and struct, calls xstrInit
 *
 * Return: ptr to xstring or NULL
 */
xstring *xstrNew(const size_t size);

/**
 * xstrInit - initialize an xstring struct
 * @x: pointer to xstring
 * @str: buffer to manage
 * @size: size of str
 * @keep: if !0, keep existing contents of str;
 *        otherwise, str[0] = '\0'
 *
 * Return: x->truncated
 */
int xstrInit(xstring *x, char *str, const size_t size, int keep);

/**
 * xstrClear - clear xstring
 * @x: pointer to xstring
 *
 * This sets x->len and x->truncated to 0 and str[0] to '\0';
 *
 * Return: x->truncated (always 0)
 */
#define xstrClear(x) (xstrInit((x), (x)->str, (x)->cap, 0))

/**
 * xstrAddChar - add a single character
 * @x: pointer to xstring
 * @c: character to append
 *
 * Return: x->truncated
 */
int xstrAddChar(xstring *x, const char c);

/**
 * xstrAdd - append a string
 * @x: pointer to xstring
 * @src: string to append
 *
 * Append as much from src as fits - if not all, flag truncation.
 *
 * Return: x->truncated
 */
int xstrAdd(xstring *x, const char *src);

/**
 * xstrAddSub - append a substring
 * @x: pointer to xstring
 * @src: string to append
 * @len: length of substring
 *
 * Append substring and '\0' if len fits, otherwise flag truncation.
 *
 * Return: x->truncated
 */
int xstrAddSub(xstring *x, const char *src, size_t len);

/** xstrCat - append multiple strings
 * @x: pointer to xstring
 * @...: one or more strings followed by NULL
 *
 * Run xstrAdd for each string.
 *
 * Return: x->truncated
 */
int xstrCat(xstring *x, ...);

/** xstrJoin - append multiple strings joined by sep
 * @x: pointer to xstring
 * @sep: separator string
 * @...: one or more strings followed by NULL
 *
 * Run xstrAdd for each string and append sep between each pair.
 *
 * Return: x->truncated
 */
int xstrJoin(xstring *x, const char *sep, ...);

/**
 * xstrAddSubs - append multiple substrings
 * @x: pointer to xstring
 * @...: one or more pairs of string and length followed by NULL
 *
 * Run xstrAddSub for each pair of string and length.
 *
 * Return: x->truncated
 */
int xstrAddSubs(xstring *x, ...);

#define transact(x, y)  ({ \
	size_t last; \
	int ret; \
	assert((x)); \
	last = (x)->len; \
	if ((ret = (y)) == -1) { \
		(x)->len = last; \
		*((x)->str + (x)->len) = '\0'; \
	} \
	ret; \
})

/**
 * xstrAddT - append a string as a transaction
 * @x: pointer to xstring
 * @src: string to append
 *
 * Run xstrAdd. Reterminate at initial length if truncation occurs.
 *
 * Return: x->truncated
 */
#define xstrAddT(x, y)        transact(x, xstrAdd((x), y))

/** xstrCatT - append multiple strings as one transaction
 * @x: pointer to xstring
 * @...: one or more strings followed by NULL
 *
 * Run xstrCat. Reterminate at initial length if truncation occurs.
 *
 * Return: x->truncated
 */
#define xstrCatT(x, y...)     transact(x, xstrCat((x) , ## y))

/** xstrJoinT - append multiple strings joined by sep as one transaction
 * @x: pointer to xstring
 * @sep: separator string
 * @...: one or more strings followed by NULL
 *
 * Run xstrJoin. Reterminate at initial length if truncation occurs.
 *
 * Return: x->truncated
 */
#define xstrJoinT(x, y...)    transact(x, xstrJoin((x) , ## y))

/**
 * xstrAddSubsT - append multiple substrings as one transaction
 * @x: pointer to xstring
 * @...: one or more pairs of string and length followed by NULL
 *
 * Run xstrAddSubs. Reterminate at initial length if truncation occurs.
 *
 * Return: x->truncated
 */
#define xstrAddSubsT(x, y...) transact(x, xstrAddSubs((x) , ## y))

/**
 * xstrAddSubT - same as xstrAddSub
 */
// addsub is already transactional
#define xstrAddSubT xstrAddSub

#define unknown -1

/**
 * xstrEq3 - test if two strings are equal
 * @x: pointer to xstring
 * @y: pointer to xstring
 *
 * Return true (1) if the strings held by x and y match and no truncation has occurred.
 * Return unknown (-1) if either is flagged as truncated.
 * Return false (0) otherwise.
 *
 * Return: -1, 0, or 1
 */
int xstrEq3(xstring *x, xstring *y);

/**
 * xstrEq - test if two strings are equal
 * @x: pointer to xstring
 * @y: pointer to xstring
 *
 * Same as xstrEq3, but return false (0) for unknown (-1).
 *
 * Return: 0 or 1
 */
#define xstrEq(x, y) (xstrEq3((x), (y)) == 1)

/**
 * xstrContains3 - test if first string contains second
 * @x: pointer to xstring
 * @y: pointer to xstring
 * @where: -1 (ends), 0 (anywhere), 1 (begins)
 *
 * If neither x nor y are truncated, returns true (1) or false (0).
 * If y is truncated, return unknown (-1).
 * If x is truncated, return true (1) if known portion of x contains y, unknown (-1) otherwise.
 *
 * Return: -1, 0, or 1
 */
int xstrContains3(xstring *x, xstring *y, int where);

/**
 * xstrContains3 - test if first string contains second
 * @x: pointer to xstring
 * @y: pointer to xstring
 * @where: -1 (ends), 0 (anywhere), 1 (begins)
 *
 * Same as xstrContains3, but return false (0) for unknown (-1).
 *
 * Return: 0 or 1
 */
#define xstrContains(x, y, w) (xstrContains3((x), (y), (w)) == 1)

/**
 * xstrFree - free malloced memory
 * @x: pointer to xstring
 */
void xstrFree(xstring *x);

#endif
